diff --git a/.ci/license-header.sh b/.ci/license-header.sh index 6b19f91185..fcc2c1f583 100755 --- a/.ci/license-header.sh +++ b/.ci/license-header.sh @@ -7,7 +7,7 @@ EXCLUDE_FILES="CPM.cmake CPMUtil.cmake GetSCMRev.cmake renderdoc_app.h tools/cpm tools/shellcheck.sh tools/update-cpm.sh tools/windows/vcvarsall.sh externals/stb externals/glad externals/getopt externals/gamemode externals/FidelityFX-FSR externals/demangle externals/bc_decoder externals/cmake-modules" # license header constants, please change when needed :)))) -YEAR=2026 +YEAR=$(date "+%Y") HOLDER="Eden Emulator Project" LICENSE="GPL-3.0-or-later" @@ -112,10 +112,10 @@ for file in $FILES; do [ "$excluded" = "true" ] && continue case "$file" in - *.cmake|*.sh|*CMakeLists.txt) + *.cmake|*.sh|*.ps1|*.py|*.rb|*.perl|*.pl|*.nix|*CMakeLists.txt) begin="#" ;; - *.kt*|*.cpp|*.h|*.qml) + *.kt|*.kts|*.cpp|*.h|*.qml|*.c|*.hpp|*.hxx|*.cxx|*.h.in|*.inc) begin="//" ;; *) @@ -185,11 +185,12 @@ if [ "$UPDATE" = "true" ]; then for file in $SRC_FILES $OTHER_FILES; do case $(basename -- "$file") in - *.cmake|*CMakeLists.txt) + # Windows Powershell wont use shebangs + *.cmake|*.ps1|*CMakeLists.txt) begin="#" shell="false" ;; - *.sh) + *.sh|*.py|*.rb|*.perl|*.pl|*.nix) begin="#" shell=true ;; diff --git a/.ci/source.sh b/.ci/source.sh deleted file mode 100755 index cbdacd1cd7..0000000000 --- a/.ci/source.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -ex - -# git-archive-all -export PATH="$PATH:/home/$USER/.local/bin" - -GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`" -GITREV="`git show -s --format='%h'`" -REV_NAME="eden-unified-source-${GITDATE}-${GITREV}" - -COMPAT_LIST='dist/compatibility_list/compatibility_list.json' - -mkdir artifacts - -touch "${COMPAT_LIST}" -git describe --abbrev=0 --always HEAD > GIT-COMMIT -git describe --tags HEAD > GIT-TAG || echo 'unknown' > GIT-TAG -git-archive-all --include "${COMPAT_LIST}" --include GIT-COMMIT --include GIT-TAG --force-submodules artifacts/"${REV_NAME}.tar" - -cd artifacts/ -xz -T0 -9 "${REV_NAME}.tar" -sha256sum "${REV_NAME}.tar.xz" > "${REV_NAME}.tar.xz.sha256sum" -cd .. diff --git a/.forgejo/workflows/translations.yml b/.forgejo/workflows/translations.yml index 4a2d52e745..613478c3ca 100644 --- a/.forgejo/workflows/translations.yml +++ b/.forgejo/workflows/translations.yml @@ -1,9 +1,9 @@ name: tx-pull on: - # tuesday, saturday at 2pm + # monday at 4pm schedule: - - cron: '0 14 * * 2,6' + - cron: '0 16 * * 1' workflow_dispatch: jobs: @@ -52,7 +52,7 @@ jobs: } EOF - curl -X 'POST' \ + curl -XPOST \ 'https://git.eden-emu.dev/api/v1/repos/eden-emu/eden/pulls' \ -H 'accept: application/json' \ -H 'Authorization: Bearer ${{ secrets.CI_FJ_TOKEN }}' \ diff --git a/.forgejo/workflows/update-deps.yml b/.forgejo/workflows/update-deps.yml index 154328f906..638fcd4bdb 100644 --- a/.forgejo/workflows/update-deps.yml +++ b/.forgejo/workflows/update-deps.yml @@ -1,4 +1,4 @@ -name: update-deps +name: Update Dependencies on: # saturday at noon @@ -7,7 +7,7 @@ on: workflow_dispatch: jobs: - tx-update: + update-deps: runs-on: source steps: - uses: actions/checkout@v4 @@ -24,18 +24,21 @@ jobs: git remote set-url origin ci:eden-emu/eden.git DATE=$(date +"%b %d") + TIMESTAMP=$(date +"%s") echo "DATE=$DATE" >> "$GITHUB_ENV" + echo "TIMESTAMP=$TIMESTAMP" >> "$GITHUB_ENV" - git switch -c update-deps-$DATE + git switch -c update-deps-$TIMESTAMP tools/cpmutil.sh package update -ac git push - name: Create PR run: | + set -x TITLE="[externals] Dependency update for $DATE" BODY="$(git show -s --format='%b')" BASE=master - HEAD=update-deps-$DATE + HEAD=update-deps-$TIMESTAMP cat << EOF > data.json { @@ -46,7 +49,7 @@ jobs: } EOF - curl -X 'POST' \ + curl -XPOST \ 'https://git.eden-emu.dev/api/v1/repos/eden-emu/eden/pulls' \ -H 'accept: application/json' \ -H 'Authorization: Bearer ${{ secrets.CI_FJ_TOKEN }}' \ diff --git a/.forgejo/ISSUE_TEMPLATE/blank_issue_template.yml b/.github/ISSUE_TEMPLATE/blank_issue_template.yml similarity index 100% rename from .forgejo/ISSUE_TEMPLATE/blank_issue_template.yml rename to .github/ISSUE_TEMPLATE/blank_issue_template.yml diff --git a/.forgejo/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml similarity index 95% rename from .forgejo/ISSUE_TEMPLATE/bug_report.yml rename to .github/ISSUE_TEMPLATE/bug_report.yml index a28f0473fb..66495a335c 100644 --- a/.forgejo/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -3,7 +3,7 @@ description: File a bug report body: - type: markdown attributes: - value: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with yuzu. + value: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with Eden. - type: checkboxes attributes: label: Is there an existing issue for this? @@ -43,7 +43,7 @@ body: id: log attributes: label: Log File - description: A log file will help our developers to better diagnose and fix the issue. Instructions can be found [here](https://yuzu-emu.org/help/reference/log-files). + description: A log file will help our developers to better diagnose and fix the issue. Instructions can be found [here](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md). validations: required: true - type: textarea diff --git a/.forgejo/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml similarity index 100% rename from .forgejo/ISSUE_TEMPLATE/config.yml rename to .github/ISSUE_TEMPLATE/config.yml diff --git a/.forgejo/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml similarity index 95% rename from .forgejo/ISSUE_TEMPLATE/feature_request.yml rename to .github/ISSUE_TEMPLATE/feature_request.yml index 766da2c876..cc588ecb98 100644 --- a/.forgejo/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -4,7 +4,7 @@ labels: "request" body: - type: markdown attributes: - value: Tech support does not belong here. You should only file an issue here if you are requesting a feature you believe would make yuzu better. + value: Tech support does not belong here. You should only file an issue here if you are requesting a feature you believe would make Eden better. - type: checkboxes attributes: label: Is there an existing issue for this? @@ -23,6 +23,6 @@ body: id: why-feature attributes: label: Why would this feature be useful? - description: A brief description of why this feature would make yuzu better. + description: A brief description of why this feature would make Eden better. validations: required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..bdf9d048e8 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,4 @@ +- [ ] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions). +- [ ] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability. + +------------------- diff --git a/.gitignore b/.gitignore index 67bdd8adf4..873b99bec2 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,6 @@ artifacts /install* vulkansdk*.exe *.tar.zst + +# Build-time generated external sources +externals/generated diff --git a/.patch/boost/0001-clang-cl.patch b/.patch/boost/0001-clang-cl.patch deleted file mode 100644 index cdabc712cb..0000000000 --- a/.patch/boost/0001-clang-cl.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/libs/cobalt/include/boost/cobalt/concepts.hpp b/libs/cobalt/include/boost/cobalt/concepts.hpp -index d49f2ec..a9bdb80 100644 ---- a/libs/cobalt/include/boost/cobalt/concepts.hpp -+++ b/libs/cobalt/include/boost/cobalt/concepts.hpp -@@ -62,7 +62,7 @@ struct enable_awaitables - template - concept with_get_executor = requires (T& t) - { -- {t.get_executor()} -> asio::execution::executor; -+ t.get_executor(); - }; - - diff --git a/.patch/httplib/0002-fix-zstd.patch b/.patch/httplib/0002-fix-zstd.patch deleted file mode 100644 index f54485ea53..0000000000 --- a/.patch/httplib/0002-fix-zstd.patch +++ /dev/null @@ -1,89 +0,0 @@ -From 509be32bbfa6eb95014860f7c9ea6d45c8ddaa56 Mon Sep 17 00:00:00 2001 -From: crueter -Date: Sun, 8 Mar 2026 15:11:12 -0400 -Subject: [PATCH] [cmake] Simplify zstd find logic, and support pre-existing - zstd target - -Some deduplication work on the zstd required/if-available logic. Also -adds support for pre-existing `zstd::libzstd` which is useful for -projects that bundle their own zstd in a way that doesn't get caught by -`CONFIG` - -Signed-off-by: crueter ---- - CMakeLists.txt | 46 ++++++++++++++++++++++++++-------------------- - 1 file changed, 26 insertions(+), 20 deletions(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 1874e36be0..8d31198006 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -241,28 +241,34 @@ endif() - # NOTE: - # zstd < 1.5.6 does not provide the CMake imported target `zstd::libzstd`. - # Older versions must be consumed via their pkg-config file. --if(HTTPLIB_REQUIRE_ZSTD) -- find_package(zstd 1.5.6 CONFIG) -- if(NOT zstd_FOUND) -- find_package(PkgConfig REQUIRED) -- pkg_check_modules(zstd REQUIRED IMPORTED_TARGET libzstd) -- add_library(zstd::libzstd ALIAS PkgConfig::zstd) -- endif() -- set(HTTPLIB_IS_USING_ZSTD TRUE) --elseif(HTTPLIB_USE_ZSTD_IF_AVAILABLE) -- find_package(zstd 1.5.6 CONFIG QUIET) -- if(NOT zstd_FOUND) -- find_package(PkgConfig QUIET) -- if(PKG_CONFIG_FOUND) -- pkg_check_modules(zstd QUIET IMPORTED_TARGET libzstd) -- -- if(TARGET PkgConfig::zstd) -+if (HTTPLIB_REQUIRE_ZSTD) -+ set(HTTPLIB_ZSTD_REQUESTED ON) -+ set(HTTPLIB_ZSTD_REQUIRED REQUIRED) -+elseif (HTTPLIB_USE_ZSTD_IF_AVAILABLE) -+ set(HTTPLIB_ZSTD_REQUESTED ON) -+ set(HTTPLIB_ZSTD_REQUIRED QUIET) -+endif() -+ -+if (HTTPLIB_ZSTD_REQUESTED) -+ if (TARGET zstd::libzstd) -+ set(HTTPLIB_IS_USING_ZSTD TRUE) -+ else() -+ find_package(zstd 1.5.6 CONFIG QUIET) -+ -+ if (NOT zstd_FOUND) -+ find_package(PkgConfig ${HTTPLIB_ZSTD_REQUIRED}) -+ pkg_check_modules(zstd ${HTTPLIB_ZSTD_REQUIRED} IMPORTED_TARGET libzstd) -+ -+ if (TARGET PkgConfig::zstd) - add_library(zstd::libzstd ALIAS PkgConfig::zstd) - endif() - endif() -+ -+ # This will always be true if zstd is required. -+ # If zstd *isn't* found when zstd is set to required, -+ # CMake will error out earlier in this block. -+ set(HTTPLIB_IS_USING_ZSTD ${zstd_FOUND}) - endif() -- # Both find_package and PkgConf set a XXX_FOUND var -- set(HTTPLIB_IS_USING_ZSTD ${zstd_FOUND}) - endif() - - # Used for default, common dirs that the end-user can change (if needed) -@@ -317,13 +323,13 @@ if(HTTPLIB_COMPILE) - $ - $ - ) -- -+ - # Add C++20 module support if requested - # Include from separate file to prevent parse errors on older CMake versions - if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.28") - include(cmake/modules.cmake) - endif() -- -+ - set_target_properties(${PROJECT_NAME} - PROPERTIES - VERSION ${${PROJECT_NAME}_VERSION} diff --git a/.patch/libadrenotools/0001-linkerns-cpm.patch b/.patch/libadrenotools/0001-linkerns-cpm.patch deleted file mode 100644 index 8c5abe28e5..0000000000 --- a/.patch/libadrenotools/0001-linkerns-cpm.patch +++ /dev/null @@ -1,20 +0,0 @@ -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 16c6092..9e75548 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -8,7 +8,14 @@ project(adrenotools LANGUAGES CXX C) - - set(GEN_INSTALL_TARGET OFF CACHE BOOL "") - --add_subdirectory(lib/linkernsbypass) -+include(CPM) -+set(CPM_USE_LOCAL_PACKAGES OFF) -+ -+CPMAddPackage( -+ NAME linkernsbypass -+ URL "https://github.com/bylaws/liblinkernsbypass/archive/aa3975893d.zip" -+ URL_HASH SHA512=43d3d146facb7ec99d066a9b8990369ab7b9eec0d5f9a67131b0a0744fde0af27d884ca1f2a272cd113718a23356530ed97703c8c0659c4c25948d50c106119e -+) - - set(LIB_SOURCES src/bcenabler.cpp - src/driver.cpp diff --git a/.patch/libadrenotools/0001-use-cpmfile-def-for-linkernsbypass.patch b/.patch/libadrenotools/0001-use-cpmfile-def-for-linkernsbypass.patch new file mode 100644 index 0000000000..965975cda5 --- /dev/null +++ b/.patch/libadrenotools/0001-use-cpmfile-def-for-linkernsbypass.patch @@ -0,0 +1,26 @@ +From 52bbc5af6523daa22ad62fe4b84bc8d623d11a53 Mon Sep 17 00:00:00 2001 +From: crueter +Date: Fri, 26 Jun 2026 01:09:40 -0400 +Subject: [PATCH] use cpmfile def for linkernsbypass + +--- + CMakeLists.txt | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 16c6092..85b242c 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -8,7 +8,8 @@ project(adrenotools LANGUAGES CXX C) + + set(GEN_INSTALL_TARGET OFF CACHE BOOL "") + +-add_subdirectory(lib/linkernsbypass) ++include(CPMUtil) ++AddJsonPackage(linkernsbypass) + + set(LIB_SOURCES src/bcenabler.cpp + src/driver.cpp +-- +2.54.0 + diff --git a/.patch/openssl-cmake/0001-cpmutil-compat.patch b/.patch/openssl-cmake/0001-cpmutil-compat.patch new file mode 100644 index 0000000000..41f51f4940 --- /dev/null +++ b/.patch/openssl-cmake/0001-cpmutil-compat.patch @@ -0,0 +1,214 @@ +From ec4c1fdf526cb9ad045abf59b29ee495bbf5023a Mon Sep 17 00:00:00 2001 +From: crueter +Date: Sat, 30 May 2026 20:56:35 -0400 +Subject: [PATCH] cpmutil compat + +--- + CMakeLists.txt | 31 ++++++++----------- + cmake/FetchOpenSSL.cmake | 64 ---------------------------------------- + cmake/GetCPM.cmake | 5 ---- + 3 files changed, 13 insertions(+), 87 deletions(-) + delete mode 100644 cmake/FetchOpenSSL.cmake + delete mode 100644 cmake/GetCPM.cmake + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 5420ecc..9ffd5a0 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -19,9 +19,7 @@ include(FetchContent) + include(ProcessorCount) + include(cmake/ConfigureOpenSSL.cmake) + include(cmake/DetectTargetPlatform.cmake) +-include(cmake/FetchOpenSSL.cmake) + include(cmake/FindVcvarsall.cmake) +-include(cmake/GetCPM.cmake) + + # Custom options + option(OPENSSL_BUILD_VERBOSE "Enable verbose output from build" OFF) +@@ -47,9 +45,6 @@ if("${OPENSSL_TARGET_PLATFORM}" STREQUAL "") + detect_target_platform(OPENSSL_TARGET_PLATFORM) + endif() + +-# Fetch OpenSSL source +-fetch_openssl() +- + # Apply patches + foreach(patch IN LISTS OPENSSL_PATCH) + if(EXISTS "${patch}" AND NOT IS_DIRECTORY "${patch}") +@@ -59,13 +54,13 @@ foreach(patch IN LISTS OPENSSL_PATCH) + + execute_process( + COMMAND git init +- WORKING_DIRECTORY ${openssl_SOURCE_DIR} ++ WORKING_DIRECTORY ${OpenSSL_SOURCE_DIR} + OUTPUT_QUIET + ERROR_QUIET + ) + execute_process( + COMMAND git apply ${patch} +- WORKING_DIRECTORY ${openssl_SOURCE_DIR} ++ WORKING_DIRECTORY ${OpenSSL_SOURCE_DIR} + OUTPUT_QUIET + ERROR_QUIET + ) +@@ -161,8 +156,8 @@ list(PREPEND OPENSSL_CONFIGURE_OPTIONS ${OPENSSL_TARGET_PLATFORM}) + # Configure OpenSSL + configure_openssl( + COMMAND ${VCVARSALL_COMMAND} +- FILE ${openssl_SOURCE_DIR}/Configure +- BUILD_DIR ${openssl_BINARY_DIR} ++ FILE ${OpenSSL_SOURCE_DIR}/Configure ++ BUILD_DIR ${OpenSSL_BINARY_DIR} + OPTIONS ${OPENSSL_CONFIGURE_OPTIONS} + ) + +@@ -203,8 +198,8 @@ endif() + # Parse Makefile + parse_makefile(${OPENSSL_MAKEFILE} "INSTALL_LIBS" OPENSSL_STATIC_LIBS) + parse_makefile(${OPENSSL_MAKEFILE} "INSTALL_SHLIBS" OPENSSL_SHARED_LIBS) +-list(TRANSFORM OPENSSL_STATIC_LIBS PREPEND "${openssl_BINARY_DIR}/") +-list(TRANSFORM OPENSSL_SHARED_LIBS PREPEND "${openssl_BINARY_DIR}/") ++list(TRANSFORM OPENSSL_STATIC_LIBS PREPEND "${OpenSSL_BINARY_DIR}/") ++list(TRANSFORM OPENSSL_SHARED_LIBS PREPEND "${OpenSSL_BINARY_DIR}/") + + foreach(LIBRARY IN LISTS OPENSSL_STATIC_LIBS) + if(LIBRARY MATCHES "crypto") +@@ -239,14 +234,14 @@ endif() + + # Provide same targets and variables as FindOpenSSL module + set(OPENSSL_FOUND ON CACHE BOOL "Override FindOpenSSL variables" FORCE) +-set(OPENSSL_INCLUDE_DIR ${openssl_SOURCE_DIR}/include ${openssl_BINARY_DIR}/include CACHE STRING "Override FindOpenSSL variables" FORCE) ++set(OPENSSL_INCLUDE_DIR ${OpenSSL_SOURCE_DIR}/include ${OpenSSL_BINARY_DIR}/include CACHE STRING "Override FindOpenSSL variables" FORCE) + set(OPENSSL_CRYPTO_LIBRARY ${OPENSSL_${OPENSSL_LIBRARY_TYPE}_CRYPTO_LIBRARY} CACHE STRING "Override FindOpenSSL variables" FORCE) + set(OPENSSL_CRYPTO_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY} ${OPENSSL_DEPENDENCIES} CACHE STRING "Override FindOpenSSL variables" FORCE) + set(OPENSSL_SSL_LIBRARY ${OPENSSL_${OPENSSL_LIBRARY_TYPE}_SSL_LIBRARY} CACHE STRING "Override FindOpenSSL variables" FORCE) + set(OPENSSL_SSL_LIBRARIES ${OPENSSL_SSL_LIBRARY} ${OPENSSL_DEPENDENCIES} CACHE STRING "Override FindOpenSSL variables" FORCE) + set(OPENSSL_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY} ${OPENSSL_SSL_LIBRARY} ${OPENSSL_DEPENDENCIES} CACHE STRING "Override FindOpenSSL variables" FORCE) + set(OPENSSL_VERSION ${OPENSSL_CONFIGURED_VERSION} CACHE STRING "Override FindOpenSSL variables" FORCE) +-set(OPENSSL_APPLINK_SOURCE ${openssl_SOURCE_DIR}/ms/applink.c CACHE STRING "Override FindOpenSSL variables" FORCE) ++set(OPENSSL_APPLINK_SOURCE ${OpenSSL_SOURCE_DIR}/ms/applink.c CACHE STRING "Override FindOpenSSL variables" FORCE) + + add_library(OpenSSL::Crypto ${OPENSSL_LIBRARY_TYPE} IMPORTED GLOBAL) + add_library(OpenSSL::SSL ${OPENSSL_LIBRARY_TYPE} IMPORTED GLOBAL) +@@ -308,8 +303,8 @@ if(ANDROID) + endif() + + file(GLOB_RECURSE OPENSSL_SOURCES +- ${openssl_SOURCE_DIR}/*.[ch] +- ${openssl_SOURCE_DIR}/*.[ch].in ++ ${OpenSSL_SOURCE_DIR}/*.[ch] ++ ${OpenSSL_SOURCE_DIR}/*.[ch].in + ) + + set(OPENSSL_BUILD_OUTPUT +@@ -322,7 +317,7 @@ add_custom_command( + OUTPUT ${OPENSSL_BUILD_OUTPUT} + COMMAND ${OPENSSL_BUILD_COMMAND} + DEPENDS ${OPENSSL_SOURCES} +- WORKING_DIRECTORY ${openssl_BINARY_DIR} ++ WORKING_DIRECTORY ${OpenSSL_BINARY_DIR} + VERBATIM + ) + +@@ -341,7 +336,7 @@ if(OPENSSL_TEST AND NOT CMAKE_CROSSCOMPILING) + add_test( + NAME openssl-test + COMMAND ${OPENSSL_BUILD_TOOL} test VERBOSE_FAILURE=yes HARNESS_JOBS=${NUMBER_OF_THREADS} +- WORKING_DIRECTORY ${openssl_BINARY_DIR} ++ WORKING_DIRECTORY ${OpenSSL_BINARY_DIR} + ) + endif() + +@@ -356,7 +351,7 @@ if(OPENSSL_INSTALL) + install(CODE + "execute_process( + COMMAND ${OPENSSL_INSTALL_COMMAND} +- WORKING_DIRECTORY \"${openssl_BINARY_DIR}\" ++ WORKING_DIRECTORY \"${OpenSSL_BINARY_DIR}\" + )" + ) + endif() +diff --git a/cmake/FetchOpenSSL.cmake b/cmake/FetchOpenSSL.cmake +deleted file mode 100644 +index a43505d..0000000 +--- a/cmake/FetchOpenSSL.cmake ++++ /dev/null +@@ -1,64 +0,0 @@ +-function(fetch_openssl) +- if(EXISTS "${OPENSSL_SOURCE}" AND IS_DIRECTORY "${OPENSSL_SOURCE}") +- # Fetch the local OpenSSL source +- if(NOT IS_ABSOLUTE "${OPENSSL_SOURCE}") +- string(PREPEND OPENSSL_SOURCE ${CMAKE_SOURCE_DIR}/) +- endif() +- +- string(REPLACE "\\" "/" openssl-source_SOURCE_DIR "${OPENSSL_SOURCE}") +- set(openssl-source_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/openssl-source-build) +- else() +- set(CPM_OPTIONS +- NAME openssl-source +- DOWNLOAD_ONLY ON +- ) +- +- if(NOT OPENSSL_CONFIGURE_VERBOSE) +- list(APPEND CPM_OPTIONS QUIET) +- endif() +- +- if("${OPENSSL_SOURCE}" MATCHES "^http") +- # Download OpenSSL source from the internet +- list(APPEND CPM_OPTIONS URL ${OPENSSL_SOURCE}) +- else() +- # Download OpenSSL source from the official website +- if("${OPENSSL_TARGET_VERSION}" STREQUAL "") +- set(OPENSSL_TARGET_VERSION ${PROJECT_VERSION}) +- endif() +- +- if(OPENSSL_TARGET_VERSION VERSION_EQUAL PROJECT_VERSION) +- list(APPEND CPM_OPTIONS URL_HASH SHA256=aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f) +- endif() +- +- if(OPENSSL_TARGET_VERSION MATCHES "^1\.1\.1[a-w]$") +- string(REPLACE "." "_" OPENSSL_TAGGED_VERSION ${OPENSSL_TARGET_VERSION}) +- list(APPEND CPM_OPTIONS URL https://github.com/openssl/openssl/releases/download/OpenSSL_${OPENSSL_TAGGED_VERSION}/openssl-${OPENSSL_TARGET_VERSION}.tar.gz) +- else() +- list(APPEND CPM_OPTIONS URL https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_TARGET_VERSION}/openssl-${OPENSSL_TARGET_VERSION}.tar.gz) +- endif() +- endif() +- +- CPMAddPackage(${CPM_OPTIONS}) +- endif() +- +- # Clean build directory if source directory has changed +- if(DEFINED CACHE{openssl-source_SOURCE_DIR_OLD} AND NOT openssl-source_SOURCE_DIR STREQUAL openssl-source_SOURCE_DIR_OLD) +- set(openssl-source_SOURCE_DIR_OLD ${openssl-source_SOURCE_DIR} CACHE INTERNAL "Previously fetched OpenSSL source") +- +- if(IS_DIRECTORY ${openssl-source_BINARY_DIR}) +- file(REMOVE_RECURSE ${openssl-source_BINARY_DIR}) +- file(MAKE_DIRECTORY ${openssl-source_BINARY_DIR}) +- endif() +- endif() +- +- # Override the FindOpenSSL module +- FetchContent_Declare( +- OpenSSL +- SOURCE_DIR ${openssl-source_SOURCE_DIR} +- BINARY_DIR ${openssl-source_BINARY_DIR} +- OVERRIDE_FIND_PACKAGE +- ) +- FetchContent_MakeAvailable(OpenSSL) +- +- return(PROPAGATE openssl_SOURCE_DIR openssl_BINARY_DIR) +-endfunction() +diff --git a/cmake/GetCPM.cmake b/cmake/GetCPM.cmake +deleted file mode 100644 +index bfc50f5..0000000 +--- a/cmake/GetCPM.cmake ++++ /dev/null +@@ -1,5 +0,0 @@ +-file( +- DOWNLOAD https://github.com/cpm-cmake/CPM.cmake/releases/latest/download/get_cpm.cmake +- ${CMAKE_CURRENT_BINARY_DIR}/get_cpm.cmake +-) +-include(${CMAKE_CURRENT_BINARY_DIR}/get_cpm.cmake) +-- +2.54.0 + diff --git a/.patch/openssl-cmake/0002-use-ccache.patch b/.patch/openssl-cmake/0002-use-ccache.patch new file mode 100644 index 0000000000..a753d4b6f1 --- /dev/null +++ b/.patch/openssl-cmake/0002-use-ccache.patch @@ -0,0 +1,54 @@ +From d46675fbb61eb6d51e478023ce4075e545ad4cfd Mon Sep 17 00:00:00 2001 +From: crueter +Date: Sat, 30 May 2026 21:11:55 -0400 +Subject: [PATCH] use ccache + +--- + CMakeLists.txt | 1 - + cmake/ConfigureOpenSSL.cmake | 12 +++--------- + 2 files changed, 3 insertions(+), 10 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 9ffd5a0..9ff14c8 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -28,7 +28,6 @@ option(OPENSSL_ENABLE_PARALLEL "Build and test in parallel if possible" ON) + option(OPENSSL_INSTALL "Install OpenSSL components to the directory" OFF) + option(OPENSSL_INSTALL_CERT "Install cert.pem to the directory" OFF) + option(OPENSSL_TEST "Enable testing and build OpenSSL self tests" OFF) +-option(OPENSSL_USE_CCACHE "Use ccache if available" ON) + + if("${OPENSSL_BUILD_TARGET}" STREQUAL "") + # Makefile target for build +diff --git a/cmake/ConfigureOpenSSL.cmake b/cmake/ConfigureOpenSSL.cmake +index 211c18b..3d8cbed 100644 +--- a/cmake/ConfigureOpenSSL.cmake ++++ b/cmake/ConfigureOpenSSL.cmake +@@ -69,15 +69,9 @@ function(apply_ccache FILE) + message(FATAL_ERROR "Couldn't find Makefile") + endif() + +- if(OPENSSL_USE_CCACHE) +- find_program(CCACHE ccache) +- +- if(NOT CCACHE) +- return() +- endif() +- ++ if(USE_CCACHE) + file(READ ${FILE} MAKEFILE) +- string(REPLACE "\nCC=" "\nCC=ccache " MAKEFILE "${MAKEFILE}") ++ string(REPLACE "\nCC=" "\nCC=${CCACHE_BINARY} " MAKEFILE "${MAKEFILE}") + + if(MSVC) + string(REPLACE "/Zi /Fdossl_static.pdb " "" MAKEFILE "${MAKEFILE}") +@@ -171,4 +165,4 @@ function(configure_openssl) + string(REPLACE "/W3" "/W0" MAKEFILE "${MAKEFILE}") + file(WRITE ${OPENSSL_MAKEFILE} "${MAKEFILE}") + endif() +-endfunction() +\ No newline at end of file ++endfunction() +-- +2.54.0 + diff --git a/.patch/openssl-cmake/0003-use-cmake-compiler-flags.patch b/.patch/openssl-cmake/0003-use-cmake-compiler-flags.patch new file mode 100644 index 0000000000..bbfa963743 --- /dev/null +++ b/.patch/openssl-cmake/0003-use-cmake-compiler-flags.patch @@ -0,0 +1,28 @@ +From 4a3cc92a7abad403529ed1cb4255ca63d9252de4 Mon Sep 17 00:00:00 2001 +From: crueter +Date: Sat, 30 May 2026 21:48:42 -0400 +Subject: [PATCH 2/2] use cmake compiler flags + +--- + cmake/ConfigureOpenSSL.cmake | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/cmake/ConfigureOpenSSL.cmake b/cmake/ConfigureOpenSSL.cmake +index 3d8cbed..3012e05 100644 +--- a/cmake/ConfigureOpenSSL.cmake ++++ b/cmake/ConfigureOpenSSL.cmake +@@ -135,7 +135,10 @@ function(configure_openssl) + endif() + + execute_process( +- COMMAND ${CONFIGURE_COMMAND} ++ COMMAND ${CMAKE_COMMAND} -E env ++ "CFLAGS=${CMAKE_C_FLAGS}" ++ "CXXFLAGS=${CMAKE_CXX_FLAGS}" ++ ${CONFIGURE_COMMAND} + WORKING_DIRECTORY ${CONFIGURE_BUILD_DIR} + ${VERBOSE_OPTION} + COMMAND_ERROR_IS_FATAL ANY +-- +2.54.0 + diff --git a/.patch/openssl-cmake/0004-use-shell-wrapper.patch b/.patch/openssl-cmake/0004-use-shell-wrapper.patch new file mode 100644 index 0000000000..76a19644df --- /dev/null +++ b/.patch/openssl-cmake/0004-use-shell-wrapper.patch @@ -0,0 +1,39 @@ +--- a/CMakeLists.txt 2026-06-01 23:53:16.498043856 -0400 ++++ b/CMakeLists.txt 2026-06-01 23:53:23.910543615 -0400 +@@ -312,13 +312,29 @@ + ${OPENSSL_SHARED_CRYPTO_LIBRARY} + ${OPENSSL_SHARED_SSL_LIBRARY} + ) +-add_custom_command( +- OUTPUT ${OPENSSL_BUILD_OUTPUT} +- COMMAND ${OPENSSL_BUILD_COMMAND} +- DEPENDS ${OPENSSL_SOURCES} +- WORKING_DIRECTORY ${OpenSSL_BINARY_DIR} +- VERBATIM +-) ++if (WIN32) ++ add_custom_command( ++ OUTPUT ${OPENSSL_BUILD_OUTPUT} ++ COMMAND ${OPENSSL_BUILD_COMMAND} ++ DEPENDS ${OPENSSL_SOURCES} ++ WORKING_DIRECTORY ${OpenSSL_BINARY_DIR} ++ VERBATIM) ++else() ++ set(_openssl_build_script "${CMAKE_CURRENT_BINARY_DIR}/BuildOpenSSL.cmake") ++ file(WRITE ${_openssl_build_script} ++ "execute_process(\n" ++ " COMMAND ${OPENSSL_BUILD_COMMAND}\n" ++ " WORKING_DIRECTORY ${OpenSSL_BINARY_DIR}\n" ++ " RESULT_VARIABLE _r)\n" ++ "if(_r)\n" ++ " message(FATAL_ERROR \"OpenSSL build failed: \${_r}\")\n" ++ "endif()\n") ++ add_custom_command( ++ OUTPUT ${OPENSSL_BUILD_OUTPUT} ++ COMMAND ${CMAKE_COMMAND} -P ${_openssl_build_script} ++ DEPENDS ${OPENSSL_SOURCES} ++ VERBATIM) ++endif() + + if(PROJECT_IS_TOP_LEVEL) + add_custom_target(openssl-build ALL DEPENDS ${OPENSSL_BUILD_OUTPUT}) diff --git a/.patch/openssl/0001-add-bundled-cert.patch b/.patch/openssl/0001-add-bundled-cert.patch new file mode 100644 index 0000000000..f30c43115f --- /dev/null +++ b/.patch/openssl/0001-add-bundled-cert.patch @@ -0,0 +1,3665 @@ +From 70eec0292dcd2778be40190c1642a45c71ddaa30 Mon Sep 17 00:00:00 2001 +From: crueter +Date: Sat, 30 May 2026 21:15:41 -0400 +Subject: [PATCH] add bundled cert + +`curl -LO https://raw.githubusercontent.com/crueter-ci/OpenSSL/refs/heads/master/cert.h` +--- + include/openssl/cert.h | 3645 ++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 3645 insertions(+) + create mode 100644 include/openssl/cert.h + +diff --git a/include/openssl/cert.h b/include/openssl/cert.h +new file mode 100644 +index 0000000..5a3f98f +--- /dev/null ++++ b/include/openssl/cert.h +@@ -0,0 +1,3645 @@ ++#ifndef CERT_H_ ++#define CERT_H_ ++ ++inline constexpr char kCert[] { ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE\n" ++ "AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw\n" ++ "CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ\n" ++ "BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND\n" ++ "VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb\n" ++ "qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY\n" ++ "HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo\n" ++ "G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA\n" ++ "lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr\n" ++ "IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/\n" ++ "0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH\n" ++ "k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47\n" ++ "4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO\n" ++ "m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa\n" ++ "cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl\n" ++ "uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI\n" ++ "KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls\n" ++ "ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG\n" ++ "AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2\n" ++ "VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT\n" ++ "VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG\n" ++ "CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA\n" ++ "cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA\n" ++ "QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA\n" ++ "7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA\n" ++ "cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA\n" ++ "QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA\n" ++ "czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu\n" ++ "aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt\n" ++ "aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud\n" ++ "DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF\n" ++ "BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp\n" ++ "D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU\n" ++ "JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m\n" ++ "AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD\n" ++ "vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms\n" ++ "tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH\n" ++ "7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h\n" ++ "I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA\n" ++ "h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF\n" ++ "d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H\n" ++ "pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx\n" ++ "CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ\n" ++ "WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ\n" ++ "BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG\n" ++ "Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/\n" ++ "yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf\n" ++ "BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz\n" ++ "WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF\n" ++ "tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z\n" ++ "374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC\n" ++ "IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL\n" ++ "mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7\n" ++ "wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS\n" ++ "MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2\n" ++ "ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet\n" ++ "UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw\n" ++ "AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H\n" ++ "YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3\n" ++ "LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD\n" ++ "nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1\n" ++ "RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM\n" ++ "LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf\n" ++ "77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N\n" ++ "JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm\n" ++ "fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp\n" ++ "6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp\n" ++ "1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B\n" ++ "9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok\n" ++ "RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv\n" ++ "uu8wd+RU4riEmViAqhOLUTpPSPaLtrM=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw\n" ++ "CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw\n" ++ "FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S\n" ++ "Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5\n" ++ "MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL\n" ++ "DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS\n" ++ "QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB\n" ++ "BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH\n" ++ "sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK\n" ++ "Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD\n" ++ "VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu\n" ++ "SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC\n" ++ "MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy\n" ++ "v+c=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV\n" ++ "BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk\n" ++ "YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV\n" ++ "BAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN\n" ++ "MzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF\n" ++ "UzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD\n" ++ "VQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v\n" ++ "dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj\n" ++ "cqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q\n" ++ "yGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH\n" ++ "2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX\n" ++ "H1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL\n" ++ "zc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR\n" ++ "p1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz\n" ++ "W7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/\n" ++ "SiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn\n" ++ "LNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3\n" ++ "n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B\n" ++ "u8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj\n" ++ "o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO\n" ++ "BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC\n" ++ "AgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L\n" ++ "9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej\n" ++ "rw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK\n" ++ "pFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0\n" ++ "vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq\n" ++ "OknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ\n" ++ "/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9\n" ++ "2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI\n" ++ "+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2\n" ++ "MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo\n" ++ "tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE\n" ++ "BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w\n" ++ "MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290\n" ++ "IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC\n" ++ "SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1\n" ++ "ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB\n" ++ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv\n" ++ "UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX\n" ++ "4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9\n" ++ "KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/\n" ++ "gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb\n" ++ "rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ\n" ++ "51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F\n" ++ "be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe\n" ++ "KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F\n" ++ "v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn\n" ++ "fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7\n" ++ "jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz\n" ++ "ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt\n" ++ "ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL\n" ++ "e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70\n" ++ "jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz\n" ++ "WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V\n" ++ "SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j\n" ++ "pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX\n" ++ "X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok\n" ++ "fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R\n" ++ "K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU\n" ++ "ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU\n" ++ "LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT\n" ++ "LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE\n" ++ "BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz\n" ++ "dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL\n" ++ "MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp\n" ++ "cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\n" ++ "AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP\n" ++ "Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr\n" ++ "ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL\n" ++ "MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1\n" ++ "yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr\n" ++ "VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/\n" ++ "nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ\n" ++ "KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG\n" ++ "XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj\n" ++ "vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt\n" ++ "Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g\n" ++ "N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC\n" ++ "nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE\n" ++ "BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz\n" ++ "dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL\n" ++ "MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp\n" ++ "cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\n" ++ "AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y\n" ++ "YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua\n" ++ "kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL\n" ++ "QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp\n" ++ "6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG\n" ++ "yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i\n" ++ "QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ\n" ++ "KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO\n" ++ "tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu\n" ++ "QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ\n" ++ "Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u\n" ++ "olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48\n" ++ "x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE\n" ++ "BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz\n" ++ "dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG\n" ++ "A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U\n" ++ "cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf\n" ++ "qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ\n" ++ "JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ\n" ++ "+jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS\n" ++ "s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5\n" ++ "HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7\n" ++ "70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG\n" ++ "V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S\n" ++ "qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S\n" ++ "5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia\n" ++ "C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX\n" ++ "OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE\n" ++ "FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/\n" ++ "BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2\n" ++ "KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg\n" ++ "Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B\n" ++ "8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ\n" ++ "MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc\n" ++ "0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ\n" ++ "u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF\n" ++ "u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH\n" ++ "YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8\n" ++ "GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO\n" ++ "RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e\n" ++ "KeC2uAloGRwYQw==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC\n" ++ "VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ\n" ++ "cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ\n" ++ "BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt\n" ++ "VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D\n" ++ "0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9\n" ++ "ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G\n" ++ "A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G\n" ++ "A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs\n" ++ "aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I\n" ++ "flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\n" ++ "ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\n" ++ "b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL\n" ++ "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\n" ++ "b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\n" ++ "ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n" ++ "9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\n" ++ "IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\n" ++ "VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n" ++ "93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\n" ++ "jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n" ++ "AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA\n" ++ "A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI\n" ++ "U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs\n" ++ "N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv\n" ++ "o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU\n" ++ "5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy\n" ++ "rqXRfboQnoZsG4q5WTP468SQvvG5\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF\n" ++ "ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\n" ++ "b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL\n" ++ "MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\n" ++ "b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK\n" ++ "gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ\n" ++ "W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg\n" ++ "1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K\n" ++ "8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r\n" ++ "2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me\n" ++ "z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR\n" ++ "8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj\n" ++ "mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz\n" ++ "7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6\n" ++ "+XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI\n" ++ "0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB\n" ++ "Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm\n" ++ "UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2\n" ++ "LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY\n" ++ "+gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS\n" ++ "k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl\n" ++ "7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm\n" ++ "btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl\n" ++ "urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+\n" ++ "fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63\n" ++ "n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE\n" ++ "76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H\n" ++ "9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT\n" ++ "4PsJYGw=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5\n" ++ "MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g\n" ++ "Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG\n" ++ "A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg\n" ++ "Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl\n" ++ "ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j\n" ++ "QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr\n" ++ "ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr\n" ++ "BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM\n" ++ "YyRIHN8wfdVoOw==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5\n" ++ "MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g\n" ++ "Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG\n" ++ "A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg\n" ++ "Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi\n" ++ "9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk\n" ++ "M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB\n" ++ "/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB\n" ++ "MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw\n" ++ "CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW\n" ++ "1KyLa2tJElMzrdfkviT8tQp21KW8EA==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE\n" ++ "AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG\n" ++ "EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM\n" ++ "FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC\n" ++ "REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp\n" ++ "Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM\n" ++ "VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+\n" ++ "SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ\n" ++ "4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L\n" ++ "cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi\n" ++ "eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV\n" ++ "HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG\n" ++ "A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3\n" ++ "DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j\n" ++ "vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP\n" ++ "DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc\n" ++ "maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D\n" ++ "lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv\n" ++ "KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w\n" ++ "LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w\n" ++ "CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0\n" ++ "MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBF\n" ++ "Q0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMHYwEAYHKoZI\n" ++ "zj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6KDP/X\n" ++ "tXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4\n" ++ "AjJn8ZQSb+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2\n" ++ "KCXWfeBmmnoJsmo7jjPXNtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMD\n" ++ "aAAwZQIwW5kp85wxtolrbNa9d+F851F+uDrNozZffPc8dz7kUK2o59JZDCaOMDtu\n" ++ "CCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo\n" ++ "9H1/IISpQuQo\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM\n" ++ "MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx\n" ++ "MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00\n" ++ "MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBD\n" ++ "QSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMIICIjAN\n" ++ "BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BBl01Z\n" ++ "4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYv\n" ++ "Ye+W/CBGvevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZ\n" ++ "kmGbzSoXfduP9LVq6hdKZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDs\n" ++ "GY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt0xU6kGpn8bRrZtkh68rZYnxGEFzedUln\n" ++ "nkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVKPNe0OwANwI8f4UDErmwh\n" ++ "3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMYsluMWuPD\n" ++ "0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzy\n" ++ "geBYBr3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8\n" ++ "ANSbhqRAvNncTFd+rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezB\n" ++ "c6eUWsuSZIKmAMFwoW4sKeFYV+xafJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lI\n" ++ "pw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU\n" ++ "dEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB\n" ++ "DAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS\n" ++ "4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPs\n" ++ "o0UvFJ/1TCplQ3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJ\n" ++ "qM7F78PRreBrAwA0JrRUITWXAdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuyw\n" ++ "xfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9GslA9hGCZcbUztVdF5kJHdWoOsAgM\n" ++ "rr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2VktafcxBPTy+av5EzH4\n" ++ "AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9qTFsR\n" ++ "0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuY\n" ++ "o7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5\n" ++ "dDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE\n" ++ "oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE\n" ++ "BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h\n" ++ "cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1\n" ++ "MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg\n" ++ "Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi\n" ++ "MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9\n" ++ "thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM\n" ++ "cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG\n" ++ "L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i\n" ++ "NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h\n" ++ "X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b\n" ++ "m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy\n" ++ "Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja\n" ++ "EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T\n" ++ "KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF\n" ++ "6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh\n" ++ "OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc\n" ++ "tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd\n" ++ "IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j\n" ++ "b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC\n" ++ "AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw\n" ++ "ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m\n" ++ "iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF\n" ++ "Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ\n" ++ "hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P\n" ++ "Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE\n" ++ "EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV\n" ++ "1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t\n" ++ "CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR\n" ++ "5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw\n" ++ "f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9\n" ++ "ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK\n" ++ "GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBU\n" ++ "MQswCQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRI\n" ++ "T1JJVFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAz\n" ++ "MTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJF\n" ++ "SUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2Jh\n" ++ "bCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFmCL3Z\n" ++ "xRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZ\n" ++ "spDyRhySsTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O5\n" ++ "58dnJCNPYwpj9mZ9S1WnP3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgR\n" ++ "at7GGPZHOiJBhyL8xIkoVNiMpTAK+BcWyqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll\n" ++ "5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRjeulumijWML3mG90Vr4Tq\n" ++ "nMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNnMoH1V6XK\n" ++ "V0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/\n" ++ "pj+bOT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZO\n" ++ "z2nxbkRs1CTqjSShGL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXn\n" ++ "jSXWgXSHRtQpdaJCbPdzied9v3pKH9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+\n" ++ "WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMBAAGjQjBAMB0GA1UdDgQWBBTF\n" ++ "7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE\n" ++ "AwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4\n" ++ "YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3Kli\n" ++ "awLwQ8hOnThJdMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u\n" ++ "+2D2/VnGKhs/I0qUJDAnyIm860Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88\n" ++ "X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuhTaRjAv04l5U/BXCga99igUOLtFkN\n" ++ "SoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW4AB+dAb/OMRyHdOo\n" ++ "P2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmpGQrI\n" ++ "+pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRz\n" ++ "znfSxqxx4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9\n" ++ "eVzYH6Eze9mCUAyTF6ps3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2\n" ++ "YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4SSPfSKcOYKMryMguTjClPPGAyzQWWYezy\n" ++ "r/6zcCwupvI=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQsw\n" ++ "CQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJ\n" ++ "VFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgy\n" ++ "MVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJ\n" ++ "TkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2JhbCBS\n" ++ "b290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jlSR9B\n" ++ "IgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK+\n" ++ "+kpRuDCK/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJK\n" ++ "sVF/BvDRgh9Obl+rg/xI1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD\n" ++ "AgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA\n" ++ "94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8gUXOQwKhbYdDFUDn9hf7B\n" ++ "43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ\n" ++ "RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD\n" ++ "VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX\n" ++ "DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y\n" ++ "ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy\n" ++ "VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr\n" ++ "mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr\n" ++ "IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK\n" ++ "mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu\n" ++ "XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy\n" ++ "dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye\n" ++ "jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1\n" ++ "BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3\n" ++ "DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92\n" ++ "9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx\n" ++ "jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0\n" ++ "Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz\n" ++ "ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS\n" ++ "R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd\n" ++ "MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg\n" ++ "Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow\n" ++ "TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw\n" ++ "HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB\n" ++ "BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr\n" ++ "6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV\n" ++ "L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91\n" ++ "1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx\n" ++ "MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ\n" ++ "QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB\n" ++ "arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr\n" ++ "Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi\n" ++ "FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS\n" ++ "P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN\n" ++ "9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP\n" ++ "AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz\n" ++ "uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h\n" ++ "9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s\n" ++ "A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t\n" ++ "OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo\n" ++ "+fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7\n" ++ "KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2\n" ++ "DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us\n" ++ "H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ\n" ++ "I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7\n" ++ "5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h\n" ++ "3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz\n" ++ "Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd\n" ++ "MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg\n" ++ "Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow\n" ++ "TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw\n" ++ "HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB\n" ++ "BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y\n" ++ "ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E\n" ++ "N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9\n" ++ "tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX\n" ++ "0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c\n" ++ "/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X\n" ++ "KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY\n" ++ "zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS\n" ++ "O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D\n" ++ "34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP\n" ++ "K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3\n" ++ "AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv\n" ++ "Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj\n" ++ "QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV\n" ++ "cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS\n" ++ "IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2\n" ++ "HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa\n" ++ "O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv\n" ++ "033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u\n" ++ "dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE\n" ++ "kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41\n" ++ "3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD\n" ++ "u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq\n" ++ "4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV\n" ++ "BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu\n" ++ "MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy\n" ++ "MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx\n" ++ "EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw\n" ++ "ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe\n" ++ "NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH\n" ++ "PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I\n" ++ "x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe\n" ++ "QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR\n" ++ "yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO\n" ++ "QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912\n" ++ "H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ\n" ++ "QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD\n" ++ "i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs\n" ++ "nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1\n" ++ "rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud\n" ++ "DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI\n" ++ "hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM\n" ++ "tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf\n" ++ "GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb\n" ++ "lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka\n" ++ "+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal\n" ++ "TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i\n" ++ "nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3\n" ++ "gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr\n" ++ "G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os\n" ++ "zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x\n" ++ "L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD\n" ++ "TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y\n" ++ "aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx\n" ++ "MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j\n" ++ "aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP\n" ++ "T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03\n" ++ "sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL\n" ++ "TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5\n" ++ "/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp\n" ++ "7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz\n" ++ "EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt\n" ++ "hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP\n" ++ "a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot\n" ++ "aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg\n" ++ "TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV\n" ++ "PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv\n" ++ "cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL\n" ++ "tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd\n" ++ "BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB\n" ++ "ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT\n" ++ "ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL\n" ++ "jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS\n" ++ "ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy\n" ++ "P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19\n" ++ "xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d\n" ++ "Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN\n" ++ "5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe\n" ++ "/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z\n" ++ "AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ\n" ++ "5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB\n" ++ "gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G\n" ++ "A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV\n" ++ "BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw\n" ++ "MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl\n" ++ "YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P\n" ++ "RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0\n" ++ "aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3\n" ++ "UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI\n" ++ "2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8\n" ++ "Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp\n" ++ "+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+\n" ++ "DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O\n" ++ "nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW\n" ++ "/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g\n" ++ "PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u\n" ++ "QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY\n" ++ "SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv\n" ++ "IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/\n" ++ "RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4\n" ++ "zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd\n" ++ "BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB\n" ++ "ZQ==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL\n" ++ "MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE\n" ++ "BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT\n" ++ "IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw\n" ++ "MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy\n" ++ "ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N\n" ++ "T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv\n" ++ "biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR\n" ++ "FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J\n" ++ "cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW\n" ++ "BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/\n" ++ "BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm\n" ++ "fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv\n" ++ "GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB\n" ++ "hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G\n" ++ "A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV\n" ++ "BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5\n" ++ "MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT\n" ++ "EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR\n" ++ "Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh\n" ++ "dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR\n" ++ "6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X\n" ++ "pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC\n" ++ "9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV\n" ++ "/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf\n" ++ "Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z\n" ++ "+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w\n" ++ "qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah\n" ++ "SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC\n" ++ "u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf\n" ++ "Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq\n" ++ "crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E\n" ++ "FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB\n" ++ "/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl\n" ++ "wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM\n" ++ "4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV\n" ++ "2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna\n" ++ "FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ\n" ++ "CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK\n" ++ "boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke\n" ++ "jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL\n" ++ "S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb\n" ++ "QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl\n" ++ "0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB\n" ++ "NVOFBkpdn627G190\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw\n" ++ "CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu\n" ++ "bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ\n" ++ "BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s\n" ++ "eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK\n" ++ "+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2\n" ++ "QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E\n" ++ "BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4\n" ++ "hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm\n" ++ "ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG\n" ++ "BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw\n" ++ "PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy\n" ++ "dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9\n" ++ "MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0\n" ++ "YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2\n" ++ "1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT\n" ++ "vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed\n" ++ "aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0\n" ++ "1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5\n" ++ "r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5\n" ++ "cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ\n" ++ "wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ\n" ++ "6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA\n" ++ "2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH\n" ++ "Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR\n" ++ "eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB\n" ++ "/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u\n" ++ "d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr\n" ++ "PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d\n" ++ "8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi\n" ++ "1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd\n" ++ "rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di\n" ++ "taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7\n" ++ "lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj\n" ++ "yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn\n" ++ "Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy\n" ++ "yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n\n" ++ "wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6\n" ++ "OV+KmalBWQewLK8=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV\n" ++ "BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X\n" ++ "DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ\n" ++ "BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3\n" ++ "DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4\n" ++ "QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny\n" ++ "gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw\n" ++ "zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q\n" ++ "130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2\n" ++ "JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw\n" ++ "DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw\n" ++ "ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT\n" ++ "AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj\n" ++ "AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG\n" ++ "9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h\n" ++ "bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc\n" ++ "fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu\n" ++ "HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w\n" ++ "t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw\n" ++ "WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw\n" ++ "WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw\n" ++ "MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x\n" ++ "MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD\n" ++ "VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX\n" ++ "BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw\n" ++ "ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO\n" ++ "ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M\n" ++ "CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu\n" ++ "I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm\n" ++ "TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh\n" ++ "C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf\n" ++ "ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz\n" ++ "IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT\n" ++ "Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k\n" ++ "JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5\n" ++ "hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB\n" ++ "GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE\n" ++ "FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of\n" ++ "1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov\n" ++ "L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo\n" ++ "dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr\n" ++ "aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq\n" ++ "hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L\n" ++ "6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG\n" ++ "HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6\n" ++ "0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB\n" ++ "lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi\n" ++ "o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1\n" ++ "gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v\n" ++ "faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63\n" ++ "Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh\n" ++ "jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw\n" ++ "3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw\n" ++ "CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw\n" ++ "JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT\n" ++ "EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0\n" ++ "WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT\n" ++ "LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX\n" ++ "BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE\n" ++ "KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm\n" ++ "Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj\n" ++ "QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8\n" ++ "EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J\n" ++ "UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn\n" ++ "nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM\n" ++ "MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D\n" ++ "ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU\n" ++ "cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3\n" ++ "WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg\n" ++ "Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw\n" ++ "IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B\n" ++ "AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH\n" ++ "UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM\n" ++ "TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU\n" ++ "BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM\n" ++ "kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x\n" ++ "AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV\n" ++ "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV\n" ++ "HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y\n" ++ "sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL\n" ++ "I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8\n" ++ "J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY\n" ++ "VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI\n" ++ "03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB\n" ++ "gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu\n" ++ "QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG\n" ++ "A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz\n" ++ "OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ\n" ++ "VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp\n" ++ "ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3\n" ++ "b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA\n" ++ "DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn\n" ++ "0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB\n" ++ "OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE\n" ++ "fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E\n" ++ "Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m\n" ++ "o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i\n" ++ "sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW\n" ++ "OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez\n" ++ "Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS\n" ++ "adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n\n" ++ "3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD\n" ++ "AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC\n" ++ "AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ\n" ++ "F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf\n" ++ "CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29\n" ++ "XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm\n" ++ "djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/\n" ++ "WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb\n" ++ "AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq\n" ++ "P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko\n" ++ "b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj\n" ++ "XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P\n" ++ "5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi\n" ++ "DrW5viSP\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6\n" ++ "MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu\n" ++ "MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV\n" ++ "BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw\n" ++ "MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg\n" ++ "U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo\n" ++ "b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG\n" ++ "SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ\n" ++ "n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q\n" ++ "p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq\n" ++ "NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF\n" ++ "8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3\n" ++ "HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa\n" ++ "mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi\n" ++ "7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF\n" ++ "ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P\n" ++ "qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ\n" ++ "v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6\n" ++ "Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1\n" ++ "vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD\n" ++ "ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4\n" ++ "WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo\n" ++ "zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR\n" ++ "5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ\n" ++ "GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf\n" ++ "5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq\n" ++ "0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D\n" ++ "P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM\n" ++ "qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP\n" ++ "0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf\n" ++ "E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMw\n" ++ "TjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29t\n" ++ "bVNjb3BlIFB1YmxpYyBUcnVzdCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNa\n" ++ "Fw00NjA0MjgxNzM1NDJaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2Nv\n" ++ "cGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDEw\n" ++ "djAQBgcqhkjOPQIBBgUrgQQAIgNiAARLNumuV16ocNfQj3Rid8NeeqrltqLxeP0C\n" ++ "flfdkXmcbLlSiFS8LwS+uM32ENEp7LXQoMPwiXAZu1FlxUOcw5tjnSCDPgYLpkJE\n" ++ "hRGnSjot6dZoL0hOUysHP029uax3OVejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD\n" ++ "VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggq\n" ++ "hkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg\n" ++ "2NED3W3ROT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liWpDVfG2XqYZpwI7UNo5uS\n" ++ "Um9poIyNStDuiw7LR47QjRE=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMw\n" ++ "TjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29t\n" ++ "bVNjb3BlIFB1YmxpYyBUcnVzdCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRa\n" ++ "Fw00NjA0MjgxNzQ0NTNaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2Nv\n" ++ "cGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDIw\n" ++ "djAQBgcqhkjOPQIBBgUrgQQAIgNiAAR4MIHoYx7l63FRD/cHB8o5mXxO1Q/MMDAL\n" ++ "j2aTPs+9xYa9+bG3tD60B8jzljHz7aRP+KNOjSkVWLjVb3/ubCK1sK9IRQq9qEmU\n" ++ "v4RDsNuESgMjGWdqb8FuvAY5N9GIIvejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD\n" ++ "VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggq\n" ++ "hkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/n\n" ++ "ich/m35rChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs73u1Z/GtMMH9ZzkXpc2AV\n" ++ "mkzw5l4lIhVtwodZ0LKOag==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQEL\n" ++ "BQAwTjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwi\n" ++ "Q29tbVNjb3BlIFB1YmxpYyBUcnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1\n" ++ "NTRaFw00NjA0MjgxNjQ1NTNaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21t\n" ++ "U2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3Qt\n" ++ "MDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwSGWjDR1C45FtnYSk\n" ++ "YZYSwu3D2iM0GXb26v1VWvZVAVMP8syMl0+5UMuzAURWlv2bKOx7dAvnQmtVzslh\n" ++ "suitQDy6uUEKBU8bJoWPQ7VAtYXR1HHcg0Hz9kXHgKKEUJdGzqAMxGBWBB0HW0al\n" ++ "DrJLpA6lfO741GIDuZNqihS4cPgugkY4Iw50x2tBt9Apo52AsH53k2NC+zSDO3Oj\n" ++ "WiE260f6GBfZumbCk6SP/F2krfxQapWsvCQz0b2If4b19bJzKo98rwjyGpg/qYFl\n" ++ "P8GMicWWMJoKz/TUyDTtnS+8jTiGU+6Xn6myY5QXjQ/cZip8UlF1y5mO6D1cv547\n" ++ "KI2DAg+pn3LiLCuz3GaXAEDQpFSOm117RTYm1nJD68/A6g3czhLmfTifBSeolz7p\n" ++ "UcZsBSjBAg/pGG3svZwG1KdJ9FQFa2ww8esD1eo9anbCyxooSU1/ZOD6K9pzg4H/\n" ++ "kQO9lLvkuI6cMmPNn7togbGEW682v3fuHX/3SZtS7NJ3Wn2RnU3COS3kuoL4b/JO\n" ++ "Hg9O5j9ZpSPcPYeoKFgo0fEbNttPxP/hjFtyjMcmAyejOQoBqsCyMWCDIqFPEgkB\n" ++ "Ea801M/XrmLTBQe0MXXgDW1XT2mH+VepuhX2yFJtocucH+X8eKg1mp9BFM6ltM6U\n" ++ "CBwJrVbl2rZJmkrqYxhTnCwuwwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G\n" ++ "A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUN12mmnQywsL5x6YVEFm45P3luG0wDQYJ\n" ++ "KoZIhvcNAQELBQADggIBAK+nz97/4L1CjU3lIpbfaOp9TSp90K09FlxD533Ahuh6\n" ++ "NWPxzIHIxgvoLlI1pKZJkGNRrDSsBTtXAOnTYtPZKdVUvhwQkZyybf5Z/Xn36lbQ\n" ++ "nmhUQo8mUuJM3y+Xpi/SB5io82BdS5pYV4jvguX6r2yBS5KPQJqTRlnLX3gWsWc+\n" ++ "QgvfKNmwrZggvkN80V4aCRckjXtdlemrwWCrWxhkgPut4AZ9HcpZuPN4KWfGVh2v\n" ++ "trV0KnahP/t1MJ+UXjulYPPLXAziDslg+MkfFoom3ecnf+slpoq9uC02EJqxWE2a\n" ++ "aE9gVOX2RhOOiKy8IUISrcZKiX2bwdgt6ZYD9KJ0DLwAHb/WNyVntHKLr4W96ioD\n" ++ "j8z7PEQkguIBpQtZtjSNMgsSDesnwv1B10A8ckYpwIzqug/xBpMu95yo9GA+o/E4\n" ++ "Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054A5U+1C0w\n" ++ "lREQKC6/oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHn\n" ++ "YfkUyq+Dj7+vsQpZXdxc1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVoc\n" ++ "icCMb3SgazNNtQEo/a2tiRc7ppqEvOuM6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQEL\n" ++ "BQAwTjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwi\n" ++ "Q29tbVNjb3BlIFB1YmxpYyBUcnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2\n" ++ "NDNaFw00NjA0MjgxNzE2NDJaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21t\n" ++ "U2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3Qt\n" ++ "MDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDh+g77aAASyE3VrCLE\n" ++ "NQE7xVTlWXZjpX/rwcRqmL0yjReA61260WI9JSMZNRTpf4mnG2I81lDnNJUDMrG0\n" ++ "kyI9p+Kx7eZ7Ti6Hmw0zdQreqjXnfuU2mKKuJZ6VszKWpCtYHu8//mI0SFHRtI1C\n" ++ "rWDaSWqVcN3SAOLMV2MCe5bdSZdbkk6V0/nLKR8YSvgBKtJjCW4k6YnS5cciTNxz\n" ++ "hkcAqg2Ijq6FfUrpuzNPDlJwnZXjfG2WWy09X6GDRl224yW4fKcZgBzqZUPckXk2\n" ++ "LHR88mcGyYnJ27/aaL8j7dxrrSiDeS/sOKUNNwFnJ5rpM9kzXzehxfCrPfp4sOcs\n" ++ "n/Y+n2Dg70jpkEUeBVF4GiwSLFworA2iI540jwXmojPOEXcT1A6kHkIfhs1w/tku\n" ++ "FT0du7jyU1fbzMZ0KZwYszZ1OC4PVKH4kh+Jlk+71O6d6Ts2QrUKOyrUZHk2EOH5\n" ++ "kQMreyBUzQ0ZGshBMjTRsJnhkB4BQDa1t/qp5Xd1pCKBXbCL5CcSD1SIxtuFdOa3\n" ++ "wNemKfrb3vOTlycEVS8KbzfFPROvCgCpLIscgSjX74Yxqa7ybrjKaixUR9gqiC6v\n" ++ "wQcQeKwRoi9C8DfF8rhW3Q5iLc4tVn5V8qdE9isy9COoR+jUKgF4z2rDN6ieZdIs\n" ++ "5fq6M8EGRPbmz6UNp2YINIos8wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G\n" ++ "A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUR9DnsSL/nSz12Vdgs7GxcJXvYXowDQYJ\n" ++ "KoZIhvcNAQELBQADggIBAIZpsU0v6Z9PIpNojuQhmaPORVMbc0RTAIFhzTHjCLqB\n" ++ "KCh6krm2qMhDnscTJk3C2OVVnJJdUNjCK9v+5qiXz1I6JMNlZFxHMaNlNRPDk7n3\n" ++ "+VGXu6TwYofF1gbTl4MgqX67tiHCpQ2EAOHyJxCDut0DgdXdaMNmEMjRdrSzbyme\n" ++ "APnCKfWxkxlSaRosTKCL4BWaMS/TiJVZbuXEs1DIFAhKm4sTg7GkcrI7djNB3Nyq\n" ++ "pgdvHSQSn8h2vS/ZjvQs7rfSOBAkNlEv41xdgSGn2rtO/+YHqP65DSdsu3BaVXoT\n" ++ "6fEqSWnHX4dXTEN5bTpl6TBcQe7rd6VzEojov32u5cSoHw2OHG1QAk8mGEPej1WF\n" ++ "sQs3BWDJVTkSBKEqz3EWnzZRSb9wO55nnPt7eck5HHisd5FUmrh1CoFSl+NmYWvt\n" ++ "PjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jauwy8CTl2d\n" ++ "lklyALKrdVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670\n" ++ "v64fG9PiO/yzcnMcmyiQiRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17O\n" ++ "rg3bhzjlP1v9mxnhMUF6cKojawHhRUzNlM47ni3niAIi9G7oyOzWPPO5std3eqx7\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb\n" ++ "MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow\n" ++ "GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj\n" ++ "YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL\n" ++ "MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE\n" ++ "BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM\n" ++ "GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP\n" ++ "ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua\n" ++ "BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe\n" ++ "3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4\n" ++ "YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR\n" ++ "rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm\n" ++ "ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU\n" ++ "oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF\n" ++ "MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v\n" ++ "QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t\n" ++ "b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF\n" ++ "AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q\n" ++ "GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz\n" ++ "Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2\n" ++ "G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi\n" ++ "l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3\n" ++ "smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw\n" ++ "CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS\n" ++ "VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5\n" ++ "NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG\n" ++ "A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB\n" ++ "BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS\n" ++ "zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0\n" ++ "QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/\n" ++ "VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g\n" ++ "PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf\n" ++ "Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l\n" ++ "dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1\n" ++ "c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO\n" ++ "PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW\n" ++ "wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV\n" ++ "dWNbFJWcHwHP2NVypw87\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw\n" ++ "CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS\n" ++ "VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5\n" ++ "NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG\n" ++ "A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB\n" ++ "BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC\n" ++ "/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD\n" ++ "wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3\n" ++ "OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g\n" ++ "PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf\n" ++ "Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l\n" ++ "dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1\n" ++ "c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO\n" ++ "PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA\n" ++ "y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb\n" ++ "gfM0agPnIjhQW+0ZT0MW\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF\n" ++ "MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD\n" ++ "bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha\n" ++ "ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM\n" ++ "HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB\n" ++ "BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03\n" ++ "UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42\n" ++ "tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R\n" ++ "ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM\n" ++ "lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp\n" ++ "/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G\n" ++ "A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G\n" ++ "A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj\n" ++ "dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy\n" ++ "MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl\n" ++ "cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js\n" ++ "L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL\n" ++ "BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni\n" ++ "acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0\n" ++ "o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K\n" ++ "zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8\n" ++ "PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y\n" ++ "Johw1+qRzT65ysCQblrGXnRl11z+o+I=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF\n" ++ "MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD\n" ++ "bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw\n" ++ "NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV\n" ++ "BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI\n" ++ "hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn\n" ++ "ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0\n" ++ "3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z\n" ++ "qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR\n" ++ "p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8\n" ++ "HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw\n" ++ "ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea\n" ++ "HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw\n" ++ "Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh\n" ++ "c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E\n" ++ "RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt\n" ++ "dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku\n" ++ "Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp\n" ++ "3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05\n" ++ "nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF\n" ++ "CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na\n" ++ "xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX\n" ++ "KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl\n" ++ "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" ++ "d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv\n" ++ "b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG\n" ++ "EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl\n" ++ "cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi\n" ++ "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c\n" ++ "JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP\n" ++ "mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+\n" ++ "wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4\n" ++ "VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/\n" ++ "AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB\n" ++ "AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW\n" ++ "BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun\n" ++ "pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC\n" ++ "dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf\n" ++ "fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm\n" ++ "NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx\n" ++ "H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe\n" ++ "+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl\n" ++ "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" ++ "d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv\n" ++ "b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG\n" ++ "EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl\n" ++ "cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi\n" ++ "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA\n" ++ "n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc\n" ++ "biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp\n" ++ "EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA\n" ++ "bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu\n" ++ "YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB\n" ++ "AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW\n" ++ "BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI\n" ++ "QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I\n" ++ "0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni\n" ++ "lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9\n" ++ "B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv\n" ++ "ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo\n" ++ "IhNzbM8m9Yop5w==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw\n" ++ "CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu\n" ++ "ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg\n" ++ "RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV\n" ++ "UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu\n" ++ "Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq\n" ++ "hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf\n" ++ "Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q\n" ++ "RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/\n" ++ "BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD\n" ++ "AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY\n" ++ "JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv\n" ++ "6pZjamVFkpUBtA==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n" ++ "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" ++ "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n" ++ "QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\n" ++ "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" ++ "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n" ++ "9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\n" ++ "CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\n" ++ "nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n" ++ "43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\n" ++ "T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\n" ++ "gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\n" ++ "BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\n" ++ "TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\n" ++ "DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\n" ++ "hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n" ++ "06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\n" ++ "PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\n" ++ "YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\n" ++ "CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh\n" ++ "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" ++ "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\n" ++ "MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT\n" ++ "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" ++ "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\n" ++ "9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\n" ++ "2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\n" ++ "1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\n" ++ "q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\n" ++ "tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\n" ++ "vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP\n" ++ "BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV\n" ++ "5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY\n" ++ "1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4\n" ++ "NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG\n" ++ "Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91\n" ++ "8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\n" ++ "pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl\n" ++ "MrY=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw\n" ++ "CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu\n" ++ "ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe\n" ++ "Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw\n" ++ "EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x\n" ++ "IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF\n" ++ "K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG\n" ++ "fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO\n" ++ "Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd\n" ++ "BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx\n" ++ "AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/\n" ++ "oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8\n" ++ "sycX\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs\n" ++ "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" ++ "d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j\n" ++ "ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL\n" ++ "MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3\n" ++ "LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug\n" ++ "RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm\n" ++ "+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW\n" ++ "PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM\n" ++ "xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB\n" ++ "Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3\n" ++ "hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg\n" ++ "EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF\n" ++ "MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA\n" ++ "FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec\n" ++ "nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z\n" ++ "eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF\n" ++ "hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2\n" ++ "Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe\n" ++ "vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep\n" ++ "+OkuE6N36B9K\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw\n" ++ "CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp\n" ++ "Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2\n" ++ "MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ\n" ++ "bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG\n" ++ "ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS\n" ++ "7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp\n" ++ "0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS\n" ++ "B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49\n" ++ "BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ\n" ++ "LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4\n" ++ "DXZDjC5Ty3zfDBeWUA==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN\n" ++ "MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT\n" ++ "HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN\n" ++ "NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs\n" ++ "IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi\n" ++ "MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+\n" ++ "ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0\n" ++ "2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp\n" ++ "wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM\n" ++ "pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD\n" ++ "nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po\n" ++ "sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx\n" ++ "Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd\n" ++ "Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX\n" ++ "KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe\n" ++ "XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL\n" ++ "tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv\n" ++ "TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN\n" ++ "AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw\n" ++ "GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H\n" ++ "PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF\n" ++ "O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ\n" ++ "REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik\n" ++ "AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv\n" ++ "/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+\n" ++ "p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw\n" ++ "MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF\n" ++ "qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK\n" ++ "ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi\n" ++ "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" ++ "d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg\n" ++ "RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV\n" ++ "UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu\n" ++ "Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG\n" ++ "SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y\n" ++ "ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If\n" ++ "xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV\n" ++ "ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO\n" ++ "DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ\n" ++ "jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/\n" ++ "CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi\n" ++ "EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM\n" ++ "fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY\n" ++ "uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK\n" ++ "chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t\n" ++ "9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB\n" ++ "hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD\n" ++ "ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2\n" ++ "SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd\n" ++ "+SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc\n" ++ "fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa\n" ++ "sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N\n" ++ "cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N\n" ++ "0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie\n" ++ "4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI\n" ++ "r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1\n" ++ "/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm\n" ++ "gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML\n" ++ "RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp\n" ++ "bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5\n" ++ "IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp\n" ++ "ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3\n" ++ "MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3\n" ++ "LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp\n" ++ "YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG\n" ++ "A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp\n" ++ "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq\n" ++ "K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe\n" ++ "sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX\n" ++ "MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT\n" ++ "XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/\n" ++ "HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH\n" ++ "4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\n" ++ "HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub\n" ++ "j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo\n" ++ "U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf\n" ++ "zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b\n" ++ "u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+\n" ++ "bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er\n" ++ "fF6adulZkMV8gzURZVE=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC\n" ++ "VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0\n" ++ "Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW\n" ++ "KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl\n" ++ "cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw\n" ++ "NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw\n" ++ "NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy\n" ++ "ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV\n" ++ "BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ\n" ++ "KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo\n" ++ "Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4\n" ++ "4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9\n" ++ "KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI\n" ++ "rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi\n" ++ "94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB\n" ++ "sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi\n" ++ "gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo\n" ++ "kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE\n" ++ "vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA\n" ++ "A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t\n" ++ "O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua\n" ++ "AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP\n" ++ "9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/\n" ++ "eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m\n" ++ "0vdXcDazv/wor3ElhVsT/h5/WrQ8\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG\n" ++ "A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3\n" ++ "d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu\n" ++ "dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq\n" ++ "RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy\n" ++ "MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD\n" ++ "VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0\n" ++ "L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g\n" ++ "Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD\n" ++ "ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi\n" ++ "A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt\n" ++ "ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH\n" ++ "Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O\n" ++ "BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC\n" ++ "R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX\n" ++ "hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC\n" ++ "VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50\n" ++ "cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs\n" ++ "IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz\n" ++ "dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy\n" ++ "NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu\n" ++ "dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt\n" ++ "dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0\n" ++ "aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj\n" ++ "YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n" ++ "AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T\n" ++ "RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN\n" ++ "cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW\n" ++ "wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1\n" ++ "U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0\n" ++ "jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP\n" ++ "BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN\n" ++ "BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/\n" ++ "jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ\n" ++ "Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v\n" ++ "1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R\n" ++ "nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH\n" ++ "VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw\n" ++ "gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL\n" ++ "Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg\n" ++ "MjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAw\n" ++ "BgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0\n" ++ "MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYTAlVT\n" ++ "MRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1\n" ++ "c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJ\n" ++ "bmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3Qg\n" ++ "Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0MIICIjANBgkqhkiG9w0B\n" ++ "AQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3DumSXbcr3DbVZwbPLqGgZ\n" ++ "2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV3imz/f3E\n" ++ "T+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j\n" ++ "5pds8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAM\n" ++ "C1rlLAHGVK/XqsEQe9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73T\n" ++ "DtTUXm6Hnmo9RR3RXRv06QqsYJn7ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNX\n" ++ "wbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5XxNMhIWNlUpEbsZmOeX7m640A\n" ++ "2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV7rtNOzK+mndm\n" ++ "nqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8\n" ++ "dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwl\n" ++ "N4y6mACXi0mWHv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNj\n" ++ "c0kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD\n" ++ "VR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9nMA0GCSqGSIb3DQEBCwUAA4ICAQAS\n" ++ "5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4QjbRaZIxowLByQzTS\n" ++ "Gwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht7LGr\n" ++ "hFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/\n" ++ "B7NTeLUKYvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uI\n" ++ "AeV8KEsD+UmDfLJ/fOPtjqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbw\n" ++ "H5Lk6rWS02FREAutp9lfx1/cH6NcjKF+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+\n" ++ "b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk\n" ++ "2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjAJOgc47Ol\n" ++ "IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk\n" ++ "5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY\n" ++ "n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE\n" ++ "BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ\n" ++ "IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0\n" ++ "MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV\n" ++ "BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w\n" ++ "HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF\n" ++ "AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj\n" ++ "Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj\n" ++ "TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u\n" ++ "KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj\n" ++ "qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm\n" ++ "MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12\n" ++ "ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP\n" ++ "zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk\n" ++ "L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC\n" ++ "jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA\n" ++ "HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC\n" ++ "AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB\n" ++ "/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg\n" ++ "p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm\n" ++ "DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5\n" ++ "COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry\n" ++ "L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf\n" ++ "JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg\n" ++ "IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io\n" ++ "2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV\n" ++ "09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ\n" ++ "XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq\n" ++ "T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe\n" ++ "MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkG\n" ++ "A1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkw\n" ++ "FwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYx\n" ++ "MDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9u\n" ++ "aXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMIICIjANBgkq\n" ++ "hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWiD59b\n" ++ "RatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9Z\n" ++ "YybNpyrOVPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3\n" ++ "QWPKzv9pj2gOlTblzLmMCcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPw\n" ++ "yJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCmfecqQjuCgGOlYx8ZzHyyZqjC0203b+J+\n" ++ "BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKAA1GqtH6qRNdDYfOiaxaJ\n" ++ "SaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9ORJitHHmkH\n" ++ "r96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj0\n" ++ "4KlGDfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9Me\n" ++ "dKZssCz3AwyIDMvUclOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIw\n" ++ "q7ejMZdnrY8XD2zHc+0klGvIg5rQmjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2\n" ++ "nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n" ++ "AQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1UdIwQYMBaAFNwu\n" ++ "H9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA\n" ++ "VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJC\n" ++ "XtzoRlgHNQIw4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd\n" ++ "6IwPS3BD0IL/qMy/pJTAvoe9iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf\n" ++ "+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS8cE54+X1+NZK3TTN+2/BT+MAi1bi\n" ++ "kvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2HcqtbepBEX4tdJP7\n" ++ "wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxSvTOB\n" ++ "TI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6C\n" ++ "MUO+1918oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn\n" ++ "4rnvyOL2NSl6dPrFf4IFYqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+I\n" ++ "aFvowdlxfv1k7/9nR4hYJS8+hge9+6jlgqispdNpQ80xiEmEU5LAsTkbOYMBMMTy\n" ++ "qfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw\n" ++ "CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU\n" ++ "MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw\n" ++ "MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp\n" ++ "Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA\n" ++ "A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo\n" ++ "27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w\n" ++ "Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw\n" ++ "TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl\n" ++ "qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH\n" ++ "szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8\n" ++ "Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk\n" ++ "MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92\n" ++ "wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p\n" ++ "aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN\n" ++ "VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID\n" ++ "AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\n" ++ "FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb\n" ++ "C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe\n" ++ "QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy\n" ++ "h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4\n" ++ "7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J\n" ++ "ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef\n" ++ "MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/\n" ++ "Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT\n" ++ "6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ\n" ++ "0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm\n" ++ "2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb\n" ++ "bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw\n" ++ "CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU\n" ++ "MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw\n" ++ "MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp\n" ++ "Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA\n" ++ "A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt\n" ++ "nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY\n" ++ "6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu\n" ++ "MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k\n" ++ "RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg\n" ++ "f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV\n" ++ "+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo\n" ++ "dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW\n" ++ "Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa\n" ++ "G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq\n" ++ "gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID\n" ++ "AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\n" ++ "FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H\n" ++ "vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8\n" ++ "0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC\n" ++ "B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u\n" ++ "NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg\n" ++ "yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev\n" ++ "HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6\n" ++ "xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR\n" ++ "TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg\n" ++ "JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV\n" ++ "7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl\n" ++ "6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD\n" ++ "VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG\n" ++ "A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw\n" ++ "WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz\n" ++ "IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi\n" ++ "AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G\n" ++ "jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2\n" ++ "4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW\n" ++ "BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7\n" ++ "VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm\n" ++ "ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD\n" ++ "VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG\n" ++ "A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw\n" ++ "WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz\n" ++ "IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi\n" ++ "AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi\n" ++ "QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR\n" ++ "HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW\n" ++ "BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D\n" ++ "9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8\n" ++ "p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD\n" ++ "VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh\n" ++ "bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw\n" ++ "MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g\n" ++ "UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT\n" ++ "BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx\n" ++ "uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV\n" ++ "HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/\n" ++ "+wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147\n" ++ "bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk\n" ++ "MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH\n" ++ "bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX\n" ++ "DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD\n" ++ "QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu\n" ++ "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc\n" ++ "8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke\n" ++ "hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD\n" ++ "VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI\n" ++ "KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg\n" ++ "515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO\n" ++ "xwy8p2Fp8fc74SrL+SvzZpA3\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG\n" ++ "A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv\n" ++ "b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw\n" ++ "MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i\n" ++ "YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT\n" ++ "aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ\n" ++ "jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp\n" ++ "xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp\n" ++ "1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG\n" ++ "snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ\n" ++ "U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8\n" ++ "9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E\n" ++ "BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B\n" ++ "AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz\n" ++ "yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE\n" ++ "38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP\n" ++ "AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad\n" ++ "DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME\n" ++ "HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G\n" ++ "A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp\n" ++ "Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4\n" ++ "MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG\n" ++ "A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI\n" ++ "hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8\n" ++ "RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT\n" ++ "gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm\n" ++ "KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd\n" ++ "QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ\n" ++ "XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw\n" ++ "DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o\n" ++ "LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU\n" ++ "RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp\n" ++ "jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK\n" ++ "6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX\n" ++ "mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs\n" ++ "Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH\n" ++ "WD9f\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg\n" ++ "MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh\n" ++ "bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx\n" ++ "MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET\n" ++ "MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ\n" ++ "KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI\n" ++ "xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k\n" ++ "ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD\n" ++ "aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw\n" ++ "LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw\n" ++ "1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX\n" ++ "k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2\n" ++ "SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h\n" ++ "bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n\n" ++ "WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY\n" ++ "rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce\n" ++ "MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD\n" ++ "AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu\n" ++ "bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN\n" ++ "nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt\n" ++ "Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61\n" ++ "55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj\n" ++ "vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf\n" ++ "cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz\n" ++ "oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp\n" ++ "nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs\n" ++ "pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v\n" ++ "JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R\n" ++ "8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4\n" ++ "5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx\n" ++ "CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD\n" ++ "ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw\n" ++ "MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex\n" ++ "HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA\n" ++ "IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq\n" ++ "R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd\n" ++ "yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud\n" ++ "DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ\n" ++ "7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8\n" ++ "+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA\n" ++ "MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD\n" ++ "VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy\n" ++ "MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt\n" ++ "c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB\n" ++ "AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ\n" ++ "OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG\n" ++ "vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud\n" ++ "316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo\n" ++ "0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE\n" ++ "y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF\n" ++ "zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE\n" ++ "+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN\n" ++ "I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs\n" ++ "x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa\n" ++ "ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC\n" ++ "4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\n" ++ "HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4\n" ++ "7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg\n" ++ "JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti\n" ++ "2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk\n" ++ "pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF\n" ++ "FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt\n" ++ "rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk\n" ++ "ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5\n" ++ "u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP\n" ++ "4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6\n" ++ "N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3\n" ++ "vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh\n" ++ "MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE\n" ++ "YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3\n" ++ "MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo\n" ++ "ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg\n" ++ "MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN\n" ++ "ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA\n" ++ "PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w\n" ++ "wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi\n" ++ "EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY\n" ++ "avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+\n" ++ "YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE\n" ++ "sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h\n" ++ "/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5\n" ++ "IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj\n" ++ "YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD\n" ++ "ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy\n" ++ "OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P\n" ++ "TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ\n" ++ "HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER\n" ++ "dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf\n" ++ "ReYNnyicsbkqWletNw+vHX/bvZ8=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx\n" ++ "EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT\n" ++ "EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp\n" ++ "ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz\n" ++ "NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH\n" ++ "EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE\n" ++ "AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw\n" ++ "DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD\n" ++ "E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH\n" ++ "/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy\n" ++ "DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh\n" ++ "GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR\n" ++ "tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA\n" ++ "AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE\n" ++ "FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX\n" ++ "WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu\n" ++ "9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr\n" ++ "gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo\n" ++ "2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO\n" ++ "LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI\n" ++ "4uJEvlz36hz1\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw\n" ++ "CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh\n" ++ "cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v\n" ++ "dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG\n" ++ "A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj\n" ++ "aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg\n" ++ "Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7\n" ++ "KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y\n" ++ "STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw\n" ++ "AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD\n" ++ "AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw\n" ++ "SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN\n" ++ "nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs\n" ++ "MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl\n" ++ "c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg\n" ++ "Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL\n" ++ "MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl\n" ++ "YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv\n" ++ "b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l\n" ++ "mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE\n" ++ "4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv\n" ++ "a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M\n" ++ "pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw\n" ++ "Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b\n" ++ "LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY\n" ++ "AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB\n" ++ "AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq\n" ++ "E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr\n" ++ "W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ\n" ++ "CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF\n" ++ "MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE\n" ++ "AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU\n" ++ "X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3\n" ++ "f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja\n" ++ "H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP\n" ++ "JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P\n" ++ "zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt\n" ++ "jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0\n" ++ "/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT\n" ++ "BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79\n" ++ "aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW\n" ++ "xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU\n" ++ "63ZTGI0RmLo=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN\n" ++ "BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl\n" ++ "c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl\n" ++ "bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv\n" ++ "b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ\n" ++ "BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj\n" ++ "YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5\n" ++ "MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0\n" ++ "dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg\n" ++ "QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa\n" ++ "jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC\n" ++ "MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi\n" ++ "C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep\n" ++ "lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof\n" ++ "TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix\n" ++ "DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k\n" ++ "IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT\n" ++ "N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v\n" ++ "dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG\n" ++ "A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh\n" ++ "ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx\n" ++ "QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1\n" ++ "dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC\n" ++ "AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA\n" ++ "4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0\n" ++ "AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10\n" ++ "4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C\n" ++ "ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV\n" ++ "9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD\n" ++ "gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6\n" ++ "Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq\n" ++ "NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko\n" ++ "LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc\n" ++ "Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV\n" ++ "HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd\n" ++ "ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I\n" ++ "XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI\n" ++ "M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot\n" ++ "9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V\n" ++ "Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea\n" ++ "j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh\n" ++ "X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ\n" ++ "l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf\n" ++ "bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4\n" ++ "pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK\n" ++ "e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0\n" ++ "vm9qp/UsQu0yrbYhnr68\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP\n" ++ "MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0\n" ++ "ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa\n" ++ "Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3\n" ++ "YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx\n" ++ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw\n" ++ "qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv\n" ++ "Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6\n" ++ "lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz\n" ++ "Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ\n" ++ "KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK\n" ++ "FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj\n" ++ "HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr\n" ++ "y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ\n" ++ "/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM\n" ++ "a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6\n" ++ "fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\n" ++ "HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG\n" ++ "SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi\n" ++ "7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc\n" ++ "SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza\n" ++ "ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc\n" ++ "XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg\n" ++ "iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho\n" ++ "L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF\n" ++ "Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr\n" ++ "kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+\n" ++ "vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU\n" ++ "YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL\n" ++ "BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ\n" ++ "SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n\n" ++ "a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5\n" ++ "NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT\n" ++ "CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u\n" ++ "Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK\n" ++ "AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO\n" ++ "dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI\n" ++ "VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV\n" ++ "9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY\n" ++ "2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY\n" ++ "vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt\n" ++ "bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb\n" ++ "x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+\n" ++ "l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK\n" ++ "TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj\n" ++ "Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP\n" ++ "BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e\n" ++ "i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw\n" ++ "DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG\n" ++ "7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk\n" ++ "MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr\n" ++ "gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk\n" ++ "GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS\n" ++ "3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm\n" ++ "Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+\n" ++ "l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c\n" ++ "JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP\n" ++ "L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa\n" ++ "LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG\n" ++ "mpv0\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" ++ "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" ++ "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" ++ "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" ++ "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" ++ "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" ++ "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" ++ "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" ++ "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" ++ "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" ++ "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" ++ "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" ++ "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" ++ "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" ++ "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" ++ "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" ++ "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" ++ "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" ++ "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" ++ "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" ++ "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" ++ "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" ++ "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" ++ "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" ++ "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" ++ "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" ++ "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" ++ "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" ++ "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw\n" ++ "CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg\n" ++ "R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00\n" ++ "MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT\n" ++ "ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw\n" ++ "EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW\n" ++ "+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9\n" ++ "ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T\n" ++ "AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI\n" ++ "zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW\n" ++ "tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1\n" ++ "/q4AaOeMSQ+2b1tbFfLn\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK\n" ++ "MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu\n" ++ "VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw\n" ++ "MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw\n" ++ "JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG\n" ++ "SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT\n" ++ "3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU\n" ++ "+ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp\n" ++ "S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1\n" ++ "bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi\n" ++ "T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL\n" ++ "vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK\n" ++ "Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK\n" ++ "dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT\n" ++ "c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv\n" ++ "l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N\n" ++ "iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB\n" ++ "/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD\n" ++ "ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH\n" ++ "6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt\n" ++ "LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93\n" ++ "nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3\n" ++ "+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK\n" ++ "W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT\n" ++ "AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq\n" ++ "l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG\n" ++ "4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ\n" ++ "mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A\n" ++ "7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN\n" ++ "MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu\n" ++ "VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN\n" ++ "MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0\n" ++ "MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi\n" ++ "MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7\n" ++ "ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy\n" ++ "RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS\n" ++ "bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF\n" ++ "/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R\n" ++ "3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw\n" ++ "EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy\n" ++ "9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V\n" ++ "GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ\n" ++ "2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV\n" ++ "WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD\n" ++ "W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/\n" ++ "BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN\n" ++ "AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj\n" ++ "t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV\n" ++ "DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9\n" ++ "TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G\n" ++ "lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW\n" ++ "mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df\n" ++ "WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5\n" ++ "+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ\n" ++ "tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA\n" ++ "GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv\n" ++ "8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4\n" ++ "MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6\n" ++ "ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD\n" ++ "VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j\n" ++ "b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq\n" ++ "scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO\n" ++ "xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H\n" ++ "LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX\n" ++ "uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD\n" ++ "yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+\n" ++ "JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q\n" ++ "rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN\n" ++ "BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L\n" ++ "hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB\n" ++ "QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+\n" ++ "HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu\n" ++ "Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg\n" ++ "QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB\n" ++ "BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx\n" ++ "MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n" ++ "AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA\n" ++ "A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb\n" ++ "laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56\n" ++ "awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo\n" ++ "JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw\n" ++ "LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT\n" ++ "VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk\n" ++ "LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb\n" ++ "UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/\n" ++ "QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+\n" ++ "naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls\n" ++ "QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD\n" ++ "VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0\n" ++ "ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G\n" ++ "CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y\n" ++ "OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx\n" ++ "FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp\n" ++ "Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o\n" ++ "dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP\n" ++ "kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc\n" ++ "cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U\n" ++ "fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7\n" ++ "N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC\n" ++ "xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1\n" ++ "+rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G\n" ++ "A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM\n" ++ "Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG\n" ++ "SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h\n" ++ "mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk\n" ++ "ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775\n" ++ "tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c\n" ++ "2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t\n" ++ "HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw\n" ++ "CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD\n" ++ "VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw\n" ++ "MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV\n" ++ "UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy\n" ++ "b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq\n" ++ "hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR\n" ++ "ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb\n" ++ "hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E\n" ++ "BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3\n" ++ "FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV\n" ++ "L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB\n" ++ "iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl\n" ++ "MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw\n" ++ "NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5\n" ++ "IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG\n" ++ "EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N\n" ++ "aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi\n" ++ "MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ\n" ++ "Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0\n" ++ "ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1\n" ++ "HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm\n" ++ "gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ\n" ++ "jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc\n" ++ "aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG\n" ++ "YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6\n" ++ "W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K\n" ++ "UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH\n" ++ "+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q\n" ++ "W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/\n" ++ "BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC\n" ++ "NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC\n" ++ "LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC\n" ++ "gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6\n" ++ "tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh\n" ++ "SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2\n" ++ "TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3\n" ++ "pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR\n" ++ "xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp\n" ++ "GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9\n" ++ "dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN\n" ++ "AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB\n" ++ "RA+GsCyRxj3qrg+E\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM\n" ++ "BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG\n" ++ "T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0\n" ++ "aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx\n" ++ "CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD\n" ++ "b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB\n" ++ "dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA\n" ++ "iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH\n" ++ "38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE\n" ++ "HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz\n" ++ "kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP\n" ++ "szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq\n" ++ "vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf\n" ++ "nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG\n" ++ "YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo\n" ++ "0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a\n" ++ "CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K\n" ++ "AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I\n" ++ "36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB\n" ++ "Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN\n" ++ "qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj\n" ++ "cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm\n" ++ "+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL\n" ++ "hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe\n" ++ "lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7\n" ++ "p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8\n" ++ "piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR\n" ++ "LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX\n" ++ "5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO\n" ++ "dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul\n" ++ "9XXeifdy\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG\n" ++ "EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3\n" ++ "MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl\n" ++ "cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR\n" ++ "dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB\n" ++ "pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM\n" ++ "b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm\n" ++ "aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz\n" ++ "IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n" ++ "MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT\n" ++ "lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz\n" ++ "AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5\n" ++ "VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG\n" ++ "ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2\n" ++ "BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG\n" ++ "AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M\n" ++ "U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh\n" ++ "bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C\n" ++ "+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC\n" ++ "bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F\n" ++ "uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2\n" ++ "XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt\n" ++ "MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg\n" ++ "Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i\n" ++ "YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x\n" ++ "CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG\n" ++ "b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh\n" ++ "bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3\n" ++ "HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx\n" ++ "WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX\n" ++ "1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk\n" ++ "u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P\n" ++ "99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r\n" ++ "M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw\n" ++ "AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB\n" ++ "BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh\n" ++ "cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5\n" ++ "gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO\n" ++ "ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf\n" ++ "aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic\n" ++ "Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw\n" ++ "CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91\n" ++ "bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg\n" ++ "Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ\n" ++ "BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu\n" ++ "ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS\n" ++ "b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni\n" ++ "eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W\n" ++ "p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E\n" ++ "BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T\n" ++ "rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV\n" ++ "57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg\n" ++ "Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL\n" ++ "BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc\n" ++ "BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00\n" ++ "MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM\n" ++ "aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG\n" ++ "SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV\n" ++ "wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe\n" ++ "rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341\n" ++ "68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh\n" ++ "4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp\n" ++ "UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o\n" ++ "abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc\n" ++ "3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G\n" ++ "KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt\n" ++ "hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO\n" ++ "Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt\n" ++ "zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB\n" ++ "BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD\n" ++ "ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC\n" ++ "MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2\n" ++ "cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN\n" ++ "qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5\n" ++ "YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv\n" ++ "b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2\n" ++ "8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k\n" ++ "NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj\n" ++ "ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp\n" ++ "q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt\n" ++ "nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x\n" ++ "GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv\n" ++ "b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV\n" ++ "BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W\n" ++ "YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa\n" ++ "GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg\n" ++ "Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J\n" ++ "WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB\n" ++ "rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp\n" ++ "+ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1\n" ++ "ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i\n" ++ "Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz\n" ++ "PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og\n" ++ "/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH\n" ++ "oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI\n" ++ "yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud\n" ++ "EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2\n" ++ "A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL\n" ++ "MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT\n" ++ "ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f\n" ++ "BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn\n" ++ "g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl\n" ++ "fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K\n" ++ "WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha\n" ++ "B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc\n" ++ "hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR\n" ++ "TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD\n" ++ "mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z\n" ++ "ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y\n" ++ "4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza\n" ++ "8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL\n" ++ "BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc\n" ++ "BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00\n" ++ "MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM\n" ++ "aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG\n" ++ "SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf\n" ++ "qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW\n" ++ "n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym\n" ++ "c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+\n" ++ "O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1\n" ++ "o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j\n" ++ "IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq\n" ++ "IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz\n" ++ "8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh\n" ++ "vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l\n" ++ "7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG\n" ++ "cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB\n" ++ "BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD\n" ++ "ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66\n" ++ "AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC\n" ++ "roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga\n" ++ "W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n\n" ++ "lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE\n" ++ "+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV\n" ++ "csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd\n" ++ "dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg\n" ++ "KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM\n" ++ "HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4\n" ++ "WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x\n" ++ "GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv\n" ++ "b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV\n" ++ "BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W\n" ++ "YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM\n" ++ "V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB\n" ++ "4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr\n" ++ "H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd\n" ++ "8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv\n" ++ "vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT\n" ++ "mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe\n" ++ "btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc\n" ++ "T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt\n" ++ "WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ\n" ++ "c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A\n" ++ "4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD\n" ++ "VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG\n" ++ "CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0\n" ++ "aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0\n" ++ "aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu\n" ++ "dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw\n" ++ "czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G\n" ++ "A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC\n" ++ "TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg\n" ++ "Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0\n" ++ "7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem\n" ++ "d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd\n" ++ "+LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B\n" ++ "4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN\n" ++ "t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x\n" ++ "DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57\n" ++ "k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s\n" ++ "zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j\n" ++ "Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT\n" ++ "mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK\n" ++ "4SVhM7JZG+Ju1zdXtg2pEto=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL\n" ++ "BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc\n" ++ "BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00\n" ++ "MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM\n" ++ "aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG\n" ++ "SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR\n" ++ "/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu\n" ++ "FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR\n" ++ "U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c\n" ++ "ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR\n" ++ "FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k\n" ++ "A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw\n" ++ "eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl\n" ++ "sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp\n" ++ "VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q\n" ++ "A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+\n" ++ "ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB\n" ++ "BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD\n" ++ "ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px\n" ++ "KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI\n" ++ "FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv\n" ++ "oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg\n" ++ "u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP\n" ++ "0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf\n" ++ "3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl\n" ++ "8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+\n" ++ "DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN\n" ++ "PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/\n" ++ "ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC\n" ++ "VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T\n" ++ "U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp\n" ++ "Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx\n" ++ "NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv\n" ++ "dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv\n" ++ "bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49\n" ++ "AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA\n" ++ "VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku\n" ++ "WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP\n" ++ "MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX\n" ++ "5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ\n" ++ "ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg\n" ++ "h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV\n" ++ "BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE\n" ++ "CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy\n" ++ "dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy\n" ++ "MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G\n" ++ "A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD\n" ++ "DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy\n" ++ "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq\n" ++ "M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf\n" ++ "OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa\n" ++ "4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9\n" ++ "HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR\n" ++ "aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA\n" ++ "b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ\n" ++ "Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV\n" ++ "PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO\n" ++ "pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu\n" ++ "UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY\n" ++ "MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV\n" ++ "HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4\n" ++ "9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW\n" ++ "s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5\n" ++ "Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg\n" ++ "cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM\n" ++ "79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz\n" ++ "/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt\n" ++ "ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm\n" ++ "Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK\n" ++ "QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ\n" ++ "w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi\n" ++ "S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07\n" ++ "mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC\n" ++ "VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T\n" ++ "U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0\n" ++ "aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz\n" ++ "WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0\n" ++ "b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS\n" ++ "b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB\n" ++ "BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI\n" ++ "7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg\n" ++ "CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud\n" ++ "EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD\n" ++ "VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T\n" ++ "kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+\n" ++ "gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE\n" ++ "BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK\n" ++ "DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp\n" ++ "Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz\n" ++ "OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv\n" ++ "dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv\n" ++ "bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN\n" ++ "AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R\n" ++ "xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX\n" ++ "qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC\n" ++ "C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3\n" ++ "6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh\n" ++ "/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF\n" ++ "YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E\n" ++ "JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc\n" ++ "US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8\n" ++ "ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm\n" ++ "+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi\n" ++ "M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV\n" ++ "HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G\n" ++ "A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV\n" ++ "cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc\n" ++ "Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs\n" ++ "PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/\n" ++ "q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0\n" ++ "cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr\n" ++ "a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I\n" ++ "H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y\n" ++ "K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu\n" ++ "nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf\n" ++ "oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY\n" ++ "Ic2wBlX7Jz9TkHCpBB5XJ7k=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw\n" ++ "CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT\n" ++ "U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2\n" ++ "MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh\n" ++ "dGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3QgQ0EgMjAyMjB2MBAG\n" ++ "ByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWyJGYm\n" ++ "acCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFN\n" ++ "SeR7T5v15wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME\n" ++ "GDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW\n" ++ "uCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp\n" ++ "15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN\n" ++ "b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO\n" ++ "MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD\n" ++ "DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX\n" ++ "DTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw\n" ++ "b3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJvb3QgQ0EgMjAyMjCC\n" ++ "AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u9nTP\n" ++ "L3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OY\n" ++ "t6/wNr/y7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0ins\n" ++ "S657Lb85/bRi3pZ7QcacoOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3\n" ++ "PnxEX4MN8/HdIGkWCVDi1FW24IBydm5MR7d1VVm0U3TZlMZBrViKMWYPHqIbKUBO\n" ++ "L9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDGD6C1vBdOSHtRwvzpXGk3\n" ++ "R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEWTO6Af77w\n" ++ "dr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS\n" ++ "+YCk8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYS\n" ++ "d66UNHsef8JmAOSqg+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoG\n" ++ "AtUjHBPW6dvbxrB6y3snm/vg1UYk7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2f\n" ++ "gTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j\n" ++ "BBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsuN+7jhHonLs0Z\n" ++ "NbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt\n" ++ "hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsM\n" ++ "QtfhWsSWTVTNj8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvf\n" ++ "R4iyrT7gJ4eLSYwfqUdYe5byiB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJ\n" ++ "DPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjUo3KUQyxi4U5cMj29TH0ZR6LDSeeW\n" ++ "P4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqoENjwuSfr98t67wVy\n" ++ "lrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7EgkaibMOlq\n" ++ "bLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2w\n" ++ "AgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q\n" ++ "r5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji\n" ++ "Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU\n" ++ "98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL\n" ++ "BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6\n" ++ "ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw\n" ++ "NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L\n" ++ "cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg\n" ++ "Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN\n" ++ "QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT\n" ++ "3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw\n" ++ "3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6\n" ++ "3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5\n" ++ "BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN\n" ++ "XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD\n" ++ "AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF\n" ++ "AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw\n" ++ "8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG\n" ++ "nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP\n" ++ "oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy\n" ++ "d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg\n" ++ "LvWpCz/UXeHPhJ/iGcJfitYgHuNztw==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw\n" ++ "CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T\n" ++ "ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN\n" ++ "MjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYG\n" ++ "A1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT\n" ++ "ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA\n" ++ "IgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccC\n" ++ "WvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+\n" ++ "6xnOQ6OjQjBAMB0GA1UdDgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8B\n" ++ "Af8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa\n" ++ "qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q\n" ++ "4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf\n" ++ "MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD\n" ++ "Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw\n" ++ "HhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY\n" ++ "MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp\n" ++ "YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB\n" ++ "AQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa\n" ++ "ef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz\n" ++ "SDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf\n" ++ "iOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X\n" ++ "ME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3\n" ++ "IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS\n" ++ "VYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE\n" ++ "SJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu\n" ++ "+Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt\n" ++ "8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L\n" ++ "HaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt\n" ++ "zwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P\n" ++ "AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c\n" ++ "mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ\n" ++ "YKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52\n" ++ "gDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA\n" ++ "Fv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB\n" ++ "JYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX\n" ++ "DhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui\n" ++ "TdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5\n" ++ "dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65\n" ++ "LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp\n" ++ "0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY\n" ++ "QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr\n" ++ "MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG\n" ++ "A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0\n" ++ "MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp\n" ++ "Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD\n" ++ "QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz\n" ++ "i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8\n" ++ "h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV\n" ++ "MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9\n" ++ "UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni\n" ++ "8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC\n" ++ "h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD\n" ++ "VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB\n" ++ "AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm\n" ++ "KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ\n" ++ "X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr\n" ++ "QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5\n" ++ "pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN\n" ++ "QSdJQO7e5iNEOdyhIta6A/I=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI\n" ++ "MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x\n" ++ "FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz\n" ++ "MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv\n" ++ "cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN\n" ++ "AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz\n" ++ "Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO\n" ++ "0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao\n" ++ "wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj\n" ++ "7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS\n" ++ "8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT\n" ++ "BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB\n" ++ "/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg\n" ++ "JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC\n" ++ "NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3\n" ++ "6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/\n" ++ "3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm\n" ++ "D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS\n" ++ "CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR\n" ++ "3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK\n" ++ "MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x\n" ++ "GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx\n" ++ "MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg\n" ++ "Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG\n" ++ "SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ\n" ++ "iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa\n" ++ "/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ\n" ++ "jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI\n" ++ "HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7\n" ++ "sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w\n" ++ "gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF\n" ++ "MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw\n" ++ "KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG\n" ++ "AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L\n" ++ "URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO\n" ++ "H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm\n" ++ "I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY\n" ++ "iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc\n" ++ "f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT\n" ++ "AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD\n" ++ "VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx\n" ++ "NjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT\n" ++ "HFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5\n" ++ "IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi\n" ++ "AASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl\n" ++ "dB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK\n" ++ "ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E\n" ++ "BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu\n" ++ "9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O\n" ++ "be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl\n" ++ "MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe\n" ++ "U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX\n" ++ "DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy\n" ++ "dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj\n" ++ "YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV\n" ++ "OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr\n" ++ "zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM\n" ++ "VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ\n" ++ "hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO\n" ++ "ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw\n" ++ "awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs\n" ++ "OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3\n" ++ "DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF\n" ++ "coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc\n" ++ "okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8\n" ++ "t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy\n" ++ "1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/\n" ++ "SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFfzCCA2egAwIBAgIJAOF8N0D9G/5nMA0GCSqGSIb3DQEBDAUAMF0xCzAJBgNV\n" ++ "BAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScw\n" ++ "JQYDVQQDEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTMwHhcNMTYwNjE2\n" ++ "MDYxNzE2WhcNMzgwMTE4MDYxNzE2WjBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc\n" ++ "U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UEAxMeU2VjdXJpdHkg\n" ++ "Q29tbXVuaWNhdGlvbiBSb290Q0EzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC\n" ++ "CgKCAgEA48lySfcw3gl8qUCBWNO0Ot26YQ+TUG5pPDXC7ltzkBtnTCHsXzW7OT4r\n" ++ "CmDvu20rhvtxosis5FaU+cmvsXLUIKx00rgVrVH+hXShuRD+BYD5UpOzQD11EKzA\n" ++ "lrenfna84xtSGc4RHwsENPXY9Wk8d/Nk9A2qhd7gCVAEF5aEt8iKvE1y/By7z/MG\n" ++ "TfmfZPd+pmaGNXHIEYBMwXFAWB6+oHP2/D5Q4eAvJj1+XCO1eXDe+uDRpdYMQXF7\n" ++ "9+qMHIjH7Iv10S9VlkZ8WjtYO/u62C21Jdp6Ts9EriGmnpjKIG58u4iFW/vAEGK7\n" ++ "8vknR+/RiTlDxN/e4UG/VHMgly1s2vPUB6PmudhvrvyMGS7TZ2crldtYXLVqAvO4\n" ++ "g160a75BflcJdURQVc1aEWEhCmHCqYj9E7wtiS/NYeCVvsq1e+F7NGcLH7YMx3we\n" ++ "GVPKp7FKFSBWFHA9K4IsD50VHUeAR/94mQ4xr28+j+2GaR57GIgUssL8gjMunEst\n" ++ "+3A7caoreyYn8xrC3PsXuKHqy6C0rtOUfnrQq8PsOC0RLoi/1D+tEjtCrI8Cbn3M\n" ++ "0V9hvqG8OmpI6iZVIhZdXw3/JzOfGAN0iltSIEdrRU0id4xVJ/CvHozJgyJUt5rQ\n" ++ "T9nO/NkuHJYosQLTA70lUhw0Zk8jq/R3gpYd0VcwCBEF/VfR2ccCAwEAAaNCMEAw\n" ++ "HQYDVR0OBBYEFGQUfPxYchamCik0FW8qy7z8r6irMA4GA1UdDwEB/wQEAwIBBjAP\n" ++ "BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQDcAiMI4u8hOscNtybS\n" ++ "YpOnpSNyByCCYN8Y11StaSWSntkUz5m5UoHPrmyKO1o5yGwBQ8IibQLwYs1OY0PA\n" ++ "FNr0Y/Dq9HHuTofjcan0yVflLl8cebsjqodEV+m9NU1Bu0soo5iyG9kLFwfl9+qd\n" ++ "9XbXv8S2gVj/yP9kaWJ5rW4OH3/uHWnlt3Jxs/6lATWUVCvAUm2PVcTJ0rjLyjQI\n" ++ "UYWg9by0F1jqClx6vWPGOi//lkkZhOpn2ASxYfQAW0q3nHE3GYV5v4GwxxMOdnE+\n" ++ "OoAGrgYWp421wsTL/0ClXI2lyTrtcoHKXJg80jQDdwj98ClZXSEIx2C/pHF7uNke\n" ++ "gr4Jr2VvKKu/S7XuPghHJ6APbw+LP6yVGPO5DtxnVW5inkYO0QR4ynKudtml+LLf\n" ++ "iAlhi+8kTtFZP1rUPcmTPCtk9YENFpb3ksP+MW/oKjJ0DvRMmEoYDjBU1cXrvMUV\n" ++ "nuiZIesnKwkK2/HmcBhWuwzkvvnoEKQTkrgc4NtnHVMDpCKn3F2SEDzq//wbEBrD\n" ++ "2NCcnWXL0CsnMQMeNuE9dnUM/0Umud1RvCPHX9jYhxBAEg09ODfnRDwYwFMJZI//\n" ++ "1ZqmfHAuc1Uh6N//g7kdPjIe1qZ9LPFm6Vwdp6POXiUyK+OVrCoHzrQoeIY8Laad\n" ++ "TdJ0MN1kURXbg4NR16/9M51NZg==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl\n" ++ "MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp\n" ++ "U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw\n" ++ "NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE\n" ++ "ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp\n" ++ "ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3\n" ++ "DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf\n" ++ "8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN\n" ++ "+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0\n" ++ "X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa\n" ++ "K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA\n" ++ "1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G\n" ++ "A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR\n" ++ "zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0\n" ++ "YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD\n" ++ "bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w\n" ++ "DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3\n" ++ "L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D\n" ++ "eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl\n" ++ "xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp\n" ++ "VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY\n" ++ "WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx\n" ++ "EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT\n" ++ "HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs\n" ++ "ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw\n" ++ "MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6\n" ++ "b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj\n" ++ "aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp\n" ++ "Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n" ++ "ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg\n" ++ "nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1\n" ++ "HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N\n" ++ "Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN\n" ++ "dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0\n" ++ "HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO\n" ++ "BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G\n" ++ "CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU\n" ++ "sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3\n" ++ "4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg\n" ++ "8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K\n" ++ "pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1\n" ++ "mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx\n" ++ "EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT\n" ++ "HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs\n" ++ "ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5\n" ++ "MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD\n" ++ "VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy\n" ++ "ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy\n" ++ "dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI\n" ++ "hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p\n" ++ "OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2\n" ++ "8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K\n" ++ "Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe\n" ++ "hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk\n" ++ "6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw\n" ++ "DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q\n" ++ "AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI\n" ++ "bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB\n" ++ "ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z\n" ++ "qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd\n" ++ "iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn\n" ++ "0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN\n" ++ "sSi6\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV\n" ++ "BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln\n" ++ "biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF\n" ++ "MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT\n" ++ "d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC\n" ++ "CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8\n" ++ "76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+\n" ++ "bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c\n" ++ "6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE\n" ++ "emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd\n" ++ "MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt\n" ++ "MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y\n" ++ "MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y\n" ++ "FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi\n" ++ "aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM\n" ++ "gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB\n" ++ "qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7\n" ++ "lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn\n" ++ "8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov\n" ++ "L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6\n" ++ "45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO\n" ++ "UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5\n" ++ "O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC\n" ++ "bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv\n" ++ "GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a\n" ++ "77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC\n" ++ "hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3\n" ++ "92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp\n" ++ "Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w\n" ++ "ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt\n" ++ "Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE\n" ++ "BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu\n" ++ "IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow\n" ++ "RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY\n" ++ "U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A\n" ++ "MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv\n" ++ "Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br\n" ++ "YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF\n" ++ "nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH\n" ++ "6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt\n" ++ "eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/\n" ++ "c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ\n" ++ "MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH\n" ++ "HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf\n" ++ "jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6\n" ++ "5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB\n" ++ "rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU\n" ++ "F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c\n" ++ "wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0\n" ++ "cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB\n" ++ "AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp\n" ++ "WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9\n" ++ "xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ\n" ++ "2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ\n" ++ "IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8\n" ++ "aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X\n" ++ "em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR\n" ++ "dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/\n" ++ "OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+\n" ++ "hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy\n" ++ "tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx\n" ++ "KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd\n" ++ "BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl\n" ++ "YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1\n" ++ "OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy\n" ++ "aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50\n" ++ "ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G\n" ++ "CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd\n" ++ "AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC\n" ++ "FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi\n" ++ "1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq\n" ++ "jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ\n" ++ "wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj\n" ++ "QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/\n" ++ "WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy\n" ++ "NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC\n" ++ "uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw\n" ++ "IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6\n" ++ "g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN\n" ++ "9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP\n" ++ "BSeOE6Fuwg==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx\n" ++ "KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd\n" ++ "BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl\n" ++ "YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1\n" ++ "OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy\n" ++ "aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50\n" ++ "ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G\n" ++ "CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN\n" ++ "8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/\n" ++ "RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4\n" ++ "hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5\n" ++ "ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM\n" ++ "EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj\n" ++ "QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1\n" ++ "A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy\n" ++ "WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ\n" ++ "1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30\n" ++ "6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT\n" ++ "91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml\n" ++ "e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p\n" ++ "TpPDpFQUWw==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx\n" ++ "GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp\n" ++ "bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w\n" ++ "KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0\n" ++ "BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy\n" ++ "dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG\n" ++ "EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll\n" ++ "IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU\n" ++ "QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT\n" ++ "TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg\n" ++ "LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7\n" ++ "a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr\n" ++ "LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr\n" ++ "N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X\n" ++ "YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/\n" ++ "iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f\n" ++ "AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH\n" ++ "V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL\n" ++ "BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh\n" ++ "AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf\n" ++ "IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4\n" ++ "lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c\n" ++ "8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf\n" ++ "lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx\n" ++ "EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT\n" ++ "VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5\n" ++ "NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT\n" ++ "B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG\n" ++ "SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF\n" ++ "10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz\n" ++ "0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh\n" ++ "MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH\n" ++ "zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc\n" ++ "46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2\n" ++ "yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi\n" ++ "laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP\n" ++ "oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA\n" ++ "BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE\n" ++ "qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm\n" ++ "4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB\n" ++ "/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL\n" ++ "1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn\n" ++ "LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF\n" ++ "H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo\n" ++ "RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+\n" ++ "nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh\n" ++ "15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW\n" ++ "6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW\n" ++ "nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j\n" ++ "wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz\n" ++ "aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy\n" ++ "KwbQBM0=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES\n" ++ "MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU\n" ++ "V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz\n" ++ "WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO\n" ++ "LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm\n" ++ "aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB\n" ++ "AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE\n" ++ "AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH\n" ++ "K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX\n" ++ "RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z\n" ++ "rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx\n" ++ "3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" ++ "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq\n" ++ "hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC\n" ++ "MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls\n" ++ "XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D\n" ++ "lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn\n" ++ "aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ\n" ++ "YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw\n" ++ "CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH\n" ++ "bWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw\n" ++ "MB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIzNTk1OVowYzELMAkGA1UEBhMCREUx\n" ++ "JzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkGA1UE\n" ++ "AwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqGSM49\n" ++ "AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/O\n" ++ "tdKPD/M12kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDP\n" ++ "f8iAC8GXs7s1J8nCG6NCMEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6f\n" ++ "MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA\n" ++ "MGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di\n" ++ "z6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn\n" ++ "27iQ7t0l\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj\n" ++ "MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0\n" ++ "eSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy\n" ++ "MDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMyNzIzNTk1OVowYzELMAkGA1UEBhMC\n" ++ "REUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkG\n" ++ "A1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIwDQYJ\n" ++ "KoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9\n" ++ "cUD/h3VCKSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHV\n" ++ "cp6R+SPWcHu79ZvB7JPPGeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMA\n" ++ "U6DksquDOFczJZSfvkgdmOGjup5czQRxUX11eKvzWarE4GC+j4NSuHUaQTXtvPM6\n" ++ "Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWol8hHD/BeEIvnHRz+sTug\n" ++ "BTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9FIS3R/qy\n" ++ "8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73J\n" ++ "co4vzLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg\n" ++ "8qKrBC7m8kwOFjQgrIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8\n" ++ "rFEz0ciD0cmfHdRHNCk+y7AO+oMLKFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12\n" ++ "mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7SWWO/gLCMk3PLNaaZlSJhZQNg\n" ++ "+y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtqeX\n" ++ "gj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2\n" ++ "p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQ\n" ++ "pGv7qHBFfLp+sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm\n" ++ "9S3ul0A8Yute1hTWjOKWi0FpkzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErw\n" ++ "M807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy/SKE8YXJN3nptT+/XOR0so8RYgDd\n" ++ "GGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4mZqTuXNnQkYRIer+\n" ++ "CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtzaL1t\n" ++ "xKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+\n" ++ "w6jv/naaoqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aK\n" ++ "L4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj\n" ++ "X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q\n" ++ "ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm\n" ++ "dTdmQRCsu/WU48IxK63nI1bMNSWSs1A=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw\n" ++ "NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv\n" ++ "b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD\n" ++ "VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2\n" ++ "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F\n" ++ "VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1\n" ++ "7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X\n" ++ "Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+\n" ++ "/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs\n" ++ "81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm\n" ++ "dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe\n" ++ "Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu\n" ++ "sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4\n" ++ "pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs\n" ++ "slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ\n" ++ "arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD\n" ++ "VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG\n" ++ "9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl\n" ++ "dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx\n" ++ "0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj\n" ++ "TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed\n" ++ "Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7\n" ++ "Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI\n" ++ "OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7\n" ++ "vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW\n" ++ "t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn\n" ++ "HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx\n" ++ "SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx\n" ++ "CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE\n" ++ "AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1\n" ++ "NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ\n" ++ "MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP\n" ++ "ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq\n" ++ "AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9\n" ++ "vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9\n" ++ "lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD\n" ++ "n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT\n" ++ "7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o\n" ++ "6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC\n" ++ "TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6\n" ++ "WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R\n" ++ "DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI\n" ++ "pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj\n" ++ "YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy\n" ++ "rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw\n" ++ "AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ\n" ++ "8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi\n" ++ "0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM\n" ++ "A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS\n" ++ "SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K\n" ++ "TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF\n" ++ "6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er\n" ++ "3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt\n" ++ "Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT\n" ++ "VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW\n" ++ "ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA\n" ++ "rBPuUBQemMc=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEM\n" ++ "BQAwWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dp\n" ++ "ZXMsIEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAe\n" ++ "Fw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEwMTlaMFoxCzAJBgNVBAYTAkNOMSUw\n" ++ "IwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtU\n" ++ "cnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUAA4IC\n" ++ "DwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNS\n" ++ "T1QY4SxzlZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqK\n" ++ "AtCWHwDNBSHvBm3dIZwZQ0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1\n" ++ "nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/VP68czH5GX6zfZBCK70bwkPAPLfSIC7Ep\n" ++ "qq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1AgdB4SQXMeJNnKziyhWTXA\n" ++ "yB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm9WAPzJMs\n" ++ "hH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gX\n" ++ "zhqcD0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAv\n" ++ "kV34PmVACxmZySYgWmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msT\n" ++ "f9FkPz2ccEblooV7WIQn3MSAPmeamseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jA\n" ++ "uPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCFTIcQcf+eQxuulXUtgQIDAQAB\n" ++ "o2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj7zjKsK5Xf/Ih\n" ++ "MBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E\n" ++ "BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4\n" ++ "wM8zAQLpw6o1D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2\n" ++ "XFNFV1pF1AWZLy4jVe5jaN/TG3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1\n" ++ "JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNjduMNhXJEIlU/HHzp/LgV6FL6qj6j\n" ++ "ITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstlcHboCoWASzY9M/eV\n" ++ "VHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys+TIx\n" ++ "xHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1on\n" ++ "AX1daBli2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d\n" ++ "7XB4tmBZrOFdRWOPyN9yaFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2Ntjj\n" ++ "gKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsASZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV\n" ++ "+Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFRJQJ6+N1rZdVtTTDIZbpo\n" ++ "FGWsJwt0ivKH\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMw\n" ++ "WjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs\n" ++ "IEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0y\n" ++ "MTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJaMFoxCzAJBgNVBAYTAkNOMSUwIwYD\n" ++ "VQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtUcnVz\n" ++ "dEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATx\n" ++ "s8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbw\n" ++ "LxYI+hW8m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJij\n" ++ "YzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mD\n" ++ "pm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/pDHel4NZg6ZvccveMA4GA1UdDwEB/wQE\n" ++ "AwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AAbbd+NvBNEU/zy4k6LHiR\n" ++ "UKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xkdUfFVZDj\n" ++ "/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw\n" ++ "CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x\n" ++ "ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1\n" ++ "c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx\n" ++ "OTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI\n" ++ "SWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI\n" ++ "b2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp\n" ++ "Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB\n" ++ "ALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn\n" ++ "swuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu\n" ++ "7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8\n" ++ "1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW\n" ++ "80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP\n" ++ "JqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l\n" ++ "RtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw\n" ++ "hI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10\n" ++ "coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc\n" ++ "BW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n\n" ++ "twiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud\n" ++ "EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud\n" ++ "DwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W\n" ++ "0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe\n" ++ "uyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q\n" ++ "lG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB\n" ++ "aCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE\n" ++ "sLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT\n" ++ "MaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe\n" ++ "qu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh\n" ++ "VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8\n" ++ "h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9\n" ++ "EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK\n" ++ "yeC2nOnOcXHebD8WpHk=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD\n" ++ "VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf\n" ++ "BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3\n" ++ "YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x\n" ++ "NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G\n" ++ "A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0\n" ++ "d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF\n" ++ "Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG\n" ++ "SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN\n" ++ "FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w\n" ++ "DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw\n" ++ "CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh\n" ++ "DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD\n" ++ "VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf\n" ++ "BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3\n" ++ "YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x\n" ++ "NzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G\n" ++ "A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0\n" ++ "d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF\n" ++ "Q0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB\n" ++ "BAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ\n" ++ "j9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF\n" ++ "1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G\n" ++ "A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3\n" ++ "AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC\n" ++ "MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu\n" ++ "Sw==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL\n" ++ "BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg\n" ++ "Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv\n" ++ "b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG\n" ++ "EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u\n" ++ "IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ\n" ++ "KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ\n" ++ "n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd\n" ++ "2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF\n" ++ "VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ\n" ++ "GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF\n" ++ "li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU\n" ++ "r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2\n" ++ "eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb\n" ++ "MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg\n" ++ "jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB\n" ++ "7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW\n" ++ "5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE\n" ++ "ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0\n" ++ "90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z\n" ++ "xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu\n" ++ "QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4\n" ++ "FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH\n" ++ "22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP\n" ++ "xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn\n" ++ "dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5\n" ++ "Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b\n" ++ "nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ\n" ++ "CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH\n" ++ "u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj\n" ++ "d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH\n" ++ "MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF\n" ++ "eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx\n" ++ "MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV\n" ++ "BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB\n" ++ "AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog\n" ++ "D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS\n" ++ "sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop\n" ++ "O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk\n" ++ "sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi\n" ++ "c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj\n" ++ "VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz\n" ++ "KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/\n" ++ "TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G\n" ++ "sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs\n" ++ "1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD\n" ++ "fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T\n" ++ "AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN\n" ++ "l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR\n" ++ "ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ\n" ++ "VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5\n" ++ "c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp\n" ++ "4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s\n" ++ "t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj\n" ++ "2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO\n" ++ "vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C\n" ++ "xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx\n" ++ "cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM\n" ++ "fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9\n" ++ "MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH\n" ++ "bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x\n" ++ "CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds\n" ++ "b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr\n" ++ "b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9\n" ++ "kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm\n" ++ "VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R\n" ++ "VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc\n" ++ "C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj\n" ++ "tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY\n" ++ "D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv\n" ++ "j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl\n" ++ "NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6\n" ++ "iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP\n" ++ "O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/\n" ++ "BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV\n" ++ "ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj\n" ++ "L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5\n" ++ "1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl\n" ++ "1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU\n" ++ "b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV\n" ++ "PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj\n" ++ "y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb\n" ++ "EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg\n" ++ "DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI\n" ++ "+Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy\n" ++ "YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX\n" ++ "UB+K+wb1whnw0A==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL\n" ++ "MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl\n" ++ "eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT\n" ++ "JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx\n" ++ "MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT\n" ++ "Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg\n" ++ "VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm\n" ++ "aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo\n" ++ "I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng\n" ++ "o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G\n" ++ "A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD\n" ++ "VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB\n" ++ "zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW\n" ++ "RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB\n" ++ "iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl\n" ++ "cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV\n" ++ "BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw\n" ++ "MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV\n" ++ "BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU\n" ++ "aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy\n" ++ "dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK\n" ++ "AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B\n" ++ "3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY\n" ++ "tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/\n" ++ "Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2\n" ++ "VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT\n" ++ "79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6\n" ++ "c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT\n" ++ "Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l\n" ++ "c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee\n" ++ "UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE\n" ++ "Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd\n" ++ "BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G\n" ++ "A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF\n" ++ "Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO\n" ++ "VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3\n" ++ "ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs\n" ++ "8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR\n" ++ "iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze\n" ++ "Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ\n" ++ "XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/\n" ++ "qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB\n" ++ "VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB\n" ++ "L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG\n" ++ "jjxDah2nGN59PRbxYvnKkKj9\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB\n" ++ "gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk\n" ++ "MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY\n" ++ "UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx\n" ++ "NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3\n" ++ "dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy\n" ++ "dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB\n" ++ "dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6\n" ++ "38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP\n" ++ "KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q\n" ++ "DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4\n" ++ "qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa\n" ++ "JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi\n" ++ "PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P\n" ++ "BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs\n" ++ "jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0\n" ++ "eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD\n" ++ "ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR\n" ++ "vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt\n" ++ "qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa\n" ++ "IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy\n" ++ "i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ\n" ++ "O+7ETPTsJ3xCwnR8gooJybQDJbw=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT\n" ++ "AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD\n" ++ "QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP\n" ++ "MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC\n" ++ "ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do\n" ++ "0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ\n" ++ "UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d\n" ++ "RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ\n" ++ "OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv\n" ++ "JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C\n" ++ "AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O\n" ++ "BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ\n" ++ "LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY\n" ++ "MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ\n" ++ "44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I\n" ++ "Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw\n" ++ "i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN\n" ++ "9u6wWk5JRFRYX0KD\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV\n" ++ "BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g\n" ++ "Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ\n" ++ "BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ\n" ++ "R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF\n" ++ "dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw\n" ++ "vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ\n" ++ "uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp\n" ++ "n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs\n" ++ "cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW\n" ++ "xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P\n" ++ "rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF\n" ++ "DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx\n" ++ "DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy\n" ++ "LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C\n" ++ "eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB\n" ++ "/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ\n" ++ "d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq\n" ++ "kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC\n" ++ "b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl\n" ++ "qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0\n" ++ "OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c\n" ++ "NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk\n" ++ "ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO\n" ++ "pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj\n" ++ "03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk\n" ++ "PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE\n" ++ "1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX\n" ++ "QRBdJ3NghVdJIgc=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV\n" ++ "BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk\n" ++ "LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv\n" ++ "b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ\n" ++ "BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg\n" ++ "THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v\n" ++ "IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv\n" ++ "xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H\n" ++ "Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G\n" ++ "A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB\n" ++ "eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo\n" ++ "jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ\n" ++ "+efcMQ==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe\n" ++ "MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0\n" ++ "ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe\n" ++ "Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw\n" ++ "IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL\n" ++ "SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF\n" ++ "AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH\n" ++ "SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh\n" ++ "ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X\n" ++ "DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1\n" ++ "TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ\n" ++ "fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA\n" ++ "sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU\n" ++ "WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS\n" ++ "nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH\n" ++ "dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip\n" ++ "NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC\n" ++ "AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF\n" ++ "MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH\n" ++ "ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB\n" ++ "uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl\n" ++ "PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP\n" ++ "JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/\n" ++ "gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2\n" ++ "j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6\n" ++ "5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB\n" ++ "o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS\n" ++ "/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z\n" ++ "Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE\n" ++ "W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D\n" ++ "hNQ+IIX3Sj0rnP0qCglN6oH4EZw=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG\n" ++ "EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx\n" ++ "IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw\n" ++ "MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln\n" ++ "biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND\n" ++ "IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci\n" ++ "MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti\n" ++ "sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O\n" ++ "BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB\n" ++ "Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c\n" ++ "3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J\n" ++ "0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ==\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG\n" ++ "EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo\n" ++ "bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g\n" ++ "RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ\n" ++ "TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s\n" ++ "b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw\n" ++ "djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0\n" ++ "WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS\n" ++ "fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB\n" ++ "zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq\n" ++ "hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB\n" ++ "CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD\n" ++ "+JbNR6iC8hZVdyR+EhCVBCyj\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG\n" ++ "A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg\n" ++ "SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw\n" ++ "MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln\n" ++ "biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v\n" ++ "dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ\n" ++ "BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ\n" ++ "HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH\n" ++ "3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH\n" ++ "GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c\n" ++ "xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1\n" ++ "aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq\n" ++ "TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL\n" ++ "BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87\n" ++ "/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4\n" ++ "kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG\n" ++ "YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT\n" ++ "+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo\n" ++ "WXzhriKi4gp6D/piq1JM4fHfyr6DDUI=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD\n" ++ "VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU\n" ++ "ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH\n" ++ "MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO\n" ++ "MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv\n" ++ "Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN\n" ++ "BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz\n" ++ "f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO\n" ++ "8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq\n" ++ "d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM\n" ++ "tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt\n" ++ "Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB\n" ++ "o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD\n" ++ "AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x\n" ++ "PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM\n" ++ "wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d\n" ++ "GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH\n" ++ "6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby\n" ++ "RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx\n" ++ "iN66zB+Afko=\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMw\n" ++ "RzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAY\n" ++ "BgNVBAMTEXZUcnVzIEVDQyBSb290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDcz\n" ++ "MTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28u\n" ++ "LEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYwEAYHKoZIzj0CAQYF\n" ++ "K4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+cToL0\n" ++ "v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUd\n" ++ "e4BdS49nTPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYD\n" ++ "VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIw\n" ++ "V53dVvHH4+m4SVBrm2nDb+zDfSXkV5UTQJtS0zvzQBm8JsctBp61ezaf9SXUY2sA\n" ++ "AjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQLYgmRWAD5Tfs0aNoJrSEG\n" ++ "GJTO\n" ++ "-----END CERTIFICATE-----\n" ++ "-----BEGIN CERTIFICATE-----\n" ++ "MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQEL\n" ++ "BQAwQzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4x\n" ++ "FjAUBgNVBAMTDXZUcnVzIFJvb3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMx\n" ++ "MDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoGA1UEChMTaVRydXNDaGluYSBDby4s\n" ++ "THRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD\n" ++ "ggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZotsSKYc\n" ++ "IrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykU\n" ++ "AyyNJJrIZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+\n" ++ "GrPSbcKvdmaVayqwlHeFXgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z9\n" ++ "8Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KAYPxMvDVTAWqXcoKv8R1w6Jz1717CbMdH\n" ++ "flqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70kLJrxLT5ZOrpGgrIDajt\n" ++ "J8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2AXPKBlim\n" ++ "0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZN\n" ++ "pGvu/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQ\n" ++ "UqqzApVg+QxMaPnu1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHW\n" ++ "OXSuTEGC2/KmSNGzm/MzqvOmwMVO9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMB\n" ++ "AAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYgscasGrz2iTAPBgNVHRMBAf8E\n" ++ "BTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAKbqSSaet\n" ++ "8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd\n" ++ "nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1j\n" ++ "bhd47F18iMjrjld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvM\n" ++ "Kar5CKXiNxTKsbhm7xqC5PD48acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIiv\n" ++ "TDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJnxDHO2zTlJQNgJXtxmOTAGytfdELS\n" ++ "S8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554WgicEFOwE30z9J4nfr\n" ++ "I8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4sEb9\n" ++ "b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNB\n" ++ "UvupLnKWnyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1P\n" ++ "Ti07NEPhmg4NpGaXutIcSkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929ven\n" ++ "sBxXVsFy6K2ir40zSbofitzmdHxghm+Hl3s=\n" ++ "-----END CERTIFICATE-----\n" ++}; ++ ++#endif // CERT_H_ +-- +2.54.0 + diff --git a/.patch/reshade/0001-from-chars-fallback.patch b/.patch/reshade/0001-from-chars-fallback.patch new file mode 100644 index 0000000000..9089b1b9d4 --- /dev/null +++ b/.patch/reshade/0001-from-chars-fallback.patch @@ -0,0 +1,11 @@ +--- a/source/effect_lexer.cpp ++++ b/source/effect_lexer.cpp +@@ -1181,7 +1181,7 @@ + } + else + { +-#if 0 ++#if !defined(__cpp_lib_to_chars) + exponent += decimal_location - mantissa_size; + + const bool exponent_negative = exponent < 0; diff --git a/.patch/unordered-dense/0001-avoid-memset-when-clearing-an-empty-table.patch b/.patch/unordered-dense/0001-avoid-memset-when-clearing-an-empty-table.patch deleted file mode 100644 index f7d7c7ebe2..0000000000 --- a/.patch/unordered-dense/0001-avoid-memset-when-clearing-an-empty-table.patch +++ /dev/null @@ -1,26 +0,0 @@ -From b3622608433c183ba868a1dc8dd9cf285eb3b916 Mon Sep 17 00:00:00 2001 -From: Dario Petrillo -Date: Thu, 27 Nov 2025 23:12:38 +0100 -Subject: [PATCH] avoid extra memset when clearing an empty table - ---- - include/ankerl/unordered_dense.h | 6 ++++-- - 1 file changed, 4 insertions(+), 2 deletions(-) - -diff --git a/include/ankerl/unordered_dense.h b/include/ankerl/unordered_dense.h -index 0835342..4938212 100644 ---- a/include/ankerl/unordered_dense.h -+++ b/include/ankerl/unordered_dense.h -@@ -1490,8 +1490,10 @@ class table : public std::conditional_t, base_table_type_map, bas - // modifiers ////////////////////////////////////////////////////////////// - - void clear() { -- m_values.clear(); -- clear_buckets(); -+ if (!empty()) { -+ m_values.clear(); -+ clear_buckets(); -+ } - } - - auto insert(value_type const& value) -> std::pair { diff --git a/.patch/vulkan-memory-allocator/0001-macos-clang.patch b/.patch/vulkan-memory-allocator/0001-macos-clang.patch new file mode 100644 index 0000000000..e965292f6f --- /dev/null +++ b/.patch/vulkan-memory-allocator/0001-macos-clang.patch @@ -0,0 +1,112 @@ +diff --git a/include/vk_mem_alloc.h b/include/vk_mem_alloc.h +index 8df0364..4856064 100644 +--- a/include/vk_mem_alloc.h ++++ b/include/vk_mem_alloc.h +@@ -3017,7 +3017,7 @@ remove them if not needed. + + #if defined(__ANDROID_API__) && (__ANDROID_API__ < 16) + #include +-static void* vma_aligned_alloc(size_t alignment, size_t size) ++static inline void* vma_aligned_alloc(size_t alignment, size_t size) + { + // alignment must be >= sizeof(void*) + if(alignment < sizeof(void*)) +@@ -3860,7 +3860,7 @@ Returned value is the found element, if present in the collection or place where + new element with value (key) should be inserted. + */ + template +-static IterT VmaBinaryFindFirstNotLess(IterT beg, IterT end, const KeyT& key, const CmpLess& cmp) ++static inline IterT VmaBinaryFindFirstNotLess(IterT beg, IterT end, const KeyT& key, const CmpLess& cmp) + { + size_t down = 0; + size_t up = size_t(end - beg); +@@ -3898,7 +3898,7 @@ Warning! O(n^2) complexity. Use only inside VMA_HEAVY_ASSERT. + T must be pointer type, e.g. VmaAllocation, VmaPool. + */ + template +-static bool VmaValidatePointerArray(uint32_t count, const T* arr) ++static inline bool VmaValidatePointerArray(uint32_t count, const T* arr) + { + for (uint32_t i = 0; i < count; ++i) + { +@@ -4188,13 +4188,13 @@ static void VmaFree(const VkAllocationCallbacks* pAllocationCallbacks, void* ptr + } + + template +-static T* VmaAllocate(const VkAllocationCallbacks* pAllocationCallbacks) ++static inline T* VmaAllocate(const VkAllocationCallbacks* pAllocationCallbacks) + { + return (T*)VmaMalloc(pAllocationCallbacks, sizeof(T), VMA_ALIGN_OF(T)); + } + + template +-static T* VmaAllocateArray(const VkAllocationCallbacks* pAllocationCallbacks, size_t count) ++static inline T* VmaAllocateArray(const VkAllocationCallbacks* pAllocationCallbacks, size_t count) + { + return (T*)VmaMalloc(pAllocationCallbacks, sizeof(T) * count, VMA_ALIGN_OF(T)); + } +@@ -4204,14 +4204,14 @@ static T* VmaAllocateArray(const VkAllocationCallbacks* pAllocationCallbacks, si + #define vma_new_array(allocator, type, count) new(VmaAllocateArray((allocator), (count)))(type) + + template +-static void vma_delete(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr) ++static inline void vma_delete(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr) + { + ptr->~T(); + VmaFree(pAllocationCallbacks, ptr); + } + + template +-static void vma_delete_array(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr, size_t count) ++static inline void vma_delete_array(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr, size_t count) + { + if (ptr != VMA_NULL) + { +@@ -4658,13 +4658,13 @@ void VmaVector::remove(size_t index) + #endif // _VMA_VECTOR_FUNCTIONS + + template +-static void VmaVectorInsert(VmaVector& vec, size_t index, const T& item) ++static inline void VmaVectorInsert(VmaVector& vec, size_t index, const T& item) + { + vec.insert(index, item); + } + + template +-static void VmaVectorRemove(VmaVector& vec, size_t index) ++static inline void VmaVectorRemove(VmaVector& vec, size_t index) + { + vec.remove(index); + } +@@ -10620,19 +10620,19 @@ static void VmaFree(VmaAllocator hAllocator, void* ptr) + } + + template +-static T* VmaAllocate(VmaAllocator hAllocator) ++static inline T* VmaAllocate(VmaAllocator hAllocator) + { + return (T*)VmaMalloc(hAllocator, sizeof(T), VMA_ALIGN_OF(T)); + } + + template +-static T* VmaAllocateArray(VmaAllocator hAllocator, size_t count) ++static inline T* VmaAllocateArray(VmaAllocator hAllocator, size_t count) + { + return (T*)VmaMalloc(hAllocator, sizeof(T) * count, VMA_ALIGN_OF(T)); + } + + template +-static void vma_delete(VmaAllocator hAllocator, T* ptr) ++static inline void vma_delete(VmaAllocator hAllocator, T* ptr) + { + if(ptr != VMA_NULL) + { +@@ -10642,7 +10642,7 @@ static void vma_delete(VmaAllocator hAllocator, T* ptr) + } + + template +-static void vma_delete_array(VmaAllocator hAllocator, T* ptr, size_t count) ++static inline void vma_delete_array(VmaAllocator hAllocator, T* ptr, size_t count) + { + if(ptr != VMA_NULL) + { diff --git a/.reuse/dep5 b/.reuse/dep5 index 081c21e583..5d6fd4379e 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -112,11 +112,6 @@ Files: src/yuzu/*.ui Copyright: 2018-2022 yuzu Emulator Project License: GPL-2.0-or-later -Files: src/yuzu/compatdb.ui - src/yuzu/main.ui -Copyright: 2014-2017 Citra Emulator Project -License: GPL-2.0-or-later - Files: src/yuzu/loading_screen.ui Copyright: 2019 James Rowe License: GPL-2.0-or-later diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..05ab7f1160 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,7 @@ +# AGENTS + +Read and follow the AI policy found in `docs/policies/AI.md`, and the coding policies found in `docs/policies/Coding.md`. + +Do not, under any circumstances, ever submit PRs or push commits on behalf of the user. + +Keep comments to a reasonable level. Do not overexplain or add/remove comments from unrelated parts of the codebase. diff --git a/CMakeLists.txt b/CMakeLists.txt index 32e873f009..f647204a4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,15 +1,18 @@ # SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later -cmake_minimum_required(VERSION 3.22) +cmake_minimum_required(VERSION 3.31) +set(CMAKE_OSX_DEPLOYMENT_TARGET "15.0" CACHE STRING "macOS deployment target") project(yuzu) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules/find") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules") set(CPM_SOURCE_CACHE ${CMAKE_SOURCE_DIR}/.cache/cpm) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_CXX_SCAN_FOR_MODULES 0) include(DetectPlatform) include(DetectArchitecture) @@ -26,23 +29,23 @@ if (NOT DEFINED ARCHITECTURE) endif() # Needed for FFmpeg w/ VAAPI and DRM -if (PLATFORM_OPENBSD) +if (OPENBSD) # OpenBSD 7.8 broke libcxx when upgrading, so we must define the PSTL backend manually set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R6/include -D_LIBCPP_PSTL_BACKEND_SERIAL=1") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R6/include -D_LIBCPP_PSTL_BACKEND_SERIAL=1") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/X11R6/lib") -elseif (PLATFORM_NETBSD) +elseif (NETBSD) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R7/include -I${CMAKE_SYSROOT}/usr/pkg/include/c++/v1") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R7/include -I${CMAKE_SYSROOT}/usr/pkg/include/c++/v1") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/X11R7/lib") endif() # NetBSD: Fun for the whole family! -if (PLATFORM_NETBSD) +if (NETBSD) set(ENV{PKG_CONFIG_PATH} "${PKG_CONFIG_PATH}:${CMAKE_SYSROOT}/usr/pkg/lib/ffmpeg7/pkgconfig") endif() -cmake_dependent_option(YUZU_STATIC_ROOM "Build a static room executable only (CI only)" OFF "PLATFORM_LINUX" OFF) +cmake_dependent_option(YUZU_STATIC_ROOM "Build a static room executable only (CI only)" OFF "LINUX" OFF) if (YUZU_STATIC_ROOM) set(YUZU_ROOM ON) set(YUZU_ROOM_STANDALONE ON) @@ -61,6 +64,7 @@ if (YUZU_STATIC_ROOM) set(Boost_USE_STATIC_LIBS ON) set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") set(OPENSSL_USE_STATIC_LIBS ON) + set(OpenSSL_FORCE_SYSTEM ON) set(zstd_FORCE_BUNDLED ON) set(fmt_FORCE_BUNDLED ON) @@ -75,6 +79,14 @@ cmake_dependent_option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet im set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundled Qt libraries") cmake_dependent_option(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSVC}" "ENABLE_QT" OFF) +option(ENABLE_DEBUG_TOOLS "Enable debugging tools (maxwell disassembler, SPIRV translator, etc)" OFF) +option(ENABLE_WERROR "Enable -Werror diagnostics" ON) + +# Lossless Scaling frame generation. Only Android. +cmake_dependent_option(ENABLE_LSFG "Enable Lossless Scaling frame generation" ON "ANDROID" OFF) + +option(ENABLE_RESHADE "Enable ReShade FX post-processing effects" ON) + # non-linux bundled qt are static if (YUZU_USE_BUNDLED_QT AND (APPLE OR NOT UNIX)) set(YUZU_STATIC_BUILD ON) @@ -119,13 +131,13 @@ if (YUZU_STATIC_BUILD) set(QuaZip-Qt6_FORCE_BUNDLED ON) set(YUZU_USE_BUNDLED_FFMPEG ON) - set(YUZU_USE_BUNDLED_SDL2 ON) + set(YUZU_USE_BUNDLED_SDL3 ON) set(YUZU_USE_BUNDLED_OPENSSL ON) set(HTTPLIB_USE_BROTLI_IF_AVAILABLE OFF) elseif(APPLE) set(YUZU_USE_BUNDLED_FFMPEG ON) - set(YUZU_USE_BUNDLED_SDL2 ON) + set(YUZU_USE_BUNDLED_SDL3 ON) set(YUZU_USE_BUNDLED_OPENSSL ON) # these libs do not properly provide static libs/let you do it with cmake @@ -152,6 +164,17 @@ if (CXX_CLANG_CL) $<$:-Wno-reserved-identifier> $<$:-Wno-deprecated-declarations> $<$:-Wno-cast-function-type-mismatch> + $<$:-Wno-c99-extensions> + $<$:-Wno-c++17-compat> + $<$:-Wno-c++11-compat-reserved-user-defined-literal> + $<$:-Wno-c++11-compat-deprecated-writable-strings> + $<$:-Wno-c++11-compat-pedantic> + $<$:-Wno-c++11-compat> + $<$:-Wno-c++0x-compat> + $<$:-Wno-c++98-c++11-compat-binary-literal> + $<$:-Wno-c++98-compat-pedantic> + $<$:-Wno-c++98-compat> + $<$:-Wno-c99-compat> $<$:/EHsc>) # REQUIRED CPU features IN Windows-amd64 if (ARCHITECTURE_x86_64) @@ -190,14 +213,10 @@ if(MSVC) $<$:$<$:/${libflag}d>> $<$:$<$:/${libflag}>> $<$:$<$:/${libflag}>> - $<$:$<$:/${libflag}>> - ) + $<$:$<$:/${libflag}>>) endif() # TODO(crueter): Cleanup, each dep that has a bundled option should allow to choose between bundled, external, system -cmake_dependent_option(YUZU_USE_EXTERNAL_SDL2 "Build SDL2 from external source" OFF "NOT MSVC;NOT ANDROID" OFF) -cmake_dependent_option(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 build" "${MSVC}" "NOT ANDROID" OFF) - option(ENABLE_CUBEB "Enables the cubeb audio backend" ON) set(EXT_DEFAULT OFF) @@ -205,9 +224,11 @@ if (MSVC OR ANDROID) set(EXT_DEFAULT ON) endif() +option(YUZU_USE_BUNDLED_SDL3 "Download bundled SDL3 build" ${EXT_DEFAULT}) + # ffmpeg option(YUZU_USE_BUNDLED_FFMPEG "Download bundled FFmpeg" ${EXT_DEFAULT}) -cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from external source" "${PLATFORM_SUN}" "NOT WIN32 AND NOT ANDROID" OFF) +cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from external source" "${SOLARIS}" "NOT WIN32 AND NOT ANDROID" OFF) # sirit set(BUNDLED_SIRIT_DEFAULT OFF) @@ -218,9 +239,9 @@ endif() option(YUZU_USE_BUNDLED_SIRIT "Download bundled sirit" ${BUNDLED_SIRIT_DEFAULT}) # FreeBSD 15+ has libusb, versions below should disable it -cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR PLATFORM_LINUX OR PLATFORM_FREEBSD OR APPLE" OFF) +cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR LINUX OR FREEBSD OR APPLE" OFF) -cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT (WIN32 AND ARCHITECTURE_arm64) AND NOT APPLE" OFF) +cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT (WIN32 AND ARCHITECTURE_arm64) AND NOT APPLE AND NOT ANDROID" OFF) mark_as_advanced(FORCE ENABLE_OPENGL) option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON) @@ -230,6 +251,10 @@ cmake_dependent_option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}") +# Install udev rules on Linux (mainly for gyros) +# Only acts on joysticks and nothing else. +cmake_dependent_option(YUZU_INSTALL_UDEV_RULES "Install udev rules for gyro access" OFF "LINUX" OFF) + option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON) option(YUZU_LEGACY "Apply patches that improve compatibility with older GPUs (e.g. Snapdragon 865) at the cost of performance" OFF) @@ -241,7 +266,7 @@ cmake_dependent_option(YUZU_ROOM_STANDALONE "Enable standalone room executable" cmake_dependent_option(YUZU_CMD "Compile the eden-cli executable" ON "NOT ANDROID" OFF) -cmake_dependent_option(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR PLATFORM_LINUX" OFF) +cmake_dependent_option(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF) option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" ON) set(YUZU_TZDB_PATH "" CACHE STRING "Path to a pre-downloaded timezone database") @@ -251,7 +276,7 @@ cmake_dependent_option(YUZU_USE_BUNDLED_MOLTENVK "Download bundled MoltenVK lib" option(YUZU_DISABLE_LLVM "Disable LLVM (useful for CI)" OFF) set(DEFAULT_YUZU_USE_BUNDLED_OPENSSL OFF) -if (EXT_DEFAULT OR PLATFORM_SUN OR PLATFORM_OPENBSD) +if (EXT_DEFAULT OR SOLARIS OR OPENBSD) set(DEFAULT_YUZU_USE_BUNDLED_OPENSSL ON) endif() @@ -263,7 +288,7 @@ if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL) set(abi ${CMAKE_ANDROID_ARCH_ABI}) set(vvl_lib_path "${CMAKE_CURRENT_SOURCE_DIR}/src/android/app/src/main/jniLibs/${abi}/") - file(COPY "${VVL_SOURCE_DIR}/${abi}/libVkLayer_khronos_validation.so" + file(COPY "${vulkan-validation-layers_SOURCE_DIR}/${abi}/libVkLayer_khronos_validation.so" DESTINATION "${vvl_lib_path}") endif() @@ -274,26 +299,7 @@ if(EXISTS ${PROJECT_SOURCE_DIR}/hooks/pre-commit AND NOT EXISTS ${PROJECT_SOURCE endif() endif() -set(compat_base dist/compatibility_list/compatibility_list) -set(compat_qrc ${compat_base}.qrc) -set(compat_json ${compat_base}.json) - -configure_file(${PROJECT_SOURCE_DIR}/${compat_qrc} - ${PROJECT_BINARY_DIR}/${compat_qrc} - COPYONLY) - -if (EXISTS ${PROJECT_SOURCE_DIR}/${compat_json}) - configure_file("${PROJECT_SOURCE_DIR}/${compat_json}" - "${PROJECT_BINARY_DIR}/${compat_json}" - COPYONLY) -endif() - -# TODO: Compat list download -if (NOT EXISTS ${PROJECT_BINARY_DIR}/${compat_json}) - file(WRITE ${PROJECT_BINARY_DIR}/${compat_json} "") -endif() - -if (ARCHITECTURE_arm64 AND (ANDROID OR PLATFORM_LINUX)) +if (ARCHITECTURE_arm64 AND (ANDROID OR LINUX)) set(HAS_NCE 1) add_compile_definitions(HAS_NCE=1) endif() @@ -302,7 +308,7 @@ if (YUZU_ROOM) add_compile_definitions(YUZU_ROOM) endif() -if ((ANDROID OR APPLE OR UNIX) AND (NOT PLATFORM_LINUX OR ANDROID) AND NOT WIN32) +if (UNIX AND NOT (LINUX OR WIN32)) if(CXX_APPLE OR CXX_CLANG) # libc++ has stop_token and jthread as experimental set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library") @@ -337,11 +343,12 @@ if (CXX_GCC OR CXX_CLANG) endif() elseif(ARCHITECTURE_arm64) # See https://gcc.gnu.org/onlinedocs/gcc/AArch64-Options.html - set(YUZU_BUILD_PRESET "custom" CACHE STRING "Build preset to use. One of: custom, generic, armv9, native") + set(YUZU_BUILD_PRESET "custom" CACHE STRING "Build preset to use. One of: custom, generic, optimized, armv9, native") set(mtune generic) - if (${YUZU_BUILD_PRESET} STREQUAL "generic") set(march armv8-a) + elseif (${YUZU_BUILD_PRESET} STREQUAL "optimized") + set(march armv8.2-a+lse+rcpc) elseif (${YUZU_BUILD_PRESET} STREQUAL "armv9") set(march armv9-a) endif() @@ -358,10 +365,6 @@ if (CXX_GCC OR CXX_CLANG) endif() endif() -# Other presets, e.g. steamdeck -# TODO(crueter): Just have every Linux/Windows use old sdl2 -set(YUZU_SYSTEM_PROFILE "generic" CACHE STRING "CMake and Externals profile to use. One of: generic, steamdeck") - # Configure C++ standard # =========================== @@ -383,13 +386,20 @@ find_package(RenderDoc MODULE) # openssl funniness if (YUZU_USE_BUNDLED_OPENSSL) set(BUILD_SHARED_LIBS OFF) + AddJsonPackage(openssl-ci) +else() AddJsonPackage(openssl) + set(OPENSSL_BUILD_VERBOSE ON) + set(OPENSSL_CONFIGURE_VERBOSE ON) + if (OpenSSL_ADDED) - add_compile_definitions(YUZU_BUNDLED_OPENSSL) + AddJsonPackage(openssl-cmake) endif() endif() -find_package(OpenSSL 3 REQUIRED) +if (OpenSSL_ADDED) + add_compile_definitions(YUZU_BUNDLED_OPENSSL) +endif() message(STATUS "Fetching needed dependencies with CPM") @@ -407,13 +417,10 @@ AddJsonPackage(boost) set(BOOST_NO_HEADERS ${Boost_ADDED}) if (Boost_ADDED) - if (MSVC OR ANDROID) - add_compile_definitions(YUZU_BOOST_v1) - endif() - + add_compile_definitions(YUZU_BOOST_v1) if (NOT MSVC OR CXX_CLANG) - # boost sucks - if (PLATFORM_SUN) + # solaris sucks + if (SOLARIS) add_compile_options($<$:-pthreads>) endif() @@ -421,8 +428,7 @@ if (Boost_ADDED) target_compile_options(boost_icl INTERFACE $<$:-Wno-shadow>) target_compile_options(boost_asio INTERFACE $<$:-Wno-conversion> - $<$:-Wno-implicit-fallthrough> - ) + $<$:-Wno-implicit-fallthrough>) endif() endif() @@ -505,7 +511,7 @@ endfunction() # ============================================= if (APPLE) - foreach(fw Carbon Metal Cocoa IOKit CoreVideo CoreMedia Security) + foreach(fw Carbon Metal Cocoa IOKit CoreVideo CoreMedia Security UniformTypeIdentifiers Foundation) find_library(${fw}_LIBRARY ${fw} REQUIRED) list(APPEND PLATFORM_LIBRARIES ${${fw}_LIBRARY}) endforeach() @@ -517,7 +523,9 @@ elseif (WIN32) # PSAPI is the Process Status API set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version crypt32 rpcrt4 gdi32 wldap32 mswsock) endif() -elseif (PLATFORM_HAIKU) +elseif (MANAGARM) + set(PLATFORM_LIBRARIES iconv intl) +elseif (HAIKUOS) # Haiku is so special :) set(PLATFORM_LIBRARIES bsd /boot/system/lib/libnetwork.so) elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$") @@ -531,7 +539,6 @@ add_subdirectory(externals) # pass targets from externals # TODO(crueter): CPMUtil Propagate func? find_package(enet) -find_package(unordered_dense REQUIRED) if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) find_package(xbyak) @@ -559,7 +566,7 @@ if (NOT YUZU_STATIC_ROOM) endif() if (NOT ANDROID) - find_package(SDL2) + find_package(SDL3) endif() if (USE_DISCORD_PRESENCE) @@ -578,14 +585,7 @@ endif() # Qt stuff if (ENABLE_QT) if (YUZU_USE_BUNDLED_QT) - # Qt 6.8+ is broken on macOS (??) - if (APPLE) - AddQt(6.7.3) - else() - AddQt(6.9.3) - endif() - - set(YUZU_STATIC_BUILD ON) + AddQt(Eden-CI/Qt 6.11.1) else() message(STATUS "Using system Qt") if (NOT Qt6_DIR) @@ -594,23 +594,7 @@ if (ENABLE_QT) list(APPEND CMAKE_PREFIX_PATH "${Qt6_DIR}") endif() - find_package(Qt6 CONFIG REQUIRED COMPONENTS Widgets Charts Concurrent) - - if (YUZU_USE_QT_MULTIMEDIA) - find_package(Qt6 REQUIRED COMPONENTS Multimedia) - endif() - - if (PLATFORM_LINUX OR PLATFORM_FREEBSD) - # yes Qt, we get it - set(QT_NO_PRIVATE_MODULE_WARNING ON) - find_package(Qt6 REQUIRED COMPONENTS DBus OPTIONAL_COMPONENTS GuiPrivate) - elseif (UNIX AND NOT APPLE) - find_package(Qt6 REQUIRED COMPONENTS DBus Gui) - endif() - - if (ENABLE_QT_TRANSLATION) - find_package(Qt6 REQUIRED COMPONENTS LinguistTools) - endif() + find_package(Qt6 REQUIRED COMPONENTS Core) if (NOT DEFINED QT_TARGET_PATH) get_target_property(qtcore_path Qt6::Core LOCATION_Release) @@ -633,21 +617,27 @@ if (ENABLE_QT) ## Components ## # Best practice is to ask for all components at once, so they are from the same version - set(YUZU_QT_COMPONENTS Core Widgets Charts Concurrent) - if (PLATFORM_LINUX) + set(YUZU_QT_COMPONENTS Core Widgets Charts Concurrent Gui) + if (LINUX OR FREEBSD) list(APPEND YUZU_QT_COMPONENTS DBus) + # yes Qt, we get it + set(QT_NO_PRIVATE_MODULE_WARNING ON) + list(APPEND YUZU_QT_OPTIONAL GuiPrivate) endif() + if (YUZU_USE_QT_MULTIMEDIA) list(APPEND YUZU_QT_COMPONENTS Multimedia) endif() + if (YUZU_USE_QT_WEB_ENGINE) list(APPEND YUZU_QT_COMPONENTS WebEngineCore WebEngineWidgets) endif() + if (ENABLE_QT_TRANSLATION) list(APPEND YUZU_QT_COMPONENTS LinguistTools) endif() - find_package(Qt6 REQUIRED COMPONENTS ${YUZU_QT_COMPONENTS}) + find_package(Qt6 REQUIRED COMPONENTS ${YUZU_QT_COMPONENTS} OPTIONAL_COMPONENTS ${YUZU_QT_OPTIONAL}) set(QT_MAJOR_VERSION 6) # Qt6 sets cxx_std_17 and we need to undo that set_target_properties(Qt6::Platform PROPERTIES INTERFACE_COMPILE_FEATURES "") @@ -715,6 +705,12 @@ endif() add_subdirectory(src) +if (ENABLE_DEBUG_TOOLS) + add_subdirectory(tools/maxwell-disas) + add_subdirectory(tools/maxwell-spirv) + add_subdirectory(tools/maxwell-ir) +endif() + # Set yuzu project or yuzu-cmd project as default StartUp Project in Visual Studio depending on whether QT is enabled or not if(ENABLE_QT) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT yuzu) @@ -730,6 +726,8 @@ endif() # https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html # https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html # https://www.freedesktop.org/software/appstream/docs/ + +# TODO: Icon/install handling n such should be put into dist/CMakeLists.txt if(ENABLE_QT AND UNIX AND NOT APPLE) install(FILES "dist/dev.eden_emu.eden.desktop" DESTINATION "share/applications") @@ -742,3 +740,10 @@ if(ENABLE_QT AND UNIX AND NOT APPLE) install(FILES "dist/dev.eden_emu.eden.metainfo.xml" DESTINATION "share/metainfo") endif() + +if (YUZU_INSTALL_UDEV_RULES) + include(GNUInstallDirs) + + install(FILES "dist/72-eden-input.rules" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/udev/rules.d") +endif() diff --git a/CMakeModules/CPM.cmake b/CMakeModules/CPM.cmake index 5544d8eefe..431766b579 100644 --- a/CMakeModules/CPM.cmake +++ b/CMakeModules/CPM.cmake @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + # CPM.cmake - CMake's missing package manager # =========================================== # See https://github.com/cpm-cmake/CPM.cmake for usage and update instructions. @@ -26,65 +29,19 @@ SOFTWARE. ]] -cmake_minimum_required(VERSION 3.14 FATAL_ERROR) +cmake_minimum_required(VERSION 3.31 FATAL_ERROR) # Initialize logging prefix if(NOT CPM_INDENT) - set(CPM_INDENT - "CPM:" - CACHE INTERNAL "" - ) + set(CPM_INDENT "CPM:" CACHE INTERNAL "") endif() -if(NOT COMMAND cpm_message) - function(cpm_message) - message(${ARGV}) - endfunction() +get_property(CPM_INITIALIZED GLOBAL PROPERTY CPM_INITIALIZED) +if(CPM_INITIALIZED) + return() endif() -if(DEFINED EXTRACTED_CPM_VERSION) - set(CURRENT_CPM_VERSION "${EXTRACTED_CPM_VERSION}${CPM_DEVELOPMENT}") -else() - set(CURRENT_CPM_VERSION 0.42.0) -endif() - -get_filename_component(CPM_CURRENT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" REALPATH) -if(CPM_DIRECTORY) - if(NOT CPM_DIRECTORY STREQUAL CPM_CURRENT_DIRECTORY) - if(CPM_VERSION VERSION_LESS CURRENT_CPM_VERSION) - message( - AUTHOR_WARNING - "${CPM_INDENT} \ -A dependency is using a more recent CPM version (${CURRENT_CPM_VERSION}) than the current project (${CPM_VERSION}). \ -It is recommended to upgrade CPM to the most recent version. \ -See https://github.com/cpm-cmake/CPM.cmake for more information." - ) - endif() - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - include(FetchContent) - endif() - return() - endif() - - get_property( - CPM_INITIALIZED GLOBAL "" - PROPERTY CPM_INITIALIZED - SET - ) - if(CPM_INITIALIZED) - return() - endif() -endif() - -if(CURRENT_CPM_VERSION MATCHES "development-version") - message( - WARNING "${CPM_INDENT} Your project is using an unstable development version of CPM.cmake. \ -Please update to a recent release if possible. \ -See https://github.com/cpm-cmake/CPM.cmake for details." - ) -endif() - -set_property(GLOBAL PROPERTY CPM_INITIALIZED true) +set_property(GLOBAL PROPERTY CPM_INITIALIZED TRUE) macro(cpm_set_policies) # the policy allows us to change options without caching @@ -92,85 +49,40 @@ macro(cpm_set_policies) set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) # the policy allows us to change set(CACHE) without caching - if(POLICY CMP0126) - cmake_policy(SET CMP0126 NEW) - set(CMAKE_POLICY_DEFAULT_CMP0126 NEW) - endif() + cmake_policy(SET CMP0126 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0126 NEW) - # The policy uses the download time for timestamp, instead of the timestamp in the archive. This + # The policy uses the download time for timestamp, + # instead of the timestamp in the archive. This # allows for proper rebuilds when a projects url changes - if(POLICY CMP0135) - cmake_policy(SET CMP0135 NEW) - set(CMAKE_POLICY_DEFAULT_CMP0135 NEW) - endif() + cmake_policy(SET CMP0135 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0135 NEW) - # treat relative git repository paths as being relative to the parent project's remote - if(POLICY CMP0150) - cmake_policy(SET CMP0150 NEW) - set(CMAKE_POLICY_DEFAULT_CMP0150 NEW) - endif() + # treat relative git repository paths as + # being relative to the parent project's remote + cmake_policy(SET CMP0150 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0150 NEW) + + # Disable sub-build nonsense + cmake_policy(SET CMP0168 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0168 NEW) endmacro() + cpm_set_policies() -option(CPM_USE_LOCAL_PACKAGES "Always try to use `find_package` to get dependencies" - $ENV{CPM_USE_LOCAL_PACKAGES} -) -option(CPM_LOCAL_PACKAGES_ONLY "Only use `find_package` to get dependencies" - $ENV{CPM_LOCAL_PACKAGES_ONLY} -) -option(CPM_DOWNLOAD_ALL "Always download dependencies from source" $ENV{CPM_DOWNLOAD_ALL}) -option(CPM_DONT_UPDATE_MODULE_PATH "Don't update the module path to allow using find_package" - $ENV{CPM_DONT_UPDATE_MODULE_PATH} -) -option(CPM_DONT_CREATE_PACKAGE_LOCK "Don't create a package lock file in the binary path" - $ENV{CPM_DONT_CREATE_PACKAGE_LOCK} -) -option(CPM_INCLUDE_ALL_IN_PACKAGE_LOCK - "Add all packages added through CPM.cmake to the package lock" - $ENV{CPM_INCLUDE_ALL_IN_PACKAGE_LOCK} -) -option(CPM_USE_NAMED_CACHE_DIRECTORIES - "Use additional directory of package name in cache on the most nested level." - $ENV{CPM_USE_NAMED_CACHE_DIRECTORIES} -) +option(CPM_DONT_UPDATE_MODULE_PATH + "Don't update the module path to allow using find_package" + $ENV{CPM_DONT_UPDATE_MODULE_PATH}) -set(CPM_VERSION - ${CURRENT_CPM_VERSION} - CACHE INTERNAL "" -) -set(CPM_DIRECTORY - ${CPM_CURRENT_DIRECTORY} - CACHE INTERNAL "" -) set(CPM_FILE ${CMAKE_CURRENT_LIST_FILE} - CACHE INTERNAL "" -) -set(CPM_PACKAGES - "" - CACHE INTERNAL "" -) -set(CPM_DRY_RUN - OFF - CACHE INTERNAL "Don't download or configure dependencies (for testing)" -) + CACHE INTERNAL "") -if(DEFINED ENV{CPM_SOURCE_CACHE}) - set(CPM_SOURCE_CACHE_DEFAULT $ENV{CPM_SOURCE_CACHE}) -else() - set(CPM_SOURCE_CACHE_DEFAULT OFF) -endif() +set(CPM_PACKAGES "" CACHE INTERNAL "") -set(CPM_SOURCE_CACHE - ${CPM_SOURCE_CACHE_DEFAULT} - CACHE PATH "Directory to download CPM dependencies" -) - -if(NOT CPM_DONT_UPDATE_MODULE_PATH AND NOT DEFINED CMAKE_FIND_PACKAGE_REDIRECTS_DIR) - set(CPM_MODULE_PATH - "${CMAKE_BINARY_DIR}/CPM_modules" - CACHE INTERNAL "" - ) +if(NOT CPM_DONT_UPDATE_MODULE_PATH + AND NOT DEFINED CMAKE_FIND_PACKAGE_REDIRECTS_DIR) + set(CPM_MODULE_PATH "${CMAKE_BINARY_DIR}/CPM_modules" CACHE INTERNAL "") # remove old modules file(REMOVE_RECURSE ${CPM_MODULE_PATH}) file(MAKE_DIRECTORY ${CPM_MODULE_PATH}) @@ -178,602 +90,105 @@ if(NOT CPM_DONT_UPDATE_MODULE_PATH AND NOT DEFINED CMAKE_FIND_PACKAGE_REDIRECTS_ set(CMAKE_MODULE_PATH "${CPM_MODULE_PATH};${CMAKE_MODULE_PATH}") endif() -if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) - set(CPM_PACKAGE_LOCK_FILE - "${CMAKE_BINARY_DIR}/cpm-package-lock.cmake" - CACHE INTERNAL "" - ) - file(WRITE ${CPM_PACKAGE_LOCK_FILE} - "# CPM Package Lock\n# This file should be committed to version control\n\n" - ) -endif() - include(FetchContent) -# Try to infer package name from git repository uri (path or url) -function(cpm_package_name_from_git_uri URI RESULT) - if("${URI}" MATCHES "([^/:]+)/?.git/?$") - set(${RESULT} - ${CMAKE_MATCH_1} - PARENT_SCOPE - ) - else() - unset(${RESULT} PARENT_SCOPE) - endif() -endfunction() - -# Find the shortest hash that can be used eg, if origin_hash is -# cccb77ae9609d2768ed80dd42cec54f77b1f1455 the following files will be checked, until one is found -# that is either empty (allowing us to assign origin_hash), or whose contents matches ${origin_hash} -# -# * .../cccb.hash -# * .../cccb77ae.hash -# * .../cccb77ae9609.hash -# * .../cccb77ae9609d276.hash -# * etc -# -# We will be able to use a shorter path with very high probability, but in the (rare) event that the -# first couple characters collide, we will check longer and longer substrings. -function(cpm_get_shortest_hash source_cache_dir origin_hash short_hash_output_var) - # for compatibility with caches populated by a previous version of CPM, check if a directory using - # the full hash already exists - if(EXISTS "${source_cache_dir}/${origin_hash}") - set(${short_hash_output_var} - "${origin_hash}" - PARENT_SCOPE - ) - return() - endif() - - foreach(len RANGE 4 40 4) - string(SUBSTRING "${origin_hash}" 0 ${len} short_hash) - set(hash_lock ${source_cache_dir}/${short_hash}.lock) - set(hash_fp ${source_cache_dir}/${short_hash}.hash) - # Take a lock, so we don't have a race condition with another instance of cmake. We will release - # this lock when we can, however, if there is an error, we want to ensure it gets released on - # it's own on exit from the function. - file(LOCK ${hash_lock} GUARD FUNCTION) - - # Load the contents of .../${short_hash}.hash - file(TOUCH ${hash_fp}) - file(READ ${hash_fp} hash_fp_contents) - - if(hash_fp_contents STREQUAL "") - # Write the origin hash - file(WRITE ${hash_fp} ${origin_hash}) - file(LOCK ${hash_lock} RELEASE) - break() - elseif(hash_fp_contents STREQUAL origin_hash) - file(LOCK ${hash_lock} RELEASE) - break() - else() - file(LOCK ${hash_lock} RELEASE) - endif() - endforeach() - set(${short_hash_output_var} - "${short_hash}" - PARENT_SCOPE - ) -endfunction() - -# Try to infer package name and version from a url -function(cpm_package_name_and_ver_from_url url outName outVer) - if(url MATCHES "[/\\?]([a-zA-Z0-9_\\.-]+)\\.(tar|tar\\.gz|tar\\.bz2|zip|ZIP)(\\?|/|$)") - # We matched an archive - set(filename "${CMAKE_MATCH_1}") - - if(filename MATCHES "([a-zA-Z0-9_\\.-]+)[_-]v?(([0-9]+\\.)*[0-9]+[a-zA-Z0-9]*)") - # We matched - (ie foo-1.2.3) - set(${outName} - "${CMAKE_MATCH_1}" - PARENT_SCOPE - ) - set(${outVer} - "${CMAKE_MATCH_2}" - PARENT_SCOPE - ) - elseif(filename MATCHES "(([0-9]+\\.)+[0-9]+[a-zA-Z0-9]*)") - # We couldn't find a name, but we found a version - # - # In many cases (which we don't handle here) the url would look something like - # `irrelevant/ACTUAL_PACKAGE_NAME/irrelevant/1.2.3.zip`. In such a case we can't possibly - # distinguish the package name from the irrelevant bits. Moreover if we try to match the - # package name from the filename, we'd get bogus at best. - unset(${outName} PARENT_SCOPE) - set(${outVer} - "${CMAKE_MATCH_1}" - PARENT_SCOPE - ) - else() - # Boldly assume that the file name is the package name. - # - # Yes, something like `irrelevant/ACTUAL_NAME/irrelevant/download.zip` will ruin our day, but - # such cases should be quite rare. No popular service does this... we think. - set(${outName} - "${filename}" - PARENT_SCOPE - ) - unset(${outVer} PARENT_SCOPE) - endif() - else() - # No ideas yet what to do with non-archives - unset(${outName} PARENT_SCOPE) - unset(${outVer} PARENT_SCOPE) - endif() -endfunction() - -function(cpm_find_package NAME VERSION) - string(REPLACE " " ";" EXTRA_ARGS "${ARGN}") - find_package(${NAME} ${VERSION} ${EXTRA_ARGS} QUIET) - if(${CPM_ARGS_NAME}_FOUND) - if(DEFINED ${CPM_ARGS_NAME}_VERSION) - set(VERSION ${${CPM_ARGS_NAME}_VERSION}) - endif() - cpm_message(STATUS "${CPM_INDENT} Using local package ${CPM_ARGS_NAME}@${VERSION}") - CPMRegisterPackage(${CPM_ARGS_NAME} "${VERSION}") - set(CPM_PACKAGE_FOUND - YES - PARENT_SCOPE - ) - else() - set(CPM_PACKAGE_FOUND - NO - PARENT_SCOPE - ) - endif() -endfunction() - -# Create a custom FindXXX.cmake module for a CPM package This prevents `find_package(NAME)` from -# finding the system library +# Create a custom FindXXX.cmake module for a CPM package. +# This prevents `find_package(NAME)` from finding the system library function(cpm_create_module_file Name) if(NOT CPM_DONT_UPDATE_MODULE_PATH) if(DEFINED CMAKE_FIND_PACKAGE_REDIRECTS_DIR) - # Redirect find_package calls to the CPM package. This is what FetchContent does when you set - # OVERRIDE_FIND_PACKAGE. The CMAKE_FIND_PACKAGE_REDIRECTS_DIR works for find_package in CONFIG - # mode, unlike the Find${Name}.cmake fallback. CMAKE_FIND_PACKAGE_REDIRECTS_DIR is not defined - # in script mode, or in CMake < 3.24. - # https://cmake.org/cmake/help/latest/module/FetchContent.html#fetchcontent-find-package-integration-examples + # Redirect find_package calls to the CPM package. + # This is what FetchContent does when you set + # OVERRIDE_FIND_PACKAGE. The CMAKE_FIND_PACKAGE_REDIRECTS_DIR works for + # find_package in CONFIG mode, unlike the Find${Name}.cmake fallback. + # CMAKE_FIND_PACKAGE_REDIRECTS_DIR is not defined in script mode + # https://cmake.org/cmake/help/latest/module/FetchContent.html string(TOLOWER ${Name} NameLower) file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/${NameLower}-config.cmake - "include(\"\${CMAKE_CURRENT_LIST_DIR}/${NameLower}-extra.cmake\" OPTIONAL)\n" - "include(\"\${CMAKE_CURRENT_LIST_DIR}/${Name}Extra.cmake\" OPTIONAL)\n" - ) - file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/${NameLower}-config-version.cmake - "set(PACKAGE_VERSION_COMPATIBLE TRUE)\n" "set(PACKAGE_VERSION_EXACT TRUE)\n" - ) + "include(\"\${CMAKE_CURRENT_LIST_DIR}/${NameLower}-extra.cmake\" \ + OPTIONAL)\n" + "include(\"\${CMAKE_CURRENT_LIST_DIR}/${Name}Extra.cmake\" OPTIONAL)\n") + + file(WRITE + ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/${NameLower}-config-version.cmake + "set(PACKAGE_VERSION_COMPATIBLE TRUE)\n" + "set(PACKAGE_VERSION_EXACT TRUE)\n") else() file(WRITE ${CPM_MODULE_PATH}/Find${Name}.cmake - "include(\"${CPM_FILE}\")\n${ARGN}\nset(${Name}_FOUND TRUE)" - ) + "include(\"${CPM_FILE}\")\n${ARGN}\nset(${Name}_FOUND TRUE)") endif() endif() endfunction() -# Find a package locally or fallback to CPMAddPackage -function(CPMFindPackage) - set(oneValueArgs NAME VERSION GIT_TAG FIND_PACKAGE_ARGUMENTS) - - cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "" ${ARGN}) - - if(NOT DEFINED CPM_ARGS_VERSION) - if(DEFINED CPM_ARGS_GIT_TAG) - cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) - endif() - endif() - - set(downloadPackage ${CPM_DOWNLOAD_ALL}) - if(DEFINED CPM_DOWNLOAD_${CPM_ARGS_NAME}) - set(downloadPackage ${CPM_DOWNLOAD_${CPM_ARGS_NAME}}) - elseif(DEFINED ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}}) - set(downloadPackage $ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}}) - endif() - if(downloadPackage) - CPMAddPackage(${ARGN}) - cpm_export_variables(${CPM_ARGS_NAME}) - return() - endif() - - cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) - - if(NOT CPM_PACKAGE_FOUND) - CPMAddPackage(${ARGN}) - cpm_export_variables(${CPM_ARGS_NAME}) - endif() - -endfunction() - # checks if a package has been added before function(cpm_check_if_package_already_added CPM_ARGS_NAME CPM_ARGS_VERSION) if("${CPM_ARGS_NAME}" IN_LIST CPM_PACKAGES) CPMGetPackageVersion(${CPM_ARGS_NAME} CPM_PACKAGE_VERSION) if("${CPM_PACKAGE_VERSION}" VERSION_LESS "${CPM_ARGS_VERSION}") - message( - WARNING - "${CPM_INDENT} Requires a newer version of ${CPM_ARGS_NAME} (${CPM_ARGS_VERSION}) than currently included (${CPM_PACKAGE_VERSION})." - ) + message(WARNING + "${CPM_INDENT} Requires a newer version of ${CPM_ARGS_NAME} " + "(${CPM_ARGS_VERSION}) than currently included " + "(${CPM_PACKAGE_VERSION}).") endif() cpm_get_fetch_properties(${CPM_ARGS_NAME}) set(${CPM_ARGS_NAME}_ADDED NO) - set(CPM_PACKAGE_ALREADY_ADDED - YES - PARENT_SCOPE - ) + set(CPM_PACKAGE_ALREADY_ADDED YES PARENT_SCOPE) cpm_export_variables(${CPM_ARGS_NAME}) else() - set(CPM_PACKAGE_ALREADY_ADDED - NO - PARENT_SCOPE - ) + set(CPM_PACKAGE_ALREADY_ADDED NO PARENT_SCOPE) endif() endfunction() -# Parse the argument of CPMAddPackage in case a single one was provided and convert it to a list of -# arguments which can then be parsed idiomatically. For example gh:foo/bar@1.2.3 will be converted -# to: GITHUB_REPOSITORY;foo/bar;VERSION;1.2.3 -function(cpm_parse_add_package_single_arg arg outArgs) - # Look for a scheme - if("${arg}" MATCHES "^([a-zA-Z]+):(.+)$") - string(TOLOWER "${CMAKE_MATCH_1}" scheme) - set(uri "${CMAKE_MATCH_2}") - - # Check for CPM-specific schemes - if(scheme STREQUAL "gh") - set(out "GITHUB_REPOSITORY;${uri}") - set(packageType "git") - elseif(scheme STREQUAL "gl") - set(out "GITLAB_REPOSITORY;${uri}") - set(packageType "git") - elseif(scheme STREQUAL "bb") - set(out "BITBUCKET_REPOSITORY;${uri}") - set(packageType "git") - # A CPM-specific scheme was not found. Looks like this is a generic URL so try to determine - # type - elseif(arg MATCHES ".git/?(@|#|$)") - set(out "GIT_REPOSITORY;${arg}") - set(packageType "git") - else() - # Fall back to a URL - set(out "URL;${arg}") - set(packageType "archive") - - # We could also check for SVN since FetchContent supports it, but SVN is so rare these days. - # We just won't bother with the additional complexity it will induce in this function. SVN is - # done by multi-arg - endif() - else() - if(arg MATCHES ".git/?(@|#|$)") - set(out "GIT_REPOSITORY;${arg}") - set(packageType "git") - else() - # Give up - message(FATAL_ERROR "${CPM_INDENT} Can't determine package type of '${arg}'") - endif() - endif() - - # For all packages we interpret @... as version. Only replace the last occurrence. Thus URIs - # containing '@' can be used - string(REGEX REPLACE "@([^@]+)$" ";VERSION;\\1" out "${out}") - - # Parse the rest according to package type - if(packageType STREQUAL "git") - # For git repos we interpret #... as a tag or branch or commit hash - string(REGEX REPLACE "#([^#]+)$" ";GIT_TAG;\\1" out "${out}") - elseif(packageType STREQUAL "archive") - # For archives we interpret #... as a URL hash. - string(REGEX REPLACE "#([^#]+)$" ";URL_HASH;\\1" out "${out}") - # We don't try to parse the version if it's not provided explicitly. cpm_get_version_from_url - # should do this at a later point - else() - # We should never get here. This is an assertion and hitting it means there's a problem with the - # code above. A packageType was set, but not handled by this if-else. - message(FATAL_ERROR "${CPM_INDENT} Unsupported package type '${packageType}' of '${arg}'") - endif() - - set(${outArgs} - ${out} - PARENT_SCOPE - ) -endfunction() - -# Check that the working directory for a git repo is clean -function(cpm_check_git_working_dir_is_clean repoPath gitTag isClean) - - find_package(Git REQUIRED) - - if(NOT GIT_EXECUTABLE) - # No git executable, assume directory is clean - set(${isClean} - TRUE - PARENT_SCOPE - ) - return() - endif() - - # check for uncommitted changes - execute_process( - COMMAND ${GIT_EXECUTABLE} status --porcelain - RESULT_VARIABLE resultGitStatus - OUTPUT_VARIABLE repoStatus - OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET - WORKING_DIRECTORY ${repoPath} - ) - if(resultGitStatus) - # not supposed to happen, assume clean anyway - message(WARNING "${CPM_INDENT} Calling git status on folder ${repoPath} failed") - set(${isClean} - TRUE - PARENT_SCOPE - ) - return() - endif() - - if(NOT "${repoStatus}" STREQUAL "") - set(${isClean} - FALSE - PARENT_SCOPE - ) - return() - endif() - - # check for committed changes - execute_process( - COMMAND ${GIT_EXECUTABLE} diff -s --exit-code ${gitTag} - RESULT_VARIABLE resultGitDiff - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_QUIET - WORKING_DIRECTORY ${repoPath} - ) - - if(${resultGitDiff} EQUAL 0) - set(${isClean} - TRUE - PARENT_SCOPE - ) - else() - set(${isClean} - FALSE - PARENT_SCOPE - ) - endif() - -endfunction() - -# Add PATCH_COMMAND to CPM_ARGS_UNPARSED_ARGUMENTS. This method consumes a list of files in ARGN -# then generates a `PATCH_COMMAND` appropriate for `ExternalProject_Add()`. This command is appended -# to the parent scope's `CPM_ARGS_UNPARSED_ARGUMENTS`. -function(cpm_add_patches) - # Return if no patch files are supplied. - if(NOT ARGN) - return() - endif() - - # Find the patch program. - find_program(PATCH_EXECUTABLE patch) - if(CMAKE_HOST_WIN32 AND NOT PATCH_EXECUTABLE) - # The Windows git executable is distributed with patch.exe. Find the path to the executable, if - # it exists, then search `../usr/bin` and `../../usr/bin` for patch.exe. - find_package(Git QUIET) - if(GIT_EXECUTABLE) - get_filename_component(extra_search_path ${GIT_EXECUTABLE} DIRECTORY) - get_filename_component(extra_search_path_1up ${extra_search_path} DIRECTORY) - get_filename_component(extra_search_path_2up ${extra_search_path_1up} DIRECTORY) - find_program( - PATCH_EXECUTABLE patch HINTS "${extra_search_path_1up}/usr/bin" - "${extra_search_path_2up}/usr/bin" - ) - endif() - endif() - if(NOT PATCH_EXECUTABLE) - message(FATAL_ERROR "Couldn't find `patch` executable to use with PATCHES keyword.") - endif() - - # Create a temporary - set(temp_list ${CPM_ARGS_UNPARSED_ARGUMENTS}) - - # Ensure each file exists (or error out) and add it to the list. - set(first_item True) - foreach(PATCH_FILE ${ARGN}) - # Make sure the patch file exists, if we can't find it, try again in the current directory. - if(NOT EXISTS "${PATCH_FILE}") - if(NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/${PATCH_FILE}") - message(FATAL_ERROR "Couldn't find patch file: '${PATCH_FILE}'") - endif() - set(PATCH_FILE "${CMAKE_CURRENT_LIST_DIR}/${PATCH_FILE}") - endif() - - # Convert to absolute path for use with patch file command. - get_filename_component(PATCH_FILE "${PATCH_FILE}" ABSOLUTE) - - # The first patch entry must be preceded by "PATCH_COMMAND" while the following items are - # preceded by "&&". - if(first_item) - set(first_item False) - list(APPEND temp_list "PATCH_COMMAND") - else() - list(APPEND temp_list "&&") - endif() - # Add the patch command to the list - list(APPEND temp_list "${PATCH_EXECUTABLE}" "-p1" "<" "${PATCH_FILE}") - endforeach() - - # Move temp out into parent scope. - set(CPM_ARGS_UNPARSED_ARGUMENTS - ${temp_list} - PARENT_SCOPE - ) - -endfunction() - -# method to overwrite internal FetchContent properties, to allow using CPM.cmake to overload -# FetchContent calls. As these are internal cmake properties, this method should be used carefully -# and may need modification in future CMake versions. Source: -# https://github.com/Kitware/CMake/blob/dc3d0b5a0a7d26d43d6cfeb511e224533b5d188f/Modules/FetchContent.cmake#L1152 -function(cpm_override_fetchcontent contentName) - cmake_parse_arguments(PARSE_ARGV 1 arg "" "SOURCE_DIR;BINARY_DIR" "") - if(NOT "${arg_UNPARSED_ARGUMENTS}" STREQUAL "") - message(FATAL_ERROR "${CPM_INDENT} Unsupported arguments: ${arg_UNPARSED_ARGUMENTS}") - endif() - - string(TOLOWER ${contentName} contentNameLower) - set(prefix "_FetchContent_${contentNameLower}") - - set(propertyName "${prefix}_sourceDir") - define_property( - GLOBAL - PROPERTY ${propertyName} - BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" - FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" - ) - set_property(GLOBAL PROPERTY ${propertyName} "${arg_SOURCE_DIR}") - - set(propertyName "${prefix}_binaryDir") - define_property( - GLOBAL - PROPERTY ${propertyName} - BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" - FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" - ) - set_property(GLOBAL PROPERTY ${propertyName} "${arg_BINARY_DIR}") - - set(propertyName "${prefix}_populated") - define_property( - GLOBAL - PROPERTY ${propertyName} - BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()" - FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}" - ) - set_property(GLOBAL PROPERTY ${propertyName} TRUE) -endfunction() - -# Download and add a package from source function(CPMAddPackage) - cpm_set_policies() - set(oneValueArgs NAME FORCE VERSION - GIT_TAG DOWNLOAD_ONLY - GITHUB_REPOSITORY - GITLAB_REPOSITORY - BITBUCKET_REPOSITORY - GIT_REPOSITORY SOURCE_DIR - FIND_PACKAGE_ARGUMENTS - NO_CACHE SYSTEM - GIT_SHALLOW EXCLUDE_FROM_ALL - SOURCE_SUBDIR - CUSTOM_CACHE_KEY - ) + SOURCE_SUBDIR) - set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND PATCHES) + set(multiValueArgs OPTIONS) - list(LENGTH ARGN argnLength) + cmake_parse_arguments(CPM_ARGS + "" + "${oneValueArgs}" + "${multiValueArgs}" + "${ARGN}") - # Parse single shorthand argument - if(argnLength EQUAL 1) - cpm_parse_add_package_single_arg("${ARGN}" ARGN) - - # The shorthand syntax implies EXCLUDE_FROM_ALL and SYSTEM - set(ARGN "${ARGN};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;") - - # Parse URI shorthand argument - elseif(argnLength GREATER 1 AND "${ARGV0}" STREQUAL "URI") - list(REMOVE_AT ARGN 0 1) # remove "URI gh:<...>@version#tag" - cpm_parse_add_package_single_arg("${ARGV1}" ARGV0) - - set(ARGN "${ARGV0};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;${ARGN}") - endif() - - cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}") - - # Set default values for arguments - if(NOT DEFINED CPM_ARGS_VERSION) - if(DEFINED CPM_ARGS_GIT_TAG) - cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION) - endif() + if (NOT DEFINED CPM_ARGS_SOURCE_DIR) + message(FATAL_ERROR "${CPM_INDENT} SOURCE_DIR is required") endif() if(CPM_ARGS_DOWNLOAD_ONLY) set(DOWNLOAD_ONLY ${CPM_ARGS_DOWNLOAD_ONLY}) else() - set(DOWNLOAD_ONLY NO) + # Default to DOWNLOAD_ONLY in script mode. + set(DOWNLOAD_ONLY ${CMAKE_SCRIPT_MODE_FILE}) endif() - if(DEFINED CPM_ARGS_GITHUB_REPOSITORY) - set(CPM_ARGS_GIT_REPOSITORY "https://github.com/${CPM_ARGS_GITHUB_REPOSITORY}.git") - elseif(DEFINED CPM_ARGS_GITLAB_REPOSITORY) - set(CPM_ARGS_GIT_REPOSITORY "https://gitlab.com/${CPM_ARGS_GITLAB_REPOSITORY}.git") - elseif(DEFINED CPM_ARGS_BITBUCKET_REPOSITORY) - set(CPM_ARGS_GIT_REPOSITORY "https://bitbucket.org/${CPM_ARGS_BITBUCKET_REPOSITORY}.git") - endif() - - if(DEFINED CPM_ARGS_GIT_REPOSITORY) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_REPOSITORY ${CPM_ARGS_GIT_REPOSITORY}) - if(NOT DEFINED CPM_ARGS_GIT_TAG) - set(CPM_ARGS_GIT_TAG v${CPM_ARGS_VERSION}) - endif() - - # If a name wasn't provided, try to infer it from the git repo - if(NOT DEFINED CPM_ARGS_NAME) - cpm_package_name_from_git_uri(${CPM_ARGS_GIT_REPOSITORY} CPM_ARGS_NAME) - endif() - endif() - - set(CPM_SKIP_FETCH FALSE) - - if(DEFINED CPM_ARGS_GIT_TAG) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_TAG ${CPM_ARGS_GIT_TAG}) - # If GIT_SHALLOW is explicitly specified, honor the value. - if(DEFINED CPM_ARGS_GIT_SHALLOW) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW ${CPM_ARGS_GIT_SHALLOW}) - endif() - endif() - - if(DEFINED CPM_ARGS_URL) - # If a name or version aren't provided, try to infer them from the URL - list(GET CPM_ARGS_URL 0 firstUrl) - cpm_package_name_and_ver_from_url(${firstUrl} nameFromUrl verFromUrl) - # If we fail to obtain name and version from the first URL, we could try other URLs if any. - # However multiple URLs are expected to be quite rare, so for now we won't bother. - - # If the caller provided their own name and version, they trump the inferred ones. - if(NOT DEFINED CPM_ARGS_NAME) - set(CPM_ARGS_NAME ${nameFromUrl}) - endif() - - # this is dumb and should not be done - # if(NOT DEFINED CPM_ARGS_VERSION) - # set(CPM_ARGS_VERSION ${verFromUrl}) - # endif() - - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS URL "${CPM_ARGS_URL}") - endif() - - # Check for required arguments - if(NOT DEFINED CPM_ARGS_NAME) - message( - FATAL_ERROR - "${CPM_INDENT} 'NAME' was not provided and couldn't be automatically inferred for package added with arguments: '${ARGN}'" - ) + message(FATAL_ERROR "${CPM_INDENT} NAME is required") + endif() + + if(NOT DEFINED CPM_ARGS_VERSION) + message(FATAL_ERROR "${CPM_INDENT} VERSION is required") endif() - # Check if package has been added before cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") if(CPM_PACKAGE_ALREADY_ADDED) cpm_export_variables(${CPM_ARGS_NAME}) return() endif() - # Check for manual overrides if(NOT CPM_ARGS_FORCE AND NOT "${CPM_${CPM_ARGS_NAME}_SOURCE}" STREQUAL "") set(PACKAGE_SOURCE ${CPM_${CPM_ARGS_NAME}_SOURCE}) set(CPM_${CPM_ARGS_NAME}_SOURCE "") CPMAddPackage( NAME "${CPM_ARGS_NAME}" + VERSION "${CPM_ARGS_VERSION}" SOURCE_DIR "${PACKAGE_SOURCE}" EXCLUDE_FROM_ALL "${CPM_ARGS_EXCLUDE_FROM_ALL}" SYSTEM "${CPM_ARGS_SYSTEM}" @@ -781,455 +196,116 @@ function(CPMAddPackage) OPTIONS "${CPM_ARGS_OPTIONS}" SOURCE_SUBDIR "${CPM_ARGS_SOURCE_SUBDIR}" DOWNLOAD_ONLY "${DOWNLOAD_ONLY}" - FORCE True - ) + FORCE True) + cpm_export_variables(${CPM_ARGS_NAME}) return() endif() - # Check for available declaration - if(NOT CPM_ARGS_FORCE AND NOT "${CPM_DECLARATION_${CPM_ARGS_NAME}}" STREQUAL "") - set(declaration ${CPM_DECLARATION_${CPM_ARGS_NAME}}) - set(CPM_DECLARATION_${CPM_ARGS_NAME} "") - CPMAddPackage(${declaration}) - cpm_export_variables(${CPM_ARGS_NAME}) - # checking again to ensure version and option compatibility - cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") - return() - endif() - - if(NOT CPM_ARGS_FORCE) - if(CPM_USE_LOCAL_PACKAGES OR CPM_LOCAL_PACKAGES_ONLY) - cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) - - if(CPM_PACKAGE_FOUND) - cpm_export_variables(${CPM_ARGS_NAME}) - return() - endif() - - if(CPM_LOCAL_PACKAGES_ONLY) - message( - SEND_ERROR - "${CPM_INDENT} ${CPM_ARGS_NAME} not found via find_package(${CPM_ARGS_NAME} ${CPM_ARGS_VERSION})" - ) - endif() - endif() - endif() - CPMRegisterPackage("${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}") - if(DEFINED CPM_ARGS_GIT_TAG) - set(PACKAGE_INFO "${CPM_ARGS_GIT_TAG}") - elseif(DEFINED CPM_ARGS_SOURCE_DIR) - set(PACKAGE_INFO "${CPM_ARGS_SOURCE_DIR}") - else() - set(PACKAGE_INFO "${CPM_ARGS_VERSION}") - endif() - if(DEFINED FETCHCONTENT_BASE_DIR) - # respect user's FETCHCONTENT_BASE_DIR if set set(CPM_FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR}) else() set(CPM_FETCHCONTENT_BASE_DIR ${CMAKE_BINARY_DIR}/_deps) endif() - cpm_add_patches(${CPM_ARGS_PATCHES}) + # Subbuild, etc + string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) - if(DEFINED CPM_ARGS_DOWNLOAD_COMMAND) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS DOWNLOAD_COMMAND ${CPM_ARGS_DOWNLOAD_COMMAND}) - elseif(DEFINED CPM_ARGS_SOURCE_DIR) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${CPM_ARGS_SOURCE_DIR}) - if(NOT IS_ABSOLUTE ${CPM_ARGS_SOURCE_DIR}) - # Expand `CPM_ARGS_SOURCE_DIR` relative path. This is important because EXISTS doesn't work - # for relative paths. - get_filename_component( - source_directory ${CPM_ARGS_SOURCE_DIR} REALPATH BASE_DIR ${CMAKE_CURRENT_BINARY_DIR} - ) - else() - set(source_directory ${CPM_ARGS_SOURCE_DIR}) - endif() - if(NOT EXISTS ${source_directory}) - string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) - # remove timestamps so CMake will re-download the dependency - file(REMOVE_RECURSE "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild") - endif() - elseif(CPM_SOURCE_CACHE AND NOT CPM_ARGS_NO_CACHE) - string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) - set(origin_parameters ${CPM_ARGS_UNPARSED_ARGUMENTS}) - list(SORT origin_parameters) - if(CPM_ARGS_CUSTOM_CACHE_KEY) - # Application set a custom unique directory name - set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${CPM_ARGS_CUSTOM_CACHE_KEY}) - elseif(CPM_USE_NAMED_CACHE_DIRECTORIES) - string(SHA1 origin_hash "${origin_parameters};NEW_CACHE_STRUCTURE_TAG") - cpm_get_shortest_hash( - "${CPM_SOURCE_CACHE}/${lower_case_name}" # source cache directory - "${origin_hash}" # Input hash - origin_hash # Computed hash - ) - set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}/${CPM_ARGS_NAME}) - else() - string(SHA1 origin_hash "${origin_parameters}") - cpm_get_shortest_hash( - "${CPM_SOURCE_CACHE}/${lower_case_name}" # source cache directory - "${origin_hash}" # Input hash - origin_hash # Computed hash - ) - set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}) - endif() - # Expand `download_directory` relative path. This is important because EXISTS doesn't work for - # relative paths. - get_filename_component(download_directory ${download_directory} ABSOLUTE) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${download_directory}) - - if(CPM_SOURCE_CACHE) - file(LOCK ${download_directory}/../cmake.lock) - endif() - - if(EXISTS ${download_directory}) - if(CPM_SOURCE_CACHE) - file(LOCK ${download_directory}/../cmake.lock RELEASE) - endif() - - cpm_store_fetch_properties( - ${CPM_ARGS_NAME} "${download_directory}" - "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-build" - ) - cpm_get_fetch_properties("${CPM_ARGS_NAME}") - - if(DEFINED CPM_ARGS_GIT_TAG AND NOT (PATCH_COMMAND IN_LIST CPM_ARGS_UNPARSED_ARGUMENTS)) - # warn if cache has been changed since checkout - cpm_check_git_working_dir_is_clean(${download_directory} ${CPM_ARGS_GIT_TAG} IS_CLEAN) - if(NOT ${IS_CLEAN}) - message( - WARNING "${CPM_INDENT} Cache for ${CPM_ARGS_NAME} (${download_directory}) is dirty" - ) - endif() - endif() - - cpm_add_subdirectory( - "${CPM_ARGS_NAME}" - "${DOWNLOAD_ONLY}" - "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" - "${${CPM_ARGS_NAME}_BINARY_DIR}" - "${CPM_ARGS_EXCLUDE_FROM_ALL}" - "${CPM_ARGS_SYSTEM}" - "${CPM_ARGS_OPTIONS}" - ) - set(PACKAGE_INFO "${PACKAGE_INFO} at ${download_directory}") - - # As the source dir is already cached/populated, we override the call to FetchContent. - set(CPM_SKIP_FETCH TRUE) - cpm_override_fetchcontent( - "${lower_case_name}" SOURCE_DIR "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" - BINARY_DIR "${${CPM_ARGS_NAME}_BINARY_DIR}" - ) - - else() - # Enable shallow clone when GIT_TAG is not a commit hash. Our guess may not be accurate, but - # it should guarantee no commit hash get mis-detected. - if(NOT DEFINED CPM_ARGS_GIT_SHALLOW) - cpm_is_git_tag_commit_hash("${CPM_ARGS_GIT_TAG}" IS_HASH) - if(NOT ${IS_HASH}) - list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW TRUE) - endif() - endif() - - # remove timestamps so CMake will re-download the dependency - file(REMOVE_RECURSE ${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild) - set(PACKAGE_INFO "${PACKAGE_INFO} to ${download_directory}") - endif() + set(fetch_source_dir ${CPM_ARGS_SOURCE_DIR}) + if(NOT IS_ABSOLUTE ${CPM_ARGS_SOURCE_DIR}) + get_filename_component( + fetch_source_dir ${CPM_ARGS_SOURCE_DIR} + REALPATH BASE_DIR ${CMAKE_CURRENT_BINARY_DIR}) endif() - if(NOT "${DOWNLOAD_ONLY}") + # FetchContent args + set(fetchContentDeclareExtraArgs "") + if(${CPM_ARGS_EXCLUDE_FROM_ALL}) + list(APPEND fetchContentDeclareExtraArgs EXCLUDE_FROM_ALL) + endif() + + if(CPM_ARGS_SYSTEM) + list(APPEND fetchContentDeclareExtraArgs SYSTEM) + endif() + + if(DEFINED CPM_ARGS_SOURCE_SUBDIR) + list(APPEND fetchContentDeclareExtraArgs + SOURCE_SUBDIR ${CPM_ARGS_SOURCE_SUBDIR}) + endif() + + if(CPM_ARGS_OPTIONS AND NOT DOWNLOAD_ONLY) + foreach(OPTION ${CPM_ARGS_OPTIONS}) + cpm_parse_option("${OPTION}") + set(${OPTION_KEY} "${OPTION_VALUE}") + endforeach() + endif() + + if(NOT DOWNLOAD_ONLY) cpm_create_module_file(${CPM_ARGS_NAME} "CPMAddPackage(\"${ARGN}\")") endif() - if(CPM_PACKAGE_LOCK_ENABLED) - if((CPM_ARGS_VERSION AND NOT CPM_ARGS_SOURCE_DIR) OR CPM_INCLUDE_ALL_IN_PACKAGE_LOCK) - cpm_add_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") - elseif(CPM_ARGS_SOURCE_DIR) - cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "local directory") - else() - cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "${ARGN}") - endif() + FetchContent_Declare(${CPM_ARGS_NAME} + ${fetchContentDeclareExtraArgs} + SOURCE_DIR ${fetch_source_dir} + DOWNLOAD_NO_PROGRESS TRUE) + + if(DOWNLOAD_ONLY) + FetchContent_Populate(${CPM_ARGS_NAME} + ${fetchContentDeclareExtraArgs} + SOURCE_DIR "${fetch_source_dir}" + BINARY_DIR "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-build" + SUBBUILD_DIR "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild" + DOWNLOAD_NO_PROGRESS TRUE) + else() + FetchContent_MakeAvailable(${CPM_ARGS_NAME}) endif() - cpm_message( - STATUS "${CPM_INDENT} Adding package ${CPM_ARGS_NAME}@${CPM_ARGS_VERSION} (${PACKAGE_INFO})" - ) + FetchContent_GetProperties(${CPM_ARGS_NAME}) + cpm_store_fetch_properties( + ${CPM_ARGS_NAME} + ${${lower_case_name}_SOURCE_DIR} + ${${lower_case_name}_BINARY_DIR}) - if(NOT CPM_SKIP_FETCH) - # CMake 3.28 added EXCLUDE, SYSTEM (3.25), and SOURCE_SUBDIR (3.18) to FetchContent_Declare. - # Calling FetchContent_MakeAvailable will then internally forward these options to - # add_subdirectory. Up until these changes, we had to call FetchContent_Populate and - # add_subdirectory separately, which is no longer necessary and has been deprecated as of 3.30. - # A Bug in CMake prevents us to use the non-deprecated functions until 3.30.3. - set(fetchContentDeclareExtraArgs "") - if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.30.3") - if(${CPM_ARGS_EXCLUDE_FROM_ALL}) - list(APPEND fetchContentDeclareExtraArgs EXCLUDE_FROM_ALL) - endif() - if(${CPM_ARGS_SYSTEM}) - list(APPEND fetchContentDeclareExtraArgs SYSTEM) - endif() - if(DEFINED CPM_ARGS_SOURCE_SUBDIR) - list(APPEND fetchContentDeclareExtraArgs SOURCE_SUBDIR ${CPM_ARGS_SOURCE_SUBDIR}) - endif() - # For CMake version <3.28 OPTIONS are parsed in cpm_add_subdirectory - if(CPM_ARGS_OPTIONS AND NOT DOWNLOAD_ONLY) - foreach(OPTION ${CPM_ARGS_OPTIONS}) - cpm_parse_option("${OPTION}") - set(${OPTION_KEY} "${OPTION_VALUE}") - endforeach() - endif() - endif() - cpm_declare_fetch( - "${CPM_ARGS_NAME}" ${fetchContentDeclareExtraArgs} "${CPM_ARGS_UNPARSED_ARGUMENTS}" - ) - - cpm_fetch_package("${CPM_ARGS_NAME}" ${DOWNLOAD_ONLY} populated ${CPM_ARGS_UNPARSED_ARGUMENTS}) - if(CPM_SOURCE_CACHE AND download_directory) - file(LOCK ${download_directory}/../cmake.lock RELEASE) - endif() - if(${populated} AND ${CMAKE_VERSION} VERSION_LESS "3.30.3") - cpm_add_subdirectory( - "${CPM_ARGS_NAME}" - "${DOWNLOAD_ONLY}" - "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}" - "${${CPM_ARGS_NAME}_BINARY_DIR}" - "${CPM_ARGS_EXCLUDE_FROM_ALL}" - "${CPM_ARGS_SYSTEM}" - "${CPM_ARGS_OPTIONS}" - ) - endif() - cpm_get_fetch_properties("${CPM_ARGS_NAME}") - endif() + cpm_get_fetch_properties("${CPM_ARGS_NAME}") set(${CPM_ARGS_NAME}_ADDED YES) cpm_export_variables("${CPM_ARGS_NAME}") endfunction() -# Fetch a previously declared package -macro(CPMGetPackage Name) - if(DEFINED "CPM_DECLARATION_${Name}") - CPMAddPackage(NAME ${Name}) - else() - message(SEND_ERROR "${CPM_INDENT} Cannot retrieve package ${Name}: no declaration available") - endif() -endmacro() - -# export variables available to the caller to the parent scope expects ${CPM_ARGS_NAME} to be set +# export variables available to the caller to the +# parent scope expects ${CPM_ARGS_NAME} to be set macro(cpm_export_variables name) - set(${name}_SOURCE_DIR - "${${name}_SOURCE_DIR}" - PARENT_SCOPE - ) - set(${name}_BINARY_DIR - "${${name}_BINARY_DIR}" - PARENT_SCOPE - ) - set(${name}_ADDED - "${${name}_ADDED}" - PARENT_SCOPE - ) - set(CPM_LAST_PACKAGE_NAME - "${name}" - PARENT_SCOPE - ) -endmacro() - -# declares a package, so that any call to CPMAddPackage for the package name will use these -# arguments instead. Previous declarations will not be overridden. -macro(CPMDeclarePackage Name) - if(NOT DEFINED "CPM_DECLARATION_${Name}") - set("CPM_DECLARATION_${Name}" "${ARGN}") - endif() -endmacro() - -function(cpm_add_to_package_lock Name) - if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) - cpm_prettify_package_arguments(PRETTY_ARGN false ${ARGN}) - file(APPEND ${CPM_PACKAGE_LOCK_FILE} "# ${Name}\nCPMDeclarePackage(${Name}\n${PRETTY_ARGN})\n") - endif() -endfunction() - -function(cpm_add_comment_to_package_lock Name) - if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) - cpm_prettify_package_arguments(PRETTY_ARGN true ${ARGN}) - file(APPEND ${CPM_PACKAGE_LOCK_FILE} - "# ${Name} (unversioned)\n# CPMDeclarePackage(${Name}\n${PRETTY_ARGN}#)\n" - ) - endif() -endfunction() - -# includes the package lock file if it exists and creates a target `cpm-update-package-lock` to -# update it -macro(CPMUsePackageLock file) - if(NOT CPM_DONT_CREATE_PACKAGE_LOCK) - get_filename_component(CPM_ABSOLUTE_PACKAGE_LOCK_PATH ${file} ABSOLUTE) - if(EXISTS ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) - include(${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}) - endif() - if(NOT TARGET cpm-update-package-lock) - add_custom_target( - cpm-update-package-lock COMMAND ${CMAKE_COMMAND} -E copy ${CPM_PACKAGE_LOCK_FILE} - ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH} - ) - endif() - set(CPM_PACKAGE_LOCK_ENABLED true) - endif() + set(${name}_SOURCE_DIR "${${name}_SOURCE_DIR}" PARENT_SCOPE) + set(${name}_BINARY_DIR "${${name}_BINARY_DIR}" PARENT_SCOPE) + set(${name}_ADDED "${${name}_ADDED}" PARENT_SCOPE) + set(CPM_LAST_PACKAGE_NAME "${name}" PARENT_SCOPE) endmacro() # registers a package that has been added to CPM function(CPMRegisterPackage PACKAGE VERSION) list(APPEND CPM_PACKAGES ${PACKAGE}) - set(CPM_PACKAGES - ${CPM_PACKAGES} - CACHE INTERNAL "" - ) - set("CPM_PACKAGE_${PACKAGE}_VERSION" - ${VERSION} - CACHE INTERNAL "" - ) + set(CPM_PACKAGES ${CPM_PACKAGES} CACHE INTERNAL "") + set("CPM_PACKAGE_${PACKAGE}_VERSION" ${VERSION}CACHE INTERNAL "") endfunction() # retrieve the current version of the package to ${OUTPUT} function(CPMGetPackageVersion PACKAGE OUTPUT) - set(${OUTPUT} - "${CPM_PACKAGE_${PACKAGE}_VERSION}" - PARENT_SCOPE - ) + set(${OUTPUT} "${CPM_PACKAGE_${PACKAGE}_VERSION}" PARENT_SCOPE) endfunction() -# declares a package in FetchContent_Declare -function(cpm_declare_fetch PACKAGE) - if(${CPM_DRY_RUN}) - cpm_message(STATUS "${CPM_INDENT} Package not declared (dry run)") - return() - endif() - - FetchContent_Declare(${PACKAGE} ${ARGN}) -endfunction() - -# returns properties for a package previously defined by cpm_declare_fetch function(cpm_get_fetch_properties PACKAGE) - if(${CPM_DRY_RUN}) - return() - endif() + set(${PACKAGE}_SOURCE_DIR "${CPM_PACKAGE_${PACKAGE}_SOURCE_DIR}" + PARENT_SCOPE) - set(${PACKAGE}_SOURCE_DIR - "${CPM_PACKAGE_${PACKAGE}_SOURCE_DIR}" - PARENT_SCOPE - ) - set(${PACKAGE}_BINARY_DIR - "${CPM_PACKAGE_${PACKAGE}_BINARY_DIR}" - PARENT_SCOPE - ) + set(${PACKAGE}_BINARY_DIR "${CPM_PACKAGE_${PACKAGE}_BINARY_DIR}" + PARENT_SCOPE) endfunction() function(cpm_store_fetch_properties PACKAGE source_dir binary_dir) - if(${CPM_DRY_RUN}) - return() - endif() - - set(CPM_PACKAGE_${PACKAGE}_SOURCE_DIR - "${source_dir}" - CACHE INTERNAL "" - ) - set(CPM_PACKAGE_${PACKAGE}_BINARY_DIR - "${binary_dir}" - CACHE INTERNAL "" - ) -endfunction() - -# adds a package as a subdirectory if viable, according to provided options -function( - cpm_add_subdirectory - PACKAGE - DOWNLOAD_ONLY - SOURCE_DIR - BINARY_DIR - EXCLUDE - SYSTEM - OPTIONS -) - - if(NOT DOWNLOAD_ONLY AND EXISTS ${SOURCE_DIR}/CMakeLists.txt) - set(addSubdirectoryExtraArgs "") - if(EXCLUDE) - list(APPEND addSubdirectoryExtraArgs EXCLUDE_FROM_ALL) - endif() - if("${SYSTEM}" AND "${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.25") - # https://cmake.org/cmake/help/latest/prop_dir/SYSTEM.html#prop_dir:SYSTEM - list(APPEND addSubdirectoryExtraArgs SYSTEM) - endif() - if(OPTIONS) - foreach(OPTION ${OPTIONS}) - cpm_parse_option("${OPTION}") - set(${OPTION_KEY} "${OPTION_VALUE}") - endforeach() - endif() - set(CPM_OLD_INDENT "${CPM_INDENT}") - set(CPM_INDENT "${CPM_INDENT} ${PACKAGE}:") - add_subdirectory(${SOURCE_DIR} ${BINARY_DIR} ${addSubdirectoryExtraArgs}) - set(CPM_INDENT "${CPM_OLD_INDENT}") - endif() -endfunction() - -# downloads a previously declared package via FetchContent and exports the variables -# `${PACKAGE}_SOURCE_DIR` and `${PACKAGE}_BINARY_DIR` to the parent scope -function(cpm_fetch_package PACKAGE DOWNLOAD_ONLY populated) - set(${populated} - FALSE - PARENT_SCOPE - ) - if(${CPM_DRY_RUN}) - cpm_message(STATUS "${CPM_INDENT} Package ${PACKAGE} not fetched (dry run)") - return() - endif() - - FetchContent_GetProperties(${PACKAGE}) - - string(TOLOWER "${PACKAGE}" lower_case_name) - - if(NOT ${lower_case_name}_POPULATED) - if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.30.3") - if(DOWNLOAD_ONLY) - # MakeAvailable will call add_subdirectory internally which is not what we want when - # DOWNLOAD_ONLY is set. Populate will only download the dependency without adding it to the - # build - FetchContent_Populate( - ${PACKAGE} - SOURCE_DIR "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-src" - BINARY_DIR "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-build" - SUBBUILD_DIR "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild" - ${ARGN} - ) - else() - FetchContent_MakeAvailable(${PACKAGE}) - endif() - else() - FetchContent_Populate(${PACKAGE}) - endif() - set(${populated} - TRUE - PARENT_SCOPE - ) - endif() - - cpm_store_fetch_properties( - ${CPM_ARGS_NAME} ${${lower_case_name}_SOURCE_DIR} ${${lower_case_name}_BINARY_DIR} - ) - - set(${PACKAGE}_SOURCE_DIR - ${${lower_case_name}_SOURCE_DIR} - PARENT_SCOPE - ) - set(${PACKAGE}_BINARY_DIR - ${${lower_case_name}_BINARY_DIR} - PARENT_SCOPE - ) + set(CPM_PACKAGE_${PACKAGE}_SOURCE_DIR "${source_dir}" CACHE INTERNAL "") + set(CPM_PACKAGE_${PACKAGE}_BINARY_DIR "${binary_dir}" CACHE INTERNAL "") endfunction() # splits a package option @@ -1237,6 +313,7 @@ function(cpm_parse_option OPTION) string(REGEX MATCH "^[^ ]+" OPTION_KEY "${OPTION}") string(LENGTH "${OPTION}" OPTION_LENGTH) string(LENGTH "${OPTION_KEY}" OPTION_KEY_LENGTH) + if(OPTION_KEY_LENGTH STREQUAL OPTION_LENGTH) # no value for key provided, assume user wants to set option to "ON" set(OPTION_VALUE "ON") @@ -1244,122 +321,7 @@ function(cpm_parse_option OPTION) math(EXPR OPTION_KEY_LENGTH "${OPTION_KEY_LENGTH}+1") string(SUBSTRING "${OPTION}" "${OPTION_KEY_LENGTH}" "-1" OPTION_VALUE) endif() - set(OPTION_KEY - "${OPTION_KEY}" - PARENT_SCOPE - ) - set(OPTION_VALUE - "${OPTION_VALUE}" - PARENT_SCOPE - ) -endfunction() - -# guesses the package version from a git tag -function(cpm_get_version_from_git_tag GIT_TAG RESULT) - string(LENGTH ${GIT_TAG} length) - if(length EQUAL 40) - # GIT_TAG is probably a git hash - set(${RESULT} - 0 - PARENT_SCOPE - ) - else() - string(REGEX MATCH "v?([0123456789.]*).*" _ ${GIT_TAG}) - set(${RESULT} - ${CMAKE_MATCH_1} - PARENT_SCOPE - ) - endif() -endfunction() - -# guesses if the git tag is a commit hash or an actual tag or a branch name. -function(cpm_is_git_tag_commit_hash GIT_TAG RESULT) - string(LENGTH "${GIT_TAG}" length) - # full hash has 40 characters, and short hash has at least 7 characters. - if(length LESS 7 OR length GREATER 40) - set(${RESULT} - 0 - PARENT_SCOPE - ) - else() - if(${GIT_TAG} MATCHES "^[a-fA-F0-9]+$") - set(${RESULT} - 1 - PARENT_SCOPE - ) - else() - set(${RESULT} - 0 - PARENT_SCOPE - ) - endif() - endif() -endfunction() - -function(cpm_prettify_package_arguments OUT_VAR IS_IN_COMMENT) - set(oneValueArgs - NAME - FORCE - VERSION - GIT_TAG - DOWNLOAD_ONLY - GITHUB_REPOSITORY - GITLAB_REPOSITORY - BITBUCKET_REPOSITORY - GIT_REPOSITORY - SOURCE_DIR - FIND_PACKAGE_ARGUMENTS - NO_CACHE - SYSTEM - GIT_SHALLOW - EXCLUDE_FROM_ALL - SOURCE_SUBDIR - ) - set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND) - cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - foreach(oneArgName ${oneValueArgs}) - if(DEFINED CPM_ARGS_${oneArgName}) - if(${IS_IN_COMMENT}) - string(APPEND PRETTY_OUT_VAR "#") - endif() - if(${oneArgName} STREQUAL "SOURCE_DIR") - string(REPLACE ${CMAKE_SOURCE_DIR} "\${CMAKE_SOURCE_DIR}" CPM_ARGS_${oneArgName} - ${CPM_ARGS_${oneArgName}} - ) - endif() - string(APPEND PRETTY_OUT_VAR " ${oneArgName} ${CPM_ARGS_${oneArgName}}\n") - endif() - endforeach() - foreach(multiArgName ${multiValueArgs}) - if(DEFINED CPM_ARGS_${multiArgName}) - if(${IS_IN_COMMENT}) - string(APPEND PRETTY_OUT_VAR "#") - endif() - string(APPEND PRETTY_OUT_VAR " ${multiArgName}\n") - foreach(singleOption ${CPM_ARGS_${multiArgName}}) - if(${IS_IN_COMMENT}) - string(APPEND PRETTY_OUT_VAR "#") - endif() - string(APPEND PRETTY_OUT_VAR " \"${singleOption}\"\n") - endforeach() - endif() - endforeach() - - if(NOT "${CPM_ARGS_UNPARSED_ARGUMENTS}" STREQUAL "") - if(${IS_IN_COMMENT}) - string(APPEND PRETTY_OUT_VAR "#") - endif() - string(APPEND PRETTY_OUT_VAR " ") - foreach(CPM_ARGS_UNPARSED_ARGUMENT ${CPM_ARGS_UNPARSED_ARGUMENTS}) - string(APPEND PRETTY_OUT_VAR " ${CPM_ARGS_UNPARSED_ARGUMENT}") - endforeach() - string(APPEND PRETTY_OUT_VAR "\n") - endif() - - set(${OUT_VAR} - ${PRETTY_OUT_VAR} - PARENT_SCOPE - ) - + + set(OPTION_KEY "${OPTION_KEY}" PARENT_SCOPE) + set(OPTION_VALUE "${OPTION_VALUE}" PARENT_SCOPE) endfunction() diff --git a/CMakeModules/CPMUtil.cmake b/CMakeModules/CPMUtil.cmake index b992f24083..a628e3fc89 100644 --- a/CMakeModules/CPMUtil.cmake +++ b/CMakeModules/CPMUtil.cmake @@ -1,44 +1,399 @@ # SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later -set(CPM_SOURCE_CACHE "${PROJECT_SOURCE_DIR}/.cache/cpm" CACHE STRING "" FORCE) +cmake_minimum_required(VERSION 3.31) -if(MSVC OR ANDROID) +if(MSVC OR ANDROID OR IOS) set(BUNDLED_DEFAULT ON) else() set(BUNDLED_DEFAULT OFF) endif() +# TODO: Handle nested cpmutil includes + +# Root directory detection +if (CMAKE_SCRIPT_MODE_FILE) + # In script mode, default to PWD + # Or you can specify one yourself + if (NOT DEFINED CPMUTIL_ROOT) + set(CPMUTIL_ROOT ${CMAKE_SOURCE_DIR}) + endif() +else() + # A project exists, we can use its directory + set(CPMUTIL_ROOT ${PROJECT_SOURCE_DIR}) +endif() + +set(CPM_SOURCE_CACHE "${CPMUTIL_ROOT}/.cache/cpm" + CACHE PATH "Directory to download CPM dependencies") + option(CPMUTIL_FORCE_BUNDLED "Force bundled packages for all CPM depdendencies" ${BUNDLED_DEFAULT}) option(CPMUTIL_FORCE_SYSTEM - "Force system packages for all CPM dependencies (NOT RECOMMENDED)" OFF) + "Force system packages for all CPM dependencies" OFF) + +set(CPMUTIL_PATCH_DIR "${CPMUTIL_ROOT}/.patch" CACHE STRING + "Directory containing patches for packages") -cmake_minimum_required(VERSION 3.22) include(CPM) +# Rudimentary target architecture detection +# This is in script mode, CI packages will be nonfunctional. TODO +if (NOT DEFINED CMAKE_SYSTEM_PROCESSOR) + message(DEBUG "[CPMUtil] No system architecture found, " + "CI packages are nonfunctional") +elseif (NOT DEFINED ARCHITECTURE) + string(TOLOWER ${CMAKE_SYSTEM_PROCESSOR} processor) + if (processor MATCHES "x86|amd64") + set(CPMUTIL_AMD64 ON) + elseif(processor MATCHES "^aarch64|^arm64|^armv8\.*") + set(CPMUTIL_ARM64 ON) + elseif(processor MATCHES "riscv") + set(CPMUTIL_RISCV64 ON) + endif() +else() + # This block exists for compatibility with my own DetectArchitecture.cmake. + if (ARCHITECTURE_x86_64) + set(CPMUTIL_AMD64 ON) + elseif(ARCHITECTURE_arm64) + set(CPMUTIL_ARM64 ON) + elseif(ARCHITECTURE_riscv64) + set(CPMUTIL_RISCV64 ON) + endif() +endif() + # cpmfile parsing -set(CPMUTIL_JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cpmfile.json") +set(CPMUTIL_JSON_FILE "${CPMUTIL_ROOT}/cpmfile.json") if(EXISTS ${CPMUTIL_JSON_FILE}) file(READ ${CPMUTIL_JSON_FILE} CPMFILE_CONTENT) - if (NOT TARGET cpmfiles) - add_custom_target(cpmfiles) - endif() - target_sources(cpmfiles PRIVATE ${CPMUTIL_JSON_FILE}) - set_property(DIRECTORY APPEND PROPERTY - CMAKE_CONFIGURE_DEPENDS - "${CPMUTIL_JSON_FILE}") + # Script mode doesn't support add_custom_target etc. + if (NOT CMAKE_SCRIPT_MODE_FILE) + if (NOT TARGET cpmfiles) + add_custom_target(cpmfiles) + endif() + + target_sources(cpmfiles PRIVATE ${CPMUTIL_JSON_FILE}) + set_property(DIRECTORY APPEND PROPERTY + CMAKE_CONFIGURE_DEPENDS + "${CPMUTIL_JSON_FILE}") + endif() else() - message(DEBUG "[CPMUtil] cpmfile ${CPMUTIL_JSON_FILE}" + message(DEBUG "[CPMUtil] cpmfile ${CPMUTIL_JSON_FILE} " "does not exist, AddJsonPackage will be a no-op") endif() -# Utility stuff -function(cpm_utils_message level name message) - message(${level} "[CPMUtil] ${name}: ${message}") +############# +# UTILITIES # +############# + +# Analogous to POSIX echo +macro(echo) + string(REPLACE ";" " " message "${ARGN}") + if (CMAKE_SCRIPT_MODE_FILE) + execute_process(COMMAND ${CMAKE_COMMAND} -E echo + "${message}") + else() + message(DEBUG "${message}") + endif() +endmacro() + +# Analogous to POSIX echo, but to stderr +macro(echo_error) + string(REPLACE ";" " " message "${ARGN}") + message(NOTICE "${message}") +endmacro() + +# Fatal error, accounting for script mode +macro(fatal) + if (CMAKE_SCRIPT_MODE_FILE) + echo_error(${ARGN}) + cmake_language(EXIT 1) + else() + message(FATAL_ERROR ${ARGN}) + endif() +endmacro() + +# Analogous to POSIX sleep +macro(sleep time) + execute_process(COMMAND ${CMAKE_COMMAND} -E sleep ${time}) +endmacro() + +# Get a package's effective URL. +function(get_package_url) + set(oneValueArgs + GIT_HOST + REPO + VERSION + ARTIFACT + PACKAGE + + URL_OUT + GIT_URL_OUT) + + cmake_parse_arguments(ARG "" "${oneValueArgs}" "" ${ARGN}) + + if (NOT DEFINED ARG_URL_OUT) + fatal("get_package_url: URL_OUT is required") + endif() + + if(NOT DEFINED ARG_GIT_HOST) + set(ARG_GIT_HOST github.com) + endif() + + if(DEFINED ARG_REPO) + set(url https://${ARG_GIT_HOST}/${ARG_REPO}) + + if (DEFINED ARG_GIT_URL_OUT) + set(${ARG_GIT_URL_OUT} "${url}") + endif() + + if(DEFINED ARG_ARTIFACT) + set(url + "${url}/releases/download/${ARG_VERSION}/${ARG_ARTIFACT}") + else() + set(url "${url}/archive/${ARG_VERSION}.tar.gz") + endif() + else() + fatal("${package}: No URL or repository defined") + endif() + + set(${ARG_URL_OUT} "${url}") + + return(PROPAGATE ${ARG_URL_OUT} ${ARG_GIT_URL_OUT}) +endfunction() + +# Get a package's cache path. +function(get_cache_path package key out) + string(TOLOWER ${package} lower_name) + + # TODO: Figure out a sln to CPM_SOURCE_CACHE; CPMConfig.cmake? + set(${out} ${CPM_SOURCE_CACHE}/${lower_name}/${key}) + + return(PROPAGATE ${out}) +endfunction() + +# Download a URL to file, with a sha512 hash +# And retry 5 times +function(cpm_download url file) + echo("Downloading ${url} to ${file}") + list(LENGTH ARGN argn_len) + if(argn_len GREATER 0) + list(GET ARGN 0 hash) + set(args EXPECTED_HASH SHA512=${hash}) + else() + unset(args) + endif() + + foreach(i RANGE 5) + file(DOWNLOAD ${url} ${file} + ${args} + STATUS ret) + + list(GET ret 0 code) + if (code EQUAL 0) + break() + endif() + + echo_error("Download failed. Trying again in 5 seconds") + sleep(5) + endforeach() + + # TODO: use return code or something + if (NOT code EQUAL 0) + fatal("Download for ${pkg_url} failed after 5 tries") + endif() +endfunction() + +# Wrapper around find_program that works with Git for Windows +macro(cpm_find_program) + # Windows needs additional paths for some utilities. + if (CMAKE_HOST_WIN32) + find_package(Git QUIET) + if(Git_FOUND) + # Search within the Git for Windows paths. + get_filename_component(extra_search_path + ${GIT_EXECUTABLE} DIRECTORY) + get_filename_component(extra_search_path_1up + ${extra_search_path} DIRECTORY) + get_filename_component(extra_search_path_2up + ${extra_search_path_1up} DIRECTORY) + + set(base_hints "${extra_search_path_1up}/usr/bin" + "${extra_search_path_2up}/usr/bin") + + # Also add core_perl to the paths, for perl commands like json_pp + set(hints "") + foreach(hint ${base_hints}) + list(APPEND hints + "${hint}" + "${hint}/core_perl") + endforeach() + + find_program(${ARGN} HINTS ${hints}) + endif() + + # If no Git is found, continue as normal + endif() + + find_program(${ARGN}) +endmacro() + +# Apply patches to a directory. +function(apply_patches patches dir) + cpm_find_program(PATCH_EXE patch) + if (NOT PATCH_EXE) + fatal("Could not find patch executable") + endif() + + foreach(patch ${patches}) + get_filename_component(patch_name ${patch} NAME) + echo("-- Applying patch ${patch_name}") + execute_process( + COMMAND ${PATCH_EXE} -p1 + INPUT_FILE ${patch} + WORKING_DIRECTORY ${dir}) + endforeach() +endfunction() + +# Fetches a file to the CPM source cache. +function(fetch_package) + set(oneValueArgs + URL + HASH + PATH + + PATCH_KEY) + + set(multiValueArgs PATCHES) + + set(optionArgs FORCE) + + cmake_parse_arguments(ARG + "${optionArgs}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT DEFINED ARG_URL) + fatal("fetch_package: URL is required") + endif() + + if (NOT DEFINED ARG_HASH) + fatal("fetch_package: HASH is required") + endif() + + if (NOT DEFINED ARG_PATH) + fatal("fetch_package: PATH is required") + endif() + + if (NOT DEFINED ARG_PATCH_KEY) + compute_patch_key("${ARG_PATCHES}" ARG_PATCH_KEY) + endif() + + # refetch if cache is invalid or forced + needs_refetch(${ARG_PATH} "${ARG_PATCH_KEY}" CACHE_INVALID) + + if (ARG_FORCE OR CACHE_INVALID) + file(REMOVE_RECURSE ${ARG_PATH}) + else() + return() + endif() + + # paths + cmake_path(ABSOLUTE_PATH ARG_PATH + NORMALIZE + OUTPUT_VARIABLE abs_path) + + cmake_path(GET abs_path PARENT_PATH path_parent) + file(MAKE_DIRECTORY ${path_parent}) + + # Get filename from URL + get_filename_component(base_filename ${ARG_URL} NAME) + + # Download + set(file ${path_parent}/${base_filename}) + cpm_download(${ARG_URL} ${file} ${ARG_HASH}) + echo("Downloaded ${base_filename}") + + # Extract + echo("Extracting ${file}...") + file(ARCHIVE_EXTRACT + INPUT ${file} + DESTINATION ${abs_path}) + + # This is copied near-verbatim from ExternalProject/extractfile.cmake.in + + # If there's just one subdirectory and nothing else, move it + file(GLOB contents "${abs_path}/*") + list(REMOVE_ITEM contents "${abs_path}/.DS_Store") + list(LENGTH contents n) + + # If n == 1 and contents points to a directory, this is a GitHub-style pack + # In this case contents points to the subdir which will get renamed + # If not, contents will point to the parent dir which will get renamed + if (n EQUAL 1 AND IS_DIRECTORY "${contents}") + set(temp_path "${abs_path}_tmp") + file(RENAME "${contents}" "${temp_path}") + file(REMOVE_RECURSE "${abs_path}") + file(RENAME "${temp_path}" "${abs_path}") + endif() + + # TODO: only echo this in script mode + echo("Extracted to ${abs_path}") + + # Apply patches + apply_patches("${ARG_PATCHES}" "${abs_path}") + + # Write patch key + file(WRITE "${abs_path}/.cpm_patch_key" ${ARG_PATCH_KEY}) + + # done! :) + file(REMOVE_RECURSE ${file}) +endfunction() + +# compute a hash of all patch file contents +# if there are no patches, returns none +function(compute_patch_key patches out) + if(NOT patches) + set("${out}" "none" PARENT_SCOPE) + return() + endif() + + set(combined "") + foreach(PATCH ${patches}) + file(READ "${PATCH}" contents) + string(APPEND combined "${contents}") + endforeach() + + string(SHA512 key "${combined}") + set("${out}" "${key}" PARENT_SCOPE) +endfunction() + +# Check if a package needs to be redownloaded. This can occur if: +# - Patch key is missing or mismatched +# - Package fetch dir is missing +# path is the package cache path +function(needs_refetch path patch_key out) + set(patch_key_file "${path}/.cpm_patch_key") + + # download directory is empty or patch key file is missing + if (NOT EXISTS ${patch_key_file}) + set(${out} TRUE PARENT_SCOPE) + return() + endif() + + # compare patch key + file(READ "${patch_key_file}" current_patch_key) + if (NOT current_patch_key STREQUAL patch_key) + set(${out} TRUE PARENT_SCOPE) + else() + set(${out} FALSE PARENT_SCOPE) + endif() +endfunction() + +# Message +function(cpm_utils_message level) + string(REPLACE ";" " " message "${ARGN}") + message(${level} "[CPMUtil] ${message}") endfunction() # propagate a variable to parent scope @@ -46,6 +401,18 @@ macro(Propagate var) set(${var} ${${var}} PARENT_SCOPE) endmacro() +# Get cpmfile content +function(get_cpmfile_content out) + set(${out} "${CPMFILE_CONTENT}" PARENT_SCOPE) +endfunction() + +# Get absolute path to cpmfile +function(get_cpmfile_path out) + file(REAL_PATH ${CPMUTIL_JSON_FILE} ${out}) + return(PROPAGATE ${out}) +endfunction() + +# idk function(array_to_list array length out) math(EXPR range "${length} - 1") @@ -58,11 +425,22 @@ function(array_to_list array length out) set("${out}" "${NEW_LIST}" PARENT_SCOPE) endfunction() -function(get_json_element object out member default) +# json +function(get_json_element object out member) + list(LENGTH ARGN argn_len) + if(argn_len GREATER 0) + list(GET ARGN 0 default) + endif() + string(JSON out_type ERROR_VARIABLE err TYPE "${object}" ${member}) if(err) - set("${out}" "${default}" PARENT_SCOPE) + if (DEFINED default) + set("${out}" "${default}" PARENT_SCOPE) + else() + unset("${out}" PARENT_SCOPE) + endif() + return() endif() @@ -70,13 +448,19 @@ function(get_json_element object out member default) if(out_type STREQUAL "ARRAY") string(JSON _len LENGTH "${object}" ${member}) - # array_to_list("${outvar}" ${_len} outvar) set("${out}_LENGTH" "${_len}" PARENT_SCOPE) endif() set("${out}" "${outvar}" PARENT_SCOPE) endfunction() +# register a package to CPMUtil's global registry +function(cpmutil_register_package name url version) + set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_NAMES ${name}) + set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_URLS ${url}) + set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS "${version}") +endfunction() + # Determine whether or not a package has a viable system candidate. function(SystemPackageViable JSON_NAME) string(JSON object GET "${CPMFILE_CONTENT}" "${JSON_NAME}") @@ -84,14 +468,17 @@ function(SystemPackageViable JSON_NAME) parse_object(${object}) string(REPLACE " " ";" find_args "${find_args}") - if (${package}_FORCE_BUNDLED) + if(${package}_FORCE_BUNDLED OR + (CPMUTIL_FORCE_BUNDLED + AND NOT ${package}_FORCE_SYSTEM + AND NOT CPMUTIL_FORCE_SYSTEM)) set(${package}_FOUND OFF) else() - find_package(${package} ${version} ${find_args} QUIET NO_POLICY_SCOPE) + find_package(${package} ${min_version} ${find_args} QUIET NO_POLICY_SCOPE) endif() - set(${pkg}_VIABLE ${${package}_FOUND} PARENT_SCOPE) - set(${pkg}_PACKAGE ${package} PARENT_SCOPE) + set(${JSON_NAME}_VIABLE ${${package}_FOUND} PARENT_SCOPE) + set(${JSON_NAME}_PACKAGE ${package} PARENT_SCOPE) endfunction() # Add several packages such that if one is bundled, @@ -128,7 +515,8 @@ function(AddDependentPackages) message(FATAL_ERROR "Partial dependency installation detected " "for the following packages:\n${package_names}\n" "You can solve this in one of two ways:\n" - "1. Install the following packages to your system if available:" + "1. Install or upgrade the following packages " + "to your system if available:" "\n\t${bundled_names}\n" "2. Set the following variables to ON:" "\n\t${system_names}\n" @@ -141,18 +529,39 @@ function(AddDependentPackages) endforeach() endfunction() +# process version/artifact replacements +macro(process_version_replacements) + # numeric version text replacement + if (numeric_version) + string(REPLACE "%NUMERIC_VERSION%" "${numeric_version}" + version ${version}) + string(REPLACE "%NUMERIC_VERSION%" "${numeric_version}" + artifact ${artifact}) + endif() + + # version text replacement + if (artifact) + string(REPLACE "%VERSION%" "${version}" + artifact ${artifact}) + endif() +endmacro() + # json util macro(parse_object object) get_json_element("${object}" package package ${JSON_NAME}) - get_json_element("${object}" repo repo "") + get_json_element("${object}" repo repo) get_json_element("${object}" ci ci OFF) - get_json_element("${object}" version version "") + get_json_element("${object}" version version) + get_json_element("${object}" git_host git_host "github.com") + + if (NOT version) + cpm_utils_message(FATAL_ERROR "${JSON_NAME}: version is required") + endif() if(ci) get_json_element("${object}" name name "${JSON_NAME}") get_json_element("${object}" extension extension "tar.zst") - get_json_element("${object}" min_version min_version "") - get_json_element("${object}" raw_disabled disabled_platforms "") + get_json_element("${object}" raw_disabled disabled_platforms) if(raw_disabled) array_to_list("${raw_disabled}" @@ -161,44 +570,21 @@ macro(parse_object object) set(disabled_platforms "") endif() else() - get_json_element("${object}" hash hash "") - get_json_element("${object}" hash_suffix hash_suffix "") - get_json_element("${object}" sha sha "") - get_json_element("${object}" url url "") - get_json_element("${object}" key key "") - 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}" hash hash) + get_json_element("${object}" min_version min_version) + get_json_element("${object}" url url) + get_json_element("${object}" artifact artifact) + 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 "") + get_json_element("${object}" numeric_version numeric_version) + get_json_element("${object}" find_args find_args) + get_json_element("${object}" skip_updates skip_updates OFF) + 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() + process_version_replacements() # format patchdir + unset(patches) if(raw_patches) math(EXPR range "${raw_patches_LENGTH} - 1") @@ -206,10 +592,11 @@ macro(parse_object object) string(JSON _patch GET "${raw_patches}" "${IDX}") set(full_patch - "${PROJECT_SOURCE_DIR}/.patch/${JSON_NAME}/${_patch}") + "${CPMUTIL_PATCH_DIR}/${JSON_NAME}/${_patch}") if(NOT EXISTS ${full_patch}) - cpm_utils_message(FATAL_ERROR ${JSON_NAME} - "specifies patch ${full_patch} which does not exist") + cpm_utils_message(FATAL_ERROR + "${JSON_NAME} specifies patch" + "${full_patch} which does not exist") endif() list(APPEND patches "${full_patch}") @@ -218,8 +605,9 @@ macro(parse_object object) # end format patchdir # options - get_json_element("${object}" raw_options options "") + get_json_element("${object}" raw_options options) + unset(options) if(raw_options) array_to_list("${raw_options}" ${raw_options_LENGTH} options) endif() @@ -234,6 +622,29 @@ macro(parse_object object) endif() endmacro() +# Get the JSON object for a key. Outputs to "object" +function(get_json_object key) + if(NOT DEFINED CPMFILE_CONTENT) + cpm_utils_message(FATAL_ERROR "${key}: No cpmfile present") + endif() + + if(NOT DEFINED key) + cpm_utils_message(FATAL_ERROR "No JSON key specified") + endif() + + string(JSON object ERROR_VARIABLE + err GET "${CPMFILE_CONTENT}" "${key}") + + if(err) + cpm_utils_message(FATAL_ERROR "${key} not found in cpmfile\n" + "${err}") + endif() + + Propagate(object) +endfunction() + +# Get the download URL given the parameters. + # The preferred usage function(AddJsonPackage) set(oneValueArgs @@ -241,13 +652,16 @@ function(AddJsonPackage) # these are overrides that can be generated at runtime, # so can be defined separately from the json - DOWNLOAD_ONLY - BUNDLED_PACKAGE FORCE_BUNDLED_PACKAGE) set(multiValueArgs OPTIONS) - cmake_parse_arguments(JSON "" "${oneValueArgs}" "${multiValueArgs}" + set(optionArgs MODULE_PATH DOWNLOAD_ONLY) + + cmake_parse_arguments(JSON + "${optionArgs}" + "${oneValueArgs}" + "${multiValueArgs}" "${ARGN}") list(LENGTH ARGN argnLength) @@ -257,35 +671,30 @@ function(AddJsonPackage) set(JSON_NAME "${ARGV0}") endif() - if(NOT DEFINED CPMFILE_CONTENT) - cpm_utils_message(WARNING ${name} - "No cpmfile, AddJsonPackage is a no-op") - return() - endif() - - if(NOT DEFINED JSON_NAME) - cpm_utils_message(FATAL_ERROR "json package" "No name specified") - endif() - - string(JSON object ERROR_VARIABLE - err GET "${CPMFILE_CONTENT}" "${JSON_NAME}") - - if(err) - cpm_utils_message(FATAL_ERROR ${JSON_NAME} "Not found in cpmfile") - endif() - + get_json_object("${JSON_NAME}") parse_object(${object}) + unset(EXTRA_ARGS) + if (JSON_MODULE_PATH) + list(APPEND EXTRA_ARGS MODULE_PATH) + endif() + + if (JSON_DOWNLOAD_ONLY) + list(APPEND EXTRA_ARGS DOWNLOAD_ONLY) + endif() + if(ci) AddCIPackage( - VERSION ${version} - NAME ${name} - REPO ${repo} - PACKAGE ${package} - EXTENSION ${extension} - MIN_VERSION ${min_version} - DISABLED_PLATFORMS ${disabled_platforms}) + VERSION "${version}" + NAME "${name}" + REPO "${repo}" + PACKAGE "${package}" + EXTENSION "${extension}" + DISABLED_PLATFORMS "${disabled_platforms}" + GIT_HOST "${git_host}" + + ${EXTRA_ARGS}) else() if (NOT DEFINED JSON_FORCE_BUNDLED_PACKAGE) set(JSON_FORCE_BUNDLED_PACKAGE OFF) @@ -294,12 +703,10 @@ function(AddJsonPackage) AddPackage( NAME "${package}" VERSION "${version}" + MIN_VERSION "${min_version}" URL "${url}" HASH "${hash}" - HASH_SUFFIX "${hash_suffix}" - SHA "${sha}" REPO "${repo}" - KEY "${key}" PATCHES "${patches}" OPTIONS "${options}" FIND_PACKAGE_ARGUMENTS "${find_args}" @@ -307,72 +714,59 @@ function(AddJsonPackage) FORCE_BUNDLED_PACKAGE "${JSON_FORCE_BUNDLED_PACKAGE}" SOURCE_SUBDIR "${source_subdir}" - GIT_VERSION ${git_version} - GIT_HOST ${git_host} + GIT_HOST "${git_host}" - ARTIFACT ${artifact} - TAG ${tag}) + ARTIFACT "${artifact}" + + ${EXTRA_ARGS}) endif() # pass stuff to parent scope Propagate(${package}_ADDED) Propagate(${package}_SOURCE_DIR) Propagate(${package}_BINARY_DIR) + Propagate(CMAKE_PREFIX_PATH) endfunction() +# Internal function function(AddPackage) - cpm_set_policies() + set(EXTRA_ARGS "") - # TODO(crueter): git clone? - - #[[ - URL configurations, descending order of precedence: - - URL [+ GIT_URL] -> bare URL fetch - - REPO + TAG + ARTIFACT -> github release artifact - - REPO + TAG -> github release archive - - REPO + SHA -> github commit archive - - REPO + BRANCH -> github branch - - Hash configurations, descending order of precedence: - - HASH -> bare sha512sum - - HASH_SUFFIX -> hash grabbed from the URL + this suffix - - HASH_URL -> hash grabbed from a URL - * technically this is unsafe since a hacker can attack that url - - NOTE: hash algo defaults to sha512 - #]] set(oneValueArgs NAME - VERSION - GIT_VERSION + MIN_VERSION GIT_HOST REPO - TAG + VERSION ARTIFACT - SHA - BRANCH HASH - HASH_SUFFIX - HASH_URL - HASH_ALGO URL - GIT_URL + CUSTOM_KEY - KEY + SOURCE_SUBDIR BUNDLED_PACKAGE FORCE_BUNDLED_PACKAGE FIND_PACKAGE_ARGUMENTS) set(multiValueArgs OPTIONS PATCHES) - cmake_parse_arguments(PKG_ARGS "" "${oneValueArgs}" "${multiValueArgs}" + set(optionArgs MODULE_PATH DOWNLOAD_ONLY) + + cmake_parse_arguments(PKG_ARGS + "${optionArgs}" + "${oneValueArgs}" + "${multiValueArgs}" "${ARGN}") if(NOT DEFINED PKG_ARGS_NAME) - cpm_utils_message(FATAL_ERROR "package" "No package name defined") + cpm_utils_message(FATAL_ERROR "AddPackage: NAME is required") + endif() + + if(NOT DEFINED PKG_ARGS_VERSION) + cpm_utils_message(FATAL_ERROR "${PKG_ARGS_NAME}: VERSION is required") endif() set(${PKG_ARGS_NAME}_CUSTOM_DIR "" CACHE STRING @@ -382,232 +776,163 @@ function(AddPackage) option(${PKG_ARGS_NAME}_FORCE_BUNDLED "Force the bundled package for ${PKG_ARGS_NAME}") - if (DEFINED ${PKG_ARGS_NAME}_CUSTOM_DIR AND - NOT ${PKG_ARGS_NAME}_CUSTOM_DIR STREQUAL "") - set(CPM_${PKG_ARGS_NAME}_SOURCE ${${PKG_ARGS_NAME}_CUSTOM_DIR}) - endif() - - if(NOT DEFINED PKG_ARGS_GIT_HOST) - set(git_host github.com) + # URL parsing + if (NOT DEFINED PKG_ARGS_URL) + get_package_url(URL_OUT "PKG_ARGS_URL" + GIT_URL_OUT "pkg_git_url" + GIT_HOST "${PKG_ARGS_GIT_HOST}" + REPO "${PKG_ARGS_REPO}" + VERSION "${PKG_ARGS_VERSION}" + ARTIFACT "${PKG_ARGS_ARTIFACT}" + PACKAGE "${PKG_ARGS_NAME}") else() - set(git_host ${PKG_ARGS_GIT_HOST}) + set(pkg_git_url ${PKG_ARGS_URL}) endif() - if(DEFINED PKG_ARGS_URL) - set(pkg_url ${PKG_ARGS_URL}) + cpm_utils_message(DEBUG "${PKG_ARGS_NAME} download URL is ${PKG_ARGS_URL}") - if(DEFINED 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}) - else() - set(pkg_git_url ${pkg_url}) - endif() - endif() - elseif(DEFINED PKG_ARGS_REPO) - set(pkg_git_url https://${git_host}/${PKG_ARGS_REPO}) - - if(DEFINED PKG_ARGS_TAG) - set(pkg_key ${PKG_ARGS_TAG}) - - if(DEFINED PKG_ARGS_ARTIFACT) - set(pkg_url - "${pkg_git_url}/releases/download/${PKG_ARGS_TAG}/${PKG_ARGS_ARTIFACT}") - else() - set(pkg_url - ${pkg_git_url}/archive/refs/tags/${PKG_ARGS_TAG}.tar.gz) - endif() - elseif(DEFINED PKG_ARGS_SHA) - set(pkg_url "${pkg_git_url}/archive/${PKG_ARGS_SHA}.tar.gz") - else() - if(DEFINED PKG_ARGS_BRANCH) - set(PKG_BRANCH ${PKG_ARGS_BRANCH}) - else() - cpm_utils_message(WARNING ${PKG_ARGS_NAME} - "REPO defined but no TAG, SHA, BRANCH, or URL" - "specified, defaulting to master") - set(PKG_BRANCH master) - endif() - - set(pkg_url ${pkg_git_url}/archive/refs/heads/${PKG_BRANCH}.tar.gz) - endif() - else() - cpm_utils_message(FATAL_ERROR ${PKG_ARGS_NAME} - "No URL or repository defined") + if(NOT DEFINED PKG_ARGS_HASH) + cpm_utils_message(FATAL_ERROR "${PKG_ARGS_NAME}: No hash defined") endif() - cpm_utils_message(DEBUG ${PKG_ARGS_NAME} "Download URL is ${pkg_url}") - - 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 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") - endif() - else() - set(pkg_key ${PKG_ARGS_KEY}) - endif() - - if(DEFINED PKG_ARGS_HASH_ALGO) - set(hash_algo ${PKG_ARGS_HASH_ALGO}) - else() - set(hash_algo SHA512) - endif() - - if(DEFINED PKG_ARGS_HASH) - set(pkg_hash "${hash_algo}=${PKG_ARGS_HASH}") - elseif(DEFINED PKG_ARGS_HASH_SUFFIX) - # funny sanity check - string(TOLOWER ${hash_algo} hash_algo_lower) - string(TOLOWER ${PKG_ARGS_HASH_SUFFIX} suffix_lower) - if(NOT ${suffix_lower} MATCHES ${hash_algo_lower}) - cpm_utils_message(WARNING - "Hash algorithm and hash suffix do not match, errors may occur") - endif() - - set(hash_url ${pkg_url}.${PKG_ARGS_HASH_SUFFIX}) - elseif(DEFINED PKG_ARGS_HASH_URL) - set(hash_url ${PKG_ARGS_HASH_URL}) - else() - cpm_utils_message(WARNING ${PKG_ARGS_NAME} - "No hash or hash URL found") - endif() - - if(DEFINED hash_url) - set(outfile ${CMAKE_CURRENT_BINARY_DIR}/${PKG_ARGS_NAME}.hash) - - # TODO(crueter): This is kind of a bad solution - # because "technically" the hash is invalidated each week - # but it works for now kjsdnfkjdnfjksdn - string(TOLOWER ${PKG_ARGS_NAME} lowername) - if(NOT EXISTS ${outfile} AND NOT EXISTS - ${CPM_SOURCE_CACHE}/${lowername}/${pkg_key}) - file(DOWNLOAD ${hash_url} ${outfile}) - endif() - - if(EXISTS ${outfile}) - file(READ ${outfile} pkg_hash_tmp) - endif() - - if(DEFINED ${pkg_hash_tmp}) - set(pkg_hash "${hash_algo}=${pkg_hash_tmp}") - endif() - endif() - - macro(set_precedence local force) - set(CPM_USE_LOCAL_PACKAGES ${local}) - set(CPM_LOCAL_PACKAGES_ONLY ${force}) - endmacro() - #[[ Precedence: - - package_FORCE_SYSTEM - - package_FORCE_BUNDLED - - CPMUTIL_FORCE_SYSTEM - - CPMUTIL_FORCE_BUNDLED - - BUNDLED_PACKAGE - - default to allow local + - FORCE_BUNDLED_PACKAGE (caller override) + - package_FORCE_SYSTEM, package_FORCE_BUNDLED (user option) + - CPMUTIL_FORCE_SYSTEM, CPMUTIL_FORCE_BUNDLED (global option) + - BUNDLED_PACKAGE (json default) + - default to allow system ]] if(PKG_ARGS_FORCE_BUNDLED_PACKAGE) - set_precedence(OFF OFF) + set(use_system OFF) elseif(${PKG_ARGS_NAME}_FORCE_SYSTEM) - set_precedence(ON ON) + set(use_system ON) + set(force_system ON) elseif(${PKG_ARGS_NAME}_FORCE_BUNDLED) - set_precedence(OFF OFF) + set(use_system OFF) elseif(CPMUTIL_FORCE_SYSTEM) - set_precedence(ON ON) + set(use_system ON) + set(force_system ON) elseif(CPMUTIL_FORCE_BUNDLED) - set_precedence(OFF OFF) + set(use_system OFF) elseif(DEFINED PKG_ARGS_BUNDLED_PACKAGE AND NOT PKG_ARGS_BUNDLED_PACKAGE STREQUAL "unset") if(PKG_ARGS_BUNDLED_PACKAGE) - set(local OFF) + set(use_system OFF) else() - set(local ON) + set(use_system ON) endif() - - set_precedence(${local} OFF) else() - set_precedence(ON OFF) + set(use_system ON) endif() - if(DEFINED PKG_ARGS_VERSION) - list(APPEND EXTRA_ARGS - VERSION ${PKG_ARGS_VERSION}) + # find_package nonsense + if(use_system) + set(find_args ${PKG_ARGS_NAME}) + if(DEFINED PKG_ARGS_MIN_VERSION) + list(APPEND find_args ${PKG_ARGS_MIN_VERSION}) + endif() + + if (DEFINED PKG_ARGS_FIND_PACKAGE_ARGUMENTS) + string(REPLACE " " ";" + passed_find_args + "${PKG_ARGS_FIND_PACKAGE_ARGUMENTS}") + + set(find_args "${find_args};${passed_find_args}") + endif() + + find_package(${find_args} QUIET) + + if(${PKG_ARGS_NAME}_FOUND) + if(DEFINED ${PKG_ARGS_NAME}_VERSION) + set(sys_ver ${${PKG_ARGS_NAME}_VERSION}) + else() + set(sys_ver ${PKG_ARGS_VERSION}) + endif() + + message(STATUS + "[CPMUtil] Using system package ${PKG_ARGS_NAME}@${sys_ver}") + + CPMRegisterPackage(${PKG_ARGS_NAME} "${sys_ver}") + + cpmutil_register_package( + ${PKG_ARGS_NAME} + ${pkg_git_url} + "${sys_ver} (system)") + + set(${PKG_ARGS_NAME}_ADDED NO PARENT_SCOPE) + + return() + endif() + + if(force_system) + string(REPLACE ";" " " str_find_args "${find_args}") + message(FATAL_ERROR "[CPMUtil] ${PKG_ARGS_NAME} not found via " + "find_package(${str_find_args})") + endif() + endif() + + # extra options + if (PKG_ARGS_OPTIONS) + list(APPEND EXTRA_ARGS OPTIONS "${PKG_ARGS_OPTIONS}") + endif() + + if (PKG_ARGS_SOURCE_SUBDIR) + list(APPEND EXTRA_ARGS SOURCE_SUBDIR "${PKG_ARGS_SOURCE_SUBDIR}") + endif() + + if (PKG_ARGS_DOWNLOAD_ONLY OR PKG_ARGS_MODULE_PATH) + list(APPEND EXTRA_ARGS DOWNLOAD_ONLY ON) + endif() + + cpm_utils_message(STATUS + "Using bundled package ${PKG_ARGS_NAME}@${PKG_ARGS_VERSION}") + + # Download/extract package + if (${PKG_ARGS_NAME}_CUSTOM_DIR STREQUAL "") + if (DEFINED PKG_ARGS_CUSTOM_KEY) + set(key ${PKG_ARGS_CUSTOM_KEY}) + else() + set(key ${PKG_ARGS_VERSION}) + endif() + + # cache path + get_cache_path(${PKG_ARGS_NAME} ${key} cache_path) + + fetch_package( + URL "${PKG_ARGS_URL}" + HASH "${PKG_ARGS_HASH}" + PATH "${cache_path}" + PATCHES "${PKG_ARGS_PATCHES}") + + set(source_dir "${cache_path}") + else() + set(source_dir "${${PKG_ARGS_NAME}_CUSTOM_DIR}") endif() CPMAddPackage( NAME ${PKG_ARGS_NAME} - URL ${pkg_url} - URL_HASH ${pkg_hash} - CUSTOM_CACHE_KEY ${pkg_key} - DOWNLOAD_ONLY ${PKG_ARGS_DOWNLOAD_ONLY} - FIND_PACKAGE_ARGUMENTS ${PKG_ARGS_FIND_PACKAGE_ARGUMENTS} + VERSION ${PKG_ARGS_VERSION} + SOURCE_DIR ${source_dir} - OPTIONS ${PKG_ARGS_OPTIONS} - PATCHES ${PKG_ARGS_PATCHES} EXCLUDE_FROM_ALL ON ${EXTRA_ARGS} - ${PKG_ARGS_UNPARSED_ARGUMENTS}) - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_NAMES ${PKG_ARGS_NAME}) - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_URLS ${pkg_git_url}) + cpmutil_register_package( + ${PKG_ARGS_NAME} + ${pkg_git_url} + ${PKG_ARGS_VERSION}) - if(${PKG_ARGS_NAME}_ADDED) - if(DEFINED PKG_ARGS_SHA) - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS - ${PKG_ARGS_SHA}) - 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") - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS "unknown") - endif() - else() - if(DEFINED CPM_PACKAGE_${PKG_ARGS_NAME}_VERSION AND NOT - "${CPM_PACKAGE_${PKG_ARGS_NAME}_VERSION}" STREQUAL "") - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS - "${CPM_PACKAGE_${PKG_ARGS_NAME}_VERSION} (system)") - else() - set_property(GLOBAL APPEND PROPERTY CPM_PACKAGE_SHAS - "unknown (system)") - endif() + set(${PKG_ARGS_NAME}_ADDED YES PARENT_SCOPE) + Propagate(${PKG_ARGS_NAME}_SOURCE_DIR) + Propagate(${PKG_ARGS_NAME}_BINARY_DIR) + + if (PKG_ARGS_MODULE_PATH) + list(PREPEND CMAKE_PREFIX_PATH "${${ARTIFACT_PACKAGE}_SOURCE_DIR}") + Propagate(CMAKE_PREFIX_PATH) endif() - - # pass stuff to parent scope - set(${PKG_ARGS_NAME}_ADDED "${${PKG_ARGS_NAME}_ADDED}" - PARENT_SCOPE) - set(${PKG_ARGS_NAME}_SOURCE_DIR "${${PKG_ARGS_NAME}_SOURCE_DIR}" - PARENT_SCOPE) - set(${PKG_ARGS_NAME}_BINARY_DIR "${${PKG_ARGS_NAME}_BINARY_DIR}" - PARENT_SCOPE) - endfunction() # TODO(crueter): we could do an AddMultiArchPackage, multiplatformpackage? @@ -619,11 +944,12 @@ function(AddCIPackage) REPO PACKAGE EXTENSION - MIN_VERSION) + GIT_HOST + PKGNAME) set(multiValueArgs DISABLED_PLATFORMS) - set(optionArgs MODULE) + set(optionArgs MODULE_PATH) cmake_parse_arguments(PKG_ARGS "${optionArgs}" @@ -632,22 +958,19 @@ function(AddCIPackage) ${ARGN}) if(NOT DEFINED PKG_ARGS_VERSION) - message(FATAL_ERROR "[CPMUtil] VERSION is required") - endif() - if(NOT DEFINED PKG_ARGS_NAME) - message(FATAL_ERROR "[CPMUtil] NAME is required") - endif() - if(NOT DEFINED PKG_ARGS_REPO) - message(FATAL_ERROR "[CPMUtil] REPO is required") - endif() - if(NOT DEFINED PKG_ARGS_PACKAGE) - message(FATAL_ERROR "[CPMUtil] PACKAGE is required") + cpm_utils_message(FATAL_ERROR "VERSION is required") endif() - if(NOT DEFINED PKG_ARGS_CMAKE_FILENAME) - set(ARTIFACT_CMAKE ${PKG_ARGS_NAME}) - else() - set(ARTIFACT_CMAKE ${PKG_ARGS_CMAKE_FILENAME}) + if(NOT DEFINED PKG_ARGS_NAME) + cpm_utils_message(FATAL_ERROR "NAME is required") + endif() + + if(NOT DEFINED PKG_ARGS_REPO) + cpm_utils_message(FATAL_ERROR "REPO is required") + endif() + + if(NOT DEFINED PKG_ARGS_PACKAGE) + cpm_utils_message(FATAL_ERROR "PACKAGE is required") endif() if(NOT DEFINED PKG_ARGS_EXTENSION) @@ -656,8 +979,10 @@ function(AddCIPackage) set(ARTIFACT_EXT ${PKG_ARGS_EXTENSION}) endif() - if(DEFINED PKG_ARGS_MIN_VERSION) - set(ARTIFACT_MIN_VERSION ${PKG_ARGS_MIN_VERSION}) + if (NOT DEFINED PKG_ARGS_GIT_HOST) + set(ARTIFACT_GIT_HOST "github.com") + else() + set(ARTIFACT_GIT_HOST "${PKG_ARGS_GIT_HOST}") endif() if(DEFINED PKG_ARGS_DISABLED_PLATFORMS) @@ -670,75 +995,152 @@ function(AddCIPackage) set(ARTIFACT_REPO ${PKG_ARGS_REPO}) set(ARTIFACT_PACKAGE ${PKG_ARGS_PACKAGE}) - if(MSVC AND ARCHITECTURE_x86_64) - set(pkgname windows-amd64) - elseif(MSVC AND ARCHITECTURE_arm64) - set(pkgname windows-arm64) - elseif(MINGW AND ARCHITECTURE_x86_64) - set(pkgname mingw-amd64) - elseif(MINGW AND ARCHITECTURE_arm64) - set(pkgname mingw-arm64) - elseif(ANDROID AND ARCHITECTURE_x86_64) - set(pkgname android-x86_64) - elseif(ANDROID AND ARCHITECTURE_arm64) - set(pkgname android-aarch64) - elseif(PLATFORM_SUN) - set(pkgname solaris-amd64) - elseif(PLATFORM_FREEBSD) - set(pkgname freebsd-amd64) - elseif(PLATFORM_LINUX AND ARCHITECTURE_x86_64) - set(pkgname linux-amd64) - elseif(PLATFORM_LINUX AND ARCHITECTURE_arm64) - set(pkgname linux-aarch64) - elseif(APPLE) - set(pkgname macos-universal) + # plat/archname + if (DEFINED PKG_ARGS_PKGNAME) + set(pkgname ${PKG_ARGS_PKGNAME}) + else() + if (MSVC) + set(platname windows) + elseif(MINGW) + set(platname mingw) + elseif(ANDROID) + set(platname android) + elseif(LINUX) + set(platname linux) + elseif(IOS) + set(platname ios) + elseif(APPLE) + set(platname macos) + else() + cpm_utils_message(WARNING + "Unsupported platform ${CMAKE_SYSTEM_NAME} for CI packages") + endif() + + if (CPMUTIL_AMD64) + set(archname amd64) + elseif(CPMUTIL_ARM64) + set(archname aarch64) + elseif(CPMUTIL_RISCV64) + set(archname riscv64) + endif() + + if (APPLE AND NOT CPMUTIL_ARM64) + cpm_utils_message(WARNING + "Unsupported platform/arch combo for CI packages") + endif() + + if (DEFINED platname AND DEFINED archname) + set(pkgname ${platname}-${archname}) + endif() endif() - if (DEFINED pkgname AND NOT "${pkgname}" IN_LIST DISABLED_PLATFORMS) + if (DEFINED pkgname + AND NOT "${pkgname}" IN_LIST DISABLED_PLATFORMS) set(ARTIFACT "${ARTIFACT_NAME}-${pkgname}-${ARTIFACT_VERSION}.${ARTIFACT_EXT}") + if (PKG_ARGS_MODULE_PATH) + set(EXTRA_ARGS MODULE_PATH) + endif() + + # download sha512sum file + # TODO: CI pkgs + set(sha512sum_url + "https://${ARTIFACT_GIT_HOST}/${ARTIFACT_REPO}/releases/download/${ARTIFACT_VERSION}/${ARTIFACT}.sha512sum") + set(sha512sum_file + "${CMAKE_CURRENT_BINARY_DIR}/.cpmutil_${ARTIFACT}_sha512sum") + + file(DOWNLOAD "${sha512sum_url}" "${sha512sum_file}" + STATUS sha512sum_status) + list(GET sha512sum_status 0 sha512sum_error) + + if(sha512sum_error) + message(FATAL_ERROR "[CPMUtil] Failed to download sha512sum " + "for ${ARTIFACT_NAME} from ${sha512sum_url}") + endif() + + file(READ "${sha512sum_file}" sha512sum_hash) + string(STRIP "${sha512sum_hash}" sha512sum_hash) + file(REMOVE "${sha512sum_file}") + AddPackage( NAME ${ARTIFACT_PACKAGE} REPO ${ARTIFACT_REPO} - TAG "v${ARTIFACT_VERSION}" - GIT_VERSION ${ARTIFACT_VERSION} + VERSION ${ARTIFACT_VERSION} ARTIFACT ${ARTIFACT} - - KEY "${pkgname}-${ARTIFACT_VERSION}" - HASH_SUFFIX sha512sum + CUSTOM_KEY "${ARTIFACT_VERSION}-${pkgname}" + HASH ${sha512sum_hash} FORCE_BUNDLED_PACKAGE ON - DOWNLOAD_ONLY ${PKG_ARGS_MODULE}) + ${EXTRA_ARGS}) set(${ARTIFACT_PACKAGE}_ADDED TRUE PARENT_SCOPE) - set(${ARTIFACT_PACKAGE}_SOURCE_DIR - "${${ARTIFACT_PACKAGE}_SOURCE_DIR}" PARENT_SCOPE) - - if (PKG_ARGS_MODULE) - list(APPEND CMAKE_PREFIX_PATH "${${ARTIFACT_PACKAGE}_SOURCE_DIR}") - set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) - endif() + Propagate(${ARTIFACT_PACKAGE}_SOURCE_DIR) + Propagate(CMAKE_PREFIX_PATH) else() - find_package(${ARTIFACT_PACKAGE} ${ARTIFACT_MIN_VERSION} REQUIRED) + cpm_utils_message(FATAL_ERROR "${ARTIFACT_NAME}:" + "Unsupported platform ${pkgname} for CI package") endif() endfunction() # Utility function for Qt -function(AddQt version) +function(AddQt repo version) + if (NOT DEFINED repo) + message(FATAL_ERROR "[CPMUtil] AddQt: repo is required") + endif() + if (NOT DEFINED version) message(FATAL_ERROR "[CPMUtil] AddQt: version is required") endif() + # TODO: update Qt + if (MSVC) + set(platname windows) + elseif(MINGW) + set(platname mingw) + elseif(ANDROID) + set(platname android) + elseif(LINUX) + set(platname linux) + elseif(IOS) + set(platname ios) + elseif(APPLE) + set(platname macos) + else() + cpm_utils_message(WARNING + "Unsupported platform ${CMAKE_SYSTEM_NAME} for CI packages") + endif() + + if (APPLE AND NOT IOS) + set(archname universal) + elseif((WIN32 OR LINUX) AND CPMUTIL_AMD64) + set(archname amd64) + elseif(WIN32 AND CPMUTIL_ARM64) + set(archname arm64) + elseif((IOS OR LINUX OR ANDROID) AND CPMUTIL_ARM64) + set(archname aarch64) + elseif(ANDROID AND CPMUTIL_AMD64) + set(archname x86_64) + else() + cpm_utils_message(WARNING + "Unsupported platform/arch combo for CI packages") + endif() + + if (DEFINED platname AND DEFINED archname) + set(pkgname ${platname}-${archname}) + endif() + AddCIPackage( - NAME Qt + NAME qt PACKAGE Qt6 VERSION ${version} - MIN_VERSION 6 - REPO crueter-ci/Qt + REPO ${repo} DISABLED_PLATFORMS android-x86_64 android-aarch64 - freebsd-amd64 solaris-amd64 openbsd-amd64 - MODULE) + MODULE_PATH + PKGNAME ${pkgname}) - set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) + find_package(Qt6 REQUIRED PATHS ${Qt6_SOURCE_DIR} NO_DEFAULT_PATH) + + Propagate(CMAKE_PREFIX_PATH) + Propagate(Qt6_SOURCE_DIR) endfunction() diff --git a/CMakeModules/WindowsCopyFiles.cmake b/CMakeModules/WindowsCopyFiles.cmake deleted file mode 100644 index a4afeb77bf..0000000000 --- a/CMakeModules/WindowsCopyFiles.cmake +++ /dev/null @@ -1,39 +0,0 @@ -# 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 - -# This file provides the function windows_copy_files. -# This is only valid on Windows. - -# Include guard -if(__windows_copy_files) - return() -endif() -set(__windows_copy_files YES) - -# Any number of files to copy from SOURCE_DIR to DEST_DIR can be specified after DEST_DIR. -# This copying happens post-build. -if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") - function(windows_copy_files TARGET SOURCE_DIR DEST_DIR) - # windows commandline expects the / to be \ so switch them - string(REPLACE "/" "\\\\" SOURCE_DIR ${SOURCE_DIR}) - string(REPLACE "/" "\\\\" DEST_DIR ${DEST_DIR}) - - # /NJH /NJS /NDL /NFL /NC /NS /NP - Silence any output - # cmake adds an extra check for command success which doesn't work too well with robocopy - # so trick it into thinking the command was successful with the || cmd /c "exit /b 0" - add_custom_command(TARGET ${TARGET} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory ${DEST_DIR} - COMMAND robocopy ${SOURCE_DIR} ${DEST_DIR} ${ARGN} /NJH /NJS /NDL /NFL /NC /NS /NP || cmd /c "exit /b 0" - ) - endfunction() -else() - function(windows_copy_files TARGET SOURCE_DIR DEST_DIR) - add_custom_command(TARGET ${TARGET} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory ${DEST_DIR} - COMMAND cp -ra ${SOURCE_DIR}/. ${DEST_DIR} - ) - endfunction() -endif() diff --git a/CMakeModules/aqt_config.ini b/CMakeModules/aqt_config.ini deleted file mode 100644 index c87caf4988..0000000000 --- a/CMakeModules/aqt_config.ini +++ /dev/null @@ -1,30 +0,0 @@ -# SPDX-FileCopyrightText: 2024 kleidis - -[aqt] -concurrency: 2 - -[mirrors] -trusted_mirrors: - https://download.qt.io -blacklist: - https://qt.mirror.constant.com - https://mirrors.ocf.berkeley.edu - https://mirrors.ustc.edu.cn - https://mirrors.tuna.tsinghua.edu.cn - https://mirrors.geekpie.club - https://mirrors-wan.geekpie.club - https://mirrors.sjtug.sjtu.edu.cn -fallbacks: - https://qtproject.mirror.liquidtelecom.com/ - https://mirrors.aliyun.com/qt/ - https://ftp.jaist.ac.jp/pub/qtproject/ - https://ftp.yz.yamagata-u.ac.jp/pub/qtproject/ - https://qt-mirror.dannhauer.de/ - https://ftp.fau.de/qtproject/ - https://mirror.netcologne.de/qtproject/ - https://mirrors.dotsrc.org/qtproject/ - https://www.nic.funet.fi/pub/mirrors/download.qt-project.org/ - https://master.qt.io/ - https://mirrors.ukfast.co.uk/sites/qt.io/ - https://ftp2.nluug.nl/languages/qt/ - https://ftp1.nluug.nl/languages/qt/ diff --git a/CMakeModules/FindDiscordRPC.cmake b/CMakeModules/find/FindDiscordRPC.cmake similarity index 93% rename from CMakeModules/FindDiscordRPC.cmake rename to CMakeModules/find/FindDiscordRPC.cmake index 5ae9f4f999..a498cb0f26 100644 --- a/CMakeModules/FindDiscordRPC.cmake +++ b/CMakeModules/find/FindDiscordRPC.cmake @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later # SPDX-FileCopyrightText: 2022 Alexandre Bouvier diff --git a/CMakeModules/FindFFmpeg.cmake b/CMakeModules/find/FindFFmpeg.cmake similarity index 98% rename from CMakeModules/FindFFmpeg.cmake rename to CMakeModules/find/FindFFmpeg.cmake index edd0e0a882..11dd08acd5 100644 --- a/CMakeModules/FindFFmpeg.cmake +++ b/CMakeModules/find/FindFFmpeg.cmake @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later # SPDX-FileCopyrightText: 2019 Citra Emulator Project diff --git a/CMakeModules/FindLLVM.cmake b/CMakeModules/find/FindLLVM.cmake similarity index 82% rename from CMakeModules/FindLLVM.cmake rename to CMakeModules/find/FindLLVM.cmake index 8dc064d5d1..f169f134d5 100644 --- a/CMakeModules/FindLLVM.cmake +++ b/CMakeModules/find/FindLLVM.cmake @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # SPDX-FileCopyrightText: 2023 Alexandre Bouvier # # SPDX-License-Identifier: GPL-3.0-or-later @@ -13,7 +16,8 @@ endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(LLVM HANDLE_COMPONENTS CONFIG_MODE) -if (LLVM_FOUND AND LLVM_Demangle_FOUND AND NOT TARGET LLVM::Demangle) +# Demangle only for Windows targets +if (WIN32 AND LLVM_FOUND AND LLVM_Demangle_FOUND AND NOT TARGET LLVM::Demangle) add_library(LLVM::Demangle INTERFACE IMPORTED) target_compile_definitions(LLVM::Demangle INTERFACE ${LLVM_DEFINITIONS}) target_include_directories(LLVM::Demangle INTERFACE ${LLVM_INCLUDE_DIRS}) diff --git a/CMakeModules/FindOpus.cmake b/CMakeModules/find/FindOpus.cmake similarity index 86% rename from CMakeModules/FindOpus.cmake rename to CMakeModules/find/FindOpus.cmake index 5557cd1baf..257642682e 100644 --- a/CMakeModules/FindOpus.cmake +++ b/CMakeModules/find/FindOpus.cmake @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later # SPDX-FileCopyrightText: 2022 yuzu Emulator Project @@ -13,7 +13,7 @@ find_package_handle_standard_args(Opus VERSION_VAR OPUS_VERSION ) -if (PLATFORM_MSYS) +if (MSYS2) FixMsysPath(PkgConfig::OPUS) endif() diff --git a/CMakeModules/FindRenderDoc.cmake b/CMakeModules/find/FindRenderDoc.cmake similarity index 83% rename from CMakeModules/FindRenderDoc.cmake rename to CMakeModules/find/FindRenderDoc.cmake index 2678b936bc..8ac4271a36 100644 --- a/CMakeModules/FindRenderDoc.cmake +++ b/CMakeModules/find/FindRenderDoc.cmake @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # SPDX-FileCopyrightText: 2023 Alexandre Bouvier # # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/CMakeModules/FindSimpleIni.cmake b/CMakeModules/find/FindSimpleIni.cmake similarity index 86% rename from CMakeModules/FindSimpleIni.cmake rename to CMakeModules/find/FindSimpleIni.cmake index 13426b25ba..1d2379fc82 100644 --- a/CMakeModules/FindSimpleIni.cmake +++ b/CMakeModules/find/FindSimpleIni.cmake @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # SPDX-FileCopyrightText: 2023 Alexandre Bouvier # # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/CMakeModules/Findenet.cmake b/CMakeModules/find/Findenet.cmake similarity index 86% rename from CMakeModules/Findenet.cmake rename to CMakeModules/find/Findenet.cmake index d80e737ebb..0b366542fe 100644 --- a/CMakeModules/Findenet.cmake +++ b/CMakeModules/find/Findenet.cmake @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later # SPDX-FileCopyrightText: 2022 Alexandre Bouvier @@ -14,7 +14,7 @@ find_package_handle_standard_args(enet VERSION_VAR ENET_VERSION ) -if (PLATFORM_MSYS) +if (MSYS2) FixMsysPath(PkgConfig::ENET) endif() diff --git a/CMakeModules/Findgamemode.cmake b/CMakeModules/find/Findgamemode.cmake similarity index 81% rename from CMakeModules/Findgamemode.cmake rename to CMakeModules/find/Findgamemode.cmake index aa2f366832..3ec966c82d 100644 --- a/CMakeModules/Findgamemode.cmake +++ b/CMakeModules/find/Findgamemode.cmake @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/CMakeModules/Findhttplib.cmake b/CMakeModules/find/Findhttplib.cmake similarity index 90% rename from CMakeModules/Findhttplib.cmake rename to CMakeModules/find/Findhttplib.cmake index 48967add96..e92a9be63f 100644 --- a/CMakeModules/Findhttplib.cmake +++ b/CMakeModules/find/Findhttplib.cmake @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # SPDX-FileCopyrightText: 2022 Andrea Pappacoda # # SPDX-License-Identifier: GPL-2.0-or-later diff --git a/CMakeModules/Findlibiw.cmake b/CMakeModules/find/Findlibiw.cmake similarity index 92% rename from CMakeModules/Findlibiw.cmake rename to CMakeModules/find/Findlibiw.cmake index 1d13d7705a..3381db0f08 100644 --- a/CMakeModules/Findlibiw.cmake +++ b/CMakeModules/find/Findlibiw.cmake @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later find_package(PkgConfig QUIET) diff --git a/CMakeModules/Findlibusb.cmake b/CMakeModules/find/Findlibusb.cmake similarity index 87% rename from CMakeModules/Findlibusb.cmake rename to CMakeModules/find/Findlibusb.cmake index 87b174c5b4..65f19ea958 100644 --- a/CMakeModules/Findlibusb.cmake +++ b/CMakeModules/find/Findlibusb.cmake @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later # SPDX-FileCopyrightText: 2022 Alexandre Bouvier @@ -14,7 +14,7 @@ find_package_handle_standard_args(libusb VERSION_VAR LIBUSB_VERSION ) -if (PLATFORM_MSYS) +if (MSYS2) FixMsysPath(PkgConfig::LIBUSB) endif() diff --git a/CMakeModules/Findlz4.cmake b/CMakeModules/find/Findlz4.cmake similarity index 85% rename from CMakeModules/Findlz4.cmake rename to CMakeModules/find/Findlz4.cmake index af230166eb..77ac390160 100644 --- a/CMakeModules/Findlz4.cmake +++ b/CMakeModules/find/Findlz4.cmake @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later # SPDX-FileCopyrightText: 2022 yuzu Emulator Project @@ -13,7 +13,7 @@ else() find_package(PkgConfig QUIET) pkg_search_module(LZ4 QUIET IMPORTED_TARGET liblz4) - if (PLATFORM_MSYS) + if (MSYS2) FixMsysPath(PkgConfig::LZ4) endif() @@ -24,7 +24,7 @@ else() endif() if (lz4_FOUND AND NOT TARGET lz4::lz4) - if (TARGET LZ4::lz4_shared) + if (TARGET LZ4::lz4_shared AND NOT YUZU_STATIC_BUILD) add_library(lz4::lz4 ALIAS LZ4::lz4_shared) elseif (TARGET LZ4::lz4_static) add_library(lz4::lz4 ALIAS LZ4::lz4_static) diff --git a/CMakeModules/Findstb.cmake b/CMakeModules/find/Findstb.cmake similarity index 89% rename from CMakeModules/Findstb.cmake rename to CMakeModules/find/Findstb.cmake index bff9985802..4808c45397 100644 --- a/CMakeModules/Findstb.cmake +++ b/CMakeModules/find/Findstb.cmake @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + # SPDX-FileCopyrightText: 2023 Alexandre Bouvier # # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/CMakeModules/Findzstd.cmake b/CMakeModules/find/Findzstd.cmake similarity index 95% rename from CMakeModules/Findzstd.cmake rename to CMakeModules/find/Findzstd.cmake index 8e44b7a892..58f0aa6117 100644 --- a/CMakeModules/Findzstd.cmake +++ b/CMakeModules/find/Findzstd.cmake @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later # SPDX-FileCopyrightText: 2022 yuzu Emulator Project diff --git a/CMakeModules/toolchains/GentooCross.cmake b/CMakeModules/toolchains/GentooCross.cmake new file mode 100644 index 0000000000..0bb78795fd --- /dev/null +++ b/CMakeModules/toolchains/GentooCross.cmake @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +set(CROSS_TARGET "" CACHE STRING "Cross-compilation target (aarch64, etc)") +set(CROSS_PLATFORM "unknown-linux-gnu" CACHE STRING "Cross-compilation platform (e.g. unknown-linux-gnu)") +set(CROSS_COMPILER "gcc" CACHE STRING "Cross compiler type (gcc or clang)") + +if (NOT CROSS_TARGET) + message(FATAL_ERROR "GentooCross used without a valid CROSS_TARGET") +endif() + +set(prefix ${CROSS_TARGET}-${CROSS_PLATFORM}) + +set(CMAKE_SYSROOT /usr/${prefix}) + +if (CROSS_COMPILER STREQUAL "gcc") + set(CMAKE_C_COMPILER ${prefix}-gcc) + set(CMAKE_CXX_COMPILER ${prefix}-g++) +elseif (CROSS_COMPILER STREQUAL "clang") + set(CMAKE_C_COMPILER ${prefix}-clang) + set(CMAKE_CXX_COMPILER ${prefix}-clang++) +else() + message(FATAL_ERROR "Unsupported cross compiler type ${CROSS_COMPILER}") +endif() + +# search programs in the host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + +# search headers and libraries in the target environment +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + +# sanity checks +if (NOT IS_DIRECTORY ${CMAKE_SYSROOT}) + message(FATAL_ERROR "Invalid sysroot ${CMAKE_SYSROOT}." + "Double-check your CROSS_TARGET and CROSS_PLATFORM.") +endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f6851a2c65..c87dd11dbe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,57 @@ # Contributing -You want to contribute? Please consult [the development guide](./docs/Development.md). +Eden has lots of different ways that you can contribute to its efforts, even without knowing how to write code: -Don't forget to [get a git account](./docs/SIGNUP.md) - not a requirement per se but it's highly recommended. +- Donate! This will help us live our lives, pay for infrastructure/testing hardware, and more + - Liberapay: + - PayPal: `business@eden-emu.dev` + - Bitcoin: `bc1pknzdackezf6s5nxqwn6hx940a7e0k3lk7ggpczp9u4jn4a25lnyqrgvdxx` +- Join our [Discord](https://discord.gg/HstXbPch7X) community +- Submit [bug reports or feature requests](https://github.com/eden-emulator/Issue-Reports/issues), or on [Codeberg](https://codeberg.org/eden-emu/eden/issues) +- Contribute to our [translation efforts](./dist/languages/README.md) + +## Code Contributions + +Eden is free, open-source, copyleft software, licensed under the terms of the [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html). You would do well to familiarize yourself with the GPL, [its FAQ](https://www.gnu.org/licenses/gpl-faq.html), and [free software as a concept](https://www.gnu.org/philosophy/free-sw.html) before contributing. If these terms are not acceptable to you, then you shouldn't contribute. + +### Policies + +- AI usage is allowed, but we reserve the right to close and rework your PR if you don't fully understand the changes you're making. + - As a common courtesy, please disclose your AI usage under the PR description, as `Assisted-by: ()`. +- Usage of any form of profanity or otherwise unsavory language is discouraged but not strictly disallowed. +- New code must follow the same style as the surrounding codebase. Exceptions may be granted in certain cases. +- Maintainers reserve the right to change your patches and pull requests at will. We will try to avoid this. + - You should generally respect all decisions made by the [code owners](docs/CODEOWNERS) in your particular subsystem. + - However, if you feel they are overstepping or are incorrect, don't be afraid to stand your ground! Maintainers are not always correct. +- While we do *not* adhere to the terms of a formal code of conduct, you are expected to treat other developers, contributors, and community members respectfully. +- You **must** have basic knowledge of [Git](https://git-scm.com/learn). Knowing how to manage your branches and follow proper fork policies is a necessity. + +### Where do I start? + +Anywhere you like! If you are facing an issue and want to fix it, go ahead. For new features, you are heavily encouraged to open a feature request on GitHub, Codeberg, or Forgejo first, discussing the motivations, potential implementation, and user flow of your desired feature. Our UI/UX designers will work with you to refine your feature before you actually choose to implement it. + +You may also search for open issues on [GitHub](https://github.com/eden-emulator/Issue-Reports/issues), [Codeberg](https://codeberg.org/eden-emu/eden/issues), or [Forgejo](https://git.eden-emu.dev/eden-emu/eden/issues). For larger features/refactors, you should first express your interest in the issue to ensure another developer isn't already working on it. + +### Great! Can I contribute already? + +There are five primary ways to contribute. + +- Submit pull requests directly to our source tree + - You must first [create an account](https://git.eden-emu.dev/user/sign_up). + - You may use an internal account, or you may use a GitHub/Codeberg account linked to our instance. +- Submit pull requests to our [GitHub mirror](https://github.com/eden-emulator/Issue-Reports) + - Note that this is subject to being removed by DMCA, so don't rely on this. +- Submit pull requests to our [Codeberg mirror](https://codeberg.org/eden-emu/eden) + - Codeberg is not subject to draconian DMCA laws, but they have been hostile to emulators such as Torzu in the past. Thus, you shouldn't rely on this either. +- Submit patches to [`patch@eden-emu.dev`](mailto:patch@eden-emu.dev) + - These **must** be in `git format-patch` format. You should familiarize yourself with the [art of patching](https://www.gitkraken.com/learn/git/git-patch) beforehand. + - Alongside the contents of your email, please attach the patch/diff file itself for easy access and use. +- Email our developers at [`developers@eden-emu.dev`](mailto:developers@eden-emu.dev) with any of your relevant findings or code changes. + +To test your changes, ensure to read the [build documentation](./docs/Build.md) for your specific platform(s) to ensure everything compiles and works properly. You should also make sure that your branch is up-to-date with the upstream `master` branch before opening a pull request. + +## Non-code Contributions + +Alongside the other contribution methods listed up top, you can also choose to contribute through documentation, organization, or community guides. These can be done either through the code contribution methods described above, or created externally and shared via our Discord community. + +If you have an external tool/page that you believe would be handy to integrate/link into Eden, please additionally email our developers at [`developers@eden-emu.dev`](mailto:developers@eden-emu.dev). diff --git a/README.md b/README.md index 44a2b4c28b..7d3fe5572e 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,9 @@
-

Eden is a free and opensource (FOSS) Switch 1 emulator, derived from Yuzu and Sudachi - started by developer Camille LaVey. -It's written in C++ with portability in mind, with builds for Windows, Linux, macOS, Android, FreeBSD and more. +

Eden is a free and open-source (FOSS) Switch 1 emulator started by developer Camille LaVey. +
+Written in C++, with builds for Windows, Linux, macOS, Android, FreeBSD and more.

@@ -50,16 +51,9 @@ Check out our [website](https://eden-emu.dev) for the latest news on exciting fe [![Packaging status](https://repology.org/badge/vertical-allrepos/eden-emulator.svg)](https://repology.org/project/eden-emulator/versions) -## Development +## Contribute -Most of the development happens on our Git server. It is also where [our central repository](https://git.eden-emu.dev/eden-emu/eden) is hosted. For development discussions, please join us on [Discord](https://discord.gg/HstXbPch7X) or [Stoat](https://stt.gg/qKgFEAbH). -You can also follow us on [X (Twitter)](https://nitter.poast.org/edenemuofficial) for updates and announcements. - -If you would like to contribute, we are open to new developers and pull requests. Please ensure that your work is of a high standard and properly documented. You can also contact any of the developers on Discord or Stoat to learn more about the current state of the emulator. - -See the [sign-up instructions](docs/SIGNUP.md) for information on registration. - -Alternatively, if you wish to add translations, go to the [Eden project on Transifex](https://app.transifex.com/edenemu/eden-emulator) and review [the translations README](./dist/languages). +To contribute to Eden; be it financially, code, bug reports, or otherwise, see our [Contributing guidelines](./CONTRIBUTING.md). ## Documentation @@ -73,23 +67,24 @@ For information on provided development tooling, see the [Tools directory](./too ## Download -You can download the latest releases from [here](https://git.eden-emu.dev/eden-emu/eden/releases). +You can download the latest releases from [our release page](https://git.eden-emu.dev/eden-emu/eden/releases). Save us some bandwidth! We have [mirrors available](./docs/user/ThirdParty.md#mirrors) as well. -## Support - -If you enjoy the project and would like to support us financially, please check out our developers' [donation pages](https://eden-emu.dev/donations)! - -Any donations received will go towards things such as: -* Switch consoles to explore and reverse-engineer the hardware -* Switch games for testing, reverse-engineering, and implementing new features -* Web hosting and infrastructure setup -* Additional hardware (e.g. GPUs as needed to improve rendering support, other peripherals to add support for, etc.) -* CI Infrastructure - -If you would prefer to support us in a different way, please join our [Discord](https://discord.gg/HstXbPch7X) and talk to Camille or any of our other developers. - ## License Eden is licensed under the GPLv3 (or any later version). Refer to the [LICENSE.txt](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/LICENSE.txt) file. + +## Special thanks + +Super special thanks to Cloudflare for preventing the git server from blowing up. + +- Yuzu +- Ryujinx +- Sudachi +- Citron +- Torzu +- Suyu +- Ryubing + +And everyone who continues or had contributed to the project! <3 diff --git a/cpmfile.json b/cpmfile.json index 1bb29afae4..0032734712 100644 --- a/cpmfile.json +++ b/cpmfile.json @@ -1,118 +1,363 @@ { - "openssl": { - "ci": true, - "package": "OpenSSL", - "name": "openssl", - "repo": "crueter-ci/OpenSSL", - "version": "3.6.0-1cb0d36b39", - "min_version": "3" + "biscuit": { + "hash": "1229f345b014f7ca544dedb4edb3311e41ba736f9aa9a67f88b5f26f3c983288c6bb6cdedcfb0b8a02c63088a37e6a0d7ba97d9c2a4d721b213916327cffe28a", + "min_version": "0.9.1", + "repo": "lioncash/biscuit", + "version": "v0.19.0" }, "boost": { - "package": "Boost", - "repo": "boostorg/boost", - "tag": "boost-%VERSION%", - "artifact": "%TAG%-cmake.tar.xz", - "hash": "6ae6e94664fe7f2fb01976b59b276ac5df8085c7503fa829d810fbfe495960cfec44fa2c36e2cb23480bc19c956ed199d4952b02639a00a6c07625d4e7130c2d", - "git_version": "1.90.0", - "version": "1.57", + "artifact": "boost-%VERSION%.tar.zst", "find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem", + "hash": "681d97be4386767662e230b2e7c9754d45e709cc5db4e747c23c27d1f5b0e87770bdf19b0bc44dcb0e8349324887bbb8fffb432098f4d0ce74a5043021a90afc", + "min_version": "1.57", + "package": "Boost", + "repo": "eden-emulator/ext-boost", + "version": "1.92.0" + }, + "boost_headers": { + "bundled": true, + "hash": "c5fa2cd72f6e6666b7963b97bc359c75284b8fb540c30f3629a028b85270c9bc66c8a051383964f2bd4c1e005a4691593d15696e7ef39ea87cf6cff9e5691fb2", + "repo": "boostorg/headers", + "version": "boost-1.91.0" + }, + "catch2": { + "hash": "7eea385d79d88a5690cde131fe7ccda97d5c54ea09d6f515000d7bf07c828809d61c1ac99912c1ee507cf933f61c1c47ecdcc45df7850ffa82714034b0fccf35", + "min_version": "3.0.1", + "package": "Catch2", "patches": [ - "0001-clang-cl.patch" - ] + "0001-solaris-isnan-fix.patch" + ], + "repo": "catchorg/Catch2", + "version": "v3.13.0" + }, + "cpp-jwt": { + "find_args": "CONFIG", + "hash": "d11cbd5ddb3197b4c5ca15679bcd76a49963e7b530b7dd132db91e042925efa20dfb2c24ccfbe7ef82a7012af80deff0f72ee25851312ae80381a462df8534b8", + "min_version": "1.4", + "options": [ + "CPP_JWT_USE_VENDORED_NLOHMANN_JSON OFF" + ], + "patches": [ + "0001-fix-missing-decl.patch" + ], + "repo": "arun11299/cpp-jwt", + "version": "7f24eb4c32" + }, + "cubeb": { + "find_args": "CONFIG", + "hash": "8a4bcb2f83ba590f52c66626e895304a73eb61928dbc57777e1822e55378e3568366f17f9da4b80036cc2ef4ea9723c32abf6e7d9bbe00fb03654f0991596ab0", + "options": [ + "USE_SANITIZERS OFF", + "BUILD_TESTS OFF", + "BUILD_TOOLS OFF", + "BUNDLE_SPEEX ON" + ], + "repo": "mozilla/cubeb", + "version": "fa02160712" + }, + "discord-rpc": { + "find_args": "MODULE", + "hash": "8d680b3a16d6f6bf292ad823cf8635595ff986f2a49f758e3009f505b540a9d75194a8cf44cddea75736a8c3e3b5160166e716a0939ce3f18cc463cf648753fe", + "package": "DiscordRPC", + "repo": "eden-emulator/discord-rpc", + "version": "76616d8675" + }, + "enet": { + "find_args": "MODULE", + "hash": "a0d2fa8c957704dd49e00a726284ac5ca034b50b00d2b20a94fa1bbfbb80841467834bfdc84aa0ed0d6aab894608fd6c86c3b94eee46343f0e6d9c22e391dbf9", + "min_version": "1.3", + "repo": "lsalzman/enet", + "version": "v1.3.18" + }, + "ffmpeg": { + "hash": "ed177621176b3961bdcaa339187d3a7688c1c8b060b79c4bb0257cbc67ad7021ae5d5adca5303b45625abbbe3d9aafdd87ce777b8690ac295290d744c875489a", + "repo": "FFmpeg/FFmpeg", + "version": "c7b5f1537d" + }, + "ffmpeg-ci": { + "ci": true, + "name": "ffmpeg", + "package": "FFmpeg", + "repo": "crueter-ci/FFmpeg", + "version": "9.0.2-1790726865-946fcce07b" }, "fmt": { - "repo": "fmtlib/fmt", - "tag": "%VERSION%", "hash": "f0da82c545b01692e9fd30fdfb613dbb8dd9716983dcd0ff19ac2a8d36f74beb5540ef38072fdecc1e34191b3682a8542ecbf3a61ef287dbba0a2679d4e023f2", - "version": "8", - "git_version": "12.1.0" + "min_version": "8", + "repo": "fmtlib/fmt", + "version": "12.1.0" + }, + "frozen": { + "hash": "b8dfe741c82bc178dfc9749d4ab5a130cee718d9ee7b71d9b547cf5f7f23027ed0152ad250012a8546399fcc1e12187efc68d89d6731256c4d2df7d04eef8d5c", + "package": "frozen", + "repo": "serge-sans-paille/frozen", + "version": "61dce5ae18" + }, + "gamemode": { + "find_args": "MODULE", + "hash": "e87ec14ed3e826d578ebf095c41580069dda603792ba91efa84f45f4571a28f4d91889675055fd6f042d7dc25b0b9443daf70963ae463e38b11bcba95f4c65a9", + "min_version": "1.7", + "repo": "FeralInteractive/gamemode", + "version": "ce6fe122f3" + }, + "httplib": { + "find_args": "MODULE GLOBAL", + "hash": "159ed94965018f2a371d45a3bfc1961e5fb1549e501ded70a6b4532d7fe99d0579c18b5195aff6e35f96f399b426cea2650ec9fb75ef80d4c9edeccb51f2e6c9", + "options": [ + "HTTPLIB_REQUIRE_OPENSSL ON", + "HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES ON" + ], + "patches": [ + "0001-mingw.patch" + ], + "repo": "yhirose/cpp-httplib", + "version": "v0.46.0" + }, + "lagoon": { + "hash": "b9380f99c6effaeccc6d8f81d4942e852c11ad28613df637e155451556ae5826f93765bee57a5c87a9740d2bd1db463ad0f55a947772fe9d57eeabae3efa373e", + "repo": "loongson-community/lagoon", + "version": "1.0.0" + }, + "libadrenotools": { + "hash": "f6526620cb752876edc5ed4c0925d57b873a8218ee09ad10859ee476e9333259784f61c1dcc55a2bcba597352d18aff22cd2e4c1925ec2ae94074e09d7da2265", + "patches": [ + "0001-use-cpmfile-def-for-linkernsbypass.patch" + ], + "repo": "eden-emulator/libadrenotools", + "version": "8ba23b42d7" + }, + "libusb": { + "find_args": "MODULE", + "hash": "98c5f7940ff06b25c9aa65aa98e23de4c79a4c1067595f4c73cc145af23a1c286639e1ba11185cd91bab702081f307b973f08a4c9746576dc8d01b3620a3aeb5", + "patches": [ + "0001-netbsd-gettime.patch" + ], + "repo": "libusb/libusb", + "version": "v1.0.29" + }, + "linkernsbypass": { + "bundled": "true", + "hash": "bbe3f1f08e2bc7172b36e8f052912cc374289fc9a8e5a39ae7547a0c232de8f57ba24883451896b7a9a5d1be0e4de5c5b0f70c2022eda18d7d5a754847521800", + "repo": "bylaws/liblinkernsbypass", + "version": "aa3975893d" + }, + "llvm-mingw": { + "artifact": "clang-rt-builtins.tar.zst", + "git_host": "git.eden-emu.dev", + "hash": "d902392caf94e84f223766e2cc51ca5fab6cae36ab8dc6ef9ef6a683ab1c483bfcfe291ef0bd38ab16a4ecc4078344fa8af72da2f225ab4c378dee23f6186181", + "repo": "eden-emu/llvm-mingw", + "version": "20250828" }, "lz4": { + "hash": "35c21a5d9cfb5bbf314a5321d02b36819491d2ee3cf8007030ca09d13ca4dae672247b7aeab553e973093604fc48221cb03dc92197c6efe8fc3746891363fdab", "name": "lz4", "repo": "lz4/lz4", - "sha": "ebb370ca83", - "hash": "35c21a5d9cfb5bbf314a5321d02b36819491d2ee3cf8007030ca09d13ca4dae672247b7aeab553e973093604fc48221cb03dc92197c6efe8fc3746891363fdab", - "source_subdir": "build/cmake" + "source_subdir": "build/cmake", + "version": "ebb370ca83" + }, + "moltenvk": { + "artifact": "MoltenVK-macOS.tar", + "bundled": true, + "hash": "5695b36ca5775819a71791557fcb40a4a5ee4495be6b8442e0b666d0c436bec02aae68cc6210183f7a5c986bdbec0e117aecfad5396e496e9c2fd5c89133a347", + "repo": "V380-Ori/Ryujinx.MoltenVK", + "version": "v1.4.1-ryujinx" }, "nlohmann": { + "hash": "6cc1e86261f8fac21cc17a33da3b6b3c3cd5c116755651642af3c9e99bb3538fd42c1bd50397a77c8fb6821bc62d90e6b91bcdde77a78f58f2416c62fc53b97d", + "min_version": "3.8", "package": "nlohmann_json", "repo": "nlohmann/json", - "tag": "v%VERSION%", - "hash": "6cc1e86261f8fac21cc17a33da3b6b3c3cd5c116755651642af3c9e99bb3538fd42c1bd50397a77c8fb6821bc62d90e6b91bcdde77a78f58f2416c62fc53b97d", - "version": "3.8", - "git_version": "3.12.0" + "version": "v3.12.0" }, - "zlib": { - "package": "ZLIB", - "repo": "madler/zlib", - "tag": "v%VERSION%", - "hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4", - "version": "1.2", - "git_version": "1.3.2", - "options": [ - "ZLIB_BUILD_SHARED OFF", - "ZLIB_INSTALL OFF" - ] + "oaknut": { + "hash": "9697e80a7d5d9bcb3ce51051a9a24962fb90ca79d215f1f03ae6b58da8ba13a63b5dda1b4dde3d26ac6445029696b8ef2883f4e5a777b342bba01283ed293856", + "min_version": "2.0.1", + "repo": "eden-emulator/oaknut", + "version": "v2.0.3" }, - "zstd": { - "repo": "facebook/zstd", - "sha": "b8d6101fba", - "hash": "cc5ad4b119a9c2ea57f0b71eeff01113bb506e0d17000159c5409cb8236d22e38c52d5e9e97e7947a4bf1b2dfc44b6c503ab2d9aedbd59458435c6a2849cb029", - "version": "1.5", - "source_subdir": "build/cmake", - "find_args": "MODULE", + "openssl": { + "hash": "29002ce50cb95a4f4f1d0e9d3f684401fbd4eac34203dc2eef3b6334af5d44aa46bf788b63a6f5c139c383eafb7269ae87a58a9a3ad5912903b9773e545ccc0a", + "min_version": "3.0.0", + "package": "OpenSSL", + "patches": [ + "0001-add-bundled-cert.patch" + ], + "repo": "openssl/openssl", + "version": "openssl-3.6.2" + }, + "openssl-ci": { + "ci": true, + "name": "openssl", + "package": "OpenSSL", + "repo": "crueter-ci/OpenSSL", + "version": "4.0.1-1788234794-b64f68a94e" + }, + "openssl-cmake": { + "bundled": true, + "hash": "2cc185c924fd70e7d886257ca0caa42b3b8f7f712f2052b4f94dde74759e27022de76178460e18c9bdfc57c366583999e198fbb6052d4e7d91c099d15a0ca63e", "options": [ - "ZSTD_BUILD_SHARED OFF" - ] + "OPENSSL_CONFIGURE_OPTIONS threads" + ], + "patches": [ + "0001-cpmutil-compat.patch", + "0002-use-ccache.patch", + "0003-use-cmake-compiler-flags.patch", + "0004-use-shell-wrapper.patch" + ], + "repo": "jimmy-park/openssl-cmake", + "version": "3.6.2" }, "opus": { - "package": "Opus", - "repo": "xiph/opus", - "sha": "a3f0ec02b3", - "hash": "9506147b0de35befda8633ff272981cc2575c860874791bd455b752f797fd7dbd1079f0ba42ccdd7bb1fe6773fa5e84b3d75667c2883dd1fb2d0e4a5fa4f8387", - "version": "1.3", "find_args": "MODULE", + "hash": "9506147b0de35befda8633ff272981cc2575c860874791bd455b752f797fd7dbd1079f0ba42ccdd7bb1fe6773fa5e84b3d75667c2883dd1fb2d0e4a5fa4f8387", + "min_version": "1.3", "options": [ "OPUS_PRESUME_NEON ON" ], + "package": "Opus", "patches": [ "0001-disable-clang-runtime-neon.patch", "0002-no-install.patch" - ] - }, - "boost_headers": { - "repo": "boostorg/headers", - "sha": "95930ca8f5", - "hash": "8a07d7a6f0065587d3005a83481a794704ae22e773b9f336fbd89ed230aaa7b4c86c03edcbae30bba8b3e20839c3131eaa2dceac037ef811533ef4eadc53b15b", - "bundled": true - }, - "llvm-mingw": { - "repo": "eden-emu/llvm-mingw", - "git_host": "git.eden-emu.dev", - "tag": "%VERSION%", - "version": "20250828", - "artifact": "clang-rt-builtins.tar.zst", - "hash": "d902392caf94e84f223766e2cc51ca5fab6cae36ab8dc6ef9ef6a683ab1c483bfcfe291ef0bd38ab16a4ecc4078344fa8af72da2f225ab4c378dee23f6186181" - }, - "vulkan-validation-layers": { - "package": "VVL", - "repo": "KhronosGroup/Vulkan-ValidationLayers", - "tag": "vulkan-sdk-%VERSION%", - "git_version": "1.4.341.0", - "artifact": "android-binaries-%VERSION%.zip", - "hash": "8812ae84cbe49e6a3418ade9c458d3be6d74a3dffd319d4502007b564d580998056e8190414368ec11b27bc83993c7a0dad713c31bcc3d9553b51243efee3753" + ], + "repo": "xiph/opus", + "version": "a3f0ec02b3" }, "quazip": { - "package": "QuaZip-Qt6", - "repo": "stachenov/quazip", - "sha": "2e95c9001b", "hash": "609c240c7f029ac26a37d8fbab51bc16284e05e128b78b9b9c0e95d083538c36047a67d682759ac990e4adb0eeb90f04f1ea7fe2253bbda7e7e3bcce32e53dd8", - "version": "1.3", - "git_version": "1.5", + "min_version": "1.3", "options": [ "QUAZIP_QT_MAJOR_VERSION 6", "QUAZIP_INSTALL OFF", - "QUAZIP_ENABLE_QTEXTCODEC OFF" - ] + "QUAZIP_ENABLE_QTEXTCODEC OFF", + "QUAZIP_BZIP2 OFF" + ], + "package": "QuaZip-Qt6", + "repo": "stachenov/quazip", + "version": "2e95c9001b" + }, + "reshade": { + "hash": "6fc9b39821ca6e2d91160f05846b3a42159fec759f5d6fba91d1a57801a1bdaf6047740578c136f0b1a60a9c29ad160d87dbfe0dbfbcdf6ea879a8a078377b8c", + "patches": [ + "0001-from-chars-fallback.patch" + ], + "repo": "crosire/reshade", + "version": "v6.8.0" + }, + "sdl3": { + "hash": "df5a323af7ac366661a3c0e887969c72584d232f3cc211419d59b0487b620b6b2859d4549c9e8df002ee489290062e466fcfddf7edc0872a37b1f2845e81c0f3", + "min_version": "3.2.10", + "package": "SDL3", + "repo": "libsdl-org/SDL", + "version": "release-3.4.8" + }, + "sdl3-ci": { + "ci": true, + "name": "SDL3", + "package": "SDL3", + "repo": "crueter-ci/SDL3", + "version": "3.4.14-1788231389-147a8ee32d" + }, + "simpleini": { + "find_args": "MODULE", + "hash": "b937c18a7b6277d77ca7ebfb216af4984810f77af4c32d101b7685369a4bd5eb61406223f82698e167e6311a728d07415ab59639fdf19eff71ad6dc2abfda989", + "package": "SimpleIni", + "repo": "brofield/simpleini", + "version": "v4.25" + }, + "sirit": { + "find_args": "CONFIG", + "hash": "b7cd6885acae3fc8698288d19febba0dd45e4a02a2b6563d2eb995a988d0847046e8d16a5dea7e0bf832b4321bcec134cdbafc553f6ede039e4cf34525c5dce5", + "options": [ + "SIRIT_USE_SYSTEM_SPIRV_HEADERS ON" + ], + "repo": "eden-emulator/sirit", + "version": "v1.0.5" + }, + "sirit-ci": { + "ci": true, + "name": "sirit", + "package": "sirit", + "repo": "eden-emulator/sirit", + "version": "v1.0.7" + }, + "spirv-headers": { + "hash": "d624371dd455c66a300344c89812598ffe11b5eedba555779f789e85c29dc67317741858c60e0744a1e6755cc0d2759b8659f0674f4cc31479c4cb6fc25ed23b", + "options": [ + "SPIRV_WERROR OFF" + ], + "package": "SPIRV-Headers", + "repo": "KhronosGroup/SPIRV-Headers", + "version": "vulkan-sdk-1.4.341.0" + }, + "tzdb": { + "artifact": "%VERSION%.tar.gz", + "git_host": "git.eden-emu.dev", + "hash": "cce65a12bf90f4ead43b24a0b95dfad77ac3d9bfbaaf66c55e6701346e7a1e44ca5d2f23f47ee35ee02271eb1082bf1762af207aad9fb236f1c8476812d008ed", + "package": "nx_tzdb", + "repo": "eden-emu/tzdb_to_nx", + "version": "230326" + }, + "vulkan-headers": { + "hash": "d2846ea228415772645eea4b52a9efd33e6a563043dd3de059e798be6391a8f0ca089f455ae420ff22574939ed0f48ed7c6ff3d5a9987d5231dbf3b3f89b484b", + "min_version": "1.4.317", + "package": "VulkanHeaders", + "repo": "KhronosGroup/Vulkan-Headers", + "version": "v1.4.345" + }, + "vulkan-memory-allocator": { + "find_args": "CONFIG", + "hash": "deb5902ef8db0e329fbd5f3f4385eb0e26bdd9f14f3a2334823fb3fe18f36bc5d235d620d6e5f6fe3551ec3ea7038638899db8778c09f6d5c278f5ff95c3344b", + "package": "VulkanMemoryAllocator", + "patches": [ + "0001-macos-clang.patch" + ], + "repo": "GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator", + "version": "v3.3.0" + }, + "vulkan-utility-libraries": { + "hash": "114f6b237a6dcba923ccc576befb5dea3f1c9b3a30de7dc741f234a831d1c2d52d8a224afb37dd57dffca67ac0df461eaaab6a5ab5e503b393f91c166680c3e1", + "package": "VulkanUtilityLibraries", + "repo": "KhronosGroup/Vulkan-Utility-Libraries", + "version": "v1.4.345" + }, + "vulkan-validation-layers": { + "artifact": "android-binaries-%NUMERIC_VERSION%.zip", + "hash": "8812ae84cbe49e6a3418ade9c458d3be6d74a3dffd319d4502007b564d580998056e8190414368ec11b27bc83993c7a0dad713c31bcc3d9553b51243efee3753", + "numeric_version": "1.4.341.0", + "repo": "KhronosGroup/Vulkan-ValidationLayers", + "version": "vulkan-sdk-%NUMERIC_VERSION%" + }, + "xbyak": { + "hash": "e0aa0a603dd3ac1a39d82213df1e73c042831aec2d6b2fe382c899651eaae4ed7e8aeb6be9c41ea0097087c9d091961ab18f313cd6a9e10d621715ffd49bfe36", + "package": "xbyak", + "repo": "herumi/xbyak", + "version": "v7.40.1" + }, + "zbic": { + "hash": "fbe2f37986377d7f0d96ae3224c80b5971df6e8b6961f68061f1975ebb2e8cb78f07b90f014b007be4023dbf5513581504e7f7d61a5237cb2a8a7a61ae11e482", + "repo": "kinnay/zbic", + "version": "11b08f2712264bbed731545085cbd9702096ceb7" + }, + "zlib": { + "hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4", + "min_version": "1.2", + "options": [ + "ZLIB_BUILD_SHARED OFF", + "ZLIB_INSTALL OFF" + ], + "package": "ZLIB", + "repo": "madler/zlib", + "version": "v1.3.2" + }, + "zstd": { + "find_args": "MODULE", + "hash": "2b006c3c110c38bd2f5896b42d36a38931c8eae261f30552f9a9fd04458d218d8f1a1425f117fd12af545ed77042e4b2d1a1a9b6286c4d0b9d74ef79f20a8f08", + "min_version": "1.5", + "options": [ + "ZSTD_BUILD_SHARED OFF" + ], + "repo": "facebook/zstd", + "source_subdir": "build/cmake", + "version": "33618c8" } } diff --git a/dist/72-eden-input.rules b/dist/72-eden-input.rules new file mode 100644 index 0000000000..b84ed200a3 --- /dev/null +++ b/dist/72-eden-input.rules @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2023 yuzu Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +# Allow systemd-logind to manage user access to hidraw with this file +# On most systems, this file should be installed to /etc/udev/rules.d/72-eden-input.rules +# Consult your distro if this is not the case + +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ENV{ID_INPUT_JOYSTICK}=="1", MODE="0660", TAG+="uaccess" diff --git a/dist/72-yuzu-input.rules b/dist/72-yuzu-input.rules deleted file mode 100644 index d64f8b28d7..0000000000 --- a/dist/72-yuzu-input.rules +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-FileCopyrightText: 2023 yuzu Emulator Project -# SPDX-License-Identifier: GPL-2.0-or-later - -# Allow systemd-logind to manage user access to hidraw with this file -# On most systems, this file should be installed to /etc/udev/rules.d/72-yuzu-input.rules -# Consult your distro if this is not the case - -# Switch Pro Controller (USB/Bluetooth) -KERNEL=="hidraw*", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="2009", MODE="0660", TAG+="uaccess" -KERNEL=="hidraw*", KERNELS=="*057e:2009*", MODE="0660", TAG+="uaccess" - -# Joy-Con L (Bluetooth) -KERNEL=="hidraw*", KERNELS=="*057e:2006*", MODE="0660", TAG+="uaccess" - -# Joy-Con R (Bluetooth) -KERNEL=="hidraw*", KERNELS=="*057e:2007*", MODE="0660", TAG+="uaccess" - -# Joy-Con Charging Grip (USB) -KERNEL=="hidraw*", ATTRS{idVendor}=="057e", ATTRS{idProduct}=="200e", MODE="0660", TAG+="uaccess" diff --git a/dist/compatibility_list/compatibility_list.json b/dist/compatibility_list/compatibility_list.json deleted file mode 100644 index b8d74e8945..0000000000 --- a/dist/compatibility_list/compatibility_list.json +++ /dev/null @@ -1,354 +0,0 @@ -[ - { - "compatibility": 0, - "directory": "the-legend-of-zelda-breath-of-the-wild", - "releases": [ - {"id": "01007EF00011E000"} - ], - "title": "The Legend of Zelda: Breath of the Wild" - }, - { - "compatibility": 1, - "directory": "super-mario-odyssey", - "releases": [ - {"id": "0100000000010000"} - ], - "title": "Super Mario Odyssey" - }, - { - "compatibility": 0, - "directory": "animal-crossing-new-horizons", - "releases": [ - {"id": "01006F8002326000"} - ], - "title": "Animal Crossing: New Horizons" - }, - { - "compatibility": 1, - "directory": "pokemon-legends-z-a", - "releases": [ - {"id": "0100F43008C44000"} - ], - "title": "Pokémon Legends: Z-A" - }, - { - "compatibility": 1, - "directory": "the-legend-of-zelda-tears-of-the-kingdom", - "releases": [ - {"id": "0100F2C0115B6000"} - ], - "title": "The Legend of Zelda: Tears of the Kingdom" - }, - { - "compatibility": 0, - "directory": "super-mario-galaxy", - "releases": [ - {"id": "010099C022B96000"} - ], - "title": "Super Mario Galaxy" - }, - { - "compatibility": 3, - "directory": "star-wars-republic-commando", - "releases": [ - {"id": "0100FA10115F8000"} - ], - "title": "Star Wars: Republic Commando" - }, - { - "compatibility": 0, - "directory": "doki-doki-literature-club-plus", - "releases": [ - {"id": "010086901543E000"} - ], - "title": "Doki Doki Literature Club Plus" - }, - { - "compatibility": 1, - "directory": "pokemon-scarlet", - "releases": [ - {"id": "0100A3D008C5C000"} - ], - "title": "Pokémon Scarlet" - }, - { - "compatibility": 1, - "directory": "pokemon-violet", - "releases": [ - {"id": "01008F6008C5E000"} - ], - "title": "Pokémon Violet" - }, - { - "compatibility": 2, - "directory": "pokemon-legends-arceus", - "releases": [ - {"id": "01001E300D162000"} - ], - "title": "Pokémon Legends: Arceus" - }, - { - "compatibility": 0, - "directory": "splatoon-2", - "releases": [ - {"id": "01003BC0000A0000"} - ], - "title": "Splatoon 2" - }, - { - "compatibility": 1, - "directory": "super-smash-bros-ultimate", - "releases": [ - {"id": "01006A800016E000"} - ], - "title": "Super Smash Bros. Ultimate" - }, - { - "compatibility": 0, - "directory": "mario-kart-8-deluxe", - "releases": [ - {"id": "0100152000022000"} - ], - "title": "Mario Kart 8 Deluxe" - }, - { - "compatibility": 0, - "directory": "splatoon-3", - "releases": [ - {"id": "0100C2500FC20000"} - ], - "title": "Splatoon 3" - }, - { - "compatibility": 0, - "directory": "new-super-mario-bros-u-deluxe", - "releases": [ - {"id": "0100EA80032EA000"} - ], - "title": "New Super Mario Bros. U Deluxe" - }, - { - "compatibility": 0, - "directory": "hyrule-warriors-age-of-calamity", - "releases": [ - {"id": "01002B00111A2000"} - ], - "title": "Hyrule Warriors: Age of Calamity" - }, - { - "compatibility": 2, - "directory": "luigis-mansion-3", - "releases": [ - {"id": "0100DCA0064A6000"} - ], - "title": "Luigi's Mansion 3" - }, - { - "compatibility": 2, - "directory": "pokemon-brilliant-diamond", - "releases": [ - {"id": "0100000011D90000"} - ], - "title": "Pokémon Brilliant Diamond" - }, - { - "compatibility": 2, - "directory": "pokemon-shining-pearl", - "releases": [ - {"id": "010018E011D92000"} - ], - "title": "Pokémon Shining Pearl" - }, - { - "compatibility": 1, - "directory": "super-mario-3d-world-bowsers-fury", - "releases": [ - {"id": "010028600EBDA000"} - ], - "title": "Super Mario 3D World + Bowser's Fury" - }, - { - "compatibility": 0, - "directory": "the-legend-of-zelda-links-awakening", - "releases": [ - {"id": "01006BB00C6F0000"} - ], - "title": "The Legend of Zelda: Link's Awakening" - }, - { - "compatibility": 1, - "directory": "fire-emblem-three-houses", - "releases": [ - {"id": "010055D009F78000"} - ], - "title": "Fire Emblem: Three Houses" - }, - { - "compatibility": 2, - "directory": "metroid-dread", - "releases": [ - {"id": "010093801237C000"} - ], - "title": "Metroid Dread" - }, - { - "compatibility": 0, - "directory": "paper-mario-the-origami-king", - "releases": [ - {"id": "0100A3900C3E2000"} - ], - "title": "Paper Mario: The Origami King" - }, - { - "compatibility": 1, - "directory": "xenoblade-chronicles-definitive-edition", - "releases": [ - {"id": "0100FF500E34A000"} - ], - "title": "Xenoblade Chronicles: Definitive Edition" - }, - { - "compatibility": 2, - "directory": "xenoblade-chronicles-3", - "releases": [ - {"id": "010074F013262000"} - ], - "title": "Xenoblade Chronicles 3" - }, - { - "compatibility": 1, - "directory": "pikmin-3-deluxe", - "releases": [ - {"id": "0100F8600D4B0000"} - ], - "title": "Pikmin 3 Deluxe" - }, - { - "compatibility": 0, - "directory": "donkey-kong-country-tropical-freeze", - "releases": [ - {"id": "0100C1F0054B6000"} - ], - "title": "Donkey Kong Country: Tropical Freeze" - }, - { - "compatibility": 1, - "directory": "kirby-and-the-forgotten-land", - "releases": [ - {"id": "01004D300C5AE000"} - ], - "title": "Kirby and the Forgotten Land" - }, - { - "compatibility": 2, - "directory": "mario-party-superstars", - "releases": [ - {"id": "01006B400D8B2000"} - ], - "title": "Mario Party Superstars" - }, - { - "compatibility": 0, - "directory": "clubhouse-games-51-worldwide-classics", - "releases": [ - {"id": "0100F8600D4B0000"} - ], - "title": "Clubhouse Games: 51 Worldwide Classics" - }, - { - "compatibility": 1, - "directory": "ring-fit-adventure", - "releases": [ - {"id": "01006B300BAF8000"} - ], - "title": "Ring Fit Adventure" - }, - { - "compatibility": 2, - "directory": "arms", - "releases": [ - {"id": "01009B500007C000"} - ], - "title": "ARMS" - }, - { - "compatibility": 0, - "directory": "super-mario-maker-2", - "releases": [ - {"id": "01009B90006DC000"} - ], - "title": "Super Mario Maker 2" - }, - { - "compatibility": 0, - "directory": "pokemon-lets-go-pikachu", - "releases": [ - {"id": "010003F003A34000"} - ], - "title": "Pokémon: Let's Go, Pikachu!" - }, - { - "compatibility": 1, - "directory": "pokemon-lets-go-eevee", - "releases": [ - {"id": "0100187003A36000"} - ], - "title": "Pokémon: Let's Go, Eevee!" - }, - { - "compatibility": 2, - "directory": "pokemon-sword", - "releases": [ - {"id": "0100ABF008968000"} - ], - "title": "Pokémon Sword" - }, - { - "compatibility": 2, - "directory": "pokemon-shield", - "releases": [ - {"id": "01008DB008C2C000"} - ], - "title": "Pokémon Shield" - }, - { - "compatibility": 1, - "directory": "new-pokemon-snap", - "releases": [ - {"id": "0100F4300C182000"} - ], - "title": "New Pokémon Snap" - }, - { - "compatibility": 0, - "directory": "mario-golf-super-rush", - "releases": [ - {"id": "0100C9C00E25C000"} - ], - "title": "Mario Golf: Super Rush" - }, - { - "compatibility": 1, - "directory": "mario-tennis-aces", - "releases": [ - {"id": "0100BDE00862A000"} - ], - "title": "Mario Tennis Aces" - }, - { - "compatibility": 2, - "directory": "wario-ware-get-it-together", - "releases": [ - {"id": "0100563010F22000"} - ], - "title": "WarioWare: Get It Together!" - }, - { - "compatibility": 0, - "directory": "big-brain-academy-brain-vs-brain", - "releases": [ - {"id": "0100190010F24000"} - ], - "title": "Big Brain Academy: Brain vs. Brain" - } -] diff --git a/dist/compatibility_list/compatibility_list.qrc b/dist/compatibility_list/compatibility_list.qrc deleted file mode 100644 index a29b735981..0000000000 --- a/dist/compatibility_list/compatibility_list.qrc +++ /dev/null @@ -1,5 +0,0 @@ - - - compatibility_list.json - - diff --git a/dist/dev.eden_emu.eden.metainfo.xml b/dist/dev.eden_emu.eden.metainfo.xml index 83398eed3b..ce5cc9a905 100644 --- a/dist/dev.eden_emu.eden.metainfo.xml +++ b/dist/dev.eden_emu.eden.metainfo.xml @@ -11,12 +11,12 @@ SPDX-License-Identifier: CC0-1.0 --> - org.eden_emu.eden + dev.eden_emu.eden CC0-1.0 eden

Nintendo Switch emulator -

Multiplatform FOSS Switch 1 emulator written in C++, derived from Yuzu and Sudachi

+

Eden is a free and open-source (FOSS) Switch 1 emulator started by developer Camille LaVey. Written in C++, with builds for Windows, Linux, macOS, Android, FreeBSD and more.

Game @@ -28,14 +28,14 @@ SPDX-License-Identifier: CC0-1.0 https://eden-emu.dev/ https://git.eden-emu.dev/eden-emu/eden/issues - https://eden-emu.dev/docs - https://eden-emu.dev/docs - https://eden-emu.dev/donations + https://eden-emu.dev/about + https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/README.md + https://liberapay.com/crueter https://explore.transifex.com/edenemu/eden-emulator - https://discord.gg/edenemu + https://discord.gg/HstXbPch7X https://git.eden-emu.dev https://git.eden-emu.dev/eden-emu/eden - org.eden_emu.eden.desktop + dev.eden_emu.eden.desktop yuzu yuzu-cmd @@ -44,6 +44,7 @@ SPDX-License-Identifier: CC0-1.0 pointing keyboard gamepad + offline-only 8192 diff --git a/dist/dev.eden_emu.eden.xml b/dist/dev.eden_emu.eden.xml index 922ebf7ff3..22acd30595 100644 --- a/dist/dev.eden_emu.eden.xml +++ b/dist/dev.eden_emu.eden.xml @@ -14,7 +14,7 @@ SPDX-License-Identifier: GPL-2.0-or-later Nintendo Switch homebrew executable NRO - + @@ -22,7 +22,7 @@ SPDX-License-Identifier: GPL-2.0-or-later Nintendo Switch homebrew executable NSO - + @@ -30,7 +30,7 @@ SPDX-License-Identifier: GPL-2.0-or-later Nintendo Switch Package NSP - + @@ -38,7 +38,7 @@ SPDX-License-Identifier: GPL-2.0-or-later Nintendo Switch Card Image XCI - + diff --git a/dist/english_plurals/README.md b/dist/english_plurals/README.md index a4954f6a68..4645d47ba4 100644 --- a/dist/english_plurals/README.md +++ b/dist/english_plurals/README.md @@ -2,6 +2,7 @@ Qt has "Translation Rules for Plurals", small example +```cpp // Take a source line like tr("Building: %n shader(s)", "", i) @@ -9,8 +10,9 @@ Qt has "Translation Rules for Plurals", small example Building: 1 shader // i = 2: Building: 2 shaders +``` -For yuzu the source language used is English, for all other languages handling of plurals is handled by Qt and the translation collaboration site. Handling plurals in the source language (English) requires special consideration. +For Yuzu the source language used is English, for all other languages handling of plurals is handled by Qt and the translation collaboration site. Handling plurals in the source language (English) requires special consideration. With CMake flag GENERATE_QT_TRANSLATION a generated_en.ts file is created from the source. It ignored by git (`.gitignore` in the project root). It is placed in this directory so that the relative refrences with the source code is correct. diff --git a/dist/icon_variations/1an.svg b/dist/icon_variations/1an.svg new file mode 100644 index 0000000000..df206b43af --- /dev/null +++ b/dist/icon_variations/1an.svg @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dist/icon_variations/1an_bgcolor b/dist/icon_variations/1an_bgcolor new file mode 100644 index 0000000000..a47436df7f --- /dev/null +++ b/dist/icon_variations/1an_bgcolor @@ -0,0 +1 @@ +#ffd700 diff --git a/dist/installer.nsi b/dist/installer.nsi new file mode 100644 index 0000000000..bc1730f23e --- /dev/null +++ b/dist/installer.nsi @@ -0,0 +1,184 @@ +; SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +; SPDX-License-Identifier: GPL-3.0-or-later + +; Usage: +; get the latest nsis: https://nsis.sourceforge.io/Download + +; Require these for makensis. +!ifndef PRODUCT_VERSION + !error "PRODUCT_VERSION must be defined" +!endif + +!ifndef ARCH + !error "ARCH must be defined" +!endif + +!ifndef VARIANT + !error "VARIANT must be defined" +!endif + +Unicode true +ManifestDPIAware true + +!define PRODUCT_NAME "Eden" +!define PRODUCT_PUBLISHER "Utopia LLC" +!define PRODUCT_WEB_SITE "https://git.eden-emu.dev" +!define PRODUCT_DIR_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\${PRODUCT_NAME}.exe" +!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" + +!define BINARY_SOURCE_DIR "..\bin" + +Name "${PRODUCT_NAME}" +OutFile "${PRODUCT_NAME}-Windows-${PRODUCT_VERSION}-${ARCH}-${VARIANT}-installer.exe" +SetCompressor /SOLID lzma +InstallDir "$LOCALAPPDATA\$(^Name)" +ShowInstDetails show +ShowUnInstDetails show + +!include "MUI2.nsh" +; Custom page plugin +!include "nsDialogs.nsh" + +; MUI Settings +!define MUI_ICON "eden.ico" +!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico" + +; License page +!insertmacro MUI_PAGE_LICENSE "..\LICENSE.txt" +; Desktop Shortcut page +Page custom desktopShortcutPageCreate desktopShortcutPageLeave +; Directory page +!insertmacro MUI_PAGE_DIRECTORY +; Instfiles page +!insertmacro MUI_PAGE_INSTFILES +; Finish page +!define MUI_FINISHPAGE_RUN "$INSTDIR\eden.exe" +!insertmacro MUI_PAGE_FINISH + +; Uninstaller pages +!insertmacro MUI_UNPAGE_INSTFILES + +; Variables +Var DesktopShortcutPageDialog +Var DesktopShortcutCheckbox +Var DesktopShortcut + +; Language files +!insertmacro MUI_LANGUAGE "English" +!insertmacro MUI_LANGUAGE "SimpChinese" +!insertmacro MUI_LANGUAGE "TradChinese" +!insertmacro MUI_LANGUAGE "Danish" +!insertmacro MUI_LANGUAGE "Dutch" +!insertmacro MUI_LANGUAGE "French" +!insertmacro MUI_LANGUAGE "German" +!insertmacro MUI_LANGUAGE "Hungarian" +!insertmacro MUI_LANGUAGE "Italian" +!insertmacro MUI_LANGUAGE "Japanese" +!insertmacro MUI_LANGUAGE "Korean" +!insertmacro MUI_LANGUAGE "Lithuanian" +!insertmacro MUI_LANGUAGE "Norwegian" +!insertmacro MUI_LANGUAGE "Polish" +!insertmacro MUI_LANGUAGE "PortugueseBR" +!insertmacro MUI_LANGUAGE "Romanian" +!insertmacro MUI_LANGUAGE "Russian" +!insertmacro MUI_LANGUAGE "Spanish" +!insertmacro MUI_LANGUAGE "Swedish" +!insertmacro MUI_LANGUAGE "Turkish" +!insertmacro MUI_LANGUAGE "Vietnamese" + +; MUI end ------ + +Function .onInit + StrCpy $DesktopShortcut 1 + + !insertmacro MUI_LANGDLL_DISPLAY +FunctionEnd + +Function desktopShortcutPageCreate + !insertmacro MUI_HEADER_TEXT "Create Desktop Shortcut" "Would you like to create a desktop shortcut?" + nsDialogs::Create 1018 + Pop $DesktopShortcutPageDialog + ${If} $DesktopShortcutPageDialog == error + Abort + ${EndIf} + + ${NSD_CreateCheckbox} 0u 0u 100% 12u "Create a desktop shortcut" + Pop $DesktopShortcutCheckbox + ${NSD_SetState} $DesktopShortcutCheckbox $DesktopShortcut + + nsDialogs::Show +FunctionEnd + +Function desktopShortcutPageLeave + ${NSD_GetState} $DesktopShortcutCheckbox $DesktopShortcut +FunctionEnd + +Section "Base" + ExecWait '"$INSTDIR\uninst.exe" /S _?=$INSTDIR' + + SectionIn RO + + SetOutPath "$INSTDIR" + + ; The binplaced build output will be included verbatim. + File /r "${BINARY_SOURCE_DIR}\*" + + ; Create start menu and desktop shortcuts + CreateShortCut "$SMPROGRAMS\$(^Name).lnk" "$INSTDIR\eden.exe" + ${If} $DesktopShortcut == 1 + CreateShortCut "$DESKTOP\$(^Name).lnk" "$INSTDIR\eden.exe" + ${EndIf} +SectionEnd + +!include "FileFunc.nsh" + +Section -Post + WriteUninstaller "$INSTDIR\uninst.exe" + + WriteRegStr HKCU "${PRODUCT_DIR_REGKEY}" "" "$INSTDIR\eden.exe" + + ; Write metadata for add/remove programs applet + WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)" + WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe" + WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\eden.exe" + WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}" + WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}" + WriteRegStr HKCU "${PRODUCT_UNINST_KEY}" "InstallLocation" "$INSTDIR" + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKCU "${PRODUCT_UNINST_KEY}" "EstimatedSize" "$0" + + WriteRegStr HKCU "Software\Classes\.nsp" "" "$(^Name)" + WriteRegStr HKCU "Software\Classes\.xci" "" "$(^Name)" + WriteRegStr HKCU "Software\Classes\.nro" "" "$(^Name)" + WriteRegStr HKCU "Software\Classes\.kip" "" "$(^Name)" + WriteRegStr HKCU "Software\Classes\$(^Name)\DefaultIcon" "" "$INSTDIR\eden.exe,0" + WriteRegStr HKCU "Software\Classes\$(^Name)\Shell\open\command" "" '"$INSTDIR\eden.exe" %1' +SectionEnd + +Section Uninstall + Delete "$DESKTOP\$(^Name).lnk" + Delete "$SMPROGRAMS\$(^Name).lnk" + + ; Be a bit careful to not delete files a user may have put into the install directory. + Delete "$INSTDIR\eden.exe" + Delete "$INSTDIR\eden-cli.exe" + Delete "$INSTDIR\uninst.exe" + Delete "$INSTDIR\LICENSE.txt" + Delete "$INSTDIR\README.md" + RMDir /r "$INSTDIR\LICENSES" + RMDir "$INSTDIR" + + DeleteRegKey HKCU "Software\Classes\.nsp" + DeleteRegKey HKCU "Software\Classes\.xci" + DeleteRegKey HKCU "Software\Classes\.nro" + DeleteRegKey HKCU "Software\Classes\.kip" + DeleteRegKey HKCU "Software\Classes\$(^Name)" + + DeleteRegKey HKCU "Software\Classes\discord-1397286652128264252" + + DeleteRegKey HKCU "${PRODUCT_UNINST_KEY}" + DeleteRegKey HKCU "${PRODUCT_DIR_REGKEY}" + + SetAutoClose true +SectionEnd diff --git a/dist/languages/.tx/config b/dist/languages/.tx/config deleted file mode 100644 index fbdfbe0178..0000000000 --- a/dist/languages/.tx/config +++ /dev/null @@ -1,14 +0,0 @@ -[main] -host = https://www.transifex.com - -[o:yuzu-emulator:p:yuzu:r:emulator] -file_filter = .ts -source_file = en.ts -source_lang = en -type = QT - -[o:yuzu-emulator:p:yuzu:r:yuzu-android] -file_filter = ../../src/android/app/src/main/res/values-/strings.xml -source_file = ../../src/android/app/src/main/res/values/strings.xml -type = ANDROID -lang_map = ja_JP:ja, ko_KR:ko, pt_BR:pt-rBR, pt_PT:pt-rPT, ru_RU:ru, vi_VN:vi, zh_CN:zh-rCN, zh_TW:zh-rTW diff --git a/dist/languages/README.md b/dist/languages/README.md index 12f26242ef..7adfa7615e 100644 --- a/dist/languages/README.md +++ b/dist/languages/README.md @@ -1,7 +1,11 @@ -This directory stores translation patches (TS files) for yuzu Qt frontend. This directory is linked with the [Eden project on transifex](https://app.transifex.com/edenemu/eden-emulator), so you can update the translation by executing `tx pull -t -a`. If you want to contribute to the translation, please go the transifex link and submit your translation there. This directory on the main repo will be synchronized with transifex periodically. Do not directly open PRs on github to modify the translation. +# Translating + +This directory stores translation patches (TS files) for Yuzu Qt frontend. This directory is linked with the [Eden project on Transifex](https://app.transifex.com/edenemu/eden-emulator), so you can update the translation by executing `tx pull -t -a` in the root of this repository. If you want to contribute to the translation, please go the Transifex link and submit your translation there. When creating/improving translations, please keep in mind: - You are responsible for providing accurate translations that do NOT contain illicit content or messages, - Many of our developers do not speak the languages you may be translating, so will only be able to help with confusions about the source language, - And bad-faith translations or attempts to insert illicit content may result in an immediate removal of access. + +New language requests will not be honored for the time being. diff --git a/dist/languages/ar.ts b/dist/languages/ar.ts index 7b6a92dc6a..73ab8a2039 100644 --- a/dist/languages/ar.ts +++ b/dist/languages/ar.ts @@ -218,157 +218,6 @@ This would ban both their forum username and their IP address. %1 - %2 (%3\%4 من الأعضاء) - متصلون - - CompatDB - - - Report Compatibility - أبلغ عن التوافق - - - - - - - - - - Report Game Compatibility - أبلغ عن توافق اللعبة - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - <html><head/><body><p><span style=" font-size:10pt;">إذا اخترت تقديم حالة اختبار إلى </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">قائمة التوافق مع إيدن - سيتم جمع المعلومات التالية وعرضها على الموقع:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">معلومات الأجهزة (وحدة المعالجة المركزية / وحدة معالجة الرسومات / نظام التشغيل) -</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">أي إصدار من إيدن الذي تقوم بتشغيله</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">حساب إيدن المتصل</li></ul></body></html> - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>هل تشتغل اللعبة؟</p></body></html> - - - - Yes The game starts to output video or audio - نعم تبدأ اللعبة في إخراج الفيديو أو الصوت - - - - No The game doesn't get past the "Launching..." screen - لا اللعبة لا تتجاوز شاشة ” جارٍ التشغيل ... “ - - - - Yes The game gets past the intro/menu and into gameplay - نعم، اللعبة تتجاوز المقدمة/القائمة وتبدأ اللعب. - - - - No The game crashes or freezes while loading or using the menu - لا، تتعطل اللعبة أو تتجمد أثناء التحميل أو أثناء استخدام القائمة - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>هل تصل اللعبة إلى اللعب؟</p></body></html> - - - - Yes The game works without crashes - نعم اللعبة تعمل بدون أعطال - - - - No The game crashes or freezes during gameplay - لا، تتعطل اللعبة أو تتجمد أثناء اللعب - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>هل تعمل اللعبة بدون أن تتعطل أو تدخل في حالة معلقة؟</p></body></html> - - - - Yes The game can be finished without any workarounds - نعم يمكن إكمال اللعبة بدون أي حلول غير تقليدية - - - - No The game can't progress past a certain area - لا لا يمكن تعدي أحد الأماكن في اللعبة - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>هل يمكنك أن تلعب اللعبة من بدايتها إلى نهايتها؟</p></body></html> - - - - Major The game has major graphical errors - اللعبة تحتوي على أخطاء رسومية كبيرة - - - - Minor The game has minor graphical errors - اللعبة تحتوي على أخطاء رسومية بسيطة - - - - None Everything is rendered as it looks on the Nintendo Switch - لا شيء، يتم عرض كل شيء كما يبدو على نينتندو سويتش - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>هل توجد أخطاء رسمية في اللعبة؟</p></body></html> - - - - Major The game has major audio errors - اللعبة تحتوي على أخطاء صوتية كبيرة - - - - Minor The game has minor audio errors - اللعبة تحتوي على أخطاء صوتية بسيطة - - - - None Audio is played perfectly - يتم تشغيل الصوت بدون مشاكل - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>هل لدي اللعبة أي مشاكل صوتية أو مؤثرات مفقودة؟</p></body></html> - - - - Thank you for your submission! - شكرا على مساهمتك - - - - Submitting - يتم الإرسال - - - - Communication error - خطأ في الاتصال - - - - An error occurred while sending the Testcase - حدث خطأ عند إرسال حالة الاختبار - - - - Next - التالي - - ConfigurationShared @@ -454,27 +303,27 @@ This would ban both their forum username and their IP address. Enable Overlay Applet - تمكين التطبيق المصغر للطبقة + تفعيل التطبيق المصغر للطبقة Enables Horizon's built-in overlay applet. Press and hold the home button for 1 second to show it. - تمكين التطبيق الصغير المدمج في Horizon. اضغط مع الاستمرار على زر الشاشة الرئيسية لمدة 1 ثانية لإظهاره. + تفعيل التطبيق المصغر المدمج في هورايزون. اضغط مع الاستمرار على زر الشاشة الرئيسية لمدة 1 ثانية لإظهاره. Output Engine: - محرك الإخراج: + :محرك الإخراج Output Device: - جهاز الإخراج: + :جهاز الإخراج Input Device: - جهاز الإدخال: + :جهاز الإدخال @@ -484,7 +333,7 @@ This would ban both their forum username and their IP address. Volume: - الصوت: + :الصوت @@ -564,7 +413,7 @@ Can help reduce stuttering at lower framerates. Accuracy: - الدقة: + :دقة المحاكاة @@ -573,138 +422,138 @@ Can help reduce stuttering at lower framerates. - + Backend: - الخلفية: + :الخلفية - CPU Overclock - رفع تردد المعالج + CPU Clocks + سرعات وحدة المعالجة المركزية - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. - يزيد من سرعة وحدة المعالجة المركزية المحاكية لإزالة بعض محددات الإطارات في الثانية. قد تشهد وحدات المعالجة المركزية الأضعف انخفاضًا في الأداء، وقد تعمل بعض الألعاب بشكل غير صحيح. -استخدم المعزز (1700 ميجاهرتز) للتشغيل بأعلى سرعة ساعة أصلية لجهاز سويتش، أو السريع (2000 ميجاهرتز) للتشغيل بسرعة ساعة مضاعفة. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + يرفع هذا الإعداد تردد المعالج المُحاكى، مما يُزيل بعض مُحددات معدل الإطارات. +قد تُلاحظ انخفاضًا في الأداء عند استخدام معالجات أضعف، وقد لا تعمل بعض الألعاب بشكلٍ صحيح. - + Custom CPU Ticks علامات وحدة المعالجة المركزية المخصصة - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. تعيين قيمة مخصصة لدقات وحدة المعالجة المركزية. القيم الأعلى يمكن أن تزيد الأداء، ولكنها قد تسبب حالات توقف تام. يوصى باستخدام نطاق 77-21000. - + Enable Host MMU Emulation (fastmem) - (fastmem) المضيف MMU تمكين محاكاة + (fastmem) المضيف MMU تفعيل محاكاة - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. يُسرّع هذا التحسين وصول برنامج الضيف إلى الذاكرة. يؤدي تفعيله إلى قراءة/كتابة ذاكرة الضيف مباشرةً في الذاكرة، واستخدام وحدة ذاكرة الجهاز المضيف MMU. أما تعطيله، فيُجبر جميع عمليات الوصول إلى الذاكرة على استخدام محاكاة وحدة ذاكرة الجهاز البرمجية MMU. - + Unfuse FMA (improve performance on CPUs without FMA) إلغاء استخدام FMA (تحسين الأداء على وحدات المعالجة المركزية بدون FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. أصلاً FMA يعمل هذا الخيار على تحسين السرعة عن طريق تقليل دقة تعليمات الضرب والإضافة المدمجة على وحدات المعالجة المركزية التي لا تدعم - + Faster FRSQRTE and FRECPE FRSQRTE و FRECPE أسرع - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. يعمل هذا الخيار على تحسين سرعة بعض وظائف النقاط العائمة التقريبية باستخدام تقريبات أصلية أقل دقة. - + Faster ASIMD instructions (32 bits only) أسرع (32 بت فقط) ASIMD تعليمات - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. يعمل هذا الخيار على تحسين سرعة وظائف النقطة العائمة ASIMD ذات 32 بت من خلال التشغيل باستخدام أوضاع تقريب غير صحيحة. - + Inaccurate NaN handling NaN معالجة غير دقيقة لـ - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. NaN يعمل هذا الخيار على تحسين السرعة عن طريق إزالة فحص. يرجى ملاحظة أن هذا يقلل أيضًا من دقة بعض تعليمات النقاط العائمة. - + Disable address space checks تعطيل عمليات التحقق من مساحة العنوان - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. يعمل هذا الخيار على تحسين السرعة من خلال إلغاء فحص الأمان قبل كل عملية ذاكرة. قد يؤدي تعطيله إلى السماح بتنفيذ تعليمات برمجية عشوائية. - + Ignore global monitor تجاهل الشاشة العالمية - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. يُحسّن هذا الخيار السرعة بالاعتماد فقط على دلالات cmpxchg لضمان سلامة تعليمات الوصول الحصري. يُرجى ملاحظة أن هذا قد يؤدي إلى توقفات وحالات تسابق أخرى. - + API: - واجهة برمجة التطبيقات: + :واجهة برمجة التطبيقات - + Changes the output graphics API. Vulkan is recommended. يُغيّر واجهة برمجة تطبيقات الرسومات الناتجة. Vulkan يُنصح باستخدام - + Device: - جهاز: + :جهاز - + This setting selects the GPU to use (Vulkan only). (Vulkan فقط) يحدد هذا الإعداد وحدة معالجة الرسومات التي سيتم استخدامها - + Resolution: - الدقة: + :الدقة - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. @@ -713,27 +562,27 @@ Options lower than 1X can cause artifacts. قد تؤدي الخيارات الأقل من 1X إلى حدوث تشوهات. - + Window Adapting Filter: - مرشح تكييف النافذة: + :مرشح التكيف مع النافذة - + FSR Sharpness: FSR حدة: - - Determines how sharpened the image will look using FSR's dynamic contrast. - FSR يقوم بتحديد مدى وضوح الصورة باستخدام التباين الديناميكي لـ + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. + يحدد مدى وضوح الصورة باستخدام التباين الديناميكي لـ FSR أو SGSR. - + Anti-Aliasing Method: - طريقة مضاد التعرج: + :طريقة مضاد التعرج - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. @@ -741,13 +590,23 @@ FXAA can produce a more stable picture in lower resolutions. يُقدم SMAA أفضل جودة. يُمكن لـ FXAA إنتاج صورة أكثر ثباتًا بدقة أقل. + + + Enable post-processing effects + تفعيل تأثيرات المعالجة اللاحقة + - Fullscreen Mode: - وضع ملء الشاشة: + Applies post-processing effects to the final image. + يُطبق تأثيرات ما بعد المعالجة على الصورة النهائية. + Fullscreen Mode: + :وضع ملء الشاشة + + + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. @@ -756,12 +615,12 @@ Exclusive fullscreen may offer better performance and better Freesync/Gsync supp قد يوفر وضع ملء الشاشة الحصري أداءً أفضل ودعمًا أفضل لتقنيتي Freesync/Gsync. - + Aspect Ratio: - نسبة العرض إلى الارتفاع: + :نسبة الأبعاد - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. @@ -770,48 +629,48 @@ Also controls the aspect ratio of captured screenshots. كما يتحكم في نسبة العرض إلى الارتفاع للقطات الشاشة الملتقطة. - + Use persistent pipeline cache استخدام ذاكرة التخزين المؤقتة الدائمة للأنابيب - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. يسمح بحفظ التظليلات في وحدة التخزين لتحميلها بشكل أسرع عند تشغيل اللعبة في المرات التالية. لا يُقصد من تعطيله سوى تصحيح الأخطاء. - + Use asynchronous GPU emulation استخدم محاكاة وحدة معالجة الرسومات غير المتزامنة - + Uses an extra CPU thread for rendering. This option should always remain enabled. يستخدم خيط معالجة إضافي للمعالجة الرسومية. يجب أن يبقى هذا الخيار مُفعّلاً دائماً. - + NVDEC emulation: NVDEC محاكاة: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. يُحدد كيفية فك تشفير الفيديوهات.يمكن استخدام وحدة المعالجة المركزية CPU أو وحدة معالجة الرسومات GPU لفك التشفير، أو عدم فك التشفير إطلاقًا (شاشة سوداء على الفيديوهات).في معظم الحالات، يُوفر فك تشفير وحدة معالجة الرسومات GPU أفضل أداء. - + ASTC Decoding Method: ASTC طريقة فك تشفير: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -823,12 +682,12 @@ stuttering but may present artifacts. وحدة المعالجة المركزية بشكل غير متزامن: استخدمها لفك تشفير قوام ASTC عند الطلب. يزيل هذا الخيار تقطع فك تشفير ASTC، ولكنه قد يُظهر بعض العيوب. - + ASTC Recompression Method: ASTC طريقة إعادة ضغط: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. @@ -836,44 +695,64 @@ BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, BC1/BC3: سيتم إعادة ضغط الصيغة الوسيطة إلى صيغة BC1 أو BC3، مما يوفر مساحة على ذاكرة الوصول العشوائي للفيديو VRAM ولكنه يُضعف جودة الصورة. - + Frame Pacing Mode (Vulkan only) (Vulkan فقط) وضع توقيت الإطارات - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. يتحكم في كيفية إدارة المحاكي لسرعة الإطارات لتقليل التقطع وجعل معدل الإطارات أكثر سلاسة واتساقًا. - + VRAM Usage Mode: VRAM وضع استهلاك: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. يُحدد ما إذا كان ينبغي على المُحاكي تفضيل توفير الذاكرة أو الاستفادة القصوى من ذاكرة الفيديو المُتاحة لتحسين الأداء. قد يؤثر الوضع المُكثّف على أداء تطبيقات أخرى، مثل برامج التسجيل. - + Skip CPU Inner Invalidation تخطي إبطال وحدة المعالجة المركزية الداخلية - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. يتخطى بعض عمليات إبطال ذاكرة التخزين المؤقتة أثناء تحديثات الذاكرة، مما يقلل من استخدام وحدة المعالجة المركزية ويحسّن زمن الوصول. قد يؤدي هذا إلى أعطال مؤقتة. - + + GPU Readback Before NCE Invalidation + NCE إعادة قراءة وحدة معالجة الرسومات قبل إبطال صلاحية + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + يفرغ بيانات ذاكرة المعالج الرسومي المؤقتة ويعيدها إلى ذاكرة المحاكي قبل أن تلغي تقنية التشغيل المباشر عمل الصفحة بالكامل، مما يحافظ على البيانات أثناء استمرار المعالج المركزي في تخزينها + + + + Patch Runtime NROs for NCE + تعديل ملفات تشغيل اللعبة فوراً لتتوافق مع تقنية التشغيل المباشر + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + يعدل كود تشغيل اللعبة تلقائياً عند فتحها. هذا الإجراء ضروري لتشغيل بعض الألعاب بدون مشاكل. + + + VSync Mode: VSync وضع: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -884,12 +763,12 @@ Immediate (no synchronization) presents whatever is available and can exhibit te يُظهر الوضع الفوري (بدون مزامنة) كل ما هو متاح، وقد يُظهر تمزقًا. - + Sync Memory Operations مزامنة عمليات الذاكرة - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. @@ -898,99 +777,122 @@ Unreal Engine 4 games often see the most significant changes thereof. التغييرات الأكثر أهمية Unreal Engine 4 غالبًا ما تشهد ألعاب. - + Enable asynchronous presentation (Vulkan only) - (Vulkan فقط) تمكين العرض التقديمي غير المتزامن + (Vulkan فقط) تفعيل العرض التقديمي غير المتزامن - + Slightly improves performance by moving presentation to a separate CPU thread. تحسين الأداء بشكل طفيف عن طريق نقل العرض التقديمي إلى مؤشر ترابط منفصل في وحدة المعالجة المركزية. - + Force maximum clocks (Vulkan only) (Vulkan فقط) فرض الحد الأقصى للساعات - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. يعمل في الخلفية أثناء انتظار أوامر الرسومات لمنع وحدة معالجة الرسومات من خفض سرعة الساعة. - + Anisotropic Filtering: - الترشيح المتباين الخواص: + :الترشيح متباين الخواص - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. يتحكم بجودة عرض الملمس بزوايا مائلة. من الآمن ضبطه على 16x على معظم وحدات معالجة الرسومات. - + GPU Mode: - وضع وحدة معالجة الرسومات: + :وضع وحدة معالجة الرسومات - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - يتحكم في وضع محاكاة وحدة معالجة الرسومات.تُعرض معظم الألعاب بشكل جيد في الوضعين السريع أو المتوازن، ولكن الوضع الدقيق لا يزال مطلوبًا لبعض الألعاب.تميل الجسيمات إلى العرض بشكل صحيح فقط في الوضع الدقيق. + يتحكم في وضع محاكاة وحدة معالجة الرسومات. +تُعرض معظم الألعاب بشكل جيد في وضع «سريع»، لكن وضع «دقيق» لا يزال ضروريًا لبعضها. +لا تُعرض الجسيمات بشكل صحيح إلا في وضع «دقيق». - + DMA Accuracy: DMA دقة: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. - الدقة الآمنة تعمل على إصلاح المشكلات في بعض الألعاب ولكنها قد تؤدي إلى انخفاض الأداء DMA يتحكم في دقة + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. + DMA يتحكم في وضع القراءة. +يُعد وضع «غير آمن» أسرع، في حين أن وضع «آمن» أكثر استقرارًا ويمكنه حل المشكلات في بعض الألعاب. +يُتبع الإعداد الافتراضي إعداد «دقة وحدة معالجة الرسومات» - + + GPU Fence Behavior: + :سلوك حاجز وحدة معالجة الرسومات + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + يتحكم في سلوك مزامنة حاجز وحدة معالجة الرسومات. +يُعد الخيار فوري الأسرع، لكنه قد يتسبب في بعض المشكلات. +يوفر الخيار متوازن توافقًا أفضل وقد يعمل على حل المشكلات في بعض الألعاب. +يعمل الخيار دقيق على تحسين التوافق بشكل أكبر على حساب بعض الأداء. +يتبع الخيار افتراضي إعداد وضع وحدة معالجة الرسومات. + + + Enable asynchronous shader compilation تفعيل تجميع التظليل غير المتزامن - + May reduce shader stutter. قد يقلل من تقطع التظليل. - - Fast GPU Time - وقت معالجة الرسومات السريع + + GPU Clocks + سرعات وحدة معالجة الرسومات - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. - يزيد من سرعة وحدة معالجة الرسومات المحاكية لزيادة الدقة الديناميكية ومسافة العرض.استخدم 256 للحصول على أقصى أداء و 512 للحصول على أقصى دقة للرسومات. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. + يجعل اللعبة تعتقد أن عمل وحدة معالجة الرسومات ينتهي بشكل أسرع مما هو عليه في الواقع، لذلك تتوقف عن خفض الدقة ومسافة العرض لتتناسب مع سرعات جهاز Switch. - + GPU Unswizzle إلغاء ترتيب بيانات وحدة معالجة الرسومات - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. باستخدام حوسبة وحدة معالجة الرسومات BCn 3D يسرع فك تشفير نسيج. قم بتعطيله في حالة حدوث أعطال أو مشاكل في الرسومات. - + GPU Unswizzle Max Texture Size الحد الأقصى لحجم النسيج في وحدة معالجة الرسومات بعد إعادة ترتيب البيانات - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. @@ -999,48 +901,48 @@ Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size حجم تدفق إلغاء ترتيب بيانات وحدة معالجة الرسومات - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. يحدد الحد الأقصى لكمية بيانات النسيج (بالميغابايت) التي تتم معالجتها لكل إطار. يمكن أن تقلل القيم الأعلى من التقطع أثناء تحميل النسيج، ولكنها قد تؤثر على اتساق الإطارات. - + GPU Unswizzle Chunk Size حجم كتلة إلغاء ترتيب بيانات وحدة معالجة الرسومات - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. يُحدد هذا الخيار عدد شرائح العمق التي تتم معالجتها في عملية إرسال واحدة. زيادة هذا الخيار قد تُحسّن الإنتاجية على وحدات معالجة الرسومات المتطورة، ولكنها قد تُسبب أخطاء في إعادة تعيين وقت الاستجابة أو انقطاع الاتصال ببرنامج التشغيل على الأجهزة ذات المواصفات الأقل. - + Use Vulkan pipeline cache Vulkan استخدام ذاكرة التخزين المؤقتة لخط أنابيب - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. يُفعّل ذاكرة التخزين المؤقت لخطوط الأنابيب الخاصة ببائع وحدة معالجة الرسومات. بتخزين ملفات ذاكرة التخزين المؤقتة للخطوط الداخلية Vulkan يمكن أن يؤدي هذا الخيار إلى تحسين وقت تحميل التظليل بشكل كبير في الحالات التي لا يقوم فيها برنامج تشغيل. - + Enable Compute Pipelines (Intel Vulkan Only) - (Intel Vulkan فقط) تمكين خطوط أنابيب الحوسبة + (Intel Vulkan فقط) تفعيل خطوط أنابيب الحوسبة - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. @@ -1049,182 +951,180 @@ Compute pipelines are always enabled on all other drivers. خطوط أنابيب الحوسبة مفعلة دائمًا على جميع برامج التشغيل الأخرى. - + Enable Reactive Flushing - تمكين التنظيف التفاعلي + تفعيل التنظيف التفاعلي - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. يستخدم التنظيف التفاعلي بدلاً من التنظيف التنبئي، مما يسمح بمزامنة الذاكرة بشكل أكثر دقة. - + Sync to framerate of video playback المزامنة مع معدل الإطارات لتشغيل الفيديو - + Run the game at normal speed during video playback, even when the framerate is unlocked. قم بتشغيل اللعبة بالسرعة العادية أثناء تشغيل الفيديو، حتى عندما يكون معدل الإطارات مفتوحًا. - + Barrier feedback loops حلقات التغذية الراجعة للحواجز - + Improves rendering of transparency effects in specific games. يحسن عرض تأثيرات الشفافية في بعض الألعاب المحددة. - + Enable buffer history - تمكين سجل التخزين المؤقت + تفعيل سجل التخزين المؤقت - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. يُتيح هذا الخيار الوصول إلى حالات التخزين المؤقت السابقة. وقد يُحسّن جودة العرض وثبات الأداء في بعض الألعاب. - + Fix bloom effects إصلاح تأثيرات التوهج - + Removes bloom in Burnout. يزيل التوهج في وضع الاحتراق. - - Enable Legacy Rescale Pass - تفعيل ميزة إعادة التحجيم القديمة - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - قد يصلح مشاكل إعادة القياس في بعض الألعاب بالاعتماد على سلوك من التنفيذ السابق. -حل لسلوك قديم يصلح تشوهات الخطوط على بطاقات AMD وIntel الرسومية، ووميض النسيج الرمادي على بطاقات Nvidia الرسومية في لعبة Luigi's Mansion 3. - - - + Extended Dynamic State الحالة الديناميكية الموسعة - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. يتحكم في عدد الميزات التي يمكن استخدامها في الحالة الديناميكية الموسعة.تسمح الحالات الأعلى بمزيد من الميزات ويمكن أن تزيد من الأداء، ولكنها قد تسبب مشكلات رسومية إضافية. - + Vertex Input Dynamic State حالة ديناميكية لإدخال الرأس - + Enables vertex input dynamic state feature for better quality and performance. يتيح ميزة الحالة الديناميكية لإدخال الرأس لتحسين الجودة والأداء. - + Sample Shading تظليل العينة - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. يسمح لمظلل الأجزاء بالتنفيذ لكل عينة في جزء متعدد العينات بدلاً من مرة واحدة لكل جزء. يحسن جودة الرسومات على حساب الأداء. القيم الأعلى تحسن الجودة ولكنها تقلل من الأداء. - + RNG Seed مولد الأرقام العشوائية - + Controls the seed of the random number generator. Mainly used for speedrunning. يتحكم في بذرة مولد الأرقام العشوائية. يُستخدم بشكل رئيسي في سباقات السرعة. - + Device Name اسم الجهاز - + The name of the console. اسم وحدة التحكم. - - Custom RTC Date: - المخصص RTC تاريخ: + + Homebrew Args + Homebrew وسائط - + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + (noglsl -مثل) عند التشغيل Homebrew سطر الأوامر الوسائط المُمررة إلى + + + + Custom RTC Date: + :تاريخ التوقيت الحقيقي المخصص + + + This option allows to change the clock of the console. Can be used to manipulate time in games. يتيح لك هذا الخيار تغيير ساعة وحدة التحكم. يمكن استخدامه للتحكم بالوقت في الألعاب. - + The number of seconds from the current unix time عدد الثواني من وقت يونكس الحالي - + Language: - اللغة: + :اللغة - + This option can be overridden when region setting is auto-select يمكن تجاوز هذا الخيار عند تحديد إعداد المنطقة تلقائيًا - + Region: - المنطقة: + :المنطقة - + The region of the console. منطقة وحدة التحكم. - + Time Zone: - المنطقة الزمنية: + :المنطقة الزمنية - + The time zone of the console. المنطقة الزمنية لوحدة التحكم. - + Sound Output Mode: - وضع إخراج الصوت: + :وضع إخراج الصوت - + Console Mode: - وضع وحدة التحكم: + :وضع وحدة التحكم - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. @@ -1233,1031 +1133,1026 @@ Setting to Handheld can help improve performance for low end systems. يُمكن أن يُساعد الضبط على الوضع المحمول على تحسين أداء الأجهزة منخفضة المواصفات. - + Prompt for user profile on boot المطالبة بملف تعريف المستخدم عند التشغيل - + Useful if multiple people use the same PC. مفيد إذا كان هناك عدة أشخاص يستخدمون نفس الكمبيوتر. - + Pause when not in focus توقف عند عدم التركيز - + Pauses emulation when focusing on other windows. يوقف المحاكاة عند التركيز على نوافذ أخرى. - + Confirm before stopping emulation تأكيد قبل إيقاف المحاكاة - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. يتجاوز المطالبات التي تطلب تأكيد إيقاف المحاكاة. -يؤدي تمكينه إلى تجاوز هذه المطالبات والخروج مباشرة من المحاكاة. +يؤدي تفعيله إلى تجاوز هذه المطالبات والخروج مباشرة من المحاكاة. - + Hide mouse on inactivity إخفاء الماوس عند عدم النشاط - + Hides the mouse after 2.5s of inactivity. يخفي الماوس بعد 2.5 ثانية من عدم النشاط. - - Disable controller applet - تعطيل تطبيق التحكم - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - يُعطّل استخدام أداة التحكم في البرامج المُحاكاة قسرًا. -عند محاولة أي برنامج فتح أداة التحكم، تُغلق فورًا. - - - + Check for updates التحقق من توفر التحديثات - + Whether or not to check for updates upon startup. ما إذا كان يجب التحقق من التحديثات عند بدء التشغيل أم لا. - + Enable Gamemode - تمكين وضع اللعبة + تفعيل وضع اللعبة - + Force X11 as Graphics Backend كخلفية رسومات X11 فرض - + Custom frontend الواجهة الأمامية المخصصة - + Real applet تطبيق صغير حقيقي - + + + Disabled + معطل + + + Never أبداً - + On Load عند التحميل - + Always دائماً - + CPU وحدة المعالجة المركزية - + GPU وحدة معالجة الرسومات - + CPU Asynchronous وحدة المعالجة المركزية غير المتزامنة - + Uncompressed (Best quality) Uncompressed (أفضل جودة) - + BC1 (Low quality) BC1 (جودة منخفضة) - + BC3 (Medium quality) BC3 (جودة متوسطة) - - + + Auto تلقائي - + 30 FPS 30 إطارًا في الثانية - + 60 FPS 60 إطارًا في الثانية - + 90 FPS 90 إطارًا في الثانية - + 120 FPS 120 إطارًا في الثانية - + Conservative محافظ - + Aggressive عدواني - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) OpenGL GLASM (Assembly Shaders, NVIDIA Only) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) OpenGL SPIR-V (Experimental, AMD/Mesa Only) - + Null لا شيء - + Fast سريع - - Balanced - متوازن - - - - + + + Accurate دقه - - + + + Default افتراضي - + Unsafe (fast) غير آمن (سريع) - + Safe (stable) آمن (مستقر) - + + Immediate + فوري + + + + Balanced + متوازن + + + Unsafe غير آمن - + Paranoid (disables most optimizations) - جنون العظمة (يعطل معظم التحسينات) + شديد الحذر (يعطل معظم التحسينات) - + Debugging تصحيح الأخطاء - + Dynarmic Dynarmic - + NCE NCE - + Borderless Windowed نافذة بلا حدود - + Exclusive Fullscreen شاشة كاملة حصرية - + No Video Output لا يوجد إخراج فيديو - + CPU Video Decoding فك تشفير فيديو وحدة المعالجة المركزية - + GPU Video Decoding (Default) فك تشفير فيديو وحدة معالجة الرسومات (افتراضي) - + 0.25X (180p/270p) [EXPERIMENTAL] 0.25X (180p/270p) [تجريبي] - + 0.5X (360p/540p) [EXPERIMENTAL] 0.5X (360p/540p) [تجريبي] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [تجريبي] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] 1.25X (900p/1350p) [تجريبي] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [تجريبي] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor Nearest Neighbor - + Bilinear Bilinear - + Bicubic Bicubic - + Gaussian Gaussian - + Lanczos Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution AMD FidelityFX Super Resolution - + Area Area - + MMPX MMPX - + Zero-Tangent Zero-Tangent - + B-Spline B-Spline - + Mitchell Mitchell - + Spline-1 Spline-1 - - + + Snapdragon Game Super Resolution + دقة فائقة للألعاب من سنابدراغون + + + + Snapdragon Game Super Resolution EdgeDir + دقة فائقة للألعاب من سنابدراغون EdgeDir + + + None لا شيء - + FXAA FXAA - + SMAA SMAA - + Default (16:9) (16:9) افتراضي - + Force 4:3 4:3 فرض - + Force 21:9 21:9 فرض - + Force 16:10 16:10 فرض - + Stretch to Window تمديد إلى النافذة - + Automatic تلقائي - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - 32x - - - - 64x - 64x - - - + Japanese (日本語) اليابانية (日本語) - + American English الإنجليزية الأمريكية - + French (français) الفرنسية (français) - + German (Deutsch) الألمانية (Deutsch) - + Italian (italiano) الإيطالية (Italiano) - + Spanish (español) الإسبانية (español) - + Chinese الصينية - + Korean (한국어) الكورية (한국어) - + Dutch (Nederlands) الهولندية (Nederlands) - + Portuguese (português) البرتغالية (Português) - + Russian (Русский) الروسية (Русский) - + Taiwanese تايواني - + British English - الإنكليزية البريطانية + الإنجليزية البريطانية - + Canadian French الكندية الفرنسية - + Latin American Spanish أمريكا اللاتينية الإسبانية - + Simplified Chinese الصينية المبسطة - + Traditional Chinese (正體中文) الصينية التقليدية (正體中文) - + Brazilian Portuguese (português do Brasil) البرتغالية البرازيلية (português do Brasil) - + Polish (polska) البولندية (polska) - + Thai (แบบไทย) التايلاندية (แบบไทย) - - + + Japan اليابان - + USA الولايات المتحدة الأمريكية - + Europe أوروبا - + Australia أستراليا - + China الصين - + Korea كوريا - + Taiwan تايوان - + Auto (%1) Auto select time zone تلقائي (%1) - + Default (%1) Default time zone افتراضي (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Cuba - + EET EET - + Egypt Egypt - + Eire Eire - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Eire - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hongkong - + HST HST - + Iceland Iceland - + Iran Iran - + Israel Israel - + Jamaica Jamaica - + Kwajalein Kwajalein - + Libya Libya - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navajo - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Poland - + Portugal Portugal - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Singapore - + Turkey Turkey - + UCT UCT - + Universal - عالمي + Universal - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulu - + Mono أحادي - + Stereo مجسم - + Surround محيطي - + 4GB DRAM (Default) 4GB DRAM (افتراضي) - + 6GB DRAM (Unsafe) 6GB DRAM (غير آمنة) - + 8GB DRAM 8GB DRAM - + 10GB DRAM (Unsafe) 10GB DRAM (غير آمنة) - + 12GB DRAM (Unsafe) 12GB DRAM (غير آمنة) - + Docked الإرساء - + Handheld محمول - - - Off - تعطيل + + + Normal + قياسي - - Boost (1700MHz) - تعزيز (1700 ميجا هرتز) + + + Boost + تعزيز - - Fast (2000MHz) - سريع (2000 ميجاهرتز) + + + Overclock + كسر السرعة - + Always ask (Default) اسأل دائمًا (افتراضي) - + Only if game specifies not to stop فقط إذا حددت اللعبة عدم التوقف - + Never ask لا تسأل أبدا - - - Medium (256) - متوسط (256) - - - - - High (512) - عالي (512) - - - + Very Small (16 MB) صغير جدًا (16 ميغابايت) - + Small (32 MB) صغير (32 ميغابايت) - + Normal (128 MB) قياسي (128 ميغابايت) - + Large (256 MB) كبير (256 ميغابايت) - + Very Large (512 MB) كبير جدًا (512 ميغابايت) - + Very Low (4 MB) منخفض جدًا (4 ميغابايت) - + Low (8 MB) منخفض (8 ميغابايت) - + Normal (16 MB) قياسي (16 ميغابايت) - + Medium (32 MB) متوسط (32 ميغابايت) - + High (64 MB) عالي (64 ميغابايت) - + Very Low (32) منخفض جدًا (32) - + Low (64) منخفض (64) - + Normal (128) قياسي (128) - - Disabled - معطل + + Medium (256) + متوسط (256) - + + High (512) + عالي (512) + + + ExtendedDynamicState 1 الحالة الديناميكية الممتدة 1 - + ExtendedDynamicState 2 الحالة الديناميكية الممتدة 2 - + ExtendedDynamicState 3 الحالة الديناميكية الممتدة 3 - + Tree View عرض قائمة - + Grid View عرض شبكة @@ -2309,7 +2204,7 @@ When a program attempts to open the controller applet, it is immediately closed. Input device: - :جهاز الادخال + :جهاز الإدخال @@ -2390,12 +2285,12 @@ When a program attempts to open the controller applet, it is immediately closed. Toggle CPU Optimizations - تشغيل/إيقاف تحسينات وحدة المعالجة المركزية + تفعيل/تعطيل تحسينات وحدة المعالجة المركزية <html><head/><body><p><span style=" font-weight:600;">For debugging only.</span><br/>If you're not sure what these do, keep all of these enabled. <br/>These settings, when disabled, only take effect when CPU Debugging is enabled. </p></body></html> - <html><head/><body><p><span style=" font-weight:600;">للتصحيح فقط.</span><br/>إذا لم تكن متأكدًا من وظيفة هذه الخيارات، فاتركها جميعًا ممكّنة. <br/>عند تعطيل هذه الإعدادات، لا تصبح سارية المفعول إلا عند تمكين تصحيح أخطاء وحدة المعالجة المركزية. </p></body></html> + <html><head/><body><p><span style=" font-weight:600;">للتصحيح فقط.</span><br/>إذا لم تكن متأكدًا من وظيفة هذه الخيارات، فاتركها جميعًا ممكّنة. <br/>عند تعطيل هذه الإعدادات، لا تصبح سارية المفعول إلا عند تفعيل تصحيح أخطاء وحدة المعالجة المركزية. </p></body></html> @@ -2406,14 +2301,14 @@ When a program attempts to open the controller applet, it is immediately closed. <div style="white-space: nowrap">يعمل هذا التحسين على تسريع عمليات الوصول إلى الذاكرة بواسطة برنامج الضيف.</div> -<div style="white-space: nowrap">يؤدي تمكينه إلى تضمين الوصول إلى مؤشرات PageTable::pointers في الكود الصادر.</div> +<div style="white-space: nowrap">يؤدي تفعيله إلى تضمين الوصول إلى مؤشرات PageTable::pointers في الكود الصادر.</div> <div style="white-space: nowrap">يؤدي تعطيل هذا إلى إجبار جميع عمليات الوصول إلى الذاكرة على المرور عبر وظائف Memory::Read/Memory::Write.</div> Enable inline page tables - تمكين جداول الصفحات المضمنة + تفعيل جداول الصفحات المضمنة @@ -2427,7 +2322,7 @@ When a program attempts to open the controller applet, it is immediately closed. Enable block linking - تمكين ربط الكتلة + تفعيل ربط الكتلة @@ -2441,7 +2336,7 @@ When a program attempts to open the controller applet, it is immediately closed. Enable return stack buffer - تمكين مخزن مؤقت لمكدس الإرجاع + تفعيل مخزن مؤقت لمكدس الإرجاع @@ -2455,7 +2350,7 @@ When a program attempts to open the controller applet, it is immediately closed. Enable fast dispatcher - تمكين المرسل السريع + تفعيل الموزع السريع @@ -2469,7 +2364,7 @@ When a program attempts to open the controller applet, it is immediately closed. Enable context elimination - تمكين حذف السياق + تفعيل حذف السياق @@ -2483,7 +2378,7 @@ When a program attempts to open the controller applet, it is immediately closed. Enable constant propagation - تمكين الانتشار الثابت + تفعيل الانتشار الثابت @@ -2497,7 +2392,7 @@ When a program attempts to open the controller applet, it is immediately closed. Enable miscellaneous optimizations - تمكين التحسينات المتنوعة + تفعيل التحسينات المتنوعة @@ -2506,14 +2401,14 @@ When a program attempts to open the controller applet, it is immediately closed. <div style="white-space: nowrap">When disabled, a misalignment is triggered on all misaligned accesses.</div> - <div style="white-space: nowrap">عند التمكين، لا يتم تشغيل عدم المحاذاة إلا عندما يتجاوز الوصول حدود الصفحة</div> + <div style="white-space: nowrap">عند التفعيل، لا يتم تشغيل عدم المحاذاة إلا عندما يتجاوز الوصول حدود الصفحة</div> <div style="white-space: nowrap">عند التعطيل، يتم تشغيل عدم المحاذاة على جميع عمليات الوصول غير المتوافقة</div> Enable misalignment check reduction - تمكين تقليل التحقق من المحاذاة غير الصحيحة + تفعيل تقليل التحقق من المحاذاة غير الصحيحة @@ -2524,14 +2419,14 @@ When a program attempts to open the controller applet, it is immediately closed. <div style="white-space: nowrap">يعمل هذا التحسين على تسريع عمليات الوصول إلى الذاكرة بواسطة برنامج الضيف.</div> -<div style="white-space: nowrap">يؤدي تمكينه إلى إجراء عمليات قراءة/كتابة ذاكرة الضيف مباشرة في الذاكرة والاستفادة من وحدة MMU الخاصة بالمضيف.</div> +<div style="white-space: nowrap">يؤدي تفعيله إلى إجراء عمليات قراءة/كتابة ذاكرة الضيف مباشرة في الذاكرة والاستفادة من وحدة MMU الخاصة بالمضيف.</div> <div style="white-space: nowrap">يؤدي تعطيل هذا إلى إجبار جميع عمليات الوصول إلى الذاكرة على استخدام محاكاة MMU البرمجية.</div> Enable Host MMU Emulation (general memory instructions) - المضيف (تعليمات الذاكرة العامة) MMU تمكين محاكاة + المضيف (تعليمات الذاكرة العامة) MMU تفعيل محاكاة @@ -2542,14 +2437,14 @@ When a program attempts to open the controller applet, it is immediately closed. <div style="white-space: nowrap">يعمل هذا التحسين على تسريع عمليات الوصول الحصري للذاكرة بواسطة برنامج الضيف.</div> -<div style="white-space: nowrap">يؤدي تمكينه إلى إجراء عمليات قراءة/كتابة للذاكرة الحصرية للضيوف مباشرة في الذاكرة والاستفادة من وحدة MMU الخاصة بالمضيف.</div> +<div style="white-space: nowrap">يؤدي تفعيله إلى إجراء عمليات قراءة/كتابة للذاكرة الحصرية للضيوف مباشرة في الذاكرة والاستفادة من وحدة MMU الخاصة بالمضيف.</div> <div style="white-space: nowrap">يؤدي تعطيل هذا إلى إجبار جميع عمليات الوصول الحصرية للذاكرة على استخدام محاكاة MMU البرمجية.</div> Enable Host MMU Emulation (exclusive memory instructions) - المضيف (تعليمات الذاكرة الحصرية) MMU تمكين محاكاة + المضيف (تعليمات الذاكرة الحصرية) MMU تفعيل محاكاة @@ -2559,13 +2454,13 @@ When a program attempts to open the controller applet, it is immediately closed. <div style="white-space: nowrap">يعمل هذا التحسين على تسريع عمليات الوصول الحصري للذاكرة بواسطة برنامج الضيف.</div> -<div style="white-space: nowrap">يؤدي تمكينه إلى تقليل التكلفة الإضافية لفشل fastmem في الوصول إلى الذاكرة الحصرية.</div> +<div style="white-space: nowrap">يؤدي تفعيله إلى تقليل التكلفة الإضافية لفشل fastmem في الوصول إلى الذاكرة الحصرية.</div> Enable recompilation of exclusive memory instructions - تمكين إعادة تجميع تعليمات الذاكرة الحصرية + تفعيل إعادة تجميع تعليمات الذاكرة الحصرية @@ -2575,13 +2470,13 @@ When a program attempts to open the controller applet, it is immediately closed. <div style="white-space: nowrap">يعمل هذا التحسين على تسريع عمليات الوصول إلى الذاكرة عن طريق السماح بنجاح الوصول إلى الذاكرة غير الصالحة.</div> - <div style="white-space: nowrap">يؤدي تمكينه إلى تقليل الحمل الزائد لجميع عمليات الوصول إلى الذاكرة وليس له أي تأثير على البرامج التي لا تصل إلى الذاكرة غير الصالحة.</div> + <div style="white-space: nowrap">يؤدي تفعيله إلى تقليل الحمل الزائد لجميع عمليات الوصول إلى الذاكرة وليس له أي تأثير على البرامج التي لا تصل إلى الذاكرة غير الصالحة.</div> Enable fallbacks for invalid memory accesses - تمكين الإجراءات الاحتياطية للوصول إلى الذاكرة غير الصالحة + تفعيل الإجراءات الاحتياطية للوصول إلى الذاكرة غير الصالحة @@ -2599,7 +2494,7 @@ When a program attempts to open the controller applet, it is immediately closed. Enable GDB Stub - GDB Stub تمكين + GDB Stub تفعيل @@ -2617,252 +2512,297 @@ When a program attempts to open the controller applet, it is immediately closed. مرشح السجلات العالمي - - When checked, the max size of the log increases from 100 MB to 1 GB - عند تحديده، يزيد الحد الأقصى لحجم السجل من 100 ميجا بايت إلى 1 جيجا بايت + + GPU Logging/Level + تسجيل/مستوى وحدة معالجة الرسومات - - Enable Extended Logging** - تفعيل السجل المطول + + Detail level for GPU logs. Off disables logging entirely. + مستوى التفاصيل لسجلات وحدة معالجة الرسومات. عند تعيين الخيار على «إيقاف» يتم تعطيل التسجيل تمامًا. - + + Off + تعطيل + + + + Errors + أخطاء + + + + Standard + قياسي + + + + Verbose + مفصّل + + + + All + الكل + + + Show Log in Console عرض السجل في الطرفية - + + When checked, the max size of the log increases from 100 MB to 1 GB + عند تحديده، يزيد الحد الأقصى لحجم السجل من 100 ميجا بايت إلى 1 جيجا بايت + + + + Enable Extended Logging** + تفعيل السجل المطول + + + Open Log Location فتح موقع السجل - + Homebrew برمجيات يُنتجها هواة - + Arguments String سلسلة الحجج - + Graphics الرسومات - + When checked, it executes shaders without loop logic changes عند تحديده، يتم تنفيذ التظليل دون تغييرات في منطق الحلقة - + Disable Loop safety checks تعطيل عمليات فحص سلامة الحلقة - + When checked, it will dump all the macro programs of the GPU عند تحديده، سيتم تفريغ جميع برامج الماكرو الخاصة بوحدة معالجة الرسومات - + Dump Maxwell Macros تفريغ وحدات ماكرو ماكسويل - + When checked, it enables Nsight Aftermath crash dumps - Nsight Aftermath عند تحديد هذا الخيار، يتم تمكين عمليات تفريغ الأعطال الخاصة بـ + Nsight Aftermath عند تحديد هذا الخيار، يتم تفعيل عمليات تفريغ الأعطال الخاصة بـ - + Enable Nsight Aftermath - Nsight Aftermath تمكين + Nsight Aftermath تفعيل - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - عند تحديده، سيتم تفريغ جميع تظليلات المجمع الأصلية من ذاكرة التخزين المؤقتة لتظليلات القرص أو اللعبة كما هي موجودة. + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + عند تفعيل هذه الخاصية، سيتم استخراج بايت كود تظليل الضيف الأصلي من ماكسويل (المدخل إلى المُجمِّع المُعاد) كملفات بامتداد .ash ضمن مجلد DumpDir/shaders/. وهذا مفيد للفحص باستخدام nvdisasm. - - Dump Game Shaders - تفريغ تظليل اللعبة + + Dump Guest (Maxwell) Shaders + تظليلات ضيف التفريغ (ماكسويل) - + Enable Renderdoc Hotkey - Renderdoc تمكين مفتاح التشغيل السريع لـ + Renderdoc تفعيل مفتاح التشغيل السريع لـ - + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower عند تفعيله، يُعطّل مُجمّع الماكرو "Just In Time". تفعيله يُبطئ تشغيل الألعاب. - + Disable Macro JIT JIT تعطيل ماكرو - + When checked, it disables the macro HLE functions. Enabling this makes games run slower عند تفعيله، يُعطّل وظائف الماكرو HLE. تفعيله يُبطئ تشغيل الألعاب. - + Disable Macro HLE HLE تعطيل ماكرو - + When checked, the graphics API enters a slower debugging mode عند تحديد هذا الخيار، تدخل واجهة برمجة التطبيقات الرسومية في وضع تصحيح أبطأ - + Enable Graphics Debugging - تمكين تصحيح الرسومات + تفعيل تصحيح الرسومات - + When checked, yuzu will log statistics about the compiled pipeline cache عند تحديد هذا الخيار، سيقوم يوزو بتسجيل إحصائيات حول ذاكرة التخزين المؤقتة للأنابيب المجمعة. - + Enable Shader Feedback - تمكين ملاحظات التظليل + تفعيل ملاحظات التظليل - + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> <html><head/><body><p>عند تحديده، يتم تعطيل إعادة ترتيب تحميلات الذاكرة المعينة، مما يسمح بربط التحميلات بعمليات سحب محددة. قد يؤدي ذلك إلى انخفاض الأداء في بعض الحالات.</p></body></html> - + Disable Buffer Reorder تعطيل إعادة ترتيب المخزن المؤقت - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + عند تحديد هذا الخيار، سيتم حفظ ملفات SPIR-V الثنائية الناتجة عن إعادة التجميع (.spv) في مجلد LogDir/shaders/. يمكنك فحصها باستخدام أدوات SPIRV (spirv-dis / spirv-cross / spirv-val) أو RenderDoc. + + + + Dump SPIR-V Shaders + SPIR-V تفريغ مظللات + + + Advanced متقدم - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. يُمكّن يوزو من التحقق من بيئة Vulkan فعّالة عند بدء تشغيل البرنامج. عطّل هذه الميزة إذا كانت تُسبب مشاكل في رؤية البرامج الخارجية ليوزو. - + Perform Startup Vulkan Check عند بدء التشغيل Vulkan إجراء فحص - + Disable Web Applet تعطيل تطبيق الويب - + Enable All Controller Types - تمكين كافة أنواع اذرع التحكم + تفعيل كافة أنواع اذرع التحكم - + Enable Auto-Stub - تمكين الملحق تلقائي + تفعيل الملحق تلقائي - + Kiosk (Quest) Mode Kiosk (Quest) Mode - + Use dev.keys dev.keys استخدام مفاتيح التطوير - + Enable Debug Asserts - تمكين تأكيدات التصحيح + تفعيل تأكيدات التصحيح - + Debugging تصحيح الأخطاء - + Battery Serial: - :رقم تسلسلي للبطارية + :الرقم التسلسلي للبطارية - + + Debug Knobs: + :أزرار تصحيح الأخطاء + + + Bitmask for quick development toggles قناع بتات للتبديل السريع بين خيارات التطوير - + Set debug knobs (bitmask) ضبط أزرار تصحيح الأخطاء (قناع البت) - + 16-bit debug knob set for quick development toggles مجموعة أزرار تصحيح الأخطاء ذات 16 بت للتبديل السريع أثناء التطوير - + (bitmask) (قناع البت) - - Debug Knobs: - أزرار تصحيح الأخطاء: - - - + Unit Serial: :الرقم التسلسلي للوحدة - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. - قم بتمكين هذه الخيار لإخراج أحدث قائمة أوامر صوتية تم إنشاؤها إلى وحدة التحكم. يؤثر فقط على الألعاب التي تستخدم عارض الصوت. + قم بتفعيل هذه الخيار لإخراج أحدث قائمة أوامر صوتية تم إنشاؤها إلى وحدة التحكم. يؤثر فقط على الألعاب التي تستخدم عارض الصوت. - + Dump Audio Commands To Console** تفريغ أوامر الصوت إلى وحدة التحكم - + Flush log output on each line مسح إخراج السجل على كل سطر - + Enable FS Access Log - FS تمكين سجل وصول + FS تفعيل سجل وصول - + Enable Verbose Reporting Services** - تمكين خدمات التقارير التفصيلية + تفعيل خدمات التقارير التفصيلية - + Censor username in logs اخفي اسم المستخدم في السجلات - + **This will be reset automatically when Eden closes. **سيتم إعادة تعيين هذا تلقائيًا عند إغلاق إيدن. @@ -3144,7 +3084,7 @@ When a program attempts to open the controller applet, it is immediately closed. Choose an action for the save data directory: - اختر إجراءً لمجلد بيانات الحفظ: + :اختر إجراءً لمجلد بيانات الحفظ @@ -3312,33 +3252,38 @@ Would you like to delete the old save data? :لون الخلفية - + % - FSR sharpening percentage (e.g. 50%) + FSR/SGSR sharpening percentage (e.g. 50%) % - + + Configure Effects... + إعداد التأثيرات... + + + Off معطل - + VSync Off VSync معطل - + Recommended مستحسن - + On مفعل - + VSync On VSync مفعل @@ -3458,7 +3403,7 @@ Would you like to delete the old save data? [waiting] - [انتظار] + [جارٍ الانتظار] @@ -3795,7 +3740,7 @@ Would you like to delete the old save data? Ring Controller - وحدة تحكم الحلقة + ذراع تحكم الحلقة @@ -3822,12 +3767,12 @@ Would you like to delete the old save data? Enable XInput 8 player support (disables web applet) - (يعطل تطبيق الويب الصغير) XInput 8 تمكين دعم مشغل + (يعطل تطبيق الويب الصغير) XInput 8 تفعيل دعم مشغل Enable UDP controllers (not needed for motion) - (غير مطلوبة للحركة) UDP تمكين وحدات التحكم + (غير مطلوبة للحركة) UDP تفعيل وحدات التحكم @@ -3837,12 +3782,12 @@ Would you like to delete the old save data? Enable direct JoyCon driver - تمكين برنامج تشغيل جوي كون المباشر + تفعيل برنامج تشغيل جوي كون المباشر Enable direct Pro Controller driver [EXPERIMENTAL] - المباشر [تجريبي] Pro Controller تمكين برنامج تشغيل + المباشر [تجريبي] Pro Controller تفعيل برنامج تشغيل @@ -3859,6 +3804,16 @@ Would you like to delete the old save data? Motion / Touch حركة / لمس + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + Disable SDL WGI/XInput (Requires restart) + (يتطلب إعادة التشغيل) SDL WGI/XInput تعطيل + ConfigureInputPerGame @@ -4211,7 +4166,7 @@ Would you like to delete the old save data? Toggle button - زر تشغيل/إيقاف + زر تفعيل/تعطيل @@ -4240,7 +4195,7 @@ Would you like to delete the old save data? Toggle axis - تشغيل/إيقاف المحور + تفعيل/تعطيل المحور @@ -4365,61 +4320,61 @@ To invert the axes, first move your joystick vertically, and then horizontally.< [waiting] - [انتظار] + [جارٍ الانتظار] - + New Profile ملف تعريف جديد - + Enter a profile name: - أدخل اسم ملف التعريف: + :أدخل اسم ملف التعريف - - + + Create Input Profile إنشاء ملف تعريف الإدخال - + The given profile name is not valid! اسم ملف التعريف المحدد غير صالح! - + Failed to create the input profile "%1" "%1" فشل في إنشاء ملف تعريف الإدخال - + Delete Input Profile حذف ملف تعريف الإدخال - + Failed to delete the input profile "%1" "%1" فشل في مسح ملف تعريف الإدخال - + Load Input Profile تحميل ملف تعريف الإدخال - + Failed to load the input profile "%1" "%1" فشل في تحميل ملف تعريف الإدخال - + Save Input Profile حفظ ملف تعريف الإدخال - + Failed to save the input profile "%1" "%1" فشل في حفظ ملف تعريف الإدخال @@ -4467,7 +4422,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Configure الإعدادات @@ -4498,7 +4453,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Test إختبار @@ -4513,77 +4468,77 @@ To invert the axes, first move your joystick vertically, and then horizontally.< إزالة الخادم - + %1:%2 %1:%2 - - - - - - + + + + + + Eden إيدن - + Port number has invalid characters يحتوي رقم المنفذ على أحرف غير صالحة - + Port has to be in range 0 and 65353 يجب أن يكون المنفذ في النطاق 0 و 65353 - + IP address is not valid غير صالح IP عنوان - + This UDP server already exists هذا موجود بالفعل UDP خادم - + Unable to add more than 8 servers غير قادر على إضافة أكثر من 8 خوادم - + Testing - اختبار + جارٍ الاختبار - + Configuring الإعدادات - + Test Successful تم الاختبار بنجاح - + Successfully received data from the server. تم استلام البيانات من الخادم بنجاح. - + Test Failed فشل الاختبار - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. تعذر تلقي بيانات صالحة من الخادم.<br> يرجى التحقق من إعداد الخادم بشكل صحيح ومن صحة العنوان والمنفذ. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. أو ضبط المعايرة قيد التقدم. <br>يرجى الانتظار حتى الانتهاء UDP اختبار @@ -4598,7 +4553,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< Enable mouse panning - تمكين تحريك الماوس + تفعيل تحريك الماوس @@ -4674,12 +4629,12 @@ Current values are %1% and %2% respectively. Emulated mouse is enabled. This is incompatible with mouse panning. - تم تمكين الماوس الذي تمت محاكاته. وهذا غير متوافق مع تحريك الماوس. + تم تفعيل الماوس الذي تمت محاكاته. وهذا غير متوافق مع تحريك الماوس. Emulated mouse is enabled - تم تمكين الماوس الذي تمت محاكاته + تم تفعيل الماوس الذي تمت محاكاته @@ -4712,7 +4667,7 @@ Current values are %1% and %2% respectively. Enable Airplane Mode - تمكين وضع الطيران + تفعيل وضع الطيران @@ -4768,57 +4723,57 @@ Current values are %1% and %2% respectively. بعض الإعدادات متوفرة فقط عندما لا تكون اللعبة قيد التشغيل. - + Add-Ons الإضافات - + System النظام - + CPU المعالج - + Graphics الرسومات - + Adv. Graphics الرسومات المتقدمة - + Ext. Graphics الرسومات الخارجية - + Audio الصوت - + Input Profiles ملفات تعريف الإدخال - + Network الشبكة - + Applets التطبيقات الصغيرة - + Properties خصائص @@ -4946,6 +4901,131 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t &فتح في مدير الملفات + + ConfigurePostProcessing + + + Post-Processing Effects + تأثيرات ما بعد المعالجة + + + + Changes apply immediately while a game is running. + تُطبق التغييرات فورًا أثناء سير اللعبة. + + + + Enable post-processing + تفعيل المعالجة اللاحقة + + + + Preset: + :الإعداد المسبق + + + + Apply + تطبيق + + + + Reset Values + إعادة تعيين القيم + + + + Return every effect to the values the preset ships with. + إعادة كل تأثير إلى القيم التي تأتي بها الإعدادات المسبقة. + + + + Save As... + حفظ باسم... + + + + Save Preset + حفظ الإعداد المسبق + + + + Preset name: + :اسم الإعداد المسبق + + + + Clear + مسح + + + + Remove every effect in use and deselect the preset. + قم بإزالة جميع المؤثرات قيد الاستخدام وإلغاء تحديد الإعداد المسبق. + + + + Add Effect + إضافة تأثير + + + + None + لا شيء + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + لا توجد تأثيرات قيد الاستخدام. اختر إعدادًا مسبقًا واضغط على تطبيق، أو أضف التأثيرات واحدًا تلو الآخر. + + + + Custom chain. Pick a preset above and press Apply to replace it. + سلسلة مخصصة. اختر إعدادًا مسبقًا أعلاه واضغط على تطبيق لاستبداله. + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + الإعداد المسبق النشط: %1 (معدّل). اضغط على إعادة تعيين القيم للعودة إلى الإعدادات الافتراضية. + + + + Active preset: %1 + الإعداد المسبق النشط: %1 + + + + Enabled + تفعيل + + + + Disabled + تعطيل + + + + Reset to defaults + إعادة التعيين إلى الإعدادات الافتراضية + + + + Effect '%1' was not found. + لم يتم العثور على التأثير '1%'. + + + + Effect failed to compile: +%1 + فشل تجميع التأثير: +%1 + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + ConfigureProfileManager @@ -5024,7 +5104,7 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t Unable to create directory %1 for storing user images. - غير قادر على إنشاء الدليل %1 لتخزين صور المستخدم. + تعذر إنشاء المجلد %1 لتخزين صور المستخدم. @@ -5077,7 +5157,7 @@ UUID: %2 Configure Ring Controller - إعدادات حلقة وحدة التحكم + إعدادات حلقة ذراع التحكم @@ -5114,7 +5194,7 @@ UUID: %2 Enable Ring Input - تمكين إدخال الحلقة + تفعيل إدخال الحلقة @@ -5162,12 +5242,12 @@ UUID: %2 Error enabling ring input - حدث خطأ أثناء تمكين إدخال الحلقة + حدث خطأ أثناء تفعيل إدخال الحلقة Direct Joycon driver is not enabled - لم يتم تمكين برنامج تشغيل جوي كون المباشر + لم يتم تفعيل برنامج تشغيل جوي كون المباشر @@ -5177,12 +5257,12 @@ UUID: %2 The current mapped device doesn't support the ring controller - الجهاز المُعيَّن حاليًا لا يدعم وحدة التحكم بالحلقة + الجهاز المعين حاليا لا يدعم ذراع التحكم بالحلقة The current mapped device doesn't have a ring attached - الجهاز المُعَيَّن حاليًا ليس به حلقة مُرفقة + الجهاز المعين حاليا ليس به حلقة مرفقة @@ -5197,7 +5277,7 @@ UUID: %2 [waiting] - [انتظار] + [جارٍ الانتظار] @@ -5254,7 +5334,7 @@ UUID: %2 Enable TAS features - TAS تمكين ميزات + TAS تفعيل ميزات @@ -5513,91 +5593,86 @@ Drag points to change position, or double-click table cells to edit values. - Show Compatibility List - عرض قائمة التوافق - - - Show Add-Ons Column عرض عمود الإضافات - + Show Size Column عرض عمود الحجم - + Show File Types Column عرض عمود أنواع الملفات - + Show Play Time Column عرض عمود زمن اللعب - + Folder Icon Size: :حجم أيقونة المجلد - + Row 1 Text: :نص السطر 1 - + Row 2 Text: :نص السطر 2 - + Screenshots لقطات الشاشة - + Ask Where To Save Screenshots (Windows Only) السؤال عن مكان حفظ لقطات الشاشة (ويندوز فقط) - + Screenshots Path: :مسار لقطات الشاشة - + ... ... - + TextLabel تسمية النص - + Resolution: :الدقة - + Select Screenshots Path... أختر مسار لقطات الشاشة - + <System> <System> - + English - الإنجليزية + English - + Auto (%1 x %2, %3 x %4) Screenshot width value تلقائي (%1 x %2, %3 x %4) @@ -5680,7 +5755,7 @@ Drag points to change position, or double-click table cells to edit values. Enable Accurate Vibration - تمكين الاهتزاز الدقيق + تفعيل الاهتزاز الدقيق @@ -5723,7 +5798,7 @@ Drag points to change position, or double-click table cells to edit values. Discord Presence - الحضور على ديسكورد + التواجد على ديسكورد @@ -5821,7 +5896,7 @@ Drag points to change position, or double-click table cells to edit values. Delete all data in this directory. THIS IS 100% IRREVERSABLE! - حذف جميع البيانات الموجودة في هذا الدليل. هذا الإجراء لا رجعة فيه بنسبة 100%! + حذف جميع البيانات الموجودة في هذا المجلد. هذه العملية لا رجعة فيها بنسبة 100%! @@ -5834,7 +5909,7 @@ Drag points to change position, or double-click table cells to edit values.استيراد البيانات لهذا المجلد. قد يستغرق هذا بعض الوقت، وسيؤدي إلى حذف جميع البيانات الموجودة! - + Calculating... يتم الحساب... @@ -5915,7 +5990,7 @@ Drag points to change position, or double-click table cells to edit values. Connecting - الاتصال + جارٍ الاتصال @@ -6038,50 +6113,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL غير متوفر! - + OpenGL shared contexts are not supported. OpenGL لا يتم دعم السياقات المشتركة - + Eden has not been compiled with OpenGL support. OpenGL لم يتم تجميع إيدن بدعم - - - + + + Error while initializing OpenGL! OpenGL حدث خطأ أثناء تهيئة - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. أو قد لا يكون لديك أحدث برنامج تشغيل للرسومات OpenGL قد لا تدعم بطاقة الرسومات الخاصة بك - + Error while initializing OpenGL 4.6! OpenGL 4.6 حدث خطأ أثناء تهيئة - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 أو قد لا يكون لديك أحدث برنامج تشغيل للرسومات OpenGL 4.6 قد لا تدعم بطاقة الرسومات الخاصة بك.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 قد لا تدعم وحدة معالجة الرسومات لديك ملحقًا واحدًا أو أكثر من ملحقات OpenGL المطلوبة. يُرجى التأكد من تثبيت أحدث برنامج تشغيل للرسومات.<br><br>GL Renderer:<br>1%<br><br>إضافات غير مدعومة: <br>2% - + This build doesn't have OpenGL support. هذا الإصدار لا يدعم OpenGL. @@ -6089,279 +6164,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory &إضافة مجلد ألعاب جديد - + Favorite مفضلة - + Start Game بدء اللعبة - + Start Game without Custom Configuration بدء اللعبة بدون الإعدادات المخصصة - + Open Save Data Location فتح موقع بيانات الحفظ - + Open Mod Data Location فتح موقع بيانات التعديلات - + Open Transferable Pipeline Cache ذاكرة التخزين المؤقتة المفتوحة القابلة للتحويل - + Link to Ryujinx Ryujinx ربط بـ - + Remove إزالة - + Remove Installed Update إزالة التحديث المثبت - + Remove All Installed DLC إزالة جميع المحتويات القابلة للتنزيل المثبتة - + Remove Custom Configuration إزالة الإعدادات المخصصة - + Remove Cache Storage إزالة تخزين ذاكرة التخزين المؤقتة - + Remove OpenGL Pipeline Cache OpenGL إزالة ذاكرة التخزين المؤقتة لخط أنابيب - + Remove Vulkan Pipeline Cache Vulkan إزالة ذاكرة التخزين المؤقتة لخط أنابيب - + Remove All Pipeline Caches إزالة جميع ذاكرات التخزين المؤقتة لخط الأنابيب - + Remove All Installed Contents إزالة كافة المحتويات المثبتة - + Manage Play Time إدارة زمن اللعب - + Edit Play Time Data تعديل بيانات زمن التشغيل - + Remove Play Time Data إزالة بيانات زمن اللعب - - + + Dump RomFS RomFS تفريغ - + Dump RomFS to SDMC SDMC إلى RomFS تفريغ - + Verify Integrity التحقق من السلامة - + Copy Title ID to Clipboard نسخ معرف العنوان إلى الحافظة - - Navigate to GameDB entry - انتقل إلى إدخال قاعدة بيانات الألعاب - - - + Create Shortcut إنشاء إختصار - + Add to Desktop إضافة إلى سطح المكتب - + Add to Applications Menu إضافة إلى قائمة التطبيقات - + Configure Game إعدادات اللعبة - + Scan Subfolders مسح الملفات الداخلية - + Remove Game Directory إزالة مجلد اللعبة - + ▲ Move Up ▲ نقل للأعلى - + ▼ Move Down ▼ نقل للأسفل - + Open Directory Location فتح موقع المجلد - + Clear مسح - - - Name - الاسم - - - - Compatibility - التوافق - - - - Add-ons - الإضافات - - - - File type - نوع الملف - - - - Size - الحجم - - - - Play time - زمن اللعب - GameListItemCompat - + Ingame في اللعبة - + Game starts, but crashes or major glitches prevent it from being completed. تبدأ اللعبة، لكن الأعطال أو الأخطاء الرئيسية تمنعها من الاكتمال. - + Perfect مثالي - + Game can be played without issues. يمكن لعب اللعبة بدون مشاكل. - + Playable قابل للعب - + Game functions with minor graphical or audio glitches and is playable from start to finish. تحتوي وظائف اللعبة على بعض الأخطاء الرسومية أو الصوتية البسيطة ويمكن تشغيلها من البداية إلى النهاية. - + Intro/Menu مقدمة/القائمة - + Game loads, but is unable to progress past the Start Screen. يتم تحميل اللعبة، ولكنها غير قادرة على التقدم بعد شاشة البدء. - + Won't Boot لا تشتغل - + The game crashes when attempting to startup. تعطل اللعبة عند محاولة بدء التشغيل. - + Not Tested لم تختبر - + The game has not yet been tested. لم يتم اختبار اللعبة بعد. + + GameListModel + + + Name + الاسم + + + + Add-ons + الإضافات + + + + File type + نوع الملف + + + + Size + الحجم + + + + Play time + زمن اللعب + + GameListPlaceholder - + Double-click to add a new folder to the game list انقر نقرًا مزدوجًا لإضافة مجلد جديد إلى قائمة الألعاب @@ -6369,17 +6437,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) %1 من %n نتيجة (نتائج)%1 من %n نتيجة (نتائج)%1 من %n نتيجة (نتائج)%1 من %n نتيجة (نتائج)%1 من %n نتيجة (نتائج)%1 من %n نتيجة (نتائج) - + Filter: :تصفية - + Enter pattern to filter أدخل النمط المطلوب لتصفية النتائج @@ -6634,42 +6702,42 @@ Debug Message: Toggle Filter Bar - تشغيل/إيقاف شريط التصفية + تفعيل/تعطيل شريط التصفية Toggle Framerate Limit - تشغيل/إيقاف حد معدل الإطارات + تفعيل/تعطيل حد معدل الإطارات Toggle Turbo Speed - تشغيل/إيقاف سرعة تيربو + تفعيل/تعطيل سرعة تيربو Toggle Slow Speed - تشغيل/إيقاف السرعة البطيئة + تفعيل/تعطيل السرعة البطيئة Toggle Mouse Panning - تحريك الماوس + تفعيل/تعطيل تحريك الماوس Toggle Renderdoc Capture - Renderdoc تشغيل/إيقاف التقاط + Renderdoc تفعيل/تعطيل التقاط Toggle Status Bar - تشغيل/إيقاف شريط الحالة + تفعيل/تعطيل شريط الحالة Toggle Performance Overlay - تشغيل/إيقاف طبقة الأداء + تفعيل/تعطيل طبقة الأداء @@ -6875,772 +6943,781 @@ Debug Message: &وضع قائمة الألعاب - + Game &Icon Size حجم &أيقونة اللعبة - + Reset Window Size to &720p 720p إعادة تعيين حجم النافذة إلى - + Reset Window Size to 720p 720p إعادة تعيين حجم النافذة إلى - + Reset Window Size to &900p 900p إعادة تعيين حجم النافذة إلى - + Reset Window Size to 900p 900p إعادة تعيين حجم النافذة إلى - + Reset Window Size to &1080p 1080p إعادة تعيين حجم النافذة إلى - + Reset Window Size to 1080p 1080p إعادة تعيين حجم النافذة إلى - + &Multiplayer &متعدد اللاعبين - + &Tools &أدوات - + Am&iibo أم&يبو - + Launch &Applet تشغيل &التطبيق المصغر - + &TAS &TAS - + &Create Home Menu Shortcut &إنشاء اختصار لقائمة الشاشة الرئيسية - + Install &Firmware تثبيت &الفيرموير - + &Help &مساعدة - + &Install Files to NAND... &تثبيت الملفات في الذاكرة الداخلية - + L&oad File... ت&حميل ملف - + Load &Folder... تحميل &مجلد - + E&xit خ&روج - - + + &Pause &إيقاف مؤقت - + &Stop &إيقاف - + &Verify Installed Contents &التحقق من المحتويات المثبتة - + &About Eden &حول إيدن - + Single &Window Mode وضع &النافذة الواحدة - + Con&figure... الإع&دادات - + Ctrl+, Ctrl+, - + Enable Overlay Display Applet - تمكين التطبيق المصغر لعرض الطبقة + تفعيل التطبيق المصغر لعرض الطبقة - + Show &Filter Bar عرض &شريط التصفية - + Show &Status Bar عرض &شريط الحالة - + Show Status Bar عرض شريط الحالة - + &Browse Public Game Lobby &تصفح قائمة الألعاب العامة - + &Create Room &إنشاء غرفة - + &Leave Room &مغادرة الغرفة - + &Direct Connect to Room &الاتصال المباشر بالغرفة - + &Show Current Room &عرض الغرفة الحالية - + F&ullscreen م&لء الشاشة - + &Restart &إعادة التشغيل - + Load/Remove &Amiibo... تحميل/إزالة &أميبو - - &Report Compatibility - &تقرير التوافق - - - + Open &Mods Page صفحة &التعديلات - - Open &Quickstart Guide - دليل &البدء السريع + + &User Handbook + &دليل المستخدم - - &FAQ - &الأسئلة الشائعة - - - + &Capture Screenshot &التقاط لقطة للشاشة - + &Album &الألبوم - + &Set Nickname and Owner &تعيين الاسم المستعار والمالك - + &Delete Game Data حذف بيانات اللعبة - + &Restore Amiibo &استعادة أميبو - + &Format Amiibo &تنسيق أميبو - + &Mii Editor &Mii محرر - + &Configure TAS... &TAS إعدادات - + Configure C&urrent Game... إعدادات اللعبة ال&حالية - - + + &Start &بدء - + &Reset &إعادة تعيين - - + + R&ecord ت&سجيل - + Open &Controller Menu &قائمة ذراع التحكم - + Install Decryption &Keys تثبيت &مفاتيح فك التشفير - + &Home Menu &القائمة الرئيسية - + &Desktop &سطح المكتب - + &Application Menu &قائمة التطبيقات - + &Root Data Folder &مجلد البيانات الرئيسي - + &NAND Folder &مجلد الذاكرة الداخلية - + &SDMC Folder &مجلد بطاقة الذاكرة - + &Mod Folder &مجلد التعديلات - + &Log Folder &مجلد السجلات - + From Folder من مجلد - + From ZIP من ملف مضغوط - + &Eden Dependencies &تبعيات إيدن - + &Data Manager &إدارة البيانات - + &Tree View &عرض قائمة - + &Grid View &عرض شبكة - + Game Icon Size حجم أيقونة اللعبة - - + + None لا شيء - + Show Game &Name عرض اسم &اللعبة - + Show &Performance Overlay عرض &طبقة الأداء - + + Post-Processing &Shaders... + المعالجة اللاحقة &الظلال... + + + + &Carousel View + &عرض دائري + + + Small (32x32) - صغير (32x32) + (32x32) صغير - + Standard (64x64) - قياسي (64x64) + (64x64) قياسي - + Large (128x128) - كبير (128x128) + (128x128) كبير - + Full Size (256x256) - حجم كامل (256x256) + (256x256) حجم كامل - + Broken Vulkan Installation Detected Vulkan تم الكشف عن تلف في تثبيت - + Vulkan initialization failed during boot. أثناء التشغيل Vulkan فشل تهيئة. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping تشغيل لعبة - + Loading Web Applet... جارٍ تحميل تطبيق الويب... - - + + Disable Web Applet تعطيل تطبيق الويب - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) قد يؤدي تعطيل أداة الويب إلى سلوك غير محدد، ويجب استخدامه فقط مع لعبة Super Mario 3D All-Stars. هل أنت متأكد من رغبتك في تعطيل أداة الويب؟ (يمكن إعادة تفعيلها في إعدادات التصحيح.) - + The amount of shaders currently being built كمية التظليلات التي يتم بناؤها حاليًا - + The current selected resolution scaling multiplier. مضاعف قياس الدقة المحددة حالياً. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. Switch سرعة المحاكاة الحالية. تشير القيم الأعلى أو الأقل من 100٪ إلى أن المحاكاة تعمل بشكل أسرع أو أبطأ من. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. عدد الإطارات في الثانية التي تعرضها اللعبة حاليًا. يختلف هذا العدد من لعبة إلى أخرى ومن مشهد إلى آخر. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. الوقت المستغرق لمحاكاة إطار على جهاز سويتش، بدون احتساب تحديد الإطارات أو المزامنة العمودية. لمحاكاة بسرعة كاملة، يجب أن يكون هذا في حدود 16.67 مللي ثانية كحد أقصى. - + Unmute إلغاء كتم الصوت - + Mute كتم الصوت - + Reset Volume إعادة تعيين مستوى الصوت - + + Configure Effects... + إعداد التأثيرات... + + + &Clear Recent Files &مسح الملفات الحديثة - + &Continue &متابعة - + Warning: Outdated Game Format تحذير: تنسيق اللعبة قديم - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. أنت تستخدم تنسيق مجلد ROM المُفكك لهذه اللعبة، وهو تنسيق قديم استُبدل بآخر مثل NCA وNAX وXCI وNSP. تفتقر مجلدات ROM المُفككة إلى الأيقونات والبيانات الوصفية ودعم التحديثات. <br>لتوضيح تنسيقات Switch المختلفة التي يدعمها Eden، يُرجى مراجعة دليل المستخدم. لن تظهر هذه الرسالة مرة أخرى. - - + + Error while loading ROM! ROM خطأ أثناء تحميل - + The ROM format is not supported. غير مدعوم ROM تنسيق. - + An error occurred initializing the video core. حدث خطأ أثناء تهيئة نواة الفيديو. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. - واجه إيدن خطأً أثناء تشغيل نواة الفيديو. عادةً ما يكون السبب هو برامج تشغيل وحدة معالجة الرسومات القديمة، بما في ذلك المدمجة منها. يُرجى مراجعة السجل لمزيد من التفاصيل. لمزيد من المعلومات حول الوصول إلى السجل، يُرجى زيارة الصفحة التالية: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>كيفيه رفع سجلات الإخطاء</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. + عادةً ما يحدث هذا بسبب برامج تشغيل وحدة معالجة الرسومات القديمة. يرجى الاطلاع على السجل لمزيد من التفاصيل. انظر: +<a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>كيفية الوصول إلى ملفات السجلات</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. حدث خطأ أثناء تحميل ROM! %1 - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. %1<br>Discord/Stoat يرجى إعادة تحميل ملفاتك أو طلب المساعدة على - + An unknown error occurred. Please see the log for more details. حدث خطأ غير معروف. يرجى الاطلاع على السجل لمزيد من التفاصيل. - + + (64-bit) (64-bit) - + + (32-bit) (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit %1 %2 - + Closing software... جارٍ إغلاق البرنامج... - + Save Data بيانات الحفظ - + Mod Data بيانات التعديل - + Error Opening %1 Folder خطأ في فتح المجلد %1 - - + + Folder does not exist! المجلد غير موجود! - + Remove Installed Game Contents? إزالة محتويات اللعبة المثبتة؟ - + Remove Installed Game Update? إزالة تحديث اللعبة المثبت؟ - + Remove Installed Game DLC? إزالة المحتوى القابل للتنزيل المثبت للعبة؟ - + Remove Entry إزالة الإدخال - + Delete OpenGL Transferable Shader Cache? OpenGL Shader حذف ذاكرة التخزين المؤقتة القابلة للنقل لـ - + Delete Vulkan Transferable Shader Cache? Vulkan Shader حذف ذاكرة التخزين المؤقتة القابلة للنقل لـ - + Delete All Transferable Shader Caches? حذف جميع مخازن ذاكرة التظليل القابلة للنقل؟ - + Remove Custom Game Configuration? إزالة إعدادات اللعبة المخصص؟ - + Remove Cache Storage? إزالة ذاكرة التخزين المؤقتة؟ - + Remove File إزالة الملف - + Remove Play Time Data إزالة بيانات زمن اللعب - + Reset play time? إعادة تعيين زمن اللعب؟ - - + + RomFS Extraction Failed! RomFS فشل استخراج - + There was an error copying the RomFS files or the user cancelled the operation. أو قام المستخدم بإلغاء العملية RomFS حدث خطأ أثناء نسخ ملفات - + Full كامل - + Skeleton Skeleton - + Select RomFS Dump Mode RomFS حدد وضع تفريغ - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. الرجاء تحديد الطريقة التي تريد بها تفريغ RomFS. <br>سيتم نسخ جميع الملفات إلى المجلد الجديد في أثناء <br>قيام الهيكل العظمي بإنشاء بنية المجلد فقط. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root لا توجد مساحة فارغة كافية في %1 لاستخراج نظام الملفات RomFS. يُرجى تحرير مساحة أو اختيار مجلد تفريغ آخر من: المحاكاة > التكوين > النظام > نظام الملفات > تفريغ الجذر. - + Extracting RomFS... RomFS استخراج... - - + + Cancel إلغاء - + RomFS Extraction Succeeded! بنجاح RomFS تم استخراج! - + The operation completed successfully. تمت العملية بنجاح. - + Error Opening %1 خطأ في فتح %1 - + Select Directory حدد المجلد - + Properties خصائص - + The game properties could not be loaded. تعذر تحميل خصائص اللعبة. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. تشغيل الملف القابل للتنفيذ (%1)؛؛جميع الملفات (*.*) - + Load File تحميل الملف - + Open Extracted ROM Directory المستخرج ROM فتح ملف - + Invalid Directory Selected تم تحديد مجلد غير صالح - + The directory you have selected does not contain a 'main' file. لا يحتوي المجلد الذي حددته على ملف رئيسي - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files تثبيت الملفات - + %n file(s) remaining %n ملف (ملفات) متبقية%n ملف (ملفات) متبقية%n ملف (ملفات) متبقية%n ملف (ملفات) متبقية%n ملف (ملفات) متبقية%n ملف (ملفات) متبقية - + Installing file "%1"... تثبيت الملف ”%1“... - - + + Install Results نتائج التثبيت - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. لتجنب أي تعارضات محتملة، ننصح المستخدمين بعدم تثبيت الألعاب الأساسية على الذاكرة الداخلية. يرجى استخدام هذه الميزة فقط لتثبيت التحديثات والمحتوى القابل للتنزيل. - + %n file(s) were newly installed %n تم تثبيت ملف (ملفات) جديدة @@ -7652,7 +7729,7 @@ Please, only use this feature to install updates and DLC. - + %n file(s) were overwritten %n تم استبدال ملف (ملفات) @@ -7664,7 +7741,7 @@ Please, only use this feature to install updates and DLC. - + %n file(s) failed to install %n فشل تثبيت ملف (ملفات) @@ -7676,361 +7753,324 @@ Please, only use this feature to install updates and DLC. - + System Application تطبيق النظام - + System Archive أرشيف النظام - + System Application Update تحديث تطبيق النظام - + Firmware Package (Type A) حزمة الفيرموير (النوع أ) - + Firmware Package (Type B) حزمة الفيرموير (النوع ب) - + Game لعبة - + Game Update تحديث اللعبة - + Game DLC الخاص باللعبة DLC الـ - + Delta Title Delta عنوان - + Select NCA Install Type... NCA حدد نوع تثبيت... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) يرجى تحديد نوع اللعبة التي ترغب في تثبيت NCA عليها: (في معظم الحالات، يكون الإعداد الافتراضي "لعبة" مناسبًا.) - + Failed to Install فشل التثبيت - + The title type you selected for the NCA is invalid. غير صالح NCA نوع العنوان الذي حددته لـ. - + File not found لم يتم العثور على الملف - + File "%1" not found لم يتم العثور على الملف "%1" - + OK موافق - + Function Disabled الوظيفة معطلة - + Compatibility list reporting is currently disabled. Check back later! تقارير قائمة التوافق معطلة حاليًا. يرجى التحقق لاحقًا! - + Error opening URL خطأ في فتح الرابط - + Unable to open the URL "%1". تعذر فتح الرابط ”%1“ - + TAS Recording TAS تسجيل - + Overwrite file of player 1? الكتابة فوق ملف اللاعب 1؟ - + Invalid config detected تم اكتشاف إعدادات غير صالح - + Handheld controller can't be used on docked mode. Pro controller will be selected. لا يمكن استخدام وحدة التحكم المحمولة في وضع الإرساء. سيتم اختيار وحدة تحكم احترافية. - - + + Amiibo أميبو - - + + The current amiibo has been removed تمت إزالة أميبو الحالي. - + Error خطأ - - + + The current game is not looking for amiibos اللعبة الحالية لا تبحث عن أميبو - + Amiibo File (%1);; All Files (*.*) جميع الملفات (%1)؛؛ ملف أميبو (*.*) - + Load Amiibo تحميل أميبو - + Error loading Amiibo data خطأ أثناء تحميل بيانات أميبو - + The selected file is not a valid amiibo الملف المحدد ليس أميبو صالح - + The selected file is already on use الملف المحدد قيد الاستخدام بالفعل - + An unknown error occurred حدث خطأ غير معروف - - - Keys not installed - المفاتيح غير مثبتة - - - - - Install decryption keys and restart Eden before attempting to install firmware. - قم بتثبيت مفاتيح فك التشفير وأعد تشغيل إيدن قبل محاولة تثبيت الفيرموير. - - - - Select Dumped Firmware Source Location - حدد موقع مصدر الفيرموير المفرغة - - - - Select Dumped Firmware ZIP - حدد ملف الفيرموير المضغوط الذي تم تفريغه - - - - Zipped Archives (*.zip) - Zipped Archives (*.zip) - - - - Firmware cleanup failed - فشل مسح الفيرموير - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - فشل تنظيف ذاكرة التخزين المؤقتة للفيرموير المستخرج. -تحقق من أذونات الكتابة في دليل الملفات المؤقتة للنظام وحاول مرة أخرى. -أبلغ نظام التشغيل عن خطأ: %1 - - - + No firmware available لا يوجد فيرموير متوفر - + Firmware Corrupted الفيرموير تالف - + Unknown applet تطبيق غير معروف - + Applet doesn't map to a known value. لا يرتبط التطبيق المصغر بقيمة معروفة. - + Record not found لم يتم العثور على السجل - + Applet not found. Please reinstall firmware. لم يتم العثور على التطبيق المصغر. يرجى إعادة تثبيت الفيرموير. - + Capture Screenshot التقاط لقطة شاشة - + PNG Image (*.png) PNG Image (*.png) - + TAS state: Running %1/%2 حالة TAS: تشغيل %1/%2 - + TAS state: Recording %1 حالة TAS: تسجيل %1 - + TAS state: Idle %1/%2 حالة TAS: خامل %1/%2 - + TAS State: Invalid حالة TAS: غير صالحة - + &Stop Running &إيقاف التشغيل - + Stop R&ecording إيقاف ال&تسجيل - + Building: %n shader(s) بناء: %n تظليل(ات)بناء: %n تظليل(ات)بناء: %n تظليل(ات)بناء: %n تظليل(ات)بناء: %n تظليل(ات)بناء: %n تظليل(ات) - + Scale: %1x %1 is the resolution scaling factor الدقة: %1x - + Speed: %1% / %2% السرعة: %1% / %2% - + Speed: %1% السرعة: %1% - + Game: %1 FPS - اللعبة: %1 FPS + اللعبة: %1 إطار - + Frame: %1 ms الإطار: %1 ms - - - FSR - FSR - - - + NO AA NO AA - + + NO FX + NO FX + + + + FX + FX + + + VOLUME: MUTE الصوت: كتم الصوت - + VOLUME: %1% Volume percentage (e.g. 50%) %1% :الصوت - + Derivation Components Missing مكونات الاشتقاق مفقودة - + Decryption keys are missing. Install them now? مفاتيح فك التشفير مفقودة. هل تريد تثبيتها الآن؟ - + Wayland Detected! Wayland تم الكشف عن - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -8041,74 +8081,74 @@ Would you like to force it for future launches? هل ترغب في فرض استخدامه في عمليات التشغيل المستقبلية؟ - + Use X11 X11 استخدم - + Continue with Wayland Wayland متابعة مع - + Don't show again لا تعرض مرة أخرى - + Restart Required إعادة التشغيل مطلوبة - + Restart Eden to apply the X11 backend. X11 أعد تشغيل إيدن لتطبيق الخلفية - + Slow بطيء - + Turbo تيربو - + Unlocked إلغاء القفل - + Select RomFS Dump Target RomFS حدد هدف تفريغ - + Please select which RomFS you would like to dump. الذي تريد تفريغه RomFS الرجاء تحديد - + Are you sure you want to close Eden? هل أنت متأكد من أنك تريد إغلاق إيدن؟ - - - + + + Eden إيدن - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. هل أنت متأكد من أنك تريد إيقاف المحاكاة؟ سيتم فقدان أي تقدم لم يتم حفظه. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8176,6 +8216,11 @@ Would you like to bypass this and exit anyway? ScaleForce ScaleForce + + + FSR + FSR + Area @@ -8187,52 +8232,57 @@ Would you like to bypass this and exit anyway? MMPX - + + SGSR + SGSR + + + + SGSR EdgeDir + SGSR EdgeDir + + + Docked الإرساء - + Handheld محمول - + Fast سريع - - Balanced - متوازن - - - + Accurate دقه - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL SPIRV OpenGL SPIRV - + OpenGL GLASM OpenGL GLASM - + Null لا شيء @@ -8271,11 +8321,11 @@ If you wish to clean up the files which were left in the old data location, you %1 -إذا كنت ترغب في تنظيف الملفات التي تركت في موقع البيانات القديم، يمكنك القيام بذلك عن طريق حذف الدليل التالي: +إذا كنت ترغب في إزالة الملفات التي بقيت في موقع البيانات القديم، فيمكنك القيام بذلك عن طريق حذف المجلد التالي: %1 - + Data was migrated successfully. تم نقل البيانات بنجاح. @@ -8617,7 +8667,7 @@ p, li { white-space: pre-wrap; } FPS - معدل الإطارات في الثانية + إطار في الثانية @@ -8627,7 +8677,7 @@ p, li { white-space: pre-wrap; } %1 fps - %1 معدل الإطارات في الثانية + %1 إطار في الثانية @@ -8768,8 +8818,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8782,10 +8832,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8800,9 +8850,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8964,50 +9014,50 @@ p, li { white-space: pre-wrap; } [غير معرف] - + %1%2 %1%2 - + [invalid] [غير صالح] - - + + %1%2Hat %3 %1%2Hat %3 - - - + + + %1%2Axis %3 %1%2محور %3 - + %1%2Axis %3,%4,%5 %1%2محور %3,%4,%5 - + %1%2Motion %3 %1%2حركة %3 - - + + %1%2Button %3 %1%2زر %3 - + [unused] [غير مستخدم] @@ -9133,47 +9183,47 @@ p, li { white-space: pre-wrap; } %1 يلعب %2 - + Play Time: %1 زمن اللعب: %1 - + Never Played لم تُلعب قط - + Version: %1 الإصدار: %1 - + Version: 1.0.0 الإصدار: 1.0.0 - + Installed SD Titles عناوين المثبتة على بطاقة الذاكرة - + Installed NAND Titles عناوين المثبتة على الذاكرة الداخلية - + System Titles عناوين النظام - + Add New Game Directory إضافة مجلد ألعاب جديد - + Favorites المفضلة @@ -9223,7 +9273,7 @@ p, li { white-space: pre-wrap; } Creation Date - تاريخ الانشاء + تاريخ الإنشاء @@ -9268,7 +9318,7 @@ p, li { white-space: pre-wrap; } No game data present - لا توجد بيانات للعبة + لا توجد بيانات اللعبة @@ -9283,7 +9333,7 @@ p, li { white-space: pre-wrap; } Set nickname and owner: - تعيين الاسم المستعار والمالك + :تعيين الاسم المستعار والمالك @@ -9294,14 +9344,14 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware تتطلب اللعبة الفيرموير - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. - اللعبة التي تحاول تشغيلها تتطلب فيرموير للتشغيل أو لتجاوز قائمة الفتح. يُرجى <a href='https://yuzu-mirror.github.io/help/quickstart'>تفريغ فيرموير وتثبيتها</a>، أو اضغط "حسنًا" للعب على أي حال + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. + قد تتطلب هذه اللعبة برنامجاً ثابتاً لتعمل بشكل صحيح، ولم يتم تثبيت أي منها لديك. يرجى تثبيت البرنامج الثابت قبل التشغيل، أو الضغط على "موافق" للتشغيل على أية حال. @@ -9310,10 +9360,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel إلغاء @@ -9334,216 +9384,251 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - فشل التحقق من الملفات التالية: + :فشل التحقق من الملفات التالية %1 - - + + Verifying integrity... جارٍ التحقق من السلامة... - - + + Integrity verification succeeded! تم التحقق من السلامة بنجاح! - - + + The operation completed successfully. اكتملت العملية بنجاح. - - + + Integrity verification failed! فشل التحقق من السلامة! - + File contents may be corrupt or missing. قد تكون محتويات الملف تالفة أو مفقودة. - + Integrity verification couldn't be performed تعذر إجراء التحقق من السلامة - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. تم إلغاء تثبيت الفيرموير، قد يكون الفيرموير في حالة سيئة أو تآلف. تعذر التحقق من صحة محتويات الملف. - + Select Dumped Keys Location حدد موقع المفاتيح المفرغة - + Decryption Keys install succeeded تم تثبيت مفاتيح فك التشفير بنجاح - + Decryption Keys install failed فشل تثبيت مفاتيح فك التشفير - + Orphaned Profiles Detected! تم الكشف عن ملفات تعريف مهملة! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> قد تحدث أمور سيئة غير متوقعة إذا لم تقرأ هذا<br>!لقد اكتشف إيدن مجلدات الحفظ التالية بدون ملف تعريف مرفق:<br>%1<br><br>ملفات التعريف التالية صالحة:<br>%2<br><br>انقر على ”موافق“ لفتح مجلد الحفظ وإصلاح ملفات التعريف الخاصة بك.<br>تلميح: انسخ محتويات المجلد الأكبر أو آخر مجلد تم تعديله إلى مكان آخر، واحذف جميع ملفات التعريف اليتيمة، وانقل المحتويات المنسوخة إلى ملف التعريف الصحيح.<br><br>هل ما زلت تشعر بالارتباك؟ انظر إلى <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>صفحة المساعدة</a>.<br> - + Really clear data? مسح البيانات بالفعل؟ - + Important data may be lost! قد يتم فقدان بيانات مهمة! - + Are you REALLY sure? هل أنت متأكد حقًا؟ - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. بمجرد حذفها، لن تتمكن من استعادة بياناتك! قم بذلك فقط إذا كنت متأكدًا بنسبة 100٪ أنك تريد حذف هذه البيانات. - + Clearing... إزالة... - + Select Export Location حدد موقع التصدير - + %1.zip %1.zip - - + + + Zipped Archives (*.zip) Zipped Archives (*.zip) - + Exporting data. This may take a while... تصدير البيانات. قد يستغرق هذا بعض الوقت... - + Exporting التصدير - + Exported Successfully تم التصدير بنجاح - + Data was exported successfully. تم تصدير البيانات بنجاح. - + Export Cancelled تم إلغاء التصدير - + Export was cancelled by the user. تم إلغاء التصدير من قبل المستخدم. - + Export Failed فشل التصدير - + Ensure you have write permissions on the targeted directory and try again. تأكد من أن لديك أذونات الكتابة على المجلد المحدد وحاول مرة أخرى. - + Select Import Location حدد موقع الاستيراد - + Import Warning تحذير الاستيراد - + All previous data in this directory will be deleted. Are you sure you wish to proceed? سيتم حذف جميع البيانات السابقة في هذا المجلد. هل أنت متأكد من رغبتك في المتابعة؟ - + Importing data. This may take a while... استيراد البيانات. قد يستغرق هذا بعض الوقت... - + Importing استيراد - + Imported Successfully تم الاستيراد بنجاح - + Data was imported successfully. تم استيراد البيانات بنجاح. - + Import Cancelled تم إلغاء الاستيراد - + Import was cancelled by the user. تم إلغاء الاستيراد من قبل المستخدم. - + Import Failed فشل الاستيراد - + Ensure you have read permissions on the targeted directory and try again. تأكد من أن لديك أذونات قراءة على المجلد المحدد وحاول مرة أخرى. + + + Keys not installed + المفاتيح غير مثبتة + + + + Install decryption keys and restart Eden before attempting to install firmware. + قم بتثبيت مفاتيح فك التشفير وأعد تشغيل إيدن قبل محاولة تثبيت الفيرموير. + + + + Select Dumped Firmware Source Location + حدد موقع مصدر الفيرموير المفرغة + + + + Select Dumped Firmware ZIP + حدد ملف الفيرموير المضغوط الذي تم تفريغه + + + + Firmware cleanup failed + فشل مسح الفيرموير + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + فشل مسح ذاكرة التخزين المؤقتة للفيرموير المستخرج. +تحقق من أذونات الكتابة في دليل النظام المؤقت وحاول مرة أخرى. +أبلغ نظام التشغيل عن خطأ: %1 + QtCommon::FS @@ -9568,7 +9653,7 @@ Only do this if you're 100% sure you want to delete this data. %1 To: %2 - تعذر ربط المجلد: + :تعذر ربط المجلد %1 To: %2 @@ -9790,72 +9875,72 @@ Would you like to manually select a portable folder to use? تعذر حذف ذاكرة التخزين المؤقتة للبيانات الوصفية. قد تكون قيد الاستخدام أو غير موجودة. - + Create Shortcut إنشاء اختصار - + Do you want to launch the game in fullscreen? هل تريد تشغيل اللعبة في وضع ملء الشاشة؟ - + Shortcut Created تم إنشاء الاختصار - + Successfully created a shortcut to %1 تم إنشاء اختصار بنجاح إلى %1 - + Shortcut may be Volatile! قد يكون الاختصار متقلبًا! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? سيؤدي هذا إلى إنشاء اختصار لصورة التطبيق الحالية. قد لا يعمل هذا بشكل جيد إذا قمت بالتحديث. هل تريد المتابعة؟ - + Failed to Create Shortcut فشل في إنشاء اختصار - + Failed to create a shortcut to %1 فشل إنشاء اختصار إلى %1 - + Create Icon إنشاء أيقونة - + Cannot create icon file. Path "%1" does not exist and cannot be created. لا يمكن إنشاء ملف الرمز. المسار ”%1“ غير موجود ولا يمكن إنشاؤه. - + No firmware available لا يوجد فيرموير متوفر - + Please install firmware to use the home menu. يرجى تثبيت الفيرموير لاستخدام القائمة الرئيسية. - + Home Menu Applet القائمة الرئيسية - + Home Menu is not available. Please reinstall firmware. القائمة الرئيسية غير متوفرة. يرجى إعادة تثبيت الفيرموير. @@ -9863,37 +9948,37 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name اسم التعديل - + What should this mod be called? ما الاسم المناسب لهذا التعديل؟ - + RomFS RomFS - + ExeFS/Patch ExeFS/التصحيح - + Cheat الغش - + Mod Type نوع التعديل - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. @@ -9902,18 +9987,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed فشل استخراج التعديل - + Failed to create temporary directory %1 %1 فشل إنشاء مجلد المؤقت - + Zip file %1 is empty الملف المضغوط 1% فارغ @@ -10467,7 +10552,7 @@ Please try again or contact the developer of the software. Select a user: - حدد مستخدمًا: + :حدد مستخدمًا @@ -10615,66 +10700,66 @@ By selecting "From Eden", previous save data stored in Ryujinx will be %1 متاح للتنزيل - + New Version Location موقع الإصدار الجديد - + All Files (*.*) جميع الملفات (*.*) - - - + + + Failed to save file فشل حفظ الملف - + Could not open file %1 for writing. تعذر فتح الملف 1% للكتابة - + Downloading... جارٍ التنزيل... - + Cancel إلغاء - + Could not write to file %1. تعذر الكتابة إلى الملف %1 - + Could not commit to file %1. تعذر حفظ التغييرات في الملف %1 - + Failed to download file فشل تنزيل الملف - + Could not download from %1%2 Error code: %3 تعذر التنزيل من %1%2 رمز الخطأ: %3 - + Download Complete اكتمل التنزيل - + Successfully downloaded %1. Would you like to open it? تم تنزيل 1% بنجاح. هل تريد فتحه؟ diff --git a/dist/languages/ca.ts b/dist/languages/ca.ts index 10db8a5e3a..534d4e4af1 100644 --- a/dist/languages/ca.ts +++ b/dist/languages/ca.ts @@ -209,155 +209,6 @@ This would ban both their forum username and their IP address. %1 - %2 (/%3/%4 membres) - connectat - - CompatDB - - - Report Compatibility - Informar sobre la compatibilitat - - - - - - - - - - Report Game Compatibility - Informar sobre la compatibilitat del joc - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>El joc arrenca?</p></body></html> - - - - Yes The game starts to output video or audio - Sí El joc comença a produïr video o audio - - - - No The game doesn't get past the "Launching..." screen - No El joc no passa de la pantalla "Iniciant..." - - - - Yes The game gets past the intro/menu and into gameplay - Sí El joc supera la introducció/menú i entra en la part jugable. - - - - No The game crashes or freezes while loading or using the menu - No El joc falla o es bloqueja mentre es carrega o s'utilitza el menú - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>El joc arriba a ser jugable?</p></body></html> - - - - Yes The game works without crashes - Sí El joc funciona sense errors - - - - No The game crashes or freezes during gameplay - No El joc falla o es bloqueja durant la part jugable. - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>Funciona el joc sense fallar, bloquejar-se o en bucle durant la part jugable?</p></body></html> - - - - Yes The game can be finished without any workarounds - Sí El joc es pot acabar sense ninguna configuració extra especifica - - - - No The game can't progress past a certain area - No El joc no pot avançar més enllà d'una zona determinada - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>És el joc completament jugable de principi a fi?</p></body></html> - - - - Major The game has major graphical errors - Major El joc té errors gràfics importants - - - - Minor The game has minor graphical errors - Menor El joc té errors gràfics menors - - - - None Everything is rendered as it looks on the Nintendo Switch - Cap Tot es renderitzat com es veu a la Nintendo Switch - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>Té el joc algun error gràfic?</p></body></html> - - - - Major The game has major audio errors - Major El joc té errors greus d'àudio - - - - Minor The game has minor audio errors - Menor El joc té errors lleus d'àudio - - - - None Audio is played perfectly - Cap L'àudio es reprodueix perfectament - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>Té el joc algun error d'àudio / efectes faltants?</p></body></html> - - - - Thank you for your submission! - Gràcies per el vostre enviament. - - - - Submitting - Enviant - - - - Communication error - Error de comunicació - - - - An error occurred while sending the Testcase - S'ha produït un error mentre s'enviava el Cas de Prova - - - - Next - Següent - - ConfigurationShared @@ -398,7 +249,7 @@ This would ban both their forum username and their IP address. Software keyboard - + Teclat de software @@ -557,231 +408,241 @@ Can help reduce stuttering at lower framerates. - + Backend: Motor: - CPU Overclock - Overclock de la CPU - - - - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. + CPU Clocks - + + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + + + + Custom CPU Ticks Ticks del CPU personalitzats - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. - + Enable Host MMU Emulation (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. - + Unfuse FMA (improve performance on CPUs without FMA) Desactivar FMA (millora el rendiment en CPUs sense FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. - + Faster FRSQRTE and FRECPE FRSQRTE i FRECPE més ràpid - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. Aquest paràmetre millora la velocitat d'algunes funcions de coma flotant, amb l'ús d'aproximacions natives menys precises. - + Faster ASIMD instructions (32 bits only) Instruccions ASIMD més ràpides (només 32 bits) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. - + Inaccurate NaN handling Gestió imprecisa NaN - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. - + Disable address space checks Desactiva les comprovacions d'espai d'adreces - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. - + Ignore global monitor Ignorar monitorització global - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. - + API: API: - + Changes the output graphics API. Vulkan is recommended. - + Device: Dispositiu: - + This setting selects the GPU to use (Vulkan only). - + Resolution: Resolució: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. - + Window Adapting Filter: Filtre d'adaptació de finestra: - + FSR Sharpness: Nitidesa FSR: - - Determines how sharpened the image will look using FSR's dynamic contrast. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. - + Anti-Aliasing Method: Mètode de suavitzat de vores - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Mode pantalla completa: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. - + Aspect Ratio: Relació d'aspecte: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. - + Use persistent pipeline cache - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. - + Use asynchronous GPU emulation - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + NVDEC emulation: Emulació NVDEC: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. - + ASTC Decoding Method: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -790,55 +651,75 @@ stuttering but may present artifacts. - + ASTC Recompression Method: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. - + Skip CPU Inner Invalidation - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: Mode de sincronització vertical: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -846,1361 +727,1371 @@ Immediate (no synchronization) presents whatever is available and can exhibit te - + Sync Memory Operations - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. - + Enable asynchronous presentation (Vulkan only) - + Slightly improves performance by moving presentation to a separate CPU thread. - + Force maximum clocks (Vulkan only) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. - + Anisotropic Filtering: Filtrat anisotròpic: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. - + GPU Mode: Mode de la GPU: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - + DMA Accuracy: Precisió DMA: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. - + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation - + May reduce shader stutter. - - Fast GPU Time + + GPU Clocks - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. - + GPU Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. - + Enable Compute Pipelines (Intel Vulkan Only) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. - + Enable Reactive Flushing - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. - + Sync to framerate of video playback - + Run the game at normal speed during video playback, even when the framerate is unlocked. - + Barrier feedback loops - + Improves rendering of transparency effects in specific games. - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. - + Vertex Input Dynamic State - + Enables vertex input dynamic state feature for better quality and performance. - + Sample Shading - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. - + RNG Seed Llavor de GNA - + Controls the seed of the random number generator. Mainly used for speedrunning. - + Device Name Nom del dispositiu - + The name of the console. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: - + This option allows to change the clock of the console. Can be used to manipulate time in games. - + The number of seconds from the current unix time - + Language: Idioma: - + This option can be overridden when region setting is auto-select - + Region: Regió: - + The region of the console. - + Time Zone: Zona horària: - + The time zone of the console. - + Sound Output Mode: - + Console Mode: Mode de la consola - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. - + Prompt for user profile on boot - + Useful if multiple people use the same PC. - + Pause when not in focus - + Pauses emulation when focusing on other windows. - + Confirm before stopping emulation - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. - + Hide mouse on inactivity Ocultar el cursor del ratolí en cas d'inactivitat - + Hides the mouse after 2.5s of inactivity. - - Disable controller applet - - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - - - - + Check for updates - + Whether or not to check for updates upon startup. - + Enable Gamemode Activa el mode joc - + Force X11 as Graphics Backend - + Custom frontend - + Real applet - + + + Disabled + + + + Never Mai - + On Load - + Always Sempre - + CPU CPU - + GPU GPU - + CPU Asynchronous - + Uncompressed (Best quality) - + BC1 (Low quality) - + BC3 (Medium quality) - - + + Auto Auto - + 30 FPS 30 FPS - + 60 FPS 60 FPS - + 90 FPS 90 FPS - + 120 FPS 120 FPS - + Conservative Conservador - + Aggressive Agressiu - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) OpenGL GLASM (Ensamblat d'ombrejadors, només NVIDIA) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) OpenGL SPIR-V (Experimental, només AMD/Mesa) - + Null Nul - + Fast Ràpid - - Balanced - Equilibrat - - - - + + + Accurate Precís - - + + + Default Valor predeterminat - + Unsafe (fast) Insegur (ràpid) - + Safe (stable) Segur (estable) - + + Immediate + + + + + Balanced + Equilibrat + + + Unsafe Insegur - + Paranoid (disables most optimizations) Paranoic (desactiva la majoria d'optimitzacions) - + Debugging Depuració - + Dynarmic Dynarmic - + NCE NCE - + Borderless Windowed Finestra sense vores - + Exclusive Fullscreen Pantalla completa exclusiva - + No Video Output Sense sortida de vídeo - + CPU Video Decoding Descodificació de vídeo a la CPU - + GPU Video Decoding (Default) Descodificació de vídeo a la GPU (Valor predeterminat) - + 0.25X (180p/270p) [EXPERIMENTAL] 0.25X (180p/270p) [EXPERIMENTAL] - + 0.5X (360p/540p) [EXPERIMENTAL] 0.5X (360p/540p) [EXPERIMENTAL] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [EXPERIMENTAL] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] 1.25X (900p/1350p) [EXPERIMENTAL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [EXPERIMENTAL] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor Veí més proper - + Bilinear Bilineal - + Bicubic Bicúbic - + Gaussian Gaussià - + Lanczos Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution Super resolució AMD FidelityFX - + Area Àrea - + MMPX MMPX - + Zero-Tangent Tangència zero - + B-Spline B-Spline - + Mitchell Mitchell - + Spline-1 Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None Cap - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Valor predeterminat (16:9) - + Force 4:3 Forçar 4:3 - + Force 21:9 Forçar 21:9 - + Force 16:10 - + Stretch to Window Estirar a la finestra - + Automatic Automàtic - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - 32x - - - - 64x - 64x - - - + Japanese (日本語) Japonès (日本語) - + American English Anglès americà - + French (français) Francès (français) - + German (Deutsch) Alemany (Deutsch) - + Italian (italiano) Italià (italiano) - + Spanish (español) Castellà (español) - + Chinese Xinès - + Korean (한국어) Coreà (한국어) - + Dutch (Nederlands) Holandès (Nederlands) - + Portuguese (português) Portuguès (português) - + Russian (Русский) Rus (Русский) - + Taiwanese Taiwanès - + British English Anglès britànic - + Canadian French Francès canadenc - + Latin American Spanish Espanyol llatinoamericà - + Simplified Chinese Xinès simplificat - + Traditional Chinese (正體中文) Xinès tradicional (正體中文) - + Brazilian Portuguese (português do Brasil) Portuguès brasiler (português do Brasil) - + Polish (polska) Polonès (polska) - + Thai (แบบไทย) Tailandès (แบบไทย) - - + + Japan Japó - + USA EUA - + Europe Europa - + Australia Austràlia - + China Xina - + Korea Corea - + Taiwan Taiwan - + Auto (%1) Auto select time zone Auto (%1) - + Default (%1) Default time zone Per defecte (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Cuba - + EET EET - + Egypt Egipte - + Eire Eire - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Eire - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hong Kong - + HST HST - + Iceland Islàndia - + Iran Iran - + Israel Isreal - + Jamaica Jamaica - + Kwajalein Kwajalein - + Libya Líbia - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navajo - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Polònia - + Portugal Portugal - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Singapur - + Turkey Turquia - + UCT UCT - + Universal Universal - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulu - + Mono Mono - + Stereo Estèreo - + Surround Envoltant - + 4GB DRAM (Default) 4GB DRAM (Insegur) - + 6GB DRAM (Unsafe) 6GB DRAM (Insegur) - + 8GB DRAM 8GB DRAM - + 10GB DRAM (Unsafe) 10GB DRAM (Insegur) - + 12GB DRAM (Unsafe) 12GB DRAM (Insegur) - + Docked Sobretaula - + Handheld Portàtil - - - Off + + + Normal - - Boost (1700MHz) + + + Boost - - Fast (2000MHz) + + + Overclock - + Always ask (Default) - + Only if game specifies not to stop Tan sols si el joc especifica no parar - + Never ask No preguntar mai - - - Medium (256) - - - - - - High (512) - - - - + Very Small (16 MB) - + Small (32 MB) - + Normal (128 MB) - + Large (256 MB) - + Very Large (512 MB) - + Very Low (4 MB) - + Low (8 MB) - + Normal (16 MB) - + Medium (32 MB) - + High (64 MB) - + Very Low (32) - + Low (64) - + Normal (128) - - Disabled + + Medium (256) - + + High (512) + + + + ExtendedDynamicState 1 - + ExtendedDynamicState 2 - + ExtendedDynamicState 3 - + Tree View - + Grid View @@ -2559,252 +2450,297 @@ When a program attempts to open the controller applet, it is immediately closed. Filtre de registre global - - When checked, the max size of the log increases from 100 MB to 1 GB - Quan està marcat, la mida màxima del registre augmenta de 100 MB a 1 GB + + GPU Logging/Level + - - Enable Extended Logging** - Habilitar registre ampliat** + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console Mostra el registre a la consola - + + When checked, the max size of the log increases from 100 MB to 1 GB + Quan està marcat, la mida màxima del registre augmenta de 100 MB a 1 GB + + + + Enable Extended Logging** + Habilitar registre ampliat** + + + Open Log Location Obrir ubicació de l'arxiu del registre - + Homebrew Homebrew - + Arguments String Cadena d'arguments - + Graphics Gràfics - + When checked, it executes shaders without loop logic changes Quan està marcat, s'executaran els shaders sense canvis de lògica de bucle - + Disable Loop safety checks Desactivar comprovacions de seguretat de bucles - + When checked, it will dump all the macro programs of the GPU Quan està activat, abocarà tots els programes macro de la GPU - + Dump Maxwell Macros Abocar Macros Mawxell - + When checked, it enables Nsight Aftermath crash dumps Quan està marcat, habilitarà els volcats dels errors d'Nsight Aftermath - + Enable Nsight Aftermath Activar Nsight Aftermath - - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Quan està marcat, bolcarà tots els shaders d'assemblador originals del cache de shaders del disc o del joc mentre els troba - - - - Dump Game Shaders - Bolcar shaders del joc - - - - Enable Renderdoc Hotkey - Activar la drecera de teclat de Renderdoc - - - - When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower - Quan està marcat, desactiva el compilador de macro Just In Time. Activar això fa que els jocs funcionin més lentament - - - - Disable Macro JIT - Desactivar macro JIT - - - - When checked, it disables the macro HLE functions. Enabling this makes games run slower - Quan està marcat, desactiva les funcions macro HLE. Habilitar-ho fa que el joc corri més lent - - - - Disable Macro HLE - Desactivar Macro HLE - - - - When checked, the graphics API enters a slower debugging mode - Quan està marcat, l'API de gràfics entrarà en un mode de depuració més lent - - - - Enable Graphics Debugging - Activar depuració de gràfics - - - - When checked, yuzu will log statistics about the compiled pipeline cache - Quan està marcat, yuzu registrarà estadístiques sobre la cache de canonada compilada - - Enable Shader Feedback - Activar informació de shaders + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + - - <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + Dump Guest (Maxwell) Shaders + Enable Renderdoc Hotkey + Activar la drecera de teclat de Renderdoc + + + + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower + Quan està marcat, desactiva el compilador de macro Just In Time. Activar això fa que els jocs funcionin més lentament + + + + Disable Macro JIT + Desactivar macro JIT + + + + When checked, it disables the macro HLE functions. Enabling this makes games run slower + Quan està marcat, desactiva les funcions macro HLE. Habilitar-ho fa que el joc corri més lent + + + + Disable Macro HLE + Desactivar Macro HLE + + + + When checked, the graphics API enters a slower debugging mode + Quan està marcat, l'API de gràfics entrarà en un mode de depuració més lent + + + + Enable Graphics Debugging + Activar depuració de gràfics + + + + When checked, yuzu will log statistics about the compiled pipeline cache + Quan està marcat, yuzu registrarà estadístiques sobre la cache de canonada compilada + + + + Enable Shader Feedback + Activar informació de shaders + + + + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + + + Disable Buffer Reorder - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced Avançat - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Permet a yuzu identificar un entorn funcional de Vulkan quan el programa és executat. Desactiva això si està causant que programes externs tinguin problemes per veure a yuzu. - + Perform Startup Vulkan Check Realitza Comprovació de Vulkan a l'Inici - + Disable Web Applet Desactivar el Web Applet - + Enable All Controller Types Activar tots els tipus de controladors - + Enable Auto-Stub - + Kiosk (Quest) Mode Mode quiosc (Quest) - + Use dev.keys - + Enable Debug Asserts Activar alertes de depuració - + Debugging Depuració - + Battery Serial: - + + Debug Knobs: + + + + Bitmask for quick development toggles - + Set debug knobs (bitmask) - + 16-bit debug knob set for quick development toggles - + (bitmask) - - Debug Knobs: - - - - + Unit Serial: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. Habilita això per imprimir l'última llista d'ordres d'àudio a la consola. Només afecta els jocs que utilitzin el renderitzador d'àudio. - + Dump Audio Commands To Console** Buidar Ordres d'Àudio a la Consola - + Flush log output on each line - + Enable FS Access Log Activar registre d'accés al FS - + Enable Verbose Reporting Services** Activa els serveis d'informes detallats** - + Censor username in logs - + **This will be reset automatically when Eden closes. @@ -3254,33 +3190,38 @@ Would you like to delete the old save data? Color de fons: - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off Apagat - + VSync Off Vsync Apagat - + Recommended Recomanat - + On Encés - + VSync On VSync Encés @@ -3801,6 +3742,16 @@ Would you like to delete the old save data? Motion / Touch Moviment / Tàctil + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4310,58 +4261,58 @@ Per invertir els eixos, primer moveu el joystick verticalment i després horitzo [esperant] - + New Profile Nou perfil - + Enter a profile name: Introdueixi un nom de perfil: - - + + Create Input Profile Crear perfil d'entrada - + The given profile name is not valid! El nom de perfil introduït no és vàlid! - + Failed to create the input profile "%1" Error al crear el perfil d'entrada "%1" - + Delete Input Profile Eliminar perfil d'entrada - + Failed to delete the input profile "%1" Error al eliminar el perfil d'entrada "%1" - + Load Input Profile Carregar perfil d'entrada - + Failed to load the input profile "%1" Error al carregar el perfil d'entrada "%1" - + Save Input Profile Guardar perfil d'entrada - + Failed to save the input profile "%1" Error al guardar el perfil d'entrada "%1" @@ -4409,7 +4360,7 @@ Per invertir els eixos, primer moveu el joystick verticalment i després horitzo - + Configure Configuració @@ -4440,7 +4391,7 @@ Per invertir els eixos, primer moveu el joystick verticalment i després horitzo - + Test Provar @@ -4455,77 +4406,77 @@ Per invertir els eixos, primer moveu el joystick verticalment i després horitzo Eliminar servidor - + %1:%2 %1:%2 - - - - - - + + + + + + Eden Eden - + Port number has invalid characters El número de port té caràcters invàlids - + Port has to be in range 0 and 65353 El port ha d'estar entre el rang 0 i 65353 - + IP address is not valid l'Adreça IP no és vàlida - + This UDP server already exists Aquest servidor UDP ja existeix - + Unable to add more than 8 servers No és possible afegir més de 8 servidors - + Testing Provant - + Configuring Configurant - + Test Successful Prova exitosa - + Successfully received data from the server. S'han rebut dades des del servidor correctament. - + Test Failed Prova fallida - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. No s'han pogut rebre dades vàlides des del servidor.<br>Si us plau, verifiqui que el servidor està configurat correctament i que la direcció i el port són correctes.  - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. La prova del UDP o la configuració de la calibració està en curs.<br>Si us plau, esperi a que acabi el procés. @@ -4709,57 +4660,57 @@ Current values are %1% and %2% respectively. Algunes configuracions són disponibles només quan el joc no està corrent. - + Add-Ons Complements - + System Sistema - + CPU CPU - + Graphics Gràfics - + Adv. Graphics Gràfics avanç. - + Ext. Graphics - + Audio Àudio - + Input Profiles Perfils d'entrada - + Network - + Applets Applets - + Properties Propietats @@ -4882,6 +4833,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -4975,7 +5050,7 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t &Edit - + &Editar @@ -4985,7 +5060,7 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t Edit User - + Editar usuari @@ -5449,91 +5524,86 @@ Arrossegui els punts per a canviar la posició, o faci doble clic a les cel·les - Show Compatibility List - - - - Show Add-Ons Column Mostrar columna de complements - + Show Size Column - + Show File Types Column - + Show Play Time Column - + Folder Icon Size: Tamany de les icones de les carpetes - + Row 1 Text: Text de la fila 1: - + Row 2 Text: Text de la fila 2: - + Screenshots Captures de pantalla - + Ask Where To Save Screenshots (Windows Only) Preguntar on guardar les captures de pantalla (només Windows) - + Screenshots Path: Ruta de les captures de pantalla: - + ... ... - + TextLabel - + Resolution: Resolució: - + Select Screenshots Path... Seleccioni el directori de les Captures de Pantalla... - + <System> <System> - + English Anglès - + Auto (%1 x %2, %3 x %4) Screenshot width value @@ -5770,7 +5840,7 @@ Arrossegui els punts per a canviar la posició, o faci doble clic a les cel·les - + Calculating... @@ -5972,50 +6042,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL no disponible! - + OpenGL shared contexts are not supported. - + Eden has not been compiled with OpenGL support. - - - + + + Error while initializing OpenGL! Error al inicialitzar OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. La seva GPU no suporta OpenGL, o no té instal·lat els últims controladors gràfics. - + Error while initializing OpenGL 4.6! Error inicialitzant OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 La seva GPU no suporta OpenGL 4.6, o no té instal·lats els últims controladors gràfics.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 És possible que la seva GPU no suporti una o més extensions necessàries d'OpenGL. Si us plau, asseguris de tenir els últims controladors de la tarjeta gràfica.<br><br>GL Renderer:<br>%1<br><br>Extensions no suportades:<br>%2 - + This build doesn't have OpenGL support. Aquesta compilació no té suport per a OpenGL. @@ -6023,279 +6093,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory - + Favorite Preferit - + Start Game Iniciar el joc - + Start Game without Custom Configuration Iniciar el joc sense la configuració personalitzada - + Open Save Data Location Obrir la ubicació dels arxius de partides guardades - + Open Mod Data Location Obrir la ubicació dels mods - + Open Transferable Pipeline Cache Obrir cache transferible de shaders de canonada - + Link to Ryujinx - + Remove Eliminar - + Remove Installed Update Eliminar actualització instal·lada - + Remove All Installed DLC Eliminar tots els DLC instal·lats - + Remove Custom Configuration Eliminar configuració personalitzada - + Remove Cache Storage - + Remove OpenGL Pipeline Cache Eliminar cache de canonada d'OpenGL - + Remove Vulkan Pipeline Cache Eliminar cache de canonada de Vulkan - + Remove All Pipeline Caches Eliminar totes les caches de canonada - + Remove All Installed Contents Eliminar tots els continguts instal·lats - + Manage Play Time - + Edit Play Time Data - + Remove Play Time Data - - + + Dump RomFS Bolcar RomFS - + Dump RomFS to SDMC Bolcar RomFS a SDMC - + Verify Integrity - + Copy Title ID to Clipboard Copiar la ID del títol al porta-retalls - - Navigate to GameDB entry - Navegar a l'entrada de GameDB - - - + Create Shortcut - + Add to Desktop - + Add to Applications Menu - + Configure Game - + Scan Subfolders Escanejar subdirectoris - + Remove Game Directory Eliminar directori de jocs - + ▲ Move Up ▲ Moure amunt - + ▼ Move Down ▼ Move avall - + Open Directory Location Obre ubicació del directori - + Clear Esborrar - - - Name - Nom - - - - Compatibility - Compatibilitat - - - - Add-ons - Complements - - - - File type - Tipus d'arxiu - - - - Size - Mida - - - - Play time - - GameListItemCompat - + Ingame - + Game starts, but crashes or major glitches prevent it from being completed. - + Perfect Perfecte - + Game can be played without issues. - + Playable - + Game functions with minor graphical or audio glitches and is playable from start to finish. - + Intro/Menu Intro / Menú - + Game loads, but is unable to progress past the Start Screen. - + Won't Boot No engega - + The game crashes when attempting to startup. El joc es bloqueja al intentar iniciar. - + Not Tested No provat - + The game has not yet been tested. Aquest joc encara no ha estat provat. + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list Faci doble clic per afegir un nou directori a la llista de jocs @@ -6303,17 +6366,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) %1 de %n resultat(s)%1 de %n resultat(s) - + Filter: Filtre: - + Enter pattern to filter Introdueixi patró per a filtrar @@ -6808,1139 +6871,1112 @@ Debug Message: - + Game &Icon Size - + Reset Window Size to &720p Reiniciar el tamany de la finestra a &720p - + Reset Window Size to 720p Reiniciar el tamany de la finestra a 720p - + Reset Window Size to &900p Reiniciar el tamany de la finestra a &900p - + Reset Window Size to 900p Reiniciar el tamany de la finestra a 900p - + Reset Window Size to &1080p Reiniciar el tamany de la finestra a &1080p - + Reset Window Size to 1080p Reiniciar el tamany de la finestra a 1080p - + &Multiplayer - + &Tools &Eines - + Am&iibo - + Launch &Applet - + &TAS &TAS - + &Create Home Menu Shortcut - + Install &Firmware - + &Help &Ajuda - + &Install Files to NAND... &instal·lar arxius a la NAND... - + L&oad File... C&arregar arxiu... - + Load &Folder... Carregar &carpeta... - + E&xit S&ortir - - + + &Pause &Pausar - + &Stop &Aturar - + &Verify Installed Contents - + &About Eden - + Single &Window Mode Mode una sola &finestra - + Con&figure... Con&figurar... - + Ctrl+, - + Enable Overlay Display Applet - + Show &Filter Bar Mostrar la barra de &filtre - + Show &Status Bar Mostrar la barra d'&estat - + Show Status Bar Mostrar barra d'estat - + &Browse Public Game Lobby - + &Create Room - + &Leave Room - + &Direct Connect to Room - + &Show Current Room - + F&ullscreen P&antalla completa - + &Restart &Reiniciar - + Load/Remove &Amiibo... Carregar/Eliminar &Amiibo... - - &Report Compatibility - &Informar de compatibilitat - - - + Open &Mods Page Obrir la pàgina de &mods - - Open &Quickstart Guide - Obre la guia d'&inici ràpid + + &User Handbook + - - &FAQ - &Preguntes freqüents - - - + &Capture Screenshot &Captura de pantalla - + &Album - + &Set Nickname and Owner - + &Delete Game Data - + &Restore Amiibo - + &Format Amiibo - + &Mii Editor - + Editor de &Mii - + &Configure TAS... &Configurar TAS... - + Configure C&urrent Game... Configurar joc a&ctual... - - + + &Start &Iniciar - + &Reset &Reiniciar - - + + R&ecord E&nregistrar - + Open &Controller Menu - + Install Decryption &Keys - + &Home Menu - + &Desktop - + &Application Menu - + &Root Data Folder - + &NAND Folder - + &SDMC Folder - + &Mod Folder - + &Log Folder - + From Folder - + From ZIP - + &Eden Dependencies - + &Data Manager - + &Tree View - + &Grid View - + Game Icon Size - - + + None Cap - + Show Game &Name - + Show &Performance Overlay - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) Petit (32x32) - + Standard (64x64) - + Large (128x128) - + Full Size (256x256) - + Broken Vulkan Installation Detected - + Vulkan initialization failed during boot. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping - + Loading Web Applet... - - + + Disable Web Applet - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + The amount of shaders currently being built - + The current selected resolution scaling multiplier. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Unmute - + Desilenciar - + Mute - + Silenciar - + Reset Volume - + + Configure Effects... + + + + &Clear Recent Files - + &Continue - + Warning: Outdated Game Format - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - - + + Error while loading ROM! - + The ROM format is not supported. - + An error occurred initializing the video core. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + An unknown error occurred. Please see the log for more details. - + + (64-bit) (64-bit) - + + (32-bit) (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + %1 %2 - + Closing software... - + Save Data - + Mod Data - + Error Opening %1 Folder - - + + Folder does not exist! - + Remove Installed Game Contents? - + Remove Installed Game Update? - + Remove Installed Game DLC? - + Remove Entry - + Delete OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? - + Remove Custom Game Configuration? - + Remove Cache Storage? - + Remove File - + Remove Play Time Data - + Reset play time? - - + + RomFS Extraction Failed! - + There was an error copying the RomFS files or the user cancelled the operation. - + Full - + Complet - + Skeleton - + Esquelet - + Select RomFS Dump Mode - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Extracting RomFS... - - + + Cancel Cancel·lar - + RomFS Extraction Succeeded! - + The operation completed successfully. - + Error Opening %1 - + Select Directory - + Properties - + Propietats - + The game properties could not be loaded. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Load File - + Carregar arxiu - + Open Extracted ROM Directory - + Invalid Directory Selected - + The directory you have selected does not contain a 'main' file. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files - + Instal·lar arxius - + %n file(s) remaining - + Installing file "%1"... - - + + Install Results - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + %n file(s) were newly installed - + %n file(s) were overwritten - + %n file(s) failed to install - + System Application - + System Archive - + System Application Update - + Firmware Package (Type A) - + Firmware Package (Type B) - + Game - + Joc - + Game Update - + Game DLC - + Delta Title - + Select NCA Install Type... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Failed to Install - + The title type you selected for the NCA is invalid. - + File not found - + File "%1" not found - + OK - + D'acord - + Function Disabled - + Compatibility list reporting is currently disabled. Check back later! - + Error opening URL - + Unable to open the URL "%1". - + TAS Recording - + Overwrite file of player 1? - + Invalid config detected - + Handheld controller can't be used on docked mode. Pro controller will be selected. - - + + Amiibo - - + + The current amiibo has been removed - + Error - - + + The current game is not looking for amiibos - + Amiibo File (%1);; All Files (*.*) - + Load Amiibo - + Error loading Amiibo data - + The selected file is not a valid amiibo - + The selected file is already on use - + An unknown error occurred - - - Keys not installed - - - - - - Install decryption keys and restart Eden before attempting to install firmware. - - - - - Select Dumped Firmware Source Location - - - - - Select Dumped Firmware ZIP - - - - - Zipped Archives (*.zip) - - - - - Firmware cleanup failed - - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available - + Firmware Corrupted - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot - + PNG Image (*.png) - + TAS state: Running %1/%2 - + TAS state: Recording %1 - + TAS state: Idle %1/%2 - + TAS State: Invalid - + &Stop Running - + Stop R&ecording - + Building: %n shader(s) - + Scale: %1x %1 is the resolution scaling factor Escala: %1x - + Speed: %1% / %2% Velocitat: %1% / %2% - + Speed: %1% Velocitat: %1% - + Game: %1 FPS Joc: %1 FPS - + Frame: %1 ms Fotograma: %1 ms - - - FSR - FSR - - - + NO AA SENSE AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE - + VOLUME: %1% Volume percentage (e.g. 50%) VOLUMEN: %1% - + Derivation Components Missing - + Decryption keys are missing. Install them now? - + Wayland Detected! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -7948,74 +7984,74 @@ Would you like to force it for future launches? - + Use X11 - + Continue with Wayland - + Don't show again - + Restart Required - + Restart Eden to apply the X11 backend. - + Slow Lent - + Turbo Turbo - + Unlocked - + Select RomFS Dump Target - + Please select which RomFS you would like to dump. - + Are you sure you want to close Eden? - - - + + + Eden Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8081,6 +8117,11 @@ Would you like to bypass this and exit anyway? ScaleForce + + + FSR + FSR + Area @@ -8092,52 +8133,57 @@ Would you like to bypass this and exit anyway? - + + SGSR + + + + + SGSR EdgeDir + + + + Docked Sobretaula - + Handheld Portàtil - + Fast - - Balanced - - - - + Accurate - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL SPIRV OpenGL SPIRV - + OpenGL GLASM OpenGL GLASM - + Null Nul @@ -8170,7 +8216,7 @@ If you wish to clean up the files which were left in the old data location, you - + Data was migrated successfully. @@ -8660,8 +8706,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8674,10 +8720,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8692,9 +8738,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8856,50 +8902,50 @@ p, li { white-space: pre-wrap; } [indefinit] - + %1%2 %1%2 - + [invalid] [invàlid] - - + + %1%2Hat %3 %1%2Rotació %3 - - - + + + %1%2Axis %3 %1%2Eix %3 - + %1%2Axis %3,%4,%5 %1%2Eixos %3,%4,%5 - + %1%2Motion %3 %1%2Moviment %3 - - + + %1%2Button %3 %1%2Botó %3 - + [unused] [sense ús] @@ -9025,47 +9071,47 @@ p, li { white-space: pre-wrap; } - + Play Time: %1 - + Never Played Mai jugat - + Version: %1 Versió: %1 - + Version: 1.0.0 Versió: 1.0.0 - + Installed SD Titles Títols instal·lats a la SD - + Installed NAND Titles Títols instal·lats a la NAND - + System Titles Títols del sistema - + Add New Game Directory Afegir un nou directori de jocs - + Favorites Preferits @@ -9186,13 +9232,13 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. @@ -9202,10 +9248,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel Cancel·lar @@ -9226,213 +9272,246 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip %1.zip - - + + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting Exportant - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9676,72 +9755,72 @@ Would you like to manually select a portable folder to use? - + Create Shortcut - + Do you want to launch the game in fullscreen? - + Shortcut Created - + Successfully created a shortcut to %1 - + Shortcut may be Volatile! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? - + Failed to Create Shortcut - + Failed to create a shortcut to %1 - + Create Icon - + Cannot create icon file. Path "%1" does not exist and cannot be created. - + No firmware available - + Please install firmware to use the home menu. - + Home Menu Applet Applet del menú d'inici - + Home Menu is not available. Please reinstall firmware. @@ -9749,55 +9828,55 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name - + What should this mod be called? - + RomFS - + ExeFS/Patch - + Cheat - + Mod Type - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty @@ -10491,65 +10570,65 @@ By selecting "From Eden", previous save data stored in Ryujinx will be - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/cs.ts b/dist/languages/cs.ts index b7469b5d5c..abbc0b4db0 100644 --- a/dist/languages/cs.ts +++ b/dist/languages/cs.ts @@ -209,155 +209,6 @@ This would ban both their forum username and their IP address. %1 - %2 (%3/%4 členů) - připojeno - - CompatDB - - - Report Compatibility - Nahlásit kompatibilitu. - - - - - - - - - - Report Game Compatibility - Nahlásit kompatibilitu hry. - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - - - - - <html><head/><body><p>Does the game boot?</p></body></html> - - - - - Yes The game starts to output video or audio - - - - - No The game doesn't get past the "Launching..." screen - - - - - Yes The game gets past the intro/menu and into gameplay - - - - - No The game crashes or freezes while loading or using the menu - - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - - - - - Yes The game works without crashes - - - - - No The game crashes or freezes during gameplay - - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - - - - - Yes The game can be finished without any workarounds - - - - - No The game can't progress past a certain area - - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - - - - - Major The game has major graphical errors - - - - - Minor The game has minor graphical errors - - - - - None Everything is rendered as it looks on the Nintendo Switch - - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - - - - - Major The game has major audio errors - - - - - Minor The game has minor audio errors - - - - - None Audio is played perfectly - - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - - - - - Thank you for your submission! - Děkujeme za odezvu! - - - - Submitting - Potvrzuji - - - - Communication error - Chyba komunikace - - - - An error occurred while sending the Testcase - Chyba při odesílání testovacího případu - - - - Next - Další - - ConfigurationShared @@ -557,231 +408,241 @@ Can help reduce stuttering at lower framerates. - + Backend: - CPU Overclock + CPU Clocks - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. - + Custom CPU Ticks - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. - + Enable Host MMU Emulation (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. - + Unfuse FMA (improve performance on CPUs without FMA) Rozložit FMA instrukce (zlepší výkon na CPU bez FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. - + Faster FRSQRTE and FRECPE Rychlejší FRSQRTE a FRECPE - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. - + Faster ASIMD instructions (32 bits only) Rychlejší instrukce ASIMD (Pouze 32 bitové) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. - + Inaccurate NaN handling Nepřesné zpracování NaN - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. - + Disable address space checks Zakázat kontrolu adres paměti - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. - + Ignore global monitor Ignorovat globální hlídač. - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. - + API: API: - + Changes the output graphics API. Vulkan is recommended. - + Device: Zařízení: - + This setting selects the GPU to use (Vulkan only). - + Resolution: Rozlišení: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. - + Window Adapting Filter: - + FSR Sharpness: - - Determines how sharpened the image will look using FSR's dynamic contrast. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. - + Anti-Aliasing Method: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Režim celé obrazovky: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. - + Aspect Ratio: Poměr stran: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. - + Use persistent pipeline cache - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. - + Use asynchronous GPU emulation - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + NVDEC emulation: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. - + ASTC Decoding Method: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -790,55 +651,75 @@ stuttering but may present artifacts. - + ASTC Recompression Method: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. - + Skip CPU Inner Invalidation - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -846,1361 +727,1371 @@ Immediate (no synchronization) presents whatever is available and can exhibit te - + Sync Memory Operations - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. - + Enable asynchronous presentation (Vulkan only) - + Slightly improves performance by moving presentation to a separate CPU thread. - + Force maximum clocks (Vulkan only) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. - + Anisotropic Filtering: Anizotropní filtrování: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. - + GPU Mode: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - + DMA Accuracy: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. - + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation - + May reduce shader stutter. - - Fast GPU Time + + GPU Clocks - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. - + GPU Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. - + Enable Compute Pipelines (Intel Vulkan Only) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. - + Enable Reactive Flushing - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. - + Sync to framerate of video playback - + Run the game at normal speed during video playback, even when the framerate is unlocked. - + Barrier feedback loops - + Improves rendering of transparency effects in specific games. - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. - + Vertex Input Dynamic State - + Enables vertex input dynamic state feature for better quality and performance. - + Sample Shading - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. - + RNG Seed RNG Seed - + Controls the seed of the random number generator. Mainly used for speedrunning. - + Device Name Název Zařízení - + The name of the console. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: - + This option allows to change the clock of the console. Can be used to manipulate time in games. - + The number of seconds from the current unix time - + Language: Jazyk: - + This option can be overridden when region setting is auto-select - + Region: Region: - + The region of the console. - + Time Zone: Časové Pásmo: - + The time zone of the console. - + Sound Output Mode: - + Console Mode: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. - + Prompt for user profile on boot - + Useful if multiple people use the same PC. - + Pause when not in focus - + Pauses emulation when focusing on other windows. - + Confirm before stopping emulation Potvrzení před zastavením emulace - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. - + Hide mouse on inactivity Skrýt myš při neaktivitě - + Hides the mouse after 2.5s of inactivity. - - Disable controller applet - - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - - - - + Check for updates - + Whether or not to check for updates upon startup. - + Enable Gamemode - + Force X11 as Graphics Backend - + Custom frontend - + Real applet - + + + Disabled + + + + Never - + On Load - + Always - + CPU CPU - + GPU - + CPU Asynchronous - + Uncompressed (Best quality) - + BC1 (Low quality) - + BC3 (Medium quality) - - + + Auto Automatické - + 30 FPS - + 60 FPS - + 90 FPS - + 120 FPS - + Conservative - + Aggressive - + Vulkan Vulkan - + OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) - + Null - + Fast - - Balanced - - - - - + + + Accurate Přesné - - + + + Default Výchozí - + Unsafe (fast) - + Safe (stable) - + + Immediate + + + + + Balanced + + + + Unsafe Nebezpečné - + Paranoid (disables most optimizations) Paranoidní (zakáže většinu optimizací) - + Debugging - + Dynarmic - + NCE - + Borderless Windowed Okno bez okrajů - + Exclusive Fullscreen Exkluzivní - + No Video Output - + CPU Video Decoding - + GPU Video Decoding (Default) - + 0.25X (180p/270p) [EXPERIMENTAL] - + 0.5X (360p/540p) [EXPERIMENTAL] - + 0.75X (540p/810p) [EXPERIMENTAL] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] - + 2X (1440p/2160p) - + 3X (2160p/3240p) - + 4X (2880p/4320p) - + 5X (3600p/5400p) - + 6X (4320p/6480p) - + 7X (5040p/7560p) - + 8X (5760p/8640p) - + Nearest Neighbor - + Bilinear Bilineární - + Bicubic - + Gaussian - + Lanczos - + ScaleForce - + AMD FidelityFX Super Resolution - + Area - + MMPX - + Zero-Tangent - + B-Spline - + Mitchell - + Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None Žádné - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Výchozí (16:9) - + Force 4:3 Vynutit 4:3 - + Force 21:9 Vynutit 21:9 - + Force 16:10 - + Stretch to Window Roztáhnout podle okna - + Automatic - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - - - - - 64x - - - - + Japanese (日本語) Japonština (日本語) - + American English - + French (français) Francouzština (français) - + German (Deutsch) Nemčina (Deutsch) - + Italian (italiano) Italština (Italiano) - + Spanish (español) Španělština (español) - + Chinese Čínština - + Korean (한국어) Korejština (한국어) - + Dutch (Nederlands) Holandština (Nederlands) - + Portuguese (português) Portugalština (português) - + Russian (Русский) Ruština (Русский) - + Taiwanese Tajwanština - + British English Britská Angličtina - + Canadian French Kanadská Francouzština - + Latin American Spanish Latinsko Americká Španělština - + Simplified Chinese Zjednodušená Čínština - + Traditional Chinese (正體中文) Tradiční Čínština (正體中文) - + Brazilian Portuguese (português do Brasil) Brazilská Portugalština (português do Brasil) - + Polish (polska) - + Thai (แบบไทย) - - + + Japan Japonsko - + USA USA - + Europe Evropa - + Australia Austrálie - + China Čína - + Korea Korea - + Taiwan Taiwan - + Auto (%1) Auto select time zone - + Default (%1) Default time zone - + CET CET - + CST6CDT CST6CDT - + Cuba Cuba - + EET EET - + Egypt Egypt - + Eire Eire - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Eire - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hongkong - + HST HST - + Iceland Island - + Iran Iran - + Israel Israel - + Jamaica Jamajka - + Kwajalein Kwajalein - + Libya Lybie - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navajo - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Polsko - + Portugal Portugalsko - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Singapur - + Turkey Turecko - + UCT UCT - + Universal Univerzální - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulu - + Mono Mono - + Stereo Stereo - + Surround Surround - + 4GB DRAM (Default) - + 6GB DRAM (Unsafe) - + 8GB DRAM - + 10GB DRAM (Unsafe) - + 12GB DRAM (Unsafe) - + Docked Zadokovaná - + Handheld Příruční - - - Off + + + Normal - - Boost (1700MHz) + + + Boost - - Fast (2000MHz) + + + Overclock - + Always ask (Default) Vždy se zeptat (Výchozí) - + Only if game specifies not to stop - + Never ask - - - Medium (256) - - - - - - High (512) - - - - + Very Small (16 MB) - + Small (32 MB) - + Normal (128 MB) - + Large (256 MB) - + Very Large (512 MB) - + Very Low (4 MB) - + Low (8 MB) - + Normal (16 MB) - + Medium (32 MB) - + High (64 MB) - + Very Low (32) - + Low (64) - + Normal (128) - - Disabled + + Medium (256) - + + High (512) + + + + ExtendedDynamicState 1 - + ExtendedDynamicState 2 - + ExtendedDynamicState 3 - + Tree View - + Grid View @@ -2551,252 +2442,297 @@ When a program attempts to open the controller applet, it is immediately closed. Centrální log filtr - - When checked, the max size of the log increases from 100 MB to 1 GB - Po povolení se zvýší maximální velikost logu z 100 MB na 1 GB. + + GPU Logging/Level + - - Enable Extended Logging** - Povolit rozšířené logování + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console Zobrazit log v konzoli - + + When checked, the max size of the log increases from 100 MB to 1 GB + Po povolení se zvýší maximální velikost logu z 100 MB na 1 GB. + + + + Enable Extended Logging** + Povolit rozšířené logování + + + Open Log Location Otevřít lokaci s logama - + Homebrew Homebrew - + Arguments String Argumenty - + Graphics Grafika - + When checked, it executes shaders without loop logic changes Když je zaškrtnuto, shadery budou exekutovány bez změn logických smyček. - + Disable Loop safety checks - + When checked, it will dump all the macro programs of the GPU Když je zaškrtnuto, dumpne všechny makro programy GPU - + Dump Maxwell Macros Dumpnout Maxwell Makra - + When checked, it enables Nsight Aftermath crash dumps Zaškrtnutí povolí crash dumpy Nsight Aftermath - + Enable Nsight Aftermath Povolit Nsight Aftermath - - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Když je zaškrtnuto, dumpne všechny originální shadery assembleru z diskové zálohy shaderů či hry jak jsou nalezeny. - - - - Dump Game Shaders - Dumpnout shadery hry - - - - Enable Renderdoc Hotkey - - - - - When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower - Po povolení se zakáže makro Just-in-Time překladač. Poté hry poběží pomaleji. - - - - Disable Macro JIT - Zakázat Makro JIT - - - - When checked, it disables the macro HLE functions. Enabling this makes games run slower - - - - - Disable Macro HLE - - - - - When checked, the graphics API enters a slower debugging mode - Po povolení se grafické API přepne do pomalejšího režimu ladění. - - - - Enable Graphics Debugging - Zapnout ladění grafiky - - - - When checked, yuzu will log statistics about the compiled pipeline cache - Když je zaškrtnuto, yuzu bude logovat statistiky o kompilované mezipaměti pipelinu - - Enable Shader Feedback - Povolit Shader Feedback + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + - - <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + Dump Guest (Maxwell) Shaders - Disable Buffer Reorder + Enable Renderdoc Hotkey - Advanced - Pokročilé + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower + Po povolení se zakáže makro Just-in-Time překladač. Poté hry poběží pomaleji. - - Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. - + + Disable Macro JIT + Zakázat Makro JIT - - Perform Startup Vulkan Check + + When checked, it disables the macro HLE functions. Enabling this makes games run slower + Disable Macro HLE + + + + + When checked, the graphics API enters a slower debugging mode + Po povolení se grafické API přepne do pomalejšího režimu ladění. + + + + Enable Graphics Debugging + Zapnout ladění grafiky + + + + When checked, yuzu will log statistics about the compiled pipeline cache + Když je zaškrtnuto, yuzu bude logovat statistiky o kompilované mezipaměti pipelinu + + + + Enable Shader Feedback + Povolit Shader Feedback + + + + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + + + + Disable Buffer Reorder + + + + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + + Advanced + Pokročilé + + + + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. + + + + + Perform Startup Vulkan Check + + + + Disable Web Applet Zakázat Web Applet - + Enable All Controller Types - + Enable Auto-Stub - + Kiosk (Quest) Mode Předváděcí (Quest/Kiosk) režim - + Use dev.keys - + Enable Debug Asserts Povolit Debug Asserts - + Debugging Ladění - + Battery Serial: - + + Debug Knobs: + + + + Bitmask for quick development toggles - + Set debug knobs (bitmask) - + 16-bit debug knob set for quick development toggles - + (bitmask) - - Debug Knobs: - - - - + Unit Serial: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. - + Dump Audio Commands To Console** - + Flush log output on each line - + Enable FS Access Log - + Enable Verbose Reporting Services** - + Censor username in logs - + **This will be reset automatically when Eden closes. @@ -3246,33 +3182,38 @@ Would you like to delete the old save data? Barva Pozadí: - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off - + VSync Off - + Recommended - + On - + VSync On @@ -3793,6 +3734,16 @@ Would you like to delete the old save data? Motion / Touch Pohyb / Dotyk + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4302,58 +4253,58 @@ Pro převrácení os nejprve posuňte joystick vertikálně, poté horizontáln [čekání] - + New Profile Nový profil - + Enter a profile name: Zadejte název profilu: - - + + Create Input Profile Vytvořit profil vstupu - + The given profile name is not valid! Zadaný název profilu není platný! - + Failed to create the input profile "%1" Nepodařilo se vytvořit profil vstupu "%1" - + Delete Input Profile Odstranit profil vstupu - + Failed to delete the input profile "%1" Nepodařilo se odstranit profil vstupu "%1" - + Load Input Profile Načíst profil vstupu - + Failed to load the input profile "%1" Nepodařilo se načíst profil vstupu "%1" - + Save Input Profile Uložit profil vstupu - + Failed to save the input profile "%1" Nepodařilo se uložit profil vstupu "%1" @@ -4401,7 +4352,7 @@ Pro převrácení os nejprve posuňte joystick vertikálně, poté horizontáln - + Configure Konfigurovat @@ -4432,7 +4383,7 @@ Pro převrácení os nejprve posuňte joystick vertikálně, poté horizontáln - + Test Test @@ -4447,77 +4398,77 @@ Pro převrácení os nejprve posuňte joystick vertikálně, poté horizontáln Odstranit server - + %1:%2 %1:%2 - - - - - - + + + + + + Eden - + Port number has invalid characters Číslo portu obsahuje neplatné znaky - + Port has to be in range 0 and 65353 Port musí být v rozsahu 0 až 65353 - + IP address is not valid IP adresa není platná - + This UDP server already exists UDP server již existuje - + Unable to add more than 8 servers Není možné přidat více než 8 serverů - + Testing Testování - + Configuring Nastavování - + Test Successful Test byl úspěšný - + Successfully received data from the server. Úspěšně jsme získali data ze serveru. - + Test Failed Test byl neúspěšný - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Nedostali jsme platná data ze serveru.<br>Prosím zkontrolujte, že váš server je nastaven správně a že adresa a port jsou zadány správně. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. Probíhá test UDP nebo konfigurace kalibrace.<br>Prosím vyčkejte na dokončení. @@ -4701,57 +4652,57 @@ Current values are %1% and %2% respectively. Některá nastavení jsou dostupná pouze, pokud hra neběží. - + Add-Ons Doplňky - + System Systém - + CPU CPU - + Graphics Grafika - + Adv. Graphics Pokroč. grafika - + Ext. Graphics - + Audio Zvuk - + Input Profiles Profily Vstupu - + Network - + Applets - + Properties Vlastnosti @@ -4874,6 +4825,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5440,91 +5515,86 @@ Táhněte body pro změnu pozice nebo dvojitě klikněte na buňky tabulky pro z - Show Compatibility List - Zobrazit Seznam Kompatibility - - - Show Add-Ons Column Ukázat sloupec Doplňky - + Show Size Column Zobrazit sloupec s Velikostí - + Show File Types Column Zobrazit sloupec s Typem souborů - + Show Play Time Column Zobrazit sloupec s Dobou hraní - + Folder Icon Size: - + Row 1 Text: Text řádku 1: - + Row 2 Text: Text řádku 2: - + Screenshots Snímek obrazovky - + Ask Where To Save Screenshots (Windows Only) Zeptat se, kam uložit snímek obrazovky (pouze Windows) - + Screenshots Path: Cesta snímků obrazovky: - + ... ... - + TextLabel - + Resolution: Rozlišení: - + Select Screenshots Path... Vyberte cestu ke snímkům obrazovky... - + <System> <System> - + English Angličtina - + Auto (%1 x %2, %3 x %4) Screenshot width value @@ -5761,7 +5831,7 @@ Táhněte body pro změnu pozice nebo dvojitě klikněte na buňky tabulky pro z - + Calculating... @@ -5963,50 +6033,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL není k dispozici! - + OpenGL shared contexts are not supported. - + Eden has not been compiled with OpenGL support. - - - + + + Error while initializing OpenGL! Chyba při inicializaci OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Vaše grafická karta pravděpodobně nepodporuje OpenGL nebo nejsou nainstalovány nejnovější ovladače. - + Error while initializing OpenGL 4.6! Chyba při inicializaci OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Vaše grafická karta pravděpodobně nepodporuje OpenGL 4.6 nebo nejsou nainstalovány nejnovější ovladače.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Vaše grafická karta pravděpodobně nepodporuje jedno nebo více rozšíření OpenGL. Ujistěte se prosím, že jsou nainstalovány nejnovější ovladače.<br><br>GL Renderer:<br>%1<br><br>Nepodporované rozšíření:<br>%2 - + This build doesn't have OpenGL support. @@ -6014,279 +6084,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory - + Favorite Oblíbené - + Start Game Spustit hru - + Start Game without Custom Configuration Spustit hru bez vlastní konfigurace - + Open Save Data Location Otevřít Lokaci Savů - + Open Mod Data Location Otevřít Lokaci Modifikací - + Open Transferable Pipeline Cache - + Link to Ryujinx - + Remove Odstranit - + Remove Installed Update Odstranit nainstalovanou aktualizaci - + Remove All Installed DLC Odstranit všechny nainstalované DLC - + Remove Custom Configuration Odstranit vlastní konfiguraci hry - + Remove Cache Storage - + Remove OpenGL Pipeline Cache - + Remove Vulkan Pipeline Cache - + Remove All Pipeline Caches - + Remove All Installed Contents Odstranit všechen nainstalovaný obsah - + Manage Play Time - + Edit Play Time Data - + Remove Play Time Data Odstranit data o době hraní - - + + Dump RomFS Vypsat RomFS - + Dump RomFS to SDMC - + Verify Integrity Ověřit Integritu - + Copy Title ID to Clipboard Zkopírovat ID Titulu do schránky - - Navigate to GameDB entry - Navigovat do GameDB - - - + Create Shortcut Vytvořit Zástupce - + Add to Desktop - + Add to Applications Menu - + Configure Game - + Scan Subfolders Prohledat podsložky - + Remove Game Directory Odstranit složku se hrou - + ▲ Move Up ▲ Posunout nahoru - + ▼ Move Down ▼ Posunout dolů - + Open Directory Location Otevřít umístění složky - + Clear Vymazat - - - Name - Název - - - - Compatibility - Kompatibilita - - - - Add-ons - Modifkace - - - - File type - Typ-Souboru - - - - Size - Velikost - - - - Play time - Doba hraní - GameListItemCompat - + Ingame - + Game starts, but crashes or major glitches prevent it from being completed. - + Perfect Perfektní - + Game can be played without issues. Hra může být hrána bez problémů. - + Playable Hratelné - + Game functions with minor graphical or audio glitches and is playable from start to finish. Hra funguje s drobnými grafickými nebo zvukovými chybami a je hratelná od začátku do konce. - + Intro/Menu Intro/Menu - + Game loads, but is unable to progress past the Start Screen. - + Won't Boot Nebootuje - + The game crashes when attempting to startup. Hra crashuje při startu. - + Not Tested Netestováno - + The game has not yet been tested. Hra ještě nebyla testována + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list Dvojitým kliknutím přidáte novou složku do seznamu her @@ -6294,17 +6357,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) - + Filter: Filtr: - + Enter pattern to filter Zadejte filtr @@ -6798,1139 +6861,1112 @@ Debug Message: - + Game &Icon Size - + Reset Window Size to &720p Nastavit velikost okna na &720p - + Reset Window Size to 720p Nastavit velikost okna na 720p - + Reset Window Size to &900p Resetovat Velikost Okna na &900p - + Reset Window Size to 900p Resetovat Velikost Okna na 900p - + Reset Window Size to &1080p Nastavit velikost okna na &1080p - + Reset Window Size to 1080p Nastavit velikost okna na 1080p - + &Multiplayer - + &Tools &Nástroje - + Am&iibo - + Launch &Applet - + &TAS - + &Create Home Menu Shortcut - + Install &Firmware - + &Help &Pomoc - + &Install Files to NAND... &Instalovat soubory na NAND... - + L&oad File... Načís&t soubor... - + Load &Folder... Načíst sl&ožku... - + E&xit E&xit - - + + &Pause &Pauza - + &Stop &Stop - + &Verify Installed Contents &Ověřit Nainstalovaný Obsah - + &About Eden - + Single &Window Mode &Režim jednoho okna - + Con&figure... &Nastavení - + Ctrl+, - + Enable Overlay Display Applet - + Show &Filter Bar Zobrazit &filtrovací panel - + Show &Status Bar Zobrazit &stavový řádek - + Show Status Bar Zobrazit Staus Bar - + &Browse Public Game Lobby - + &Create Room &Vytvořit Místnost - + &Leave Room &Opustit Místnost - + &Direct Connect to Room - + &Show Current Room - + F&ullscreen &Celá obrazovka - + &Restart &Restartovat - + Load/Remove &Amiibo... - - &Report Compatibility - &Nahlásit kompatibilitu - - - + Open &Mods Page Otevřít stránku s &modifikacemi - - Open &Quickstart Guide - Otevřít &rychlého průvodce + + &User Handbook + - - &FAQ - Často &kladené otázky - - - + &Capture Screenshot Za&chytit snímek obrazovky - + &Album - + &Set Nickname and Owner &Nastavit Přezdívku a Vlastníka - + &Delete Game Data &Odstranit Herní Data - + &Restore Amiibo &Obnovit Amiibo - + &Format Amiibo - + &Mii Editor - + &Configure TAS... - + Configure C&urrent Game... Nastavení současné hry - - + + &Start &Start - + &Reset &Resetovat - - + + R&ecord - + Open &Controller Menu Otevřít &Menu Ovladače - + Install Decryption &Keys - + &Home Menu - + &Desktop - + &Application Menu - + &Root Data Folder - + &NAND Folder - + &SDMC Folder - + &Mod Folder - + &Log Folder - + From Folder - + From ZIP - + &Eden Dependencies - + &Data Manager - + &Tree View - + &Grid View - + Game Icon Size - - + + None - + Show Game &Name - + Show &Performance Overlay - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) - + Standard (64x64) - + Large (128x128) - + Full Size (256x256) - + Broken Vulkan Installation Detected - + Vulkan initialization failed during boot. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping - + Loading Web Applet... - - + + Disable Web Applet - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + The amount of shaders currently being built - + The current selected resolution scaling multiplier. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Unmute - + Mute - + Reset Volume - + + Configure Effects... + + + + &Clear Recent Files - + &Continue - + Warning: Outdated Game Format - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - - + + Error while loading ROM! - + The ROM format is not supported. - + An error occurred initializing the video core. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + An unknown error occurred. Please see the log for more details. - + + (64-bit) - + + (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + Closing software... - + Save Data - + Mod Data - + Error Opening %1 Folder - - + + Folder does not exist! - + Remove Installed Game Contents? - + Remove Installed Game Update? - + Remove Installed Game DLC? - + Remove Entry - + Delete OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? - + Remove Custom Game Configuration? - + Remove Cache Storage? - + Remove File - + Remove Play Time Data - + Reset play time? - - + + RomFS Extraction Failed! - + There was an error copying the RomFS files or the user cancelled the operation. - + Full - + Skeleton - + Select RomFS Dump Mode - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Extracting RomFS... - - + + Cancel - + RomFS Extraction Succeeded! - + The operation completed successfully. - + Error Opening %1 - + Select Directory - + Properties - + The game properties could not be loaded. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Load File - + Open Extracted ROM Directory - + Invalid Directory Selected - + The directory you have selected does not contain a 'main' file. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files - + %n file(s) remaining - + Installing file "%1"... - - + + Install Results - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + %n file(s) were newly installed - + %n file(s) were overwritten - + %n file(s) failed to install - + System Application - + System Archive - + System Application Update - + Firmware Package (Type A) - + Firmware Package (Type B) - + Game - + Game Update - + Game DLC - + Delta Title - + Select NCA Install Type... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Failed to Install - + The title type you selected for the NCA is invalid. - + File not found - + File "%1" not found - + OK - + Function Disabled - + Compatibility list reporting is currently disabled. Check back later! - + Error opening URL - + Unable to open the URL "%1". - + TAS Recording - + Overwrite file of player 1? - + Invalid config detected - + Handheld controller can't be used on docked mode. Pro controller will be selected. - - + + Amiibo - - + + The current amiibo has been removed - + Error - - + + The current game is not looking for amiibos - + Amiibo File (%1);; All Files (*.*) - + Load Amiibo - + Error loading Amiibo data - + The selected file is not a valid amiibo - + The selected file is already on use - + An unknown error occurred - - - Keys not installed - - - - - - Install decryption keys and restart Eden before attempting to install firmware. - - - - - Select Dumped Firmware Source Location - - - - - Select Dumped Firmware ZIP - - - - - Zipped Archives (*.zip) - - - - - Firmware cleanup failed - - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available - + Firmware Corrupted - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot - + PNG Image (*.png) - + TAS state: Running %1/%2 - + TAS state: Recording %1 - + TAS state: Idle %1/%2 - + TAS State: Invalid - + &Stop Running - + Stop R&ecording - + Building: %n shader(s) - + Scale: %1x %1 is the resolution scaling factor - + Speed: %1% / %2% - + Speed: %1% - + Game: %1 FPS - + Frame: %1 ms - - - FSR - - - - + NO AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE - + VOLUME: %1% Volume percentage (e.g. 50%) - + Derivation Components Missing - + Decryption keys are missing. Install them now? - + Wayland Detected! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -7938,74 +7974,74 @@ Would you like to force it for future launches? - + Use X11 - + Continue with Wayland - + Don't show again - + Restart Required - + Restart Eden to apply the X11 backend. - + Slow - + Turbo - + Unlocked - + Select RomFS Dump Target - + Please select which RomFS you would like to dump. - + Are you sure you want to close Eden? - - - + + + Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8071,6 +8107,11 @@ Would you like to bypass this and exit anyway? ScaleForce + + + FSR + + Area @@ -8082,52 +8123,57 @@ Would you like to bypass this and exit anyway? - + + SGSR + + + + + SGSR EdgeDir + + + + Docked - + Handheld - + Fast - - Balanced - - - - + Accurate - + Vulkan - + OpenGL GLSL - + OpenGL SPIRV - + OpenGL GLASM - + Null @@ -8160,7 +8206,7 @@ If you wish to clean up the files which were left in the old data location, you - + Data was migrated successfully. @@ -8648,8 +8694,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8662,10 +8708,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8680,9 +8726,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8844,50 +8890,50 @@ p, li { white-space: pre-wrap; } - + %1%2 %1%2 - + [invalid] - - + + %1%2Hat %3 - - - + + + %1%2Axis %3 - + %1%2Axis %3,%4,%5 - + %1%2Motion %3 - - + + %1%2Button %3 - + [unused] [nepoužito] @@ -9013,47 +9059,47 @@ p, li { white-space: pre-wrap; } %1 hraje %2 - + Play Time: %1 - + Never Played - + Version: %1 - + Version: 1.0.0 - + Installed SD Titles Nainstalované SD tituly - + Installed NAND Titles Nainstalované NAND tituly - + System Titles Systémové tituly - + Add New Game Directory Přidat novou složku s hrami - + Favorites Oblíbené @@ -9174,13 +9220,13 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. @@ -9190,10 +9236,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9214,213 +9260,246 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9664,72 +9743,72 @@ Would you like to manually select a portable folder to use? - + Create Shortcut - + Do you want to launch the game in fullscreen? - + Shortcut Created - + Successfully created a shortcut to %1 - + Shortcut may be Volatile! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? - + Failed to Create Shortcut - + Failed to create a shortcut to %1 - + Create Icon - + Cannot create icon file. Path "%1" does not exist and cannot be created. - + No firmware available - + Please install firmware to use the home menu. - + Home Menu Applet - + Home Menu is not available. Please reinstall firmware. @@ -9737,55 +9816,55 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name - + What should this mod be called? - + RomFS - + ExeFS/Patch - + Cheat - + Mod Type - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty @@ -10479,65 +10558,65 @@ By selecting "From Eden", previous save data stored in Ryujinx will be - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/da.ts b/dist/languages/da.ts index 91380077a2..4e34f94af9 100644 --- a/dist/languages/da.ts +++ b/dist/languages/da.ts @@ -211,155 +211,6 @@ Dette vil bandlyse både vedkommendes forum-brugernavn og IP-adresse.%1 - %2 (%3/%4 medlemmer) - forbundet - - CompatDB - - - Report Compatibility - Rapportér Kompatibilitet - - - - - - - - - - Report Game Compatibility - Rapportér Spilkompatibilitet - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - - - - - <html><head/><body><p>Does the game boot?</p></body></html> - - - - - Yes The game starts to output video or audio - - - - - No The game doesn't get past the "Launching..." screen - - - - - Yes The game gets past the intro/menu and into gameplay - - - - - No The game crashes or freezes while loading or using the menu - - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - - - - - Yes The game works without crashes - - - - - No The game crashes or freezes during gameplay - - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - - - - - Yes The game can be finished without any workarounds - - - - - No The game can't progress past a certain area - - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - - - - - Major The game has major graphical errors - - - - - Minor The game has minor graphical errors - - - - - None Everything is rendered as it looks on the Nintendo Switch - - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - - - - - Major The game has major audio errors - - - - - Minor The game has minor audio errors - - - - - None Audio is played perfectly - - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - - - - - Thank you for your submission! - Tak for din indsendelse! - - - - Submitting - Sender - - - - Communication error - Kommunikationsfejl - - - - An error occurred while sending the Testcase - Der skete en fejl under indsendelse af Test-sagen - - - - Next - Næste - - ConfigurationShared @@ -559,231 +410,241 @@ Can help reduce stuttering at lower framerates. - + Backend: - CPU Overclock + CPU Clocks - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. - + Custom CPU Ticks - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. - + Enable Host MMU Emulation (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. - + Unfuse FMA (improve performance on CPUs without FMA) Adskil FMA (forbedr ydeevne på CPUer uden FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. - + Faster FRSQRTE and FRECPE Hurtigere FRSQRTE og FRECPE - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. - + Faster ASIMD instructions (32 bits only) Hurtigere ASIMD-instrukser (kun 32-bit) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. - + Inaccurate NaN handling Unøjagtig NaN-håndtering - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. - + Disable address space checks Deaktivér adresseplads-kontrol - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. - + Ignore global monitor Ignorér global overvågning - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. - + API: API: - + Changes the output graphics API. Vulkan is recommended. - + Device: Enhed: - + This setting selects the GPU to use (Vulkan only). - + Resolution: Opløsning: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. - + Window Adapting Filter: Vinduestilpassende Filter: - + FSR Sharpness: - - Determines how sharpened the image will look using FSR's dynamic contrast. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. - + Anti-Aliasing Method: Anti-Aliaseringsmetode: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Fuldskærmstilstand: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. - + Aspect Ratio: Skærmformat: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. - + Use persistent pipeline cache - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. - + Use asynchronous GPU emulation - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + NVDEC emulation: NVDEC-emulering: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. - + ASTC Decoding Method: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -792,55 +653,75 @@ stuttering but may present artifacts. - + ASTC Recompression Method: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. - + Skip CPU Inner Invalidation - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -848,1361 +729,1371 @@ Immediate (no synchronization) presents whatever is available and can exhibit te - + Sync Memory Operations - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. - + Enable asynchronous presentation (Vulkan only) - + Slightly improves performance by moving presentation to a separate CPU thread. - + Force maximum clocks (Vulkan only) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. - + Anisotropic Filtering: Anisotropisk Filtrering: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. - + GPU Mode: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - + DMA Accuracy: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. - + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation - + May reduce shader stutter. - - Fast GPU Time + + GPU Clocks - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. - + GPU Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. - + Enable Compute Pipelines (Intel Vulkan Only) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. - + Enable Reactive Flushing - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. - + Sync to framerate of video playback - + Run the game at normal speed during video playback, even when the framerate is unlocked. - + Barrier feedback loops - + Improves rendering of transparency effects in specific games. - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. - + Vertex Input Dynamic State - + Enables vertex input dynamic state feature for better quality and performance. - + Sample Shading - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. - + RNG Seed RNG-Seed - + Controls the seed of the random number generator. Mainly used for speedrunning. - + Device Name - + The name of the console. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: - + This option allows to change the clock of the console. Can be used to manipulate time in games. - + The number of seconds from the current unix time - + Language: - + This option can be overridden when region setting is auto-select - + Region: Region - + The region of the console. - + Time Zone: Tidszone - + The time zone of the console. - + Sound Output Mode: - + Console Mode: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. - + Prompt for user profile on boot - + Useful if multiple people use the same PC. - + Pause when not in focus - + Pauses emulation when focusing on other windows. - + Confirm before stopping emulation - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. - + Hide mouse on inactivity Skjul mus ved inaktivitet - + Hides the mouse after 2.5s of inactivity. - - Disable controller applet - - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - - - - + Check for updates - + Whether or not to check for updates upon startup. - + Enable Gamemode - + Force X11 as Graphics Backend - + Custom frontend - + Real applet - + + + Disabled + + + + Never - + On Load - + Always - + CPU CPU - + GPU - + CPU Asynchronous - + Uncompressed (Best quality) - + BC1 (Low quality) - + BC3 (Medium quality) - - + + Auto Automatisk - + 30 FPS - + 60 FPS - + 90 FPS - + 120 FPS - + Conservative - + Aggressive - + Vulkan - + OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) - + Null - + Fast - - Balanced - - - - - + + + Accurate Nøjagtig - - + + + Default Standard - + Unsafe (fast) - + Safe (stable) - + + Immediate + + + + + Balanced + + + + Unsafe Usikker - + Paranoid (disables most optimizations) Paranoid (deaktiverer de fleste optimeringer) - + Debugging - + Dynarmic - + NCE - + Borderless Windowed Uindrammet Vindue - + Exclusive Fullscreen Eksklusiv Fuld Skærm - + No Video Output Ingen Video-Output - + CPU Video Decoding CPU-Video Afkodning - + GPU Video Decoding (Default) GPU-Video Afkodning (Standard) - + 0.25X (180p/270p) [EXPERIMENTAL] - + 0.5X (360p/540p) [EXPERIMENTAL] - + 0.75X (540p/810p) [EXPERIMENTAL] 0,75X (540p/810p) [EKSPERIMENTEL] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) - + 8X (5760p/8640p) - + Nearest Neighbor Nærmeste Nabo - + Bilinear Bilineær - + Bicubic Bikubisk - + Gaussian Gausisk - + Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution - + Area - + MMPX - + Zero-Tangent - + B-Spline - + Mitchell - + Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None Ingen - + FXAA FXAA - + SMAA - + Default (16:9) Standard (16:9) - + Force 4:3 Tving 4:3 - + Force 21:9 Tving 21:9 - + Force 16:10 - + Stretch to Window Stræk til Vindue - + Automatic - + 2x - + 4x - + 8x - + 16x - - 32x - - - - - 64x - - - - + Japanese (日本語) Japansk (日本語) - + American English - + French (français) Fransk (français) - + German (Deutsch) Tysk (Deutsch) - + Italian (italiano) Italiensk (italiano) - + Spanish (español) Spansk (español) - + Chinese Kinesisk - + Korean (한국어) Koreansk (한국어) - + Dutch (Nederlands) Hollandsk (Nederlands) - + Portuguese (português) Portugisisk (português) - + Russian (Русский) Russisk (Русский) - + Taiwanese Taiwanesisk - + British English Britisk Engelsk - + Canadian French Candadisk Fransk - + Latin American Spanish Latinamerikansk Spansk - + Simplified Chinese Forenklet Kinesisk - + Traditional Chinese (正體中文) Traditionelt Kinesisk (正體中文) - + Brazilian Portuguese (português do Brasil) Braziliansk Portugisisk (português do Brasil) - + Polish (polska) - + Thai (แบบไทย) - - + + Japan Japan - + USA USA - + Europe Europa - + Australia Australien - + China Kina - + Korea Korea - + Taiwan Taiwan - + Auto (%1) Auto select time zone - + Default (%1) Default time zone - + CET CET - + CST6CDT CST6CDT - + Cuba Cuba - + EET EET - + Egypt Ægypten - + Eire Eire - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Eire - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hongkong - + HST HST - + Iceland Island - + Iran Iran - + Israel Israel - + Jamaica Jamaica - + Kwajalein Kwajalein - + Libya Libyen - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navajo - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Polen - + Portugal Portugal - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Singapore - + Turkey Tyrkiet - + UCT UCT - + Universal Universel - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulu - + Mono Mono - + Stereo Stereo - + Surround Surround - + 4GB DRAM (Default) - + 6GB DRAM (Unsafe) - + 8GB DRAM - + 10GB DRAM (Unsafe) - + 12GB DRAM (Unsafe) - + Docked Dokket - + Handheld Håndholdt - - - Off + + + Normal - - Boost (1700MHz) + + + Boost - - Fast (2000MHz) + + + Overclock - + Always ask (Default) - + Only if game specifies not to stop - + Never ask - - - Medium (256) - - - - - - High (512) - - - - + Very Small (16 MB) - + Small (32 MB) - + Normal (128 MB) - + Large (256 MB) - + Very Large (512 MB) - + Very Low (4 MB) - + Low (8 MB) - + Normal (16 MB) - + Medium (32 MB) - + High (64 MB) - + Very Low (32) - + Low (64) - + Normal (128) - - Disabled + + Medium (256) - + + High (512) + + + + ExtendedDynamicState 1 - + ExtendedDynamicState 2 - + ExtendedDynamicState 3 - + Tree View - + Grid View @@ -2559,252 +2450,297 @@ When a program attempts to open the controller applet, it is immediately closed. Globalt Logfilter - - When checked, the max size of the log increases from 100 MB to 1 GB - Når valgt, øges loggens maksimale størrelse fra 100 MB til 1 GB + + GPU Logging/Level + - - Enable Extended Logging** - Aktivér Udvidet Logning** + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console Vis Log i Konsol - + + When checked, the max size of the log increases from 100 MB to 1 GB + Når valgt, øges loggens maksimale størrelse fra 100 MB til 1 GB + + + + Enable Extended Logging** + Aktivér Udvidet Logning** + + + Open Log Location Åbn Logplacering - + Homebrew Hjemmebrændt - + Arguments String Argumentsstreng - + Graphics Grafik - + When checked, it executes shaders without loop logic changes Når valgt, eksekverer den shadere, uden loop-logik-forandringer - + Disable Loop safety checks Deaktivér Loop-sikkerhedskontrol - + When checked, it will dump all the macro programs of the GPU Når valgt, vil den dumpe alle GPUens makroprogrammer - + Dump Maxwell Macros Dump Maxwell-Makroer - + When checked, it enables Nsight Aftermath crash dumps Når valgt, aktiverer det Nsight Aftermath nedbruds-nedfældelser - + Enable Nsight Aftermath Aktivér Nsight Aftermath - - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Når valgt, vil den dumpe alle de originale samler-shadere fra diskens shader-lager eller game, som fundet - - - - Dump Game Shaders - Dump Spil-Shadere - - - - Enable Renderdoc Hotkey - - - - - When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower - Når valgt, deaktiverer den makro-Just-In-Time-kompileringen. Aktivering heraf får spil til at køre langsommere - - - - Disable Macro JIT - Deaktivér Makro-JIT - - - - When checked, it disables the macro HLE functions. Enabling this makes games run slower - - - - - Disable Macro HLE - - - - - When checked, the graphics API enters a slower debugging mode - Når valgt, påbegynder grafik-APIen en langsommere fejlfindingstilstand - - - - Enable Graphics Debugging - Aktivér Grafik-Fejlfinding - - - - When checked, yuzu will log statistics about the compiled pipeline cache - Når valgt, vil yuzu logføre statistikker om det kompilerede rørlinje-mellemlager - - Enable Shader Feedback - Aktivér Shader-Tilbagemelding + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + - - <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + Dump Guest (Maxwell) Shaders - Disable Buffer Reorder + Enable Renderdoc Hotkey + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower + Når valgt, deaktiverer den makro-Just-In-Time-kompileringen. Aktivering heraf får spil til at køre langsommere + + + + Disable Macro JIT + Deaktivér Makro-JIT + + + + When checked, it disables the macro HLE functions. Enabling this makes games run slower + + + + + Disable Macro HLE + + + + + When checked, the graphics API enters a slower debugging mode + Når valgt, påbegynder grafik-APIen en langsommere fejlfindingstilstand + + + + Enable Graphics Debugging + Aktivér Grafik-Fejlfinding + + + + When checked, yuzu will log statistics about the compiled pipeline cache + Når valgt, vil yuzu logføre statistikker om det kompilerede rørlinje-mellemlager + + + + Enable Shader Feedback + Aktivér Shader-Tilbagemelding + + + + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + + + + Disable Buffer Reorder + + + + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced Avanceret - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Gør Yuzu i stand til at kontrollere for et funktionelt Vulkan-miljø, når programmet starter op. Deaktivering af dette forårsager problemer med at eksterne programmer ser Yuzu. - + Perform Startup Vulkan Check Udfør Vulkan-Kontrol Under Opstart - + Disable Web Applet Deaktivér Net-Applet - + Enable All Controller Types Aktivér Alle Kontrolenhedstyper - + Enable Auto-Stub - + Kiosk (Quest) Mode Kiosk (Rejse)-Tilstand - + Use dev.keys - + Enable Debug Asserts Aktivér Fejlfindingshævdelser - + Debugging Fejlfinding - + Battery Serial: - + + Debug Knobs: + + + + Bitmask for quick development toggles - + Set debug knobs (bitmask) - + 16-bit debug knob set for quick development toggles - + (bitmask) - - Debug Knobs: - - - - + Unit Serial: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. Aktivér dette, for at udgyde den senest genererede lyd-kommandoliste til konsollen. Påvirker kun spil, som gør brug af lyd-renderingen. - + Dump Audio Commands To Console** Dump Lydkommandoer Til Konsol** - + Flush log output on each line - + Enable FS Access Log Aktivér FS-Tilgangslog - + Enable Verbose Reporting Services** Aktivér Vitterlig Rapporteringstjeneste - + Censor username in logs - + **This will be reset automatically when Eden closes. @@ -3254,33 +3190,38 @@ Would you like to delete the old save data? Baggrundsfarve: - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off - + VSync Off - + Recommended - + On - + VSync On @@ -3801,6 +3742,16 @@ Would you like to delete the old save data? Motion / Touch Bevægelse / Berøring + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4310,58 +4261,58 @@ Bevæg, for at omvende akserne, først din styrepind lodret og så vandret.[venter] - + New Profile Ny Profil - + Enter a profile name: Indtast et profilnavn: - - + + Create Input Profile Opret Input-Profil - + The given profile name is not valid! Det angivne profilnavn er ikke gyldigt! - + Failed to create the input profile "%1" Oprettelse af input-profil "%1" mislykkedes - + Delete Input Profile Slet Input-Profil - + Failed to delete the input profile "%1" Sletning af input-profil "%1" mislykkedes - + Load Input Profile Indlæs Input-Profil - + Failed to load the input profile "%1" Indlæsning af input-profil "%1" mislykkedes - + Save Input Profile Gem Input-Profil - + Failed to save the input profile "%1" Lagring af input-profil "%1" mislykkedes @@ -4409,7 +4360,7 @@ Bevæg, for at omvende akserne, først din styrepind lodret og så vandret. - + Configure Konfigurér @@ -4440,7 +4391,7 @@ Bevæg, for at omvende akserne, først din styrepind lodret og så vandret. - + Test Afprøv @@ -4455,77 +4406,77 @@ Bevæg, for at omvende akserne, først din styrepind lodret og så vandret.Fjernserver - + %1:%2 %1:%2 - - - - - - + + + + + + Eden - + Port number has invalid characters Portnummer indeholder ugyldige tegn - + Port has to be in range 0 and 65353 Port skal være imellem 0 and 65353 - + IP address is not valid IP-adresse er ikke gyldig - + This UDP server already exists Denne UDP-server eksisterer allerede - + Unable to add more than 8 servers Ude af stand til, at tilføje mere end 8 servere - + Testing Afprøvning - + Configuring Konfigurér - + Test Successful Afprøvning Lykkedes - + Successfully received data from the server. Modtagelse af data fra serveren lykkedes. - + Test Failed Afprøvning Mislykkedes - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Kunne ikke modtage gyldig data fra serveren.<br>Bekræft venligst, at serveren er opsat korrekt, og at adressen og porten er korrekte. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. UDP-Afprøvnings- eller -kalibreringskonfiguration er i gang.<br>vent venligst på, at de bliver færdige. @@ -4709,57 +4660,57 @@ Current values are %1% and %2% respectively. - + Add-Ons Tilføjelser - + System System - + CPU CPU - + Graphics Grafik - + Adv. Graphics - + Ext. Graphics - + Audio Lyd - + Input Profiles - + Network - + Applets - + Properties Egenskaber @@ -4882,6 +4833,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5448,91 +5523,86 @@ Træk punkter, for at skifte position, eller dobbeltklik i tabelceller, for at r - Show Compatibility List - - - - Show Add-Ons Column Vis Tilføjelser-Kolonne - + Show Size Column - + Show File Types Column - + Show Play Time Column - + Folder Icon Size: Mappe-Ikonstørrelse: - + Row 1 Text: Række 1-Tekst: - + Row 2 Text: Række 2-Tekst: - + Screenshots Skærmbilleder - + Ask Where To Save Screenshots (Windows Only) Spørg Hvor Skærmbilleder Skal Gemmes (Kun Windows) - + Screenshots Path: Skærmbilledsti: - + ... ... - + TextLabel - + Resolution: Opløsning: - + Select Screenshots Path... Vælg Skærmbilledsti... - + <System> <System> - + English Engelsk - + Auto (%1 x %2, %3 x %4) Screenshot width value @@ -5769,7 +5839,7 @@ Træk punkter, for at skifte position, eller dobbeltklik i tabelceller, for at r - + Calculating... @@ -5971,50 +6041,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! - + OpenGL shared contexts are not supported. - + Eden has not been compiled with OpenGL support. - - - + + + Error while initializing OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. - + Error while initializing OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 - + This build doesn't have OpenGL support. @@ -6022,279 +6092,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory - + Favorite - + Start Game - + Start Game without Custom Configuration - + Open Save Data Location Åbn Gemt Data-Placering - + Open Mod Data Location Åbn Mod-Data-Placering - + Open Transferable Pipeline Cache - + Link to Ryujinx - + Remove Fjern - + Remove Installed Update - + Remove All Installed DLC - + Remove Custom Configuration - + Remove Cache Storage - + Remove OpenGL Pipeline Cache - + Remove Vulkan Pipeline Cache - + Remove All Pipeline Caches - + Remove All Installed Contents - + Manage Play Time - + Edit Play Time Data - + Remove Play Time Data - - + + Dump RomFS - + Dump RomFS to SDMC - + Verify Integrity - + Copy Title ID to Clipboard Kopiér Titel-ID til Udklipsholder - - Navigate to GameDB entry - - - - + Create Shortcut - + Add to Desktop - + Add to Applications Menu - + Configure Game - + Scan Subfolders - + Remove Game Directory - + ▲ Move Up - + ▼ Move Down - + Open Directory Location - + Clear Ryd - - - Name - Navn - - - - Compatibility - Kompatibilitet - - - - Add-ons - Tilføjelser - - - - File type - Filtype - - - - Size - Størrelse - - - - Play time - - GameListItemCompat - + Ingame - + Game starts, but crashes or major glitches prevent it from being completed. - + Perfect Perfekt - + Game can be played without issues. - + Playable - + Game functions with minor graphical or audio glitches and is playable from start to finish. - + Intro/Menu Intro/Menu - + Game loads, but is unable to progress past the Start Screen. - + Won't Boot Starter Ikke Op - + The game crashes when attempting to startup. - + Not Tested Ikke Afprøvet - + The game has not yet been tested. Spillet er endnu ikke blevet afprøvet. + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list @@ -6302,17 +6365,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) - + Filter: Filter: - + Enter pattern to filter @@ -6806,1139 +6869,1112 @@ Debug Message: - + Game &Icon Size - + Reset Window Size to &720p - + Reset Window Size to 720p - + Reset Window Size to &900p - + Reset Window Size to 900p - + Reset Window Size to &1080p - + Reset Window Size to 1080p - + &Multiplayer - + &Tools - + Am&iibo - + Launch &Applet - + &TAS - + &Create Home Menu Shortcut - + Install &Firmware - + &Help &Hjælp - + &Install Files to NAND... - + L&oad File... - + Load &Folder... - + E&xit - - + + &Pause - + &Stop - + &Verify Installed Contents - + &About Eden - + Single &Window Mode - + Con&figure... - + Ctrl+, - + Enable Overlay Display Applet - + Show &Filter Bar - + Show &Status Bar - + Show Status Bar Vis Statuslinje - + &Browse Public Game Lobby - + &Create Room - + &Leave Room - + &Direct Connect to Room - + &Show Current Room - + F&ullscreen - + &Restart - + Load/Remove &Amiibo... - - &Report Compatibility - - - - + Open &Mods Page - - Open &Quickstart Guide + + &User Handbook - - &FAQ - - - - + &Capture Screenshot - + &Album - + &Set Nickname and Owner - + &Delete Game Data - + &Restore Amiibo - + &Format Amiibo - + &Mii Editor - + &Configure TAS... - + Configure C&urrent Game... - - + + &Start - + &Reset - - + + R&ecord - + Open &Controller Menu - + Install Decryption &Keys - + &Home Menu - + &Desktop - + &Application Menu - + &Root Data Folder - + &NAND Folder - + &SDMC Folder - + &Mod Folder - + &Log Folder - + From Folder - + From ZIP - + &Eden Dependencies - + &Data Manager - + &Tree View - + &Grid View - + Game Icon Size - - + + None - + Show Game &Name - + Show &Performance Overlay - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) - + Standard (64x64) - + Large (128x128) - + Full Size (256x256) - + Broken Vulkan Installation Detected - + Vulkan initialization failed during boot. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping - + Loading Web Applet... - - + + Disable Web Applet - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + The amount of shaders currently being built - + The current selected resolution scaling multiplier. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Unmute - + Mute - + Reset Volume - + + Configure Effects... + + + + &Clear Recent Files - + &Continue - + Warning: Outdated Game Format - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - - + + Error while loading ROM! - + The ROM format is not supported. - + An error occurred initializing the video core. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + An unknown error occurred. Please see the log for more details. - + + (64-bit) - + + (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + Closing software... - + Save Data - + Mod Data - + Error Opening %1 Folder - - + + Folder does not exist! - + Remove Installed Game Contents? - + Remove Installed Game Update? - + Remove Installed Game DLC? - + Remove Entry - + Delete OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? - + Remove Custom Game Configuration? - + Remove Cache Storage? - + Remove File - + Remove Play Time Data - + Reset play time? - - + + RomFS Extraction Failed! - + There was an error copying the RomFS files or the user cancelled the operation. - + Full - + Skeleton - + Select RomFS Dump Mode - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Extracting RomFS... - - + + Cancel - + RomFS Extraction Succeeded! - + The operation completed successfully. - + Error Opening %1 - + Select Directory - + Properties - + The game properties could not be loaded. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Load File - + Open Extracted ROM Directory - + Invalid Directory Selected - + The directory you have selected does not contain a 'main' file. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files - + %n file(s) remaining - + Installing file "%1"... - - + + Install Results - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + %n file(s) were newly installed - + %n file(s) were overwritten - + %n file(s) failed to install - + System Application - + System Archive - + System Application Update - + Firmware Package (Type A) - + Firmware Package (Type B) - + Game - + Game Update - + Game DLC - + Delta Title - + Select NCA Install Type... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Failed to Install - + The title type you selected for the NCA is invalid. - + File not found - + File "%1" not found - + OK - + Function Disabled - + Compatibility list reporting is currently disabled. Check back later! - + Error opening URL - + Unable to open the URL "%1". - + TAS Recording - + Overwrite file of player 1? - + Invalid config detected - + Handheld controller can't be used on docked mode. Pro controller will be selected. - - + + Amiibo - - + + The current amiibo has been removed - + Error - - + + The current game is not looking for amiibos - + Amiibo File (%1);; All Files (*.*) - + Load Amiibo - + Error loading Amiibo data - + The selected file is not a valid amiibo - + The selected file is already on use - + An unknown error occurred - - - Keys not installed - - - - - - Install decryption keys and restart Eden before attempting to install firmware. - - - - - Select Dumped Firmware Source Location - - - - - Select Dumped Firmware ZIP - - - - - Zipped Archives (*.zip) - - - - - Firmware cleanup failed - - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available - + Firmware Corrupted - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot - + PNG Image (*.png) - + TAS state: Running %1/%2 - + TAS state: Recording %1 - + TAS state: Idle %1/%2 - + TAS State: Invalid - + &Stop Running - + Stop R&ecording - + Building: %n shader(s) - + Scale: %1x %1 is the resolution scaling factor - + Speed: %1% / %2% - + Speed: %1% - + Game: %1 FPS - + Frame: %1 ms - - - FSR - - - - + NO AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE - + VOLUME: %1% Volume percentage (e.g. 50%) - + Derivation Components Missing - + Decryption keys are missing. Install them now? - + Wayland Detected! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -7946,74 +7982,74 @@ Would you like to force it for future launches? - + Use X11 - + Continue with Wayland - + Don't show again - + Restart Required - + Restart Eden to apply the X11 backend. - + Slow - + Turbo - + Unlocked - + Select RomFS Dump Target - + Please select which RomFS you would like to dump. - + Are you sure you want to close Eden? - - - + + + Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8079,6 +8115,11 @@ Would you like to bypass this and exit anyway? ScaleForce + + + FSR + + Area @@ -8090,52 +8131,57 @@ Would you like to bypass this and exit anyway? - + + SGSR + + + + + SGSR EdgeDir + + + + Docked - + Handheld - + Fast - - Balanced - - - - + Accurate - + Vulkan - + OpenGL GLSL - + OpenGL SPIRV - + OpenGL GLASM - + Null @@ -8168,7 +8214,7 @@ If you wish to clean up the files which were left in the old data location, you - + Data was migrated successfully. @@ -8652,8 +8698,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8666,10 +8712,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8684,9 +8730,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8848,50 +8894,50 @@ p, li { white-space: pre-wrap; } - + %1%2 - + [invalid] - - + + %1%2Hat %3 - - - + + + %1%2Axis %3 - + %1%2Axis %3,%4,%5 - + %1%2Motion %3 - - + + %1%2Button %3 - + [unused] [ubrugt] @@ -9017,47 +9063,47 @@ p, li { white-space: pre-wrap; } - + Play Time: %1 - + Never Played - + Version: %1 - + Version: 1.0.0 - + Installed SD Titles Installerede SD-Titler - + Installed NAND Titles Installerede NAND-Titler - + System Titles Systemtitler - + Add New Game Directory Tilføj Ny Spilmappe - + Favorites @@ -9178,13 +9224,13 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. @@ -9194,10 +9240,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9218,213 +9264,246 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9668,72 +9747,72 @@ Would you like to manually select a portable folder to use? - + Create Shortcut - + Do you want to launch the game in fullscreen? - + Shortcut Created - + Successfully created a shortcut to %1 - + Shortcut may be Volatile! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? - + Failed to Create Shortcut - + Failed to create a shortcut to %1 - + Create Icon - + Cannot create icon file. Path "%1" does not exist and cannot be created. - + No firmware available - + Please install firmware to use the home menu. - + Home Menu Applet - + Home Menu is not available. Please reinstall firmware. @@ -9741,55 +9820,55 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name - + What should this mod be called? - + RomFS - + ExeFS/Patch - + Cheat - + Mod Type - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty @@ -10473,65 +10552,65 @@ By selecting "From Eden", previous save data stored in Ryujinx will be - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/de.ts b/dist/languages/de.ts index 93652b117d..a911437c44 100644 --- a/dist/languages/de.ts +++ b/dist/languages/de.ts @@ -26,17 +26,24 @@ li.unchecked::marker { content: "\2610"; } li.checked::marker { content: "\2612"; } </style></head><body style=" font-family:'Noto Sans'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt;">Eden is an experimental open-source emulator for the Nintendo Switch licensed under GPLv3.0+ which is based on the yuzu emulator which ended development back in March 2024. <br /><br />This software should not be used to play games you have not legally obtained.</span></p></body></html> - + <!DOCTYPE HTML PUBLIC „-//W3C//DTD HTML 4.0//EN“ „http://www.w3.org/TR/REC-html40/strict.dtd“> +<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> +p, li { white-space: pre-wrap; } +hr { height: 1px; border-width: 0; } +li.unchecked::marker { content: „\2610“; } +li.checked::marker { content: „\2612“; } +</style></head><body style=" font-family:‚Noto Sans‘; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:‚MS Shell Dlg 2‘; font-size:12pt;">Eden ist ein experimenteller, quelloffener Emulator für die Nintendo Switch, der unter der GPLv3.0+ lizenziert ist und auf dem Emulator yuzu basiert, dessen Entwicklung im März 2024 eingestellt wurde. <br /><br />Diese Software sollte nicht zum Spielen von Spielen verwendet werden, die du nicht legal erworben hast.</span></p></body></html> <html><head/><body><p><a href="https://eden-emulator.github.io/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://git.eden-emu.dev"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/activity/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://discord.gg/HstXbPch7X"><span style=" text-decoration: underline; color:#039be5;">Discord</span></a> | <a href="https://stt.gg/qKgFEAbH"><span style=" text-decoration: underline; color:#039be5;">Stoat</span></a> | <a href="https://nitter.poast.org/edenemuofficial"><span style=" text-decoration: underline; color:#039be5;">Twitter</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/src/branch/master/LICENSE.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html> - + <html><head/><body><p><a href="https://eden-emulator.github.io/"><span style=" text-decoration: underline; color:#039be5;">Webseite</span></a> | <a href="https://git.eden-emu.dev"><span style=" text-decoration: underline; color:#039be5;">Quell-Code</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/activity/contributors"><span style=" text-decoration: underline; color:#039be5;">Mitwirkende</span></a> | <a href="https://discord.gg/HstXbPch7X"><span style=" text-decoration: underline; color:#039be5;">Discord</span></a> | <a href="https://stt.gg/qKgFEAbH"><span style=" text-decoration: underline; color:#039be5;">Hermelin</span></a> | <a href="https://nitter.poast.org/edenemuofficial"><span style=" text-decoration: underline; color:#039be5;">Twitter</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/src/branch/master/LICENSE.txt"><span style=" text-decoration: underline; color:#039be5;">Lizenz</span></a></p></body></html> <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; is a trademark of Nintendo. Eden is not affiliated with Nintendo in any way.</span></p></body></html> - + <html><head/><body><p><span style=" font-size:7pt;">„Nintendo Switch“ ist eine Marke von Nintendo. Eden steht in keinerlei Verbindung zu Nintendo.</span></p></body></html> @@ -210,155 +217,6 @@ Dies würde deren Forum-Benutzernamen und deren IP-Adresse sperren.%1 - %2 (%3/%4 Mitglieder) - verbunden - - CompatDB - - - Report Compatibility - Kompatibilität melden - - - - - - - - - - Report Game Compatibility - Kompatibilität des Spiels melden - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body>Startet das Spiel?</p></body></html> - - - - Yes The game starts to output video or audio - Ja Das Spiel beginnt mit der Video- oder Audioausgabe - - - - No The game doesn't get past the "Launching..." screen - Das Spiel kommt nicht über den "Starten..."-Bildschirm hinaus - - - - Yes The game gets past the intro/menu and into gameplay - Ja Das Spiel kommt über das Intro/Menü hinaus und ins Gameplay - - - - No The game crashes or freezes while loading or using the menu - Nein das Spiel stürzt ab oder friert ein während des Ladens des Menüs. - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>Erreicht das Spiel den Spielverlauf?</p></body></html> - - - - Yes The game works without crashes - Ja Das Spiel funktioniert ohne Abstürze. - - - - No The game crashes or freezes during gameplay - Nein Das Spiel stürzt ab oder freezed während des spielen. - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>Funktioniert das Spiel ohne Abstürze, einfrieren oder dass es sich während des Spielverlaufs aufhängt?</p></body></html> - - - - Yes The game can be finished without any workarounds - Ja Das Spiel kann ohne Workarounds abgeschlossen werden. - - - - No The game can't progress past a certain area - Nein Spezielle Bereiche des Spieles können nicht abgeschlossen werden. - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>Ist das Spiel komplett spielbar von Anfang bis Ende?</p></body></html> - - - - Major The game has major graphical errors - Schwerwiegend  Das Spiel hat schwerwiegende graphische Fehler - - - - Minor The game has minor graphical errors - Kleinere Das Spiel hat kleinere graphische Fehler - - - - None Everything is rendered as it looks on the Nintendo Switch - Keine  Alles wird genau so gerendert wie es auf der Nintendo Switch aussieht - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>Hat das Spiel irgendwelche grafischen Störungen?</p></body></html> - - - - Major The game has major audio errors - Schwerwiegend Das Spiel hat schwerwiegende Audio-Fehler - - - - Minor The game has minor audio errors - Kleinere Das Spiel hat kleinere Audio Fehler - - - - None Audio is played perfectly - Keine Audio wird perfekt abgespielt - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>Hat das Spiel irgendwelche Tonstörungen / fehlende Effekte?</p></body></html> - - - - Thank you for your submission! - Vielen Dank für deinen Beitrag! - - - - Submitting - Absenden - - - - Communication error - Kommunikationsfehler - - - - An error occurred while sending the Testcase - Beim Senden des Berichtes ist ein Fehler aufgetreten. - - - - Next - Weiter - - ConfigurationShared @@ -379,7 +237,7 @@ Dies würde deren Forum-Benutzernamen und deren IP-Adresse sperren. Data erase - + Datenlöschung @@ -389,12 +247,12 @@ Dies würde deren Forum-Benutzernamen und deren IP-Adresse sperren. Net connect - + Netzwerkverbindung Player select - + Spielerauswahl @@ -404,52 +262,52 @@ Dies würde deren Forum-Benutzernamen und deren IP-Adresse sperren. Mii Edit - + Mii bearbeiten Online web - + Online-Web Shop - + Geschäft Photo viewer - + Fotobetrachter Offline web - + Offline-Web Login share - + Anmelden Teilen Wifi web auth - + WLAN-Web-Authentifizierung My page - + Meine Seite Enable Overlay Applet - + Überlagerungs-Applet aktivieren Enables Horizon's built-in overlay applet. Press and hold the home button for 1 second to show it. - + Aktiviert das in Horizon integrierte Überlagerungs-Applet. Halte die Home-Taste 1 Sekunde lang gedrückt, um es anzuzeigen. @@ -502,7 +360,8 @@ Dies ist vor allem eine Debug-Option und sollte nicht deaktiviert werden. Increases the amount of emulated RAM. Doesn't affect performance/stability but may allow HD texture mods to load. - + Erhöht die Menge des emulierten Arbeitsspeichers. +Hat keinen Einfluss auf Leistung oder Stabilität, ermöglicht jedoch möglicherweise das Laden von HD-Textur-Mods. @@ -548,7 +407,8 @@ Deaktivieren bedeutet das keine Wiederholratenbegrenzung stattfindet. Synchronizes CPU core speed with the game's maximum rendering speed to boost FPS without affecting game speed (animations, physics, etc.). Can help reduce stuttering at lower framerates. - + Synchronisiert die Taktfrequenz der CPU-Kerne mit der maximalen Rendering-Geschwindigkeit des Spiels, um die BpS zu steigern, ohne die Spielgeschwindigkeit (Animationen, Physik usw.) zu beeinträchtigen. +Kann dazu beitragen, Ruckler bei niedrigeren Bildraten zu reduzieren. @@ -562,399 +422,477 @@ Can help reduce stuttering at lower framerates. - + Backend: Backend: - CPU Overclock - CPU Übertaktung + CPU Clocks + CPU-Takte - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. - Zwingt die emulierte CPU, mit höherer Taktrate zu laufen, wodurch bestimmte FPS-Begrenzungen reduziert werden. Schwächere CPUs haben vielleicht reduzierte Leistung, und manche Spiele haben vielleicht Probleme. -Verwende Boost (1700MHz), um mit der höchsten nativen Taktrate der Switch zu laufen, oder Fast (2000MHz), um mit der doppelten Taktrate zu laufen. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + Erhöht den Takt der emulierten CPU Berichte, was manche FPS-Begrenzer entfernt. Schwächere CPUs haben eventuell schlechtere Leistung, und bestimmte Spiele verhalten vielleicht sich nicht richtig. - + Custom CPU Ticks - + Benutzerdefinierte CPU-Ticks - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. - + Lege einen benutzerdefinierten Wert für die CPU-Ticks fest. Höhere Werte können die Leistung steigern, jedoch auch zu Verklemmungen führen. Es wird ein Bereich von 77 bis 21000 empfohlen. - + Enable Host MMU Emulation (fastmem) Host-MMU-Emulation aktivieren (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. Diese Optimierung beschleunigt Speicherzugriffe durch das Gastprogramm. Wenn aktiviert, erfolgen Speicherlese- und -schreibvorgänge des Gastes direkt im Speicher und nutzen die MMU des Hosts. Das Deaktivieren erzwingt die Verwendung der Software-MMU-Emulation für alle Speicherzugriffe. - + Unfuse FMA (improve performance on CPUs without FMA) Unfuse FMA (erhöht Leistung auf CPUs ohne FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. Diese Option verbessert die Geschwindigkeit, indem die Genauigkeit von "Fused-Multiply-Add"-Anweisungen auf CPUs ohne native FMA-Unterstützung reduziert wird. - + Faster FRSQRTE and FRECPE Schnelleres FRSQRTE und FRECPE - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. Diese Option verbessert die Geschwindigkeit einiger ungenauen Fließkomma-Funktionen, indem weniger genaue native Annäherungen benutzt werden. - + Faster ASIMD instructions (32 bits only) Schnellere ASIMD-Instruktionen (nur 32-Bit) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. Diese Option verbessert die Geschwindigkeit von 32-Bit-ASIMD-Fließkomma-Funktionen, indem diese mit inkorrekten Rundungsmodi ausgeführt werden. - + Inaccurate NaN handling Ungenaue NaN-Verarbeitung - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. - + Diese Option verbessert die Geschwindigkeit durch Wegfall der NaN-Prüfung. +Bitte beachte, dass dies auch die Genauigkeit bestimmter Gleitkommaoperationen verringert. - + Disable address space checks Adressraumprüfungen deaktivieren - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. - + Diese Option verbessert die Geschwindigkeit, indem sie eine Sicherheitsprüfung vor jedem Speicherzugriff überspringt. +Wird sie deaktiviert, kann dies zur Ausführung von beliebigem Code führen. - + Ignore global monitor Globalen Monitor ignorieren - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. - + Diese Option erhöht die Geschwindigkeit, indem sie sich ausschließlich auf die Semantik von `cmpxchg` stützt, um die Sicherheit von Befehlen für exklusiven Zugriff zu gewährleisten. +Bitte beachte, dass dies zu Verklemmungen und anderen Race Conditions führen kann. - + API: API: - + Changes the output graphics API. Vulkan is recommended. - + Ändert die Ausgabe-Grafik-API. +Vulkan wird empfohlen. - + Device: Gerät: - + This setting selects the GPU to use (Vulkan only). Diese Einstellung wählt aus welche Grafikkarte benutzt werden soll (Nur Vulkan). - + Resolution: Auflösung: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. - + Erzwingt das Rendern in einer anderen Auflösung. +Höhere Auflösungen erfordern mehr VRAM und Bandbreite. +Optionen unter 1x können Artefakte verursachen. - + Window Adapting Filter: Bildschirmanpassungsfilter: - + FSR Sharpness: - FSR-Schärfe + FSR-Schärfe: - - Determines how sharpened the image will look using FSR's dynamic contrast. - + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. + Legt fest, wie scharf das Bild mithilfe des dynamischen Kontrasts von FSR oder SGSR dargestellt wird. - + Anti-Aliasing Method: Kantenglättungs-Methode: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. + Die zu verwendende Anti-Aliasing-Methode. +SMAA bietet die beste Bildqualität. +FXAA liefert bei niedrigeren Auflösungen ein stabileres Bild. + + + + Enable post-processing effects + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Vollbild-Modus: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. - + Die Methode, mit der das Fenster im Vollbildmodus dargestellt wird. +Der randlose Modus bietet die beste Kompatibilität mit der Bildschirmtastatur, die manche Spiele für die Eingabe erfordern. +Der exklusive Vollbildmodus bietet möglicherweise eine bessere Leistung und eine bessere Freesync-/G-Sync-Unterstützung. - + Aspect Ratio: Seitenverhältnis: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. - - - - - Use persistent pipeline cache - + Dehnt den Renderer so, dass er dem angegebenen Seitenverhältnis entspricht. +Die meisten Spiele unterstützen nur das Seitenverhältnis 16:9, sodass Anpassungen erforderlich sind, um andere Seitenverhältnisse zu erhalten. +Steuert außerdem das Seitenverhältnis der aufgenommenen Bildschirmfotos. + Use persistent pipeline cache + Persistenten Rohrleitungs-Cache verwenden + + + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. - - - - - Use asynchronous GPU emulation - - - - - Uses an extra CPU thread for rendering. -This option should always remain enabled. - + Ermöglicht das Speichern von Shadern, um sie beim nächsten Spielstart schneller laden zu können. +Die Deaktivierung ist ausschließlich für Fehlerbehebungszwecke vorgesehen. + Use asynchronous GPU emulation + Asynchrone GPU-Emulation verwenden + + + + Uses an extra CPU thread for rendering. +This option should always remain enabled. + Verwendet einen zusätzlichen CPU-Thread für das Rendern. +Diese Option sollte immer aktiviert bleiben. + + + NVDEC emulation: NVDEC-Emulation: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. - + Legt fest, wie Videos decodiert werden sollen. +Dabei kann entweder die CPU oder die GPU für die Dekodierung verwendet werden, oder es findet überhaupt keine Dekodierung statt (schwarzer Bildschirm bei Videos). +In den meisten Fällen bietet die GPU-Dekodierung die beste Leistung. - + ASTC Decoding Method: ASTC-Dekodier-Methode: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). CPU Asynchronously: Use the CPU to decode ASTC textures on demand. EliminatesASTC decoding stuttering but may present artifacts. - + Diese Option legt fest, wie ASTC-Texturen dekodiert werden sollen. +CPU: Die Dekodierung erfolgt über die CPU. +GPU: Die Dekodierung von ASTC-Texturen erfolgt über die Compute-Shader der GPU (empfohlen). +CPU asynchron: Die Dekodierung von ASTC-Texturen erfolgt bei Bedarf über die CPU. Dies verhindert Ruckeln bei der ASTC-Dekodierung, +kann jedoch zu Artefakten führen. - + ASTC Recompression Method: ASTC-Rekompression-Methode: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. - + Die meisten GPUs unterstützen keine ASTC-Texturen und müssen diese daher in ein Zwischenformat dekomprimieren: RGBA8. +BC1/BC3: Das Zwischenformat wird wieder in das BC1- oder BC3-Format komprimiert, +was VRAM spart, jedoch zu Einbußen bei der Bildqualität führt. - + Frame Pacing Mode (Vulkan only) - - - - - Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + Frame-Pacing-Modus (nur Vulkan) + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. + Steuert, wie der Emulator das Frame-Pacing verwaltet, um Ruckeln zu reduzieren und die Bildrate flüssiger und gleichmäßiger zu gestalten. + + + VRAM Usage Mode: VRAM-Nutzungs Modus: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. - + Legt fest, ob der Emulator vorrangig Speicherplatz sparen oder den verfügbaren Grafikspeicher zur Leistungsoptimierung maximal ausnutzen soll. +Der aggressive Modus kann die Leistung anderer Anwendungen, wie z. B. Aufnahmesoftware, beeinträchtigen. - + Skip CPU Inner Invalidation CPU-interne Invalidierung überspringen - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. Überspringt bestimmte Cache-Invalidierungen auf CPU-Seite während Speicherupdates, reduziert die CPU-Auslastung und verbessert die Leistung. Dies verursacht vielleicht Abstürze. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: VSync-Modus: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. Immediate (no synchronization) presents whatever is available and can exhibit tearing. - + FIFO (VSync) lässt keine Frames ausfallen und verursacht kein Tearing, ist jedoch durch die Bildwiederholfrequenz des Bildschirms begrenzt. +FIFO Relaxed lässt Tearing zu, während es sich von einer Verlangsamung erholt. +Postkasten kann eine geringere Latenz als FIFO aufweisen und verursacht kein Tearing, lässt jedoch möglicherweise Bilder ausfallen. +Sofort (ohne Synchronisation) zeigt alles an, was verfügbar ist, und kann Tearing verursachen. - + Sync Memory Operations Speicheroperationen synchronisieren - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. - + Gewährleistet die Datenkonsistenz zwischen Rechen- und Speicheroperationen. +Diese Option behebt Probleme in Spielen, kann jedoch zu Leistungseinbußen führen. +Bei Spielen mit Unreal Engine 4 sind die Auswirkungen oft am deutlichsten spürbar. - + Enable asynchronous presentation (Vulkan only) Aktiviere asynchrone Präsentation (Nur Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. - + Verbessert die Leistung geringfügig, indem die Darstellung auf einen separaten CPU-Thread verlagert wird. - + Force maximum clocks (Vulkan only) Erzwinge Maximale Taktrate (Vulkan only) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. Lässt im Hintergrund die GPU Aufgaben erledigen während diese auf Grafikbefehle wartet, damit diese nicht herunter taktet. - + Anisotropic Filtering: Anisotrope Filterung: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. - + Steuert die Qualität der Texturwiedergabe bei schrägen Blickwinkeln. +Bei den meisten Grafikkarten kann dieser Wert bedenkenlos auf 16x eingestellt werden. - + GPU Mode: GPU-Modus: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - Steuert den GPU-Emulationsmodus. Die meisten Spiele werden im Modus Schnell oder Ausgeglichen gut gerendert, für einige ist jedoch weiterhin der Modus Akkurat erforderlich. Partikel werden in der Regel nur im Modus Akkurat korrekt gerendert. + Steuert den GPU-Emulationsmodus. +Die meisten Spiele werden im Modus Schnell in Ordnung gerendert, für einige ist jedoch weiterhin der Modus Akkurat erforderlich. +Partikel werden in der Regel nur im Modus Akkurat korrekt gerendert. - + DMA Accuracy: DMA-Genauigkeit: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. + Steuert den DMA-Lesemodus. +Unsicher ist schneller, während Sicher stabiler ist und in manchen Spielen Probleme lösen kann. +Standard folgt der GPU-Genauigkeit Einstellung. + + + + GPU Fence Behavior: + GPU-Zaunobjekt-Verhalten: + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. - + Enable asynchronous shader compilation Aktiviere asynchrones Shader-Kompilieren - + May reduce shader stutter. - Reduziert vielleicht Shader stottern. + Kann Shader-Ruckler reduzieren. - - Fast GPU Time - Schnelle GPU-Zeit + + GPU Clocks + GPU-Takte - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. - + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. + Führt dazu, dass das Spiel glaubt, die Arbeit der GPU würde früher fertig sein, als sie eigentlich ist. Dadurch hört das Spiel auf die Auflösung und Render-Distanz zu verringern um sich den Takten der Switch anzupassen. - + GPU Unswizzle GPU-Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + Beschleunigt die Dekodierung von BCn-3D-Texturen mithilfe von GPU-Rechenleistung. +Deaktiviere diese Option, falls Abstürze oder Grafikfehler auftreten. - + GPU Unswizzle Max Texture Size GPU-Unswizzle max. Texturgröße - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. @@ -963,1257 +901,1262 @@ Während die GPU für mittelgroße und große Texturen schneller ist, kann die C Passen Sie diesen Wert an, um das Gleichgewicht zwischen GPU-Beschleunigung und CPU-Overhead zu finden. - + GPU Unswizzle Stream Size GPU-Unswizzle-Streamgröße - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + Legt die maximale Menge an Texturdaten (in MiB) fest, die pro Bild verarbeitet wird. +Höhere Werte können Ruckeln beim Laden von Texturen verringern, können sich jedoch auf die Bildrate auswirken. - + GPU Unswizzle Chunk Size GPU-Unswizzle Chunk-Größe - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Legt die Anzahl der Tiefenschnitte fest, die in einem einzelnen Durchlauf verarbeitet werden. +Eine Erhöhung dieses Werts kann den Durchsatz auf High-End-GPUs verbessern, bei schwächerer Hardware jedoch zu TDR- oder Treiber-Auszeiten führen. - + Use Vulkan pipeline cache Vulkan-Pipeline-Cache verwenden - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. - + Aktiviert den herstellerspezifischen Pipeline-Cache der GPU. +Diese Option kann die Ladezeit von Shadern erheblich verkürzen, wenn der Vulkan-Treiber die Pipeline-Cache-Dateien nicht intern speichert. - + Enable Compute Pipelines (Intel Vulkan Only) Aktiviere Compute-Pipelines (Nur Intel Vulkan) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. Wird von einigen Spielen benötigt. -Diese Einstellung ist nur für proprietäre Intel-Treiber und kann bei Aktivierung zu Abstürzen führen. -Bei allen anderen Treibern sind Compute-Pipelines immer aktiviert +Diese Einstellung ist nur bei proprietären Intel-Treibern vorhanden und kann bei Aktivierung zu einem Absturz führen. +Bei allen anderen Treibern sind die Rechen-Rohrleitungen immer aktiviert. - + Enable Reactive Flushing Aktiviere Reactives Flushing - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. Benutzt Reactive-Flushing anstatt Predictive-Flushing, welches akkurateres Speicher-Synchronisieren erlaubt. - + Sync to framerate of video playback Synchronisiere mit Bildrate von Video-Wiedergaben - + Run the game at normal speed during video playback, even when the framerate is unlocked. - Lasse das Spiel in der normalen Geschwindigkeit abspielen, trotz freigeschalteter Bildrate (FPS) + Lasse das Spiel in der normalen Geschwindigkeit abspielen, trotz freigeschalteter Bildrate - + Barrier feedback loops - Barrier-Feedback-Loops + Barrier-Feedback-Schleifen - + Improves rendering of transparency effects in specific games. Verbessert das Rendering von Transparenzeffekten in bestimmten Spielen. - + Enable buffer history - Aktiviere Puffer Verlauf + Puffer-Verlauf aktivieren - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Ermöglicht den Zugriff auf frühere Pufferzustände. +Diese Option kann in manchen Spielen die Darstellungsqualität und die Leistungsstabilität verbessern. - + Fix bloom effects - Behebt Boomeffekte + Bloom-Effekte beheben - + Removes bloom in Burnout. Entfernt Bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State - - - - - Controls the number of features that can be used in Extended Dynamic State. -Higher states allow for more features and can increase performance, but may cause additional graphical issues. - + Erweiterter dynamischer Zustand - Vertex Input Dynamic State - - - - - Enables vertex input dynamic state feature for better quality and performance. - + Controls the number of features that can be used in Extended Dynamic State. +Higher states allow for more features and can increase performance, but may cause additional graphical issues. + Legt die Anzahl der Funktionen fest, die im erweiterten dynamischen Modus verwendet werden können. +Höhere Stufen ermöglichen mehr Funktionen und können die Leistung steigern, können jedoch zusätzliche grafische Probleme verursachen. - Sample Shading - + Vertex Input Dynamic State + Dynamischer Zustand der Scheitelpunkteingabe + Enables vertex input dynamic state feature for better quality and performance. + Aktiviert die Funktion für den dynamischen Status der Scheitelpunkteingabe, um eine bessere Qualität und Leistung zu erzielen. + + + + Sample Shading + Probe-Schattierung + + + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. - + Ermöglicht es dem Fragment-Shader, pro Abtastwert in einem mehrfach abgetasteten Fragment ausgeführt zu werden, anstatt nur einmal pro Fragment. Verbessert die Grafikqualität auf Kosten der Leistung. +Höhere Werte verbessern die Qualität, beeinträchtigen jedoch die Leistung. - + RNG Seed - RNG-Seed + Startwert für den Zufallszahlengenerator - + Controls the seed of the random number generator. Mainly used for speedrunning. - + Steuert den Startwert des Zufallszahlengenerators. +Wird hauptsächlich für Schnelldurchläufe verwendet. - + Device Name Gerätename - + The name of the console. Der Name der Konsole. - + + Homebrew Args + Homebrew-Argumente + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + Befehlszeilenargumente, die beim Start an Homebrew übergeben werden (z. B. -noglsl). + + + Custom RTC Date: Benutzerdefinierte Echtzeituhrdatum: - + This option allows to change the clock of the console. Can be used to manipulate time in games. Diese Option erlaubt die Änderung der Uhr der Konsole. Kann benutzt werden um Zeit in Spielen zu manipulieren. - + The number of seconds from the current unix time - + Die Anzahl der Sekunden seit dem aktuellen Unix-Zeitstempel - + Language: Sprache: - + This option can be overridden when region setting is auto-select - Diese Einstellung kann überschrieben werden, falls deine Region auf Automatisch eingestellt ist. + Diese Option kann überschrieben werden, wenn die Regionseinstellung auf „Automatische Auswahl“ steht - + Region: Region: - + The region of the console. Die Region der Konsole. - + Time Zone: Zeitzone: - + The time zone of the console. Die Zeitzone der Konsole. - + Sound Output Mode: Tonausgangsmodus: - + Console Mode: Konsolenmodus: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. - + Legt fest, ob sich die Konsole im Dock- oder Handgerät-Modus befindet. +Je nach dieser Einstellung ändern sich bei Spielen die Auflösung, die Detailstufe und die unterstützten Controller. +Die Einstellung auf „Handgerät“ kann dazu beitragen, die Leistung auf Systemen mit geringerer Leistung zu verbessern. - + Prompt for user profile on boot - Beim Start nach Nutzer fragen + Beim Systemstart nach dem Benutzerprofil fragen - + Useful if multiple people use the same PC. - Nützlich falls mehrere Personen den gleichen Computer benutzen. + Nützlich, wenn mehrere Personen denselben PC nutzen. - + Pause when not in focus - Pausiere falls nicht im Fokus + Pausieren, wenn nicht im Fokus - + Pauses emulation when focusing on other windows. - Pausiere Emulation falls andere Fenster im Fokus/Vordergrund sind. + Pausiert die Emulation, wenn der Fokus auf andere Fenster wechselt. - + Confirm before stopping emulation Vor dem Stoppen der Emulation bestätigen - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. - - - - - Hide mouse on inactivity - Mauszeiger verstecken - - - - Hides the mouse after 2.5s of inactivity. - Den Mauszeiger nach 2,5 Sekunden Inaktivität verstecken. - - - - Disable controller applet - Deaktiviere Controller-Applet - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - - - - - Check for updates - Auf Updates überprüfen + Überschreibt die Abfragen zur Bestätigung des Beendens der Emulation. +Wenn diese Option aktiviert ist, werden solche Abfragen umgangen und die Emulation wird direkt beendet. - Whether or not to check for updates upon startup. - Ob nach Updates während des Startens gesucht werden soll. + Hide mouse on inactivity + Mauszeiger verbergen + + + + Hides the mouse after 2.5s of inactivity. + Verbirgt den Mauszeiger nach 2,5 Sekunden Inaktivität. + + + + Check for updates + Auf Aktualisierungen überprüfen + Whether or not to check for updates upon startup. + Ob nach Aktualisierungen während des Startens gesucht werden soll. + + + Enable Gamemode - GameMode aktivieren + Spielmodus aktivieren - + Force X11 as Graphics Backend - + X11 als Grafik-Backend erzwingen - + Custom frontend - Benutzerdefinierte Frontend + Benutzerdefiniertes Frontend - + Real applet Echtes Applet - + + + Disabled + Deaktiviert + + + Never Niemals - + On Load Beim Laden - + Always Immer - + CPU CPU - + GPU GPU - + CPU Asynchronous CPU Asynchron - + Uncompressed (Best quality) Unkomprimiert (Beste Qualität) - + BC1 (Low quality) BC1 (Niedrige Qualität) - + BC3 (Medium quality) BC3 (Mittlere Qualität) - - + + Auto Auto - + 30 FPS - 30 FPS + 30 BpS - + 60 FPS - 60 FPS + 60 BpS - + 90 FPS - 90 FPS - - - - 120 FPS - 120 FPS + 90 BpS + 120 FPS + 120 BpS + + + Conservative Konservativ - + Aggressive Aggressiv - + Vulkan Vulkan - - - OpenGL GLSL - - - - - OpenGL GLASM (Assembly Shaders, NVIDIA Only) - - - - - OpenGL SPIR-V (Experimental, AMD/Mesa Only) - - + OpenGL GLSL + OpenGL GLSL + + + + OpenGL GLASM (Assembly Shaders, NVIDIA Only) + OpenGL GLASM (Assembler-Shader, nur NVIDIA) + + + + OpenGL SPIR-V (Experimental, AMD/Mesa Only) + OpenGL SPIR-V (experimentell, nur AMD/Mesa) + + + Null Null - + Fast Schnell - - Balanced - Ausgeglichen - - - - + + + Accurate Akkurat - - + + + Default - Standard + Vorgabe - + Unsafe (fast) Unsicher (schnell) - + Safe (stable) Sicher (stabil) - + + Immediate + Direkt + + + + Balanced + Ausgeglichen + + + Unsafe Unsicher - + Paranoid (disables most optimizations) Paranoid (deaktiviert die meisten Optimierungen) - + Debugging - Debugging + Fehlerbehebung - + Dynarmic Dynarmic - + NCE NCE - + Borderless Windowed Rahmenloses Fenster - + Exclusive Fullscreen Exklusiver Vollbildmodus - + No Video Output Keine Videoausgabe - + CPU Video Decoding CPU Video Dekodierung - + GPU Video Decoding (Default) GPU Video Dekodierung (Standard) - + 0.25X (180p/270p) [EXPERIMENTAL] 0.25X (180p/270p) [EXPERIMENTELL] - + 0.5X (360p/540p) [EXPERIMENTAL] 0,5X (360p/540p) [EXPERIMENTELL] - + 0.75X (540p/810p) [EXPERIMENTAL] 0,75X (540p/810p) [EXPERIMENTELL] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] 1.25X (900p/1350p) [EXPERIMENTELL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1,5X (1080p/1620p) [EXPERIMENTELL] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor Nearest-Neighbor - + Bilinear Bilinear - + Bicubic Bikubisch - + Gaussian Gaussian - + Lanczos - + Lanczos - + ScaleForce ScaleForce - - - AMD FidelityFX Super Resolution - - - - - Area - - - - - MMPX - - - - - Zero-Tangent - - - - - B-Spline - - - - - Mitchell - - - - - Spline-1 - - - + AMD FidelityFX Super Resolution + AMD FidelityFX Super-Auflösung + + + + Area + Bereich + + + + MMPX + MMPX + + + + Zero-Tangent + Nulltangente + + + + B-Spline + B-Spline + + + + Mitchell + Mitchell + + + + Spline-1 + Spline-1 + + + + Snapdragon Game Super Resolution + Snapdragon Spiel-Superauflösung + + + + Snapdragon Game Super Resolution EdgeDir + Snapdragon Spiel-Superauflösung EdgeDir + + + None Keiner - + FXAA FXAA - + SMAA SMAA - + Default (16:9) - Standard (16:9) + Vorgabe (16:9) - + Force 4:3 - Erzwinge 4:3 + 4:3 erzwingen - + Force 21:9 - Erzwinge 21:9 + 21:9 erzwingen - + Force 16:10 - Erzwinge 16:10 + 16:10 erzwingen - + Stretch to Window Auf Fenster anpassen - + Automatic Automatisch - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - - - - - 64x - - - - + Japanese (日本語) Japanisch (日本語) - + American English Amerikanisches Englisch - + French (français) Französisch (français) - + German (Deutsch) Deutsch (German) - + Italian (italiano) Italienisch (italiano) - + Spanish (español) Spanisch (español) - + Chinese Chinesisch - + Korean (한국어) Koreanisch (한국어) - + Dutch (Nederlands) Niederländisch (Nederlands) - + Portuguese (português) Portugiesisch (português) - + Russian (Русский) Russisch (Русский) - + Taiwanese Taiwanesisch - + British English Britisches Englisch - + Canadian French Kanadisches Französisch - + Latin American Spanish Lateinamerikanisches Spanisch - + Simplified Chinese Vereinfachtes Chinesisch - + Traditional Chinese (正體中文) Traditionelles Chinesisch (正體中文) - + Brazilian Portuguese (português do Brasil) Brasilianisches Portugiesisch (português do Brasil) - + Polish (polska) - + Polnisch (Polen) - + Thai (แบบไทย) - + Thailändisch (Thailändische Version) - - + + Japan Japan - + USA USA - + Europe Europa - + Australia Australien - + China China - + Korea Korea - + Taiwan Taiwan - + Auto (%1) Auto select time zone Automatisch (%1) - + Default (%1) Default time zone Standard (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Kuba - + EET EET - + Egypt Ägypten - + Eire Eire - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Eire - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hongkong - + HST HST - + Iceland Island - + Iran Iran - + Israel Israel - + Jamaica Jamaika - + Kwajalein Kwajalein - + Libya Libyen - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navajo - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Polen - + Portugal Portugal - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Singapur - + Turkey Türkei - + UCT UCT - + Universal Universal - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulu - + Mono Mono - + Stereo Stereo - + Surround Surround - + 4GB DRAM (Default) - 4GB DRAM (Standard) + 4GB DRAM (Vorgabe) - + 6GB DRAM (Unsafe) 6GB DRAM (Unsicher) - + 8GB DRAM 8GB DRAM - + 10GB DRAM (Unsafe) 10GB DRAM (Unsicher) - + 12GB DRAM (Unsafe) 12GB DRAM (Unsicher) - + Docked - Im Dock + Angedockt - + Handheld - Handheld + Handgerät - - - Off - Aus + + + Normal + Normal - - Boost (1700MHz) - Boost (1700MHz) + + + Boost + Beschleunigung - - Fast (2000MHz) - Schnell (2000MHz) + + + Overclock + Übertaktung - + Always ask (Default) - Immer fragen (Standard) + Immer fragen (Vorgabe) - + Only if game specifies not to stop Nur wenn ein Spiel vorgibt, nicht zu stoppen - + Never ask Niemals fragen - - - Medium (256) - Mittel (256) - - - - - High (512) - Hoch (512) - - - + Very Small (16 MB) Sehr klein (16 MB) - + Small (32 MB) Klein (32 MB) - + Normal (128 MB) Normal (128 MB) - + Large (256 MB) Groß (256 MB) - + Very Large (512 MB) Sehr groß (512 MB) - + Very Low (4 MB) Sehr niedrig (4 MB) - + Low (8 MB) Niedrig (8 MB) - + Normal (16 MB) Normal (16 MB) - + Medium (32 MB) Mittel (32 MB) - + High (64 MB) Hoch (64 MB) - + Very Low (32) Sehr niedrig (32) - + Low (64) Niedrig (64) - + Normal (128) Normal (128) - - - Disabled - Deaktiviert - - - - ExtendedDynamicState 1 - - - - - ExtendedDynamicState 2 - - + Medium (256) + Mittel (256) + + + + High (512) + Hoch (512) + + + + ExtendedDynamicState 1 + ErweiterterDynamischerZustand 1 + + + + ExtendedDynamicState 2 + ErweiterterDynamischerZustand 2 + + + ExtendedDynamicState 3 - + ErweiterterDynamischerZustand 3 - + Tree View - Baum Ansicht + Baumansicht - + Grid View - Raster Ansicht + Rasteransicht @@ -2231,7 +2174,7 @@ When a program attempts to open the controller applet, it is immediately closed. Applet mode preference - + Einstellung für den Applet-Modus @@ -2258,7 +2201,7 @@ When a program attempts to open the controller applet, it is immediately closed. Camera Image Source: - Kamera Bildquelle: + Bildquelle der Kamera: @@ -2283,7 +2226,7 @@ When a program attempts to open the controller applet, it is immediately closed. Restore Defaults - Standardwerte wiederherstellen + Vorgabewerte wiederherstellen @@ -2311,7 +2254,7 @@ When a program attempts to open the controller applet, it is immediately closed. We recommend setting accuracy to "Auto". - Wir empfehlen die Genauigkeit auf "Auto" zu setzen. + Wir empfehlen, die Genauigkeit auf „Autom.“ einzustellen. @@ -2367,7 +2310,7 @@ When a program attempts to open the controller applet, it is immediately closed. Enable inline page tables - Enable inline page tables + Inline-Seitentabellen aktivieren @@ -2375,13 +2318,13 @@ When a program attempts to open the controller applet, it is immediately closed. <div>This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.</div> - <div>This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.</div> + <div>Diese Optimierung vermeidet Disponent-Nachschlagungen, indem sie es den ausgegebenen Basisblöcken ermöglicht, direkt zu anderen Basisblöcken zu springen, wenn der Ziel-PC statisch ist.</div> Enable block linking - Enable block linking + Blockverknüpfung aktivieren @@ -2389,13 +2332,13 @@ When a program attempts to open the controller applet, it is immediately closed. <div>This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.</div> - <div>This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.</div> + <div>Diese Optimierung vermeidet Disponent-Nachschlagungen, indem sie potenzielle Rücksprungadressen von BL-Befehlen nachverfolgt. Dies entspricht in etwa dem, was bei einem Rücksprungstapelpuffer auf einer echten CPU geschieht.</div> Enable return stack buffer - Return stack buffer aktivieren + Rückgabestapelpuffer aktivieren @@ -2403,13 +2346,13 @@ When a program attempts to open the controller applet, it is immediately closed. <div>Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.</div> - <div>Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.</div> + <div>Ein zweistufiges Dispositionssystem aktivieren. Zunächst wird ein schnellerer, in Assembler geschriebener Dispatcher mit einem kleinen MRU-Cache für Sprungziele verwendet. Wenn dieser versagt, greift das Dispositionssystem auf den langsameren C++-Disponenten zurück.</div> Enable fast dispatcher - Enable fast dispatcher + Schnellen Disponenten aktivieren @@ -2417,13 +2360,13 @@ When a program attempts to open the controller applet, it is immediately closed. <div>Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.</div> - <div>Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.</div> + <div>Aktiviert eine IR-Optimierung, die unnötige Zugriffe auf die CPU-Kontextstruktur reduziert.</div> Enable context elimination - Enable context elimination + Kontexteliminierung aktivieren @@ -2431,13 +2374,13 @@ When a program attempts to open the controller applet, it is immediately closed. <div>Enables IR optimizations that involve constant propagation.</div> - <div>Enables IR optimizations that involve constant propagation.</div> + <div>Aktiviert IR-Optimierungen, die die Konstantenausbreitung beinhalten.</div> Enable constant propagation - Enable constant propagation + Konstanten-Propagation aktivieren @@ -2445,13 +2388,13 @@ When a program attempts to open the controller applet, it is immediately closed. <div>Enables miscellaneous IR optimizations.</div> - <div>Enables miscellaneous IR optimizations.</div> + <div>Aktiviert verschiedene IR-Optimierungen.</div> Enable miscellaneous optimizations - Enable miscellaneous optimizations + Verschiedene Optimierungen aktivieren @@ -2460,14 +2403,14 @@ When a program attempts to open the controller applet, it is immediately closed. <div style="white-space: nowrap">When disabled, a misalignment is triggered on all misaligned accesses.</div> - <div style="white-space: nowrap">When enabled, a misalignment is only triggered when an access crosses a page boundary.</div> - <div style="white-space: nowrap">When disabled, a misalignment is triggered on all misaligned accesses.</div> + <div style="white-space: nowrap">Bei Aktivierung wird eine Fehlausrichtung nur ausgelöst, wenn ein Zugriff eine Seitengrenze überschreitet.</div><\div> + <div style="white-space: nowrap">Bei Deaktivierung wird eine Fehlausrichtung bei allen fehlausgerichteten Zugriffen ausgelöst.</div> Enable misalignment check reduction - Enable misalignment check reduction + Reduzierung der Fehlausrichtungsprüfung aktivieren @@ -2477,13 +2420,15 @@ When a program attempts to open the controller applet, it is immediately closed. <div style="white-space: nowrap">Disabling this forces all memory accesses to use Software MMU Emulation.</div> - <div style="white-space: nowrap">Diese Optimierung verschnellert Zugriff auf den Speicher durch ein Gast programm.</div> - <div style="white-space: nowrap"> + <div style="white-space: nowrap">Diese Optimierung beschleunigt die Speicherzugriffe durch das Gastprogramm.</div> + <div style="white-space: nowrap">Wenn diese Option aktiviert ist, erfolgen Lese- und Schreibvorgänge im Gastspeicher direkt im Speicher und nutzen die MMU des Hosts.</div> + <div style="white-space: nowrap">Wenn Sie diese Option deaktivieren, werden alle Speicherzugriffe zwingend über die Software-MMU-Emulation abgewickelt.</div> + Enable Host MMU Emulation (general memory instructions) - Aktiviert Host MMU Emulation (Generale Speicher Anweisung) + Host-MMU-Emulation aktivieren (allgemeine Speicherinstruktionen) @@ -2493,15 +2438,15 @@ When a program attempts to open the controller applet, it is immediately closed. <div style="white-space: nowrap">Disabling this forces all exclusive memory accesses to use Software MMU Emulation.</div> -<div style="white-space: nowrap"> Diese Optimierung beschleunigt exklusive Speicherzugriffe durch das Gastprogramm.</div> -<div style="white-space: nowrap"> Die Aktivierung führt dazu, dass gastexklusive Speicherlese- und -schreibvorgänge direkt im Speicher erfolgen und die MMU des Hosts verwendet wird.</div> -<div style="white-space: nowrap"> Die Deaktivierung dieser Funktion zwingt alle exklusiven Speicherzugriffe zur Verwendung der Software-MMU-Emulation.</div> - + <div style="white-space: nowrap">Diese Optimierung beschleunigt exklusive Speicherzugriffe durch das Gastprogramm.</div> + <div style="white-space: nowrap">Wenn diese Option aktiviert ist, werden exklusive Lese- und Schreibzugriffe des Gastes direkt im Speicher ausgeführt und nutzen die MMU des Hosts.</div> + <div style="white-space: nowrap">Durch Deaktivieren dieser Option werden alle exklusiven Speicherzugriffe gezwungen, die Software-MMU-Emulation zu verwenden.</div> + Enable Host MMU Emulation (exclusive memory instructions) - Aktiviere Host MMU Emulation (exlusive memory instructions). + Host-MMU-Emulation aktivieren (exklusive Speicherinstruktionen) @@ -2510,14 +2455,14 @@ When a program attempts to open the controller applet, it is immediately closed. <div style="white-space: nowrap">Enabling it reduces the overhead of fastmem failure of exclusive memory accesses.</div> -<div style="white-space: nowrap"> Diese Optimierung beschleunigt exklusive Speicherzugriffe durch das Gastprogramm.</div> -<div style="white-space: nowrap"> Durch die Aktivierung wird der Overhead von Fastmem-Fehlern bei exklusiven Speicherzugriffen reduziert.</div> - + <div style="white-space: nowrap">Diese Optimierung beschleunigt exklusive Speicherzugriffe durch das Gastprogramm.</div> + <div style="white-space: nowrap">Durch ihre Aktivierung wird der Overhead bei Fastmem-Fehlern bei exklusiven Speicherzugriffen reduziert.</div> + Enable recompilation of exclusive memory instructions - Neukompilierung von Anweisungen mit exklusivem Speicher aktivieren + Neukompilierung exklusiver Speicherinstruktionen aktivieren @@ -2526,14 +2471,14 @@ When a program attempts to open the controller applet, it is immediately closed. <div style="white-space: nowrap">Enabling it reduces the overhead of all memory accesses and has no impact on programs that don't access invalid memory.</div> -<div style="white-space: nowrap"> Diese Optimierung beschleunigt die Speicherzugriffe, indem sie ungültige Speicherzugriffe zulässt.</div> -<div style="white-space: nowrap"> Die Aktivierung reduziert den Overhead aller Speicherzugriffe und hat keine Auswirkungen auf Programme, die nicht auf ungültigen Speicher zugreifen.</div> - + <div style="white-space: nowrap">Diese Optimierung beschleunigt Speicherzugriffe, indem sie ungültige Speicherzugriffe zulässt.</div> + <div style="white-space: nowrap">Die Aktivierung dieser Option verringert den Overhead aller Speicherzugriffe und hat keine Auswirkungen auf Programme, die nicht auf ungültigen Speicher zugreifen.</div> + Enable fallbacks for invalid memory accesses - Fallbacks für ungültige Speicherzugriffe einschalten + Rückfälle für ungültige Speicherzugriffe aktivieren @@ -2546,7 +2491,7 @@ When a program attempts to open the controller applet, it is immediately closed. Debugger - Debugger + Fehlerbeheber @@ -2561,262 +2506,307 @@ When a program attempts to open the controller applet, it is immediately closed. Logging - Logging + Protokollierung Global Log Filter - Globaler Log-Filter + Globaler Protokoll-Filter - - When checked, the max size of the log increases from 100 MB to 1 GB - Wenn diese Option aktiviert ist, erhöht sich die maximale Größe des Logs von 100 MB auf 1 GB + + GPU Logging/Level + GPU-Protokollierung/Stufe - - Enable Extended Logging** - Erweitertes Logging aktivieren** - - - - Show Log in Console - Log in der Konsole zeigen - - - - Open Log Location - Log-Verzeichnis öffnen + + Detail level for GPU logs. Off disables logging entirely. + Detaillierungsgrad für GPU-Protokolle. „Aus“ deaktiviert die Protokollierung vollständig. + Off + Aus + + + + Errors + Fehler + + + + Standard + Standard + + + + Verbose + Ausführlich + + + + All + Alle + + + + Show Log in Console + Protokoll in der Konsole zeigen + + + + When checked, the max size of the log increases from 100 MB to 1 GB + Wenn diese Option aktiviert ist, erhöht sich die maximale Größe des Protokolls von 100 MB auf 1 GB + + + + Enable Extended Logging** + Erweiterte Protokollierung aktivieren** + + + + Open Log Location + Protokoll-Verzeichnis öffnen + + + Homebrew Homebrew - + Arguments String - String-Argumente + Argument-Zeichenkette - + Graphics Grafik - + When checked, it executes shaders without loop logic changes - Wenn diese Option aktiviert ist, werden Shader ohne Änderungen der looplogik ausgeführt. + Wenn aktiviert, führt es Shader ohne Änderungen an der Schleifenlogik aus - + Disable Loop safety checks - Loop-safety-checks deaktivieren + Sicherheitsprüfungen für Schleifen deaktivieren - + When checked, it will dump all the macro programs of the GPU Wenn diese Option aktiviert ist, werden alle Makroprogramme der GPU gedumpt - + Dump Maxwell Macros - Maxwell-Macros dumpen + Maxwell-Macros auslesen - + When checked, it enables Nsight Aftermath crash dumps - Wenn diese Option aktiviert ist, werden Nsight Aftermath-Crash-Dumps zugelassen. + Wenn aktiviert, werden Nsight.-Nachwirkungsabsturz-Speicherauszüge aktiviert - + Enable Nsight Aftermath Nsight Aftermath aktivieren - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Wenn diese Option aktiviert ist, werden alle Original-Assembler-Shader aus dem Festplatten-Shader-Cache oder welche von dem Spiel gefunden gefunden gedumpt. + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + Wenn aktiviert, wird der ursprüngliche Maxwell-Guest-Shader-Bytecode (die Eingabe für den Rekompilierer) als .ash-Dateien unter DumpDir/shaders/ ausgelesen. Dies ist nützlich für die Analyse mit nvdisasm. - - Dump Game Shaders - Spiele-Shader dumpen + + Dump Guest (Maxwell) Shaders + Guest-Shader (Maxwell) auslesen - + Enable Renderdoc Hotkey - Renderdoc-Hotkey aktivieren + Renderdoc-Tastenkürzel aktivieren - + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower - Diese Option deaktiviert den Macro-JIT-Compiler. Dies wird die Geschwindigkeit verringern. + Wenn aktiviert, wird der Gerade-noch-rechtzeitig-Kompilierer (JIT) für Makros deaktiviert. Dies führt dazu, dass Spiele langsamer laufen. - + Disable Macro JIT Macro-JIT deaktivieren - + When checked, it disables the macro HLE functions. Enabling this makes games run slower - When checked, it disables the macro HLE functions. Enabling this makes games run slower + Wenn aktiviert, werden die HLE-Funktionen des Makros deaktiviert. Durch die Aktivierung dieser Option laufen Spiele langsamer. - + Disable Macro HLE Deaktiviert Macro-HLE - + When checked, the graphics API enters a slower debugging mode - Wenn diese Option aktiviert ist, wechselt die Grafik-API in einen langsameren Debug-Modus + Wenn diese Option aktiviert ist, wechselt die Grafik-API in einen langsameren Fehlerbehebungs-Modus - + Enable Graphics Debugging - Grafik-Debugging aktivieren + Grafik-Fehlerbehebung aktivieren - + When checked, yuzu will log statistics about the compiled pipeline cache - Wenn ausgewählt wird yuzu Log Statistiken über den kompilierte Pipeline Chache sammeln. + Wenn aktiviert, protokolliert Yuzu Statistiken zum kompilierten Rohrleitungs-Cache - + Enable Shader Feedback - Shader-Feedback aktivieren + Shader-Rückmeldung aktivieren - + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> <html><head/><body><p>Wenn aktiviert, wird das Umsortieren von Speicheruploads deaktiviert, was es ermöglicht, Uploads mit bestimmten Draws zu verknüpfen. Kann in einigen Fällen die Leistung beeinträchtigen.</p></body></html> - + Disable Buffer Reorder - Deaktiviere Buffer-Reorder + Puffer-Neuanordnung deaktivieren - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + Wenn aktiviert, werden die vom Rekompilierer ausgegebenen SPIR-V-Binärdateien (.spv) unter LogDir/shaders/ ausgelesen. Du kannst diese mit SPIRV-Werkzeuge (spirv-dis / spirv-cross / spirv-val) oder RenderDoc untersuchen. + + + + Dump SPIR-V Shaders + SPIR-V-Shader auslesen + + + Advanced Erweitert - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Ermöglicht es yuzu, beim Programmstart nach einer funktionierenden Vulkan-Umgebung zu suchen. Deaktivieren Sie dies, wenn dies zu Problemen mit externen Programmen führt, die yuzu sehen. - + Perform Startup Vulkan Check Vulkan-Prüfung beim Start durchführen - + Disable Web Applet - Deaktiviere die Web Applikation + Web-Applet deaktivieren - + Enable All Controller Types - Aktiviere alle Arten von Controllern + Alle Controller-Typen aktivieren - + Enable Auto-Stub - + „Auto-Stub“ aktivieren - + Kiosk (Quest) Mode Kiosk(Quest)-Modus - - - Use dev.keys - - - - - Enable Debug Asserts - aktiviere Debug-Meldungen - - - - Debugging - Debugging - - - - Battery Serial: - - - Bitmask for quick development toggles - + Use dev.keys + dev.keys verwenden - - Set debug knobs (bitmask) - + + Enable Debug Asserts + Fehlerbehebungs-Meldungen aktivieren - - 16-bit debug knob set for quick development toggles - + + Debugging + Fehlerbehebung - - (bitmask) - - - - - Debug Knobs: - - - - - Unit Serial: - - - - - Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. - Aktivieren Sie diese Option, um den zuletzt generierten Audio-Log auf der Konsole auszugeben. Betrifft nur Spiele, die den Audio-Renderer verwenden. - - - - Dump Audio Commands To Console** - Audio-Befehle auf die Konsole als Dump abspeichern** - - - - Flush log output on each line - - - - - Enable FS Access Log - FS-Zugriffslog aktivieren - - - - Enable Verbose Reporting Services** - Ausführliche Berichtsdienste aktivieren** + + Battery Serial: + Batterie-Seriennummer: - Censor username in logs - + Debug Knobs: + Fehlerbehebungsregler: - + + Bitmask for quick development toggles + Bitmaske für schnelle Entwicklungs-Umschalter + + + + Set debug knobs (bitmask) + Fehlerbehebungs-Regler einstellen (Bitmaske) + + + + 16-bit debug knob set for quick development toggles + 16-Bit-Fehlerbehebungs-Regler für schnelle Entwicklungs-Umschalter + + + + (bitmask) + (Bitmaske) + + + + Unit Serial: + Seriennummer des Geräts: + + + + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. + Aktiviere diese Option, um das zuletzt generierte Audio-Protokoll auf der Konsole auszugeben. Betrifft nur Spiele, die den Audio-Renderer verwenden. + + + + Dump Audio Commands To Console** + Audiobefehle in die Konsole als Speicherauszug ausgeben** + + + + Flush log output on each line + Protokollausgabe nach jeder Zeile leeren + + + + Enable FS Access Log + FS-Zugriffsprotokoll aktivieren + + + + Enable Verbose Reporting Services** + Ausführliche Berichtsdienste aktivieren** + + + + Censor username in logs + Benutzernamen in Protokollen zensieren + + + **This will be reset automatically when Eden closes. - + **Dies wird automatisch zurückgesetzt, wenn Eden geschlossen wird. @@ -2824,7 +2814,7 @@ When a program attempts to open the controller applet, it is immediately closed. Configure Debug Controller - Debug-Controller einrichten + Fehlerbehebungs-Controller konfigurieren @@ -2834,7 +2824,7 @@ When a program attempts to open the controller applet, it is immediately closed. Defaults - Standardwerte + Vorgabewerte @@ -2848,7 +2838,7 @@ When a program attempts to open the controller applet, it is immediately closed. Debug - Debug + Fehlerbehebung @@ -2861,7 +2851,7 @@ When a program attempts to open the controller applet, it is immediately closed. Eden Configuration - Eden Konfiguration + Eden-Konfiguration @@ -2888,7 +2878,7 @@ When a program attempts to open the controller applet, it is immediately closed. Debug - Debug + Fehlerbehebung @@ -2910,7 +2900,7 @@ When a program attempts to open the controller applet, it is immediately closed. GraphicsAdvanced - GraphicsAdvanced + GraphikFortgeschritten @@ -2920,7 +2910,7 @@ When a program attempts to open the controller applet, it is immediately closed. Hotkeys - Hotkeys + Tastenkürzel @@ -3000,7 +2990,7 @@ When a program attempts to open the controller applet, it is immediately closed. Gamecard - Gamecard + Spielkarte @@ -3020,17 +3010,17 @@ When a program attempts to open the controller applet, it is immediately closed. Patch Manager - Patchmanager + Patch-Verwalter Dump Decompressed NSOs - Dekomprimierte NSOs dumpen + Dekomprimierte NSOs auslesen Dump ExeFS - ExeFS dumpen + ExeFS auslesen @@ -3040,17 +3030,17 @@ When a program attempts to open the controller applet, it is immediately closed. Dump Root - Root dumpen + Root auslesen Caching - Caching + Zwischenspeicherung Cache Game List Metadata - Metadaten der Spieleliste cachen + Metadaten der Spieleliste zwischenspeichern @@ -3076,7 +3066,7 @@ When a program attempts to open the controller applet, it is immediately closed. Select Gamecard Path... - Gamecard-Pfad auswählen... + Spielkarten-Pfad auswählen... @@ -3096,17 +3086,17 @@ When a program attempts to open the controller applet, it is immediately closed. Choose an action for the save data directory: - + Wähle eine Aktion für das Speicherdatenverzeichnis: Set Custom Path - Lege einen Benutzerdefinierter Pfad fest + Benutzerdefinierten Pfad festlegen Reset to NAND - + Auf NAND zurücksetzen @@ -3117,7 +3107,13 @@ New: %2 Would you like to migrate saves from the old location? WARNING: This will overwrite any conflicting saves in the new location! - + Speicherdaten sind sowohl am alten als auch am neuen Speicherort vorhanden. + +Alt: %1 +Neu: %2 + +Möchtest du die Speicherdaten vom alten Speicherort übertragen? +WARNUNG: Dadurch werden alle Speicherdaten am neuen Speicherort überschrieben, die mit den vorhandenen Daten in Konflikt stehen! @@ -3125,7 +3121,10 @@ WARNING: This will overwrite any conflicting saves in the new location! From: %1 To: %2 - + Möchtest du deine Speicherdaten an den neuen Speicherort übertragen? + +Von: %1 +Nach: %2 @@ -3151,7 +3150,7 @@ To: %2 Failed to create destination directory. - + Zielverzeichnis konnte nicht erstellt werden. @@ -3170,7 +3169,9 @@ To: %2 Save data has been migrated successfully. Would you like to delete the old save data? - + Die Speicherdaten wurden erfolgreich übertragen. + +Möchtest du die alten Speicherdaten löschen? @@ -3194,7 +3195,7 @@ Would you like to delete the old save data? Add directories to scan for DLCs and Updates without installing to NAND - + Verzeichnisse hinzufügen, in denen nach DLCs und Aktualisierungen gesucht werden soll, ohne diese auf dem NAND zu installieren @@ -3214,7 +3215,7 @@ Would you like to delete the old save data? Eden - + Eden @@ -3224,7 +3225,7 @@ Would you like to delete the old save data? Select External Content Directory... - + Externes Inhaltsverzeichnis auswählen... @@ -3234,7 +3235,7 @@ Would you like to delete the old save data? This directory is already in the list. - + Dieses Verzeichnis ist bereits in der Liste enthalten. @@ -3265,33 +3266,38 @@ Would you like to delete the old save data? Hintergrundfarbe: - + % - FSR sharpening percentage (e.g. 50%) + FSR/SGSR sharpening percentage (e.g. 50%) % - + + Configure Effects... + + + + Off Aus - + VSync Off Vsync Aus - + Recommended Empfohlen - + On An - + VSync On Vsync An @@ -3311,7 +3317,7 @@ Would you like to delete the old save data? Advanced Graphics Settings - Erweiterte Grafik-Einstellungen + Erweiterte Grafikeinstellungen @@ -3334,12 +3340,12 @@ Would you like to delete the old save data? Changing these options from their default may cause issues. Novitii cavete! - + Das Ändern dieser Vorgabe-Einstellungen kann zu Problemen führen. Anfänger seien gewarnt! Vulkan Extensions - Vulkan Erweiterungen + Vulkan-Erweiterungen @@ -3350,7 +3356,7 @@ Would you like to delete the old save data? Extended Dynamic State is disabled on macOS due to MoltenVK compatibility issues that cause black screens. - + „Erweiterter Dynamischer Zustand“ ist unter macOS deaktiviert, da es aufgrund von Kompatibilitätsproblemen mit MoltenVK zu schwarzen Bildschirmen kommt. @@ -3358,12 +3364,12 @@ Would you like to delete the old save data? Hotkey Settings - Hotkey-Einstellungen + Tastenkürzel-Einstellungen Hotkeys - Hotkeys + Tastenkürzel @@ -3378,7 +3384,7 @@ Would you like to delete the old save data? Restore Defaults - Standardwerte wiederherstellen + Vorgabewerte wiederherstellen @@ -3388,12 +3394,12 @@ Would you like to delete the old save data? Hotkey - Hotkey + Tastenkürzel Controller Hotkey - Controller-Hotkey + Controller-Tastenkürzel @@ -3406,7 +3412,7 @@ Would you like to delete the old save data? The entered key sequence is already assigned to: %1 - Die eingegebene Sequenz ist bereits vergeben an: %1 + Die eingegebene Sequenz ist bereits zugewiesen zu: %1 @@ -3421,7 +3427,7 @@ Would you like to delete the old save data? Invalid hotkey settings - Ungültige Hotkey-Einstellungen + Ungültige Tastenkürzel-Einstellungen @@ -3431,7 +3437,7 @@ Would you like to delete the old save data? Restore Default - Standardwerte wiederherstellen + Vorgabewerte wiederherstellen @@ -3446,12 +3452,12 @@ Would you like to delete the old save data? The default button sequence is already assigned to: %1 - Die Standard Tastenfolge ist bereits belegt von: %1 + Die Vorgabe-Tastenfolge ist bereits zugewiesen zu: %1 The default key sequence is already assigned to: %1 - Die Standard-Sequenz ist bereits vergeben an: %1 + Die Vorgabe-Sequenz ist bereits zugewiesen zu: %1 @@ -3528,7 +3534,7 @@ Would you like to delete the old save data? Handheld - Handheld + Handgerät @@ -3599,7 +3605,7 @@ Would you like to delete the old save data? Defaults - Standardwerte + Vorgabewerte @@ -3617,7 +3623,7 @@ Would you like to delete the old save data? Joycon Colors - Joyconfarben + Joycon-Farben @@ -3725,7 +3731,7 @@ Would you like to delete the old save data? Touchscreen - Touchscreen + Berührungsbildschirm @@ -3770,12 +3776,12 @@ Would you like to delete the old save data? Requires restarting Eden - Erfordert einen Neustart Edens + Erfordert einen Neustart von Eden Enable XInput 8 player support (disables web applet) - Unterstützung für XInput 8-Player aktivieren (deaktiviert das Web-Applet) + Unterstützung für XInput-8-Spieler aktivieren (deaktiviert das Web-Applet) @@ -3790,12 +3796,12 @@ Would you like to delete the old save data? Enable direct JoyCon driver - Aktiviere direkten JoyCon-Treiber + Direkten JoyCon-Treiber aktivieren Enable direct Pro Controller driver [EXPERIMENTAL] - Aktiviere direkten Pro Controller-Treiber [EXPERIMENTELL] + Direkten Pro-Controller-Treiber aktivieren [EXPERIMENTELL] @@ -3810,7 +3816,17 @@ Would you like to delete the old save data? Motion / Touch - Bewegung / Touch + Bewegung / Berührung + + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + Zielt darauf ab, den SDL-GUIDE-Button-Hack zu deaktivieren: synthetisches GUIDE(HOME) Event wenn SELECT(MINUS) + START(PLUS) gedrückt wird. Beeinflusst eventuell Win bezogene trigger/rumble/etc Sachen. + + + + Disable SDL WGI/XInput (Requires restart) + Deaktiviere SDL WGI/XInput (erfordert Neustart) @@ -3828,57 +3844,57 @@ Would you like to delete the old save data? Input Profiles - Eingabe-Profile + Eingabeprofile Player 1 Profile - Profil Spieler 1 + Profil von Spieler 1 Player 2 Profile - Profil Spieler 2 + Profil von Spieler 2 Player 3 Profile - Profil Spieler 3 + Profil von Spieler 3 Player 4 Profile - Profil Spieler 4 + Profil von Spieler 4 Player 5 Profile - Profil Spieler 5 + Profil von Spieler 5 Player 6 Profile - Profil Spieler 6 + Profil von Spieler 6 Player 7 Profile - Profil Spieler 7 + Profil von Spieler 7 Player 8 Profile - Profil Spieler 8 + Profil von Spieler 8 Use global input configuration - Verwende globale Eingabe-Konfiguration + Globale Eingabekonfiguration verwenden Player %1 profile - Profil Spieler %1 + Profil von Spieler %1 @@ -3998,7 +4014,7 @@ Would you like to delete the old save data? Deadzone: 0% - Deadzone: 0% + Totzone: 0 % @@ -4089,7 +4105,7 @@ Would you like to delete the old save data? Home - Home + Heim @@ -4258,7 +4274,7 @@ Um die Achsen umzukehren, bewege den Joystick zuerst vertikal und dann horizonta Handheld - Handheld + Handgerät @@ -4321,58 +4337,58 @@ Um die Achsen umzukehren, bewege den Joystick zuerst vertikal und dann horizonta [wartet] - + New Profile Neues Profil - + Enter a profile name: Profilnamen eingeben: - - + + Create Input Profile Eingabeprofil erstellen - + The given profile name is not valid! Angegebener Profilname ist nicht gültig! - + Failed to create the input profile "%1" Erstellen des Eingabeprofils "%1" ist fehlgeschlagen - + Delete Input Profile Eingabeprofil löschen - + Failed to delete the input profile "%1" Löschen des Eingabeprofils "%1" ist fehlgeschlagen - + Load Input Profile Eingabeprofil laden - + Failed to load the input profile "%1" Laden des Eingabeprofils "%1" ist fehlgeschlagen - + Save Input Profile Eingabeprofil speichern - + Failed to save the input profile "%1" Speichern des Eingabeprofils "%1" ist fehlgeschlagen @@ -4392,7 +4408,7 @@ Um die Achsen umzukehren, bewege den Joystick zuerst vertikal und dann horizonta Defaults - Standardwerte + Vorgabewerte @@ -4400,12 +4416,12 @@ Um die Achsen umzukehren, bewege den Joystick zuerst vertikal und dann horizonta Configure Motion / Touch - Bewegung / Touch einrichten + Bewegung / Berührung einrichten Touch - Touch + Berührung @@ -4420,7 +4436,7 @@ Um die Achsen umzukehren, bewege den Joystick zuerst vertikal und dann horizonta - + Configure Einrichtung @@ -4432,7 +4448,7 @@ Um die Achsen umzukehren, bewege den Joystick zuerst vertikal und dann horizonta CemuhookUDP Config - CemuhookUDP Konfiguration + CemuhookUDP-Konfiguration @@ -4451,7 +4467,7 @@ Um die Achsen umzukehren, bewege den Joystick zuerst vertikal und dann horizonta - + Test Testen @@ -4466,77 +4482,77 @@ Um die Achsen umzukehren, bewege den Joystick zuerst vertikal und dann horizonta Server löschen - + %1:%2 %1:%2 - - - - - - + + + + + + Eden - + Eden - + Port number has invalid characters Port-Nummer hat ungültige Zeichen - + Port has to be in range 0 and 65353 Port muss zwischen 0 und 65353 liegen - + IP address is not valid IP Adresse ist ungültig - + This UDP server already exists Dieser UDP-Server existiert bereits - + Unable to add more than 8 servers Es können nicht mehr als 8 Server hinzugefügt werden - + Testing Testen - + Configuring Einrichten - + Test Successful Test erfolgreich - + Successfully received data from the server. Daten wurden erfolgreich vom Server empfangen. - + Test Failed Test fehlgeschlagen - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Konnte keine Daten vom Server empfangen.<br>Prüfe bitte, dass der Server korrekt eingerichtet wurde und dass Adresse und Port korrekt sind. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. UDP-Test oder Kalibration wird gerade durchgeführt.<br>Bitte warte einen Moment. @@ -4546,12 +4562,12 @@ Um die Achsen umzukehren, bewege den Joystick zuerst vertikal und dann horizonta Configure mouse panning - Mausschwenk konfigurieren + Mausschwenken konfigurieren Enable mouse panning - Maus-Panning aktivieren + Mausschwenken aktivieren @@ -4585,7 +4601,7 @@ Um die Achsen umzukehren, bewege den Joystick zuerst vertikal und dann horizonta Deadzone counterweight - Deadzone-Gegengewicht + Totzonen-Gegengewicht @@ -4595,12 +4611,12 @@ Um die Achsen umzukehren, bewege den Joystick zuerst vertikal und dann horizonta Deadzone - Deadzone + Totzone Stick decay - Stick-Abklingzeit + Stick-Verfall @@ -4615,7 +4631,7 @@ Um die Achsen umzukehren, bewege den Joystick zuerst vertikal und dann horizonta Default - Standard + Vorgabe @@ -4688,7 +4704,7 @@ Aktuell liegen die Werte bei %1% bzw. %2%. Title ID - Titel ID + Titel-ID @@ -4721,57 +4737,57 @@ Aktuell liegen die Werte bei %1% bzw. %2%. Einige Einstellungen sind nur verfügbar, wenn kein Spiel aktiv ist. - + Add-Ons Add-Ons - + System System - + CPU CPU - + Graphics Grafik - + Adv. Graphics Erw. Grafik - + Ext. Graphics - + Ext. Grafiken - + Audio Audio - + Input Profiles Eingabe-Profile - + Network Netzwerk - + Applets - + Applets - + Properties Einstellungen @@ -4840,59 +4856,186 @@ Kontrolliere die Logs für Details. Zipped Mod Location - + Speicherort der gezippten Mod Zipped Archives (*.zip) - + ZIP-Archive (*.zip) Invalid Selection - + Ungültige Auswahl Only mods, cheats, and patches can be deleted. To delete NAND-installed updates, right-click the game in the game list and click Remove -> Remove Installed Update. - + Es können nur Mods, Cheats und Patches gelöscht werden. +Um auf dem NAND installierte Aktualisierungen zu löschen, klicke mit der rechten Maustaste auf das Spiel in der Spieleliste und wähle „Entfernen“ -> „Installierte Aktualisierung entfernen“. You are about to delete the following installed mods: - + Du bist dabei, die folgenden installierten Mods zu löschen: + Once deleted, these can NOT be recovered. Are you 100% sure you want to delete them? - + +Einmal gelöscht, können diese nicht wiederhergestellt werden. Bist du dir zu 100 % sicher, dass du sie löschen möchtest? Delete add-on(s)? - + Add-ons löschen? Successfully deleted - + Erfolgreich gelöscht Successfully deleted all selected mods. - + Alle ausgewählten Mods wurden erfolgreich gelöscht. &Delete - + &Löschen &Open in File Manager + &Im Dateimanager öffnen + + + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. @@ -4954,7 +5097,7 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t Error occurred attempting to overwrite previous image at: %1. - Fehler beim Überschreiben des vorherigen Bildes bei: %1 + Beim Versuch, das vorherige Bild unter %1 zu überschreiben, ist ein Fehler aufgetreten. @@ -5054,7 +5197,7 @@ UUID: %2 Deadzone: 0% - Deadzone: 0% + Totzone: 0% @@ -5070,12 +5213,12 @@ UUID: %2 Enable - Aktiviere + Aktivieren Ring Sensor Value - Ringsensor-Wert + Ringsensor-Wert @@ -5086,7 +5229,7 @@ UUID: %2 Restore Defaults - Standardwerte wiederherstellen + Vorgabewerte wiederherstellen @@ -5112,7 +5255,7 @@ UUID: %2 Error enabling ring input - Fehler beim Aktivieren des Ring-Inputs + Fehler beim Aktivieren des Ring-Eingangs @@ -5171,7 +5314,7 @@ UUID: %2 Warning: "%1" is not a valid language for region "%2" - Achtung: "%1" ist keine valide Sprache für die Region "%2" + WARNUNG: "%1" ist keine valide Sprache für die Region "%2" @@ -5184,17 +5327,17 @@ UUID: %2 <html><head/><body><p>Reads controller input from scripts in the same format as TAS-nx scripts.<br/>For a more detailed explanation, please consult the user handbook.</p></body></html> - + <html><head/><body><p>Liest Controller-Eingaben aus Skripten im gleichen Format wie TAS-nx-Skripte ein.<br/>Eine ausführlichere Erläuterung findest du im Benutzerhandbuch.</p></body></html> To check which hotkeys control the playback/recording, please refer to the Hotkey settings (Configure -> General -> Hotkeys). - Um zu überprüfen, welche Hotkeys die Wiedergabe/Aufnahme steuern, sehen Sie bitte in den Hotkey-Einstellungen nach (Konfigurieren -> Allgemein -> Hotkeys). + Um zu überprüfen, welche Tastenkürzel die Wiedergabe/Aufnahme steuern, sieh bitte in den Tastenkürzel-Einstellungen nach (Konfigurieren -> Allgemein -> Tastenkürzel). WARNING: This is an experimental feature.<br/>It will not play back scripts frame perfectly with the current, imperfect syncing method. - ACHTUNG: Dies ist ein experimentes Feature.<br/>Es wird scripts nicht perfekt mit der momentanen, unperfekten Synchronisationsmethode abspielen. + WARNUNG: Dies ist eine experimentelle Funktion.<br/>Mit der derzeitigen, unvollkommenen Synchronisationsmethode werden Skripte nicht frame-genau wiedergegeben. @@ -5209,7 +5352,7 @@ UUID: %2 Loop script - Script loopen + Schleifen-Skript @@ -5219,12 +5362,12 @@ UUID: %2 Show recording dialog - + Aufnahmedialog anzeigen Script Directory - Skript-Verzeichnis + Skriptverzeichnis @@ -5247,7 +5390,7 @@ UUID: %2 Select TAS Load Directory... - TAS-Lade-Verzeichnis auswählen... + TAS-Ladeverzeichnis auswählen... @@ -5255,7 +5398,7 @@ UUID: %2 Configure Touchscreen Mappings - Touchscreen-Belegung einrichten + Berührungsbildschirm-Belegung konfigurieren @@ -5281,8 +5424,8 @@ UUID: %2 Click the bottom area to add a point, then press a button to bind. Drag points to change position, or double-click table cells to edit values. - Klicke das untere Feld um einen Punkt hinzuzufügen und drücke dann eine Taste, die diesen Punkt auslösen soll. -Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf die Tabelle, um die Belegung des Punktes zu ändern. + Klicke auf den unteren Bereich, um einen Punkt hinzuzufügen, und drücke dann eine Taste, um die Zuordnung festzulegen. +Ziehe die Punkte, um ihre Position zu ändern, oder doppelklicke auf Tabellenzellen, um die Werte zu bearbeiten. @@ -5347,12 +5490,12 @@ Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf Configure Touchscreen - Touchscreen einrichten: + Berührungsbildschirm konfigurieren Warning: The settings in this page affect the inner workings of Eden's emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing. - + Warnung: Die Einstellungen auf dieser Seite beeinflussen die interne Funktionsweise des emulierten Berührungsbildschirms von Eden. Änderungen daran können zu unerwünschtem Verhalten führen – beispielsweise dazu, dass der Berührungsbildschirm nur teilweise oder gar nicht mehr funktioniert. Du solltest diese Seite nur verwenden, wenn du genau weist, was du tust. @@ -5454,7 +5597,7 @@ Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf Theme: - Theme: + Thema: @@ -5463,91 +5606,86 @@ Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf - Show Compatibility List - Kompatibilitätsliste anzeigen - - - Show Add-Ons Column Add-On Spalte anzeigen - + Show Size Column "Größe"-Spalte anzeigen - + Show File Types Column "Dateityp"-Spalte anzeigen - + Show Play Time Column "Spielzeit"-Spalte anzeigen - + Folder Icon Size: Ordner-Icon Größe: - + Row 1 Text: Zeile 1 Text: - + Row 2 Text: Zeile 2 Text: - + Screenshots - Screenshots + Bildschirmfotos - + Ask Where To Save Screenshots (Windows Only) Frage nach, wo Screenshots gespeichert werden sollen (Nur Windows) - + Screenshots Path: - Screenshotpfad + Bildschirmfotos-Pfad: - + ... ... - + TextLabel - TextLabel + Textetikett - + Resolution: Auflösung: - + Select Screenshots Path... - Screenshotpfad auswählen... + Bildschirmfotos-Pfad auswählen... - + <System> <System> - + English Englisch - + Auto (%1 x %2, %3 x %4) Screenshot width value Auto (%1 x %2, %3 x %4) @@ -5558,12 +5696,12 @@ Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf Configure Vibration - Stelle die Vibration ein + Vibration konfigurieren Press any controller button to vibrate the controller. - Drücke einen Knopf um den Controller vibrieren zu lassen. + Drücke einen Knopf, um den Controller vibrieren zu lassen. @@ -5648,7 +5786,7 @@ Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf Eden Web Service - + Eden-Web-Dienst @@ -5728,17 +5866,17 @@ Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf Shaders - + Shader User NAND - + Nutzer-NAND System NAND - + System-NAND @@ -5761,30 +5899,30 @@ Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf Tooltip - + Kurzinfo Open with your system file manager - Mit deinem System eigenen Dateimanager öffnen + Mit deinem System-Dateiverwalter öffnen Delete all data in this directory. THIS IS 100% IRREVERSABLE! - + Alle Daten in diesem Verzeichnis löschen. DIES IST ZU 100 % UNUMKEHRBAR! Export all data in this directory. This may take a while! - + Alle Daten in diesem Verzeichnis exportieren. Das kann eine Weile dauern! Import data for this directory. This may take a while, and will delete ALL EXISTING DATA! - + Daten für dieses Verzeichnis importieren. Dies kann eine Weile dauern und löscht ALLE BESTEHENDEN DATEN! - + Calculating... Berechnen... @@ -5827,12 +5965,12 @@ Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf Server Address - Serveradresse + Server-Adresse <html><head/><body><p>Server address of the host</p></body></html> - <html><head/><body><p>Serveradresse des Hosts</p></body></html> + <html><head/><body><p>Server-Adresse des Hosts</p></body></html> @@ -5847,7 +5985,7 @@ Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf Nickname - Nickname + Spitzname @@ -5883,7 +6021,7 @@ Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf Room name is not valid. Must be 4 to 20 alphanumeric characters. - Raumname ist ungültig. Muss aus 4 bis 20 alphanumerischen Zeichen bestehen. + Der Raumname ist ungültig. Er muss aus 4 bis 20 alphanumerischen Zeichen bestehen. @@ -5893,7 +6031,7 @@ Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf IP is not a valid IPv4 address. - + Die IP ist keine gültige IPv4-Adresse. @@ -5903,7 +6041,7 @@ Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf You must choose a Preferred Game to host a room. If you do not have any games in your game list yet, add a game folder by clicking on the plus icon in the game list. - + Du musst ein bevorzugtes Spiel auswählen, um einen Raum zu erstellen. Wenn du noch keine Spiele in deiner Spieleliste hast, füge einen Spielordner hinzu, indem du auf das Plus-Symbol in der Spieleliste klickst. @@ -5913,7 +6051,7 @@ Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf Unable to connect to the host. Verify that the connection settings are correct. If you still cannot connect, contact the room host and verify that the host is properly configured with the external port forwarded. - + Es kann keine Verbindung zum Host hergestellt werden. Überprüfe, ob die Verbindungseinstellungen korrekt sind. Wenn die Verbindung weiterhin nicht hergestellt werden kann, wende dich an den Host des Raums und vergewissere dich, dass dieser ordnungsgemäß konfiguriert ist und die Portweiterleitung eingerichtet ist. @@ -5928,12 +6066,12 @@ Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf The host of the room has banned you. Speak with the host to unban you or try a different room. - + Der Host des Raums hat dich gesperrt. Sprich mit dem Host, um die Sperre aufzuheben, oder versuche es in einem anderen Raum. Version mismatch! Please update to the latest version of Eden. If the problem persists, contact the room host and ask them to update the server. - + Versionskonflikt! Bitte aktualisiere auf die neueste Version von Eden. Sollte das Problem weiterhin bestehen, wende dich bitte an den Raumhost und bitte ihn, den Server zu aktualisieren. @@ -5943,22 +6081,22 @@ Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf An unknown error occurred. If this error continues to occur, please open an issue - Ein unbekannter Fehler ist aufgetreten. Sollte der Fehler weiterhin auftreten, erstelle bitte eine Fehlermeldung. + Es ist ein unbekannter Fehler aufgetreten. Sollte dieser Fehler weiterhin auftreten, öffne bitte ein Problem. Connection to room lost. Try to reconnect. - + Die Verbindung zum Raum wurde unterbrochen. Versuche, die Verbindung erneut herzustellen. You have been kicked by the room host. - + Du wurdest vom Raumhost rausgeworfen. IP address is already in use. Please choose another. - IP-Addresse wird bereits genutzt. Bitte wählen Sie eine andere. + IP-Adresse wird bereits genutzt. Bitte wähleeine andere. @@ -5969,13 +6107,15 @@ Ziehe die Punkte mit deiner Maus, um ihre Position zu ändern. Doppelklicke auf The user you are trying to kick/ban could not be found. They may have left the room. - + Der Nutzer, den du entfernen/sperren möchtest, konnte nicht gefunden werden. +Möglicherweise hat er den Raum bereits verlassen. No valid network interface is selected. Please go to Configure -> System -> Network and make a selection. - + Es ist keine gültige Netzwerkschnittstelle ausgewählt. +Bitte gehe zu „Konfigurieren“ -> „System“ -> „Netzwerk“ und triff eine Auswahl. @@ -5986,330 +6126,323 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL nicht verfügbar! - + OpenGL shared contexts are not supported. Gemeinsame OpenGL-Kontexte werden nicht unterstützt. - + Eden has not been compiled with OpenGL support. - + Eden wurde nicht mit OpenGL-Unterstützung kompiliert. - - - + + + Error while initializing OpenGL! Fehler beim Initialisieren von OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Deine Grafikkarte unterstützt kein OpenGL oder du hast nicht den neusten Treiber installiert. - + Error while initializing OpenGL 4.6! Fehler beim Initialisieren von OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Deine Grafikkarte unterstützt OpenGL 4.6 nicht, oder du benutzt nicht die neuste Treiberversion.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Deine Grafikkarte unterstützt anscheinend nicht eine oder mehrere von yuzu benötigten OpenGL-Erweiterungen. Bitte stelle sicher, dass du den neusten Grafiktreiber installiert hast.<br><br>GL Renderer:<br>%1<br><br>Nicht unterstützte Erweiterungen:<br>%2 - + This build doesn't have OpenGL support. - + Diese Version bietet keine OpenGL-Unterstützung. GameList - + &Add New Game Directory &Neues Spieleverzeichnis hinzufügen - + Favorite Favorit - + Start Game Spiel starten - + Start Game without Custom Configuration Spiel ohne benutzerdefinierte Spiel-Einstellungen starten - + Open Save Data Location Spielstand-Verzeichnis öffnen - + Open Mod Data Location Mod-Verzeichnis öffnen - + Open Transferable Pipeline Cache - Transferierbaren Pipeline-Cache öffnen + Transferierbaren Rohrleitungs-Cache öffnen - + Link to Ryujinx - + Verknüpfung zu Ryujinx - + Remove Entfernen - + Remove Installed Update - Installiertes Update entfernen + Installierte Aktualisierung entfernen - + Remove All Installed DLC Alle installierten DLCs entfernen - + Remove Custom Configuration - Spiel-Einstellungen entfernen + Benutzerdefinierte Konfiguration entfernen - + Remove Cache Storage Cache-Speicher entfernen - + Remove OpenGL Pipeline Cache - OpenGL-Pipeline-Cache entfernen + OpenGL-Rohrleitungs-Cache entfernen - + Remove Vulkan Pipeline Cache - Vulkan-Pipeline-Cache entfernen + Vulkan-Rohrleitungs-Cache entfernen - + Remove All Pipeline Caches - Alle Pipeline-Caches entfernen + Alle Rohrleitungs-Caches entfernen - + Remove All Installed Contents Alle installierten Inhalte entfernen - + Manage Play Time Spielzeit verwalten - + Edit Play Time Data Spielzeit-Daten bearbeiten - + Remove Play Time Data Spielzeit-Daten entfernen - - + + Dump RomFS RomFS speichern - + Dump RomFS to SDMC - RomFS nach SDMC dumpen + RomFS nach SDMC auslesen - + Verify Integrity Integrität überprüfen - + Copy Title ID to Clipboard Title-ID in die Zwischenablage kopieren - - Navigate to GameDB entry - GameDB-Eintrag öffnen - - - + Create Shortcut Verknüpfung erstellen - + Add to Desktop Zum Desktop hinzufügen - + Add to Applications Menu Zum Menü "Anwendungen" hinzufügen - + Configure Game Spiel konfigurieren - + Scan Subfolders Unterordner scannen - + Remove Game Directory Spieleverzeichnis entfernen - + ▲ Move Up ▲ Nach Oben - + ▼ Move Down ▼ Nach Unten - + Open Directory Location Verzeichnis öffnen - + Clear Löschen - - - Name - Name - - - - Compatibility - Kompatibilität - - - - Add-ons - Add-ons - - - - File type - Dateityp - - - - Size - Größe - - - - Play time - Spielzeit - GameListItemCompat - + Ingame Im Spiel - + Game starts, but crashes or major glitches prevent it from being completed. Spiel startet, stürzt jedoch ab oder hat signifikante Glitches, die es verbieten es durchzuspielen. - + Perfect Perfekt - + Game can be played without issues. Das Spiel kann ohne Probleme gespielt werden. - + Playable Spielbar - + Game functions with minor graphical or audio glitches and is playable from start to finish. Das Spiel funktioniert mit minimalen grafischen oder Tonstörungen und ist komplett spielbar. - + Intro/Menu Intro/Menü - + Game loads, but is unable to progress past the Start Screen. Das Spiel lädt, ist jedoch nicht im Stande den Startbildschirm zu passieren. - + Won't Boot Startet nicht - + The game crashes when attempting to startup. Das Spiel stürzt beim Versuch zu starten ab. - + Not Tested Nicht getestet - + The game has not yet been tested. Spiel wurde noch nicht getestet. + + GameListModel + + + Name + Name + + + + Add-ons + Add-ons + + + + File type + Dateityp + + + + Size + Größe + + + + Play time + Spielzeit + + GameListPlaceholder - + Double-click to add a new folder to the game list Doppelklicke, um einen neuen Ordner zur Spieleliste hinzuzufügen. @@ -6317,17 +6450,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) %1 von %n Ergebnis%1 von %n Ergebnisse(n) - + Filter: Filter: - + Enter pattern to filter Wörter zum Filtern eingeben @@ -6382,7 +6515,7 @@ Please go to Configure -> System -> Network and make a selection. Load Previous Ban List - Vorherige Bann-Liste laden + Vorherige Bannliste laden @@ -6411,7 +6544,8 @@ Please go to Configure -> System -> Network and make a selection. Failed to announce the room to the public lobby. In order to host a room publicly, you must have a valid Eden account configured in Emulation -> Configure -> Web. If you do not want to publish a room in the public lobby, then select Unlisted instead. Debug Message: - + Der Raum konnte nicht in der öffentlichen Lobby angekündigt werden. Um einen Raum öffentlich zu hosten, muss in „Emulation -> Konfigurieren -> Web“ ein gültiges Eden-Konto eingerichtet sein. Wenn du einen Raum nicht in der öffentlichen Lobby veröffentlichen möchtest, wähle stattdessen „Nicht gelistet“. +Fehlerbehebungs-Meldung: @@ -6511,7 +6645,7 @@ Debug Message: Exit Eden - Verlasse Eden + Eden verlassen @@ -6656,12 +6790,12 @@ Debug Message: Loading Shaders 387 / 1628 - Shader 387 / 1628 wird geladen... + Shader werden geladen 387 / 1628 Loading Shaders %v out of %m - Shader %v / %m wird geladen... + Shader werden geladen: %v von %m @@ -6676,7 +6810,7 @@ Debug Message: Loading Shaders %1 / %2 - Shader %1 / %2 wird geladen... + Shader werden geladen %1 / %2 @@ -6700,7 +6834,7 @@ Debug Message: Nickname - Nickname + Spitzname @@ -6715,7 +6849,7 @@ Debug Message: Games I Own - Spiele die ich besitze + Spiele, die ich besitze @@ -6735,7 +6869,7 @@ Debug Message: Password Required to Join - Passwort zum Joinen benötigt + Passwort zum Beitreten erforderlich @@ -6813,1346 +6947,1343 @@ Debug Message: &Debugging - &Debugging + &Fehlerbehebung &Game List Mode - &Spieleliste Modus + &Spielelisten-Modus - + Game &Icon Size - + Spielsymbolgröße - + Reset Window Size to &720p Fenstergröße auf &720p zurücksetzen - + Reset Window Size to 720p Fenstergröße auf 720p zurücksetzen - + Reset Window Size to &900p Fenstergröße auf &900p zurücksetzen - + Reset Window Size to 900p Fenstergröße auf 900p zurücksetzen - + Reset Window Size to &1080p Fenstergröße auf &1080p zurücksetzen - + Reset Window Size to 1080p Fenstergröße auf 1080p zurücksetzen - + &Multiplayer &Mehrspieler - + &Tools &Werkzeuge - + Am&iibo - + Am&iibo - + Launch &Applet - + &Applet starten - + &TAS &TAS - + &Create Home Menu Shortcut - + &Startmenü-Verknüpfung erstellen - + Install &Firmware - + Installieren &Firmware - + &Help &Hilfe - + &Install Files to NAND... &Dateien im NAND installieren... - + L&oad File... Datei &laden... - + Load &Folder... &Verzeichnis laden... - + E&xit S&chließen - - + + &Pause &Pause - + &Stop &Stop - + &Verify Installed Contents Installierte Inhalte &überprüfen - + &About Eden &Über Eden - + Single &Window Mode &Einzelfenster-Modus - + Con&figure... - Kon&figurieren + Kon&figurieren... - + Ctrl+, - Strg+ + Strg+, - + Enable Overlay Display Applet - + Applet für die Überlagerungsanzeige aktivieren - + Show &Filter Bar &Filterleiste anzeigen - + Show &Status Bar &Statusleiste anzeigen - + Show Status Bar Statusleiste anzeigen - + &Browse Public Game Lobby &Öffentliche Spiele-Lobbys durchsuchen - + &Create Room &Raum erstellen - + &Leave Room &Raum verlassen - + &Direct Connect to Room &Direkte Verbindung zum Raum - + &Show Current Room &Aktuellen Raum anzeigen - + F&ullscreen Vollbild (&u) - + &Restart Neusta&rt - + Load/Remove &Amiibo... &Amiibo laden/entfernen... - - &Report Compatibility - &Kompatibilität melden - - - + Open &Mods Page &Mods-Seite öffnen - - Open &Quickstart Guide - &Schnellstart-Anleitung öffnen + + &User Handbook + - - &FAQ - &FAQ - - - + &Capture Screenshot &Bildschirmfoto aufnehmen - + &Album - + &Album - + &Set Nickname and Owner Spitzname und Besitzer &festlegen - + &Delete Game Data Spiel-Daten &löschen - + &Restore Amiibo Amiibo &wiederherstellen - + &Format Amiibo Amiibo &formatieren - + &Mii Editor - + &Mii-Editor - + &Configure TAS... &TAS &konfigurieren... - + Configure C&urrent Game... &Spiel-Einstellungen ändern... - - + + &Start &Start - + &Reset &Zurücksetzen - - + + R&ecord Aufnahme - + Open &Controller Menu Öffne &Controller-Menü - + Install Decryption &Keys - + Entschlüsselungsschlüssel installieren + + + + &Home Menu + &Startmenü + + + + &Desktop + &Desktop + + + + &Application Menu + &Anwendungsmenü - &Home Menu - + &Root Data Folder + &Stammdatenordner - &Desktop - + &NAND Folder + &NAND-Ordner - &Application Menu - + &SDMC Folder + &SDMC-Ordner - &Root Data Folder - + &Mod Folder + &Mod-Ordner - &NAND Folder - + &Log Folder + &Protokoll-Ordner - &SDMC Folder - + From Folder + Aus Ordner - &Mod Folder - + From ZIP + Aus ZIP - &Log Folder - + &Eden Dependencies + &Eden-Abhängigkeiten - From Folder - - - - - From ZIP - - - - - &Eden Dependencies - - - - &Data Manager - + &Datenverwalter - + &Tree View - + &Baumansicht - + &Grid View - + &Rasteransicht - + Game Icon Size - + Spielsymbolgröße - - + + None - + Keine - + Show Game &Name - + Spiel&name anzeigen - + Show &Performance Overlay + Leistungsüberlagerung anzeigen + + + + Post-Processing &Shaders... - + + &Carousel View + &Karussellansicht + + + Small (32x32) Klein (32x32) - + Standard (64x64) Standard (64x64) - + Large (128x128) Groß (128x128) - + Full Size (256x256) Volle Größe (256x256) - + Broken Vulkan Installation Detected Defekte Vulkan-Installation erkannt - + Vulkan initialization failed during boot. - + Initialisierung von Vulkan ist beim Systemstart fehlgeschlagen. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping Spiel wird ausgeführt - + Loading Web Applet... - + Web-Applet wird geladen... - - + + Disable Web Applet - + Web-Applet deaktivieren - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + Das Deaktivieren des Web-Applets kann zu unvorhersehbarem Verhalten führen und sollte nur bei „Super Mario 3D All-Stars“ verwendet werden. Möchtest du das Web-Applet wirklich deaktivieren? +(Dies kann in den Fehlerbehebungs-Einstellungen wieder aktiviert werden.) - + The amount of shaders currently being built - + Die Anzahl der Shader, die derzeit erstellt werden - + The current selected resolution scaling multiplier. - + Der aktuell ausgewählte Skalierungsfaktor für die Auflösung. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + Aktuelle Emulationsgeschwindigkeit. Werte über oder unter 100 % bedeuten, dass die Emulation schneller oder langsamer läuft als auf einer Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Wie viele Bilder pro Sekunde das Spiel derzeit anzeigt. Dies variiert von Spiel zu Spiel und von Szene zu Szene. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Die Zeit, die für die Emulation eines Switch-Frames benötigt wird, ohne Berücksichtigung von Framelimiting oder V-Sync. Bei einer Emulation mit voller Geschwindigkeit sollte dieser Wert höchstens 16,67 ms betragen. - + Unmute - Ton aktivieren + Stummschaltung aufheben - + Mute Stummschalten - + Reset Volume Lautstärke zurücksetzen - + + Configure Effects... + + + + &Clear Recent Files - + &Zuletzt verwendete Dateien löschen - + &Continue - + &Fortfahren - + Warning: Outdated Game Format - + Warnung: Veraltetes Spielformat - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - + Du verwendest für dieses Spiel das dekontruierte ROM-Verzeichnisformat - ein veraltetes Format, das durch andere Formate wie NCA, NAX, XCI oder NSP abgelöst wurde. Dekonstruierten ROM-Verzeichnissen fehlen Symbole, Metadaten und die Unterstützung für Aktualisierungen. <br>Eine Erläuterung der verschiedenen Switch-Formate, die Eden unterstützt, findest du in unserem Benutzerhandbuch. Diese Meldung wird nicht erneut angezeigt. - - + + Error while loading ROM! - + Fehler beim Laden des ROMs! - + The ROM format is not supported. - + Das ROM-Format wird nicht unterstützt. - + An error occurred initializing the video core. + Beim Initialisieren des Videokerns ist ein Fehler aufgetreten. + + + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. - - - - + Error while loading ROM! %1 %1 signifies a numeric error code. - + Fehler beim Laden des ROMs! %1 - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + %1<br>Bitte erstelle deine Dateien erneut oder bitte auf Discord/Stoat um Hilfe. - + An unknown error occurred. Please see the log for more details. - + Es ist ein unbekannter Fehler aufgetreten. Weitere Informationen findest du im Protokoll. - + + (64-bit) - + (64-bit) - + + (32-bit) - + (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + %1 %2 - + Closing software... - + Software wird geschlossen... - + Save Data - + Speicherdaten - + Mod Data - + Mod-Daten - + Error Opening %1 Folder - + Fehler beim Öffnen des Ordners %1 - - + + Folder does not exist! - + Der Ordner existiert nicht! - + Remove Installed Game Contents? - + Installierte Spielinhalte entfernen? - + Remove Installed Game Update? - + Installierte Spielaktualisierung entfernen? - + Remove Installed Game DLC? - + Installiertes Spiel-DLC entfernen? - + Remove Entry - + Eintrag entfernen - + Delete OpenGL Transferable Shader Cache? - + Den übertragbaren Shader-Cache von OpenGL löschen? - + Delete Vulkan Transferable Shader Cache? - + Den übertragbaren Shader-Cache von Vulkan löschen? - + Delete All Transferable Shader Caches? - + Alle übertragbaren Shader-Caches löschen? - + Remove Custom Game Configuration? - + Benutzerdefinierte Spielkonfiguration entfernen? - + Remove Cache Storage? - + Cache-Speicher löschen? - + Remove File - + Datei entfernen - + Remove Play Time Data - + Wiedergabezeitdaten löschen - + Reset play time? - + Spielzeit zurücksetzen? - - + + RomFS Extraction Failed! - + RomFS-Extraktion fehlgeschlagen! - + There was an error copying the RomFS files or the user cancelled the operation. - + Beim Kopieren der RomFS-Dateien ist ein Fehler aufgetreten oder der Benutzer hat den Vorgang abgebrochen. - + Full - + Vollständig - + Skeleton - + Skelett - + Select RomFS Dump Mode - + RomFS-Auslesemodus auswählen - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + Bitte wähle aus, wie das RomFS kopiert werden soll. <br>Vollständig kopiert alle Dateien in das neue Verzeichnis, während <br>Skelett lediglich die Verzeichnisstruktur erstellt. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Unter %1 ist nicht genügend Speicherplatz vorhanden, um das RomFS zu extrahieren. Bitte schaffe Speicherplatz oder wähle unter „Emulation > Konfigurieren > System > Dateisystem > Dump-Stammverzeichnis“ ein anderes Dump-Verzeichnis aus. - + Extracting RomFS... - + RomFS wird extrahiert... - - + + Cancel - + Abbrechen - + RomFS Extraction Succeeded! - + RomFS wurde erfolgreich extrahiert! - + The operation completed successfully. - + Der Vorgang wurde erfolgreich abgeschlossen. - + Error Opening %1 - + Fehler beim Öffnen von %1 - + Select Directory - + Verzeichnis auswählen - + Properties - + Eigenschaften - + The game properties could not be loaded. - + Die Spieleigenschaften konnten nicht geladen werden. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Switch-Ausführungsdatei (%1);;Alle Dateien (*.*) - + Load File - + Datei laden - + Open Extracted ROM Directory - + Verzeichnis mit dem extrahierten ROM öffnen - + Invalid Directory Selected - + Ungültiges Verzeichnis ausgewählt - + The directory you have selected does not contain a 'main' file. - + Das von dir ausgewählte Verzeichnis enthält keine „main“-Datei. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Installierbare Switch-Dateien (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX-Kassetten-Image (*.xci) - + Install Files - + Dateien installieren - + %n file(s) remaining - + %n Datei(en) verbleiben%n Datei(en) verbleiben - + Installing file "%1"... - + Datei „%1“ wird installiert... - - + + Install Results - + Installationsergebnisse - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + Um mögliche Konflikte zu vermeiden, raten wir davon ab, Basisspiele auf dem NAND-Speicher zu installieren. +Bitte nutze diese Funktion ausschließlich zur Installation von Aktualisierungen und DLCs. - + %n file(s) were newly installed - + %n Datei(en) wurden neu installiert +%n Datei(en) wurden neu installiert + - + %n file(s) were overwritten - + %n Datei(en) wurden überschrieben +%n Datei(en) wurden überschrieben + - + %n file(s) failed to install - + %n Datei(en) konnten nicht installiert werden +%n Datei(en) konnten nicht installiert werden + - + System Application - + Systemanwendung - + System Archive - + Systemarchiv - + System Application Update - + System-Anwendungsaktualisierung - + Firmware Package (Type A) - + Firmware-Paket (Typ A) - + Firmware Package (Type B) - + Firmware-Paket (Typ B) - + Game - + Spiel - + Game Update - + Spielaktualisierung - + Game DLC - + Spiel-DLC - + Delta Title - + Delta-Titel - + Select NCA Install Type... - + NCA-Installationstyp auswählen... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Bitte wähle den Titeltyp aus, unter dem du diese NCA installieren möchtest: +(In den meisten Fällen ist die Vorgabe „Spiel“ ausreichend.) - + Failed to Install - + Installation fehlgeschlagen - + The title type you selected for the NCA is invalid. - + Der von dir für die NCA ausgewählte Titel ist ungültig. - + File not found - + Datei nicht gefunden - + File "%1" not found - + Datei „%1“ nicht gefunden - + OK - + OK - + Function Disabled - + Funktion deaktiviert - + Compatibility list reporting is currently disabled. Check back later! - + Die Anzeige der Kompatibilitätsliste ist derzeit deaktiviert. Schaue später noch einmal vorbei! - + Error opening URL - + Fehler beim Öffnen der URL - + Unable to open the URL "%1". - + Die URL „%1“ kann nicht geöffnet werden. - + TAS Recording - + TAS-Aufnahme - + Overwrite file of player 1? - + Datei von Spieler 1 überschreiben? - + Invalid config detected - + Ungültige Konfiguration erkannt - + Handheld controller can't be used on docked mode. Pro controller will be selected. - + Der Handgerät-Controller kann im angedockten Modus nicht verwendet werden. Es wird der Pro-Controller ausgewählt. - - + + Amiibo - + Amiibo - - + + The current amiibo has been removed - + Das aktuelle amiibo wurde entfernt - + Error - + Fehler - - + + The current game is not looking for amiibos - + Das aktuelle Spiel sucht nicht nach Amiibos - + Amiibo File (%1);; All Files (*.*) - + Amiibo-Datei (%1);; Alle Dateien (*.*) - + Load Amiibo - + Amiibo laden - + Error loading Amiibo data - + Fehler beim Laden der Amiibo-Daten - + The selected file is not a valid amiibo - + Die ausgewählte Datei ist kein gültiges amiibo - + The selected file is already on use - + Die ausgewählte Datei wird bereits verwendet - + An unknown error occurred - + Ein unbekannter Fehler ist aufgetreten - - - Keys not installed - + + No firmware available + Keine Firmware verfügbar - - - Install decryption keys and restart Eden before attempting to install firmware. - + + Firmware Corrupted + Firmware beschädigt - - Select Dumped Firmware Source Location - + + Unknown applet + Unbekanntes Applet - - Select Dumped Firmware ZIP - + + Applet doesn't map to a known value. + „Applet“ lässt sich keinem bekannten Wert zuordnen. - - Zipped Archives (*.zip) - + + Record not found + Aufnahme nicht gefunden - - Firmware cleanup failed - + + Applet not found. Please reinstall firmware. + Applet nicht gefunden. Bitte installiere die Firmware neu. - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - - No firmware available - - - - - Firmware Corrupted - - - - - Unknown applet - - - - - Applet doesn't map to a known value. - - - - - Record not found - - - - - Applet not found. Please reinstall firmware. - - - - Capture Screenshot + Bildschirmfoto aufnehmen + + + + PNG Image (*.png) + PNG-Bild (*.png) + + + + TAS state: Running %1/%2 + TAS-Status: %1/%2 läuft + + + + TAS state: Recording %1 + TAS-Status: %1 wird aufgenommen + + + + TAS state: Idle %1/%2 + TAS-Status: Leerlauf %1/%2 + + + + TAS State: Invalid + TAS-Status: Ungültig + + + + &Stop Running + &Ausführung beenden + + + + Stop R&ecording + Aufnahme beenden + + + + Building: %n shader(s) + Erstellung: %n SchattiererErstelle: %n Shader + + + + Scale: %1x + %1 is the resolution scaling factor + Maßstab: 1:1 + + + + Speed: %1% / %2% + Geschwindigkeit: %1% / %2% + + + + Speed: %1% + Geschwindigkeit: %1% + + + + Game: %1 FPS + Spiel: %1 FPS + + + + Frame: %1 ms + Frame: %1 ms + + + + NO AA + Kein AA + + + + NO FX - - PNG Image (*.png) + + FX - TAS state: Running %1/%2 - + VOLUME: MUTE + LAUTSTÄRKE: STUMM - - TAS state: Recording %1 - + + VOLUME: %1% + Volume percentage (e.g. 50%) + LAUTSTÄRKE: %1% - - TAS state: Idle %1/%2 - - - - - TAS State: Invalid - - - - - &Stop Running - - - - - Stop R&ecording - - - - - Building: %n shader(s) - - - - - Scale: %1x - %1 is the resolution scaling factor - - - - - Speed: %1% / %2% - + + Derivation Components Missing + Ableitungskomponenten fehlen - Speed: %1% - - - - - Game: %1 FPS - - - - - Frame: %1 ms - - - - - - FSR - - - - - NO AA - - - - - VOLUME: MUTE - - - - - VOLUME: %1% - Volume percentage (e.g. 50%) - - - - - Derivation Components Missing - - - - Decryption keys are missing. Install them now? - + Entschlüsselungsschlüssel fehlen. Möchtest du diese jetzt installieren? - + Wayland Detected! - + Wayland erkannt! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. Would you like to force it for future launches? - + Wayland ist bekannt für erhebliche Leistungsprobleme und mysteriöse Fehler. +Es wird empfohlen, stattdessen X11 zu verwenden. + +Möchtest du dies für zukünftige Starts erzwingen? - + Use X11 - + X11 verwenden - + Continue with Wayland - + Mit Wayland fortfahren - + Don't show again - + Nicht mehr anzeigen - + Restart Required - + Neustart erforderlich - + Restart Eden to apply the X11 backend. - + Starte Eden neu, um das X11-Backend zu aktivieren. - + Slow - + Langsam - + Turbo - + Turbo - + Unlocked - + Entsperrt - + Select RomFS Dump Target - + RomFS-Ausleseziel auswählen - + Please select which RomFS you would like to dump. - + Bitte wähle aus, welches RomFS du auslesen möchtest. - + Are you sure you want to close Eden? - + Möchtest du Eden wirklich schließen? - - - + + + Eden - + Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + Möchtest du die Emulation wirklich beenden? Alle nicht gespeicherten Fortschritte gehen dabei verloren. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? - + Die derzeit ausgeführte Anwendung hat Eden angewiesen, nicht beendet zu werden. + +Möchtest du diese Anweisung ignorieren und das Programm trotzdem beenden? FXAA - + FXAA SMAA - + SMAA Nearest - + Nächstgelegen Bilinear - + Bilinear Bicubic - + Bikubisch Zero-Tangent - + Nulltangente B-Spline - + B-Spline Mitchell - + Mitchell Spline-1 - + Spline-1 Gaussian - + Gauß Lanczos - + Lanczos ScaleForce - + ScaleForce + + + + FSR + FSR Area - + Bereich MMPX - + MMPX - + + SGSR + SGSR + + + + SGSR EdgeDir + SGSR EdgeDir + + + Docked - + Angedockt - + Handheld - - - - - Fast - - - - - Balanced - + Handheld + Fast + Schnell + + + Accurate - + Genau - + Vulkan - + Vulkan - + OpenGL GLSL - + OpenGL GLSL - + OpenGL SPIRV - - - - - OpenGL GLASM - + OpenGL SPIRV + OpenGL GLASM + OpenGL GLASM + + + Null - + Null @@ -8161,7 +8292,8 @@ Would you like to bypass this and exit anyway? Linking the old directory failed. You may need to re-run with administrative privileges on Windows. OS gave error: %1 - + Das Verknüpfen des alten Verzeichnisses ist fehlgeschlagen. Unter Windows musst du den Vorgang möglicherweise mit Administratorrechten erneut ausführen. +Das Betriebssystem hat folgenden Fehler gemeldet: %1 @@ -8172,7 +8304,13 @@ If this is not desirable, delete the following files: %2 %3 %4 - + + +Bitte beachte, dass deine Konfiguration und deine Daten mit %1 geteilt werden. +Solltest du dies nicht wünschen, lösche bitte die folgenden Dateien: +%2 +%3 +%4 @@ -8180,12 +8318,15 @@ If this is not desirable, delete the following files: If you wish to clean up the files which were left in the old data location, you can do so by deleting the following directory: %1 - + + +Wenn du die Dateien bereinigen möchtest, die am alten Speicherort zurückgeblieben sind, kannst du dies tun, indem du das folgende Verzeichnis löschst: +%1 - + Data was migrated successfully. - + Die Daten wurden erfolgreich migriert. @@ -8193,12 +8334,12 @@ If you wish to clean up the files which were left in the old data location, you Import Mods - + Mods importieren The specified folder or archive contains the following mods. Select which ones to install. - + Der angegebene Ordner oder das angegebene Archiv enthält die folgenden Mods. Wähle aus, welche installiert werden sollen. @@ -8242,7 +8383,7 @@ If you wish to clean up the files which were left in the old data location, you IP Address - IP-Addresse + IP-Adresse @@ -8286,8 +8427,8 @@ If you wish to clean up the files which were left in the old data location, you Failed to update the room information. Please check your Internet connection and try hosting the room again. Debug Message: - Aktualisieren der Rauminformationen fehlgeschlagen. Überprüfe deine Internetverbindung und versuche erneut einen Raum zu erstellen. -Debug Message: + Die Rauminformationen konnten nicht aktualisiert werden. Bitte überprüfe deine Internetverbindung und versuche erneut, den Raum zu hosten. +Fehlerbehebungs-Meldung: @@ -8311,7 +8452,7 @@ Proceed anyway? You are about to close the room. Any network connections will be closed. - Du bist dabei den Raum zu schließen. Jede Verbindung zum Netzwerk wird geschlossen. + Du bist dabei, den Raum zu schließen. Alle Netzwerkverbindungen werden getrennt. @@ -8321,7 +8462,7 @@ Proceed anyway? You are about to leave the room. Any network connections will be closed. - Du bist dabei den Raum zu verlassen. Jede Verbindung zum Netzwerk wird geschlossen. + Du bist dabei, den Raum zu verlassen. Alle Netzwerkverbindungen werden geschlossen. @@ -8330,127 +8471,127 @@ Proceed anyway? New User - + Neuer Benutzer Change Avatar - + Avatar ändern Set Image - + Bild festlegen UUID - + UUID Eden - + Eden Username - + Benutzername UUID must be 32 hex characters (0-9, A-F) - + Die UUID muss aus 32 Hexadezimalzeichen bestehen (0–9, A–F) Generate - + Generieren Select User Image - + Benutzerbild auswählen Image Formats (*.jpg *.jpeg *.png *.bmp) - + Bildformate (*.jpg *.jpeg *.png *.bmp) No firmware available - + Keine Firmware verfügbar Please install the firmware to use firmware avatars. - + Installiere die Firmware, um Firmware-Avatare nutzen zu können. Error loading archive - + Fehler beim Laden des Archivs Archive is not available. Please install/reinstall firmware. - + Das Archiv ist nicht verfügbar. Bitte installiere oder aktualisiere die Firmware. Could not locate RomFS. Your file or decryption keys may be corrupted. - + RomFS konnte nicht gefunden werden. Deine Datei oder deine Entschlüsselungsschlüssel sind möglicherweise beschädigt. Error extracting archive - + Fehler beim Entpacken des Archivs Could not extract RomFS. Your file or decryption keys may be corrupted. - + RomFS konnte nicht extrahiert werden. Deine Datei oder deine Entschlüsselungsschlüssel sind möglicherweise beschädigt. Error finding image directory - + Fehler beim Finden des Bildverzeichnisses Failed to find image directory in the archive. - + Das Bildverzeichnis im Archiv konnte nicht gefunden werden. No images found - + Keine Bilder gefunden No avatar images were found in the archive. - + Im Archiv wurden keine Avatar-Bilder gefunden. All Good Tooltip - + Alles bestens Must be 32 hex characters (0-9, a-f) Tooltip - + Muss aus 32 Hexadezimalzeichen bestehen (0–9, a–f) Must be between 1 and 32 characters Tooltip - + Muss zwischen 1 und 32 Zeichen lang sein @@ -8491,73 +8632,73 @@ p, li { white-space: pre-wrap; } Form - + Formular Frametime - + Frame-Zeit 0 ms - + 0 ms Min: 0 - + Min: 0 Max: 0 - + Max: 0 Avg: 0 - + Durchschnitt: 0 FPS - + BpS 0 fps - + 0 BpS %1 fps - + %1 BpS Avg: %1 - + Durchschnitt: %1 Min: %1 - + Min: %1 Max: %1 - + Max: %1 %1 ms - + %1 ms @@ -8573,22 +8714,22 @@ p, li { white-space: pre-wrap; } Select - + Auswählen Cancel - + Abbrechen Background Color - + Hintergrundfarbe Select Firmware Avatar - + Firmware-Avatar auswählen @@ -8598,56 +8739,59 @@ p, li { white-space: pre-wrap; } Migration - + Migration Clear Shader Cache - + Shader-Cache leeren Keep Old Data - + Alte Daten behalten Clear Old Data - + Alte Daten löschen Link Old Directory - + Altes Verzeichnis verknüpfen - + + + No - + Nein You can manually re-trigger this prompt by deleting the new config directory: %1 - + Du kannst diese Aufforderung manuell erneut auslösen, indem du das neue Konfigurationsverzeichnis löschst: +%1 Migrating - + Migrieren Migrating, this may take a while... - + Die Migration läuft, das kann eine Weile dauern... @@ -8672,8 +8816,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8686,10 +8830,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8704,9 +8848,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8868,50 +9012,50 @@ p, li { white-space: pre-wrap; } [undefiniert] - + %1%2 %1%2 - + [invalid] [ungültig] - - + + %1%2Hat %3 %1%2Hat %3 - - - + + + %1%2Axis %3 %1%2Achse %3 - + %1%2Axis %3,%4,%5 %1%2Achse %3,%4,%5 - + %1%2Motion %3 %1%2Bewegung %3 - - + + %1%2Button %3 %1%2Knopf %3 - + [unused] [unbenutzt] @@ -8970,7 +9114,7 @@ p, li { white-space: pre-wrap; } Touch - Touch + Berühren @@ -9037,47 +9181,47 @@ p, li { white-space: pre-wrap; } %1 spielt %2 - + Play Time: %1 - + Spielzeit: %1 - + Never Played - + Noch nie gespielt - + Version: %1 - + Version: %1 - + Version: 1.0.0 - + Version: 1.0.0 - + Installed SD Titles Installierte SD-Titel - + Installed NAND Titles Installierte NAND-Titel - + System Titles Systemtitel - + Add New Game Directory Neues Spieleverzeichnis hinzufügen - + Favorites Favoriten @@ -9142,7 +9286,7 @@ p, li { white-space: pre-wrap; } dd/MM/yyyy - TT/MM/JJJJ + TT/MM/JJJJ @@ -9177,7 +9321,7 @@ p, li { white-space: pre-wrap; } The following amiibo data will be formatted: - Die folgenden amiibo-Daten werden formatiert: + Die folgenden Amiibo-Daten werden formatiert: @@ -9192,258 +9336,296 @@ p, li { white-space: pre-wrap; } Do you wish to restore this amiibo? - Möchtest du diese amiibo wiederherstellen? + Möchtest du diese Amiibo wiederherstellen? QtCommon::Content - + Game Requires Firmware - + Für das Spiel ist eine Firmware erforderlich - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. Installing Firmware... - + Firmware wird installiert... - - - - + + + + Cancel - + Abbrechen Firmware Install Failed - + Firmware-Installation fehlgeschlagen Firmware Install Succeeded - + Firmware-Installation erfolgreich Firmware integrity verification failed! - + Überprüfung der Firmware-Integrität fehlgeschlagen! - + Verification failed for the following files: %1 - + Die Überprüfung der folgenden Dateien ist fehlgeschlagen: + +%1 - - + + Verifying integrity... - + Integrität wird überprüft... - - + + Integrity verification succeeded! - + Die Integritätsprüfung war erfolgreich! - - + + The operation completed successfully. - + Der Vorgang wurde erfolgreich abgeschlossen. - - + + Integrity verification failed! - + Die Integritätsprüfung ist fehlgeschlagen! - + File contents may be corrupt or missing. - + Der Inhalt der Datei ist möglicherweise beschädigt oder fehlt. - + Integrity verification couldn't be performed - + Die Integritätsprüfung konnte nicht durchgeführt werden - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Die Firmware-Installation wurde abgebrochen. Die Firmware ist möglicherweise fehlerhaft oder beschädigt. Der Inhalt der Datei konnte nicht auf Gültigkeit überprüft werden. - + Select Dumped Keys Location - + Speicherort der ausgelesenen Schlüssel auswählen - + Decryption Keys install succeeded - + Installation der Entschlüsselungsschlüssel erfolgreich - + Decryption Keys install failed - + Installation der Entschlüsselungsschlüssel fehlgeschlagen - + Orphaned Profiles Detected! - + Verwaiste Profile erkannt! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + UNERWARTETE PROBLEME KÖNNTEN AUFTRETEN, WENN DU DIES NICHT LIEST!<br>Eden hat die folgenden Speicherverzeichnisse ohne zugeordnetes Profil erkannt:<br>%1<br><br>Die folgenden Profile sind gültig:<br>%2<br><br>Klicke auf „OK“, um deinen Speicherordner zu öffnen und deine Profile zu korrigieren.<br>Tipp: Kopiere den Inhalt des größten oder zuletzt geänderten Ordners an einen anderen Ort, lösche alle verwaisten Profile und verschiebe die kopierten Inhalte in das intakte Profil.<br><br>Immer noch unsicher? Lies auf der <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>Hilfeseite</a> nach.<br> - + Really clear data? - + Daten wirklich löschen? - + Important data may be lost! - + Wichtige Daten könnten verloren gehen! - + Are you REALLY sure? - + Bist du dir WIRKLICH sicher? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Sobald deine Daten gelöscht sind, können sie NICHT wiederhergestellt werden! +Führe diesen Vorgang nur durch, wenn du dir zu 100 % sicher bist, dass du diese Daten löschen möchtest. - + Clearing... - + Säuberung... - + Select Export Location - + Exportziel auswählen - + %1.zip - + %1.zip - - + + + Zipped Archives (*.zip) - + ZIP-Archive (*.zip) + + + + Exporting data. This may take a while... + Daten werden exportiert. Das kann eine Weile dauern... - Exporting data. This may take a while... - - - - Exporting - + Exportieren - + Exported Successfully - + Erfolgreich exportiert + + + + Data was exported successfully. + Die Daten wurden erfolgreich exportiert. - Data was exported successfully. - - - - Export Cancelled - + Export abgebrochen - + Export was cancelled by the user. - + Der Export wurde vom Benutzer abgebrochen. - + Export Failed - + Export fehlgeschlagen - + Ensure you have write permissions on the targeted directory and try again. - + Stelle sicher, dass du Schreibrechte für das Zielverzeichnis hast, und versuche es erneut. - + Select Import Location - + Importziel auswählen - + Import Warning - + Importwarnung - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Alle bisherigen Daten in diesem Verzeichnis werden gelöscht. Möchtest du wirklich fortfahren? + + + + Importing data. This may take a while... + Daten werden importiert. Das kann eine Weile dauern... - Importing data. This may take a while... - - - - Importing - + Importieren - + Imported Successfully - + Erfolgreich importiert + + + + Data was imported successfully. + Die Daten wurden erfolgreich importiert. - Data was imported successfully. - - - - Import Cancelled - + Import abgebrochen + + + + Import was cancelled by the user. + Der Import wurde vom Benutzer abgebrochen. - Import was cancelled by the user. - - - - Import Failed - + Import fehlgeschlagen - + Ensure you have read permissions on the targeted directory and try again. - + Stelle sicher, dass du Lesezugriff auf das Zielverzeichnis hast, und versuche es erneut. + + + + Keys not installed + Schlüssel nicht installiert + + + + Install decryption keys and restart Eden before attempting to install firmware. + Installiere die Entschlüsselungsschlüssel und starte Eden neu, bevor du versuchst, die Firmware zu installieren. + + + + Select Dumped Firmware Source Location + Speicherort der ausgelesenen Firmware auswählen + + + + Select Dumped Firmware ZIP + ZIP-Datei der ausgelesenen Firmware auswählen + + + + Firmware cleanup failed + Firmware-Bereinigung ist fehlgeschlagen + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + Der Cache für die extrahierte Firmware konnte nicht bereinigt werden. +Überprüfe die Schreibrechte im temporären Systemverzeichnis und versuche es erneut. +Vom Betriebssystem gemeldeter Fehler: %1 @@ -9451,17 +9633,17 @@ Only do this if you're 100% sure you want to delete this data. Linked Save Data - + Verknüpfte Speicherdaten Save data has been linked. - + Die Speicherdaten wurden verknüpft. Failed to link save data - + Verknüpfung der Speicherdaten fehlgeschlagen @@ -9469,76 +9651,81 @@ Only do this if you're 100% sure you want to delete this data. %1 To: %2 - + Verzeichnis konnte nicht verknüpft werden: + %1 +Nach: + %2 Already Linked - + Bereits verknüpft This title is already linked to Ryujinx. Would you like to unlink it? - + Dieser Titel ist bereits mit Ryujinx verknüpft. Möchtest du die Verknüpfung aufheben? Failed to unlink old directory - + Fehler beim Löschen des alten Verzeichnisses OS returned error: %1 - + Das Betriebssystem hat folgenden Fehler zurückgegeben: %1 Failed to copy save data - + Speicherdaten konnten nicht kopiert werden. Unlink Successful - + Verknüpfung erfolgreich aufgehoben Successfully unlinked Ryujinx save data. Save data has been kept intact. - + Ryujinx-Speicherdaten erfolgreich entknüpft. Die Speicherdaten blieben intakt. Could not find Ryujinx installation - + Ryujinx-Installation konnte nicht gefunden werden. Could not find a valid Ryujinx installation. This may typically occur if you are using Ryujinx in portable mode. Would you like to manually select a portable folder to use? - + Es konnte keine gültige Ryujinx-Installation gefunden werden. Dies tritt in der Regel auf, wenn du Ryujinx im portablen Modus verwendest. + +Möchtest du manuell einen portablen Ordner auswählen? Ryujinx Portable Location - + Standort von Ryujinx-Portabel Not a valid Ryujinx directory - + Kein gültiges Ryujinx-Verzeichnis The specified directory does not contain valid Ryujinx data. - + Das angegebene Verzeichnis enthält keine gültigen Ryujinx-Daten. Could not find Ryujinx save data - + Ryujinx-Speicherdaten konnten nicht gefunden werden. @@ -9546,17 +9733,17 @@ Would you like to manually select a portable folder to use? Error Removing Contents - + Fehler beim Entfernen der Inhalte Error Removing Update - + Fehler beim Entfernen der Aktualisierung Error Removing DLC - + Fehler beim Entfernen des DLC @@ -9566,252 +9753,254 @@ Would you like to manually select a portable folder to use? Successfully Removed - + Erfolgreich entfernt Successfully removed the installed base game. - + Das installierte Basisspiel wurde erfolgreich entfernt. The base game is not installed in the NAND and cannot be removed. - + Das Basisspiel ist nicht im NAND installiert und kann nicht entfernt werden. Successfully removed the installed update. - + Die installierte Aktualisierung wurde erfolgreich entfernt. There is no update installed for this title. - + Für diesen Titel ist keine Aktualisierung installiert. There are no DLCs installed for this title. - + Für diesen Titel sind keine DLCs installiert. Successfully removed %1 installed DLC. - + Der installierte DLC %1 wurde erfolgreich entfernt. Error Removing Transferable Shader Cache - + Fehler beim Entfernen des übertragbaren Shader-Caches A shader cache for this title does not exist. - + Für diesen Titel gibt es keinen Shader-Cache. Successfully removed the transferable shader cache. - + Der übertragbare Shader-Cache wurde erfolgreich entfernt. Failed to remove the transferable shader cache. - + Der übertragbare Shader-Cache konnte nicht entfernt werden. Error Removing Vulkan Driver Pipeline Cache - + Fehler beim Entfernen des Vulkan-Treiber-Rohrleitungs-Caches Failed to remove the driver pipeline cache. - + Der Treiber-Rohrleitungs-Cache konnte nicht gelöscht werden. Error Removing Transferable Shader Caches - + Fehler beim Entfernen übertragbarer Shader-Caches Successfully removed the transferable shader caches. - + Die übertragbaren Shader-Caches wurden erfolgreich entfernt. Failed to remove the transferable shader cache directory. - + Das Verzeichnis des übertragbaren Shader-Caches konnte nicht entfernt werden. Error Removing Custom Configuration - + Fehler beim Entfernen der benutzerdefinierten Konfiguration A custom configuration for this title does not exist. - + Für diesen Titel gibt es keine benutzerdefinierte Konfiguration. Successfully removed the custom game configuration. - + Die benutzerdefinierte Spielkonfiguration wurde erfolgreich entfernt. Failed to remove the custom game configuration. - + Die benutzerdefinierte Spielkonfiguration konnte nicht entfernt werden. Reset Metadata Cache - + Metadaten-Cache zurücksetzen The metadata cache is already empty. - + Der Metadaten-Cache ist bereits leer. The operation completed successfully. - + Der Vorgang wurde erfolgreich abgeschlossen. The metadata cache couldn't be deleted. It might be in use or non-existent. - + Der Metadaten-Cache konnte nicht gelöscht werden. Möglicherweise wird er gerade verwendet oder ist nicht vorhanden. - + Create Shortcut - + Verknüpfung erstellen - + Do you want to launch the game in fullscreen? - + Möchtest du das Spiel im Vollbildmodus starten? - + Shortcut Created - + Verknüpfung erstellt - + Successfully created a shortcut to %1 - - - - - Shortcut may be Volatile! - + Verknüpfung zu %1 erfolgreich erstellt. - This will create a shortcut to the current AppImage. This may not work well if you update. Continue? - + Shortcut may be Volatile! + Die Verknüpfung kann unzuverlässig sein! - - Failed to Create Shortcut - + + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? + Dadurch wird eine Verknüpfung zum aktuellen AppImage erstellt. Nach einer Aktualisierung funktioniert dies möglicherweise nicht mehr einwandfrei. Möchtest du fortfahren? - Failed to create a shortcut to %1 - + Failed to Create Shortcut + Verknüpfung konnte nicht erstellt werden. - - Create Icon - + + Failed to create a shortcut to %1 + Verknüpfung zu %1 konnte nicht erstellt werden. - Cannot create icon file. Path "%1" does not exist and cannot be created. - + Create Icon + Symbol erstellen - - No firmware available - + + Cannot create icon file. Path "%1" does not exist and cannot be created. + Die Icon-Datei kann nicht erstellt werden. Der Pfad „%1“ existiert nicht und kann nicht erstellt werden. - Please install firmware to use the home menu. - + No firmware available + Keine Firmware verfügbar - - Home Menu Applet - + + Please install firmware to use the home menu. + Bitte installiere die Firmware, um das Startmenü nutzen zu können. + Home Menu Applet + Startseiten-Menü-Applet + + + Home Menu is not available. Please reinstall firmware. - + Das Startmenü ist nicht verfügbar. Bitte installiere die Firmware neu. QtCommon::Mod - + Mod Name - + Mod-Name - + What should this mod be called? Wie soll diese Mod heißen? - + RomFS - + RomFS - + ExeFS/Patch - - - - - Cheat - + ExeFS/Patch + Cheat + Cheat + + + Mod Type Mod Typ - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - + Der Mod-Typ konnte nicht automatisch erkannt werden. Bitte gib den Typ des heruntergeladenen Mods manuell an. + +Die meisten Mods sind RomFS-Mods, während Patches (.pchtxt) in der Regel ExeFS-Mods sind. - - + + Mod Extract Failed - + Mod-Extraktion fehlgeschlagen - + Failed to create temporary directory %1 - + Fehler beim Erstellen des temporären Verzeichnisses %1 - + Zip file %1 is empty - + ZIP-Datei %1 ist leer @@ -9819,12 +10008,12 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. Error Opening Shader Cache - + Fehler beim Öffnen des Shader-Caches Failed to create or open shader cache for this title, ensure your app data directory has write permissions. - + Der Shader-Cache für diesen Titel konnte nicht erstellt oder geöffnet werden. Stelle sicher, dass dein App-Datenverzeichnis über Schreibrechte verfügt. @@ -9832,153 +10021,159 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. Contains game save data. DO NOT REMOVE UNLESS YOU KNOW WHAT YOU'RE DOING! - + Enthält Spielspeicherdaten. NICHT ENTFERNEN, ES SEI DENN, DU WEIßT, WAS DU TUST! Contains Vulkan and OpenGL pipeline caches. Generally safe to remove. - + Enthält Vulkan- und OpenGL-Rohrleitungs-Caches. Kann in der Regel bedenkenlos entfernt werden. Contains updates and DLC for games. - + Enthält Aktualisierungen und DLCs für Spiele. Contains firmware and applet data. - + Enthält Firmware- und Applet-Daten. Contains game mods, patches, and cheats. - + Enthält Spiel-Mods, Patches und Cheats. Decryption Keys were successfully installed - + Entschlüsselungsschlüssel wurden erfolgreich installiert Unable to read key directory, aborting - + Schlüsselverzeichnis kann nicht gelesen werden – Abbruch. One or more keys failed to copy. - + Ein oder mehrere Schlüssel konnten nicht kopiert werden. Verify your keys file has a .keys extension and try again. - + Überprüfe, ob deine Schlüsseldatei die Erweiterung .keys hat, und versuche es erneut. Decryption Keys failed to initialize. Check that your dumping tools are up to date and re-dump keys. - + Die Entschlüsselungsschlüssel konnten nicht initialisiert werden. Überprüfe, ob deine Auslesewerkzeuge auf dem neuesten Stand sind, und lies die Schlüssel erneut aus. Successfully installed firmware version %1 - + Firmware-Version %1 erfolgreich installiert Unable to locate potential firmware NCA files - + Potenzielle NCA-Dateien für die Firmware konnten nicht gefunden werden Failed to delete one or more firmware files. - + Das Löschen einer oder mehrerer Firmware-Dateien ist fehlgeschlagen. One or more firmware files failed to copy into NAND. - + Eine oder mehrere Firmware-Dateien konnten nicht in den NAND kopiert werden. Firmware installation cancelled, firmware may be in a bad state or corrupted. Restart Eden or re-install firmware. - + Die Firmware-Installation wurde abgebrochen. Die Firmware ist möglicherweise fehlerhaft oder beschädigt. Starte Eden neu oder installiere die Firmware erneut. Firmware missing. Firmware is required to run certain games and use the Home Menu. - + Firmware fehlt. Für die Ausführung bestimmter Spiele und die Nutzung des Startmenüs ist Firmware erforderlich. Firmware reported as present, but was unable to be read. Check for decryption keys and redump firmware if necessary. - + Die Firmware wurde als vorhanden gemeldet, konnte jedoch nicht gelesen werden. Überprüfe die Entschlüsselungsschlüssel und speichere die Firmware gegebenenfalls erneut. Eden has detected user data for the following emulators: - + Eden hat Benutzerdaten für die folgenden Emulatoren erkannt: Would you like to migrate your data for use in Eden? Select the corresponding button to migrate data from that emulator. This may take a while. - + Möchtest du deine Daten für die Verwendung in Eden migrieren? +Klicke auf die entsprechende Schaltfläche, um Daten aus diesem Emulator zu migrieren. +Dies kann eine Weile dauern. Clearing shader cache is recommended for all users. Do not uncheck unless you know what you're doing. - + Das Leeren des Shader-Caches wird allen Nutzern empfohlen. +Deaktiviere diese Option nur, wenn du genau weißt, was du tust. Keeps the old data directory. This is recommended if you aren't space-constrained and want to keep separate data for the old emulator. - + Behält das alte Datenverzeichnis bei. Dies wird empfohlen, wenn du nicht unter Platzmangel leidest +und die Daten für den alten Emulator separat aufbewahren möchtest. Deletes the old data directory. This is recommended on devices with space constraints. - + Löscht das alte Datenverzeichnis. +Dies wird auf Geräten mit begrenztem Speicherplatz empfohlen. Creates a filesystem link between the old directory and Eden directory. This is recommended if you want to share data between emulators. - + Erstellt eine Dateisystemverknüpfung zwischen dem alten Verzeichnis und dem Eden-Verzeichnis. +Dies wird empfohlen, wenn du Daten zwischen Emulatoren austauschen möchtest. Ryujinx title database does not exist. - + Ryujinx-Titeldatenbank existiert nicht. Invalid header on Ryujinx title database. - + Ungültiger Header in der Ryujinx-Titeldatenbank. Invalid magic header on Ryujinx title database. - + Ungültiger Magic-Header in der Ryujinx-Titeldatenbank. Invalid byte alignment on Ryujinx title database. - + Ungültige Byte-Ausrichtung in der Ryujinx-Titeldatenbank. No items found in Ryujinx title database. - + In der Ryujinx-Titel-Datenbank wurden keine Einträge gefunden. Title %1 not found in Ryujinx title database. - + Der Titel %1 wurde in der Ryujinx-Titeldatenbank nicht gefunden. @@ -10402,24 +10597,26 @@ p, li { white-space: pre-wrap; } Ryujinx Link - + Ryujinx-Verknüpfung Linking save data to Ryujinx lets both Ryujinx and Eden reference the same save files for your games. By selecting "From Eden", previous save data stored in Ryujinx will be deleted, and vice versa for "From Ryujinx". - + Wenn du deine Speicherdaten mit Ryujinx verknüpfst, können sowohl Ryujinx als auch Eden auf dieselben Speicherdateien für deine Spiele zugreifen. + +Wenn du „Von Eden“ auswählst, werden die zuvor in Ryujinx gespeicherten Daten gelöscht, und umgekehrt, wenn du „Von Ryujinx“ wählst. From Eden - + Von Eden From Ryujinx - + Von Ryujinx @@ -10429,12 +10626,12 @@ By selecting "From Eden", previous save data stored in Ryujinx will be Failed to link save data - + Verknüpfen der Speicherdaten fehlgeschlagen OS returned error: %1 - + Betriebssystem hat folgenden Fehler zurückgegeben: %1 @@ -10450,7 +10647,7 @@ By selecting "From Eden", previous save data stored in Ryujinx will be Set Play Time Data - + Spielzeitdaten festlegen @@ -10470,7 +10667,7 @@ By selecting "From Eden", previous save data stored in Ryujinx will be Total play time reached maximum. - + Die maximale Spielzeit wurde erreicht. @@ -10478,92 +10675,93 @@ By selecting "From Eden", previous save data stored in Ryujinx will be Update Available - + Aktualisierung verfügbar <a href="%1">View on Forgejo</a> - + <a href="%1">Auf Forgejo ansehen</a> Would you like to install this update? - + Möchtest du diese Aktualisierung installieren? Available Versions - + Verfügbare Versionen %1 is available for download. - - - - - New Version Location - + %1 steht zum Herunterladen bereit. + New Version Location + Speicherort der neuen Version + + + All Files (*.*) - + Alle Dateien (*.*) - - - + + + Failed to save file - + Speichern der Datei fehlgeschlagen - + Could not open file %1 for writing. - + Datei %1 konnte nicht zum Schreiben geöffnet werden. - + Downloading... - + Wird heruntergeladen... - + Cancel - + Abbrechen - + Could not write to file %1. - + Es konnte nicht in die Datei %1 geschrieben werden. - + Could not commit to file %1. - + Änderungen konnten nicht für Datei %1 übernommen werden. - + Failed to download file - + Herunterladen der Datei fehlgeschlagen - + Could not download from %1%2 Error code: %3 - + Das Herunterladen von %1%2 konnte nicht durchgeführt werden +Fehler-Code: %3 - + Download Complete - + Download abgeschlossen - + Successfully downloaded %1. Would you like to open it? - + %1 wurde erfolgreich heruntergeladen. Möchtest du die Datei öffnen? \ No newline at end of file diff --git a/dist/languages/el.ts b/dist/languages/el.ts index ba849128fa..b3ec9c6f5e 100644 --- a/dist/languages/el.ts +++ b/dist/languages/el.ts @@ -211,155 +211,6 @@ This would ban both their forum username and their IP address. %1 - %2 (%3/%4 μέλη) - συνδεμένα - - CompatDB - - - Report Compatibility - Αναφορά Συμβατότητας - - - - - - - - - - Report Game Compatibility - Αναφορά Συμβατότητας Παιχνιδιού - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - - - - - <html><head/><body><p>Does the game boot?</p></body></html> - Το παιχνίδι ξεκινάει; - - - - Yes The game starts to output video or audio - Ναι, το παιχνίδι αρχίζει να εμφανίζει βίντεο ή ήχο. - - - - No The game doesn't get past the "Launching..." screen - - - - - Yes The game gets past the intro/menu and into gameplay - - - - - No The game crashes or freezes while loading or using the menu - - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - - - - - Yes The game works without crashes - Ναι, το παιχνίδι λειτουργεί χωρίς να κρασάρει. - - - - No The game crashes or freezes during gameplay - Όχι, το παιχνίδι κρασάρει ή παγώνει κατά τη διάρκεια του παιχνιδιού. - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - Λειτουργεί το παιχνίδι χωρίς να κρασάρει, να παγώνει ή να κολλάει κατά τη διάρκεια του παιχνιδιού; - - - - Yes The game can be finished without any workarounds - - - - - No The game can't progress past a certain area - - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - - - - - Major The game has major graphical errors - - - - - Minor The game has minor graphical errors - Ελάχιστα Το παιχνίδι έχει ελάχιστα γραφικά σφάλματα - - - - None Everything is rendered as it looks on the Nintendo Switch - Κανένα Όλα απεικονίζονται όπως φαίνονται στο Nintendo Switch - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>Έχει το παιχνίδι οποιαδήποτε γραφική ατέλεια; - - - - Major The game has major audio errors - - - - - Minor The game has minor audio errors - - - - - None Audio is played perfectly - - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - - - - - Thank you for your submission! - Ευχαριστούμε για την συμμετοχή! - - - - Submitting - Υποβάλλεται - - - - Communication error - Σφάλμα επικοινωνίας - - - - An error occurred while sending the Testcase - Προέκυψε ένα σφάλμα κατά την αποστολή της Υπόθεσης Τεστ - - - - Next - Επόμενο - - ConfigurationShared @@ -559,231 +410,241 @@ Can help reduce stuttering at lower framerates. - + Backend: - CPU Overclock + CPU Clocks - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. - + Custom CPU Ticks - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. - + Enable Host MMU Emulation (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. - + Unfuse FMA (improve performance on CPUs without FMA) Αχρησιμοποίητο FMA (βελτιώνει την απόδοση σε επεξεργαστές χωρίς FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. - + Faster FRSQRTE and FRECPE Ταχύτερη FRSQRTE και FRECPE - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. - + Faster ASIMD instructions (32 bits only) Ταχύτερες οδηγίες ASIMD (μόνο 32 bits) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. - + Inaccurate NaN handling Ανακριβής χειρισμός NaN - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. - + Disable address space checks Απενεργοποίηση ελέγχου χώρου διευθύνσεων - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. - + Ignore global monitor Αγνοήση καθολικής επίβλεψης - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. - + API: API: - + Changes the output graphics API. Vulkan is recommended. - + Device: Συσκευή: - + This setting selects the GPU to use (Vulkan only). - + Resolution: Ανάλυση: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. - + Window Adapting Filter: Φίλτρο Προσαρμογής Παραθύρου: - + FSR Sharpness: - - Determines how sharpened the image will look using FSR's dynamic contrast. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. - + Anti-Aliasing Method: Μέθοδος Anti-Aliasing: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Λειτουργία Πλήρους Οθόνης: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. - + Aspect Ratio: Αναλογία Απεικόνισης: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. - + Use persistent pipeline cache - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. - + Use asynchronous GPU emulation - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + NVDEC emulation: Εξομοίωση NVDEC: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. - + ASTC Decoding Method: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -792,55 +653,75 @@ stuttering but may present artifacts. - + ASTC Recompression Method: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. - + Skip CPU Inner Invalidation - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -848,1361 +729,1371 @@ Immediate (no synchronization) presents whatever is available and can exhibit te - + Sync Memory Operations - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. - + Enable asynchronous presentation (Vulkan only) - + Slightly improves performance by moving presentation to a separate CPU thread. - + Force maximum clocks (Vulkan only) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. - + Anisotropic Filtering: Ανισοτροπικό Φιλτράρισμα: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. - + GPU Mode: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - + DMA Accuracy: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. - + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation - + May reduce shader stutter. - - Fast GPU Time + + GPU Clocks - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. - + GPU Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. - + Enable Compute Pipelines (Intel Vulkan Only) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. - + Enable Reactive Flushing - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. - + Sync to framerate of video playback - + Run the game at normal speed during video playback, even when the framerate is unlocked. - + Barrier feedback loops - + Improves rendering of transparency effects in specific games. - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. - + Vertex Input Dynamic State - + Enables vertex input dynamic state feature for better quality and performance. - + Sample Shading - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. - + RNG Seed RNG Seed - + Controls the seed of the random number generator. Mainly used for speedrunning. - + Device Name - + The name of the console. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: - + This option allows to change the clock of the console. Can be used to manipulate time in games. - + The number of seconds from the current unix time - + Language: - + This option can be overridden when region setting is auto-select - + Region: Περιφέρεια: - + The region of the console. - + Time Zone: Ζώνη Ώρας: - + The time zone of the console. - + Sound Output Mode: - + Console Mode: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. - + Prompt for user profile on boot - + Useful if multiple people use the same PC. - + Pause when not in focus - + Pauses emulation when focusing on other windows. - + Confirm before stopping emulation - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. - + Hide mouse on inactivity Απόκρυψη δρομέα ποντικιού στην αδράνεια - + Hides the mouse after 2.5s of inactivity. - - Disable controller applet - - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - - - - + Check for updates - + Whether or not to check for updates upon startup. - + Enable Gamemode - + Force X11 as Graphics Backend - + Custom frontend - + Real applet - + + + Disabled + + + + Never - + On Load - + Always - + CPU CPU - + GPU - + CPU Asynchronous - + Uncompressed (Best quality) - + BC1 (Low quality) - + BC3 (Medium quality) - - + + Auto Αυτόματη - + 30 FPS - + 60 FPS - + 90 FPS - + 120 FPS - + Conservative - + Aggressive - + Vulkan Vulkan - + OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) - + Null - + Fast - - Balanced - - - - - + + + Accurate Ακριβής - - + + + Default Προεπιλεγμένο - + Unsafe (fast) - + Safe (stable) - - - Unsafe - Επισφαλής - - Paranoid (disables most optimizations) + Immediate + Balanced + + + + + Unsafe + Επισφαλής + + + + Paranoid (disables most optimizations) + + + + Debugging - + Dynarmic - + NCE - + Borderless Windowed Παραθυροποιημένο Χωρίς Όρια - + Exclusive Fullscreen Αποκλειστική Πλήρης Οθόνη - + No Video Output Χωρίς Έξοδο Βίντεο - + CPU Video Decoding Αποκωδικοποίηση Βίντεο CPU - + GPU Video Decoding (Default) Αποκωδικοποίηση Βίντεο GPU (Προεπιλογή) - + 0.25X (180p/270p) [EXPERIMENTAL] - + 0.5X (360p/540p) [EXPERIMENTAL] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [ΠΕΙΡΑΜΑΤΙΚΟ] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) - + 8X (5760p/8640p) - + Nearest Neighbor Πλησιέστερος Γείτονας - + Bilinear Διγραμμικό - + Bicubic Δικυβικό - + Gaussian Gaussian - + Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution - + Area - + MMPX - + Zero-Tangent - + B-Spline - + Mitchell - + Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None Κανένα - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Προεπιλογή (16:9) - + Force 4:3 Επιβολή 4:3 - + Force 21:9 Επιβολή 21:9 - + Force 16:10 Επιβολή 16:10 - + Stretch to Window Επέκταση στο Παράθυρο - + Automatic Αυτόματα - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - - - - - 64x - - - - + Japanese (日本語) Ιαπωνικά (日本語) - + American English - + French (français) Γαλλικά (Français) - + German (Deutsch) Γερμανικά (Deutsch) - + Italian (italiano) Ιταλικά (Italiano) - + Spanish (español) Ισπανικά (Español) - + Chinese Κινέζικα - + Korean (한국어) Κορεάτικα (한국어) - + Dutch (Nederlands) Ολλανδικά (Nederlands) - + Portuguese (português) Πορτογαλικά (Português) - + Russian (Русский) Ρώσικα (Русский) - + Taiwanese Ταϊβανέζικα - + British English Βρετανικά Αγγλικά - + Canadian French Καναδικά Γαλλικά - + Latin American Spanish Λατινοαμερικάνικα Ισπανικά - + Simplified Chinese Απλοποιημένα Κινέζικα - + Traditional Chinese (正體中文) Παραδοσιακά Κινέζικα (正體中文) - + Brazilian Portuguese (português do Brasil) Πορτογαλικά Βραζιλίας (Português do Brasil) - + Polish (polska) - + Thai (แบบไทย) - - + + Japan Ιαπωνία - + USA ΗΠΑ - + Europe Ευρώπη - + Australia Αυστραλία - + China Κίνα - + Korea Κορέα - + Taiwan Ταϊβάν - + Auto (%1) Auto select time zone - + Default (%1) Default time zone - + CET CET - + CST6CDT CST6CDT - + Cuba Κούβα - + EET EET - + Egypt Αίγυπτος - + Eire - + EST EST - + EST5EDT EST5EDT - + GB - + GB-Eire - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Γκρήνουιτς - + Hongkong Χονγκ Κονγκ - + HST HST - + Iceland Ισλανδία - + Iran Ιράν - + Israel Ισραήλ - + Jamaica Ιαμαϊκή - + Kwajalein - + Libya Λιβύη - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Ναβάχο - + NZ - + NZ-CHAT - + Poland Πολωνία - + Portugal Πορτογαλία - + PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Σιγκαπούρη - + Turkey Τουρκία - + UCT UCT - + Universal Παγκόσμια - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu - + Mono Μονοφωνικό - + Stereo Στέρεοφωνικό - + Surround - + 4GB DRAM (Default) - + 6GB DRAM (Unsafe) - + 8GB DRAM - + 10GB DRAM (Unsafe) - + 12GB DRAM (Unsafe) - + Docked Docked - + Handheld Handheld - - - Off + + + Normal - - Boost (1700MHz) + + + Boost - - Fast (2000MHz) + + + Overclock - + Always ask (Default) - + Only if game specifies not to stop - + Never ask - - - Medium (256) - - - - - - High (512) - - - - + Very Small (16 MB) - + Small (32 MB) - + Normal (128 MB) - + Large (256 MB) - + Very Large (512 MB) - + Very Low (4 MB) - + Low (8 MB) - + Normal (16 MB) - + Medium (32 MB) - + High (64 MB) - + Very Low (32) - + Low (64) - + Normal (128) - - Disabled + + Medium (256) - + + High (512) + + + + ExtendedDynamicState 1 - + ExtendedDynamicState 2 - + ExtendedDynamicState 3 - + Tree View - + Grid View @@ -2551,252 +2442,297 @@ When a program attempts to open the controller applet, it is immediately closed. Παγκόσμιο φίλτρο αρχείου καταγραφής - - When checked, the max size of the log increases from 100 MB to 1 GB - Όταν επιλεγεί, το μέγιστο μέγεθος του αρχείου καταγραφής αυξάνεται από 100 MB σε 1 GB + + GPU Logging/Level + - - Enable Extended Logging** - Ενεργοποίηση Εκτεταμένης Καταγραφής** + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console - + + When checked, the max size of the log increases from 100 MB to 1 GB + Όταν επιλεγεί, το μέγιστο μέγεθος του αρχείου καταγραφής αυξάνεται από 100 MB σε 1 GB + + + + Enable Extended Logging** + Ενεργοποίηση Εκτεταμένης Καταγραφής** + + + Open Log Location Άνοιγμα θέσης του αρχείου καταγραφής - + Homebrew Σπιτικό - + Arguments String - + Graphics Γραφικά - + When checked, it executes shaders without loop logic changes Όταν είναι επιλεγμένο, εκτελεί shaders χωρίς αλλαγές στη λογική του βρόχου - + Disable Loop safety checks Απενεργοποίηση των ελέγχων ασφαλείας βρόχου - + When checked, it will dump all the macro programs of the GPU - + Dump Maxwell Macros - + When checked, it enables Nsight Aftermath crash dumps Όταν είναι επιλεγμένο, ενεργοποιεί την ένδειξη σφαλμάτων Nsight Aftermath - + Enable Nsight Aftermath Ενεργοποίηση του Nsight Aftermath - - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Όταν επιλεγεί, θα απορρίψει όλους τους αρχικούς assembler shaders από την κρυφή μνήμη ή το παιχνίδι σκίασης δίσκου όπως βρέθηκε - - - - Dump Game Shaders - - - - - Enable Renderdoc Hotkey - - - - - When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower - Όταν είναι επιλεγμένο, απενεργοποιεί τη μακροεντολή Just In Time compiler. Η ενεργοποίηση αυτού κάνει τα παιχνίδια να τρέχουν πιο αργά - - - - Disable Macro JIT - Απενεργοποίηση του Macro JIT - - - - When checked, it disables the macro HLE functions. Enabling this makes games run slower - - - - - Disable Macro HLE - - - - - When checked, the graphics API enters a slower debugging mode - Όταν είναι επιλεγμένο, το API γραφικών εισέρχεται σε μια πιο αργή λειτουργία εντοπισμού σφαλμάτων - - - - Enable Graphics Debugging - Ενεργοποίηση του Εντοπισμού Σφαλμάτων Γραφικών - - - - When checked, yuzu will log statistics about the compiled pipeline cache - - - Enable Shader Feedback - Ενεργοποίηση Shader Feedback + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + - - <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + Dump Guest (Maxwell) Shaders - Disable Buffer Reorder + Enable Renderdoc Hotkey + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower + Όταν είναι επιλεγμένο, απενεργοποιεί τη μακροεντολή Just In Time compiler. Η ενεργοποίηση αυτού κάνει τα παιχνίδια να τρέχουν πιο αργά + + + + Disable Macro JIT + Απενεργοποίηση του Macro JIT + + + + When checked, it disables the macro HLE functions. Enabling this makes games run slower + + + + + Disable Macro HLE + + + + + When checked, the graphics API enters a slower debugging mode + Όταν είναι επιλεγμένο, το API γραφικών εισέρχεται σε μια πιο αργή λειτουργία εντοπισμού σφαλμάτων + + + + Enable Graphics Debugging + Ενεργοποίηση του Εντοπισμού Σφαλμάτων Γραφικών + + + + When checked, yuzu will log statistics about the compiled pipeline cache + + + + + Enable Shader Feedback + Ενεργοποίηση Shader Feedback + + + + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + + + + Disable Buffer Reorder + + + + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced Προχωρημένα - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Επιτρέπει στο yuzu να ελέγχει για ένα λειτουργικό περιβάλλον Vulkan κατά την εκκίνηση του προγράμματος. Απενεργοποιήστε το αν αυτό προκαλεί προβλήματα με τα εξωτερικά προγράμματα που βλέπουν το yuzu. - + Perform Startup Vulkan Check Εκτέλεση ελέγχου Vulkan κατά την εκκίνηση - + Disable Web Applet - + Enable All Controller Types - + Enable Auto-Stub - + Kiosk (Quest) Mode - + Use dev.keys - + Enable Debug Asserts Ενεργοποίηση Βεβαιώσεων Εντοπισμού Σφαλμάτων - + Debugging Εντοπισμός Σφαλμάτων - + Battery Serial: - + + Debug Knobs: + + + + Bitmask for quick development toggles - + Set debug knobs (bitmask) - + 16-bit debug knob set for quick development toggles - + (bitmask) - - Debug Knobs: - - - - + Unit Serial: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. - + Dump Audio Commands To Console** - + Flush log output on each line - + Enable FS Access Log - + Enable Verbose Reporting Services** - + Censor username in logs - + **This will be reset automatically when Eden closes. @@ -3246,33 +3182,38 @@ Would you like to delete the old save data? Χρώμα Φόντου: - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off - + VSync Off - + Recommended - + On - + VSync On @@ -3793,6 +3734,16 @@ Would you like to delete the old save data? Motion / Touch + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4302,58 +4253,58 @@ To invert the axes, first move your joystick vertically, and then horizontally.< [αναμονή] - + New Profile Νέο Προφίλ - + Enter a profile name: Εισαγάγετε ένα όνομα προφίλ: - - + + Create Input Profile Δημιουργία Προφίλ Χειρισμού - + The given profile name is not valid! Το όνομα του προφίλ δεν είναι έγκυρο! - + Failed to create the input profile "%1" Η δημιουργία του προφίλ χειρισμού "%1" απέτυχε - + Delete Input Profile Διαγραφή Προφίλ Χειρισμού - + Failed to delete the input profile "%1" Η διαγραφή του προφίλ χειρισμού "%1" απέτυχε - + Load Input Profile Φόρτωση Προφίλ Χειρισμού - + Failed to load the input profile "%1" Η φόρτωση του προφίλ χειρισμού "%1" απέτυχε - + Save Input Profile Αποθήκευση Προφίλ Χειρισμού - + Failed to save the input profile "%1" Η αποθήκευση του προφίλ χειρισμού "%1" απέτυχε @@ -4401,7 +4352,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Configure Διαμόρφωση @@ -4432,7 +4383,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Test Τεστ @@ -4447,77 +4398,77 @@ To invert the axes, first move your joystick vertically, and then horizontally.< Κατάργηση Διακομιστή - + %1:%2 %1:%2 - - - - - - + + + + + + Eden - + Port number has invalid characters Ο αριθμός θύρας έχει μη έγκυρους χαρακτήρες - + Port has to be in range 0 and 65353 Η θύρα πρέπει να ανήκει στο εύρος 0 και 65353 - + IP address is not valid Η διεύθυνση IP δεν είναι έγκυρη - + This UDP server already exists Αυτός ο διακομιστής UDP υπάρχει ήδη - + Unable to add more than 8 servers Δεν είναι δυνατή η προσθήκη περισσότερων από 8 διακομιστών - + Testing Δοκιμή - + Configuring Διαμόρφωση - + Test Successful Τεστ Επιτυχές - + Successfully received data from the server. Λήφθηκαν με επιτυχία δεδομένα από τον διακομιστή. - + Test Failed Η Δοκιμή Απέτυχε - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Δεν ήταν δυνατή η λήψη έγκυρων δεδομένων από τον διακομιστή.<br>Βεβαιωθείτε ότι ο διακομιστής έχει ρυθμιστεί σωστά και ότι η διεύθυνση και η θύρα είναι σωστές. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. Η δοκιμή UDP ή η διαμόρφωση βαθμονόμησης είναι σε εξέλιξη.<br>Παρακαλώ περιμένετε να τελειώσουν. @@ -4701,57 +4652,57 @@ Current values are %1% and %2% respectively. - + Add-Ons Πρόσθετα - + System Σύστημα - + CPU CPU - + Graphics Γραφικά - + Adv. Graphics Προχ. Γραφικά - + Ext. Graphics - + Audio Ήχος - + Input Profiles - + Network - + Applets - + Properties Ιδιότητες @@ -4874,6 +4825,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5439,91 +5514,86 @@ Drag points to change position, or double-click table cells to edit values. - Show Compatibility List - - - - Show Add-Ons Column - + Show Size Column - + Show File Types Column - + Show Play Time Column - + Folder Icon Size: - + Row 1 Text: - + Row 2 Text: - + Screenshots Στιγμιότυπα - + Ask Where To Save Screenshots (Windows Only) - + Screenshots Path: - + ... ... - + TextLabel - + Resolution: Ανάλυση: - + Select Screenshots Path... - + <System> <System> - + English Αγγλικά - + Auto (%1 x %2, %3 x %4) Screenshot width value @@ -5760,7 +5830,7 @@ Drag points to change position, or double-click table cells to edit values. - + Calculating... @@ -5962,50 +6032,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! Το OpenGL δεν είναι διαθέσιμο! - + OpenGL shared contexts are not supported. - + Eden has not been compiled with OpenGL support. - - - + + + Error while initializing OpenGL! Σφάλμα κατα την αρχικοποίηση του OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. - + Error while initializing OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 - + This build doesn't have OpenGL support. @@ -6013,279 +6083,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory - + Favorite Αγαπημένο - + Start Game Έναρξη παιχνιδιού - + Start Game without Custom Configuration - + Open Save Data Location Άνοιγμα Τοποθεσίας Αποθήκευσης Δεδομένων - + Open Mod Data Location Άνοιγμα Τοποθεσίας Δεδομένων Mod - + Open Transferable Pipeline Cache - + Link to Ryujinx - + Remove Αφαίρεση - + Remove Installed Update Αφαίρεση Εγκατεστημένης Ενημέρωσης - + Remove All Installed DLC Αφαίρεση Όλων των Εγκατεστημένων DLC - + Remove Custom Configuration - + Remove Cache Storage - + Remove OpenGL Pipeline Cache - + Remove Vulkan Pipeline Cache - + Remove All Pipeline Caches Καταργήστε Όλη την Κρυφή μνήμη του Pipeline - + Remove All Installed Contents Καταργήστε Όλο το Εγκατεστημένο Περιεχόμενο - + Manage Play Time - + Edit Play Time Data - + Remove Play Time Data - - + + Dump RomFS Απόθεση του RomFS - + Dump RomFS to SDMC Απόθεση του RomFS στο SDMC - + Verify Integrity - + Copy Title ID to Clipboard Αντιγραφή του Title ID στο Πρόχειρο - - Navigate to GameDB entry - Μεταβείτε στην καταχώρηση GameDB - - - + Create Shortcut - + Add to Desktop - + Add to Applications Menu - + Configure Game - + Scan Subfolders Σκανάρισμα Υποφακέλων - + Remove Game Directory Αφαίρεση Φακέλου Παιχνιδιών - + ▲ Move Up ▲ Μετακίνηση Επάνω - + ▼ Move Down ▼ Μετακίνηση Κάτω - + Open Directory Location Ανοίξτε την Τοποθεσία Καταλόγου - + Clear Καθαρισμός - - - Name - Όνομα - - - - Compatibility - Συμβατότητα - - - - Add-ons - Πρόσθετα - - - - File type - Τύπος αρχείου - - - - Size - Μέγεθος - - - - Play time - - GameListItemCompat - + Ingame - + Game starts, but crashes or major glitches prevent it from being completed. - + Perfect Τέλεια - + Game can be played without issues. - + Playable - + Game functions with minor graphical or audio glitches and is playable from start to finish. - + Intro/Menu Εισαγωγή/Μενου - + Game loads, but is unable to progress past the Start Screen. - + Won't Boot Δεν ξεκινά - + The game crashes when attempting to startup. Το παιχνίδι διακόπτεται κατά την προσπάθεια εκκίνησης. - + Not Tested Μη Τεσταρισμένο - + The game has not yet been tested. Το παιχνίδι δεν έχει ακόμα τεσταριστεί. + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list Διπλο-κλικ για προσθήκη νεου φακέλου στη λίστα παιχνιδιών @@ -6293,17 +6356,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) - + Filter: Φίλτρο: - + Enter pattern to filter Εισαγάγετε μοτίβο για φιλτράρισμα @@ -6797,1139 +6860,1112 @@ Debug Message: - + Game &Icon Size - + Reset Window Size to &720p - + Reset Window Size to 720p - + Reset Window Size to &900p - + Reset Window Size to 900p - + Reset Window Size to &1080p - + Reset Window Size to 1080p - + &Multiplayer &Πολλαπλών Παικτών - + &Tools - + Am&iibo - + Launch &Applet - + &TAS &TAS - + &Create Home Menu Shortcut - + Install &Firmware - + &Help - + &Install Files to NAND... - + L&oad File... - + Load &Folder... - + E&xit - - + + &Pause &Παύση - + &Stop &Σταμάτημα - + &Verify Installed Contents - + &About Eden - + Single &Window Mode - + Con&figure... - + Ctrl+, - + Enable Overlay Display Applet - + Show &Filter Bar - + Show &Status Bar - + Show Status Bar - + &Browse Public Game Lobby &Περιήγηση σε δημόσιο λόμπι παιχνιδιού - + &Create Room &Δημιουργία δωματίου - + &Leave Room &Αποχωρήσει από το δωμάτιο - + &Direct Connect to Room &Άμεση σύνδεση σε Δωμάτιο - + &Show Current Room &Εμφάνιση τρέχοντος δωματίου - + F&ullscreen - + &Restart - + Load/Remove &Amiibo... - - &Report Compatibility - - - - + Open &Mods Page - - Open &Quickstart Guide + + &User Handbook - - &FAQ - - - - + &Capture Screenshot - + &Album - + &Set Nickname and Owner - + &Delete Game Data - + &Restore Amiibo - + &Format Amiibo - + &Mii Editor - + &Configure TAS... - + Configure C&urrent Game... - - + + &Start &Έναρξη - + &Reset - - + + R&ecord - + Open &Controller Menu - + Install Decryption &Keys - + &Home Menu - + &Desktop - + &Application Menu - + &Root Data Folder - + &NAND Folder - + &SDMC Folder - + &Mod Folder - + &Log Folder - + From Folder - + From ZIP - + &Eden Dependencies - + &Data Manager - + &Tree View - + &Grid View - + Game Icon Size - - + + None - + Show Game &Name - + Show &Performance Overlay - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) - + Standard (64x64) - + Large (128x128) - + Full Size (256x256) - + Broken Vulkan Installation Detected - + Vulkan initialization failed during boot. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping - + Loading Web Applet... - - + + Disable Web Applet - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + The amount of shaders currently being built - + The current selected resolution scaling multiplier. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Unmute - + Mute - + Reset Volume - + + Configure Effects... + + + + &Clear Recent Files - + &Continue - + Warning: Outdated Game Format - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - - + + Error while loading ROM! - + The ROM format is not supported. - + An error occurred initializing the video core. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + An unknown error occurred. Please see the log for more details. - + + (64-bit) - + + (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + Closing software... - + Save Data - + Mod Data - + Error Opening %1 Folder - - + + Folder does not exist! - + Remove Installed Game Contents? - + Remove Installed Game Update? - + Remove Installed Game DLC? - + Remove Entry - + Delete OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? - + Remove Custom Game Configuration? - + Remove Cache Storage? - + Remove File - + Remove Play Time Data - + Reset play time? - - + + RomFS Extraction Failed! - + There was an error copying the RomFS files or the user cancelled the operation. - + Full - + Skeleton - + Select RomFS Dump Mode - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Extracting RomFS... - - + + Cancel - + RomFS Extraction Succeeded! - + The operation completed successfully. - + Error Opening %1 - + Select Directory - + Properties - + The game properties could not be loaded. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Load File - + Open Extracted ROM Directory - + Invalid Directory Selected - + The directory you have selected does not contain a 'main' file. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files - + %n file(s) remaining - + Installing file "%1"... - - + + Install Results - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + %n file(s) were newly installed - + %n file(s) were overwritten - + %n file(s) failed to install - + System Application - + System Archive - + System Application Update - + Firmware Package (Type A) - + Firmware Package (Type B) - + Game - + Game Update - + Game DLC - + Delta Title - + Select NCA Install Type... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Failed to Install - + The title type you selected for the NCA is invalid. - + File not found - + File "%1" not found - + OK - + Function Disabled - + Compatibility list reporting is currently disabled. Check back later! - + Error opening URL - + Unable to open the URL "%1". - + TAS Recording - + Overwrite file of player 1? - + Invalid config detected - + Handheld controller can't be used on docked mode. Pro controller will be selected. - - + + Amiibo - - + + The current amiibo has been removed - + Error - - + + The current game is not looking for amiibos - + Amiibo File (%1);; All Files (*.*) - + Load Amiibo - + Error loading Amiibo data - + The selected file is not a valid amiibo - + The selected file is already on use - + An unknown error occurred - - - Keys not installed - - - - - - Install decryption keys and restart Eden before attempting to install firmware. - - - - - Select Dumped Firmware Source Location - - - - - Select Dumped Firmware ZIP - - - - - Zipped Archives (*.zip) - - - - - Firmware cleanup failed - - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available - + Firmware Corrupted - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot - + PNG Image (*.png) - + TAS state: Running %1/%2 - + TAS state: Recording %1 - + TAS state: Idle %1/%2 - + TAS State: Invalid - + &Stop Running - + Stop R&ecording - + Building: %n shader(s) - + Scale: %1x %1 is the resolution scaling factor - + Speed: %1% / %2% - + Speed: %1% - + Game: %1 FPS - + Frame: %1 ms - - - FSR - - - - + NO AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE - + VOLUME: %1% Volume percentage (e.g. 50%) - + Derivation Components Missing - + Decryption keys are missing. Install them now? - + Wayland Detected! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -7937,74 +7973,74 @@ Would you like to force it for future launches? - + Use X11 - + Continue with Wayland - + Don't show again - + Restart Required - + Restart Eden to apply the X11 backend. - + Slow - + Turbo - + Unlocked - + Select RomFS Dump Target - + Please select which RomFS you would like to dump. - + Are you sure you want to close Eden? - - - + + + Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8070,6 +8106,11 @@ Would you like to bypass this and exit anyway? ScaleForce + + + FSR + + Area @@ -8081,52 +8122,57 @@ Would you like to bypass this and exit anyway? - + + SGSR + + + + + SGSR EdgeDir + + + + Docked - + Handheld - + Fast - - Balanced - - - - + Accurate - + Vulkan - + OpenGL GLSL - + OpenGL SPIRV - + OpenGL GLASM - + Null @@ -8159,7 +8205,7 @@ If you wish to clean up the files which were left in the old data location, you - + Data was migrated successfully. @@ -8644,8 +8690,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8658,10 +8704,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8676,9 +8722,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8840,50 +8886,50 @@ p, li { white-space: pre-wrap; } - + %1%2 - + [invalid] - - + + %1%2Hat %3 - - - + + + %1%2Axis %3 - + %1%2Axis %3,%4,%5 - + %1%2Motion %3 - - + + %1%2Button %3 - + [unused] [άδειο] @@ -9009,47 +9055,47 @@ p, li { white-space: pre-wrap; } %1 παίζει %2 - + Play Time: %1 - + Never Played - + Version: %1 - + Version: 1.0.0 - + Installed SD Titles - + Installed NAND Titles - + System Titles Τίτλοι Συστήματος - + Add New Game Directory Προσθήκη Νέας Τοποθεσίας Παιχνιδιών - + Favorites Αγαπημένα @@ -9170,13 +9216,13 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. @@ -9186,10 +9232,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9210,213 +9256,246 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9660,72 +9739,72 @@ Would you like to manually select a portable folder to use? - + Create Shortcut - + Do you want to launch the game in fullscreen? - + Shortcut Created - + Successfully created a shortcut to %1 - + Shortcut may be Volatile! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? - + Failed to Create Shortcut - + Failed to create a shortcut to %1 - + Create Icon - + Cannot create icon file. Path "%1" does not exist and cannot be created. - + No firmware available - + Please install firmware to use the home menu. - + Home Menu Applet - + Home Menu is not available. Please reinstall firmware. @@ -9733,55 +9812,55 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name - + What should this mod be called? - + RomFS - + ExeFS/Patch - + Cheat - + Mod Type - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty @@ -10469,65 +10548,65 @@ By selecting "From Eden", previous save data stored in Ryujinx will be - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/es.ts b/dist/languages/es.ts index 115ca93dc6..8cda19bc6b 100644 --- a/dist/languages/es.ts +++ b/dist/languages/es.ts @@ -218,155 +218,6 @@ Esto vetará su nombre de usuario del foro y su dirección IP. %1 - %2 (%3/%4 miembros) - conectado - - CompatDB - - - Report Compatibility - Informar de compatibilidad - - - - - - - - - - Report Game Compatibility - Informar de compatibilidad con el juego - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - <html><head/><body><p><span style=" font-size:10pt;">Si elijes entregar un caso de prueba a la </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">lista de compatibilidad de Eden</span></a><span style=" font-size:10pt;">, Se recopilará y mostrará la siguiente información en el sito:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Información del hardware (CPU / GPU / Sistema operativo)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qué version de Eden estás usando</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">La cuenta de eden que está conectada </li></ul></body></html> - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>¿El juego se inicia?</p></body></html> - - - - Yes The game starts to output video or audio - Sí El juego empieza a reproducir vídeo o audio - - - - No The game doesn't get past the "Launching..." screen - No El juego no consigue avanzar más allá de la pantalla de "Iniciando..." - - - - Yes The game gets past the intro/menu and into gameplay - Sí El juego avanza del menú/introducción y entra al juego - - - - No The game crashes or freezes while loading or using the menu - No El juego se bloquea o se congela al cargar o al usar el menú - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>¿El juego llega a ser jugable?</p></body></html> - - - - Yes The game works without crashes - Sí El juego funciona sin errores - - - - No The game crashes or freezes during gameplay - No El juego se bloquea o se congela durante la partida - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>¿El juego funciona sin que se interrumpa, se congele o se bloquee durante la partida?</p></body></html> - - - - Yes The game can be finished without any workarounds - Sí El juego se puede terminar sin ningún tipo de solución temporal. - - - - No The game can't progress past a certain area - No El juego no puede avanzar más allá de una zona concreta - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>¿El juego es completamente jugable de principio a fin?</p></body></html> - - - - Major The game has major graphical errors - Importantes El juego tiene errores gráficos importantes - - - - Minor The game has minor graphical errors - Menores El juego tiene pequeños errores gráficos - - - - None Everything is rendered as it looks on the Nintendo Switch - Ninguno Todo está renderizado conforme se muestra en la Nintendo Switch - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>¿El juego tiene algún error gráfico?</p></body></html> - - - - Major The game has major audio errors - Importantes El juego tiene bastantes problemas de audio - - - - Minor The game has minor audio errors - Menores El juego tiene pequeños problemas de audio - - - - None Audio is played perfectly - Ninguno El audio se reproduce perfectamente - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>¿El juego tiene algún problema de audio o faltan efectos de sonido?</p></body></html> - - - - Thank you for your submission! - Gracias por tu contribución. - - - - Submitting - Enviando - - - - Communication error - Error de comunicación - - - - An error occurred while sending the Testcase - Ha ocurrido un error mientras se enviaba el caso de prueba - - - - Next - Siguiente - - ConfigurationShared @@ -556,8 +407,8 @@ Desactivarlo hará que el juego se renderice lo más rápido posible según tu o Synchronizes CPU core speed with the game's maximum rendering speed to boost FPS without affecting game speed (animations, physics, etc.). Can help reduce stuttering at lower framerates. - Sincroniza la velocidad del núcleo de la CPU con la velocidad máxima de renderizado del juego para aumentar los fotogramas por segundo sin afectar la velocidad del juego (animaciones, físicas, etc.). -Puede ayudar a reducir los tirones o parpadeos en tasas de fotogramas bajas. + Sincroniza la velocidad del núcleo de la CPU con la velocidad máxima de renderizado del juego para potenciar los fotogramas por segundo sin afectar a la velocidad del juego (animaciones, físicas, etc.). +Puede ayudar a reducir los tirones o parpadeos en las tasas de fotogramas bajas. @@ -571,39 +422,38 @@ Puede ayudar a reducir los tirones o parpadeos en tasas de fotogramas bajas. - + Backend: Motor: - CPU Overclock - Overclock de la CPU + CPU Clocks + Relojes de CPU - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. - Overcloquea la CPU emulada para eliminar algunos limitadores de FPS. en CPUs más humildes puede reducir el rendimiento y algunos juegos pueden funcionar incorrectamente. -Usa Boost (1700MHz) para ejecutar a la velocidad nativa maximo de Switch, o Fast (2000MHz) para ejecutar al doble velocidad. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + - + Custom CPU Ticks Ticks de CPU personalizados - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. Establece un valor personalizado para los ticks de la CPU. Valores más altos pueden aumentar el rendimiento, pero también pueden causar bloqueos. Se recomienda un rango de 77-21000. - + Enable Host MMU Emulation (fastmem) Activar la emulación del huesped MMU (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. @@ -612,99 +462,99 @@ Al actualizarlo, las lecturas/escrituras de memoria del programa invitado se rea Desactivando este opción obliga a que todos los accesos a la memoria utilicen el Software MMU emulado. - + Unfuse FMA (improve performance on CPUs without FMA) Desactivar FMA (mejora el rendimiento en las CPU sin FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. Esta opción mejora el rendimiento al reducir la precisión de las instrucciones fused-multiply-add en las CPU sin soporte nativo FMA. - + Faster FRSQRTE and FRECPE FRSQRTE y FRECPE rápidos - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. - Esta opción mejora el rendimiento de algunas funciones aproximadas de punto flotante al utilizar aproximaciones nativas menos precisas. + Esta opción mejora la velocidad de algunas funciones aproximadas de punto flotante mediante el uso de aproximaciones nativas menos precisas. - + Faster ASIMD instructions (32 bits only) Instrucciones ASIMD rápidas (solo 32 bits) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. Esta opción mejora la velocidad de las funciones de punto flotante ASIMD de 32 bits al ejecutarlas con redondeos incorrectos. - + Inaccurate NaN handling Gestión imprecisa NaN - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. Esta opción mejora el rendimiento al no hacer comprobaciones "NaN". Ten en cuenta que, a cambio, reduce la precisión de ciertas instrucciones de coma flotante. - + Disable address space checks Desactivar comprobación del espacio de destino - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. Esta opción mejora la velocidad eliminando una verificación de seguridad antes de cada operación de memoria. - + Ignore global monitor Ignorar monitorización global - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. Esta opción mejora el rendimiento al depender solo de la semántica de cmpxchg para garantizar la seguridad de las instrucciones de acceso exclusivas. Ten en cuenta que puede resultar en bloqueos y otras condiciones de carrera. - + API: API: - + Changes the output graphics API. Vulkan is recommended. Cambia la API de salida gráfica. Se recomienda Vulkan. - + Device: Dispositivo: - + This setting selects the GPU to use (Vulkan only). Este ajuste selecciona la GPU a usar (solo Vulkan). - + Resolution: Resolución: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. @@ -713,27 +563,27 @@ Las resoluciones altas requieren más VRAM y ancho de banda. Las opciones inferiores a 1X pueden generar errores visuales. - + Window Adapting Filter: Filtro adaptable de ventana: - + FSR Sharpness: Nitidez FSR: - - Determines how sharpened the image will look using FSR's dynamic contrast. - Determina el nivel de nitidez de la imagen utilizando el contraste dinámico de FSR. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. + Determina el grado de nitidez que tendrá la imagen usando el contraste dinámico de FSR o SGSR. - + Anti-Aliasing Method: Método de suavizado de bordes: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. @@ -741,13 +591,23 @@ FXAA can produce a more stable picture in lower resolutions. SMAA ofrece mejor calidad. FXAA puede producir mejores resultados visuales en resoluciones bajas. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Modo pantalla completa: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. @@ -756,12 +616,12 @@ Ventana sin bordes ofrece la mejor compatibilidad con el teclado en pantalla que Pantalla completa exclusiva puede ofrecer mejor rendimiento y mejor soporte para FreeSync/G-Sync/VRR. - + Aspect Ratio: Relación de aspecto: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. @@ -770,36 +630,36 @@ La mayoría de los juegos solo admiten 16:9, por lo que se requieren modificacio También controla la relación de aspecto de las capturas de pantalla. - + Use persistent pipeline cache Usar el caché de canalización persistente - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. Permite almacenar los sombreadores para que carguen más rápido la próxima vez que inicies el juego. Desactivarlo solo está destinado para depuración. - + Use asynchronous GPU emulation Usar emulación asíncrona de la GPU - + Uses an extra CPU thread for rendering. This option should always remain enabled. Usa un hilo adicional de la CPU para el renderizado. Esta opción siempre debe permanecer activada. - + NVDEC emulation: Emulación NVDEC: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. @@ -808,12 +668,12 @@ Puede usar la CPU, GPU o no descodificar (mostrará una pantalla en negro durant En la mayoría de casos, descodificar mediante la GPU es la mejor opción. - + ASTC Decoding Method: Modo descodificación ASTC: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -825,12 +685,12 @@ GPU: utiliza los sombreadores computables de la GPU para descodificar las textur CPU asíncrona: usa la CPU para descodificar las texturas ASTC bajo demanda. Elimina los tirones causados por la descodificación ASTC, pero puede generar errores visuales. - + ASTC Recompression Method: Modo recompresión ASTC: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. @@ -838,44 +698,64 @@ BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, BC1/BC3: El formato intermedio será recomprimido a BC1 o BC3, lo que ahorra VRAM, pero degrada la calidad de la imagen. - + Frame Pacing Mode (Vulkan only) Modo de ritmo de fotogramas (solo Vulkan) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. Controla cómo el emulador gestiona el ritmo de los fotogramas para reducir los tirones y hacer que la velocidad de los fotogramas sea más suave y consistente. - + VRAM Usage Mode: Modo de uso de la VRAM: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. Selecciona si el emulador prefiere ahorrar memoria o maximizar el uso de memoria de video disponible para mejorar el rendimiento. El modo agresivo puede afectar al rendimiento de otras aplicaciones, como a los programas de grabación. - + Skip CPU Inner Invalidation Saltar invalidación interna de la CPU - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. Omite ciertas invalidaciones de caché durante las actualizaciones de memoria, reduciendo el uso de la CPU y mejorando la latencia. Esto puede causar bloqueos leves. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: Modo VSync: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -886,12 +766,12 @@ Mailbox ofrece menor latencia que FIFO y evita el tearing, aunque puede perder a Immediate (sin sincronización) muestra los fotogramas tan pronto están disponibles, pero puede generar desgarros de imagen. - + Sync Memory Operations Sincronizar operaciones de memoria - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. @@ -900,102 +780,116 @@ Esta opción arregla errores en los juegos, pero puede afectar negativamente al Los juegos con Unreal Engine 4 son con frecuencia los que muestran cambios significantes en esto. - + Enable asynchronous presentation (Vulkan only) Activar presentación asíncrona (solo Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. Mejora el rendimiento ligeramente al usar un hilo de CPU adicional para la presentación. - + Force maximum clocks (Vulkan only) Forzar relojes al máximo (solo Vulkan) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. Ejecuta los procesos en segundo plano mientras se espera a las instrucciones gráficas para evitar que la GPU reduzca su velocidad de reloj. - + Anisotropic Filtering: Filtrado anisotrópico: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. Controla la calidad del renderizado de texturas en ángulos oblicuos. Es seguro configurarlo en 16x en la mayoría de las GPU. - + GPU Mode: Modo de la GPU: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - Controla el modo de emulación de la GPU -La mayoridad de juegos renderizarán bien con el modo rápido o balanceado, pero el preciso todavía esta requerido para algunos. -Las partículas tienden a representarse correctamente solo con el modo preciso. + - + DMA Accuracy: Precisión de DMA: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. - Controla la precisión del DMA. La opción de precisión segura corrige errores en algunos juegos, pero puede reducir el rendimiento + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. + Controla el modo de lectura DMA. +Inseguro es más rápido, mientras que seguro es más estable y puede solucionar fallos en algunos juegos. +Predeterminado sigue el ajuste de la precisión de GPU. - + + GPU Fence Behavior: + Comportamiento de vallado de la GPU: + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation Activa la compilación de shaders asincrónicos - + May reduce shader stutter. Puede reducir los tirones causados por la carga de sombreadores. - - Fast GPU Time - Tiempo rápido de la GPU + + GPU Clocks + Relojes de GPU - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. - Overcloquea la GPU emulada para aumentar la resolución dinámica y la distancia de renderizado. -Use 256 para el máximo rendimiento y 512 para la máxima fidelidad gráfica. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. + - + GPU Unswizzle Desentrelazado de la GPU - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. Acelera la decodificación de texturas 3D BCn mediante computación de la GPU. Desactívela si experimenta problemas o fallos gráficos. - + GPU Unswizzle Max Texture Size Tamaño máximo de textura de desentrelazado de la GPU - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. @@ -1004,46 +898,46 @@ Aunque la GPU es más rápida para texturas medianas y grandes, la CPU puede ser Ajuste este valor para encontrar el equilibrio entre la aceleración de la GPU y la sobrecarga de la CPU. - + GPU Unswizzle Stream Size Tamaño del flujo de desentrelazado de la GPU - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. Establece la cantidad máxima de datos de textura (en MiB) procesados ​​por cada fotograma. Valores más altos pueden reducir la distorsión durante la carga de texturas, pero pueden afectar a la consistencia de los fotogramas. - + GPU Unswizzle Chunk Size Tamaño del trozo de desentrelazado de la GPU - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. Determina la cantidad de cortes de profundidad procesados ​​en un solo envío. Aumentar este valor puede mejorar el rendimiento en una GPU de gama alta, pero puede causar tirones y problemas en los tiempos de respuesta en hardware más modesto. - + Use Vulkan pipeline cache Usar caché de canalización de Vulkan - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. Activa la caché de canalización específica del fabricante. Esta opción puede mejorar los tiempos de cargas de sombreadores en el caso de que el controlador de Vulkan no lo almacene internamente. - + Enable Compute Pipelines (Intel Vulkan Only) Activar la canalizaciones de cómputo (solo Intel Vulkan) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. @@ -1052,184 +946,182 @@ Este ajuste solo existe para los controladores propietarios de Intel y puede cau En los demás controladores, la canalización de cómputo siempre está activada. - + Enable Reactive Flushing Activar limpieza reactiva - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. Usa limpieza de memoria reactiva en vez de predictiva, permitiendo una sincronización de memoria más precisa. - + Sync to framerate of video playback Sincronizar a fotogramas de reproducción de vídeo - + Run the game at normal speed during video playback, even when the framerate is unlocked. Ejecuta el juego a velocidad normal durante la reproducción de vídeos, incluso cuando no hay límite de fotogramas. - + Barrier feedback loops Bucles de feedback de barrera - + Improves rendering of transparency effects in specific games. Mejora la renderización de los efectos de transparencia en ciertos juegos. - + Enable buffer history Activar el historial del búfer - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. Habilita el acceso a estados de búfer anteriores. Esta opción puede mejorar la calidad de renderizado y la consistencia en el rendimiento de algunos juegos. - + Fix bloom effects Arreglar efectos de resplandor - + Removes bloom in Burnout. Elimina el resplandor en Burnout. - - Enable Legacy Rescale Pass - Activar el pase de reescalado heredado - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - Puede arreglar los problemas de reescalado en algunos juegos confiando en el comportamiento de la implementación anterior. -Solución alternativa de comportamiento heredado que corrige los artefactos de línea de la GPU AMD y el parpadeo de texturas gris de la GPU Nvidia en Luigis Mansion 3. - - - + Extended Dynamic State Estado dinámico extendido - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. Controla el número de funciones que pueden ser usadas en el Estado Dinámico Extendido. Números más altos permiten úsar más funciones y pueden aumentar el rendimiento, pero tambén pueden causar errores gráficos. - + Vertex Input Dynamic State Estado dinámico de entrada de vértices - + Enables vertex input dynamic state feature for better quality and performance. Activa la función de estado dinámico de entrada de vértices para una mejor calidad y rendimiento. - + Sample Shading Sombreado de muestra - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. Permite que el sombreador de fragmentos se ejecute por muestra en un fragmento multimuestreado, en lugar de una sola vez por fragmento. Mejora la calidad de los gráficos a costa de rendimiento. Los valores más altos mejoran la calidad, pero reducen el rendimiento. - + RNG Seed Semilla de GNA - + Controls the seed of the random number generator. Mainly used for speedrunning. Controla la semilla del generador de números aleatorios. Usado principalmente para speedrunning. - + Device Name Nombre del dispositivo - + The name of the console. El nombre de la consola - + + Homebrew Args + Argumentos Homebrew + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + Argumentos en la línea de comandos pasados al homebrew al ser lanzado (p.e. -noglsl). + + + Custom RTC Date: Fecha Personalizada RTC: - + This option allows to change the clock of the console. Can be used to manipulate time in games. Esta opción permite cambiar el reloj de la consola. Puede ser usado para manipular el tiempo en juegos. - + The number of seconds from the current unix time Número de segundos de la hora actual de Unix - + Language: Idioma: - + This option can be overridden when region setting is auto-select Esta opción puede ser reemplazada cuando la configuración de región está en selección automática. - + Region: Región: - + The region of the console. La Región de la Consola. - + Time Zone: Zona horaria: - + The time zone of the console. La zona horaria de la consola. - + Sound Output Mode: Método de salida de sonido: - + Console Mode: Modo consola: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. @@ -1238,1030 +1130,1025 @@ Los juegos cambiarán su resolución, detalles y compatibilidad con los mandos s Configurar el modo portátil puede mejorar el rendimiento en sistemas de gama baja. - + Prompt for user profile on boot Solicitud para perfil de usuario al arrancar - + Useful if multiple people use the same PC. Útil si múltiples personas usan la misma PC - + Pause when not in focus Pausar cuando la ventana no esté activa - + Pauses emulation when focusing on other windows. - Pausa la emulación cuando esta activa otra ventana diferente. + Pausa la emulación cuando otra ventana diferente está activa. - + Confirm before stopping emulation Confirmar detención - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. Anula las solicitudes de confirmación para detener la emulación. Al habilitar esta opción, se omiten dichas solicitudes y se sale directamente de la emulación. - + Hide mouse on inactivity Ocultar el cursor por inactividad. - + Hides the mouse after 2.5s of inactivity. Oculta el mouse después de 2.5s de inactividad. - - Disable controller applet - Desactivar applet de mandos - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - Desactiva forzosamente el uso del applet del controlador en programas emulados. -Cuando un programa intenta abrir el applet del controlador, se cierra inmediatamente. - - - + Check for updates Buscar actualizaciones - + Whether or not to check for updates upon startup. Si se deben buscar actualizaciones al iniciar o no. - + Enable Gamemode Activar Modo Juego - + Force X11 as Graphics Backend Forzar X11 como motor gráfico - + Custom frontend Interfaz personalizada - + Real applet Applet real - + + + Disabled + Desactivado + + + Never Nunca - + On Load Al cargar - + Always Siempre - + CPU CPU - + GPU GPU - + CPU Asynchronous CPU asíncrona - + Uncompressed (Best quality) Sin compresión (Mejor calidad) - + BC1 (Low quality) BC1 (Calidad baja) - + BC3 (Medium quality) BC3 (Calidad media) - - + + Auto Auto - + 30 FPS 30 FPS - + 60 FPS 60 FPS - + 90 FPS 90 FPS - + 120 FPS 120 FPS - + Conservative Conservativo - + Aggressive Agresivo - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) OpenGL GLASM (Ensamblado de sombreadores, solo NVIDIA) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) OpenGL SPIR-V (Experimental, solo AMD/Mesa) - + Null Ninguno - + Fast Rápido - - Balanced - Balanceado - - - - + + + Accurate Preciso - - + + + Default Predeterminado - + Unsafe (fast) Inseguro (rápido) - + Safe (stable) Seguro (estable) - + + Immediate + Inmediato + + + + Balanced + Balanceado + + + Unsafe Impreciso - + Paranoid (disables most optimizations) Paranoico (Deshabilita la mayoría de optimizaciones) - + Debugging Depuración - + Dynarmic DynARMic - + NCE NCE - + Borderless Windowed Ventana sin bordes - + Exclusive Fullscreen Pantalla completa - + No Video Output Sin salida de vídeo - + CPU Video Decoding Decodificación de vídeo en la CPU - + GPU Video Decoding (Default) Decodificación de vídeo en GPU (Por defecto) - + 0.25X (180p/270p) [EXPERIMENTAL] 0.25X (180p/270p) [EXPERIMENTAL] - + 0.5X (360p/540p) [EXPERIMENTAL] x0,5 (360p/540p) [EXPERIMENTAL] - + 0.75X (540p/810p) [EXPERIMENTAL] x0,75 (540p/810p) [EXPERIMENTAL] - + 1X (720p/1080p) x1 (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] 1.25X (900p/1350p) [EXPERIMENTAL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] x1,5 (1080p/1620p) [EXPERIMENTAL] - + 2X (1440p/2160p) x2 (1440p/2160p) - + 3X (2160p/3240p) x3 (2160p/3240p) - + 4X (2880p/4320p) x4 (2880p/4320p) - + 5X (3600p/5400p) x5 (3600p/5400p) - + 6X (4320p/6480p) x6 (4320p/6480p) - + 7X (5040p/7560p) x7 (5040p/7560p) - + 8X (5760p/8640p) x8 (5760p/8640p) - + Nearest Neighbor Vecino más próximo - + Bilinear Bilineal - + Bicubic Bicúbico - + Gaussian Gaussiano - + Lanczos Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution Super Resolución AMD FidelityFX - + Area Área - + MMPX MMPX - + Zero-Tangent Tangente cero - + B-Spline Ranura B - + Mitchell Mitchell - + Spline-1 Spline-1 - - + + Snapdragon Game Super Resolution + Super resolución de juego Snapdragon + + + + Snapdragon Game Super Resolution EdgeDir + Filo direcorio de super resolución de juego Snapdragon + + + None Ninguno - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Predeterminado (16:9) - + Force 4:3 Forzar 4:3 - + Force 21:9 Forzar 21:9 - + Force 16:10 Forzar 16:10 - + Stretch to Window Estirar a la ventana - + Automatic Automático - + 2x x2 - + 4x x4 - + 8x x8 - + 16x x16 - - 32x - 32x - - - - 64x - 64x - - - + Japanese (日本語) Japonés (日本語) - + American English Inglés estadounidense - + French (français) Francés (français) - + German (Deutsch) Alemán (deutsch) - + Italian (italiano) Italiano (italiano) - + Spanish (español) Español - + Chinese Chino - + Korean (한국어) Coreano (한국어) - + Dutch (Nederlands) Holandés (nederlands) - + Portuguese (português) Portugués (português) - + Russian (Русский) Ruso (Русский) - + Taiwanese Taiwanés - + British English Inglés británico - + Canadian French Francés canadiense - + Latin American Spanish Español latinoamericano - + Simplified Chinese Chino simplificado - + Traditional Chinese (正體中文) Chino tradicional (正體中文) - + Brazilian Portuguese (português do Brasil) Portugués brasileño (português do Brasil) - + Polish (polska) Polaco (polska) - + Thai (แบบไทย) Tailandés (แบบไทย) - - + + Japan Japón - + USA EEUU - + Europe Europa - + Australia Australia - + China China - + Korea Corea - + Taiwan Taiwán - + Auto (%1) Auto select time zone Auto (%1) - + Default (%1) Default time zone Predeterminada (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Cuba - + EET EET - + Egypt Egipto - + Eire Eire - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Eire - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hongkong - + HST HST - + Iceland Islandia - + Iran Irán - + Israel Israel - + Jamaica Jamaica - + Kwajalein Kwajalein - + Libya Libia - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navajo - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Polonia - + Portugal Portugal - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Singapur - + Turkey Turquía - + UCT UCT - + Universal Universal - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulú - + Mono Mono - + Stereo Estéreo - + Surround Envolvente - + 4GB DRAM (Default) 4GB DRAM (Por defecto) - + 6GB DRAM (Unsafe) 6GB DRAM (Inseguro) - + 8GB DRAM 8GB DRAM - + 10GB DRAM (Unsafe) 10GB DRAM (Inseguro) - + 12GB DRAM (Unsafe) 12GB DRAM (Inseguro) - + Docked Sobremesa - + Handheld Portátil - - - Off - Apagado + + + Normal + Normal - - Boost (1700MHz) - Boost (1700MHz) + + + Boost + Impulso - - Fast (2000MHz) - Rápido (2000MHz) + + + Overclock + Overclock - + Always ask (Default) Preguntar siempre (Por defecto) - + Only if game specifies not to stop Solo si el juego pide no ser cerrado - + Never ask Nunca preguntar - - - Medium (256) - Medio (256) - - - - - High (512) - Alto (512) - - - + Very Small (16 MB) Muy pequeño (16 MB) - + Small (32 MB) Pequeño (32 MB) - + Normal (128 MB) Normal (128 MB) - + Large (256 MB) Grande (256 MB) - + Very Large (512 MB) Muy grande (512 MB) - + Very Low (4 MB) Muy bajo (4 MB) - + Low (8 MB) Bajo (8 MB) - + Normal (16 MB) Normal (16 MB) - + Medium (32 MB) Medio (32 MB) - + High (64 MB) Alto (64 MB) - + Very Low (32) Muy bajo (32) - + Low (64) Bajo (64) - + Normal (128) Normal (128) - - Disabled - Desactivado + + Medium (256) + Medio (256) - + + High (512) + Alto (512) + + + ExtendedDynamicState 1 ModoDynamicoExtendido 1 - + ExtendedDynamicState 2 ModoDynamicoExtendido 2 - + ExtendedDynamicState 3 ModoDynamicoExtendido 3 - + Tree View Vista en árbol - + Grid View Vista en cuadrícula @@ -2621,252 +2508,297 @@ Cuando un programa intenta abrir el applet del controlador, se cierra inmediatam Filtro del registro global - - When checked, the max size of the log increases from 100 MB to 1 GB - Al activarlo, el tamaño máximo del registro aumenta de 100 MB a 1 GB. + + GPU Logging/Level + Nivel/Registros de la GPU - - Enable Extended Logging** - Habilitar el registro extendido** + + Detail level for GPU logs. Off disables logging entirely. + Nivel de detalle para los registros de la GPU. En Apagado desactiva todos los registros. - + + Off + Apagado + + + + Errors + Errores + + + + Standard + Estándar + + + + Verbose + Detallado + + + + All + Todos + + + Show Log in Console Ver el registro en la consola - + + When checked, the max size of the log increases from 100 MB to 1 GB + Al activarlo, el tamaño máximo del registro aumenta de 100 MB a 1 GB. + + + + Enable Extended Logging** + Habilitar el registro extendido** + + + Open Log Location Abrir la ubicación del archivo de registro - + Homebrew Homebrew - + Arguments String Cadena de argumentos - + Graphics Gráficos - + When checked, it executes shaders without loop logic changes Al activarlo, se ejecutarán los sombreadores sin cambios en bucles lógicos. - + Disable Loop safety checks Desactivar comprobaciones de seguridad de bucles - + When checked, it will dump all the macro programs of the GPU Al activarlo, se volcarán todos los programas macro de la GPU. - + Dump Maxwell Macros Volcar macros Maxwell - + When checked, it enables Nsight Aftermath crash dumps Al activarlo, se habilitan los volcados Nsight Aftermath de bloqueos o errores. - + Enable Nsight Aftermath Activar Nsight Aftermath - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Al activarlo, se volcarán todos los sombreadores del ensamblador original de la caché de sombreadores en el disco o del juego tal y como se encuentren. + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + Al activarlo, volcará el bytecode original del sombreador huesped Maxwell (la entrada al recompilador) como archivos .ash bajo DumpDir/shaders/. Útil para inspeccionar con nvdisasm. - - Dump Game Shaders - Volcar sombreadores del juego + + Dump Guest (Maxwell) Shaders + Volcar sombreadores huesped (Maxwell) - + Enable Renderdoc Hotkey Atajo para activar Renderdoc - + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower Al activarlo, se desactiva el compilador de macro Just In Time. Activar esto hace que los juegos se ejecuten más lento. - + Disable Macro JIT Desactivar macro JIT - + When checked, it disables the macro HLE functions. Enabling this makes games run slower Al activarlo, desactiva las funciones de macro HLE. Activar esto hace que los juegos se ejecuten más lento. - + Disable Macro HLE Desactivar macro HLE - + When checked, the graphics API enters a slower debugging mode Al activarlo, la API gráfica entrará en un modo de depuración más lento. - + Enable Graphics Debugging Activar depuración de gráficos - + When checked, yuzu will log statistics about the compiled pipeline cache Al activarlo, yuzu realizará un registro de estadísticas del caché de canalización compilado. - + Enable Shader Feedback Activar información de shaders - + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> - <html><head/><body><p>Al activarse, se desactiva la reordenación de las subidas de memoria mapeadas, lo que permite asociar subidas con extracciones específicas. Puede reducir el rendimiento en algunos casos.</p></body></html> + <html><head/><body><p>Al activarlo, se desactiva la reordenación de las subidas de memoria mapeadas, lo que permite asociar subidas con extracciones específicas. Puede reducir el rendimiento en algunos casos.</p></body></html> - + Disable Buffer Reorder Desactivar el reordenamiento de búferes - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + Al activarlo, se volcarán los archivos binarios SPIR-V (.spv) generados por el recompilador en LogDir/shaders/. Inspeccionarlos con SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) o RenderDoc. + + + + Dump SPIR-V Shaders + Volcar sombreadores SPIR-V + + + Advanced Avanzado - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. - Permite a Eden comprobar si el entorno de Vulkan funciona cuando el programa se inicia. Desactive esto si está causando problemas a programas externos que ven a Eden. + Permite a Eden comprobar si el entorno de Vulkan funciona cuando el programa se inicia. Desactiva esto si está causando problemas a programas externos que ven a Eden. - + Perform Startup Vulkan Check Realizar comprobación de Vulkan al iniciar - + Disable Web Applet Desactivar applet web - + Enable All Controller Types Activar todos los tipos de controladores - + Enable Auto-Stub Activar Auto-Stub - + Kiosk (Quest) Mode Modo quiosco (Quest) - + Use dev.keys Usar dev.keys - + Enable Debug Asserts Activar alertas de depuración - + Debugging Depuración - + Battery Serial: Nº de serie de la batería: - + + Debug Knobs: + Interruptores de depuración: + + + Bitmask for quick development toggles Bitmask para toggles de desarrollo rapido - + Set debug knobs (bitmask) Establecer parámetros de depuración (bitmask) - + 16-bit debug knob set for quick development toggles Conjunto de interruptores de depuración de 16 bits para toggles de desarrollo rapido - + (bitmask) (bitmask) - - Debug Knobs: - Interruptores de depuración: - - - + Unit Serial: Nº de serie de la unidad: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. Activa esta opción para mostrar en la consola la última lista de comandos de audio generada. Solo afecta a los juegos que utilizan el renderizador de audio. - + Dump Audio Commands To Console** Volcar comandos de audio a la consola** - + Flush log output on each line Limpia el registro de salida en cada línea - + Enable FS Access Log Activar registro de acceso FS - + Enable Verbose Reporting Services** Activar servicios de reporte detallados** - + Censor username in logs Censura del nombre de usuario en los archivos de registro - + **This will be reset automatically when Eden closes. **Esto se reiniciara automáticamente cuando Eden se cierre. @@ -3328,33 +3260,38 @@ Would you like to delete the old save data? Color de fondo: - + % - FSR sharpening percentage (e.g. 50%) + FSR/SGSR sharpening percentage (e.g. 50%) % - + + Configure Effects... + Configurar efectos... + + + Off Desactivado - + VSync Off VSync Desactivado - + Recommended Recomendado - + On Activado - + VSync On VSync Activado @@ -3875,6 +3812,16 @@ Would you like to delete the old save data? Motion / Touch Movimiento / táctil + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + Desactivar SDL WGI/XInput (reinicio obligatorio) + ConfigureInputPerGame @@ -4384,58 +4331,58 @@ Para invertir los ejes, mueve primero el joystick de manera vertical, y luego ho [esperando] - + New Profile Nuevo perfil - + Enter a profile name: Introduce un nombre de perfil: - - + + Create Input Profile Crear perfil de entrada - + The given profile name is not valid! ¡El nombre de perfil introducido no es válido! - + Failed to create the input profile "%1" Error al crear el perfil de entrada "%1" - + Delete Input Profile Eliminar perfil de entrada - + Failed to delete the input profile "%1" Error al eliminar el perfil de entrada "%1" - + Load Input Profile Cargar perfil de entrada - + Failed to load the input profile "%1" Error al cargar el perfil de entrada "%1" - + Save Input Profile Guardar perfil de entrada - + Failed to save the input profile "%1" Error al guardar el perfil de entrada "%1" @@ -4483,7 +4430,7 @@ Para invertir los ejes, mueve primero el joystick de manera vertical, y luego ho - + Configure Configurar @@ -4514,7 +4461,7 @@ Para invertir los ejes, mueve primero el joystick de manera vertical, y luego ho - + Test Probar @@ -4529,77 +4476,77 @@ Para invertir los ejes, mueve primero el joystick de manera vertical, y luego ho Eliminar servidor - + %1:%2 %1:%2 - - - - - - + + + + + + Eden Eden - + Port number has invalid characters El número del puerto tiene caracteres que no son válidos - + Port has to be in range 0 and 65353 El puerto debe estar en un rango entre 0 y 65353 - + IP address is not valid Dirección IP no válida - + This UDP server already exists Este servidor UDP ya existe - + Unable to add more than 8 servers No es posible añadir más de 8 servidores - + Testing Probando - + Configuring Configurando - + Test Successful Prueba existosa - + Successfully received data from the server. Se han recibido con éxito los datos del servidor. - + Test Failed Prueba fallida - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. No se han podido recibir datos válidos del servidor.<br>Por favor, verifica que el servidor esté configurado correctamente y que la dirección y el puerto sean correctos. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. La prueba de UDP o la configuración de la calibración está en curso.<br>Por favor, espera a que termine el proceso. @@ -4784,57 +4731,57 @@ Los valores actuales son %1% y %2% respectivamente. Algunos ajustes solo están disponibles cuando no se estén ejecutando los juegos. - + Add-Ons Complementos - + System Sistema - + CPU CPU - + Graphics Gráficos - + Adv. Graphics Gráficos avanz. - + Ext. Graphics Extensiones Gráficas - + Audio Audio - + Input Profiles Perfiles de entrada - + Network Red - + Applets Applets - + Properties Propiedades @@ -4961,6 +4908,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t &Abrir en el gestor de archivos + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + Aplicar + + + + Reset Values + Restablecer valores + + + + Return every effect to the values the preset ships with. + + + + + Save As... + Guardar como... + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + Añadir efecto + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + Activado + + + + Disabled + Desactivado + + + + Reset to defaults + Restaurar los valores predeterminados + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5249,7 +5320,7 @@ UUID: %2 <html><head/><body><p>Reads controller input from scripts in the same format as TAS-nx scripts.<br/>For a more detailed explanation, please consult the user handbook.</p></body></html> - <html><head/><body><p>Lee la entrada del mando desde los scripts en el mismo formato que los scripts TAS-nx.<br/>Para una explicación más detallada, por favor consulte el manual de usuario.</p></body></html> + <html><head/><body><p>Lee la entrada del mando desde los scripts en el mismo formato que los scripts TAS-nx.<br/>Para una explicación más detallada, por favor consulta el manual de usuario.</p></body></html> @@ -5528,91 +5599,86 @@ Arrastre los puntos para cambiar de posición, o haga doble clic en las celdas d - Show Compatibility List - Mostrar lista de compatibilidad + Show Add-Ons Column + Mostrar la columna de complementos - Show Add-Ons Column - Mostrar columna de complementos - - - Show Size Column Mostrar columna de tamaño - + Show File Types Column Mostrar columna de tipos de archivo - + Show Play Time Column Mostrar columna de tiempo de juego - + Folder Icon Size: Tamaño de los iconos de la carpeta: - + Row 1 Text: Texto de fila 1: - + Row 2 Text: Texto de fila 2: - + Screenshots Capturas de pantalla - + Ask Where To Save Screenshots (Windows Only) Preguntar dónde guardar las capturas de pantalla (solo en Windows) - + Screenshots Path: Ruta de las capturas de pantalla: - + ... ... - + TextLabel TextLabel - + Resolution: Resolución: - + Select Screenshots Path... Selecciona la ruta de las capturas de pantalla: - + <System> <System> - + English Inglés - + Auto (%1 x %2, %3 x %4) Screenshot width value Auto (%1 x %2, %3 x %4) @@ -5849,7 +5915,7 @@ Arrastre los puntos para cambiar de posición, o haga doble clic en las celdas d Importar todos los datos en este directorio. Esto puede tomar un tiempo, ¡y borrará TODOS LOS DATOS EXISTENTES! - + Calculating... Calculando... @@ -6053,50 +6119,50 @@ Por favor, vaya a Configuración -> Sistema -> Red y selecciona una interf GRenderWindow - - + + OpenGL not available! ¡OpenGL no está disponible! - + OpenGL shared contexts are not supported. Los contextos compartidos de OpenGL no son compatibles. - + Eden has not been compiled with OpenGL support. Eden no ha sido compilado con soporte para OpenGL. - - - + + + Error while initializing OpenGL! ¡Error al inicializar OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Tu GPU no soporta OpenGL, o no tienes instalados los últimos controladores gráficos. - + Error while initializing OpenGL 4.6! ¡Error al iniciar OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Tu GPU no soporta OpenGL 4.6, o no tienes instalado el último controlador de la tarjeta gráfica.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Es posible que la GPU no soporte una o más extensiones necesarias de OpenGL . Por favor, asegúrate de tener los últimos controladores de la tarjeta gráfica.<br><br>GL Renderer:<br>%1<br><br>Extensiones no soportadas:<br>%2 - + This build doesn't have OpenGL support. Esta compilación no tiene soporte para OpenGL. @@ -6104,279 +6170,272 @@ Por favor, vaya a Configuración -> Sistema -> Red y selecciona una interf GameList - + &Add New Game Directory &Añadir un nuevo directorio de juego - + Favorite Favorito - + Start Game Iniciar juego - + Start Game without Custom Configuration Iniciar juego sin la configuración personalizada - + Open Save Data Location Abrir ubicación de los archivos de guardado - + Open Mod Data Location Abrir ubicación de los mods - + Open Transferable Pipeline Cache Abrir caché de canalización de shaders transferibles - + Link to Ryujinx Enlace a Ryujinx - + Remove Eliminar - + Remove Installed Update Eliminar la actualización instalada - + Remove All Installed DLC Eliminar todos los DLC instalados - + Remove Custom Configuration Eliminar la configuración personalizada - + Remove Cache Storage Quitar almacenamiento de caché - + Remove OpenGL Pipeline Cache Eliminar caché de canalización de OpenGL - + Remove Vulkan Pipeline Cache Eliminar caché de canalización de Vulkan - + Remove All Pipeline Caches Eliminar todas las cachés de canalización - + Remove All Installed Contents Eliminar todo el contenido instalado - + Manage Play Time Gestionar tiempo de juego - + Edit Play Time Data Editar los datos del tiempo de juego - + Remove Play Time Data Eliminar información del tiempo de juego - - + + Dump RomFS Volcar RomFS - + Dump RomFS to SDMC Volcar RomFS a SDMC - + Verify Integrity Verificar integridad - + Copy Title ID to Clipboard Copiar la ID del título al portapapeles - - Navigate to GameDB entry - Ir a la sección de bases de datos del juego - - - + Create Shortcut Crear acceso directo - + Add to Desktop Añadir al escritorio - + Add to Applications Menu Añadir al menú de aplicaciones - + Configure Game Configurar juego - + Scan Subfolders Escanear subdirectorios - + Remove Game Directory Eliminar directorio de juegos - + ▲ Move Up ▲ Mover hacia arriba - + ▼ Move Down ▼ Mover hacia abajo - + Open Directory Location Abrir ubicación del directorio - + Clear Limpiar - - - Name - Nombre - - - - Compatibility - Compatibilidad - - - - Add-ons - Complementos - - - - File type - Tipo de archivo - - - - Size - Tamaño - - - - Play time - Tiempo de juego - GameListItemCompat - + Ingame En juego - + Game starts, but crashes or major glitches prevent it from being completed. El juego se inicia, pero se bloquea o se producen fallos importantes que impiden completarlo. - + Perfect Perfecta - + Game can be played without issues. El juego se puede jugar sin problemas. - + Playable Jugable - + Game functions with minor graphical or audio glitches and is playable from start to finish. El juego tiene algunos errores gráficos o de sonido, pero se puede jugar de principio a fin. - + Intro/Menu Inicio/Menu - + Game loads, but is unable to progress past the Start Screen. El juego se ejecuta, pero no puede avanzar de la pantalla de inicio. - + Won't Boot No funciona - + The game crashes when attempting to startup. El juego se bloquea al intentar iniciar. - + Not Tested Sin testear - + The game has not yet been tested. El juego todavía no ha sido testeado todavía. + + GameListModel + + + Name + Nombre + + + + Add-ons + Complementos + + + + File type + Tipo de archivo + + + + Size + Tamaño + + + + Play time + Tiempo de juego + + GameListPlaceholder - + Double-click to add a new folder to the game list Haz doble clic para agregar un nuevo directorio a la lista de juegos. @@ -6384,17 +6443,17 @@ Por favor, vaya a Configuración -> Sistema -> Red y selecciona una interf GameListSearchField - + %1 of %n result(s) %1 de %n resulto(s)%1 de %n resultado(s)%1 de %n resultado(s) - + Filter: Búsqueda: - + Enter pattern to filter Introduce un patrón para buscar @@ -6890,772 +6949,780 @@ Mensaje de depuración: Modo de la lista de &juegos - + Game &Icon Size Tamaño del &icono de los juegos - + Reset Window Size to &720p Reiniciar el tamaño de la ventana a &720p - + Reset Window Size to 720p Reiniciar el tamaño de la ventana a 720p - + Reset Window Size to &900p Reiniciar el tamaño de la ventana a &900p - + Reset Window Size to 900p Reiniciar el tamaño de la ventana a 900p - + Reset Window Size to &1080p Reiniciar el tamaño de la ventana a &1080p - + Reset Window Size to 1080p Reiniciar el tamaño de la ventana a 1080p - + &Multiplayer &Multijugador - + &Tools &Herramientas - + Am&iibo Am&iibo - + Launch &Applet Ejecutar &Applet - + &TAS &TAS - + &Create Home Menu Shortcut &Crear un acceso directo al menú de inicio - + Install &Firmware Instalar &Firmware - + &Help &Ayuda - + &Install Files to NAND... &Instalar archivos en NAND... - + L&oad File... C&argar archivo... - + Load &Folder... Cargar &carpeta - + E&xit S&alir - - + + &Pause &Pausar - + &Stop &Detener - + &Verify Installed Contents &Verificar los contenidos instalados - + &About Eden &Acerca de Eden - + Single &Window Mode Modo &ventana - + Con&figure... Con&figurar... - + Ctrl+, Ctrl+, - + Enable Overlay Display Applet Activar applet de visualización superpuesta - + Show &Filter Bar Mostrar barra de &búsqueda - + Show &Status Bar Mostrar barra de &estado - + Show Status Bar Mostrar barra de estado - + &Browse Public Game Lobby &Buscar en la sala de juegos públicos - + &Create Room &Crear sala - + &Leave Room &Abandonar sala - + &Direct Connect to Room &Conexión directa a una sala - + &Show Current Room &Mostrar sala actual - + F&ullscreen P&antalla completa - + &Restart &Reiniciar - + Load/Remove &Amiibo... Cargar/Eliminar &Amiibo... - - &Report Compatibility - &Reporte de compatibilidad - - - + Open &Mods Page Abrir página de &mods - - Open &Quickstart Guide - Abrir guía de &inicio rápido + + &User Handbook + Manual de &usuario - - &FAQ - &Preguntas frecuentes - - - + &Capture Screenshot &Captura de pantalla - + &Album &Álbum - + &Set Nickname and Owner &Darle nombre y propietario - + &Delete Game Data &Borrar datos de juego - + &Restore Amiibo &Restaurar Amiibo - + &Format Amiibo &Formatear Amiibo - + &Mii Editor &Editor de Mii - + &Configure TAS... &Configurar TAS... - + Configure C&urrent Game... Configurar el j&uego actual... - - + + &Start &Iniciar - + &Reset &Reiniciar - - + + R&ecord G&rabar - + Open &Controller Menu Abrir el menú de &mandos - + Install Decryption &Keys Instalar &llaves de desencriptación - + &Home Menu &Menú de inicio - + &Desktop &Escritorio - + &Application Menu &Menú de la aplicación - + &Root Data Folder Carpeta de datos &raíz - + &NAND Folder Carpeta &NAND - + &SDMC Folder Carpeta &SDMC - + &Mod Folder Carpeta de &mods - + &Log Folder Carpeta de &registros - + From Folder Desde una carpeta - + From ZIP Desde un archivo ZIP - + &Eden Dependencies &Dependencias de Eden - + &Data Manager Gestor de &datos - + &Tree View Vista en &árbol - + &Grid View Vista en &cuadrícula - + Game Icon Size Tamaño del icono de los juegos - - + + None Nada - + Show Game &Name Mostrar el &nombre del juego - + Show &Performance Overlay Mostrar superposición de &rendimiento - + + Post-Processing &Shaders... + + + + + &Carousel View + Vista de &carrusel + + + Small (32x32) Pequeño (32x32) - + Standard (64x64) Estandar (64x64) - + Large (128x128) Grande (128x128) - + Full Size (256x256) Tamaño completo (256x256) - + Broken Vulkan Installation Detected Detectada instalación fallida de Vulkan - + Vulkan initialization failed during boot. La inicialización de Vulkan falló durante el arranque. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping Ejecutando un juego - + Loading Web Applet... Cargando applet web... - - + + Disable Web Applet Desactivar applet web - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) Desactivar el applet web puede causar comportamientos inesperados y debería solo ser usado con Super Mario 3D All-Stars. ¿Está seguro de que quiere desactivar el applet web? (Puede ser activado en las ajustes de depuración.) - + The amount of shaders currently being built La cantidad de sombreadores que se están contruyendo actualmente - + The current selected resolution scaling multiplier. El multiplicador de escalado de resolución seleccionado actualmente. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. Velocidad de emulación actual. Los valores superiores o inferiores al 100% indican que la emulación se ejecuta más rápida o más lenta que en una Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. La cantidad de fotogramas por segundo que muestra el juego actualmente. Esto varía según el juego y la escena. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. Tiempo empleado en emular un fotograma de la Switch, sin contar la limitación de fotogramas ni la sincronización vertical. Para una emulación a máxima velocidad, debería ser de 16,67 ms como máximo. - + Unmute Desilenciar - + Mute Silenciar - + Reset Volume Restablecer volumen - + + Configure Effects... + + + + &Clear Recent Files &Limpiar archivos recientes - + &Continue &Continuar - + Warning: Outdated Game Format Advertencia: formato del juego obsoleto - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - Está usando el formato de directorio ROM deconstruido para este juego, el cual es un formato obsoleto que ha sido reemplazado por otros como NCA, NAX, XCI o NSP. Los directorios ROM deconstruidos carecen de iconos, metadatos y compatibilidad con actualizaciones.<br>Para obtener una explicación de los distintos formatos de la Switch compatibles con Eden, consulte nuestro manual de usuario. Este mensaje no volverá a aparecer de nuevo. + Estás usando el formato de directorio ROM deconstruido para este juego, el cual es un formato obsoleto que ha sido reemplazado por otros como NCA, NAX, XCI o NSP. Los directorios ROM deconstruidos carecen de iconos, metadatos y compatibilidad con actualizaciones.<br>Para obtener una explicación de los distintos formatos de Switch compatibles con Eden, consulta nuestro manual de usuario. Este mensaje no volverá a aparecer de nuevo. - - + + Error while loading ROM! ¡Error al cargar la ROM! - + The ROM format is not supported. El formato de la ROM no está soportado. - + An error occurred initializing the video core. Se produjo un error al inicializar el núcleo de video. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. - Eden ha detectado un error al ejecutar el núcleo de vídeo. Esto suele deberse por usar controladores de la GPU obsoletos, incluidos los integrados. Consulte el registro para obtener más información. Para más información sobre cómo acceder al archivo de registro, por favor consulte la siguiente página: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>Cómo subir el archivo de registro</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. + - + Error while loading ROM! %1 %1 signifies a numeric error code. ¡Error al cargar la ROM! %1 - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. %1<br>Por favor vuelva a volcar sus archivos, o pida ayuda en Discord/Stoat. - + An unknown error occurred. Please see the log for more details. Ha ocurrido un error desconocido. Por favor mire el registro para más detalles. - + + (64-bit) (64-bit) - + + (32-bit) (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit %1 %2 - + Closing software... Cerrando el programa... - + Save Data Datos de guardado - + Mod Data Datos del mod - + Error Opening %1 Folder Error abriendo la carpeta %1 - - + + Folder does not exist! ¡La carpeta no existe! - + Remove Installed Game Contents? ¿Eliminar el contenido instalado del juego? - + Remove Installed Game Update? ¿Eliminar la actualización instalada del juego? - + Remove Installed Game DLC? ¿Eliminar el contenido descargable instalado del juego? - + Remove Entry Eliminar entrada - + Delete OpenGL Transferable Shader Cache? ¿Desea eliminar el caché transferible de sombreadores de OpenGL? - + Delete Vulkan Transferable Shader Cache? ¿Desea eliminar el caché transferible de sombreadores de Vulkan? - + Delete All Transferable Shader Caches? ¿Desea eliminar todo el caché transferible de sombreadores? - + Remove Custom Game Configuration? ¿Borrar configuración personalizada del juego? - + Remove Cache Storage? ¿Borrar el almacenamiento de caché? - + Remove File Eliminar archivo - + Remove Play Time Data Eliminar datos del tiempo de juego - + Reset play time? ¿Reiniciar el tiempo de juego? - - + + RomFS Extraction Failed! ¡Fallo al extraer el RomFS! - + There was an error copying the RomFS files or the user cancelled the operation. Ocurrió un error al copiar los archivos de la RomFS o el usuario canceló la operación. - + Full Completo - + Skeleton Esqueleto - + Select RomFS Dump Mode Seleccione el método de volcado del RomFS - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - Por favor seleccione como quiere que sea volcada la RomFS.<br>Completa copiará todos los archivos en el nuevo directorio mientras que <br>esqueleto solo creará la estructura de los directorios. + Por favor selecciona como quieres que sea volcada la RomFS.<br>Completa copiará todos los archivos en el nuevo directorio mientras que <br>esqueleto solo creará la estructura de los directorios. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root No hay suficiente espacio libre en %1 para extraer la RomFS. Por favor libera espacio o selecciona un directorio de volcado diferente en Emulación > Configurar > Sistema > Sistema de archivos > Volcado raiz - + Extracting RomFS... Extrayendo el RomFS... - - + + Cancel Cancelar - + RomFS Extraction Succeeded! ¡La extracción del RomFS ha sido un éxito! - + The operation completed successfully. La operación se completó con éxito. - + Error Opening %1 Error al abrir %1 - + Select Directory Seleccionar directorio - + Properties Propiedades - + The game properties could not be loaded. No se pudieron cargar las propiedades del juego. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. Ejecutable de Switch (%1);;Todos los archivos (*.*) - + Load File Cargar archivo - + Open Extracted ROM Directory Abrir directorio de la ROM extraída - + Invalid Directory Selected Seleccionado directorio inválido - + The directory you have selected does not contain a 'main' file. El directorio seleccionado no contiene un archivo 'main'. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) Archivo de Switch instalable (*.nca *.nsp *.xci);;Archivo de contenido de Nintendo (*.nca);;Paquete de envío de Nintendo (*.nsp);;Imagen de cartucho NX (*.xci) - + Install Files Instalar archivos - + %n file(s) remaining %n archivo(s) restantes%n archivo(s) restantes%n archivo(s) restantes - + Installing file "%1"... Instalando el archivo "%1"... - - + + Install Results Resultados de la instalación - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. Para evitar posibles conflictos, desaconsejamos instalar juegos base en la NAND. Por favor, use solo esta función para instalar actualizaciones y contenido descargable. - + %n file(s) were newly installed %n archivo(s) se instalaron recientemente @@ -7664,7 +7731,7 @@ Por favor, use solo esta función para instalar actualizaciones y contenido desc - + %n file(s) were overwritten %n archivo(s) fueron sobreescritos @@ -7673,7 +7740,7 @@ Por favor, use solo esta función para instalar actualizaciones y contenido desc - + %n file(s) failed to install fallo al instalar %n archivo(s) @@ -7682,361 +7749,324 @@ Por favor, use solo esta función para instalar actualizaciones y contenido desc - + System Application Aplicación del sistema - + System Archive Archivo del sistema - + System Application Update Actualización de la aplicación del sistema - + Firmware Package (Type A) Paquete de firmware (Tipo A) - + Firmware Package (Type B) Paquete de firmware (Tipo B) - + Game Juego - + Game Update Actualización del juego - + Game DLC Contenido descargable del juego - + Delta Title Título Delta - + Select NCA Install Type... Seleccione el tipo de instalación NCA... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) Seleccione el tipo de título en el que desea instalar este NCA: (En la mayoría de los casos, la opción predeterminada "Juego" es suficiente). - + Failed to Install Fallo al instalar - + The title type you selected for the NCA is invalid. El tipo de título que ha seleccionado para el NCA es inválido. - + File not found Archivo no encontrado - + File "%1" not found Archivo "%1" no encontrado - + OK Aceptar - + Function Disabled Función desactivada - + Compatibility list reporting is currently disabled. Check back later! Los informes de la lista de compatibilidad están deshabilitados. ¡Vuelva más tarde! - + Error opening URL Error abriendo URL - + Unable to open the URL "%1". No se pudo abrir URL "%1". - + TAS Recording Grabación TAS - + Overwrite file of player 1? ¿Sobrescribir el archivo del jugador 1? - + Invalid config detected Detectada configuración inválida - + Handheld controller can't be used on docked mode. Pro controller will be selected. El mando del modo portátil no puede usarse en el modo sobremesa. El mando pro será seleccionado en su lugar. - - + + Amiibo Amiibo - - + + The current amiibo has been removed El amiibo actual fue borrado - + Error Error - - + + The current game is not looking for amiibos El juego actual no está buscando amiibos - + Amiibo File (%1);; All Files (*.*) Archivo Amiibo (%1);; Todos los archivos (*.*) - + Load Amiibo Cargar Amiibo - + Error loading Amiibo data Error al cargar los datos del Amiibo - + The selected file is not a valid amiibo El archivo seleccionado no es un amiibo válido - + The selected file is already on use El archivo seleccionado se encuentra en uso - + An unknown error occurred Ha ocurrido un error desconocido - - - Keys not installed - Claves no instaladas - - - - - Install decryption keys and restart Eden before attempting to install firmware. - Instale las claves de desencriptado y reinicie Eden antes de intentar instalar el firmware. - - - - Select Dumped Firmware Source Location - Seleccionar ubicación fuente del firmware volcado - - - - Select Dumped Firmware ZIP - Seleccionar ZIP del firmware volcado - - - - Zipped Archives (*.zip) - Archivos comprimidos (*.zip) - - - - Firmware cleanup failed - Fallo al limpiar firmware - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - Fallo al limpiar el cache del firmware extraído. -Compruebe los permisos de escritura en el directorio de temporales del sistema e inténtelo de nuevo. -Error reportado por el sistema operativo: %1 - - - + No firmware available No hay firmware disponible - + Firmware Corrupted Firmware corrompido - + Unknown applet Applet desconocido - + Applet doesn't map to a known value. Applet no asigna a un valor conocido. - + Record not found Grabación no encontrada - + Applet not found. Please reinstall firmware. Apple no encontrado. Por favor vuelva a instalar el firmware. - + Capture Screenshot Captura de pantalla - + PNG Image (*.png) Imagen PNG (*.png) - + TAS state: Running %1/%2 Estado de TAS: Ejecutando %1/%2 - + TAS state: Recording %1 Estado de TAS: Grabando %1 - + TAS state: Idle %1/%2 Estado de TAS: Inactivo %1/%2 - + TAS State: Invalid Estado de TAS: Inválido - + &Stop Running &Parar de ejecutarse - + Stop R&ecording &Parar grabación - + Building: %n shader(s) Construyendo: %n sombreador(es)Construyendo: %n sombreador(es)Construyendo: %n sombreador(es) - + Scale: %1x %1 is the resolution scaling factor Escala: %1x - + Speed: %1% / %2% Velocidad: %1% / %2% - + Speed: %1% Velocidad: %1% - + Game: %1 FPS Juego: %1 FPS - + Frame: %1 ms Fotograma: %1 ms - - - FSR - FSR - - - + NO AA NO AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE VOLUMEN: SILENCIADO - + VOLUME: %1% Volume percentage (e.g. 50%) VOLUMEN: %1% - + Derivation Components Missing Componentes de derivación ausentes - + Decryption keys are missing. Install them now? Las claves de desencriptado están ausentes. ¿Desea instalarlas ahora? - + Wayland Detected! ¡Wayland detectado! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -8047,74 +8077,74 @@ Se recomienda usar X11 en su lugar. ¿Le gustaría forzar a usarlo en futuras ejecuciones? - + Use X11 Usar X11 - + Continue with Wayland Continuar con Wayland - + Don't show again No mostrar de nuevo - + Restart Required Reinicio requerido - + Restart Eden to apply the X11 backend. Reinicie Eden para aplicar el motor X11. - + Slow Lento - + Turbo Turbo - + Unlocked Desbloqueado - + Select RomFS Dump Target Seleccione el destinatario para el volcado del RomFS - + Please select which RomFS you would like to dump. Por favor seleccione que RomFS desea volcar. - + Are you sure you want to close Eden? ¿Está seguro de que quiere cerrar Eden? - - - + + + Eden Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. ¿Está seguro de que desea parar la emulación? Cualquier progreso sin guardar se perderá. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8182,6 +8212,11 @@ Would you like to bypass this and exit anyway? ScaleForce ScaleForce + + + FSR + FSR + Area @@ -8193,52 +8228,57 @@ Would you like to bypass this and exit anyway? MMPX - + + SGSR + SGSR + + + + SGSR EdgeDir + SGSR EdgeDir + + + Docked Anclado - + Handheld Portátil - + Fast Rápido - - Balanced - Equilibrado - - - + Accurate Preciso - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL SPIRV OpenGL SPIRV - + OpenGL GLASM OpenGL GLASM - + Null Nulo @@ -8281,7 +8321,7 @@ Si quieres limpiar los archivos que se quedaron en el ubicacion de datos anticua %1 - + Data was migrated successfully. Datos se migraron con exito. @@ -8774,8 +8814,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8788,10 +8828,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8806,9 +8846,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8970,50 +9010,50 @@ p, li { white-space: pre-wrap; } [sin definir] - + %1%2 %1%2 - + [invalid] [inválido] - - + + %1%2Hat %3 %1%2Rotación %3 - - - + + + %1%2Axis %3 %1%2Eje %3 - + %1%2Axis %3,%4,%5 %1%2Eje %3,%4,%5 - + %1%2Motion %3 %1%2Movimiento %3 - - + + %1%2Button %3 %1%2Botón %3 - + [unused] [no usado] @@ -9139,47 +9179,47 @@ p, li { white-space: pre-wrap; } %1 esta jugando %2 - + Play Time: %1 Tiempo de juego: %1 - + Never Played Nunca jugado - + Version: %1 Versión: %1 - + Version: 1.0.0 Versión: 1.0.0 - + Installed SD Titles Títulos instalados en la SD - + Installed NAND Titles Títulos instalados en NAND - + System Titles Títulos del sistema - + Add New Game Directory Añadir un nuevo directorio de juego - + Favorites Favoritos @@ -9300,14 +9340,14 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware El juego requiere firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. - El juego que está tratando de abrir requiere firmware para arrancar o para pasar el menú de apertura. Por favor, <a href='https://yuzu-mirror.github.io/help/quickstart'>volcar e instalar el firmware</a>, o presionar "OK" para abrir de todas formas. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. + @@ -9316,10 +9356,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel Cancelar @@ -9340,7 +9380,7 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 @@ -9349,209 +9389,244 @@ p, li { white-space: pre-wrap; } %1 - - + + Verifying integrity... Verificando integridad... - - + + Integrity verification succeeded! ¡La verificación de integridad ha sido un éxito! - - + + The operation completed successfully. La operación se completó con éxito. - - + + Integrity verification failed! ¡Verificación de integridad se fallo! - + File contents may be corrupt or missing. Los contenidos del archivo pueden estar corruptos. - + Integrity verification couldn't be performed No se pudo ejecutar la verificación de integridad - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. Instalacion de firmware cancellado , firmware podria estar en un mal estado o coruptos. contenidos de el archivo no pudieron ser verificados para validez. - + Select Dumped Keys Location Seleccionar ubicación de origen de los llaves volcados - + Decryption Keys install succeeded Instalación de llaves de descifra salo con exito - + Decryption Keys install failed Instalacion de las llaves de descifra se fallo - + Orphaned Profiles Detected! ¡Se detectaron perfiles huérfanos! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> ¡PODRÍAN SUCEDER COSAS MALAS E INESPERADAS SI NO LEE ESTO!<br>Eden ha detectado que los siguientes directorios de guardado no tienen perfil asociado:<br>%1<br><br>Los siguientes perfiles son válidos:<br>%2<br><br>Haga clic en "Aceptar" para abrir la carpeta de guardado y arreglar sus perfiles.<br>Consejo: copie el contenido de la carpeta más grande o la última modificada en otro lugar, elimine todos los perfiles huérfanos y mueva el contenido copiado al perfil correcto.<br><br>¿Aún tiene dudas? Consulte la <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>página de ayuda</a>.<br> - + Really clear data? ¿Realmente deseas borrar los datos? - + Important data may be lost! ¡Podrías perder información importante! - + Are you REALLY sure? ¿Estás REALMENTE seguro? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. ¡Una vez eliminados, tus datos no podrán recuperarse! Haz esto solo si estás 100% seguro de que deseas borrarlos. - + Clearing... Limpiando... - + Select Export Location Selecciona la Ubicación de Exportación. - + %1.zip %1.zip - - + + + Zipped Archives (*.zip) Archivos comprimidos (*.zip) - + Exporting data. This may take a while... Exportando datos. Esto puede tardar un poco... - + Exporting Exportando - + Exported Successfully Exportación Exitosa. - + Data was exported successfully. Los datos se exportaron correctamente. - + Export Cancelled Exportación cancelada. - + Export was cancelled by the user. La exportación fue cancelada por el usuario. - + Export Failed Exportación Fallida - + Ensure you have write permissions on the targeted directory and try again. Asegúrate de tener permisos de escritura en el directorio seleccionado e inténtalo nuevamente. - + Select Import Location Seleccionar ubicación de importación. - + Import Warning Advertencia al importar datos - + All previous data in this directory will be deleted. Are you sure you wish to proceed? Todos los datos anteriores en este directorio serán eliminados. ¿Estás seguro de que deseas continuar? - + Importing data. This may take a while... Importando datos. Esto puede tomar unos minutos... - + Importing Importando - + Imported Successfully Importación completada con éxito. - + Data was imported successfully. Los datos se importaron correctamente. - + Import Cancelled La importación fue cancelada. - + Import was cancelled by the user. La importación fue cancelada por el usuario. - + Import Failed Importación Fallida. - + Ensure you have read permissions on the targeted directory and try again. Asegúrate de tener permisos de lectura en el directorio seleccionado e inténtalo nuevamente. + + + Keys not installed + Claves no instaladas + + + + Install decryption keys and restart Eden before attempting to install firmware. + Instalar las claves de desencriptación y reiniciar Eden antes de intentar instalar el firmware. + + + + Select Dumped Firmware Source Location + Seleccionar ubicación origen del firmware volcado + + + + Select Dumped Firmware ZIP + Seleccionar el archivo ZIP del firmware volcado + + + + Firmware cleanup failed + Fallo al limpiar firmware + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + Fallo al limpiar el caché del firmware extraído. +Verifique los permisos de escritura en el directorio temporal del sistema e inténtalo de nuevo. +Error reportado por el sistema operativo: %1 + QtCommon::FS @@ -9627,9 +9702,9 @@ A: Could not find a valid Ryujinx installation. This may typically occur if you are using Ryujinx in portable mode. Would you like to manually select a portable folder to use? - No se pudo encontrar una instalación válida de Ryujinx. Esto suele ocurrir si usa Ryujinx en modo portable. + No se pudo encontrar una instalación válida de Ryujinx. Esto suele ocurrir si usas Ryujinx en modo portable. -¿Desea seleccionar manualmente una carpeta portable? +¿Deseas seleccionar manualmente una carpeta portable? @@ -9800,72 +9875,72 @@ Would you like to manually select a portable folder to use? El caché de metadatos no se pudo eliminar. Podría estar en uso actualmente o no existe. - + Create Shortcut Crear acceso directo - + Do you want to launch the game in fullscreen? ¿Desea iniciar el juego en pantalla completa? - + Shortcut Created Acceso directo creado - + Successfully created a shortcut to %1 Se ha creado un acceso directo a %1 con éxito - + Shortcut may be Volatile! ¡El acceso directo podría ser volátil! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? Esto creará un acceso directo a la AppImage actual. Puede que no funcione bien si actualiza. ¿Continuar? - + Failed to Create Shortcut Fallo al crear el acceso directo - + Failed to create a shortcut to %1 Fallo al crear un acceso directo a %1 - + Create Icon Crear icono - + Cannot create icon file. Path "%1" does not exist and cannot be created. No se puede crear el archivo de icono. La ruta "%1" no existe y no se pudo creer. - + No firmware available No hay firmware disponible - + Please install firmware to use the home menu. Por favor intenta instalar firmware para usar el menu de inicio. - + Home Menu Applet Applet del Menu de Inicio - + Home Menu is not available. Please reinstall firmware. Menu de inicio no esta disponible. Por favor intenta reinstalar firmware. @@ -9873,37 +9948,37 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name Nombre del mod - + What should this mod be called? ¿Cómo debería llamarse este mod? - + RomFS RomFS - + ExeFS/Patch ExeFS/Parche - + Cheat Truco - + Mod Type Tipo de mod - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. @@ -9912,18 +9987,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed Fallo al extraer el mod - + Failed to create temporary directory %1 Fallo al crear directorio temporal %1 - + Zip file %1 is empty El archivo zip %1 está vacío @@ -10629,66 +10704,66 @@ Seleccionando "Desde Eden", los datos de guardado anteriores alojados %1 está disponible para descargar. - + New Version Location Ubicación de la nueva versión - + All Files (*.*) Todos los archivos (*.*) - - - + + + Failed to save file Fallo al guardar el archivo - + Could not open file %1 for writing. No se pudo abrir el archivo %1 para su escritura. - + Downloading... Descargando... - + Cancel Cancelar - + Could not write to file %1. No se pudo escribir en el archivo %1. - + Could not commit to file %1. No se pudo cometer en el archivo %1. - + Failed to download file Fallo al descargar el archivo - + Could not download from %1%2 Error code: %3 No se pudo descargar desde %1%2 Código de error: %3 - + Download Complete Descarga completada - + Successfully downloaded %1. Would you like to open it? %1 descargado con éxito. ¿Desea abrirlo? diff --git a/dist/languages/fi.ts b/dist/languages/fi.ts index 41917c09db..5b075f0b0e 100644 --- a/dist/languages/fi.ts +++ b/dist/languages/fi.ts @@ -209,155 +209,6 @@ This would ban both their forum username and their IP address. %1 - %2 (%3/%4 jäsentä) - yhdistetty - - CompatDB - - - Report Compatibility - Raportoi yhteensopivuus - - - - - - - - - - Report Game Compatibility - Raportoi pelin yhteensopivuus - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - <html><head/><body><p><span style=" font-size:10pt;">Jos päätät lähettää testiraportin </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">edenin yhteensopivuuslistalle</span></a><span style=" font-size:10pt;">, seuraava informaatio kerätään ja näytetään sivustolla:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Järjestelmäinformaatio (CPU / GPU / Käyttöjärjestelmä)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Käyttämäsi edenin versio</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Yhdistetty eden -käyttäjätilisi</li></ul></body></html> - - - - <html><head/><body><p>Does the game boot?</p></body></html> - Käynnistyykö peli? - - - - Yes The game starts to output video or audio - Kyllä, peli näyttää kuvaa ja äänet kuuluvat - - - - No The game doesn't get past the "Launching..." screen - Ei Peli ei etene "Käynnistetään..." näytöstä - - - - Yes The game gets past the intro/menu and into gameplay - Kyllä Peli etenee introsta/päävalikosta itse peliin - - - - No The game crashes or freezes while loading or using the menu - Ei Peli kaatuu tai jumittuu ladatessaan ta kun päävalikkoa käytetään. - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>Päästäänkö itse peliin?</p></body></html> - - - - Yes The game works without crashes - Kyllä Peli toimii kaatumatta - - - - No The game crashes or freezes during gameplay - Ei Peli kaatuu tai jumittuu pelin aikana - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>Toimiiko peli kaatumatta, jumittumatta tai lukittumatta?</p></body></html> - - - - Yes The game can be finished without any workarounds - Kyllä Peli on pelattavissa loppuun ilman kiertoteitä - - - - No The game can't progress past a certain area - Ei Pelissä ei voi edetä tietyn osoion jälkeen - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>Onko peli täysin pelattavissa alusta loppuun?</p></body></html> - - - - Major The game has major graphical errors - Suuria Pelin grafiikoissa on suuria virheitä - - - - Minor The game has minor graphical errors - Pieniä Pelin grafiikoissa on pikkuvirheitä - - - - None Everything is rendered as it looks on the Nintendo Switch - Ei mitään Grafiikat vastaavat täysin Nintendo Switch:iä - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>Onko pelissä joitain grafiikan häiriöitä?</p></body></html> - - - - Major The game has major audio errors - Suuria Pelin äänessä on isoja vikoja - - - - Minor The game has minor audio errors - Pieniä Pelin äänessä on pikkuvikoja - - - - None Audio is played perfectly - Ei mitään Äänet toistuvat täydellisesti - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>Onko pelissä äänen häiriöitä / puutteita?</p></body></html> - - - - Thank you for your submission! - Kiitos raportistasi! - - - - Submitting - Lähetetään - - - - Communication error - Lähetysvirhe - - - - An error occurred while sending the Testcase - Testiraporttia lähetettäessä tapahtui virhe - - - - Next - Seuraava - - ConfigurationShared @@ -559,231 +410,241 @@ Can help reduce stuttering at lower framerates. - + Backend: - CPU Overclock + CPU Clocks - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. - + Custom CPU Ticks - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. - + Enable Host MMU Emulation (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. - + Unfuse FMA (improve performance on CPUs without FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. - + Faster FRSQRTE and FRECPE - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. - + Faster ASIMD instructions (32 bits only) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. - + Inaccurate NaN handling - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. - + Disable address space checks - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. - + Ignore global monitor - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. - + API: - + Changes the output graphics API. Vulkan is recommended. - + Device: - + This setting selects the GPU to use (Vulkan only). - + Resolution: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. - + Window Adapting Filter: - + FSR Sharpness: - - Determines how sharpened the image will look using FSR's dynamic contrast. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. - + Anti-Aliasing Method: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. + + + Enable post-processing effects + + - Fullscreen Mode: + Applies post-processing effects to the final image. + Fullscreen Mode: + + + + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. - + Aspect Ratio: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. - + Use persistent pipeline cache - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. - + Use asynchronous GPU emulation - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + NVDEC emulation: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. - + ASTC Decoding Method: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -792,55 +653,75 @@ stuttering but may present artifacts. - + ASTC Recompression Method: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. - + Skip CPU Inner Invalidation - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -848,1361 +729,1371 @@ Immediate (no synchronization) presents whatever is available and can exhibit te - + Sync Memory Operations - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. - + Enable asynchronous presentation (Vulkan only) - + Slightly improves performance by moving presentation to a separate CPU thread. - + Force maximum clocks (Vulkan only) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. - + Anisotropic Filtering: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. - + GPU Mode: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - + DMA Accuracy: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. - + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation - + May reduce shader stutter. - - Fast GPU Time + + GPU Clocks - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. - + GPU Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. - + Enable Compute Pipelines (Intel Vulkan Only) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. - + Enable Reactive Flushing - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. - + Sync to framerate of video playback - + Run the game at normal speed during video playback, even when the framerate is unlocked. - + Barrier feedback loops - + Improves rendering of transparency effects in specific games. - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. - + Vertex Input Dynamic State - + Enables vertex input dynamic state feature for better quality and performance. - + Sample Shading - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. - + RNG Seed - + Controls the seed of the random number generator. Mainly used for speedrunning. - + Device Name - + The name of the console. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: - + This option allows to change the clock of the console. Can be used to manipulate time in games. - + The number of seconds from the current unix time - + Language: - + This option can be overridden when region setting is auto-select - + Region: - + The region of the console. - + Time Zone: - + The time zone of the console. - + Sound Output Mode: - + Console Mode: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. - + Prompt for user profile on boot - + Useful if multiple people use the same PC. - + Pause when not in focus - + Pauses emulation when focusing on other windows. - + Confirm before stopping emulation - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. - + Hide mouse on inactivity - + Hides the mouse after 2.5s of inactivity. - - Disable controller applet - - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - - - - + Check for updates - + Whether or not to check for updates upon startup. - + Enable Gamemode - + Force X11 as Graphics Backend - + Custom frontend - + Real applet - + + + Disabled + + + + Never - + On Load - + Always - + CPU - + GPU - + CPU Asynchronous - + Uncompressed (Best quality) - + BC1 (Low quality) - + BC3 (Medium quality) - - + + Auto - + 30 FPS - + 60 FPS - + 90 FPS - + 120 FPS - + Conservative - + Aggressive - + Vulkan - + OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) - + Null - + Fast - - Balanced - - - - - + + + Accurate - - + + + Default - + Unsafe (fast) - + Safe (stable) - - - Unsafe - - - Paranoid (disables most optimizations) + Immediate - Debugging + Balanced - - Dynarmic - - - - - NCE + + Unsafe - Borderless Windowed + Paranoid (disables most optimizations) - Exclusive Fullscreen + Debugging - No Video Output + Dynarmic - CPU Video Decoding + NCE - - GPU Video Decoding (Default) + + Borderless Windowed - - 0.25X (180p/270p) [EXPERIMENTAL] - - - - - 0.5X (360p/540p) [EXPERIMENTAL] - - - - - 0.75X (540p/810p) [EXPERIMENTAL] + + Exclusive Fullscreen - 1X (720p/1080p) + No Video Output - 1.25X (900p/1350p) [EXPERIMENTAL] + CPU Video Decoding - 1.5X (1080p/1620p) [EXPERIMENTAL] - - - - - 2X (1440p/2160p) - - - - - 3X (2160p/3240p) - - - - - 4X (2880p/4320p) - - - - - 5X (3600p/5400p) + GPU Video Decoding (Default) - 6X (4320p/6480p) + 0.25X (180p/270p) [EXPERIMENTAL] - 7X (5040p/7560p) + 0.5X (360p/540p) [EXPERIMENTAL] - 8X (5760p/8640p) + 0.75X (540p/810p) [EXPERIMENTAL] + + + + + 1X (720p/1080p) + + + + + 1.25X (900p/1350p) [EXPERIMENTAL] + + + + + 1.5X (1080p/1620p) [EXPERIMENTAL] - Nearest Neighbor + 2X (1440p/2160p) - Bilinear + 3X (2160p/3240p) - Bicubic + 4X (2880p/4320p) - Gaussian + 5X (3600p/5400p) - Lanczos + 6X (4320p/6480p) - ScaleForce + 7X (5040p/7560p) - AMD FidelityFX Super Resolution - - - - - Area - - - - - MMPX - - - - - Zero-Tangent + 8X (5760p/8640p) - B-Spline + Nearest Neighbor - Mitchell + Bilinear - Spline-1 + Bicubic + + + + + Gaussian + + + + + Lanczos + + + + + ScaleForce - - None + AMD FidelityFX Super Resolution - FXAA + Area - SMAA + MMPX + + + + + Zero-Tangent + + + + + B-Spline + + + + + Mitchell - Default (16:9) + Spline-1 - Force 4:3 + Snapdragon Game Super Resolution - Force 21:9 + Snapdragon Game Super Resolution EdgeDir - - Force 16:10 + + None - - Stretch to Window + + FXAA - Automatic - - - - - 2x - - - - - 4x + SMAA - 8x + Default (16:9) - 16x + Force 4:3 - 32x + Force 21:9 - 64x + Force 16:10 - - Japanese (日本語) + + Stretch to Window + + + + + Automatic - American English + 2x - French (français) + 4x - German (Deutsch) + 8x - Italian (italiano) - - - - - Spanish (español) - - - - - Chinese - - - - - Korean (한국어) - - - - - Dutch (Nederlands) + 16x - Portuguese (português) + Japanese (日本語) - Russian (Русский) + American English - Taiwanese + French (français) - British English + German (Deutsch) - Canadian French + Italian (italiano) - Latin American Spanish + Spanish (español) - Simplified Chinese + Chinese - Traditional Chinese (正體中文) + Korean (한국어) - Brazilian Portuguese (português do Brasil) + Dutch (Nederlands) - Polish (polska) + Portuguese (português) - Thai (แบบไทย) + Russian (Русский) + + + + + Taiwanese + + + + + British English + + + + + Canadian French - - Japan + Latin American Spanish - USA + Simplified Chinese - Europe + Traditional Chinese (正體中文) - Australia + Brazilian Portuguese (português do Brasil) - China + Polish (polska) - Korea + Thai (แบบไทย) - - Taiwan + + + Japan + + + + + USA + + + + + Europe + Australia + + + + + China + + + + + Korea + + + + + Taiwan + + + + Auto (%1) Auto select time zone - + Default (%1) Default time zone - + CET - + CST6CDT - + Cuba - + EET - + Egypt - + Eire - + EST - + EST5EDT - + GB - + GB-Eire - + GMT - + GMT+0 - + GMT-0 - + GMT0 - + Greenwich - + Hongkong - + HST - + Iceland - + Iran - + Israel - + Jamaica - + Kwajalein - + Libya - + MET - + MST - + MST7MDT - + Navajo - + NZ - + NZ-CHAT - + Poland - + Portugal - + PRC - + PST8PDT - + ROC - + ROK - + Singapore - + Turkey - + UCT - + Universal - + UTC - + W-SU - + WET - + Zulu - + Mono - + Stereo - + Surround - + 4GB DRAM (Default) - + 6GB DRAM (Unsafe) - + 8GB DRAM - + 10GB DRAM (Unsafe) - + 12GB DRAM (Unsafe) - + Docked - + Handheld - - - Off + + + Normal - - Boost (1700MHz) + + + Boost - - Fast (2000MHz) + + + Overclock - + Always ask (Default) - + Only if game specifies not to stop - + Never ask - - - Medium (256) - - - - - - High (512) - - - - + Very Small (16 MB) - + Small (32 MB) - + Normal (128 MB) - + Large (256 MB) - + Very Large (512 MB) - + Very Low (4 MB) - + Low (8 MB) - + Normal (16 MB) - + Medium (32 MB) - + High (64 MB) - + Very Low (32) - + Low (64) - + Normal (128) - - Disabled + + Medium (256) - + + High (512) + + + + ExtendedDynamicState 1 - + ExtendedDynamicState 2 - + ExtendedDynamicState 3 - + Tree View - + Grid View @@ -2530,252 +2421,297 @@ Ota sisäiset sivutaulukot käyttöön Lokitiedoston filtteri - - When checked, the max size of the log increases from 100 MB to 1 GB + + GPU Logging/Level - - Enable Extended Logging** + + Detail level for GPU logs. Off disables logging entirely. - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console - - Open Log Location - Avaa lokitiedoston sijainti - - - - Homebrew - Homebrew - - - - Arguments String - Argumentit - - - - Graphics - Grafiikat - - - - When checked, it executes shaders without loop logic changes - - - - - Disable Loop safety checks + + When checked, the max size of the log increases from 100 MB to 1 GB + Enable Extended Logging** + + + + + Open Log Location + Avaa lokitiedoston sijainti + + + + Homebrew + Homebrew + + + + Arguments String + Argumentit + + + + Graphics + Grafiikat + + + + When checked, it executes shaders without loop logic changes + + + + + Disable Loop safety checks + + + + When checked, it will dump all the macro programs of the GPU - + Dump Maxwell Macros - + When checked, it enables Nsight Aftermath crash dumps - + Enable Nsight Aftermath - - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - - - - - Dump Game Shaders - - - - - Enable Renderdoc Hotkey - - - - - When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower - - - - - Disable Macro JIT - Poista Macro JIT käytöstä - - - - When checked, it disables the macro HLE functions. Enabling this makes games run slower - - - - - Disable Macro HLE - - - - - When checked, the graphics API enters a slower debugging mode - - - - - Enable Graphics Debugging - Ota käyttöön grafiikkavirheenjäljitys - - - - When checked, yuzu will log statistics about the compiled pipeline cache - - - Enable Shader Feedback + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. - - <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + Dump Guest (Maxwell) Shaders - Disable Buffer Reorder + Enable Renderdoc Hotkey - Advanced - Edistyneet asetukset - - - - Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower - - Perform Startup Vulkan Check + + Disable Macro JIT + Poista Macro JIT käytöstä + + + + When checked, it disables the macro HLE functions. Enabling this makes games run slower + Disable Macro HLE + + + + + When checked, the graphics API enters a slower debugging mode + + + + + Enable Graphics Debugging + Ota käyttöön grafiikkavirheenjäljitys + + + + When checked, yuzu will log statistics about the compiled pipeline cache + + + + + Enable Shader Feedback + + + + + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + + + + Disable Buffer Reorder + + + + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + + Advanced + Edistyneet asetukset + + + + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. + + + + + Perform Startup Vulkan Check + + + + Disable Web Applet - + Enable All Controller Types - + Enable Auto-Stub - + Kiosk (Quest) Mode Kiosk (vieras)tila - + Use dev.keys - + Enable Debug Asserts - + Debugging Virheenjäljitys - + Battery Serial: - + + Debug Knobs: + + + + Bitmask for quick development toggles - + Set debug knobs (bitmask) - + 16-bit debug knob set for quick development toggles - + (bitmask) - - Debug Knobs: - - - - + Unit Serial: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. - + Dump Audio Commands To Console** - + Flush log output on each line - + Enable FS Access Log - + Enable Verbose Reporting Services** - + Censor username in logs - + **This will be reset automatically when Eden closes. @@ -3225,33 +3161,38 @@ Would you like to delete the old save data? Taustan väri: - + % - FSR sharpening percentage (e.g. 50%) + FSR/SGSR sharpening percentage (e.g. 50%) - + + Configure Effects... + + + + Off - + VSync Off - + Recommended - + On - + VSync On @@ -3772,6 +3713,16 @@ Would you like to delete the old save data? Motion / Touch + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4280,58 +4231,58 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + New Profile - + Enter a profile name: - - + + Create Input Profile - + The given profile name is not valid! - + Failed to create the input profile "%1" - + Delete Input Profile - + Failed to delete the input profile "%1" - + Load Input Profile - + Failed to load the input profile "%1" - + Save Input Profile - + Failed to save the input profile "%1" @@ -4379,7 +4330,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Configure Säädä @@ -4410,7 +4361,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Test @@ -4425,77 +4376,77 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + %1:%2 - - - - - - + + + + + + Eden - + Port number has invalid characters - + Port has to be in range 0 and 65353 - + IP address is not valid - + This UDP server already exists - + Unable to add more than 8 servers - + Testing - + Configuring - + Test Successful - + Successfully received data from the server. - + Test Failed - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. @@ -4679,57 +4630,57 @@ Current values are %1% and %2% respectively. - + Add-Ons Lisäosat - + System Järjestelmä - + CPU CPU (prosessori) - + Graphics Grafiikat - + Adv. Graphics - + Ext. Graphics - + Audio Ääni - + Input Profiles - + Network - + Applets - + Properties Ominaisuudet @@ -4852,6 +4803,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5417,91 +5492,86 @@ Drag points to change position, or double-click table cells to edit values. - Show Compatibility List - - - - Show Add-Ons Column Näytä lisäosasarake - + Show Size Column - + Show File Types Column - + Show Play Time Column - + Folder Icon Size: - + Row 1 Text: Rivin 1 teksti: - + Row 2 Text: Rivin 2 teksti: - + Screenshots Kuvakaappaukset - + Ask Where To Save Screenshots (Windows Only) Kysy minne kuvakaappaukset tallennetaan (Vain Windowsilla) - + Screenshots Path: Kuvakaappauksien polku: - + ... ... - + TextLabel - + Resolution: - + Select Screenshots Path... Valitse polku kuvakaappauksille... - + <System> <System> - + English Englanti - + Auto (%1 x %2, %3 x %4) Screenshot width value @@ -5738,7 +5808,7 @@ Drag points to change position, or double-click table cells to edit values. - + Calculating... @@ -5940,50 +6010,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! openGL ei ole saatavilla! - + OpenGL shared contexts are not supported. - + Eden has not been compiled with OpenGL support. - - - + + + Error while initializing OpenGL! Virhe käynnistäessä OpenGL ydintä! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. - + Error while initializing OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 - + This build doesn't have OpenGL support. @@ -5991,279 +6061,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory - + Favorite - + Start Game - + Start Game without Custom Configuration - + Open Save Data Location Avaa tallennuskansio - + Open Mod Data Location Avaa modien tallennuskansio - + Open Transferable Pipeline Cache - + Link to Ryujinx - + Remove Poista - + Remove Installed Update Poista asennettu päivitys - + Remove All Installed DLC Poista kaikki asennetut DLC:t - + Remove Custom Configuration Poista mukautettu määritys - + Remove Cache Storage - + Remove OpenGL Pipeline Cache - + Remove Vulkan Pipeline Cache - + Remove All Pipeline Caches - + Remove All Installed Contents Poista kaikki asennettu sisältö - + Manage Play Time - + Edit Play Time Data - + Remove Play Time Data - - + + Dump RomFS Dumppaa RomFS - + Dump RomFS to SDMC - + Verify Integrity - + Copy Title ID to Clipboard Kopioi nimike ID leikepöydälle - - Navigate to GameDB entry - Siirry GameDB merkintään - - - + Create Shortcut - + Add to Desktop - + Add to Applications Menu - + Configure Game - + Scan Subfolders Skannaa alakansiot - + Remove Game Directory Poista pelikansio - + ▲ Move Up ▲ Liiku ylös - + ▼ Move Down ▼ Liiku alas - + Open Directory Location Avaa hakemisto - + Clear Tyhjennä - - - Name - Nimi - - - - Compatibility - Yhteensopivuus - - - - Add-ons - Lisäosat - - - - File type - Tiedostotyyppi - - - - Size - Koko - - - - Play time - - GameListItemCompat - + Ingame - + Game starts, but crashes or major glitches prevent it from being completed. - + Perfect Täydellinen - + Game can be played without issues. - + Playable - + Game functions with minor graphical or audio glitches and is playable from start to finish. - + Intro/Menu Intro/Valikko - + Game loads, but is unable to progress past the Start Screen. - + Won't Boot Ei käynnisty - + The game crashes when attempting to startup. Peli kaatuu käynnistettäessä. - + Not Tested Ei testattu - + The game has not yet been tested. Peliä ei ole vielä testattu + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list Tuplaklikkaa lisätäksesi uusi kansio pelilistaan. @@ -6271,17 +6334,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) - + Filter: Suodatin: - + Enter pattern to filter Syötä suodatettava tekstipätkä @@ -6775,1139 +6838,1112 @@ Debug Message: - + Game &Icon Size - + Reset Window Size to &720p - + Reset Window Size to 720p - + Reset Window Size to &900p - + Reset Window Size to 900p - + Reset Window Size to &1080p - + Reset Window Size to 1080p - + &Multiplayer - + &Tools - + Am&iibo - + Launch &Applet - + &TAS - + &Create Home Menu Shortcut - + Install &Firmware - + &Help &Apu - + &Install Files to NAND... - + L&oad File... - + Load &Folder... - + E&xit P&oistu - - + + &Pause &Pysäytä - + &Stop &Lopeta - + &Verify Installed Contents - + &About Eden - + Single &Window Mode - + Con&figure... - + Ctrl+, - + Enable Overlay Display Applet - + Show &Filter Bar - + Show &Status Bar - + Show Status Bar Näytä statuspalkki - + &Browse Public Game Lobby - + &Create Room - + &Leave Room - + &Direct Connect to Room - + &Show Current Room - + F&ullscreen - + &Restart - + Load/Remove &Amiibo... - - &Report Compatibility - - - - + Open &Mods Page - - Open &Quickstart Guide + + &User Handbook - - &FAQ - - - - + &Capture Screenshot - + &Album - + &Set Nickname and Owner - + &Delete Game Data - + &Restore Amiibo - + &Format Amiibo - + &Mii Editor - + &Configure TAS... - + Configure C&urrent Game... - - + + &Start &Käynnistä - + &Reset - - + + R&ecord - + Open &Controller Menu - + Install Decryption &Keys - + &Home Menu - + &Desktop - + &Application Menu - + &Root Data Folder - + &NAND Folder - + &SDMC Folder - + &Mod Folder - + &Log Folder - + From Folder - + From ZIP - + &Eden Dependencies - + &Data Manager - + &Tree View - + &Grid View - + Game Icon Size - - + + None - + Show Game &Name - + Show &Performance Overlay - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) - + Standard (64x64) - + Large (128x128) - + Full Size (256x256) - + Broken Vulkan Installation Detected - + Vulkan initialization failed during boot. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping - + Loading Web Applet... - - + + Disable Web Applet - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + The amount of shaders currently being built - + The current selected resolution scaling multiplier. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Unmute - + Mute - + Reset Volume - + + Configure Effects... + + + + &Clear Recent Files - + &Continue - + Warning: Outdated Game Format - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - - + + Error while loading ROM! - + The ROM format is not supported. - + An error occurred initializing the video core. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + An unknown error occurred. Please see the log for more details. - + + (64-bit) - + + (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + Closing software... - + Save Data - + Mod Data - + Error Opening %1 Folder - - + + Folder does not exist! - + Remove Installed Game Contents? - + Remove Installed Game Update? - + Remove Installed Game DLC? - + Remove Entry - + Delete OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? - + Remove Custom Game Configuration? - + Remove Cache Storage? - + Remove File - + Remove Play Time Data - + Reset play time? - - + + RomFS Extraction Failed! - + There was an error copying the RomFS files or the user cancelled the operation. - + Full - + Skeleton - + Select RomFS Dump Mode - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Extracting RomFS... - - + + Cancel - + RomFS Extraction Succeeded! - + The operation completed successfully. - + Error Opening %1 - + Select Directory - + Properties - + The game properties could not be loaded. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Load File - + Open Extracted ROM Directory - + Invalid Directory Selected - + The directory you have selected does not contain a 'main' file. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files - + %n file(s) remaining - + Installing file "%1"... - - + + Install Results - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + %n file(s) were newly installed - + %n file(s) were overwritten - + %n file(s) failed to install - + System Application - + System Archive - + System Application Update - + Firmware Package (Type A) - + Firmware Package (Type B) - + Game - + Game Update - + Game DLC - + Delta Title - + Select NCA Install Type... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Failed to Install - + The title type you selected for the NCA is invalid. - + File not found - + File "%1" not found - + OK - + Function Disabled - + Compatibility list reporting is currently disabled. Check back later! - + Error opening URL - + Unable to open the URL "%1". - + TAS Recording - + Overwrite file of player 1? - + Invalid config detected - + Handheld controller can't be used on docked mode. Pro controller will be selected. - - + + Amiibo - - + + The current amiibo has been removed - + Error - - + + The current game is not looking for amiibos - + Amiibo File (%1);; All Files (*.*) - + Load Amiibo - + Error loading Amiibo data - + The selected file is not a valid amiibo - + The selected file is already on use - + An unknown error occurred - - - Keys not installed - - - - - - Install decryption keys and restart Eden before attempting to install firmware. - - - - - Select Dumped Firmware Source Location - - - - - Select Dumped Firmware ZIP - - - - - Zipped Archives (*.zip) - - - - - Firmware cleanup failed - - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available - + Firmware Corrupted - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot - + PNG Image (*.png) - + TAS state: Running %1/%2 - + TAS state: Recording %1 - + TAS state: Idle %1/%2 - + TAS State: Invalid - + &Stop Running - + Stop R&ecording - + Building: %n shader(s) - + Scale: %1x %1 is the resolution scaling factor - + Speed: %1% / %2% - + Speed: %1% - + Game: %1 FPS - + Frame: %1 ms - - - FSR - - - - + NO AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE - + VOLUME: %1% Volume percentage (e.g. 50%) - + Derivation Components Missing - + Decryption keys are missing. Install them now? - + Wayland Detected! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -7915,74 +7951,74 @@ Would you like to force it for future launches? - + Use X11 - + Continue with Wayland - + Don't show again - + Restart Required - + Restart Eden to apply the X11 backend. - + Slow - + Turbo - + Unlocked - + Select RomFS Dump Target - + Please select which RomFS you would like to dump. - + Are you sure you want to close Eden? - - - + + + Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8048,6 +8084,11 @@ Would you like to bypass this and exit anyway? ScaleForce + + + FSR + + Area @@ -8059,52 +8100,57 @@ Would you like to bypass this and exit anyway? - + + SGSR + + + + + SGSR EdgeDir + + + + Docked - + Handheld - + Fast - - Balanced - - - - + Accurate - + Vulkan - + OpenGL GLSL - + OpenGL SPIRV - + OpenGL GLASM - + Null @@ -8137,7 +8183,7 @@ If you wish to clean up the files which were left in the old data location, you - + Data was migrated successfully. @@ -8621,8 +8667,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8635,10 +8681,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8653,9 +8699,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8817,50 +8863,50 @@ p, li { white-space: pre-wrap; } - + %1%2 - + [invalid] - - + + %1%2Hat %3 - - - + + + %1%2Axis %3 - + %1%2Axis %3,%4,%5 - + %1%2Motion %3 - - + + %1%2Button %3 - + [unused] [ei käytössä] @@ -8986,47 +9032,47 @@ p, li { white-space: pre-wrap; } - + Play Time: %1 - + Never Played - + Version: %1 - + Version: 1.0.0 - + Installed SD Titles Asennetut SD-sovellukset - + Installed NAND Titles Asennetut NAND-sovellukset - + System Titles Järjestelmäsovellukset - + Add New Game Directory Lisää uusi pelikansio - + Favorites @@ -9147,13 +9193,13 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. @@ -9163,10 +9209,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9187,213 +9233,246 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9637,72 +9716,72 @@ Would you like to manually select a portable folder to use? - + Create Shortcut - + Do you want to launch the game in fullscreen? - + Shortcut Created - + Successfully created a shortcut to %1 - + Shortcut may be Volatile! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? - + Failed to Create Shortcut - + Failed to create a shortcut to %1 - + Create Icon - + Cannot create icon file. Path "%1" does not exist and cannot be created. - + No firmware available - + Please install firmware to use the home menu. - + Home Menu Applet - + Home Menu is not available. Please reinstall firmware. @@ -9710,55 +9789,55 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name - + What should this mod be called? - + RomFS - + ExeFS/Patch - + Cheat - + Mod Type - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty @@ -10442,65 +10521,65 @@ By selecting "From Eden", previous save data stored in Ryujinx will be - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/fr.ts b/dist/languages/fr.ts index d53b1902d4..2f2ddd1481 100644 --- a/dist/languages/fr.ts +++ b/dist/languages/fr.ts @@ -218,155 +218,6 @@ Cela bannirait à la fois son nom d'utilisateur du forum et son adresse IP. %1 - %2 (%3/%4 membres) - connecté - - CompatDB - - - Report Compatibility - Signaler la Compatibilité - - - - - - - - - - Report Game Compatibility - Signaler la Compatibilité d'un Jeu - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - <html><head/><body><p><span style=" font-size:10pt;">Si vous choisissez de soumettre un cas de test à la </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">Liste de Compatibilité d'eden </span></a><span style=" font-size:10pt;">, Les informations suivantes seront collectées et affichées sur le site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Informations Matérielles (CPU / GPU / Système d'Exploitation)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Quelle version d'eden vous utilisez</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Le compte eden connecté</li></ul></body></html> - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>Est-ce-que le jeu se lance ?</p></body></html> - - - - Yes The game starts to output video or audio - Oui Le jeu démarre avec l’affichage et le son - - - - No The game doesn't get past the "Launching..." screen - Non Le jeu reste bloqué sur l’écran "Lancement..." - - - - Yes The game gets past the intro/menu and into gameplay - Oui Le jeu fonctionne après avoir passé l’intro et/ou le menu - - - - No The game crashes or freezes while loading or using the menu - Non Le jeu crash ou freeze pendant le chargement ou pendant l'utilisation du menu - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>Est-ce-que le jeu atteint le gameplay ?</p></body></html> - - - - Yes The game works without crashes - Oui Le jeu fonctionne sans crasher - - - - No The game crashes or freezes during gameplay - Non Le jeu crash ou freeze pendant le gameplay - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>Est-ce que le jeu crash, freeze ou se verrouille pendant le gameplay ?</p></body></html> - - - - Yes The game can be finished without any workarounds - Oui Le jeu peut être fini sans manipulations - - - - No The game can't progress past a certain area - Non Le jeu ne peut plus progresser après un certain point - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>Est-ce-que le jeu est complètement jouable du début à la fin ?</p></body></html> - - - - Major The game has major graphical errors - Majeur Le jeu a des erreurs graphiques majeures - - - - Minor The game has minor graphical errors - Mineur Le jeu a des erreurs graphiques mineures - - - - None Everything is rendered as it looks on the Nintendo Switch - Aucun Tout est rendu comme ça apparait sur la Nintendo Switch - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>Est-ce-que le jeu a des glitchs graphiques ?</p></body></html> - - - - Major The game has major audio errors - Majeur Le jeu a des erreurs d'audio majeures - - - - Minor The game has minor audio errors - Mineur Le jeu a des erreurs d'audio mineures - - - - None Audio is played perfectly - Aucun L'audio est joué parfaitement - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p> Est-ce-que le jeu a des glitchs audio ou des effets manquants ? </p></body></html> - - - - Thank you for your submission! - Merci de votre suggestion ! - - - - Submitting - Envoi - - - - Communication error - Erreur de communication - - - - An error occurred while sending the Testcase - Une erreur est survenue lors de l'envoi du cas-type - - - - Next - Suivant - - ConfigurationShared @@ -570,39 +421,38 @@ Peut aider à réduire les saccades à des framerates faibles. - + Backend: Arrière-plan : - CPU Overclock - Overclocking CPU + CPU Clocks + - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. - Overclocke le CPU émulé pour supprimer certains limiteurs FPS. Les CPU plus faibles peuvent voir leurs performances réduites et certains jeux peuvent se comporter de manière incorrecte. -Utilisez Boost (1700 MHz) pour fonctionner à l'horloge native la plus élevée de la Switch, ou Fast (2000 MHz) pour fonctionner à l'horloge 2x + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + - + Custom CPU Ticks Ticks CPU personnalisés - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. Définissez une valeur personnalisée pour les cycles CPU. Des valeurs plus élevées peuvent améliorer les performances, mais risquent de provoquer des blocages. Une plage de 77-21000 est recommandée. - + Enable Host MMU Emulation (fastmem) Activer l'émulation MMU de l'hôte (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. @@ -611,100 +461,100 @@ L'activer permet à l'invité de lire/écrire directement dans la mém Désactiver cela force tous les accès mémoire à utiliser l'émulation logicielle de la MMU. - + Unfuse FMA (improve performance on CPUs without FMA) Désactivation du FMA (améliore les performances des CPU sans FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. Cette option améliore la vitesse en réduisant la précision des instructions de multiplication et addition fusionnées sur les processeurs qui ne prennent pas en charge nativement FMA. - + Faster FRSQRTE and FRECPE FRSQRTE et FRECPE plus rapides - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. Cette option améliore la vitesse de certaines fonctions à virgule flottante approximatives en utilisant des approximations natives moins précises. - + Faster ASIMD instructions (32 bits only) Instructions ASIMD plus rapides (32 bits seulement) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. Cette option améliore la vitesse des fonctions à virgule flottante ASIMD sur 32 bits en utilisant des modes d'arrondi incorrects. - + Inaccurate NaN handling Traitement NaN imprécis - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. Cette option améliore la vitesse en supprimant la vérification des NaN. Veuillez noter que cela réduit également la précision de certaines instructions en virgule flottante. - + Disable address space checks Désactiver les vérifications de l'espace d'adresse - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. Cette option améliore la vitesse en supprimant une vérification de sécurité avant chaque opération mémoire. La désactiver peut permettre l’exécution de code arbitraire. - + Ignore global monitor Ignorer le moniteur global - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. Cette option améliore la vitesse en se basant uniquement sur la sémantique de cmpxchg pour garantir la sécurité des instructions d'accès exclusif. Veuillez noter que cela peut entraîner des blocages et d'autres conditions de concurrence. - + API: API : - + Changes the output graphics API. Vulkan is recommended. Modifie l’API graphique utilisée en sortie. Vulkan est recommandé. - + Device: Appareil : - + This setting selects the GPU to use (Vulkan only). Ce paramètre permet de sélectionner le GPU à utiliser (Vulkan uniquement). - + Resolution: Résolution : - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. @@ -713,27 +563,27 @@ Des résolutions plus élevées nécessitent plus de VRAM et de bande passante. Des options inférieures à 1X peuvent provoquer des artefacts. - + Window Adapting Filter: Filtre de fenêtre adaptatif : - + FSR Sharpness: Netteté FSR : - - Determines how sharpened the image will look using FSR's dynamic contrast. - Détermine le niveau de netteté de l’image en utilisant le contraste dynamique de FSR. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. + - + Anti-Aliasing Method: Méthode d'anticrénelage : - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. @@ -741,13 +591,23 @@ FXAA can produce a more stable picture in lower resolutions. SMAA offre la meilleure qualité. FXAA peut produire une image plus stable à des résolutions plus faibles. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Mode Plein écran : - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. @@ -756,12 +616,12 @@ Sans bordure offre la meilleure compatibilité avec le clavier à l'écran Le mode plein écran exclusif peut offrir de meilleures performances et un meilleur support Freesync/Gsync. - + Aspect Ratio: Format : - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. @@ -770,35 +630,35 @@ La plupart des jeux ne supportent que le format 16:9, donc des modifications son Contrôle également le format des captures d’écran. - + Use persistent pipeline cache Conserver le cache du rendu graphique - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. Permet de sauvegarder les shaders sur le stockage pour un chargement plus rapide lors des démarrages ultérieurs du jeu. Le désactiver est uniquement destiné au débogage. - + Use asynchronous GPU emulation - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + NVDEC emulation: Émulation NVDEC : - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. @@ -807,12 +667,12 @@ Elles peuvent être décodées soit par le CPU, soit par le GPU, ou pas du tout Dans la plupart des cas, le décodage GPU offre les meilleures performances. - + ASTC Decoding Method: Méthode de décodage ASTC : - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -824,12 +684,12 @@ GPU : Utiliser les shaders de calcul du GPU pour décoder les textures ASTC (rec CPU asynchrone : Utiliser le CPU pour décoder les textures ASTC à la demande. Élimine les saccades liées au décodage ASTC, mais peut provoquer des artefacts. - + ASTC Recompression Method: Méthode de recompression ASTC : - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. @@ -837,44 +697,64 @@ BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, BC1/BC3 : Le format intermédiaire sera recompressé en BC1 ou BC3, ce qui économise de la VRAM mais dégrade la qualité de l’image. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: Mode d'utilisation de la VRAM : - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. Permet de choisir si l’émulateur doit privilégier la conservation de la mémoire ou utiliser au maximum la mémoire vidéo disponible pour les performances. Le mode agressif peut affecter les performances d’autres applications, comme les logiciels d’enregistrement. - + Skip CPU Inner Invalidation Ignorer l'invalidation interne du CPU - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. Ignore certaines invalidations de cache lors des mises à jour de la mémoire, réduisant l’utilisation du CPU et améliorant la latence. Cela peut provoquer des plantages légers. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: Mode VSync : - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -885,12 +765,12 @@ Mailbox peut offrir une latence inférieure à FIFO et n’entraîne pas de déc Immediate (pas de synchronisation) affiche ce qui est disponible et peut provoquer du déchirement. - + Sync Memory Operations Synchroniser les opérations mémoire - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. @@ -899,144 +779,159 @@ Cette option corrige des problèmes dans les jeux, mais peut dégrader les perfo Les jeux Unreal Engine 4 sont souvent ceux qui bénéficient le plus de ce réglage. - + Enable asynchronous presentation (Vulkan only) Activer la présentation asynchrone (Vulkan uniquement) - + Slightly improves performance by moving presentation to a separate CPU thread. Améliore légèrement les performances en déplaçant la présentation vers un thread CPU séparé. - + Force maximum clocks (Vulkan only) Forcer la fréquence d'horloge maximale (Vulkan uniquement) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. Les exécutions fonctionnent en arrière-plan en attendant les commandes graphiques pour empêcher le GPU de réduire sa vitesse de fréquence d'horloge. - + Anisotropic Filtering: Filtrage anisotropique : - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. Contrôle la qualité du rendu des textures sous des angles obliques. Il est sûr de le régler à 16x sur la plupart des GPU. - + GPU Mode: Mode GPU : - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - + DMA Accuracy: Précision du DMA : - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. - Contrôle la précision des transferts DMA. Une précision plus élevée corrige certains problèmes dans certains jeux, mais peut réduire les performances. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. + - + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation Activer la compilation asynchrone des shaders - + May reduce shader stutter. Peut réduire les saccades dues aux shaders. - - Fast GPU Time - Temps GPU rapide - - - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. + + GPU Clocks - + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. + + + + GPU Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache Utiliser le cache de pipeline Vulkan - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. Active le cache de pipeline spécifique au fournisseur de GPU. Cette option peut améliorer considérablement le temps de chargement des shaders dans les cas où le pilote Vulkan ne stocke pas les fichiers de cache de pipeline en interne. - + Enable Compute Pipelines (Intel Vulkan Only) Activer les pipelines de calcul (Vulkan sur Intel uniquement) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. @@ -1045,95 +940,84 @@ Ce réglage n’existe que pour les pilotes propriétaires Intel et peut provoqu Les pipelines de calcul sont toujours activés sur tous les autres pilotes. - + Enable Reactive Flushing Activer le Vidage Réactif - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. Utilise une purge réactive au lieu d'une purge prédictive, permettant une synchronisation de la mémoire plus précise. - + Sync to framerate of video playback Synchro la fréquence d'image de la relecture du vidéo - + Run the game at normal speed during video playback, even when the framerate is unlocked. Éxécuter le jeu à une vitesse normale pendant la relecture du vidéo, même-ci la fréquence d'image est dévérouillée. - + Barrier feedback loops Boucles de rétroaction de barrière - + Improves rendering of transparency effects in specific games. Améliore le rendu des effets de transparence dans des jeux spécifiques. - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State État dynamique étendu - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. - + Vertex Input Dynamic State - + Enables vertex input dynamic state feature for better quality and performance. - + Sample Shading Échantillonnage de shading - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. Permet au shader de fragments de s’exécuter pour chaque échantillon dans un fragment multi-échantillonné, au lieu d’une seule fois par fragment. @@ -1141,86 +1025,96 @@ Améliore la qualité graphique au prix de performances réduites. Des valeurs plus élevées améliorent la qualité mais dégradent les performances. - + RNG Seed Seed RNG - + Controls the seed of the random number generator. Mainly used for speedrunning. Contrôle la graine du générateur de nombres aléatoires. Principalement utilisé pour le speedrun. - + Device Name Nom de l'appareil - + The name of the console. Nom de la console. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: Date RTC personnalisée : - + This option allows to change the clock of the console. Can be used to manipulate time in games. Cette option permet de modifier l’horloge de la console. Peut être utilisée pour manipuler le temps dans les jeux. - + The number of seconds from the current unix time Nombre de secondes écoulées depuis le 1er janvier 1970. - + Language: Langue : - + This option can be overridden when region setting is auto-select Cette option peut être remplacée lorsque la région est sur auto. - + Region: Région : - + The region of the console. Région de la console. - + Time Zone: Fuseau horaire : - + The time zone of the console. Fuseau horaire de la console. - + Sound Output Mode: Mode de sortie sonore : - + Console Mode: Mode console : - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. @@ -1229,1031 +1123,1026 @@ Les jeux adaptent leur résolution, leurs paramètres graphiques et les manettes Passer en mode Portable peut améliorer les performances sur les systèmes peu puissants. - + Prompt for user profile on boot Choisir l’utilisateur au démarrage. - + Useful if multiple people use the same PC. Utile si plusieurs personnes utilisent le même PC. - + Pause when not in focus Pause lorsque la fenêtre n’est pas active. - + Pauses emulation when focusing on other windows. Met l’émulation en pause dès que l’utilisateur change de fenêtre. - + Confirm before stopping emulation Confirmer avant d'arrêter l'émulation - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. Ignore les demandes de confirmation pour arrêter l’émulation. L’activer permet de contourner ces confirmations et de quitter directement l’émulation. - + Hide mouse on inactivity Cacher la souris en cas d'inactivité - + Hides the mouse after 2.5s of inactivity. Cache le curseur après 2,5 secondes d’inactivité. - - Disable controller applet - Désactiver l'applet du contrôleur - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - Désactive de force le menu de configuration des manettes dans les programmes émulés. -Lorsqu’un programme tente d’ouvrir ce menu, il est immédiatement fermé. - - - + Check for updates Rechercher des mises à jours - + Whether or not to check for updates upon startup. Vérifier ou non les mises à jour au démarrage. - + Enable Gamemode Activer le mode jeu - + Force X11 as Graphics Backend Forcer X11 comme moteur graphique - + Custom frontend Interface personnalisée - + Real applet Applet réel - + + + Disabled + Désactivé + + + Never Jamais - + On Load Au chargement - + Always Toujours - + CPU CPU - + GPU GPU - + CPU Asynchronous CPU Asynchrone - + Uncompressed (Best quality) Non compressé (Meilleure qualité) - + BC1 (Low quality) BC1 (Basse qualité) - + BC3 (Medium quality) BC3 (Qualité moyenne) - - + + Auto Auto - + 30 FPS - + 60 FPS - + 90 FPS - + 120 FPS - + Conservative Conservateur - + Aggressive Agressif - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) - + Null Nul - + Fast Rapide - - Balanced - Moyen - - - - + + + Accurate Précis - - + + + Default Par défaut - + Unsafe (fast) Insecure (rapide) - + Safe (stable) Sûr (stable) - + + Immediate + + + + + Balanced + Moyen + + + Unsafe Risqué - + Paranoid (disables most optimizations) Paranoïaque (désactive la plupart des optimisations) - + Debugging Débogage - + Dynarmic Dynamique - + NCE NCE - + Borderless Windowed Fenêtré sans bordure - + Exclusive Fullscreen Plein écran exclusif - + No Video Output Pas de sortie vidéo - + CPU Video Decoding Décodage Vidéo sur le CPU - + GPU Video Decoding (Default) Décodage Vidéo sur le GPU (par défaut) - + 0.25X (180p/270p) [EXPERIMENTAL] 0.25X (180p/270p) [EXPÉRIMENTAL] - + 0.5X (360p/540p) [EXPERIMENTAL] 0.5X (360p/540p) [EXPERIMENTAL] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [EXPÉRIMENTAL] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] 1,25X (900p/1350p) [EXPÉRIMENTAL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [EXPÉRIMENTAL] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor Plus proche voisin - + Bilinear Bilinéaire - + Bicubic Bicubique - + Gaussian Gaussien - + Lanczos Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution AMD FidelityFX Super Resolution - + Area Area (Par zone) - + MMPX MMPX - + Zero-Tangent Zero-Tangent - + B-Spline B-Spline - + Mitchell Mitchell - + Spline-1 Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None Aucun - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Par défaut (16:9) - + Force 4:3 Forcer le 4:3 - + Force 21:9 Forcer le 21:9 - + Force 16:10 Forcer le 16:10 - + Stretch to Window Étirer à la fenêtre - + Automatic Automatique - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - 32x - - - - 64x - 64x - - - + Japanese (日本語) Japonais (日本語) - + American English Anglais Américain - + French (français) Français (français) - + German (Deutsch) Allemand (Deutsch) - + Italian (italiano) Italien (italiano) - + Spanish (español) Espagnol (español) - + Chinese Chinois - + Korean (한국어) Coréen (한국어) - + Dutch (Nederlands) Néerlandais (Nederlands) - + Portuguese (português) Portugais (português) - + Russian (Русский) Russe (Русский) - + Taiwanese Taïwanais - + British English Anglais Britannique - + Canadian French Français Canadien - + Latin American Spanish Espagnol d'Amérique Latine - + Simplified Chinese Chinois Simplifié - + Traditional Chinese (正體中文) Chinois Traditionnel (正體中文) - + Brazilian Portuguese (português do Brasil) Portugais Brésilien (português do Brasil) - + Polish (polska) - + Thai (แบบไทย) - - + + Japan Japon - + USA É.-U.A. - + Europe Europe - + Australia Australie - + China Chine - + Korea Corée - + Taiwan Taïwan - + Auto (%1) Auto select time zone Auto (%1) - + Default (%1) Default time zone Par défaut (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Cuba - + EET EET - + Egypt Égypte - + Eire Eire - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Eire - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hong Kong - + HST HST - + Iceland Islande - + Iran Iran - + Israel Israël - + Jamaica Jamaïque - + Kwajalein Kwajalein - + Libya Libye - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navajo - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Pologne - + Portugal Portugal - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Singapour - + Turkey Turquie - + UCT UCT - + Universal Universel - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulu - + Mono Mono - + Stereo Stéréo - + Surround Surround - + 4GB DRAM (Default) 4 GB DRAM (Par défaut) - + 6GB DRAM (Unsafe) 6 GB DRAM (Risqué) - + 8GB DRAM 8GO DRAM - + 10GB DRAM (Unsafe) 10GO DRAM (Insecure) - + 12GB DRAM (Unsafe) 12GO DRAM (Insecure) - + Docked Mode TV - + Handheld Mode Portable - - - Off - Désactivé + + + Normal + - - Boost (1700MHz) - Boost (1700MHz) + + + Boost + - - Fast (2000MHz) - Rapide (2000MHz) + + + Overclock + - + Always ask (Default) Toujours demander (par défaut) - + Only if game specifies not to stop Uniquement si le jeu précise de ne pas s'arrêter - + Never ask Jamais demander - - - Medium (256) - Moyen (256) - - - - - High (512) - Élevé (512) - - - + Very Small (16 MB) Très petit (16Mo) - + Small (32 MB) Petit (32Mo) - + Normal (128 MB) Normal (128Mo) - + Large (256 MB) Large (256Mo) - + Very Large (512 MB) Très large (512Mo) - + Very Low (4 MB) Très faible (4 Mo) - + Low (8 MB) Faible (8 Mo) - + Normal (16 MB) Normal (16 Mo) - + Medium (32 MB) Moyen (32 Mo) - + High (64 MB) Élevé (64 Mo) - + Very Low (32) Très faible (32) - + Low (64) Faible (64) - + Normal (128) Normal (128) - - Disabled - Désactivé + + Medium (256) + Moyen (256) - + + High (512) + Élevé (512) + + + ExtendedDynamicState 1 État dynamique étendu 1 - + ExtendedDynamicState 2 État dynamique étendu 2 - + ExtendedDynamicState 3 État dynamique étendu 3 - + Tree View Vue en liste - + Grid View Vue en grille @@ -2613,252 +2502,297 @@ Lorsqu’un programme tente d’ouvrir ce menu, il est immédiatement fermé.
Filtre de log global - - When checked, the max size of the log increases from 100 MB to 1 GB - Lorsque la case est cochée, la taille maximum du journal augmente de 100 Mo à 1 Go + + GPU Logging/Level + - - Enable Extended Logging** - Activer la journalisation étendue** + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console Afficher le journal dans la console - + + When checked, the max size of the log increases from 100 MB to 1 GB + Lorsque la case est cochée, la taille maximum du journal augmente de 100 Mo à 1 Go + + + + Enable Extended Logging** + Activer la journalisation étendue** + + + Open Log Location Ouvrir l'emplacement du journal de logs - + Homebrew Homebrew - + Arguments String Chaîne d'arguments - + Graphics Graphismes - + When checked, it executes shaders without loop logic changes Lorsque la case est cochée, exécuter les shaders sans changer la boucle de logique - + Disable Loop safety checks Désactiver les vérifications de boucle - + When checked, it will dump all the macro programs of the GPU Lorsque la case est cochée, il videra tous les programmes de macro du GPU - + Dump Maxwell Macros Copier les "Maxwell Macros" - + When checked, it enables Nsight Aftermath crash dumps Lorsque la case est cochée, cette option active les "crash dumps" pour Nsight Aftermath - + Enable Nsight Aftermath Activer Nsight Aftermath - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Lorsque la case est cochée, il videra tous les shaders d'assemblage d'origine du cache de shader de disque ou du jeu tels qu'ils ont été trouvés + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + - - Dump Game Shaders - Extraire les shaders du jeu + + Dump Guest (Maxwell) Shaders + - + Enable Renderdoc Hotkey Activer le raccourci Renderdoc - + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower Lorsque la case est cochée, désactive le compilateur de macros JIT. L'activer ralentit les jeux - + Disable Macro JIT Désactiver les macros JIT - + When checked, it disables the macro HLE functions. Enabling this makes games run slower Lorsque la case est cochée, désactive les fonctions macro HLE. L'activer ralentit les jeux - + Disable Macro HLE Désactiver les macros HLE - + When checked, the graphics API enters a slower debugging mode Lorsque la case est cochée, l'API graphique entre dans un mode de débogage plus lent - + Enable Graphics Debugging Activer le débogage des graphismes - + When checked, yuzu will log statistics about the compiled pipeline cache Lorsque la case est cochée, yuzu enregistrera les journaux de statistiques à propos de la cache de pipeline compilée - + Enable Shader Feedback Activer le retour d'information des shaders - + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> <html><head/><body><p>Lorsque la case est cochée, désactive le réordonnancement des téléchargements de mémoire mappée, ce qui permet d'associer les téléchargements à des dessins spécifiques. Cela peut réduire les performances dans certains cas.</p></body></html> - + Disable Buffer Reorder Désactiver la réorganisation du tampon - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced Avancé - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Active yuzu pour chercher pour un environnement Vulkan fonctionnel quand le programme démarre. Desactiver ceci si cela cause des problèmes avec des programmes externes. - + Perform Startup Vulkan Check Performe un check de Vulkan au démarrage - + Disable Web Applet Désactiver l'applet web - + Enable All Controller Types Activer tous les types de contrôleurs - + Enable Auto-Stub Activer le stub de GDB - + Kiosk (Quest) Mode Mode Kiosk (Quest) - + Use dev.keys - + Enable Debug Asserts Activer les assertions de débogage - + Debugging Débogage - + Battery Serial: - + + Debug Knobs: + + + + Bitmask for quick development toggles - + Set debug knobs (bitmask) - + 16-bit debug knob set for quick development toggles - + (bitmask) - - Debug Knobs: - - - - + Unit Serial: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. Activez cette option pour afficher la dernière liste de commandes audio générée sur la console. N'affecte que les jeux utilisant le moteur de rendu audio. - + Dump Audio Commands To Console** Déversez les commandes audio à la console** - + Flush log output on each line Force l’écriture immédiate des logs à chaque nouvelle ligne. - + Enable FS Access Log Activer la journalisation des accès du système de fichiers - + Enable Verbose Reporting Services** Activer les services de rapport verbeux** - + Censor username in logs Censurer le nom d'utilisateur dans les logs - + **This will be reset automatically when Eden closes. **Ceci sera automatiquement réinitialisé à la fermeture d'Eden. @@ -3308,33 +3242,38 @@ Would you like to delete the old save data? Couleur de l’arrière plan : - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off Désactivé - + VSync Off VSync Désactivée - + Recommended Recommandé - + On Activé - + VSync On VSync Activée @@ -3855,6 +3794,16 @@ Would you like to delete the old save data? Motion / Touch Mouvement / Tactile + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4364,58 +4313,58 @@ Pour inverser les axes, bougez d'abord votre joystick verticalement, puis h [en attente] - + New Profile Nouveau Profil - + Enter a profile name: Entrez un nom de profil : - - + + Create Input Profile Créer un profil d'entrée - + The given profile name is not valid! Le nom de profil donné est invalide ! - + Failed to create the input profile "%1" Échec de la création du profil d'entrée "%1" - + Delete Input Profile Supprimer le profil d'entrée - + Failed to delete the input profile "%1" Échec de la suppression du profil d'entrée "%1" - + Load Input Profile Charger le profil d'entrée - + Failed to load the input profile "%1" Échec du chargement du profil d'entrée "%1" - + Save Input Profile Sauvegarder le profil d'entrée - + Failed to save the input profile "%1" Échec de la sauvegarde du profil d'entrée "%1" @@ -4463,7 +4412,7 @@ Pour inverser les axes, bougez d'abord votre joystick verticalement, puis h - + Configure Configurer @@ -4494,7 +4443,7 @@ Pour inverser les axes, bougez d'abord votre joystick verticalement, puis h - + Test Tester @@ -4509,77 +4458,77 @@ Pour inverser les axes, bougez d'abord votre joystick verticalement, puis h Retirer un serveur - + %1:%2 %1:%2 - - - - - - + + + + + + Eden Eden - + Port number has invalid characters Le numéro de port contient des caractères invalides - + Port has to be in range 0 and 65353 Le port doit être entre 0 et 65353 - + IP address is not valid L'adresse IP n'est pas valide - + This UDP server already exists Ce serveur UDP existe déjà - + Unable to add more than 8 servers Impossible d'ajouter plus de 8 serveurs - + Testing Essai - + Configuring Configuration - + Test Successful Test réussi - + Successfully received data from the server. Données reçues du serveur avec succès. - + Test Failed Test échoué - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Impossible de recevoir des données valides du serveur.<br>Veuillez vérifier que le serveur est correctement configuré et que l'adresse et le port sont corrects. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. Le test UDP ou la configuration de l'étalonnage est en cours.<br>Veuillez attendre qu'ils se terminent. @@ -4764,57 +4713,57 @@ Les valeurs actuelles sont respectivement de %1% et %2%. Certains paramètres ne sont disponibles que lorsqu'un jeu n'est pas en cours d'exécution. - + Add-Ons Extensions - + System Système - + CPU CPU - + Graphics Graphiques - + Adv. Graphics Adv. Graphiques - + Ext. Graphics - + Audio Audio - + Input Profiles Profils d'entrée - + Network - + Applets - + Properties Propriétés @@ -4937,6 +4886,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5504,91 +5577,86 @@ Faites glisser les points pour modifier la position ou double-cliquez sur les ce - Show Compatibility List - Afficher la liste de compatibilité - - - Show Add-Ons Column Afficher la colonne des extensions - + Show Size Column Afficher la taille des colonnes - + Show File Types Column Afficher la colonne des types de fichier - + Show Play Time Column Afficher la colonne de temps de jeu - + Folder Icon Size: Taille de l'icône du dossier : - + Row 1 Text: Texte rangée 1 : - + Row 2 Text: Texte rangée 2 : - + Screenshots Captures d'écran - + Ask Where To Save Screenshots (Windows Only) Demander où enregistrer les captures d'écran (Windows uniquement) - + Screenshots Path: Chemin du dossier des captures d'écran : - + ... ... - + TextLabel TextLabel - + Resolution: Résolution : - + Select Screenshots Path... Sélectionnez le chemin du dossier des captures d'écran... - + <System> <System> - + English Anglais - + Auto (%1 x %2, %3 x %4) Screenshot width value Auto (%1 x %2, %3 x %4) @@ -5825,7 +5893,7 @@ Faites glisser les points pour modifier la position ou double-cliquez sur les ce Importer les données dans ce répertoire. Cela peut prendre un certain temps et supprimera TOUTES LES DONNÉES EXISTANTES ! - + Calculating... Calcul en cours… @@ -6029,50 +6097,50 @@ Veuillez aller dans Configurer -> Système -> Réseau puis en choisir une. GRenderWindow - - + + OpenGL not available! OpenGL n'est pas disponible ! - + OpenGL shared contexts are not supported. Les contextes OpenGL partagés ne sont pas pris en charge. - + Eden has not been compiled with OpenGL support. Eden n'a pas été compilé avec le support OpenGL - - - + + + Error while initializing OpenGL! Erreur lors de l'initialisation d'OpenGL ! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Votre GPU peut ne pas prendre en charge OpenGL, ou vous n'avez pas les derniers pilotes graphiques. - + Error while initializing OpenGL 4.6! Erreur lors de l'initialisation d'OpenGL 4.6 ! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Votre GPU peut ne pas prendre en charge OpenGL 4.6 ou vous ne disposez pas du dernier pilote graphique: %1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Votre GPU peut ne pas prendre en charge une ou plusieurs extensions OpenGL requises. Veuillez vous assurer que vous disposez du dernier pilote graphique.<br><br>GL Renderer :<br>%1<br><br>Extensions non prises en charge :<br>%2 - + This build doesn't have OpenGL support. @@ -6080,279 +6148,272 @@ Veuillez aller dans Configurer -> Système -> Réseau puis en choisir une. GameList - + &Add New Game Directory - + Favorite Préférer - + Start Game Démarrer le jeu - + Start Game without Custom Configuration Démarrer le jeu sans configuration personnalisée - + Open Save Data Location Ouvrir l'emplacement des données de sauvegarde - + Open Mod Data Location Ouvrir l'emplacement des données des mods - + Open Transferable Pipeline Cache Ouvrir le cache de pipelines transférable - + Link to Ryujinx Lier à Ryujinx - + Remove Supprimer - + Remove Installed Update Supprimer mise à jour installée - + Remove All Installed DLC Supprimer tous les DLC installés - + Remove Custom Configuration Supprimer la configuration personnalisée - + Remove Cache Storage Supprimer le stockage du cache - + Remove OpenGL Pipeline Cache Supprimer le cache de pipelines OpenGL - + Remove Vulkan Pipeline Cache Supprimer le cache de pipelines Vulkan - + Remove All Pipeline Caches Supprimer tous les caches de pipelines - + Remove All Installed Contents Supprimer tout le contenu installé - + Manage Play Time Gérer le Temps de Jeu - + Edit Play Time Data Modifier les Données de Temps de Jeu - + Remove Play Time Data Supprimer les données de temps de jeu - - + + Dump RomFS Extraire la RomFS - + Dump RomFS to SDMC Décharger RomFS vers SDMC - + Verify Integrity Vérifier l'intégrité - + Copy Title ID to Clipboard Copier l'ID du titre dans le Presse-papiers - - Navigate to GameDB entry - Accédez à l'entrée GameDB - - - + Create Shortcut Créer un raccourci - + Add to Desktop Ajouter au bureau - + Add to Applications Menu Ajouter au menu des applications - + Configure Game Configurer le jeux - + Scan Subfolders Scanner les sous-dossiers - + Remove Game Directory Supprimer le répertoire du jeu - + ▲ Move Up ▲ Monter - + ▼ Move Down ▼ Descendre - + Open Directory Location Ouvrir l'emplacement du répertoire - + Clear Effacer - - - Name - Nom - - - - Compatibility - Compatibilité - - - - Add-ons - Extensions - - - - File type - Type de fichier - - - - Size - Taille - - - - Play time - Temps de jeu - GameListItemCompat - + Ingame En jeu - + Game starts, but crashes or major glitches prevent it from being completed. Le jeu se lance, mais crash ou des bugs majeurs l'empêchent d'être complété. - + Perfect Parfait - + Game can be played without issues. Le jeu peut être joué sans problèmes. - + Playable Jouable - + Game functions with minor graphical or audio glitches and is playable from start to finish. Le jeu fonctionne avec des glitchs graphiques ou audio mineurs et est jouable du début à la fin. - + Intro/Menu Intro/Menu - + Game loads, but is unable to progress past the Start Screen. Le jeu charge, mais ne peut pas progresser après le menu de démarrage. - + Won't Boot Ne démarre pas - + The game crashes when attempting to startup. Le jeu crash au lancement. - + Not Tested Non testé - + The game has not yet been tested. Le jeu n'a pas encore été testé. + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list Double-cliquez pour ajouter un nouveau dossier à la liste de jeux @@ -6360,17 +6421,17 @@ Veuillez aller dans Configurer -> Système -> Réseau puis en choisir une. GameListSearchField - + %1 of %n result(s) %1 sur %n résultat%1 sur %n résultats%1 sur %n résultat(s) - + Filter: Filtre : - + Enter pattern to filter Entrez un motif à filtrer @@ -6866,1139 +6927,1112 @@ Message de débogage : - + Game &Icon Size - + Reset Window Size to &720p &Réinitialiser la taille de la fenêtre à 720p - + Reset Window Size to 720p Réinitialiser la taille de la fenêtre à 720p - + Reset Window Size to &900p Réinitialiser la taille de la fenêtre à &900p - + Reset Window Size to 900p Réinitialiser la taille de la fenêtre à 900p - + Reset Window Size to &1080p Réinitialiser la taille de la fenêtre à &1080p - + Reset Window Size to 1080p Réinitialiser la taille de la fenêtre à 1080p - + &Multiplayer &Multijoueur - + &Tools &Outils - + Am&iibo Am&iibo - + Launch &Applet - + &TAS &TAS - + &Create Home Menu Shortcut &Créer un Raccourci du Menu d'Accueil - + Install &Firmware Installer le &Firmware - + &Help &Aide - + &Install Files to NAND... &Installer des fichiers sur la NAND... - + L&oad File... &Charger un fichier... - + Load &Folder... &Charger un dossier - + E&xit Q&uitter - - + + &Pause &Pause - + &Stop &Arrêter - + &Verify Installed Contents &Vérifier les contenus installés - + &About Eden &À propos d'Eden - + Single &Window Mode &Mode fenêtre unique - + Con&figure... &Configurer... - + Ctrl+, Ctrl+, - + Enable Overlay Display Applet - + Show &Filter Bar &Afficher la barre de filtre - + Show &Status Bar &Afficher la barre d'état - + Show Status Bar Afficher la barre d'état - + &Browse Public Game Lobby &Parcourir le menu des jeux publics - + &Create Room &Créer un salon - + &Leave Room &Quitter le salon - + &Direct Connect to Room &Connexion directe au salon - + &Show Current Room &Afficher le salon actuel - + F&ullscreen P&lein écran - + &Restart &Redémarrer - + Load/Remove &Amiibo... Charger/Retirer un &Amiibo… - - &Report Compatibility - &Signaler la compatibilité - - - + Open &Mods Page Ouvrir la &page des mods - - Open &Quickstart Guide - Ouvrir le &guide de démarrage rapide + + &User Handbook + - - &FAQ - &FAQ - - - + &Capture Screenshot &Capture d'écran - + &Album - + &Set Nickname and Owner &Définir le surnom et le propriétaire - + &Delete Game Data &Supprimer les données du jeu - + &Restore Amiibo &Restaurer l'amiibo - + &Format Amiibo &Formater l'amiibo - + &Mii Editor - + &Configure TAS... &Configurer TAS... - + Configure C&urrent Game... Configurer le j&eu actuel... - - + + &Start &Démarrer - + &Reset &Réinitialiser - - + + R&ecord En&registrer - + Open &Controller Menu Ouvrir le &menu des manettes - + Install Decryption &Keys Installer les &clés de déchiffrement - + &Home Menu - + &Desktop &Bureau - + &Application Menu &Menu de l'Application - + &Root Data Folder &Dossier de Données (Root) Principal. - + &NAND Folder &Dossier NAND - + &SDMC Folder &Dossier SDMC - + &Mod Folder &Dossier Mod - + &Log Folder &Dossier Log - + From Folder Depuis un dossier - + From ZIP Depuis une archive ZIP - + &Eden Dependencies Dépendances d'&Eden - + &Data Manager &Gestionnaire de données - + &Tree View - + &Grid View - + Game Icon Size - - + + None Aucun - + Show Game &Name - + Show &Performance Overlay - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) - + Standard (64x64) - + Large (128x128) - + Full Size (256x256) - + Broken Vulkan Installation Detected - + Vulkan initialization failed during boot. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping - + Loading Web Applet... - - + + Disable Web Applet - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + The amount of shaders currently being built - + The current selected resolution scaling multiplier. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Unmute Remettre le son - + Mute Couper le son - + Reset Volume Réinitialiser le volume - + + Configure Effects... + + + + &Clear Recent Files - + &Continue &Continuer - + Warning: Outdated Game Format - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - - + + Error while loading ROM! - + The ROM format is not supported. - + An error occurred initializing the video core. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + An unknown error occurred. Please see the log for more details. - + + (64-bit) (64-bit) - + + (32-bit) (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + Closing software... Fermeture du logiciel... - + Save Data Données de sauvegarde - + Mod Data - + Error Opening %1 Folder - - + + Folder does not exist! - + Remove Installed Game Contents? - + Remove Installed Game Update? - + Remove Installed Game DLC? - + Remove Entry - + Delete OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? - + Remove Custom Game Configuration? - + Remove Cache Storage? - + Remove File - + Remove Play Time Data - + Reset play time? Réinitialiser le temps de jeu ? - - + + RomFS Extraction Failed! L'extraction de la RomFS a échoué ! - + There was an error copying the RomFS files or the user cancelled the operation. - + Full - + Skeleton - + Select RomFS Dump Mode - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Extracting RomFS... - - + + Cancel Annuler - + RomFS Extraction Succeeded! Extraction de la RomFS réussi ! - + The operation completed successfully. - + Error Opening %1 - + Select Directory - + Properties Propriétés - + The game properties could not be loaded. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Load File - + Open Extracted ROM Directory - + Invalid Directory Selected - + The directory you have selected does not contain a 'main' file. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files - + %n file(s) remaining - + Installing file "%1"... - - + + Install Results - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + %n file(s) were newly installed - + %n file(s) were overwritten - + %n file(s) failed to install - + System Application - + System Archive - + System Application Update - + Firmware Package (Type A) - + Firmware Package (Type B) - + Game Jeu - + Game Update - + Game DLC - + Delta Title - + Select NCA Install Type... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Failed to Install Échec de l'installation - + The title type you selected for the NCA is invalid. - + File not found Fichier non trouvé - + File "%1" not found - + OK OK - + Function Disabled - + Compatibility list reporting is currently disabled. Check back later! - + Error opening URL - + Unable to open the URL "%1". - + TAS Recording - + Overwrite file of player 1? - + Invalid config detected - + Handheld controller can't be used on docked mode. Pro controller will be selected. - - + + Amiibo Amiibo - - + + The current amiibo has been removed - + Error Erreur - - + + The current game is not looking for amiibos - + Amiibo File (%1);; All Files (*.*) - + Load Amiibo Charger un Amiibo - + Error loading Amiibo data - + The selected file is not a valid amiibo - + The selected file is already on use - + An unknown error occurred - - - Keys not installed - - - - - - Install decryption keys and restart Eden before attempting to install firmware. - - - - - Select Dumped Firmware Source Location - - - - - Select Dumped Firmware ZIP - - - - - Zipped Archives (*.zip) - - - - - Firmware cleanup failed - - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available - + Firmware Corrupted Firmware corrompu - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot - + PNG Image (*.png) - + TAS state: Running %1/%2 - + TAS state: Recording %1 - + TAS state: Idle %1/%2 - + TAS State: Invalid - + &Stop Running - + Stop R&ecording - + Building: %n shader(s) - + Scale: %1x %1 is the resolution scaling factor - + Speed: %1% / %2% - + Speed: %1% - + Game: %1 FPS - + Frame: %1 ms - - - FSR - FSR - - - + NO AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE VOLUME : MUET - + VOLUME: %1% Volume percentage (e.g. 50%) - + Derivation Components Missing - + Decryption keys are missing. Install them now? - + Wayland Detected! Wayland détecté ! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -8006,74 +8040,74 @@ Would you like to force it for future launches? - + Use X11 - + Continue with Wayland - + Don't show again - + Restart Required - + Restart Eden to apply the X11 backend. - + Slow - + Turbo - + Unlocked - + Select RomFS Dump Target - + Please select which RomFS you would like to dump. - + Are you sure you want to close Eden? - - - + + + Eden Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8139,6 +8173,11 @@ Would you like to bypass this and exit anyway? ScaleForce ScaleForce + + + FSR + FSR + Area @@ -8150,52 +8189,57 @@ Would you like to bypass this and exit anyway? - + + SGSR + + + + + SGSR EdgeDir + + + + Docked Mode TV - + Handheld Mode Portable - + Fast - - Balanced - - - - + Accurate - + Vulkan Vulkan - + OpenGL GLSL - + OpenGL SPIRV - + OpenGL GLASM - + Null @@ -8238,7 +8282,7 @@ Si vous souhaitez supprimer les fichiers qui ont été laissés dans l'anci %1 - + Data was migrated successfully. Les données ont été migré avec succès @@ -8731,8 +8775,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8745,10 +8789,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8763,9 +8807,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8927,50 +8971,50 @@ p, li { white-space: pre-wrap; } [non défini] - + %1%2 %1%2 - + [invalid] [invalide] - - + + %1%2Hat %3 %1%2Chapeau %3 - - - + + + %1%2Axis %3 %1%2Axe %3 - + %1%2Axis %3,%4,%5 %1%2Axe %3,%4,%5 - + %1%2Motion %3 %1%2Mouvement %3 - - + + %1%2Button %3 %1%2Bouton %3 - + [unused] [inutilisé] @@ -9096,47 +9140,47 @@ p, li { white-space: pre-wrap; } %1 joue à %2 - + Play Time: %1 - + Never Played - + Version: %1 - + Version: 1.0.0 - + Installed SD Titles Titres installés sur la SD - + Installed NAND Titles Titres installés sur la NAND - + System Titles Titres Système - + Add New Game Directory Ajouter un nouveau répertoire de jeu - + Favorites Favoris @@ -9257,14 +9301,14 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware Le jeu nécessite un firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. - Le jeu que vous essayez de lancer nécessite un firmware pour démarrer ou pour passer le menu d’ouverture. Veuillez <a href='https://yuzu-mirror.github.io/help/quickstart'>dumper et installer le firmware</a>, ou appuyez sur « OK » pour lancer quand même. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. + @@ -9273,10 +9317,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel Annuler @@ -9297,7 +9341,7 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 @@ -9306,207 +9350,240 @@ p, li { white-space: pre-wrap; } %1 - - + + Verifying integrity... Vérification de l'intégrité... - - + + Integrity verification succeeded! La vérification de l'intégrité réussi ! - - + + The operation completed successfully. L'opération s'est déroulée avec succès. - - + + Integrity verification failed! La vérification de l'intégrité a échoué ! - + File contents may be corrupt or missing. Le contenu d'un fichier peut être corrompu or manquant. - + Integrity verification couldn't be performed La vérification de l'intégrité n'a pas pu être effectuée - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. Installation du firmware annulée, le firmware est peut-être en mauvais état ou corrompu. Impossible de vérifier la validité du contenu du fichier. - + Select Dumped Keys Location Sélectionner Emplacement Clés Extraites - + Decryption Keys install succeeded Installation des clés de décryptage avec succès - + Decryption Keys install failed Installation des clés de décryptage échoué - + Orphaned Profiles Detected! Profils orphelins détectés ! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> DES CHOSES GRAVES INATTENDUES PEUVENT SURVENIR SI VOUS NE LISEZ PAS CECI !<br>Eden a détecté les répertoires de sauvegarde suivants sans profil associé :<br>%1<br><br>Les profils suivants sont valides :<br>%2<br><br>Cliquez sur « OK » pour ouvrir votre dossier de sauvegarde et corriger vos profils.<br>Astuce : copiez le contenu du dossier le plus volumineux ou le plus récemment modifié ailleurs, supprimez tous les profils orphelins et déplacez le contenu copié vers le profil correct.<br><br>Toujours confus ? Consultez la <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>page d’aide</a>.<br> - + Really clear data? Vraiment effacer les données ? - + Important data may be lost! Des données importantes peuvent être perdues ! - + Are you REALLY sure? Êtes-vous VRAIMENT sûr ? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. Une fois supprimées, vos données NE POURRONT PAS être récupérées ! Ne faites cela que si vous êtes sûr à 100 % de vouloir supprimer ces données. - + Clearing... Suppression en cours… - + Select Export Location Sélectionner l’emplacement d’exportation - + %1.zip %1.zip - - + + + Zipped Archives (*.zip) Archives compressées (*.zip) - + Exporting data. This may take a while... Exportation des données en cours. Cela peut prendre un certain temps… - + Exporting Exportation en cours… - + Exported Successfully Exportation réussie - + Data was exported successfully. Les données ont été exportées avec succès. - + Export Cancelled Exportation annulée - + Export was cancelled by the user. L’exportation a été annulée par l’utilisateur. - + Export Failed Échec de l’exportation - + Ensure you have write permissions on the targeted directory and try again. Assurez-vous d’avoir les permissions d’écriture sur le répertoire ciblé et réessayez. - + Select Import Location Sélectionner l’emplacement d’importation - + Import Warning Avertissement d’importation - + All previous data in this directory will be deleted. Are you sure you wish to proceed? Toutes les données précédentes de ce répertoire seront supprimées. Êtes-vous sûr de vouloir continuer ? - + Importing data. This may take a while... Importation des données en cours. Cela peut prendre un certain temps… - + Importing Importation en cours… - + Imported Successfully Importation réussie - + Data was imported successfully. Les données ont été importées avec succès. - + Import Cancelled Importation annulée - + Import was cancelled by the user. L’importation a été annulée par l’utilisateur. - + Import Failed Échec de l’importation - + Ensure you have read permissions on the targeted directory and try again. Assurez-vous d’avoir les permissions de lecture sur le répertoire ciblé et réessayez. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9753,72 +9830,72 @@ Would you like to manually select a portable folder to use? Le cache des métadonnées n'a pas pu être supprimé. Il est peut-être en cours d'utilisation ou inexistant. - + Create Shortcut Créer un raccourci - + Do you want to launch the game in fullscreen? Voulez-vous lancer le jeu en plein écran ? - + Shortcut Created Raccourcis crée - + Successfully created a shortcut to %1 Création d'un raccourci vers %1 réussi avec succès - + Shortcut may be Volatile! Les raccourcis peuvent être instables ! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? Cela créera un raccourci vers l'AppImage actuel. Cela peut ne pas fonctionner correctement si vous effectuez une mise à jour. Continuer ? - + Failed to Create Shortcut Échec de la création du raccourci - + Failed to create a shortcut to %1 Échec de la création d'un raccourci vers %1 - + Create Icon Créer une icône - + Cannot create icon file. Path "%1" does not exist and cannot be created. Impossible de créer le fichier icône. Le chemin "%1" n'existe pas et ne peut être créé. - + No firmware available Pas de firmware disponible - + Please install firmware to use the home menu. Veuillez installer un firmware pour utiliser le menu d'accueil - + Home Menu Applet Applet Menu d'accueil - + Home Menu is not available. Please reinstall firmware. Le menu d'accueil n'est pas disponible. Veuillez réinstaller le firmware @@ -9826,55 +9903,55 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name - + What should this mod be called? - + RomFS - + ExeFS/Patch - + Cheat - + Mod Type - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty @@ -10575,65 +10652,65 @@ En sélectionnant « Depuis Eden », les données de sauvegarde précédemment s - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/hu.ts b/dist/languages/hu.ts index a9067946f0..29ba70527e 100644 --- a/dist/languages/hu.ts +++ b/dist/languages/hu.ts @@ -211,155 +211,6 @@ Ez kitiltaná a fórum felhasználóneve és az IP címe alapján. %1 - %2 (%3/%4 tag) - csatlakozva - - CompatDB - - - Report Compatibility - Kompatibilitás jelentése - - - - - - - - - - Report Game Compatibility - Játék kompatibilitásának jelentése - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>Elindul a játék?</p></body></html> - - - - Yes The game starts to output video or audio - Igen A játék ad ki magából videót vagy hangot - - - - No The game doesn't get past the "Launching..." screen - Nem A játék nem megy tovább az "Indítás..." képernyőn - - - - Yes The game gets past the intro/menu and into gameplay - Igen A játék tovább megy az intro/menü szekción, és betölt a játékmenet - - - - No The game crashes or freezes while loading or using the menu - Nem A játék összeomlik, lefagy betöltés, vagy a menü használata közben - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>A játék eléri a játékmenetet?</p></body></html> - - - - Yes The game works without crashes - Igen A játék működik összeomlások nélkül - - - - No The game crashes or freezes during gameplay - Nem A játék összeomlik, vagy lefagy játékmenet közben - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>Működik a játék összeomlások, lefagyások vagy leállások nélkül játékmenet közben?</p></body></html> - - - - Yes The game can be finished without any workarounds - Igen A játék befejezhető akármilyen probléma megoldás nélkül - - - - No The game can't progress past a certain area - Nem A játékot nem lehet egy ponton túl tovább vinni - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>A játék teljesen befejezhető az elejétől a végéig?</p></body></html> - - - - Major The game has major graphical errors - Jelentős A játéknak jelentős grafikai problémái vannak - - - - Minor The game has minor graphical errors - Enyhe A játéknak enyhe grafikai problémái vannak  - - - - None Everything is rendered as it looks on the Nintendo Switch - Nincs Minden úgy van betöltve, ahogy Nintendo Switchen néz ki - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>Van a játéknak akármilyen grafikai problémája?</p></body></html> - - - - Major The game has major audio errors - Jelentős A játéknak jelentős hangproblémái vannak - - - - Minor The game has minor audio errors - Enyhe A játéknak enyhe hangproblémái vannak - - - - None Audio is played perfectly - Nincs A hang tökéletesen működik - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>Van a játéknak akármilyen hang problémája / kihagyott effektje?</p></body></html> - - - - Thank you for your submission! - Köszönjük a jelentést! - - - - Submitting - Beküldés - - - - Communication error - Kommunikációs probléma - - - - An error occurred while sending the Testcase - Probléma lépett fel a tesztelési jelentés küldése során - - - - Next - Következő - - ConfigurationShared @@ -559,174 +410,184 @@ Can help reduce stuttering at lower framerates. - + Backend: Backend: - CPU Overclock + CPU Clocks - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. - + Custom CPU Ticks - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. - + Enable Host MMU Emulation (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. - + Unfuse FMA (improve performance on CPUs without FMA) FMA kikapcsolása (javítja a teljesítményt FMA nélküli CPU-kon) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. Ez az opció a fused-multiply-add utasítások pontosságának csökkentésével javítja a sebességet olyan CPU-k esetén, amelyek nem rendelkeznek natív FMA támogatással. - + Faster FRSQRTE and FRECPE Gyorsabb FRSQRTE és FRECPE - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. Ez az opció javítja néhány közelítő lebegőpontos függvény sebességét azáltal, hogy kevésbé pontos natív megközelítést használ. - + Faster ASIMD instructions (32 bits only) Gyorsabb ASIMD utasítások (csak 32 bit) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. Ez az opció növeli a 32 bites ASIMD lebegőpontos függvények sebességét a helytelen kerekítési módok használatával. - + Inaccurate NaN handling Pontatlan NaN kezelés - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. Ez az opció növeli a sebességet a NaN ellenőrzés kihagyásával. Kérjük, vedd figyelembe, hogy ez bizonyos lebegőpontos utasítások pontosságát is csökkenti. - + Disable address space checks Címtartomány-ellenőrzések kikapcsolása - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. - + Ignore global monitor Globális monitorozás mellőzése - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. - + API: API: - + Changes the output graphics API. Vulkan is recommended. - + Device: Eszköz: - + This setting selects the GPU to use (Vulkan only). - + Resolution: Felbontás: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. - + Window Adapting Filter: Ablakadaptív szűrő: - + FSR Sharpness: FSR élesség: - - Determines how sharpened the image will look using FSR's dynamic contrast. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. - + Anti-Aliasing Method: Élsimítási módszer: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Teljes képernyős mód: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. @@ -735,47 +596,47 @@ A borderless (szegély nélküli) biztosítja a legjobb kompatibilitást a képe Az exkluzív teljes képernyő jobb teljesítményt és jobb Freesync/Gsync támogatást kínálhat. - + Aspect Ratio: Képarány: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. - + Use persistent pipeline cache - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. Lehetővé teszi az árnyékolók tárolását a gyorsabb betöltés érdekében a következő játékindításokkor. Kikapcsolása csak hibakeresésre szolgál. - + Use asynchronous GPU emulation - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + NVDEC emulation: NVDEC emuláció: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. @@ -784,12 +645,12 @@ A dekódoláshoz használhatja a CPU-t vagy a GPU-t, vagy egyáltalán nem vége A legtöbb esetben a GPU dekódolás nyújtja a legjobb teljesítményt. - + ASTC Decoding Method: ASTC dekódoló módszer: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -798,55 +659,75 @@ stuttering but may present artifacts. - + ASTC Recompression Method: ASTC újraszűrési módszer: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: VRAM használati mód: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. - + Skip CPU Inner Invalidation - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: VSync mód: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -854,1361 +735,1371 @@ Immediate (no synchronization) presents whatever is available and can exhibit te - + Sync Memory Operations - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. - + Enable asynchronous presentation (Vulkan only) Aszinkron prezentálás engedélyezése (csak Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. Kicsit javítja a teljesítményt azáltal, hogy a megjelenítést külön CPU szálra helyezi át. - + Force maximum clocks (Vulkan only) Maximális órajelek kényszerítése (csak Vulkan) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. A háttérben fut, miközben várja a grafikai parancsokat, hogy a GPU ne csökkentse az órajelét. - + Anisotropic Filtering: Anizotropikus szűrés: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. - + GPU Mode: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - + DMA Accuracy: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. - + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation - + May reduce shader stutter. - - Fast GPU Time + + GPU Clocks - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. - + GPU Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache Vulkan pipeline gyorsítótár használata. - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. - + Enable Compute Pipelines (Intel Vulkan Only) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. - + Enable Reactive Flushing Reaktív ürítés használata - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. Reaktív ürítést használ a prediktív ürítés helyett, ami pontosabb memóriaszinkronizálást tesz lehetővé. - + Sync to framerate of video playback Szinkronizálás a videolejátszás képkockasebességéhez - + Run the game at normal speed during video playback, even when the framerate is unlocked. A játék futtatása normál sebességgel videolejátszás közben, még akkor is, ha a képkockasebesség fel van oldva. - + Barrier feedback loops - + Improves rendering of transparency effects in specific games. Javítja az átlátszósági effektek megjelenítését bizonyos játékokban. - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. - + Vertex Input Dynamic State - + Enables vertex input dynamic state feature for better quality and performance. - + Sample Shading - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. - + RNG Seed - + Controls the seed of the random number generator. Mainly used for speedrunning. - + Device Name Eszköznév - + The name of the console. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: Egyéni RTC dátum: - + This option allows to change the clock of the console. Can be used to manipulate time in games. - + The number of seconds from the current unix time - + Language: Nyelv: - + This option can be overridden when region setting is auto-select - + Region: Régió: - + The region of the console. - + Time Zone: Időzóna: - + The time zone of the console. - + Sound Output Mode: Hangkimeneti mód: - + Console Mode: Konzol mód: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. - + Prompt for user profile on boot - + Useful if multiple people use the same PC. - + Pause when not in focus - + Pauses emulation when focusing on other windows. - + Confirm before stopping emulation Emuláció leállításának megerősítése - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. - + Hide mouse on inactivity Egér elrejtése inaktivitáskor - + Hides the mouse after 2.5s of inactivity. - - Disable controller applet - Vezérlő applet letiltása - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - - - - + Check for updates - + Whether or not to check for updates upon startup. - + Enable Gamemode Játékmód engedélyezése - + Force X11 as Graphics Backend - + Custom frontend Egyéni frontend - + Real applet Valódi applet - + + + Disabled + + + + Never - + On Load - + Always - + CPU CPU - + GPU GPU - + CPU Asynchronous CPU aszinkron - + Uncompressed (Best quality) Tömörítetlen (legjobb minőség) - + BC1 (Low quality) BC1 (alacsony minőség) - + BC3 (Medium quality) BC3 (közepes minőség) - - + + Auto Automatikus - + 30 FPS - + 60 FPS - + 90 FPS - + 120 FPS - + Conservative Takarékos - + Aggressive Aggresszív - + Vulkan Vulkan - + OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) - + Null Null - + Fast - - Balanced - - - - - + + + Accurate Pontos - - + + + Default Alapértelmezett - + Unsafe (fast) - + Safe (stable) - + + Immediate + + + + + Balanced + + + + Unsafe Nem biztonságos - + Paranoid (disables most optimizations) Paranoid (a legtöbb optimalizálást letiltja) - + Debugging - + Dynarmic Dinamikus - + NCE NCE - + Borderless Windowed Szegély nélküli ablak - + Exclusive Fullscreen Exkluzív teljes képernyő - + No Video Output Nincs videokimenet - + CPU Video Decoding CPU videódekódolás - + GPU Video Decoding (Default) GPU videódekódolás (alapértelmezett) - + 0.25X (180p/270p) [EXPERIMENTAL] - + 0.5X (360p/540p) [EXPERIMENTAL] 0.5X (360p/540p) [KÍSÉRLETI] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [KÍSÉRLETI] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [KÍSÉRLETI] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor Legközelebbi szomszéd - + Bilinear Bilineáris - + Bicubic Bikubikus - + Gaussian Gauss-féle - + Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution - + Area - + MMPX - + Zero-Tangent - + B-Spline - + Mitchell - + Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None Nincs - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Alapértelmezett (16:9) - + Force 4:3 4:3 kényszerítése - + Force 21:9 21:9 kényszerítése - + Force 16:10 16:10 kényszerítése - + Stretch to Window Ablakhoz nyújtás - + Automatic Automatikus - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - - - - - 64x - - - - + Japanese (日本語) Japán (日本語) - + American English Amerikai angol - + French (français) Francia (français) - + German (Deutsch) Német (Deutsch) - + Italian (italiano) Olasz (italiano) - + Spanish (español) Spanyol (español) - + Chinese Kínai - + Korean (한국어) Koreai (한국어) - + Dutch (Nederlands) Holland (Nederlands) - + Portuguese (português) Portugál (português) - + Russian (Русский) Orosz (Русский) - + Taiwanese Tajvani - + British English Brit Angol - + Canadian French Kanadai francia - + Latin American Spanish Latin-amerikai spanyol - + Simplified Chinese Egyszerűsített kínai - + Traditional Chinese (正體中文) Hagyományos kínai (正體中文) - + Brazilian Portuguese (português do Brasil) Brazíliai portugál (português do Brasil) - + Polish (polska) - + Thai (แบบไทย) - - + + Japan Japán - + USA USA - + Europe Európa - + Australia Ausztrália - + China Kína - + Korea Korea - + Taiwan Tajvan - + Auto (%1) Auto select time zone Automatikus (%1) - + Default (%1) Default time zone Alapértelmezett (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Kuba - + EET EET - + Egypt Egyiptom - + Eire Eire - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Eire - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hongkong - + HST HST - + Iceland Izland - + Iran Irán - + Israel Izrael - + Jamaica Jamaika - + Kwajalein Kwajalein - + Libya Líbia - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navahó - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Lengyelország - + Portugal Portugália - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Szingapúr - + Turkey Törökország - + UCT UCT - + Universal Univerzális - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulu - + Mono Mono - + Stereo Sztereó - + Surround Térhangzás - + 4GB DRAM (Default) 4GB DRAM (Alapértelmezett) - + 6GB DRAM (Unsafe) 6GB DRAM (Nem biztonságos) - + 8GB DRAM - + 10GB DRAM (Unsafe) - + 12GB DRAM (Unsafe) - + Docked Dokkolt - + Handheld Kézi - - - Off + + + Normal - - Boost (1700MHz) + + + Boost - - Fast (2000MHz) + + + Overclock - + Always ask (Default) Mindig kérdezz rá (alapértelmezett) - + Only if game specifies not to stop Csak akkor, ha a játék kifejezetten kéri a folytatást. - + Never ask Soha ne kérdezz rá - - - Medium (256) - - - - - - High (512) - - - - + Very Small (16 MB) - + Small (32 MB) - + Normal (128 MB) - + Large (256 MB) - + Very Large (512 MB) - + Very Low (4 MB) - + Low (8 MB) - + Normal (16 MB) - + Medium (32 MB) - + High (64 MB) - + Very Low (32) - + Low (64) - + Normal (128) - - Disabled + + Medium (256) - + + High (512) + + + + ExtendedDynamicState 1 - + ExtendedDynamicState 2 - + ExtendedDynamicState 3 - + Tree View - + Grid View @@ -2541,252 +2432,297 @@ When a program attempts to open the controller applet, it is immediately closed. Globális naplózási szűrő - - When checked, the max size of the log increases from 100 MB to 1 GB - Ha be van jelölve, a napló maximális mérete 100 MB-ról 1 GB-ra nő. + + GPU Logging/Level + - - Enable Extended Logging** - Bővített naplózás engedélyezése + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console Napló mutatása a Konzolban - + + When checked, the max size of the log increases from 100 MB to 1 GB + Ha be van jelölve, a napló maximális mérete 100 MB-ról 1 GB-ra nő. + + + + Enable Extended Logging** + Bővített naplózás engedélyezése + + + Open Log Location Naplózási hely megnyitása - + Homebrew - + Arguments String - + Graphics Grafika - + When checked, it executes shaders without loop logic changes Ha be van jelölve, végrehajtja az árnyékolókat ismétlő logikai változtatások nélkül - + Disable Loop safety checks Ismétlő biztonsági ellenőrzések tiltása - + When checked, it will dump all the macro programs of the GPU Ha be van jelölve, ki fogja menteni a GPU összes makróprogramját. - + Dump Maxwell Macros Maxwell makrók kimentése - + When checked, it enables Nsight Aftermath crash dumps Ha be van jelölve, engedélyezi az Nsight Aftermath összeomlási naplókat. - + Enable Nsight Aftermath Nsight Aftermath engedélyezése - - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Ha be van jelölve, az összes eredeti assembler árnyékolót ki fogja menteni a lemez árnyékoló gyorsítótárból vagy a játékból, ahogyan azt találja. - - - - Dump Game Shaders - Játék árnyékolók kimentése - - - - Enable Renderdoc Hotkey - Renderdoc gyorsgomb engedélyezése - - - - When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower - Ha be van jelölve, letiltja a macro Just In Time fordítót. Ennek engedélyezése lelassítja a játékok sebességét. - - - - Disable Macro JIT - Macro JIT tiltása - - - - When checked, it disables the macro HLE functions. Enabling this makes games run slower - Ha be van jelölve, letiltja a macro HLE funkciókat. Ennek engedélyezése lelassítja a játékok sebességét. - - - - Disable Macro HLE - Macro HLE tiltása - - - - When checked, the graphics API enters a slower debugging mode - Ha be van jelölve, a grafikus API lassabb hibakereső módba lép. - - - - Enable Graphics Debugging - Grafikai hibakeresés engedélyezése - - - - When checked, yuzu will log statistics about the compiled pipeline cache - Ha be van jelölve, a yuzu naplózza a fordított pipeline gyorsítótár statisztikáit. - - Enable Shader Feedback - Árnyékoló visszajelzés engedélyezése + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + - - <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + Dump Guest (Maxwell) Shaders + Enable Renderdoc Hotkey + Renderdoc gyorsgomb engedélyezése + + + + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower + Ha be van jelölve, letiltja a macro Just In Time fordítót. Ennek engedélyezése lelassítja a játékok sebességét. + + + + Disable Macro JIT + Macro JIT tiltása + + + + When checked, it disables the macro HLE functions. Enabling this makes games run slower + Ha be van jelölve, letiltja a macro HLE funkciókat. Ennek engedélyezése lelassítja a játékok sebességét. + + + + Disable Macro HLE + Macro HLE tiltása + + + + When checked, the graphics API enters a slower debugging mode + Ha be van jelölve, a grafikus API lassabb hibakereső módba lép. + + + + Enable Graphics Debugging + Grafikai hibakeresés engedélyezése + + + + When checked, yuzu will log statistics about the compiled pipeline cache + Ha be van jelölve, a yuzu naplózza a fordított pipeline gyorsítótár statisztikáit. + + + + Enable Shader Feedback + Árnyékoló visszajelzés engedélyezése + + + + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + + + Disable Buffer Reorder Puffer újrarendezés letiltása - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced Haladó - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Lehetővé teszi, hogy a yuzu ellenőrizze a Vulkan környezet működését a program indításakor. Ha ez problémát okoz a külső programoknak a yuzu észlelésében, akkor tiltsd le ezt az opciót. - + Perform Startup Vulkan Check Vulkan ellenőrzése indításkor - + Disable Web Applet Web applet letiltása - + Enable All Controller Types Összes vezérlőtípus engedélyezése - + Enable Auto-Stub - + Kiosk (Quest) Mode Kiosk (Quest) mód - + Use dev.keys - + Enable Debug Asserts - + Debugging Hibakeresés - + Battery Serial: - + + Debug Knobs: + + + + Bitmask for quick development toggles - + Set debug knobs (bitmask) - + 16-bit debug knob set for quick development toggles - + (bitmask) - - Debug Knobs: - - - - + Unit Serial: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. - + Dump Audio Commands To Console** Audioparancsok kimentése a Konzolba** - + Flush log output on each line - + Enable FS Access Log FS hozzáférési napló engedélyezése - + Enable Verbose Reporting Services** Részletes jelentést nyújtó szolgáltatások engedélyezése** - + Censor username in logs - + **This will be reset automatically when Eden closes. @@ -3236,33 +3172,38 @@ Would you like to delete the old save data? Háttérszín: - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off Ki - + VSync Off VSync Ki - + Recommended Ajánlott - + On Be - + VSync On VSync Be @@ -3783,6 +3724,16 @@ Would you like to delete the old save data? Motion / Touch Mozgás / Érintés + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4292,58 +4243,58 @@ A tengely megfordításához mozgasd a kart először függőlegesen, majd vízs [várakozás] - + New Profile Új profil - + Enter a profile name: Add meg a profil nevét: - - + + Create Input Profile Beviteli profil létrehozása - + The given profile name is not valid! A megadott profilnév érvénytelen! - + Failed to create the input profile "%1" A "%1" beviteli profilt nem sikerült létrehozni - + Delete Input Profile Beviteli profil törlése - + Failed to delete the input profile "%1" A "%1" beviteli profilt nem sikerült eltávolítani - + Load Input Profile Beviteli profil betöltése - + Failed to load the input profile "%1" A "%1" beviteli profilt nem sikerült betölteni - + Save Input Profile Beviteli profil mentése - + Failed to save the input profile "%1" A "%1" beviteli profilt nem sikerült elmenteni @@ -4391,7 +4342,7 @@ A tengely megfordításához mozgasd a kart először függőlegesen, majd vízs - + Configure Konfigurálás @@ -4422,7 +4373,7 @@ A tengely megfordításához mozgasd a kart először függőlegesen, majd vízs - + Test Teszt @@ -4437,77 +4388,77 @@ A tengely megfordításához mozgasd a kart először függőlegesen, majd vízs Szerver eltávolítása - + %1:%2 %1:%2 - - - - - - + + + + + + Eden - + Port number has invalid characters A port érvénytelen karaktereket tartalmaz - + Port has to be in range 0 and 65353 A portnak 0 és 65353 közötti tartományban kell lennie. - + IP address is not valid Érvénytelen IP-cím - + This UDP server already exists Ez az UDP szerver már létezik - + Unable to add more than 8 servers 8-nál több kiszolgálót nem lehet hozzáadni - + Testing Tesztelés - + Configuring Konfigurálás - + Test Successful Sikeres teszt - + Successfully received data from the server. Az adatok sikeresen beérkeztek a kiszolgálótól. - + Test Failed Sikertelen teszt - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Nem lehetett érvényes adatot fogadni a szervertől. <br>Ellenőrizd, hogy a szerver megfelelően van-e beállítva, valamint a cím és a port helyes. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. UDP tesztelés vagy a kalibrálás konfigurálása folyamatban van.<br>Kérjük, várj, amíg befejeződik. @@ -4692,57 +4643,57 @@ A jelenlegi érték %1% és %2%. Néhány beállítás csak akkor érhető el, amikor nem fut játék. - + Add-Ons Kiegészítők - + System Rendszer - + CPU CPU - + Graphics Grafika - + Adv. Graphics Haladó graf. - + Ext. Graphics - + Audio Hang - + Input Profiles Beviteli profilok - + Network - + Applets - + Properties Tulajdonságok @@ -4865,6 +4816,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5432,91 +5507,86 @@ Húzd a pontokat a pozíció megváltoztatásához, vagy kattints duplán a táb - Show Compatibility List - Kompatibilitási lista mutatása - - - Show Add-Ons Column Kiegészítők oszlop mutatása - + Show Size Column Méret oszlop mutatása - + Show File Types Column Fájltípus oszlop mutatása - + Show Play Time Column Játékidő oszlop mutatása - + Folder Icon Size: Mappa ikonméret: - + Row 1 Text: 1. sor szövege: - + Row 2 Text: 2. sor szövege: - + Screenshots Képernyőmentések - + Ask Where To Save Screenshots (Windows Only) Kérdezze meg a képernyőmentések útvonalát (csak Windowson) - + Screenshots Path: Képernyőmentések útvonala: - + ... ... - + TextLabel - + Resolution: Felbontás: - + Select Screenshots Path... Képernyőmentések útvonala... - + <System> <System> - + English Angol - + Auto (%1 x %2, %3 x %4) Screenshot width value Automatikus (%1 x %2, %3 x %4) @@ -5753,7 +5823,7 @@ Húzd a pontokat a pozíció megváltoztatásához, vagy kattints duplán a táb - + Calculating... @@ -5955,50 +6025,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL nem elérhető! - + OpenGL shared contexts are not supported. - + Eden has not been compiled with OpenGL support. - - - + + + Error while initializing OpenGL! Hiba történt az OpenGL inicializálása során! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Lehetséges, hogy a GPU-d nem támogatja az OpenGL-t, vagy nem a legfrissebb grafikus illesztőprogram van telepítve. - + Error while initializing OpenGL 4.6! Hiba történt az OpenGL 4.6 inicializálása során! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Lehetséges, hogy a GPU-d nem támogatja az OpenGL 4.6-ot, vagy nem a legfrissebb grafikus illesztőprogram van telepítve.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Előfordulhat, hogy a GPU-d nem támogat egy vagy több szükséges OpenGL kiterjesztést. Győződj meg róla, hogy a legújabb videokártya-illesztőprogramot használod.<br><br>GL Renderer:<br>%1<br><br>Nem támogatott kiterjesztések:<br>%2 - + This build doesn't have OpenGL support. @@ -6006,279 +6076,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory - + Favorite Kedvenc - + Start Game Játék indítása - + Start Game without Custom Configuration Játék indítása egyéni konfiguráció nélkül - + Open Save Data Location Mentett adatok helyének megnyitása - + Open Mod Data Location Modadatok helyének megnyitása - + Open Transferable Pipeline Cache Áthelyezhető pipeline gyorsítótár megnyitása - + Link to Ryujinx - + Remove Eltávolítás - + Remove Installed Update Telepített frissítés eltávolítása - + Remove All Installed DLC Összes telepített DLC eltávolítása - + Remove Custom Configuration Egyéni konfiguráció eltávolítása - + Remove Cache Storage Gyorsítótár ürítése - + Remove OpenGL Pipeline Cache OpenGL Pipeline gyorsítótár eltávolítása - + Remove Vulkan Pipeline Cache Vulkan pipeline gyorsítótár eltávolítása - + Remove All Pipeline Caches Az összes Pipeline gyorsítótár törlése - + Remove All Installed Contents Összes telepített tartalom törlése - + Manage Play Time - + Edit Play Time Data - + Remove Play Time Data Játékidő törlése - - + + Dump RomFS RomFS kimentése - + Dump RomFS to SDMC RomFS kimentése SDMC-re - + Verify Integrity Integritás ellenőrzése - + Copy Title ID to Clipboard Játék címének vágólapra másolása - - Navigate to GameDB entry - GameDB bejegyzéshez navigálás - - - + Create Shortcut Parancsikon létrehozása - + Add to Desktop Asztalhoz adás - + Add to Applications Menu Alkalmazások menühöz adás - + Configure Game - + Scan Subfolders Almappák szkennelése - + Remove Game Directory Játékkönyvtár eltávolítása - + ▲ Move Up ▲ Feljebb mozgatás - + ▼ Move Down ▼ Lejjebb mozgatás - + Open Directory Location Könyvtár helyének megnyitása - + Clear Törlés - - - Name - Név - - - - Compatibility - Kompatibilitás - - - - Add-ons - Kiegészítők - - - - File type - Fájltípus - - - - Size - Méret - - - - Play time - Játékidő - GameListItemCompat - + Ingame Játékban - + Game starts, but crashes or major glitches prevent it from being completed. A játék elindul, de összeomlik, vagy súlyos hibák miatt nem fejezhető be. - + Perfect Tökéletes - + Game can be played without issues. A játék problémamentesen játszható. - + Playable Játszható - + Game functions with minor graphical or audio glitches and is playable from start to finish. A játék kisebb grafikai- és hanghibákkal végigjátszható. - + Intro/Menu Bevezető/Menü - + Game loads, but is unable to progress past the Start Screen. A játék betölt, de nem jut tovább a Kezdőképernyőn. - + Won't Boot Nem indul - + The game crashes when attempting to startup. A játék összeomlik indításkor. - + Not Tested Nem tesztelt - + The game has not yet been tested. Ez a játék még nem lett tesztelve. + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list Dupla kattintással új mappát adhatsz hozzá a játéklistához. @@ -6286,17 +6349,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) %1 a(z) %n találatból%1 a(z) %n találatból - + Filter: Szűrés: - + Enter pattern to filter Adj meg egy mintát a szűréshez @@ -6791,1139 +6854,1112 @@ Debug Message: - + Game &Icon Size - + Reset Window Size to &720p Ablakfelbontás visszaállítása erre: &720p - + Reset Window Size to 720p Ablakfelbontás visszaállítása 720p-re - + Reset Window Size to &900p Ablakfelbontás visszaállítása erre: &900p - + Reset Window Size to 900p Ablakfelbontás visszaállítása 900p-re - + Reset Window Size to &1080p Ablakfelbontás visszaállítása erre: &1080p - + Reset Window Size to 1080p Ablakfelbontás visszaállítása 1080p-re - + &Multiplayer &Multiplayer - + &Tools &Eszközök - + Am&iibo - + Launch &Applet - + &TAS &TAS - + &Create Home Menu Shortcut - + Install &Firmware - + &Help &Segítség - + &Install Files to NAND... &Fájlok telepítése a NAND-ra... - + L&oad File... F&ájl betöltése... - + Load &Folder... &Mappa betöltése... - + E&xit K&ilépés - - + + &Pause &Szünet - + &Stop &Leállítás - + &Verify Installed Contents &Telepített tartalom ellenőrzése - + &About Eden - + Single &Window Mode &Egyablakos mód - + Con&figure... Kon&figurálás... - + Ctrl+, - + Enable Overlay Display Applet - + Show &Filter Bar &Szűrősáv mutatása - + Show &Status Bar &Állapotsáv mutatása - + Show Status Bar Állapotsáv mutatása - + &Browse Public Game Lobby &Nyilvános játéklobbi böngészése - + &Create Room &Szoba létrehozása - + &Leave Room &Szoba elhagyása - + &Direct Connect to Room &Közvetlen csatlakozás szobához - + &Show Current Room &Jelenlegi szoba megjelenítése - + F&ullscreen T&eljes képernyő - + &Restart &Újraindítás - + Load/Remove &Amiibo... &Amiibo betöltése/törlése... - - &Report Compatibility - &Kompatibilitás jelentése - - - + Open &Mods Page &Modok oldal megnyitása - - Open &Quickstart Guide - &Gyorstájékoztató megnyitása + + &User Handbook + - - &FAQ - &GYIK - - - + &Capture Screenshot &Képernyőkép készítése - + &Album - + &Set Nickname and Owner &Becenév és tulajdonos beállítása - + &Delete Game Data &Játékadatok törlése - + &Restore Amiibo &Amiibo helyreállítása - + &Format Amiibo &Amiibo formázása - + &Mii Editor - + &Configure TAS... &TAS konfigurálása... - + Configure C&urrent Game... J&elenlegi játék konfigurálása... - - + + &Start &Indítás - + &Reset &Visszaállítás - - + + R&ecord F&elvétel - + Open &Controller Menu &Vezérlő menü megnyitása - + Install Decryption &Keys - + &Home Menu - + &Desktop - + &Application Menu - + &Root Data Folder - + &NAND Folder - + &SDMC Folder - + &Mod Folder - + &Log Folder - + From Folder - + From ZIP - + &Eden Dependencies - + &Data Manager - + &Tree View - + &Grid View - + Game Icon Size - - + + None - + Show Game &Name - + Show &Performance Overlay - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) - + Standard (64x64) - + Large (128x128) - + Full Size (256x256) - + Broken Vulkan Installation Detected - + Vulkan initialization failed during boot. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping - + Loading Web Applet... - - + + Disable Web Applet - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + The amount of shaders currently being built - + The current selected resolution scaling multiplier. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Unmute - + Mute - + Reset Volume - + + Configure Effects... + + + + &Clear Recent Files - + &Continue - + Warning: Outdated Game Format - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - - + + Error while loading ROM! - + The ROM format is not supported. - + An error occurred initializing the video core. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + An unknown error occurred. Please see the log for more details. - + + (64-bit) - + + (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + Closing software... - + Save Data - + Mod Data - + Error Opening %1 Folder - - + + Folder does not exist! - + Remove Installed Game Contents? - + Remove Installed Game Update? - + Remove Installed Game DLC? - + Remove Entry - + Delete OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? - + Remove Custom Game Configuration? - + Remove Cache Storage? - + Remove File - + Remove Play Time Data - + Reset play time? - - + + RomFS Extraction Failed! - + There was an error copying the RomFS files or the user cancelled the operation. - + Full - + Skeleton - + Select RomFS Dump Mode - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Extracting RomFS... - - + + Cancel - + RomFS Extraction Succeeded! - + The operation completed successfully. - + Error Opening %1 - + Select Directory - + Properties - + The game properties could not be loaded. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Load File - + Open Extracted ROM Directory - + Invalid Directory Selected - + The directory you have selected does not contain a 'main' file. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files - + %n file(s) remaining - + Installing file "%1"... - - + + Install Results - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + %n file(s) were newly installed - + %n file(s) were overwritten - + %n file(s) failed to install - + System Application - + System Archive - + System Application Update - + Firmware Package (Type A) - + Firmware Package (Type B) - + Game - + Game Update - + Game DLC - + Delta Title - + Select NCA Install Type... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Failed to Install - + The title type you selected for the NCA is invalid. - + File not found - + File "%1" not found - + OK - + Function Disabled - + Compatibility list reporting is currently disabled. Check back later! - + Error opening URL - + Unable to open the URL "%1". - + TAS Recording - + Overwrite file of player 1? - + Invalid config detected - + Handheld controller can't be used on docked mode. Pro controller will be selected. - - + + Amiibo - - + + The current amiibo has been removed - + Error - - + + The current game is not looking for amiibos - + Amiibo File (%1);; All Files (*.*) - + Load Amiibo - + Error loading Amiibo data - + The selected file is not a valid amiibo - + The selected file is already on use - + An unknown error occurred - - - Keys not installed - - - - - - Install decryption keys and restart Eden before attempting to install firmware. - - - - - Select Dumped Firmware Source Location - - - - - Select Dumped Firmware ZIP - - - - - Zipped Archives (*.zip) - - - - - Firmware cleanup failed - - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available - + Firmware Corrupted - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot - + PNG Image (*.png) - + TAS state: Running %1/%2 - + TAS state: Recording %1 - + TAS state: Idle %1/%2 - + TAS State: Invalid - + &Stop Running - + Stop R&ecording - + Building: %n shader(s) - + Scale: %1x %1 is the resolution scaling factor - + Speed: %1% / %2% - + Speed: %1% - + Game: %1 FPS - + Frame: %1 ms - - - FSR - - - - + NO AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE - + VOLUME: %1% Volume percentage (e.g. 50%) - + Derivation Components Missing - + Decryption keys are missing. Install them now? - + Wayland Detected! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -7931,74 +7967,74 @@ Would you like to force it for future launches? - + Use X11 - + Continue with Wayland - + Don't show again - + Restart Required - + Restart Eden to apply the X11 backend. - + Slow - + Turbo - + Unlocked - + Select RomFS Dump Target - + Please select which RomFS you would like to dump. - + Are you sure you want to close Eden? - - - + + + Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8064,6 +8100,11 @@ Would you like to bypass this and exit anyway? ScaleForce + + + FSR + + Area @@ -8075,52 +8116,57 @@ Would you like to bypass this and exit anyway? - + + SGSR + + + + + SGSR EdgeDir + + + + Docked - + Handheld - + Fast - - Balanced - - - - + Accurate - + Vulkan - + OpenGL GLSL - + OpenGL SPIRV - + OpenGL GLASM - + Null @@ -8153,7 +8199,7 @@ If you wish to clean up the files which were left in the old data location, you - + Data was migrated successfully. @@ -8643,8 +8689,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8657,10 +8703,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8675,9 +8721,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8839,50 +8885,50 @@ p, li { white-space: pre-wrap; } [nem definiált] - + %1%2 %1%2 - + [invalid] [érvénytelen] - - + + %1%2Hat %3 - - - + + + %1%2Axis %3 %1%2Tengely %3 - + %1%2Axis %3,%4,%5 %1%2Tengely %3,%4,%5 - + %1%2Motion %3 - - + + %1%2Button %3 %1%2Gomb %3 - + [unused] [nem használt] @@ -9008,47 +9054,47 @@ p, li { white-space: pre-wrap; } %1 ezzel játszik: %2 - + Play Time: %1 - + Never Played - + Version: %1 - + Version: 1.0.0 - + Installed SD Titles Telepített SD játékok - + Installed NAND Titles Telepített NAND játékok - + System Titles Rendszercímek - + Add New Game Directory Új játékkönyvtár hozzáadása - + Favorites Kedvencek @@ -9169,13 +9215,13 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. @@ -9185,10 +9231,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9209,213 +9255,246 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9659,72 +9738,72 @@ Would you like to manually select a portable folder to use? - + Create Shortcut - + Do you want to launch the game in fullscreen? - + Shortcut Created - + Successfully created a shortcut to %1 - + Shortcut may be Volatile! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? - + Failed to Create Shortcut - + Failed to create a shortcut to %1 - + Create Icon - + Cannot create icon file. Path "%1" does not exist and cannot be created. - + No firmware available - + Please install firmware to use the home menu. - + Home Menu Applet - + Home Menu is not available. Please reinstall firmware. @@ -9732,55 +9811,55 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name - + What should this mod be called? - + RomFS - + ExeFS/Patch - + Cheat - + Mod Type - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty @@ -10474,65 +10553,65 @@ By selecting "From Eden", previous save data stored in Ryujinx will be - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/id.ts b/dist/languages/id.ts index d759c99d70..7792d12f35 100644 --- a/dist/languages/id.ts +++ b/dist/languages/id.ts @@ -218,155 +218,6 @@ Ini akan melarang nama pengguna forum mereka dan alamat IP mereka. %1 - %2 (%3/%4 anggota) - terhubung - - CompatDB - - - Report Compatibility - Laporkan Kekompatibelan - - - - - - - - - - Report Game Compatibility - Laporkan Kekompatibelan Permainan - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - <html><head/><body><p><span style=" font-size:10pt;">Jika Anda memilih untuk mengirimkan uji kasus ke </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">List Kompatibilitas Eden</span></a><span style=" font-size:10pt;">, Berikut beberapa informasi yang akan diambil dan diperlihatkan di Situs:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Informasi Hardware (CPU / GPU / Sistem Operasi)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Versi Eden yang Anda jalankan</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Akun Eden yang terkoneksi</li></ul></body></html> - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>Apakah permainan dapat dijalankan?</p></body></html> - - - - Yes The game starts to output video or audio - Ya Permainan mulai mengeluarkan video atau audio - - - - No The game doesn't get past the "Launching..." screen - Tidak, permainan tidak melewati layar "Launching..." - - - - Yes The game gets past the intro/menu and into gameplay - Ya Permainan melewati intro/menu dan masuk ke gameplay - - - - No The game crashes or freezes while loading or using the menu - Tidak, permainan mengalami crash atau membeku saat memuat atau menggunakan menu - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>Apakah permainan mencapai gameplay?</p></body></html> - - - - Yes The game works without crashes - Ya Permainan berfungsi tanpa crash - - - - No The game crashes or freezes during gameplay - Tidak, permainan mengalami crash atau membeku selama bermain. - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>Apakah permainan berfungsi tanpa crash, membeku, atau terkunci selama bermain?</p></body></html> - - - - Yes The game can be finished without any workarounds - Ya, permainan ini dapat selesai tanpa ada solusi alternatif. - - - - No The game can't progress past a certain area - Tidak, permainan tidak dapat melanjutkan melewati area tertentu. - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>Apakah permainan ini sepenuhnya dapat dimainkan dari awal hingga akhir?</p></body></html> - - - - Major The game has major graphical errors - Major Permainan ini memiliki kesalahan grafis yang besar - - - - Minor The game has minor graphical errors - Permainan ini memiliki kesalahan grafis yang kecil. - - - - None Everything is rendered as it looks on the Nintendo Switch - Semua Semuanya dirender seperti yang terlihat di Nintendo Switch - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>Apakah permainan ini memiliki kesalahan grafis?</p></body></html> - - - - Major The game has major audio errors - Permainan ini memiliki kesalahan audio yang besar - - - - Minor The game has minor audio errors - Permainan ini memiliki kesalahan audio yang kecil. - - - - None Audio is played perfectly - Tidak ada Audio diputar dengan sempurna - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>Apakah permainan ini memiliki gangguan audio / efek yang hilang?</p></body></html> - - - - Thank you for your submission! - Terima kasih atas pengajuan yang Anda kirimkan! - - - - Submitting - Mengajukan - - - - Communication error - Kesalahan komunikasi - - - - An error occurred while sending the Testcase - Terjadi kesalahan saat mengirim Testcase - - - - Next - Berikutnya - - ConfigurationShared @@ -567,39 +418,38 @@ Can help reduce stuttering at lower framerates. - + Backend: Backend: - CPU Overclock + CPU Clocks - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. - Overclock emulasi CPU untuk menghapus beberapa limiter FPS. CPU yang lebih lemah dapat mengalami penurunan kinerja, dan game tertentu dapat berjalan tidak semestinya. -untuk menjalankan clock asli tertingi Switch, Gunakan Boost (1700MHz), atau Fast (2000MHz) untuk menjalankan 2x clock. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + - + Custom CPU Ticks Siklus CPU Kustom - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. - + Enable Host MMU Emulation (fastmem) Aktifkan Emulasi Host MMU (memori cepat) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. @@ -608,137 +458,147 @@ Saat diaktifkan, dapat menyebablkan reads/writes memori tamu dilakukan langsung Menonaktifkan opsi ini akan memaksa semua akses memori untuk menggunakan Emulasi MMU Perangkat Lunak. - + Unfuse FMA (improve performance on CPUs without FMA) Pisahkan FMA (meningkatkan performa pada CPU tanpa FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. Opsi ini meningkatkan kecepatan dengan mengurangi akurasi instruksi fused-multiply-add pada CPU tanpa dukungan FMA asli. - + Faster FRSQRTE and FRECPE FRSQRTE dan FRECPE lebih cepat - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. Opsi ini meningkatkan kecepatan beberapa fungsi titik mengambang perkiraan dengan menggunakan perkiraan asli yang kurang akurat. - + Faster ASIMD instructions (32 bits only) Instruksi ASIMD lebih cepat (hanya untuk 32 bits) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. Opsi ini meningkatkan kecepatan fungsi floating-point ASIMD 32 bit dengan menjalankannya dengan mode pembulatan yang salah. - + Inaccurate NaN handling Penanganan NaN tidak akurat - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. Opsi ini meningkatkan kecepatan dengan menghilangkan pemeriksaan NaN. Harap dicatat ini juga mengurangi akurasi instruksi titik mengambang tertentu. - + Disable address space checks Matikan pengecekan adress space - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. - + Ignore global monitor Abaikan monitor global - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. Opsi ini meningkatkan kecepatan dengan hanya mengandalkan semantik cmpxchg untuk memastikan keamanan instruksi akses eksklusif. Harap dicatat ini dapat menyebabkan deadlock dan kondisi perlombaan lainnya. - + API: API: - + Changes the output graphics API. Vulkan is recommended. - + Device: Perangkat: - + This setting selects the GPU to use (Vulkan only). - + Resolution: Resolusi: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. - + Window Adapting Filter: Filter Menyelaraskan dengan Layar: - + FSR Sharpness: Ketajaman FSR - - Determines how sharpened the image will look using FSR's dynamic contrast. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. - + Anti-Aliasing Method: Metode Anti-Aliasing: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Mode Layar Penuh: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. @@ -747,47 +607,47 @@ Borderless menawarkan kompatibilitas terbaik dengan keyboard di layar yang dimin Layar penuh eksklusif mungkin menawarkan performa yang lebih baik dan dukungan Freesync/Gsync yang lebih baik. - + Aspect Ratio: Rasio Aspek: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. - + Use persistent pipeline cache - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. Memungkinkan penyimpanan shader untuk mempercepat pengambilan pada saat game berikutnya dimulai. Menonaktifkannya hanya dimaksudkan untuk debugging. - + Use asynchronous GPU emulation - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + NVDEC emulation: Emulasi NVDEC: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. @@ -796,12 +656,12 @@ Ini dapat menggunakan CPU atau GPU untuk dekode, atau tidak melakukan dekode sam Dalam kebanyakan kasus, dekode GPU memberikan kinerja terbaik. - + ASTC Decoding Method: ASTC Metode Dekoding: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -810,55 +670,75 @@ stuttering but may present artifacts. - + ASTC Recompression Method: ASTC Metode Pemampatan Ulang: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: Mode Penggunaan VRAM: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. - + Skip CPU Inner Invalidation Abaikan Invalidasi Internal CPU - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: Mode Sinkronisasi Vertikal - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -866,1362 +746,1372 @@ Immediate (no synchronization) presents whatever is available and can exhibit te - + Sync Memory Operations Sinkronisasi Operasi Memori - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. - + Enable asynchronous presentation (Vulkan only) Aktifkan presentasi asinkron (hanya Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. Meningkatkan kinerja sedikit dengan memindahkan presentasi ke thread CPU terpisah. - + Force maximum clocks (Vulkan only) Paksa jam maximum (Vulkan only) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. Berjalan bekerja di latar belakang sambil menunggu perintah grafis untuk mencegah GPU agar tidak menurunkan kecepatan jamnya. - + Anisotropic Filtering: Anisotropic Filtering: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. - + GPU Mode: Mode GPU: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - + DMA Accuracy: Akurasi DMA - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. - + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation Gunakan kompilasi shader asinkron - + May reduce shader stutter. - - Fast GPU Time - Percepatan Waktu CPU - - - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. + + GPU Clocks - + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. + + + + GPU Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache Gunakan pipeline cache Vulkan - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. Memungkinkan cache pipeline spesifik vendor GPU. Opsi ini dapat meningkatkan waktu pemuatan shader secara signifikan dalam kasus di mana driver Vulkan tidak menyimpan file cache pipeline secara internal. - + Enable Compute Pipelines (Intel Vulkan Only) Aktifkan Pipa Komputasi (Hanya Intel Vulkan) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. - + Enable Reactive Flushing Aktifkan Reactive Flushing - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. Menggunakan pemadatan reaktif alih-alih pemadatan prediktif, memungkinkan sinkronisasi memori yang lebih akurat. - + Sync to framerate of video playback Sinkronkan dengan kecepatan pemutaran video - + Run the game at normal speed during video playback, even when the framerate is unlocked. Jalankan permainan dengan kecepatan normal selama pemutaran video, bahkan ketika framerate tidak terkunci. - + Barrier feedback loops Loop umpan balik penghalang - + Improves rendering of transparency effects in specific games. Meningkatkan rendering efek transparansi dalam game tertentu. - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects Perbaiki efek bloom - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. - + Vertex Input Dynamic State - + Enables vertex input dynamic state feature for better quality and performance. - + Sample Shading Pencahayaan Sampel - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. - + RNG Seed Benih RNG - + Controls the seed of the random number generator. Mainly used for speedrunning. - + Device Name Nama Perangkat - + The name of the console. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: Tanggal RTC Kustom: - + This option allows to change the clock of the console. Can be used to manipulate time in games. - + The number of seconds from the current unix time - + Language: Bahasa - + This option can be overridden when region setting is auto-select - + Region: Wilayah: - + The region of the console. - + Time Zone: Zona Waktu: - + The time zone of the console. - + Sound Output Mode: Mode keluaran suara. - + Console Mode: Mode Konsol - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. - + Prompt for user profile on boot - + Useful if multiple people use the same PC. - + Pause when not in focus - + Pauses emulation when focusing on other windows. - + Confirm before stopping emulation Konfirmasi sebelum menghentikan emulasi - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. - + Hide mouse on inactivity Sembunyikan mouse saat tidak aktif - + Hides the mouse after 2.5s of inactivity. - - Disable controller applet - Nonaktifkan aplikasi pengontrol - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - - - - + Check for updates Cek Pembaruan - + Whether or not to check for updates upon startup. - + Enable Gamemode Aktifkan Mode Permainan - + Force X11 as Graphics Backend - + Custom frontend Tampilan depan kustom - + Real applet Aplikasi nyata - + + + Disabled + + + + Never Tidak Pernah - + On Load - + Always Selalu - + CPU CPU - + GPU GPU - + CPU Asynchronous CPU sinkron - + Uncompressed (Best quality) Tidak terkompresi (Kualitas Terbaik) - + BC1 (Low quality) BC1 (Kualitas rendah) - + BC3 (Medium quality) BC3 (Kualitas sedang) - - + + Auto Otomatis - + 30 FPS - + 60 FPS - + 90 FPS - + 120 FPS - + Conservative Konservatif - + Aggressive Agresif - + Vulkan Vulkan - + OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) - + Null Null - + Fast Cepat - - Balanced - Seimbang - - - - + + + Accurate Akurat - - + + + Default Bawaan - + Unsafe (fast) - + Safe (stable) - + + Immediate + + + + + Balanced + Seimbang + + + Unsafe Berbahaya - + Paranoid (disables most optimizations) Paranoid (menonaktifkan sebagian besar optimasi) - + Debugging - + Dynarmic Dynarmic - + NCE NCE - + Borderless Windowed Layar Tanpa Batas - + Exclusive Fullscreen Layar Penuh Eksklusif - + No Video Output Tidak ada Keluaran Suara - + CPU Video Decoding Penguraian Video menggunakan CPU - + GPU Video Decoding (Default) Penguraian Video menggunakan GPU (Bawaan) - + 0.25X (180p/270p) [EXPERIMENTAL] - + 0.5X (360p/540p) [EXPERIMENTAL] 0.5X (360p/540p) [EKSPERIMENTAL] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [EKSPERIMENTAL] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [EKSPERIMENTAL] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor Nearest Neighbor - + Bilinear Biliner - + Bicubic Bikubik - + Gaussian Gaussian - + Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution - + Area - + MMPX - + Zero-Tangent - + B-Spline - + Mitchell - + Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None Tak ada - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Bawaan (16:9) - + Force 4:3 Paksa 4:3 - + Force 21:9 Paksa 21:9 - + Force 16:10 Paksa 16:10 - + Stretch to Window Regangkan ke Layar - + Automatic Otomatis - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - - - - - 64x - - - - + Japanese (日本語) Jepang (日本語) - + American English Bahasa Inggris Amerika - + French (français) Prancis (français) - + German (Deutsch) Jerman (Deutsch) - + Italian (italiano) Italia (italiano) - + Spanish (español) Spanyol (español) - + Chinese Cina - + Korean (한국어) Korea (한국어) - + Dutch (Nederlands) Belanda (Nederlands) - + Portuguese (português) Portugis (português) - + Russian (Русский) Rusia (Русский) - + Taiwanese Taiwan - + British English Inggris Britania - + Canadian French Prancis Kanada - + Latin American Spanish Spanyol Amerika Latin - + Simplified Chinese Cina Sederhana - + Traditional Chinese (正體中文) Cina Tradisional (正體中文) - + Brazilian Portuguese (português do Brasil) Portugis Brazil (português do Brasil) - + Polish (polska) - + Thai (แบบไทย) - - + + Japan Jepang - + USA USA - + Europe Eropa - + Australia Australia - + China Tiongkok - + Korea Korea - + Taiwan Taiwan - + Auto (%1) Auto select time zone Auto (%1) - + Default (%1) Default time zone Bawaan (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Kuba - + EET EET - + Egypt Mesir - + Eire Éire - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Éire - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hongkong - + HST HST - + Iceland Islandia - + Iran Iran - + Israel Israel - + Jamaica Jamaika - + Kwajalein Kwajalein - + Libya Libya - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navajo - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Polandia - + Portugal Portugal - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Singapura - + Turkey Turki - + UCT UCT - + Universal Universal - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulu - + Mono Mono - + Stereo Stereo - + Surround Surround - + 4GB DRAM (Default) 4GB DRAM (Bawaan) - + 6GB DRAM (Unsafe) 6GB DRAM (Tidak Aman) - + 8GB DRAM - + 10GB DRAM (Unsafe) 10GB DRAM (Tidak Aman) - + 12GB DRAM (Unsafe) 12GB DRAM (Tidak Aman) - + Docked Terpasang - + Handheld Jinjing - - - Off + + + Normal - - Boost (1700MHz) + + + Boost - - Fast (2000MHz) + + + Overclock - + Always ask (Default) Selalu tanyakan (Bawaan) - + Only if game specifies not to stop Hanya jika permainan menentukan untuk tidak berhenti - + Never ask Jangan pernah bertanya - - - Medium (256) - Sedang (256) - - - - - High (512) - Tinggi (512) - - - + Very Small (16 MB) Sangat Kecil (16 MB) - + Small (32 MB) Kecil (32 MB) - + Normal (128 MB) - + Large (256 MB) - + Very Large (512 MB) - + Very Low (4 MB) - + Low (8 MB) - + Normal (16 MB) - + Medium (32 MB) - + High (64 MB) - + Very Low (32) - + Low (64) - + Normal (128) - - Disabled - + + Medium (256) + Sedang (256) - + + High (512) + Tinggi (512) + + + ExtendedDynamicState 1 - + ExtendedDynamicState 2 - + ExtendedDynamicState 3 - + Tree View - + Grid View @@ -2580,252 +2470,297 @@ Memungkinkan berbagai macam optimasi IR. Catatan Penyaring Global - - When checked, the max size of the log increases from 100 MB to 1 GB - Saat dicentang, ukuran maksimum log meningkat dari 100 MB menjadi 1 GB + + GPU Logging/Level + - - Enable Extended Logging** - Aktifkan Pencatatan Yang Diperluas** + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console Tampilkan Catatan Di Konsol - + + When checked, the max size of the log increases from 100 MB to 1 GB + Saat dicentang, ukuran maksimum log meningkat dari 100 MB menjadi 1 GB + + + + Enable Extended Logging** + Aktifkan Pencatatan Yang Diperluas** + + + Open Log Location Buka Lokasi Catatan - + Homebrew Homebrew - + Arguments String String Argumen - + Graphics Grafis - + When checked, it executes shaders without loop logic changes Saat dinyalakan, akan menjalankan shader tanpa perubahan logika loop - + Disable Loop safety checks Matikan cek keamanan Loop - + When checked, it will dump all the macro programs of the GPU Ketika dicentang, itu akan membuang semua program makro dari GPU - + Dump Maxwell Macros Dump Maxwell Macros - + When checked, it enables Nsight Aftermath crash dumps Ketika dicentang, itu mengaktifkan dump kecelakaan Nsight Aftermath - + Enable Nsight Aftermath Aktifkan Nsight Aftermath - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Ketika dicentang, itu akan membuang semua shader assembler asli dari cache shader disk atau game yang ditemukan. + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + - - Dump Game Shaders - Dump Shaders Permainan + + Dump Guest (Maxwell) Shaders + - + Enable Renderdoc Hotkey Aktifkan Renderdoc Hotkey - + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower Saat dicentang, ini menonaktifkan kompiler makro Just In Time. Mengaktifkan ini membuat game berjalan lebih lambat - + Disable Macro JIT Matikan Macro JIT - + When checked, it disables the macro HLE functions. Enabling this makes games run slower Saat dicentang, ini menonaktifkan fungsi HLE makro. Mengaktifkan ini membuat game berjalan lebih lambat. - + Disable Macro HLE Matikan Macro HLE - + When checked, the graphics API enters a slower debugging mode Ketika dicentang, API grafis memasuki mode debugging yang lebih lambat - + Enable Graphics Debugging Nyalakan Pengawakutuan Grafis - + When checked, yuzu will log statistics about the compiled pipeline cache Saat dinyalakan, yuzu akan mencatat statstik tentang pipeline cache yang disusun - + Enable Shader Feedback Nyalakan Umpan Balik Shader - + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> <html><head/><body><p>Ketika dicentang, menonaktifkan pengurutan ulang unggahan memori yang dipetakan yang memungkinkan mengaitkan unggahan dengan gambaran tertentu. Dapat mengurangi kinerja dalam beberapa kasus.</p></body></html> - + Disable Buffer Reorder Nonaktifkan Buffer Reorder - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced Lanjutan - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Memungkinkan yuzu untuk memeriksa lingkungan Vulkan yang berfungsi saat program dijalankan. Nonaktifkan ini jika ini menyebabkan masalah dengan program eksternal yang melihat yuzu. - + Perform Startup Vulkan Check Lakukan Pemeriksaan Startup Vulkan - + Disable Web Applet Matikan Applet Web - + Enable All Controller Types Aktifkan Semua Jenis Controller - + Enable Auto-Stub - + Kiosk (Quest) Mode Mode Kiosk (Pencarian) - + Use dev.keys - + Enable Debug Asserts Nyalakan Awakutu Assert - + Debugging Pengawakutuan - + Battery Serial: - + + Debug Knobs: + + + + Bitmask for quick development toggles - + Set debug knobs (bitmask) - + 16-bit debug knob set for quick development toggles - + (bitmask) - - Debug Knobs: - - - - + Unit Serial: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. Aktifkan ini untuk menghasilkan daftar perintah audio terbaru ke konsol. Hanya mempengaruhi permainan yang menggunakan renderer audio. - + Dump Audio Commands To Console** Dump Perintah Audio Ke Console** - + Flush log output on each line - + Enable FS Access Log Nyalakan Log Akses FS - + Enable Verbose Reporting Services** Nyalakan Layanan Laporan Bertele-tele** - + Censor username in logs - + **This will be reset automatically when Eden closes. @@ -3275,33 +3210,38 @@ Would you like to delete the old save data? Warna Latar: - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off Mati - + VSync Off VSync Mati - + Recommended Direkomendasikan - + On Nyala - + VSync On VSync Aktif @@ -3822,6 +3762,16 @@ Would you like to delete the old save data? Motion / Touch Gerakan / Sentuhan + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4331,58 +4281,58 @@ Untuk membalikkan sumbu, pertama gerakkan joystik secara tegak lurus, lalu menda [menunggu] - + New Profile Profil Baru - + Enter a profile name: Masukkan nama profil: - - + + Create Input Profile Ciptakan Profil Masukan - + The given profile name is not valid! Nama profil yang diberi tidak sah! - + Failed to create the input profile "%1" Gagal membuat profil masukan "%1" - + Delete Input Profile Hapus Profil Masukan - + Failed to delete the input profile "%1" Gagal menghapus profil masukan "%1" - + Load Input Profile Muat Profil Masukan - + Failed to load the input profile "%1" Gagal memuat profil masukan "%1" - + Save Input Profile Simpat Profil Masukan - + Failed to save the input profile "%1" Gagal menyimpan profil masukan "%1" @@ -4430,7 +4380,7 @@ Untuk membalikkan sumbu, pertama gerakkan joystik secara tegak lurus, lalu menda - + Configure Konfigurasi @@ -4461,7 +4411,7 @@ Untuk membalikkan sumbu, pertama gerakkan joystik secara tegak lurus, lalu menda - + Test Uji coba @@ -4476,77 +4426,77 @@ Untuk membalikkan sumbu, pertama gerakkan joystik secara tegak lurus, lalu menda Hapus Server - + %1:%2 %1:%2 - - - - - - + + + + + + Eden - + Port number has invalid characters Terdapat karakter tidak sah di angka port - + Port has to be in range 0 and 65353 Port harus berada dalam jangkauan 0 dan 65353 - + IP address is not valid Alamat IP tidak sah - + This UDP server already exists Server UDP ini sudah ada - + Unable to add more than 8 servers Tidak dapat menambah lebih dari 8 server - + Testing Menguji - + Configuring Mengkonfigur - + Test Successful Tes Berhasil - + Successfully received data from the server. Berhasil menerima data dari server. - + Test Failed Uji coba Gagal - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Tidak dapat menerima data yang sah dari server.<br>Mohon periksa bahwa server telah diatur dengan benar dan alamat dan port sudah sesuai. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. Uji coba UDP atau kalibrasi konfigurasi sedang berjalan.<br>Mohon tunggu hingga selesai. @@ -4730,57 +4680,57 @@ Current values are %1% and %2% respectively. Beberapa pengaturan hanya tersedia ketika permainan tidak sedang berjalan. - + Add-Ons Pengaya (Add-On) - + System Sistem - + CPU CPU - + Graphics Grafis - + Adv. Graphics Ljtan. Grafik - + Ext. Graphics - + Audio Audio - + Input Profiles Profil Masukan - + Network - + Applets - + Properties Properti @@ -4903,6 +4853,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5468,91 +5542,86 @@ Drag points to change position, or double-click table cells to edit values. - Show Compatibility List - - - - Show Add-Ons Column Tampilkan Kolom Pengaya (Add-On) - + Show Size Column - + Show File Types Column - + Show Play Time Column - + Folder Icon Size: Ukuran Ikon Folder: - + Row 1 Text: Teks Baris 1: - + Row 2 Text: Teks Baris 2: - + Screenshots Screenshot - + Ask Where To Save Screenshots (Windows Only) Menanya Dimana Untuk Menyimpan Screenshot (Hanya untuk Windows) - + Screenshots Path: Jalur Screenshot: - + ... ... - + TextLabel - + Resolution: Resolusi: - + Select Screenshots Path... Pilih Jalur Screenshot... - + <System> <System> - + English Bahasa Inggris - + Auto (%1 x %2, %3 x %4) Screenshot width value @@ -5789,7 +5858,7 @@ Drag points to change position, or double-click table cells to edit values. - + Calculating... @@ -5991,50 +6060,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL tidak tersedia! - + OpenGL shared contexts are not supported. - + Eden has not been compiled with OpenGL support. - - - + + + Error while initializing OpenGL! Terjadi kesalahan menginisialisasi OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. VGA anda mungkin tidak mendukung OpenGL, atau anda tidak memiliki pemacu piranti (driver) grafis terbaharu. - + Error while initializing OpenGL 4.6! Terjadi kesalahan menginisialisasi OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 VGA anda mungkin tidak mendukung OpenGL 4.6, atau anda tidak memiliki pemacu piranti (driver) grafis terbaharu.<br><br>Pemuat GL:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 VGA anda mungkin tidak mendukung satu atau lebih ekstensi OpenGL. Mohon pastikan bahwa anda memiliki pemacu piranti (driver) grafis terbaharu.<br><br>Pemuat GL:<br>%1<br><br>Ekstensi yang tidak didukung:<br>%2 - + This build doesn't have OpenGL support. @@ -6042,279 +6111,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory - + Favorite Favorit - + Start Game Mulai permainan - + Start Game without Custom Configuration - + Open Save Data Location Buka Lokasi Data Penyimpanan - + Open Mod Data Location Buka Lokasi Data Mod - + Open Transferable Pipeline Cache - + Link to Ryujinx - + Remove Singkirkan - + Remove Installed Update - + Remove All Installed DLC - + Remove Custom Configuration - + Remove Cache Storage - + Remove OpenGL Pipeline Cache - + Remove Vulkan Pipeline Cache - + Remove All Pipeline Caches - + Remove All Installed Contents Hapus semua konten terinstall. - + Manage Play Time Kelola waktu bermain - + Edit Play Time Data Edit data waktu bermain - + Remove Play Time Data Hapus data waktu bermain - - + + Dump RomFS Dump RomFS - + Dump RomFS to SDMC - + Verify Integrity Verifikasi Integritas - + Copy Title ID to Clipboard Salin Judul ID ke Clipboard. - - Navigate to GameDB entry - Pindah ke tampilan GameDB - - - + Create Shortcut Buat pintasan - + Add to Desktop Menambahkan ke Desktop - + Add to Applications Menu - + Configure Game - + Scan Subfolders Memindai subfolder - + Remove Game Directory - + ▲ Move Up - + ▼ Move Down - + Open Directory Location Buka Lokasi Direktori - + Clear Bersihkan - - - Name - Nama - - - - Compatibility - Kompatibilitas - - - - Add-ons - Pengaya (Add-On) - - - - File type - Tipe berkas - - - - Size - Ukuran - - - - Play time - Waktu bermain - GameListItemCompat - + Ingame - + Game starts, but crashes or major glitches prevent it from being completed. - + Perfect Sempurna - + Game can be played without issues. Permainan dapat dimainkan tanpa kendala. - + Playable - + Game functions with minor graphical or audio glitches and is playable from start to finish. - + Intro/Menu Awal/Menu - + Game loads, but is unable to progress past the Start Screen. - + Won't Boot Tidak Akan Berjalan - + The game crashes when attempting to startup. Gim rusak saat mencoba untuk memulai. - + Not Tested Belum dites - + The game has not yet been tested. Gim belum pernah dites. + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list Klik dua kali untuk menambahkan folder sebagai daftar permainan. @@ -6322,17 +6384,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) - + Filter: - + Enter pattern to filter Masukkan pola untuk memfilter @@ -6826,1139 +6888,1112 @@ Debug Message: - + Game &Icon Size - + Reset Window Size to &720p Atur ulang ukuran bingkai ke &720p - + Reset Window Size to 720p Atur ulang ukuran bingkai ke 720p - + Reset Window Size to &900p Atur ulang ukuran bingkai ke &900p - + Reset Window Size to 900p Atur ulang ukuran bingkai ke 900p - + Reset Window Size to &1080p - + Reset Window Size to 1080p - + &Multiplayer - + &Tools - + Am&iibo - + Launch &Applet - + &TAS - + &Create Home Menu Shortcut - + Install &Firmware - + &Help - + &Install Files to NAND... - + L&oad File... - + Load &Folder... - + E&xit - - + + &Pause &Jeda - + &Stop - + &Verify Installed Contents - + &About Eden - + Single &Window Mode - + Con&figure... - + Ctrl+, - + Enable Overlay Display Applet - + Show &Filter Bar - + Show &Status Bar - + Show Status Bar Munculkan Status Bar - + &Browse Public Game Lobby - + &Create Room - + &Leave Room - + &Direct Connect to Room - + &Show Current Room - + F&ullscreen - + &Restart - + Load/Remove &Amiibo... - - &Report Compatibility - - - - + Open &Mods Page - - Open &Quickstart Guide - Buka %Panduan cepat - - - - &FAQ + + &User Handbook - + &Capture Screenshot - + &Album - + &Set Nickname and Owner - + &Delete Game Data - + &Restore Amiibo - + &Format Amiibo - + &Mii Editor - + &Configure TAS... - + Configure C&urrent Game... - - + + &Start &Mulai - + &Reset - - + + R&ecord R&ekam - + Open &Controller Menu - + Install Decryption &Keys - + &Home Menu - + &Desktop - + &Application Menu - + &Root Data Folder - + &NAND Folder - + &SDMC Folder - + &Mod Folder - + &Log Folder - + From Folder - + From ZIP - + &Eden Dependencies - + &Data Manager - + &Tree View - + &Grid View - + Game Icon Size - - + + None - + Show Game &Name - + Show &Performance Overlay - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) - + Standard (64x64) - + Large (128x128) - + Full Size (256x256) - + Broken Vulkan Installation Detected - + Vulkan initialization failed during boot. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping - + Loading Web Applet... - - + + Disable Web Applet - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + The amount of shaders currently being built - + The current selected resolution scaling multiplier. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Unmute - + Mute - + Reset Volume - + + Configure Effects... + + + + &Clear Recent Files - + &Continue - + Warning: Outdated Game Format - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - - + + Error while loading ROM! - + The ROM format is not supported. - + An error occurred initializing the video core. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + An unknown error occurred. Please see the log for more details. - + + (64-bit) - + + (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + Closing software... - + Save Data - + Mod Data - + Error Opening %1 Folder - - + + Folder does not exist! - + Remove Installed Game Contents? - + Remove Installed Game Update? - + Remove Installed Game DLC? - + Remove Entry - + Delete OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? - + Remove Custom Game Configuration? - + Remove Cache Storage? - + Remove File - + Remove Play Time Data - + Reset play time? - - + + RomFS Extraction Failed! - + There was an error copying the RomFS files or the user cancelled the operation. - + Full - + Skeleton - + Select RomFS Dump Mode - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Extracting RomFS... - - + + Cancel - + RomFS Extraction Succeeded! - + The operation completed successfully. - + Error Opening %1 - + Select Directory - + Properties - + The game properties could not be loaded. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Load File - + Open Extracted ROM Directory - + Invalid Directory Selected - + The directory you have selected does not contain a 'main' file. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files - + %n file(s) remaining - + Installing file "%1"... - - + + Install Results - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + %n file(s) were newly installed - + %n file(s) were overwritten - + %n file(s) failed to install - + System Application - + System Archive - + System Application Update - + Firmware Package (Type A) - + Firmware Package (Type B) - + Game - + Game Update - + Game DLC - + Delta Title - + Select NCA Install Type... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Failed to Install - + The title type you selected for the NCA is invalid. - + File not found - + File "%1" not found - + OK - + Function Disabled - + Compatibility list reporting is currently disabled. Check back later! - + Error opening URL - + Unable to open the URL "%1". - + TAS Recording - + Overwrite file of player 1? - + Invalid config detected - + Handheld controller can't be used on docked mode. Pro controller will be selected. - - + + Amiibo - - + + The current amiibo has been removed - + Error - - + + The current game is not looking for amiibos - + Amiibo File (%1);; All Files (*.*) - + Load Amiibo - + Error loading Amiibo data - + The selected file is not a valid amiibo - + The selected file is already on use - + An unknown error occurred - - - Keys not installed - - - - - - Install decryption keys and restart Eden before attempting to install firmware. - - - - - Select Dumped Firmware Source Location - - - - - Select Dumped Firmware ZIP - - - - - Zipped Archives (*.zip) - - - - - Firmware cleanup failed - - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available - + Firmware Corrupted - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot - + PNG Image (*.png) - + TAS state: Running %1/%2 - + TAS state: Recording %1 - + TAS state: Idle %1/%2 - + TAS State: Invalid - + &Stop Running - + Stop R&ecording - + Building: %n shader(s) - + Scale: %1x %1 is the resolution scaling factor - + Speed: %1% / %2% - + Speed: %1% - + Game: %1 FPS - + Frame: %1 ms - - - FSR - - - - + NO AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE - + VOLUME: %1% Volume percentage (e.g. 50%) - + Derivation Components Missing - + Decryption keys are missing. Install them now? - + Wayland Detected! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -7966,74 +8001,74 @@ Would you like to force it for future launches? - + Use X11 - + Continue with Wayland - + Don't show again - + Restart Required - + Restart Eden to apply the X11 backend. - + Slow - + Turbo - + Unlocked - + Select RomFS Dump Target - + Please select which RomFS you would like to dump. - + Are you sure you want to close Eden? - - - + + + Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8099,6 +8134,11 @@ Would you like to bypass this and exit anyway? ScaleForce + + + FSR + + Area @@ -8110,52 +8150,57 @@ Would you like to bypass this and exit anyway? - + + SGSR + + + + + SGSR EdgeDir + + + + Docked - + Handheld - + Fast - - Balanced - - - - + Accurate - + Vulkan - + OpenGL GLSL - + OpenGL SPIRV - + OpenGL GLASM - + Null @@ -8188,7 +8233,7 @@ If you wish to clean up the files which were left in the old data location, you - + Data was migrated successfully. @@ -8672,8 +8717,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8686,10 +8731,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8704,9 +8749,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8868,50 +8913,50 @@ p, li { white-space: pre-wrap; } - + %1%2 - + [invalid] - - + + %1%2Hat %3 - - - + + + %1%2Axis %3 - + %1%2Axis %3,%4,%5 - + %1%2Motion %3 %1%2Gerakan %3 - - + + %1%2Button %3 - + [unused] @@ -9037,47 +9082,47 @@ p, li { white-space: pre-wrap; } - + Play Time: %1 - + Never Played - + Version: %1 - + Version: 1.0.0 - + Installed SD Titles - + Installed NAND Titles - + System Titles - + Add New Game Directory Tambahkan direktori permainan - + Favorites @@ -9198,13 +9243,13 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. @@ -9214,10 +9259,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9238,213 +9283,246 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9688,72 +9766,72 @@ Would you like to manually select a portable folder to use? - + Create Shortcut - + Do you want to launch the game in fullscreen? - + Shortcut Created - + Successfully created a shortcut to %1 - + Shortcut may be Volatile! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? - + Failed to Create Shortcut - + Failed to create a shortcut to %1 - + Create Icon - + Cannot create icon file. Path "%1" does not exist and cannot be created. - + No firmware available - + Please install firmware to use the home menu. - + Home Menu Applet - + Home Menu is not available. Please reinstall firmware. @@ -9761,55 +9839,55 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name Nama Mod - + What should this mod be called? - + RomFS RomFS - + ExeFS/Patch - + Cheat Cheat - + Mod Type Tipe Mod - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty @@ -10493,65 +10571,65 @@ By selecting "From Eden", previous save data stored in Ryujinx will be - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/it.ts b/dist/languages/it.ts index d85159bdc2..2cf9191129 100644 --- a/dist/languages/it.ts +++ b/dist/languages/it.ts @@ -218,155 +218,6 @@ Questo bannerà sia il suo nome utente del forum che il suo indirizzo IP.%1 - %2 (%3/%4 membri) - connesso - - CompatDB - - - Report Compatibility - Segnala compatibilità - - - - - - - - - - Report Game Compatibility - Segnala la compatibilità del gioco - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - <html><head/><body><p><span style=" font-size:10pt;">Se dovessi scegliere di inviare una segnalazione alla </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">lista di compatibilità di Eden</span></a><span style=" font-size:10pt;">, le seguenti informazioni saranno raccolte e visualizzate sul sito:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Informazioni sull'hardware (CPU / GPU / sistema operativo)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">La versione di Eden in uso</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">L'account Eden connesso</li></ul></body></html> - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>Il gioco parte?</p></body></html> - - - - Yes The game starts to output video or audio - Sì Il gioco inizia a riprodurre audio o video - - - - No The game doesn't get past the "Launching..." screen - No Il gioco non supera la schermata d'avvio - - - - Yes The game gets past the intro/menu and into gameplay - Sì Il gioco supera l'introduzione/il menù e raggiunge il gameplay - - - - No The game crashes or freezes while loading or using the menu - No Il gioco va in crash o si blocca durante il caricamento o usando il menù - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>Il gioco raggiunge il gameplay?</p></body></html> - - - - Yes The game works without crashes - Sì Il gioco funziona senza andare in crash - - - - No The game crashes or freezes during gameplay - No Il gioco va in crash o si blocca durante il gameplay - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>Il gioco funziona senza bloccarsi o andare in crash durante il gameplay?</p></body></html> - - - - Yes The game can be finished without any workarounds - Sì Il gioco può essere completato senza alcun espediente - - - - No The game can't progress past a certain area - No Non è possibile progredire oltre una certa area - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>Il gioco è completamente giocabile dall'inizio alla fine?</p></body></html> - - - - Major The game has major graphical errors - Gravi Il gioco presenta problemi grafici gravi - - - - Minor The game has minor graphical errors - Lievi Il gioco presenta problemi grafici lievi - - - - None Everything is rendered as it looks on the Nintendo Switch - Nessuno Il gioco si presenta esattamente come su Nintendo Switch - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p> Il gioco presenta dei problemi grafici? </p></body></html> - - - - Major The game has major audio errors - Gravi Il gioco presenta gravi problemi sonori - - - - Minor The game has minor audio errors - Lievi Il gioco presenta lievi problemi sonori - - - - None Audio is played perfectly - Nessuno Il gioco non presenta alcun problema nel comparto sonoro - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>Il gioco presenta effetti sonori mancanti o difetti audio?</p></body></html> - - - - Thank you for your submission! - Grazie per la tua segnalazione! - - - - Submitting - Invio in corso - - - - Communication error - Errore di comunicazione - - - - An error occurred while sending the Testcase - Si è verificato un errore durante l'invio della segnalazione - - - - Next - Successivo - - ConfigurationShared @@ -570,39 +421,38 @@ Può ridurre lo stuttering quando il framerate è basso. - + Backend: Back-end: - CPU Overclock - Overclock della CPU + CPU Clocks + - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. - Aumenta il clock della CPU emulata per rimuovere alcuni limitatori di FPS. Le CPU meno potenti potrebbero avere prestazioni ridotte, e alcuni giochi potrebbero comportarsi in maniera errata. -Usa Boost (1700MHz) per girare al massimo clock di Switch, oppure Veloce (2000MHz) per un clock raddoppiato. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + - + Custom CPU Ticks Tick CPU personalizzati - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. Imposta un valore personalizzato per i tick della CPU. Valori più alti possono aumentare le prestazioni, ma possono anche causare stalli nei giochi. Si consiglia un valore compreso tra 77 e 21000. - + Enable Host MMU Emulation (fastmem) Abilita l'emulazione della MMU nell'host (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. @@ -611,100 +461,100 @@ Abilitandola, le letture/scritture nella memoria del guest vengono eseguite dire Disabilitandola, tutti gli accessi alla memoria vengono costretti a utilizzare l'emulazione software della MMU. - + Unfuse FMA (improve performance on CPUs without FMA) Non fondere FMA (migliora le prestazioni della CPU senza FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. Quest'opzione migliora la velocità riducendo la precisione delle istruzioni fused-multiply-add (FMA) sulle CPU senza il supporto nativo a FMA. - + Faster FRSQRTE and FRECPE FRSQRTE e FRECPE più veloci - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. Quest'opzione migliora la velocità di alcune funzioni in virgola mobile approssimate utilizzando delle approssimazioni native meno accurate. - + Faster ASIMD instructions (32 bits only) Istruzioni ASIMD più veloci (solo 32 bit) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. Quest'opzione migliora la velocità delle funzioni in virgola mobile ASIMD a 32 bit eseguendole con modalità di arrotondamento non corrette. - + Inaccurate NaN handling Gestione inaccurata NaN - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. Quest'opzione migliora la velocità rimuovendo il controllo dei valori NaN. Tieni presente che ciò riduce la precisione di alcune istruzioni in virgola mobile. - + Disable address space checks Disattiva i controlli dello spazio degli indirizzi - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. Quest'opzione migliora la velocità eliminando un controllo di sicurezza prima di ogni operazione di memoria. Disabilitarla potrebbe permettere l'esecuzione di codice malevolo. - + Ignore global monitor Ignora il monitor globale - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. Quest'opzione migliora la velocità affidandosi alla semantica di cmpxchg per garantire sicurezza nelle istruzioni con accesso esclusivo. Nota che questo può causare stalli e altre race condition. - + API: API: - + Changes the output graphics API. Vulkan is recommended. Cambia l'API dell'output grafico. Vulkan è consigliato. - + Device: Dispositivo: - + This setting selects the GPU to use (Vulkan only). Quest'opzione seleziona la GPU da usare (solo con Vulkan). - + Resolution: Risoluzione: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. @@ -713,27 +563,27 @@ Alte risoluzioni hanno bisogno di piu VRAM e banda. Opzioni inferiori a 1X possono causare artefatti. - + Window Adapting Filter: Filtro di adattamento alla finestra: - + FSR Sharpness: Nitidezza FSR: - - Determines how sharpened the image will look using FSR's dynamic contrast. - Determina quanto sarà nitida l'immagine utilizzando il contrasto dinamico di FSR. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. + - + Anti-Aliasing Method: Metodo di anti-aliasing: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. @@ -741,13 +591,23 @@ FXAA can produce a more stable picture in lower resolutions. SMAA offre la migliore qualità. FXAA può produrre un'immagine più stabile nelle risoluzioni più basse. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Modalità schermo intero: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. @@ -756,12 +616,12 @@ Exclusive fullscreen may offer better performance and better Freesync/Gsync supp "Schermo intero esclusivo" può offrire prestazioni maggiori e un supporto migliore al Freesync/Gsync. - + Aspect Ratio: Rapporto d'aspetto: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. @@ -770,36 +630,36 @@ La maggior parte dei giochi supporta solo 16:9, quindi le mod sono necessarie pe Controlla anche il rapporto d'aspetto degli screenshot. - + Use persistent pipeline cache Usa la cache persistente delle pipeline - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. Permette di salvare gli shader su disco per velocizzarne il caricamento negli avvii successivi del gioco. Disabilitarla ha senso solo quando si effettua il debug. - + Use asynchronous GPU emulation Abilita l'emulazione asincrona della GPU - + Uses an extra CPU thread for rendering. This option should always remain enabled. Utilizza un thread aggiuntivo della CPU per il rendering. Quest'opzione dovrebbe sempre rimanere abilitata. - + NVDEC emulation: Emulazione NVDEC: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. @@ -808,12 +668,12 @@ In most cases, GPU decoding provides the best performance. Nella maggior parte dei casi, la decodifica tramite GPU fornisce prestazioni migliori. - + ASTC Decoding Method: Metodo di decodifica ASTC: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -825,12 +685,12 @@ GPU: Usa i compute shader della GPU per decodificare le texture ASTC (consigliat CPU (Asincrono): Usa la CPU per decodificare le texture ASTC se richiesto. Elimina lo stuttering causato dalla decodifica ma potrebbe generare artefatti visivi. - + ASTC Recompression Method: Metodo di ricompressione ASTC: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. @@ -838,44 +698,64 @@ BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, BC1/BC3: Il formato intermedio sarà ricompresso nel formato BC1 o BC3, risparmiando VRAM ma peggiorando la qualità visiva. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: Modalità di utilizzo della VRAM: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. Determina se l'emulatore dovrebbe risparmiare memoria o utilizzarne il più possibile per massimizzare le prestazioni. La modalità aggressiva potrebbe impattare sulle prestazioni di altre applicazioni, come i programmi di registrazione. - + Skip CPU Inner Invalidation Salta invalidamento interno CPU - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. Salta alcuni invalidamenti della cache durante gli aggiornamenti della memoria, riducendo l'uso della CPU e migliorando la latenza. Questo potrebbe causare soft-crash. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: Modalità VSync: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -886,12 +766,12 @@ Mailbox può avere una latenza minore di FIFO e non ha tearing ma potrebbe perde Immediate (no sincronizzazione) mostra tutto il disponibile, quindi anche tearing. - + Sync Memory Operations Sincronizza operazioni di memoria - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. @@ -900,147 +780,159 @@ Questa opzione dovrebbe risolvere problemi in alcuni giochi, ma potrebbe ridurre I giochi con Unreal Engine 4 sembrano essere i più colpiti. - + Enable asynchronous presentation (Vulkan only) Abilita la presentazione asincrona (solo Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. Migliora di poco le prestazioni spostando la presentazione su un thread della CPU separato. - + Force maximum clocks (Vulkan only) Forza clock massimi (solo Vulkan) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. Esegue del lavoro in background durante l'attesa dei comandi grafici per evitare che la GPU diminuisca la sua velocità di clock. - + Anisotropic Filtering: Filtro anisotropico: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. Controlla la qualità del rendering delle texture negli angoli obliqui. Si può utilizzare in modo sicuro al 16x su quasi tutte le GPU. - + GPU Mode: Modalità GPU: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - Controlla la modalità di emulazione della GPU. -La maggior parte dei giochi viene renderizzata senza problemi con le modalità "Veloce" o "Bilanciata", ma per alcuni titoli è necessaria la modalità "Accurata". -I particellari tendono a essere renderizzati correttamente solo in modalità "Accurata". - - - - DMA Accuracy: - Precisione DMA: - - - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. - Controlla la precisione del DMA. La precisione "Sicura" risolve dei problemi in alcuni giochi, ma può ridurre le prestazioni. - - - - Enable asynchronous shader compilation - Abilita la compilazione asincrona degli shader - - - - May reduce shader stutter. - Può ridurre i fenomeni di stuttering (scatti) causati dagli shader. - - - - Fast GPU Time - Tempo GPU veloce - - - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. - Aumenta il clock della GPU emulata per aumentare la risoluzione dinamica e la distanza di rendering. -Usa 256 per massimizzare le prestazioni e 512 per massimizzare la qualità visiva. - - - - GPU Unswizzle + DMA Accuracy: + Precisione DMA: + + + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. + + + + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + + Enable asynchronous shader compilation + Abilita la compilazione asincrona degli shader + + + + May reduce shader stutter. + Può ridurre i fenomeni di stuttering (scatti) causati dagli shader. + + + + GPU Clocks + + + + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. + + + + + GPU Unswizzle + + + + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache Usa la cache delle pipeline di Vulkan - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. Abilita la cache delle pipeline specifica del produttore della GPU. Quest'opzione può ridurre di molto i tempi di caricamento degli shader nei casi in cui il driver Vulkan non memorizza la cache delle pipeline internamente. - + Enable Compute Pipelines (Intel Vulkan Only) Abilita le compute pipeline (solo per Vulkan su Intel) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. @@ -1049,182 +941,181 @@ Quest'impostazione esiste solo per i driver proprietari Intel e potrebbe fa Le compute pipelines sono sempre attivate su tutti gli altri drivers. - + Enable Reactive Flushing Abilita il flushing reattivo - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. Utilizza il flushing reattivo invece di quello predittivo, al fine di ottenere una sincronizzazione della memoria più accurata. - + Sync to framerate of video playback Sincronizza il framerate a quello del video - + Run the game at normal speed during video playback, even when the framerate is unlocked. Esegue il gioco a velocità normale durante le cutscene, anche quando il framerate è sbloccato. - + Barrier feedback loops Barrier feedback loops - + Improves rendering of transparency effects in specific games. Migliora il rendering degli effetti di trasparenza in alcuni giochi. - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State Stato dinamico esteso - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. Controlla il numero di funzionalità che possono essere usate con lo stato dinamico esteso. Gli stati più alti consentono più funzionalità e possono migliorare le prestazioni, ma possono causare ulteriori problemi grafici. - + Vertex Input Dynamic State - + Enables vertex input dynamic state feature for better quality and performance. - + Sample Shading Sample shading - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. Permette al fragment shader di eseguire per campione in un frammento multi-campione invece che una volta per frammento. Migliora la qualità grafica a scapito delle prestazioni. Alti valori migliorano la qualità ma peggiorano le prestazioni. - + RNG Seed Seed RNG - + Controls the seed of the random number generator. Mainly used for speedrunning. Controlla il seme del generatore di numeri casuali. Principalmente utilizzato per le speedrun. - + Device Name Nome del dispositivo - + The name of the console. Il nome della console. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: Data RTC personalizzata: - + This option allows to change the clock of the console. Can be used to manipulate time in games. Quest'opzione permette di modificare l'orologio della console. Può essere usato per manipolare il tempo nei giochi. - + The number of seconds from the current unix time Il numero di secondi dal tempo Unix attuale - + Language: Lingua: - + This option can be overridden when region setting is auto-select Può essere rimpiazzato se il fuso orario della Regione è impostato su Auto - + Region: Regione: - + The region of the console. La regione della console. - + Time Zone: Fuso orario: - + The time zone of the console. Il fuso orario della console. - + Sound Output Mode: Modalità di output del suono: - + Console Mode: Modalità console: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. @@ -1233,1031 +1124,1026 @@ I giochi ne terranno conto e modificheranno la risoluzione, i dettagli e i contr Impostare l'opzione su "Portatile" può aiutare a migliorare le prestazioni sui sistemi meno potenti. - + Prompt for user profile on boot Scegli il profilo utente all'avvio - + Useful if multiple people use the same PC. Utile se più persone utilizzano lo stesso PC. - + Pause when not in focus Metti in pausa quando la finestra non è in primo piano - + Pauses emulation when focusing on other windows. Mette in pausa l'emulazione quando altre finestre sono in primo piano. - + Confirm before stopping emulation Chiedi conferma prima di arrestare l'emulazione - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. Sovrascrive le richieste di conferma per fermare l'emulazione. Abilitarla bypassa queste richieste e l'emulazione cesserà immediatamente. - + Hide mouse on inactivity Nascondi il puntatore del mouse se inattivo - + Hides the mouse after 2.5s of inactivity. Nasconde il mouse dopo 2,5 secondi di inattività. - - Disable controller applet - Disabilita l'applet controller - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - Disabilita forzatamente l'uso dell'applet del controller nei programmi emulati. -Quando un programma proverà ad aprire l'applet del controller, quest'ultimo verrà immediatamente chiuso. - - - + Check for updates Controlla la presenza di aggiornamenti - + Whether or not to check for updates upon startup. Determina se controllare o meno la presenza di aggiornamenti all'avvio. - + Enable Gamemode Abilita Gamemode - + Force X11 as Graphics Backend Forza l'uso del back-end grafico X11 - + Custom frontend Frontend personalizzato - + Real applet Applet reale - + + + Disabled + Disabilitato + + + Never Mai - + On Load Al caricamento - + Always Sempre - + CPU CPU - + GPU GPU - + CPU Asynchronous CPU (Asincrono) - + Uncompressed (Best quality) Nessuna compressione (qualità migliore) - + BC1 (Low quality) BC1 (qualità bassa) - + BC3 (Medium quality) BC3 (qualità media) - - + + Auto Automatico - + 30 FPS 30 FPS - + 60 FPS 60 FPS - + 90 FPS 90 FPS - + 120 FPS 120 FPS - + Conservative Conservativa - + Aggressive Aggressiva - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) OpenGL GLASM (shader assembly, solo NVIDIA) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) OpenGL SPIR-V (sperimentale, solo AMD/MESA) - + Null Nullo - + Fast Veloce - - Balanced - Bilanciata - - - - + + + Accurate Accurata - - + + + Default Predefinito - + Unsafe (fast) Non sicuro (veloce) - + Safe (stable) Sicuro (stabile) - + + Immediate + + + + + Balanced + Bilanciata + + + Unsafe Non sicura - + Paranoid (disables most optimizations) Paranoica (disabilita la maggior parte delle ottimizzazioni) - + Debugging Debug - + Dynarmic Dynarmic - + NCE NCE - + Borderless Windowed Finestra senza bordi - + Exclusive Fullscreen Schermo intero esclusivo - + No Video Output Nessun output video - + CPU Video Decoding Decodifica video CPU - + GPU Video Decoding (Default) Decodifica video GPU (predefinita) - + 0.25X (180p/270p) [EXPERIMENTAL] 0.25X (180p/270p) [SPERIMENTALE] - + 0.5X (360p/540p) [EXPERIMENTAL] 0.5X (360p/540p) [SPERIMENTALE] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [SPERIMENTALE] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] 1.25X (900p/1350p) [SPERIMENTALE] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [SPERIMENTALE] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor Nearest neighbor - + Bilinear Bilineare - + Bicubic Bicubico - + Gaussian Gaussiano - + Lanczos Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution AMD FidelityFX Super Resolution - + Area Area - + MMPX MMPX - + Zero-Tangent Zero-Tangent - + B-Spline B-Spline - + Mitchell Mitchell - + Spline-1 Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None Nessuna - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Predefinito (16:9) - + Force 4:3 Forza 4:3 - + Force 21:9 Forza 21:9 - + Force 16:10 Forza 16:10 - + Stretch to Window Allunga a finestra - + Automatic Automatico - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - 32x - - - - 64x - 64x - - - + Japanese (日本語) Giapponese (日本語) - + American English Inglese americano - + French (français) Francese (français) - + German (Deutsch) Tedesco (Deutsch) - + Italian (italiano) Italiano - + Spanish (español) Spagnolo (español) - + Chinese Cinese - + Korean (한국어) Coreano (한국어) - + Dutch (Nederlands) Olandese (Nederlands) - + Portuguese (português) Portoghese (português) - + Russian (Русский) Russo (Русский) - + Taiwanese Taiwanese - + British English Inglese britannico - + Canadian French Francese canadese - + Latin American Spanish Spagnolo latino-americano - + Simplified Chinese Cinese semplificato - + Traditional Chinese (正體中文) Cinese tradizionale (正體中文) - + Brazilian Portuguese (português do Brasil) Portoghese brasiliano (português do Brasil) - + Polish (polska) Polacco (polska) - + Thai (แบบไทย) Thailandese (แบบไทย) - - + + Japan Giappone - + USA USA - + Europe Europa - + Australia Australia - + China Cina - + Korea Corea - + Taiwan Taiwan - + Auto (%1) Auto select time zone Automatico (%1) - + Default (%1) Default time zone Predefinito (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Cuba - + EET EET - + Egypt Egitto - + Eire Eire - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Eire - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hongkong - + HST HST - + Iceland Islanda - + Iran Iran - + Israel Israele - + Jamaica Giamaica - + Kwajalein Kwajalein - + Libya Libia - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navajo - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Polonia - + Portugal Portogallo - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Singapore - + Turkey Turchia - + UCT UCT - + Universal Universale - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulu - + Mono Mono - + Stereo Stereo - + Surround Surround - + 4GB DRAM (Default) 4GB DRAM (Predefinito) - + 6GB DRAM (Unsafe) 6GB DRAM (Non sicuro) - + 8GB DRAM 8GB DRAM - + 10GB DRAM (Unsafe) 10GB DRAM (Non sicuro) - + 12GB DRAM (Unsafe) 12GB DRAM (Non sicuro) - + Docked Dock - + Handheld Portatile - - - Off - Disattivato + + + Normal + - - Boost (1700MHz) - Boost (1700MHz) + + + Boost + - - Fast (2000MHz) - Veloce (2000MHz) + + + Overclock + - + Always ask (Default) Chiedi sempre (Predefinito) - + Only if game specifies not to stop Solo se il gioco richiede di non essere arrestato - + Never ask Non chiedere mai - - - Medium (256) - Medio (256) - - - - - High (512) - Alto (512) - - - + Very Small (16 MB) Molto piccola (16 MB) - + Small (32 MB) Piccola (32 MB) - + Normal (128 MB) Normale (128 MB) - + Large (256 MB) Grande (256 MB) - + Very Large (512 MB) Molto grande (512 MB) - + Very Low (4 MB) Molto bassa (4 MB) - + Low (8 MB) Bassa (8 MB) - + Normal (16 MB) Normale (16 MB) - + Medium (32 MB) Media (32 MB) - + High (64 MB) Alta (64 MB) - + Very Low (32) Molto basso (32) - + Low (64) Basso (64) - + Normal (128) Normale (128) - - Disabled - Disabilitato + + Medium (256) + Medio (256) - + + High (512) + Alto (512) + + + ExtendedDynamicState 1 Stato dinamico esteso 1 - + ExtendedDynamicState 2 Stato dinamico esteso 2 - + ExtendedDynamicState 3 Stato dinamico esteso 3 - + Tree View Vista ad albero - + Grid View Vista a griglia @@ -2615,252 +2501,297 @@ Quando un programma proverà ad aprire l'applet del controller, quest' Filtro log globale - - When checked, the max size of the log increases from 100 MB to 1 GB - Quando l'opzione è selezionata, la dimensione massima del log aumenterà da 100 MB a 1 GB + + GPU Logging/Level + - - Enable Extended Logging** - Abilita il log esteso** + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console Mostra i log nella console - + + When checked, the max size of the log increases from 100 MB to 1 GB + Quando l'opzione è selezionata, la dimensione massima del log aumenterà da 100 MB a 1 GB + + + + Enable Extended Logging** + Abilita il log esteso** + + + Open Log Location Apri cartella dei log - + Homebrew Homebrew - + Arguments String Stringa degli argomenti - + Graphics Grafica - + When checked, it executes shaders without loop logic changes Quando l'opzione è selezionata, gli shader verranno eseguiti senza alcun cambiamento nella logica dei loop - + Disable Loop safety checks Disabilita i controlli di sicurezza dei loop - + When checked, it will dump all the macro programs of the GPU Quando l'opzione è selezionata, verranno estratti tutti i programmi macro della GPU - + Dump Maxwell Macros Estrai macro Maxwell - + When checked, it enables Nsight Aftermath crash dumps Quando l'opzione è selezionata, abilita i crash dump di Nsight Aftermath - + Enable Nsight Aftermath Abilita Nsight Aftermath - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Quando l'opzione è selezionata, verrà eseguito il dump degli shader originali in assembler dalla shader cache o dal gioco + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + - - Dump Game Shaders - Estrai shader del gioco + + Dump Guest (Maxwell) Shaders + - + Enable Renderdoc Hotkey Abilita scorciatoia Renderdoc - + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower Quando l'opzione è selezionata, disabilita il compilatore Just-In-Time delle macro. Abilitare questa opzione rende i giochi più lenti - + Disable Macro JIT Disabilita JIT macro - + When checked, it disables the macro HLE functions. Enabling this makes games run slower Quando l'opzione è selezionata, disabilita le funzioni HLE delle macro. Abilitare questa opzione rende i giochi più lenti - + Disable Macro HLE Disabilita HLE macro - + When checked, the graphics API enters a slower debugging mode Quando l'opzione è selezionata, l'API grafica entra in una modalità di debug più lenta - + Enable Graphics Debugging Abilita il debug della grafica - + When checked, yuzu will log statistics about the compiled pipeline cache Quando l'opzione è selezionata, yuzu scriverà nel log le statistiche riguardanti la cache delle pipeline compilate - + Enable Shader Feedback Abilita lo shader feedback - + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> <html><head/><body><p>Quando abilitato, il riordinamento della memoria già mappata viene disabilitato. In alcuni casi, può ridurre le performance.</p></body></html> - + Disable Buffer Reorder Disabilita il riordinamento dei buffer - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced Avanzate - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Se abilitato, yuzu verificherà se è presente un ambiente Vulkan funzionante quando il programma viene eseguito. Disabilita questa impostazione se hai problemi con altre applicazioni mentre usi yuzu. - + Perform Startup Vulkan Check Esegui controllo di Vulkan all'avvio - + Disable Web Applet Disabilita l'applet web - + Enable All Controller Types Abilita tutti i tipi di controller - + Enable Auto-Stub Abilita stub automatico - + Kiosk (Quest) Mode Modalità Kiosk (Quest) - + Use dev.keys Usa dev.keys - + Enable Debug Asserts Abilita le asserzioni di debug - + Debugging Debug - + Battery Serial: Codice seriale della batteria: - + + Debug Knobs: + + + + Bitmask for quick development toggles - + Set debug knobs (bitmask) - + 16-bit debug knob set for quick development toggles - + (bitmask) - - Debug Knobs: - - - - + Unit Serial: Codice seriale dell'unità: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. Abilita questa opzione per stampare l'ultima lista dei comandi audio nella console. Impatta solo i giochi che usano il renderer audio. - + Dump Audio Commands To Console** Stampa i comandi audio nella console** - + Flush log output on each line Svuota l'output del log su ogni riga - + Enable FS Access Log Abilita log di accesso al FS - + Enable Verbose Reporting Services** Abilita servizi di segnalazione dettagliata** - + Censor username in logs Censura il nome utente nei log - + **This will be reset automatically when Eden closes. **L'opzione verrà automaticamente ripristinata alla chiusura di Eden. @@ -3322,33 +3253,38 @@ Vuoi cancellare i dati precedenti? Colore dello sfondo: - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off Disattivato - + VSync Off VSync disattivato - + Recommended Consigliata - + On Attivato - + VSync On VSync attivato @@ -3869,6 +3805,16 @@ Vuoi cancellare i dati precedenti? Motion / Touch Movimento/tocco + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4378,58 +4324,58 @@ Per invertire gli assi, prima muovi la levetta verticalmente, e poi orizzontalme [in attesa] - + New Profile Nuovo profilo - + Enter a profile name: Inserisci un nome profilo: - - + + Create Input Profile Crea un profilo di input - + The given profile name is not valid! Il nome profilo inserito non è valido! - + Failed to create the input profile "%1" Impossibile creare il profilo di input "%1" - + Delete Input Profile Elimina un profilo di input - + Failed to delete the input profile "%1" Impossibile eliminare il profilo di input "%1" - + Load Input Profile Carica un profilo di input - + Failed to load the input profile "%1" Impossibile caricare il profilo di input "%1" - + Save Input Profile Salva un profilo di Input - + Failed to save the input profile "%1" Impossibile creare il profilo di input "%1" @@ -4477,7 +4423,7 @@ Per invertire gli assi, prima muovi la levetta verticalmente, e poi orizzontalme - + Configure Configura @@ -4508,7 +4454,7 @@ Per invertire gli assi, prima muovi la levetta verticalmente, e poi orizzontalme - + Test Test @@ -4523,77 +4469,77 @@ Per invertire gli assi, prima muovi la levetta verticalmente, e poi orizzontalme Rimuovi un server - + %1:%2 %1:%2 - - - - - - + + + + + + Eden Eden - + Port number has invalid characters Il numero di porta contiene caratteri non validi - + Port has to be in range 0 and 65353 La valore della porta deve essere compreso tra 0 e 65353 inclusi - + IP address is not valid Indirizzo IP non valido - + This UDP server already exists Questo server UDP esiste già - + Unable to add more than 8 servers Impossibile aggiungere più di 8 server - + Testing Testando - + Configuring Configurando - + Test Successful Test riuscito - + Successfully received data from the server. Ricevuti con successo dati dal server. - + Test Failed Test fallito - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Impossibile ricevere dati validi dal server.<br> Verificare che il server sia impostato correttamente e che indirizzo e porta siano corretti. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. È in corso il test UDP o la configurazione della calibrazione,<br> attendere che finiscano. @@ -4782,57 +4728,57 @@ Per attivarlo, disattiva il mouse emulato. Alcune impostazioni sono disponibili soltanto quando un gioco non è in esecuzione. - + Add-Ons Add-on - + System Sistema - + CPU CPU - + Graphics Grafica - + Adv. Graphics Grafica (Avanzate) - + Ext. Graphics Grafica (Estensioni) - + Audio Audio - + Input Profiles Profili di input - + Network Rete - + Applets Applet - + Properties Proprietà @@ -4960,6 +4906,130 @@ Una volta rimosse, non potranno più essere recuperate. Sei assolutamente sicuro &Apri nel gestore file + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5527,91 +5597,86 @@ Trascina i punti per cambiare posizione, oppure clicca due volte la cella in tab - Show Compatibility List - Mostra compatibilità - - - Show Add-Ons Column Mostra colonna Add-on - + Show Size Column Mostra colonna Dimensione - + Show File Types Column Mostra colonna Tipo di file - + Show Play Time Column Mostra colonna Tempo di gioco - + Folder Icon Size: Dimensione dell'icona delle cartelle: - + Row 1 Text: Testo riga 1: - + Row 2 Text: Testo riga 2: - + Screenshots Screenshot - + Ask Where To Save Screenshots (Windows Only) Chiedi dove salvare gli screenshot (solo Windows) - + Screenshots Path: Percorso degli screenshot: - + ... ... - + TextLabel Etichetta - + Resolution: Risoluzione: - + Select Screenshots Path... Seleziona il percorso degli screenshot... - + <System> <System> - + English Inglese - + Auto (%1 x %2, %3 x %4) Screenshot width value Automatica (%1 x %2, %3 x %4) @@ -5848,7 +5913,7 @@ Trascina i punti per cambiare posizione, oppure clicca due volte la cella in tab Importa dati in questa cartella. Potrebbe impiegare un po' di tempo, e TUTTI I DATI ESISTENTI verranno cancellati! - + Calculating... Calcolo in corso... @@ -6052,50 +6117,50 @@ Vai su Configura -> Sistema -> Rete e selezionane una. GRenderWindow - - + + OpenGL not available! OpenGL non disponibile! - + OpenGL shared contexts are not supported. Gli shared context di OpenGL non sono supportati. - + Eden has not been compiled with OpenGL support. Eden non è stato compilato con il supporto a OpenGL. - - - + + + Error while initializing OpenGL! Errore durante l'inizializzazione di OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. La tua GPU potrebbe non supportare OpenGL, o non hai installato l'ultima versione dei driver video. - + Error while initializing OpenGL 4.6! Errore durante l'inizializzazione di OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 La tua GPU potrebbe non supportare OpenGL 4.6, o non hai installato l'ultima versione dei driver video.<br><br>Renderer GL:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 La tua GPU potrebbe non supportare una o più estensioni OpenGL richieste. Assicurati di aver installato i driver video più recenti.<br><br>Renderer GL:<br>%1<br><br>Estensioni non supportate:<br>%2 - + This build doesn't have OpenGL support. @@ -6103,279 +6168,272 @@ Vai su Configura -> Sistema -> Rete e selezionane una. GameList - + &Add New Game Directory &Aggiungi nuova cartella dei giochi - + Favorite Preferito - + Start Game Avvia gioco - + Start Game without Custom Configuration Avvia gioco senza la configurazione personalizzata - + Open Save Data Location Apri la cartella dei dati di salvataggio - + Open Mod Data Location Apri la cartella delle mod - + Open Transferable Pipeline Cache Apri la cartella della cache trasferibile delle pipeline - + Link to Ryujinx Collega con Ryujinx - + Remove Rimuovi - + Remove Installed Update Rimuovi l'aggiornamento installato - + Remove All Installed DLC Rimuovi tutti i DLC installati - + Remove Custom Configuration Rimuovi la configurazione personalizzata - + Remove Cache Storage Rimuovi la cache del gioco - + Remove OpenGL Pipeline Cache Rimuovi la cache delle pipeline OpenGL - + Remove Vulkan Pipeline Cache Rimuovi la cache delle pipeline Vulkan - + Remove All Pipeline Caches Rimuovi tutte le cache delle pipeline - + Remove All Installed Contents Rimuovi tutti i contenuti installati - + Manage Play Time Gestisci il tempo di gioco - + Edit Play Time Data Modifica il tempo di gioco - + Remove Play Time Data Reimposta il tempo di gioco - - + + Dump RomFS Estrai RomFS - + Dump RomFS to SDMC Estrai RomFS su SDMC - + Verify Integrity Verifica integrità - + Copy Title ID to Clipboard Copia il Title ID negli appunti - - Navigate to GameDB entry - Vai alla pagina di GameDB - - - + Create Shortcut Crea scorciatoia - + Add to Desktop Aggiungi al desktop - + Add to Applications Menu Aggiungi al menù delle applicazioni - + Configure Game Configura gioco - + Scan Subfolders Scansiona le sottocartelle - + Remove Game Directory Rimuovi cartella dei giochi - + ▲ Move Up ▲ Sposta in alto - + ▼ Move Down ▼ Sposta in basso - + Open Directory Location Apri cartella - + Clear Cancella - - - Name - Nome - - - - Compatibility - Compatibilità - - - - Add-ons - Add-on - - - - File type - Tipo di file - - - - Size - Dimensione - - - - Play time - Tempo di gioco - GameListItemCompat - + Ingame In-game - + Game starts, but crashes or major glitches prevent it from being completed. Il gioco parte, ma non può essere completato a causa di arresti anomali o di glitch importanti. - + Perfect Perfetto - + Game can be played without issues. Il gioco funziona senza problemi. - + Playable Giocabile - + Game functions with minor graphical or audio glitches and is playable from start to finish. Il gioco presenta alcuni glitch audio o video minori ed è possibile giocare dall'inizio alla fine. - + Intro/Menu Intro/Menù - + Game loads, but is unable to progress past the Start Screen. Il gioco si avvia, ma è impossibile proseguire oltre la schermata iniziale. - + Won't Boot Non si avvia - + The game crashes when attempting to startup. Il gioco si blocca quando viene avviato. - + Not Tested Non testato - + The game has not yet been tested. Il gioco non è ancora stato testato. + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list Clicca due volte per aggiungere una nuova cartella alla lista dei giochi @@ -6383,17 +6441,17 @@ Vai su Configura -> Sistema -> Rete e selezionane una. GameListSearchField - + %1 of %n result(s) %1 di %n risultato%1 di %n di risultati%1 di %n risultati - + Filter: Filtro: - + Enter pattern to filter Inserisci pattern per filtrare @@ -6889,772 +6947,780 @@ Messaggio debug: &Modalità della lista dei giochi - + Game &Icon Size Dimensione delle &icone dei giochi - + Reset Window Size to &720p Ripristina le dimensioni della finestra a &720p - + Reset Window Size to 720p Ripristina le dimensioni della finestra a 720p - + Reset Window Size to &900p Ripristina le dimensioni della finestra a &900p - + Reset Window Size to 900p Ripristina le dimensioni della finestra a 900p - + Reset Window Size to &1080p Ripristina le dimensioni della finestra a &1080p - + Reset Window Size to 1080p Ripristina le dimensioni della finestra a 1080p - + &Multiplayer &Multigiocatore - + &Tools &Strumenti - + Am&iibo Am&iibo - + Launch &Applet Avvia &applet - + &TAS &TAS - + &Create Home Menu Shortcut &Crea scorciatoia per il menù Home - + Install &Firmware Installa &firmware - + &Help &Aiuto - + &Install Files to NAND... &Installa file su NAND... - + L&oad File... Carica &file... - + Load &Folder... Carica &cartella... - + E&xit &Esci - - + + &Pause &Pausa - + &Stop Arre&sta - + &Verify Installed Contents &Verifica i contenuti installati - + &About Eden &Informazioni su Eden - + Single &Window Mode &Modalità finestra singola - + Con&figure... Configura... - + Ctrl+, Ctrl+, - + Enable Overlay Display Applet - + Show &Filter Bar Mostra barra del &filtro - + Show &Status Bar Mostra barra di &stato - + Show Status Bar Mostra barra di stato - + &Browse Public Game Lobby &Sfoglia lobby di gioco pubblica - + &Create Room &Crea stanza - + &Leave Room &Esci dalla stanza - + &Direct Connect to Room Collegamento &diretto a una stanza - + &Show Current Room &Mostra stanza attuale - + F&ullscreen Schermo intero - + &Restart &Riavvia - + Load/Remove &Amiibo... Carica/Rimuovi &Amiibo... - - &Report Compatibility - &Segnala la compatibilità - - - + Open &Mods Page Apri la pagina delle &mod - - Open &Quickstart Guide - Apri la &guida introduttiva + + &User Handbook + - - &FAQ - &Domande frequenti - - - + &Capture Screenshot Cattura schermo - + &Album &Album - + &Set Nickname and Owner &Imposta nickname e proprietario - + &Delete Game Data &Rimuovi i dati di gioco - + &Restore Amiibo &Ripristina gli Amiibo - + &Format Amiibo &Formatta gli Amiibo - + &Mii Editor Editor &Mii - + &Configure TAS... &Configura TAS... - + Configure C&urrent Game... Configura il gioco in uso... - - + + &Start &Avvia - + &Reset &Reimposta - - + + R&ecord R&egistra - + Open &Controller Menu Apri il menù dei &controller - + Install Decryption &Keys Installa le &chiavi di crittografia - + &Home Menu Menù &Home - + &Desktop &Desktop - + &Application Menu &Menù delle applicazioni - + &Root Data Folder Cartella &principale dei dati - + &NAND Folder Cartella &NAND - + &SDMC Folder Cartella &SDMC - + &Mod Folder Cartella delle &mod - + &Log Folder Cartella dei &log - + From Folder Da una cartella - + From ZIP Da un file ZIP - + &Eden Dependencies &Dipendenze di Eden - + &Data Manager Gestione &dati - + &Tree View Vista ad &albero - + &Grid View Vista a &griglia - + Game Icon Size Dimensione delle icone dei giochi - - + + None Nessuno - + Show Game &Name Mostra i nomi dei giochi - + Show &Performance Overlay Mostra &prestazioni in sovrimpressione - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) Piccola (32x32) - + Standard (64x64) Normale (64x64) - + Large (128x128) Grande (128x128) - + Full Size (256x256) Intera (256x256) - + Broken Vulkan Installation Detected Rilevata installazione di Vulkan non funzionante - + Vulkan initialization failed during boot. L'inizializzazione di Vulkan è fallita durante l'avvio. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping Gioco in esecuzione - + Loading Web Applet... Caricamento dell'applet web... - - + + Disable Web Applet Disabilita l'applet web - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) Disabilitare l'applet web potrebbe causare dei comportamenti indesiderati e andrebbe fatto solo con Super Mario 3D All-Stars. Sei sicuro di voler procedere? (Puoi riabilitarlo quando vuoi nelle impostazioni di debug.) - + The amount of shaders currently being built Il numero di shader in fase di compilazione - + The current selected resolution scaling multiplier. Il moltiplicatore attuale della risoluzione. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. Velocità attuale dell'emulazione. Valori più alti o più bassi di 100% indicano che l'emulazione sta funzionando più velocemente o lentamente rispetto a una Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. Il numero di fotogrammi al secondo che il gioco sta attualmente renderizzando. Può variare in base al gioco e alla situazione. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. Tempo necessario per emulare un fotogramma della Switch, senza tenere conto del limite al framerate o del V-Sync. Per un'emulazione alla massima velocità, il valore non dovrebbe essere superiore a 16.67 ms. - + Unmute Riattiva - + Mute Silenzia - + Reset Volume Reimposta volume - + + Configure Effects... + + + + &Clear Recent Files &Cancella i file recenti - + &Continue &Continua - + Warning: Outdated Game Format Attenzione: Formato del gioco obsoleto - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. Stai usando una cartella contenente una ROM decostruita per avviare questo gioco, che è un formato obsoleto e sostituito da NCA, NAX, XCI o NSP. Le ROM decostruite non hanno icone né metadati e non supportano gli aggiornamenti.<br>Per una spiegazione sui vari formati della console Switch supportati da Eden, consulta il manuale utente. Non riceverai di nuovo questo avviso. - - + + Error while loading ROM! Errore nel caricamento della ROM! - + The ROM format is not supported. Il formato della ROM non è supportato. - + An error occurred initializing the video core. Errore durante l'inizializzazione del componente video di base - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. - Eden ha riscontrato un errore durante l'esecuzione del componente video di base. Di solito questo errore è causato da driver GPU obsoleti, compresi quelli integrati. Consulta il log per maggiori dettagli. Per informazioni su come accedere al log, visita <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>questa pagina</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. + - + Error while loading ROM! %1 %1 signifies a numeric error code. Errore nel caricamento della ROM! %1 - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. %1<br>Esegui un nuovo dump dei tuoi file o chiedi aiuto su Discord/Stoat. - + An unknown error occurred. Please see the log for more details. Si è verificato un errore sconosciuto. Consulta il log per maggiori dettagli. - + + (64-bit) (64 bit) - + + (32-bit) (32 bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit %1 %2 - + Closing software... Chiusura del software in corso... - + Save Data Dati di salvataggio - + Mod Data Dati delle mod - + Error Opening %1 Folder Impossibile aprire la cartella %1 - - + + Folder does not exist! La cartella non esiste! - + Remove Installed Game Contents? Rimuovere il contenuto del gioco installato? - + Remove Installed Game Update? Rimuovere l'aggiornamento installato? - + Remove Installed Game DLC? Rimuovere il DLC installato? - + Remove Entry Rimuovi voce - + Delete OpenGL Transferable Shader Cache? Vuoi rimuovere la cache trasferibile degli shader OpenGL? - + Delete Vulkan Transferable Shader Cache? Vuoi rimuovere la cache trasferibile degli shader Vulkan? - + Delete All Transferable Shader Caches? Vuoi rimuovere tutte le cache trasferibili degli shader? - + Remove Custom Game Configuration? Rimuovere la configurazione personalizzata del gioco? - + Remove Cache Storage? Rimuovere la cache del gioco? - + Remove File Rimuovi file - + Remove Play Time Data Reimposta il tempo di gioco - + Reset play time? Vuoi reimpostare il tempo di gioco? - - + + RomFS Extraction Failed! Estrazione RomFS fallita! - + There was an error copying the RomFS files or the user cancelled the operation. Si è verificato un errore durante la copia dei file del RomFS o l'operazione è stata annullata dall'utente. - + Full Completa - + Skeleton Cartelle - + Select RomFS Dump Mode Seleziona la modalità di estrazione del RomFS - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. Seleziona come vorresti estrarre il RomFS. <br>La modalità Completa copierà tutti i file in una nuova cartella mentre<br>la modalità Cartelle creerà solamente la struttura delle cartelle. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root Non c'è abbastanza spazio disponibile in %1 per estrarre il RomFS. Libera lo spazio o seleziona una cartella di estrazione diversa in Emulazione > Configura > Sistema > File system > Cartella di estrazione - + Extracting RomFS... Estrazione RomFS in corso... - - + + Cancel Annulla - + RomFS Extraction Succeeded! Estrazione RomFS riuscita! - + The operation completed successfully. L'operazione è stata completata con successo. - + Error Opening %1 Impossibile aprire %1 - + Select Directory Seleziona cartella - + Properties Proprietà - + The game properties could not be loaded. Non è stato possibile caricare le proprietà del gioco. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. Eseguibile Switch (%1);;Tutti i file (*.*) - + Load File Carica file - + Open Extracted ROM Directory Apri la cartella della ROM estratta - + Invalid Directory Selected Cartella selezionata non valida - + The directory you have selected does not contain a 'main' file. La cartella che hai selezionato non contiene un file "main". - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) File Switch installabili (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files Installa file - + %n file(s) remaining %n file rimanente%n di file rimanenti%n file rimanenti - + Installing file "%1"... Installazione del file "%1"... - - + + Install Results Risultati dell'installazione - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. Per evitare possibli conflitti, sconsigliamo di installare i giochi base su NAND. Usa questa funzione solo per installare aggiornamenti e DLC. - + %n file(s) were newly installed %n nuovo file è stato installato @@ -7663,7 +7729,7 @@ Usa questa funzione solo per installare aggiornamenti e DLC. - + %n file(s) were overwritten %n file è stato sovrascritto @@ -7672,7 +7738,7 @@ Usa questa funzione solo per installare aggiornamenti e DLC. - + %n file(s) failed to install %n file non è stato installato a causa di errori @@ -7681,359 +7747,324 @@ Usa questa funzione solo per installare aggiornamenti e DLC. - + System Application Applicazione di sistema - + System Archive Archivio di sistema - + System Application Update Aggiornamento di un'applicazione di sistema - + Firmware Package (Type A) Pacchetto firmware (tipo A) - + Firmware Package (Type B) Pacchetto firmware (tipo B) - + Game Gioco - + Game Update Aggiornamento di gioco - + Game DLC DLC - + Delta Title Titolo delta - + Select NCA Install Type... Seleziona il tipo di installazione NCA... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) Seleziona il tipo del file NCA da installare: (Nella maggior parte dei casi, il valore predefinito "Gioco" va bene) - + Failed to Install Installazione fallita - + The title type you selected for the NCA is invalid. Il tipo che hai selezionato per il file NCA non è valido. - + File not found File non trovato - + File "%1" not found File "%1" non trovato - + OK OK - + Function Disabled Funzionalità disabilitata - + Compatibility list reporting is currently disabled. Check back later! La segnalazione della compatibilità è al momento disabilitata. Torna a controllare più avanti! - + Error opening URL Impossibile aprire l'URL - + Unable to open the URL "%1". Non è stato possibile aprire l'URL "%1". - + TAS Recording Registrazione TAS - + Overwrite file of player 1? Vuoi sovrascrivere il file del giocatore 1? - + Invalid config detected Rilevata configurazione non valida - + Handheld controller can't be used on docked mode. Pro controller will be selected. Il controller portatile non può essere utilizzato in modalità dock. Verrà selezionato il controller Pro. - - + + Amiibo Amiibo - - + + The current amiibo has been removed L'Amiibo corrente è stato rimosso - + Error Errore - - + + The current game is not looking for amiibos Il gioco in uso non è alla ricerca di Amiibo - + Amiibo File (%1);; All Files (*.*) File Amiibo (%1);; Tutti i file (*.*) - + Load Amiibo Carica Amiibo - + Error loading Amiibo data Impossibile caricare i dati dell'Amiibo - + The selected file is not a valid amiibo Il file selezionato non è un Amiibo valido - + The selected file is already on use Il file selezionato è già in uso - + An unknown error occurred Si è verificato un errore sconosciuto - - - Keys not installed - Chiavi non installate - - - - - Install decryption keys and restart Eden before attempting to install firmware. - Installa le chiavi di crittografia e riavvia Eden prima di installare il firmware. - - - - Select Dumped Firmware Source Location - Seleziona il percorso del firmware estratto - - - - Select Dumped Firmware ZIP - Seleziona il file ZIP del firmware estratto - - - - Zipped Archives (*.zip) - Archivi compressi (*.zip) - - - - Firmware cleanup failed - Pulizia del firmware fallita - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available Nessun firmware disponibile - + Firmware Corrupted Firmware corrotto - + Unknown applet Applet sconosciuto - + Applet doesn't map to a known value. L'applet non è associato a un valore noto. - + Record not found Non trovato - + Applet not found. Please reinstall firmware. Applet non trovato. Reinstalla il firmware. - + Capture Screenshot Cattura screenshot - + PNG Image (*.png) Immagine PNG (*.png) - + TAS state: Running %1/%2 Stato TAS: In esecuzione (%1/%2) - + TAS state: Recording %1 Stato TAS: Registrazione in corso (%1) - + TAS state: Idle %1/%2 Stato TAS: In attesa (%1/%2) - + TAS State: Invalid Stato TAS: Non valido - + &Stop Running &Interrompi esecuzione - + Stop R&ecording Interrompi r&egistrazione - + Building: %n shader(s) Compilazione di %n shaderCompilazione di %n shaderCompilazione di %n shader - + Scale: %1x %1 is the resolution scaling factor Risoluzione: %1x - + Speed: %1% / %2% Velocità: %1% / %2% - + Speed: %1% Velocità: %1% - + Game: %1 FPS Gioco: %1 FPS - + Frame: %1 ms Frame: %1 ms - - - FSR - FSR - - - + NO AA NO AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE VOLUME: MUTO - + VOLUME: %1% Volume percentage (e.g. 50%) VOLUME: %1% - + Derivation Components Missing Componenti di derivazione mancanti - + Decryption keys are missing. Install them now? Chiavi di crittografia mancanti. Vuoi installarle ora? - + Wayland Detected! Wayland rilevato! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -8044,74 +8075,74 @@ Si consiglia invece di usare X11. Vuoi forzare l'uso di quest'ultimo per i prossimi avvii? - + Use X11 Usa X11 - + Continue with Wayland Continua con Wayland - + Don't show again Non mostrare di nuovo - + Restart Required Riavvio richiesto - + Restart Eden to apply the X11 backend. Riavvia Eden per usare il back-end X11. - + Slow Lento - + Turbo Turbo - + Unlocked Sbloccato - + Select RomFS Dump Target Seleziona RomFS da estrarre - + Please select which RomFS you would like to dump. Seleziona quale RomFS vorresti estrarre. - + Are you sure you want to close Eden? Sei sicuro di voler uscire da Eden? - - - + + + Eden Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. Sei sicuro di voler arrestare l'emulazione? Tutti i progressi non salvati verranno perduti. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8178,6 +8209,11 @@ Vuoi uscire comunque? ScaleForce ScaleForce + + + FSR + FSR + Area @@ -8189,52 +8225,57 @@ Vuoi uscire comunque? MMPX - + + SGSR + + + + + SGSR EdgeDir + + + + Docked Dock - + Handheld Portatile - + Fast Veloce - - Balanced - Bilanciata - - - + Accurate Accurata - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL SPIRV OpenGL SPIR-V - + OpenGL GLASM OpenGL GLASM - + Null Nullo @@ -8267,7 +8308,7 @@ If you wish to clean up the files which were left in the old data location, you - + Data was migrated successfully. I dati sono stati trasferiti con successo. @@ -8759,8 +8800,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8773,10 +8814,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8791,9 +8832,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8955,50 +8996,50 @@ p, li { white-space: pre-wrap; } [indefinito] - + %1%2 %1%2 - + [invalid] [non valido] - - + + %1%2Hat %3 %1%2Freccia %3 - - - + + + %1%2Axis %3 %1%2Asse %3 - + %1%2Axis %3,%4,%5 %1%2Asse %3,%4,%5 - + %1%2Motion %3 %1%2Movimento %3 - - + + %1%2Button %3 %1%2Pulsante %3 - + [unused] [inutilizzato] @@ -9124,47 +9165,47 @@ p, li { white-space: pre-wrap; } %1 sta giocando a %2 - + Play Time: %1 Tempo di gioco: %1 - + Never Played Mai giocato - + Version: %1 Versione: %1 - + Version: 1.0.0 Versione: 1.0.0 - + Installed SD Titles Titoli SD installati - + Installed NAND Titles Titoli NAND installati - + System Titles Titoli di sistema - + Add New Game Directory Aggiungi nuova cartella dei giochi - + Favorites Preferiti @@ -9285,14 +9326,14 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware Firmware richiesto - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. - Il gioco che stai cercando di avviare richiede il firmware per poter partire o per superare il menù iniziale. <a href='https://yuzu-mirror.github.io/help/quickstart'>Esegui il dump del firmware e installalo</a>, o premi "OK" per continuare lo stesso. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. + @@ -9301,10 +9342,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel Annulla @@ -9325,7 +9366,7 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 @@ -9334,207 +9375,240 @@ p, li { white-space: pre-wrap; } %1 - - + + Verifying integrity... Verifica dell'integrità in corso... - - + + Integrity verification succeeded! Verifica dell'integrità riuscita! - - + + The operation completed successfully. L'operazione è stata completata con successo. - - + + Integrity verification failed! Verifica dell'integrità fallita! - + File contents may be corrupt or missing. I contenuti dei file potrebbero essere corrotti o mancanti. - + Integrity verification couldn't be performed Impossibile effettuare la verifica dell'integrità - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. Installazione del firmware annullata, il firmware potrebbe essere corrotto o in cattivo stato. Non è stato possibile controllare la validità dei contenuti dei file. - + Select Dumped Keys Location Seleziona il percorso delle chiavi estratte - + Decryption Keys install succeeded Installazione delle chiavi di crittografia riuscita - + Decryption Keys install failed Installazione delle chiavi di crittografia fallita - + Orphaned Profiles Detected! Rilevati profili scollegati! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? Eliminare i dati? - + Important data may be lost! Potresti perdere dei dati importanti! - + Are you REALLY sure? Sei DAVVERO sicuro? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. Una volta eliminati, i tuoi dati NON potranno essere ripristinati! Procedi solo se sei assolutamente sicuro di voler eliminare questi dati. - + Clearing... Eliminazione in corso... - + Select Export Location Scegli dove esportare i dati - + %1.zip %1.zip - - + + + Zipped Archives (*.zip) Archivi compressi (*.zip) - + Exporting data. This may take a while... Esportazione dei dati in corso. Potrebbe richiedere un po' di tempo... - + Exporting Esportazione in corso - + Exported Successfully Esportazione completata - + Data was exported successfully. I dati sono stati esportati con successo. - + Export Cancelled Esportazione annullata - + Export was cancelled by the user. L'esportazione è stata annullata dall'utente. - + Export Failed Esportazione fallita - + Ensure you have write permissions on the targeted directory and try again. Assicurati di disporre dei permessi di scrittura nella cartella selezionata e poi riprova. - + Select Import Location Seleziona il file da importare - + Import Warning Attenzione - + All previous data in this directory will be deleted. Are you sure you wish to proceed? Tutti i dati già presenti in questa cartella verranno eliminati. Sei sicuro di voler procedere? - + Importing data. This may take a while... Importazione dei dati in corso. Potrebbe richiedere un po' di tempo... - + Importing Importazione in corso - + Imported Successfully Importazione completata - + Data was imported successfully. I dati sono stati importati con successo. - + Import Cancelled Importazione annullata - + Import was cancelled by the user. L'importazione è stata annullata dall'utente. - + Import Failed Importazione fallita - + Ensure you have read permissions on the targeted directory and try again. Assicurati di disporre dei permessi di lettura nella cartella selezionata e poi riprova. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9783,72 +9857,72 @@ Vuoi selezionare manualmente la cartella dell'installazione portatile da us Impossibile eliminare la cache dei metadati. Potrebbe essere in uso o inesistente. - + Create Shortcut Crea scorciatoia - + Do you want to launch the game in fullscreen? Vuoi avviare il gioco a schermo intero? - + Shortcut Created Scorciatoia creata - + Successfully created a shortcut to %1 Scorciatoia creata con successo per %1 - + Shortcut may be Volatile! Scorciatoia potenzialmente instabile! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? Verrà creata una scorciatoia all'AppImage attuale. Potrebbe non funzionare correttamente se effettui un aggiornamento. Vuoi continuare? - + Failed to Create Shortcut Impossibile creare la scorciatoia - + Failed to create a shortcut to %1 Si è verificato un errore nel creare la scorciatoia per %1 - + Create Icon Crea icona - + Cannot create icon file. Path "%1" does not exist and cannot be created. Impossibile creare il file dell'icona. Il percorso "%1" non esiste e non può essere creato. - + No firmware available Nessun firmware disponibile - + Please install firmware to use the home menu. Installa il firmware per usare il menù Home. - + Home Menu Applet Applet menù Home - + Home Menu is not available. Please reinstall firmware. Il menù Home non è disponibile. Reinstalla il firmware. @@ -9856,37 +9930,37 @@ Vuoi selezionare manualmente la cartella dell'installazione portatile da us QtCommon::Mod - + Mod Name Nome mod - + What should this mod be called? Qual è il nome di questa mod? - + RomFS RomFS - + ExeFS/Patch ExeFS/Patch - + Cheat Trucco - + Mod Type Tipologia mod - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. @@ -9895,18 +9969,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed Estrazione mod fallita - + Failed to create temporary directory %1 Impossibile creare la cartella temporanea %1 - + Zip file %1 is empty Il file ZIP %1 è vuoto @@ -10608,65 +10682,65 @@ Selezionando "Da Eden", i dati di salvataggio pre-esistenti in Ryujinx - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/ja_JP.ts b/dist/languages/ja_JP.ts index c1ab881dde..2341618395 100644 --- a/dist/languages/ja_JP.ts +++ b/dist/languages/ja_JP.ts @@ -211,155 +211,6 @@ This would ban both their forum username and their IP address. %1 - %2 (%3/%4 メンバー) - 接続済み - - CompatDB - - - Report Compatibility - 互換性の報告 - - - - - - - - - - Report Game Compatibility - ゲームの互換性を報告 - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>ゲームは起動しますか?</p></body></html> - - - - Yes The game starts to output video or audio - はい ゲームは映像または音声の出力を開始します - - - - No The game doesn't get past the "Launching..." screen - - - - - Yes The game gets past the intro/menu and into gameplay - - - - - No The game crashes or freezes while loading or using the menu - - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>ゲームはゲームプレイまで達しますか?</p></body></html> - - - - Yes The game works without crashes - はい ゲームはクラッシュせず動作します - - - - No The game crashes or freezes during gameplay - - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>ゲームプレイ中にクラッシュしたり、フリーズしたり、ロックしたりすることなく動作しますか?</p></body></html> - - - - Yes The game can be finished without any workarounds - はい 回避策なしでゲームを終えることができます - - - - No The game can't progress past a certain area - いいえ あるエリアを超えるとゲームが進行しない - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <head/><body><p><html>ゲームは最初から最後まで完全にプレーできますか?</p></body></html> - - - - Major The game has major graphical errors - メジャー ゲームは大きなグラフィックエラーがあります - - - - Minor The game has minor graphical errors - マイナー ゲームはマイナーなグラフィックエラーがあります - - - - None Everything is rendered as it looks on the Nintendo Switch - なし すべてはニンテンドースイッチと同様にレンダリングされます - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>ゲームにグラフィックの不具合はありますか?</p></body></html> - - - - Major The game has major audio errors - メジャー このゲームには大きなオーディオエラーがあります - - - - Minor The game has minor audio errors - マイナー このゲームにはマイナーなオーディオエラーがあります - - - - None Audio is played perfectly - なし オーディオは完璧に再生されます - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>ゲームにオーディオの不具合や欠落はありますか? - - - - Thank you for your submission! - ご協力ありがとうございます! - - - - Submitting - 送信中 - - - - Communication error - 通信エラー - - - - An error occurred while sending the Testcase - テストケースの送信中にエラーが発生しました - - - - Next - 次へ - - ConfigurationShared @@ -559,231 +410,241 @@ Can help reduce stuttering at lower framerates. - + Backend: バックエンド: - CPU Overclock + CPU Clocks - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. - + Custom CPU Ticks - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. - + Enable Host MMU Emulation (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. - + Unfuse FMA (improve performance on CPUs without FMA) FMAの融合を解除 (FMAに対応していないCPUのパフォーマンスを向上させる) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. このオプションは, ネイティブのFMAサポートがないCPU上で, 融合積和(fused-multiply-add)命令の精度を下げて高速化します. - + Faster FRSQRTE and FRECPE Faster FRSQRTE and FRECPE - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. このオプションは、より精度の低い近似値を使用することで、近似浮動小数点関数の速度を向上させます。 - + Faster ASIMD instructions (32 bits only) 高速なASIMD命令 (32bitのみ) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. このオプションは、不正確な丸めモードで実行することにより、32ビットASIMD浮動小数点関数の速度を向上させます。 - + Inaccurate NaN handling 不正確な非数値の取り扱い - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. - + Disable address space checks アドレス空間チェックの無効化 - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. - + Ignore global monitor - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. - + API: API: - + Changes the output graphics API. Vulkan is recommended. - + Device: 使用デバイス: - + This setting selects the GPU to use (Vulkan only). - + Resolution: 解像度: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. - + Window Adapting Filter: ウィンドウ適応フィルター: - + FSR Sharpness: FSR シャープネス: - - Determines how sharpened the image will look using FSR's dynamic contrast. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. - + Anti-Aliasing Method: アンチエイリアス方式: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: フルスクリーンモード: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. - + Aspect Ratio: アスペクト比: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. - + Use persistent pipeline cache - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. - + Use asynchronous GPU emulation - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + NVDEC emulation: NVDEC エミュレーション: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. - + ASTC Decoding Method: ASTC デコード方式: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -792,55 +653,75 @@ stuttering but may present artifacts. - + ASTC Recompression Method: ASTC 再圧縮方式: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. - + Skip CPU Inner Invalidation - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: 垂直同期: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -851,1361 +732,1371 @@ Mailbox は FIFO よりも遅延が小さくティアリングがありません Immediate (no synchronization) は表示可能なものをすべて表示し, ティアリング発生の可能性があります. - + Sync Memory Operations メモリ操作の同期 - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. - + Enable asynchronous presentation (Vulkan only) 非同期プレゼンテーション (Vulkan のみ) - + Slightly improves performance by moving presentation to a separate CPU thread. - + Force maximum clocks (Vulkan only) 最大クロック強制 (Vulkan のみ) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. GPUのクロックスピードを下げないように、グラフィックコマンドを待っている間、バックグラウンドで作業を実行させます。 - + Anisotropic Filtering: 異方性フィルタリング: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. - + GPU Mode: GPUモード: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - + DMA Accuracy: DMA精度: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. - + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation - + May reduce shader stutter. - - Fast GPU Time + + GPU Clocks - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. - + GPU Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache Vulkan パイプラインキャッシュを使用 - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. - + Enable Compute Pipelines (Intel Vulkan Only) コンピュート・パイプラインの有効化(インテル Vulkan のみ) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. - + Enable Reactive Flushing - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. - + Sync to framerate of video playback ビデオ再生のフレームレートに同期する - + Run the game at normal speed during video playback, even when the framerate is unlocked. - + Barrier feedback loops - + Improves rendering of transparency effects in specific games. 特定のゲームにおける透明エフェクトのレンダリングを改善します。 - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. - + Vertex Input Dynamic State - + Enables vertex input dynamic state feature for better quality and performance. - + Sample Shading - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. - + RNG Seed 乱数シード値の変更 - + Controls the seed of the random number generator. Mainly used for speedrunning. - + Device Name デバイス名 - + The name of the console. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: - + This option allows to change the clock of the console. Can be used to manipulate time in games. - + The number of seconds from the current unix time - + Language: 言語: - + This option can be overridden when region setting is auto-select - + Region: 地域: - + The region of the console. - + Time Zone: タイムゾーン: - + The time zone of the console. - + Sound Output Mode: 音声出力モード: - + Console Mode: コンソールモード: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. - + Prompt for user profile on boot - + Useful if multiple people use the same PC. - + Pause when not in focus - + Pauses emulation when focusing on other windows. - + Confirm before stopping emulation エミュレーションを停止する前に確認する - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. - + Hide mouse on inactivity 非アクティブ時にマウスカーソルを隠す - + Hides the mouse after 2.5s of inactivity. - - Disable controller applet - コントローラーアプレットの無効化 - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - - - - + Check for updates - + Whether or not to check for updates upon startup. - + Enable Gamemode - + Force X11 as Graphics Backend - + Custom frontend - + Real applet - + + + Disabled + 無効 + + + Never - + On Load - + Always - + CPU CPU - + GPU GPU - + CPU Asynchronous CPU 非同期 - + Uncompressed (Best quality) 圧縮しない (最高品質) - + BC1 (Low quality) BC1 (低品質) - + BC3 (Medium quality) BC3 (中品質) - - + + Auto 自動 - + 30 FPS - + 60 FPS - + 90 FPS - + 120 FPS - + Conservative - + Aggressive - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) OpenGL GLASM (アセンブリシェーダー、NVIDIA のみ) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) OpenGL SPIR-V(実験的、AMD/Mesaのみ) - + Null - + Fast - - Balanced - - - - - + + + Accurate 正確 - - + + + Default デフォルト - + Unsafe (fast) - + Safe (stable) - + + Immediate + + + + + Balanced + + + + Unsafe 不安定 - + Paranoid (disables most optimizations) パラノイド (ほとんどの最適化を無効化) - + Debugging - + Dynarmic Dynarmic - + NCE NCE - + Borderless Windowed ボーダーレスウィンドウ - + Exclusive Fullscreen 排他的フルスクリーン - + No Video Output ビデオ出力しない - + CPU Video Decoding ビデオをCPUでデコード - + GPU Video Decoding (Default) ビデオをGPUでデコード (デフォルト) - + 0.25X (180p/270p) [EXPERIMENTAL] 0.25X (180p/270p) [EXPERIMENTAL] - + 0.5X (360p/540p) [EXPERIMENTAL] 0.5X (360p/540p) [実験的] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [実験的] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] 1.25X (900p/1350p) [EXPERIMENTAL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [実験的] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor Nearest Neighbor - + Bilinear Bilinear - + Bicubic Bicubic - + Gaussian Gaussian - + Lanczos Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution AMD FidelityFX Super Resolution - + Area - + MMPX MMPX - + Zero-Tangent - + B-Spline - + Mitchell - + Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None なし - + FXAA FXAA - + SMAA SMAA - + Default (16:9) デフォルト (16:9) - + Force 4:3 強制 4:3 - + Force 21:9 強制 21:9 - + Force 16:10 強制 16:10 - + Stretch to Window ウィンドウに合わせる - + Automatic 自動 - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - 32x - - - - 64x - 64x - - - + Japanese (日本語) 日本語 - + American English アメリカ英語 - + French (français) フランス語 (français) - + German (Deutsch) ドイツ語 (Deutsch) - + Italian (italiano) イタリア語 (italiano) - + Spanish (español) スペイン語 (español) - + Chinese 中国語 - + Korean (한국어) 韓国語 (한국어) - + Dutch (Nederlands) オランダ語 (Nederlands) - + Portuguese (português) ポルトガル語 (português) - + Russian (Русский) ロシア語 (Русский) - + Taiwanese 台湾語 - + British English イギリス英語 - + Canadian French カナダフランス語 - + Latin American Spanish ラテンアメリカスペイン語 - + Simplified Chinese 簡体字中国語 - + Traditional Chinese (正體中文) 繁体字中国語 (正體中文) - + Brazilian Portuguese (português do Brasil) ブラジルポルトガル語 (português do Brasil) - + Polish (polska) - + Thai (แบบไทย) - - + + Japan 日本 - + USA アメリカ - + Europe ヨーロッパ - + Australia オーストラリア - + China 中国 - + Korea 韓国 - + Taiwan 台湾 - + Auto (%1) Auto select time zone 自動 (%1) - + Default (%1) Default time zone 既定 (%1) - + CET 中央ヨーロッパ時間 - + CST6CDT CST6CDT - + Cuba キューバ - + EET 東ヨーロッパ標準時 - + Egypt エジプト - + Eire アイルランド - + EST アメリカ東部標準時 - + EST5EDT EST5EDT - + GB GB - + GB-Eire イギリス-アイルランド - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich グリニッジ - + Hongkong 香港 - + HST ハワイ標準時 - + Iceland アイスランド - + Iran イラン - + Israel イスラエル - + Jamaica ジャマイカ - + Kwajalein クェゼリン - + Libya リビア - + MET 中東時間 - + MST MST - + MST7MDT MST7MDT - + Navajo ナバホ - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland ポーランド - + Portugal ポルトガル - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore シンガポール - + Turkey トルコ - + UCT UCT - + Universal ユニバーサル - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu ズールー - + Mono モノラル - + Stereo ステレオ - + Surround サラウンド - + 4GB DRAM (Default) 4GB DRAM (デフォルト) - + 6GB DRAM (Unsafe) 6GB DRAM (不安定) - + 8GB DRAM 8GB DRAM - + 10GB DRAM (Unsafe) - + 12GB DRAM (Unsafe) - + Docked Docked - + Handheld 携帯モード - - - Off + + + Normal - - Boost (1700MHz) - Boost (1700MHz) + + + Boost + - - Fast (2000MHz) - Fast (2000MHz) + + + Overclock + - + Always ask (Default) 常に確認する (デフォルト) - + Only if game specifies not to stop ゲームが停止しないように指定しているときのみ - + Never ask 確認しない - - - Medium (256) - - - - - - High (512) - - - - + Very Small (16 MB) - + Small (32 MB) - + Normal (128 MB) - + Large (256 MB) - + Very Large (512 MB) - + Very Low (4 MB) - + Low (8 MB) - + Normal (16 MB) - + Medium (32 MB) - + High (64 MB) - + Very Low (32) - + Low (64) - + Normal (128) - - Disabled - 無効 + + Medium (256) + - + + High (512) + + + + ExtendedDynamicState 1 - + ExtendedDynamicState 2 - + ExtendedDynamicState 3 - + Tree View - + Grid View @@ -2565,252 +2456,297 @@ When a program attempts to open the controller applet, it is immediately closed. グローバルログフィルター - - When checked, the max size of the log increases from 100 MB to 1 GB - チェックすると、ログの最大サイズが100MBから1GBに増加します。 + + GPU Logging/Level + - - Enable Extended Logging** - 拡張ログの有効化** + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console コンソールにログを表示 - + + When checked, the max size of the log increases from 100 MB to 1 GB + チェックすると、ログの最大サイズが100MBから1GBに増加します。 + + + + Enable Extended Logging** + 拡張ログの有効化** + + + Open Log Location ログ出力フォルダを開く - + Homebrew Homebrew - + Arguments String 引数 - + Graphics グラフィック - + When checked, it executes shaders without loop logic changes チェックすると、ループロジックを変更せずにシェーダーを実行します。 - + Disable Loop safety checks ループ安全性チェックの無効化 - + When checked, it will dump all the macro programs of the GPU チェックすると、GPUのすべてのマクロプログラムをダンプします - + Dump Maxwell Macros Maxwellマクロをダンプ - + When checked, it enables Nsight Aftermath crash dumps チェックすると、Nsight Aftermathのクラッシュダンプが有効になります - + Enable Nsight Aftermath Nsight Aftermathの有効化 - - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - チェックすると、ディスクシェーダーキャッシュまたはゲームからオリジナルのアセンブラーシェーダーをすべてダンプします - - - - Dump Game Shaders - ゲームシェーダーをダンプ - - - - Enable Renderdoc Hotkey - Renderdocのホットキーを有効化 - - - - When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower - チェックすると、マクロのJust In Timeコンパイラが無効になります。有効にすると、ゲームの動作が遅くなります。 - - - - Disable Macro JIT - Macro JITを無効化 - - - - When checked, it disables the macro HLE functions. Enabling this makes games run slower - 有効化すると、マクロHLE機能を無効にします。これを有効にすると、ゲームの動作が遅くなります - - - - Disable Macro HLE - Macro HLE を無効化 - - - - When checked, the graphics API enters a slower debugging mode - チェックすると、 グラフィックAPIはより遅いデバッグモードになります。 - - - - Enable Graphics Debugging - グラフィックデバッグの有効化 - - - - When checked, yuzu will log statistics about the compiled pipeline cache - チェックすると、コンパイルしたパイプラインキャッシュの統計情報をロギングします - - Enable Shader Feedback - シェーダーフィードバックの有効j化 + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + - - <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + Dump Guest (Maxwell) Shaders + Enable Renderdoc Hotkey + Renderdocのホットキーを有効化 + + + + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower + チェックすると、マクロのJust In Timeコンパイラが無効になります。有効にすると、ゲームの動作が遅くなります。 + + + + Disable Macro JIT + Macro JITを無効化 + + + + When checked, it disables the macro HLE functions. Enabling this makes games run slower + 有効化すると、マクロHLE機能を無効にします。これを有効にすると、ゲームの動作が遅くなります + + + + Disable Macro HLE + Macro HLE を無効化 + + + + When checked, the graphics API enters a slower debugging mode + チェックすると、 グラフィックAPIはより遅いデバッグモードになります。 + + + + Enable Graphics Debugging + グラフィックデバッグの有効化 + + + + When checked, yuzu will log statistics about the compiled pipeline cache + チェックすると、コンパイルしたパイプラインキャッシュの統計情報をロギングします + + + + Enable Shader Feedback + シェーダーフィードバックの有効j化 + + + + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + + + Disable Buffer Reorder - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced 高度 - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. プログラム起動時にyuzuがVulkan環境が動作しているかどうかを確認するようにします。外部プログラムがyuzuを参照する際に問題がある場合は、これを無効にしてください。 - + Perform Startup Vulkan Check スタートアップ時にVulkanのチェックを実行する - + Disable Web Applet Webアプレットの無効化 - + Enable All Controller Types すべてのコントローラータイプを有効にする - + Enable Auto-Stub - + Kiosk (Quest) Mode Kiosk (Quest) Mode - + Use dev.keys - + Enable Debug Asserts デバッグアサートの有効化 - + Debugging デバッグ - + Battery Serial: - + + Debug Knobs: + + + + Bitmask for quick development toggles - + Set debug knobs (bitmask) - + 16-bit debug knob set for quick development toggles - + (bitmask) - - Debug Knobs: - - - - + Unit Serial: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. これを有効にすると、最新のオーディオコマンドリストがコンソールに出力されます。オーディオレンダラーを使用するゲームにのみ影響します。 - + Dump Audio Commands To Console** オーディオコマンドをコンソールにダンプ** - + Flush log output on each line - + Enable FS Access Log FSアクセスログの有効化 - + Enable Verbose Reporting Services** 詳細なレポートサービスの有効化** - + Censor username in logs - + **This will be reset automatically when Eden closes. @@ -3260,33 +3196,38 @@ Would you like to delete the old save data? 背景色: - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off オフ - + VSync Off VSync オフ - + Recommended 推奨 - + On オン - + VSync On VSync オン @@ -3807,6 +3748,16 @@ Would you like to delete the old save data? Motion / Touch モーション / タッチ + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4316,58 +4267,58 @@ To invert the axes, first move your joystick vertically, and then horizontally.< [待機中] - + New Profile 新規プロファイル - + Enter a profile name: プロファイル名を入力: - - + + Create Input Profile 入力プロファイルを作成 - + The given profile name is not valid! プロファイル名が無効です! - + Failed to create the input profile "%1" 入力プロファイル "%1" の作成に失敗しました - + Delete Input Profile 入力プロファイルを削除 - + Failed to delete the input profile "%1" 入力プロファイル "%1" の削除に失敗しました - + Load Input Profile 入力プロファイルをロード - + Failed to load the input profile "%1" 入力プロファイル "%1" のロードに失敗しました - + Save Input Profile 入力プロファイルをセーブ - + Failed to save the input profile "%1" 入力プロファイル "%1" のセーブに失敗しました @@ -4415,7 +4366,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Configure 設定 @@ -4446,7 +4397,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Test テスト @@ -4461,77 +4412,77 @@ To invert the axes, first move your joystick vertically, and then horizontally.< サーバーを削除 - + %1:%2 %1:%2 - - - - - - + + + + + + Eden Eden - + Port number has invalid characters ポート番号に無効な文字が含まれています - + Port has to be in range 0 and 65353 ポート番号は0から65353の間で設定してください - + IP address is not valid IPアドレスが無効です - + This UDP server already exists このUDPサーバーはすでに存在してます - + Unable to add more than 8 servers 8個以上のサーバーを追加することはできません - + Testing テスト中 - + Configuring 設定中 - + Test Successful テスト成功 - + Successfully received data from the server. サーバーからのデータ受信に成功しました。 - + Test Failed テスト失敗 - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. 有効なデータを受信できませんでした。<br>サーバーが正しくセットアップされ、アドレスとポートが正しいことを確認してください。 - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. UDPテストまたはキャリブレーション実行中です。<br>完了までお待ちください。 @@ -4715,57 +4666,57 @@ Current values are %1% and %2% respectively. いくつかの設定はゲームが実行中でないときのみ設定できます - + Add-Ons アドオン - + System システム - + CPU CPU - + Graphics グラフィック - + Adv. Graphics 高度なグラフィック - + Ext. Graphics - + Audio サウンド - + Input Profiles 入力プロファイル - + Network ネットワーク - + Applets - + Properties プロパティ @@ -4888,6 +4839,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5455,91 +5530,86 @@ Drag points to change position, or double-click table cells to edit values. - Show Compatibility List - 互換性リストを表示 - - - Show Add-Ons Column アドオンを表示 - + Show Size Column サイズを表示 - + Show File Types Column ファイルタイプを表示 - + Show Play Time Column プレイ時間を表示 - + Folder Icon Size: フォルダアイコンサイズ: - + Row 1 Text: 1行目の表示内容: - + Row 2 Text: 2行目の表示内容: - + Screenshots スクリーンショット - + Ask Where To Save Screenshots (Windows Only) スクリーンショット時に保存先を確認する(Windowsのみ) - + Screenshots Path: スクリーンショットの保存先: - + ... ... - + TextLabel - + Resolution: 解像度: - + Select Screenshots Path... スクリーンショットの保存先を選択... - + <System> <System> - + English English - + Auto (%1 x %2, %3 x %4) Screenshot width value @@ -5776,7 +5846,7 @@ Drag points to change position, or double-click table cells to edit values. - + Calculating... @@ -5978,50 +6048,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGLは使用できません! - + OpenGL shared contexts are not supported. - + Eden has not been compiled with OpenGL support. - - - + + + Error while initializing OpenGL! OpenGL初期化エラー - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. GPUがOpenGLをサポートしていないか、グラフィックスドライバーが最新ではありません。 - + Error while initializing OpenGL 4.6! OpenGL4.6初期化エラー! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 GPUがOpenGL4.6をサポートしていないか、グラフィックスドライバーが最新ではありません。<br><br>GL レンダラ:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 GPUが1つ以上の必要なOpenGL拡張機能をサポートしていない可能性があります。最新のグラフィックドライバを使用していることを確認してください。<br><br>GL レンダラ:<br>%1<br><br>サポートされていない拡張機能:<br>%2 - + This build doesn't have OpenGL support. @@ -6029,279 +6099,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory - + Favorite お気に入り - + Start Game ゲームを開始 - + Start Game without Custom Configuration カスタム設定なしでゲームを開始 - + Open Save Data Location セーブデータディレクトリを開く - + Open Mod Data Location Modデータディレクトリを開く - + Open Transferable Pipeline Cache パイプラインキャッシュを開く - + Link to Ryujinx - + Remove 削除 - + Remove Installed Update インストールされているアップデートを削除 - + Remove All Installed DLC 全てのインストールされているDLCを削除 - + Remove Custom Configuration カスタム設定を削除 - + Remove Cache Storage キャッシュストレージを削除 - + Remove OpenGL Pipeline Cache OpenGLパイプラインキャッシュを削除 - + Remove Vulkan Pipeline Cache Vulkanパイプラインキャッシュを削除 - + Remove All Pipeline Caches すべてのパイプラインキャッシュを削除 - + Remove All Installed Contents 全てのインストールされているコンテンツを削除 - + Manage Play Time - + Edit Play Time Data - + Remove Play Time Data プレイ時間情報を削除 - - + + Dump RomFS RomFSをダンプ - + Dump RomFS to SDMC RomFSをSDMCにダンプ - + Verify Integrity 整合性を確認 - + Copy Title ID to Clipboard タイトルIDをクリップボードへコピー - - Navigate to GameDB entry - GameDBエントリを表示 - - - + Create Shortcut ショートカットを作成 - + Add to Desktop デスクトップに追加 - + Add to Applications Menu アプリケーションメニューに追加 - + Configure Game - + Scan Subfolders サブフォルダをスキャンする - + Remove Game Directory ゲームディレクトリを削除する - + ▲ Move Up ▲ 上へ移動 - + ▼ Move Down ▼ 下へ移動 - + Open Directory Location ディレクトリの場所を開く - + Clear クリア - - - Name - ゲーム名 - - - - Compatibility - 互換性 - - - - Add-ons - アドオン - - - - File type - ファイル種別 - - - - Size - ファイルサイズ - - - - Play time - プレイ時間 - GameListItemCompat - + Ingame - + Game starts, but crashes or major glitches prevent it from being completed. ゲームは始まるが、クラッシュや大きな不具合でクリアできない。 - + Perfect カンペキ - + Game can be played without issues. ゲームは問題なくプレイできる。 - + Playable プレイ可 - + Game functions with minor graphical or audio glitches and is playable from start to finish. ゲームは、グラフィックやオーディオに小さな不具合はあるが、最初から最後までプレイできる。 - + Intro/Menu イントロ - + Game loads, but is unable to progress past the Start Screen. ゲームはロードされるが、スタート画面から先に進めない。 - + Won't Boot 起動不可 - + The game crashes when attempting to startup. ゲームは起動時にクラッシュしました。 - + Not Tested 未テスト - + The game has not yet been tested. このゲームはまだテストされていません。 + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list 新しいゲームリストフォルダを追加するにはダブルクリックしてください。 @@ -6309,17 +6372,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) - + Filter: フィルター: - + Enter pattern to filter フィルターパターンを入力 @@ -6814,1139 +6877,1112 @@ Debug Message: - + Game &Icon Size - + Reset Window Size to &720p &720P - + Reset Window Size to 720p ウィンドウサイズを720Pにリセット - + Reset Window Size to &900p &900P - + Reset Window Size to 900p ウィンドウサイズを900Pにリセット - + Reset Window Size to &1080p &1080P - + Reset Window Size to 1080p ウィンドウサイズを1080Pにリセット - + &Multiplayer マルチプレイヤー (&M) - + &Tools ツール(&T) - + Am&iibo - + Launch &Applet - + &TAS &TAS - + &Create Home Menu Shortcut - + Install &Firmware - + &Help ヘルプ(&H) - + &Install Files to NAND... ファイルをNANDにインストール...(&I) - + L&oad File... ファイルをロード...(&L) - + Load &Folder... フォルダをロード...(&F) - + E&xit 終了(&E) - - + + &Pause 中断(&P) - + &Stop 停止(&S) - + &Verify Installed Contents インストールされたコンテンツを確認(&V) - + &About Eden - + Single &Window Mode シングルウィンドウモード(&W) - + Con&figure... 設定...(&F) - + Ctrl+, - + Enable Overlay Display Applet - + Show &Filter Bar フィルターバーを表示 (&F) - + Show &Status Bar ステータスバー(&S) - + Show Status Bar ステータスバーの表示 - + &Browse Public Game Lobby 公開ゲームロビーを参照 (&B) - + &Create Room ルームを作成 (&C) - + &Leave Room ルームを退出 (&L) - + &Direct Connect to Room ルームに直接接続 (&D) - + &Show Current Room 現在のルームを表示 (&S) - + F&ullscreen 全画面表示(&F) - + &Restart 再実行(&R) - + Load/Remove &Amiibo... &Amiibo をロード/削除... - - &Report Compatibility - 互換性を報告(&R) - - - + Open &Mods Page &Modページを開く - - Open &Quickstart Guide - クイックスタートガイドを開く(&Q) + + &User Handbook + - - &FAQ - &FAQ - - - + &Capture Screenshot スクリーンショットをキャプチャ(&C) - + &Album - + &Set Nickname and Owner オーナーとニックネームを設定 (&S) - + &Delete Game Data ゲームデータの消去 (&D) - + &Restore Amiibo Amiibo を復旧 (&R) - + &Format Amiibo Amiibo を初期化(&F) - + &Mii Editor - + &Configure TAS... TASを設定... (&C) - + Configure C&urrent Game... 現在のゲームを設定...(&U) - - + + &Start 実行(&S) - + &Reset リセット(&R) - - + + R&ecord 記録(&R) - + Open &Controller Menu コントローラーメニューを開く (&C) - + Install Decryption &Keys - + &Home Menu - + &Desktop - + &Application Menu - + &Root Data Folder - + &NAND Folder - + &SDMC Folder - + &Mod Folder - + &Log Folder - + From Folder - + From ZIP - + &Eden Dependencies - + &Data Manager - + &Tree View - + &Grid View - + Game Icon Size - - + + None - + Show Game &Name - + Show &Performance Overlay - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) - + Standard (64x64) - + Large (128x128) - + Full Size (256x256) - + Broken Vulkan Installation Detected - + Vulkan initialization failed during boot. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping - + Loading Web Applet... - - + + Disable Web Applet - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + The amount of shaders currently being built - + The current selected resolution scaling multiplier. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Unmute - + Mute - + Reset Volume - + + Configure Effects... + + + + &Clear Recent Files - + &Continue - + Warning: Outdated Game Format - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - - + + Error while loading ROM! - + The ROM format is not supported. - + An error occurred initializing the video core. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + An unknown error occurred. Please see the log for more details. - + + (64-bit) - + + (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + Closing software... - + Save Data - + Mod Data - + Error Opening %1 Folder - - + + Folder does not exist! - + Remove Installed Game Contents? - + Remove Installed Game Update? - + Remove Installed Game DLC? - + Remove Entry - + Delete OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? - + Remove Custom Game Configuration? - + Remove Cache Storage? - + Remove File - + Remove Play Time Data - + Reset play time? - - + + RomFS Extraction Failed! - + There was an error copying the RomFS files or the user cancelled the operation. - + Full - + Skeleton - + Select RomFS Dump Mode - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Extracting RomFS... - - + + Cancel - + RomFS Extraction Succeeded! - + The operation completed successfully. - + Error Opening %1 - + Select Directory - + Properties - + The game properties could not be loaded. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Load File - + Open Extracted ROM Directory - + Invalid Directory Selected - + The directory you have selected does not contain a 'main' file. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files - + %n file(s) remaining - + Installing file "%1"... - - + + Install Results - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + %n file(s) were newly installed - + %n file(s) were overwritten - + %n file(s) failed to install - + System Application - + System Archive - + System Application Update - + Firmware Package (Type A) - + Firmware Package (Type B) - + Game - + Game Update - + Game DLC - + Delta Title - + Select NCA Install Type... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Failed to Install - + The title type you selected for the NCA is invalid. - + File not found - + File "%1" not found - + OK - + Function Disabled - + Compatibility list reporting is currently disabled. Check back later! - + Error opening URL - + Unable to open the URL "%1". - + TAS Recording - + Overwrite file of player 1? - + Invalid config detected - + Handheld controller can't be used on docked mode. Pro controller will be selected. - - + + Amiibo - - + + The current amiibo has been removed - + Error - - + + The current game is not looking for amiibos - + Amiibo File (%1);; All Files (*.*) - + Load Amiibo - + Error loading Amiibo data - + The selected file is not a valid amiibo - + The selected file is already on use - + An unknown error occurred - - - Keys not installed - - - - - - Install decryption keys and restart Eden before attempting to install firmware. - - - - - Select Dumped Firmware Source Location - - - - - Select Dumped Firmware ZIP - - - - - Zipped Archives (*.zip) - - - - - Firmware cleanup failed - - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available - + Firmware Corrupted - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot - + PNG Image (*.png) - + TAS state: Running %1/%2 - + TAS state: Recording %1 - + TAS state: Idle %1/%2 - + TAS State: Invalid - + &Stop Running - + Stop R&ecording - + Building: %n shader(s) - + Scale: %1x %1 is the resolution scaling factor - + Speed: %1% / %2% - + Speed: %1% - + Game: %1 FPS - + Frame: %1 ms - - - FSR - - - - + NO AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE - + VOLUME: %1% Volume percentage (e.g. 50%) - + Derivation Components Missing - + Decryption keys are missing. Install them now? - + Wayland Detected! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -7954,74 +7990,74 @@ Would you like to force it for future launches? - + Use X11 - + Continue with Wayland - + Don't show again - + Restart Required - + Restart Eden to apply the X11 backend. - + Slow - + Turbo - + Unlocked - + Select RomFS Dump Target - + Please select which RomFS you would like to dump. - + Are you sure you want to close Eden? - - - + + + Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8087,6 +8123,11 @@ Would you like to bypass this and exit anyway? ScaleForce + + + FSR + + Area @@ -8098,52 +8139,57 @@ Would you like to bypass this and exit anyway? - + + SGSR + + + + + SGSR EdgeDir + + + + Docked - + Handheld - + Fast - - Balanced - - - - + Accurate - + Vulkan - + OpenGL GLSL - + OpenGL SPIRV - + OpenGL GLASM - + Null @@ -8176,7 +8222,7 @@ If you wish to clean up the files which were left in the old data location, you - + Data was migrated successfully. @@ -8666,8 +8712,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8680,10 +8726,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8698,9 +8744,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8862,50 +8908,50 @@ p, li { white-space: pre-wrap; } [未定義] - + %1%2 %1%2 - + [invalid] [無効] - - + + %1%2Hat %3 - - - + + + %1%2Axis %3 - + %1%2Axis %3,%4,%5 - + %1%2Motion %3 %1%2モーション %3 - - + + %1%2Button %3 %1%2ボタン %3 - + [unused] [未使用] @@ -9031,47 +9077,47 @@ p, li { white-space: pre-wrap; } %1は%2をプレイ中です - + Play Time: %1 - + Never Played - + Version: %1 - + Version: 1.0.0 - + Installed SD Titles インストール済みSDタイトル - + Installed NAND Titles インストール済みNANDタイトル - + System Titles システムタイトル - + Add New Game Directory 新しいゲームディレクトリを追加する - + Favorites お気に入り @@ -9192,13 +9238,13 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. @@ -9208,10 +9254,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9232,213 +9278,246 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9682,72 +9761,72 @@ Would you like to manually select a portable folder to use? - + Create Shortcut - + Do you want to launch the game in fullscreen? - + Shortcut Created - + Successfully created a shortcut to %1 - + Shortcut may be Volatile! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? - + Failed to Create Shortcut - + Failed to create a shortcut to %1 - + Create Icon - + Cannot create icon file. Path "%1" does not exist and cannot be created. - + No firmware available - + Please install firmware to use the home menu. - + Home Menu Applet - + Home Menu is not available. Please reinstall firmware. @@ -9755,55 +9834,55 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name - + What should this mod be called? - + RomFS - + ExeFS/Patch - + Cheat - + Mod Type - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty @@ -10497,65 +10576,65 @@ By selecting "From Eden", previous save data stored in Ryujinx will be - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/ko_KR.ts b/dist/languages/ko_KR.ts index 590a031a80..c78a853a20 100644 --- a/dist/languages/ko_KR.ts +++ b/dist/languages/ko_KR.ts @@ -4,12 +4,12 @@ About Eden - + Eden에 대해 <html><head/><body><p><span style=" font-size:28pt;">Eden</span></p></body></html> - + <html><head/><body><p><span style=" font-size:28pt;">Eden</span></p></body></html> @@ -26,17 +26,24 @@ li.unchecked::marker { content: "\2610"; } li.checked::marker { content: "\2612"; } </style></head><body style=" font-family:'Noto Sans'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt;">Eden is an experimental open-source emulator for the Nintendo Switch licensed under GPLv3.0+ which is based on the yuzu emulator which ended development back in March 2024. <br /><br />This software should not be used to play games you have not legally obtained.</span></p></body></html> - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> +p, li { white-space: pre-wrap; } +hr { height: 1px; border-width: 0; } +li.unchecked::marker { content: "\2610"; } +li.checked::marker { content: "\2612"; } +</style></head><body style=" font-family:'Noto Sans'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:12pt;">Eden은 GPLv3.0+ 라이선스를 받은 실험적인 오픈 소스 닌텐도 스위치 에뮬레이터로, 2024년 3월에 개발이 종료된 yuzu 에뮬레이터를 기반으로 합니다. <br /><br />이 소프트웨어는 법적으로 획득하지 않은 게임을 플레이하는 데 사용되어서는 안 됩니다.</span></p></body></html> <html><head/><body><p><a href="https://eden-emulator.github.io/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://git.eden-emu.dev"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/activity/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://discord.gg/HstXbPch7X"><span style=" text-decoration: underline; color:#039be5;">Discord</span></a> | <a href="https://stt.gg/qKgFEAbH"><span style=" text-decoration: underline; color:#039be5;">Stoat</span></a> | <a href="https://nitter.poast.org/edenemuofficial"><span style=" text-decoration: underline; color:#039be5;">Twitter</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/src/branch/master/LICENSE.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html> - + <html><head/><body><p><a href="https://eden-emulator.github.io/"><span style=" text-decoration: underline; color:#039be5;">웹사이트</span></a> | <a href="https://git.eden-emu.dev"><span style=" text-decoration: underline; color:#039be5;">소스 코드</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/activity/contributors"><span style=" text-decoration: underline; color:#039be5;">기여자</span></a> | <a href="https://discord.gg/HstXbPch7X"><span style=" text-decoration: underline; color:#039be5;">디스코드</span></a> | <a href="https://stt.gg/qKgFEAbH"><span style=" text-decoration: underline; color:#039be5;">Stoat</span></a> | <a href="https://nitter.poast.org/edenemuofficial"><span style=" text-decoration: underline; color:#039be5;">트위터</span></a> | <a href="https://git.eden-emu.dev/eden-emu/eden/src/branch/master/LICENSE.txt"><span style=" text-decoration: underline; color:#039be5;">라이선스</span></a></p></body></html> <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; is a trademark of Nintendo. Eden is not affiliated with Nintendo in any way.</span></p></body></html> - + <html><head/><body><p><span style=" font-size:7pt;">&quot;닌텐도 스위치&quot;는 닌텐도의 상표이며, Eden은 닌텐도와 어떤 방식으로도 제휴되어 있지 않습니다.</span></p></body></html> @@ -64,7 +71,7 @@ li.checked::marker { content: "\2612"; } Configuration completed! - 설정 완료! + 구성 완료! @@ -133,12 +140,12 @@ li.checked::marker { content: "\2612"; } When you block a player, you will no longer receive chat messages from them.<br><br>Are you sure you would like to block %1? - 플레이어를 차단하면 더 이상 채팅 메시지를 받을 수 없습니다.<br><br>%1을(를) 차단하겠습니까? + 플레이어를 차단하면 해당 플레이어로부터 더 이상 채팅 메시지를 받지 못하게 됩니다.<br><br>%1을(를) 차단하겠습니까? Kick - 추방 + 강퇴 @@ -148,12 +155,12 @@ li.checked::marker { content: "\2612"; } Kick Player - 추방된 플레이어 + 강퇴된 플레이어 Are you sure you would like to <b>kick</b> %1? - 정말로 %1을 <b>추방</b>하겠습니까?? + 정말 %1을(를) <b>강퇴</b>시키겠습니까? @@ -165,9 +172,9 @@ li.checked::marker { content: "\2612"; } Are you sure you would like to <b>kick and ban</b> %1? This would ban both their forum username and their IP address. - %1을 <b>추방</b>하겠습니까? + 정말 %1을(를) <b>강퇴 및 차단</b>하겠습니까? -이렇게 하면 포럼 사용자 이름과 IP 주소가 모두 금지됩니다. +이렇게 하면 포럼 사용자 이름과 IP 주소 모두 차단됩니다. @@ -211,155 +218,6 @@ This would ban both their forum username and their IP address. %1 - %2 (%3/%4 멤버) - 연결됨 - - CompatDB - - - Report Compatibility - 호환성 보고 - - - - - - - - - - Report Game Compatibility - 게임 호환성 보고 - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>게임이 부팅되나요?</p></body></html> - - - - Yes The game starts to output video or audio - 예 게임이 비디오 또는 오디오 출력을 시작합니다 - - - - No The game doesn't get past the "Launching..." screen - 아니요 게임이 "실행 중..." 화면을 통과하지 않습니다 - - - - Yes The game gets past the intro/menu and into gameplay - 예 게임이 인트로/메뉴를 지나 게임 플레이로 들어갑니다 - - - - No The game crashes or freezes while loading or using the menu - 아니요 메뉴를 로드하거나 사용하는 동안 게임이 충돌하거나 멈춥니다 - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>게임이 게임 플레이에 도달합니까?</p></body></html> - - - - Yes The game works without crashes - 예 충돌 없이 게임이 작동합니다 - - - - No The game crashes or freezes during gameplay - 아니요 게임 플레이 중에 게임이 충돌하거나 멈춥니다 - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>게임 플레이 중에 충돌, 정지 또는 잠김 없이 게임이 작동합니까?</p></body></html> - - - - Yes The game can be finished without any workarounds - 예 해결 방법 없이 게임을 종료할 수 있습니다 - - - - No The game can't progress past a certain area - 아니요 게임이 특정 영역을 지나서 진행할 수 없습니다 - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>게임을 처음부터 끝까지 완벽하게 플레이할 수 있습니까?</p></body></html> - - - - Major The game has major graphical errors - 주요 게임에 주요 그래픽 오류가 있습니다 - - - - Minor The game has minor graphical errors - 마이너 게임에 경미한 그래픽 오류가 있습니다 - - - - None Everything is rendered as it looks on the Nintendo Switch - 없음 닌텐도 스위치에서 보이는 대로 모든 것이 렌더링됩니다 - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>게임에 그래픽 결함이 있습니까?</p></body></html> - - - - Major The game has major audio errors - 메이저 게임에 주요 오디오 오류가 있습니다 - - - - Minor The game has minor audio errors - 마이너 게임에 사소한 오디오 오류가 있습니다 - - - - None Audio is played perfectly - 없음 오디오가 완벽하게 재생됩니다 - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>게임에 오디오 결함/효과 누락이 있습니까?</p></body></html> - - - - Thank you for your submission! - 제출 감사합니다! - - - - Submitting - 제출중 - - - - Communication error - 통신 에러 - - - - An error occurred while sending the Testcase - 테스트 결과 전송 중 오류 발생 - - - - Next - 다음 - - ConfigurationShared @@ -370,17 +228,17 @@ This would ban both their forum username and their IP address. Amiibo editor - + Amiibo 편집기 Controller configuration - + 컨트롤러 구성 Data erase - + 데이터 삭제 @@ -390,12 +248,12 @@ This would ban both their forum username and their IP address. Net connect - + 망 연결 Player select - + 플레이어 선택 @@ -405,52 +263,52 @@ This would ban both their forum username and their IP address. Mii Edit - + Mii 편집 Online web - + 온라인 웹 Shop - + 숍 Photo viewer - + 사진 뷰어 Offline web - + 오프라인 웹 Login share - + 로그인 공유 Wifi web auth - + Wi-Fi 웹 인증 My page - + 마이페이지 Enable Overlay Applet - + 오버레이 애플릿 활성화 Enables Horizon's built-in overlay applet. Press and hold the home button for 1 second to show it. - + Horizon의 내장 오버레이 애플릿을 활성화합니다. HOME 버튼을 1초 동안 길게 누르면 애플릿이 나타납니다. @@ -470,7 +328,7 @@ This would ban both their forum username and their IP address. Mute audio - + 오디오 음소거 @@ -480,72 +338,77 @@ This would ban both their forum username and their IP address. Mute audio when in background - 백그라운드에서 오디오 음소거 + 백그라운드에서 실행될 때 오디오 음소거 Multicore CPU Emulation - 멀티 코어 CPU 에뮬레이션 + 다중코어 CPU 에뮬레이션 This option increases CPU emulation thread use from 1 to the maximum of 4. This is mainly a debug option and shouldn't be disabled. - + 이 옵션은 CPU 에뮬레이션 스레드 사용량을 1에서 최대 4까지 증가시킵니다. +이 옵션은 주로 디버그용이므로 비활성화해서는 안 됩니다. Memory Layout - + 메모리 레이아웃 Increases the amount of emulated RAM. Doesn't affect performance/stability but may allow HD texture mods to load. - + 에뮬레이트된 RAM의 용량을 증가시킵니다. +성능/안정성에는 영향을 미치지 않지만 HD 텍스처 모드가 로드될 수 있습니다. Limit Speed Percent - 속도 퍼센트 제한 + 속도 제한 비율 Controls the game's maximum rendering speed, but it's up to each game if it runs faster or not. 200% for a 30 FPS game is 60 FPS, and for a 60 FPS game it will be 120 FPS. Disabling it means unlocking the framerate to the maximum your PC can reach. - + 게임의 최대 렌더링 속도를 제어하지만, 실제로 더 빠르게 실행될지는 각 게임에 따라 다릅니다. +30 FPS 게임에서 200%로 설정하면 60 FPS가 되고, 60 FPS 게임에서는 120 FPS가 됩니다. +이 옵션을 비활성화하면 PC가 도달할 수 있는 최대 프레임률까지 프레임 속도 제한이 해제됩니다. Turbo Speed - + 터보 속도 When the Turbo Speed hotkey is pressed, the speed will be limited to this percentage. - + 터보 속도 단축키를 누르면 속도가 이 비율로 제한됩니다. Slow Speed - + 느린 속도 When the Slow Speed hotkey is pressed, the speed will be limited to this percentage. - + 느린 속도 단축키를 누르면 속도가 이 비율로 제한됩니다. Synchronize Core Speed - + 코어 속도 동기화 Synchronizes CPU core speed with the game's maximum rendering speed to boost FPS without affecting game speed (animations, physics, etc.). Can help reduce stuttering at lower framerates. - + CPU 코어 속도를 게임의 최대 렌더링 속도와 동기화하여 게임 속도(애니메이션, 물리 등)에 영향을 주지 않고 FPS를 향상시킵니다. +낮은 프레임률에서 발생하는 끊김 현상을 줄이는 데 도움될 수 있습니다. @@ -555,1656 +418,1747 @@ Can help reduce stuttering at lower framerates. Change the accuracy of the emulated CPU (for debugging only). - + 에뮬레이트된 CPU의 정확도를 변경합니다(디버깅용으로만 사용). - + Backend: - + 백엔드: - CPU Overclock - + CPU Clocks + CPU 클럭 - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. - + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + 에뮬레이트된 CPU가 보고하는 클럭 속도를 높여 일부 FPS 제한을 해제합니다. +성능이 낮은 CPU의 경우 성능이 저하될 수 있으며, 일부 게임은 제대로 작동하지 않을 수 있습니다. - + Custom CPU Ticks - + 사용자 지정 CPU 틱 - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. - + CPU 틱 값을 사용자 지정으로 설정합니다. 값이 높을수록 성능이 향상될 수 있지만 교착 상태가 발생할 수 있습니다. 77~21000 범위의 값을 권장합니다. - + Enable Host MMU Emulation (fastmem) - + 호스트 MMU 에뮬레이션 사용(패스트멤) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. - + 이 최적화는 게스트 프로그램의 메모리 접근 속도를 높입니다. +이 옵션을 활성화하면 게스트 메모리 읽기/쓰기가 메모리에서 직접 수행되고 호스트의 MMU를 사용하게 됩니다. +이 옵션을 비활성화하면 모든 메모리 접근에 소프트웨어 MMU 에뮬레이션을 사용하도록 강제됩니다. - + Unfuse FMA (improve performance on CPUs without FMA) - FMA 분리 (FMA를 지원하지 않는 CPU에서의 성능을 향상시킵니다) + FMA 분리(FMA를 지원하지 않는 CPU의 성능 향상) + + + + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. + 이 옵션은 네이티브 FMA를 지원하지 않는 CPU의 fused-multiply-add 명령어 정확도를 낮춰 속도를 향상시킵니다. - This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. - - - - Faster FRSQRTE and FRECPE 더 빠른 FRSQRTE와 FRECPE - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. - + 이 옵션은 정확도가 낮은 네이티브 근사값을 사용하여 일부 근사 부동 소수점 함수의 속도를 향상시킵니다. - + Faster ASIMD instructions (32 bits only) 더 빠른 ASIMD 명령어(32비트 전용) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. - + 이 옵션은 잘못된 반올림 모드를 사용하여 실행함으로써 32비트 ASIMD 부동 소수점 함수의 속도를 향상시킵니다. - + Inaccurate NaN handling 부정확한 NaN 처리 - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. - + 이 옵션은 NaN 검사를 제거하여 속도를 향상시킵니다. +이로 인해 특정 부동 소수점 명령어의 정확도가 떨어질 수 있다는 점을 유의해 주세요. - + Disable address space checks 주소 공간 검사 비활성화 - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. - + 이 옵션은 모든 메모리 작업 전에 안전 검사를 제거하여 속도를 향상시킵니다. +이 옵션을 비활성화하면 임의 코드 실행이 허용될 수 있습니다. - + Ignore global monitor 글로벌 모니터 무시 - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. - + 이 옵션은 cmpxchg의 의미론에만 의존하여 배타적 접근 명령의 안전성을 보장함으로써 속도를 향상시킵니다. +이로 인해 교착 상태 및 기타 경합 조건이 발생할 수 있다는 점을 유의해 주세요. - + API: API: - + Changes the output graphics API. Vulkan is recommended. - + 출력 그래픽 API를 변경합니다. +Vulkan을 권장합니다. - + Device: 장치: - + This setting selects the GPU to use (Vulkan only). - + 이 설정은 사용할 GPU를 선택합니다(Vulkan 전용). - + Resolution: 해상도: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. - + 다른 해상도로 렌더링하도록 강제합니다. +더 높은 해상도는 더 많은 VRAM과 대역폭을 필요로 합니다. +1X보다 낮은 옵션은 아티팩트를 유발할 수 있습니다. - + Window Adapting Filter: - 윈도우 적응형 필터: + 창 적응형 필터: - + FSR Sharpness: FSR 선명도: - - Determines how sharpened the image will look using FSR's dynamic contrast. - + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. + FSR 또는 SGSR의 동적 대비를 사용하여 이미지가 얼마나 선명하게 보일지 결정합니다. - + Anti-Aliasing Method: 안티에일리어싱 방식: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. + 사용할 안티에일리어싱 방식. +SMAA는 최고의 화질을 제공합니다. +FXAA는 저해상도에서 더 안정적인 화면을 구현할 수 있습니다. + + + + Enable post-processing effects + Applies post-processing effects to the final image. + + + + Fullscreen Mode: 전체 화면 모드: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. - - - - - Aspect Ratio: - 화면비: + 창을 전체 화면으로 렌더링하는 데 사용되는 방법입니다. +테두리 없는 창 모드는 일부 게임에서 입력을 위해 요구하는 화면 키보드와의 호환성이 가장 좋습니다. +전체 화면 전용 모드는 더 나은 성능과 FreeSync/GSync 지원을 제공할 수 있습니다. + Aspect Ratio: + 화면 비율: + + + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. - - - - - Use persistent pipeline cache - + 렌더러를 지정된 화면 비율에 맞게 늘립니다. +대부분의 게임은 16:9만 지원하므로 다른 비율을 얻으려면 수정이 필요합니다. +또한 캡처된 스크린샷의 화면 비율도 제어합니다. + Use persistent pipeline cache + 지속적 파이프라인 캐시 사용 + + + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. - - - - - Use asynchronous GPU emulation - - - - - Uses an extra CPU thread for rendering. -This option should always remain enabled. - + 셰이더를 저장소에 저장하여 다음 게임 실행 시 더 빠르게 불러올 수 있도록 합니다. +이 옵션을 비활성화하는 것은 디버깅 목적으로만 사용하세요. + Use asynchronous GPU emulation + 비동기 GPU 에뮬레이션 사용 + + + + Uses an extra CPU thread for rendering. +This option should always remain enabled. + 렌더링에 추가 CPU 스레드를 사용합니다. +이 옵션은 항상 활성화된 상태로 유지해야 합니다. + + + NVDEC emulation: NVDEC 에뮬레이션: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. - + 비디오를 디코딩하는 방법을 지정합니다. +디코딩에 CPU 또는 GPU를 사용할 수 있으며, 디코딩을 전혀 수행하지 않을 수도 있습니다(비디오가 검은 화면으로 표시됨). +대부분의 경우 GPU 디코딩이 가장 좋은 성능을 제공합니다. - + ASTC Decoding Method: - + ASTC 디코딩 방식: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). CPU Asynchronously: Use the CPU to decode ASTC textures on demand. EliminatesASTC decoding stuttering but may present artifacts. - + 이 옵션은 ASTC 텍스처를 디코딩하는 방식을 제어합니다. +CPU: CPU를 사용하여 디코딩합니다. +GPU: GPU의 컴퓨트 셰이더를 사용하여 ASTC 텍스처를 디코딩합니다(권장). +CPU 비동기: 필요에 따라 CPU를 사용하여 ASTC 텍스처를 디코딩합니다. ASTC 디코딩으로 인한 끊김 현상이 없어지지만, 아티팩트가 발생할 수 있습니다. - + ASTC Recompression Method: - + ASTC 재압축 방식: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. - + 대부분의 GPU는 ASTC 텍스처를 지원하지 않으므로 중간 형식인 RGBA8로 압축 해제해야 합니다. +BC1/BC3: 중간 형식은 BC1 또는 BC3 형식으로 재압축되어 + VRAM을 절약하지만 이미지 품질이 저하됩니다. - + Frame Pacing Mode (Vulkan only) - - - - - Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + 프레임 페이싱 모드(Vulkan 전용) - VRAM Usage Mode: - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. + 에뮬레이터가 프레임 페이싱을 관리하는 방식을 제어하여 끊김 현상을 줄이고 프레임 속도를 더욱 부드럽고 일관되게 만듭니다. - + + VRAM Usage Mode: + VRAM 사용 모드: + + + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. - + 에뮬레이터가 메모리를 절약할지, 아니면 사용 가능한 비디오 메모리를 최대한 활용하여 성능을 향상시킬지 선택합니다. +적극적 모드는 녹화 소프트웨어와 같은 다른 애플리케이션의 성능에 영향을 줄 수 있습니다. - + Skip CPU Inner Invalidation - - - - - Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. - + CPU 내부 무효화 건너뛰기 - VSync Mode: - VSync 모드: + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. + 메모리 업데이트 중 특정 캐시 무효화를 건너뛰어 CPU 사용량을 줄이고 지연 시간을 개선합니다. 하지만 이로 인해 소프트 크래시가 발생할 수 있습니다. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + + VSync Mode: + 수직 동기화 모드: + + + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. Immediate (no synchronization) presents whatever is available and can exhibit tearing. - - - - - Sync Memory Operations - - - - - Ensures data consistency between compute and memory operations. -This option fixes issues in games, but may degrade performance. -Unreal Engine 4 games often see the most significant changes thereof. - - - - - Enable asynchronous presentation (Vulkan only) - 비동기 프레젠테이션 활성화(Vulkan만 해당) - - - - Slightly improves performance by moving presentation to a separate CPU thread. - - - - - Force maximum clocks (Vulkan only) - 강제 최대 클록 (Vulkan 전용) - - - - Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. - 실행은 GPU가 클럭 속도를 낮추지 않도록 그래픽 명령을 기다리는 동안 백그라운드에서 작동합니다. + FIFO(VSync)는 프레임 드롭이나 화면 찢김 현상이 없지만 화면 새로 고침률이 제한됩니다. +FIFO Relaxed는 속도 저하 후 복구 과정에서 화면 찢김 현상이 발생할 수 있습니다. +Mailbox는 FIFO보다 지연 시간이 짧고 화면 찢김 현상이 없지만 프레임 드롭이 발생할 수 있습니다. +즉각적(동기화 없음)는 사용 가능한 모든 화면을 표시하며 화면 찢김 현상이 발생할 수 있습니다. + Sync Memory Operations + 메모리 작업 동기화 + + + + Ensures data consistency between compute and memory operations. +This option fixes issues in games, but may degrade performance. +Unreal Engine 4 games often see the most significant changes thereof. + 컴퓨팅 및 메모리 작업 간의 데이터 일관성을 보장합니다. +이 옵션은 게임 문제를 해결하지만 성능이 저하될 수 있습니다. +대개 언리얼 엔진 4 게임에서 이러한 변경 사항이 가장 두드러지게 나타납니다. + + + + Enable asynchronous presentation (Vulkan only) + 비동기 프레젠테이션 활성화(Vulkan 전용) + + + + Slightly improves performance by moving presentation to a separate CPU thread. + 프레젠테이션을 별도의 CPU 스레드로 이동시켜 성능을 약간 향상시킵니다. + + + + Force maximum clocks (Vulkan only) + 최대 클럭 강제 적용(Vulkan 전용) + + + + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. + 그래픽 명령을 기다리는 동안 백그라운드에서 작업을 실행하여 GPU 클럭 속도가 저하되는 것을 방지합니다. + + + Anisotropic Filtering: 비등방성 필터링: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. - - - - - GPU Mode: - - - - - Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. -Particles tend to only render correctly with Accurate mode. - - - - - DMA Accuracy: - - - - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. - - - - - Enable asynchronous shader compilation - - - - - May reduce shader stutter. - - - - - Fast GPU Time - + 비스듬한 각도에서의 텍스처 렌더링 품질을 제어합니다. +대부분의 GPU에서 16x로 설정해도 안전합니다. - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. - + GPU Mode: + GPU 모드: - - GPU Unswizzle - + + Controls the GPU emulation mode. +Most games render fine with Fast, but Accurate is still required for some. +Particles tend to only render correctly with Accurate mode. + GPU 에뮬레이션 모드를 제어합니다. +대부분의 게임은 고속 모드에서도 제대로 렌더링되지만, 일부 게임에서는 정확 모드가 필요합니다. +특히 조각들은 정확 모드에서만 제대로 렌더링되는 경향이 있습니다. - Accelerates BCn 3D texture decoding using GPU compute. -Disable if experiencing crashes or graphical glitches. - + DMA Accuracy: + DMA 정확도: + + + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. + DMA 읽기 모드를 제어합니다. +불안정 모드는 더 빠르며, 안전 모드는 더 안정적이며 일부 게임에서 발생하는 문제를 해결할 수 있습니다. +기본값은 GPU 정확도 설정을 따릅니다. - GPU Unswizzle Max Texture Size - + GPU Fence Behavior: + GPU 펜스 동작: - Sets the maximum size (MiB) for GPU-based texture unswizzling. -While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. -Adjust this to find the balance between GPU acceleration and CPU overhead. + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + Enable asynchronous shader compilation + 비동기 셰이더 컴파일 활성화 + + + + May reduce shader stutter. + 셰이더 끊김 현상을 줄일 수 있습니다. + + + + GPU Clocks + GPU 클럭 + - GPU Unswizzle Stream Size - + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. + 게임이 GPU 작업이 실제보다 더 빨리 완료된다고 착각하게 만들어, 스위치의 클럭 속도에 맞추기 위해 해상도와 렌더 거리를 낮추는 작업을 중단하게 합니다. - - Sets the maximum amount of texture data (in MiB) processed per frame. -Higher values can reduce stutter during texture loading but may impact frame consistency. - + + GPU Unswizzle + GPU 언스위즐 - - GPU Unswizzle Chunk Size - + + Accelerates BCn 3D texture decoding using GPU compute. +Disable if experiencing crashes or graphical glitches. + GPU 컴퓨팅을 사용하여 BCn 3D 텍스처 디코딩을 가속화합니다. +충돌이나 그래픽 결함이 발생하는 경우 이 기능을 비활성화하세요. - Determines the number of depth slices processed in a single dispatch. -Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + GPU Unswizzle Max Texture Size + GPU 언스위즐 최대 텍스처 크기 - + + Sets the maximum size (MiB) for GPU-based texture unswizzling. +While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. +Adjust this to find the balance between GPU acceleration and CPU overhead. + GPU 기반 텍스처 언스위즐링의 최대 크기(MiB)를 설정합니다. +GPU는 중간 및 큰 크기의 텍스처에서 더 빠르지만, 매우 작은 텍스처에서는 CPU가 더 효율적일 수 있습니다. +이 값을 조정하여 GPU 가속과 CPU 오버헤드 간의 균형을 찾으세요. + + + + GPU Unswizzle Stream Size + GPU 언스위즐 스트림 크기 + + + + Sets the maximum amount of texture data (in MiB) processed per frame. +Higher values can reduce stutter during texture loading but may impact frame consistency. + 프레임당 처리할 최대 텍스처 데이터 양(MiB 단위)을 설정합니다. +값이 높을수록 텍스처 로딩 중 끊김 현상을 줄일 수 있지만 프레임 일관성에 영향을 미칠 수 있습니다. + + + + GPU Unswizzle Chunk Size + GPU 언스위즐 청크 크기 + + + + Determines the number of depth slices processed in a single dispatch. +Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. + 단일 디스패치에서 처리할 깊이 슬라이스 수를 결정합니다. +이 값을 높이면 고성능 GPU에서 처리량이 향상될 수 있지만, 성능이 낮은 하드웨어에서는 TDR 오류나 드라이버 시간 초과가 발생할 수 있습니다. + + + Use Vulkan pipeline cache Vulkan 파이프라인 캐시 사용 - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. - + GPU 제조사별 파이프라인 캐시를 활성화합니다. +이 옵션은 Vulkan 드라이버가 파이프라인 캐시 파일을 내부에 저장하지 않는 경우 셰이더 로딩 시간을 크게 향상시킬 수 있습니다. - + Enable Compute Pipelines (Intel Vulkan Only) - + 컴퓨트 파이프라인 활성화(인텔 Vulkan 전용) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. - + 일부 게임에 필요합니다. +이 설정은 인텔 독점 드라이버에만 적용되며 활성화된 경우 충돌할 수 있습니다. +다른 모든 드라이버에서는 컴퓨트 파이프라인이 항상 활성화됩니다. - + Enable Reactive Flushing 반응형 플러싱 활성화 - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. - - - - - Sync to framerate of video playback - 동영상 재생 프레임 속도에 동기화 - - - - Run the game at normal speed during video playback, even when the framerate is unlocked. - 프레임 속도가 잠금 해제된 상태에서도 동영상 재생 중에 일반 속도로 게임을 실행합니다. - - - - Barrier feedback loops - 차단 피드백 루프 - - - - Improves rendering of transparency effects in specific games. - 특정 게임에서 투명도 효과의 렌더링을 개선합니다. - - - - Enable buffer history - - - - - Enables access to previous buffer states. -This option may improve rendering quality and performance consistency in some games. - - - - - Fix bloom effects - - - - - Removes bloom in Burnout. - + 예측형 플러싱 대신 반응형 플러싱을 사용하여 보다 정확한 메모리 동기화를 가능하게 합니다. - Enable Legacy Rescale Pass - + Sync to framerate of video playback + 비디오 재생 프레임 속도에 동기화 - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - + Run the game at normal speed during video playback, even when the framerate is unlocked. + 프레임률 제한이 해제된 경우에도 비디오 재생 중에는 게임을 정상 속도로 실행합니다. - - Extended Dynamic State - + + Barrier feedback loops + 피드백 루프 차단 + + + + Improves rendering of transparency effects in specific games. + 특정 게임에서 투명 효과 렌더링을 개선합니다. + + + + Enable buffer history + 버퍼 기록 활성화 + + + + Enables access to previous buffer states. +This option may improve rendering quality and performance consistency in some games. + 이전 버퍼 상태에 접근할 수 있도록 합니다. +이 옵션은 일부 게임에서 렌더링 품질과 성능 일관성을 향상시킬 수 있습니다. - Controls the number of features that can be used in Extended Dynamic State. -Higher states allow for more features and can increase performance, but may cause additional graphical issues. - + Fix bloom effects + 블룸 효과 수정 + + + + Removes bloom in Burnout. + Burnout에서의 블룸 현상을 제거합니다. + + + + Extended Dynamic State + 확장된 동적 상태 - Vertex Input Dynamic State - - - - - Enables vertex input dynamic state feature for better quality and performance. - + Controls the number of features that can be used in Extended Dynamic State. +Higher states allow for more features and can increase performance, but may cause additional graphical issues. + 확장된 동적 상태에서 사용할 수 있는 기능의 수를 제어합니다. +상태 수가 많을수록 더 많은 기능을 사용할 수 있고 성능이 향상될 수 있지만, 추가적인 그래픽 문제가 발생할 수 있습니다. - Sample Shading - + Vertex Input Dynamic State + 정점 입력 동적 상태 - Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. -Higher values improve quality but degrade performance. - + Enables vertex input dynamic state feature for better quality and performance. + 정점 입력의 동적 상태 기능을 활성화하여 품질과 성능을 향상시킵니다. - + + Sample Shading + 샘플 셰이딩 + + + + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. +Higher values improve quality but degrade performance. + 조각 셰이더가 다중샘플 조각에서 한 번당 한 조각이 아닌 샘플별로 실행될 수 있도록 합니다. 성능 저하를 감수하고 그래픽 품질을 향상시킵니다. +값이 높을수록 품질은 향상되지만 성능은 저하됩니다. + + + RNG Seed RNG 시드 - + Controls the seed of the random number generator. Mainly used for speedrunning. - + 난수 생성기의 시드 값을 제어합니다. +주로 스피드런에 사용됩니다. - + Device Name 장치 이름 - + The name of the console. - + 콘솔의 이름 - - Custom RTC Date: - - - - - This option allows to change the clock of the console. -Can be used to manipulate time in games. - + + Homebrew Args + 홈브루 인수 - The number of seconds from the current unix time - + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + 홈브루 실행 시 전달되는 명령줄 인수(예: -noglsl). - Language: - + Custom RTC Date: + 사용자 지정 RTC 날짜: + This option allows to change the clock of the console. +Can be used to manipulate time in games. + 이 옵션을 사용하면 콘솔의 시간을 변경할 수 있습니다. +게임 내 시간을 조작하는 데 사용할 수 있습니다. + + + + The number of seconds from the current unix time + 현재 unix 시간으로부터 경과한 초 수 + + + + Language: + 언어: + + + This option can be overridden when region setting is auto-select - + 이 옵션은 지역 설정이 자동 선택으로 되어 있을 경우 번복될 수 있습니다. - + Region: - 국가: + 지역: - + The region of the console. - + 콘솔의 지역 - + Time Zone: - 시계: + 시간대: - + The time zone of the console. - + 콘솔의 시간대 - + Sound Output Mode: 소리 출력 모드: - + Console Mode: - + 콘솔 모드: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. - + 콘솔이 독 모드 또는 휴대 모드인지를 선택합니다. +게임은 이 설정에 따라 해상도, 세부 정보 및 지원되는 컨트롤러가 변경됩니다. +휴대 모드로 설정하면 저사양 시스템의 성능을 향상시키는 데 도움이 될 수 있습니다. - + Prompt for user profile on boot - - - - - Useful if multiple people use the same PC. - - - - - Pause when not in focus - - - - - Pauses emulation when focusing on other windows. - - - - - Confirm before stopping emulation - - - - - Overrides prompts asking to confirm stopping the emulation. -Enabling it bypasses such prompts and directly exits the emulation. - + 부팅 시 유저 프로필 입력 요청 - Hide mouse on inactivity - 비활성 상태일 때 마우스 숨기기 + Useful if multiple people use the same PC. + 여러 사람이 같은 PC를 사용할 때 유용합니다. - Hides the mouse after 2.5s of inactivity. - + Pause when not in focus + 초점이 맞지 않을 때 일시 중지 - Disable controller applet - 컨트롤러 애플릿 비활성화 + Pauses emulation when focusing on other windows. + 다른 창에 초점을 맞출 때 에뮬레이션을 일시 중지합니다. - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - + Confirm before stopping emulation + 에뮬레이션을 중지하기 전에 확인 - - Check for updates - + + Overrides prompts asking to confirm stopping the emulation. +Enabling it bypasses such prompts and directly exits the emulation. + 에뮬레이션 중지를 확인하는 메시지를 무시합니다. +이 옵션을 활성화하면 이러한 메시지를 건너뛰고 에뮬레이션을 바로 종료합니다. - Whether or not to check for updates upon startup. - + Hide mouse on inactivity + 마우스 커서가 비활동 상태일 때 숨기기 + + + + Hides the mouse after 2.5s of inactivity. + 마우스 커서가 2.5초 동안 활동이 없으면 숨깁니다. + + + + Check for updates + 업데이트 확인 + Whether or not to check for updates upon startup. + 시작 시 업데이트를 확인할지 여부. + + + Enable Gamemode - + 게임 모드 활성화 - + Force X11 as Graphics Backend - + X11을 그래픽 백엔드로 강제 설정 - + Custom frontend - + 사용자 정의 프론트엔드 - + Real applet - + 실제 애플릿 - + + + Disabled + 비활성화됨 + + + Never - + 안 함 - + On Load - - - - - Always - + 로드 시 + Always + 항상 + + + CPU CPU - + GPU GPU - + CPU Asynchronous - + 비동기 CPU - + Uncompressed (Best quality) 비압축(최고 품질) - + BC1 (Low quality) BC1(저품질) - + BC3 (Medium quality) BC3(중간 품질) - - + + Auto 자동 - + 30 FPS - + 30 FPS - + 60 FPS - + 60 FPS - + 90 FPS - - - - - 120 FPS - + 90 FPS - Conservative - + 120 FPS + 120 FPS - - Aggressive - + + Conservative + 소극적 - Vulkan - Vulcan - - - - OpenGL GLSL - - - - - OpenGL GLASM (Assembly Shaders, NVIDIA Only) - + Aggressive + 적극적 - OpenGL SPIR-V (Experimental, AMD/Mesa Only) - + Vulkan + Vulkan + OpenGL GLSL + OpenGL GLSL + + + + OpenGL GLASM (Assembly Shaders, NVIDIA Only) + OpenGL GLASM(어셈블리 셰이더, NVIDIA 전용) + + + + OpenGL SPIR-V (Experimental, AMD/Mesa Only) + OpenGL SPIR-V(실험적, AMD/Mesa 전용) + + + Null Null - + Fast - + 고속 - - Balanced - - - - - + + + Accurate - 정확함 + 정확 - - + + + Default 기본값 - + Unsafe (fast) - + 불안전(빠름) - + Safe (stable) - - - - - Unsafe - 최적화 (안전하지 않음) + 안전(안정적) - Paranoid (disables most optimizations) - 편집증(대부분의 최적화 비활성화) + Immediate + 즉시 - Debugging - + Balanced + 균형 - - Dynarmic - - - - - NCE - + + Unsafe + 불안전 - Borderless Windowed - 경계 없는 창 모드 + Paranoid (disables most optimizations) + 편집증적(대부분의 최적화를 비활성화함) - Exclusive Fullscreen - 독점 전체화면 모드 + Debugging + 디버깅 + Dynarmic + 동적 + + + + NCE + NCE + + + + Borderless Windowed + 테두리 없는 창 + + + + Exclusive Fullscreen + 독점 전체 화면 + + + No Video Output 비디오 출력 없음 - + CPU Video Decoding CPU 비디오 디코딩 - + GPU Video Decoding (Default) GPU 비디오 디코딩(기본값) - + 0.25X (180p/270p) [EXPERIMENTAL] - + 0.25X (180p/270p) [실험적] - + 0.5X (360p/540p) [EXPERIMENTAL] - + 0.5X (360p/540p) [실험적] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [실험적] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] - + 1.25X (900p/1350p) [실험적] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [실험적] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor - 최근접 보간 + 최단입점 - + Bilinear - Bilinear + 쌍선형 - + Bicubic - Bicubic + 쌍입방 - + Gaussian 가우시안 - + Lanczos - + 란초스 - + ScaleForce ScaleForce - - - AMD FidelityFX Super Resolution - - - - - Area - - - - - MMPX - - - - - Zero-Tangent - - - - - B-Spline - - - - - Mitchell - - - - - Spline-1 - - - + AMD FidelityFX Super Resolution + AMD FidelityFX Super Resolution + + + + Area + 영역 + + + + MMPX + MMPX + + + + Zero-Tangent + 제로탄젠트 + + + + B-Spline + B-스플라인 + + + + Mitchell + 미첼 + + + + Spline-1 + 스플라인-1 + + + + Snapdragon Game Super Resolution + Snapdragon Game Super Resolution + + + + Snapdragon Game Super Resolution EdgeDir + Snapdragon Game Super Resolution EdgeDir + + + None 없음 - + FXAA FXAA - + SMAA SMAA - + Default (16:9) - 기본 (16:9) + 기본(16:9) - + Force 4:3 강제 4:3 - + Force 21:9 강제 21:9 - + Force 16:10 강제 16:10 - + Stretch to Window - 창에 맞게 늘림 + 화면 늘리기 - + Automatic 자동 - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - - - - - 64x - - - - + Japanese (日本語) - 일본어 (日本語) + 일본어(日本語) - + American English - 미국 영어 + 미국식 영어 - + French (français) 프랑스어(français) - + German (Deutsch) 독일어(Deutsch) - + Italian (italiano) 이탈리아어(italiano) - + Spanish (español) 스페인어(español) - + Chinese 중국어 - + Korean (한국어) - 한국어 (Korean) + 한국어(Korean) - + Dutch (Nederlands) - 네덜란드어 (Nederlands) + 네덜란드어(Nederlands) - + Portuguese (português) 포르투갈어(português) - + Russian (Русский) - 러시아어 (Русский) + 러시아어(Русский) - + Taiwanese 대만어 - + British English - 영어 (British English) + 영국식 영어 - + Canadian French 캐나다 프랑스어 - + Latin American Spanish - 라틴 아메리카 스페인어 + 라틴아메리카 스페인어 - + Simplified Chinese - 간체 + 중국어 간체 - + Traditional Chinese (正體中文) - 중국어 번체 (正體中文) + 중국어 번체(正體中文) - + Brazilian Portuguese (português do Brasil) 브라질 포르투갈어(português do Brasil) - + Polish (polska) - + 폴란드어(polska) - + Thai (แบบไทย) - + 태국어(แบบไทย) - - + + Japan 일본 - + USA 미국 - + Europe 유럽 - + Australia 호주 - + China 중국 - + Korea 대한민국 - + Taiwan 대만 - + Auto (%1) Auto select time zone - 자동 (%1) + 자동(%1) - + Default (%1) Default time zone - 기본 (%1) + 기본(%1) - + CET - 중앙유럽 표준시(CET) + CET - + CST6CDT CST6CDT - + Cuba 쿠바 - + EET - 동유럽 표준시(EET) + EET - + Egypt 이집트 - + Eire - Eire + 아일랜드 - + EST EST - + EST5EDT EST5EDT - + GB - 영국 하계 표준시(GB) + 영국 - + GB-Eire - GB-Eire + 영국-아일랜드 - + GMT - 그리니치 표준시(GMT) + GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich 그리니치 - + Hongkong 홍콩 - + HST - 하와이-알류샨 표준시(HST) + HST - + Iceland 아이슬란드 - + Iran 이란 - + Israel 이스라엘 - + Jamaica 자메이카 - + Kwajalein - 크와잘린 + 콰잘레인 - + Libya 리비아 - + MET - 중앙유럽 표준시(MET) + MET - + MST - 산악 표준시(MST) + MST - + MST7MDT MST7MDT - + Navajo 나바호 - + NZ - 뉴질랜드 표준시(NZ) + 뉴질랜드 - + NZ-CHAT - 채텀 표준시(NZ-CHAT) + 뉴질랜드-채텀 - + Poland 폴란드 - + Portugal 포르투갈 - + PRC - PRC + 중국 - + PST8PDT PST8PDT - + ROC - ROC + 대만 - + ROK - 북한 표준시(ROK) + 대한민국 - + Singapore 싱가포르 - + Turkey 터키 - + UCT UCT - + Universal - Universal + 세계 - + UTC - 협정 세계시(UTC) + UTC - + W-SU - 유럽/모스크바(W-SU) + 모스크바 - + WET - 서유럽 + WET - + Zulu - 줄루 + Zulu - + Mono 모노 - + Stereo 스테레오 - + Surround 서라운드 - - - 4GB DRAM (Default) - - - - - 6GB DRAM (Unsafe) - - - - - 8GB DRAM - - - - - 10GB DRAM (Unsafe) - - - - - 12GB DRAM (Unsafe) - - - - - Docked - 거치 모드 - + 4GB DRAM (Default) + 4GB DRAM(기본값) + + + + 6GB DRAM (Unsafe) + 6GB DRAM(불안전) + + + + 8GB DRAM + 8GB DRAM + + + + 10GB DRAM (Unsafe) + 10GB DRAM(불안전) + + + + 12GB DRAM (Unsafe) + 12GB DRAM(불안전) + + + + Docked + 독 모드 + + + Handheld 휴대 모드 - - - - Off - - - - - Boost (1700MHz) - - - - - Fast (2000MHz) - - - - - Always ask (Default) - - - - - Only if game specifies not to stop - - - Never ask - - - - - - Medium (256) - - - - - - High (512) - - - - - Very Small (16 MB) - - - - - Small (32 MB) - - - - - Normal (128 MB) - - - - Large (256 MB) - + Normal + 보통 + - Very Large (512 MB) - + Boost + 부스트 - - Very Low (4 MB) - + + + Overclock + 오버클럭 + + + + Always ask (Default) + 항상 묻기(기본값) + + + + Only if game specifies not to stop + 게임에서 멈추지 말라고 명시한 경우에만 + + + + Never ask + 절대 묻지 않음 - Low (8 MB) - + Very Small (16 MB) + 아주 작은 크기(16 MB) - Normal (16 MB) - + Small (32 MB) + 작은 크기(32 MB) - Medium (32 MB) - + Normal (128 MB) + 보통 크기(128 MB) - High (64 MB) - + Large (256 MB) + 큰 크기(256 MB) - - Very Low (32) - + + Very Large (512 MB) + 아주 큰 크기(512 MB) - Low (64) - + Very Low (4 MB) + 매우 낮은 크기(4 MB) - Normal (128) - + Low (8 MB) + 낮은 크기(8 MB) + + + + Normal (16 MB) + 보통 크기(16 MB) + + + + Medium (32 MB) + 중간 크기(32 MB) + + + + High (64 MB) + 높은 크기(64 MB) - Disabled - + Very Low (32) + 매우 낮은 크기(32) - ExtendedDynamicState 1 - + Low (64) + 낮은 크기(64) - ExtendedDynamicState 2 - + Normal (128) + 보통 크기(128) + Medium (256) + 보통(256) + + + + High (512) + 빠름(512) + + + + ExtendedDynamicState 1 + 확장된 동적 상태 1 + + + + ExtendedDynamicState 2 + 확장된 동적 상태 2 + + + ExtendedDynamicState 3 - + 확장된 동적 상태 3 - + Tree View - + 트리 보기 - + Grid View - + 격자 보기 @@ -2212,17 +2166,17 @@ When a program attempts to open the controller applet, it is immediately closed. Form - 형태 + 형식 Applets - + 애플릿 Applet mode preference - + 애플릿 모드 선호도 @@ -2274,7 +2228,7 @@ When a program attempts to open the controller applet, it is immediately closed. Restore Defaults - 기본값으로 초기화 + 기본값 복원 @@ -2287,7 +2241,7 @@ When a program attempts to open the controller applet, it is immediately closed. Form - Form + 유형 @@ -2307,17 +2261,17 @@ When a program attempts to open the controller applet, it is immediately closed. CPU Backend - + CPU 백엔드 Unsafe CPU Optimization Settings - 안전하지 않은 CPU 최적화 설정 + 불안전 CPU 최적화 설정 These settings reduce accuracy for speed. - 이 설정은 속도를 위해 정확도를 떨어뜨립니다. + 이러한 설정은 속도를 위해 정확도를 낮춥니다. @@ -2325,7 +2279,7 @@ When a program attempts to open the controller applet, it is immediately closed. Form - 포럼 + 유형 @@ -2340,7 +2294,7 @@ When a program attempts to open the controller applet, it is immediately closed. <html><head/><body><p><span style=" font-weight:600;">For debugging only.</span><br/>If you're not sure what these do, keep all of these enabled. <br/>These settings, when disabled, only take effect when CPU Debugging is enabled. </p></body></html> - <html><head/><body><p><span style=" font-weight:600;">디버깅 전용</span><br/>이것들이 무엇을 하는지 잘 모르겠다면, 이것들을 모두 활성화 상태로 두십시오.<br/>비활성화된 경우 이러한 설정은 CPU 디버깅이 활성화된 경우에만 적용됩니다.</p></body></html> + <html><head/><body><p><span style=" font-weight:600;">디버깅 전용</span><br/>이러한 설정들이 무엇을 하는지 잘 모르겠다면, 모두 활성화 상태로 두세요.<br/>비활성화된 경우 이러한 설정은 CPU 디버깅이 활성화된 경우에만 적용됩니다.</p></body></html> @@ -2350,9 +2304,9 @@ When a program attempts to open the controller applet, it is immediately closed. <div style="white-space: nowrap">Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.</div> - <div style="white-space: nowrap">이 최적화는 게스트 프로그램에 의한 메모리 접근 속도를 높입니다. </div> - <div style="white-space: nowrap">이 기능을 활성화하면 페이지 테이블::pointers to pageTable:exceed code에 대한 액세스가 실행됩니다.</div> - <div style="white-space: nowrap">이것을 비활성화 하면 모든 메모리 접근이Memory::Read/Memory::Write functions를 겪게됩니다.</div> + <div style="white-space: nowrap">이 최적화는 게스트 프로그램의 메모리 접근 속도를 높입니다.</div> + <div style="white-space: nowrap">이 기능을 활성화하면 PageTable::pointers에 대한 접근이 출력된 코드에 인라인으로 포함됩니다.</div> + <div style="white-space: nowrap">이 기능을 비활성화하면 모든 메모리 접근이 Memory::Read/Memory::Write 함수를 통해 이루어지게 됩니다.</div> @@ -2386,7 +2340,7 @@ When a program attempts to open the controller applet, it is immediately closed. Enable return stack buffer - 리턴 스택 버퍼 활성화 + 반환 스택 버퍼 활성화 @@ -2394,7 +2348,7 @@ When a program attempts to open the controller applet, it is immediately closed. <div>Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.</div> - <div>2계층 디스패치 시스템을 활성화합니다. 어셈블리로 작성된 더 빠른 디스패처에는 점프 대상의 작은 MRU 캐시가 먼저 사용됩니다. 실패하면 디스패치는 더 느린 C ++ 디스패처로 대체됩니다.</div> + <div>이중 계층 디스패치 시스템을 활성화합니다. 어셈블리로 작성된 더 빠른 디스패처는 점프 대상의 MRU 캐시 크기가 작으므로 먼저 사용됩니다. 만약 이 디스패처가 실패하면, 더 느린 C++ 디스패처로 되돌아갑니다.</div> @@ -2408,13 +2362,13 @@ When a program attempts to open the controller applet, it is immediately closed. <div>Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.</div> - <div>CPU 컨텍스트 구조에 대한 불필요한 액세스를 줄이는 IR 최적화를 활성화합니다.</div> + <div>CPU 콘텍스트 구조에 대한 불필요한 액세스를 줄이는 IR 최적화를 활성화합니다.</div> Enable context elimination - 컨텍스트 제거 활성화 + 콘텍스트 제거 활성화 @@ -2503,9 +2457,8 @@ When a program attempts to open the controller applet, it is immediately closed. <div style="white-space: nowrap">Enabling it reduces the overhead of fastmem failure of exclusive memory accesses.</div> - - <div style="white-space: nowrap">이 최적화는 게스트 프로그램의 독점 메모리 액세스 속도를 높입니다.</div> - <div style="white-space: nowrap">활성화하면 독점 메모리 액세스의 fastmem 실패 오버헤드가 줄어듭니다.</div> + <div style="white-space: nowrap">이 최적화는 게스트 프로그램의 전용 메모리 접근 속도를 향상시킵니다.</div> + <div style="white-space: nowrap">이 옵션을 활성화하면 전용 메모리 접근 시 패스트멤 실패로 인한 오버헤드가 줄어듭니다.</div> @@ -2563,254 +2516,299 @@ When a program attempts to open the controller applet, it is immediately closed. 전역 로그 필터 - - When checked, the max size of the log increases from 100 MB to 1 GB - 이 옵션을 활성화 시, 로그 파일의 최대 용량이 100MB에서 1GB로 증가합니다. + + GPU Logging/Level + GPU 로깅/레벨 - - Enable Extended Logging** - 확장된 로깅 활성화** + + Detail level for GPU logs. Off disables logging entirely. + GPU 로그의 상세 수준입니다. 끄면 로깅이 완전히 비활성화됩니다. - + + Off + 끔 + + + + Errors + 오류 + + + + Standard + 기본 + + + + Verbose + 상세 + + + + All + 전체 + + + Show Log in Console 콘솔에 로그 표시 - + + When checked, the max size of the log increases from 100 MB to 1 GB + 이 옵션을 활성화 시, 로그 파일의 최대 용량이 100MB에서 1GB로 증가합니다. + + + + Enable Extended Logging** + 확장된 로깅 활성화** + + + Open Log Location - 로그 경로 열기 + 로그 위치 열기 - + Homebrew - 홈브류 + 홈브루 - + Arguments String - 실행인수 + 인수 문자열 - + Graphics 그래픽 - + When checked, it executes shaders without loop logic changes - 체크 시 루프 로직 변경 없이 셰이더 실행 + 이 옵션을 선택하면 루프 로직 변경 없이 셰이더를 실행합니다 - + Disable Loop safety checks 루프 안전 검사 비활성화 - + When checked, it will dump all the macro programs of the GPU - 체크하면 GPU의 모든 매크로 프로그램을 덤프합니다. + 이 옵션을 선택하면 GPU의 모든 매크로 프로그램이 덤프됩니다 - + Dump Maxwell Macros Maxwell 매크로 덤프 - + When checked, it enables Nsight Aftermath crash dumps - 선택하면 Nsight Aftermath 크래시 덤프가 활성화됩니다. + 이 옵션을 선택하면 Nsight Aftermath 크래시 덤프가 활성화됩니다 - + Enable Nsight Aftermath Nsight Aftermath 활성화 - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - 선택하면 디스크 셰이더 캐시 또는 게임에서 찾은 모든 원본 어셈블러 셰이더를 덤프합니다. + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + 이 옵션을 선택하면 원래 Maxwell 게스트 셰이더 바이트코드(재컴파일러에 대한 입력)가 DumpDir/shaders/ 아래에 .ash 파일로 덤프됩니다. nvdisasm로 검사하는 데 유용합니다. - - Dump Game Shaders - 게임 셰이더 덤프 + + Dump Guest (Maxwell) Shaders + 게스트(Maxwell) 셰이더 덤프 - + Enable Renderdoc Hotkey - + Renderdoc 단축키 활성화 - + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower - 이 옵션에 체크할 시, 매크로 JIT 컴파일러를 비활성화 합니다. 게임 속도가 느려지니 유의하십시오. + 이 옵션을 선택하면 Just In Time 매크로 컴파일러가 비활성화됩니다. 이 옵션을 활성화하면 게임 실행 속도가 느려집니다 - + Disable Macro JIT Macro JIT 비활성화 - + When checked, it disables the macro HLE functions. Enabling this makes games run slower - 선택하면 매크로 HLE 기능이 비활성화됩니다. 이 기능을 활성화하면 게임 실행 속도가 느려짐 + 이 옵션을 선택하면 매크로 HLE 기능이 비활성화됩니다. 이 옵션을 활성화하면 게임 실행 속도가 느려집니다 - + Disable Macro HLE 매크로 HLE 비활성화 - + When checked, the graphics API enters a slower debugging mode - 이 옵션을 활성화 시, 그래픽 API가 디버깅 모드로 진입하여 속도가 느려집니다. + 이 옵션을 선택하면 그래픽 API가 느린 디버깅 모드로 전환됩니다 - + Enable Graphics Debugging 그래픽 디버깅 활성화 - + When checked, yuzu will log statistics about the compiled pipeline cache - 선택하면 yuzu는 컴파일된 파이프라인 캐시에 대한 통계를 기록합니다. + 이 옵션을 선택하면 yuzu는 컴파일된 파이프라인 캐시에 대한 통계를 로그에 기록합니다 - + Enable Shader Feedback 셰이더 피드백 활성화 - + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> - + <html><head/><body><p>이 옵션을 선택하면 매핑된 메모리 업로드의 재배열 기능이 비활성화되어 특정 드로우와 업로드를 연결할 수 있습니다. 경우에 따라 성능이 저하될 수 있습니다.</p></body></html> - + Disable Buffer Reorder - + 버퍼 재배열 비활성화 - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + 이 옵션을 선택하면 재컴파일러의 출력 SPIR-V 바이너리(.spv)가 LogDir/shaders/ 아래에 덤프됩니다. SPIRV-Tools(spirv-dis / spirv-cross / spirv-val) 또는 RenderDoc을 통해 검사합니다. + + + + Dump SPIR-V Shaders + SPIR-V 셰이더 덤프 + + + Advanced 고급 - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. - 프로그램 시작시 yuzu가 Vulkan 환경을 확인할 수 있도록 합니다. 외부 프로그램에서 유자를 보는 데 문제가 있는 경우 이 기능을 비활성화합니다. + 프로그램이 시작될 때 yuzu가 Vulkan 환경이 제대로 작동하는지 확인하도록 설정합니다. 외부 프로그램에서 yuzu를 인식하는 데 문제가 발생하는 경우 이 옵션을 비활성화하세요. - + Perform Startup Vulkan Check 시작시 Vulkan 검사 수행 - + Disable Web Applet 웹 애플릿 비활성화 - + Enable All Controller Types 모든 컨트롤러 유형 활성화 - + Enable Auto-Stub - + 자동 스텁 활성화 - + Kiosk (Quest) Mode - Kiosk (Quest) 모드 + 키오스크(퀘스트) 모드 - + Use dev.keys - + 개발자 키 사용 - + Enable Debug Asserts 디버그 에러 검출 활성화 - + Debugging 디버깅 - + Battery Serial: - + 배터리 시리얼: - + + Debug Knobs: + 디버그 노브: + + + Bitmask for quick development toggles - + 빠른 개발을 위한 비트마스크 토글 - + Set debug knobs (bitmask) - + 디버그 노브 설정(비트마스크) - + 16-bit debug knob set for quick development toggles - + 빠른 개발을 위한 16비트 디버그 노브 설정 토글 - + (bitmask) - + (비트마스크) - - Debug Knobs: - - - - + Unit Serial: - + 장치 시리얼: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. 이 옵션을 활성화하면 가장 최근에 생성된 오디오 명령어 목록을 콘솔에 출력할 수 있습니다. 오디오 렌더러를 사용하는 게임에만 영향을 줍니다. - + Dump Audio Commands To Console** 콘솔에 오디오 명령어 덤프 - + Flush log output on each line - + 각 줄의 로그 출력을 플러시합니다 - + Enable FS Access Log FS 액세스 로그 활성화 - + Enable Verbose Reporting Services** 자세한 리포팅 서비스 활성화** - + Censor username in logs - + 로그에서 사용자 이름 검열 - + **This will be reset automatically when Eden closes. - + **이 설정은 Eden이 닫히면 자동으로 재설정됩니다. @@ -2818,12 +2816,12 @@ When a program attempts to open the controller applet, it is immediately closed. Configure Debug Controller - 디버그 컨트롤러 설정 + 디버그 컨트롤러 구성 Clear - 비우기 + 지우기 @@ -2836,7 +2834,7 @@ When a program attempts to open the controller applet, it is immediately closed. Form - 포럼 + 형식 @@ -2855,7 +2853,7 @@ When a program attempts to open the controller applet, it is immediately closed. Eden Configuration - + Eden 구성 @@ -2865,7 +2863,7 @@ When a program attempts to open the controller applet, it is immediately closed. Applets - + 애플릿 @@ -2909,7 +2907,7 @@ When a program attempts to open the controller applet, it is immediately closed. GraphicsExtra - + 그래픽 별도 기능 @@ -2954,7 +2952,7 @@ When a program attempts to open the controller applet, it is immediately closed. Form - Form + 형식 @@ -2964,7 +2962,7 @@ When a program attempts to open the controller applet, it is immediately closed. Storage Directories - 저장소 + 저장소 디렉터리 @@ -2989,7 +2987,7 @@ When a program attempts to open the controller applet, it is immediately closed. Save Data - + 저장 데이터 @@ -2999,12 +2997,12 @@ When a program attempts to open the controller applet, it is immediately closed. Path - 주소 + 경로 Inserted - 삽입 + 삽입됨 @@ -3029,7 +3027,7 @@ When a program attempts to open the controller applet, it is immediately closed. Mod Load Root - 모드 경로 + 모드 불러오기 경로 @@ -3054,53 +3052,53 @@ When a program attempts to open the controller applet, it is immediately closed. Select Emulated NAND Directory... - 가상 NAND 경로 선택 + 에뮬레이트된 NAND 디렉터리 선택... Select Emulated SD Directory... - 가상 SD 경로 선택 + 에뮬레이트된 SD 디렉터리 선택... Select Save Data Directory... - + 저장 데이터 디렉터리 선택... Select Gamecard Path... - 게임카드 경로 설정 + 게임카드 경로 선택... Select Dump Directory... - 덤프 경로 설정 + 덤프 디렉터리 선택... Select Mod Load Directory... - 모드 불러오기 경로 설정 + 모드 불러오기 디렉터리 선택... Save Data Directory - + 저장 데이터 디렉터리 Choose an action for the save data directory: - + 저장 데이터 디렉터리에 대한 작업을 선택하세요: Set Custom Path - + 사용자 지정 경로 설정 Reset to NAND - + NAND로 재설정 @@ -3111,7 +3109,13 @@ New: %2 Would you like to migrate saves from the old location? WARNING: This will overwrite any conflicting saves in the new location! - + 저장 데이터는 이전 위치와 새 위치 모두에 있습니다. + +이전 위치: %1 +새 위치: %2 + +이전 위치의 저장 데이터를 옮기겠습니까? +경고: 새 위치에 있는 충돌하는 저장 데이터는 덮어쓰여집니다! @@ -3119,51 +3123,57 @@ WARNING: This will overwrite any conflicting saves in the new location! From: %1 To: %2 - + 저장 데이터를 새 위치로 옮기겠습니까? + +원본: %1 +대상: %2 Migrate Save Data - + 저장 데이터 마이그레이션 Migrating save data... - + 저장 데이터를 마이그레이션하는 중... Cancel - + 취소 Migration Failed - + 마이그레이션 실패 Failed to create destination directory. - + 대상 디렉터리를 생성하는 데 실패했습니다. Failed to migrate save data: %1 - + 저장 데이터 마이그레이션에 실패했습니다: +%1 Migration Complete - + 마이그레이션 완료 Save data has been migrated successfully. Would you like to delete the old save data? - + 저장 데이터 마이그레이션이 성공적으로 완료되었습니다. + +이전 저장 데이터를 삭제하겠습니까? @@ -3171,7 +3181,7 @@ Would you like to delete the old save data? Form - 형태 + 유형 @@ -3182,22 +3192,22 @@ Would you like to delete the old save data? External Content - + 외부 콘텐츠 Add directories to scan for DLCs and Updates without installing to NAND - + NAND에 설치하지 않고 DLC 및 업데이트를 검색할 디렉터리를 추가합니다 Add Directory - + 디렉터리 추가 Remove Selected - + 선택한 항목 제거 @@ -3207,27 +3217,27 @@ Would you like to delete the old save data? Eden - + Eden This reset all settings and remove all per-game configurations. This will not delete game directories, profiles, or input profiles. Proceed? - 모든 환경 설정과 게임별 맞춤 설정이 초기화됩니다. 게임 디렉토리나 프로필, 또는 입력 프로필은 삭제되지 않습니다. 진행하시겠습니까? + 이렇게 하면 모든 설정이 초기화되고 게임별 구성이 모두 제거됩니다. 게임 디렉터리, 프로필 또는 입력 프로필은 삭제되지 않습니다. 계속하겠습니까? Select External Content Directory... - + 외부 콘텐츠 디렉터리 선택... Directory Already Added - + 디렉터리가 이미 추가됨 This directory is already in the list. - + 이 디렉터리는 이미 목록에 있습니다. @@ -3235,7 +3245,7 @@ Would you like to delete the old save data? Form - Form + 유형 @@ -3245,7 +3255,7 @@ Would you like to delete the old save data? API Settings - API 설정 + API 설정 @@ -3258,35 +3268,40 @@ Would you like to delete the old save data? 배경색: - + % - FSR sharpening percentage (e.g. 50%) + FSR/SGSR sharpening percentage (e.g. 50%) % - + + Configure Effects... + + + + Off 끔 - + VSync Off - 수직동기화 끔 + 수직 동기화 끔 - + Recommended - 추천 + 권장 - + On 켬 - + VSync On - 수직동기화 켬 + 수직 동기화 켬 @@ -3294,7 +3309,7 @@ Would you like to delete the old save data? Form - Form + 유형 @@ -3312,38 +3327,38 @@ Would you like to delete the old save data? Form - + 유형 Extras - + 별도 기능 Hacks - + 핵 Changing these options from their default may cause issues. Novitii cavete! - + 이러한 옵션을 기본값에서 변경하면 문제가 발생할 수 있습니다. Novitii cavete! Vulkan Extensions - + Vulkan 확장 기능 % Sample Shading percentage (e.g. 50%) - + % Extended Dynamic State is disabled on macOS due to MoltenVK compatibility issues that cause black screens. - + MoltenVK 호환성 문제로 인해 검은 화면이 발생하는 macOS에서는 확장된 동적 상태가 비활성화되어 있습니다. @@ -3366,12 +3381,12 @@ Would you like to delete the old save data? Clear All - 모두 비우기 + 모두 지우기 Restore Defaults - 초기화 + 기본값 복원 @@ -3399,7 +3414,7 @@ Would you like to delete the old save data? The entered key sequence is already assigned to: %1 - 입력한 키 시퀀스가 %1에 이미 할당되었습니다. + 입력된 키 시퀀스는 이미 다음에 할당되었습니다: %1 @@ -3409,27 +3424,27 @@ Would you like to delete the old save data? Invalid - 유효하지않음 + 무효 Invalid hotkey settings - + 유효하지 않은 단축키 설정 An error occurred. Please report this issue on github. - + 오류가 발생했습니다. 이 문제를 깃허브에 신고해 주세요. Restore Default - 초기화 + 기본값 복원 Clear - 비우기 + 지우기 @@ -3439,12 +3454,12 @@ Would you like to delete the old save data? The default button sequence is already assigned to: %1 - 기본 키 시퀀스가 %1에 이미 할당되었습니다. + 기본 버튼 시퀀스가 이미 다음에 할당되었습니다: %1 The default key sequence is already assigned to: %1 - 기본 키 시퀀스가 %1에 이미 할당되었습니다. + 기본 키 시퀀스가 이미 다음에 할당되었습니다: %1 @@ -3452,7 +3467,7 @@ Would you like to delete the old save data? ConfigureInput - 입력 설정 + 입력 구성 @@ -3516,7 +3531,7 @@ Would you like to delete the old save data? Docked - 거치 모드 + 독 모드 @@ -3532,12 +3547,12 @@ Would you like to delete the old save data? Configure - 설정 + 구성 Motion - 모션 컨트롤 + 모션 @@ -3597,7 +3612,7 @@ Would you like to delete the old save data? Clear - 비우기 + 지우기 @@ -3605,7 +3620,7 @@ Would you like to delete the old save data? Configure Input - 입력 설정 + 입력 구성 @@ -3718,7 +3733,7 @@ Would you like to delete the old save data? Touchscreen - 터치 스크린 + 터치스크린 @@ -3736,7 +3751,7 @@ Would you like to delete the old save data? Configure - 설정 + 구성 @@ -3763,7 +3778,7 @@ Would you like to delete the old save data? Requires restarting Eden - + Eden 재시작 필요 @@ -3788,22 +3803,32 @@ Would you like to delete the old save data? Enable direct Pro Controller driver [EXPERIMENTAL] - 다이렉트 Pro 컨트롤러 드라이버 활성화[실험적]  + 다이렉트 Pro 컨트롤러 드라이버 활성화[실험적] Allows unlimited uses of the same Amiibo in games that would otherwise limit you to one use. - 한 번으로 사용이 제한되는 게임에서 동일한 아미보를 무제한으로 사용할 수 있습니다. + 일반적으로는 한 번만 사용할 수 있는 동일한 Amiibo를 게임에서 무제한으로 사용할 수 있게 해줍니다. Use random Amiibo ID - 무작위 아미보 ID 사용 + 무작위 Amiibo ID 사용 Motion / Touch - 모션 컨트롤/ 터치 + 모션 / 터치 + + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + SDL GUIDE 버튼 해킹을 비활성화하는 것을 목표로 합니다: SELECT(-) + START(+) 버튼을 동시에 눌렀을 때 GUIDE(HOME) 이벤트를 인위적으로 발생시킵니다. Win 관련 트리거/진동 기능 등에 영향을 줄 수 있습니다. + + + + Disable SDL WGI/XInput (Requires restart) + SDL WGI/XInput 비활성화(재시작 필요) @@ -3811,7 +3836,7 @@ Would you like to delete the old save data? Form - 형태 + 형식 @@ -3821,47 +3846,47 @@ Would you like to delete the old save data? Input Profiles - 입력 프로파일 + 입력 프로필 Player 1 Profile - 플레이어 1 프로파일 + 플레이어 1 프로필 Player 2 Profile - 플레이어 2 프로파일 + 플레이어 2 프로필 Player 3 Profile - 플레이어 3 프로파일 + 플레이어 3 프로필 Player 4 Profile - 플레이어 4 프로파일 + 플레이어 4 프로필 Player 5 Profile - 플레이어 5 프로파일 + 플레이어 5 프로필 Player 6 Profile - 플레이어 6 프로파일 + 플레이어 6 프로필 Player 7 Profile - 플레이어 7 프로파일 + 플레이어 7 프로필 Player 8 Profile - 플레이어 8 프로파일 + 플레이어 8 프로필 @@ -3871,7 +3896,7 @@ Would you like to delete the old save data? Player %1 profile - 플레이어 %1 프로파일 + 플레이어 %1 프로필 @@ -3879,7 +3904,7 @@ Would you like to delete the old save data? Configure Input - 입력 설정 + 입력 구성 @@ -3925,7 +3950,7 @@ Would you like to delete the old save data? Down - 아래쪽 + 아래 @@ -3957,7 +3982,7 @@ Would you like to delete the old save data? Up - 위쪽 + 위 @@ -4002,7 +4027,7 @@ Would you like to delete the old save data? D-Pad - D-패드 + 십자키 @@ -4082,7 +4107,7 @@ Would you like to delete the old save data? Home - 홈 + HOME @@ -4127,7 +4152,7 @@ Would you like to delete the old save data? Configure - 설정 + 구성 @@ -4135,7 +4160,7 @@ Would you like to delete the old save data? Clear - 초기화 + 지우기 @@ -4168,7 +4193,7 @@ Would you like to delete the old save data? Invert axis - 축 뒤집기 + 축 반전 @@ -4186,7 +4211,7 @@ Would you like to delete the old save data? Toggle axis - axis 토글 + 축 토글 @@ -4207,8 +4232,8 @@ Would you like to delete the old save data? After pressing OK, first move your joystick horizontally, and then vertically. To invert the axes, first move your joystick vertically, and then horizontally. - OK 버튼을 누른 후에 먼저 조이스틱을 수평으로 움직이고, 그 다음 수직으로 움직이세요. -축을 뒤집으려면 수직으로 먼저 움직인 뒤에 수평으로 움직이세요. + 확인 버튼을 누른 후, 먼저 조이스틱을 수평으로 움직인 다음 수직으로 움직이세요. +축을 반전시키려면 먼저 조이스틱을 수직으로 움직인 다음 수평으로 움직이세요. @@ -4231,7 +4256,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< Pro Controller - 프로 컨트롤러 + Pro 컨트롤러 @@ -4301,7 +4326,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< C-Stick - C-Stick + C스틱 @@ -4314,58 +4339,58 @@ To invert the axes, first move your joystick vertically, and then horizontally.< [대기중] - + New Profile 새 프로필 - + Enter a profile name: 프로필 이름을 입력하세요: - - + + Create Input Profile 입력 프로필 생성 - + The given profile name is not valid! - 해당 프로필 이름은 사용할 수 없습니다! + 지정된 프로필 이름이 유효하지 않습니다! - + Failed to create the input profile "%1" "%1" 입력 프로필 생성 실패 - + Delete Input Profile 입력 프로필 삭제 - + Failed to delete the input profile "%1" "%1" 입력 프로필 삭제 실패 - + Load Input Profile 입력 프로필 불러오기 - + Failed to load the input profile "%1" "%1" 입력 프로필 불러오기 실패 - + Save Input Profile 입력 프로필 저장 - + Failed to save the input profile "%1" "%1" 입력 프로필 저장 실패 @@ -4380,7 +4405,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< Clear - 초기화 + 지우기 @@ -4393,7 +4418,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< Configure Motion / Touch - 모션 설정 / 터치 + 모션 / 터치 구성 @@ -4408,14 +4433,14 @@ To invert the axes, first move your joystick vertically, and then horizontally.< (100, 50) - (1800, 850) - (100, 50) - (1800, 850) + (100, 50) ~ (1800, 850) - + Configure - 설정 + 구성 @@ -4425,12 +4450,12 @@ To invert the axes, first move your joystick vertically, and then horizontally.< CemuhookUDP Config - CemuhookUDP 설정 + CemuhookUDP 구성 You may use any Cemuhook compatible UDP input source to provide motion and touch input. - Cemuhook 호환이 가능한 모든 UDP 인풋 장치를 모션 컨트롤과 터치 입력용으로 사용할 수 있습니다. + 모션 및 터치 입력을 제공하기 위해 Cemuhook과 호환되는 모든 UDP 입력 소스를 사용할 수 있습니다. @@ -4444,7 +4469,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Test 테스트 @@ -4459,79 +4484,79 @@ To invert the axes, first move your joystick vertically, and then horizontally.< 원격 서버 - + %1:%2 %1:%2 - - - - - - + + + + + + Eden - + Eden - + Port number has invalid characters 포트 번호에 유효하지 않은 글자가 있습니다. - + Port has to be in range 0 and 65353 포트 번호는 0부터 65353까지이어야 합니다. - + IP address is not valid IP 주소가 유효하지 않습니다. - + This UDP server already exists 해당 UDP 서버는 이미 존재합니다. - + Unable to add more than 8 servers 8개보다 많은 서버를 추가하실 수는 없습니다. - + Testing 테스트 중 - + Configuring - 설정 중 + 구성 중 - + Test Successful 테스트 성공 - + Successfully received data from the server. - 서버에서 성공적으로 데이터를 받았습니다. + 서버에서 데이터를 성공적으로 수신했습니다. - + Test Failed 테스트 실패 - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. - 서버에서 유효한 데이터를 수신할 수 없습니다.<br>서버가 올바르게 설정되어 있고 주소와 포트가 올바른지 확인하십시오. + 서버에서 유효한 데이터를 수신할 수 없습니다.<br>서버 설정이 올바르게 되어 있는지, 주소와 포트가 정확한지 확인해 주세요. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. - UDP 테스트와 교정 설정이 진행 중입니다.<br>끝날 때까지 기다려주세요. + UDP 테스트 또는 보정 구성이 진행 중입니다.<br>완료될 때까지 기다려 주세요. @@ -4539,7 +4564,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< Configure mouse panning - 마우스 패닝 설정 + 마우스 패닝 구성 @@ -4549,7 +4574,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< Can be toggled via a hotkey. Default hotkey is Ctrl + F9 - 핫키를 통해 전환할 수 있습니다. 기본 핫키는 Ctrl + F9입니다 + 단축키를 통해 토글할 수 있습니다. 기본 단축키는 Ctrl + F9입니다. @@ -4619,17 +4644,17 @@ Current values are %1% and %2% respectively. Emulated mouse is enabled. This is incompatible with mouse panning. - 에뮬레이트 마우스가 활성화되었습니다. 이것은 마우스 패닝과 호환되지 않습니다. + 에뮬레이트된 마우스가 활성화되었습니다. 이것은 마우스 패닝과 호환되지 않습니다. Emulated mouse is enabled - 에뮬레이트 마우스 사용 + 에뮬레이트된 마우스가 활성화됨 Real mouse input and mouse panning are incompatible. Please disable the emulated mouse in input advanced settings to allow mouse panning. - 실제 마우스 입력과 마우스 패닝은 호환되지 않습니다. 마우스 패닝을 허용하려면 입력 고급 설정에서 에뮬레이트 마우스를 비활성화하세요. + 실제 마우스 입력과 마우스 패닝은 호환되지 않습니다. 마우스 패닝을 허용하려면 입력 고급 설정에서 에뮬레이트된 마우스를 비활성화해 주세요. @@ -4637,7 +4662,7 @@ Current values are %1% and %2% respectively. Form - 포럼 + 형태 @@ -4657,7 +4682,7 @@ Current values are %1% and %2% respectively. Enable Airplane Mode - + 비행기 모드 활성화 @@ -4665,7 +4690,7 @@ Current values are %1% and %2% respectively. Dialog - Dialog + 다이얼로그 @@ -4713,57 +4738,57 @@ Current values are %1% and %2% respectively. 일부 설정은 게임이 실행 중이 아닐 때만 사용할 수 있습니다. - + Add-Ons 부가 기능 - + System 시스템 - + CPU CPU - + Graphics 그래픽 - + Adv. Graphics 고급 그래픽 - + Ext. Graphics - + 그래픽 별도 기능 - + Audio 오디오 - + Input Profiles 입력 프로파일 - - - Network - - - Applets - + Network + 네트워크 - + + Applets + 애플릿 + + + Properties 속성 @@ -4773,22 +4798,22 @@ Current values are %1% and %2% respectively. Form - Form + 유형 Add-Ons - 애드온 + 부가 기능 Import Mod from ZIP - + ZIP에서 모드 불러오기 Import Mod from Folder - + 폴더에서 모드 불러오기 @@ -4803,86 +4828,215 @@ Current values are %1% and %2% respectively. Mod Install Succeeded - + 모드 설치 성공 Successfully installed all mods. - + 모든 모드가 성공적으로 설치되었습니다. Mod Install Failed - + 모드 설치 실패 Failed to install the following mods: %1 Check the log for details. - + 다음 모드 설치에 실패했습니다: + %1 +자세한 내용은 로그를 확인하세요. Mod Folder - + 모드 폴더 Zipped Mod Location - + 압축된 모드 위치 Zipped Archives (*.zip) - + 압축된 아카이브(*.zip) Invalid Selection - + 유효하지 않은 선택 Only mods, cheats, and patches can be deleted. To delete NAND-installed updates, right-click the game in the game list and click Remove -> Remove Installed Update. - + 모드, 치트, 패치만 삭제할 수 있습니다. +NAND에 설치된 업데이트를 삭제하려면 게임 목록에서 게임을 우클릭한 후 제거 -> 설치된 업데이트 제거를 클릭하세요. You are about to delete the following installed mods: - + 다음 설치된 모드를 삭제하려고 합니다: + Once deleted, these can NOT be recovered. Are you 100% sure you want to delete them? - + +삭제한 후에는 복구할 수 없습니다. 정말 100% 삭제하고 싶으신 건가요? Delete add-on(s)? - + 부가 기능을 삭제하겠습니까? Successfully deleted - + 성공적으로 삭제됨 Successfully deleted all selected mods. - + 선택한 모드가 모두 성공적으로 삭제되었습니다. &Delete - + 삭제(&D) &Open in File Manager + 파일 관리자에서 열기(&O) + + + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. @@ -4891,7 +5045,7 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t Form - Form + 형식 @@ -4921,7 +5075,7 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t Profile management is available only when game is not running. - 프로필 관리자는 게임이 작동 중이지 않을 때만 사용 가능합니다. + 프로필 관리는 게임이 실행 중이 아닐 때만 가능합니다. @@ -4959,37 +5113,37 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t Error creating user image directory - 사용자 이미지 디렉토리 생성 오류 + 유저 이미지 디렉터리 생성 중 오류 발생 Unable to create directory %1 for storing user images. - 사용자 이미지를 저장하기 위한 %1 디렉토리를 만들 수 없습니다. + 유저 이미지를 저장하기 위한 %1 디렉터리를 만들 수 없습니다. Error saving user image - + 유저 이미지 저장 중 오류 발생 Unable to save image to file - + 이미지를 파일로 저장할 수 없음 &Edit - + 편집(&E) &Delete - + 삭제(&D) Edit User - + 유저 편집 @@ -4997,7 +5151,7 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t Delete this user? All of the user's save data will be deleted. - 이 사용자를 삭제하시겠습니까? 사용자의 저장 데이터가 모두 삭제됩니다. + 이 유저를 삭제하겠습니까? 유저의 저장 데이터가 모두 삭제됩니다. @@ -5017,12 +5171,12 @@ UUID: %2 Configure Ring Controller - 링 컨트롤러 설정 + 링 컨트롤러 구성 To use Ring-Con, configure player 1 as right Joy-Con (both physical and emulated), and player 2 as left Joy-Con (left physical and dual emulated) before starting the game. - 링콘을 사용하려면 게임을 시작하기 전에 플레이어 1을 오른쪽 조이콘 (실제 및 에뮬레이션 모두)으로, 플레이어 2를 왼쪽 조이콘 (왼쪽 실제 및 듀얼 에뮬레이션)으로 구성합니다. + 링콘을 사용하려면 게임을 시작하기 전에 플레이어 1을 오른쪽 조이콘(실제 및 에뮬레이트 모두)으로, 플레이어 2를 왼쪽 조이콘(왼쪽 실제 및 이중 에뮬레이트)으로 구성합니다. @@ -5076,12 +5230,12 @@ UUID: %2 Restore Defaults - 기본값으로 초기화 + 기본값 복원 Clear - 초기화 + 지우기 @@ -5091,7 +5245,7 @@ UUID: %2 Invert axis - 축 뒤집기 + 축 반전 @@ -5112,7 +5266,7 @@ UUID: %2 Configuring - 설정 중 + 구성 중 @@ -5145,7 +5299,7 @@ UUID: %2 Form - Form + 형식 @@ -5174,12 +5328,12 @@ UUID: %2 <html><head/><body><p>Reads controller input from scripts in the same format as TAS-nx scripts.<br/>For a more detailed explanation, please consult the user handbook.</p></body></html> - + TAS-nx 스크립트와 동일한 형식으로 스크립트에서 컨트롤러 입력을 읽습니다. 자세한 내용은 사용자 설명서를 참조해 주세요. To check which hotkeys control the playback/recording, please refer to the Hotkey settings (Configure -> General -> Hotkeys). - 재생/녹화를 제어하는 ​​단축키를 확인하려면 단축키 설정(설정 -> 일반 -> 단축키)을 참조하십시오. + 재생/녹화를 제어하는 ​​단축키를 확인하려면 단축키 설정(구성 -> 일반 -> 단축키)을 참조해 주세요. @@ -5204,12 +5358,12 @@ UUID: %2 Pause execution during loads - 로드 중 실행 일시중지 + 불러오는 동안 실행 일시 중지 Show recording dialog - + 녹화 다이얼로그 표시 @@ -5219,7 +5373,7 @@ UUID: %2 Path - 주소 + 경로 @@ -5232,12 +5386,12 @@ UUID: %2 TAS Configuration - TAS 설정 + TAS 구성 Select TAS Load Directory... - TAS 로드 디렉토리 선택... + TAS 불러오기 디렉터리 선택... @@ -5245,7 +5399,7 @@ UUID: %2 Configure Touchscreen Mappings - 터치 스크린 매핑 설정 + 터치스크린 매핑 구성 @@ -5265,19 +5419,19 @@ UUID: %2 Rename - 이름 바꾸기 + 개명 Click the bottom area to add a point, then press a button to bind. Drag points to change position, or double-click table cells to edit values. 하단 영역을 클릭하여 포인트를 추가 한 다음 버튼을 눌러 바인딩합니다. -포인트들을 끌어서 위치를 변경하거나 테이블 셀을 두 번 클릭하여 값을 편집합니다. +포인트를 끌어서 위치를 변경하거나 테이블 셀을 더블 클릭하여 값을 편집합니다. Delete Point - 포인트 삭제하기 + 포인트 삭제 @@ -5309,17 +5463,17 @@ Drag points to change position, or double-click table cells to edit values. Delete Profile - 프로필 삭제하기 + 프로필 삭제 Delete profile %1? - %1 프로필을 삭제하시겠습니까? + %1 프로필을 삭제하겠습니까? Rename Profile - 프로필 이름 재설정 + 프로필 개명 @@ -5337,12 +5491,12 @@ Drag points to change position, or double-click table cells to edit values. Configure Touchscreen - 터치 스크린 + 터치스크린 구성 Warning: The settings in this page affect the inner workings of Eden's emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing. - + 경고: 이 페이지의 설정은 Eden의 에뮬레이트된 터치스크린의 내부 작동 방식에 영향을 미칩니다. 설정을 변경하면 터치스크린이 부분적으로 또는 전혀 작동하지 않는 등 원치 않는 동작이 발생할 수 있습니다. 이 페이지는 자신이 무엇을 하고 있는지 정확히 알고 있는 경우에만 사용하세요. @@ -5367,7 +5521,7 @@ Drag points to change position, or double-click table cells to edit values. Restore Defaults - 기본값으로 초기화 + 기본값 복원 @@ -5381,17 +5535,17 @@ Drag points to change position, or double-click table cells to edit values. Small (24x24) - 작은 크기 (24x24) + 작은 크기(24x24) Standard (48x48) - 기본 크기 (48x48) + 기본 크기(48x48) Large (72x72) - 큰 크기 (72x72) + 큰 크기(72x72) @@ -5401,7 +5555,7 @@ Drag points to change position, or double-click table cells to edit values. Filetype - 파일타입 + 파일 형식 @@ -5419,7 +5573,7 @@ Drag points to change position, or double-click table cells to edit values. Form - Form + 형식 @@ -5434,7 +5588,7 @@ Drag points to change position, or double-click table cells to edit values. Note: Changing language will apply your configuration. - 참고: 언어를 변경하면 설정에 반영됩니다 + 참고: 언어를 변경하면 구성이 적용됩니다. @@ -5453,94 +5607,89 @@ Drag points to change position, or double-click table cells to edit values. - Show Compatibility List - 환성 목록 표시 + Show Add-Ons Column + 부가 기능 열 표시 - Show Add-Ons Column - 추가 기능 열 표시 - - - Show Size Column 크기 열 표시 - + Show File Types Column 파일 형식 열 표시 - + Show Play Time Column - + 플레이 시간 열 표시 - + Folder Icon Size: 폴더 아이콘 크기: - + Row 1 Text: 1번째 행 텍스트: - + Row 2 Text: 2번째 행 텍스트: - + Screenshots 스크린샷 - + Ask Where To Save Screenshots (Windows Only) - 스크린샷 저장 위치 물어보기 (Windows 전용) + 스크린샷 저장 위치 물어보기(Windows 전용) - + Screenshots Path: - 스크린샷 경로 : + 스크린샷 경로: - + ... ... - + TextLabel - + 텍스트 라벨 - + Resolution: 해상도: - + Select Screenshots Path... 스크린샷 경로 선택... - + <System> <System> - + English English - + Auto (%1 x %2, %3 x %4) Screenshot width value - 자동 (%1 x %2, %3 x %4) + 자동(%1 x %2, %3 x %4) @@ -5548,12 +5697,12 @@ Drag points to change position, or double-click table cells to edit values. Configure Vibration - 진동 설정 + 진동 구성 Press any controller button to vibrate the controller. - 컨트롤러를 진동시키려면 아무 컨트롤러 버튼이나 누르십시오. + 컨트롤러를 진동시키려면 아무 컨트롤러 버튼이나 누르세요. @@ -5620,7 +5769,7 @@ Drag points to change position, or double-click table cells to edit values. Enable Accurate Vibration - 정교한 진동 활성화 + 정밀한 진동 활성화 @@ -5628,7 +5777,7 @@ Drag points to change position, or double-click table cells to edit values. Form - Form + 형식 @@ -5638,22 +5787,22 @@ Drag points to change position, or double-click table cells to edit values. Eden Web Service - + Eden 웹 서비스 Token: - 토큰: + 토큰: Username: - 유저 이름: + 유저 이름: Generate - + 생성 @@ -5663,31 +5812,31 @@ Drag points to change position, or double-click table cells to edit values. Discord Presence - Discord 알림 + 디스코드 알림 Show Current Game in your Discord Status - 디스코드에 실행중인 게임을 나타낼 수 있습니다. + 디스코드 상태에 현재 게임 표시 All Good Tooltip - + 모두 좋음 Must be between 4-20 characters Tooltip - + 4~20자 사이여야 함 Must be 48 characters, and lowercase a-z Tooltip - + 48자여야 하며, a~z는 모두 소문자여야 함 @@ -5708,37 +5857,37 @@ Drag points to change position, or double-click table cells to edit values. Data Manager - + 데이터 관리자 Deleting ANY data is IRREVERSABLE! - + 어떤 데이터를 삭제하든 되돌릴 수 없습니다! Shaders - + 셰이더 User NAND - + 유저 NAND System NAND - + 시스템 NAND Mods - + 모드 Saves - + 저장 @@ -5746,37 +5895,37 @@ Drag points to change position, or double-click table cells to edit values. Form - + 양식 Tooltip - + 툴팁 Open with your system file manager - + 시스템 파일 관리자로 엽니다 Delete all data in this directory. THIS IS 100% IRREVERSABLE! - + 이 디렉터리의 모든 데이터를 삭제합니다. 이 작업은 100% 되돌릴 수 없습니다! Export all data in this directory. This may take a while! - + 이 디렉터리에 있는 모든 데이터를 내보냅니다. 시간이 다소 걸릴 수 있습니다! Import data for this directory. This may take a while, and will delete ALL EXISTING DATA! - + 이 디렉터리의 데이터를 가져옵니다. 시간이 다소 걸릴 수 있으며, 기존 데이터는 모두 삭제됩니다! - + Calculating... - + 계산 중... @@ -5784,27 +5933,27 @@ Drag points to change position, or double-click table cells to edit values. Eden Dependencies - + Eden 의존물 <html><head/><body><p><span style=" font-size:28pt;">Eden Dependencies</span></p></body></html> - + <html><head/><body><p><span style=" font-size:28pt;">Eden 의존물</span></p></body></html> <html><head/><body><p>The projects that make Eden possible</p></body></html> - + <html><head/><body><p>Eden을 실행할 수 있게 하는 프로젝트들</p></body></html> Dependency - + 의존물 Version - + 버전 @@ -5855,7 +6004,7 @@ Drag points to change position, or double-click table cells to edit values. Connecting - 연결중 + 연결 중 @@ -5868,438 +6017,433 @@ Drag points to change position, or double-click table cells to edit values. Username is not valid. Must be 4 to 20 alphanumeric characters. - + 유저 이름이 유효하지 않습니다. 4자에서 20자 사이의 영숫자여야 합니다. Room name is not valid. Must be 4 to 20 alphanumeric characters. - + 방 이름이 유효하지 않습니다. 4자에서 20자 사이의 영숫자여야 합니다. Username is already in use or not valid. Please choose another. - + 유저 이름이 이미 사용 중이거나 유효하지 않습니다. 다른 이름을 선택해 주세요. IP is not a valid IPv4 address. - + IP는 유효한 IPv4 주소가 아닙니다. Port must be a number between 0 to 65535. - + 포트는 0에서 65535 사이의 숫자여야 합니다. You must choose a Preferred Game to host a room. If you do not have any games in your game list yet, add a game folder by clicking on the plus icon in the game list. - + 방을 호스팅하려면 선호하는 게임을 선택해야 합니다. 게임 목록에 아직 게임이 없다면 게임 목록에서 더하기 아이콘을 클릭하여 게임 폴더를 추가하세요. Unable to find an internet connection. Check your internet settings. - + 인터넷 연결을 찾을 수 없습니다. 인터넷 설정을 확인하세요. Unable to connect to the host. Verify that the connection settings are correct. If you still cannot connect, contact the room host and verify that the host is properly configured with the external port forwarded. - + 호스트에 연결할 수 없습니다. 연결 설정이 올바른지 확인하세요. 그래도 연결할 수 없는 경우 방 관리자에게 문의하여 외부 포트 포워딩이 올바르게 구성되어 있는지 확인하세요. Unable to connect to the room because it is already full. - + 이미 방이 꽉 차서 연결할 수 없습니다. Creating a room failed. Please retry. Restarting Eden might be necessary. - + 방 생성에 실패했습니다. 다시 시도해 주세요. Eden을 재시작해야 할 수도 있습니다. The host of the room has banned you. Speak with the host to unban you or try a different room. - + 방 호스트가 당신을 차단했습니다. 호스트에게 문의하여 차단을 해제해 달라고 요청하거나 다른 방을 이용해 보세요. Version mismatch! Please update to the latest version of Eden. If the problem persists, contact the room host and ask them to update the server. - + 버전이 일치하지 않습니다! Eden을 최신 버전으로 업데이트해 주세요. 문제가 지속되면 방 관리자에게 문의하여 서버 업데이트를 요청하세요. Incorrect password. - + 비밀번호가 잘못되었습니다. An unknown error occurred. If this error continues to occur, please open an issue - + 알 수 없는 오류가 발생했습니다. 이 오류가 계속 발생하는 경우, 문제를 신고해 주세요. Connection to room lost. Try to reconnect. - + 방 연결이 끊겼습니다. 다시 연결해 보세요. You have been kicked by the room host. - + 당신은 방 호스트에게 추방당했습니다. IP address is already in use. Please choose another. - + 해당 IP 주소는 이미 사용 중입니다. 다른 주소를 선택해 주세요. You do not have enough permission to perform this action. - + 이 작업을 수행할 권한이 충분하지 않습니다. The user you are trying to kick/ban could not be found. They may have left the room. - + 강퇴/차단하려는 사용자를 찾을 수 없습니다. +방을 나갔을 수 있습니다. No valid network interface is selected. Please go to Configure -> System -> Network and make a selection. - + 유효한 네트워크 인터페이스가 선택되지 않았습니다. +구성 -> 시스템 -> 네트워크로 이동하여 선택해 주세요. Error - + 오류 GRenderWindow - - + + OpenGL not available! OpenGL을 사용할 수 없습니다! - + OpenGL shared contexts are not supported. - OpenGL 공유 컨텍스트는 지원되지 않습니다. + OpenGL 공유 콘텍스트는 지원되지 않습니다. - + Eden has not been compiled with OpenGL support. - + Eden은 OpenGL 지원 기능을 포함하여 컴파일되지 않았습니다. - - - + + + Error while initializing OpenGL! OpenGL을 초기화하는 동안 오류가 발생했습니다! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. 사용하시는 GPU가 OpenGL을 지원하지 않거나, 최신 그래픽 드라이버가 설치되어 있지 않습니다. - + Error while initializing OpenGL 4.6! OpenGL 4.6 초기화 중 오류 발생! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 - 사용하시는 GPU가 OpenGL 4.6을 지원하지 않거나 최신 그래픽 드라이버가 설치되어 있지 않습니다. <br><br>GL 렌더링 장치:<br>%1 + 사용 중인 GPU가 OpenGL 4.6을 지원하지 않거나 최신 그래픽 드라이버가 설치되어 있지 않습니다.<br><br>GL 렌더러:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 - 사용하시는 GPU가 1개 이상의 OpenGL 확장 기능을 지원하지 않습니다. 최신 그래픽 드라이버가 설치되어 있는지 확인하세요. <br><br>GL 렌더링 장치:<br>%1<br><br>지원하지 않는 확장 기능:<br>%2 + 사용 중인 GPU가 하나 이상의 필수 OpenGL 확장 기능을 지원하지 않을 수 있습니다. 최신 그래픽 드라이버가 설치되어 있는지 확인해 주세요.<br><br>GL 렌더러:<br>%1<br><br>지원되지 않는 확장 기능:<br>%2 - + This build doesn't have OpenGL support. - + 이 빌드는 OpenGL을 지원하지 않습니다. GameList - + &Add New Game Directory - + 새 게임 디렉터리 추가(&A) - + Favorite - 선호하는 게임 + 즐겨찾는 게임 - + Start Game 게임 시작 - + Start Game without Custom Configuration - 맞춤 설정 없이 게임 시작 + 맞춤 구성 없이 게임 시작 - + Open Save Data Location - 세이브 데이터 경로 열기 + 저장 데이터 위치 열기 - + Open Mod Data Location - MOD 데이터 경로 열기 + 모드 데이터 위치 열기 - + Open Transferable Pipeline Cache - 전송 가능한 파이프라인 캐시 열기 + 이동 가능한 파이프라인 캐시 열기 - + Link to Ryujinx - + Ryujinx에 연결 - + Remove 제거 - + Remove Installed Update - 설치된 업데이트 삭제 + 설치된 업데이트 제거 - + Remove All Installed DLC - 설치된 모든 DLC 삭제 + 설치된 모든 DLC 제거 - + Remove Custom Configuration 사용자 지정 구성 제거 - + Remove Cache Storage - 캐시 스토리지 제거 + 캐시 저장소 제거 - + Remove OpenGL Pipeline Cache OpenGL 파이프라인 캐시 제거 - + Remove Vulkan Pipeline Cache Vulkan 파이프라인 캐시 제거 - + Remove All Pipeline Caches 모든 파이프라인 캐시 제거 - + Remove All Installed Contents - 설치된 모든 컨텐츠 제거 + 설치된 모든 콘텐츠 제거 - + Manage Play Time - + 플레이 시간 관리 - + Edit Play Time Data - + 플레이 시간 데이터 편집 - + Remove Play Time Data - + 플레이 시간 데이터 제거 - - + + Dump RomFS RomFS를 덤프 - + Dump RomFS to SDMC RomFS를 SDMC로 덤프 - + Verify Integrity - + 무결성 인증 - + Copy Title ID to Clipboard 클립보드에 타이틀 ID 복사 - - Navigate to GameDB entry - GameDB 항목으로 이동 - - - + Create Shortcut 바로가기 만들기 - + Add to Desktop 데스크톱에 추가 - + Add to Applications Menu 애플리케이션 메뉴에 추가 - + Configure Game - + 게임 구성 - + Scan Subfolders 하위 폴더 스캔 - + Remove Game Directory - 게임 디렉토리 제거 + 게임 디렉터리 제거 - + ▲ Move Up ▲ 위로 이동 - + ▼ Move Down ▼ 아래로 이동 - + Open Directory Location - 디렉토리 위치 열기 + 디렉터리 위치 열기 - + Clear - 초기화 - - - - Name - 이름 - - - - Compatibility - 호환성 - - - - Add-ons - 부가 기능 - - - - File type - 파일 형식 - - - - Size - 크기 - - - - Play time - + 지우기 GameListItemCompat - + Ingame 게임 내 - + Game starts, but crashes or major glitches prevent it from being completed. 게임이 시작되지만, 충돌이나 주요 결함으로 인해 게임이 완료되지 않습니다. - + Perfect 완벽함 - + Game can be played without issues. 문제 없이 게임 플레이가 가능합니다. - + Playable - 재생 가능 + 플레이 가능 - + Game functions with minor graphical or audio glitches and is playable from start to finish. 약간의 그래픽 또는 오디오 결함이 있는 게임 기능이 있으며 처음부터 끝까지 플레이할 수 있습니다. - + Intro/Menu 인트로/메뉴 - + Game loads, but is unable to progress past the Start Screen. 게임이 로드되지만 시작 화면을 지나서 진행할 수 없습니다. - + Won't Boot 실행 불가 - + The game crashes when attempting to startup. - 게임 실행 시 크래시가 일어납니다. + 게임 실행 시 게임이 튕깁니다. - + Not Tested 테스트되지 않음 - + The game has not yet been tested. 이 게임은 아직 테스트되지 않았습니다. + + GameListModel + + + Name + 이름 + + + + Add-ons + 부가 기능 + + + + File type + 파일 형식 + + + + Size + 크기 + + + + Play time + 플레이 시간 + + GameListPlaceholder - + Double-click to add a new folder to the game list 더블 클릭하여 게임 목록에 새 폴더 추가 @@ -6307,17 +6451,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) - + %n개 결과 중 %1개 - + Filter: 필터: - + Enter pattern to filter 검색 필터 입력 @@ -6401,7 +6545,8 @@ Please go to Configure -> System -> Network and make a selection. Failed to announce the room to the public lobby. In order to host a room publicly, you must have a valid Eden account configured in Emulation -> Configure -> Web. If you do not want to publish a room in the public lobby, then select Unlisted instead. Debug Message: - + 공개 로비에 방을 알리는 데 실패했습니다. 방을 공개적으로 호스팅하려면 에뮬레이션 -> 구성 -> 웹에서 유효한 Eden 계정이 구성되어 있어야 합니다. 공개 로비에 방을 게시하지 않으려면 대신 '비공개'를 선택하세요. +디버그 메시지: @@ -6476,17 +6621,17 @@ Debug Message: Change GPU Mode - + GPU 모드 변경 Configure - + 구성 Configure Current Game - + 현재 게임 구성 @@ -6496,52 +6641,52 @@ Debug Message: Exit Fullscreen - 전체화면 종료 + 전체 화면 종료 Exit Eden - + Eden 종료 Fullscreen - 전체화면 + 전체 화면 Load File - 파일 로드 + 파일 불러오기 Load/Remove Amiibo - Amiibo 로드/제거 + Amiibo 불러오기/제거 Browse Public Game Lobby - + 공개 게임 로비 찾아보기 Create Room - + 방 만들기 Direct Connect to Room - + 방에 직접 연결 Leave Room - + 방 나가기 Show Current Room - + 현재 방 표시 @@ -6561,17 +6706,17 @@ Debug Message: TAS Reset - TAS 리셋 + TAS 재설정 TAS Start/Stop - TAS 시작/멈춤 + TAS 시작/중지 Toggle Filter Bar - 상태 표시줄 전환 + 필터 바 토글 @@ -6581,32 +6726,32 @@ Debug Message: Toggle Turbo Speed - + 터보 속도 토글 Toggle Slow Speed - + 느린 속도 토글 Toggle Mouse Panning - 마우스 패닝 활성화 + 마우스 패닝 토글 Toggle Renderdoc Capture - + Renderdoc 캡처 토글 Toggle Status Bar - 상태 표시줄 전환 + 상태 표시줄 토글 Toggle Performance Overlay - + 성능 오버레이 토글 @@ -6614,7 +6759,7 @@ Debug Message: Please confirm these are the files you wish to install. - 설치하려는 파일이 맞는지 확인하십시오. + 설치하려는 파일이 맞는지 확인해 주세요. @@ -6667,7 +6812,7 @@ Debug Message: Loading Shaders %1 / %2 - 셰이더 로딩 %1 / %2 + 셰이더 불러오는 중 %1 / %2 @@ -6756,7 +6901,7 @@ Debug Message: Refreshing - 새로 고치는 중 + 새로 고침 중 @@ -6784,7 +6929,7 @@ Debug Message: Open &Eden Folders - + Eden 폴더 열기(&E) @@ -6799,7 +6944,7 @@ Debug Message: &Reset Window Size - 창 크기 초기화 (&R) + 창 크기 재설정(&R) @@ -6809,1341 +6954,1335 @@ Debug Message: &Game List Mode - + 게임 목록 모드(&G) - + Game &Icon Size - + 게임 아이콘 크기(&I) - + Reset Window Size to &720p - 창 크기를 720p로 맞추기(&7) + 창 크기를 720p로 재설정(&7) - + Reset Window Size to 720p - 창 크기를 720p로 맞추기 + 창 크기를 720p로 재설정 - + Reset Window Size to &900p - 창 크기를 900p로 맞추기(&9) + 창 크기를 900p로 재설정(&9) - + Reset Window Size to 900p - 창 크기를 900p로 맞추기 + 창 크기를 900p로 재설정 - + Reset Window Size to &1080p - 창 크기를 1080p로 맞추기(&1) + 창 크기를 1080p로 재설정(&1) - + Reset Window Size to 1080p - 창 크기를 1080p로 맞추기 + 창 크기를 1080p로 재설정 - + &Multiplayer 멀티플레이어(&M) - + &Tools 도구(&T) - + Am&iibo - + Amiibo(&i) - + Launch &Applet - + 애플릿 실행(&A) - + &TAS TAS(&T) - + &Create Home Menu Shortcut - + HOME 메뉴 바로가기 만들기(&C) - + Install &Firmware - + 펌웨어 설치(&F) - + &Help 도움말(&H) - + &Install Files to NAND... - 낸드에 파일 설치(&I) + NAND에 파일 설치(&I) - + L&oad File... - 파일 불러오기...(&L) + 파일 불러오기...(&o) - + Load &Folder... 폴더 불러오기...(&F) - + E&xit - 종료(&X) + 종료(&x) - - + + &Pause 일시중지(&P) - + &Stop 정지(&S) - + &Verify Installed Contents - + 설치된 콘텐츠 확인(&V) - + &About Eden - + Eden에 대해(&A) - + Single &Window Mode - 싱글 창 모드(&W) + 단일 창 모드(&W) - + Con&figure... 설정(&f) - + Ctrl+, - + Ctrl+, - + Enable Overlay Display Applet - + 오버레이 디스플레이 애플릿 활성화 - + Show &Filter Bar - 필터링 바 표시(&F) + 필터 바 표시(&F) - + Show &Status Bar - 상태 표시줄 보이기(&S) + 상태 표시줄 표시(&S) - + Show Status Bar - 상태 표시줄 보이기 + 상태 표시줄 표시 - + &Browse Public Game Lobby 공개 게임 로비 찾아보기(&B) - + &Create Room 방 만들기(&C) - + &Leave Room - 방에서 나가기(&L) + 방 나가기(&L) - + &Direct Connect to Room 방에 직접 연결(&D) - + &Show Current Room 현재 방 표시(&S) - + F&ullscreen 전체 화면(&u) - + &Restart 재시작(&R) - + Load/Remove &Amiibo... - Amiibo 로드/제거(&A)... + Amiibo 불러오기/제거(&A)... - - &Report Compatibility - 호환성 보고(&R) - - - + Open &Mods Page - 게임 모드 페이지 열기(&M) + 모드 페이지 열기(&M) - - Open &Quickstart Guide - 빠른 시작 가이드 열기(&Q) + + &User Handbook + - - &FAQ - FAQ(&F) + + &Capture Screenshot + 스크린샷 캡처(&C) + + + + &Album + 앨범(&A) + + + + &Set Nickname and Owner + 닉네임과 오너 설정(&S) - &Capture Screenshot - 스크린샷 찍기(&C) + &Delete Game Data + 게임 데이터 삭제(&D) - &Album - + &Restore Amiibo + Amiibo 복원(&R) - &Set Nickname and Owner - + &Format Amiibo + Amiibo 포맷(&F) - &Delete Game Data - + &Mii Editor + Mii 편집기(&M) - &Restore Amiibo - - - - - &Format Amiibo - - - - - &Mii Editor - - - - &Configure TAS... - TAS설정...(&C) + TAS 구성...(&C) - + Configure C&urrent Game... - 실행중인 게임 맞춤 설정...(&u) + 현재 게임 구성...(&u) - - + + &Start 시작(&S) - + &Reset - 리셋(&R) + 재설정(&R) + + + + + R&ecord + 기록(&e) + + + + Open &Controller Menu + 컨트롤러 메뉴 열기(&C) + + + + Install Decryption &Keys + 복호화 키 설치(&K) - - R&ecord - 레코드(&e) + &Home Menu + HOME 메뉴(&H) - Open &Controller Menu - + &Desktop + 데스크톱(&D) - Install Decryption &Keys - + &Application Menu + 애플리케이션 메뉴(&A) - &Home Menu - + &Root Data Folder + 데이터 폴더 경로(&R) - &Desktop - + &NAND Folder + NAND 폴더(&N) - &Application Menu - + &SDMC Folder + SDMC 폴더(&S) - &Root Data Folder - + &Mod Folder + 모드 폴더(&M) - &NAND Folder - + &Log Folder + 로그 폴더(&L) - &SDMC Folder - + From Folder + 폴더에서 - &Mod Folder - + From ZIP + ZIP에서 - &Log Folder - + &Eden Dependencies + Eden 의존물(&E) - From Folder - - - - - From ZIP - - - - - &Eden Dependencies - - - - &Data Manager - + 데이터 관리자(&D) - + &Tree View - + 트리 보기(&T) - + &Grid View - + 격자 보기(&G) - + Game Icon Size - + 게임 아이콘 크기 - - + + None - + 없음 - + Show Game &Name - + 게임 이름 표시(&N) - + Show &Performance Overlay + 성능 오버레이 표시(&P) + + + + Post-Processing &Shaders... - + + &Carousel View + 캐러셀 보기(&C) + + + Small (32x32) - + 작은 크기(32x32) - + Standard (64x64) - + 기본 크기(64x64) - + Large (128x128) - + 큰 크기(128x128) - + Full Size (256x256) - + 전체 크기(256x256) - + Broken Vulkan Installation Detected - + 손상된 Vulkan 설치가 발견됨 - + Vulkan initialization failed during boot. - + 부팅 중 Vulkan 초기화에 실패했습니다. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping - + 게임 실행 중 - + Loading Web Applet... - + 웹 애플릿을 불러오는 중... - - + + Disable Web Applet - + 웹 애플릿 비활성화 - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + 웹 애플릿을 비활성화하면 예기치 않은 동작이 발생할 수 있으며 슈퍼 마리오 3D 컬렉션에서만 사용해야 합니다. 웹 애플릿을 비활성화하겠습니까? +(디버그 설정에서 다시 활성화할 수 있습니다.) - + The amount of shaders currently being built - + 현재 구축 중인 셰이더 수 - + The current selected resolution scaling multiplier. - + 현재 선택된 해상도 조정 배율입니다. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + 현재 에뮬레이션 속도입니다. 100%보다 높거나 낮은 값은 에뮬레이션이 스위치보다 빠르거나 느리게 실행되고 있음을 나타냅니다. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + 현재 게임이 표시하는 초당 프레임 수입니다. 이 값은 게임 및 장면에 따라 다릅니다. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + 프레임 제한이나 수직 동기화를 제외하고 스위치 프레임을 에뮬레이션하는 데 걸리는 시간입니다. 최고 속도 에뮬레이션의 경우 최대 16.67 밀리초입니다. - + Unmute - + 음소거 해제 - + Mute - + 음소거 - + Reset Volume + 볼륨 재설정 + + + + Configure Effects... - + &Clear Recent Files - + 최근 파일 지우기(&C) - + &Continue - + 재개(&C) - + Warning: Outdated Game Format - + 경고: 구식 게임 형식입니다 - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - + 현재 이 게임에 대해 구형 형식인 분해된 ROM 디렉터리 형식을 사용하고 있습니다. 이 형식은 NCA, NAX, XCI 또는 NSP와 같은 다른 형식으로 대체되었습니다. 분해된 ROM 디렉터리에는 아이콘, 메타데이터 및 업데이트 지원 기능이 없습니다.<br>Eden에서 지원하는 다양한 스위치 형식에 대한 자세한 내용은 사용자 설명서를 참조해 주세요. 이 메시지는 다시 표시되지 않습니다. - - + + Error while loading ROM! - + ROM 불러오는 중 오류 발생! - + The ROM format is not supported. - + 해당 ROM 형식은 지원되지 않습니다. - + An error occurred initializing the video core. + 비디오 코어 초기화 중 오류가 발생했습니다. + + + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. - - - - + Error while loading ROM! %1 %1 signifies a numeric error code. - + ROM 불러오는 중 오류 발생! %1 - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + %1<br>파일을 다시 덤프하거나 디스코드/Stoat에서 도움을 요청해 주세요. - + An unknown error occurred. Please see the log for more details. - + 알 수 없는 오류가 발생했습니다. 자세한 내용은 로그를 참조해 주세요. - + + (64-bit) - + (64비트) - + + (32-bit) - + (32비트) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + %1 %2 - + Closing software... - + 소프트웨어 닫는 중... - + Save Data - + 저장 데이터 - + Mod Data - + 모드 데이터 - + Error Opening %1 Folder - + %1 폴더 여는 중 오류 발생 - - + + Folder does not exist! - + 폴더가 존재하지 않습니다! - + Remove Installed Game Contents? - + 설치된 게임 콘텐츠를 제거하겠습니까? - + Remove Installed Game Update? - + 설치된 게임 업데이트를 제거하겠습니까? - + Remove Installed Game DLC? - + 설치된 게임 DLC를 제거하겠습니까? - + Remove Entry - + 항목 제거 - + Delete OpenGL Transferable Shader Cache? - + OpenGL 이동 가능한 셰이더 캐시를 삭제하겠습니까? - + Delete Vulkan Transferable Shader Cache? - + Vulkan 이동 가능한 셰이더 캐시를 삭제하겠습니까? - + Delete All Transferable Shader Caches? - + 이동 가능한 셰이더 캐시를 모두 삭제하겠습니까? - + Remove Custom Game Configuration? - + 사용자 지정 게임 구성을 제거하겠습니까? - + Remove Cache Storage? - + 캐시 저장소를 제거하겠습니까? - + Remove File - + 파일 제거 - + Remove Play Time Data - + 플레이 시간 데이터 제거 - + Reset play time? - + 플레이 시간을 재설정하겠습니까? - - + + RomFS Extraction Failed! - + RomFS 추출 실패! - + There was an error copying the RomFS files or the user cancelled the operation. - + RomFS 파일을 복사하는 중에 오류가 발생했거나 사용자가 작업을 취소했습니다. - + Full - + 전체 - + Skeleton - + 뼈대 - + Select RomFS Dump Mode - + RomFS 덤프 모드 선택 - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + RomFS 덤프 방식을 선택해 주세요. 전체를 선택하면 모든 파일이 새 디렉터리로 복사되고, 뼈대를 선택하면 디렉터리 구조만 생성됩니다. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + %1 위치에 RomFS를 추출할 수 있는 여유 공간이 부족합니다. 공간을 확보하거나 에뮬레이션 > 구성 > 시스템 > 파일 시스템 > 덤프 경로에서 다른 덤프 디렉터리를 선택해 주세요. - + Extracting RomFS... - + RomFS 추출 중... - - + + Cancel - + 취소 - + RomFS Extraction Succeeded! - + RomFS 추출 성공! - + The operation completed successfully. - + 작업이 성공적으로 완료되었습니다. - + Error Opening %1 - + %1을(를) 여는 중 오류 발생 - + Select Directory - + 디렉터리 선택 - + Properties - + 속성 - + The game properties could not be loaded. - + 게임 속성을 불러올 수 없습니다. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + 스위치 실행 파일(%1);;모든 파일(*.*) - + Load File - + 파일 불러오기 - + Open Extracted ROM Directory - + 추출된 ROM 디렉터리 열기 - + Invalid Directory Selected - + 유효하지 않은 디렉터리 선택됨 - + The directory you have selected does not contain a 'main' file. - + 선택한 디렉터리에는 'main' 파일이 없습니다. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + 설치 가능한 스위치 파일(*.nca, *.nsp, *.xci);;닌텐도 콘텐츠 아카이브(*.nca);;닌텐도 제출 패키지(*.nsp);;NX 카트리지 이미지(*.xci) - + Install Files - + 파일 설치 - + %n file(s) remaining - + %n개 파일 남음 - + Installing file "%1"... - + "%1" 파일 설치 중... - - + + Install Results - + 설치 결과 - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + 잠재적인 충돌을 방지하기 위해 저희는 사용자가 기본 게임을 NAND에 설치하는 것은 권장하지 않습니다. +이 기능은 업데이트 및 DLC 설치에만 사용해 주세요. - + %n file(s) were newly installed - + %n개 파일이 새로 설치됨 + - + %n file(s) were overwritten - + %n개 파일이 덮어쓰기됨 + - + %n file(s) failed to install - + %n개 파일 설치 실패 + - + System Application - + 시스템 애플리케이션 - + System Archive - + 시스템 아카이브 - + System Application Update - + 시스템 애플리케이션 업데이트 - + Firmware Package (Type A) - + 펌웨어 패키지(A타입) - + Firmware Package (Type B) - + 펌웨어 패키지(B타입) - + Game - + 게임 - + Game Update - + 게임 업데이트 - + Game DLC - + 게임 DLC - + Delta Title - + 델타 타이틀 - + Select NCA Install Type... - + NCA 설치 유형 선택... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + 이 NCA를 설치할 타이틀 유형을 선택해 주세요: +(대부분의 경우 기본값인 '게임'을 선택하면 됩니다.) - + Failed to Install - + 설치 실패 - + The title type you selected for the NCA is invalid. - + NCA에 대해 선택한 타이틀 유형이 유효하지 않습니다. - + File not found - + 파일을 찾을 수 없음 - + File "%1" not found - + "%1" 파일을 찾을 수 없음 - + OK - + 확인 - + Function Disabled - + 기능 비활성화됨 - + Compatibility list reporting is currently disabled. Check back later! - + 호환성 목록 보고 옵션이 현재 비활성화되어 있습니다. 나중에 다시 확인해 주세요! - + Error opening URL - + URL 여는 중 오류 발생 - + Unable to open the URL "%1". - + "%1" URL을 열 수 없습니다. - + TAS Recording - + TAS 녹화 - + Overwrite file of player 1? - + 플레이어 1의 파일을 덮어쓰겠습니까? - + Invalid config detected - + 무효한 구성이 발견됨 - + Handheld controller can't be used on docked mode. Pro controller will be selected. - + 휴대 모드 컨트롤러는 독 모드에서 사용할 수 없습니다. Pro 컨트롤러로 선택됩니다. - - + + Amiibo - + Amiibo - - + + The current amiibo has been removed - + 현재 amiibo가 제거됨 - + Error - + 오류 - - + + The current game is not looking for amiibos - + 현재 게임은 amiibo를 찾고 있지 않습니다 - + Amiibo File (%1);; All Files (*.*) - + Amiibo 파일(%1);; 모든 파일(*.*) - + Load Amiibo - + Amiibo 불러오기 - + Error loading Amiibo data - + Amiibo 데이터 불러오는 중 오류 발생 - + The selected file is not a valid amiibo - + 선택한 파일은 유효한 amiibo가 아닙니다 - + The selected file is already on use - + 선택한 파일은 이미 사용 중입니다 - + An unknown error occurred - + 알 수 없는 오류 발생 - - - Keys not installed - + + No firmware available + 사용 가능한 펌웨어 없음 - - - Install decryption keys and restart Eden before attempting to install firmware. - + + Firmware Corrupted + 펌웨어 손상됨 - - Select Dumped Firmware Source Location - + + Unknown applet + 알 수 없는 애플릿 - - Select Dumped Firmware ZIP - + + Applet doesn't map to a known value. + 애플릿이 알려진 값에 매핑되지 않습니다. - - Zipped Archives (*.zip) - + + Record not found + 기록을 찾을 수 없음 - - Firmware cleanup failed - + + Applet not found. Please reinstall firmware. + 애플릿을 찾을 수 없습니다. 펌웨어를 다시 설치해 주세요. - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - - No firmware available - - - - - Firmware Corrupted - - - - - Unknown applet - - - - - Applet doesn't map to a known value. - - - - - Record not found - - - - - Applet not found. Please reinstall firmware. - - - - Capture Screenshot + 스크린샷 캡처 + + + + PNG Image (*.png) + PNG 이미지(*.png) + + + + TAS state: Running %1/%2 + TAS 상태: %1/%2 실행 중 + + + + TAS state: Recording %1 + TAS 상태: %1 녹화 중 + + + + TAS state: Idle %1/%2 + TAS 상태: %1/%2 유휴 + + + + TAS State: Invalid + TAS 상태: 무효 + + + + &Stop Running + 실행 중지(&S) + + + + Stop R&ecording + 녹화 중지(&e) + + + + Building: %n shader(s) + 구축 중: %n개 셰이더 + + + + Scale: %1x + %1 is the resolution scaling factor + 크기: %1x + + + + Speed: %1% / %2% + 속도: %1% / %2% + + + + Speed: %1% + 속도: %1% + + + + Game: %1 FPS + 게임: %1 FPS + + + + Frame: %1 ms + 프레임: %1 밀리초 + + + + NO AA + AA 없음 + + + + NO FX - - PNG Image (*.png) + + FX - TAS state: Running %1/%2 - + VOLUME: MUTE + 볼륨: 음소거 - - TAS state: Recording %1 - + + VOLUME: %1% + Volume percentage (e.g. 50%) + 볼륨: %1% - - TAS state: Idle %1/%2 - - - - - TAS State: Invalid - - - - - &Stop Running - - - - - Stop R&ecording - - - - - Building: %n shader(s) - - - - - Scale: %1x - %1 is the resolution scaling factor - - - - - Speed: %1% / %2% - + + Derivation Components Missing + 파생 구성 요소 누락 - Speed: %1% - - - - - Game: %1 FPS - - - - - Frame: %1 ms - - - - - - FSR - - - - - NO AA - - - - - VOLUME: MUTE - - - - - VOLUME: %1% - Volume percentage (e.g. 50%) - - - - - Derivation Components Missing - - - - Decryption keys are missing. Install them now? - + 복호화 키가 없습니다. 지금 설치하겠습니까? - + Wayland Detected! - + Wayland 발견됨! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. Would you like to force it for future launches? - + Wayland는 심각한 성능 문제와 원인을 알 수 없는 버그가 있는 것으로 알려져 있습니다. +대신 X11 사용을 권장합니다. + +향후 출시를 위해 Wayland 사용을 강제하겠습니까? - + Use X11 - + X11 사용 - + Continue with Wayland - + Wayland와 계속 - + Don't show again - + 다시 보지 않기 - + Restart Required - + 재시작이 필요함 - + Restart Eden to apply the X11 backend. - + X11 백엔드를 적용하려면 Eden을 다시 시작하세요. - + Slow - + 저속 - + Turbo - + 터보 - + Unlocked - + 제한 해제 - + Select RomFS Dump Target - + RomFS 덤프 대상 선택 - + Please select which RomFS you would like to dump. - + 덤프할 RomFS를 선택하세요. - + Are you sure you want to close Eden? - + 정말 Eden을 닫겠습니까? - - - + + + Eden - + Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + 정말 에뮬레이션을 중지하겠습니까? 저장되지 않은 진행 상황은 모두 손실됩니다. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? - + 현재 실행 중인 애플리케이션이 Eden에게 종료하지 말라고 요청했습니다. + +이 요청을 무시하고 종료하겠습니까? FXAA - + FXAA SMAA - + SMAA Nearest - + 최근방 Bilinear - + 쌍선형 Bicubic - + 쌍입방 Zero-Tangent - + 제로탄젠트 B-Spline - + B-스플라인 Mitchell - + 미첼 Spline-1 - + 스플라인-1 Gaussian - + 가우시안 Lanczos - + 란초스 ScaleForce - + ScaleForce + + + + FSR + FSR Area - + 영역 MMPX - + MMPX - + + SGSR + SGSR + + + + SGSR EdgeDir + SGSR EdgeDir + + + Docked - + 독 모드 - + Handheld - - - - - Fast - - - - - Balanced - + 휴대 모드 + Fast + 고속 + + + Accurate - + 정확 - + Vulkan - + Vulkan - + OpenGL GLSL - + OpenGL GLSL - + OpenGL SPIRV - - - - - OpenGL GLASM - + OpenGL SPIRV + OpenGL GLASM + OpenGL GLASM + + + Null - + Null @@ -8152,7 +8291,8 @@ Would you like to bypass this and exit anyway? Linking the old directory failed. You may need to re-run with administrative privileges on Windows. OS gave error: %1 - + 이전 디렉터리 연결에 실패했습니다. Windows에서는 관리자 권한으로 다시 실행해야 할 수 있습니다. +OS 오류: %1 @@ -8163,7 +8303,13 @@ If this is not desirable, delete the following files: %2 %3 %4 - + + +구성 및 데이터가 %1와(과) 공유된다는 점을 유의하세요. +원하지 않는 경우 다음 파일을 삭제하세요: +%2 +%3 +%4 @@ -8171,12 +8317,15 @@ If this is not desirable, delete the following files: If you wish to clean up the files which were left in the old data location, you can do so by deleting the following directory: %1 - + + +이전 데이터 위치에 남아 있는 파일을 정리하고 싶다면, 다음 디렉터리를 삭제하면 됩니다: +%1 - + Data was migrated successfully. - + 데이터 마이그레이션이 성공적으로 완료되었습니다. @@ -8184,12 +8333,12 @@ If you wish to clean up the files which were left in the old data location, you Import Mods - + 모드 가져오기 The specified folder or archive contains the following mods. Select which ones to install. - + 지정된 폴더 또는 아카이브 콘텐츠에는 다음 모드가 포함되어 있습니다. 설치할 모드를 선택하세요. @@ -8208,7 +8357,7 @@ If you wish to clean up the files which were left in the old data location, you Refreshing - 새로 고치는 중 + 새로 고침 중 @@ -8228,7 +8377,7 @@ If you wish to clean up the files which were left in the old data location, you Forum Username - 포럼 사용자이름 + 포럼 사용자 이름 @@ -8278,7 +8427,7 @@ If you wish to clean up the files which were left in the old data location, you Failed to update the room information. Please check your Internet connection and try hosting the room again. Debug Message: 방 정보를 업데이트하지 못했습니다. 인터넷 연결을 확인하고 방을 다시 호스팅해 보세요. -디버그 메시지: +디버그 메시지: @@ -8292,8 +8441,8 @@ Debug Message: Joining a room when the game is already running is discouraged and can cause the room feature not to work correctly. Proceed anyway? - 게임이 이미 실행 중일 때 방에 참여하는 것은 권장되지 않으며 방 기능이 제대로 작동하지 않을 수 있습니다. -그래도 진행하시겠습니까? + 게임이 이미 실행 중일 때 방에 참여하는 것은 권장되지 않으며, 방 기능이 제대로 작동하지 않을 수 있습니다. +그래도 진행하겠습니까? @@ -8322,127 +8471,127 @@ Proceed anyway? New User - + 새 유저 Change Avatar - + 아바타 변경 Set Image - + 이미지 설정 UUID - + UUID Eden - + Eden Username - + 유저 이름 UUID must be 32 hex characters (0-9, A-F) - + UUID는 32자리 16진수 문자(0~9, A~F)여야 함 Generate - + 생성 Select User Image - + 유저 이미지 선택 Image Formats (*.jpg *.jpeg *.png *.bmp) - + 이미지 형식(*.jpg *.jpeg *.png *.bmp) No firmware available - + 사용 가능한 펌웨어 없음 Please install the firmware to use firmware avatars. - + 펌웨어 아바타를 사용하려면 펌웨어를 설치해 주세요. Error loading archive - + 아카이브 불러오는 중 오류 발생 Archive is not available. Please install/reinstall firmware. - + 아카이브를 사용할 수 없습니다. 펌웨어를 설치/재설치해 주세요. Could not locate RomFS. Your file or decryption keys may be corrupted. - + RomFS를 찾을 수 없습니다. 파일 또는 복호화 키가 손상되었을 수 있습니다. Error extracting archive - + 아카이브 추출 중 오류 발생 Could not extract RomFS. Your file or decryption keys may be corrupted. - + RomFS를 추출할 수 없습니다. 파일 또는 복호화 키가 손상되었을 수 있습니다. Error finding image directory - + 이미지 디렉터리를 찾는 중 오류 발생 Failed to find image directory in the archive. - + 아카이브에서 이미지 디렉터리를 찾지 못했습니다. No images found - + 이미지를 찾을 수 없음 No avatar images were found in the archive. - + 아카이브에서 아바타 이미지를 찾을 수 없습니다. All Good Tooltip - + 모두 좋음 Must be 32 hex characters (0-9, a-f) Tooltip - + 32자리 16진수 문자(0~9, a~f)여야 함 Must be between 1 and 32 characters Tooltip - + 1자에서 32자 사이여야 함 @@ -8450,7 +8599,7 @@ Proceed anyway? Dialog - 대화 + 다이얼로그 @@ -8462,7 +8611,7 @@ Proceed anyway? OK - OK + 확인 @@ -8483,73 +8632,73 @@ p, li { white-space: pre-wrap; } Form - + 형식 Frametime - + 프레임타임 0 ms - + 0 밀리초 Min: 0 - + 최소: 0 Max: 0 - + 최대: 0 Avg: 0 - + 평균: 0 FPS - + FPS 0 fps - + 0 fps %1 fps - + %1 fps Avg: %1 - + 평균: %1 Min: %1 - + 최소: %1 Max: %1 - + 최대: %1 %1 ms - + %1 밀리초 @@ -8565,22 +8714,22 @@ p, li { white-space: pre-wrap; } Select - + 선택 Cancel - + 취소 Background Color - + 배경색 Select Firmware Avatar - + 펌웨어 아바타 선택 @@ -8590,56 +8739,59 @@ p, li { white-space: pre-wrap; } Migration - + 마이그레이션 Clear Shader Cache - + 셰이더 캐시 지우기 Keep Old Data - + 이전 데이터 보관 Clear Old Data - + 이전 데이터 지우기 Link Old Directory - + 이전 디렉터리 연결 - + + + No - + 아니요 You can manually re-trigger this prompt by deleting the new config directory: %1 - + 새 구성 디렉터리를 삭제하면 이 프롬프트를 수동으로 다시 트리거할 수 있습니다: +%1 Migrating - + 마이그레이션 중 Migrating, this may take a while... - + 마이그레이션 중입니다. 시간이 다소 걸릴 수 있습니다... @@ -8664,8 +8816,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8678,10 +8830,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8696,9 +8848,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8827,7 +8979,7 @@ p, li { white-space: pre-wrap; } Cross - 엑스 + 가위표 @@ -8845,13 +8997,13 @@ p, li { white-space: pre-wrap; } Share - Share + 공유 Options - Options + 옵션 @@ -8860,50 +9012,50 @@ p, li { white-space: pre-wrap; } [설정안됨] - + %1%2 %1%2 - + [invalid] - [유효하지않음] + [무효함] - - + + %1%2Hat %3 %1%2방향키 %3 - - - + + + %1%2Axis %3 - %1%2Axis %3 + %1%2축 %3 - + %1%2Axis %3,%4,%5 - %1%2Axis %3,%4,%5 + %1%2축 %3,%4,%5 - + %1%2Motion %3 %1%2모션 %3 - - + + %1%2Button %3 %1%2버튼 %3 - + [unused] [미사용] @@ -8952,7 +9104,7 @@ p, li { white-space: pre-wrap; } Home - 홈 + HOME @@ -8983,12 +9135,12 @@ p, li { white-space: pre-wrap; } Task - Task + 태스크 Extra - Extra + 별도 @@ -9005,7 +9157,7 @@ p, li { white-space: pre-wrap; } %1%2%3Axis %4 - %1%2%3Axis %4 + %1%2%3축 %4 @@ -9016,7 +9168,7 @@ p, li { white-space: pre-wrap; } Not playing a game - 게임을 하지 않음 + 게임을 안 하고 있음 @@ -9029,47 +9181,47 @@ p, li { white-space: pre-wrap; } %1이(가) %2을(를) 플레이 중입니다 - + Play Time: %1 - + 플레이 시간: %1 - + Never Played - + 플레이한 적 없음 - + Version: %1 - + 버전: %1 - + Version: 1.0.0 - + 버전: 1.0.0 - + Installed SD Titles 설치된 SD 타이틀 - + Installed NAND Titles 설치된 NAND 타이틀 - + System Titles 시스템 타이틀 - + Add New Game Directory - 새 게임 디렉토리 추가 + 새 게임 디렉터리 추가 - + Favorites 선호하는 게임 @@ -9114,7 +9266,7 @@ p, li { white-space: pre-wrap; } Owner - 소유자 + 오너 @@ -9124,7 +9276,7 @@ p, li { white-space: pre-wrap; } dd/MM/yyyy - 일일/월월/년년년년 + dd/MM/yyyy @@ -9134,7 +9286,7 @@ p, li { white-space: pre-wrap; } dd/MM/yyyy - 일일/월월/년년년년 + dd/MM/yyyy @@ -9179,7 +9331,7 @@ p, li { white-space: pre-wrap; } Set nickname and owner: - 닉네임 및 소유자 설정: + 닉네임과 오너 설정: @@ -9190,252 +9342,290 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware - + 게임을 실행하려면 펌웨어가 필요함 - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. Installing Firmware... - + 펌웨어 설치 중... - - - - + + + + Cancel - + 취소 Firmware Install Failed - + 펌웨어 설치 실패 Firmware Install Succeeded - + 펌웨어 설치 성공 Firmware integrity verification failed! - + 펌웨어 무결성 인증에 실패했습니다! - + Verification failed for the following files: %1 - + 다음 파일에 대한 검증에 실패했습니다: + +%1 - - + + Verifying integrity... - + 무결성 인증 중... - - + + Integrity verification succeeded! - + 무결성 인증에 성공했습니다! - - + + The operation completed successfully. - + 작업이 성공적으로 완료되었습니다. - - + + Integrity verification failed! - + 무결성 인증에 실패했습니다! - + File contents may be corrupt or missing. - + 파일 내용이 손상되었거나 누락되었을 수 있습니다. - + Integrity verification couldn't be performed - + 무결성 인증을 진행할 수 없습니다 - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + 펌웨어 설치가 취소되었습니다. 펌웨어 상태가 좋지 않거나 손상되었을 수 있습니다. 파일 내용의 유효성을 확인할 수 없습니다. - + Select Dumped Keys Location - + 덤프된 키 위치 ​​선택 - + Decryption Keys install succeeded - + 복호화 키 설치 성공 - + Decryption Keys install failed - + 복호화 키 설치 실패 - + Orphaned Profiles Detected! - + 사용되지 않는 프로필이 발견됨! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + 이 내용을 읽지 않으면 예상치 못한 문제가 발생할 수 있습니다!<br>Eden이 프로필에 연결되지 않은 다음 저장 디렉터리를 발견했습니다:<br>%1<br><br>다음 프로필이 유효합니다:<br>%2<br><br>"확인"을 클릭하여 저장 폴더를 열고 프로필을 수정하세요.<br>힌트: 가장 크거나 마지막으로 수정된 폴더의 내용을 다른 곳에 복사하고, 연결되지 않은 프로필을 모두 삭제한 다음, 복사한 내용을 유효한 프로필로 이동하세요.<br><br>여전히 이해가 안 되나요? <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>도움말 페이지</a>를 참조하세요.<br> - + Really clear data? - + 정말로 데이터를 지우겠습니까? - + Important data may be lost! - + 중요 데이터가 손실될 수 있습니다! - + Are you REALLY sure? - + 정말 확실해요? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + 한 번 삭제된 데이터는 절대 복구할 수 없습니다! +이 데이터를 100% 삭제하고 싶을 때만 이 작업을 수행하세요. - + Clearing... - + 지우는 중... - + Select Export Location - + 내보내기 위치 선택 - + %1.zip - + %1.zip - - + + + Zipped Archives (*.zip) - + 압축된 아카이브(*.zip) + + + + Exporting data. This may take a while... + 데이터를 내보내는 중입니다. 시간이 다소 걸릴 수 있습니다... - Exporting data. This may take a while... - - - - Exporting - + 내보내는 중 - + Exported Successfully - + 성공적으로 내보냄 + + + + Data was exported successfully. + 데이터를 성공적으로 내보냈습니다. - Data was exported successfully. - - - - Export Cancelled - + 내보내기 취소됨 - + Export was cancelled by the user. - + 사용자가 내보내기를 취소했습니다. - + Export Failed - + 내보내기 실패 - + Ensure you have write permissions on the targeted directory and try again. - + 대상 디렉터리에 쓰기 권한이 있는지 확인하고 다시 시도하세요. - + Select Import Location - + 가져오기 위치 선택 - + Import Warning - + 가져오기 경고 - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + 이 디렉터리의 이전 데이터가 모두 삭제됩니다. 계속하겠습니까? + + + + Importing data. This may take a while... + 데이터를 가져오는 중입니다. 시간이 다소 걸릴 수 있습니다... - Importing data. This may take a while... - - - - Importing - + 가져오는 중 - + Imported Successfully - + 성공적으로 가져옴 + + + + Data was imported successfully. + 데이터를 성공적으로 가져왔습니다. - Data was imported successfully. - - - - Import Cancelled - + 가져오기 취소됨 + + + + Import was cancelled by the user. + 사용자가 가져오기를 취소했습니다. - Import was cancelled by the user. - - - - Import Failed - + 가져오기 실패 - + Ensure you have read permissions on the targeted directory and try again. - + 대상 디렉터리에 읽기 권한이 있는지 확인하고 다시 시도하세요. + + + + Keys not installed + 키가 설치되지 않음 + + + + Install decryption keys and restart Eden before attempting to install firmware. + 펌웨어를 설치하기 전에 복호화 키를 설치하고 Eden을 재시작하세요. + + + + Select Dumped Firmware Source Location + 덤프된 펌웨어 소스 위치 선택 + + + + Select Dumped Firmware ZIP + 덤프된 펌웨어 ZIP 선택 + + + + Firmware cleanup failed + 펌웨어 정리 실패 + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + 추출된 펌웨어 캐시를 정리하는 데 실패했습니다. +시스템 임시 디렉터리의 쓰기 권한을 확인하고 다시 시도하세요. +OS에서 보고된 오류: %1 @@ -9443,17 +9633,17 @@ Only do this if you're 100% sure you want to delete this data. Linked Save Data - + 연결된 저장 데이터 Save data has been linked. - + 저장 데이터가 연결되었습니다. Failed to link save data - + 저장 데이터 연결에 실패했습니다 @@ -9461,76 +9651,81 @@ Only do this if you're 100% sure you want to delete this data. %1 To: %2 - + 디렉터리를 연결할 수 없습니다: + %1 +대상: + %2 Already Linked - + 이미 연결됨 This title is already linked to Ryujinx. Would you like to unlink it? - + 이 타이틀은 이미 Ryujinx에 연결되어 있습니다. 연결을 해제하겠습니까? Failed to unlink old directory - + 이전 디렉터리 연결 해제에 실패했습니다 OS returned error: %1 - + OS에서 반환된 오류: %1 Failed to copy save data - + 저장 데이터 복사에 실패했습니다 Unlink Successful - + 연결 해제 성공 Successfully unlinked Ryujinx save data. Save data has been kept intact. - + Ryujinx 저장 데이터 연결이 성공적으로 해제되었습니다. 저장 데이터는 그대로 유지되었습니다. Could not find Ryujinx installation - + Ryujinx 설치 파일을 찾을 수 없음 Could not find a valid Ryujinx installation. This may typically occur if you are using Ryujinx in portable mode. Would you like to manually select a portable folder to use? - + 유효한 Ryujinx 설치 파일을 찾을 수 없습니다. 이는 일반적으로 Ryujinx를 휴대용 모드로 사용하는 경우에 발생할 수 있습니다. + +사용할 휴대용 폴더를 수동으로 선택하겠습니까? Ryujinx Portable Location - + Ryujinx 이동 가능 위치 Not a valid Ryujinx directory - + 유효한 Ryujinx 디렉터리가 아님 The specified directory does not contain valid Ryujinx data. - + 지정된 디렉터리에 유효한 Ryujinx 데이터가 없습니다. Could not find Ryujinx save data - + Ryujinx 저장 데이터를 찾을 수 없음 @@ -9538,17 +9733,17 @@ Would you like to manually select a portable folder to use? Error Removing Contents - + 콘텐츠 제거 중 오류 발생 Error Removing Update - + 업데이트 제거 중 오류 발생 Error Removing DLC - + DLC 제거 중 오류 발생 @@ -9558,252 +9753,254 @@ Would you like to manually select a portable folder to use? Successfully Removed - + 성공적으로 제거됨 Successfully removed the installed base game. - + 설치된 기본 게임이 성공적으로 제거됐습니다. The base game is not installed in the NAND and cannot be removed. - + 기본 게임은 NAND에 설치되어 있지 않으며 제거할 수 없습니다. Successfully removed the installed update. - + 설치된 업데이트가 성공적으로 제거됐습니다. There is no update installed for this title. - + 이 타이틀에 설치된 업데이트가 없습니다. There are no DLCs installed for this title. - + 이 타이틀에 설치된 DLC가 없습니다. Successfully removed %1 installed DLC. - + 설치된 DLC %1이(가) 성공적으로 제거됐습니다. Error Removing Transferable Shader Cache - + 이동 가능한 셰이더 캐시 제거 중 오류 발생 A shader cache for this title does not exist. - + 이 타이틀에 대한 셰이더 캐시가 존재하지 않습니다. Successfully removed the transferable shader cache. - + 이동 가능한 셰이더 캐시를 성공적으로 제거했습니다. Failed to remove the transferable shader cache. - + 이동 가능한 셰이더 캐시를 제거하는 데 실패했습니다. Error Removing Vulkan Driver Pipeline Cache - + Vulkan 드라이버 파이프라인 캐시 제거 중 오류 발생 Failed to remove the driver pipeline cache. - + 드라이버 파이프라인 캐시를 제거하는 데 실패했습니다. Error Removing Transferable Shader Caches - + 이동 가능한 셰이더 캐시 제거 중 오류 발생 Successfully removed the transferable shader caches. - + 이동 가능한 셰이더 캐시를 성공적으로 제거했습니다. Failed to remove the transferable shader cache directory. - + 이동 가능한 셰이더 캐시 디렉터리를 제거하는 데 실패했습니다. Error Removing Custom Configuration - + 사용자 지정 구성 제거 중 오류 발생 A custom configuration for this title does not exist. - + 이 타이틀에 대한 사용자 지정 구성이 존재하지 않습니다. Successfully removed the custom game configuration. - + 사용자 지정 게임 구성을 성공적으로 제거했습니다. Failed to remove the custom game configuration. - + 사용자 지정 게임 구성을 제거하지 못했습니다. Reset Metadata Cache - + 메타 데이터 캐시 초기화 The metadata cache is already empty. - + 메타데이터 캐시가 이미 비어있습니다. The operation completed successfully. - + 작업이 성공적으로 완료되었습니다. The metadata cache couldn't be deleted. It might be in use or non-existent. - + 메타데이터 캐시를 삭제할 수 없습니다. 사용 중이거나 존재하지 않을 수 있습니다. - + Create Shortcut - + 바로가기 만들기 - + Do you want to launch the game in fullscreen? - + 게임을 전체 화면으로 실행하겠습니까? - + Shortcut Created - + 바로가기 생성됨 - + Successfully created a shortcut to %1 - - - - - Shortcut may be Volatile! - + %1 바로가기를 성공적으로 만듬 - This will create a shortcut to the current AppImage. This may not work well if you update. Continue? - + Shortcut may be Volatile! + 바로 가기는 변동성이 있을 수 있습니다! - - Failed to Create Shortcut - + + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? + 이렇게 하면 현재 앱 이미지에 대한 바로가기가 생성됩니다. 업데이트 후에는 제대로 작동하지 않을 수 있습니다. 계속하겠습니까? - Failed to create a shortcut to %1 - + Failed to Create Shortcut + 바로가기 만들기 실패 - - Create Icon - + + Failed to create a shortcut to %1 + %1 바로가기를 만드는 데 실패함 - Cannot create icon file. Path "%1" does not exist and cannot be created. - + Create Icon + 아이콘 만들기 - - No firmware available - + + Cannot create icon file. Path "%1" does not exist and cannot be created. + 아이콘 파일을 만들 수 없습니다. 경로 "%1"이(가) 존재하지 않으며 만들 수 없습니다. - Please install firmware to use the home menu. - + No firmware available + 사용 가능한 펌웨어 없음 - - Home Menu Applet - + + Please install firmware to use the home menu. + HOME 메뉴를 사용하려면 펌웨어를 설치하세요. + Home Menu Applet + HOME 메뉴 애플릿 + + + Home Menu is not available. Please reinstall firmware. - + HOME 메뉴를 사용할 수 없습니다. 펌웨어를 다시 설치해 주세요. QtCommon::Mod - + Mod Name - + 모드 이름 - + What should this mod be called? - + 이 모드의 이름은 뭐라고 해야 할까요? - + RomFS - + RomFS - + ExeFS/Patch - - - - - Cheat - + ExeFS/패치 - Mod Type - + Cheat + 치트 - + + Mod Type + 모드 유형 + + + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - + 모드 유형을 자동으로 감지할 수 없습니다. 다운로드한 모드의 유형을 수동으로 지정해 주세요. + +대부분의 모드는 RomFS 모드이지만, 패치 파일(.pchtxt)은 일반적으로 ExeFS 모드입니다. - - + + Mod Extract Failed - + 모드 추출 실패 - + Failed to create temporary directory %1 - + 임시 디렉터리 %1을(를) 생성하는 데 실패함 - + Zip file %1 is empty - + 압축 파일 %1이(가) 비어 있음 @@ -9811,12 +10008,12 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. Error Opening Shader Cache - + 셰이더 캐시 여는 중 오류 발생 Failed to create or open shader cache for this title, ensure your app data directory has write permissions. - + 이 타이틀에 대한 셰이더 캐시를 생성하거나 열 수 없습니다. 앱 데이터 디렉터리에 쓰기 권한이 있는지 확인하세요. @@ -9824,153 +10021,158 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. Contains game save data. DO NOT REMOVE UNLESS YOU KNOW WHAT YOU'RE DOING! - + 게임 저장 데이터가 포함되어 있습니다. 본인이 무엇을 하는건지 정확히 알지 못하면 절대 제거하지 마세요! Contains Vulkan and OpenGL pipeline caches. Generally safe to remove. - + Vulkan 및 OpenGL 파이프라인 캐시가 포함되어 있습니다. 일반적으로 제거해도 안전합니다. Contains updates and DLC for games. - + 게임 업데이트 및 DLC가 포함되어 있습니다. Contains firmware and applet data. - + 펌웨어 및 애플릿 데이터가 포함되어 있습니다. Contains game mods, patches, and cheats. - + 게임 모드, 패치, 치트가 포함되어 있습니다. Decryption Keys were successfully installed - + 복호화 키가 성공적으로 설치됨 Unable to read key directory, aborting - + 키 디렉터리를 읽을 수 없음, 중단 중 One or more keys failed to copy. - + 하나 이상의 키 복사에 실패했습니다. Verify your keys file has a .keys extension and try again. - + 키 파일에 .keys 확장자가 있는지 확인하고 다시 시도하세요. Decryption Keys failed to initialize. Check that your dumping tools are up to date and re-dump keys. - + 복호화 키 초기화에 실패했습니다. 덤프 도구가 최신 상태인지 확인하고 키를 다시 덤프하세요. Successfully installed firmware version %1 - + 펌웨어 버전 %1이(가) 성공적으로 설치됨 Unable to locate potential firmware NCA files - + 잠재적인 펌웨어 NCA 파일을 찾을 수 없음 Failed to delete one or more firmware files. - + 펌웨어 파일 하나 이상을 삭제하는 데 실패했습니다. One or more firmware files failed to copy into NAND. - + 하나 이상의 펌웨어 파일이 NAND로 복사되는 데 실패했습니다. Firmware installation cancelled, firmware may be in a bad state or corrupted. Restart Eden or re-install firmware. - + 펌웨어 설치가 취소되었습니다. 펌웨어 상태가 좋지 않거나 손상되었을 수 있습니다. Eden을 재시작하거나 펌웨어를 재설치하세요. Firmware missing. Firmware is required to run certain games and use the Home Menu. - + 펌웨어가 없습니다. 일부 게임을 실행하고 HOME 메뉴를 사용하려면 펌웨어가 필요합니다. Firmware reported as present, but was unable to be read. Check for decryption keys and redump firmware if necessary. - + 펌웨어가 있는 것으로 보고되었지만 읽을 수 없습니다. 복호화 키를 확인하고 필요한 경우 펌웨어를 다시 덤프하세요. Eden has detected user data for the following emulators: - + Eden은 다음 에뮬레이터에 대한 유저 데이터를 발견했습니다: Would you like to migrate your data for use in Eden? Select the corresponding button to migrate data from that emulator. This may take a while. - + Eden에서 사용하기 위해 데이터를 마이그레이션하겠습니까? +해당 에뮬레이터에서 데이터를 마이그레이션하려면 해당 버튼을 선택하세요. +이 작업은 다소 시간이 걸릴 수 있습니다. Clearing shader cache is recommended for all users. Do not uncheck unless you know what you're doing. - + 모든 사용자에게 셰이더 캐시를 지우는 것을 권장합니다. +무엇을 하는 건지 정확히 알지 못하는 경우 이 옵션을 해제하지 마세요. Keeps the old data directory. This is recommended if you aren't space-constrained and want to keep separate data for the old emulator. - + 이전 데이터 디렉터리를 유지합니다. 저장 공간에 제한이 없고 이전 에뮬레이터 데이터를 별도로 보관하려는 경우 이 옵션을 권장합니다. Deletes the old data directory. This is recommended on devices with space constraints. - + 이전 데이터 디렉터리를 삭제합니다. +저장 공간이 제한된 기기에서 사용하는 것이 좋습니다. Creates a filesystem link between the old directory and Eden directory. This is recommended if you want to share data between emulators. - + 이전 디렉터리와 Eden 디렉터리 사이를 연결할 파일 시스템을 생성합니다. +에뮬레이터 간에 데이터를 공유하려는 경우 이 방법을 권장합니다. Ryujinx title database does not exist. - + Ryujinx 타이틀 데이터베이스가 존재하지 않습니다. Invalid header on Ryujinx title database. - + Ryujinx 타이틀 데이터베이스에 잘못된 헤더가 있습니다. Invalid magic header on Ryujinx title database. - + Ryujinx 타이틀 데이터베이스에 잘못된 매직 헤더가 있습니다. Invalid byte alignment on Ryujinx title database. - + Ryujinx 타이틀 데이터베이스에 잘못된 바이트 정렬이 있습니다. No items found in Ryujinx title database. - + Ryujinx 타이틀 데이터베이스에서 항목을 찾을 수 없습니다. Title %1 not found in Ryujinx title database. - + %1 타이틀을 Ryujinx 타이틀 데이터베이스에서 찾을 수 없습니다. @@ -9983,7 +10185,7 @@ This is recommended if you want to share data between emulators. Supported Controller Types: - 지원하는 컨트롤러 유형: + 지원되는 컨트롤러 유형: @@ -9993,7 +10195,7 @@ This is recommended if you want to share data between emulators. 1 - 8 - 1 - 8 + 1 ~ 8 @@ -10011,7 +10213,7 @@ This is recommended if you want to share data between emulators. Pro Controller - 프로 컨트롤러 + Pro 컨트롤러 @@ -10062,7 +10264,7 @@ This is recommended if you want to share data between emulators. Use Current Config - 현재 설정 사용 + 현재 구성 사용 @@ -10114,7 +10316,7 @@ This is recommended if you want to share data between emulators. Docked - 거치 모드 + 독 모드 @@ -10125,7 +10327,7 @@ This is recommended if you want to share data between emulators. Configure - 설정 + 구성 @@ -10195,12 +10397,12 @@ This is recommended if you want to share data between emulators. Not enough controllers - + 컨트롤러가 충분하지 않음 GameCube Controller - GameCube 컨트롤러 + 게임큐브 컨트롤러 @@ -10235,21 +10437,21 @@ This is recommended if you want to share data between emulators. Error Code: %1-%2 (0x%3) - 에러 코드: %1-%2 (0x%3) + 오류 코드: %1-%2 (0x%3) An error has occurred. Please try again or contact the developer of the software. 오류가 발생했습니다. -다시 시도해 보시거나 해당 소프트웨어 개발자에게 연락하십시오. +다시 시도해 보거나 해당 소프트웨어 개발자에게 문의해 주세요. An error occurred on %1 at %2. Please try again or contact the developer of the software. - %2에서 %1에 대한 오류가 발생했습니다. -다시 시도해 보시거나 해당 소프트웨어 개발자에게 문의 하십시오. + %1의 %2에서 오류가 발생했습니다. +다시 시도해 보거나 해당 소프트웨어 개발자에게 문의해 주세요. @@ -10278,7 +10480,7 @@ Please try again or contact the developer of the software. Users - 사용자 + 유저 @@ -10289,17 +10491,17 @@ Please try again or contact the developer of the software. Profile Selector - 프로필 선택 + 프로필 선택기 Profile Icon Editor - 프로필 아이콘 에디터 + 프로필 아이콘 편집기 Profile Nickname Editor - 프로필 이름 에디터 + 프로필 이름 편집기 @@ -10309,47 +10511,47 @@ Please try again or contact the developer of the software. Who is using Nintendo eShop? - 누가 Nintendo eShop을 사용하고 있습니까? + 누가 닌텐도 e숍을 사용하고 있나요? Who is making this purchase? - 누가 이 구매를 하고 있습니까? + 누가 이 구매를 하고 있나요? Who is posting? - 누가 게시하고 있습니까? + 누가 게시하고 있나요? Select a user to link to a Nintendo Account. - Nintendo 계정에 연결할 사용자를 선택하십시오. + 닌텐도 어카운트에 연결할 유저를 선택하세요. Change settings for which user? - 어떤 사용자의 설정을 변경하시겠습니까? + 어떤 유저의 설정을 변경할까요? Format data for which user? - 어떤 사용자의 데이터를 포맷하시겠습니까? + 어떤 유저의 데이터를 포맷할까요? Which user will be transferred to another console? - 어떤 사용자가 다른 본체로 이전되나요? + 어떤 유저가 다른 본체로 이전되나요? Send save data for which user? - 어떤 사용자의 저장 데이터를 보내시겠습니까? + 어떤 유저의 저장 데이터를 보낼까요? Select a user: - 사용자를 선택하세요: + 유저를 선택하세요: @@ -10357,7 +10559,7 @@ Please try again or contact the developer of the software. Software Keyboard - 가상 키보드 + 소프트웨어 키보드 @@ -10381,7 +10583,7 @@ p, li { white-space: pre-wrap; } OK - OK + 확인 @@ -10394,39 +10596,41 @@ p, li { white-space: pre-wrap; } Ryujinx Link - + Ryujinx 연동 Linking save data to Ryujinx lets both Ryujinx and Eden reference the same save files for your games. By selecting "From Eden", previous save data stored in Ryujinx will be deleted, and vice versa for "From Ryujinx". - + Ryujinx와 Eden에 저장 데이터를 연동하면 Ryujinx와 Eden 모두 동일한 게임 저장 파일을 참조할 수 있습니다. + +"Eden에서 가져오기"를 선택하면 Ryujinx에 저장된 이전 저장 데이터가 삭제되고, "Ryujinx에서 가져오기"를 선택하면 그 반대로 Ryujinx에 저장된 이전 저장 데이터가 삭제됩니다. From Eden - + Eden에서 From Ryujinx - + Ryujinx에서 Cancel - + 취소 Failed to link save data - + 저장 데이터 연결에 실패했습니다 OS returned error: %1 - + OS에서 반환된 오류: %1 @@ -10434,7 +10638,7 @@ By selecting "From Eden", previous save data stored in Ryujinx will be Enter a hotkey - 단축키 입력 + 단축키를 입력하세요 @@ -10442,27 +10646,27 @@ By selecting "From Eden", previous save data stored in Ryujinx will be Set Play Time Data - + 플레이 시간 데이터 설정 Hours: - + 시간: Minutes: - + 분: Seconds: - + 초: Total play time reached maximum. - + 총 플레이 시간이 최대치에 도달했습니다. @@ -10470,92 +10674,93 @@ By selecting "From Eden", previous save data stored in Ryujinx will be Update Available - + 업데이트 가능 <a href="%1">View on Forgejo</a> - + <a href="%1">Forgejo에서 보기</a> Would you like to install this update? - + 이 업데이트를 설치하겠습니까? Available Versions - + 사용 가능한 버전 %1 is available for download. - - - - - New Version Location - + %1은(는) 다운로드가 가능합니다. + New Version Location + 새 버전 위치 + + + All Files (*.*) - + 모든 파일(*.*) - - - + + + Failed to save file - + 파일 저장 실패 - + Could not open file %1 for writing. - + %1 파일을 쓰기 위해 열 수 없습니다. - + Downloading... - + 다운로드 중... - + Cancel - + 취소 - + Could not write to file %1. - + %1 파일에 쓸 수 없습니다. - + Could not commit to file %1. - + %1 파일에 커밋할 수 없습니다. - + Failed to download file - + 파일 다운로드 실패 - + Could not download from %1%2 Error code: %3 - + %1%2에서 다운로드할 수 없습니다 +오류 코드: %3 - + Download Complete - + 다운로드 완료 - + Successfully downloaded %1. Would you like to open it? - + %1이(가) 성공적으로 다운로드됐습니다. 열어보겠습니까? \ No newline at end of file diff --git a/dist/languages/nb.ts b/dist/languages/nb.ts index 1c876beafc..191099c395 100644 --- a/dist/languages/nb.ts +++ b/dist/languages/nb.ts @@ -211,155 +211,6 @@ Dette vil bannlyse både deres forum brukernavn og deres IP adresse.%1 - %2 (%3/%4 medlemmer) - tilkoblet - - CompatDB - - - Report Compatibility - Meld inn kompatibilitet - - - - - - - - - - Report Game Compatibility - Meld inn spillkompatibilitet - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>Starter spillet?</p></body></html> - - - - Yes The game starts to output video or audio - Ja Spillet begynner å sende ut video eller lyd - - - - No The game doesn't get past the "Launching..." screen - Nei Spillet kommer ikke forbi "Starter..." skjermen - - - - Yes The game gets past the intro/menu and into gameplay - Ja Spillet kommer forbi introen/menyen og inn i spillet - - - - No The game crashes or freezes while loading or using the menu - Nei Spillet kræsjer eller fryser mens den laster eller mens man bruker menyen - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>Kommer spillet til spillingen?</p></body></html> - - - - Yes The game works without crashes - Ja Spillet fungerer uten noen kræsj - - - - No The game crashes or freezes during gameplay - Nei Spillet kræsjer eller fryser under spilling - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>Fungerer spillet uten å kræsje, fryse eller låse seg under spilling?</p></body></html> - - - - Yes The game can be finished without any workarounds - Ja Spillet kan bli fullført uten noen omveier - - - - No The game can't progress past a certain area - Nei Spillet kommer ikke forbi et vist punkt - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>Er spillet fullstendig spillbart fra start til slutt?</p></body></html> - - - - Major The game has major graphical errors - Større Spillet har større grafiske problemer - - - - Minor The game has minor graphical errors - Mindre Spillet har mindre grafiske problemer - - - - None Everything is rendered as it looks on the Nintendo Switch - Ingen Alt er gjengitt slik det ser ut på Nintendo Switch - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>Har spillet noen grafiske glicher?</p></body></html> - - - - Major The game has major audio errors - Større Spillet har større lydproblemer - - - - Minor The game has minor audio errors - Mindre Spillet har mindre lydproblemer - - - - None Audio is played perfectly - Ingen Lyden spilles av perfekt - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>Har spillet noen glicher med lyd / manglende effekter?</p></body></html> - - - - Thank you for your submission! - Takk for innsendingen din! - - - - Submitting - Sender inn - - - - Communication error - Kommunikasjonsfeil - - - - An error occurred while sending the Testcase - En feil oppstod under sending av testtilfellet - - - - Next - Neste - - ConfigurationShared @@ -559,231 +410,241 @@ Can help reduce stuttering at lower framerates. - + Backend: - CPU Overclock - CPU-overklokking - - - - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. + CPU Clocks - + + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + + + + Custom CPU Ticks - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. - + Enable Host MMU Emulation (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. - + Unfuse FMA (improve performance on CPUs without FMA) Del opp FMA (forbedre ytelsen på prosessorer uten FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. - + Faster FRSQRTE and FRECPE Raskere FRSQRTE og FRECPE - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. - + Faster ASIMD instructions (32 bits only) Raskere ASIMD-instruksjoner (kun 32-bit) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. - + Inaccurate NaN handling Unøyaktig NaN-håndtering - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. - + Disable address space checks Slå av adresseromskontroller - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. - + Ignore global monitor Ignorer global overvåkning - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. - + API: API: - + Changes the output graphics API. Vulkan is recommended. - + Device: Enhet: - + This setting selects the GPU to use (Vulkan only). - + Resolution: Oppløsning: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. - + Window Adapting Filter: Vindustilpasningsfilter: - + FSR Sharpness: FSR-skarphet: - - Determines how sharpened the image will look using FSR's dynamic contrast. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. - + Anti-Aliasing Method: Antialiasingmetode: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Fullskjermmodus: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. - + Aspect Ratio: Bildeforhold: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. - + Use persistent pipeline cache - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. - + Use asynchronous GPU emulation - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + NVDEC emulation: NVDEC-emulering: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. - + ASTC Decoding Method: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -792,55 +653,75 @@ stuttering but may present artifacts. - + ASTC Recompression Method: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. - + Skip CPU Inner Invalidation - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: VSync-modus: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -848,1361 +729,1371 @@ Immediate (no synchronization) presents whatever is available and can exhibit te - + Sync Memory Operations - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. - + Enable asynchronous presentation (Vulkan only) Aktiver asynkron presentasjon (kun Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. - + Force maximum clocks (Vulkan only) Tving maksikal klokkehastighet (kun Vulkan) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. Kjører arbeid i bakgrunnen mens den venter på grafikkommandoer for å forhindre at GPU-en senker klokkehastigheten. - + Anisotropic Filtering: Anisotropisk filtrering: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. - + GPU Mode: GPU-modus: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - + DMA Accuracy: DMA-nøyaktighet: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. - + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation - + May reduce shader stutter. - - Fast GPU Time + + GPU Clocks - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. - + GPU Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache Bruk Vulkan-rørledningsbuffer - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. - + Enable Compute Pipelines (Intel Vulkan Only) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. - + Enable Reactive Flushing Aktiver Reaktiv Tømming - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. - + Sync to framerate of video playback Synkroniser med bildefrekvensen for videoavspilling - + Run the game at normal speed during video playback, even when the framerate is unlocked. Kjør spillet i normal hastighet under videoavspilling, selv når bildefrekvensen er låst opp. - + Barrier feedback loops Tilbakekoblingssløyfer for barrierer - + Improves rendering of transparency effects in specific games. Forbedrer gjengivelsen av transparenseffekter i spesifikke spill. - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. - + Vertex Input Dynamic State - + Enables vertex input dynamic state feature for better quality and performance. - + Sample Shading - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. - + RNG Seed RNG-frø - + Controls the seed of the random number generator. Mainly used for speedrunning. - + Device Name Enhetsnavn - + The name of the console. Konsollens navn. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: Selvvalgt RTC-dato: - + This option allows to change the clock of the console. Can be used to manipulate time in games. - + The number of seconds from the current unix time - + Language: Språk: - + This option can be overridden when region setting is auto-select - + Region: Region: - + The region of the console. Konsollens region. - + Time Zone: Tidssone: - + The time zone of the console. Konsollens tidssone. - + Sound Output Mode: Lydutdatamodus: - + Console Mode: Konsollmodus: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. - + Prompt for user profile on boot - + Useful if multiple people use the same PC. - + Pause when not in focus - + Pauses emulation when focusing on other windows. - + Confirm before stopping emulation Bekreft før stopping av emulering - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. - + Hide mouse on inactivity Gjem mus under inaktivitet - + Hides the mouse after 2.5s of inactivity. - - Disable controller applet - Deaktiver kontroller-appleten - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - - - - + Check for updates Se etter oppdateringer - + Whether or not to check for updates upon startup. - + Enable Gamemode - + Force X11 as Graphics Backend - + Custom frontend - + Real applet - + + + Disabled + Skrudd av + + + Never Aldri - + On Load - + Always Alltid - + CPU CPU - + GPU Skjermkort - + CPU Asynchronous - + Uncompressed (Best quality) Ukomprimert (Best kvalitet) - + BC1 (Low quality) BC1 (Lav kvalitet) - + BC3 (Medium quality) BC3 (Middels kvalitet) - - + + Auto Auto - + 30 FPS 30 FPS - + 60 FPS 60 FPS - + 90 FPS 90 FPS - + 120 FPS 120 FPS - + Conservative - + Aggressive Aggressiv - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) OpenGL SPIR-V (eksperimentelt, kun AMD/Mesa) - + Null Null - + Fast Rask - - Balanced - Balansert - - - - + + + Accurate Nøyaktig - - + + + Default Standard - + Unsafe (fast) Utrygg (raskt) - + Safe (stable) Trygg (stabil) - + + Immediate + + + + + Balanced + Balansert + + + Unsafe Utrygt - + Paranoid (disables most optimizations) Paranoid (deaktiverer de fleste optimaliseringer) - + Debugging - + Dynarmic - + NCE NCE - + Borderless Windowed Rammeløst vindu - + Exclusive Fullscreen Eksklusiv fullskjerm - + No Video Output Ingen videoutdata - + CPU Video Decoding CPU-videodekoding - + GPU Video Decoding (Default) GPU-videodekoding (Standard) - + 0.25X (180p/270p) [EXPERIMENTAL] 0,25× (180p/270p) [EKSPERIMENTELL] - + 0.5X (360p/540p) [EXPERIMENTAL] 0,5× (360p/540p) [EKSPERIMENTELL] - + 0.75X (540p/810p) [EXPERIMENTAL] 0,75× (540p/810p) [EKSPERIMENTELL] - + 1X (720p/1080p) 1× (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] 1,25× (900p/1350p) [EKSPERIMENTELL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1,5× (1080p/1620p) [EKSPERIMENTELL] - + 2X (1440p/2160p) 2× (1440p/2160p) - + 3X (2160p/3240p) 3× (2160p/3240p) - + 4X (2880p/4320p) 4× (2880p/4320p) - + 5X (3600p/5400p) 5× (3600p/5400p) - + 6X (4320p/6480p) 6× (4320p/6480p) - + 7X (5040p/7560p) 7× (5040p/7560p) - + 8X (5760p/8640p) 8× (5760p/8640p) - + Nearest Neighbor Nærmeste nabo - + Bilinear Bilineær - + Bicubic Bikubisk - + Gaussian Gaussisk - + Lanczos Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution AMD FidelityFX Super Resolution - + Area Område - + MMPX MMPX - + Zero-Tangent - + B-Spline B-Spline - + Mitchell Mitchell - + Spline-1 Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None Ingen - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Standard (16:9) - + Force 4:3 Tving 4:3 - + Force 21:9 Tving 21:9 - + Force 16:10 Tving 16:10 - + Stretch to Window Strekk til vinduet - + Automatic Automatisk - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - 32x - - - - 64x - 64x - - - + Japanese (日本語) Japansk (日本語) - + American English Amerikansk engelsk - + French (français) Fransk (français) - + German (Deutsch) Tysk (Deutsch) - + Italian (italiano) Italiensk (italiano) - + Spanish (español) Spansk (español) - + Chinese Kinesisk - + Korean (한국어) Koreansk (한국어) - + Dutch (Nederlands) Nederlandsk (Nederlands) - + Portuguese (português) Portugisisk (português) - + Russian (Русский) Russisk (Русский) - + Taiwanese Taiwansk - + British English Britisk engelsk - + Canadian French Kanadisk fransk - + Latin American Spanish Latinamerikansk spansk - + Simplified Chinese Forenklet kinesisk - + Traditional Chinese (正體中文) Tradisjonell kinesisk (正體中文) - + Brazilian Portuguese (português do Brasil) Brasiliansk portugisisk (português do Brasil) - + Polish (polska) Polsk (polska) - + Thai (แบบไทย) Thai (แบบไทย) - - + + Japan Japan - + USA USA - + Europe Europa - + Australia Australia - + China Folkerepublikken Kina - + Korea Korea - + Taiwan Taiwan (Republikken Kina) - + Auto (%1) Auto select time zone Auto (%1) - + Default (%1) Default time zone Standard (%1) - + CET Sentraleuropeisk tid - + CST6CDT CST6CDT - + Cuba Cuba - + EET Østeuropeisk tid - + Egypt Egypt - + Eire Republikken Irland - + EST EST - + EST5EDT EST5EDT - + GB Storbritannia - + GB-Eire GB-Eire - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hongkong - + HST HST - + Iceland Island - + Iran Iran - + Israel Israel - + Jamaica Jamaica - + Kwajalein Kwajalein - + Libya Libya - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navajo - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Polen - + Portugal Portugal - + PRC Folkerepublikken Kina - + PST8PDT PST8PDT - + ROC Taiwan (Republikken Kina) - + ROK Sør-Korea - + Singapore Singapore - + Turkey Tyrkia - + UCT UCT - + Universal Universalt - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulu - + Mono Mono - + Stereo Stereo - + Surround Surround - + 4GB DRAM (Default) 4 GB DRAM (Standard) - + 6GB DRAM (Unsafe) 6 GB DRAM (Utrygt) - + 8GB DRAM 8 GB DRAM - + 10GB DRAM (Unsafe) 10 GB DRAM (Utrygt) - + 12GB DRAM (Unsafe) 12 GB DRAM (Utrygt) - + Docked I dokking - + Handheld Håndholdt - - - Off - Av + + + Normal + - - Boost (1700MHz) - Boost (1700 MHz) + + + Boost + - - Fast (2000MHz) - Rask (2000 MHz) + + + Overclock + - + Always ask (Default) Spør alltid (Standard) - + Only if game specifies not to stop - + Never ask Aldri spør - - - Medium (256) - Middels (256) - - - - - High (512) - Høy (512) - - - + Very Small (16 MB) Veldig liten (16 MB) - + Small (32 MB) Liten (32 MB) - + Normal (128 MB) Normal (128 MB) - + Large (256 MB) Stor (256 MB) - + Very Large (512 MB) Veldig stor (512 MB) - + Very Low (4 MB) Veldig lav (4 MB) - + Low (8 MB) Lav (8 MB) - + Normal (16 MB) Normal (16 MB) - + Medium (32 MB) Middels (32 MB) - + High (64 MB) Høy (64 MB) - + Very Low (32) Svært lav (32) - + Low (64) Lav (64) - + Normal (128) Normal (128) - - Disabled - Skrudd av + + Medium (256) + Middels (256) - + + High (512) + Høy (512) + + + ExtendedDynamicState 1 - + ExtendedDynamicState 2 - + ExtendedDynamicState 3 - + Tree View - + Grid View @@ -2562,252 +2453,297 @@ When a program attempts to open the controller applet, it is immediately closed. Global Loggfilter - - When checked, the max size of the log increases from 100 MB to 1 GB - Når dette er på øker maksstørrelsen til loggen fra 100 MB til 1 GB + + GPU Logging/Level + - - Enable Extended Logging** - Slå på utvidet loggføring** + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console Vis logg i konsollen - + + When checked, the max size of the log increases from 100 MB to 1 GB + Når dette er på øker maksstørrelsen til loggen fra 100 MB til 1 GB + + + + Enable Extended Logging** + Slå på utvidet loggføring** + + + Open Log Location Åpne loggplassering - + Homebrew Hjemmebrent - + Arguments String Argument streng - + Graphics Grafikk - + When checked, it executes shaders without loop logic changes Når dette er på kjører shader-e uten endring i løkkelogikk - + Disable Loop safety checks Deaktive Loop sikkerhetssjekker - + When checked, it will dump all the macro programs of the GPU Når det er merket av, vil det dumpe alle makroprogrammene til GPUen - + Dump Maxwell Macros Dump Maxwell Makroer - + When checked, it enables Nsight Aftermath crash dumps Når avhuket, aktiverer Nsight Aftermath kræsjdumper - + Enable Nsight Aftermath Aktiver Nsight Aftermath - - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Når det er merket av, vil det dumpe alle de originale assembler shaders fra disk shader cache eller spillet som funnet - - - - Dump Game Shaders - Dump spillskyggelegginger - - - - Enable Renderdoc Hotkey - Aktiver hurtigtast for Renderdoc - - - - When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower - Når den er merket av, deaktiverer den makrokompilatoren Just In Time. Aktivering av dette gjør at spill kjører saktere - - - - Disable Macro JIT - Deaktiver Macro JIT - - - - When checked, it disables the macro HLE functions. Enabling this makes games run slower - Når det er merket av, deaktiverer det makro HLE-funksjonene. Aktivering av dette gjør at spillene kjører saktere - - - - Disable Macro HLE - Deaktiver Macro HLE - - - - When checked, the graphics API enters a slower debugging mode - Når dette er på går grafikk–API-et inn i en tregere feilsøkingsmodus - - - - Enable Graphics Debugging - Slå på Grafikkfeilsøking - - - - When checked, yuzu will log statistics about the compiled pipeline cache - Når det er merket av, vil yuzu logge statistikk om den kompilerte rørledningsbufferen - - Enable Shader Feedback - Slå på shader-tilbakemelding + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + - - <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + Dump Guest (Maxwell) Shaders + Enable Renderdoc Hotkey + Aktiver hurtigtast for Renderdoc + + + + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower + Når den er merket av, deaktiverer den makrokompilatoren Just In Time. Aktivering av dette gjør at spill kjører saktere + + + + Disable Macro JIT + Deaktiver Macro JIT + + + + When checked, it disables the macro HLE functions. Enabling this makes games run slower + Når det er merket av, deaktiverer det makro HLE-funksjonene. Aktivering av dette gjør at spillene kjører saktere + + + + Disable Macro HLE + Deaktiver Macro HLE + + + + When checked, the graphics API enters a slower debugging mode + Når dette er på går grafikk–API-et inn i en tregere feilsøkingsmodus + + + + Enable Graphics Debugging + Slå på Grafikkfeilsøking + + + + When checked, yuzu will log statistics about the compiled pipeline cache + Når det er merket av, vil yuzu logge statistikk om den kompilerte rørledningsbufferen + + + + Enable Shader Feedback + Slå på shader-tilbakemelding + + + + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + + + Disable Buffer Reorder - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced Avansert - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Gjør det mulig for yuzu å se etter et fungerende Vulkan-miljø når programmet starter opp. Deaktiver dette hvis dette forårsaker problemer ved at eksterne programmer ser yuzu. - + Perform Startup Vulkan Check Utfør Vulkan-sjekk ved oppstart - + Disable Web Applet Slå av web-applet - + Enable All Controller Types Skru på alle kontrollertyper - + Enable Auto-Stub - + Kiosk (Quest) Mode Kioskmodus (Quest-modus) - + Use dev.keys - + Enable Debug Asserts Aktiver Feilsøkingsoppgaver - + Debugging Feilsøking - + Battery Serial: - + + Debug Knobs: + + + + Bitmask for quick development toggles - + Set debug knobs (bitmask) - + 16-bit debug knob set for quick development toggles - + (bitmask) - - Debug Knobs: - - - - + Unit Serial: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. Aktiver dette for å sende den siste genererte lydkommandolisten til konsollen. Påvirker bare spill som bruker lydrenderen. - + Dump Audio Commands To Console** Dump lydkommandoer til konsollen** - + Flush log output on each line - + Enable FS Access Log Skru på FS-tilgangslogg - + Enable Verbose Reporting Services** Aktiver Verbose Reporting Services** - + Censor username in logs - + **This will be reset automatically when Eden closes. @@ -3257,33 +3193,38 @@ Would you like to delete the old save data? Bakgrunnsfarge: - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off Av - + VSync Off VSync Av - + Recommended Anbefalt - + On På - + VSync On VSync På @@ -3804,6 +3745,16 @@ Would you like to delete the old save data? Motion / Touch Bevegelse / Berøring + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4313,58 +4264,58 @@ For å invertere aksene, flytt først stikken vertikalt, og så horistonalt.[venter] - + New Profile Ny profil - + Enter a profile name: Skriv inn et profilnavn: - - + + Create Input Profile Opprett inndataprofil - + The given profile name is not valid! Det oppgitte profilenavnet er ugyldig! - + Failed to create the input profile "%1" Klarte ikke lage inndataprofil "%1" - + Delete Input Profile Slett inndataprofil - + Failed to delete the input profile "%1" Klarte ikke slette inndataprofil "%1" - + Load Input Profile Last inn inndataprofil - + Failed to load the input profile "%1" Klarte ikke laste inn inndataprofil "%1" - + Save Input Profile Lagre inndataprofil - + Failed to save the input profile "%1" Klarte ikke lagre inndataprofil "%1" @@ -4412,7 +4363,7 @@ For å invertere aksene, flytt først stikken vertikalt, og så horistonalt. - + Configure Sett opp @@ -4443,7 +4394,7 @@ For å invertere aksene, flytt først stikken vertikalt, og så horistonalt. - + Test Test @@ -4458,77 +4409,77 @@ For å invertere aksene, flytt først stikken vertikalt, og så horistonalt.Fjern tjener - + %1:%2 %1:%2 - - - - - - + + + + + + Eden Eden - + Port number has invalid characters Portnummeret har ugyldige tegn - + Port has to be in range 0 and 65353 Porten må være i intervallet 0 til 65353 - + IP address is not valid IP-adressen er ugyldig - + This UDP server already exists Denne UDP-tjeneren eksisterer allerede - + Unable to add more than 8 servers Kan ikke legge til mer enn 8 tjenere - + Testing Testing - + Configuring Konfigurering - + Test Successful Testen var vellykket - + Successfully received data from the server. Mottatt data fra serveren vellykket. - + Test Failed Testen mislyktes - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Kunne ikke motta gyldig data fra serveren.<br>Vennligst bekreft at serveren er satt opp riktig og at adressen og porten er riktige. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. UDP-test eller kalibrasjonskonfigurering er i fremgang.<br>Vennligst vent for dem til å bli ferdig. @@ -4713,57 +4664,57 @@ Gjeldende verdier er henholdsvis %1% og %2%. Noen innstillinger er bare tilgjengelige når spillet ikke er i gang. - + Add-Ons Tillegg - + System System - + CPU CPU - + Graphics Grafikk - + Adv. Graphics Avansert grafikk - + Ext. Graphics - + Audio Lyd - + Input Profiles Inndataprofiler - + Network Nettverk - + Applets - + Properties Egenskaper @@ -4886,6 +4837,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5452,91 +5527,86 @@ Dra punkter for å endre posisjon, eller dobbelttrykk på tabellfelter for å re - Show Compatibility List - Vis kompatibilitetsliste - - - Show Add-Ons Column Vis tilleggskolonnen - + Show Size Column Vis størrelseskolonne - + Show File Types Column Vis filtypekolonne - + Show Play Time Column - + Folder Icon Size: Mappeikonstørrelse: - + Row 1 Text: Rad 1 tekst: - + Row 2 Text: Rad 2 tekst: - + Screenshots Skjermbilder - + Ask Where To Save Screenshots (Windows Only) Spør om hvor skjermbilder skal lagres (kun for Windows) - + Screenshots Path: Skjermbilde-filbane: - + ... ... - + TextLabel TextLabel - + Resolution: Oppløsning: - + Select Screenshots Path... Velg skjermbilde-filbane... - + <System> <System> - + English Engelsk - + Auto (%1 x %2, %3 x %4) Screenshot width value Auto (%1 x %2, %3 x %4) @@ -5773,7 +5843,7 @@ Dra punkter for å endre posisjon, eller dobbelttrykk på tabellfelter for å re - + Calculating... @@ -5975,50 +6045,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL ikke tilgjengelig! - + OpenGL shared contexts are not supported. Delte OpenGL-kontekster støttes ikke. - + Eden has not been compiled with OpenGL support. - - - + + + Error while initializing OpenGL! Feil under initialisering av OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Det kan hende at GPU-en din ikke støtter OpenGL, eller at du ikke har den nyeste grafikkdriveren. - + Error while initializing OpenGL 4.6! Feil under initialisering av OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Det kan hende at GPU-en din ikke støtter OpenGL 4.6, eller at du ikke har den nyeste grafikkdriveren.<br><br>GL-renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Det kan hende at GPU-en din ikke støtter én eller flere nødvendige OpenGL-utvidelser. Vennligst sørg for at du har den nyeste grafikkdriveren.<br><br>GL-renderer: <br>%1<br><br>Ikke-støttede utvidelser:<br>%2 - + This build doesn't have OpenGL support. @@ -6026,279 +6096,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory Legg til ny spillm&appe - + Favorite Legg til som favoritt - + Start Game Start spill - + Start Game without Custom Configuration Start spill uten tilpasset oppsett - + Open Save Data Location Åpne lagrefilplassering - + Open Mod Data Location Åpne moddataplassering - + Open Transferable Pipeline Cache Åpne overførbar rørledningsbuffer - + Link to Ryujinx - + Remove Fjern - + Remove Installed Update Fjern installert oppdatering - + Remove All Installed DLC Fjern all installert DLC - + Remove Custom Configuration Fjern tilpasset oppsett - + Remove Cache Storage Tøm hurtiglager - + Remove OpenGL Pipeline Cache Fjern OpenGL-rørledningsbuffer - + Remove Vulkan Pipeline Cache Fjern Vulkan-rørledningsbuffer - + Remove All Pipeline Caches Fjern alle rørledningsbuffere - + Remove All Installed Contents Fjern alt installert innhold - + Manage Play Time - + Edit Play Time Data - + Remove Play Time Data - - + + Dump RomFS Dump RomFS - + Dump RomFS to SDMC Dump RomFS til SDMC - + Verify Integrity Verifiser integritet - + Copy Title ID to Clipboard Kopier Tittel-ID til Utklippstavle - - Navigate to GameDB entry - Naviger til GameDB-oppføring - - - + Create Shortcut Opprett snarvei - + Add to Desktop Legg Til På Skrivebordet - + Add to Applications Menu Legg Til Applikasjonsmenyen - + Configure Game Oppsett av spillet - + Scan Subfolders Skann Undermapper - + Remove Game Directory Fjern spillmappe - + ▲ Move Up ▲ Flytt Opp - + ▼ Move Down ▼ Flytt Ned - + Open Directory Location Åpne Spillmappe - + Clear Tøm - - - Name - Navn - - - - Compatibility - Kompatibilitet - - - - Add-ons - Tilleggsprogrammer - - - - File type - Filtype - - - - Size - Størrelse - - - - Play time - - GameListItemCompat - + Ingame i Spillet - + Game starts, but crashes or major glitches prevent it from being completed. Spillet starter, men krasjer eller større feil gjør at det ikke kan fullføres. - + Perfect Perfekt - + Game can be played without issues. Spillet kan spilles uten problemer. - + Playable Spillbar - + Game functions with minor graphical or audio glitches and is playable from start to finish. Spillet fungerer med mindre grafiske eller lydfeil og kan spilles fra start til slutt. - + Intro/Menu Intro/Meny - + Game loads, but is unable to progress past the Start Screen. Spillet lastes inn, men kan ikke gå videre forbi startskjermen. - + Won't Boot Vil ikke starte - + The game crashes when attempting to startup. Spillet krasjer under oppstart. - + Not Tested Ikke testet - + The game has not yet been tested. Spillet har ikke blitt testet ennå. + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list Dobbeltrykk for å legge til en ny mappe i spillisten @@ -6306,17 +6369,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) %1 of %n resultat%1 av %n resultat(er) - + Filter: Filter: - + Enter pattern to filter Angi mønster for å filtrere @@ -6811,1139 +6874,1112 @@ Debug Message: &Spilliste-modus - + Game &Icon Size Spill&ikonstørrelse - + Reset Window Size to &720p Tilbakestill vindusstørrelsen til &720p - + Reset Window Size to 720p Tilbakestill vindusstørrelsen til 720p - + Reset Window Size to &900p Tilbakestill vindusstørrelsen til &900p - + Reset Window Size to 900p Tilbakestill vindusstørrelsen til 900p - + Reset Window Size to &1080p Tilbakestill vindusstørrelsen til &1080p - + Reset Window Size to 1080p Tilbakestill vindusstørrelsen til 1080p - + &Multiplayer &Flerspiller - + &Tools Verk&tøy - + Am&iibo am&iibo - + Launch &Applet - + &TAS &TAS - + &Create Home Menu Shortcut - + Install &Firmware Installér &fastvare - + &Help &Hjelp - + &Install Files to NAND... &Installér filer til NAND ... - + L&oad File... La&st inn fil ... - + Load &Folder... Last inn ma&ppe... - + E&xit A&vslutt - - + + &Pause &Pause - + &Stop &Stopp - + &Verify Installed Contents - + &About Eden &Om Eden - + Single &Window Mode Enkelt&vindusmodus - + Con&figure... Se&tt opp ... - + Ctrl+, - + Enable Overlay Display Applet - + Show &Filter Bar Vis &filterlinje - + Show &Status Bar Vis &statuslinje - + Show Status Bar Vis statuslinje - + &Browse Public Game Lobby Bla gjennom den offentlige spillo&bbyen - + &Create Room &Opprett rom - + &Leave Room &Forlat rom - + &Direct Connect to Room &Direkte tilkobling til rommet - + &Show Current Room &Vis nåværende rom - + F&ullscreen F&ullskjerm - + &Restart &Omstart - + Load/Remove &Amiibo... Last/Fjern &amiibo ... - - &Report Compatibility - Meld inn kompati&bilitet - - - + Open &Mods Page Åpne Modifikasjonssiden (&M) - - Open &Quickstart Guide - Åpne Hurtigstartsguiden (&Q) + + &User Handbook + - - &FAQ - &FAQ - - - + &Capture Screenshot &Ta skjermklipp - + &Album &Album - + &Set Nickname and Owner - + &Delete Game Data &Slett spilldata - + &Restore Amiibo &Gjenopprett amiibo - + &Format Amiibo - + &Mii Editor - + &Configure TAS... &Konfigurer TAS ... - + Configure C&urrent Game... Konfigurer nåværende spill ... - - + + &Start &Start - + &Reset &Omstart - - + + R&ecord T&a opp - + Open &Controller Menu Åpne &kontrollermeny - + Install Decryption &Keys - + &Home Menu &Hjem-meny - + &Desktop &Skrivebord - + &Application Menu &Appmeny - + &Root Data Folder &Rotdatamappe - + &NAND Folder &NAND-mappe - + &SDMC Folder &SDMC-mappe - + &Mod Folder &Mod-mappe - + &Log Folder &Loggmappe - + From Folder Fra mappe - + From ZIP Fra ZIP - + &Eden Dependencies &Eden-avhengigheter - + &Data Manager &Databehandler - + &Tree View &Trevisning - + &Grid View &Rutenettvisning - + Game Icon Size Spillikonstørrelse - - + + None Ingen - + Show Game &Name Vis spill&navn - + Show &Performance Overlay Vis &ytelsesoverlegg - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) Liten (32x32) - + Standard (64x64) Standard (64x64) - + Large (128x128) Stor (128x128) - + Full Size (256x256) Full størrelse (256x256) - + Broken Vulkan Installation Detected - + Vulkan initialization failed during boot. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping Kjører et spill - + Loading Web Applet... - - + + Disable Web Applet - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + The amount of shaders currently being built - + The current selected resolution scaling multiplier. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Unmute Slå på lyd - + Mute Demp - + Reset Volume Tilbakestill volum - + + Configure Effects... + + + + &Clear Recent Files &Tøm nylige filer - + &Continue &Fortsett - + Warning: Outdated Game Format - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - - + + Error while loading ROM! - + The ROM format is not supported. - + An error occurred initializing the video core. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + An unknown error occurred. Please see the log for more details. - + + (64-bit) (64-bit) - + + (32-bit) (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + Closing software... Lukker programvare ... - + Save Data - + Mod Data - + Error Opening %1 Folder - - + + Folder does not exist! Mappen finnes ikke! - + Remove Installed Game Contents? - + Remove Installed Game Update? - + Remove Installed Game DLC? - + Remove Entry - + Delete OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? - + Remove Custom Game Configuration? - + Remove Cache Storage? - + Remove File Fjern fil - + Remove Play Time Data - + Reset play time? - - + + RomFS Extraction Failed! - + There was an error copying the RomFS files or the user cancelled the operation. - + Full Full - + Skeleton Skjelett - + Select RomFS Dump Mode - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Extracting RomFS... Utvinner RomFS... - - + + Cancel Avbryt - + RomFS Extraction Succeeded! - + The operation completed successfully. - + Error Opening %1 - + Select Directory Velg mappe - + Properties Egenskaper - + The game properties could not be loaded. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Load File - + Open Extracted ROM Directory - + Invalid Directory Selected - + The directory you have selected does not contain a 'main' file. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files - + %n file(s) remaining - + Installing file "%1"... Installerer filen «%1» ... - - + + Install Results - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + %n file(s) were newly installed - + %n file(s) were overwritten - + %n file(s) failed to install - + System Application Systemapp - + System Archive Systemarkiv - + System Application Update - + Firmware Package (Type A) Fastvarepakke (Type A) - + Firmware Package (Type B) Fastvarepakke (Type B) - + Game Spill - + Game Update Spilloppdatering - + Game DLC Spill-DLC - + Delta Title - + Select NCA Install Type... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Failed to Install - + The title type you selected for the NCA is invalid. - + File not found Filen ble ikke funnet - + File "%1" not found - + OK OK - + Function Disabled - + Compatibility list reporting is currently disabled. Check back later! - + Error opening URL Feil under åpning av URL - + Unable to open the URL "%1". - + TAS Recording TAS-opptak - + Overwrite file of player 1? - + Invalid config detected - + Handheld controller can't be used on docked mode. Pro controller will be selected. - - + + Amiibo amiibo - - + + The current amiibo has been removed - + Error Feil - - + + The current game is not looking for amiibos Det nåværende spillet ser ikke etter amiiboer - + Amiibo File (%1);; All Files (*.*) amiibo-fil (%1);; Alle filer (*.*) - + Load Amiibo Last inn amiibo - + Error loading Amiibo data - + The selected file is not a valid amiibo - + The selected file is already on use - + An unknown error occurred - - - Keys not installed - - - - - - Install decryption keys and restart Eden before attempting to install firmware. - - - - - Select Dumped Firmware Source Location - - - - - Select Dumped Firmware ZIP - - - - - Zipped Archives (*.zip) - Zippede arkiver (*.zip) - - - - Firmware cleanup failed - - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available Ingen fastvare tilgjengelig - + Firmware Corrupted - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot Ta skjermklipp - + PNG Image (*.png) PNG-bilde (*.png) - + TAS state: Running %1/%2 - + TAS state: Recording %1 - + TAS state: Idle %1/%2 - + TAS State: Invalid - + &Stop Running - + Stop R&ecording - + Building: %n shader(s) - + Scale: %1x %1 is the resolution scaling factor Skala: %1x - + Speed: %1% / %2% Hastighet: %1% / %2% - + Speed: %1% Hastighet: %1% - + Game: %1 FPS Spill: %1 FPS - + Frame: %1 ms Ramme: %1 ms - - - FSR - FSR - - - + NO AA INGEN AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE VOLUM: DEMP - + VOLUME: %1% Volume percentage (e.g. 50%) VOLUM: %1% - + Derivation Components Missing - + Decryption keys are missing. Install them now? - + Wayland Detected! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -7951,74 +7987,74 @@ Would you like to force it for future launches? - + Use X11 Bruk X11 - + Continue with Wayland - + Don't show again Ikke vis igjen - + Restart Required - + Restart Eden to apply the X11 backend. - + Slow Treg - + Turbo Turbo - + Unlocked Ubegrenset - + Select RomFS Dump Target - + Please select which RomFS you would like to dump. - + Are you sure you want to close Eden? - - - + + + Eden Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8084,6 +8120,11 @@ Would you like to bypass this and exit anyway? ScaleForce + + + FSR + FSR + Area @@ -8095,52 +8136,57 @@ Would you like to bypass this and exit anyway? MMPX - + + SGSR + + + + + SGSR EdgeDir + + + + Docked I dokking - + Handheld Håndholdt - + Fast Rask - - Balanced - Balansert - - - + Accurate Nøyaktig - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL SPIRV OpenGL SPIRV - + OpenGL GLASM OpenGL GLASM - + Null Null @@ -8173,7 +8219,7 @@ If you wish to clean up the files which were left in the old data location, you - + Data was migrated successfully. @@ -8665,8 +8711,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8679,10 +8725,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8697,9 +8743,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8861,50 +8907,50 @@ p, li { white-space: pre-wrap; } [udefinert] - + %1%2 %1%2 - + [invalid] [ugyldig] - - + + %1%2Hat %3 %1%2Hat %3 - - - + + + %1%2Axis %3 %1%2Akse %3 - + %1%2Axis %3,%4,%5 %1%2Akse %3,%4,%5 - + %1%2Motion %3 %1%2Bevegelse %3 - - + + %1%2Button %3 %1%2Knapp %3 - + [unused] [ubrukt] @@ -9030,47 +9076,47 @@ p, li { white-space: pre-wrap; } %1 spiller %2 - + Play Time: %1 Tid spilt: %1 - + Never Played Aldri spilt - + Version: %1 Versjon: %1 - + Version: 1.0.0 Versjon: 1.0.0 - + Installed SD Titles Installerte SD-titler - + Installed NAND Titles Installerte NAND-titler - + System Titles Systemtitler - + Add New Game Directory Legg til ny spillmappe - + Favorites Favoritter @@ -9191,13 +9237,13 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware Spillet krever fastvare - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. @@ -9207,10 +9253,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel Avbryt @@ -9231,213 +9277,246 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? Er du HELT sikker? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip %1.zip - - + + + Zipped Archives (*.zip) Zippede arkiver (*.zip) - + Exporting data. This may take a while... - + Exporting Eksporterer - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed Eksportering mislyktes - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing Importerer - + Imported Successfully Importeringen var vellykket - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed Importering mislyktes - + Ensure you have read permissions on the targeted directory and try again. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9681,72 +9760,72 @@ Would you like to manually select a portable folder to use? - + Create Shortcut Opprett snarvei - + Do you want to launch the game in fullscreen? - + Shortcut Created - + Successfully created a shortcut to %1 - + Shortcut may be Volatile! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? - + Failed to Create Shortcut - + Failed to create a shortcut to %1 - + Create Icon Opprett ikon - + Cannot create icon file. Path "%1" does not exist and cannot be created. - + No firmware available Ingen fastvare tilgjengelig - + Please install firmware to use the home menu. - + Home Menu Applet - + Home Menu is not available. Please reinstall firmware. @@ -9754,55 +9833,55 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name Mod-navn - + What should this mod be called? - + RomFS RomFS - + ExeFS/Patch ExeFS/Patch - + Cheat - + Mod Type Mod-type - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty «%1»-zip-filen er tom @@ -10496,65 +10575,65 @@ By selecting "From Eden", previous save data stored in Ryujinx will be - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/nl.ts b/dist/languages/nl.ts index 81d70da92c..8e99ce56d4 100644 --- a/dist/languages/nl.ts +++ b/dist/languages/nl.ts @@ -211,155 +211,6 @@ Dit zou zowel hun forum gebruikersnaam als hun IP-adres verbannen. %1 - %2 (%3/%4 leden) - verbonden - - CompatDB - - - Report Compatibility - Rapporteer Compatibiliteit - - - - - - - - - - Report Game Compatibility - Rapporteer Spelcompatibiliteit - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>Start het spel op?</p></body></html> - - - - Yes The game starts to output video or audio - Ja Het spel begint video of audio te produceren - - - - No The game doesn't get past the "Launching..." screen - Nee Het spel komt niet voorbij het "Starten..." scherm - - - - Yes The game gets past the intro/menu and into gameplay - Ja Het spel komt voorbij het intro/menu en begint met het spel - - - - No The game crashes or freezes while loading or using the menu - Nee Het spel loopt vast tijdens het laden of gebruik van het menu - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>Bereikt het spel de gameplay?</p></body></html> - - - - Yes The game works without crashes - Ja Het spel werkt zonder crashes - - - - No The game crashes or freezes during gameplay - Nee Het spel loopt vast of loopt vast tijdens het spelen - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>Werkt het spel zonder te crashen, te bevriezen of vast te lopen tijdens het spelen?</p></body></html> - - - - Yes The game can be finished without any workarounds - Ja Het spel kan zonder omwegen worden uitgespeeld - - - - No The game can't progress past a certain area - Nee Het spel kan niet verder dan een bepaald gebied - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>Is het spel volledig speelbaar van begin tot eind?</p></body></html> - - - - Major The game has major graphical errors - Major Het spel heeft aanzienlijke grafische fouten - - - - Minor The game has minor graphical errors - Minor Het spel heeft lichte grafische fouten - - - - None Everything is rendered as it looks on the Nintendo Switch - Geen Alles wordt weergegeven zoals het eruit ziet op de Nintendo Switch - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>Heeft het spel grafische glitches?</p></body></html> - - - - Major The game has major audio errors - Major Het spel heeft aanzienlijke audiofouten - - - - Minor The game has minor audio errors - Minor Het spel heeft lichte audiofouten - - - - None Audio is played perfectly - Geen Audio wordt perfect afgespeeld - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>Heeft het spel audio-glitches / ontbrekende effecten?</p></body></html> - - - - Thank you for your submission! - Bedankt voor je inzending! - - - - Submitting - Inzenden - - - - Communication error - Communicatiefout - - - - An error occurred while sending the Testcase - Er is een fout gebeurd tijdens het versturen van de Testcase - - - - Next - Volgende - - ConfigurationShared @@ -562,40 +413,38 @@ Kan helpen om haperingen bij lagere framerates te verminderen. - + Backend: - CPU Overclock + CPU Clocks - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. - Overklokt de geëmuleerde CPU om enkele FPS-beperkingen op te heffen. Zwakkere CPU's kunnen minder presteren en bepaalde games kunnen mogelijk niet goed werken. -Gebruik Boost (1700 MHz) om op de hoogste native kloksnelheid van de Switch te draaien, of Fast (2000 MHz) om op 2x kloksnelheid te draaien. - + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + - + Custom CPU Ticks Aangepaste CPU-ticks - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. - + Enable Host MMU Emulation (fastmem) Host MMU-emulatie inschakelen (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. @@ -604,195 +453,205 @@ Als u deze optie inschakelt, worden lees- en schrijfbewerkingen in het gastgeheu Als u deze optie uitschakelt, worden alle geheugentoegangen gedwongen om gebruik te maken van software-MMU-emulatie. - + Unfuse FMA (improve performance on CPUs without FMA) Ontbind FMA (verbeterd prestatie op CPU's zonder FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. Deze optie verbetert de snelheid door de nauwkeurigheid van fused-multiply-add-instructies op CPU's zonder native FMA-ondersteuning te verminderen. - + Faster FRSQRTE and FRECPE Snellere FRSRTE en FRECPE - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. Deze optie verbetert de snelheid van sommige benaderende drijvende-kommagetal-functies door minder nauwkeurige native benaderingen te gebruiken. - + Faster ASIMD instructions (32 bits only) Snellere ASIMD-instructies (alleen 32-bits) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. Deze optie verbetert de snelheid van 32-bits ASIMD-drijvende-kommagetallen door te werken met onjuiste afrondingsmodi. - + Inaccurate NaN handling Onnauwkeurige NaN-verwerking - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. Deze optie verbetert de snelheid door NaN-controle te verwijderen. Houd er rekening mee dat dit ook de nauwkeurigheid van bepaalde drijvende-kommainstructies vermindert. - + Disable address space checks Schakel adresruimtecontroles uit - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. - + Ignore global monitor Negeer globale monitor - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. Deze optie verbetert de snelheid door alleen te vertrouwen op de semantiek van cmpxchg om de veiligheid van exclusieve toegangsinstructies te waarborgen. Houd er rekening mee dat dit kan leiden tot deadlocks en andere race conditions. - + API: API: - + Changes the output graphics API. Vulkan is recommended. - + Device: Apparaat: - + This setting selects the GPU to use (Vulkan only). - + Resolution: Resolutie: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. - + Window Adapting Filter: Window Adapting Filter: - + FSR Sharpness: FSR-scherpte: - - Determines how sharpened the image will look using FSR's dynamic contrast. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. - + Anti-Aliasing Method: Antialiasing-methode: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Volledig scherm modus: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. - + Aspect Ratio: Aspect Ratio: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. - + Use persistent pipeline cache - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. - + Use asynchronous GPU emulation - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + NVDEC emulation: NVDEC-emulatie: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. - + ASTC Decoding Method: ASTC Decodeer Methode: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -801,55 +660,75 @@ stuttering but may present artifacts. - + ASTC Recompression Method: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. - + Skip CPU Inner Invalidation - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: VSync-modus: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -857,1361 +736,1371 @@ Immediate (no synchronization) presents whatever is available and can exhibit te - + Sync Memory Operations - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. - + Enable asynchronous presentation (Vulkan only) Schakel asynchrone presentatie in (alleen Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. - + Force maximum clocks (Vulkan only) Forceer maximale klokken (alleen Vulkan) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. Werkt op de achtergrond terwijl er wordt gewacht op grafische opdrachten om te voorkomen dat de GPU zijn kloksnelheid verlaagt. - + Anisotropic Filtering: Anisotrope Filtering: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. - + GPU Mode: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - + DMA Accuracy: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. - + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation - + May reduce shader stutter. - - Fast GPU Time + + GPU Clocks - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. - + GPU Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache Gebruik Vulkan-pijplijn-cache - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. - + Enable Compute Pipelines (Intel Vulkan Only) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. - + Enable Reactive Flushing Schakel Reactive Flushing In - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. - + Sync to framerate of video playback - + Run the game at normal speed during video playback, even when the framerate is unlocked. - + Barrier feedback loops - + Improves rendering of transparency effects in specific games. - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. - + Vertex Input Dynamic State - + Enables vertex input dynamic state feature for better quality and performance. - + Sample Shading - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. - + RNG Seed RNG Seed - + Controls the seed of the random number generator. Mainly used for speedrunning. - + Device Name Apparaatnaam - + The name of the console. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: Aangepaste RTC Datum: - + This option allows to change the clock of the console. Can be used to manipulate time in games. - + The number of seconds from the current unix time - + Language: Taal: - + This option can be overridden when region setting is auto-select - + Region: Regio: - + The region of the console. - + Time Zone: Tijdzone: - + The time zone of the console. - + Sound Output Mode: Geluidsuitvoermodus: - + Console Mode: Console Modus: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. - + Prompt for user profile on boot - + Useful if multiple people use the same PC. - + Pause when not in focus - + Pauses emulation when focusing on other windows. - + Confirm before stopping emulation Bevestig voordat u de emulatie stopt - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. - + Hide mouse on inactivity Verberg muis wanneer inactief - + Hides the mouse after 2.5s of inactivity. - - Disable controller applet - Controller-applet uitschakelen - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - - - - + Check for updates - + Whether or not to check for updates upon startup. - + Enable Gamemode - + Force X11 as Graphics Backend - + Custom frontend - + Real applet - + + + Disabled + + + + Never - + On Load - + Always - + CPU CPU - + GPU - + CPU Asynchronous - + Uncompressed (Best quality) - + BC1 (Low quality) BC1 (Lage Kwaliteit) - + BC3 (Medium quality) BC3 (Gemiddelde kwaliteit) - - + + Auto Auto - + 30 FPS - + 60 FPS - + 90 FPS - + 120 FPS - + Conservative - + Aggressive - + Vulkan Vulkan - + OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) - + Null Null - + Fast - - Balanced - - - - - + + + Accurate Accuraat - - + + + Default Standaard - + Unsafe (fast) - + Safe (stable) - + + Immediate + + + + + Balanced + + + + Unsafe Onveilig - + Paranoid (disables most optimizations) Paranoid (schakelt de meeste optimalisaties uit) - + Debugging - + Dynarmic - + NCE - + Borderless Windowed Randloos Venster - + Exclusive Fullscreen Exclusief Volledig Scherm - + No Video Output Geen Video-uitvoer - + CPU Video Decoding CPU Videodecodering - + GPU Video Decoding (Default) GPU Videodecodering (Standaard) - + 0.25X (180p/270p) [EXPERIMENTAL] - + 0.5X (360p/540p) [EXPERIMENTAL] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [EXPERIMENTEEL] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [EXPERIMENTEEL] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor Nearest Neighbor - + Bilinear Bilinear - + Bicubic Bicubic - + Gaussian Gaussian - + Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution - + Area - + MMPX - + Zero-Tangent - + B-Spline - + Mitchell - + Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None Geen - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Standaart (16:9) - + Force 4:3 Forceer 4:3 - + Force 21:9 Forceer 21:9 - + Force 16:10 Forceer 16:10 - + Stretch to Window Uitrekken naar Venster - + Automatic Automatisch - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - - - - - 64x - - - - + Japanese (日本語) Japans (日本語) - + American English Amerikaans-Engels - + French (français) Frans (Français) - + German (Deutsch) Duits (Deutsch) - + Italian (italiano) Italiaans (italiano) - + Spanish (español) Spaans (Español) - + Chinese Chinees - + Korean (한국어) Koreaans (한국어) - + Dutch (Nederlands) Nederlands (Nederlands) - + Portuguese (português) Portugees (português) - + Russian (Русский) Russisch (Русский) - + Taiwanese Taiwanese - + British English Brits-Engels - + Canadian French Canadees-Frans - + Latin American Spanish Latijns-Amerikaans Spaans - + Simplified Chinese Vereenvoudigd Chinees - + Traditional Chinese (正體中文) Traditioneel Chinees (正體中文) - + Brazilian Portuguese (português do Brasil) Braziliaans-Portugees (português do Brasil) - + Polish (polska) - + Thai (แบบไทย) - - + + Japan Japan - + USA USA - + Europe Europa - + Australia Australië - + China China - + Korea Korea - + Taiwan Taiwan - + Auto (%1) Auto select time zone Auto (%1) - + Default (%1) Default time zone Standaard (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Cuba - + EET EET - + Egypt Egypte - + Eire Eire - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Eire - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hongkong - + HST HST - + Iceland Ijsland - + Iran Iran - + Israel Israel - + Jamaica Jamaica - + Kwajalein Kwajalein - + Libya Libië - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navajo - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Polen - + Portugal Portugal - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Singapore - + Turkey Turkije - + UCT UCT - + Universal Universeel - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulu - + Mono Mono - + Stereo Stereo - + Surround Surround - + 4GB DRAM (Default) - + 6GB DRAM (Unsafe) - + 8GB DRAM - + 10GB DRAM (Unsafe) - + 12GB DRAM (Unsafe) - + Docked Docked - + Handheld Handheld - - - Off + + + Normal - - Boost (1700MHz) + + + Boost - - Fast (2000MHz) + + + Overclock - + Always ask (Default) - + Only if game specifies not to stop - + Never ask - - - Medium (256) - - - - - - High (512) - - - - + Very Small (16 MB) - + Small (32 MB) - + Normal (128 MB) - + Large (256 MB) - + Very Large (512 MB) - + Very Low (4 MB) - + Low (8 MB) - + Normal (16 MB) - + Medium (32 MB) - + High (64 MB) - + Very Low (32) - + Low (64) - + Normal (128) - - Disabled + + Medium (256) - + + High (512) + + + + ExtendedDynamicState 1 - + ExtendedDynamicState 2 - + ExtendedDynamicState 3 - + Tree View - + Grid View @@ -2559,252 +2448,297 @@ When a program attempts to open the controller applet, it is immediately closed. Globale Log Filter - - When checked, the max size of the log increases from 100 MB to 1 GB - Indien aangevinkt, neemt de maximale grootte van de log toe van 100 MB tot 1 GB + + GPU Logging/Level + - - Enable Extended Logging** - Schakel Uitgebreid Loggen** in + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console Toon Login-console - + + When checked, the max size of the log increases from 100 MB to 1 GB + Indien aangevinkt, neemt de maximale grootte van de log toe van 100 MB tot 1 GB + + + + Enable Extended Logging** + Schakel Uitgebreid Loggen** in + + + Open Log Location Open Loglocatie - + Homebrew Homebrew - + Arguments String Argumentenrij - + Graphics Graphics - + When checked, it executes shaders without loop logic changes Indien aangevinkt, voert het shaders uit zonder wijzigingen in de luslogica - + Disable Loop safety checks Schakel Lusveiligheidscontroles uit - + When checked, it will dump all the macro programs of the GPU Indien aangevinkt, worden alle macroprogramma's van de GPU gedumpt - + Dump Maxwell Macros Dump Maxwell-macro's - + When checked, it enables Nsight Aftermath crash dumps Indien aangevinkt schakelt het Nsight Aftermath crashdumps in - + Enable Nsight Aftermath Schakel Nsight Aftermath in - - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Indien aangevinkt, zal het alle originele assembler shaders van de disk shader cache of het gevonden spel dumpen - - - - Dump Game Shaders - Dump Spel-shaders - - - - Enable Renderdoc Hotkey - Schakel Renderdoc-sneltoets in - - - - When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower - Indien aangevinkt, wordt de macro Just In Time-compiler uitgeschakeld. Als je dit inschakelt, worden spellen langzamer - - - - Disable Macro JIT - Schakel Macro JIT uit - - - - When checked, it disables the macro HLE functions. Enabling this makes games run slower - Indien aangevinkt, schakelt het de macro HLE functies uit. Inschakelen maakt spellen langzamer - - - - Disable Macro HLE - Schakel Macro HLE uit - - - - When checked, the graphics API enters a slower debugging mode - Indien aangevinkt, gaat de grafische API naar een langzamere foutopsporingsmodus - - - - Enable Graphics Debugging - Schakel Graphics Foutopsporing in - - - - When checked, yuzu will log statistics about the compiled pipeline cache - Indien aangevinkt, zal yuzu statistieken registreren over de gecompileerde pijplijn-cache - - Enable Shader Feedback - Schakel Shader Feedback in + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + - - <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + Dump Guest (Maxwell) Shaders + Enable Renderdoc Hotkey + Schakel Renderdoc-sneltoets in + + + + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower + Indien aangevinkt, wordt de macro Just In Time-compiler uitgeschakeld. Als je dit inschakelt, worden spellen langzamer + + + + Disable Macro JIT + Schakel Macro JIT uit + + + + When checked, it disables the macro HLE functions. Enabling this makes games run slower + Indien aangevinkt, schakelt het de macro HLE functies uit. Inschakelen maakt spellen langzamer + + + + Disable Macro HLE + Schakel Macro HLE uit + + + + When checked, the graphics API enters a slower debugging mode + Indien aangevinkt, gaat de grafische API naar een langzamere foutopsporingsmodus + + + + Enable Graphics Debugging + Schakel Graphics Foutopsporing in + + + + When checked, yuzu will log statistics about the compiled pipeline cache + Indien aangevinkt, zal yuzu statistieken registreren over de gecompileerde pijplijn-cache + + + + Enable Shader Feedback + Schakel Shader Feedback in + + + + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + + + Disable Buffer Reorder - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced Geavanceerd - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Laat yuzu controleren op een werkende Vulkan-omgeving wanneer het programma opstart. Schakel dit uit als dit problemen veroorzaakt met externe programma's die yuzu zien. - + Perform Startup Vulkan Check Voer Vulkan-controle bij het opstarten uit - + Disable Web Applet Schakel Webapplet uit - + Enable All Controller Types Schakel Alle Controler-soorten in - + Enable Auto-Stub - + Kiosk (Quest) Mode Kiosk-modus (Quest) - + Use dev.keys - + Enable Debug Asserts Schakel Debug-asserts in - + Debugging Debugging - + Battery Serial: - + + Debug Knobs: + + + + Bitmask for quick development toggles - + Set debug knobs (bitmask) - + 16-bit debug knob set for quick development toggles - + (bitmask) - - Debug Knobs: - - - - + Unit Serial: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. Zet dit aan om de laatst gegenereerde audio commandolijst naar de console te sturen. Alleen van invloed op spellen die de audio renderer gebruiken. - + Dump Audio Commands To Console** Dump Audio-opdrachten naar Console** - + Flush log output on each line - + Enable FS Access Log Schakel FS-toegangslogboek in - + Enable Verbose Reporting Services** Schakel Verbose Reporting Services** in - + Censor username in logs - + **This will be reset automatically when Eden closes. @@ -3254,33 +3188,38 @@ Would you like to delete the old save data? Achtergrondkleur: - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off Uit - + VSync Off VSync Uit - + Recommended Aanbevolen - + On Aan - + VSync On VSync Aan @@ -3801,6 +3740,16 @@ Would you like to delete the old save data? Motion / Touch Beweging / Touch + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4310,58 +4259,58 @@ Om de assen om te keren, beweeg je de joystick eerst verticaal en vervolgens hor [aan het wachten] - + New Profile Nieuw Profiel - + Enter a profile name: Voer een profielnaam in: - - + + Create Input Profile Maak Invoerprofiel - + The given profile name is not valid! De ingevoerde profielnaam is niet geldig! - + Failed to create the input profile "%1" Kon invoerprofiel "%1" niet maken - + Delete Input Profile Verwijder Invoerprofiel - + Failed to delete the input profile "%1" Kon invoerprofiel "%1" niet verwijderen - + Load Input Profile Laad Invoerprofiel - + Failed to load the input profile "%1" Kon invoerprofiel "%1" niet laden - + Save Input Profile Sla Invoerprofiel op - + Failed to save the input profile "%1" Kon invoerprofiel "%1" niet opslaan @@ -4409,7 +4358,7 @@ Om de assen om te keren, beweeg je de joystick eerst verticaal en vervolgens hor - + Configure Configureer @@ -4440,7 +4389,7 @@ Om de assen om te keren, beweeg je de joystick eerst verticaal en vervolgens hor - + Test Test @@ -4455,77 +4404,77 @@ Om de assen om te keren, beweeg je de joystick eerst verticaal en vervolgens hor Verwijder Server - + %1:%2 %1:%2 - - - - - - + + + + + + Eden - + Port number has invalid characters Poortnummer bevat ongeldige tekens - + Port has to be in range 0 and 65353 Poort moet in bereik 0 en 65353 zijn - + IP address is not valid IP-adress is niet geldig - + This UDP server already exists Deze UDP-server bestaat al - + Unable to add more than 8 servers Kan niet meer dan 8 servers toevoegen - + Testing Testen - + Configuring Configureren - + Test Successful Test Succesvol - + Successfully received data from the server. De data van de server is succesvol ontvangen. - + Test Failed Test Gefaald - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Kan niet de juiste data van de server ontvangen.<br>Controleer of de server correct is ingesteld en of het adres en de poort correct zijn. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. UDP-test of kalibratieconfiguratie is bezig.<br>Wacht tot ze klaar zijn. @@ -4710,57 +4659,57 @@ De huidige waarden zijn %1% en %2%. Sommige instellingen zijn alleen beschikbaar als een spel niet actief is. - + Add-Ons Add-Ons - + System Systeem - + CPU CPU - + Graphics Graphics - + Adv. Graphics Adv. Graphics - + Ext. Graphics - + Audio Audio - + Input Profiles Invoerprofielen - + Network - + Applets - + Properties Eigenschappen @@ -4883,6 +4832,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5450,91 +5523,86 @@ Versleep punten om de positie te veranderen, of dubbelklik op tabelcellen om waa - Show Compatibility List - Toon Compatibiliteitslijst - - - Show Add-Ons Column Toon Kolom Add-Ons - + Show Size Column Toon Kolomgrootte - + Show File Types Column Toon Kolom Bestandstypen - + Show Play Time Column - + Folder Icon Size: Grootte Mapicoon: - + Row 1 Text: Rij 1 Tekst: - + Row 2 Text: Rij 2 Tekst: - + Screenshots Schermafbeelding - + Ask Where To Save Screenshots (Windows Only) Vraag waar schermafbeeldingen moeten worden opgeslagen (alleen Windows) - + Screenshots Path: Schermafbeeldingspad: - + ... ... - + TextLabel TextLabel - + Resolution: Resolutie: - + Select Screenshots Path... Selecteer Schermafbeeldingspad... - + <System> <System> - + English Engels - + Auto (%1 x %2, %3 x %4) Screenshot width value Auto (%1 x %2, %3 x %4) @@ -5771,7 +5839,7 @@ Versleep punten om de positie te veranderen, of dubbelklik op tabelcellen om waa - + Calculating... @@ -5973,50 +6041,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL niet beschikbaar! - + OpenGL shared contexts are not supported. OpenGL gedeelde contexten worden niet ondersteund. - + Eden has not been compiled with OpenGL support. - - - + + + Error while initializing OpenGL! Fout tijdens het initialiseren van OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Je GPU ondersteunt mogelijk geen OpenGL, of je hebt niet de laatste grafische stuurprogramma. - + Error while initializing OpenGL 4.6! Fout tijdens het initialiseren van OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Je GPU ondersteunt mogelijk OpenGL 4.6 niet, of je hebt niet het laatste grafische stuurprogramma.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Je GPU ondersteunt mogelijk een of meer vereiste OpenGL-extensies niet. Zorg ervoor dat je het laatste grafische stuurprogramma hebt.<br><br>GL Renderer:<br>%1<br><br>Ondersteunde extensies:<br>%2 - + This build doesn't have OpenGL support. @@ -6024,279 +6092,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory - + Favorite Favoriet - + Start Game Start Spel - + Start Game without Custom Configuration Start Spel zonder Aangepaste Configuratie - + Open Save Data Location Open Locatie van Save-data - + Open Mod Data Location Open Locatie van Mod-data - + Open Transferable Pipeline Cache Open Overdraagbare Pijplijn-cache - + Link to Ryujinx - + Remove Verwijder - + Remove Installed Update Verwijder Geïnstalleerde Update - + Remove All Installed DLC Verwijder Alle Geïnstalleerde DLC's - + Remove Custom Configuration Verwijder Aangepaste Configuraties - + Remove Cache Storage Verwijder Cache-opslag - + Remove OpenGL Pipeline Cache Verwijder OpenGL-pijplijn-cache - + Remove Vulkan Pipeline Cache Verwijder Vulkan-pijplijn-cache - + Remove All Pipeline Caches Verwijder Alle Pijplijn-caches - + Remove All Installed Contents Verwijder Alle Geïnstalleerde Inhoud - + Manage Play Time - + Edit Play Time Data - + Remove Play Time Data - - + + Dump RomFS Dump RomFS - + Dump RomFS to SDMC Dump RomFS naar SDMC - + Verify Integrity Verifieer Integriteit - + Copy Title ID to Clipboard Kopiëer Titel-ID naar Klembord - - Navigate to GameDB entry - Navigeer naar GameDB-invoer - - - + Create Shortcut Maak Snelkoppeling - + Add to Desktop Toevoegen aan Bureaublad - + Add to Applications Menu Toevoegen aan menu Toepassingen - + Configure Game - + Scan Subfolders Scan Submappen - + Remove Game Directory Verwijder Spelmap - + ▲ Move Up ▲ Omhoog - + ▼ Move Down ▼ Omlaag - + Open Directory Location Open Maplocatie - + Clear Verwijder - - - Name - Naam - - - - Compatibility - Compatibiliteit - - - - Add-ons - Add-ons - - - - File type - Bestandssoort - - - - Size - Grootte - - - - Play time - - GameListItemCompat - + Ingame In het spel - + Game starts, but crashes or major glitches prevent it from being completed. Het spel start, maar crashes of grote glitches voorkomen dat het wordt voltooid. - + Perfect Perfect - + Game can be played without issues. Het spel kan zonder problemen gespeeld worden. - + Playable Speelbaar - + Game functions with minor graphical or audio glitches and is playable from start to finish. Het spel werkt met kleine grafische of audiofouten en is speelbaar van begin tot eind. - + Intro/Menu Intro/Menu - + Game loads, but is unable to progress past the Start Screen. Het spel wordt geladen, maar komt niet verder dan het startscherm. - + Won't Boot Start niet op - + The game crashes when attempting to startup. Het spel loopt vast bij het opstarten. - + Not Tested Niet Getest - + The game has not yet been tested. Het spel is nog niet getest. + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list Dubbel-klik om een ​​nieuwe map toe te voegen aan de spellijst @@ -6304,17 +6365,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) %1 van %n resultaat(en)%1 van %n resultaat(en) - + Filter: Filter: - + Enter pattern to filter Voer patroon in om te filteren @@ -6809,1139 +6870,1112 @@ Debug Message: - + Game &Icon Size - + Reset Window Size to &720p Herstel Venstergrootte naar &720p - + Reset Window Size to 720p Herstel Venstergrootte naar 720p - + Reset Window Size to &900p Herstel Venstergrootte naar &900p - + Reset Window Size to 900p Herstel Venstergrootte naar 900p - + Reset Window Size to &1080p Herstel Venstergrootte naar &1080p - + Reset Window Size to 1080p Herstel Venstergrootte naar 1080p - + &Multiplayer &Multiplayer - + &Tools &Tools - + Am&iibo - + Launch &Applet - + &TAS &TAS - + &Create Home Menu Shortcut - + Install &Firmware - + &Help &Help - + &Install Files to NAND... &Installeer Bestanden naar NAND... - + L&oad File... L&aad Bestand... - + Load &Folder... Laad &Map... - + E&xit A&fsluiten - - + + &Pause &Onderbreken - + &Stop &Stop - + &Verify Installed Contents - + &About Eden - + Single &Window Mode Modus Enkel Venster - + Con&figure... Con&figureer... - + Ctrl+, - + Enable Overlay Display Applet - + Show &Filter Bar Toon &Filterbalk - + Show &Status Bar Toon &Statusbalk - + Show Status Bar Toon Statusbalk - + &Browse Public Game Lobby &Bladeren door Openbare Spellobby - + &Create Room &Maak Kamer - + &Leave Room &Verlaat Kamer - + &Direct Connect to Room &Directe Verbinding met Kamer - + &Show Current Room &Toon Huidige Kamer - + F&ullscreen Volledig Scherm - + &Restart &Herstart - + Load/Remove &Amiibo... Laad/Verwijder &Amiibo... - - &Report Compatibility - &Rapporteer Compatibiliteit - - - + Open &Mods Page Open &Mod-pagina - - Open &Quickstart Guide - Open &Snelstartgids + + &User Handbook + - - &FAQ - &FAQ - - - + &Capture Screenshot &Leg Schermafbeelding Vast - + &Album - + &Set Nickname and Owner - + &Delete Game Data - + &Restore Amiibo - + &Format Amiibo - + &Mii Editor - + &Configure TAS... &Configureer TAS... - + Configure C&urrent Game... Configureer Huidig Spel... - - + + &Start &Start - + &Reset &Herstel - - + + R&ecord Opnemen - + Open &Controller Menu - + Install Decryption &Keys - + &Home Menu - + &Desktop - + &Application Menu - + &Root Data Folder - + &NAND Folder - + &SDMC Folder - + &Mod Folder - + &Log Folder - + From Folder - + From ZIP - + &Eden Dependencies - + &Data Manager - + &Tree View - + &Grid View - + Game Icon Size - - + + None - + Show Game &Name - + Show &Performance Overlay - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) - + Standard (64x64) - + Large (128x128) - + Full Size (256x256) - + Broken Vulkan Installation Detected - + Vulkan initialization failed during boot. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping - + Loading Web Applet... - - + + Disable Web Applet - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + The amount of shaders currently being built - + The current selected resolution scaling multiplier. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Unmute - + Mute - + Reset Volume - + + Configure Effects... + + + + &Clear Recent Files - + &Continue - + Warning: Outdated Game Format - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - - + + Error while loading ROM! - + The ROM format is not supported. - + An error occurred initializing the video core. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + An unknown error occurred. Please see the log for more details. - + + (64-bit) - + + (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + Closing software... - + Save Data - + Mod Data - + Error Opening %1 Folder - - + + Folder does not exist! - + Remove Installed Game Contents? - + Remove Installed Game Update? - + Remove Installed Game DLC? - + Remove Entry - + Delete OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? - + Remove Custom Game Configuration? - + Remove Cache Storage? - + Remove File - + Remove Play Time Data - + Reset play time? - - + + RomFS Extraction Failed! - + There was an error copying the RomFS files or the user cancelled the operation. - + Full - + Skeleton - + Select RomFS Dump Mode - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Extracting RomFS... - - + + Cancel - + RomFS Extraction Succeeded! - + The operation completed successfully. - + Error Opening %1 - + Select Directory - + Properties - + The game properties could not be loaded. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Load File - + Open Extracted ROM Directory - + Invalid Directory Selected - + The directory you have selected does not contain a 'main' file. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files - + %n file(s) remaining - + Installing file "%1"... - - + + Install Results - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + %n file(s) were newly installed - + %n file(s) were overwritten - + %n file(s) failed to install - + System Application - + System Archive - + System Application Update - + Firmware Package (Type A) - + Firmware Package (Type B) - + Game - + Game Update - + Game DLC - + Delta Title - + Select NCA Install Type... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Failed to Install - + The title type you selected for the NCA is invalid. - + File not found - + File "%1" not found - + OK - + Function Disabled - + Compatibility list reporting is currently disabled. Check back later! - + Error opening URL - + Unable to open the URL "%1". - + TAS Recording - + Overwrite file of player 1? - + Invalid config detected - + Handheld controller can't be used on docked mode. Pro controller will be selected. - - + + Amiibo - - + + The current amiibo has been removed - + Error - - + + The current game is not looking for amiibos - + Amiibo File (%1);; All Files (*.*) - + Load Amiibo - + Error loading Amiibo data - + The selected file is not a valid amiibo - + The selected file is already on use - + An unknown error occurred - - - Keys not installed - - - - - - Install decryption keys and restart Eden before attempting to install firmware. - - - - - Select Dumped Firmware Source Location - - - - - Select Dumped Firmware ZIP - - - - - Zipped Archives (*.zip) - - - - - Firmware cleanup failed - - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available - + Firmware Corrupted - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot - + PNG Image (*.png) - + TAS state: Running %1/%2 - + TAS state: Recording %1 - + TAS state: Idle %1/%2 - + TAS State: Invalid - + &Stop Running - + Stop R&ecording - + Building: %n shader(s) - + Scale: %1x %1 is the resolution scaling factor - + Speed: %1% / %2% - + Speed: %1% - + Game: %1 FPS - + Frame: %1 ms - - - FSR - - - - + NO AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE - + VOLUME: %1% Volume percentage (e.g. 50%) - + Derivation Components Missing - + Decryption keys are missing. Install them now? - + Wayland Detected! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -7949,74 +7983,74 @@ Would you like to force it for future launches? - + Use X11 - + Continue with Wayland - + Don't show again - + Restart Required - + Restart Eden to apply the X11 backend. - + Slow - + Turbo - + Unlocked - + Select RomFS Dump Target - + Please select which RomFS you would like to dump. - + Are you sure you want to close Eden? - - - + + + Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8082,6 +8116,11 @@ Would you like to bypass this and exit anyway? ScaleForce + + + FSR + + Area @@ -8093,52 +8132,57 @@ Would you like to bypass this and exit anyway? - + + SGSR + + + + + SGSR EdgeDir + + + + Docked - + Handheld - + Fast - - Balanced - - - - + Accurate - + Vulkan - + OpenGL GLSL - + OpenGL SPIRV - + OpenGL GLASM - + Null @@ -8171,7 +8215,7 @@ If you wish to clean up the files which were left in the old data location, you - + Data was migrated successfully. @@ -8661,8 +8705,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8675,10 +8719,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8693,9 +8737,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8857,50 +8901,50 @@ p, li { white-space: pre-wrap; } [ongedefinieerd] - + %1%2 %1%2 - + [invalid] [ongeldig] - - + + %1%2Hat %3 %1%2Hat %3 - - - + + + %1%2Axis %3 %1%2As %3 - + %1%2Axis %3,%4,%5 %1%2As %3,%4,%5 - + %1%2Motion %3 %1%2Beweging %3 - - + + %1%2Button %3 %1%2Knop %3 - + [unused] [ongebruikt] @@ -9026,47 +9070,47 @@ p, li { white-space: pre-wrap; } %1 speelt %2 - + Play Time: %1 - + Never Played - + Version: %1 - + Version: 1.0.0 - + Installed SD Titles Geïnstalleerde SD-titels - + Installed NAND Titles Geïnstalleerde NAND-titels - + System Titles Systeemtitels - + Add New Game Directory Voeg Nieuwe Spelmap Toe - + Favorites Favorieten @@ -9187,13 +9231,13 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. @@ -9203,10 +9247,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9227,213 +9271,246 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9677,72 +9754,72 @@ Would you like to manually select a portable folder to use? - + Create Shortcut - + Do you want to launch the game in fullscreen? - + Shortcut Created - + Successfully created a shortcut to %1 - + Shortcut may be Volatile! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? - + Failed to Create Shortcut - + Failed to create a shortcut to %1 - + Create Icon - + Cannot create icon file. Path "%1" does not exist and cannot be created. - + No firmware available - + Please install firmware to use the home menu. - + Home Menu Applet - + Home Menu is not available. Please reinstall firmware. @@ -9750,55 +9827,55 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name - + What should this mod be called? - + RomFS - + ExeFS/Patch - + Cheat - + Mod Type - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty @@ -10492,65 +10569,65 @@ By selecting "From Eden", previous save data stored in Ryujinx will be - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/pl.ts b/dist/languages/pl.ts index ef2078c554..28a51ae4f8 100644 --- a/dist/languages/pl.ts +++ b/dist/languages/pl.ts @@ -218,155 +218,6 @@ To zbanuje jego/jej nick na forum, oraz jego/jej adres IP. %1 - %2 (%3/%4 członków) - połączono - - CompatDB - - - Report Compatibility - Zgłoś kompatybilność - - - - - - - - - - Report Game Compatibility - Zgłoś kompatybilność gry - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - <html><head/><body><p><span style=" font-size:10pt;">Jeśli zdecydujesz się przesłać przypadek testowy do </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">listy kompatybilności Edenu</span></a><span style=" font-size:10pt;">, następujące informacje zostaną zebrane i wyświetlone na stronie:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Informacje o sprzęcie (procesor / karta graficzna / system operacyjny)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Wersja Eden, z której korzystasz</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Połączone konto Eden</li></ul></body></html> - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>Czy gra się uruchamia?</p></body></html> - - - - Yes The game starts to output video or audio - Tak, gra zaczyna pokazywać obraz lub słychać dźwięk. - - - - No The game doesn't get past the "Launching..." screen - Nie, gra nie przechodzi przez ekran "Ładowania..." - - - - Yes The game gets past the intro/menu and into gameplay - Tak, gra przechodzi przez intro/ekran główny oraz do rozgrywki. - - - - No The game crashes or freezes while loading or using the menu - Nie, gra zawiesza się podczas ładowania lub poruszania się w menu. - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>Czy gra dociera do rozgrywki?</p></body></html> - - - - Yes The game works without crashes - Tak, gra działa bezawaryjnie. - - - - No The game crashes or freezes during gameplay - Nie, gra się zawiesza podczas rozgrywki. - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>Czy gra działa bezawaryjnie podczas rozgrywki?</p></body></html> - - - - Yes The game can be finished without any workarounds - Tak, gra może być ukończona bez żadnych obejść. - - - - No The game can't progress past a certain area - Nie, w grze nie można przejść do określonego obszaru. - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>Czy gra jest kompletnie grywalna od początku aż do jej końca?</p></body></html> - - - - Major The game has major graphical errors - Poważne, gra posiada poważne problemy graficzne. - - - - Minor The game has minor graphical errors - Drobne, gra zawiera drobne błędy graficzne. - - - - None Everything is rendered as it looks on the Nintendo Switch - Żadnych, Wszystko jest renderowane tak jak wygląda na Nintendo Switchu. - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>Czy gra posiada jakieś błędy graficzne?</p></body></html> - - - - Major The game has major audio errors - Poważne, gra posiada poważne problemy dźwiękowe. - - - - Minor The game has minor audio errors - Drobne, gra zawiera drobne błędy dźwiękowe. - - - - None Audio is played perfectly - Żadnych, Dźwięk jest odtwarzany perfekcyjnie. - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>Czy gra posiada jakiekolwiek błędy dźwiękowe/brakujące efekty?</p></body></html> - - - - Thank you for your submission! - Dziękujemy za Twoją opinię! - - - - Submitting - Wysyłanie - - - - Communication error - Błąd komunikacyjny - - - - An error occurred while sending the Testcase - Wystąpił błąd podczas wysyłania Testcase'u - - - - Next - Następny - - ConfigurationShared @@ -570,39 +421,38 @@ Może pomóc zredukować przycięcia przy niższej liczbie klatek. - + Backend: Backend: - CPU Overclock - Podkręcanie CPU + CPU Clocks + - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. - Podkręca emulowany procesor, aby usunąć niektóre ograniczenia liczby klatek na sekundę. Słabsze procesory mogą wykazywać obniżoną wydajność, a niektóre gry mogą działać nieprawidłowo. -Użyj opcji Boost (1700 MHz), aby uruchomić urządzenie z najwyższą natywną częstotliwością taktowania Switch, lub Szybki (2000 MHz), aby uruchomić urządzenie z dwukrotnie wyższą częstotliwością taktowania. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + - + Custom CPU Ticks Niestandardowe cykle CPU - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. Ustaw niestandardową wartość taktów CPU. Wyższe wartości mogą zwiększyć wydajność, ale mogą też powodować zawieszanie się gry. Zalecany zakres to 77–21000. - + Enable Host MMU Emulation (fastmem) Włącz emulację MMU hosta (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. @@ -611,100 +461,100 @@ Włączenie powoduje, że odczyty/zapisy pamięci gościa trafiają bezpośredni Wyłączenie wymusza użycie emulacji programowej MMU dla wszystkich dostępów do pamięci. - + Unfuse FMA (improve performance on CPUs without FMA) Unfuse FMA (zwiększ wydajność na procesorach bez FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. Ta opcja zwiększa szybkość kosztem dokładności instrukcji FMA (fused-multiply-add) na procesorach bez natywnego wsparcia FMA. - + Faster FRSQRTE and FRECPE Szybsze FRSQRTE i FRECPE - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. Ta opcja poprawia szybkość niektórych funkcji zmiennoprzecinkowych, używając mniej dokładnych natywnych aproksymacji. - + Faster ASIMD instructions (32 bits only) Szybsze instrukcje ASIMD (Tylko 32-bit) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. Ta opcja przyspiesza 32-bitowe funkcje zmiennoprzecinkowe ASIMD, uruchamiając je z nieprawidłowymi trybami zaokrąglania. - + Inaccurate NaN handling Niedokładna obsługa NaN - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. Ta opcja zwiększa szybkość poprzez wyłączenie sprawdzania NaN. Uwaga: obniża to dokładność niektórych instrukcji zmiennoprzecinkowych. - + Disable address space checks Wyłącz sprawdzanie przestrzeni adresów - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. Ta opcja poprawia szybkość poprzez usunięcie sprawdzania bezpieczeństwa przed każdą operacją pamięci. Wyłączenie może umożliwić wykonanie dowolnego kodu. - + Ignore global monitor Ignoruj ogólne monitorowanie - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. Ta opcja zwiększa szybkość, polegając wyłącznie na semantyce „cmpxchg” w celu zapewnienia bezpieczeństwa instrukcji z dostępem wyłącznym. Uwaga: może to prowadzić do zakleszczeń i innych warunków wyścigu. - + API: API: - + Changes the output graphics API. Vulkan is recommended. Zmienia wyjściowe API grafiki. Zalecany jest Vulkan. - + Device: Urządzenie: - + This setting selects the GPU to use (Vulkan only). To ustawienie wybiera używany GPU (tylko Vulkan). - + Resolution: Rozdzielczość: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. @@ -713,27 +563,27 @@ Wyższe rozdzielczości wymagają więcej VRAM i przepustowości. Opcje poniżej 1x mogą powodować artefakty. - + Window Adapting Filter: Filtr Adaptującego Okna: - + FSR Sharpness: Ostrość FSR: - - Determines how sharpened the image will look using FSR's dynamic contrast. - Określa, jak bardzo obraz będzie wyostrzony przy użyciu dynamicznego kontrastu FSR. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. + - + Anti-Aliasing Method: Metoda Anty-Aliasingu: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. @@ -741,13 +591,23 @@ FXAA can produce a more stable picture in lower resolutions. SMAA oferuje najlepszą jakość. FXAA może zapewnić stabilniejszy obraz w niższych rozdzielczościach. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Tryb Pełnoekranowy: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. @@ -756,12 +616,12 @@ Tryb bez ramek zapewnia najlepszą zgodność z klawiaturą ekranową wymaganą Pełny ekran wyłączny może oferować lepszą wydajność i lepsze wsparcie FreeSync/G-Sync. - + Aspect Ratio: Format obrazu: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. @@ -770,35 +630,35 @@ Większość gier obsługuje wyłącznie 16:9, więc inne proporcje wymagają mo Kontroluje także proporcje przechwytywanych zrzutów ekranu. - + Use persistent pipeline cache Używaj trwałej pamięci podręcznej (cache) potoków - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. Pozwala zapisywać shadery w pamięci masowej, aby przyspieszyć ładowanie przy kolejnych uruchomieniach gry. Wyłączanie tej opcji jest przeznaczone do debugowania. - + Use asynchronous GPU emulation - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + NVDEC emulation: Emulacja NVDEC: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. @@ -807,12 +667,12 @@ Dekodowanie może być wykonywane przez CPU albo GPU, albo może być całkowici W większości przypadków najlepszą wydajność zapewnia dekodowanie na GPU. - + ASTC Decoding Method: Metoda dekodowania ASTC: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -824,12 +684,12 @@ GPU: użyj shaderów obliczeniowych GPU do dekodowania tekstur ASTC (zalecane). CPU asynchronicznie: użyj CPU do dekodowania na żądanie. Eliminuje zacięcia podczas dekodowania ASTC, ale może powodować artefakty. - + ASTC Recompression Method: Metoda rekompresji ASTC: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. @@ -837,44 +697,64 @@ BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, BC1/BC3: format pośredni zostanie ponownie skompresowany do BC1 lub BC3 — oszczędza VRAM kosztem jakości obrazu. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: Tryb wykorzystania VRAM: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. Określa, czy emulator ma oszczędzać pamięć, czy maksymalnie wykorzystywać dostępną pamięć wideo dla wydajności. Tryb agresywny może pogorszyć działanie innych aplikacji, np. oprogramowania do nagrywania. - + Skip CPU Inner Invalidation Pomiń wewnętrzne unieważnienie procesora CPU - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. Pomija niektóre unieważnienia pamięci podręcznej po stronie CPU podczas aktualizacji pamięci, zmniejszając użycie CPU i poprawiając jego wydajność. Może powodować błędy lub awarie w niektórych grach. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: Tryb synchronizacji pionowej: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -885,12 +765,12 @@ Mailbox może mieć niższe opóźnienia niż FIFO i nie powoduje tearingu, ale Immediate (bez synchronizacji) wyświetla wszystko, co jest dostępne, i może powodować tearing. - + Sync Memory Operations Synchronizuj operacje pamięci - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. @@ -898,147 +778,159 @@ Unreal Engine 4 games often see the most significant changes thereof. Gry z Unreal Engine 4 mogą być najbardziej dotknięte. - + Enable asynchronous presentation (Vulkan only) Włącz asynchroniczną prezentację (tylko Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. Nieznacznie poprawia wydajność, przenosząc prezentację na oddzielny wątek CPU. - + Force maximum clocks (Vulkan only) Wymuś maksymalne zegary (Tylko Vulkan) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. Uruchamia pracę w tle podczas oczekiwania na komendy graficzne aby GPU nie obniżało taktowania. - + Anisotropic Filtering: Filtrowanie anizotropowe: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. Steruje jakością renderowania tekstur pod ostrymi kątami. Na większości GPU bezpieczną wartością jest 16x. - + GPU Mode: Tryb GPU: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - Steruje trybem emulacji GPU. -Większość gier renderuje się poprawnie w trybach Szybki lub Zrównoważony, ale dla niektórych nadal wymagany jest tryb Dokładny. -Efekty cząsteczkowe zwykle renderują się poprawnie tylko w trybie Dokładnym. - - - - DMA Accuracy: - Dokładność DMA: - - - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. - Steruje precyzją DMA. Tryb „bezpieczna precyzja” usuwa problemy w niektórych grach, ale może pogorszyć wydajność. - - - - Enable asynchronous shader compilation - Włącz asynchroniczną kompilację shaderów - - - - May reduce shader stutter. - Może zmniejszyć zacięcia spowodowane kompilacją shaderów. - - - - Fast GPU Time - Szybki czas GPU - - - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. - Podkręca emulowane GPU, aby zwiększyć dynamiczną rozdzielczość i zasięg renderowania. -Ustaw 256 dla maksymalnej wydajności, a 512 dla maksymalnej jakości grafiki. - - - - GPU Unswizzle + DMA Accuracy: + Dokładność DMA: + + + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. + + + + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + + Enable asynchronous shader compilation + Włącz asynchroniczną kompilację shaderów + + + + May reduce shader stutter. + Może zmniejszyć zacięcia spowodowane kompilacją shaderów. + + + + GPU Clocks + + + + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. + + + + + GPU Unswizzle + + + + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache Użyj pamięci podręcznej strumienia dla Vulkana - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. Włącza specyficzną dla producenta GPU pamięć podręczną potoków. Ta opcja może znacząco skrócić czas ładowania shaderów w przypadkach, gdy sterownik Vulkan nie przechowuje wewnętrznie plików pamięci podręcznej potoków. - + Enable Compute Pipelines (Intel Vulkan Only) Włącz potoki obliczeniowe (tylko Intel Vulkan) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. @@ -1047,182 +939,181 @@ To ustawienie istnieje wyłącznie dla sterowników Intela i może spowodować b Na wszystkich pozostałych sterownikach potoki obliczeniowe są zawsze włączone. - + Enable Reactive Flushing Włącz reaktywne opróżnianie buforów - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. Używa opróżniania reaktywnego zamiast predykcyjnego, co umożliwia dokładniejsze synchronizowanie pamięci. - + Sync to framerate of video playback Synchronizuj do liczby klatek odtwarzanego wideo - + Run the game at normal speed during video playback, even when the framerate is unlocked. Uruchamiaj grę z normalną prędkością podczas odtwarzania wideo, nawet przy odblokowanej liczbie klatek na sekundę. - + Barrier feedback loops Pętle sprzężenia zwrotnego barier - + Improves rendering of transparency effects in specific games. Poprawia renderowanie efektów przezroczystości w niektórych grach. - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State Rozszerzony stan dynamiczny - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. Kontroluje liczbę funkcji, które mogą być używane w Extended Dynamic State. Wyższe poziomy pozwalają na użycie większej liczby funkcji i mogą zwiększyć wydajność, ale mogą powodować dodatkowe problemy z grafiką. - + Vertex Input Dynamic State Dynamiczny stan wejścia wierzchołków - + Enables vertex input dynamic state feature for better quality and performance. Włącza funkcję dynamicznego stanu wejścia wierzchołków, poprawiając jakość i wydajność. - + Sample Shading Cieniowanie próbkowe - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. Pozwala wykonywać shader fragmentów dla każdej próbki we fragmencie wielokrotnie próbkowanym zamiast raz dla każdego fragmentu. Poprawia jakość grafiki kosztem wydajności. Wyższe wartości poprawiają jakość, ale obniżają wydajność. - + RNG Seed Ziarno RNG - + Controls the seed of the random number generator. Mainly used for speedrunning. Ustala ziarno generatora liczb losowych. Głównie używane do speedrunów. - + Device Name Nazwa urządzenia - + The name of the console. The name of the console. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: Własna data RTC - + This option allows to change the clock of the console. Can be used to manipulate time in games. Ta opcja pozwala zmienić zegar konsoli. Może służyć do manipulowania czasem w grach. - + The number of seconds from the current unix time Liczba sekund od bieżącego czasu Unix - + Language: Język: - + This option can be overridden when region setting is auto-select Ta opcja może zostać nadpisana, gdy ustawienie regionu to automatyczny wybór. - + Region: Region: - + The region of the console. Region konsoli. - + Time Zone: Strefa czasowa: - + The time zone of the console. Strefa czasowa konsoli. - + Sound Output Mode: Tryb wyjścia dźwięku: - + Console Mode: Tryb konsoli - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. @@ -1231,1031 +1122,1026 @@ W zależności od tego ustawienia gry zmienią swoją rozdzielczość, szczegó Ustawienie na Handheld może poprawić wydajność na słabszych komputerach. - + Prompt for user profile on boot Pytaj o profil użytkownika przy uruchomieniu. - + Useful if multiple people use the same PC. Przydatne, gdy z tego samego PC korzysta wiele osób. - + Pause when not in focus Wstrzymuj, gdy okno nie jest aktywne - + Pauses emulation when focusing on other windows. Wstrzymuje emulację po przełączeniu na inne okna. - + Confirm before stopping emulation Potwierdzaj przed zatrzymaniem emulacji - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. Nadpisuje monity z prośbą o potwierdzenie zatrzymania emulacji. Włączenie powoduje pominięcie takich monitów i bezpośrednie wyjście z emulacji. - + Hide mouse on inactivity Ukryj mysz przy braku aktywności - + Hides the mouse after 2.5s of inactivity. Ukrywa kursor po 2,5 s bezczynności. - - Disable controller applet - Wyłącz aplet kontrolera - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - Wymusza wyłączenie użycia apletu kontrolera w oprogramowaniu uruchamianym w emulacji. -Jeśli emulowane oprogramowanie próbuje otworzyć aplet kontrolera, jest on natychmiast zamykany. - - - + Check for updates Sprawdź aktualizacje - + Whether or not to check for updates upon startup. Czy sprawdzać aktualizacje przy uruchomieniu. - + Enable Gamemode Włącz Tryb gry - + Force X11 as Graphics Backend Wymuś X11 jako backend grafiki - + Custom frontend Niestandardowy frontend - + Real applet Prawdziwy aplet - + + + Disabled + Wyłączone + + + Never Nigdy - + On Load Przy wczytywaniu - + Always Zawsze - + CPU CPU - + GPU GPU - + CPU Asynchronous Asynchroniczne CPU - + Uncompressed (Best quality) Brak (najlepsza jakość) - + BC1 (Low quality) BC1 (niska jakość) - + BC3 (Medium quality) BC3 (średnia jakość) - - + + Auto Automatyczny - + 30 FPS - + 60 FPS - + 90 FPS - + 120 FPS - + Conservative Konserwatywny - + Aggressive Agresywny - + Vulkan Vulkan - + OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) - + Null Null - + Fast Szybkie - - Balanced - Zrównoważony - - - - + + + Accurate Dokładny - - + + + Default Domyślny - + Unsafe (fast) Niezalecane (szybkie) - + Safe (stable) Bezpieczne (stabilne) - + + Immediate + + + + + Balanced + Zrównoważony + + + Unsafe Niebezpieczny - + Paranoid (disables most optimizations) Paranoiczne (wyłącza większość optymalizacji) - + Debugging Debugowanie - + Dynarmic Dynamiczny - + NCE NCE - + Borderless Windowed W oknie (Bezramkowy) - + Exclusive Fullscreen Exclusive Fullscreen - + No Video Output Brak wyjścia wideo - + CPU Video Decoding Dekodowanie Wideo przez CPU - + GPU Video Decoding (Default) Dekodowanie Wideo przez GPU (Domyślne) - + 0.25X (180p/270p) [EXPERIMENTAL] 0,25x (180p/270p) [EKSPERYMENTALNE] - + 0.5X (360p/540p) [EXPERIMENTAL] 0,5x (360p/540p) [EKSPERYMENTALNE] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [EKSPERYMENTALNE] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] 1.25X (900p/1350p) [EKSPERYMENTALNE] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [Ekperymentalnie] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor Najbliższy sąsiadujący - + Bilinear Bilinearny - + Bicubic Bikubiczny - + Gaussian Kulisty - + Lanczos Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution AMD FidelityFX Super Resolution - + Area Obszar - + MMPX MMPX - + Zero-Tangent Zero-Tangent - + B-Spline B-Spline - + Mitchell Mitchell - + Spline-1 Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None Żadna (wyłączony) - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Domyślne (16:9) - + Force 4:3 Wymuś 4:3 - + Force 21:9 Wymuś 21:9 - + Force 16:10 Wymuś 16:10 - + Stretch to Window Rozciągnij do Okna - + Automatic Automatyczne - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - 32x - - - - 64x - 64x - - - + Japanese (日本語) Japoński (日本語) - + American English Angielski Amerykański - + French (français) Francuski (français) - + German (Deutsch) Niemiecki (Niemcy) - + Italian (italiano) Włoski (italiano) - + Spanish (español) Hiszpański (español) - + Chinese Chiński - + Korean (한국어) Koreański (한국어) - + Dutch (Nederlands) Duński (Holandia) - + Portuguese (português) Portugalski (português) - + Russian (Русский) Rosyjski (Русский) - + Taiwanese Tajwański - + British English Angielski Brytyjski - + Canadian French Fancuski (Kanada) - + Latin American Spanish Hiszpański (Latin American) - + Simplified Chinese Chiński (Uproszczony) - + Traditional Chinese (正體中文) Chiński tradycyjny (正體中文) - + Brazilian Portuguese (português do Brasil) Portugalski (português do Brasil) - + Polish (polska) - + Thai (แบบไทย) - - + + Japan Japonia - + USA USA - + Europe Europa - + Australia Australia - + China Chiny - + Korea Korea - + Taiwan Tajwan - + Auto (%1) Auto select time zone Auto (%1) - + Default (%1) Default time zone Domyślne (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Cuba - + EET EET - + Egypt Egipt - + Eire Irlandia - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Eire - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hongkong - + HST HST - + Iceland Islandia - + Iran Iran - + Israel Izrael - + Jamaica Jamajka - + Kwajalein Kwajalein - + Libya Libia - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navajo - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Polska - + Portugal Portugalia - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Singapur - + Turkey Turcja - + UCT UCT - + Universal Uniwersalny - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulu - + Mono Mono - + Stereo Stereo - + Surround Surround - + 4GB DRAM (Default) 4GB DRAM (Domyślne) - + 6GB DRAM (Unsafe) 6GB DRAM (NIebezpieczne) - + 8GB DRAM 8GB DRAM - + 10GB DRAM (Unsafe) 10GB DRAM (NIebezpieczne) - + 12GB DRAM (Unsafe) 12GB DRAM (NIebezpieczne) - + Docked Zadokowany - + Handheld Przenośnie - - - Off - Wyłączone + + + Normal + - - Boost (1700MHz) - Boost (1700MHz) + + + Boost + - - Fast (2000MHz) - Szybki (2000MHz) + + + Overclock + - + Always ask (Default) Zawsze pytaj (Domyślne) - + Only if game specifies not to stop Tylko jeśli gra określa, aby nie zatrzymywać - + Never ask Nie pytaj więcej - - - Medium (256) - Średnie (256) - - - - - High (512) - Wysokie (512) - - - + Very Small (16 MB) - + Small (32 MB) - + Normal (128 MB) - + Large (256 MB) - + Very Large (512 MB) - + Very Low (4 MB) - + Low (8 MB) - + Normal (16 MB) - + Medium (32 MB) - + High (64 MB) - + Very Low (32) - + Low (64) - + Normal (128) - - Disabled - Wyłączone + + Medium (256) + Średnie (256) - + + High (512) + Wysokie (512) + + + ExtendedDynamicState 1 ExtendedDynamicState 1 - + ExtendedDynamicState 2 ExtendedDynamicState 2 - + ExtendedDynamicState 3 ExtendedDynamicState 3 - + Tree View - + Grid View @@ -2613,252 +2499,297 @@ Gdy ta opcja jest włączona, niedopasowanie jest uruchamiane tylko wtedy, gdy d Globalny filtr rejestrów - - When checked, the max size of the log increases from 100 MB to 1 GB - Kiedy zaznaczony, maksymalny rozmiar logu wzrasta ze 100 MB do 1 GB + + GPU Logging/Level + - - Enable Extended Logging** - Włącz Przedłużony Logging** + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console Pokaż Log w konsoli - + + When checked, the max size of the log increases from 100 MB to 1 GB + Kiedy zaznaczony, maksymalny rozmiar logu wzrasta ze 100 MB do 1 GB + + + + Enable Extended Logging** + Włącz Przedłużony Logging** + + + Open Log Location Otwórz miejsce rejestrów - + Homebrew Homebrew - + Arguments String Linijka argumentu - + Graphics Grafika - + When checked, it executes shaders without loop logic changes Gdy zaznaczone, używa shaderów bez zmian logicznych pętli - + Disable Loop safety checks Wyłącz Zapętlanie sprawdzania bezpieczeństwa - + When checked, it will dump all the macro programs of the GPU Kiedy jest zaznaczone, będą zrzucane wszystkie makro programy GPU - + Dump Maxwell Macros Zrzuć Makra Maxwell - + When checked, it enables Nsight Aftermath crash dumps Gdy zaznaczone, włącza zrzucanie awarii Nsight Aftermath - + Enable Nsight Aftermath Włącz Nsight Aftermath - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Po zaznaczeniu, zrzuci wszystkie oryginalne shadery asemblera z pamięci podręcznej dysku shaderów albo gry, jeśli zostaną znalezione + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + - - Dump Game Shaders - Zrzuć Shadery Gry + + Dump Guest (Maxwell) Shaders + - + Enable Renderdoc Hotkey Włącz skrót klawiszowy RenderDoc - + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower Gdy zaznaczone, wyłącza kompilator makr Just In Time. Włączenie tej opcji spowalnia działanie gier - + Disable Macro JIT Wyłącz Makro JIT - + When checked, it disables the macro HLE functions. Enabling this makes games run slower Kiedy jest zaznaczone, wyłączane są funkcje makra HLE. Włączenie tego powoduje spadek wydajności w grach. - + Disable Macro HLE Wyłącz makra HLE - + When checked, the graphics API enters a slower debugging mode Gdy zaznaczone, API grafiki przechodzi w wolniejszy tryb debugowania - + Enable Graphics Debugging Włącz debugowanie grafiki - + When checked, yuzu will log statistics about the compiled pipeline cache Po zaznaczeniu, yuzu będzie rejestrować statystyki dotyczące skompilowanej pamięci podręcznej. - + Enable Shader Feedback Włącz funkcję Feedbacku Shaderów - + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> <html><head/><body><p>Po zaznaczeniu wyłącza zmianę kolejności operacji przesyłania danych ze zmapowanej pamięci, co pozwala powiązać te przesyłania z konkretnymi wywołaniami rysowania. W niektórych przypadkach może obniżyć wydajność.</p></body></html> - + Disable Buffer Reorder Wyłącz zmianę kolejności buforów - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced Zaawansowane - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Umożliwia yuzu sprawdzanie działającego środowiska Vulkan podczas uruchamiania programu. Wyłącz to, jeśli powoduje to problemy z zewnętrznymi programami widzącymi yuzu. - + Perform Startup Vulkan Check Przeprowadź sprawdzanie uruchamiania Vulkana - + Disable Web Applet Wyłącz Aplet internetowy - + Enable All Controller Types Włącz wszystkie Typy Kontrolerów - + Enable Auto-Stub Włącz Auto-Stub - + Kiosk (Quest) Mode Tryb Kiosk (Quest) - + Use dev.keys - + Enable Debug Asserts Włącz potwierdzenia debugowania - + Debugging Debugowanie - + Battery Serial: Numer seryjny baterii: - + + Debug Knobs: + + + + Bitmask for quick development toggles Maska bitowa szybkich przełączników deweloperskich - + Set debug knobs (bitmask) Ustaw przełączniki debugowania (maska bitowa) - + 16-bit debug knob set for quick development toggles 16-bitowy zestaw przełączników debugowania do szybkiej zmiany ustawień deweloperskich - + (bitmask) (maska bitowa) - - Debug Knobs: - Przełączniki debugowania: - - - + Unit Serial: Przełączniki debugowania: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. Włącz tę opcję, aby wyświetlić ostatnio wygenerowaną listę poleceń dźwiękowych na konsoli. Wpływa tylko na gry korzystające z renderera dźwięku. - + Dump Audio Commands To Console** Zrzuć polecenia audio do konsoli** - + Flush log output on each line Wypisuj logi z opróżnieniem bufora po każdej linii - + Enable FS Access Log Włącz dziennik Dostępu FS - + Enable Verbose Reporting Services** Włącz Pełne Usługi Raportowania** - + Censor username in logs Ukrywaj nazwę użytkownika w logach - + **This will be reset automatically when Eden closes. **To zostanie automatycznie zresetowane po zamknięciu Edena. @@ -3320,33 +3251,38 @@ Czy chcesz usunąć stare dane zapisu? Kolor tła - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off Wyłączone - + VSync Off VSync wyłączony - + Recommended Zalecane - + On Włączone - + VSync On VSync aktywny @@ -3867,6 +3803,16 @@ Czy chcesz usunąć stare dane zapisu? Motion / Touch Ruch / Dotyk + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4376,58 +4322,58 @@ Aby odwrócić osie, najpierw przesuń joystick pionowo, a następnie poziomo.[oczekiwanie] - + New Profile Nowy profil - + Enter a profile name: Wpisz nazwę profilu: - - + + Create Input Profile Utwórz profil wejściowy - + The given profile name is not valid! Podana nazwa profilu jest nieprawidłowa! - + Failed to create the input profile "%1" Nie udało się utworzyć profilu wejściowego "%1" - + Delete Input Profile Usuń profil wejściowy - + Failed to delete the input profile "%1" Nie udało się usunąć profilu wejściowego "%1" - + Load Input Profile Załaduj profil wejściowy - + Failed to load the input profile "%1" Nie udało się wczytać profilu wejściowego "%1" - + Save Input Profile Zapisz profil wejściowy - + Failed to save the input profile "%1" Nie udało się zapisać profilu wejściowego "%1" @@ -4475,7 +4421,7 @@ Aby odwrócić osie, najpierw przesuń joystick pionowo, a następnie poziomo. - + Configure Konfiguruj @@ -4506,7 +4452,7 @@ Aby odwrócić osie, najpierw przesuń joystick pionowo, a następnie poziomo. - + Test Test @@ -4521,77 +4467,77 @@ Aby odwrócić osie, najpierw przesuń joystick pionowo, a następnie poziomo.Usuń serwer - + %1:%2 %1:%2 - - - - - - + + + + + + Eden Eden - + Port number has invalid characters Port zawiera nieprawidłowe znaki - + Port has to be in range 0 and 65353 Port musi być w zakresie 0-65353 - + IP address is not valid Adres IP nie jest prawidłowy - + This UDP server already exists Ten serwer UDP już istnieje - + Unable to add more than 8 servers Nie można dodać więcej niż 8 serwerów - + Testing Testowanie - + Configuring Konfigurowanie - + Test Successful Test Udany - + Successfully received data from the server. Pomyślnie odebrano dane z serwera. - + Test Failed Test nieudany - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Nie można odebrać poprawnych danych z serwera.<br>Sprawdź, czy serwer jest poprawnie skonfigurowany, a adres i port są prawidłowe. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. Trwa konfiguracja testu UDP lub kalibracji.<br>Poczekaj na zakończenie. @@ -4776,57 +4722,57 @@ Obecne wartości to odpowiednio %1% i %2%. Niektóre ustawienia są dostępne tylko, gdy gra nie jest uruchomiona. - + Add-Ons Dodatki - + System System - + CPU CPU - + Graphics Grafika - + Adv. Graphics Zaaw. Grafika - + Ext. Graphics Dodatkowa grafika - + Audio Dźwięk - + Input Profiles Profil wejściowy - + Network Sieć - + Applets - + Properties Właściwości @@ -4949,6 +4895,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5516,91 +5586,86 @@ Przeciągnij punkty, aby zmienić pozycję, lub kliknij dwukrotnie komórki tabe - Show Compatibility List - Pokaż listę kompatybilności - - - Show Add-Ons Column Pokaż kolumnę dodatków - + Show Size Column Pokaż kolumnę rozmiarów - + Show File Types Column Pokaż kolumnę typów plików - + Show Play Time Column Pokaż kolumnę z czasem gry - + Folder Icon Size: Rozmiar Ikony Folderu - + Row 1 Text: Tekst 1. linii - + Row 2 Text: Tekst 2. linii - + Screenshots Zrzuty ekranu - + Ask Where To Save Screenshots (Windows Only) Pytaj gdzie zapisać zrzuty ekranu (Tylko dla Windows) - + Screenshots Path: Ścieżka zrzutów ekranu: - + ... ... - + TextLabel TextLabel - + Resolution: Rozdzielczość: - + Select Screenshots Path... Wybierz ścieżkę zrzutów ekranu... - + <System> <System> - + English Angielski - + Auto (%1 x %2, %3 x %4) Screenshot width value Automatycznie (%1 x %2, %3 x %4) @@ -5837,7 +5902,7 @@ Przeciągnij punkty, aby zmienić pozycję, lub kliknij dwukrotnie komórki tabe Zaimportuj dane dla tego katalogu. To może chwilę potrwać i USUNIE WSZYSTKIE ISTNIEJĄCE DANE! - + Calculating... Obliczanie... @@ -6041,50 +6106,50 @@ Przejdź do sekcji Konfiguracja -> System -> Sieć i dokonaj wyboru. GRenderWindow - - + + OpenGL not available! OpenGL niedostępny! - + OpenGL shared contexts are not supported. Współdzielone konteksty OpenGL nie są obsługiwane. - + Eden has not been compiled with OpenGL support. Eden nie został skompilowany z obsługą OpenGL. - - - + + + Error while initializing OpenGL! Błąd podczas inicjowania OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Twoja karta graficzna może nie obsługiwać OpenGL lub nie masz najnowszych sterowników karty graficznej. - + Error while initializing OpenGL 4.6! Błąd podczas inicjowania OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Twoja karta graficzna może nie obsługiwać OpenGL 4.6 lub nie masz najnowszych sterowników karty graficznej.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Twoja karta graficzna może nie obsługiwać co najmniej jednego wymaganego rozszerzenia OpenGL. Upewnij się, że masz najnowsze sterowniki karty graficznej<br><br>GL Renderer:<br>%1<br><br>Nieobsługiwane rozszerzenia:<br>%2 - + This build doesn't have OpenGL support. @@ -6092,279 +6157,272 @@ Przejdź do sekcji Konfiguracja -> System -> Sieć i dokonaj wyboru. GameList - + &Add New Game Directory - + Favorite Ulubione - + Start Game Uruchom grę - + Start Game without Custom Configuration Uruchom grę bez niestandardowej konfiguracji - + Open Save Data Location Otwórz lokalizację zapisów - + Open Mod Data Location Otwórz lokalizację modyfikacji - + Open Transferable Pipeline Cache Otwórz Transferowalną Pamięć Podręczną Pipeline - + Link to Ryujinx Połącz z Ryujinx - + Remove Usuń - + Remove Installed Update Usuń zainstalowaną łatkę - + Remove All Installed DLC Usuń wszystkie zainstalowane DLC - + Remove Custom Configuration Usuń niestandardową konfigurację - + Remove Cache Storage Usuń pamięć podręczną - + Remove OpenGL Pipeline Cache Usuń Pamięć Podręczną Pipeline OpenGL - + Remove Vulkan Pipeline Cache Usuń Pamięć Podręczną Pipeline Vulkan - + Remove All Pipeline Caches Usuń całą pamięć podręczną Pipeline - + Remove All Installed Contents Usuń całą zainstalowaną zawartość - + Manage Play Time Zarządzaj czasem gry - + Edit Play Time Data Edytuj dane czasu gry - + Remove Play Time Data Usuń dane dotyczące czasu gry - - + + Dump RomFS Zrzuć RomFS - + Dump RomFS to SDMC Zrzuć RomFS do SDMC - + Verify Integrity Sprawdź integralność - + Copy Title ID to Clipboard Kopiuj identyfikator gry do schowka - - Navigate to GameDB entry - Nawiguj do wpisu kompatybilności gry - - - + Create Shortcut Utwórz skrót - + Add to Desktop Dodaj do pulpitu - + Add to Applications Menu Dodaj do menu aplikacji - + Configure Game Skonfiguruj grę - + Scan Subfolders Skanuj podfoldery - + Remove Game Directory Usuń katalog gier - + ▲ Move Up ▲ Przenieś w górę - + ▼ Move Down ▼ Przenieś w dół - + Open Directory Location Otwórz lokalizacje katalogu - + Clear Wyczyść - - - Name - Nazwa gry - - - - Compatibility - Kompatybilność - - - - Add-ons - Dodatki - - - - File type - Typ pliku - - - - Size - Rozmiar - - - - Play time - Czas gry - GameListItemCompat - + Ingame W grze - + Game starts, but crashes or major glitches prevent it from being completed. Gra uruchamia się, ale awarie lub poważne błędy uniemożliwiają jej ukończenie. - + Perfect Perfekcyjnie - + Game can be played without issues. Można grać bez problemów. - + Playable Grywalna - + Game functions with minor graphical or audio glitches and is playable from start to finish. Gra działa z drobnymi błędami graficznymi lub dźwiękowymi oraz jest grywalna od początku aż do końca. - + Intro/Menu Intro/Menu - + Game loads, but is unable to progress past the Start Screen. Gra się ładuje, ale nie może przejść przez ekran początkowy. - + Won't Boot Nie uruchamia się - + The game crashes when attempting to startup. Ta gra się zawiesza przy próbie startu. - + Not Tested Nie testowane - + The game has not yet been tested. Ta gra nie została jeszcze przetestowana. + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list Kliknij podwójnie aby dodać folder do listy gier @@ -6372,17 +6430,17 @@ Przejdź do sekcji Konfiguracja -> System -> Sieć i dokonaj wyboru. GameListSearchField - + %1 of %n result(s) %1 z %n wyniku%1 z %n wyników%1 z %n wyników%1 z %n wyników - + Filter: Filter: - + Enter pattern to filter Wpisz typ do filtra @@ -6878,773 +6936,781 @@ Komunikat debugowania: - + Game &Icon Size - + Reset Window Size to &720p Zresetuj rozmiar okna do &720p - + Reset Window Size to 720p Zresetuj rozmiar okna do 720p - + Reset Window Size to &900p Zresetuj Rozmiar okna do &900p - + Reset Window Size to 900p Zresetuj Rozmiar okna do 900p - + Reset Window Size to &1080p Zresetuj rozmiar okna do &1080p - + Reset Window Size to 1080p Zresetuj rozmiar okna do 1080p - + &Multiplayer &Multiplayer - + &Tools &Narzędzia - + Am&iibo Am&iibo - + Launch &Applet - + &TAS &TAS - + &Create Home Menu Shortcut &Utwórz skrót w Menu głównym - + Install &Firmware Zainstaluj &Firmware - + &Help &Pomoc - + &Install Files to NAND... &Zainstaluj pliki na NAND... - + L&oad File... &Załaduj Plik... - + Load &Folder... Załaduj &Folder... - + E&xit &Wyjście - - + + &Pause &Pauza - + &Stop &Stop - + &Verify Installed Contents &Zweryfikuj zainstalowane zasoby - + &About Eden &O Eden - + Single &Window Mode Tryb &Pojedyńczego Okna - + Con&figure... &Konfiguruj... - + Ctrl+, Ctrl+, - + Enable Overlay Display Applet Włącz aplet wyświetlania nakładki - + Show &Filter Bar Pokaż Pasek &Filtrów - + Show &Status Bar Pokaż Pasek &Statusu - + Show Status Bar Pokaż pasek statusu - + &Browse Public Game Lobby &Przeglądaj publiczne lobby gier - + &Create Room &Utwórz Pokój - + &Leave Room &Wyjdź z Pokoju - + &Direct Connect to Room &Bezpośrednie połączenie z pokojem - + &Show Current Room &Pokaż bieżący pokój - + F&ullscreen &Pełny ekran - + &Restart &Restart - + Load/Remove &Amiibo... Załaduj/Usuń &Amiibo... - - &Report Compatibility - &Zaraportuj kompatybilność - - - + Open &Mods Page Otwórz stronę z &Modami - - Open &Quickstart Guide - Otwórz &Poradnik szybkiego startu + + &User Handbook + - - &FAQ - &FAQ - - - + &Capture Screenshot &Zrób zdjęcie - + &Album - + &Set Nickname and Owner &Ustaw nick oraz właściciela - + &Delete Game Data &Usuń dane gry - + &Restore Amiibo &Przywróć Amiibo - + &Format Amiibo &Sformatuj Amiibo - + &Mii Editor - + &Configure TAS... &Skonfiguruj TAS - + Configure C&urrent Game... Skonfiguruj &obecną grę... - - + + &Start &Start - + &Reset &Zresetuj - - + + R&ecord &Nagraj - + Open &Controller Menu Otwórz Menu &kontrolera - + Install Decryption &Keys Zainstaluj &klucze deszyfrujące - + &Home Menu - + &Desktop &Pulpit - + &Application Menu Menu &Aplikacji - + &Root Data Folder Folder &Root (danych głównych) - + &NAND Folder Folder &NAND - + &SDMC Folder Folder &SDMC - + &Mod Folder Folder &modów - + &Log Folder Folder &logów - + From Folder Z Folderu - + From ZIP Z pliku ZIP - + &Eden Dependencies Zależności &Eden - + &Data Manager Manager &Danych - + &Tree View - + &Grid View - + Game Icon Size - - + + None Żadna (wyłączony) - + Show Game &Name - + Show &Performance Overlay - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) - + Standard (64x64) - + Large (128x128) - + Full Size (256x256) - + Broken Vulkan Installation Detected Wykryto uszkodzoną instalację Vulkan - + Vulkan initialization failed during boot. Inicjalizacja Vulkana nie powiodła się podczas uruchamiania. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping Gra uruchomiona - + Loading Web Applet... Ładowanie apletu internetowego... - - + + Disable Web Applet Wyłącz Aplet sieciowy - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) Wyłączenie apletu sieciowego może prowadzić do nieprzewidywalnego działania i powinno być używane wyłącznie z Super Mario 3D All-Stars. Na pewno chcesz wyłączyć aplet sieciowy? (Tę opcję można ponownie włączyć w ustawieniach debugowania). - + The amount of shaders currently being built Liczba shaderów aktualnie budowanych - + The current selected resolution scaling multiplier. Aktualnie wybrany mnożnik skalowania rozdzielczości. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. Bieżąca prędkość emulacji. Wartości wyższe lub niższe niż 100% oznaczają, że emulacja działa odpowiednio szybciej lub wolniej niż na Switchu. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. Liczba klatek na sekundę aktualnie wyświetlanych przez grę. Wartość ta zależy od gry oraz od konkretnej sceny. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. Czas potrzebny na emulację jednej klatki Switcha, bez uwzględniania ogranicznika klatek i synchronizacji pionowej. Dla emulacji z pełną prędkością powinno to być maksymalnie 16,67 ms. - + Unmute Wyłącz wyciszenie - + Mute Wycisz - + Reset Volume Zresetuj głośność - + + Configure Effects... + + + + &Clear Recent Files &Usuń Ostatnie pliki - + &Continue &Kontynuuj - + Warning: Outdated Game Format Ostrzeżenie: Nieaktualny format gry - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. Używasz dla tej gry formatu katalogu zdekonstruowanego ROM-u, który jest przestarzały i został zastąpiony innymi formatami, takimi jak NCA, NAX, XCI czy NSP. Katalogi takich ROM-ów nie zawierają ikon, metadanych ani nie obsługują aktualizacji.<br>Aby uzyskać wyjaśnienie dotyczące różnych formatów Switcha obsługiwanych przez Eden, zajrzyj do naszego podręcznika użytkownika. Ten komunikat nie zostanie wyświetlony ponownie. - - + + Error while loading ROM! Błąd podczas wczytywania ROMu! - + The ROM format is not supported. Ten format ROMu nie jest wspierany. - + An error occurred initializing the video core. Wystąpił błąd podczas inicjowania rdzenia wideo. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. - Podczas uruchamiania rdzenia wideo wystąpił błąd. Zazwyczaj jest to spowodowane nieaktualnymi sterownikami GPU, w tym sterownikami zintegrowanymi. Więcej szczegółów można znaleźć w logu. Więcej informacji na temat dostępu do logu można znaleźć na następującej stronie: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>Jak przesłać Log</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. + - + Error while loading ROM! %1 %1 signifies a numeric error code. Błąd podczas wczytywania ROMu! %1 - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. %1<br>Proszę ponownie zrzucić pliki lub poprosić o pomoc na Discordzie/Stoat. - + An unknown error occurred. Please see the log for more details. Wystąpił nieznany błąd. Więcej informacji można znaleźć w pliku log. - + + (64-bit) (64-bit) - + + (32-bit) (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit %1 %2 - + Closing software... Zamykanie aplikacji... - + Save Data Zapis danych - + Mod Data Dane modów - + Error Opening %1 Folder Błąd podczas otwierania folderu %1 - - + + Folder does not exist! Folder nie istnieje! - + Remove Installed Game Contents? Usunąć zainstalowaną zawartość gry? - + Remove Installed Game Update? Usunąć zainstalowaną aktualizację gry? - + Remove Installed Game DLC? Usunąć zainstalowane DLC gry? - + Remove Entry Usuń wpis - + Delete OpenGL Transferable Shader Cache? Usunąć przenośną pamięć podręczną shaderów OpenGL? - + Delete Vulkan Transferable Shader Cache? Usunąć przenośną pamięć podręczną shaderów Vulkan? - + Delete All Transferable Shader Caches? Usunąć wszystkie przenośne pamięci podręczne shaderów? - + Remove Custom Game Configuration? Usunąć niestandardową konfigurację gry? - + Remove Cache Storage? Usunąć pamięć podręczną? - + Remove File Usuń plik - + Remove Play Time Data Usuń dane czasu gry - + Reset play time? Zresetować czas gry? - - + + RomFS Extraction Failed! Wyodrębnianie RomFS nie powiodło się! - + There was an error copying the RomFS files or the user cancelled the operation. Wystąpił błąd podczas kopiowania plików RomFS lub użytkownik przerwał operację. - + Full Pełny - + Skeleton Szkielet - + Select RomFS Dump Mode Wybierz tryb zrzutu RomFS - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. Wybierz sposób wykonania zrzutu RomFS. <br>Tryb „Pełny” skopiuje wszystkie pliki do nowego katalogu, <br>natomiast „Szkielet” utworzy jedynie strukturę katalogów. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root Nie ma wystarczająco miejsca w %1 aby wyodrębnić RomFS. Zwolnij trochę miejsca, albo zmień ścieżkę zrzutu RomFs w Emulacja > Konfiguruj > System > System Plików > Źródło Zrzutu - + Extracting RomFS... Wypakowywanie RomFS... - - + + Cancel Anuluj - + RomFS Extraction Succeeded! Wypakowanie RomFS zakończone pomyślnie! - + The operation completed successfully. Operacja zakończona pomyślnie. - + Error Opening %1 Błąd podczas otwierania %1 - + Select Directory Wybierz folder - + Properties Właściwości - + The game properties could not be loaded. Właściwości tej gry nie mogły zostać załadowane. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. Plik wykonywalny Switcha (%1);;Wszystkie pliki (*.*) - + Load File Załaduj plik - + Open Extracted ROM Directory Otwórz folder wypakowanego ROMu - + Invalid Directory Selected Wybrano niewłaściwy folder - + The directory you have selected does not contain a 'main' file. Wybrany folder nie zawiera 'głównego' pliku. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) Instalacyjne pliki Switch'a (*.nca *.nsp *.xci);;Archiwum zawartości Nintendo (*.nca);;Pakiet poddany Nintendo (*.nsp);;Obraz z kartridża NX (*.xci) - + Install Files Zainstaluj pliki - + %n file(s) remaining Pozostał %n plikPozostały %n plikiPozostało %n plikówPozostało %n plików - + Installing file "%1"... Instalowanie pliku "%1"... - - + + Install Results Wynik instalacji - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. Aby uniknąć ewentualnych konfliktów, odradzamy użytkownikom instalowanie gier na NAND. Proszę, używaj tej funkcji tylko do instalowania łatek i DLC. - + %n file(s) were newly installed %n plik został nowo zainstalowany @@ -7654,7 +7720,7 @@ Proszę, używaj tej funkcji tylko do instalowania łatek i DLC. - + %n file(s) were overwritten %n plik został nadpisany @@ -7664,7 +7730,7 @@ Proszę, używaj tej funkcji tylko do instalowania łatek i DLC. - + %n file(s) failed to install Nie udało się zainstalować %n pliku @@ -7674,361 +7740,324 @@ Proszę, używaj tej funkcji tylko do instalowania łatek i DLC. - + System Application Aplikacja systemowa - + System Archive Archiwum systemu - + System Application Update Aktualizacja aplikacji systemowej - + Firmware Package (Type A) Paczka systemowa (Typ A) - + Firmware Package (Type B) Paczka systemowa (Typ B) - + Game Gra - + Game Update Aktualizacja gry - + Game DLC Dodatek do gry - + Delta Title Tytuł Delta - + Select NCA Install Type... Wybierz typ instalacji NCA... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) Wybierz typ tytułu, do którego chcesz zainstalować ten NCA, jako: (W większości przypadków domyślna "gra" jest w porządku.) - + Failed to Install Instalacja nieudana - + The title type you selected for the NCA is invalid. Typ tytułu wybrany dla NCA jest nieprawidłowy. - + File not found Nie znaleziono pliku - + File "%1" not found Nie znaleziono pliku "%1" - + OK OK - + Function Disabled Funkcja wyłączona - + Compatibility list reporting is currently disabled. Check back later! Zgłaszanie do listy kompatybilności jest obecnie wyłączone. Spróbuj ponownie później! - + Error opening URL Błąd otwierania adresu URL - + Unable to open the URL "%1". Nie można otworzyć adresu URL "%1". - + TAS Recording Nagrywanie TAS - + Overwrite file of player 1? Nadpisać plik gracza 1? - + Invalid config detected Wykryto nieprawidłową konfigurację - + Handheld controller can't be used on docked mode. Pro controller will be selected. Nie można używać kontrolera handheld w trybie zadokowanym. Zostanie wybrany kontroler Pro. - - + + Amiibo Amiibo - - + + The current amiibo has been removed Amiibo zostało "zdjęte" - + Error Błąd - - + + The current game is not looking for amiibos Ta gra nie szuka amiibo - + Amiibo File (%1);; All Files (*.*) Plik Amiibo (%1);;Wszyskie pliki (*.*) - + Load Amiibo Zamontuj Amiibo - + Error loading Amiibo data Błąd podczas ładowania danych Amiibo - + The selected file is not a valid amiibo Wybrany plik nie jest poprawnym amiibo - + The selected file is already on use Wybrany plik jest już w użyciu - + An unknown error occurred Wystąpił nieznany błąd - - - Keys not installed - Klucze nie zainstalowane - - - - - Install decryption keys and restart Eden before attempting to install firmware. - Zainstaluj klucze deszyfrujące i uruchom ponownie Eden przed próbą instalacji firmware’u. - - - - Select Dumped Firmware Source Location - Wybierz lokalizację źródła zrzuconego firmware’u - - - - Select Dumped Firmware ZIP - Wybierz plik ZIP ze zrzuconym Firmwarem - - - - Zipped Archives (*.zip) - Archiwa ZIP (.zip) - - - - Firmware cleanup failed - Czyszczenie firmware’u nie powiodło się - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - Nie udało się wyczyścić pamięci podręcznej wyodrębnionego firmware’u. -Sprawdź uprawnienia zapisu do systemowego katalogu tymczasowego i spróbuj ponownie. -Błąd zgłoszony przez system: %1 - - - + No firmware available Brak dostępnego firmware'u - + Firmware Corrupted Uszkodzony Firmware - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot Zrób zrzut ekranu - + PNG Image (*.png) Obrazek PNG (*.png) - + TAS state: Running %1/%2 Status TAS: Działa %1%2 - + TAS state: Recording %1 Status TAS: Nagrywa %1 - + TAS state: Idle %1/%2 Status TAS: Bezczynny %1%2 - + TAS State: Invalid Status TAS: Niepoprawny - + &Stop Running &Wyłącz - + Stop R&ecording Przestań &Nagrywać - + Building: %n shader(s) Budowanie: %n shaderBudowanie: %n shaderyBudowanie: %n shaderówBudowanie: %n shaderów - + Scale: %1x %1 is the resolution scaling factor Skala: %1x - + Speed: %1% / %2% Prędkość: %1% / %2% - + Speed: %1% Prędkość: %1% - + Game: %1 FPS Gra: %1 FPS - + Frame: %1 ms Klatka: %1 ms - - - FSR - FSR - - - + NO AA BEZ AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE Głośność: Wyciszony - + VOLUME: %1% Volume percentage (e.g. 50%) Głośność: %1% - + Derivation Components Missing Brak komponentów wyprowadzania - + Decryption keys are missing. Install them now? - + Wayland Detected! Wykryto Waylanda! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -8038,74 +8067,74 @@ Zaleca się zamiast tego używać X11. Czy chcesz wymusić jego użycie przy przyszłych uruchomieniach? - + Use X11 Używaj X11 - + Continue with Wayland Kontynuuj z Waylandem - + Don't show again Nie pokazuj ponownie - + Restart Required Wymagane ponowne uruchomienie - + Restart Eden to apply the X11 backend. Uruchom ponownie Edena, aby zastosować backend X11. - + Slow - + Turbo - + Unlocked - + Select RomFS Dump Target Wybierz cel zrzutu RomFS - + Please select which RomFS you would like to dump. Proszę wybrać RomFS, jakie chcesz zrzucić. - + Are you sure you want to close Eden? Czy na pewno chcesz zamknąć Edena? - - - + + + Eden Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. Czy na pewno chcesz zatrzymać emulację? Wszystkie niezapisane postępy zostaną utracone. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8172,6 +8201,11 @@ Czy chcesz to pominąć i wyjść mimo to? ScaleForce ScaleForce + + + FSR + FSR + Area @@ -8183,52 +8217,57 @@ Czy chcesz to pominąć i wyjść mimo to? MMPX - + + SGSR + + + + + SGSR EdgeDir + + + + Docked Zadokowany - + Handheld Przenośny - + Fast Szybkie - - Balanced - Zrównoważony - - - + Accurate Dokładny - + Vulkan Vulkan - + OpenGL GLSL - + OpenGL SPIRV - + OpenGL GLASM - + Null Null @@ -8271,7 +8310,7 @@ Jeśli chcesz usunąć pliki, które pozostały w starej lokalizacji danych, mo %1 - + Data was migrated successfully. Dane zostały pomyślnie przeniesione. @@ -8764,8 +8803,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8778,10 +8817,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8796,9 +8835,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8960,50 +8999,50 @@ p, li { white-space: pre-wrap; } [niezdefiniowane] - + %1%2 %1%2 - + [invalid] [niepoprawne] - - + + %1%2Hat %3 %1%2Drążek %3 - - - + + + %1%2Axis %3 %1%2Oś %3 - + %1%2Axis %3,%4,%5 %1%2Oś %3,%4,%5 - + %1%2Motion %3 %1%2Ruch %3 - - + + %1%2Button %3 %1%2Przycisk %3 - + [unused] [nieużywane] @@ -9129,47 +9168,47 @@ p, li { white-space: pre-wrap; } %1 gra w %2 - + Play Time: %1 - + Never Played - + Version: %1 - + Version: 1.0.0 - + Installed SD Titles Zainstalowane tytuły SD - + Installed NAND Titles Zainstalowane tytuły NAND - + System Titles Tytuły systemu - + Add New Game Directory Dodaj nowy katalog gier - + Favorites Ulubione @@ -9290,14 +9329,14 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware Gra wymaga firmware’u - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. - Gra, którą próbujesz uruchomić, wymaga firmware’u do startu lub do przejścia ekranu początkowego. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>Zrzuć i zainstaluj firmware</a> albo naciśnij „OK”, aby mimo to uruchomić. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. + @@ -9306,10 +9345,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel Anuluj @@ -9330,7 +9369,7 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 @@ -9338,207 +9377,240 @@ p, li { white-space: pre-wrap; } %1 - - + + Verifying integrity... Weryfikacja integralności... - - + + Integrity verification succeeded! Weryfikacja integralności zakończona sukcesem! - - + + The operation completed successfully. Operacja zakończona pomyślnie. - - + + Integrity verification failed! Weryfikacja integralności nie powiodła się! - + File contents may be corrupt or missing. Zawartość pliku może być uszkodzona lub brakująca. - + Integrity verification couldn't be performed Nie można było przeprowadzić weryfikacji integralności - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. Instalacja firmware’u została anulowana — firmware może być w złym stanie lub uszkodzony. Nie udało się sprawdzić poprawności zawartości plików. - + Select Dumped Keys Location Wybierz lokalizację zrzutu kluczy - + Decryption Keys install succeeded Instalacja kluczy deszyfrujących powiodła się - + Decryption Keys install failed Instalacja kluczy deszyfrujących nie powiodła się - + Orphaned Profiles Detected! Wykryto osierocone profile! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> MOGĄ WYSTĄPIĆ NIEOCZEKIWANE PROBLEMY, JEŚLI TEGO NIE PRZECZYTASZ!<br>Eden wykrył następujące katalogi zapisów bez przypisanego profilu:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Kliknij „OK”, aby otworzyć folder zapisów i naprawić profile.<br>Wskazówka: skopiuj zawartość największego lub ostatnio modyfikowanego folderu w inne miejsce, usuń wszystkie osierocone profile, a następnie przenieś skopiowaną zawartość do właściwego profilu.<br><br>Nadal masz wątpliwości? Zobacz<a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>tronę pomocy</a>.<br> - + Really clear data? Na pewno wyczyścić dane? - + Important data may be lost! Ważne dane mogą zostać utracone! - + Are you REALLY sure? Czy NA PEWNO chcesz to zrobić? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. Po usunięciu Twoje dane NIE WRÓCĄ! Wykonaj to tylko, jeśli w 100% chcesz usunąć te dane. - + Clearing... Czyszczenie… - + Select Export Location Wybierz lokalizację eksportu - + %1.zip %1.zip - - + + + Zipped Archives (*.zip) Archiwa ZIP (.zip) - + Exporting data. This may take a while... Eksportowanie danych. To może chwilę potrwać… - + Exporting Eksportowanie - + Exported Successfully Wyeksportowano pomyślnie - + Data was exported successfully. Dane zostały pomyślnie wyeksportowane. - + Export Cancelled Eksport anulowany - + Export was cancelled by the user. Eksport został anulowany przez użytkownika. - + Export Failed Eksport nie powiódł się - + Ensure you have write permissions on the targeted directory and try again. Upewnij się, że masz uprawnienia zapisu do docelowego katalogu i spróbuj ponownie. - + Select Import Location Wybierz lokalizację importu - + Import Warning Ostrzeżenie dotyczące importu - + All previous data in this directory will be deleted. Are you sure you wish to proceed? Wszystkie dotychczasowe dane w tym katalogu zostaną usunięte. Czy na pewno chcesz kontynuować? - + Importing data. This may take a while... Importowanie danych. To może chwilę potrwać… - + Importing Importowanie - + Imported Successfully Zaimportowano pomyślnie - + Data was imported successfully. Dane zostały pomyślnie zaimportowane. - + Import Cancelled Import anulowany - + Import was cancelled by the user. Import został anulowany przez użytkownika. - + Import Failed Import nie powiódł się - + Ensure you have read permissions on the targeted directory and try again. Upewnij się, że masz uprawnienia odczytu do docelowego katalogu i spróbuj ponownie. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9787,72 +9859,72 @@ Czy chcesz ręcznie wybrać folder przenośny do użycia? Nie można było usunąć pamięci podręcznej metadanych. Może być używana albo nie istnieje. - + Create Shortcut Utwórz skrót - + Do you want to launch the game in fullscreen? Uruchomić grę w trybie pełnoekranowym? - + Shortcut Created Utworzono skrót - + Successfully created a shortcut to %1 Pomyślnie utworzono skrót do %1 - + Shortcut may be Volatile! Skrót może być nietrwały! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? Zostanie utworzony skrót do bieżącego AppImage. Po aktualizacji może działać nieprawidłowo. Kontynuować? - + Failed to Create Shortcut Nie udało się utworzyć skrótu - + Failed to create a shortcut to %1 Nie udało się utworzyć skrótu do %1 - + Create Icon Utwórz ikonę - + Cannot create icon file. Path "%1" does not exist and cannot be created. Nie można utworzyć pliku ikony. Ścieżka „%1” nie istnieje i nie można jej utworzyć. - + No firmware available Brak dostępnego firmware'u - + Please install firmware to use the home menu. Zainstaluj firmware, aby używać Menu głównego. - + Home Menu Applet Aplet „Menu główne” - + Home Menu is not available. Please reinstall firmware. Menu główne jest niedostępne. Zainstaluj ponownie firmware. @@ -9860,55 +9932,55 @@ Czy chcesz ręcznie wybrać folder przenośny do użycia? QtCommon::Mod - + Mod Name - + What should this mod be called? - + RomFS - + ExeFS/Patch - + Cheat - + Mod Type - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty @@ -10609,65 +10681,65 @@ Wybierając „Z Eden”, dotychczasowe dane zapisu przechowywane w Ryujinx zost - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/pt_BR.ts b/dist/languages/pt_BR.ts index aa64808086..063a1bff4f 100644 --- a/dist/languages/pt_BR.ts +++ b/dist/languages/pt_BR.ts @@ -119,12 +119,12 @@ li.checked::marker { content: "\2612"; } %1 has been banned - %1 foi banido(a) + %1 foi banido %1 has been unbanned - %1 foi desbanido(a) + %1 foi desbanido @@ -218,155 +218,6 @@ Isto banirá tanto o nome de usuário como o endereço IP do fórum.%1 - %2 (%3/%4 membros) - conectados - - CompatDB - - - Report Compatibility - Reportar Compatibilidade - - - - - - - - - - Report Game Compatibility - Reportar compatibilidade de jogos - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - <html><head/><body><p><span style=" font-size:10pt;">Caso opte por enviar um teste para a</span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">Lista de Compatibilidade do eden</span></a><span style=" font-size:10pt;">, As seguintes informações serão coletadas e exibidas no site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Informações de Hardware (CPU/GPU/Sistema Operacional)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qual versão do eden está sendo utilizada</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A conta eden conectada.</li></ul></body></html> - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>O jogo inicializa?</p></body></html> - - - - Yes The game starts to output video or audio - Sim. O jogo começou por vídeo ou áudio. - - - - No The game doesn't get past the "Launching..." screen - Não. O Jogo não passou da tela de inicialização "Launching..." - - - - Yes The game gets past the intro/menu and into gameplay - Sim O Jogo passou da tela de menu/introdução e começou o gameplay - - - - No The game crashes or freezes while loading or using the menu - Não O jogo travou e/ou apresentou falhas graves durante o carregamento ou utilizando o menu - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>O jogo chega a gameplay?</p></body></html> - - - - Yes The game works without crashes - Sim O jogo funciona sem crashes - - - - No The game crashes or freezes during gameplay - Não O jogo crasha ou congela durante a gameplay - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>O jogo funciona sem crashar, congelar ou travar durante a gameplay?</p></body></html> - - - - Yes The game can be finished without any workarounds - Sim O jogo pode ser concluido sem o uso de soluções alternativas - - - - No The game can't progress past a certain area - Não Não é possível progredir no jogo a partir de uma certa área - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>O jogo é completamente jogável do início ao fim?</p></body></html> - - - - Major The game has major graphical errors - Grave O jogo tem grandes erros gráficos - - - - Minor The game has minor graphical errors - Pequenos O jogo tem pequenos erros gráficos - - - - None Everything is rendered as it looks on the Nintendo Switch - Nenhum Tudo é renderizado como no Nintendo Switch - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>O jogo tem alguma falha gráfica?</p></body></html> - - - - Major The game has major audio errors - Graves O jogo tem graves erros de áudio - - - - Minor The game has minor audio errors - Pequenos O jogo tem pequenos erros de audio - - - - None Audio is played perfectly - Nenhum O áudio é reproduzido perfeitamente - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>O jogo tem alguma falha no áudio / efeitos ausentes?</p></body></html> - - - - Thank you for your submission! - Obrigado pelo seu envio! - - - - Submitting - Enviando - - - - Communication error - Erro de comunicação - - - - An error occurred while sending the Testcase - Um erro ocorreu ao enviar o caso de teste - - - - Next - Próximo - - ConfigurationShared @@ -377,7 +228,7 @@ Isto banirá tanto o nome de usuário como o endereço IP do fórum. Amiibo editor - Editor de Amiibo + Editor Amiibo @@ -569,39 +420,38 @@ Pode ajudar a reduzir travamentos (engasgos) em taxas de quadros mais baixas. - + Backend: Backend: - CPU Overclock - Overclock da CPU + CPU Clocks + - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. - Faz overclock na CPU emulada para remover alguns limitadores de FPS. CPUs mais fracas podem apresentar redução de desempenho, e certos jogos podem se comportar de forma anormal. -Use 'Boost' (1700MHz) para rodar no clock nativo mais alto do Switch, ou 'Rápido' (2000MHz) para rodar com o dobro do clock. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + - + Custom CPU Ticks Ciclos de CPU Personalizados - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. Defina um valor personalizado de ciclos da CPU. Valores mais altos podem aumentar o desempenho, mas também podem causar travamentos. É recomendado usar um intervalo entre 77-21000. - + Enable Host MMU Emulation (fastmem) Ativar Emulação de MMU do Anfitrião (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. @@ -610,99 +460,99 @@ Ativá-la faz com que as leituras/escritas de memória do convidado sejam feitas Desativar esta opção força todos os acessos à memória a usarem a Emulação de MMU por Software. - + Unfuse FMA (improve performance on CPUs without FMA) FMA inseguro (melhora desempenho no CPU sem FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. Essa opção melhora a velocidade ao reduzir a precisão de instruções de fused-multiply-add em CPUs sem suporte nativo ao FMA. - + Faster FRSQRTE and FRECPE FRSQRTE e FRECPE mais rápido - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. Essa opção melhora a velocidade de algumas funções aproximadas de pontos flutuantes ao usar aproximações nativas precisas. - + Faster ASIMD instructions (32 bits only) Instruções ASIMD mais rápidas (apenas 32 bits) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. Essa opção melhora a velocidade de funções de pontos flutuantes de 32 bits ASIMD ao executá-las com modos de arredondamento incorretos. - + Inaccurate NaN handling Tratamento impreciso de NaN - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. Esta opção melhora a velocidade ao remover a checagem NaN. Por favor, note que isso também reduzirá a precisão de certas instruções de ponto flutuante. - + Disable address space checks Desativar a verificação do espaço de endereços - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. Esta opção melhora a velocidade ao eliminar uma verificação de segurança antes de cada operação de memória. Desativá-la pode permitir a execução de código arbitrário. - + Ignore global monitor Ignorar monitor global - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. Esta opção melhora a velocidade ao depender apenas das semânticas do cmpxchg pra garantir a segurança das instruções de acesso exclusivo. Por favor, note que isso pode resultar em travamentos e outras condições de execução. - + API: API: - + Changes the output graphics API. Vulkan is recommended. Altera a API gráfica de saída. O uso do Vulkan é recomendado. - + Device: Dispositivo: - + This setting selects the GPU to use (Vulkan only). Esta configuração seleciona a GPU a ser usada (apenas com Vulkan). - + Resolution: Resolução: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. @@ -711,27 +561,27 @@ Resoluções mais altas exigem mais VRAM e largura de banda. Opções abaixo de 1X podem causar artefatos. - + Window Adapting Filter: Filtro de adaptação de janela: - + FSR Sharpness: FSR Sharpness: - - Determines how sharpened the image will look using FSR's dynamic contrast. - Determines how sharpened the image will look using FSR's dynamic contrast. "Define o nível de nitidez da imagem usando o contraste dinâmico do FSR." + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. + Determina o quão nítida a imagem ficará usando o contraste dinâmico do FSR ou do SGSR. - + Anti-Aliasing Method: Método de Anti-Aliasing - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. @@ -739,13 +589,23 @@ FXAA can produce a more stable picture in lower resolutions. SMAA oferece a melhor qualidade. FXAA pode produzir uma imagem mais estável em resoluções mais baixas. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Tela Cheia - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. @@ -754,12 +614,12 @@ Sem borda oferece a melhor compatibilidade com o teclado na tela que alguns jogo Tela cheia exclusiva pode oferecer melhor desempenho e melhor suporte a Freesync/Gsync. - + Aspect Ratio: Proporção do Ecrã: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. @@ -768,35 +628,36 @@ A maioria dos jogos suporta apenas 16:9, sendo necessárias modificações para Também controla a proporção das capturas de tela. - + Use persistent pipeline cache Usar cache persistente de pipeline - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. Permite guardar os shaders para carregar os jogos nas execuções seguintes. Desabiltar essa opção só serve para propósitos de depuração. - + Use asynchronous GPU emulation - + Usar emulação assíncrona de GPU - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + Usa uma thread de CPU extra para renderização. +Esta opção deve estar sempre habilitada. - + NVDEC emulation: Emulação NVDEC: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. @@ -805,12 +666,12 @@ Tanto a CPU quanto a GPU podem ser utilizadas para decodificação, ou não deco Na maioria dos casos, a decodificação pela GPU fornece um melhor desempenho. - + ASTC Decoding Method: Método de Decodificação ASTC: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -823,56 +684,76 @@ CPU Assíncrona: Usa a CPU para decodificar texturas ASTC sob demanda. Elimina t mas pode apresentar artefatos visuais. - + ASTC Recompression Method: Método de Recompressão ASTC: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. A maioria das GPUs não suporta texturas ASTC e precisa descompactá-las para um formato intermediário: RGBA8.BC1/BC3: O formato intermediário será recomprimido para BC1 ou BC3, economizando VRAM mas degradando a qualidade da imagem. - + Frame Pacing Mode (Vulkan only) Modo de Ritmo de Quadros (apenas Vulkan) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. Controle como o emulador gerencia o ritmo de quadros para reduzir travamentos (engasgos) e deixar a taxa de quadros mais suave e consistente. - + VRAM Usage Mode: Modo de Uso da VRAM: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. Define se o emulador deve priorizar a economia de memória ou fazer o uso máximo da memória de vídeo disponível para melhorar o desempenho. O modo agressivo pode impactar a performance de outros aplicativos, como softwares de gravação. - + Skip CPU Inner Invalidation Ignorar invalidação interna da CPU - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. Ignora certas invalidações de cache durante atualizações de memória, reduzindo o uso da CPU e melhorando a latência. Isso pode causar travamentos leves. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: Modo de Sincronização vertical: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -883,12 +764,12 @@ Mailbox pode ter menor latência que FIFO e não apresenta cortes, mas pode perd Immediate (sem sincronização) exibe o que estiver disponível e pode apresentar cortes. - + Sync Memory Operations Sincronizar operações de memória - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. @@ -897,102 +778,114 @@ Esta opção corrige problemas em diversos jogos, mas pode reduzir o desempenho. Jogos baseados na Unreal Engine 4 costumam apresentar as mudanças mais significativas. - + Enable asynchronous presentation (Vulkan only) Ativar apresentação assíncrona (Somente Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. Melhora ligeiramente o desempenho ao mover a apresentação para uma thread de CPU separada. - + Force maximum clocks (Vulkan only) Forçar clock máximo (somente Vulkan) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. Executa trabalho em segundo plano aguardando pelos comandos gráficos para evitar a GPU de reduzir seu clock. - + Anisotropic Filtering: Filtro Anisotrópico: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. Controla a qualidade da renderização de texturas em ângulos oblíquos. Seguro definir em 16x na maioria das GPUs. - + GPU Mode: Modo da GPU: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - Controla o modo de emulação da GPU. -A maioria dos jogos roda bem nos modos Rápido ou Balanceado, mas o modo Preciso ainda é necessário para alguns. -Partículas geralmente só renderizam corretamente no modo Preciso. + - + DMA Accuracy: Precisão do DMA: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. - Controla a precisão do DMA. A precisão Segura corrige problemas em alguns jogos, mas pode reduzir o desempenho. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. + - + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation Ativar compilação assíncrona de shaders - + May reduce shader stutter. Pode reduzir travamentos de shaders. - - Fast GPU Time - Tempo Rápido da GPU + + GPU Clocks + - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. - Faz overclock da GPU emulada para aumentar a resolução dinâmica e a distância de renderização. -Use 256 para desempenho máximo e 512 para fidelidade gráfica máxima. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. + - + GPU Unswizzle GPU Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. Acelera a decodificação de texturas 3D BCn usando processamento da GPU. Desative se ocorrerem travamentos ou falhas gráficas. - + GPU Unswizzle Max Texture Size Tamanho Máximo de Textura para GPU Unswizzle - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. @@ -1001,48 +894,48 @@ A GPU é mais rápida para texturas médias e grandes, mas a CPU pode ser mais e Ajuste para encontrar o equilíbrio entre aceleração da GPU e sobrecarga da CPU. - + GPU Unswizzle Stream Size Tamanho do Fluxo de GPU Unswizzle - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. Define a quantidade máxima de dados de textura (em MiB) processados por quadro. Valores mais altos podem reduzir travamentos durante o carregamento de texturas, mas podem afetar a consistência dos quadros. - + GPU Unswizzle Chunk Size Tamanho do Bloco de GPU Unswizzle - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. Determina o número de fatias de profundidade processadas em uma única execução. Aumentar esse valor pode melhorar o desempenho em GPUs avançadas, mas pode causar TDR ou tempo limite do driver em hardwares mais fracos. - + Use Vulkan pipeline cache Utilizar cache de pipeline do Vulkan - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. Habilita o cache de pipeline da fabricante da GPU. Esta opção pode melhorar o tempo de carregamento de shaders significantemente em casos onde o driver Vulkan não armazena o cache de pipeline internamente. - + Enable Compute Pipelines (Intel Vulkan Only) Habilitar Pipeline de Computação (Somente Intel Vulkan) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. @@ -1051,184 +944,182 @@ Esta configuração existe apenas para drivers proprietários da Intel e pode ca Pipelines de computação estão sempre habilitados em outros drivers. - + Enable Reactive Flushing Habilitar Flushing Reativo - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. Usa flushing reativo ao invés de flushing preditivo, permitindo mais precisão na sincronização da memória. - + Sync to framerate of video playback Sincronizar com o framerate da reprodução de vídeo - + Run the game at normal speed during video playback, even when the framerate is unlocked. Executa o jogo na velocidade normal durante a reprodução de vídeo, mesmo se o framerate estiver desbloqueado. - + Barrier feedback loops Ciclos de feedback de barreira - + Improves rendering of transparency effects in specific games. Melhora a renderização de efeitos de transparência em jogos específicos. - + Enable buffer history Ativar histórico de buffer - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. Permite acesso a estados anteriores do buffer. Esta opção pode melhorar a qualidade de renderização e a consistência de desempenho em alguns jogos. - + Fix bloom effects Corrigir efeitos de bloom - + Removes bloom in Burnout. Remove o bloom no Burnout. - - Enable Legacy Rescale Pass - Ativar Passagem de Redimensionamento Legada - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - Pode corrigir problemas de redimensionamento em alguns jogos, baseando-se no comportamento da implementação anterior. -Solução legada que corrige artefatos de linha em GPUs AMD e Intel, e cintilação de texturas cinzas em GPUs Nvidia no Luigi's Mansion 3. - - - + Extended Dynamic State Estado Dinâmico Estendido - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. Controla o número de recursos que podem ser usados no Estado Dinâmico Estendido. Estados mais altos permitem mais recursos e podem aumentar o desempenho, mas também podem causar problemas gráficos adicionais. - + Vertex Input Dynamic State Estado Dinâmico de Entrada de Vértices - + Enables vertex input dynamic state feature for better quality and performance. Ativa o recurso de estado dinâmico de entrada de vértices para melhor qualidade e desempenho. - + Sample Shading Sombreamento por Amostragem - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. Permite que o shader de fragmento seja processado por cada amostra em fragmentos multiamostrados, em vez de uma única vez por fragmento. Melhora a qualidade gráfica ao custo de desempenho. Valores mais altos aumentam a qualidade, mas reduzem a performance. - + RNG Seed Semente de RNG - + Controls the seed of the random number generator. Mainly used for speedrunning. Controla a semente do gerador de números aleatórios. Usado principalmente em speedruns. - + Device Name Nome do Dispositivo - + The name of the console. O nome do console. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: Data personalizada do RTC: - + This option allows to change the clock of the console. Can be used to manipulate time in games. Esta opção permite alterar o relógio do console. Pode ser usada para manipular o tempo nos jogos. - + The number of seconds from the current unix time Número de segundos a partir do tempo Unix atual - + Language: Idioma: - + This option can be overridden when region setting is auto-select Esta opção pode ser substituída quando a configuração de região estiver em seleção automática - + Region: Região: - + The region of the console. A região do console. - + Time Zone: Fuso Horário: - + The time zone of the console. O fuso horário do console. - + Sound Output Mode: Modo de saída de som - + Console Mode: Modo Console: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. @@ -1237,1031 +1128,1026 @@ Os jogos ajustarão resolução, detalhes e controladores suportados conforme es Definir como Portátil pode ajudar a melhorar o desempenho em sistemas mais fracos. - + Prompt for user profile on boot Solicitar perfil de usuário na inicialização - + Useful if multiple people use the same PC. Útil se várias pessoas usam o mesmo PC. - + Pause when not in focus Pausar quando não estiver em foco - + Pauses emulation when focusing on other windows. Pausa a emulação ao focar em outras janelas. - + Confirm before stopping emulation Confirmar antes de parar a emulação - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. Substitui as confirmações ao parar a emulação. Ao ativar, ignora esses avisos e encerra diretamente a emulação. - + Hide mouse on inactivity Esconder rato quando inactivo. - + Hides the mouse after 2.5s of inactivity. Oculta o mouse após 2,5s de inatividade. - - Disable controller applet - Desabilitar applet de controle - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - Desativa forçadamente o uso do applet de controle em programas emulados. -Quando um programa tenta abrir o applet, ele é imediatamente fechado. - - - + Check for updates Verificar atualizações - + Whether or not to check for updates upon startup. Define se deve verificar atualizações na inicialização. - + Enable Gamemode Habilitar Gamemode - + Force X11 as Graphics Backend Forçar X11 como backend gráfico - + Custom frontend Frontend customizado - + Real applet Applet real - + + + Disabled + Desativado + + + Never Nunca - + On Load Ao carregar - + Always Sempre - + CPU CPU - + GPU GPU - + CPU Asynchronous CPU Assíncrona - + Uncompressed (Best quality) Descompactado (Melhor Qualidade) - + BC1 (Low quality) BC1 (Baixa qualidade) - + BC3 (Medium quality) BC3 (Média qualidade) - - + + Auto Automático - + 30 FPS 30 FPS - + 60 FPS 60 FPS - + 90 FPS 90 FPS - + 120 FPS 120 FPS - + Conservative Conservador - + Aggressive Agressivo - + Vulkan Vulcano - + OpenGL GLSL OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) OpenGL GLASM (Shaders em Assembly, apenas NVIDIA) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) OpenGL SPIR-V (Experimental, apenas AMD/Mesa) - + Null Nenhum - + Fast Rápido - - Balanced - Balanceado - - - - + + + Accurate Preciso - - + + + Default Padrão - + Unsafe (fast) Inseguro (rápido) - + Safe (stable) Seguro (estável) - + + Immediate + + + + + Balanced + Balanceado + + + Unsafe Inseguro - + Paranoid (disables most optimizations) Paranoia (desativa a maioria das otimizações) - + Debugging Depuração - + Dynarmic Dynarmic - + NCE NCE - + Borderless Windowed Janela sem bordas - + Exclusive Fullscreen Tela cheia exclusiva - + No Video Output Sem saída de vídeo - + CPU Video Decoding Decodificação de vídeo pela CPU - + GPU Video Decoding (Default) Decodificação de vídeo pela GPU (Padrão) - + 0.25X (180p/270p) [EXPERIMENTAL] 0.25X (180p/270p) [EXPERIMENTAL] - + 0.5X (360p/540p) [EXPERIMENTAL] 0.5X (360p/540p) [EXPERIMENTAL] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [EXPERIMENTAL] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] 1.25X (900p/1350p) [EXPERIMENTAL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [EXPERIMENTAL] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor Vizinho mais próximo - + Bilinear Bilinear - + Bicubic Bicúbico - + Gaussian Gaussiano - + Lanczos Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution AMD FidelityFX Super Resolution - + Area Área - + MMPX MMPX - + Zero-Tangent Zero-Tangent - + B-Spline B-Spline - + Mitchell Mitchell - + Spline-1 Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None Nenhum - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Padrão (16:9) - + Force 4:3 Forçar 4:3 - + Force 21:9 Forçar 21:9 - + Force 16:10 Forçar 16:10 - + Stretch to Window Esticar à Janela - + Automatic Automático - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - 32x - - - - 64x - 64x - - - + Japanese (日本語) Japonês (日本語) - + American English Inglês Americano - + French (français) Francês (français) - + German (Deutsch) Alemão (Deutsch) - + Italian (italiano) Italiano (italiano) - + Spanish (español) Espanhol (español) - + Chinese Chinês - + Korean (한국어) Coreano (한국어) - + Dutch (Nederlands) Holandês (Nederlands) - + Portuguese (português) Português (português) - + Russian (Русский) Russo (Русский) - + Taiwanese Taiwanês - + British English Inglês Britânico - + Canadian French Francês Canadense - + Latin American Spanish Espanhol Latino-Americano - + Simplified Chinese Chinês Simplificado - + Traditional Chinese (正體中文) Chinês Tradicional (正 體 中文) - + Brazilian Portuguese (português do Brasil) Português do Brasil (Brazilian Portuguese) - + Polish (polska) Polonês (polska) - + Thai (แบบไทย) Tailandês (แบบไทย) - - + + Japan Japão - + USA EUA - + Europe Europa - + Australia Austrália - + China China - + Korea Coreia - + Taiwan Taiwan - + Auto (%1) Auto select time zone Auto (%1) - + Default (%1) Default time zone Padrão (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Cuba - + EET EET - + Egypt Egito - + Eire Irlanda - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Irlanda - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hongkong - + HST HST - + Iceland Islândia - + Iran Irã - + Israel Israel - + Jamaica Jamaica - + Kwajalein Kwajalein - + Libya Líbia - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navajo - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Polônia - + Portugal Portugal - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Singapura - + Turkey Turquia - + UCT UCT - + Universal Universal - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulu - + Mono Mono - + Stereo Estéreo - + Surround Surround - + 4GB DRAM (Default) 4GB DRAM (Padrão) - + 6GB DRAM (Unsafe) 6GB DRAM (Não seguro) - + 8GB DRAM 8GB DRAM - + 10GB DRAM (Unsafe) 10GB DRAM (Inseguro) - + 12GB DRAM (Unsafe) 12GB DRAM (Inseguro) - + Docked Ancorado - + Handheld Portátil - - - Off - Desligado + + + Normal + - - Boost (1700MHz) - Impulso (1700MHz) + + + Boost + - - Fast (2000MHz) - Rápido (2000MHz) + + + Overclock + - + Always ask (Default) Sempre perguntar (Padrão) - + Only if game specifies not to stop Somente se o jogo especificar para não parar - + Never ask Nunca perguntar - - - Medium (256) - Médio (256) - - - - - High (512) - Alto (512) - - - + Very Small (16 MB) Muito Pequeno (16 MB) - + Small (32 MB) Pequeno (32 MB) - + Normal (128 MB) Normal (128 MB) - + Large (256 MB) Grande (256 MB) - + Very Large (512 MB) Muito Grande (512 MB) - + Very Low (4 MB) Muito Baixo (4 MB) - + Low (8 MB) Baixo (8 MB) - + Normal (16 MB) Normal (16 MB) - + Medium (32 MB) Médio (32 MB) - + High (64 MB) Alto (64 MB) - + Very Low (32) Muito Baixo (32) - + Low (64) Baixo (64) - + Normal (128) Normal (128) - - Disabled - Desativado + + Medium (256) + Médio (256) - + + High (512) + Alto (512) + + + ExtendedDynamicState 1 Estado Dinâmico Estendido 1 - + ExtendedDynamicState 2 Estado Dinâmico Estendido 2 - + ExtendedDynamicState 3 Estado Dinâmico Estendido 3 - + Tree View Visualização em Árvore - + Grid View Visualização em Grade @@ -2614,252 +2500,297 @@ Quando um programa tenta abrir o applet, ele é imediatamente fechado.Filtro de registro global - - When checked, the max size of the log increases from 100 MB to 1 GB - Quando ativado, o tamanho máximo do registo aumenta de 100 MB para 1 GB + + GPU Logging/Level + - - Enable Extended Logging** - Ativar registros avançados** + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console Mostrar Relatório na Consola - + + When checked, the max size of the log increases from 100 MB to 1 GB + Quando ativado, o tamanho máximo do registo aumenta de 100 MB para 1 GB + + + + Enable Extended Logging** + Ativar registros avançados** + + + Open Log Location Abrir a localização do registro - + Homebrew Homebrew - + Arguments String Argumentos String - + Graphics Gráficos - + When checked, it executes shaders without loop logic changes Quando ativado, executa shaders sem mudanças de lógica de loop - + Disable Loop safety checks Desativar verificação de segurança de loops - + When checked, it will dump all the macro programs of the GPU Quando marcada, essa opção irá despejar todos os macro programas da GPU - + Dump Maxwell Macros Despejar macros Maxwell - + When checked, it enables Nsight Aftermath crash dumps Quando ativado, ativa a extração de registros de travamento do Nsight Aftermath - + Enable Nsight Aftermath Ativar Nsight Aftermath - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Se selecionado, descarrega todos os shaders originários do cache do disco ou do jogo conforme encontrá-los. + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + - - Dump Game Shaders - Descarregar shaders do jogo + + Dump Guest (Maxwell) Shaders + - + Enable Renderdoc Hotkey Habilitar atalho para Renderdoc - + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower Quando ativado, desativa o macro compilador Just In Time. Ativar isto faz os jogos rodarem mais lentamente. - + Disable Macro JIT Desactivar Macro JIT - + When checked, it disables the macro HLE functions. Enabling this makes games run slower Quando marcado, desabilita as funções do macro HLE. Habilitar esta opção faz com que os jogos rodem mais lentamente - + Disable Macro HLE Desabilitar o Macro HLE - + When checked, the graphics API enters a slower debugging mode Quando ativado, a API gráfica entra em um modo de depuração mais lento. - + Enable Graphics Debugging Activar Depuração Gráfica - + When checked, yuzu will log statistics about the compiled pipeline cache Quando ativado, o yuzu registrará estatísticas sobre o cache de pipeline compilado - + Enable Shader Feedback Ativar Feedback de Shaders - + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> <html><head/><body><p>Quando selecionado, desabilita a reordenação de uploads de memória mapeada, o que permite associar uploads com chamados específicos. Pode reduzir o desempenho em alguns casos.</p></body></html> - + Disable Buffer Reorder Desabilitar a Reordenação de Buffer - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced Avançado - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Permite que o yuzu procure por um ambiente Vulkan funcional quando o programa iniciar. Desabilite essa opção se estiver causando conflitos com programas externos visualizando o yuzu. - + Perform Startup Vulkan Check Executar checagem do Vulkan na inicialização - + Disable Web Applet Desativar Web Applet - + Enable All Controller Types Ativar todos os tipos de controles - + Enable Auto-Stub Ativar Auto-Stub - + Kiosk (Quest) Mode Modo Quiosque (Quest) - + Use dev.keys Usar dev.keys - + Enable Debug Asserts Ativar asserções de depuração - + Debugging Depuração - + Battery Serial: Número de Série da Bateria: - + + Debug Knobs: + + + + Bitmask for quick development toggles - + Bitmask para rápida alternativa de desenvolvimento - + Set debug knobs (bitmask) - + Definir knobs de depuração (bitmask) - + 16-bit debug knob set for quick development toggles - + Knob de depuração 16-bit definido para rápida alternativa de desenvolvimento - + (bitmask) - + (bitmask) - - Debug Knobs: - - - - + Unit Serial: - + Número de série: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. Habilite essa opção para gravar a última saída da lista de comandos de áudio para o console. Somente afetará jogos que utilizam o renderizador de áudio. - + Dump Audio Commands To Console** Despejar comandos de áudio no console** - + Flush log output on each line - + Liberar o log a cada linha - + Enable FS Access Log Ativar acesso de registro FS - + Enable Verbose Reporting Services** Ativar serviços de relatório detalhado** - + Censor username in logs - + Censurar nome de usuário nos logs - + **This will be reset automatically when Eden closes. **Reseterá automaticamente quando o Eden fechar. @@ -2960,7 +2891,7 @@ Quando um programa tenta abrir o applet, ele é imediatamente fechado. GraphicsExtra - + Gráficos Extra @@ -3040,7 +2971,7 @@ Quando um programa tenta abrir o applet, ele é imediatamente fechado. Save Data - + Dados de Salvamento @@ -3116,7 +3047,7 @@ Quando um programa tenta abrir o applet, ele é imediatamente fechado. Select Save Data Directory... - + Selecionar o diretório de salvamento @@ -3136,22 +3067,22 @@ Quando um programa tenta abrir o applet, ele é imediatamente fechado. Save Data Directory - + Salvar diretório de salvamento Choose an action for the save data directory: - + Escolha uma ação para o diretório de salvamento: Set Custom Path - + Definir caminho personalizado Reset to NAND - + Resetar a NAND @@ -3162,7 +3093,13 @@ New: %2 Would you like to migrate saves from the old location? WARNING: This will overwrite any conflicting saves in the new location! - + Salvamento existe tanto no antigo quanto nas novas locações. + +Antigo: %1 +Novo: %2 + +Você gostaria de migrar seu salvamento do antigo local? +ATENÇÃO: Isso irá sobrescrever qualquer salvamento conflitante no novo local! @@ -3170,51 +3107,57 @@ WARNING: This will overwrite any conflicting saves in the new location! From: %1 To: %2 - + Você gostaria de migrar seus salvamentos para o novo local? + +De: %1 +Para: %2 Migrate Save Data - + Migrar salvamento Migrating save data... - + Migrando salvamento... Cancel - + Cancelar Migration Failed - + Migração Falhou Failed to create destination directory. - + Falha ao criar diretório de destino. Failed to migrate save data: %1 - + Falha ao migrar salvamento: +1% Migration Complete - + Migração Completa Save data has been migrated successfully. Would you like to delete the old save data? - + Salvamento foi migrado com sucesso + +Gostaria de deletar o salvamento antigo? @@ -3309,33 +3252,38 @@ Would you like to delete the old save data? Cor de fundo: - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off Desligado - + VSync Off Sincronização vertical desligada - + Recommended Recomendado - + On Ligado - + VSync On Sincronização vertical ligada @@ -3363,27 +3311,27 @@ Would you like to delete the old save data? Form - + Formar Extras - + Extras Hacks - + Hacks Changing these options from their default may cause issues. Novitii cavete! - + Alterar essas opções de seus padrões pode causar problemas. È una trappola, Bino! Vulkan Extensions - + Extensões Vulkan @@ -3394,7 +3342,7 @@ Would you like to delete the old save data? Extended Dynamic State is disabled on macOS due to MoltenVK compatibility issues that cause black screens. - + Estado Dinâmico Extendido é desativado no MacOS, devido a problemas de compatibilidade com o MoltenVK, que causa telas pretas. @@ -3856,6 +3804,16 @@ Would you like to delete the old save data? Motion / Touch Movimento / Toque + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4365,58 +4323,58 @@ Para inverter os eixos, mova o seu analógico primeiro verticalmente e depois ho [em espera] - + New Profile Novo Perfil - + Enter a profile name: Introduza um novo nome de perfil: - - + + Create Input Profile Criar perfil de controlo - + The given profile name is not valid! O nome de perfil dado não é válido! - + Failed to create the input profile "%1" Falha ao criar o perfil de controlo "%1" - + Delete Input Profile Apagar Perfil de Controlo - + Failed to delete the input profile "%1" Falha ao apagar o perfil de controlo "%1" - + Load Input Profile Carregar perfil de controlo - + Failed to load the input profile "%1" Falha ao carregar o perfil de controlo "%1" - + Save Input Profile Salvar perfil de controle - + Failed to save the input profile "%1" Falha ao salvar o perfil de controle "%1" @@ -4464,7 +4422,7 @@ Para inverter os eixos, mova o seu analógico primeiro verticalmente e depois ho - + Configure Configurar @@ -4495,7 +4453,7 @@ Para inverter os eixos, mova o seu analógico primeiro verticalmente e depois ho - + Test Testar @@ -4510,77 +4468,77 @@ Para inverter os eixos, mova o seu analógico primeiro verticalmente e depois ho Remover Servidor - + %1:%2 %1:%2 - - - - - - + + + + + + Eden Eden - + Port number has invalid characters O número da porta tem caracteres inválidos - + Port has to be in range 0 and 65353 A porta tem que estar entre 0 e 65353 - + IP address is not valid O endereço IP não é válido - + This UDP server already exists Este servidor UDP já existe - + Unable to add more than 8 servers Não é possível adicionar mais de 8 servidores - + Testing Testando - + Configuring Configurando - + Test Successful Teste Bem-Sucedido - + Successfully received data from the server. Dados recebidos do servidor com êxito. - + Test Failed Teste Falhou - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Não foi possível receber dados válidos do servidor.<br>Por favor verifica que o servidor está configurado correctamente e o endereço e porta estão correctos. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. Teste UDP ou configuração de calibragem em progresso.<br> Por favor espera que termine. @@ -4709,7 +4667,7 @@ Os valores atuais são %1% e %2% respectivamente. Enable Airplane Mode - + Habilitar Modo Avião @@ -4765,57 +4723,57 @@ Os valores atuais são %1% e %2% respectivamente. Algumas configurações só estão disponíveis apenas quando não houver nenhum jogo em execução. - + Add-Ons Add-Ons - + System Sistema - + CPU CPU - + Graphics Gráficos - + Adv. Graphics Gráficos Avç. - + Ext. Graphics - + Gráficos Ext. - + Audio Audio - + Input Profiles Perfis de controle - + Network - + Rede - + Applets Applets - + Properties Propriedades @@ -4835,12 +4793,12 @@ Os valores atuais são %1% e %2% respectivamente. Import Mod from ZIP - + Importar Mod a partir de ZIP Import Mod from Folder - + Importar Mod a partir da Pasta @@ -4855,86 +4813,215 @@ Os valores atuais são %1% e %2% respectivamente. Mod Install Succeeded - + Instalação de Mod Concluída Successfully installed all mods. - + Todos os Mods foram instalados com sucesso. Mod Install Failed - + Instalação de Mod Falhou Failed to install the following mods: %1 Check the log for details. - + Falha ao instalar os seguintes Mods: + %1 +Cheque o log para mais detalhes. Mod Folder - + Pasta de Mods Zipped Mod Location - + Local de Mod Empacotado Zipped Archives (*.zip) - + Arquivos Empacotados (*.zip) Invalid Selection - + Seleção inválida Only mods, cheats, and patches can be deleted. To delete NAND-installed updates, right-click the game in the game list and click Remove -> Remove Installed Update. - + Apenas Mods, trapaças e correções podem ser deletadas. +Para deletar atualizações instaladas na NAND, pressione o botão direito no jogo na lista e clique em Remover -> Remova Atualização Instalada. You are about to delete the following installed mods: - + Você está a prestes de deletar os seguintes Mods instalados: + Once deleted, these can NOT be recovered. Are you 100% sure you want to delete them? - + +Uma vez deletado, esses NÃO podem ser recuperados. Você tem 100% de certeza que quer deletar eles? Delete add-on(s)? - + Deletar add-on(s)? Successfully deleted - + Deletado com sucesso Successfully deleted all selected mods. - + Todos os Mods selecionados foram deletados com sucesso. &Delete - + &Deletar &Open in File Manager + &Abrir Gerenciador de Arquivos + + + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. @@ -5021,27 +5108,27 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t Error saving user image - + Erro ao salvar imagem de usuário Unable to save image to file - + Incapaz de salvar imagem para arquivo &Edit - + &Editar &Delete - + &Deletar Edit User - + Editar Usuário @@ -5226,7 +5313,7 @@ UUID: %2 <html><head/><body><p>Reads controller input from scripts in the same format as TAS-nx scripts.<br/>For a more detailed explanation, please consult the user handbook.</p></body></html> - + <html><head/><body><p>Lê as entradas do controle a partir de scripts no mesmo formato que o TAS-nx.<br/>Para uma explicação mais detalhada, por favor consulte o manual do usuário.</p></body></html> @@ -5261,7 +5348,7 @@ UUID: %2 Show recording dialog - + Mostrar gravações de diálogo @@ -5394,7 +5481,7 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta Warning: The settings in this page affect the inner workings of Eden's emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing. - + Atenção: As configurações desta página podem mudar o funcionamento interno do touchscreen emulado do Eden. Mudar elas pode causar problemas indesejados, como o touchscreen parando de funcionar parcialmente e até mesmo por completo. Você deverá apenas usar essa página se você sabe o que está fazendo. @@ -5505,91 +5592,86 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta - Show Compatibility List - Exibir Lista de Compatibilidade - - - Show Add-Ons Column Mostrar coluna de Add-Ons - + Show Size Column Exibir Coluna Tamanho - + Show File Types Column Exibir Coluna Tipos de Arquivos - + Show Play Time Column Exibir coluna Tempo jogado - + Folder Icon Size: Tamanho do ícone da pasta: - + Row 1 Text: Linha 1 Texto: - + Row 2 Text: Linha 2 Texto: - + Screenshots Captura de Ecrã - + Ask Where To Save Screenshots (Windows Only) Perguntar Onde Guardar Capturas de Tela (Apenas Windows) - + Screenshots Path: Caminho das Capturas de Ecrã: - + ... ... - + TextLabel TextLabel - + Resolution: Resolução: - + Select Screenshots Path... Seleccionar Caminho de Capturas de Ecrã... - + <System> <System> - + English Inglês - + Auto (%1 x %2, %3 x %4) Screenshot width value Auto (%1 x %2, %3 x %4) @@ -5690,7 +5772,7 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta Eden Web Service - + Serviço Web do Eden @@ -5705,7 +5787,7 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta Generate - + Gerar @@ -5727,19 +5809,19 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta All Good Tooltip - + Tudo certo Must be between 4-20 characters Tooltip - + Deve conter entre 4-20 caracteres Must be 48 characters, and lowercase a-z Tooltip - + Deve ser em 48 caracteres, e letras minúsculas de a-z @@ -5760,12 +5842,12 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta Data Manager - + Gestor de Dados Deleting ANY data is IRREVERSABLE! - + Deletar QUAISQUER dados é IRREVERSÍVEL! @@ -5775,12 +5857,12 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta User NAND - + NAND de Usuário System NAND - + NAND do Sistema @@ -5790,7 +5872,7 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta Saves - + Salvamentos @@ -5798,37 +5880,37 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta Form - + Formar Tooltip - + Dica de Ferramenta Open with your system file manager - + Abrir gerenciador de arquivos do sistema Delete all data in this directory. THIS IS 100% IRREVERSABLE! - + Deletar todos os dados nesse diretório. ISSO É 100% IRREVERSÍVEL! Export all data in this directory. This may take a while! - + Exportar todos dados desse diretório. Isso pode levar um tempo! Import data for this directory. This may take a while, and will delete ALL EXISTING DATA! - + Importar todos os dados para esse diretório. Isso pode levar um tempo, e vai deletar TODOS OS DADOS JÁ EXISTENTES! - + Calculating... - + Calculando... @@ -5851,12 +5933,12 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta Dependency - + Dependência Version - + Versão @@ -5920,27 +6002,27 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta Username is not valid. Must be 4 to 20 alphanumeric characters. - + O Nome de Usuário não é válido. Deve conter entre 4 à 20 caracteres alfanuméricos. Room name is not valid. Must be 4 to 20 alphanumeric characters. - + Nome de Sala não é válido. Deve conter entre 4 à 20 caracteres alfanuméricos. Username is already in use or not valid. Please choose another. - + Nome de Usuário já está em uso ou não é válido. Por favor, escolha outro. IP is not a valid IPv4 address. - + IP não é um endereço válido IPv4. Port must be a number between 0 to 65535. - + O Número de Port deve ser entre 0 até 65535. @@ -5950,7 +6032,7 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta Unable to find an internet connection. Check your internet settings. - + Não foi possível achar uma conexão de Internet. Cheque suas configurações de Internet. @@ -5960,7 +6042,7 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta Unable to connect to the room because it is already full. - + Não foi possível conectar na sala porque já está cheia. @@ -5970,7 +6052,7 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta The host of the room has banned you. Speak with the host to unban you or try a different room. - + O Anfitrião da sala te baniu. Fale com o Anfitrião para que ele retire seu banimento ou tente uma sala diferente. @@ -5980,378 +6062,373 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta Incorrect password. - + Senha Incorreta. An unknown error occurred. If this error continues to occur, please open an issue - + Um erro desconhecido ocorreu. Se isso continuar a prosseguir, por favor, abra uma "Issue" em nosso repositório e reporte Connection to room lost. Try to reconnect. - + Conexão com a Sala foi perdida. Tentando Reconectar. You have been kicked by the room host. - + Você foi chutado da Sala do Anfitrião. IP address is already in use. Please choose another. - + Endereço IP já está em uso. Por favor, escolha outro. You do not have enough permission to perform this action. - + Você não tem permissão o suficiente para fazer essa ação. The user you are trying to kick/ban could not be found. They may have left the room. - + O Usuário que você está tentar Chutar/Banir não pode ser encontrado. +Ele deve ter saído da Sala. No valid network interface is selected. Please go to Configure -> System -> Network and make a selection. - + Nenhuma Interface de Rede foi selecionada. +Por favor vá para Configuração -> Sistema -> Rede e selecione. Error - + Erro GRenderWindow - - + + OpenGL not available! OpenGL não está disponível! - + OpenGL shared contexts are not supported. Shared contexts do OpenGL não são suportados. - + Eden has not been compiled with OpenGL support. - + Eden não foi compilado com suporte a OpenGL. - - - + + + Error while initializing OpenGL! Erro ao inicializar OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. O seu GPU pode não suportar OpenGL, ou não tem os drivers gráficos mais recentes. - + Error while initializing OpenGL 4.6! Erro ao inicializar o OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 - O teu GPU pode não suportar OpenGL 4.6, ou não tem os drivers gráficos mais recentes. + A sua GPU não tem suporte a OpenGL 4.6, talvez você não esteja usando os drivers gráficos mais atuais.<br><br>Renderizador GL:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Sua GPU pode não suportar uma ou mais extensões necessárias do OpenGL. Verifique se você possui a última versão dos drivers gráficos.<br><br>Renderizador GL:<br>%1<br><br>Extensões não suportadas:<br>%2 - + This build doesn't have OpenGL support. - + Essa build não suporte a OpenGL. GameList - + &Add New Game Directory &Adicionar novo diretório de jogos - + Favorite Favorito - + Start Game Iniciar jogo - + Start Game without Custom Configuration Iniciar jogo sem configuração personalizada - + Open Save Data Location Abrir Localização de Dados Salvos - + Open Mod Data Location Abrir a Localização de Dados do Mod - + Open Transferable Pipeline Cache Abrir cache de pipeline transferível - + Link to Ryujinx Vincular ao Ryujinx - + Remove Remover - + Remove Installed Update Remover Actualizações Instaladas - + Remove All Installed DLC Remover Todos os DLC Instalados - + Remove Custom Configuration Remover Configuração Personalizada - + Remove Cache Storage Remove a Cache do Armazenamento - + Remove OpenGL Pipeline Cache Remover cache de pipeline do OpenGL - + Remove Vulkan Pipeline Cache Remover cache de pipeline do Vulkan - + Remove All Pipeline Caches Remover todos os caches de pipeline - + Remove All Installed Contents Remover Todos os Conteúdos Instalados - + Manage Play Time - + Gerenciar Tempo de Jogo - + Edit Play Time Data - + Editar Dados do Tempo de Jogo - + Remove Play Time Data Remover dados de tempo jogado - - + + Dump RomFS Despejar RomFS - + Dump RomFS to SDMC Extrair RomFS para SDMC - + Verify Integrity Verificar integridade - + Copy Title ID to Clipboard Copiar título de ID para a área de transferência - - Navigate to GameDB entry - Navegue para a Entrada da Base de Dados de Jogos - - - + Create Shortcut Criar Atalho - + Add to Desktop Adicionar à Área de Trabalho - + Add to Applications Menu Adicionar ao Menu de Aplicativos - + Configure Game - + Configurar Jogo - + Scan Subfolders Examinar Sub-pastas - + Remove Game Directory Remover diretório do Jogo - + ▲ Move Up ▲ Mover para Cima - + ▼ Move Down ▼ Mover para Baixo - + Open Directory Location Abrir Localização do diretório - + Clear Limpar - - - Name - Nome - - - - Compatibility - Compatibilidade - - - - Add-ons - Add-ons - - - - File type - Tipo de Arquivo - - - - Size - Tamanho - - - - Play time - Tempo jogado - GameListItemCompat - + Ingame Não Jogável - + Game starts, but crashes or major glitches prevent it from being completed. O jogo inicia, porém problemas ou grandes falhas impedem que ele seja concluído. - + Perfect Perfeito - + Game can be played without issues. O jogo pode ser jogado sem problemas. - + Playable Jogável - + Game functions with minor graphical or audio glitches and is playable from start to finish. O jogo funciona com pequenas falhas gráficas ou de áudio e pode ser reproduzido do início ao fim. - + Intro/Menu Introdução / Menu - + Game loads, but is unable to progress past the Start Screen. O jogo carrega, porém não consegue passar da tela inicial. - + Won't Boot Não Inicia - + The game crashes when attempting to startup. O jogo trava ao tentar iniciar. - + Not Tested Não Testado - + The game has not yet been tested. O jogo ainda não foi testado. + + GameListModel + + + Name + Nome + + + + Add-ons + Complementos + + + + File type + Tipo de arquivo + + + + Size + Tamanho + + + + Play time + Tempo de jogo + + GameListPlaceholder - + Double-click to add a new folder to the game list Clique duas vezes para adicionar uma nova pasta à lista de jogos @@ -6359,17 +6436,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) - + Filter: Filtro: - + Enter pattern to filter Digite o padrão para filtrar @@ -6634,12 +6711,12 @@ Mensagem de Depuração: Toggle Turbo Speed - + Alternar Modo Turbo Toggle Slow Speed - + Alternar Velocidade Lenta @@ -6865,1139 +6942,1113 @@ Mensagem de Depuração: Modo Lista de &Jogos - + Game &Icon Size Tamanho do &Ícone do Jogo - + Reset Window Size to &720p Restaurar tamanho da janela para &720p - + Reset Window Size to 720p Restaurar tamanho da janela para 720p - + Reset Window Size to &900p Restaurar tamanho da janela para &900p - + Reset Window Size to 900p Restaurar tamanho da janela para 900p - + Reset Window Size to &1080p Restaurar tamanho da janela para &1080p - + Reset Window Size to 1080p Restaurar tamanho da janela para 1080p - + &Multiplayer &Multijogador - + &Tools &Ferramentas - + Am&iibo Am&iibo - + Launch &Applet Iniciar &Applet - + &TAS &TAS - + &Create Home Menu Shortcut - + &Criar atalho para o menu Home - + Install &Firmware Instalar &Firmware - + &Help &Ajuda - + &Install Files to NAND... &Instalar arquivos na NAND... - + L&oad File... C&arregar arquivo... - + Load &Folder... Carregar &pasta... - + E&xit &Sair - - + + &Pause &Pausa - + &Stop &Parar - + &Verify Installed Contents &Verificar conteúdo instalado - + &About Eden &Sobre o Eden - + Single &Window Mode Modo de &janela única - + Con&figure... Con&figurar... - + Ctrl+, Ctrl+, - + Enable Overlay Display Applet - + Habilitar a Sobreposição de Display do Applet - + Show &Filter Bar Mostrar Barra de &Filtros - + Show &Status Bar Mostrar Barra de &Estado - + Show Status Bar Mostrar Barra de Estado - + &Browse Public Game Lobby &Navegar no Lobby de Salas Públicas - + &Create Room &Criar Sala - + &Leave Room &Sair da Sala - + &Direct Connect to Room Conectar &Diretamente Numa Sala - + &Show Current Room Exibir &Sala Atual - + F&ullscreen T&ela cheia - + &Restart &Reiniciar - + Load/Remove &Amiibo... Carregar/Remover &Amiibo... - - &Report Compatibility - &Reportar compatibilidade - - - + Open &Mods Page Abrir Página de &Mods - - Open &Quickstart Guide - Abrir &guia de início rápido + + &User Handbook + - - &FAQ - &Perguntas frequentes - - - + &Capture Screenshot &Captura de Tela - + &Album &Álbum - + &Set Nickname and Owner &Definir apelido e proprietário - + &Delete Game Data &Remover dados do jogo - + &Restore Amiibo &Recuperar Amiibo - + &Format Amiibo &Formatar Amiibo - + &Mii Editor - + &Editor de Mii - + &Configure TAS... &Configurar TAS - + Configure C&urrent Game... Configurar jogo atual... - - + + &Start &Começar - + &Reset &Restaurar - - + + R&ecord G&ravar - + Open &Controller Menu Menu Abrir &Controles - + Install Decryption &Keys - + Instalar Chaves de &Encriptação - + &Home Menu - + &Menu Home - + &Desktop &Área de Trabalho - + &Application Menu - + &Menu de Aplicativo - + &Root Data Folder - + &Pasta Raiz de Dados - + &NAND Folder &Pasta NAND - + &SDMC Folder - + &Pasta SDMC + + + + &Mod Folder + &Pasta de Mods + + + + &Log Folder + &Pasta de Logs + + + + From Folder + Pela Pasta - &Mod Folder - + From ZIP + Pelo ZIP - &Log Folder - + &Eden Dependencies + &Dependências do Eden - From Folder - - - - - From ZIP - - - - - &Eden Dependencies - - - - &Data Manager - + &Gestor de Dados - + &Tree View - + &Visualização em Árvore - + &Grid View - + &Visualizar Grid - + Game Icon Size - + Tamanho do Ícone do jogo - - + + None - Nenhum(a) + Nenhum - + Show Game &Name - + Mostrar Nome de &Jogo - + Show &Performance Overlay Mostrar Sobreposição de &Desempenho - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) Pequeno (32x32) - + Standard (64x64) Padrão (64x64) - + Large (128x128) Grande (128x128) - + Full Size (256x256) Tamanho completo (256x256) - + Broken Vulkan Installation Detected Detectada Instalação Defeituosa do Vulkan - + Vulkan initialization failed during boot. - + Inicialização do Vulkan falhou durante o boot. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping - + Rodando um jogo - + Loading Web Applet... - + Carregando Applet Web... - - + + Disable Web Applet - + Desativar Web Applet - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + Desativar o Applet Web pode problemas indefinidos e deverá apenas ser usando com o Super Mario 3D All-Stars. Você tem certeza que quer desativar o Applet Web? +(Isso pode ser reativado nas Configurações de Depuração.) - + The amount of shaders currently being built - + A Quantidade total de Shaders está sendo atualmente construída - + The current selected resolution scaling multiplier. - + A atual multiplicação de escala de resolução selecionada. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + Velocidade atual da emulação. Valores altos ou mais baixo que 100% indicam que o emulador está rodando mais rápido ou mais lento que um Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Quantos quadros por segundo o jogo está mostrando atualmente. Isto varia de jogo para jogo e de cena a cena. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Unmute - + Mute - + Reset Volume - + + Configure Effects... + + + + &Clear Recent Files - + &Continue - + Warning: Outdated Game Format - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - - + + Error while loading ROM! - + The ROM format is not supported. - + An error occurred initializing the video core. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + An unknown error occurred. Please see the log for more details. - + + (64-bit) - + + (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + Closing software... - + Save Data - + Mod Data - + Error Opening %1 Folder - - + + Folder does not exist! - + Remove Installed Game Contents? - + Remove Installed Game Update? - + Remove Installed Game DLC? - + Remove Entry - + Delete OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? - + Remove Custom Game Configuration? - + Remove Cache Storage? - + Remove File - + Remove Play Time Data - + Reset play time? - - + + RomFS Extraction Failed! - + There was an error copying the RomFS files or the user cancelled the operation. - + Full - + Skeleton - + Select RomFS Dump Mode - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Extracting RomFS... - - + + Cancel - + RomFS Extraction Succeeded! - + The operation completed successfully. - + Error Opening %1 - + Select Directory Selecionar Diretório - + Properties - + The game properties could not be loaded. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Load File - + Open Extracted ROM Directory Abrir o Diretório da ROM extraída - + Invalid Directory Selected Diretório selecionado inválido - + The directory you have selected does not contain a 'main' file. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files - + %n file(s) remaining - + Installing file "%1"... - - + + Install Results - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + %n file(s) were newly installed - + %n file(s) were overwritten - + %n file(s) failed to install - + System Application - + System Archive - + System Application Update - + Firmware Package (Type A) - + Firmware Package (Type B) - + Game - + Game Update - + Game DLC - + Delta Title - + Select NCA Install Type... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Failed to Install - + The title type you selected for the NCA is invalid. - + File not found - + File "%1" not found - + OK - + Function Disabled - + Compatibility list reporting is currently disabled. Check back later! - + Error opening URL - + Unable to open the URL "%1". - + TAS Recording - + Overwrite file of player 1? - + Invalid config detected - + Handheld controller can't be used on docked mode. Pro controller will be selected. - - + + Amiibo - + Amiibo - - + + The current amiibo has been removed - + O Amiibo atual foi removido - + Error - - + + The current game is not looking for amiibos - + Amiibo File (%1);; All Files (*.*) - + Load Amiibo - + Error loading Amiibo data - + The selected file is not a valid amiibo - + The selected file is already on use - + An unknown error occurred - - - Keys not installed - - - - - - Install decryption keys and restart Eden before attempting to install firmware. - - - - - Select Dumped Firmware Source Location - - - - - Select Dumped Firmware ZIP - - - - - Zipped Archives (*.zip) - - - - - Firmware cleanup failed - - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available - + Firmware Corrupted - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot - + PNG Image (*.png) - + TAS state: Running %1/%2 - + TAS state: Recording %1 - + TAS state: Idle %1/%2 - + TAS State: Invalid - + &Stop Running - + Stop R&ecording - + Building: %n shader(s) - + Scale: %1x %1 is the resolution scaling factor - + Speed: %1% / %2% - + Speed: %1% - + Game: %1 FPS - + Frame: %1 ms - - - FSR - - - - + NO AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE - + VOLUME: %1% Volume percentage (e.g. 50%) - + Derivation Components Missing - + Decryption keys are missing. Install them now? - + Wayland Detected! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -8005,74 +8056,74 @@ Would you like to force it for future launches? - + Use X11 - + Continue with Wayland - + Don't show again - + Restart Required - + Restart Eden to apply the X11 backend. - + Slow - + Turbo - + Unlocked - + Select RomFS Dump Target - + Please select which RomFS you would like to dump. - + Are you sure you want to close Eden? - - - + + + Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8138,6 +8189,11 @@ Would you like to bypass this and exit anyway? ScaleForce + + + FSR + + Area @@ -8149,52 +8205,57 @@ Would you like to bypass this and exit anyway? - + + SGSR + + + + + SGSR EdgeDir + + + + Docked - + Handheld - + Fast - - Balanced - - - - + Accurate - + Vulkan - + OpenGL GLSL - + OpenGL SPIRV - + OpenGL GLASM - + Null @@ -8227,7 +8288,7 @@ If you wish to clean up the files which were left in the old data location, you - + Data was migrated successfully. @@ -8719,8 +8780,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8733,10 +8794,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8751,9 +8812,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8915,50 +8976,50 @@ p, li { white-space: pre-wrap; } [indefinido] - + %1%2 %1%2 - + [invalid] [inválido] - - + + %1%2Hat %3 %1%2Direcional %3 - - - + + + %1%2Axis %3 %1%2Eixo %3 - + %1%2Axis %3,%4,%5 %1%2Eixo %3,%4,%5 - + %1%2Motion %3 %1%2Movimentação %3 - - + + %1%2Button %3 %1%2Botão %3 - + [unused] [sem uso] @@ -9084,47 +9145,47 @@ p, li { white-space: pre-wrap; } %1 está jogando %2 - + Play Time: %1 - + Never Played - + Version: %1 - + Version: 1.0.0 - + Installed SD Titles Títulos SD instalados - + Installed NAND Titles Títulos NAND instalados - + System Titles Títulos do sistema - + Add New Game Directory Adicionar novo diretório de jogos - + Favorites Favoritos @@ -9245,13 +9306,13 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. @@ -9261,10 +9322,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9285,213 +9346,246 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9737,72 +9831,72 @@ Gostaria de selecionar manualmente uma pasta portátil para usar? - + Create Shortcut - + Do you want to launch the game in fullscreen? - + Shortcut Created - + Successfully created a shortcut to %1 - + Shortcut may be Volatile! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? - + Failed to Create Shortcut - + Failed to create a shortcut to %1 - + Create Icon - + Cannot create icon file. Path "%1" does not exist and cannot be created. - + No firmware available - + Please install firmware to use the home menu. - + Home Menu Applet - + Home Menu is not available. Please reinstall firmware. @@ -9810,55 +9904,55 @@ Gostaria de selecionar manualmente uma pasta portátil para usar? QtCommon::Mod - + Mod Name - + What should this mod be called? - + RomFS - + ExeFS/Patch - + Cheat - + Mod Type - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 Falha ao criar um diretório temporário %1 - + Zip file %1 is empty @@ -10554,65 +10648,65 @@ Ao selecionar "Do Eden", os dados salvos anteriores armazenados no Ryu - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/pt_PT.ts b/dist/languages/pt_PT.ts index 1e535c5e92..eb9b0c12a4 100644 --- a/dist/languages/pt_PT.ts +++ b/dist/languages/pt_PT.ts @@ -211,155 +211,6 @@ Isto banirá tanto o nome de usuário do fórum como o endereço IP.%1 - %2 (%3/%4 membros) - conectado - - CompatDB - - - Report Compatibility - Reportar Compatibilidade - - - - - - - - - - Report Game Compatibility - Reportar compatibilidade de jogos - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>O jogo inicializa?</p></body></html> - - - - Yes The game starts to output video or audio - Sim. O jogo começou por vídeo ou áudio. - - - - No The game doesn't get past the "Launching..." screen - Não. O Jogo não passou da tela de inicialização "Launching..." - - - - Yes The game gets past the intro/menu and into gameplay - Sim O Jogo passou da tela de menu/introdução e começou o gameplay - - - - No The game crashes or freezes while loading or using the menu - Não O jogo travou e/ou apresentou falhas graves durante o carregamento ou utilizando o menu - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>O jogo chega a gameplay?</p></body></html> - - - - Yes The game works without crashes - Sim O jogo funciona sem crashes - - - - No The game crashes or freezes during gameplay - Não O jogo crasha ou congela durante a gameplay - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>O jogo funciona sem crashar, congelar ou travar durante a gameplay?</p></body></html> - - - - Yes The game can be finished without any workarounds - Sim O jogo pode ser concluido sem o uso de soluções alternativas - - - - No The game can't progress past a certain area - Não Não é possível progredir no jogo a partir de uma certa área - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>O jogo é completamente jogável do início ao fim?</p></body></html> - - - - Major The game has major graphical errors - Grave O jogo tem grandes erros gráficos - - - - Minor The game has minor graphical errors - Pequenos O jogo tem pequenos erros gráficos - - - - None Everything is rendered as it looks on the Nintendo Switch - Nenhum Tudo é renderizado como no Nintendo Switch - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>O jogo tem alguma falha gráfica?</p></body></html> - - - - Major The game has major audio errors - Graves O jogo tem graves erros de áudio - - - - Minor The game has minor audio errors - Pequenos O jogo tem pequenos erros de audio - - - - None Audio is played perfectly - Nenhum O áudio é reproduzido perfeitamente - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>O jogo tem alguma falha no áudio / efeitos ausentes?</p></body></html> - - - - Thank you for your submission! - Obrigado pelo seu envio! - - - - Submitting - Entregando - - - - Communication error - Erro de comunicação - - - - An error occurred while sending the Testcase - Um erro ocorreu ao enviar o caso de teste - - - - Next - Próximo - - ConfigurationShared @@ -559,175 +410,185 @@ Can help reduce stuttering at lower framerates. - + Backend: Backend: - CPU Overclock + CPU Clocks - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. - + Custom CPU Ticks - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. - + Enable Host MMU Emulation (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. - + Unfuse FMA (improve performance on CPUs without FMA) FMA inseguro (Melhorar performance no CPU sem FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. Essa opção melhora a velocidade ao reduzir a precisão de instruções de fused-multiply-add em CPUs sem suporte nativo ao FMA. - + Faster FRSQRTE and FRECPE FRSQRTE e FRECPE mais rápido - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. Essa opção melhora a velocidade de algumas funções aproximadas de pontos flutuantes ao usar aproximações nativas precisas. - + Faster ASIMD instructions (32 bits only) Instruções ASIMD mais rápidas (apenas 32 bits) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. Essa opção melhora a velocidade de funções de pontos flutuantes de 32 bits ASIMD ao executá-las com modos de arredondamento incorretos. - + Inaccurate NaN handling Tratamento impreciso de NaN - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. Esta opção melhora a velocidade ao remover a checagem NaN. Por favor, note que isso também reduzirá a precisão de certas instruções de ponto flutuante. - + Disable address space checks Desativar a verificação do espaço de endereços - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. - + Ignore global monitor Ignorar monitor global - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. Esta opção melhora a velocidade ao depender apenas das semânticas do cmpxchg pra garantir a segurança das instruções de acesso exclusivo. Por favor, note que isso pode resultar em travamentos e outras condições de execução. - + API: API: - + Changes the output graphics API. Vulkan is recommended. - + Device: Dispositivo: - + This setting selects the GPU to use (Vulkan only). - + Resolution: Resolução: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. - + Window Adapting Filter: Filtro de adaptação de janela: - + FSR Sharpness: FSR Sharpness: - - Determines how sharpened the image will look using FSR's dynamic contrast. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. - + Anti-Aliasing Method: Método de Anti-Aliasing - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Tela Cheia - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. @@ -736,47 +597,47 @@ Sem borda oferece a melhor compatibilidade com o teclado na tela que alguns jogo Tela cheia exclusiva pode oferecer melhor performance e melhor suporte a Freesync/Gsync. - + Aspect Ratio: Proporção do Ecrã: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. - + Use persistent pipeline cache - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. Permite guardar os shaders para carregar os jogos nas execuções seguintes. Desabiltar essa opção só serve para propósitos de depuração. - + Use asynchronous GPU emulation - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + NVDEC emulation: Emulação NVDEC: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. @@ -785,12 +646,12 @@ Tanto a CPU quanto a GPU podem ser utilizadas para decodificação, ou não deco Na maioria dos casos, a decodificação pela GPU fornece uma melhor performance. - + ASTC Decoding Method: Método de Decodificação ASTC: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -799,55 +660,75 @@ stuttering but may present artifacts. - + ASTC Recompression Method: Método de Recompressão ASTC: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: Modo de Uso da VRAM: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. - + Skip CPU Inner Invalidation - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: Modo de Sincronização vertical: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -855,1362 +736,1372 @@ Immediate (no synchronization) presents whatever is available and can exhibit te - + Sync Memory Operations - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. - + Enable asynchronous presentation (Vulkan only) Ativar apresentação assíncrona (Somente Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. Melhora ligeiramente o desempenho ao mover a apresentação para uma thread de CPU separada. - + Force maximum clocks (Vulkan only) Forçar clock máximo (somente Vulkan) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. Executa trabalho em segundo plano aguardando pelos comandos gráficos para evitar a GPU de reduzir seu clock. - + Anisotropic Filtering: Filtro Anisotrópico: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. - + GPU Mode: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - + DMA Accuracy: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. - + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation - + May reduce shader stutter. - - Fast GPU Time + + GPU Clocks - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. - + GPU Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache Utilizar cache de pipeline do Vulkan - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. Habilita o cache de pipeline da fabricante da GPU. Esta opção pode melhorar o tempo de carregamento de shaders significantemente em casos onde o driver Vulkan não armazena o cache de pipeline internamente. - + Enable Compute Pipelines (Intel Vulkan Only) Habilitar Pipeline de Computação (Somente Intel Vulkan) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. - + Enable Reactive Flushing Habilitar Flushing Reativo - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. Usa flushing reativo ao invés de flushing preditivo, permitindo mais precisão na sincronização da memória. - + Sync to framerate of video playback Sincronizar com o framerate da reprodução de vídeo - + Run the game at normal speed during video playback, even when the framerate is unlocked. Executa o jogo na velocidade normal durante a reprodução de vídeo, mesmo se o framerate estiver desbloqueado. - + Barrier feedback loops Ciclos de feedback de barreira - + Improves rendering of transparency effects in specific games. Melhora a renderização de efeitos de transparência em jogos específicos. - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. - + Vertex Input Dynamic State - + Enables vertex input dynamic state feature for better quality and performance. - + Sample Shading - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. - + RNG Seed Semente de RNG - + Controls the seed of the random number generator. Mainly used for speedrunning. - + Device Name Nome do Dispositivo - + The name of the console. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: Data personalizada do RTC: - + This option allows to change the clock of the console. Can be used to manipulate time in games. - + The number of seconds from the current unix time - + Language: Idioma: - + This option can be overridden when region setting is auto-select - + Region: Região: - + The region of the console. - + Time Zone: Fuso Horário: - + The time zone of the console. - + Sound Output Mode: Modo de saída de som - + Console Mode: Modo Console: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. - + Prompt for user profile on boot - + Useful if multiple people use the same PC. - + Pause when not in focus - + Pauses emulation when focusing on other windows. - + Confirm before stopping emulation Confirmar antes de parar a emulação - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. - + Hide mouse on inactivity Esconder rato quando inactivo. - + Hides the mouse after 2.5s of inactivity. - - Disable controller applet - Desabilitar miniaplicativo de controle - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - - - - + Check for updates - + Whether or not to check for updates upon startup. - + Enable Gamemode Habilitar Gamemode - + Force X11 as Graphics Backend - + Custom frontend Frontend customizado - + Real applet Miniaplicativo real - + + + Disabled + + + + Never - + On Load - + Always - + CPU CPU - + GPU GPU - + CPU Asynchronous CPU Assíncrona - + Uncompressed (Best quality) Descompactado (Melhor Q - + BC1 (Low quality) BC1 (Baixa qualidade) - + BC3 (Medium quality) BC3 (Média qualidade) - - + + Auto Automático - + 30 FPS - + 60 FPS - + 90 FPS - + 120 FPS - + Conservative Conservador - + Aggressive Agressivo - + Vulkan Vulcano - + OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) - + Null Nenhum (desativado) - + Fast - - Balanced - - - - - + + + Accurate Preciso - - + + + Default Padrão - + Unsafe (fast) - + Safe (stable) - + + Immediate + + + + + Balanced + + + + Unsafe Inseguro - + Paranoid (disables most optimizations) Paranoia (desativa a maioria das otimizações) - + Debugging - + Dynarmic Dynarmic - + NCE NCE - + Borderless Windowed Janela sem bordas - + Exclusive Fullscreen Tela cheia exclusiva - + No Video Output Sem saída de vídeo - + CPU Video Decoding Decodificação de vídeo pela CPU - + GPU Video Decoding (Default) Decodificação de vídeo pela GPU (Padrão) - + 0.25X (180p/270p) [EXPERIMENTAL] - + 0.5X (360p/540p) [EXPERIMENTAL] 0.5X (360p/540p) [EXPERIMENTAL] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [EXPERIMENTAL] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [EXPERIMENTAL] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor Vizinho mais próximo - + Bilinear Bilinear - + Bicubic Bicúbico - + Gaussian Gaussiano - + Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution - + Area - + MMPX - + Zero-Tangent - + B-Spline - + Mitchell - + Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None Nenhum - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Padrão (16:9) - + Force 4:3 Forçar 4:3 - + Force 21:9 Forçar 21:9 - + Force 16:10 Forçar 16:10 - + Stretch to Window Esticar à Janela - + Automatic Automático - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - - - - - 64x - - - - + Japanese (日本語) Japonês (日本語) - + American English Inglês Americano - + French (français) Francês (français) - + German (Deutsch) Alemão (Deutsch) - + Italian (italiano) Italiano (italiano) - + Spanish (español) Espanhol (español) - + Chinese Chinês - + Korean (한국어) Coreano (한국어) - + Dutch (Nederlands) Holandês (Nederlands) - + Portuguese (português) Português (português) - + Russian (Русский) Russo (Русский) - + Taiwanese Taiwanês - + British English Inglês Britânico - + Canadian French Francês Canadense - + Latin American Spanish Espanhol Latino-Americano - + Simplified Chinese Chinês Simplificado - + Traditional Chinese (正體中文) Chinês Tradicional (正 體 中文) - + Brazilian Portuguese (português do Brasil) Português do Brasil (Brazilian Portuguese) - + Polish (polska) - + Thai (แบบไทย) - - + + Japan Japão - + USA EUA - + Europe Europa - + Australia Austrália - + China China - + Korea Coreia - + Taiwan Taiwan - + Auto (%1) Auto select time zone Auto (%1) - + Default (%1) Default time zone Padrão (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Cuba - + EET EET - + Egypt Egipto - + Eire Irlanda - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Irlanda - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hongkong - + HST HST - + Iceland Islândia - + Iran Irão - + Israel Israel - + Jamaica Jamaica - + Kwajalein Kwajalein - + Libya Líbia - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navajo - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Polónia - + Portugal Portugal - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Singapura - + Turkey Turquia - + UCT UCT - + Universal Universal - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulu - + Mono Mono - + Stereo Estéreo - + Surround Surround - + 4GB DRAM (Default) 4GB DRAM (Padrão) - + 6GB DRAM (Unsafe) 6GB DRAM (Não seguro) - + 8GB DRAM - + 10GB DRAM (Unsafe) - + 12GB DRAM (Unsafe) - + Docked Ancorado - + Handheld Portátil - - - Off + + + Normal - - Boost (1700MHz) + + + Boost - - Fast (2000MHz) + + + Overclock - + Always ask (Default) Sempre perguntar (Padrão) - + Only if game specifies not to stop Somente se o jogo especificar para não parar - + Never ask Nunca perguntar - - - Medium (256) - - - - - - High (512) - - - - + Very Small (16 MB) - + Small (32 MB) - + Normal (128 MB) - + Large (256 MB) - + Very Large (512 MB) - + Very Low (4 MB) - + Low (8 MB) - + Normal (16 MB) - + Medium (32 MB) - + High (64 MB) - + Very Low (32) - + Low (64) - + Normal (128) - - Disabled + + Medium (256) - + + High (512) + + + + ExtendedDynamicState 1 - + ExtendedDynamicState 2 - + ExtendedDynamicState 3 - + Tree View - + Grid View @@ -2562,252 +2453,297 @@ When a program attempts to open the controller applet, it is immediately closed. Filtro de registro global - - When checked, the max size of the log increases from 100 MB to 1 GB - Quando ativado, o tamanho máximo do registo aumenta de 100 MB para 1 GB + + GPU Logging/Level + - - Enable Extended Logging** - Ativar registros avançados** + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console Mostrar Relatório na Consola - + + When checked, the max size of the log increases from 100 MB to 1 GB + Quando ativado, o tamanho máximo do registo aumenta de 100 MB para 1 GB + + + + Enable Extended Logging** + Ativar registros avançados** + + + Open Log Location Abrir a localização do registro - + Homebrew Homebrew - + Arguments String Argumentos String - + Graphics Gráficos - + When checked, it executes shaders without loop logic changes Quando ativado, executa shaders sem mudanças de lógica de loop - + Disable Loop safety checks Desativar verificação de segurança de loops - + When checked, it will dump all the macro programs of the GPU Quando marcada, essa opção irá despejar todos os macro programas da GPU - + Dump Maxwell Macros Despejar macros Maxwell - + When checked, it enables Nsight Aftermath crash dumps Quando ativado, ativa a extração de registros de travamento do Nsight Aftermath - + Enable Nsight Aftermath Ativar Nsight Aftermath - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Se selecionado, descarrega todos os shaders originários do cache do disco ou do jogo conforme encontrá-los. + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + - - Dump Game Shaders - Descarregar shaders do jogo + + Dump Guest (Maxwell) Shaders + - + Enable Renderdoc Hotkey Habilitar atalho para Renderdoc - + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower Quando ativado, desativa o macro compilador Just In Time. Ativar isto faz os jogos rodarem mais lentamente. - + Disable Macro JIT Desactivar Macro JIT - + When checked, it disables the macro HLE functions. Enabling this makes games run slower Quando marcado, desabilita as funções do macro HLE. Habilitar esta opção faz com que os jogos rodem mais lentamente - + Disable Macro HLE Desabilitar o Macro HLE - + When checked, the graphics API enters a slower debugging mode Quando ativado, a API gráfica entra em um modo de depuração mais lento. - + Enable Graphics Debugging Activar Depuração Gráfica - + When checked, yuzu will log statistics about the compiled pipeline cache Quando ativado, o yuzu registrará estatísticas sobre o cache de pipeline compilado - + Enable Shader Feedback Ativar Feedback de Shaders - + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> <html><head/><body><p>Quando selecionado, desabilita a reordenação de uploads de memória mapeada, o que permite associar uploads com chamados específicos. Pode reduzir a performance em alguns casos.</p></body></html> - + Disable Buffer Reorder Desabilitar a Reordenação de Buffer - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced Avançado - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Permite que o yuzu procure por um ambiente Vulkan funcional quando o programa iniciar. Desabilite essa opção se estiver causando conflitos com programas externos visualizando o yuzu. - + Perform Startup Vulkan Check Executar checagem do Vulkan na inicialização - + Disable Web Applet Desativar Web Applet - + Enable All Controller Types Ativar todos os tipos de controles - + Enable Auto-Stub - + Kiosk (Quest) Mode Modo Quiosque (Quest) - + Use dev.keys - + Enable Debug Asserts Ativar asserções de depuração - + Debugging Depuração - + Battery Serial: - + + Debug Knobs: + + + + Bitmask for quick development toggles - + Set debug knobs (bitmask) - + 16-bit debug knob set for quick development toggles - + (bitmask) - - Debug Knobs: - - - - + Unit Serial: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. Habilite essa opção para gravar a última saída da lista de comandos de áudio para o console. Somente afetará jogos que utilizam o renderizador de áudio. - + Dump Audio Commands To Console** Despejar comandos de áudio no console** - + Flush log output on each line - + Enable FS Access Log Ativar acesso de registro FS - + Enable Verbose Reporting Services** Ativar serviços de relatório detalhado** - + Censor username in logs - + **This will be reset automatically when Eden closes. @@ -3257,33 +3193,38 @@ Would you like to delete the old save data? Cor de fundo: - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off Desligado - + VSync Off Sincronização vertical desligada - + Recommended Recomendado - + On Ligado - + VSync On Sincronização vertical ligada @@ -3804,6 +3745,16 @@ Would you like to delete the old save data? Motion / Touch Movimento / Toque + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4313,58 +4264,58 @@ Para inverter os eixos, mova o seu analógico primeiro verticalmente e depois ho [em espera] - + New Profile Novo Perfil - + Enter a profile name: Introduza um novo nome de perfil: - - + + Create Input Profile Criar perfil de controlo - + The given profile name is not valid! O nome de perfil dado não é válido! - + Failed to create the input profile "%1" Falha ao criar o perfil de controlo "%1" - + Delete Input Profile Apagar Perfil de Controlo - + Failed to delete the input profile "%1" Falha ao apagar o perfil de controlo "%1" - + Load Input Profile Carregar perfil de controlo - + Failed to load the input profile "%1" Falha ao carregar o perfil de controlo "%1" - + Save Input Profile Guardar perfil de controlo - + Failed to save the input profile "%1" Falha ao guardar o perfil de controlo "%1" @@ -4412,7 +4363,7 @@ Para inverter os eixos, mova o seu analógico primeiro verticalmente e depois ho - + Configure Configurar @@ -4443,7 +4394,7 @@ Para inverter os eixos, mova o seu analógico primeiro verticalmente e depois ho - + Test Testar @@ -4458,77 +4409,77 @@ Para inverter os eixos, mova o seu analógico primeiro verticalmente e depois ho Remover Servidor - + %1:%2 %1:%2 - - - - - - + + + + + + Eden - + Port number has invalid characters O número da porta tem caracteres inválidos - + Port has to be in range 0 and 65353 A porta tem que estar entre 0 e 65353 - + IP address is not valid O endereço IP não é válido - + This UDP server already exists Este servidor UDP já existe - + Unable to add more than 8 servers Não é possível adicionar mais de 8 servidores - + Testing Testando - + Configuring Configurando - + Test Successful Teste Bem-Sucedido - + Successfully received data from the server. Dados recebidos do servidor com êxito. - + Test Failed Teste Falhou - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Não foi possível receber dados válidos do servidor.<br>Por favor verifica que o servidor está configurado correctamente e o endereço e porta estão correctos. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. Teste UDP ou configuração de calibragem em progresso.<br> Por favor espera que termine. @@ -4713,57 +4664,57 @@ Os valores atuais são %1% e %2% respectivamente. Algumas configurações só estão disponíveis apenas quando não houver nenhum jogo em execução. - + Add-Ons Add-Ons - + System Sistema - + CPU CPU - + Graphics Gráficos - + Adv. Graphics Gráficos Avç. - + Ext. Graphics - + Audio Audio - + Input Profiles Perfis de controle - + Network - + Applets - + Properties Propriedades @@ -4886,6 +4837,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5453,91 +5528,86 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta - Show Compatibility List - Exibir Lista de Compatibilidade - - - Show Add-Ons Column Mostrar coluna de Add-Ons - + Show Size Column Exibir Coluna Tamanho - + Show File Types Column Exibir Coluna Tipos de Arquivos - + Show Play Time Column Exibir coluna Tempo jogado - + Folder Icon Size: Tamanho do ícone da pasta: - + Row 1 Text: Linha 1 Texto: - + Row 2 Text: Linha 2 Texto: - + Screenshots Captura de Ecrã - + Ask Where To Save Screenshots (Windows Only) Perguntar Onde Guardar Capturas de Ecrã (Apenas Windows) - + Screenshots Path: Caminho das Capturas de Ecrã: - + ... ... - + TextLabel TextLabel - + Resolution: Resolução: - + Select Screenshots Path... Seleccionar Caminho de Capturas de Ecrã... - + <System> <System> - + English Inglês - + Auto (%1 x %2, %3 x %4) Screenshot width value Auto (%1 x %2, %3 x %4) @@ -5774,7 +5844,7 @@ Arrasta os pontos para mudar a posição, ou dá duplo-clique nas células da ta - + Calculating... @@ -5976,50 +6046,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL não está disponível! - + OpenGL shared contexts are not supported. Shared contexts do OpenGL não são suportados. - + Eden has not been compiled with OpenGL support. - - - + + + Error while initializing OpenGL! Erro ao inicializar OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. O seu GPU pode não suportar OpenGL, ou não tem os drivers gráficos mais recentes. - + Error while initializing OpenGL 4.6! Erro ao inicializar o OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 O teu GPU pode não suportar OpenGL 4.6, ou não tem os drivers gráficos mais recentes. - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Sua GPU pode não suportar uma ou mais extensões necessárias do OpenGL. Verifique se você possui a última versão dos drivers gráficos.<br><br>Renderizador GL:<br>%1<br><br>Extensões não suportadas:<br>%2 - + This build doesn't have OpenGL support. @@ -6027,279 +6097,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory - + Favorite Favorito - + Start Game Iniciar jogo - + Start Game without Custom Configuration Iniciar jogo sem configuração personalizada - + Open Save Data Location Abrir Localização de Dados Salvos - + Open Mod Data Location Abrir a Localização de Dados do Mod - + Open Transferable Pipeline Cache Abrir cache de pipeline transferível - + Link to Ryujinx - + Remove Remover - + Remove Installed Update Remover Actualizações Instaladas - + Remove All Installed DLC Remover Todos os DLC Instalados - + Remove Custom Configuration Remover Configuração Personalizada - + Remove Cache Storage Remove a Cache do Armazenamento - + Remove OpenGL Pipeline Cache Remover cache de pipeline do OpenGL - + Remove Vulkan Pipeline Cache Remover cache de pipeline do Vulkan - + Remove All Pipeline Caches Remover todos os caches de pipeline - + Remove All Installed Contents Remover Todos os Conteúdos Instalados - + Manage Play Time - + Edit Play Time Data - + Remove Play Time Data Remover dados de tempo jogado - - + + Dump RomFS Despejar RomFS - + Dump RomFS to SDMC Extrair RomFS para SDMC - + Verify Integrity Verificar integridade - + Copy Title ID to Clipboard Copiar título de ID para a área de transferência - - Navigate to GameDB entry - Navegue para a Entrada da Base de Dados de Jogos - - - + Create Shortcut Criar Atalho - + Add to Desktop Adicionar à Área de Trabalho - + Add to Applications Menu Adicionar ao Menu de Aplicativos - + Configure Game - + Scan Subfolders Examinar Sub-pastas - + Remove Game Directory Remover diretório do Jogo - + ▲ Move Up ▲ Mover para Cima - + ▼ Move Down ▼ Mover para Baixo - + Open Directory Location Abrir Localização do diretório - + Clear Limpar - - - Name - Nome - - - - Compatibility - Compatibilidade - - - - Add-ons - Add-ons - - - - File type - Tipo de Arquivo - - - - Size - Tamanho - - - - Play time - Tempo jogado - GameListItemCompat - + Ingame Não Jogável - + Game starts, but crashes or major glitches prevent it from being completed. O jogo inicia, porém problemas ou grandes falhas impedem que ele seja concluído. - + Perfect Perfeito - + Game can be played without issues. O jogo pode ser jogado sem problemas. - + Playable Jogável - + Game functions with minor graphical or audio glitches and is playable from start to finish. O jogo funciona com pequenas falhas gráficas ou de áudio e pode ser reproduzido do início ao fim. - + Intro/Menu Introdução / Menu - + Game loads, but is unable to progress past the Start Screen. O jogo carrega, porém não consegue passar da tela inicial. - + Won't Boot Não Inicia - + The game crashes when attempting to startup. O jogo trava ao tentar iniciar. - + Not Tested Não Testado - + The game has not yet been tested. O jogo ainda não foi testado. + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list Clique duas vezes para adicionar uma nova pasta à lista de jogos @@ -6307,17 +6370,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) - + Filter: Filtro: - + Enter pattern to filter Digite o padrão para filtrar @@ -6812,1139 +6875,1112 @@ Debug Message: - + Game &Icon Size - + Reset Window Size to &720p Restaurar tamanho da janela para &720p - + Reset Window Size to 720p Restaurar tamanho da janela para 720p - + Reset Window Size to &900p Restaurar tamanho da janela para &900p - + Reset Window Size to 900p Restaurar tamanho da janela para 900p - + Reset Window Size to &1080p Restaurar tamanho da janela para &1080p - + Reset Window Size to 1080p Restaurar tamanho da janela para 1080p - + &Multiplayer &Multijogador - + &Tools &Ferramentas - + Am&iibo - + Launch &Applet - + &TAS &TAS - + &Create Home Menu Shortcut - + Install &Firmware - + &Help &Ajuda - + &Install Files to NAND... &Instalar arquivos na NAND... - + L&oad File... C&arregar arquivo... - + Load &Folder... Carregar &pasta... - + E&xit &Sair - - + + &Pause &Pausa - + &Stop &Parar - + &Verify Installed Contents &Verificar conteúdo instalado - + &About Eden - + Single &Window Mode Modo de &janela única - + Con&figure... Con&figurar... - + Ctrl+, - + Enable Overlay Display Applet - + Show &Filter Bar Mostrar Barra de &Filtros - + Show &Status Bar Mostrar Barra de &Estado - + Show Status Bar Mostrar Barra de Estado - + &Browse Public Game Lobby &Navegar no Lobby de Salas Públicas - + &Create Room &Criar Sala - + &Leave Room &Sair da Sala - + &Direct Connect to Room Conectar &Diretamente Numa Sala - + &Show Current Room Exibir &Sala Atual - + F&ullscreen T&ela cheia - + &Restart &Reiniciar - + Load/Remove &Amiibo... Carregar/Remover &Amiibo... - - &Report Compatibility - &Reportar compatibilidade - - - + Open &Mods Page Abrir Página de &Mods - - Open &Quickstart Guide - Abrir &guia de início rápido + + &User Handbook + - - &FAQ - &Perguntas frequentes - - - + &Capture Screenshot &Captura de Tela - + &Album - + &Set Nickname and Owner &Definir apelido e proprietário - + &Delete Game Data &Remover dados do jogo - + &Restore Amiibo &Recuperar Amiibo - + &Format Amiibo &Formatar Amiibo - + &Mii Editor - + &Configure TAS... &Configurar TAS - + Configure C&urrent Game... Configurar jogo atual... - - + + &Start &Começar - + &Reset &Restaurar - - + + R&ecord G&ravar - + Open &Controller Menu Menu Abrir &Controles - + Install Decryption &Keys - + &Home Menu - + &Desktop - + &Application Menu - + &Root Data Folder - + &NAND Folder - + &SDMC Folder - + &Mod Folder - + &Log Folder - + From Folder - + From ZIP - + &Eden Dependencies - + &Data Manager - + &Tree View - + &Grid View - + Game Icon Size - - + + None - + Show Game &Name - + Show &Performance Overlay - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) - + Standard (64x64) - + Large (128x128) - + Full Size (256x256) - + Broken Vulkan Installation Detected - + Vulkan initialization failed during boot. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping - + Loading Web Applet... - - + + Disable Web Applet - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + The amount of shaders currently being built - + The current selected resolution scaling multiplier. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Unmute - + Mute - + Reset Volume - + + Configure Effects... + + + + &Clear Recent Files - + &Continue - + Warning: Outdated Game Format - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - - + + Error while loading ROM! - + The ROM format is not supported. - + An error occurred initializing the video core. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + An unknown error occurred. Please see the log for more details. - + + (64-bit) - + + (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + Closing software... - + Save Data - + Mod Data - + Error Opening %1 Folder - - + + Folder does not exist! - + Remove Installed Game Contents? - + Remove Installed Game Update? - + Remove Installed Game DLC? - + Remove Entry - + Delete OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? - + Remove Custom Game Configuration? - + Remove Cache Storage? - + Remove File - + Remove Play Time Data - + Reset play time? - - + + RomFS Extraction Failed! - + There was an error copying the RomFS files or the user cancelled the operation. - + Full - + Skeleton - + Select RomFS Dump Mode - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Extracting RomFS... - - + + Cancel - + RomFS Extraction Succeeded! - + The operation completed successfully. - + Error Opening %1 - + Select Directory - + Properties - + The game properties could not be loaded. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Load File - + Open Extracted ROM Directory - + Invalid Directory Selected - + The directory you have selected does not contain a 'main' file. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files - + %n file(s) remaining - + Installing file "%1"... - - + + Install Results - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + %n file(s) were newly installed - + %n file(s) were overwritten - + %n file(s) failed to install - + System Application - + System Archive - + System Application Update - + Firmware Package (Type A) - + Firmware Package (Type B) - + Game - + Game Update - + Game DLC - + Delta Title - + Select NCA Install Type... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Failed to Install - + The title type you selected for the NCA is invalid. - + File not found - + File "%1" not found - + OK - + Function Disabled - + Compatibility list reporting is currently disabled. Check back later! - + Error opening URL - + Unable to open the URL "%1". - + TAS Recording - + Overwrite file of player 1? - + Invalid config detected - + Handheld controller can't be used on docked mode. Pro controller will be selected. - - + + Amiibo - - + + The current amiibo has been removed - + Error - - + + The current game is not looking for amiibos - + Amiibo File (%1);; All Files (*.*) - + Load Amiibo - + Error loading Amiibo data - + The selected file is not a valid amiibo - + The selected file is already on use - + An unknown error occurred - - - Keys not installed - - - - - - Install decryption keys and restart Eden before attempting to install firmware. - - - - - Select Dumped Firmware Source Location - - - - - Select Dumped Firmware ZIP - - - - - Zipped Archives (*.zip) - - - - - Firmware cleanup failed - - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available - + Firmware Corrupted - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot - + PNG Image (*.png) - + TAS state: Running %1/%2 - + TAS state: Recording %1 - + TAS state: Idle %1/%2 - + TAS State: Invalid - + &Stop Running - + Stop R&ecording - + Building: %n shader(s) - + Scale: %1x %1 is the resolution scaling factor - + Speed: %1% / %2% - + Speed: %1% - + Game: %1 FPS - + Frame: %1 ms - - - FSR - - - - + NO AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE - + VOLUME: %1% Volume percentage (e.g. 50%) - + Derivation Components Missing - + Decryption keys are missing. Install them now? - + Wayland Detected! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -7952,74 +7988,74 @@ Would you like to force it for future launches? - + Use X11 - + Continue with Wayland - + Don't show again - + Restart Required - + Restart Eden to apply the X11 backend. - + Slow - + Turbo - + Unlocked - + Select RomFS Dump Target - + Please select which RomFS you would like to dump. - + Are you sure you want to close Eden? - - - + + + Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8085,6 +8121,11 @@ Would you like to bypass this and exit anyway? ScaleForce + + + FSR + + Area @@ -8096,52 +8137,57 @@ Would you like to bypass this and exit anyway? - + + SGSR + + + + + SGSR EdgeDir + + + + Docked - + Handheld - + Fast - - Balanced - - - - + Accurate - + Vulkan - + OpenGL GLSL - + OpenGL SPIRV - + OpenGL GLASM - + Null @@ -8174,7 +8220,7 @@ If you wish to clean up the files which were left in the old data location, you - + Data was migrated successfully. @@ -8664,8 +8710,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8678,10 +8724,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8696,9 +8742,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8860,50 +8906,50 @@ p, li { white-space: pre-wrap; } [indefinido] - + %1%2 %1%2 - + [invalid] [inválido] - - + + %1%2Hat %3 %1%2Direcional %3 - - - + + + %1%2Axis %3 %1%2Eixo %3 - + %1%2Axis %3,%4,%5 %1%2Eixo %3,%4,%5 - + %1%2Motion %3 %1%2Movimentação %3 - - + + %1%2Button %3 %1%2Botão %3 - + [unused] [sem uso] @@ -9029,47 +9075,47 @@ p, li { white-space: pre-wrap; } %1 está jogando %2 - + Play Time: %1 - + Never Played - + Version: %1 - + Version: 1.0.0 - + Installed SD Titles Títulos SD instalados - + Installed NAND Titles Títulos NAND instalados - + System Titles Títulos do sistema - + Add New Game Directory Adicionar novo diretório de jogos - + Favorites Favoritos @@ -9190,13 +9236,13 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. @@ -9206,10 +9252,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9230,213 +9276,246 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9680,72 +9759,72 @@ Would you like to manually select a portable folder to use? - + Create Shortcut - + Do you want to launch the game in fullscreen? - + Shortcut Created - + Successfully created a shortcut to %1 - + Shortcut may be Volatile! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? - + Failed to Create Shortcut - + Failed to create a shortcut to %1 - + Create Icon - + Cannot create icon file. Path "%1" does not exist and cannot be created. - + No firmware available - + Please install firmware to use the home menu. - + Home Menu Applet - + Home Menu is not available. Please reinstall firmware. @@ -9753,55 +9832,55 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name - + What should this mod be called? - + RomFS - + ExeFS/Patch - + Cheat - + Mod Type - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty @@ -10495,65 +10574,65 @@ By selecting "From Eden", previous save data stored in Ryujinx will be - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/ru_RU.ts b/dist/languages/ru_RU.ts index 063804860b..bb44335970 100644 --- a/dist/languages/ru_RU.ts +++ b/dist/languages/ru_RU.ts @@ -218,155 +218,6 @@ This would ban both their forum username and their IP address. %1 - %2 (%3/%4 участников) - подключено - - CompatDB - - - Report Compatibility - Сообщить о совместимости - - - - - - - - - - Report Game Compatibility - Сообщить о совместимости игры - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - <html><head/><body><p><span style=" font-size:10pt;">Если вы захотите отправить отчет в </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">список совместимости eden</a><span style=" font-size:10pt;">, следующая информация будет собрана и отображена на сайте:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Информация о железе (ЦП / ГП / Операционная система)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Версия eden</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Подключенный аккаунт Eden</li></ul></body></html> - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>Запускается ли игра?</p></body></html> - - - - Yes The game starts to output video or audio - Да Игра начинает выводить видео или аудио - - - - No The game doesn't get past the "Launching..." screen - Нет Игра не проходит дальше экрана "Запуск..." - - - - Yes The game gets past the intro/menu and into gameplay - Да Игра переходит от вступления/меню к геймплею - - - - No The game crashes or freezes while loading or using the menu - Нет Игра вылетает или зависает при загрузке или использовании меню - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>Дотягивает ли игра до геймплея?</p></body></html> - - - - Yes The game works without crashes - Да Игра работает без вылетов - - - - No The game crashes or freezes during gameplay - Нет Игра крашится или зависает во время геймплея - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>Работает ли игра без вылетов, фризов или полного зависания во время игрового процесса?</p></body></html> - - - - Yes The game can be finished without any workarounds - Да Игра может быть завершена без каких-либо обходных путей - - - - No The game can't progress past a certain area - Нет Игру невозможно пройти дальше определенной области - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>Возможно ли игру пройти полностью от начала до конца?</p></body></html> - - - - Major The game has major graphical errors - Серьезные В игре есть серьезные проблемы с графикой - - - - Minor The game has minor graphical errors - Небольшие В игре есть небольшие проблемы с графикой - - - - None Everything is rendered as it looks on the Nintendo Switch - Никаких Все выглядит так, как и на Nintendo Switch - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>Есть ли в игре проблемы с графикой?</p></body></html> - - - - Major The game has major audio errors - Серьезные В игре есть серьезные проблемы со звуком - - - - Minor The game has minor audio errors - Небольшие В игре есть небольшие проблемы со звуком - - - - None Audio is played perfectly - Никаких Звук воспроизводится идеально - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>Есть ли в игре какие-либо проблемы со звуком / отсутствующие эффекты?</p></body></html> - - - - Thank you for your submission! - Спасибо за ваш отчет! - - - - Submitting - Отправка - - - - Communication error - Ошибка соединения - - - - An error occurred while sending the Testcase - Произошла ошибка при отправке отчета - - - - Next - Далее - - ConfigurationShared @@ -571,39 +422,39 @@ Can help reduce stuttering at lower framerates. - + Backend: Бэкэнд: - CPU Overclock - Разгон CPU + CPU Clocks + Частоты CPU - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. - Разгоняет эмулируемый CPU для снятия некоторых ограничителей FPS. На более слабых процессорах это может снизить производительность, а некоторые игры могут работать некорректно. -Используйте режим Boost (1700 МГц), чтобы запускать игры на максимальной штатной частоте Switch, или режим Fast (2000 МГц), чтобы работать на удвоенной частоте. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + Увеличивает частоту, которую сообщает эмулируемый CPU, из-за чего могут быть убраны некоторые ограничители FPS. +Слабые процессоры могут потерять производительность, а некоторые игры могут работать неправильно. - + Custom CPU Ticks Пользовательские тики CPU - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. Задает пользовательское значение тиков CPU. Более высокие значения могут повысить производительность, но могут вызвать взаимные блокировки. Рекомендуемый диапазон: 77–21000. - + Enable Host MMU Emulation (fastmem) Включить эмуляцию MMU хоста (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. @@ -612,98 +463,98 @@ Disabling this forces all memory accesses to use Software MMU Emulation. - + Unfuse FMA (improve performance on CPUs without FMA) Отключить FMA (улучшает производительность на ЦП без FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. Этот вариант улучшает скорость путем снижения точности инструкций слияния-умножения-сложения на процессорах без поддержки нативной FMA. - + Faster FRSQRTE and FRECPE Ускоренные FRSQRTE и FRECPE - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. Этот вариант улучшает скорость некоторых приближенных функций с плавающей запятой за счет использования менее точных встроенных приближений. - + Faster ASIMD instructions (32 bits only) Ускоренные инструкции ASIMD (только 32 бит) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. Этот вариант улучшает скорость 32-битных функций с плавающей запятой ASIMD путем выполнения с неправильными режимами округления. - + Inaccurate NaN handling Неточная обработка NaN - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. Этот вариант улучшает скорость отключая проверки на NaN. Обратите внимание, что это также снижает точность некоторых операций с плавающей запятой. - + Disable address space checks Отключить проверку адресного пространства - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. Эта опция повышает скорость за счет исключения проверки безопасности перед каждой операцией с памятью. Отключение может привести к выполнению произвольного кода. - + Ignore global monitor Игнорировать глобальный мониторинг - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. Эта опция повышает скорость, полагаясь только на семантику cmpxchg для обеспечения безопасности инструкций исключительного доступа. Обратите внимание, что это может привести к дедлокам и race condition. - + API: API: - + Changes the output graphics API. Vulkan is recommended. Изменяет графический API вывода. Рекомендуется использовать Vulkan. - + Device: Устройство: - + This setting selects the GPU to use (Vulkan only). Этот параметр определяет используемый GPU (только Vulkan). - + Resolution: Разрешение: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. @@ -712,27 +563,27 @@ Options lower than 1X can cause artifacts. Опции ниже 1X могут вызывать артефакты. - + Window Adapting Filter: Фильтр масштабирования окна: - + FSR Sharpness: Резкость FSR: - - Determines how sharpened the image will look using FSR's dynamic contrast. - Определяет степень резкости изображения при использовании динамического контраста FSR. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. + Определяет степень резкости изображения при использовании динамического контраста FSR или SGSR. - + Anti-Aliasing Method: Метод сглаживания: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. @@ -740,13 +591,23 @@ FXAA can produce a more stable picture in lower resolutions. SMAA обеспечивает лучшее качество. FXAA может давать более стабильное изображение на низком разрешении. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Полноэкранный режим: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. @@ -755,12 +616,12 @@ Exclusive fullscreen may offer better performance and better Freesync/Gsync supp Эксклюзивный полноэкранный режим может иметь лучшую производительность и лучшую поддержку Freesync/G-Sync. - + Aspect Ratio: Соотношение сторон: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. @@ -769,35 +630,35 @@ Also controls the aspect ratio of captured screenshots. Также управляет соотношением сторон скриншотов. - + Use persistent pipeline cache Использовать постоянный кэш конвейера - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. Позволяет сохранять шейдеры на диск для более быстрой загрузки при последующем запуске игры. Отключение этой функции предназначено только для отладки. - + Use asynchronous GPU emulation Использовать асинхронную эмуляцию GPU - + Uses an extra CPU thread for rendering. This option should always remain enabled. Использует дополнительный поток CPU для рендеринга. Эта опция всегда должна оставаться включенной. - + NVDEC emulation: Эмуляция NVDEC: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. @@ -806,12 +667,12 @@ In most cases, GPU decoding provides the best performance. В большинстве случаев декодирование с использованием ГП обеспечивает лучшую производительность. - + ASTC Decoding Method: Метод декодирования ASTC: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -823,12 +684,12 @@ GPU: использовать вычислительные шейдеры ГП Асинхронный CPU: использовать ЦП для декодирования текстур ASTC по мере необходимости. Устраняет фризы при декодировании ASTC, но может вызывать артефакты. - + ASTC Recompression Method: Метод пересжатия ASTC: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. @@ -836,44 +697,64 @@ BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, BC1/BC3: промежуточный формат будет перекодирован в BC1 или BC3, экономя видеопамять, но снижая качество изображения. - + Frame Pacing Mode (Vulkan only) Режим стабилизации кадров (только Vulkan) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. Определяет способ управления выводом кадров для уменьшения подергиваний и повышения плавности и стабильности частоты кадров. - + VRAM Usage Mode: Режим VRAM: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. Определяет, должен ли эмулятор экономить память или использовать всю доступную видеопамять для повышения производительности. Агрессивный режим может негативно сказаться на производительности других приложений, например программ записи. - + Skip CPU Inner Invalidation Пропуск внутренней инвалидизации CPU - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. Пропускает некоторые операции сброса кэша при обновлении памяти, снижая нагрузку на ЦП и уменьшая задержку. Может вызывать сбои в работе игр. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: Режим VSync: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -884,12 +765,12 @@ Mailbox может обеспечивать меньшую задержку, ч Immediate (без синхронизации) выводит все доступные кадры и может иметь разрывы. - + Sync Memory Operations Синхронизация операций памяти - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. @@ -898,102 +779,116 @@ Unreal Engine 4 games often see the most significant changes thereof. Игры на Unreal Engine 4 чаще всего демонстрируют заметные изменения. - + Enable asynchronous presentation (Vulkan only) Включить асинхронный вывод (только Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. Немного повышает производительность, перенося вывод кадра в отдельный поток CPU. - + Force maximum clocks (Vulkan only) Принудительно зафиксировать максимальную тактовую частоту (только для Vulkan) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. Выполняет фоновые операции во время ожидания графических команд, предотвращая снижение тактовой частоты GPU. - + Anisotropic Filtering: Анизотропная фильтрация: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. Управляет качеством отображения текстур под наклонными углами. На большинстве GPU безопасно использовать значение 16x. - + GPU Mode: Режим GPU: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - Управляет режимами эмуляции GPU. -Большинство игр работают без проблем в режимах «Производительность» или «Баланс», но для некоторых нужна «Точность». -Эффекты частиц обычно корректны только в режиме «Точность». + Управляет режимом эмуляции ГПУ. Большинство игр нормально отображаются в режиме Быстрый, но для некоторых требуется режим Точный. Частицы обычно корректно отображаются только в режиме Точный. - + DMA Accuracy: Точность DMA: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. - Управляет точностью DMA. Безопасный режим исправляет ошибки в некоторых играх, но может снижать производительность. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. + Управляет режимом чтения DMA. +Небезопасный быстрее, а Безопасный стабильнее и может исправить проблемы в некоторых играх. +По умолчанию соответствует настройке точности ГПУ. - + + GPU Fence Behavior: + Поведение барьеров ГПУ: + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation Включить асинхронную компиляцию шейдеров - + May reduce shader stutter. Может уменьшить фризы, вызванные компиляцией шейдеров. - - Fast GPU Time - Fast GPU Time + + GPU Clocks + Частоты GPU - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. - Разгоняет эмулируемый GPU для увеличения динамического разрешения и дальности прорисовки. -Используйте значение 256 для максимальной производительности и 512 для максимального качества графики. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. + Заставляет игру думать, что GPU завершает свою работу быстрее, чем на самом деле, чтобы она перестала подстраиваться под частоты Switch, понижать разрешение и дальность прорисовки. - + GPU Unswizzle GPU-десвиззлинг (GPU Unswizzle) - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. Ускоряет декодирование 3D-текстур BCn с использованием вычислений GPU. Отключите при возникновении сбоев или графических артефактов. - + GPU Unswizzle Max Texture Size Максимальный размер текстур для GPU-десвиззлинга - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. @@ -1002,48 +897,48 @@ Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size Размер потока GPU-десвиззлинга - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. Задает максимальный объем данных текстур (в МиБ), обрабатываемый за кадр. Более высокие значения могут уменьшить подергивания при загрузке текстур, но это может повлиять на стабильность кадров. - + GPU Unswizzle Chunk Size Размер блока GPU-десвиззлинга - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. Определяет количество слоев глубины, обрабатываемых за один вызов. Увеличение этого значения может повысить пропускную способность на производительных GPU, но на слабом оборудовании способно вызвать TDR или тайм-ауты драйвера. - + Use Vulkan pipeline cache Использовать кэш конвейеров Vulkan - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. Включает кэш конвейеров, зависящий от производителя GPU. Эта опция может значительно ускорить загрузку шейдеров в тех случаях, когда драйвер Vulkan не сохраняет такие файлы кэша внутри себя. - + Enable Compute Pipelines (Intel Vulkan Only) Включить вычислительные конвейеры (только для Intel Vulkan) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. @@ -1052,184 +947,182 @@ Compute pipelines are always enabled on all other drivers. На остальных драйверах вычислительные конвейеры всегда включены. - + Enable Reactive Flushing Включить реактивный сброс - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. Использует реактивный сброс (Reactive Flushing) вместо прогнозирующего, обеспечивая более точную синхронизацию памяти. - + Sync to framerate of video playback Синхронизировать с частотой кадров видео - + Run the game at normal speed during video playback, even when the framerate is unlocked. Поддерживать нормальную скорость игры во время воспроизведения видео, даже если частота кадров разблокирована. - + Barrier feedback loops Циклы обратной связи барьеров - + Improves rendering of transparency effects in specific games. Улучшает отрисовку эффектов прозрачности в некоторых играх. - + Enable buffer history Включить историю буфера - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. Предоставляет доступ к предыдущим состояниям буфера. В некоторых играх может повысить качество рендеринга и стабильность производительности. - + Fix bloom effects Исправить эффекты bloom - + Removes bloom in Burnout. Удаляет эффект bloom в игре Burnout. - - Enable Legacy Rescale Pass - Включить устаревший режим масштабирования - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - Может исправить проблемы масштабирования в некоторых играх, используя поведение предыдущей реализации. -Это обходное решение устраняет артефакты линий на GPU AMD и Intel, а также мерцание серых текстур на GPU Nvidia в Luigis Mansion 3. - - - + Extended Dynamic State Расширенное динамическое состояние (Extended Dynamic State) - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. Определяет количество функций, доступных в Extended Dynamic State. Более высокие значения позволяют использовать больше функций и иногда повышают производительность, но могут вызвать дополнительные графические проблемы. - + Vertex Input Dynamic State Динамическое состояние ввода вершин (Vertex Input Dynamic State) - + Enables vertex input dynamic state feature for better quality and performance. Включает функцию динамического состояния ввода вершин для улучшения качества и производительности. - + Sample Shading Множественное затенение (Sample Shading) - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. Позволяет фрагментному шейдеру выполняться для каждого сэмпла в мультисемплированном фрагменте вместо одного раза. Повышает качество графики за счет снижения производительности. Более высокие значения повышают качество, но сильнее снижают производительность. - + RNG Seed Сид RNG - + Controls the seed of the random number generator. Mainly used for speedrunning. Задает начальное значение генератора случайных чисел. В основном используется для спидранов. - + Device Name Название устройства - + The name of the console. Имя консоли. - + + Homebrew Args + Аргументы Homebrew + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + Аргументы командной строки при запуске homebrew (например, -noglsl). + + + Custom RTC Date: Пользовательская RTC-дата: - + This option allows to change the clock of the console. Can be used to manipulate time in games. Позволяет изменить системные часы консоли. Может использоваться для изменения времени в играх. - + The number of seconds from the current unix time Количество секунд от текущего UNIX-времени. - + Language: Язык: - + This option can be overridden when region setting is auto-select Эта настройка может быть переопределена при автоматическом выборе региона. - + Region: Регион: - + The region of the console. Регион консоли. - + Time Zone: Часовой пояс: - + The time zone of the console. Часовой пояс консоли. - + Sound Output Mode: Режим вывода звука: - + Console Mode: Консольный режим: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. @@ -1238,1031 +1131,1026 @@ Setting to Handheld can help improve performance for low end systems. Установка в портативный режим может повысить производительность на слабых системах. - + Prompt for user profile on boot Запрашивать профиль пользователя при запуске игры - + Useful if multiple people use the same PC. Полезно, если одним ПК пользуются несколько человек. - + Pause when not in focus Приостанавливать, если окно не активно - + Pauses emulation when focusing on other windows. Приостанавливает эмуляцию при переключении на другие окна. - + Confirm before stopping emulation Запрашивать подтверждение перед остановкой эмуляции - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. Убирает запросы на подтверждение остановки эмуляции. При включении этой настройки эмуляция завершается сразу, без подтверждения. - + Hide mouse on inactivity Скрывать курсор при бездействии - + Hides the mouse after 2.5s of inactivity. Скрывает курсор мыши после 2,5 секунд бездействия. - - Disable controller applet - Отключить апплет контроллера - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - Принудительно отключает использование апплета контроллера в эмулируемых программах. -При попытке открыть апплет он сразу закрывается. - - - + Check for updates Проверять обновления - + Whether or not to check for updates upon startup. Проверять обновления при запуске. - + Enable Gamemode Включить режим игры - + Force X11 as Graphics Backend Принудительно использовать X11 как графический бэкенд - + Custom frontend Собственный интерфейс эмулятора - + Real applet Приложение из прошивки - + + + Disabled + Отключено + + + Never Никогда - + On Load При загрузке - + Always Всегда - + CPU CPU - + GPU GPU - + CPU Asynchronous Асинхронный CPU - + Uncompressed (Best quality) Без сжатия (наилучшее качество) - + BC1 (Low quality) BC1 (низкое качество) - + BC3 (Medium quality) BC3 (среднее качество) - - + + Auto Авто - + 30 FPS 30 FPS - + 60 FPS 60 FPS - + 90 FPS 90 FPS - + 120 FPS 120 FPS - + Conservative Консервативный - + Aggressive Агрессивный - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) OpenGL GLASM (ассемблерные шейдеры, только для NVIDIA) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) OpenGL SPIR-V (экспериментальный, только для AMD/Mesa) - + Null Null - + Fast Производительность - - Balanced - Баланс - - - - + + + Accurate Точность - - + + + Default По умолчанию - + Unsafe (fast) Небезопасно (быстро) - + Safe (stable) Безопасно (стабильно) - + + Immediate + Мгновенный + + + + Balanced + Баланс + + + Unsafe Небезопасно - + Paranoid (disables most optimizations) Параноик (отключает большинство оптимизаций) - + Debugging Отладка - + Dynarmic Dynarmic - + NCE NCE - + Borderless Windowed Безрамочный режим - + Exclusive Fullscreen Эксклюзивный полноэкранный - + No Video Output Отсутствие видеовыхода - + CPU Video Decoding Декодирование видео на CPU - + GPU Video Decoding (Default) Декодирование видео на GPU (По умолчанию) - + 0.25X (180p/270p) [EXPERIMENTAL] 0.25X (180p/270p) [Экспериментально] - + 0.5X (360p/540p) [EXPERIMENTAL] 0.5X (360p/540p) [Экспериментально] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [Экспериментально] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] 1.25X (900p/1350p) [ЭКПЕРИМЕНТАЛЬНО] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [Экспериментально] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor Ближайший сосед (Nearest Neighbor) - + Bilinear Билинейный - + Bicubic Бикубический - + Gaussian По Гауссу - + Lanczos Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution AMD FidelityFX™️ Super Resolution - + Area Область (Area) - + MMPX MMPX - + Zero-Tangent Zero-Tangent - + B-Spline B-Spline - + Mitchell Mitchell - + Spline-1 Spline-1 - - + + Snapdragon Game Super Resolution + Snapdragon Game Super Resolution + + + + Snapdragon Game Super Resolution EdgeDir + Snapdragon Game Super Resolution EdgeDir + + + None Никакой - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Стандартное (16:9) - + Force 4:3 Принудительно 4:3 - + Force 21:9 Принудительно 21:9 - + Force 16:10 Принудительно 16:10 - + Stretch to Window Растянуть до окна - + Automatic Автоматически - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - 32x - - - - 64x - 64x - - - + Japanese (日本語) Японский (日本語) - + American English Американский английский - + French (français) Французский (français) - + German (Deutsch) Немецкий (Deutsch) - + Italian (italiano) Итальянский (italiano) - + Spanish (español) Испанский (español) - + Chinese Китайский - + Korean (한국어) Корейский (한국어) - + Dutch (Nederlands) Голландский (Nederlands) - + Portuguese (português) Португальский (português) - + Russian (Русский) Русский - + Taiwanese Тайваньский - + British English Британский английский - + Canadian French Канадский французский - + Latin American Spanish Латиноамериканский испанский - + Simplified Chinese Упрощённый китайский - + Traditional Chinese (正體中文) Традиционный китайский (正體中文) - + Brazilian Portuguese (português do Brasil) Бразильский португальский (português do Brasil) - + Polish (polska) Польский (polska) - + Thai (แบบไทย) Тайский (แบบไทย) - - + + Japan Япония - + USA США - + Europe Европа - + Australia Австралия - + China Китай - + Korea Корея - + Taiwan Тайвань - + Auto (%1) Auto select time zone Авто (%1) - + Default (%1) Default time zone По умолчанию (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Куба - + EET EET - + Egypt Египет - + Eire Эйре - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Эйре - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Гринвич - + Hongkong Гонконг - + HST HST - + Iceland Исландия - + Iran Иран - + Israel Израиль - + Jamaica Ямайка - + Kwajalein Кваджалейн - + Libya Ливия - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Навахо - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Польша - + Portugal Португалия - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Сингапур - + Turkey Турция - + UCT UCT - + Universal Универсальный - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Зулу - + Mono Моно - + Stereo Стерео - + Surround Объемный звук - + 4GB DRAM (Default) 4 ГБ ОЗУ (По умолчанию) - + 6GB DRAM (Unsafe) 6 ГБ ОЗУ (Небезопасно) - + 8GB DRAM 8 ГБ ОЗУ - + 10GB DRAM (Unsafe) 10 ГБ ОЗУ (Небезопасно) - + 12GB DRAM (Unsafe) 12 ГБ ОЗУ (Небезопасно) - + Docked В док-станции - + Handheld Портативный - - - Off - Отключено + + + Normal + Нормальная - - Boost (1700MHz) - Boost (1700MHz) + + + Boost + Ускоренная - - Fast (2000MHz) - Fast (2000MHz) + + + Overclock + Разгон - + Always ask (Default) Всегда спрашивать (По умолчанию) - + Only if game specifies not to stop Только если игра указывает не останавливать - + Never ask Никогда не спрашивать - - - Medium (256) - Средний (256) - - - - - High (512) - Высокий (512) - - - + Very Small (16 MB) Очень маленький (16 МБ) - + Small (32 MB) Маленький (32 МБ) - + Normal (128 MB) Нормальный (128 МБ) - + Large (256 MB) Большой (256 МБ) - + Very Large (512 MB) Очень большой (512 МБ) - + Very Low (4 MB) Очень низкий (4 МБ) - + Low (8 MB) Низкий (8 МБ) - + Normal (16 MB) Нормальный (16 МБ) - + Medium (32 MB) Средний (32 МБ) - + High (64 MB) Высокий (64 МБ) - + Very Low (32) Очень низкий (32) - + Low (64) Низкий (64) - + Normal (128) Нормальный (128) - - Disabled - Отключено + + Medium (256) + Средний (256) - + + High (512) + Высокий (512) + + + ExtendedDynamicState 1 ExtendedDynamicState 1 - + ExtendedDynamicState 2 ExtendedDynamicState 2 - + ExtendedDynamicState 3 ExtendedDynamicState 3 - + Tree View Древовидный - + Grid View Сетчатый @@ -2622,252 +2510,297 @@ When a program attempts to open the controller applet, it is immediately closed. Глобальный фильтр журналов - - When checked, the max size of the log increases from 100 MB to 1 GB - Если включено, максимальный размер журнала увеличивается со 100 МБ до 1 ГБ + + GPU Logging/Level + Уровень ведения журнала ГПУ - - Enable Extended Logging** - Включить расширенное ведение журнала** + + Detail level for GPU logs. Off disables logging entirely. + Уровень подробностей для журнала GPU. - + + Off + Выкл. + + + + Errors + Ошибки + + + + Standard + Стандартный + + + + Verbose + Подробный (Verbose) + + + + All + Все + + + Show Log in Console Показывать журнал в консоли - + + When checked, the max size of the log increases from 100 MB to 1 GB + Если включено, максимальный размер журнала увеличивается со 100 МБ до 1 ГБ + + + + Enable Extended Logging** + Включить расширенное ведение журнала** + + + Open Log Location Открыть папку журналов - + Homebrew Homebrew - + Arguments String Строка аргументов - + Graphics Графика - + When checked, it executes shaders without loop logic changes Если включено, шейдеры выполняются без изменения логики цикла - + Disable Loop safety checks Отключить проверку безопасности цикла - + When checked, it will dump all the macro programs of the GPU Если включено, будет дампить все макропрограммы ГП - + Dump Maxwell Macros Дамп макросов Maxwell - + When checked, it enables Nsight Aftermath crash dumps Если включено, включает дампы крашей Nsight Aftermath - + Enable Nsight Aftermath Включить Nsight Aftermath - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Если включено, будет дампить все оригинальные шейдеры ассемблера из кэша шейдеров на диске или игры как найденные + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + Если включено, будет дампать оригинальный байткод Maxwell guest shader (Входные данные для рекомпилятора) как .ash файлы в папку DumpDir/shaders/. Полезно для анализа с помощью nvdisasm. - - Dump Game Shaders - Дамп игровых шейдеров + + Dump Guest (Maxwell) Shaders + Дампать шейдеры Guest (Maxwell) - + Enable Renderdoc Hotkey Включить горячую клавишу Renderdoc - + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower Если включено, отключает компилятор макроса Just In Time. Включение этого параметра замедляет работу игр - + Disable Macro JIT Отключить макрос JIT - + When checked, it disables the macro HLE functions. Enabling this makes games run slower Если флажок установлен, он отключает функции макроса HLE. Включение этого параметра замедляет работу игр - + Disable Macro HLE Выключить макрос HLE - + When checked, the graphics API enters a slower debugging mode Если включено, графический API переходит в более медленный режим отладки - + Enable Graphics Debugging Включить отладку графики - + When checked, yuzu will log statistics about the compiled pipeline cache Если включено, yuzu будет записывать статистику о скомпилированном кэше конвейера - + Enable Shader Feedback Включить обратную связь о шейдерах - + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> <html><head/><body><p>Если этот флажок установлен, отключается переупорядочение загрузок в память, что позволяет связать загрузки с определенными отрисовками. В некоторых случаях может снизить производительность.</p></body></html> - + Disable Buffer Reorder Отключить переупорядочивание буфера - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + Если включено, будет дампать выходные SPIR-V binaries (.spv) рекомпилятора в папку LogDir/shaders/. Исследуйте с помощью SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) или RenderDoc. + + + + Dump SPIR-V Shaders + Дампать шейдеры SPIR-V + + + Advanced Расширенные - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Позволяет yuzu проверять наличие рабочей среды Vulkan при запуске программы. Отключите эту опцию, если это вызывает проблемы с тем, что внешние программы видят yuzu. - + Perform Startup Vulkan Check Выполнять проверку Vulkan при запуске - + Disable Web Applet Отключить веб-апплет - + Enable All Controller Types Включить все типы контроллеров - + Enable Auto-Stub Включить Auto-Stub - + Kiosk (Quest) Mode Режим киоска (квест) - + Use dev.keys Использовать dev.keys - + Enable Debug Asserts Включить отладочные утверждения - + Debugging Отладка - + Battery Serial: Серийный номер батареи: - + + Debug Knobs: + Параметры отладки: + + + Bitmask for quick development toggles Битовая маска для быстрого переключения режимов разработки - + Set debug knobs (bitmask) Установить параметры отладки (битовая маска) - + 16-bit debug knob set for quick development toggles 16-битный набор параметров отладки для быстрого переключения режимов - + (bitmask) (битовая маска) - - Debug Knobs: - Параметры отладки: - - - + Unit Serial: Серийный номер устройства: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. Включите эту опцию, чтобы вывести на консоль последний сгенерированный список аудиокоманд. Влияет только на игры, использующие аудио рендерер. - + Dump Audio Commands To Console** Дамп аудиокоманд в консоль** - + Flush log output on each line Сбрасывать вывод лога после каждой строки - + Enable FS Access Log Включить журнал доступа к ФС - + Enable Verbose Reporting Services** Включить службу отчетов в развернутом виде** - + Censor username in logs Скрывать имя пользователя в логах - + **This will be reset automatically when Eden closes. **Это будет автоматически сброшено после закрытия Eden. @@ -3329,33 +3262,38 @@ Would you like to delete the old save data? Цвет фона: - + % - FSR sharpening percentage (e.g. 50%) + FSR/SGSR sharpening percentage (e.g. 50%) % - + + Configure Effects... + + + + Off Отключена - + VSync Off VSync Off - + Recommended Рекомендуется - + On Включена - + VSync On VSync On @@ -3876,6 +3814,16 @@ Would you like to delete the old save data? Motion / Touch Движение и сенсор + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + Направлер на отключение хака кнопки SDL GUIDE: симулированное событие GUIDE(HOME), когда нажаты SELECT(MINUS) + START(PLUS). Может влияет на вещи Windows: триггеры, вибрация и т. д. + + + + Disable SDL WGI/XInput (Requires restart) + Отключить SDL WGI/XInput (Требуется перезагрзка) + ConfigureInputPerGame @@ -4385,58 +4333,58 @@ To invert the axes, first move your joystick vertically, and then horizontally.< [ожидание] - + New Profile Новый профиль - + Enter a profile name: Введите имя профиля: - - + + Create Input Profile Создать профиль управления - + The given profile name is not valid! Заданное имя профиля недействительно! - + Failed to create the input profile "%1" Не удалось создать профиль управления "%1" - + Delete Input Profile Удалить профиль управления - + Failed to delete the input profile "%1" Не удалось удалить профиль управления "%1" - + Load Input Profile Загрузить профиль управления - + Failed to load the input profile "%1" Не удалось загрузить профиль управления "%1" - + Save Input Profile Сохранить профиль управления - + Failed to save the input profile "%1" Не удалось сохранить профиль управления "%1" @@ -4484,7 +4432,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Configure Настроить @@ -4515,7 +4463,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Test Тест @@ -4530,77 +4478,77 @@ To invert the axes, first move your joystick vertically, and then horizontally.< Удалить сервер - + %1:%2 %1:%2 - - - - - - + + + + + + Eden Eden - + Port number has invalid characters Номер порта содержит недопустимые символы - + Port has to be in range 0 and 65353 Порт должен быть в районе от 0 до 65353 - + IP address is not valid IP-адрес недействителен - + This UDP server already exists Этот UDP сервер уже существует - + Unable to add more than 8 servers Невозможно добавить более 8 серверов - + Testing Тестирование - + Configuring Настройка - + Test Successful Тест успешен - + Successfully received data from the server. Успешно получена информация с сервера - + Test Failed Тест провален - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Не удалось получить действительные данные с сервера.<br>Убедитесь, что сервер правильно настроен, а также проверьте адрес и порт. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. Тест UDP или калибрация в процессе.<br>Пожалуйста, подождите завершения. @@ -4785,57 +4733,57 @@ Current values are %1% and %2% respectively. Некоторые настройки доступны только тогда, когда игра не запущена. - + Add-Ons Дополнения - + System Система - + CPU ЦП - + Graphics Графика - + Adv. Graphics Расш. графика - + Ext. Graphics Доп. графика - + Audio Звук - + Input Profiles Профили управления - + Network Сеть - + Applets Апплеты - + Properties Параметры игры @@ -4964,6 +4912,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t &Открыть в файловом менеджере + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5531,91 +5603,86 @@ Drag points to change position, or double-click table cells to edit values. - Show Compatibility List - Показывать список совместимости - - - Show Add-Ons Column Показывать столбец дополнений - + Show Size Column Показывать столбец размера - + Show File Types Column Показвыать столбец типа файлов - + Show Play Time Column Показать столбец времени воспроизведения - + Folder Icon Size: Размер иконки папки: - + Row 1 Text: Текст 1-ой строки: - + Row 2 Text: Текст 2-ой строки: - + Screenshots Скриншоты - + Ask Where To Save Screenshots (Windows Only) Запрашивать путь для сохранения скриншотов (только для Windows) - + Screenshots Path: Путь для скриншотов: - + ... ... - + TextLabel TextLabel - + Resolution: Разрешение: - + Select Screenshots Path... Выберите папку для скриншотов... - + <System> <System> - + English English - + Auto (%1 x %2, %3 x %4) Screenshot width value Авто (%1 x %2, %3 x %4) @@ -5852,7 +5919,7 @@ Drag points to change position, or double-click table cells to edit values.Импортирует данные для этого каталога. Это может занять некоторое время и приведет к удалению ВСЕХ СУЩЕСТВУЮЩИХ ДАННЫХ! - + Calculating... Подсчитываем... @@ -6056,50 +6123,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL не доступен! - + OpenGL shared contexts are not supported. Общие контексты OpenGL не поддерживаются. - + Eden has not been compiled with OpenGL support. Eden не был скомпилирован с поддержкой OpenGL. - - - + + + Error while initializing OpenGL! Ошибка при инициализации OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Ваш ГП может не поддерживать OpenGL, или у вас установлен устаревший графический драйвер. - + Error while initializing OpenGL 4.6! Ошибка при инициализации OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Ваш ГП может не поддерживать OpenGL 4.6, или у вас установлен устаревший графический драйвер.<br><br>Рендерер GL:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Ваш ГП может не поддерживать одно или несколько требуемых расширений OpenGL. Пожалуйста, убедитесь в том, что у вас установлен последний графический драйвер.<br><br>Рендерер GL:<br>%1<br><br>Неподдерживаемые расширения:<br>%2 - + This build doesn't have OpenGL support. В этой сборке отсутствует поддержка OpenGL. @@ -6107,279 +6174,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory &Добавить новую папку с играми - + Favorite Избранное - + Start Game Запустить игру - + Start Game without Custom Configuration Запустить игру без пользовательской настройки - + Open Save Data Location Открыть папку сохранений - + Open Mod Data Location Открыть папку модов - + Open Transferable Pipeline Cache Открыть переносной кэш конвейера - + Link to Ryujinx Ссылка на Ryujinx - + Remove Удалить - + Remove Installed Update Удалить установленное обновление - + Remove All Installed DLC Удалить все установленные DLC - + Remove Custom Configuration Удалить пользовательскую настройку - + Remove Cache Storage Удалить кэш-хранилище - + Remove OpenGL Pipeline Cache Удалить кэш конвейера OpenGL - + Remove Vulkan Pipeline Cache Удалить кэш конвейера Vulkan - + Remove All Pipeline Caches Удалить весь кэш конвейеров - + Remove All Installed Contents Удалить все установленное содержимое - + Manage Play Time Manage Play Time - + Edit Play Time Data Изменить дату Игрового времени - + Remove Play Time Data Удалить данные игрового времени - - + + Dump RomFS Дамп RomFS - + Dump RomFS to SDMC Сдампить RomFS в SDMC - + Verify Integrity Проверить целостность - + Copy Title ID to Clipboard Скопировать ID приложения в буфер обмена - - Navigate to GameDB entry - Открыть отчет о совместимости - - - + Create Shortcut Создать ярлык - + Add to Desktop На рабочий стол - + Add to Applications Menu В меню приложений - + Configure Game Настроить игру - + Scan Subfolders Сканировать подпапки - + Remove Game Directory Удалить папку с играми - + ▲ Move Up ▲ Переместить вверх - + ▼ Move Down ▼ Переместить вниз - + Open Directory Location Открыть расположение папки - + Clear Очистить - - - Name - Имя - - - - Compatibility - Совместимость - - - - Add-ons - Дополнения - - - - File type - Тип файла - - - - Size - Размер - - - - Play time - Время игры - GameListItemCompat - + Ingame В игре - + Game starts, but crashes or major glitches prevent it from being completed. Игра запускается, но вылеты или серьезные баги не позволяют пройти ее до конца. - + Perfect Идеально - + Game can be played without issues. Игра полностью работает без проблем. - + Playable Играбельно - + Game functions with minor graphical or audio glitches and is playable from start to finish. Игра проходима от начала до конца, возможны лишь мелкие графические или звуковые ошибки. - + Intro/Menu Вступление/Меню - + Game loads, but is unable to progress past the Start Screen. Игра загружается, но не проходит дальше стартового экрана. - + Won't Boot Не запускается - + The game crashes when attempting to startup. Игра вылетает при запуске. - + Not Tested Не проверено - + The game has not yet been tested. Игру еще не проверяли на совместимость. + + GameListModel + + + Name + Название + + + + Add-ons + Дополнения + + + + File type + Тип файла + + + + Size + Размер + + + + Play time + Время игры + + GameListPlaceholder - + Double-click to add a new folder to the game list Нажмите дважды, чтобы добавить новую папку в список игр @@ -6387,17 +6447,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) %1 из %n результат(ов)%1 из %n результат(ов)%1 из %n результат(ов)%1 из %n результат(ов) - + Filter: Поиск: - + Enter pattern to filter Введите текст для поиска @@ -6893,772 +6953,780 @@ Debug Message: &Вид списка игр - + Game &Icon Size Размер &иконок - + Reset Window Size to &720p Сбросить размер окна до &720p - + Reset Window Size to 720p Сбросить размер окна до 720p - + Reset Window Size to &900p Сбросить размер окна до &900p - + Reset Window Size to 900p Сбросить размер окна до 900p - + Reset Window Size to &1080p Сбросить размер окна до &1080p - + Reset Window Size to 1080p Сбросить размер окна до 1080p - + &Multiplayer &Мультиплеер - + &Tools &Инструменты - + Am&iibo Amiibo - + Launch &Applet &Апплеты - + &TAS &TAS - + &Create Home Menu Shortcut Создать &ярлык Home Menu - + Install &Firmware Установить &прошивку - + &Help &Помощь - + &Install Files to NAND... &Установить файлы в NAND... - + L&oad File... Загрузить &файл... - + Load &Folder... Загрузить &папку... - + E&xit &Выход - - + + &Pause &Пауза - + &Stop &Стоп - + &Verify Installed Contents Провер&ить установленное содержимое - + &About Eden &О Eden - + Single &Window Mode &Режим одного окна - + Con&figure... Параметр&ы... - + Ctrl+, Ctrl+, - + Enable Overlay Display Applet Включить отображение апплета оверлея - + Show &Filter Bar Показать п&анель поиска - + Show &Status Bar Показать панель с&татуса - + Show Status Bar Показать панель статуса - + &Browse Public Game Lobby &Просмотреть публичные игровые лобби - + &Create Room &Создать комнату - + &Leave Room Покинуть &комнату - + &Direct Connect to Room П&рямое подключение к комнате - + &Show Current Room Показать &текущую комнату - + F&ullscreen &Полноэкранный - + &Restart П&ерезапустить - + Load/Remove &Amiibo... &Загрузить/Удалить Amiibo... - - &Report Compatibility - &Сообщить о совместимости - - - + Open &Mods Page Открыть страницу &модов - - Open &Quickstart Guide - Открыть руководство &пользователя + + &User Handbook + - - &FAQ - &FAQ - - - + &Capture Screenshot &Сделать скриншот - + &Album Открыть &Альбом - + &Set Nickname and Owner Установить &никнейм и владельца - + &Delete Game Data &Удалить данные игры - + &Restore Amiibo &Восстановить Amiibo - + &Format Amiibo &Форматировать Amiibo - + &Mii Editor Открыт&ь Mii Editor - + &Configure TAS... &Настройка TAS... - + Configure C&urrent Game... &Настроить текущую игру... - - + + &Start &Запустить - + &Reset &Сбросить - - + + R&ecord З&апись - + Open &Controller Menu Открыть &Меню контроллеров - + Install Decryption &Keys &Установить ключи дешифровки - + &Home Menu Отк&рыть Home Menu - + &Desktop &Рабочий стол - + &Application Menu &Меню приложений - + &Root Data Folder &Корневая папка - + &NAND Folder П&апка NAND - + &SDMC Folder Па&пка SDMC - + &Mod Folder Папка &модов - + &Log Folder Папка &журнала - + From Folder Из &папки - + From ZIP &Из ZIP - + &Eden Dependencies &Зависимости Eden - + &Data Manager Упра&вление данными - + &Tree View &Древовидный - + &Grid View &Сетчатый - + Game Icon Size Размер иконки игры - - + + None Никакой - + Show Game &Name Показать игру - + Show &Performance Overlay &Оверлей производительности - + + Post-Processing &Shaders... + + + + + &Carousel View + &В Виде Карусели + + + Small (32x32) Маленький (32х32) - + Standard (64x64) Стандартный (64х64) - + Large (128x128) Большой (128х128) - + Full Size (256x256) Полноразмерный (256х256) - + Broken Vulkan Installation Detected Обнаружена поврежденная установка Vulkan - + Vulkan initialization failed during boot. Не удалось выполнить инициализацию Vulkan во время загрузки. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping Запущена игра - + Loading Web Applet... Загрузка веб-апплета... - - + + Disable Web Applet Отключить веб-апплет - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) Отключение веб-апплета может привести к неожиданному поведению и должно использоваться только, если вы хотите запустить Super Mario 3D All-Stars. Вы уверены, что хотите отключить веб-апплет? (Его можно снова включить в настройках отладки.) - + The amount of shaders currently being built Количество создаваемых шейдеров на данный момент - + The current selected resolution scaling multiplier. Текущий выбранный множитель масштабирования разрешения. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. Текущая скорость эмуляции. Значения выше или ниже 100% указывают на то, что эмуляция идет быстрее или медленнее, чем на Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. Количество кадров в секунду в данный момент. Значение будет различаться между играми и сценами. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. Время, которое нужно для эмуляции одного кадра Switch, не принимая во внимание ограничение FPS или вертикальную синхронизацию. Для эмуляции в полной скорости значение должно быть не больше 16,67 мс. - + Unmute Включить звук - + Mute Выключить звук - + Reset Volume Сбросить громкость - + + Configure Effects... + + + + &Clear Recent Files &Очистить недавние файлы - + &Continue &Продолжить - + Warning: Outdated Game Format Предупреждение: устаревший формат игры - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. Для этой игры вы используете разархивированный формат ROM'а, который является устаревшим и был заменен другими, такими как NCA, NAX, XCI или NSP. В разархивированных каталогах ROM'а отсутствуют иконки, метаданные и поддержка обновлений.<br><br>Для получения информации о различных форматах Switch, поддерживаемых Eden, ознакомьтесь с руководством пользователя. Это сообщение больше не будет отображаться. - - + + Error while loading ROM! Ошибка при загрузке ROM'а! - + The ROM format is not supported. Формат ROM'а не поддерживается. - + An error occurred initializing the video core. Произошла ошибка при инициализации видеоядра. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. - Eden столкнулся с ошибкой при работе видеоядра. Обычно это вызвано устаревшими драйверами GPU, включая интегрированные. Проверьте журнал для получения подробностей. Информацию о том, как получить доступ к журналу, см. на странице: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>Как загрузить файл журнала</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. + - + Error while loading ROM! %1 %1 signifies a numeric error code. Ошибка при загрузке ROM'а! %1 - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. %1<br>Пожалуйста, пересоздайте дампы ваших файлов или обратитесь за помощью в Discord/Stoat. - + An unknown error occurred. Please see the log for more details. Произошла неизвестная ошибка. Пожалуйста, проверьте журнал для подробностей. - + + (64-bit) (64-bit) - + + (32-bit) (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit %1 %2 - + Closing software... Закрываем программу... - + Save Data Сохранения - + Mod Data Данные модов - + Error Opening %1 Folder Ошибка при открытии папки %1 - - + + Folder does not exist! Папка не существует! - + Remove Installed Game Contents? Удалить установленное содержимое игр? - + Remove Installed Game Update? Удалить установленное обновление игры? - + Remove Installed Game DLC? Удалить установленные DLC игры? - + Remove Entry Удалить запись - + Delete OpenGL Transferable Shader Cache? Удалить переносной кэш шейдеров OpenGL? - + Delete Vulkan Transferable Shader Cache? Удалить переносной кэш шейдеров Vulkan? - + Delete All Transferable Shader Caches? Удалить весь переносной кэш шейдеров? - + Remove Custom Game Configuration? Удалить пользовательскую настройку игры? - + Remove Cache Storage? Удалить кэш-хранилище? - + Remove File Удалить файл - + Remove Play Time Data Удалить данные игрового времени - + Reset play time? Сбросить время игры? - - + + RomFS Extraction Failed! Не удалось извлечь RomFS! - + There was an error copying the RomFS files or the user cancelled the operation. Произошла ошибка при копировании файлов RomFS или пользователь отменил операцию. - + Full Полный - + Skeleton Скелет - + Select RomFS Dump Mode Выберите режим дампа RomFS - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. Пожалуйста, выберите, как вы хотите выполнить дамп RomFS. <br>Полный скопирует все файлы в новую папку, в то время как <br>скелет создаст только структуру папок. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root В %1 недостаточно свободного места для извлечения RomFS. Пожалуйста, освободите место или выберите другую папку для дампа в Эмуляция > Параметры > Система > Файловая система > Корень дампа - + Extracting RomFS... Извлечение RomFS... - - + + Cancel Отмена - + RomFS Extraction Succeeded! Извлечение RomFS прошло успешно! - + The operation completed successfully. Операция выполнена успешно. - + Error Opening %1 Ошибка при открытии %1 - + Select Directory Выбрать папку - + Properties Свойства - + The game properties could not be loaded. Не удалось загрузить свойства игры. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. Исполняемый файл Switch (%1);;Все файлы (*.*) - + Load File Загрузить файл - + Open Extracted ROM Directory Открыть папку извлеченного ROM'а - + Invalid Directory Selected Выбрана недопустимая папка - + The directory you have selected does not contain a 'main' file. Папка, которую вы выбрали, не содержит файла 'main'. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) Устанавливаемый файл Switch (*.nca *.nsp *.xci);;Архив контента Nintendo (*.nca);;Пакет подачи Nintendo (*.nsp);;Образ картриджа NX (*.xci) - + Install Files Установить файлы - + %n file(s) remaining %n файл(ов) осталось%n файл(ов) осталось%n файл(ов) осталось%n файл(ов) осталось - + Installing file "%1"... Установка "%1"... - - + + Install Results Результаты - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. Чтобы избежать возможных конфликтов, мы не рекомендуем пользователям устанавливать игры в NAND. Пожалуйста, используйте эту функцию только для установки обновлений и DLC. - + %n file(s) were newly installed %n файл(ов) были установлены недавно @@ -7668,7 +7736,7 @@ Please, only use this feature to install updates and DLC. - + %n file(s) were overwritten %n файл(ов) были перезаписаны @@ -7678,7 +7746,7 @@ Please, only use this feature to install updates and DLC. - + %n file(s) failed to install %n файл(ов) не удалось установить @@ -7688,361 +7756,324 @@ Please, only use this feature to install updates and DLC. - + System Application Системное приложение - + System Archive Системный архив - + System Application Update Обновление системного приложения - + Firmware Package (Type A) Пакет прошивки (Тип А) - + Firmware Package (Type B) Пакет прошивки (Тип Б) - + Game Игра - + Game Update Обновление игры - + Game DLC DLC игры - + Delta Title Дельта-титул - + Select NCA Install Type... Выберите тип установки NCA... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) Пожалуйста, выберите тип приложения, который вы хотите установить для этого NCA: (В большинстве случаев, подходит стандартный выбор «Игра».) - + Failed to Install Ошибка установки - + The title type you selected for the NCA is invalid. Тип приложения, который вы выбрали для NCA, недействителен. - + File not found Файл не найден - + File "%1" not found Файл "%1" не найден - + OK ОК - + Function Disabled Функция отключена - + Compatibility list reporting is currently disabled. Check back later! Отправка отчетов о совместимости временно отключена. Доступ будет восстановлен позднее! - + Error opening URL Ошибка при открытии URL - + Unable to open the URL "%1". Не удалось открыть URL: "%1". - + TAS Recording Запись TAS - + Overwrite file of player 1? Перезаписать файл игрока 1? - + Invalid config detected Обнаружена недопустимая конфигурация - + Handheld controller can't be used on docked mode. Pro controller will be selected. Портативный контроллер не может быть использован в режиме док-станции. Будет выбран контроллер Pro. - - + + Amiibo Amiibo - - + + The current amiibo has been removed Текущий amiibo был удален - + Error Ошибка - - + + The current game is not looking for amiibos Текущая игра не ищет amiibo - + Amiibo File (%1);; All Files (*.*) Файл Amiibo (%1);; Все Файлы (*.*) - + Load Amiibo Загрузить Amiibo - + Error loading Amiibo data Ошибка загрузки данных Amiibo - + The selected file is not a valid amiibo Выбранный файл не является допустимым amiibo - + The selected file is already on use Выбранный файл уже используется - + An unknown error occurred Произошла неизвестная ошибка - - - Keys not installed - Ключи не установлены - - - - - Install decryption keys and restart Eden before attempting to install firmware. - Установите ключи дешифрования и перезапустите Eden перед установкой прошивки. - - - - Select Dumped Firmware Source Location - Выберите дамп распакованной прошивки - - - - Select Dumped Firmware ZIP - Выберите дамп прошивки в ZIP-архиве - - - - Zipped Archives (*.zip) - Архивы ZIP (*.zip) - - - - Firmware cleanup failed - Не удалось очистить прошивку - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - Не удалось очистить кэш распакованной прошивки. -Проверьте права на запись во временный системный каталог и повторите попытку. -Ошибка ОС: %1 - - - + No firmware available Прошивка недоступна - + Firmware Corrupted Прошивка повреждена - + Unknown applet Неизвестный апплет - + Applet doesn't map to a known value. Апплет не соответствует ни одному известному значению. - + Record not found Запись не найдена - + Applet not found. Please reinstall firmware. Апплет не найден. Пожалуйста, переустановите прошивку. - + Capture Screenshot Сделать скриншот - + PNG Image (*.png) Изображение PNG (*.png) - + TAS state: Running %1/%2 Состояние TAS: Выполняется %1/%2 - + TAS state: Recording %1 Состояние TAS: Записывается %1 - + TAS state: Idle %1/%2 Состояние TAS: Простой %1/%2 - + TAS State: Invalid Состояние TAS: Неверное - + &Stop Running &Остановка - + Stop R&ecording &Закончить запись - + Building: %n shader(s) Компиляция %n шейдер(ов)Компиляция %n шейдер(ов)Компиляция %n шейдер(ов)Компиляция %n шейдер(ов) - + Scale: %1x %1 is the resolution scaling factor Масштаб: %1x - + Speed: %1% / %2% Скорость: %1% / %2% - + Speed: %1% Скорость: %1% - + Game: %1 FPS Игра: %1 FPS - + Frame: %1 ms Кадр: %1 мс - - - FSR - FSR - - - + NO AA БЕЗ СГЛАЖИВАНИЯ - + + NO FX + + + + + FX + + + + VOLUME: MUTE ГРОМКОСТЬ: ЗАГЛУШЕНА - + VOLUME: %1% Volume percentage (e.g. 50%) ГРОМКОСТЬ: %1% - + Derivation Components Missing Отсутствуют необходимые компоненты - + Decryption keys are missing. Install them now? Ключи дешифрования отсутствуют. Хотите установить их? - + Wayland Detected! Обнаружен Wayland! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -8053,74 +8084,74 @@ Would you like to force it for future launches? Хотите включить X11 для последующих запусков? - + Use X11 Использовать X11 - + Continue with Wayland Продолжить с Wayland - + Don't show again Больше не показывать - + Restart Required Требуется перезапуск - + Restart Eden to apply the X11 backend. Перезапустите Eden, чтобы применить X11. - + Slow Замедлено - + Turbo Турбо - + Unlocked Разблокировано - + Select RomFS Dump Target Выбор цели для дампа RomFS - + Please select which RomFS you would like to dump. Выберите RomFS, который вы хотите извлечь. - + Are you sure you want to close Eden? Вы уверены, что хотите закрыть Eden? - - - + + + Eden Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. Вы уверены, что хотите остановить эмуляцию? Любой несохраненный прогресс будет потерян. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8188,6 +8219,11 @@ Would you like to bypass this and exit anyway? ScaleForce ScaleForce + + + FSR + FSR + Area @@ -8199,52 +8235,57 @@ Would you like to bypass this and exit anyway? MMPX - + + SGSR + SGSR + + + + SGSR EdgeDir + SGSR EdgeDir + + + Docked В док-станции - + Handheld Портативный - + Fast Производительность - - Balanced - Баланс - - - + Accurate Точность - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL SPIRV OpenGL SPIRV - + OpenGL GLASM OpenGL GLASM - + Null Null @@ -8287,7 +8328,7 @@ If you wish to clean up the files which were left in the old data location, you %1 - + Data was migrated successfully. Данные успешно перенесены. @@ -8780,8 +8821,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8794,10 +8835,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8812,9 +8853,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8976,50 +9017,50 @@ p, li { white-space: pre-wrap; } [не определено] - + %1%2 %1%2 - + [invalid] [недопустимо] - - + + %1%2Hat %3 %1%2Крест. %3 - - - + + + %1%2Axis %3 %1%2Ось %3 - + %1%2Axis %3,%4,%5 %1%2Ось %3,%4,%5 - + %1%2Motion %3 %1%2Движение %3 - - + + %1%2Button %3 %1%2Кнопка %3 - + [unused] [не используется] @@ -9145,47 +9186,47 @@ p, li { white-space: pre-wrap; } %1 играет в %2 - + Play Time: %1 Время игры: %1 - + Never Played Не запускалась - + Version: %1 Версия: %1 - + Version: 1.0.0 Версия: 1.0.0 - + Installed SD Titles Установленные SD игры - + Installed NAND Titles Установленные NAND игры - + System Titles Системные игры - + Add New Game Directory Добавить новую папку с играми - + Favorites Избранные @@ -9306,14 +9347,14 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware Для игры требуется прошивка - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. - Игра, которую вы пытаетесь запустить, требует прошивку для запуска или прохождения начального меню. Пожалуйста, <a href='https://yuzu-mirror.github.io/help/quickstart'>сделайте дамп и установите прошивку</a>, или нажмите «OK», чтобы запустить игру в любом случае. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. + @@ -9322,10 +9363,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel Отмена @@ -9346,7 +9387,7 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 @@ -9355,207 +9396,242 @@ p, li { white-space: pre-wrap; } %1 - - + + Verifying integrity... Проверка целостности... - - + + Integrity verification succeeded! Проверка целостности прошла успешно! - - + + The operation completed successfully. Операция выполнена успешно. - - + + Integrity verification failed! Проверка целостности не удалась! - + File contents may be corrupt or missing. Содержимое файла может быть повреждено или отсутствовать. - + Integrity verification couldn't be performed Проверка целостности не может быть выполнена - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. Установка прошивки отменена, прошивка может быть повреждена. Содержимое файла не удалось проверить на корректность. - + Select Dumped Keys Location Выберите местоположение дампнутых ключей - + Decryption Keys install succeeded Установка ключей дешифровки прошла успешно. - + Decryption Keys install failed Ошибка установки ключей дешифровки - + Orphaned Profiles Detected! Обнаружены «сиротские» профили! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> МОГУТ ПРОИЗОЙТИ НЕПРЕДВИДЕННЫЕ ПРОБЛЕМЫ, ЕСЛИ ВЫ НЕ ПРОЧИТАЕТЕ ЭТО!<br>Eden обнаружил следующие папки сохранений без привязанного профиля:<br>%1<br><br>Найдены следующие корректные профили:<br>%2<br><br>Нажмите «OK», чтобы открыть папку сохранений и исправить профили.<br>Совет: скопируйте содержимое самой большой или последней измененной папки в другое место, удалите все «сиротские» профили и переместите скопированные данные в правильный профиль.<br><br>Если есть вопросы, прочитайте <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>справку</a>.<br> - + Really clear data? Действительно очистить данные? - + Important data may be lost! Важные данные могут быть потеряны! - + Are you REALLY sure? Вы уверены? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. Данные будут безвозвратно потеряны после удаления! Делайте это только если вы абсолютно уверены, что хотите их удалить. - + Clearing... Очистка... - + Select Export Location Выберите папку для экспорта - + %1.zip %1.zip - - + + + Zipped Archives (*.zip) Архивы ZIP (*.zip) - + Exporting data. This may take a while... Идет экспорт данных. Это может занять некоторое время... - + Exporting Экспорт - + Exported Successfully Экспорт выполнен успешно - + Data was exported successfully. Данные успешно экспортированы. - + Export Cancelled Экспорт отменен - + Export was cancelled by the user. Экспорт был отменен пользователем. - + Export Failed Не удалось экспортировать - + Ensure you have write permissions on the targeted directory and try again. Проверьте, есть ли права на запись в выбранную папку, и повторите попытку. - + Select Import Location Выберите папку для импорта - + Import Warning Предупреждение при импорте - + All previous data in this directory will be deleted. Are you sure you wish to proceed? Все существующие данные в этой папке будут удалены. Вы уверены, что хотите продолжить? - + Importing data. This may take a while... Идет импорт данных. Это может занять некоторое время... - + Importing Импорт - + Imported Successfully Импорт успешно выполнен - + Data was imported successfully. Данные успешно импортированы. - + Import Cancelled Импорт отменен - + Import was cancelled by the user. Импорт был отменен пользователем. - + Import Failed Не удалось импортировать - + Ensure you have read permissions on the targeted directory and try again. Проверьте, есть ли права на чтение в выбранной папке, и повторите попытку. + + + Keys not installed + Ключи не установлены + + + + Install decryption keys and restart Eden before attempting to install firmware. + Установите ключи дешифрования и перезапустите Eden перед установкой прошивки. + + + + Select Dumped Firmware Source Location + Выберите дамп распакованной прошивки + + + + Select Dumped Firmware ZIP + Выберите дамп прошивки в ZIP-архиве + + + + Firmware cleanup failed + Не удалось очистить прошивку + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + Не удалось очистить кэш распакованной прошивки. +Проверьте права на запись во временный системный каталог и повторите попытку. +Ошибка ОС: %1 + QtCommon::FS @@ -9804,72 +9880,72 @@ Would you like to manually select a portable folder to use? Кэш метаданных не может быть удален. Возможно, он используется или отсутствует. - + Create Shortcut Создать ярлык - + Do you want to launch the game in fullscreen? Вы хотите запустить игру в полноэкранном режиме? - + Shortcut Created Ярлык создан - + Successfully created a shortcut to %1 Успешно создан ярлык в %1 - + Shortcut may be Volatile! Ярлык может быть нестабильным! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? Это создаст ярлык для текущего AppImage. Он может не работать после обновлений. Продолжить? - + Failed to Create Shortcut Не удалось создать ярлык - + Failed to create a shortcut to %1 Не удалось создать ярлык для %1 - + Create Icon Создать иконку - + Cannot create icon file. Path "%1" does not exist and cannot be created. Невозможно создать файл иконки. Путь "%1" не существует и не может быть создан. - + No firmware available Прошивка недоступна - + Please install firmware to use the home menu. Пожалуйста, установите прошивку, чтобы использовать Home Menu. - + Home Menu Applet Приложение Home Menu - + Home Menu is not available. Please reinstall firmware. Home Menu недоступно. Переустановите прошивку. @@ -9877,37 +9953,37 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name Название мода - + What should this mod be called? Как назвать этот мод? - + RomFS RomFS - + ExeFS/Patch ExeFS/Patch - + Cheat Чит - + Mod Type Тип мода - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. @@ -9916,18 +9992,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed Ошибка извлечения мода - + Failed to create temporary directory %1 Не удалось создать временную папку %1 - + Zip file %1 is empty ZIP-файл %1 пуст @@ -10629,66 +10705,66 @@ By selecting "From Eden", previous save data stored in Ryujinx will be %1 доступна для загрузки. - + New Version Location Местоположение новой версии - + All Files (*.*) Все файлы (*.*) - - - + + + Failed to save file Не удалось сохранить файл - + Could not open file %1 for writing. Не удалось открыть файл %1 для записи. - + Downloading... Загрузка... - + Cancel Отмена - + Could not write to file %1. Не удалось записать в файл %1. - + Could not commit to file %1. Не удалось сохранить файл %1. - + Failed to download file Не удалось загрузить файл - + Could not download from %1%2 Error code: %3 Не удалось загрузить из %1%2 Код ошибки: %3 - + Download Complete Загрузка завершена - + Successfully downloaded %1. Would you like to open it? %1 был успешно загружен. Хотите открыть? diff --git a/dist/languages/sv.ts b/dist/languages/sv.ts index 3865e56ee0..818a84cbe1 100644 --- a/dist/languages/sv.ts +++ b/dist/languages/sv.ts @@ -51,7 +51,7 @@ li.checked::marker { content: "\2612"; } Communicating with the server... - Kommunicerar med servern... + Kommunicerar med servern… @@ -192,7 +192,7 @@ Detta skulle stänga av både deras forumanvändarnamn och deras IP-adress. Moderation... - Moderering... + Moderering… @@ -218,155 +218,6 @@ Detta skulle stänga av både deras forumanvändarnamn och deras IP-adress.%1 - %2 (%3/%4 medlemmar) - ansluten - - CompatDB - - - Report Compatibility - Rapportera kompatibilitet - - - - - - - - - - Report Game Compatibility - Rapportera spelkompatibilitet - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - <html><head/><body><p><span style=" font-size:10pt;">Om du väljer att skicka in ett testresultat till </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">edens kompatibilitetslista</span></a><span style=" font-size:10pt;"> kommer följande information kommer att samlas in och visas på webbplatsen:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hårdvaruinformation (CPU / GPU / operativsystem)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Vilken version av eden du kör</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Det anslutna eden-kontot</li></ul></body></html> - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>Startar spelet?</p></body></html> - - - - Yes The game starts to output video or audio - Ja Spelet börjar mata ut video eller ljud - - - - No The game doesn't get past the "Launching..." screen - Nej Spelet kommer inte längre än till skärmen "Startar..." - - - - Yes The game gets past the intro/menu and into gameplay - Ja Spelet tar sig förbi intro/meny och in i spelet - - - - No The game crashes or freezes while loading or using the menu - Nej Spelet kraschar eller fryser när det laddas eller när menyn används - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>Når spelet upp till spelbarhet?</p></body></html> - - - - Yes The game works without crashes - Ja Spelet fungerar utan kraschar - - - - No The game crashes or freezes during gameplay - Nej Spelet kraschar eller fryser under spelets gång - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>Fungerar spelet utan att krascha, frysa eller låsa sig under spelets gång?</p></body></html> - - - - Yes The game can be finished without any workarounds - Ja Spelet kan slutföras utan några lösningar - - - - No The game can't progress past a certain area - Nej Spelet kan inte fortsätta förbi ett visst område - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>Är spelet helt spelbart från början till slut?</p></body></html> - - - - Major The game has major graphical errors - Större Spelet har stora grafiska fel - - - - Minor The game has minor graphical errors - Mindre Spelet har mindre grafiska fel - - - - None Everything is rendered as it looks on the Nintendo Switch - Inget Allt återges som det ser ut på Nintendo Switch - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>Har spelet några grafiska problem?</p></body></html> - - - - Major The game has major audio errors - Större Spelet har stora ljudfel - - - - Minor The game has minor audio errors - Mindre Spelet har mindre ljudfel - - - - None Audio is played perfectly - Inget Ljudet spelas upp perfekt - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>Har spelet några ljudstörningar / saknade effekter?</p></body></html> - - - - Thank you for your submission! - Tack för att du skickade in! - - - - Submitting - Skickar - - - - Communication error - Kommunikationsfel - - - - An error occurred while sending the Testcase - Ett fel inträffade när testfallet skickades - - - - Next - Nästa - - ConfigurationShared @@ -524,7 +375,7 @@ Påverkar inte prestanda/stabilitet men kan göra det möjligt att läsa in HD-t 200% for a 30 FPS game is 60 FPS, and for a 60 FPS game it will be 120 FPS. Disabling it means unlocking the framerate to the maximum your PC can reach. Kontrollerar spelets maximala renderingshastighet, men det är upp till varje spel om det körs snabbare eller inte. -200% för ett spel med 30 bilder/s är 60 bilder/s, och för ett spel med 60 bilder/s blir det 120 bilder/s. +200 % för ett spel med 30 bilder/s är 60 bilder/s och för ett spel med 60 bilder/s blir det 120 bilder/s. Att inaktivera det innebär att du låser upp bildfrekvensen till det maximala som din dator kan nå. @@ -571,39 +422,39 @@ Kan bidra till att minska hackande vid lägre bildfrekvenser. - + Backend: Bakände: - CPU Overclock - CPU-överklockning + CPU Clocks + CPU-klockfrekvenser - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. - Överklockar den emulerade processorn för att ta bort vissa FPS-begränsningar. Svagare processorer kan få minskad prestanda och vissa spel kan bete sig felaktigt. -Använd Boost (1700MHz) för att köra med Switchens högsta inbyggda klocka, eller Fast (2000MHz) för att köra med 2x klocka. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + Höjer den klockfrekvens som den emulerade processorn rapporterar, vilket tar bort vissa FPS-begränsare. +Svagare processorer kan få sämre prestanda och vissa spel kan fungera felaktigt. - + Custom CPU Ticks Anpassade CPU-ticks - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. - Ange ett anpassat värde för CPU-ticks. Högre värden kan öka prestandan, men kan orsaka deadlocks. Ett intervall på 77-21000 rekommenderas. + Ange ett anpassat värde för CPU-ticks. Högre värden kan öka prestandan, men kan orsaka deadlocks. Ett intervall på 77 till 21000 rekommenderas. - + Enable Host MMU Emulation (fastmem) Aktivera värd-MMU-emulering (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. @@ -612,100 +463,100 @@ Om den aktiveras sker läsning/skrivning av gästminnet direkt i minnet och anv Om den inaktiveras tvingas all minnesåtkomst att använda programvaru-MMU-emulering. - + Unfuse FMA (improve performance on CPUs without FMA) Unfuse FMA (förbättrar prestanda på CPU:er utan FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. Detta alternativ förbättrar hastigheten genom att minska noggrannheten i fused-multiply-add-instruktioner på processorer utan inbyggt FMA-stöd. - + Faster FRSQRTE and FRECPE Snabbare FRSQRTE och FRECPE - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. Detta alternativ förbättrar hastigheten för vissa approximativa flyttalsfunktioner genom att använda mindre exakta inbyggda approximationer. - + Faster ASIMD instructions (32 bits only) Snabbare ASIMD-instruktioner (endast 32 bitar) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. Detta alternativ förbättrar hastigheten för 32-bitars ASIMD flyttalsfunktioner genom att köra med felaktiga avrundningslägen. - + Inaccurate NaN handling Felaktig NaN-hantering - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. Detta alternativ förbättrar hastigheten genom att ta bort NaN-kontrollen. Observera att detta också minskar noggrannheten för vissa instruktioner för flyttal. - + Disable address space checks Inaktivera kontroller av adressutrymme - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. Det här alternativet förbättrar hastigheten genom att eliminera en säkerhetskontroll före varje minnesoperation. Om du inaktiverar det kan det bli möjligt att köra godtycklig kod. - + Ignore global monitor Ignorera global monitor - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. Detta alternativ förbättrar hastigheten genom att endast förlita sig på semantiken i cmpxchg för att garantera säkerheten för instruktioner med exklusiv åtkomst. Observera att detta kan leda till deadlocks och andra tävlingsförhållanden. - + API: API: - + Changes the output graphics API. Vulkan is recommended. Ändrar grafik-API:et för utdata. Vulkan rekommenderas. - + Device: Enhet: - + This setting selects the GPU to use (Vulkan only). Denna inställning väljer vilken GPU som ska användas (endast Vulkan). - + Resolution: Status: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. @@ -714,27 +565,27 @@ Högre upplösningar kräver mer VRAM och bandbredd. Alternativ lägre än 1X kan orsaka artefakter. - + Window Adapting Filter: Fönsteranpassande filter: - + FSR Sharpness: FSR-skärpa: - - Determines how sharpened the image will look using FSR's dynamic contrast. - Bestämmer hur skarp bilden ska se ut med hjälp av FSR:s dynamiska kontrast. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. + Avgör hur skarp bilden ser ut med FSR:s eller SGSR:s dynamiska kontrast. - + Anti-Aliasing Method: Metod för kantutjämning: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. @@ -742,13 +593,23 @@ FXAA can produce a more stable picture in lower resolutions. SMAA erbjuder den bästa kvaliteten. FXAA kan ge en stabilare bild i lägre upplösningar. + + + Enable post-processing effects + Aktivera efterbehandlingseffekter + + Applies post-processing effects to the final image. + Tillämpar efterbehandlingseffekter på den slutliga bilden. + + + Fullscreen Mode: Helskärmsläge: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. @@ -757,12 +618,12 @@ Borderless ger bäst kompatibilitet med skärmtangentbordet som vissa spel kräv Exklusiv helskärm kan ge bättre prestanda och bättre stöd för Freesync/Gsync. - + Aspect Ratio: Bildförhållande: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. @@ -771,36 +632,36 @@ De flesta spel stöder endast 16:9, så modifieringar krävs för att få andra Kontrollerar även bildförhållandet för tagna skärmdumpar. - + Use persistent pipeline cache Använd permanent pipeline-cache - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. Gör det möjligt att spara shaders i lagringsutrymmet för snabbare laddning vid nästa spelstart. Att inaktivera detta är endast avsett för felsökning. - + Use asynchronous GPU emulation Använd asynkron GPU-emulering - + Uses an extra CPU thread for rendering. This option should always remain enabled. Använder en extra CPU-tråd för rendering. Detta alternativ bör alltid vara aktiverat. - + NVDEC emulation: NVDEC-emulering: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. @@ -809,12 +670,12 @@ Den kan antingen använda CPU eller GPU för avkodning, eller inte utföra någo I de flesta fall ger GPU-avkodning bäst prestanda. - + ASTC Decoding Method: ASTC-avkodningsmetod: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -827,12 +688,12 @@ CPU asynkront: Använd CPU:n för att avkoda ASTC-texturer vid behov. Eliminerar men kan ge artefakter. - + ASTC Recompression Method: ASTC-återkomprimeringsmetod: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. @@ -841,44 +702,64 @@ BC1/BC3: Det mellanliggande formatet kommer att komprimeras om till BC1- eller B vilket sparar VRAM men försämrar bildkvaliteten. - + Frame Pacing Mode (Vulkan only) Frame Pacing Mode (endast Vulkan) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. Styr hur emulatorn hanterar bildhastigheten för att minska hackighet och göra bildfrekvensen jämnare och mer konsekvent. - + VRAM Usage Mode: VRAM-användningsläge: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. Väljer om emulatorn ska prioritera att spara minne eller utnyttja tillgängligt videominne maximalt för prestanda. Aggressivt läge kan påverka prestandan hos andra program, till exempel inspelningsprogram. - + Skip CPU Inner Invalidation Hoppa över CPU:ns interna ogiltigförklaring - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. Hoppar över vissa cache-ogiltigförklaringar under minnesuppdateringar, vilket minskar CPU-användningen och förbättrar latensen. Detta kan orsaka mjuka krascher. - + + GPU Readback Before NCE Invalidation + GPU-återläsning före NCE-ogiltigförklaring + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + Skriver tillbaka GPU-buffertdata till gästens minne innan NCE ogiltigförklarar hela gästsidan, så att data bevaras medan CPU-lagringen fortsätter. + + + + Patch Runtime NROs for NCE + Korrigera NRO:er under körning för NCE + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + Tillämpar NCE-korrigeringar på NRO-moduler som läses in under körning. Det krävs för att vissa spel ska fungera korrekt. + + + VSync Mode: VSync-läge: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -889,119 +770,136 @@ Mailbox kan ha lägre latens än FIFO och uppvisar inte tearing, men kan tappa b Immediate (ingen synkronisering) visar allt som är tillgängligt och kan uppvisa tearing. - + Sync Memory Operations Synkronisera minnesoperationer - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. Kontrollerar datakonsistens mellan beräknings- och minnesoperationer. - Det här alternativet åtgärdar problem i spel, men kan försämra prestandan. - Unreal Engine 4-spel upplever ofta de mest betydande förändringarna av detta. - + Enable asynchronous presentation (Vulkan only) Aktivera asynkron presentation (endast Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. Förbättrar prestandan något genom att flytta presentationen till en separat CPU-tråd. - + Force maximum clocks (Vulkan only) Tvinga fram maximal klockfrekvens (endast Vulkan) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. Körs i bakgrunden i väntan på grafikkommandon för att förhindra att GPU:n sänker sin klockhastighet. - + Anisotropic Filtering: Anisotropisk filtrering: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. Kontrollerar kvaliteten på texturrendering vid sneda vinklar. - -Säker att ställa in på 16x på de flesta GPU:er. +Det går att ställa in 16× på de flesta GPU:er. - + GPU Mode: GPU-läge: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - Kontrollerar GPU-emuleringsläget. -De flesta spel renderas bra med lägena Snabb eller Balanserad, men för vissa krävs fortfarande läget Noggrann. -Partiklar tenderar att endast renderas korrekt med läget Noggrann. + Styr GPU-emuleringsläget. +De flesta spel återges bra med Snabb, men Exakt krävs fortfarande för vissa. +Partiklar återges ofta korrekt endast i läget Exakt. - + DMA Accuracy: DMA-noggrannhet: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. - Kontrollerar DMA-precisionens noggrannhet. Säker precision åtgärdar problem i vissa spel men kan försämra prestandan. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. + Styr DMA-läget för läsning. +Osäker är snabbare, medan Säker är stabilare och kan lösa problem i vissa spel. +Standard följer inställningen för GPU-noggrannhet. - + + GPU Fence Behavior: + Beteende för GPU-staket: + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + Styr synkroniseringsbeteendet för GPU-staket. +Omedelbar är det snabbaste alternativet, men kan orsaka vissa problem. +Balanserad ger bättre kompatibilitet och kan lösa problem i vissa spel. +Exakt förbättrar kompatibiliteten ytterligare, men på bekostnad av viss prestanda. +Standard följer inställningen för GPU-läge. + + + Enable asynchronous shader compilation Aktivera asynkron shaderkompilering - + May reduce shader stutter. Kan minska shader-hackighet. - - Fast GPU Time - Snabb GPU-tid + + GPU Clocks + GPU-klockfrekvenser - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. - Överklockar den emulerade GPU:n för att öka den dynamiska upplösningen och renderingsavståndet. -Använd 256 för maximal prestanda och 512 för maximal grafisk trohet. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. + Får spelet att tro att GPU-arbetet avslutas snabbare än det gör, så att det slutar sänka upplösning och ritaravstånd för att passa Switch-klockfrekvenserna. - + GPU Unswizzle GPU Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. Accelererar avkodning av BCn 3D-texturer med hjälp av GPU-beräkningar. Inaktivera om du upplever krascher eller grafiska fel. - + GPU Unswizzle Max Texture Size Maximal texturstorlek för GPU Unswizzle - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. @@ -1010,48 +908,48 @@ GPU är snabbare för medelstora och stora texturer, men CPU kan vara effektivar Justera detta för att hitta balansen mellan GPU-acceleration och CPU-överbelastning. - + GPU Unswizzle Stream Size Strömstorlek för GPU Unswizzle - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. Ställer in den maximala mängden texturdata (i MiB) som bearbetas per bildruta. Högre värden kan minska hackighet under texturinläsning men kan påverka bildrutans konsistens. - + GPU Unswizzle Chunk Size Chunk-storlek för GPU Unswizzle - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. Bestämmer antalet djupskivor som bearbetas i en enda sändning. Att öka detta kan förbättra genomströmningen på avancerade GPU:er, men kan orsaka TDR eller drivrutinstidsgränser på svagare hårdvara. - + Use Vulkan pipeline cache Använda Vulkan pipeline-cache - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. Aktiverar GPU-leverantörsspecifik pipeline-cache. Det här alternativet kan förbättra laddningstiden för shaders avsevärt i fall där Vulkan-drivrutinen inte lagrar pipeline-cache-filer internt. - + Enable Compute Pipelines (Intel Vulkan Only) Aktivera compute pipelines (endast Intel Vulkan) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. @@ -1060,184 +958,182 @@ Denna inställning finns endast för Intels egna drivrutiner och kan orsaka kras Beräkningspipelines är alltid aktiverade på alla andra drivrutiner. - + Enable Reactive Flushing Aktivera Reactive Flushing - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. Använder reaktiv rensning i stället för prediktiv rensning, vilket ger mer exakt minnessynkning. - + Sync to framerate of video playback Synkronisera med bildfrekvensen för videouppspelning - + Run the game at normal speed during video playback, even when the framerate is unlocked. Kör spelet i normal hastighet under videouppspelning, även när bildfrekvensen är upplåst. - + Barrier feedback loops Återkopplingsloopar för barriärer - + Improves rendering of transparency effects in specific games. Förbättrar renderingen av transparenseffekter i vissa spel. - + Enable buffer history Aktivera bufferthistorik - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. Aktiverar åtkomst till tidigare bufferttillstånd. Det här alternativet kan förbättra renderingskvaliteten och prestandakonsistensen i vissa spel. - + Fix bloom effects Korrigera bloom-effekter - + Removes bloom in Burnout. Tar bort bloom i Burnout. - - Enable Legacy Rescale Pass - Aktivera äldre omskalningspass - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - Kan åtgärda skalningsproblem i vissa spel genom att förlita sig på beteendet från den tidigare implementeringen. -Äldre beteende som åtgärdar linjeartefakter på AMD- och Intel-GPU:er och grå texturflimmer på Nvidia-GPU:er i Luigis Mansion 3. - - - + Extended Dynamic State Utökad dynamisk status - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. Kontrollerar antalet funktioner som kan användas i utökat dynamiskt tillstånd. Högre tillstånd möjliggör fler funktioner och kan öka prestandan, men kan orsaka ytterligare grafiska problem. - + Vertex Input Dynamic State Dynamiskt tillstånd för vertexinmatning - + Enables vertex input dynamic state feature for better quality and performance. Aktiverar funktionen för dynamiskt tillstånd för vertexinmatning för bättre kvalitet och prestanda. - + Sample Shading Provskuggning - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. Tillåter fragment-shadern att exekveras per prov i ett multisamplade fragment istället för en gång per fragment. Förbättrar grafikens kvalitet på bekostnad av prestanda. Högre värden förbättrar kvaliteten men försämrar prestandan. - + RNG Seed RNG-frö - + Controls the seed of the random number generator. Mainly used for speedrunning. Att kontrollera fröet till slumptalsgeneratorn. Används främst för speedrunning. - + Device Name Enhetsnamn - + The name of the console. Konsolens namn. - + + Homebrew Args + Homebrew-argument + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + Kommandoradsargument som skickas till homebrew vid start (t.ex. -noglsl). + + + Custom RTC Date: Anpassat RTC-datum: - + This option allows to change the clock of the console. Can be used to manipulate time in games. Med det här alternativet kan du ändra klockan på konsolen. Kan användas för att manipulera tiden i spel. - + The number of seconds from the current unix time Antalet sekunder från aktuell Unix-tid - + Language: Språk: - + This option can be overridden when region setting is auto-select - Det här alternativet kan åsidosättas när regioninställningen är automatiskt vald. + Det här alternativet kan åsidosättas när regioninställningen är automatiskt vald - + Region: Region: - + The region of the console. Konsolens region. - + Time Zone: Tidszon: - + The time zone of the console. Konsolens tidszon. - + Sound Output Mode: Ljudutmatningsläge: - + Console Mode: Konsolläge: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. @@ -1246,1031 +1142,1026 @@ Spel ändrar upplösning, detaljer och stödda kontroller beroende på denna ins Inställningen Handhållen kan förbättra prestandan för enklare system. - + Prompt for user profile on boot Fråga efter användarprofil vid uppstart - + Useful if multiple people use the same PC. Användbart om flera personer använder samma dator. - + Pause when not in focus Pausa när inte i fokus - + Pauses emulation when focusing on other windows. Pausar emulering när fokus är på andra fönster. - + Confirm before stopping emulation Bekräfta innan emuleringen stoppas - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. Åsidosätter frågor om att bekräfta att emuleringen ska avslutas. Om du aktiverar den hoppar du över sådana uppmaningar och avslutar emuleringen direkt. - + Hide mouse on inactivity Dölj musen vid inaktivitet - + Hides the mouse after 2.5s of inactivity. Döljer musen efter 2,5 sekunders inaktivitet. - - Disable controller applet - Inaktivera kontroller-appleten - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - Inaktiverar med tvång användningen av kontrollerappletten i emulerade program. -När ett program försöker öppna kontrollerappletten stängs den omedelbart. - - - + Check for updates Leta efter uppdateringar - + Whether or not to check for updates upon startup. Om uppdateringar ska sökas vid start eller inte. - + Enable Gamemode Aktivera Gamemode - + Force X11 as Graphics Backend Tvinga X11 som grafikbackend - + Custom frontend Anpassad frontend - + Real applet Verklig applet - + + + Disabled + Inaktiverad + + + Never Aldrig - + On Load Vid inläsning - + Always Alltid - + CPU CPU - + GPU GPU - + CPU Asynchronous CPU asynkron - + Uncompressed (Best quality) Okomprimerad (bästa kvalitet) - + BC1 (Low quality) BC1 (låg kvalitet) - + BC3 (Medium quality) BC3 (medelhög kvalitet) - - + + Auto Auto - + 30 FPS 30 bilder/s - + 60 FPS 60 bilder/s - + 90 FPS 90 bilder/s - + 120 FPS 120 bilder/s - + Conservative Konservativ - + Aggressive Aggressiv - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) OpenGL GLASM (Assembly Shaders, endast NVIDIA) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) OpenGL SPIR-V (Experimentell, endast AMD/Mesa) - + Null Null - + Fast Snabb - - Balanced - Balanserad - - - - + + + Accurate Exakt - - + + + Default Standard - + Unsafe (fast) Osäker (snabb) - + Safe (stable) Säker (stabil) - + + Immediate + Omedelbar + + + + Balanced + Balanserad + + + Unsafe Inte säker - + Paranoid (disables most optimizations) Paranoid (inaktiverar de flesta optimeringar) - + Debugging Felsökning - + Dynarmic Dynarmisk - + NCE NCE - + Borderless Windowed Ramlöst fönsterläge - + Exclusive Fullscreen Exklusiv helskärm - + No Video Output Ingen videoutgång - + CPU Video Decoding CPU-videoavkodning - + GPU Video Decoding (Default) GPU videoavkodning (standard) - + 0.25X (180p/270p) [EXPERIMENTAL] 0.25X (180p/270p) [EXPERIMENTELL] - + 0.5X (360p/540p) [EXPERIMENTAL] 0.5X (360p/540p) [EXPERIMENTELL] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [EXPERIMENTELL] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] 1.25X (900p/1350p) [EXPERIMENTELL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [EXPERIMENTELL] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor Närmsta granne - + Bilinear Bilinjär - + Bicubic Bikubisk - + Gaussian Gaussisk - + Lanczos Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution AMD FidelityFX Super Resolution - + Area Område - + MMPX MMPX - + Zero-Tangent Zero-Tangent - + B-Spline B-Spline - + Mitchell Mitchell - + Spline-1 Spline-1 - - + + Snapdragon Game Super Resolution + Snapdragon Game Super Resolution + + + + Snapdragon Game Super Resolution EdgeDir + Snapdragon Game Super Resolution EdgeDir + + + None Ingen - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Standard (16:9) - + Force 4:3 Tvinga 4:3 - + Force 21:9 Tvinga 21:9 - + Force 16:10 Tvinga 16:10 - + Stretch to Window Sträck ut till fönster - + Automatic Automatiskt - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - 32x - - - - 64x - 64x - - - + Japanese (日本語) Japanska (日本語) - + American English Amerikansk engelska - + French (français) Franska (français) - + German (Deutsch) Tyska (Deutsch) - + Italian (italiano) Italienska (italiano) - + Spanish (español) Spanska (español) - + Chinese Kinesiska - + Korean (한국어) Koreanska (한국어) - + Dutch (Nederlands) Nederländska (Nederlands) - + Portuguese (português) Portugisiska (português) - + Russian (Русский) Ryska (Русский) - + Taiwanese Taiwanesiska - + British English Brittisk engelska - + Canadian French Kanadensisk franska - + Latin American Spanish Latinamerikansk spanska - + Simplified Chinese Förenklad kinesiska - + Traditional Chinese (正體中文) Traditionell kinesiska (正體中文) - + Brazilian Portuguese (português do Brasil) Brasiliansk portugisiska (português do Brasil) - + Polish (polska) Polska (polska) - + Thai (แบบไทย) Thai (แบบไทย) - - + + Japan Japan - + USA USA - + Europe Europa - + Australia Australien - + China Kina - + Korea Korea - + Taiwan Taiwan - + Auto (%1) Auto select time zone Auto (%1) - + Default (%1) Default time zone Standard (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Kuba - + EET EET - + Egypt Egypten - + Eire Irland - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Irland - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hongkong - + HST HST - + Iceland Island - + Iran Iran - + Israel Israel - + Jamaica Jamaica - + Kwajalein Kwajalein - + Libya Libyen - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navajo - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Polen - + Portugal Portugal - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Singapore - + Turkey Turkiet - + UCT UCT - + Universal Universal - + UTC UTC - + W-SU W-SU - + WET VÅT - + Zulu Zulu - + Mono Mono - + Stereo Stereo - + Surround Surround - + 4GB DRAM (Default) 4 GB DRAM (standard) - + 6GB DRAM (Unsafe) 6 GB DRAM (osäker) - + 8GB DRAM 8 GB DRAM - + 10GB DRAM (Unsafe) 10 GB DRAM (osäker) - + 12GB DRAM (Unsafe) 12 GB DRAM (osäker) - + Docked Dockad - + Handheld Handhållen - - - Off - Av + + + Normal + Normal - - Boost (1700MHz) - Boost (1700MHz) + + + Boost + Förstärk - - Fast (2000MHz) - Snabb (2000 MHz) + + + Overclock + Överklocka - + Always ask (Default) Fråga alltid (standard) - + Only if game specifies not to stop Endast om spelet anger att det inte ska stoppas - + Never ask Fråga aldrig - - - Medium (256) - Medium (256) - - - - - High (512) - Hög (512) - - - + Very Small (16 MB) Mycket liten (16 MB) - + Small (32 MB) Liten (32 MB) - + Normal (128 MB) Normal (128 MB) - + Large (256 MB) Stor (256 MB) - + Very Large (512 MB) Mycket stor (512 MB) - + Very Low (4 MB) Mycket låg (4 MB) - + Low (8 MB) Låg (8 MB) - + Normal (16 MB) Normal (16 MB) - + Medium (32 MB) Medium (32 MB) - + High (64 MB) Hög (64 MB) - + Very Low (32) Mycket låg (32) - + Low (64) Låg (64) - + Normal (128) Normal (128) - - Disabled - Inaktiverad + + Medium (256) + Medium (256) - + + High (512) + Hög (512) + + + ExtendedDynamicState 1 ExtendedDynamicState 1 - + ExtendedDynamicState 2 ExtendedDynamicState 2 - + ExtendedDynamicState 3 ExtendedDynamicState 3 - + Tree View Trädvy - + Grid View Rutnätsvy @@ -2630,252 +2521,297 @@ När ett program försöker öppna kontrollerappletten stängs den omedelbart.Globalt loggfilter - - When checked, the max size of the log increases from 100 MB to 1 GB - När detta markeras ökar maxstorleken på loggen från 100 MB till 1 GB + + GPU Logging/Level + GPU-loggning/-nivå - - Enable Extended Logging** - Aktivera utökad loggning** + + Detail level for GPU logs. Off disables logging entirely. + Detaljnivå för GPU-loggar. Av stänger av loggningen helt. - + + Off + Av + + + + Errors + Fel + + + + Standard + Standard + + + + Verbose + Utförlig + + + + All + Alla + + + Show Log in Console Visa logg i konsolen - + + When checked, the max size of the log increases from 100 MB to 1 GB + När detta markeras ökar maxstorleken på loggen från 100 MB till 1 GB + + + + Enable Extended Logging** + Aktivera utökad loggning** + + + Open Log Location Öppna loggplats - + Homebrew Homebrew - + Arguments String Argument String - + Graphics Grafik - + When checked, it executes shaders without loop logic changes När den är markerad körs shaders utan ändringar i looplogiken - + Disable Loop safety checks Inaktivera säkerhetskontroller för loop - + When checked, it will dump all the macro programs of the GPU När den är markerad kommer den att dumpa alla makroprogram på GPU:n - + Dump Maxwell Macros Dumpa Maxwell-makron - + When checked, it enables Nsight Aftermath crash dumps När den är markerad aktiverar den Nsight Aftermath-kraschdumpar - + Enable Nsight Aftermath Aktivera Nsight Aftermath - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - När den är markerad kommer den att dumpa alla ursprungliga assembler shaders från diskens shader cache eller spelet som hittas + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + När detta är markerat dumpas den ursprungliga Maxwell-gästshaderns bytekod (indata till omkompilatorn) som .ash-filer under DumpDir/shaders/. Användbart för granskning med nvdisasm. - - Dump Game Shaders - Dumpa spel-shaders + + Dump Guest (Maxwell) Shaders + Dumpa gästshaders (Maxwell) - + Enable Renderdoc Hotkey Aktivera snabbtangent för Renderdoc - + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower När den är markerad inaktiveras makrokompilatorn Just In Time. Om du aktiverar detta går spelen långsammare - + Disable Macro JIT Inaktivera Macro JIT - + When checked, it disables the macro HLE functions. Enabling this makes games run slower När den är markerad inaktiveras HLE-funktionerna för makro. Om du aktiverar detta går spelen långsammare - + Disable Macro HLE Inaktivera makro HLE - + When checked, the graphics API enters a slower debugging mode När den är markerad går grafik-API:et in i ett långsammare felsökningsläge - + Enable Graphics Debugging Aktivera felsökning av grafik - + When checked, yuzu will log statistics about the compiled pipeline cache När den är markerad kommer yuzu att logga statistik om den kompilerade pipeline-cachen - + Enable Shader Feedback Aktivera shader-återkoppling - + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> <html><head/><body><p>När den är markerad inaktiveras omordning av uppladdningar i mappat minne, vilket gör det möjligt att associera uppladdningar med specifika dragningar. Kan minska prestandan i vissa fall.</p></body></html> - + Disable Buffer Reorder Inaktivera omordning av buffertar - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + När detta är markerat dumpas omkompilatorns SPIR-V-binärer (.spv) under LogDir/shaders/. Granska dem med SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) eller RenderDoc. + + + + Dump SPIR-V Shaders + Dumpa SPIR-V-shaders + + + Advanced Avancerat - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Aktiverar yuzu för att kontrollera att Vulkan-miljön fungerar när programmet startas. Inaktivera detta om det orsakar problem med att externa program ser yuzu. - + Perform Startup Vulkan Check Utför Vulkan-kontroll vid uppstart - + Disable Web Applet Inaktivera webbapplet - + Enable All Controller Types Aktivera alla typer av kontroller - + Enable Auto-Stub Aktivera Auto-Stub - + Kiosk (Quest) Mode Kiosk-läge (Quest) - + Use dev.keys Använd dev.keys - + Enable Debug Asserts Aktivera Debug Asserts - + Debugging Felsökning - + Battery Serial: Batteriets serienummer: - + + Debug Knobs: + Felsökningsreglage: + + + Bitmask for quick development toggles Bitmask för snabba utvecklingsväxlingar - + Set debug knobs (bitmask) Ställ in felsökningsknoppar (bitmask) - + 16-bit debug knob set for quick development toggles Ställ in 16-bitars felsökningsknoppar för snabba utvecklingsväxlingar - + (bitmask) (bitmask) - - Debug Knobs: - Felsökningsknoppar: - - - + Unit Serial: Enhetens serienummer: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. Aktivera detta för att skicka ut den senaste genererade ljudkommandolistan till konsolen. Påverkar endast spel som använder ljudrenderaren. - + Dump Audio Commands To Console** Dumpa ljudkommandon till konsolen** - + Flush log output on each line Spola loggutmatningen på varje rad - + Enable FS Access Log Aktivera FS Access Log - + Enable Verbose Reporting Services** Aktivera Verbose Reporting Services** - + Censor username in logs Censurera användarnamn i loggar - + **This will be reset automatically when Eden closes. **Detta återställs automatiskt när Eden stänger. @@ -3046,7 +2982,7 @@ När ett program försöker öppna kontrollerappletten stängs den omedelbart. ... - ... + … @@ -3121,33 +3057,33 @@ När ett program försöker öppna kontrollerappletten stängs den omedelbart. Select Emulated NAND Directory... - Välj katalog för emulerad NAND... + Välj katalog för emulerad NAND… Select Emulated SD Directory... - Välj katalog för emulerad SD... + Välj katalog för emulerad SD… Select Save Data Directory... - Välj katalog för sparat data... + Välj katalog för sparat data… Select Gamecard Path... - Välj sökväg för Gamecard... + Välj sökväg för Gamecard… Select Dump Directory... - Välj dumpkatalog... + Välj dumpkatalog… Select Mod Load Directory... - Välj katalog för modinläsningar... + Välj katalog för modinläsningar… @@ -3205,7 +3141,7 @@ Till: %2 Migrating save data... - Migrerar sparat data... + Migrerar sparat data… @@ -3296,7 +3232,7 @@ Vill du ta bort gammalt sparat data? Select External Content Directory... - Välj katalog för externt innehåll... + Välj katalog för externt innehåll… @@ -3337,33 +3273,38 @@ Vill du ta bort gammalt sparat data? Bakgrundsfärg: - + % - FSR sharpening percentage (e.g. 50%) + FSR/SGSR sharpening percentage (e.g. 50%) % - + + Configure Effects... + Konfigurera effekter… + + + Off Av - + VSync Off VSync Av - + Recommended Rekommenderad - + On På - + VSync On VSync På @@ -3884,6 +3825,16 @@ Vill du ta bort gammalt sparat data? Motion / Touch Rörelse / beröring + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + Avsedd att stänga av SDL:s GUIDE-knappshack: syntetisk GUIDE(HOME)-händelse när SELECT(MINUS) + START(PLUS) trycks ned. Kan påverka Windows-relaterade avtryckare, vibrationer med mera + + + + Disable SDL WGI/XInput (Requires restart) + Inaktivera SDL WGI/XInput (kräver omstart) + ConfigureInputPerGame @@ -4076,7 +4027,7 @@ Vill du ta bort gammalt sparat data? Modifier Range: 0% - Modifieringsintervall: 0% + Modifieringsintervall: 0 % @@ -4260,7 +4211,7 @@ Vill du ta bort gammalt sparat data? Choose a value between 0% and 100% - Välj ett värde mellan 0% och 100% + Välj ett värde mellan 0 % och 100 % @@ -4298,13 +4249,13 @@ Om du vill invertera axlarna för du joysticken först vertikalt och sedan horis Deadzone: %1% - Dödzon: %1% + Dödzon: %1 % Modifier Range: %1% - Modifieringsintervall: %1% + Modifieringsintervall: %1 % @@ -4393,58 +4344,58 @@ Om du vill invertera axlarna för du joysticken först vertikalt och sedan horis [väntar] - + New Profile Ny profil - + Enter a profile name: Ange ett profilnamn: - - + + Create Input Profile Skapa inmatningsprofil - + The given profile name is not valid! Det angivna profilnamnet är inte giltigt! - + Failed to create the input profile "%1" Misslyckades med att skapa inmatningsprofilen "%1" - + Delete Input Profile Radera inmatningsprofil - + Failed to delete the input profile "%1" Misslyckades med att ta bort inmatningsprofilen "%1" - + Load Input Profile Läs in inmatningsprofil - + Failed to load the input profile "%1" Misslyckades med att läsa in inmatningsprofilen "%1" - + Save Input Profile Spara inmatningsprofil - + Failed to save the input profile "%1" Misslyckades med att spara inmatningsprofilen "%1" @@ -4492,7 +4443,7 @@ Om du vill invertera axlarna för du joysticken först vertikalt och sedan horis - + Configure Konfigurera @@ -4523,7 +4474,7 @@ Om du vill invertera axlarna för du joysticken först vertikalt och sedan horis - + Test Testa @@ -4538,77 +4489,77 @@ Om du vill invertera axlarna för du joysticken först vertikalt och sedan horis Ta bort server - + %1:%2 %1:%2 - - - - - - + + + + + + Eden Eden - + Port number has invalid characters Portnumret har ogiltiga tecken - + Port has to be in range 0 and 65353 Port måste ligga inom intervallet 0 och 65353 - + IP address is not valid IP-adressen är inte giltig - + This UDP server already exists Denna UDP-server finns redan - + Unable to add more than 8 servers Det går inte att lägga till fler än 8 servrar - + Testing Testar - + Configuring Konfigurerar - + Test Successful Testet lyckades - + Successfully received data from the server. Tog emot data från servern. - + Test Failed Testet misslyckades - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Kunde inte ta emot giltiga data från servern.<br>Kontrollera att servern är korrekt konfigurerad och att adress och port är korrekta. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. UDP Test- eller kalibreringskonfiguration pågår.<br>Vänta tills de är klara. @@ -4693,8 +4644,8 @@ Om du vill invertera axlarna för du joysticken först vertikalt och sedan horis Mouse panning works better with a deadzone of 0% and a range of 100%. Current values are %1% and %2% respectively. - Muspanorering fungerar bättre med en dödzon på 0% a och ett intervall på 100%. -Nuvarande värden är %1% respektive %2%. + Muspanorering fungerar bättre med en dödzon på 0 % och ett intervall på 100 %. +Nuvarande värden är %1 % respektive %2 %. @@ -4793,57 +4744,57 @@ Nuvarande värden är %1% respektive %2%. Vissa inställningar är endast tillgängliga när ett spel inte är igång. - + Add-Ons Tillägg - + System System - + CPU CPU - + Graphics Grafik - + Adv. Graphics Avancerad grafik - + Ext. Graphics Ext. grafik - + Audio Ljud - + Input Profiles Inmatningsprofiler - + Network Nätverk - + Applets Appletar - + Properties Egenskaper @@ -4943,7 +4894,7 @@ För att ta bort NAND-installerade uppdateringar, högerklicka på spelet i spel Once deleted, these can NOT be recovered. Are you 100% sure you want to delete them? -När de har tagits bort kan de INTE återställas. Är du 100% säker på att du vill ta bort dem? +När de har tagits bort kan de INTE återställas. Är du 100 % säker på att du vill ta bort dem? @@ -4971,6 +4922,131 @@ När de har tagits bort kan de INTE återställas. Är du 100% säker på att du Ö&ppna i filhanterare + + ConfigurePostProcessing + + + Post-Processing Effects + Efterbearbetningseffekter + + + + Changes apply immediately while a game is running. + Ändringarna tillämpas omedelbart medan ett spel körs. + + + + Enable post-processing + Aktivera efterbearbetning + + + + Preset: + Förinställning: + + + + Apply + Tillämpa + + + + Reset Values + Återställ värden + + + + Return every effect to the values the preset ships with. + Återställ varje effekt till de värden som förinställningen levereras med. + + + + Save As... + Spara som… + + + + Save Preset + Spara förinställning + + + + Preset name: + Förinställningsnamn: + + + + Clear + Rensa + + + + Remove every effect in use and deselect the preset. + Ta bort alla använda effekter och avmarkera förinställningen. + + + + Add Effect + Lägg till effekt + + + + None + Ingen + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + Inga effekter används. Välj en förinställning och tryck på Verkställ, eller lägg till effekter en i taget. + + + + Custom chain. Pick a preset above and press Apply to replace it. + Anpassad kedja. Välj en förinställning ovan och tryck på Tillämpa för att ersätta den. + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + Aktiv förinställning: %1 (ändrad). Tryck på Återställ värden för att återgå till ursprungsvärdena. + + + + Active preset: %1 + Aktiv förinställning: %1 + + + + Enabled + Aktiverad + + + + Disabled + Inaktiverad + + + + Reset to defaults + Återställ standardvärden + + + + Effect '%1' was not found. + Effekten ”%1” hittades inte. + + + + Effect failed to compile: +%1 + Effekten kunde inte kompileras: +%1 + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + Inga användbara ReShade FX-effekter hittades. Placera .fx-filer i mappen post_shaders. + + ConfigureProfileManager @@ -5182,7 +5258,7 @@ UUID: %2 Deadzone: %1% - Dödzon: %1% + Dödzon: %1 % @@ -5309,7 +5385,7 @@ UUID: %2 ... - ... + … @@ -5322,7 +5398,7 @@ UUID: %2 Select TAS Load Directory... - Välj katalog för TAS-inläsningar... + Välj katalog för TAS-inläsningar… @@ -5538,91 +5614,86 @@ Dra punkterna för att ändra position, eller dubbelklicka på tabellcellerna f - Show Compatibility List - Visa kompatibilitetslista - - - Show Add-Ons Column Visa tilläggskolumn - + Show Size Column Visa storlek kolumn - + Show File Types Column Visa filtypskolumnen - + Show Play Time Column Visa speltidskolumn - + Folder Icon Size: Ikonstorlek för mapp: - + Row 1 Text: Rad 1-text: - + Row 2 Text: Rad 2-text: - + Screenshots Skärmbilder - + Ask Where To Save Screenshots (Windows Only) Fråga var du vill spara skärmdumpar (endast Windows) - + Screenshots Path: Sökväg för skärmdumpar: - + ... - ... + … - + TextLabel TextLabel - + Resolution: Status: - + Select Screenshots Path... - Välj sökväg för skärmdumpar... + Välj sökväg för skärmdumpar… - + <System> <System> - + English Engelska - + Auto (%1 x %2, %3 x %4) Screenshot width value Auto (%1 x %2, %3 x %4) @@ -5808,12 +5879,12 @@ Dra punkterna för att ändra position, eller dubbelklicka på tabellcellerna f User NAND - + Användar-NAND System NAND - + System-NAND @@ -5859,9 +5930,9 @@ Dra punkterna för att ändra position, eller dubbelklicka på tabellcellerna f Importera data för denna katalog. Detta kan ta en stund och kommer att ta bort ALLT BEFINTLIGT DATA! - + Calculating... - Beräknar... + Beräknar… @@ -6063,330 +6134,323 @@ Gå till Konfigurera -> System -> Nätverk och gör ett val. GRenderWindow - - + + OpenGL not available! OpenGL är inte tillgängligt! - + OpenGL shared contexts are not supported. Delade OpenGL-kontexter stöds inte. - + Eden has not been compiled with OpenGL support. Eden har inte kompilerats med OpenGL-stöd. - - - + + + Error while initializing OpenGL! Fel vid initiering av OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Din GPU kanske inte stöder OpenGL, eller så har du inte den senaste grafikdrivrutinen. - + Error while initializing OpenGL 4.6! Fel vid initiering av OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Din GPU kanske inte stöder OpenGL 4.6, eller så har du inte den senaste grafikdrivrutinen.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Din GPU kanske inte stöder ett eller flera av de nödvändiga OpenGL-tilläggen. Se till att du har den senaste grafikdrivrutinen.<br><br>GL Renderer:<br>%1<br><br>Tillägg som inte stöds:<br>%2 - + This build doesn't have OpenGL support. - + Den här versionen saknar stöd för OpenGL. GameList - + &Add New Game Directory &Lägg till ny spelkatalog - + Favorite Favorit - + Start Game Starta spel - + Start Game without Custom Configuration Starta spelet utan anpassad konfiguration - + Open Save Data Location Öppna plats för sparat data - + Open Mod Data Location Öppna plats för moddata - + Open Transferable Pipeline Cache Öppna cache för överförbar pipeline - + Link to Ryujinx Länka till Ryujinx - + Remove Ta bort - + Remove Installed Update Ta bort installerad uppdatering - + Remove All Installed DLC Ta bort alla installerade DLC - + Remove Custom Configuration Ta bort anpassad konfiguration - + Remove Cache Storage Ta bort cache-lagring - + Remove OpenGL Pipeline Cache Ta bort OpenGL Pipeline Cache - + Remove Vulkan Pipeline Cache Ta bort Vulkan Pipeline Cache - + Remove All Pipeline Caches Ta bort alla pipeline-cacher - + Remove All Installed Contents Ta bort allt installerat innehåll - + Manage Play Time Hantera speltid - + Edit Play Time Data Redigera data för speltid - + Remove Play Time Data Ta bort data om speltid - - + + Dump RomFS Dumpa RomFS - + Dump RomFS to SDMC Dumpa RomFS till SDMC - + Verify Integrity Verifiera integritet - + Copy Title ID to Clipboard Kopiera titel-id till urklipp - - Navigate to GameDB entry - Navigera till GameDB-post - - - + Create Shortcut Skapa genväg - + Add to Desktop Lägg till på skrivbordet - + Add to Applications Menu Lägg till i programmenyn - + Configure Game Konfigurera spelet - + Scan Subfolders Sök igenom undermappar - + Remove Game Directory Ta bort spelkatalog - + ▲ Move Up ▲ Flytta upp - + ▼ Move Down ▼ Flytta ner - + Open Directory Location Öppna katalogplats - + Clear Rensa - - - Name - Namn - - - - Compatibility - Kompatibilitet - - - - Add-ons - Tillägg - - - - File type - Filtyp - - - - Size - Storlek - - - - Play time - Speltid - GameListItemCompat - + Ingame Spelproblem - + Game starts, but crashes or major glitches prevent it from being completed. Spelet startar men kraschar eller större fel gör att det inte kan slutföras. - + Perfect Perfekt - + Game can be played without issues. Spelet kan spelas utan problem. - + Playable Spelbart - + Game functions with minor graphical or audio glitches and is playable from start to finish. Spelet fungerar med små grafiska eller ljudmässiga fel och är spelbart från början till slut. - + Intro/Menu Intro/Meny - + Game loads, but is unable to progress past the Start Screen. Spelet läses in men det går inte att komma förbi startskärmen. - + Won't Boot Startar inte - + The game crashes when attempting to startup. Spelet kraschar när det försöker starta. - + Not Tested Inte testat - + The game has not yet been tested. Spelet har ännu inte testats. + + GameListModel + + + Name + Namn + + + + Add-ons + Tillägg + + + + File type + Filtyp + + + + Size + Storlek + + + + Play time + Speltid + + GameListPlaceholder - + Double-click to add a new folder to the game list Dubbelklicka för att lägga till en ny mapp i spellistan @@ -6394,17 +6458,17 @@ Gå till Konfigurera -> System -> Nätverk och gör ett val. GameListSearchField - + %1 of %n result(s) %1 av %n resultat%1 av %n resultat - + Filter: Filtrera: - + Enter pattern to filter Ange mönster för att filtrera @@ -6750,7 +6814,7 @@ Felsökningsmeddelande: Loading... - Läser in... + Läser in… @@ -6760,7 +6824,7 @@ Felsökningsmeddelande: Launching... - Startar... + Startar… @@ -6900,772 +6964,780 @@ Felsökningsmeddelande: Läge för s&pellista - + Game &Icon Size Storlek för spel&ikon - + Reset Window Size to &720p Återställ fönsterstorlek till &720p - + Reset Window Size to 720p Återställ fönsterstorlek till 720p - + Reset Window Size to &900p Återställ fönsterstorlek till &900p - + Reset Window Size to 900p Återställ fönsterstorleken till 900p - + Reset Window Size to &1080p Återställ fönsterstorlek till &1080p - + Reset Window Size to 1080p Återställ fönsterstorleken till 1080p - + &Multiplayer &Flera spelare - + &Tools Ver&ktyg - + Am&iibo Am&iibo - + Launch &Applet Starta &applet - + &TAS &TAS - + &Create Home Menu Shortcut S&kapa genväg till startmenyn - + Install &Firmware Installera &firmware - + &Help &Hjälp - + &Install Files to NAND... - &Installera filer till NAND... + &Installera filer till NAND… - + L&oad File... - Läs &in fil... + Läs &in fil… - + Load &Folder... - Läs in &mapp... + Läs in &mapp… - + E&xit A&vsluta - - + + &Pause &Paus - + &Stop &Stoppa - + &Verify Installed Contents &Verifiera installerat innehåll - + &About Eden &Om Eden - + Single &Window Mode Enkelt &fönsterläge - + Con&figure... - Kon&figurera... + Kon&figurera… - + Ctrl+, Ctrl+, - + Enable Overlay Display Applet Aktivera applet för överläggsvisning - + Show &Filter Bar Visa &filterfält - + Show &Status Bar Visa &statusrad - + Show Status Bar Visa statusrad - + &Browse Public Game Lobby &Bläddra i offentlig spellobby - + &Create Room &Skapa rum - + &Leave Room &Lämna rummet - + &Direct Connect to Room &Direkt anslutning till rum - + &Show Current Room &Visa aktuellt rum - + F&ullscreen H&elskärm - + &Restart Starta o&m - + Load/Remove &Amiibo... - Läs in/ta bort &Amiibo... + Läs in/ta bort &Amiibo… - - &Report Compatibility - &Rapportera kompatibilitet - - - + Open &Mods Page Öppna &Mods-sidan - - Open &Quickstart Guide - Öppna &snabbstartsguide + + &User Handbook + &Användarhandbok - - &FAQ - &Frågor och svar - - - + &Capture Screenshot &Ta skärmdump - + &Album &Album - + &Set Nickname and Owner &Ange smeknamn och ägare - + &Delete Game Data &Radera speldata - + &Restore Amiibo Å&terställ Amiibo - + &Format Amiibo &Formatera Amiibo - + &Mii Editor &Mii-redigerare - + &Configure TAS... - &Konfigurera TAS... + &Konfigurera TAS… - + Configure C&urrent Game... - Konfigurera a&ktuellt spel... + Konfigurera a&ktuellt spel… - - + + &Start &Starta - + &Reset Starta &om - - + + R&ecord Spela &in - + Open &Controller Menu Öppna &kontrollermenyn - + Install Decryption &Keys Installera avkrypteringsn&ycklar - + &Home Menu &Hemmeny - + &Desktop S&krivbord - + &Application Menu &Applikationsmeny - + &Root Data Folder &Rotdatamapp - + &NAND Folder &NAND-mapp - + &SDMC Folder &SDMC-mapp - + &Mod Folder &Mod-mapp - + &Log Folder &Loggmapp - + From Folder Från mapp - + From ZIP Från ZIP - + &Eden Dependencies &Edens beroenden - + &Data Manager &Datahanterare - + &Tree View &Trädvy - + &Grid View &Rutnätsvy - + Game Icon Size Storlek för spelikon - - + + None Ingen - + Show Game &Name Visa spel&namn - + Show &Performance Overlay Visa &prestandaöverlägg - + + Post-Processing &Shaders... + Efterbearbetning och &skuggning… + + + + &Carousel View + &Karusellvy + + + Small (32x32) Liten (32x32) - + Standard (64x64) Standard (64x64) - + Large (128x128) Stor (128x128) - + Full Size (256x256) Full storlek (256x256) - + Broken Vulkan Installation Detected Felaktig Vulkan-installation upptäcktes - + Vulkan initialization failed during boot. Vulkan-initialiseringen misslyckades under uppstarten. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping Kör ett spel - + Loading Web Applet... - Läser in webbapplet... + Läser in webbapplet… - - + + Disable Web Applet Inaktivera webbapplet - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) Att inaktivera webbappletten kan leda till odefinierat beteende och bör endast användas med Super Mario 3D All-Stars. Är du säker på att du vill inaktivera webbappletten? (Detta kan återaktiveras i felsökningsinställningarna.) - + The amount of shaders currently being built Antalet shaders som för närvarande byggs - + The current selected resolution scaling multiplier. Den aktuella valda multiplikatorn för upplösningsskalning. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - Aktuell emuleringshastighet. Värden högre eller lägre än 100% indikerar att emuleringen körs snabbare eller långsammare än en Switch. + Aktuell emuleringshastighet. Värden högre eller lägre än 100 % indikerar att emuleringen körs snabbare eller långsammare än en Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. Hur många bildrutor per sekund spelet för närvarande visar. Detta varierar från spel till spel och från scen till scen. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. Tid som krävs för att emulera en Switch-bildruta, exklusive bildbegränsning eller v-synkronisering. För emulering i full hastighet bör detta vara högst 16,67 ms. - + Unmute Aktivera ljud - + Mute Tyst - + Reset Volume Återställ volym - + + Configure Effects... + Konfigurera effekter… + + + &Clear Recent Files &Töm tidigare filer - + &Continue &Fortsätt - + Warning: Outdated Game Format Varning: Föråldrat spelformat - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. Du använder det dekonstruerade ROM-katalogformatet för detta spel, vilket är ett föråldrat format som har ersatts av andra format såsom NCA, NAX, XCI eller NSP. Dekonstruerade ROM-kataloger saknar ikoner, metadata och uppdateringsstöd.<br> För en förklaring av de olika Switch-format som Eden har stöd för, se vår användarhandbok. Detta meddelande kommer inte att visas igen. - - + + Error while loading ROM! Fel vid inläsning av ROM! - + The ROM format is not supported. ROM-formatet stöds inte. - + An error occurred initializing the video core. Ett fel uppstod vid initialiseringen av videokärnan. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. - Eden har stött på ett fel vid körning av videokärnan. Detta orsakas vanligtvis av föråldrade GPU-drivrutiner, inklusive integrerade sådana. Se loggen för mer information. För mer information om hur du kommer åt loggen, se följande sida: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>Hur man laddar upp loggfilen</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. + Detta orsakas vanligtvis av inaktuella grafikdrivrutiner. Se loggen för mer information. Se: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>Så här kommer du åt loggfiler</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. Fel vid inläsning av ROM! %1 - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. %1<br>Dumpa dina filer igen eller fråga på Discord/Stoat för hjälp. - + An unknown error occurred. Please see the log for more details. Ett okänt fel har uppstått. Se loggen för mer information. - + + (64-bit) (64-bitar) - + + (32-bit) (32-bitar) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit %1 %2 - + Closing software... - Stänger programvara... + Stänger programvara… - + Save Data Sparat data - + Mod Data Mod-data - + Error Opening %1 Folder Fel vid öppning av mappen %1 - - + + Folder does not exist! Mappen finns inte! - + Remove Installed Game Contents? Ta bort installerat spelinnehåll? - + Remove Installed Game Update? Ta bort installerad speluppdatering? - + Remove Installed Game DLC? Ta bort installerat spel-DLC? - + Remove Entry Ta bort post - + Delete OpenGL Transferable Shader Cache? Ta bort OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? Ta bort Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? Ta bort alla Transferable Shader Caches? - + Remove Custom Game Configuration? Ta bort anpassad spelkonfiguration? - + Remove Cache Storage? Ta bort cachelagring? - + Remove File Ta bort fil - + Remove Play Time Data Ta bort data om speltid - + Reset play time? Nollställ speltid? - - + + RomFS Extraction Failed! Extrahering av RomFS misslyckades! - + There was an error copying the RomFS files or the user cancelled the operation. Det uppstod ett fel vid kopieringen av RomFS-filerna eller så avbröt användaren åtgärden. - + Full Fullständigt - + Skeleton Skelett - + Select RomFS Dump Mode Välj dumpläge för RomFS - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. Välj hur du vill att RomFS ska dumpas. <br>Fullständigt kopierar alla filer till den nya katalogen, medan <br>skelett endast skapar katalogstrukturen. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - Det finns inte tillräckligt med ledigt utrymme på %1 för att extrahera RomFS. Frigör utrymme eller välj en annan dumpkatalog under Emulering > Konfigurera > System > Filsystem > Dumprot. + Det finns inte tillräckligt med ledigt utrymme på %1 för att extrahera RomFS. Frigör utrymme eller välj en annan dumpkatalog under Emulering > Konfigurera > System > Filsystem > Dumprot - + Extracting RomFS... - Extraherar RomFS... + Extraherar RomFS… - - + + Cancel Avbryt - + RomFS Extraction Succeeded! Extrahering av RomFS lyckades! - + The operation completed successfully. Operationen slutfördes. - + Error Opening %1 Fel vid öppning av %1 - + Select Directory Välj katalog - + Properties Egenskaper - + The game properties could not be loaded. Spelegenskaperna kunde inte läsas in. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. Körbar Switch-fil (%1);;Alla filer (*.*) - + Load File Läs in fil - + Open Extracted ROM Directory Öppna katalog för extraherad ROM - + Invalid Directory Selected Ogiltig katalog valdes - + The directory you have selected does not contain a 'main' file. Den katalog du har valt innehåller ingen ”main”-fil. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) Installerbar Switch-fil (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files Installera filer - + %n file(s) remaining %n fil återstår%n filer återstår - + Installing file "%1"... - Installerar filen "%1"... + Installerar filen "%1"… - - + + Install Results Installationsresultat - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. För att undvika eventuella konflikter avråder vi användare från att installera basspel på NAND. Använd endast denna funktion för att installera uppdateringar och DLC. - + %n file(s) were newly installed %n ny fil installerades @@ -7673,7 +7745,7 @@ Använd endast denna funktion för att installera uppdateringar och DLC. - + %n file(s) were overwritten %n fil skrevs över @@ -7681,7 +7753,7 @@ Använd endast denna funktion för att installera uppdateringar och DLC. - + %n file(s) failed to install %n fil gick inte att installera @@ -7689,361 +7761,324 @@ Använd endast denna funktion för att installera uppdateringar och DLC. - + System Application Systemapplikation - + System Archive Systemarkiv - + System Application Update Uppdatering för systemapplikation - + Firmware Package (Type A) Firmware-paket (Type A) - + Firmware Package (Type B) Firmware-paket (Type B) - + Game Spel - + Game Update Speluppdatering - + Game DLC DLC för spel - + Delta Title Deltatitel - + Select NCA Install Type... - Välj NCA-installationstyp... + Välj NCA-installationstyp… - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) Välj vilken typ av titel du vill installera denna NCA som: (I de flesta fall är standardinställningen ”Spel” tillräcklig.) - + Failed to Install Misslyckades med att installera - + The title type you selected for the NCA is invalid. Den titeltypen du valt för NCA är ogiltig. - + File not found Filen hittades inte - + File "%1" not found Filen "%1" hittades inte - + OK Ok - + Function Disabled Funktion inaktiverad - + Compatibility list reporting is currently disabled. Check back later! Rapportering till kompatibilitetslistan är för närvarande inaktiverad. Kom tillbaka senare! - + Error opening URL Fel vid öppning av URL - + Unable to open the URL "%1". Det går inte att öppna URL:en ”%1”. - + TAS Recording TAS-inspelning - + Overwrite file of player 1? Skriv över fil för spelare 1? - + Invalid config detected Ogiltig konfiguration upptäcktes - + Handheld controller can't be used on docked mode. Pro controller will be selected. Handhållen kontroller kan inte användas i dockat läge. Pro-kontrollern kommer att väljas. - - + + Amiibo Amiibo - - + + The current amiibo has been removed - Den aktuella amiibo har tagits bort. + Den aktuella amiibo har tagits bort - + Error Fel - - + + The current game is not looking for amiibos Det aktuella spelet letar inte efter amiibos - + Amiibo File (%1);; All Files (*.*) Amiibo-fil (%1);; Alla filer (*.*) - + Load Amiibo Läs in Amiibo - + Error loading Amiibo data Fel vid läsning av Amiibo-data - + The selected file is not a valid amiibo - Den valda filen är inte en giltig amiibo. + Den valda filen är inte en giltig amiibo - + The selected file is already on use Den valda filen används redan - + An unknown error occurred Ett okänt fel uppstod - - - Keys not installed - Nycklar inte installerade - - - - - Install decryption keys and restart Eden before attempting to install firmware. - Installera avkrypteringsnycklar och starta om Eden innan du försöker installera firmware. - - - - Select Dumped Firmware Source Location - Välj plats för dumpad firmware-källa - - - - Select Dumped Firmware ZIP - Välj dumpad firmware-ZIP - - - - Zipped Archives (*.zip) - Zippade arkiv (*.zip) - - - - Firmware cleanup failed - Uppstädning av firmware misslyckades - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - Det gick inte att rensa den extraherade firmware-cachen. -Kontrollera skrivbehörigheten i systemets temporära katalog och försök igen. -OS rapporterade fel: %1 - - - + No firmware available Ingen firmware tillgänglig - + Firmware Corrupted Firmware är skadat - + Unknown applet Okänd applet - + Applet doesn't map to a known value. Appleten mappar inte till ett känt värde. - + Record not found Posten hittades inte - + Applet not found. Please reinstall firmware. Appleten hittades inte. Installera om fast programvara. - + Capture Screenshot Ta skärmbild - + PNG Image (*.png) PNG-bild (*.png) - + TAS state: Running %1/%2 TAS-tillstånd: Kör %1/%2 - + TAS state: Recording %1 TAS-tillstånd: Spelar in %1 - + TAS state: Idle %1/%2 TAS-tillstånd: Overksam %1/%2 - + TAS State: Invalid TAS-tillstånd: Ogiltig - + &Stop Running &Stoppa körning - + Stop R&ecording Stoppa i&nspelning - + Building: %n shader(s) Bygger: %n shaderBygger: %n shaders - + Scale: %1x %1 is the resolution scaling factor Skala: %1x - + Speed: %1% / %2% - Hastighet: %1% / %2% + Hastighet: %1 % / %2 % - + Speed: %1% - Hastighet: %1% + Hastighet: %1 % - + Game: %1 FPS Spel: %1 bilder/s - + Frame: %1 ms Bildruta: %1 ms - - - FSR - FSR - - - + NO AA NO AA - + + NO FX + INGA FX + + + + FX + FX + + + VOLUME: MUTE VOLYM: TYST - + VOLUME: %1% Volume percentage (e.g. 50%) - VOLYM: %1% + VOLYM: %1 % - + Derivation Components Missing Deriveringskomponenter saknas - + Decryption keys are missing. Install them now? Avkrypteringsnycklar saknas. Installera dem nu? - + Wayland Detected! Wayland upptäcktes! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -8054,74 +8089,74 @@ Det rekommenderas att använda X11 istället. Vill du tvinga det för framtida starter? - + Use X11 Använd X11 - + Continue with Wayland Fortsätt med Wayland - + Don't show again Visa inte igen - + Restart Required Omstart krävs - + Restart Eden to apply the X11 backend. Starta om Eden för att tillämpa X11-backend. - + Slow Långsam - + Turbo Turbo - + Unlocked Upplåst - + Select RomFS Dump Target Välj RomFS-dumpmål - + Please select which RomFS you would like to dump. Välj vilken RomFS du vill dumpa. - + Are you sure you want to close Eden? Är du säker på att du vill stänga Eden? - - - + + + Eden Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. Är du säker på att du vill stoppa emuleringen? Alla osparade framsteg kommer att gå förlorade. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8189,6 +8224,11 @@ Vill du kringgå detta och stänga ändå? ScaleForce ScaleForce + + + FSR + FSR + Area @@ -8200,52 +8240,57 @@ Vill du kringgå detta och stänga ändå? MMPX - + + SGSR + SGSR + + + + SGSR EdgeDir + SGSR EdgeDir + + + Docked Dockad - + Handheld Handhållen - + Fast Snabb - - Balanced - Balanserad - - - + Accurate Noggrann - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL SPIRV OpenGL SPIRV - + OpenGL GLASM OpenGL GLASM - + Null Null @@ -8288,7 +8333,7 @@ Om du vill rensa upp bland de filer som låg kvar på den gamla dataplatsen kan %1 - + Data was migrated successfully. Datamigrering lyckades. @@ -8298,7 +8343,7 @@ Om du vill rensa upp bland de filer som låg kvar på den gamla dataplatsen kan Import Mods - + Importera moddar @@ -8466,7 +8511,7 @@ Fortsätt ändå? UUID must be 32 hex characters (0-9, A-F) - UUID måste vara 32 hexadecimala tecken (0-9, A-F) + UUID måste vara 32 hexadecimala tecken (0 till 9, A-F) @@ -8550,7 +8595,7 @@ Fortsätt ändå? Must be 32 hex characters (0-9, a-f) Tooltip - Måste vara 32 hexadecimala tecken (0-9, a-f) + Måste vara 32 hexadecimala tecken (0 till 9, a-f) @@ -8756,7 +8801,7 @@ p, li { white-space: pre-wrap; } Migrating, this may take a while... - Migrerar, det kan ta ett tag... + Migrerar, det kan ta ett tag… @@ -8781,8 +8826,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8795,10 +8840,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8813,9 +8858,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8977,50 +9022,50 @@ p, li { white-space: pre-wrap; } [odefinierad] - + %1%2 %1%2 - + [invalid] [ogiltig] - - + + %1%2Hat %3 %1%2Hatt %3 - - - + + + %1%2Axis %3 %1%2Axel %3 - + %1%2Axis %3,%4,%5 %1%2Axel %3,%4,%5 - + %1%2Motion %3 %1%2Motion %3 - - + + %1%2Button %3 %1%2Knapp %3 - + [unused] [oanvänd] @@ -9146,47 +9191,47 @@ p, li { white-space: pre-wrap; } %1 spelar %2 - + Play Time: %1 Speltid: %1 - + Never Played Aldrig spelat - + Version: %1 Version: %1 - + Version: 1.0.0 Version: 1.0.0 - + Installed SD Titles Installerade SD-titlar - + Installed NAND Titles Installerade NAND-titlar - + System Titles Systemtitlar - + Add New Game Directory Lägg till ny spelkatalog - + Favorites Favoriter @@ -9271,7 +9316,7 @@ p, li { white-space: pre-wrap; } ... - ... + … @@ -9307,26 +9352,26 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware Spelet kräver firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. - Spelet som du försöker starta kräver firmware för att starta eller komma förbi startmenyn. <a href='https://yuzu-mirror.github.io/help/quickstart'>Dumpa och installera firmware</a> eller tryck på ”OK” för att starta ändå. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. + Det här spelet kan kräva fast programvara för att fungera korrekt, men du har ingen installerad. Installera fast programvara innan du startar spelet, eller tryck på "OK" för att starta ändå. Installing Firmware... - Installerar firmware... + Installerar firmware… - - - - + + + + Cancel Avbryt @@ -9347,7 +9392,7 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 @@ -9356,207 +9401,242 @@ p, li { white-space: pre-wrap; } %1 - - + + Verifying integrity... - Verifierar integritet... + Verifierar integritet… - - + + Integrity verification succeeded! Integritetsverifieringen lyckades! - - + + The operation completed successfully. Operationen slutfördes utan problem. - - + + Integrity verification failed! Integritetsverifieringen misslyckades! - + File contents may be corrupt or missing. Filens innehåll kan vara skadat eller saknas. - + Integrity verification couldn't be performed Integritetsverifiering kunde inte utföras - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. Firmwareinstallationen avbruten, firmware kan vara i dåligt skick eller skadad. Filens innehåll kunde inte kontrolleras för giltighet. - + Select Dumped Keys Location Välj plats för dumpade nycklar - + Decryption Keys install succeeded Installation av avkrypteringsnycklar lyckades - + Decryption Keys install failed Installationen av avkrypteringsnycklar misslyckades - + Orphaned Profiles Detected! Föräldralösa profiler upptäcktes! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - OVÄNTADE PROBLEM KAN UPPSTÅ OM DU INTE LÄSER DETTA! <br>Eden har upptäckt följande sparningskataloger utan bifogade profiler:<br>%1<br><br>Följande profiler är giltiga:<br>%2<br><br>Klicka på ”OK” för att öppna din sparningsmapp och fixa dina profiler.<br>Tips: kopiera innehållet i den största eller senast ändrade mappen till en annan plats, ta bort alla övergivna profiler och flytta det kopierade innehållet till den giltiga profilen.<br><br>Fortfarande förvirrad? Se hjälpsidan<a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>.<br> + OVÄNTADE PROBLEM KAN UPPSTÅ OM DU INTE LÄSER DETTA!<br>Eden har upptäckt följande sparningskataloger utan bifogade profiler:<br>%1<br><br>Följande profiler är giltiga:<br>%2<br><br>Klicka på ”OK” för att öppna din sparningsmapp och rätta till dina profiler.<br>Tips: kopiera innehållet i den största eller senast ändrade mappen till en annan plats, ta bort alla övergivna profiler och flytta det kopierade innehållet till den giltiga profilen.<br><br>Fortfarande förvirrad? Se <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>hjälpsidan</a>.<br> - + Really clear data? Verkligen tömma data? - + Important data may be lost! Viktig data kan gå förlorad! - + Are you REALLY sure? Är du VERKLIGEN säker? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. När dina data har raderats kan de INTE återställas! -Gör detta endast om du är 100% säker på att du vill radera dessa data. +Gör detta endast om du är 100 % säker på att du vill radera dessa data. - + Clearing... - Tömmer... + Tömmer… - + Select Export Location Välj exportplats - + %1.zip %1.zip - - + + + Zipped Archives (*.zip) Zippade arkiv (*.zip) - + Exporting data. This may take a while... - Exporterar data. Detta kan ta en stund... + Exporterar data. Detta kan ta en stund… - + Exporting Exporterar - + Exported Successfully Exporten lyckades - + Data was exported successfully. Data har exporterats. - + Export Cancelled Exporten avbröts - + Export was cancelled by the user. Exporten avbröts av användaren. - + Export Failed Exporten misslyckades - + Ensure you have write permissions on the targeted directory and try again. Kontrollera att du har skrivbehörighet till den aktuella katalogen och försök igen. - + Select Import Location Välj importplats - + Import Warning Importvarning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? Alla tidigare data i denna katalog kommer att raderas. Är du säker på att du vill fortsätta? - + Importing data. This may take a while... - Importerar data. Detta kan ta en stund... + Importerar data. Detta kan ta en stund… - + Importing Importerar - + Imported Successfully Importen lyckades - + Data was imported successfully. Data har importerats. - + Import Cancelled Importen avbröts - + Import was cancelled by the user. Importen avbröts av användaren. - + Import Failed Importen misslyckades - + Ensure you have read permissions on the targeted directory and try again. Kontrollera att du har läsbehörighet till den aktuella katalogen och försök igen. + + + Keys not installed + Nycklar är inte installerade + + + + Install decryption keys and restart Eden before attempting to install firmware. + Installera dekrypteringsnycklar och starta om Eden innan du försöker installera fast programvara. + + + + Select Dumped Firmware Source Location + Välj platsen för den dumpade fasta programvaran + + + + Select Dumped Firmware ZIP + Välj ZIP-fil med dumpad fast programvara + + + + Firmware cleanup failed + Rensning av fast programvara misslyckades + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + Kunde inte rensa cacheminnet för extraherad fast programvara. +Kontrollera skrivbehörigheten i systemets tillfälliga katalog och försök igen. +Operativsystemet rapporterade fel: %1 + QtCommon::FS @@ -9805,72 +9885,72 @@ Vill du manuellt välja en portabel mapp att använda? Metadatacachen kunde inte tas bort. Den kan vara i bruk eller finns inte. - + Create Shortcut Skapa genväg - + Do you want to launch the game in fullscreen? Vill du starta spelet i helskärm? - + Shortcut Created Genväg skapad - + Successfully created a shortcut to %1 Skapade en genväg till %1 - + Shortcut may be Volatile! Genvägen kan vara instabil! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? Detta skapar en genväg till den aktuella AppImage. Detta kanske inte fungerar bra om du uppdaterar. Vill du fortsätta? - + Failed to Create Shortcut Misslyckades med att skapa genväg - + Failed to create a shortcut to %1 Misslyckades med att skapa en genväg till %1 - + Create Icon Skapa ikon - + Cannot create icon file. Path "%1" does not exist and cannot be created. Det går inte att skapa ikonfilen. Sökvägen ”%1” finns inte och kan inte skapas. - + No firmware available Inget firmware tillgängligt - + Please install firmware to use the home menu. Installera firmware för att använda hemmenyn. - + Home Menu Applet Applet för hemmeny - + Home Menu is not available. Please reinstall firmware. Hemmenyn är inte tillgänglig. Installera om firmware. @@ -9878,37 +9958,37 @@ Vill du manuellt välja en portabel mapp att använda? QtCommon::Mod - + Mod Name Modnamn - + What should this mod be called? Vad ska denna mod heta? - + RomFS RomFS - + ExeFS/Patch ExeFS/Patch - + Cheat Fusk - + Mod Type Modtyp - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. @@ -9917,18 +9997,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed Mod-extrahering misslyckades - + Failed to create temporary directory %1 Det gick inte att skapa den tillfälliga katalogen %1 - + Zip file %1 is empty Zip-filen %1 är tom @@ -10001,12 +10081,12 @@ De flesta mods är RomFS-mods, men patchar (.pchtxt) är vanligtvis ExeFS-mods.< Successfully installed firmware version %1 - Firmware-version %1 har installerats. + Firmware-version %1 har installerats Unable to locate potential firmware NCA files - Det går inte att hitta potentiella NCA-filer för firmware. + Det går inte att hitta potentiella NCA-filer för firmware @@ -10126,7 +10206,7 @@ Detta rekommenderas om du vill dela data mellan emulatorer. 1 - 8 - 1 - 8 + 1 till 8 @@ -10536,7 +10616,7 @@ p, li { white-space: pre-wrap; } By selecting "From Eden", previous save data stored in Ryujinx will be deleted, and vice versa for "From Ryujinx". Genom att länka sparade data till Ryujinx kan både Ryujinx och Eden referera till samma sparade filer för dina spel. -Om du väljer ”Från Eden” tas tidigare sparade data bort som lagrats i Ryujinx, och vice versa för ”Från Ryujinx”. +Om du väljer ”Från Eden” tas tidigare sparade data bort som lagrats i Ryujinx och vice versa för ”Från Ryujinx”. @@ -10630,67 +10710,68 @@ Om du väljer ”Från Eden” tas tidigare sparade data bort som lagrats i Ryuj %1 finns tillgänglig för hämtning. - + New Version Location - + Plats för ny version - + All Files (*.*) Alla filer (*.*) - - - + + + Failed to save file Misslyckades med att spara filen - + Could not open file %1 for writing. Kunde inte öppna filen %1 för skrivning. - + Downloading... - Hämtar ner... + Hämtar ner… - + Cancel Avbryt - + Could not write to file %1. - + Kunde inte skriva till filen %1. - + Could not commit to file %1. - + Kunde inte verkställa ändringar i filen %1. - + Failed to download file - + Kunde inte hämta filen - + Could not download from %1%2 Error code: %3 - + Kunde inte hämta från %1%2 +Felkod: %3 - + Download Complete - + Hämtningen är klar - + Successfully downloaded %1. Would you like to open it? - + %1 har hämtats. Vill du öppna den? \ No newline at end of file diff --git a/dist/languages/tr_TR.ts b/dist/languages/tr_TR.ts index 5cc62a47d8..b63445c3fb 100644 --- a/dist/languages/tr_TR.ts +++ b/dist/languages/tr_TR.ts @@ -218,155 +218,6 @@ Bu işlem onların hem forum kullanıcı adını hem de IP adresini banlar.%1 - %2 (%3/%4 oyuncu) - bağlanıldı - - CompatDB - - - Report Compatibility - Uyumluluk Bildir - - - - - - - - - - Report Game Compatibility - Oyun Uyumluluğu Bildir - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - <html><head/><body><p><span style=" font-size:10pt;">Eğer </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">Eden Uyumluluk Listesi</span></a><span style=" font-size:10pt;">'ne bir test durumu göndermeyi seçerseniz, aşağıdaki bilgiler toplanacak ve sitede görüntülenecektir:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Donanım Bilgileri (CPU / GPU / İşletim Sistemi)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Çalıştırdığınız eden sürümü</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Bağlı eden hesabı</li></ul></body></html> - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>Oyun açılıyor mu?</p></body></html> - - - - Yes The game starts to output video or audio - Evet Oyun açılıyor ve ses ve/veya görüntü çıktısı veriyor - - - - No The game doesn't get past the "Launching..." screen - Hayır Oyun "Başlatılıyor..." ekranında takılı kalıyor - - - - Yes The game gets past the intro/menu and into gameplay - Evet Oyun, ana menü/intro bölümü geçildikten sonra asıl oyuna başlatılabiliyor. - - - - No The game crashes or freezes while loading or using the menu - Hayır Oyun yüklenirken veya ana menüdeyken takılı kalıyor. - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>Oyun, oynanma aşamasına gelebiliyor mu?</p></body></html> - - - - Yes The game works without crashes - Evet Oyundayken takılı kalma yaşanmıyor. - - - - No The game crashes or freezes during gameplay - Hayır Oyun, oyundayken donuyor veya çöküyor - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>Oyun, oynanış sırasında takılmadan veya çökmeden oynanabiliyor mu?</p></body></html> - - - - Yes The game can be finished without any workarounds - Evet Oyun, herhangi bir kısmı atlanmadan bitirilebiliyor - - - - No The game can't progress past a certain area - Hayır Oyun belirli bölgelerde takılı kalıyor veya çalışmıyor - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>Oyun, baştan sona oynanıp bitirilebiliyor mu?</p></body></html> - - - - Major The game has major graphical errors - Büyük Oyunda bariz grafik hataları mevcut - - - - Minor The game has minor graphical errors - Küçük Oyunda ufak tefek grafik hataları mevcut - - - - None Everything is rendered as it looks on the Nintendo Switch - Yok Oyun, bir Nintendo Switch'de nasıl görünüyorsa aynı görünüyor - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>Oyunda herhangi bir grafik hatası var mı?</p></body></html> - - - - Major The game has major audio errors - Büyük Oyunda bariz ses hataları mevcut - - - - Minor The game has minor audio errors - Küçük Oyunda ufak tefek ses hataları mevcut - - - - None Audio is played perfectly - Yok Oyun sesi mükemmel duyuluyor - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>Oyun ses hatalarına / kayıp efektlere sahip mi?</p></body></html> - - - - Thank you for your submission! - Bildirdiğiniz için teşekkür ederiz! - - - - Submitting - Bildiriliyor - - - - Communication error - Bağlantı hatası - - - - An error occurred while sending the Testcase - Testcase gönderilirken bir hata oldu - - - - Next - İleri - - ConfigurationShared @@ -570,39 +421,38 @@ Düşük kare hızlarında takılmaları azaltmaya yardımcı olabilir. - + Backend: Arkayüz: - CPU Overclock - CPU Hız Aşırtma + CPU Clocks + - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. - Bazı FPS sınırlayıcılarını kaldırmak için emüle edilen CPU'ya, yani işlemciye hız aşırtma yapar. Daha zayıf CPU'larda performans düşebilir ve bazı oyunlar düzgün çalışmayabilir. -Switch'in en yüksek yerel saat hızında çalıştırmak için Boost'u (1700MHz), ya da 2x saat hızında çalıştırmak için Fast'i (2000MHz) kullanın. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + - + Custom CPU Ticks Özel İşlemci Döngüleri - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. İşlemci/CPU tick hızı için özel bir değer belirleyin. Daha yüksek değerler performansı artırabilir, ancak kilitlenmelere de neden olabilir. 77-21000 aralığı tavsiye edilir. - + Enable Host MMU Emulation (fastmem) Ana Bilgisayar MMU Emülasyonunu Etkinleştir (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. @@ -611,148 +461,158 @@ Bunu etkinleştirmek, misafir bellek okuma/yazma işlemlerinin doğrudan belleğ Bunu devre dışı bırakmak, tüm bellek erişimlerinin, Yazılımsal MMU Emülasyonu kullanmaya zorlamasına neden olur. - + Unfuse FMA (improve performance on CPUs without FMA) FMA'yı Ayır (FMA olmayan CPU'larda performansı artırır) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. Bu seçenek, gömülü/yerel FMA desteği olmayan CPU'larda, FMA komutlarının doğruluğunu/hassasiyetini düşürerek hızı artırır. - + Faster FRSQRTE and FRECPE Daha hızlı FRSQRTE ve FRECPE - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. Bu seçenek, daha az doğru olan gömülü/yerel yaklaşıklıkları kullanarak, bazı yaklaşık floating-point işlevlerinin hızını artırır. - + Faster ASIMD instructions (32 bits only) Daha hızlı ASIMD komutları (yalnızca 32 bit) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. Bu seçenek, incorrect durumdaki rounding mode'ları ile çalıştırarak 32 bit ASIMD floating-point işlevlerinin hızını artırır. - + Inaccurate NaN handling Uygunsuz NaN kullanımı - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. Bu seçenek, NaN denetimini kaldırarak hızı artırır. Lütfen unutmayın, bu aynı zamanda bazı floating-point işlemlerinin doğruluğunu azaltır. - + Disable address space checks Adres boşluğu kontrolünü kapatır. - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. Bu seçenek, her bellek işleminden önce bir güvenlik kontrolünü kaldırarak hızı artırır. Devre dışı bırakılması, rastgele kod yürütülmesine izin verebilir. - + Ignore global monitor Global monitörü görmezden gel - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. Bu seçenek, özel erişim talimatlarının güvenliğini sağlamak için yalnızca cmpxchg semantiğine güvenerek hızı artırır. Lütfen bunun kilitlenmelere ve diğer yarış durumlarına neden olabileceğini unutmayın. - + API: API: - + Changes the output graphics API. Vulkan is recommended. Çıkış grafik API'sini değiştirir. Vulkan önerilir. - + Device: Cihaz: - + This setting selects the GPU to use (Vulkan only). Bu ayar, kullanılacak GPU'yu seçer (yalnızca Vulkan). - + Resolution: Çözünürlük: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. Farklı bir çözünürlükte işleme yapmaya zorlar. Yüksek çözünürlükler daha fazla VRAM ve bant genişliği gerektirir. 1X'ten düşük seçenekler yapay bozulmalara neden olabilir. - + Window Adapting Filter: Pencereye Uyarlı Filtre: - + FSR Sharpness: FSR Keskinliği: - - Determines how sharpened the image will look using FSR's dynamic contrast. - FSR'ın dinamik kontrast teknolojisini kullanarak, görüntünün ne kadar keskinleştirileceğini belirler. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. + - + Anti-Aliasing Method: Kenar Yumuşatma Yöntemi: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. Kullanılacak kenar yumuşatma yöntemi. SMAA en iyi kaliteyi sunar. FXAA, düşük çözünürlüklerde daha kararlı bir görüntü oluşturabilir. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Tam Ekran Modu: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. Pencereyi tam ekranda işlemek için kullanılan yöntem. Sınırsız, bazı oyunların giriş için istediği ekran klavyesi ile en iyi uyumluluğu sunar. Özel tam ekran, daha iyi performans ve daha iyi Freesync/Gsync desteği sağlayabilir. - + Aspect Ratio: En-Boy Oranı: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. @@ -761,46 +621,46 @@ Also controls the aspect ratio of captured screenshots. Ayrıca, yakalanan ekran görüntülerinin en-boy oranını da kontrol eder. - + Use persistent pipeline cache Kalıcı işlem hattı önbelleğini kullan - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. Gölgelendiriclerin sonraki oyun açılışlarında daha hızlı yüklenmesi için depolama alanına kaydedilmesine olanak tanır. Devre dışı bırakılması yalnızca hata ayıklama amaçlıdır. - + Use asynchronous GPU emulation - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + NVDEC emulation: NVDEC emülasyonu: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. Videoların nasıl çözüleceğini belirtir. Kod çözme için CPU veya GPU kullanabilir veya hiç kod çözme işlemi yapmayabilir (videolarda siyah ekran). Çoğu durumda GPU ile kod çözme en iyi performansı sağlar. - + ASTC Decoding Method: ASTC Kod Çözme Yöntemi - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -809,55 +669,75 @@ stuttering but may present artifacts. Bu seçenek ASTC dokularının nasıl çözüleceğini kontrol eder. CPU: Kod çözme için işlemciyi kullanır. GPU: ASTC dokularını çözmek için GPU'nun hesaplama gölgelendiricilerini kullanır (önerilir). CPU Asenkron: ASTC dokularını talep üzerine çözmek için işlemciyi kullanır. ASTC kod çözme kaynaklı takılmaları giderir ancak görsel bozulmalara neden olabilir. - + ASTC Recompression Method: ASTC Yeniden Sıkıştırma Yöntemi - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. Çoğu GPU, ASTC dokuları için doğrudan desteğe sahip değildir ve bir ara formata (RGBA8) açılmalıdır. BC1/BC3: Ara format BC1 veya BC3 formatında yeniden sıkıştırılarak VRAM tasarrufu sağlar ancak görüntü kalitesini düşürür. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: VRAM Kullanım Modu - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. Emülatörün belleği korumayı mı yoksa performans için mevcut video belleğini maksimum düzeyde kullanmayı mı tercih edeceğini seçer. Agresif mod, kayıt yazılımları gibi diğer uygulamaların performansını etkileyebilir. - + Skip CPU Inner Invalidation CPU Geçersiz Kılma'yı Atla - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. Bellek güncellemeleri sırasında belirli önbellek geçersiz kılma işlemlerini atlayarak işlemci kullanımını azaltır ve gecikmeyi iyileştirir. Bu, hafif çökmelere neden olabilir. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: VSync Modu: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -865,1362 +745,1372 @@ Immediate (no synchronization) presents whatever is available and can exhibit te FIFO (VSync) kare düşürmez veya yırtılma göstermez ancak ekran yenileme hızıyla sınırlıdır. FIFO Relaxed, yavaşlamadan toparlanırken yırtılmaya izin verir. Mailbox, FIFO'dan daha düşük gecikmeye sahip olabilir ve yırtılma yapmaz ancak kare düşürebilir. Immediate (senkronizasyon yok), mevcut olanı anında sunar ve yırtılmalara neden olabilir. - + Sync Memory Operations Bellek İşlemlerini Senkronize Et - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. Hesaplama ve bellek işlemleri arasında veri tutarlılığı sağlar. Bu seçenek oyunlardaki sorunları giderir ancak performansı düşürebilir. Unreal Engine 4 oyunları genellikle bundan en önemli ölçüde etkilenenlerdir. - + Enable asynchronous presentation (Vulkan only) Asenkron sunumu etkinleştir (Yalnızca Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. Sunum işlemini ayrı bir işlemci iş parçacığına taşıyarak performansı biraz artırır. - + Force maximum clocks (Vulkan only) En yüksek hızı zorla (Yalnızca Vulkan için) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. Grafik komutlarını beklerken GPU'nun hızının düşmesini engellemek için arka planda görev yürütür - + Anisotropic Filtering: Anisotropic Filtering: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. Eğik açılardaki doku oluşturma kalitesini kontrol eder. Çoğu grafik kartında 16x olarak ayarlanması güvenlidir. - + GPU Mode: Grafik Kartı Modu - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - Grafik kartı emülasyon modunu kontrol eder. Çoğu oyun Hızlı veya Dengeli modlarda sorunsuz çalışır, ancak bazıları için hala Doğru modu gereklidir. Parçacıklar genellikle yalnızca Doğru modda düzgün görüntülenir. - - - - DMA Accuracy: - DMA Doğruluğu: - - - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. - DMA'in hassasiyet doğruluğunu yönetir. Güvenli hassasiyet, bazı oyunlardaki sorunları giderir, fakat performansı düşürebilir. - - - - Enable asynchronous shader compilation - Asenkron gölgelendirici derlemeyi etkinleştir - - - - May reduce shader stutter. - Gölgelendirici/shader takılmalarını azaltabilir. - - - - Fast GPU Time - Hızlı Grafik Kartı Süresi - - - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. - Dinamik çözünürlüğü ve çizim mesafesini artırmak için emüle edilen grafik kartına hız aşırtma uygular. Maksimum performans için 256, maksimum grafik doğruluğu için 512 kullanın. - - - - GPU Unswizzle + DMA Accuracy: + DMA Doğruluğu: + + + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. + + + + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + + Enable asynchronous shader compilation + Asenkron gölgelendirici derlemeyi etkinleştir + + + + May reduce shader stutter. + Gölgelendirici/shader takılmalarını azaltabilir. + + + + GPU Clocks + + + + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. + + + + + GPU Unswizzle + + + + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache Vulkan pipeline önbelleği kullan - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. Grafik kartı üreticisine özel işlem hattı önbelleğini etkinleştirir. Bu seçenek, Vulkan sürücüsünün işlem hattı önbellek dosyalarını dahili olarak saklamadığı durumlarda gölgelendirici yükleme süresini önemli ölçüde iyileştirebilir. - + Enable Compute Pipelines (Intel Vulkan Only) Hesaplama İşlem Hatlarını Etkinleştir (Yalnızca Intel Vulkan) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. Bazı oyunlar için gereklidir. Bu ayar yalnızca Intel'in tescilli sürücüleri için mevcuttur ve etkinleştirilirse çökmeye neden olabilir. Hesaplama işlem hatları diğer tüm sürücülerde her zaman etkindir. - + Enable Reactive Flushing Reaktif Temizlemeyi Etkinleştir - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. Tahminli temizleme yerine reaktif temizleme kullanarak daha doğru bellek senkronizasyonu sağlar. - + Sync to framerate of video playback Video oynatma kare hızına senkronize et - + Run the game at normal speed during video playback, even when the framerate is unlocked. Kare hızı kilidi açık olsa bile video oynatımı sırasında oyunu normal hızda çalıştırır. - + Barrier feedback loops Bariyer geri besleme döngüleri - + Improves rendering of transparency effects in specific games. Belirli oyunlarda şeffaflık efektlerinin oluşturulmasını iyileştirir. - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State Genişletilmiş Dinamik Durum - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. Genişletilmiş Dinamik Durumda kullanılabilecek özelliklerin sayısını kontrol eder. Daha yüksek durumlar daha fazla özelliğe izin verir ve performansı artırabilir, ancak ek grafik sorunlarına neden olabilir. - + Vertex Input Dynamic State Vertex Dinamik Durumu - + Enables vertex input dynamic state feature for better quality and performance. Daha iyi kalite ve performans için Vertex dinamik durum özelliğini etkinleştirir. - + Sample Shading Örnek Gölgelendirme - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. Parça gölgelendiricinin, her parça için bir kez yerine çoklu örneklenmiş bir parçadaki her örnek için yürütülmesine olanak tanır. Performans pahasına grafik kalitesini artırır. Daha yüksek değerler kaliteyi artırır ancak performansı düşürür. - + RNG Seed RNG çekirdeği - + Controls the seed of the random number generator. Mainly used for speedrunning. Rastgele sayı üretecinin tohumunu kontrol eder. Esas olarak hızlı bitirme denemeleri için kullanılır. - + Device Name Cihaz İsmi - + The name of the console. Konsolun adı - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: Özel RTC Tarihi - + This option allows to change the clock of the console. Can be used to manipulate time in games. Bu seçenek konsolun saatini değiştirmeye olanak tanır. Oyunlarda zamanı manipüle etmek için kullanılabilir. - + The number of seconds from the current unix time Mevcut unix zamanından itibaren saniye sayısı - + Language: Dil: - + This option can be overridden when region setting is auto-select Bölge ayarı otomatik seçim olduğunda bu seçenek geçersiz kılınabilir. - + Region: Bölge: - + The region of the console. Konsolun bölgesi - + Time Zone: Saat Dilimi: - + The time zone of the console. Konsolun saat dilimi - + Sound Output Mode: Ses Çıkış Modu: - + Console Mode: Konsol Modu: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. Konsolun Yerleşik veya El Modunda olup olmadığını seçer. Oyunlar bu ayara bağlı olarak çözünürlüklerini, detaylarını ve desteklenen kontrolcülerini değiştirecektir. El Moduna ayarlamak, düşük seviyeli sistemler için performansı artırmaya yardımcı olabilir. - + Prompt for user profile on boot Açılışta kullanıcı profili için sor - + Useful if multiple people use the same PC. Aynı bilgisayarı birden fazla kişi kullanıyorsa yararlıdır. - + Pause when not in focus Odaklı değilken duraklat - + Pauses emulation when focusing on other windows. Diğer pencerelere odaklanıldığında emülasyonu duraklatır. - + Confirm before stopping emulation Emülasyonu durdurmadan önce onayla - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. Emülasyonu durdurma onayı isteklerini/istemlerini geçersiz kılar. Etkinleştirildiğinde, bu tür istekleri/istemleri atlar ve emülasyonu doğrudan/dirket olarak kapatır. - + Hide mouse on inactivity Hareketsizlik durumunda imleci gizle - + Hides the mouse after 2.5s of inactivity. 2,5 saniye hareketsizlikten sonra fareyi gizler. - - Disable controller applet - Kontrolcü aplikasyonunu devre dışı bırak - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - Emüle edilen programlarda kontrolcü aplikasyonunun kullanımını zorla devre dışı bırakır. Bir program kontrolcü aplikasyonunu açmaya çalıştığında, aplikasyon anında kapatılır. - - - + Check for updates Güncellemeleri Kontrol Et - + Whether or not to check for updates upon startup. Başlangıçta güncellemelerin kontrol edilip edilmeyeceği. - + Enable Gamemode Oyun Modunu/Gamemode Etkinleştir - + Force X11 as Graphics Backend Grafik arka ucu olarak X11'i zorla - + Custom frontend Özel ön yüz - + Real applet Gerçek aplikasyon - + + + Disabled + Devre Dışı + + + Never Asla - + On Load Yüklemede - + Always Her zaman - + CPU CPU - + GPU GPU - + CPU Asynchronous Asenkron CPU - + Uncompressed (Best quality) Sıkıştırılmamış (En iyi kalite) - + BC1 (Low quality) BC1 (Düşük kalite) - + BC3 (Medium quality) BC3 (Orta kalite) - - + + Auto Otomatik - + 30 FPS - + 60 FPS - + 90 FPS - + 120 FPS - + Conservative Muhafazakar - + Aggressive Agresif - + Vulkan Vulkan - + OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) - + Null Boş - + Fast Hızlı - - Balanced - Dengeli - - - - + + + Accurate Doğru - - + + + Default Varsayılan - + Unsafe (fast) Güvenli Değil (hızlı) - + Safe (stable) Güvenli (Stabil) - + + Immediate + + + + + Balanced + Dengeli + + + Unsafe Güvensiz - + Paranoid (disables most optimizations) Paranoya (çoğu optimizasyonu kapatır) - + Debugging Hata ayıklama - + Dynarmic Dinamik - + NCE NCE - + Borderless Windowed Kenarlıksız Tam Ekran - + Exclusive Fullscreen Ayrılmış Tam Ekran - + No Video Output Video Çıkışı Yok - + CPU Video Decoding CPU Video Decoding - + GPU Video Decoding (Default) GPU Video Decoding (Varsayılan) - + 0.25X (180p/270p) [EXPERIMENTAL] 0.25X (180p/270p) [DENEYSEL] - + 0.5X (360p/540p) [EXPERIMENTAL] 0.5X (360p/540p) [DENEYSEL] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [DENEYSEL] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] 1.25X (900p/1350p) [DENEYSEL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [DENEYSEL] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor En Yakın Komşu Algoritması - + Bilinear Bilinear - + Bicubic Bicubic - + Gaussian Gausyen - + Lanczos Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution AMD FidelityFX Süper Çözünürlük - + Area Area - + MMPX MMPX - + Zero-Tangent Zero-Tangent - + B-Spline B-Spline - + Mitchell Mitchell - + Spline-1 Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None Yok - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Varsayılan (16:9) - + Force 4:3 4:3'e Zorla - + Force 21:9 21:9'a Zorla - + Force 16:10 16:10'a Zorla - + Stretch to Window Ekrana Sığdır - + Automatic Otomatik - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - 32x - - - - 64x - 64x - - - + Japanese (日本語) Japonca (日本語) - + American English Amerikan İngilizcesi - + French (français) Fransızca (français) - + German (Deutsch) Almanca (Deutsch) - + Italian (italiano) İtalyanca (italiano) - + Spanish (español) İspanyolca (español) - + Chinese Çince - + Korean (한국어) Korece (한국어) - + Dutch (Nederlands) Flemenkçe (Nederlands) - + Portuguese (português) Portekizce (português) - + Russian (Русский) Rusça (Русский) - + Taiwanese Tayvanca - + British English İngiliz İngilizcesi - + Canadian French Kanada Fransızcası - + Latin American Spanish Latin Amerika İspanyolcası - + Simplified Chinese Basitleştirilmiş Çince - + Traditional Chinese (正體中文) Geleneksel Çince (正體中文) - + Brazilian Portuguese (português do Brasil) Brezilya Portekizcesi (português do Brasil) - + Polish (polska) - + Thai (แบบไทย) - - + + Japan Japonya - + USA ABD - + Europe Avrupa - + Australia Avustralya - + China Çin - + Korea Kore - + Taiwan Tayvan - + Auto (%1) Auto select time zone Otomatik (%1) - + Default (%1) Default time zone Varsayılan (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Küba - + EET EET - + Egypt Mısır - + Eire İrlanda - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-İrlanda - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 MT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hong Kong - + HST HST - + Iceland İzlanda - + Iran İran - + Israel İsrail - + Jamaica Jamaika - + Kwajalein Kwajalein - + Libya Libya - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navaho - + NZ Yeni Zelanda - + NZ-CHAT Chatham Adaları - + Poland Polonya - + Portugal Portekiz - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Singapur - + Turkey Türkiye - + UCT UCT - + Universal Evrensel - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulu - + Mono Mono - + Stereo Stereo - + Surround Surround - + 4GB DRAM (Default) 4GB DRAM (Varsayılan) - + 6GB DRAM (Unsafe) 6GB DRAM (Güvenli Değil) - + 8GB DRAM 8GB DRAM - + 10GB DRAM (Unsafe) 10GB DRAM (Güvenli Değil) - + 12GB DRAM (Unsafe) 12GB DRAM (Güvenli Değil) - + Docked Dock Modu Aktif - + Handheld Taşınabilir - - - Off - Kapalı + + + Normal + - - Boost (1700MHz) - Takviye (1700MHz) + + + Boost + - - Fast (2000MHz) - Hızlı (2000MHz) + + + Overclock + - + Always ask (Default) Her zaman sor (Varsayılan) - + Only if game specifies not to stop Sadece oyun durdurulmamasını belirtirse - + Never ask Asla sorma - - - Medium (256) - Orta (256) - - - - - High (512) - Yüksek (512) - - - + Very Small (16 MB) - + Small (32 MB) - + Normal (128 MB) - + Large (256 MB) - + Very Large (512 MB) - + Very Low (4 MB) - + Low (8 MB) - + Normal (16 MB) - + Medium (32 MB) - + High (64 MB) - + Very Low (32) - + Low (64) - + Normal (128) - - Disabled - Devre Dışı + + Medium (256) + Orta (256) - + + High (512) + Yüksek (512) + + + ExtendedDynamicState 1 Genişletilmiş Dinamik Durum 1 - + ExtendedDynamicState 2 Genişletilmiş Dinamik Durum 2 - + ExtendedDynamicState 3 Genişletilmiş Dinamik Durum 3 - + Tree View - + Grid View @@ -2578,252 +2468,297 @@ When a program attempts to open the controller applet, it is immediately closed. Evrensel Günlük Kaydı Filtresi - - When checked, the max size of the log increases from 100 MB to 1 GB - Etkinleştirildiğinde log'un boyut sınırı 100 MB'tan 1 GB'a çıkar + + GPU Logging/Level + - - Enable Extended Logging** - Uzatılmış Hata Kaydını Etkinleştir. + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console Konsolda Log'u Göster - + + When checked, the max size of the log increases from 100 MB to 1 GB + Etkinleştirildiğinde log'un boyut sınırı 100 MB'tan 1 GB'a çıkar + + + + Enable Extended Logging** + Uzatılmış Hata Kaydını Etkinleştir. + + + Open Log Location Günlük Kaydı Konumunu Aç - + Homebrew Homebrew - + Arguments String Arguments String - + Graphics Grafikler - + When checked, it executes shaders without loop logic changes İşaretlendiğinde shaderları döngü mantık değişimleri olmaksızın uygular - + Disable Loop safety checks Döngü güvenliği kontrolünü devre dışı bırak - + When checked, it will dump all the macro programs of the GPU Kullanılırsa, GPU'daki bütün makro uygulamalar dump'lanır - + Dump Maxwell Macros Maxwell Makro'larını Dump'la - + When checked, it enables Nsight Aftermath crash dumps İşaretlendiğinde Nsight Aftermath çökme dökümlerini etkinleştirir. - + Enable Nsight Aftermath Nsight Aftermath'ı Etkinleştir - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Kullanılırsa, asıl assembler shader dosyaları diskten shader önbelleği ya da oyun bulundukça dump'lanır + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + - - Dump Game Shaders - Oyun Shader'larını Dump'la + + Dump Guest (Maxwell) Shaders + - + Enable Renderdoc Hotkey Renderdoc Kısayol Tuşunu Etkinleştir - + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower İşaretlendiğinde Makro JIT derleyicisini devre dışı bırakır. Bu seçeneği etkinleştirmek oyunların yavaş çalışmasına neden olur. - + Disable Macro JIT Macro JIT'i devre dışı bırak - + When checked, it disables the macro HLE functions. Enabling this makes games run slower Kullanılırsa makro HLE işlevselliği kapatılır. Bu seçeneği açmak oyunların yavaşlamasına sebep olur - + Disable Macro HLE Makro HLE'yi Kapat - + When checked, the graphics API enters a slower debugging mode Etkinleştirildiğinde, grafik API'ı daha yavaş bir hata ayıklama moduna girer. - + Enable Graphics Debugging Grafik Hata Ayıklama Modunu Etkinleştir - + When checked, yuzu will log statistics about the compiled pipeline cache Etkinleştirildiğinde, yuzu derlenen pipeline cache istatistiklerini log'a kaydeder. - + Enable Shader Feedback Shader Geribildirimini Etkinleştir - + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> <html><head/><body><p>İşaretlendiğinde, yüklemelerin belirli çizimlerle ilişkilendirilmesine olanak tanıyan eşlenmiş bellek yüklemelerinin yeniden sıralanmasını devre dışı bırakır. Bazı durumlarda performansı düşürebilir.</p></body></html> - + Disable Buffer Reorder Arabellek Yeniden Sıralamayı Devre Dışı Bırak - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced Gelişmiş - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Bu seçenek, program açılırken Vulkan ortam işlevselliğini kontrol etmesini sağlar. Diğer programlar yuzu'yu görmekte sorun yaşıyorsa bu seçeneği kapatın. - + Perform Startup Vulkan Check Açılırken Vulkan Taraması Yap - + Disable Web Applet Web Uygulamasını Devre Dışı Bırak - + Enable All Controller Types Bütün Kontrolcü Türlerini Etkinleştir - + Enable Auto-Stub Otomatik Taslağı Etkinleştir - + Kiosk (Quest) Mode Kiosk (Quest) Modu - + Use dev.keys - + Enable Debug Asserts Hata Ayıklama Assert'lerini Etkinleştir - + Debugging Hata ayıklama - + Battery Serial: Pil Seri Numarası: - + + Debug Knobs: + + + + Bitmask for quick development toggles Hızlı geliştirme geçişleri için bit maskesi - + Set debug knobs (bitmask) Hata ayıklama düğmelerini ayarla (bit maskesi) - + 16-bit debug knob set for quick development toggles Hızlı geliştirme geçişleri için 16 bit hata ayıklama düğmesi seti - + (bitmask) (bitmask) - - Debug Knobs: - Hata Ayıklama Düğmeleri: - - - + Unit Serial: Ünite Seri Numarası: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. Bu seçenek açıksa son oluşturulan ses komutları konsolda gösterilir. Sadece ses işleyicisi kullanan oyunları etkiler. - + Dump Audio Commands To Console** Konsola Ses Komutlarını Aktar** - + Flush log output on each line Her satırda günlük çıktısını boşalt - + Enable FS Access Log FS Erişim Kaydını Etkinleştir - + Enable Verbose Reporting Services** Detaylı Raporlama Hizmetini Etkinleştir - + Censor username in logs Günlüklerde kullanıcı adını sansürle - + **This will be reset automatically when Eden closes. **Bu, Eden kapandığında otomatik olarak sıfırlanacaktır. @@ -3285,33 +3220,38 @@ Eski kayıt verilerini silmek ister misiniz? Arkaplan Rengi: - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off Kapalı - + VSync Off VSync Kapalı - + Recommended Önerilen - + On Açık - + VSync On Vsync Açık @@ -3832,6 +3772,16 @@ Eski kayıt verilerini silmek ister misiniz? Motion / Touch Hareket / Dokunmatik + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4341,58 +4291,58 @@ Eksenleri ters çevirmek için, önce joystickinizi dikey sonra yatay olarak har [bekleniyor] - + New Profile Yeni Profil - + Enter a profile name: Bir profil ismi girin: - - + + Create Input Profile Kontrol Profili Oluştur - + The given profile name is not valid! Girilen profil ismi geçerli değil! - + Failed to create the input profile "%1" "%1" kontrol profili oluşturulamadı - + Delete Input Profile Kontrol Profilini Kaldır - + Failed to delete the input profile "%1" "%1" kontrol profili kaldırılamadı - + Load Input Profile Kontrol Profilini Yükle - + Failed to load the input profile "%1" "%1" kontrol profili yüklenemedi - + Save Input Profile Kontrol Profilini Kaydet - + Failed to save the input profile "%1" "%1" kontrol profili kaydedilemedi @@ -4440,7 +4390,7 @@ Eksenleri ters çevirmek için, önce joystickinizi dikey sonra yatay olarak har - + Configure Yapılandır @@ -4471,7 +4421,7 @@ Eksenleri ters çevirmek için, önce joystickinizi dikey sonra yatay olarak har - + Test Test @@ -4486,77 +4436,77 @@ Eksenleri ters çevirmek için, önce joystickinizi dikey sonra yatay olarak har Server'ı Kaldır - + %1:%2 %1:%2 - - - - - - + + + + + + Eden Eden - + Port number has invalid characters Port numarasında geçersiz karakterler var - + Port has to be in range 0 and 65353 Port 0 ila 65353 aralığında olmalıdır - + IP address is not valid IP adresi geçerli değil - + This UDP server already exists Bu UDP sunucusu zaten var - + Unable to add more than 8 servers 8'den fazla server eklenemez - + Testing Test Ediliyor - + Configuring Yapılandırılıyor - + Test Successful Test Başarılı - + Successfully received data from the server. Bilgi başarıyla sunucudan kaldırıldı. - + Test Failed Test Başarısız - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Serverdan geçerli veri alınamadı.<br>Lütfen sunucunun doğru ayarlandığını ya da adres ve portun doğru olduğunu kontrol edin. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. UDP testi ya da yapılandırılması devrede.<br>Lütfen bitmesini bekleyin. @@ -4741,57 +4691,57 @@ Mevcut değerler sırasıyla %1 ve %2'dir. Bazı ayarlar yalnızca bir oyun çalışmadığında kullanılabilir. - + Add-Ons Eklentiler - + System Sistem - + CPU CPU - + Graphics Grafikler - + Adv. Graphics Gelişmiş Grafikler - + Ext. Graphics Ek Grafikler - + Audio Ses - + Input Profiles Kontrol Profilleri - + Network Ağ - + Applets - + Properties Özellikler @@ -4914,6 +4864,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5481,91 +5555,86 @@ Noktanın konumunu değiştirmek için sürükleyin ya da sayıların üstüne - Show Compatibility List - Uyumluluk Listesini Göster - - - Show Add-Ons Column Eklentiler kolonunu göster - + Show Size Column Boyut Sütununu Göster - + Show File Types Column Dosya Türü Sütununu Göster - + Show Play Time Column - + Folder Icon Size: Dosya Simge Boyutu: - + Row 1 Text: 1. Sıra Yazısı: - + Row 2 Text: 2. Sıra Yazısı: - + Screenshots Ekran Görüntüleri - + Ask Where To Save Screenshots (Windows Only) Ekran Görüntülerinin Nereye Kaydedileceğini Belirle (Windows'a Özel) - + Screenshots Path: Ekran Görüntülerinin Konumu: - + ... ... - + TextLabel - + Resolution: Çözünürlük: - + Select Screenshots Path... Ekran Görüntülerinin Konumunu Seçin... - + <System> <System> - + English İngilizce - + Auto (%1 x %2, %3 x %4) Screenshot width value @@ -5802,7 +5871,7 @@ Noktanın konumunu değiştirmek için sürükleyin ya da sayıların üstüne Bu dizin için veri içe aktarın. Bu biraz zaman alabilir ve MEVCUT TÜM VERİLERİ silecek! - + Calculating... Hesaplanıyor... @@ -6004,50 +6073,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL kullanıma uygun değil! - + OpenGL shared contexts are not supported. OpenGL paylaşılan bağlam desteklenmiyor. - + Eden has not been compiled with OpenGL support. - - - + + + Error while initializing OpenGL! OpenGl başlatılırken bir hata oluştu! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. GPU'nuz OpenGL desteklemiyor veya güncel bir grafik sürücüsüne sahip değilsiniz. - + Error while initializing OpenGL 4.6! OpenGl 4.6 başlatılırken bir hata oluştu! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 GPU'nuz OpenGL 4.6'yı desteklemiyor veya güncel bir grafik sürücüsüne sahip değilsiniz.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 GPU'nuz gereken bir yada daha fazla OpenGL eklentisini desteklemiyor Lütfen güncel bir grafik sürücüsüne sahip olduğunuzdan emin olun.<br><br>GL Renderer:<br>%1<br><br> Desteklenmeyen Eklentiler:<br>%2 - + This build doesn't have OpenGL support. @@ -6055,279 +6124,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory - + Favorite Favori - + Start Game Oyunu Başlat - + Start Game without Custom Configuration Oyunu Özel Yapılandırma Olmadan Başlat - + Open Save Data Location Kayıt Dosyası Konumunu Aç - + Open Mod Data Location Mod Dosyası Konumunu Aç - + Open Transferable Pipeline Cache Transfer Edilebilir Pipeline Cache'ini Aç - + Link to Ryujinx - + Remove Kaldır - + Remove Installed Update Yüklenmiş Güncellemeleri Kaldır - + Remove All Installed DLC Yüklenmiş DLC'leri Kaldır - + Remove Custom Configuration Oyuna Özel Yapılandırmayı Kaldır - + Remove Cache Storage - + Remove OpenGL Pipeline Cache OpenGL Pipeline Cache'ini Kaldır - + Remove Vulkan Pipeline Cache Vulkan Pipeline Cache'ini Kaldır - + Remove All Pipeline Caches Bütün Pipeline Cache'lerini Kaldır - + Remove All Installed Contents Tüm Yüklenmiş İçeriği Kaldır - + Manage Play Time - + Edit Play Time Data - + Remove Play Time Data - - + + Dump RomFS RomFS Dump Et - + Dump RomFS to SDMC RomFS'i SDMC'ye çıkar. - + Verify Integrity - + Copy Title ID to Clipboard Title ID'yi Panoya Kopyala - - Navigate to GameDB entry - GameDB sayfasına yönlendir - - - + Create Shortcut Kısayol Oluştur - + Add to Desktop Masaüstüne Ekle - + Add to Applications Menu Uygulamalar Menüsüne Ekl - + Configure Game - + Scan Subfolders Alt Klasörleri Tara - + Remove Game Directory Oyun Konumunu Kaldır - + ▲ Move Up ▲Yukarı Git - + ▼ Move Down ▼Aşağı Git - + Open Directory Location Oyun Dosyası Konumunu Aç - + Clear Temizle - - - Name - İsim - - - - Compatibility - Uyumluluk - - - - Add-ons - Eklentiler - - - - File type - Dosya türü - - - - Size - Boyut - - - - Play time - - GameListItemCompat - + Ingame Oyunda - + Game starts, but crashes or major glitches prevent it from being completed. Oyun başlatılabiliyor, fakat bariz hatalardan veya çökme sorunlarından dolayı bitirilemiyor. - + Perfect Mükemmel - + Game can be played without issues. Oyun sorunsuz bir şekilde oynanabiliyor. - + Playable Oynanabilir - + Game functions with minor graphical or audio glitches and is playable from start to finish. Oyun küçük grafik veya ses hatalarıyla çalışıyor ve baştan sona kadar oynanabilir. - + Intro/Menu İntro/Menü - + Game loads, but is unable to progress past the Start Screen. Oyun açılıyor, fakat ana menüden ileri gidilemiyor. - + Won't Boot Açılmıyor - + The game crashes when attempting to startup. Oyun açılmaya çalışıldığında çöküyor. - + Not Tested Test Edilmedi - + The game has not yet been tested. Bu oyun henüz test edilmedi. + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list Oyun listesine yeni bir klasör eklemek için çift tıklayın. @@ -6335,17 +6397,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) %n sonucun %1'i%n sonucun %1'i - + Filter: Filtre: - + Enter pattern to filter Filtrelemek için bir düzen giriniz @@ -6840,1139 +6902,1112 @@ Debug Message: - + Game &Icon Size - + Reset Window Size to &720p Pencere Boyutunu &720p'ye Sıfırla - + Reset Window Size to 720p Pencere Boyutunu 720p'ye Sıfırla - + Reset Window Size to &900p Pencere Boyutunu &900p'ye Sıfırla - + Reset Window Size to 900p Pencere Boyutunu 900p'ye Sıfırla - + Reset Window Size to &1080p Pencere Boyutunu &1080p'ye Sıfırla - + Reset Window Size to 1080p Pencere Boyutunu 1080p'ye Sıfırla - + &Multiplayer &Çok Oyunculu - + &Tools &Aletler - + Am&iibo - + Launch &Applet - + &TAS &TAS - + &Create Home Menu Shortcut - + Install &Firmware - + &Help &Yardım - + &Install Files to NAND... &NAND'e Dosya Kur... - + L&oad File... &Dosyayı Yükle... - + Load &Folder... &Klasörü Yükle... - + E&xit &Çıkış - - + + &Pause &Duraklat - + &Stop Du&rdur - + &Verify Installed Contents &Kurulu İçerikleri Onayla - + &About Eden - + Single &Window Mode &Tek Pencere Modu - + Con&figure... &Yapılandır... - + Ctrl+, - + Enable Overlay Display Applet - + Show &Filter Bar &Filtre Çubuğu'nu Göster - + Show &Status Bar &Durum Çubuğu'nu Göster - + Show Status Bar Durum Çubuğunu Göster - + &Browse Public Game Lobby &Herkese Açık Oyun Lobilerine Göz At - + &Create Room &Oda Oluştur - + &Leave Room &Odadan Ayrıl - + &Direct Connect to Room &Odaya Direkt Bağlan - + &Show Current Room &Şu Anki Odayı Göster - + F&ullscreen &Tam Ekran - + &Restart &Yeniden Başlat - + Load/Remove &Amiibo... &Amiibo Yükle/Kaldır - - &Report Compatibility - &Uyumluluk Bildir - - - + Open &Mods Page &Mod Sayfasını Aç - - Open &Quickstart Guide - &Hızlı Başlangıç Kılavuzunu Aç + + &User Handbook + - - &FAQ - &SSS - - - + &Capture Screenshot &Ekran Görüntüsü Al - + &Album - + &Set Nickname and Owner - + &Delete Game Data - + &Restore Amiibo - + &Format Amiibo - + &Mii Editor - + &Configure TAS... &TAS'i Ayarla... - + Configure C&urrent Game... &Geçerli Oyunu Yapılandır... - - + + &Start B&aşlat - + &Reset &Sıfırla - - + + R&ecord K&aydet - + Open &Controller Menu - + Install Decryption &Keys - + &Home Menu - + &Desktop - + &Application Menu - + &Root Data Folder - + &NAND Folder - + &SDMC Folder - + &Mod Folder - + &Log Folder - + From Folder - + From ZIP - + &Eden Dependencies - + &Data Manager - + &Tree View - + &Grid View - + Game Icon Size - - + + None - + Show Game &Name - + Show &Performance Overlay - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) - + Standard (64x64) - + Large (128x128) - + Full Size (256x256) - + Broken Vulkan Installation Detected Bozuk Vulkan Kurulumu Algılandı - + Vulkan initialization failed during boot. Açılış sırasında Vulkan başlatma işlemi başarısız oldu. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping Bir oyun çalıştırılıyor - + Loading Web Applet... Web Uygulaması Yükleniyor... - - + + Disable Web Applet Web Uygulamasını Devre Dışı Bırak - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + The amount of shaders currently being built - + The current selected resolution scaling multiplier. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Unmute Sesi aç - + Mute Sessize al - + Reset Volume Sesi Sıfırla - + + Configure Effects... + + + + &Clear Recent Files &Son Dosyaları Temizle - + &Continue - + Warning: Outdated Game Format - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - - + + Error while loading ROM! - + The ROM format is not supported. - + An error occurred initializing the video core. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + An unknown error occurred. Please see the log for more details. - + + (64-bit) - + + (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + Closing software... - + Save Data - + Mod Data - + Error Opening %1 Folder - - + + Folder does not exist! - + Remove Installed Game Contents? - + Remove Installed Game Update? - + Remove Installed Game DLC? - + Remove Entry - + Delete OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? - + Remove Custom Game Configuration? - + Remove Cache Storage? - + Remove File - + Remove Play Time Data - + Reset play time? - - + + RomFS Extraction Failed! - + There was an error copying the RomFS files or the user cancelled the operation. - + Full - + Skeleton - + Select RomFS Dump Mode - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Extracting RomFS... - - + + Cancel - + RomFS Extraction Succeeded! - + The operation completed successfully. - + Error Opening %1 - + Select Directory - + Properties - + The game properties could not be loaded. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Load File - + Open Extracted ROM Directory - + Invalid Directory Selected - + The directory you have selected does not contain a 'main' file. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files - + %n file(s) remaining - + Installing file "%1"... - - + + Install Results - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + %n file(s) were newly installed - + %n file(s) were overwritten - + %n file(s) failed to install - + System Application - + System Archive - + System Application Update - + Firmware Package (Type A) - + Firmware Package (Type B) - + Game - + Game Update - + Game DLC - + Delta Title - + Select NCA Install Type... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Failed to Install - + The title type you selected for the NCA is invalid. - + File not found - + File "%1" not found - + OK - + Function Disabled - + Compatibility list reporting is currently disabled. Check back later! - + Error opening URL URL açılırken hata oluştu - + Unable to open the URL "%1". - + TAS Recording TAS İşlemi Kaydı - + Overwrite file of player 1? - + Invalid config detected - + Handheld controller can't be used on docked mode. Pro controller will be selected. - - + + Amiibo Amiibo - - + + The current amiibo has been removed Mevcut Amiibo kaldırıldı - + Error - - + + The current game is not looking for amiibos - + Amiibo File (%1);; All Files (*.*) - + Load Amiibo - + Error loading Amiibo data - + The selected file is not a valid amiibo - + The selected file is already on use - + An unknown error occurred - - - Keys not installed - - - - - - Install decryption keys and restart Eden before attempting to install firmware. - - - - - Select Dumped Firmware Source Location - - - - - Select Dumped Firmware ZIP - - - - - Zipped Archives (*.zip) - - - - - Firmware cleanup failed - - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available - + Firmware Corrupted Donanım Yazılımı/Firmware Bozuk - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot - + PNG Image (*.png) - + TAS state: Running %1/%2 TAS durumu: %1/%2 Çalışıyor - + TAS state: Recording %1 - + TAS state: Idle %1/%2 - + TAS State: Invalid - + &Stop Running - + Stop R&ecording - + Building: %n shader(s) - + Scale: %1x %1 is the resolution scaling factor Ölçek: %1x - + Speed: %1% / %2% - + Speed: %1% - + Game: %1 FPS - + Frame: %1 ms - - - FSR - - - - + NO AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE - + VOLUME: %1% Volume percentage (e.g. 50%) - + Derivation Components Missing - + Decryption keys are missing. Install them now? - + Wayland Detected! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -7980,74 +8015,74 @@ Would you like to force it for future launches? - + Use X11 - + Continue with Wayland - + Don't show again - + Restart Required - + Restart Eden to apply the X11 backend. - + Slow - + Turbo - + Unlocked - + Select RomFS Dump Target - + Please select which RomFS you would like to dump. - + Are you sure you want to close Eden? - - - + + + Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8113,6 +8148,11 @@ Would you like to bypass this and exit anyway? ScaleForce + + + FSR + + Area @@ -8124,52 +8164,57 @@ Would you like to bypass this and exit anyway? - + + SGSR + + + + + SGSR EdgeDir + + + + Docked - + Handheld - + Fast - - Balanced - - - - + Accurate - + Vulkan - + OpenGL GLSL - + OpenGL SPIRV - + OpenGL GLASM - + Null @@ -8202,7 +8247,7 @@ If you wish to clean up the files which were left in the old data location, you - + Data was migrated successfully. @@ -8692,8 +8737,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8706,10 +8751,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8724,9 +8769,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8888,50 +8933,50 @@ p, li { white-space: pre-wrap; } [belirsiz] - + %1%2 %1%2 - + [invalid] [geçersiz] - - + + %1%2Hat %3 %1%2Hat %3 - - - + + + %1%2Axis %3 %1%2Eksen %3 - + %1%2Axis %3,%4,%5 %1%2Eksen %3,%4,%5 - + %1%2Motion %3 %1%2Hareket %3 - - + + %1%2Button %3 %1%2Tuş %3 - + [unused] [kullanılmayan] @@ -9057,47 +9102,47 @@ p, li { white-space: pre-wrap; } %1 %2'yi oynuyor - + Play Time: %1 - + Never Played - + Version: %1 - + Version: 1.0.0 - + Installed SD Titles Yüklenmiş SD Oyunları - + Installed NAND Titles Yüklenmiş NAND Oyunları - + System Titles Sistemde Yüklü Oyunlar - + Add New Game Directory Yeni Oyun Konumu Ekle - + Favorites Favoriler @@ -9218,13 +9263,13 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. @@ -9234,10 +9279,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9258,213 +9303,246 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9708,72 +9786,72 @@ Would you like to manually select a portable folder to use? - + Create Shortcut - + Do you want to launch the game in fullscreen? - + Shortcut Created - + Successfully created a shortcut to %1 - + Shortcut may be Volatile! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? - + Failed to Create Shortcut - + Failed to create a shortcut to %1 - + Create Icon - + Cannot create icon file. Path "%1" does not exist and cannot be created. - + No firmware available - + Please install firmware to use the home menu. - + Home Menu Applet - + Home Menu is not available. Please reinstall firmware. @@ -9781,55 +9859,55 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name - + What should this mod be called? - + RomFS - + ExeFS/Patch - + Cheat - + Mod Type - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty @@ -10523,65 +10601,65 @@ By selecting "From Eden", previous save data stored in Ryujinx will be - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/uk.ts b/dist/languages/uk.ts index fe0651bdb1..fd166bc4f9 100644 --- a/dist/languages/uk.ts +++ b/dist/languages/uk.ts @@ -218,155 +218,6 @@ This would ban both their forum username and their IP address. %1 — %2 (учасники: %3/%4) — під’єднано - - CompatDB - - - Report Compatibility - Повідомити про сумісність - - - - - - - - - - Report Game Compatibility - Повідомити про сумісність гри - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - <html><head/><body><p><span style=" font-size:10pt;">Якщо ви вирішили додати результат власного тестування до</span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">переліку сумісності eden</span></a><span style=" font-size:10pt;">, на сайті буде зібрано й показано таку інформацію:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">інформація про обладнання (ЦП / ГП / операційна система)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Яка версія eden використовувалася</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Під’єднаний обліковий запис eden</li></ul></body></html> - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>Чи запускається гра?</p></body></html> - - - - Yes The game starts to output video or audio - Так Гра починає виводити відео або звук - - - - No The game doesn't get past the "Launching..." screen - Ні Гра не проходить далі екрана «Запуск...» - - - - Yes The game gets past the intro/menu and into gameplay - Так Гра переходить від вступу/меню до ігрового процесу - - - - No The game crashes or freezes while loading or using the menu - Ні Гра зависає або відбувається збій під час завантаження або використання меню - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>Чи вдається перейти до ігрового процесу?</p></body></html> - - - - Yes The game works without crashes - Так Гра працює без збоїв - - - - No The game crashes or freezes during gameplay - Ні Гра зависає або відбувається збій під час ігрового процесу - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>Чи працює гра без зависань, збоїв або неможливості продовжити ігровий процес?</p></body></html> - - - - Yes The game can be finished without any workarounds - Так Гру можна пройти без будь-яких сторонніх рішень - - - - No The game can't progress past a certain area - Ні Гру неможливо пройти далі певного місця - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>Чи можливо гру пройти повністю від початку до кінця?</p></body></html> - - - - Major The game has major graphical errors - Серйозні У грі є серйозні проблеми з графікою - - - - Minor The game has minor graphical errors - Незначні У грі є незначні проблеми з графікою - - - - None Everything is rendered as it looks on the Nintendo Switch - Жодних Усе виглядає так, як і на Nintendo Switch - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>Чи є в гри проблеми з графікою?</p></body></html> - - - - Major The game has major audio errors - Серйозні У грі є серйозні проблеми зі звуком - - - - Minor The game has minor audio errors - Незначні У грі є незначні проблеми зі звуком - - - - None Audio is played perfectly - Жодних Звук відтворюється ідеально - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>Чи є в гри проблеми зі звуком / відсутністю ефектів?</p></body></html> - - - - Thank you for your submission! - Дякуємо за ваш звіт! - - - - Submitting - Надсилання - - - - Communication error - Помилка з’єднання - - - - An error occurred while sending the Testcase - Сталася помилка під час надсилання звіту - - - - Next - Далі - - ConfigurationShared @@ -571,39 +422,39 @@ Can help reduce stuttering at lower framerates. - + Backend: Бекенд: - CPU Overclock - Розгін ЦП + CPU Clocks + Тактові частоти ЦП - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. - Розгін емульованого ЦП, щоб прибрати деякі обмеження частоти кадрів. Слабші ЦП можуть зіткнутися зі зменшеною продуктивністю, а деякі ігри можуть працювати некоректно. -Використовуйте «Підвищення (1700 МГц)», щоб емулювати максимальну тактову частоту справжнього Switch, або «Швидко (2000 МГц)», щоб подвоїти частоту. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + Збільшує тактову частоту, про яку повідомляє емульований ЦП, завдяки чому прибираються деякі обмеження частоти кадрів. +На слабших ЦП це може спричинити зменшення продуктивності, а деякі ігри можуть неправильно відтворюватися. - + Custom CPU Ticks Користувацькі такти ЦП - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. Налаштування власного значення тактів ЦП. Більші значення можуть збільшити продуктивність, але також можуть спричинити блокування. Рекомендовані значення в діапазоні 77–21000. - + Enable Host MMU Emulation (fastmem) Увімкнути емуляцію MMU хоста (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. @@ -612,100 +463,100 @@ Disabling this forces all memory accesses to use Software MMU Emulation. - + Unfuse FMA (improve performance on CPUs without FMA) Не використовувати FMA (покращує продуктивність на ЦП без FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. Це налаштування покращує швидкість завдяки зменшенню точності виконання операцій множення й складання з окрегленням на ЦП без вбудованої підтримки FMA. - + Faster FRSQRTE and FRECPE Швидші FRSQRTE та FRECPE - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. Це налаштування покращує швидкість виконання деяких приблизних функцій із рухомою комою завдяки використанню менш точних вбудованих приближеннях. - + Faster ASIMD instructions (32 bits only) Швидші інструкції ASIMD (лише 32 біти) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. Це налаштування покращує швидкість виконання 32 бітових функцій ASIMD із рухомою комою завдяки використанню неправильних режиміс округлення. - + Inaccurate NaN handling Неточна обробка NaN - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. Це налаштування покращує швидкість завдяки вилученню перевірки NaN. Зверніть увагу, що також це зменшує точність виконання певних інструкцій із рухомою комою. - + Disable address space checks Вимкнути перевірки адресного простору - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. Це налаштування покращує швидкість завдяки вимкненню перевірок безпеки перед операціями з пам’яттю. Вимкнення може дозволити грі виконувати довільний код. - + Ignore global monitor Ігнорувати глобальний моніторинг - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. Це налаштування покращує швидкість завдяки покладанню лише на семантику cmpxchg, щоб забезпечити безпеку інструкцій ексклюзивного доступу. Зверніть увагу, що це може спричинити взаємне блокування або інші умови змагання даних. - + API: API: - + Changes the output graphics API. Vulkan is recommended. Змінює API виведення графіки. Рекомендовано використовувати Vulkan. - + Device: Пристрій: - + This setting selects the GPU to use (Vulkan only). Це налаштування вибирає ГП для використання (лише Vulkan). - + Resolution: Роздільність: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. @@ -714,27 +565,27 @@ Options lower than 1X can cause artifacts. Варіанти нище ніж 1X можуть спричинити проблеми з візуалізацією. - + Window Adapting Filter: Фільтр адаптації вікна: - + FSR Sharpness: Різкість FSR: - - Determines how sharpened the image will look using FSR's dynamic contrast. - Визначає, наскільки різким буде виглядати зображення при використанні динамічного контрасту FSR. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. + Визначає, наскільки різким буде виглядати зображення при використанні динамічного контрасту FSR або SGSR. - + Anti-Aliasing Method: Метод згладжування: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. @@ -742,13 +593,23 @@ FXAA can produce a more stable picture in lower resolutions. SMAA забезпечує найкращу якість. FXAA може створювати стабільніше зображення при низьких роздільностях. + + + Enable post-processing effects + Увімкнути ефекти постобробки + + Applies post-processing effects to the final image. + Застосовує ефекти постобробки до фінального зображення. + + + Fullscreen Mode: Повноекранний режим: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. @@ -757,12 +618,12 @@ Exclusive fullscreen may offer better performance and better Freesync/Gsync supp «Ексклюзивний повноекранний» може надати кращі продуктивність і підтримку Freesync/Gsync. - + Aspect Ratio: Співвідношення сторін: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. @@ -771,36 +632,36 @@ Also controls the aspect ratio of captured screenshots. Також керує співвідношеням сторін знімків екрана. - + Use persistent pipeline cache Використовувати стійкий кеш конвеєра - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. Дозволяє зберігати шейдери на накопичувачі для швидшого завантаження під час наступних запусків гри. Вимкнення цього налаштування задумане лише для зневадження. - + Use asynchronous GPU emulation Використовувати асинхронну емуляцію ГП - + Uses an extra CPU thread for rendering. This option should always remain enabled. Використовує додатковий потік ЦП для візуалізації. Це налаштування повинно завжди залишатися увімкненим. - + NVDEC emulation: Емуляція NVDEC: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. @@ -809,12 +670,12 @@ In most cases, GPU decoding provides the best performance. У більшості випадків декодування за допомогою ГП забезпечує найкращу продуктивність. - + ASTC Decoding Method: Метод декодування ASTC: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -826,12 +687,12 @@ stuttering but may present artifacts. Асинхронно ЦП: Використання ЦП для декодування ASTC-текстур по мірі їх викликів. Повністю усуває затримки декодування ASTC ціною проблем з візуалізацією, поки текстури декодуються. - + ASTC Recompression Method: Метод перестиснення ASTC: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. @@ -839,44 +700,64 @@ BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, BC1/BC3: Проміжний формат буде перепаковано у формат BC1 або BC3 для збереження відеопам’яті, але це негатривно вплине на якість зображення. - + Frame Pacing Mode (Vulkan only) Режим виведення кадрів (лише Vulkan) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. Керує тим, як емулятор виконує виведення кадрів, щоб зменшити затримки й забезпечити плавнішу й стабільнішу частоту кадрів. - + VRAM Usage Mode: Режим використання відеопам’яті: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. Це налаштування вибирає, чи повинен емулятор надавати перевагу заощадженню пам’яті, чи по максимуму використовувати доступну відеопам’ять задля продуктивності. Режим «Агресивно» може вплинути на продуктивність інших застосунків, як-от засоби запису. - + Skip CPU Inner Invalidation Пропускати внутрішнє анулювання ЦП - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. Пропускає деякі анулювання кешу під час оновлень пам’яті, зменшуючи використання ЦП й виправляючи затримки. Це може спричинити збої. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: Режим вертикальної синхронізації: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -887,12 +768,12 @@ Mailbox може мати меншу затримку, ніж FIFO, і не ма Immediate (без синхронізації) показує всі кадри й може створювати розриви. - + Sync Memory Operations Синхронізувати операції з пам’яттю - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. @@ -901,102 +782,116 @@ Unreal Engine 4 games often see the most significant changes thereof. Ігри на Unreal Engine 4 часто зазнають найзначніших змін. - + Enable asynchronous presentation (Vulkan only) Увімкнути асинхронне подання (лише Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. Трохи покращує продуктивність завдяки переміщенню подання на окремий потік ЦП. - + Force maximum clocks (Vulkan only) Примусово максимальна тактова частота (лише Vulkan) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. Виконує роботу у фоновому режимі в очікуванні графічних команд, не даючи змоги ГП знижувати тактову частоту. - + Anisotropic Filtering: Анізотропна фільтрація: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. Керує якістю візуалізації текстур під непрямими кутами. Для більшості ГП можна вільно вибирати 16x. - + GPU Mode: Режим ГП: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. Керує режимом емуляції ГП. -Більшість ігор добре візуалізуються з режимами «Швидко» або «Збалансовано», але деякі ігри можуть потребувати режиму «Точно». +Більшість ігор добре візуалізуються з режимом «Швидко», але деякі ігри можуть потребувати режиму «Точно». Частинки зазвичай правильно візуалізуються лише з режимом «Точно». - + DMA Accuracy: Точність DMA: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. - Керує точністю DMA. Вища точність виправляє проблеми з деякими іграми, але може погіршити продуктивність. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. + - + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation Увімкнути асинхронну компіляцію шейдерів - + May reduce shader stutter. Може зменшити шейдерні затримки. - - Fast GPU Time - Швидкий час роботи ГП + + GPU Clocks + - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. - Розганяє емульований ГП для збільшення динамічної роздільності та відстані візуалізації. -Використовуйте 256 для максимальної продуктивності та 512 для максимальної точності графіки. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. + - + GPU Unswizzle Розпакування за допомогою ГП - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. Прискорює декодування 3D-текстур BCn застосовуючи обчислення за допомогою ГП. Вимкніть у разі збоїв або проблем із графікою. - + GPU Unswizzle Max Texture Size Максимальний розмір текстур для розпакування за допомогою ГП - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. @@ -1005,48 +900,48 @@ Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size Розмір потоку розпакування за допомогою ГП - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. Встановлює максимальний обсяг даних текстур (у МіБ) для обробки на кадр. Вищі значення здатні зменшити затримки під час завантаження текстур, але можуть вплинути на стабільність кадрів. - + GPU Unswizzle Chunk Size Розмір блоків розпакування за допомогою ГП - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. Визначає кількість зрізів глибини, оброблених за одне відправлення. Збільшення здатне покращити пропускну здатність на потужних ГП, але може призвести до TDR або затримок драйвера зі слабшим устаткуванням. - + Use Vulkan pipeline cache Використовувати кеш конвеєра Vulkan - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. Вмикає особливий для різних виробників ГП кеш конвеєра. Це налаштування може значно зменшити час завантаження шейдерів у випадках, коли драйвер Vulkan не зберігає власний кеш конвеєра. - + Enable Compute Pipelines (Intel Vulkan Only) Увімкнути обчислювальні конвеєри (лише Intel Vulkan) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. @@ -1055,184 +950,182 @@ Compute pipelines are always enabled on all other drivers. Обчислювальні конвеєри завжди увімкнені у всіх інших драйверах. - + Enable Reactive Flushing Увімкнути реактивне очищення - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. Використовує реактивне очищення замість прогнозованого, забезпечуючи точнішу синхронізацію пам’яті. - + Sync to framerate of video playback Синхронізувати частоту кадрів з відтворенням відео - + Run the game at normal speed during video playback, even when the framerate is unlocked. Відтворювати гру з нормальною швидкістю під час відтворення відео навіть при розблокованій частоті кадрів. - + Barrier feedback loops Бар’єрні цикли відгуку - + Improves rendering of transparency effects in specific games. Покращує візуалізацію ефектів прозорості в деяких іграх. - + Enable buffer history Увімкнути історію буфера - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. Вмикає доступ до попередніх станів буфера. Цей параметр може покращити якість візуалізації та стабільну продуктивність у деяких іграх. - + Fix bloom effects Виправити ефекти світіння - + Removes bloom in Burnout. Прибирає світіння в Burnout. - - Enable Legacy Rescale Pass - Увімкнути застаріле масштабування - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - Може виправити проблеми з масштабуванням в іграх, покладаючись на поведінку з попередньої імплементації. -Застаріле масштабування виправляє артефакти з лініями на ГП від AMD та Intel, а також сіре блимання текстур на ГП від Nvidia в Luigis Mansion 3. - - - + Extended Dynamic State Розширений динамічний стан - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. Керує кількістю функцій, які можна використовувати в «Розширеному динамічному стані». Вищі значення допускають більше функцій і можуть збільшити продуктивність, але можуть спричинити додаткові проблеми з графікою. - + Vertex Input Dynamic State Динамічний стан введення вершин - + Enables vertex input dynamic state feature for better quality and performance. Вмикає можливість динамічного стану введення вершин для кращих якості й продуктивності. - + Sample Shading Шейдинг зразків - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. Дозволяє виконувати фрагмент шейдера для кожного зразка в багатозразковому фрагменті замість одного разу для кожного фрагмента. Покращує якість графікі ціною втрати продуктивності. Вищі значення покращують якість, але погіршують продуктивність. - + RNG Seed Початкове значення RNG - + Controls the seed of the random number generator. Mainly used for speedrunning. Керує початковим значення генератора випадкових чисел. Зазвичай використовується в спідранах. - + Device Name Назва пристрою - + The name of the console. Назва консолі. - + + Homebrew Args + Параметри запуску Homebrew + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + Параметри командного рядка, що передаються Homebrew при запуску (як-от noglsl). + + + Custom RTC Date: Користувацька дата RTC: - + This option allows to change the clock of the console. Can be used to manipulate time in games. Це налаштування дозволяє змінити час годинника консолі. Можна використовувати для маніпуляцій із часом в іграх. - + The number of seconds from the current unix time Кількість секунд від поточного unix-часу. - + Language: Мова: - + This option can be overridden when region setting is auto-select Це налаштування може перевизначитися, якщо налаштування регіону вибирається автоматично - + Region: Регіон: - + The region of the console. Регіон консолі. - + Time Zone: Часовий пояс: - + The time zone of the console. Часовий пояс консолі. - + Sound Output Mode: Режим виведення звуку: - + Console Mode: Режим консолі: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. @@ -1241,1031 +1134,1026 @@ Setting to Handheld can help improve performance for low end systems. Налаштування «Портативний» може покращити продуктивність на слабких системах. - + Prompt for user profile on boot Запитувати профіль користувача під час запуску - + Useful if multiple people use the same PC. Корисно, якщо одним комп’ютером користуються кілька користувачів. - + Pause when not in focus Призупиняти, якщо не у фокусі - + Pauses emulation when focusing on other windows. Призупиняє емуляцію при фокусування на інших вікнах. - + Confirm before stopping emulation Підтверджувати зупинку емуляції - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. Перевизначає запити на підтвердження зупинки емуляції. Увімкнення обходить такі запити й одразу зупиняє емуляцію. - + Hide mouse on inactivity Приховувати курсор миші при бездіяльності - + Hides the mouse after 2.5s of inactivity. Приховує курсор миші після 2,5 с її бездіяльності. - - Disable controller applet - Вимкнути аплет контролера - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - Примусово вимикає використання в емульованих програмах аплета контролера. -Якщо програма спробує відкрити аплет контролера, він одразу закриється. - - - + Check for updates Перевіряти оновлення - + Whether or not to check for updates upon startup. Чи перевіряти оновлення при запуску. - + Enable Gamemode Увімкнути ігровий режим - + Force X11 as Graphics Backend Примусово використовувати X11 як графічний бекенд - + Custom frontend Користувацький фронтенд - + Real applet Справжній аплет - + + + Disabled + Вимкнено + + + Never Ніколи - + On Load При завантаженні - + Always Завжди - + CPU ЦП - + GPU ГП - + CPU Asynchronous Асинхронно ЦП - + Uncompressed (Best quality) Без стиснення (Найкраща якість) - + BC1 (Low quality) ВС1 (Низька якість) - + BC3 (Medium quality) ВС3 (Середня якість) - - + + Auto Автоматично - + 30 FPS 30 к/с - + 60 FPS 60 к/с - + 90 FPS 90 к/с - + 120 FPS 120 к/с - + Conservative Заощадження - + Aggressive Агресивно - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) OpenGL GLASM (асемблерні шейдери, лише NVIDIA) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) OpenGL SPIR-V (експериментально, лише AMD/Mesa) - + Null Нічого - + Fast Швидко - - Balanced - Збалансовано - - - - + + + Accurate Точно - - + + + Default Стандартно - + Unsafe (fast) Небезпечно (швидко) - + Safe (stable) Безпечно (стабільно) - + + Immediate + + + + + Balanced + Збалансовано + + + Unsafe Небезпечно - + Paranoid (disables most optimizations) Параноїк (вимикає більшість оптимізацій) - + Debugging Зневадження - + Dynarmic Динамічно - + NCE NCE - + Borderless Windowed Безрамкове вікно - + Exclusive Fullscreen Ексклюзивний повноекранний - + No Video Output Виведення відео відсутнє - + CPU Video Decoding Декодування відео на ЦП - + GPU Video Decoding (Default) Декодування відео на ГП (стандатно) - + 0.25X (180p/270p) [EXPERIMENTAL] 0.25X (180p/270p) [ЕКСПЕРИМЕНТАЛЬНО] - + 0.5X (360p/540p) [EXPERIMENTAL] 0.5X (360p/540p) [ЕКСПЕРИМЕНТАЛЬНО] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [ЕКСПЕРИМЕНТАЛЬНО] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] 1.25X (900p/1350p) [ЕКСПЕРИМЕНТАЛЬНО] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [ЕКСПЕРИМЕНТАЛЬНО] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor Найближчий сусід - + Bilinear Білінійний - + Bicubic Бікубічний - + Gaussian Ґаусса - + Lanczos Ланцоша - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution AMD FidelityFX Super Resolution - + Area Області - + MMPX MMPX - + Zero-Tangent Нульовий тангенс - + B-Spline B-Spline - + Mitchell Мітчелла - + Spline-1 Spline-1 - - + + Snapdragon Game Super Resolution + Snapdragon Game Super Resolution + + + + Snapdragon Game Super Resolution EdgeDir + Snapdragon Game Super Resolution EdgeDir + + + None Немає - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Стандартно (16:9) - + Force 4:3 Примусово 4:3 - + Force 21:9 Примусово 21:9 - + Force 16:10 Примусово 16:10 - + Stretch to Window Розтягнути до вікна - + Automatic Автоматично - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - 32x - - - - 64x - 64x - - - + Japanese (日本語) Японська (日本語) - + American English Американська англійська - + French (français) Французька (français) - + German (Deutsch) Німецька (Deutsch) - + Italian (italiano) Італійська (italiano) - + Spanish (español) Іспанська (español) - + Chinese Китайська - + Korean (한국어) Корейська (한국어) - + Dutch (Nederlands) Нідерландська (Nederlands) - + Portuguese (português) Португальська (português) - + Russian (Русский) Російська (Русский) - + Taiwanese Тайванська - + British English Британська англійська - + Canadian French Канадська французька - + Latin American Spanish Латиноамериканська іспанська - + Simplified Chinese Спрощена китайська - + Traditional Chinese (正體中文) Традиційна китайська (正體中文) - + Brazilian Portuguese (português do Brasil) Бразильська португальська (português do Brasil) - + Polish (polska) Польська (polska) - + Thai (แบบไทย) Тайська (แบบไทย) - - + + Japan Японія - + USA США - + Europe Європа - + Australia Австралія - + China Китай - + Korea Корея - + Taiwan Тайвань - + Auto (%1) Auto select time zone Автоматично (%1) - + Default (%1) Default time zone Стандартно (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Куба - + EET EET - + Egypt Єгипет - + Eire Ейре - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Eire - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Гринвіч - + Hongkong Гонконг - + HST HST - + Iceland Ісландія - + Iran Іран - + Israel Ізраїль - + Jamaica Ямайка - + Kwajalein Кваджалейн - + Libya Лівія - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Навахо - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Польща - + Portugal Португалія - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Сінгапур - + Turkey Туреччина - + UCT UCT - + Universal Універсальний - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Зулу - + Mono Моно - + Stereo Стерео - + Surround Об’ємний - + 4GB DRAM (Default) 4GB DRAM (стандартно) - + 6GB DRAM (Unsafe) 6GB DRAM (небезпечно) - + 8GB DRAM 8GB DRAM - + 10GB DRAM (Unsafe) 10GB DRAM (небезпечно) - + 12GB DRAM (Unsafe) 12GB DRAM (небезпечно) - + Docked У докстанції - + Handheld Портативний - - - Off - Вимкнено + + + Normal + - - Boost (1700MHz) - Підвищення (1700 МГц) + + + Boost + - - Fast (2000MHz) - Швидко (2000 МГц) + + + Overclock + - + Always ask (Default) Завжди запитувати (стандартно) - + Only if game specifies not to stop Лише якщо гра вказує не зупиняти - + Never ask Ніколи не запитувати - - - Medium (256) - Середньо (256) - - - - - High (512) - Високо (512) - - - + Very Small (16 MB) Дуже малий (16 МБ) - + Small (32 MB) Малий (32 МБ) - + Normal (128 MB) Нормальний (128 МБ) - + Large (256 MB) Великий (256 МБ) - + Very Large (512 MB) Дуже великий (512 МБ) - + Very Low (4 MB) Дуже низький (4 МБ) - + Low (8 MB) Низький (8 МБ) - + Normal (16 MB) Нормальний (16 МБ) - + Medium (32 MB) Середній (32 МБ) - + High (64 MB) Високий (64 МБ) - + Very Low (32) Дуже низький (32) - + Low (64) Низький (64) - + Normal (128) Нормальний (128) - - Disabled - Вимкнено + + Medium (256) + Середньо (256) - + + High (512) + Високо (512) + + + ExtendedDynamicState 1 Розширений динамічний стан 1 - + ExtendedDynamicState 2 Розширений динамічний стан 2 - + ExtendedDynamicState 3 Розширений динамічний стан 3 - + Tree View Дерево вибору - + Grid View Таблиця @@ -2625,252 +2513,297 @@ When a program attempts to open the controller applet, it is immediately closed. Глобальний фільтр журналу - - When checked, the max size of the log increases from 100 MB to 1 GB - Якщо увімкнено, максимальний розмір журналу збільшується зі 100 МБ до 1 ГБ. + + GPU Logging/Level + - - Enable Extended Logging** - Увімкнути розширене журналювання** + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console Показувати журнал у консолі - + + When checked, the max size of the log increases from 100 MB to 1 GB + Якщо увімкнено, максимальний розмір журналу збільшується зі 100 МБ до 1 ГБ. + + + + Enable Extended Logging** + Увімкнути розширене журналювання** + + + Open Log Location Відкрити розташування журналу - + Homebrew Homebrew - + Arguments String Рядок аргументів - + Graphics Графіка - + When checked, it executes shaders without loop logic changes Якщо увімкнено, шейдери виконуються без змін логіки циклів. - + Disable Loop safety checks Вимкнути перевірки безпеки циклів - + When checked, it will dump all the macro programs of the GPU Якщо увімкнено, виводитимуться всі макропрограми ГП. - + Dump Maxwell Macros Виводити макроси Maxwell - + When checked, it enables Nsight Aftermath crash dumps Якщо увімкнено, вмикає виведення збоїв Nsight Aftermath. - + Enable Nsight Aftermath Увімкнути Nsight Aftermath - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Якщо увімкнено, при виявленні зберігатимуться всі оригінальні шейдери асемблера з кешу шейдерів накопичувача або гри. + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + - - Dump Game Shaders - Зберігати шейдери ігор + + Dump Guest (Maxwell) Shaders + - + Enable Renderdoc Hotkey Увімкнути сполучення клавіш Renderdoc - + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower Якщо увімкнено, вимикає макрокомпілятор Just In Time. Увімкнення цього налаштування cповільнює роботу ігор. - + Disable Macro JIT Вимкнути макрос JIT - + When checked, it disables the macro HLE functions. Enabling this makes games run slower Якщо увімкнено, вимикає макрофункції HLE. Увімкнення цього налаштування сповільнює роботу ігор. - + Disable Macro HLE Вимкнути макрос HLE - + When checked, the graphics API enters a slower debugging mode Якщо увімкнено, графічний API переходить у повільніший режим зневадження. - + Enable Graphics Debugging Увімкнути зневадження графіки - + When checked, yuzu will log statistics about the compiled pipeline cache Якщо увімкнено, yuzu записуватиме статистику щодо скомпільованого кешу конвеєра. - + Enable Shader Feedback Увімкнути відгук шейдерів - + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> <html><head/><body><p>Якщо увімкнено, вимикає перевпорядкування призначених відвантажень пам’яті, що дозволяє пов’язувати відвантаження з певними відмальовуваннями. У деяких випадках може зменшити продуктивність.</p></body></html> - + Disable Buffer Reorder Вимкнути перевпорядкування буфера - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced Додаткові - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Дозволяє yuzu перевіряти наявність робочого середовища Vulkan під час запуску програми. Вимкніть це налаштування, якщо це спричиняє проблеми з баченням yuzu зовнішніми програмами. - + Perform Startup Vulkan Check Виконувати перевірку Vulkan під час запуску - + Disable Web Applet Вимкнути вебаплет - + Enable All Controller Types Увімкнути всі типи контролерів - + Enable Auto-Stub Увімкнути автоматичні заглушки - + Kiosk (Quest) Mode Режим кіоску (Quest) - + Use dev.keys Використовувати dev.keys - + Enable Debug Asserts Увімкнути зневаджувальні перевірки - + Debugging Зневадження - + Battery Serial: Серійний номер акумулятора: - + + Debug Knobs: + + + + Bitmask for quick development toggles Бітова маска для швидких перемикань під час розробки - + Set debug knobs (bitmask) Встановити зневаджувальні регулятори (бітові маски) - + 16-bit debug knob set for quick development toggles 16-бітовий зневаджувальний регулятор для швидких перемикань під час розробки - + (bitmask) (бітова маска) - - Debug Knobs: - Зневаджувальні регулятори: - - - + Unit Serial: Серійний номер пристрою: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. Увімкніть, щоб виводити до консолі перелік останніх згенерированих аудіокоманд. Впливає лише на ігри, які використовують аудіорендерер. - + Dump Audio Commands To Console** Виводити аудіокоманди до консолі** - + Flush log output on each line Скидати журнал виведення з кожним рядком - + Enable FS Access Log Увімкнути журнал доступу до файлової системи - + Enable Verbose Reporting Services** Увімкнути служби докладних звітів** - + Censor username in logs Приховувати ім’я користувача в журналі - + **This will be reset automatically when Eden closes. **Це налаштування автоматично скинеться після закриття Eden. @@ -3332,33 +3265,38 @@ Would you like to delete the old save data? Колір тла: - + % - FSR sharpening percentage (e.g. 50%) + FSR/SGSR sharpening percentage (e.g. 50%) % - + + Configure Effects... + + + + Off Вимкнено - + VSync Off Вертикальну синхронізацію вимкнено - + Recommended Рекомендовано - + On Увімкнено - + VSync On Вертикальну синхронізацію увімкнено @@ -3879,6 +3817,16 @@ Would you like to delete the old save data? Motion / Touch Рух і сенсор + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4388,58 +4336,58 @@ To invert the axes, first move your joystick vertically, and then horizontally.< [очікування] - + New Profile Новий профіль - + Enter a profile name: Введіть назву профілю: - - + + Create Input Profile Створити профіль введення - + The given profile name is not valid! Задана назва профілю неправильна! - + Failed to create the input profile "%1" Не вдалося створити профіль введення «%1» - + Delete Input Profile Видалити профіль введення - + Failed to delete the input profile "%1" Не вдалося видалити профіль введення «%1» - + Load Input Profile Завантажити профіль введення - + Failed to load the input profile "%1" Не вдалося завантажити профіль введення «%1» - + Save Input Profile Зберегти профіль введення - + Failed to save the input profile "%1" Не вдалося зберегти профіль введення «%1» @@ -4487,7 +4435,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Configure Налаштувати @@ -4518,7 +4466,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Test Тест @@ -4533,77 +4481,77 @@ To invert the axes, first move your joystick vertically, and then horizontally.< Вилучити сервер - + %1:%2 %1:%2 - - - - - - + + + + + + Eden Eden - + Port number has invalid characters Номер порту містить неправильні символи - + Port has to be in range 0 and 65353 Порт повинен бути в дівпазоні 0–65353 - + IP address is not valid Неправильна IP-адреса - + This UDP server already exists Цей UDP-сервер уже існує - + Unable to add more than 8 servers Неможливо додати більше 8 серверів - + Testing Тестування - + Configuring Налаштування - + Test Successful Тест успішний - + Successfully received data from the server. Успішно отримано інформацію із сервера - + Test Failed Тест провалено - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Не вдалося отримати правильні дані з сервера.<br>Переконайтеся, що сервер правильно налаштований і вказані правильні адреса й порт. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. Відбувається налаштування калібрування або тестування UDP.<br>Дочекайтеся завершення. @@ -4788,57 +4736,57 @@ Current values are %1% and %2% respectively. Деякі налаштування доступні лише коли гра не запущена. - + Add-Ons Додатки - + System Система - + CPU ЦП - + Graphics Графіка - + Adv. Graphics Графіка (дод.) - + Ext. Graphics Графіка (дод.) - + Audio Звук - + Input Profiles Профілі введення - + Network Мережа - + Applets Аплети - + Properties Властивості @@ -4966,6 +4914,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t [&O] Відкрити у файловому менеджері + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5533,91 +5605,86 @@ Drag points to change position, or double-click table cells to edit values. - Show Compatibility List - Показувати стовпчик «Сумісність» - - - Show Add-Ons Column Показувати стовпчик «Додатки» - + Show Size Column Показувати стовпчик «Розмір» - + Show File Types Column Показувати стовпчик «Тип файлу» - + Show Play Time Column Показувати стовпчик «Награний час» - + Folder Icon Size: Розмір значка теки: - + Row 1 Text: Текст 1-го рядка: - + Row 2 Text: Текст 2-го рядка: - + Screenshots Знімки екрана - + Ask Where To Save Screenshots (Windows Only) Запитувати про місце для збереження знімка екрана (лише Windows) - + Screenshots Path: Тека знімків екрана: - + ... ... - + TextLabel TextLabel - + Resolution: Роздільність: - + Select Screenshots Path... Виберіть теку знімків екрана... - + <System> <System> - + English English - + Auto (%1 x %2, %3 x %4) Screenshot width value Автоматично (%1 x %2, %3 x %4) @@ -5854,7 +5921,7 @@ Drag points to change position, or double-click table cells to edit values.Імпортувати дані до цієї теки. Це може тривати певний час і видалить УСІ НАЯВНІ ДАНІ! - + Calculating... Обчислення... @@ -6058,50 +6125,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL недоступний! - + OpenGL shared contexts are not supported. Спільні контексти OpenGL не підтримуються. - + Eden has not been compiled with OpenGL support. Eden не скомпільовано з підтримкою OpenGL. - - - + + + Error while initializing OpenGL! Помилка під час ініціалізації OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. Ваш ГП може не підтримувати OpenGL або у вас встановлено застарілий графічний драйвер. - + Error while initializing OpenGL 4.6! Помилка під час ініціалізації OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 Ваш ГП може не підтримувати OpenGL 4.6 або у вас встановлено застарілий графічний драйвер.<br><br>Візуалізатор GL:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 Ваш ГП може не підтримувати одне або кілька розширень, необхідних для OpenGL. Переконайтеся, що у вас встановлено останній графічний драйвер.<br><br>Візуалізатор GL:<br>%1<br><br>Непідтримувані розширення:<br>%2 - + This build doesn't have OpenGL support. Ця збірка не підтримує OpenGL. @@ -6109,279 +6176,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory [&A] Додати нову теку з іграми - + Favorite Улюблені - + Start Game Запустити гру - + Start Game without Custom Configuration Запустити гру без користувацького налаштування - + Open Save Data Location Відкрити теку з даними збережень - + Open Mod Data Location Відкрити теку модів - + Open Transferable Pipeline Cache Відкрити переміщуваний кеш конвеєра - + Link to Ryujinx Під’єднати до Ryujinx - + Remove Вилучити - + Remove Installed Update Вилучити встановлене оновлення - + Remove All Installed DLC Вилучити всі доповнення - + Remove Custom Configuration Вилучити користувацьке налаштування - + Remove Cache Storage Вилучити сховище кешу - + Remove OpenGL Pipeline Cache Вилучити кеш конвеєра OpenGL - + Remove Vulkan Pipeline Cache Вилучити кеш конвеєра Vulkan - + Remove All Pipeline Caches Вилучити всі кеші конвеєра - + Remove All Installed Contents Вилучити весь встановлений вміст - + Manage Play Time Керувати награним часом - + Edit Play Time Data Редагувати награний час - + Remove Play Time Data Вилучити дані награного часу - - + + Dump RomFS Створити дамп RomFS - + Dump RomFS to SDMC Створити дамп RomFS у SDMC - + Verify Integrity Перевірити цілісність - + Copy Title ID to Clipboard Скопіювати ID проєкту до буфера обміну - - Navigate to GameDB entry - Перейти до запису GameDB - - - + Create Shortcut Створити ярлик - + Add to Desktop Додати до стільниці - + Add to Applications Menu Додати до меню застосунків - + Configure Game Налаштувати гру - + Scan Subfolders Сканувати підтеки - + Remove Game Directory Вилучити теку гри - + ▲ Move Up ▲ Перемістити вверх - + ▼ Move Down ▼ Перемістити вниз - + Open Directory Location Відкрити розташування теки - + Clear Очистити - - - Name - Назва - - - - Compatibility - Сумісність - - - - Add-ons - Додатки - - - - File type - Тип файлу - - - - Size - Розмір - - - - Play time - Награний час - GameListItemCompat - + Ingame Запускається - + Game starts, but crashes or major glitches prevent it from being completed. Гра запускається, але збої або серйозні баги перешкоджають її успішному проходженню. - + Perfect Ідеально - + Game can be played without issues. У гру можна грати без проблем. - + Playable Придатна до гри - + Game functions with minor graphical or audio glitches and is playable from start to finish. Гра має незначні графічні або звукові проблеми, але її можна пройти від початку до кінця. - + Intro/Menu Вступ/меню - + Game loads, but is unable to progress past the Start Screen. Гра завантажується, але не може просунутися далі стартового екрана. - + Won't Boot Не запускається - + The game crashes when attempting to startup. Під час спроби запуску гри відбувається збій. - + Not Tested Не протестовано - + The game has not yet been tested. Гру ще не протестовано. + + GameListModel + + + Name + Назва + + + + Add-ons + Додатки + + + + File type + Тип файлу + + + + Size + Розмір + + + + Play time + Награний час + + GameListPlaceholder - + Double-click to add a new folder to the game list Натисніть двічі, щоб додати нову теку до переліку ігор @@ -6389,17 +6449,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) %1 із %n результату%1 із %n результатів%1 із %n результатів%1 із %n результатів - + Filter: Фільтр: - + Enter pattern to filter Введіть шаблон для фільтрування @@ -6895,772 +6955,780 @@ Debug Message: [&G] Режим переліку ігор - + Game &Icon Size [&I] Розмір значків ігор - + Reset Window Size to &720p [&7] Скинути розмір вікна до 720p - + Reset Window Size to 720p Скинути розмір вікна до 720p - + Reset Window Size to &900p [&9] Скинути розмір вікна до 900p - + Reset Window Size to 900p Скинути розмір вікна до 900p - + Reset Window Size to &1080p [&1] Скинути розмір вікна до 1080p - + Reset Window Size to 1080p Скинути розмір вікна до 1080p - + &Multiplayer [&M] Багатоосібна гра - + &Tools [&T] Інструменти - + Am&iibo [&I] Amiibo - + Launch &Applet [&A] Запустити аплет - + &TAS [&T] TAS - + &Create Home Menu Shortcut [&C] Створити ярлик меню-домівки - + Install &Firmware [&F] Встановити прошивку - + &Help [&H] Допомога - + &Install Files to NAND... [&I] Встановити файли до NAND... - + L&oad File... [&O] Завантажити файл... - + Load &Folder... [&F] Завантажити теку... - + E&xit [&X] Вийти - - + + &Pause [&P] Призупинити - + &Stop [&S] Зупинити - + &Verify Installed Contents [&V] Перевірити встановлений вміст - + &About Eden [&A] Про Eden - + Single &Window Mode [&W] Одновіконний режим - + Con&figure... [&F] Налаштувати... - + Ctrl+, Ctrl+, - + Enable Overlay Display Applet Увімкнути аплет показу оверлея - + Show &Filter Bar [&F] Показати панель фільтрування - + Show &Status Bar [&S] Показати панель стану - + Show Status Bar Показати панель стану - + &Browse Public Game Lobby [&B] Переглянути публічні ігрові лобі - + &Create Room [&C] Створити кімнату - + &Leave Room [&L] Покинути кімнату - + &Direct Connect to Room [&D] Пряме з’єднання з кімнатою - + &Show Current Room [&S] Показати поточну кімнату - + F&ullscreen [&U] Повноекранний - + &Restart [&R] Перезапустити - + Load/Remove &Amiibo... [&A] Завантажити/вилучити amiibo... - - &Report Compatibility - [&R] Повідомити про сумісність - - - + Open &Mods Page [&M] Відкрити сторінку модів - - Open &Quickstart Guide - [&Q] Відкрити посібник користувача + + &User Handbook + - - &FAQ - [&F] ЧаПи - - - + &Capture Screenshot [&C] Зробити знімок екрана - + &Album [&A] Альбом - + &Set Nickname and Owner [&S] Указати псевдонім і власника - + &Delete Game Data [&D] Видалити дані гри - + &Restore Amiibo [&R] Відновити amiibo - + &Format Amiibo [&F] Форматувати amiibo - + &Mii Editor [&M] Редактор Mii - + &Configure TAS... [&C] Налаштувати TAS... - + Configure C&urrent Game... [&U] Налаштувати поточну гру... - - + + &Start [&S] Запустити - + &Reset [&S] Скинути - - + + R&ecord [&E] Запис - + Open &Controller Menu [&C] Відкрити меню контролерів - + Install Decryption &Keys [&K] Встановити ключі дешифрування - + &Home Menu [&H] Меню-домівка - + &Desktop [&D] Стільниця - + &Application Menu [&A] Меню застосунків - + &Root Data Folder [&R] Коренева тека даних - + &NAND Folder [&N] Тека NAND - + &SDMC Folder [&S] Тека SDMC - + &Mod Folder [&M] Тека модів - + &Log Folder [&L] Тека журналу - + From Folder З теки - + From ZIP Із ZIP - + &Eden Dependencies [&E] Залежності Eden - + &Data Manager [&D] Керування даними - + &Tree View [&T] Дерево вибору - + &Grid View [&G] Таблиця - + Game Icon Size Розмір значків ігор - - + + None Жодного - + Show Game &Name [&N] Показати назву гри - + Show &Performance Overlay [&P] Показати оверлей продуктивності - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) Маленький (32х32) - + Standard (64x64) Стандартний (64х64) - + Large (128x128) Великий (128х128) - + Full Size (256x256) Повнорозмірний (256х256) - + Broken Vulkan Installation Detected Виявлено пошкоджене встановлення Vulkan - + Vulkan initialization failed during boot. Не вдалося ініціалізувати Vulkan під час запуску. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping Запущено гру - + Loading Web Applet... Завантаження вебаплета... - - + + Disable Web Applet Вимкнути вебаплет - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) Вимкнення вебапплета може призвести до несподіваної поведінки, і це слід робити лише для Super Mario 3D All-Stars. Ви впевнені, що хочете вимкнути вебапплет? (Його можна знову увімкнути в налаштуваннях зневадження.) - + The amount of shaders currently being built Кількість наразі створених шейдерів - + The current selected resolution scaling multiplier. Наразі вибраний множник масштабування роздільності. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. Поточна швидкість емуляції. Значення вище або нижче 100% вказують на те, що емуляція йде швидше або повільніше, ніж на Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. Частота кадрів, яку наразі показує гра. Значення змінюватиметься залежно від гри та з кожною сценою. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. Час, потрібний для емуляції 1 кадру Switch, не враховуючи обмеження частоти кадрів або вертикальну синхронізацію. Для повношвидкісної емуляції значення повинно бути не вище 16,67 мс. - + Unmute Увімкнути звук - + Mute Вимкнути звук - + Reset Volume Скинути гучність - + + Configure Effects... + + + + &Clear Recent Files [&C] Очистити нещодавні файли - + &Continue [&C] Продовжити - + Warning: Outdated Game Format Увага: Застарілий формат гри - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. Для цієї гри ви використовуєте формат теки з деконструйованим ROM, який є застарілим форматом, заміненим на інші, як-от NCA, NAX, XCI, або NSP. У тек із деконструйованими ROM немає значків, метаданих, а також вони не підтримують оновлення.<br>Для подробиць стосовно різноманітних форматів Switch, які підтримує Eden, ознайомтеся з нашим посібником користувача. Це повідомлення не буде показано знову. - - + + Error while loading ROM! Помилка під час завантаження ROM! - + The ROM format is not supported. Непідтримуваний формат ROM. - + An error occurred initializing the video core. Сталася помилка під час ініціалізації відеоядра. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. - В Eden сталася помилка під час роботи відеоядра. Зазвичай це відбувається через застарілі драйвери ГП, зокрема інтегрованих. Для подробиць перегляньте журнал. Для додаткової інформації стосовно доступу до журналу перегляньте таку сторінку: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>Як відвантажити файл журналу</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. + - + Error while loading ROM! %1 %1 signifies a numeric error code. Помилка під час завантаження ROM! %1 - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. %1<br>Створіть новий дамп файлів або зверніться по допомогу в Discord/Stoat. - + An unknown error occurred. Please see the log for more details. Сталася невідома помилка. Ознайомтеся з журналом, щоб дізнатися подробиці. - + + (64-bit) (64-бітовий) - + + (32-bit) (32-бітовий) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit %1 %2 - + Closing software... Закриття програмного засобу... - + Save Data Дані збережень - + Mod Data Дані модів - + Error Opening %1 Folder Помилка під час відкриття теки «%1» - - + + Folder does not exist! Теки не існує! - + Remove Installed Game Contents? Вилучити встановлений вміст гри? - + Remove Installed Game Update? Вилучити встановлені оновлення гри? - + Remove Installed Game DLC? Вилучити встановлені доповнення гри? - + Remove Entry Вилучити запис - + Delete OpenGL Transferable Shader Cache? Видалити переміщуваний кеш шейдерів OpenGL? - + Delete Vulkan Transferable Shader Cache? Видалити переміщуваний кеш шейдерів Vulkan? - + Delete All Transferable Shader Caches? Видалити весь переміщуваний кеш шейдерів? - + Remove Custom Game Configuration? Вилучити користувацькі налаштування гри? - + Remove Cache Storage? Вилучити сховище кешу? - + Remove File Вилучити файл - + Remove Play Time Data Вилучити дані награного часу - + Reset play time? Скинути награний час? - - + + RomFS Extraction Failed! Не вдалося видобути RomFS! - + There was an error copying the RomFS files or the user cancelled the operation. Під час копіювання файлів RomFS сталася помилка або користувач скасував операцію. - + Full Повний - + Skeleton Скелет - + Select RomFS Dump Mode Виберіть режим створення дампу RomFS - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. Виберіть, як ви хочете виконати дамп RomFS <br>Повний скопіює всі файли до нової теки, тоді як <br>скелет створить лише структуру тек. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root За адресою «%1» недостатньо вільного місця для видобування RomFS. Звільніть місце або виберіть іншу теку для створення дампу в «Емуляція» → «Налаштувати» → «Система» → «Файлова система» → «Коренева тека дампів». - + Extracting RomFS... Видобування RomFS... - - + + Cancel Скасувати - + RomFS Extraction Succeeded! RomFS видобуто успішно! - + The operation completed successfully. Операцію успішно виконано. - + Error Opening %1 Помилка під час відкриття «%1» - + Select Directory Вибрати теку - + Properties Властивості - + The game properties could not be loaded. Неможливо завантажити властивості гри. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. Виконуваний файл Switch (%1);;Усі файли (*.*) - + Load File Завантажити файл - + Open Extracted ROM Directory Відкрити теку видобутого ROM - + Invalid Directory Selected Вибрано неправильну теку - + The directory you have selected does not contain a 'main' file. Вибрана тека не містить файлу «main». - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) Встановлюваний файл Switch (*.nca, *.nsp, *.xci);;Архів вмісту Nintendo (*.nca);;Пакет подання Nintendo (*.nsp);;Образ картриджа NX (*.xci) - + Install Files Встановити файли - + %n file(s) remaining Лишився 1 файлЛишилося %n файлиЛишилося %n файлівЛишилося %n файлів - + Installing file "%1"... Встановлення файлу «%1»... - - + + Install Results Результати встановлення - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. Щоб уникнути можливих конфліктів, ми не радимо користувачам встановлювати ігри в NAND. Користуйтеся цією функцією лише для встановлення оновлень і доповнень. - + %n file(s) were newly installed Щойно встановлено %n файл @@ -7670,7 +7738,7 @@ Please, only use this feature to install updates and DLC. - + %n file(s) were overwritten Перезаписано %n файл @@ -7680,7 +7748,7 @@ Please, only use this feature to install updates and DLC. - + %n file(s) failed to install Не вдалося встановити %n файл @@ -7690,361 +7758,324 @@ Please, only use this feature to install updates and DLC. - + System Application Системний застосунок - + System Archive Системний архів - + System Application Update Оновлення системного застосунку - + Firmware Package (Type A) Пакет прошивки (Тип А) - + Firmware Package (Type B) Пакет прошивки (Тип Б) - + Game Гра - + Game Update Оновлення гри - + Game DLC Доповнення гри - + Delta Title Проєкт «Дельта» - + Select NCA Install Type... Виберіть тип встановлення NCA... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) Виберіть тип проєкту, який ви хочете встановити для цього NCA: (У більшості випадків підходить стандартний вибір «Гра».) - + Failed to Install Не вдалося встановити - + The title type you selected for the NCA is invalid. Тип проєкту, який ви вибрали для NCA, неправильний. - + File not found Файл не виявлено - + File "%1" not found Файл «%1» не виявлено - + OK Гаразд - + Function Disabled Функцію вимкнену - + Compatibility list reporting is currently disabled. Check back later! Звітування для переліку сумісності наразі вимкнено. Зазирніть пізніше! - + Error opening URL Помилка під час відкриття URL - + Unable to open the URL "%1". Не вдалося відкрити URL: «%1». - + TAS Recording Записування TAS - + Overwrite file of player 1? Перезаписати файл гравця 1? - + Invalid config detected Виявлено неправильне налаштування - + Handheld controller can't be used on docked mode. Pro controller will be selected. Портативний контролер неможливо використовувати в режимі докстанції. Буде вибрано контролер Pro. - - + + Amiibo Amiibo - - + + The current amiibo has been removed Поточний amiibo вилучено - + Error Помилка - - + + The current game is not looking for amiibos Поточна гра не очікує amiibo - + Amiibo File (%1);; All Files (*.*) Файл amiibo (%1);; Усі файли (*.*) - + Load Amiibo Завантажити amiibo - + Error loading Amiibo data Помилка під час завантаження даних amiibo - + The selected file is not a valid amiibo Вибраний файл не є дійсним amiibo - + The selected file is already on use Вибраний файл уже використовується - + An unknown error occurred Сталася невідома помилка - - - Keys not installed - Ключі не встановлено - - - - - Install decryption keys and restart Eden before attempting to install firmware. - Встановіть ключі дешифрування та перезапустіть Eden, перш ніж спробувати встановити прошивку. - - - - Select Dumped Firmware Source Location - Виберіть розташування дампу прошивки - - - - Select Dumped Firmware ZIP - Виберіть ZIP із дампом прошивки - - - - Zipped Archives (*.zip) - Zip-архіви (*.zip) - - - - Firmware cleanup failed - Не вдалося очистити прошивку - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - Не вдалося очистити видобутий кеш прошивки. -Перевірте дозволи на запис у системної теки temp і спробуйте знову. -Помилка зі звіту від ОС: %1 - - - + No firmware available Немає доступних прошивок - + Firmware Corrupted Прошивка пошкоджена - + Unknown applet Невідомий аплет - + Applet doesn't map to a known value. Аплет не призначено до відомого значення. - + Record not found Запис не виявлено - + Applet not found. Please reinstall firmware. Аплет не виявлено. Перевстановіть прошивку. - + Capture Screenshot Зробити знімок екрана - + PNG Image (*.png) Зображення PNG (*.png) - + TAS state: Running %1/%2 Стан TAS: Працює %1/%2 - + TAS state: Recording %1 Стан TAS: Триває запис %1 - + TAS state: Idle %1/%2 Стан TAS: Бездіяльність %1/%2 - + TAS State: Invalid Стан TAS: Неправильний - + &Stop Running [&S] Зупинити - + Stop R&ecording [&E] Зупинити запис - + Building: %n shader(s) Компіляція: %n шейдерКомпіляція: %n шейдериКомпіляція: %n шейдерівКомпіляція: %n шейдерів - + Scale: %1x %1 is the resolution scaling factor Масштаб: %1x - + Speed: %1% / %2% Швидкість: %1% / %2% - + Speed: %1% Швидкість: %1% - + Game: %1 FPS Гра: %1 к/с - + Frame: %1 ms Кадр: %1 мс - - - FSR - FSR - - - + NO AA БЕЗ ЗГЛАДЖУВАННЯ - + + NO FX + + + + + FX + + + + VOLUME: MUTE ГУЧНІСТЬ: ВИМКНЕНО - + VOLUME: %1% Volume percentage (e.g. 50%) ГУЧНІСТЬ: %1% - + Derivation Components Missing Відсутні компоненти виведення - + Decryption keys are missing. Install them now? Відсутні ключі шифрування. Встановити їх зараз? - + Wayland Detected! Виявлено Wayland! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -8055,74 +8086,74 @@ Would you like to force it for future launches? Хочете примусово увімкнути його для наступних запусків? - + Use X11 Використовувати X11 - + Continue with Wayland Продовжити з Wayland - + Don't show again Не показувати знову - + Restart Required Потрібен перезапуск - + Restart Eden to apply the X11 backend. Перезапуск Eden для застосування бекенду X11. - + Slow Сповільнення - + Turbo Прискорення - + Unlocked Розблоковано - + Select RomFS Dump Target Виберіть розташування для створення дампу RomFS - + Please select which RomFS you would like to dump. Виберіть, який дамп RomFS ви хочете створити. - + Are you sure you want to close Eden? Ви впевнені, що хочете закрити Eden? - - - + + + Eden Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. Ви впевнені, що хочете зупинити емуляцію? Увесь незбережений поступ буде втрачено. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8190,6 +8221,11 @@ Would you like to bypass this and exit anyway? ScaleForce ScaleForce + + + FSR + FSR + Area @@ -8201,52 +8237,57 @@ Would you like to bypass this and exit anyway? MMPX - + + SGSR + SGSR + + + + SGSR EdgeDir + SGSR EdgeDir + + + Docked У докстанції - + Handheld Портативний - + Fast Швидко - - Balanced - Збалансовано - - - + Accurate Точно - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL SPIRV OpenGL SPIRV - + OpenGL GLASM OpenGL GLASM - + Null Нічого @@ -8289,7 +8330,7 @@ If you wish to clean up the files which were left in the old data location, you %1 - + Data was migrated successfully. Дані перенесено успішно. @@ -8782,8 +8823,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8796,10 +8837,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8814,9 +8855,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8978,50 +9019,50 @@ p, li { white-space: pre-wrap; } [невизначено] - + %1%2 %1%2 - + [invalid] [неправильно] - - + + %1%2Hat %3 %1%2Напр. %3 - - - + + + %1%2Axis %3 %1%2Вісь %3 - + %1%2Axis %3,%4,%5 %1%2Вісь %3,%4,%5 - + %1%2Motion %3 %1%2Рух %3 - - + + %1%2Button %3 %1%2Кнопка %3 - + [unused] [не використано] @@ -9147,47 +9188,47 @@ p, li { white-space: pre-wrap; } %1 грає в %2 - + Play Time: %1 Награний час: %1 - + Never Played Ще не зіграно - + Version: %1 Версія: %1 - + Version: 1.0.0 Версія: 1.0.0 - + Installed SD Titles Проєкти, встановлені до SD - + Installed NAND Titles Проєкти, встановлені до NAND - + System Titles Системні проєкти - + Add New Game Directory Додати нову теку з іграми - + Favorites Улюблені @@ -9308,14 +9349,14 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware Гра потребує прошивку - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. - Гра, яку ви намагаєтеся запустити, потребує прошивку, щоб запуститися або пройти меню запуску. <a href='https://yuzu-mirror.github.io/help/quickstart'>Створіть дамп і встановіть прошивку</a> або натисніть «Гаразд», щоб однаково запустити. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. + @@ -9324,10 +9365,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel Скасувати @@ -9348,7 +9389,7 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 @@ -9357,207 +9398,242 @@ p, li { white-space: pre-wrap; } %1 - - + + Verifying integrity... Перевірка цілісності... - - + + Integrity verification succeeded! Перевірка цілісності успішна! - - + + The operation completed successfully. Операцію успішно завершено. - - + + Integrity verification failed! Не вдалося перевірити цілісність! - + File contents may be corrupt or missing. Файли вмісту можуть бути пошкоджені або відсутні. - + Integrity verification couldn't be performed Неможливо виконати перевірку цілісності - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. Встановлення прошивки скасовано. Можливо, прошивка в поганому стані або пошкоджена. Неможливо перевірити на дійсність файли вмісту. - + Select Dumped Keys Location Виберіть розатшування дампу ключів - + Decryption Keys install succeeded Ключі дешифрування успішно встановлено - + Decryption Keys install failed Не вдалося встановити ключі дешифрування - + Orphaned Profiles Detected! Виявлено покинуті профілі! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> ЯКЩО ВИ ЦЕ НЕ ПРОЧИТАЄТЕ, МОЖУТЬ СТАТИСЯ НЕОЧІКУВАНІ ПОГАНІ РЕЧІ!<br>Eden виявив такі теки збережень без прикріпленого профілю:<br>%1<br><br>Є такі дійсні профілі:<br>%2<br><br>Натисніть «ОК», щоб відкрити теку збережень і полагодити свої профілі.<br>Порада: скопіюйте у будь-яке інше місце вміст найбільшої теки, у якій нещодавно були зміни, видаліть профілі, що лишилися та перемістіть скопійований вміст до провильного профілю.<br><br>Досі не розумієте, що робити? Перегляньте <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>сторінку допомоги</a>.<br> - + Really clear data? Дійсно очистити дані? - + Important data may be lost! Може бути втрачено важливі дані! - + Are you REALLY sure? Ви ДІЙСНО впевнені? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. Після видалення ваші дані буде НЕМОЖЛИВО повернути! Виконуйте цю дію, лише якщо ви на 100% упевнені, що хочете видалити ці дані. - + Clearing... Очищення... - + Select Export Location Виберіть розташування для експортування - + %1.zip %1.zip - - + + + Zipped Archives (*.zip) Zip-архіви (*.zip) - + Exporting data. This may take a while... Експортування даних. Це може тривати певний час... - + Exporting Експортування - + Exported Successfully Успішно експортовано - + Data was exported successfully. Дані успішно експортовано. - + Export Cancelled Експортування скасовано - + Export was cancelled by the user. Експортування скасовано користувачем. - + Export Failed Не вдалося експортувати - + Ensure you have write permissions on the targeted directory and try again. Запевніться, що у вас є дозволи на записування до вказаної теки й спробуйте знову. - + Select Import Location Виберіть розташування для імпортування - + Import Warning Попередження щодо імпортування - + All previous data in this directory will be deleted. Are you sure you wish to proceed? Усі попередні в цій теці будуть видалені. Ви впевнені, що хочете продовжити? - + Importing data. This may take a while... Імпортування даних. Це може тривати певний час... - + Importing Імпортування - + Imported Successfully Успішно імпортовано - + Data was imported successfully. Дані успішно імпортовано. - + Import Cancelled Імпортування скасовано - + Import was cancelled by the user. Імпортування скасовано користувачем. - + Import Failed Не вдалося імпортувати - + Ensure you have read permissions on the targeted directory and try again. Запевніться, що у вас є дозволи на читання зі вказаної теки й спробуйте знову. + + + Keys not installed + Ключі не встановлено + + + + Install decryption keys and restart Eden before attempting to install firmware. + Встановіть ключі дешифрування та перезапустіть Eden, перш ніж спробувати встановити прошивку. + + + + Select Dumped Firmware Source Location + Виберіть розташування дампу прошивки + + + + Select Dumped Firmware ZIP + Виберіть ZIP із дампом прошивки + + + + Firmware cleanup failed + Не вдалося очистити прошивку + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + Не вдалося очистити видобутий кеш прошивки. +Перевірте дозволи на запис у системної теки temp і спробуйте знову. +Помилка зі звіту від ОС: %1 + QtCommon::FS @@ -9806,72 +9882,72 @@ Would you like to manually select a portable folder to use? Неможливо видалити кеш метаданих. Можливо, він використовується або не існує. - + Create Shortcut Створити ярлик - + Do you want to launch the game in fullscreen? Ви хочете запустити гру в повноеранному режимі? - + Shortcut Created Ярлик створено - + Successfully created a shortcut to %1 Успішно створено ярлик для: %1 - + Shortcut may be Volatile! Ярлик може бути нестабільним! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? Це створить ярлик для поточного AppImage. Можливо, він не буде належно працювати після оновлення. Продовжити? - + Failed to Create Shortcut Не вдалося створити ярлик - + Failed to create a shortcut to %1 Не вдалося створити ярлик для: %1 - + Create Icon Створити значок - + Cannot create icon file. Path "%1" does not exist and cannot be created. Неможливо створити файл значка. Шлях «%1» не існує або не може бути створений. - + No firmware available Немає доступних прошивок - + Please install firmware to use the home menu. Встановіть прошивку, щоб користуватися меню-домівкою. - + Home Menu Applet Аплет меню-домівки - + Home Menu is not available. Please reinstall firmware. Меню-домівка недоступна. Перевстановіть прошивку. @@ -9879,37 +9955,37 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name Назва мода - + What should this mod be called? Як повинен називатися цей мод? - + RomFS RomFS - + ExeFS/Patch ExeFS/патч - + Cheat Чит - + Mod Type Тип мода - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. @@ -9918,18 +9994,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed Не вдалося видобути мод - + Failed to create temporary directory %1 Не вдалося створити тимчасову теку %1 - + Zip file %1 is empty Zip-файл %1 порожній @@ -10630,66 +10706,66 @@ By selecting "From Eden", previous save data stored in Ryujinx will be %1 доступно для завантаження. - + New Version Location Розташування нової версії - + All Files (*.*) Усі файли (*.*) - - - + + + Failed to save file Не вдалося зберегти файл - + Could not open file %1 for writing. Не вдалося відкрити файл «%1» для запису. - + Downloading... Завантаження... - + Cancel Скасувати - + Could not write to file %1. Не вдалося записати до файлу «%1». - + Could not commit to file %1. Не вдалося вкласти до файлу «%1». - + Failed to download file Не вдалося завантажити файл - + Could not download from %1%2 Error code: %3 Не вдалося завантажити з %1%2 Код помилки: %3 - + Download Complete Завантажено - + Successfully downloaded %1. Would you like to open it? %1 успішно завантажено. Хочете відкрити? diff --git a/dist/languages/vi.ts b/dist/languages/vi.ts index 5cbda3c24f..f0a7e591bd 100644 --- a/dist/languages/vi.ts +++ b/dist/languages/vi.ts @@ -211,155 +211,6 @@ Việc này sẽ ban tên người dùng trên diễn đàn và IP của họ lu %1 - %2 (%3/%4 thành viên) - đã kết nối - - CompatDB - - - Report Compatibility - Báo cáo độ tương thích - - - - - - - - - - Report Game Compatibility - Báo cáo về độ tương thích của game - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>Game có khởi chạy thành công hay không?</p></body></html> - - - - Yes The game starts to output video or audio - Có Game có xuất ra hình và âm thanh - - - - No The game doesn't get past the "Launching..." screen - Không Game không thể qua được màn hình "Launching..." - - - - Yes The game gets past the intro/menu and into gameplay - Có Game có thể qua được khúc intro/menu và vô được game - - - - No The game crashes or freezes while loading or using the menu - Không Game crash hoặc đơ khi đang loading hoặc sử dụng menu - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>Game có vào được gameplay không?</p></body></html> - - - - Yes The game works without crashes - Có Game chạy ổn định, không bị crash - - - - No The game crashes or freezes during gameplay - Không Game crash hoặc đơ trong lúc chơi - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>Game chạy có ổn định với không crash, đơ hoặc bị kẹt trong lúc chơi hay không?</p></body></html> - - - - Yes The game can be finished without any workarounds - Có Game có thể hoàn thành mà không cần phải làm gì thêm - - - - No The game can't progress past a certain area - Không Game không thể qua được mốt số khúc - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>Game có chơi được từ đầu đến cuối hay không?</p></body></html> - - - - Major The game has major graphical errors - Lỗi nặng Game bị lỗi hình ảnh nặng - - - - Minor The game has minor graphical errors - Lỗi nhẹ Game bị lỗi hình ảnh nhẹ - - - - None Everything is rendered as it looks on the Nintendo Switch - Không lỗi Game nhìn y hệt như trên Nintendo Switch - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>Game có bị lỗi gì về hình ảnh không?</p></body></html> - - - - Major The game has major audio errors - Lỗi nặng Game bị lỗi âm thanh nặng - - - - Minor The game has minor audio errors - Lỗi nhẹ Game bị lỗi âm thanh nhẹ - - - - None Audio is played perfectly - Không lỗi Âm thanh hoạt động hoàn hảo - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>Game có bị lỗi âm thanh hay lỗi hiệu ứng hay không?</p></body></html> - - - - Thank you for your submission! - Cảm ơn bạn đã gửi đến cho chúng tôi! - - - - Submitting - Đang gửi - - - - Communication error - Đã xảy ra lỗi giao tiếp với máy chủ - - - - An error occurred while sending the Testcase - Lỗi khi gửi Testcase - - - - Next - Tiếp theo - - ConfigurationShared @@ -559,231 +410,241 @@ Can help reduce stuttering at lower framerates. - + Backend: - CPU Overclock + CPU Clocks - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. - + Custom CPU Ticks - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. - + Enable Host MMU Emulation (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. - + Unfuse FMA (improve performance on CPUs without FMA) Không dùng FMA (tăng hiệu suất cho các dòng CPU không hỗ trợ FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. - + Faster FRSQRTE and FRECPE FRSQRTE và FRECPE nhanh hơn - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. - + Faster ASIMD instructions (32 bits only) Các chỉ thị ASIMD nhanh hơn (chỉ cho 32 bit) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. - + Inaccurate NaN handling Xử lí NaN không chính xác - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. - + Disable address space checks Tắt kiểm tra không gian địa chỉ - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. - + Ignore global monitor Bỏ qua màn hình chung - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. - + API: API: - + Changes the output graphics API. Vulkan is recommended. - + Device: Thiết bị: - + This setting selects the GPU to use (Vulkan only). - + Resolution: Độ phân giải: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. - + Window Adapting Filter: Bộ lọc điều chỉnh cửa sổ: - + FSR Sharpness: Độ nét FSR: - - Determines how sharpened the image will look using FSR's dynamic contrast. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. - + Anti-Aliasing Method: Phương pháp khử răng cưa: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Chế độ toàn màn hình: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. - + Aspect Ratio: Tỉ lệ khung hình: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. - + Use persistent pipeline cache - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. - + Use asynchronous GPU emulation - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + NVDEC emulation: Giả lập NVDEC: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. - + ASTC Decoding Method: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -792,55 +653,75 @@ stuttering but may present artifacts. - + ASTC Recompression Method: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. - + Skip CPU Inner Invalidation - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: Chế độ Vsync: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -848,1361 +729,1371 @@ Immediate (no synchronization) presents whatever is available and can exhibit te - + Sync Memory Operations - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. - + Enable asynchronous presentation (Vulkan only) Bật hiển thị bất đồng bộ (chỉ cho Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. - + Force maximum clocks (Vulkan only) Buộc chạy ở xung nhịp tối đa (chỉ cho Vulkan) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. Chạy các công việc trong nền trong khi đang chờ lệnh đồ họa để giữ cho GPU không giảm xung nhịp. - + Anisotropic Filtering: Lọc bất đẳng hướng: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. - + GPU Mode: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - + DMA Accuracy: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. - + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation - + May reduce shader stutter. - - Fast GPU Time + + GPU Clocks - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. - + GPU Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache Dùng bộ nhớ đệm pipeline Vulkan - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. - + Enable Compute Pipelines (Intel Vulkan Only) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. - + Enable Reactive Flushing Bật xả tương ứng - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. - + Sync to framerate of video playback Đồng bộ hóa với tốc độ khung hình khi phát video - + Run the game at normal speed during video playback, even when the framerate is unlocked. Chạy game với tốc độ bình thường trong quá trình phát video, ngay cả khi tốc độ khung hình được mở khóa. - + Barrier feedback loops Vòng lặp phản hồi rào cản - + Improves rendering of transparency effects in specific games. Cải thiện hiệu quả kết xuất của hiệu ứng trong suốt trong một số game. - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. - + Vertex Input Dynamic State - + Enables vertex input dynamic state feature for better quality and performance. - + Sample Shading - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. - + RNG Seed Hạt giống RNG - + Controls the seed of the random number generator. Mainly used for speedrunning. - + Device Name Tên thiết bị - + The name of the console. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: - + This option allows to change the clock of the console. Can be used to manipulate time in games. - + The number of seconds from the current unix time - + Language: - + This option can be overridden when region setting is auto-select - + Region: Vùng: - + The region of the console. - + Time Zone: Múi giờ: - + The time zone of the console. - + Sound Output Mode: Chế độ đầu ra âm thanh: - + Console Mode: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. - + Prompt for user profile on boot - + Useful if multiple people use the same PC. - + Pause when not in focus - + Pauses emulation when focusing on other windows. - + Confirm before stopping emulation - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. - + Hide mouse on inactivity Ẩn con trỏ chuột khi không dùng - + Hides the mouse after 2.5s of inactivity. - - Disable controller applet - Vô hiệu hoá applet tay cầm - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - - - - + Check for updates - + Whether or not to check for updates upon startup. - + Enable Gamemode - + Force X11 as Graphics Backend - + Custom frontend - + Real applet - + + + Disabled + + + + Never - + On Load - + Always - + CPU CPU - + GPU - + CPU Asynchronous - + Uncompressed (Best quality) Không nén (Chất lượng tốt nhất) - + BC1 (Low quality) BC1 (Chất lượng thấp) - + BC3 (Medium quality) BC3 (Chất lượng trung bình) - - + + Auto Tự động - + 30 FPS - + 60 FPS - + 90 FPS - + 120 FPS - + Conservative - + Aggressive - + Vulkan Vulkan - + OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) - + Null Null - + Fast - - Balanced - - - - - + + + Accurate Chính xác - - + + + Default Mặc định - + Unsafe (fast) - + Safe (stable) - + + Immediate + + + + + Balanced + + + + Unsafe Không an toàn - + Paranoid (disables most optimizations) Paranoid (vô hiệu hoá hầu hết sự tối ưu) - + Debugging - + Dynarmic - + NCE - + Borderless Windowed Cửa sổ không viền - + Exclusive Fullscreen Toàn màn hình - + No Video Output Không có đầu ra video - + CPU Video Decoding Giải mã video bằng CPU - + GPU Video Decoding (Default) Giải mã video bằng GPU (Mặc định) - + 0.25X (180p/270p) [EXPERIMENTAL] - + 0.5X (360p/540p) [EXPERIMENTAL] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [THỬ NGHIỆM] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [THỬ NGHIỆM] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor Nearest Neighbor - + Bilinear Bilinear - + Bicubic Bicubic - + Gaussian Gaussian - + Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution - + Area - + MMPX - + Zero-Tangent - + B-Spline - + Mitchell - + Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None Không có - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Mặc định (16:9) - + Force 4:3 Dùng 4:3 - + Force 21:9 Dùng 21:9 - + Force 16:10 Dùng 16:10 - + Stretch to Window Mở rộng đến cửa sổ - + Automatic Tự động - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - - - - - 64x - - - - + Japanese (日本語) Tiếng Nhật (日本語) - + American English Tiếng Anh Mỹ - + French (français) Tiếng Pháp (French) - + German (Deutsch) Tiếng Đức (Deutsch) - + Italian (italiano) Tiếng Ý (italiano) - + Spanish (español) Tiếng Tây Ban Nha (Español) - + Chinese Tiếng Trung - + Korean (한국어) Tiếng Hàn (한국어) - + Dutch (Nederlands) Tiếng Hà Lan (Nederlands) - + Portuguese (português) Tiếng Bồ Đào Nha (Portuguese) - + Russian (Русский) Tiếng Nga (Русский) - + Taiwanese Tiếng Đài Loan - + British English Tiếng Anh Anh - + Canadian French Tiếng Pháp Canada - + Latin American Spanish Tiếng Tây Ban Nha Mỹ Latinh - + Simplified Chinese Tiếng Trung giản thể - + Traditional Chinese (正體中文) Tiếng Trung phồn thể (正體中文) - + Brazilian Portuguese (português do Brasil) Tiếng Bồ Đào Nha Brasil (Português do Brasil) - + Polish (polska) - + Thai (แบบไทย) - - + + Japan Nhật Bản - + USA Hoa Kỳ - + Europe Châu Âu - + Australia Úc - + China Trung Quốc - + Korea Hàn Quốc - + Taiwan Đài Loan - + Auto (%1) Auto select time zone Tự động (%1) - + Default (%1) Default time zone Mặc định (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Cuba - + EET EET - + Egypt Ai Cập - + Eire Eire - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Eire - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hồng Kông - + HST HST - + Iceland Iceland - + Iran Iran - + Israel Israel - + Jamaica Jamaica - + Kwajalein Kwajalein - + Libya Libya - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navajo - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Ba Lan - + Portugal Bồ Đào Nha - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Singapore - + Turkey Thổ Nhĩ Kỳ - + UCT UCT - + Universal Quốc tế - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulu - + Mono Mono - + Stereo Stereo - + Surround Surround - + 4GB DRAM (Default) - + 6GB DRAM (Unsafe) - + 8GB DRAM - + 10GB DRAM (Unsafe) - + 12GB DRAM (Unsafe) - + Docked Docked - + Handheld Handheld - - - Off + + + Normal - - Boost (1700MHz) + + + Boost - - Fast (2000MHz) + + + Overclock - + Always ask (Default) - + Only if game specifies not to stop - + Never ask - - - Medium (256) - - - - - - High (512) - - - - + Very Small (16 MB) - + Small (32 MB) - + Normal (128 MB) - + Large (256 MB) - + Very Large (512 MB) - + Very Low (4 MB) - + Low (8 MB) - + Normal (16 MB) - + Medium (32 MB) - + High (64 MB) - + Very Low (32) - + Low (64) - + Normal (128) - - Disabled + + Medium (256) - + + High (512) + + + + ExtendedDynamicState 1 - + ExtendedDynamicState 2 - + ExtendedDynamicState 3 - + Tree View - + Grid View @@ -2562,252 +2453,297 @@ When a program attempts to open the controller applet, it is immediately closed. Bộ lọc nhật ký chung - - When checked, the max size of the log increases from 100 MB to 1 GB - Khi kích hoạt, kích thước tối đa của tập tin nhật ký tăng từ 100 MB lên 1 GB + + GPU Logging/Level + - - Enable Extended Logging** - Bật ghi nhật ký mở rộng** + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console Hiện nhật ký trong console - + + When checked, the max size of the log increases from 100 MB to 1 GB + Khi kích hoạt, kích thước tối đa của tập tin nhật ký tăng từ 100 MB lên 1 GB + + + + Enable Extended Logging** + Bật ghi nhật ký mở rộng** + + + Open Log Location Mở vị trí nhật ký - + Homebrew Homebrew - + Arguments String Chuỗi đối số - + Graphics Đồ hoạ - + When checked, it executes shaders without loop logic changes Khi kích hoạt, nó thực thi các shader mà không thay đổi logic vòng lặp - + Disable Loop safety checks Tắt kiểm tra an toàn vòng lặp - + When checked, it will dump all the macro programs of the GPU Khi kích hoạt, nó sẽ trích xuất tất cả chương trình macro của GPU - + Dump Maxwell Macros Trích xuất Maxwell Macros - + When checked, it enables Nsight Aftermath crash dumps Khi kích hoạt, nó cho phép ghi nhận lỗi Nsight Aftermath - + Enable Nsight Aftermath Bật Nsight Aftermath - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Khi kích hoạt, nó sẽ trích xuất tất cả shader của trình hợp dịch từ bộ nhớ đệm shader trên ổ cứng hoặc từ game khi tìm thấy - - - - Dump Game Shaders - Trích xuất shader game - - - - Enable Renderdoc Hotkey + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. - - When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower - Khi kích hoạt, nó sẽ tắt trình biên dịch Just In Time cho macro. Bật tính năng này sẽ làm cho các game chạy chậm hơn - - - - Disable Macro JIT - Tắt Macro JIT - - - - When checked, it disables the macro HLE functions. Enabling this makes games run slower - Khi kích hoạt, nó sẽ tắt các chức năng Macro HLE. Bật tính năng này sẽ làm cho các game chạy chậm hơn - - - - Disable Macro HLE - Tắt Macro HLE - - - - When checked, the graphics API enters a slower debugging mode - Khi kích hoạt, API đồ hoạ sẽ chuyển sang chế độ gỡ lỗi chậm hơn - - - - Enable Graphics Debugging - Kích hoạt chế độ gỡ lỗi đồ hoạ - - - - When checked, yuzu will log statistics about the compiled pipeline cache - Khi kích hoạt, yuzu sẽ ghi lại các thống kê về bộ nhớ đệm pipeline đã được biên dịch - - - - Enable Shader Feedback - Bật phản hồi shader - - - - <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + Dump Guest (Maxwell) Shaders - Disable Buffer Reorder + Enable Renderdoc Hotkey + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower + Khi kích hoạt, nó sẽ tắt trình biên dịch Just In Time cho macro. Bật tính năng này sẽ làm cho các game chạy chậm hơn + + + + Disable Macro JIT + Tắt Macro JIT + + + + When checked, it disables the macro HLE functions. Enabling this makes games run slower + Khi kích hoạt, nó sẽ tắt các chức năng Macro HLE. Bật tính năng này sẽ làm cho các game chạy chậm hơn + + + + Disable Macro HLE + Tắt Macro HLE + + + + When checked, the graphics API enters a slower debugging mode + Khi kích hoạt, API đồ hoạ sẽ chuyển sang chế độ gỡ lỗi chậm hơn + + + + Enable Graphics Debugging + Kích hoạt chế độ gỡ lỗi đồ hoạ + + + + When checked, yuzu will log statistics about the compiled pipeline cache + Khi kích hoạt, yuzu sẽ ghi lại các thống kê về bộ nhớ đệm pipeline đã được biên dịch + + + + Enable Shader Feedback + Bật phản hồi shader + + + + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + + + + Disable Buffer Reorder + + + + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced Nâng cao - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Cho phép yuzu kiểm tra môi trường Vulkan hoạt động khi chương trình bắt đầu. Vô hiệu hóa tính năng này nếu nó gây ra vấn đề cho các chương trình bên ngoài khi nhìn thấy yuzu. - + Perform Startup Vulkan Check Thực hiện kiểm tra Vulkan khi khởi động - + Disable Web Applet Tắt applet web - + Enable All Controller Types Cho phép tất cả các loại tay cầm - + Enable Auto-Stub - + Kiosk (Quest) Mode Chế độ Kiosk (Khách) - + Use dev.keys - + Enable Debug Asserts Bật kiểm tra lỗi gỡ lỗi - + Debugging Gỡ lỗi - + Battery Serial: - + + Debug Knobs: + + + + Bitmask for quick development toggles - + Set debug knobs (bitmask) - + 16-bit debug knob set for quick development toggles - + (bitmask) - - Debug Knobs: - - - - + Unit Serial: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. Bật tính năng này để đưa ra danh sách lệnh âm thanh mới nhất đã tạo ra đến console. Chỉ ảnh hưởng đến các game sử dụng bộ mã hóa âm thanh. - + Dump Audio Commands To Console** Trích xuất các lệnh âm thanh đến console** - + Flush log output on each line - + Enable FS Access Log Bật nhật ký truy cập FS - + Enable Verbose Reporting Services** Bật dịch vụ báo cáo chi tiết** - + Censor username in logs - + **This will be reset automatically when Eden closes. @@ -3257,33 +3193,38 @@ Would you like to delete the old save data? Màu nền: - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off Tắt - + VSync Off Tắt Vsync - + Recommended Đề xuất - + On Bật - + VSync On Bật Vsync @@ -3804,6 +3745,16 @@ Would you like to delete the old save data? Motion / Touch Chuyển động / Cảm ứng + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4313,58 +4264,58 @@ To invert the axes, first move your joystick vertically, and then horizontally.< [đang chờ] - + New Profile Hồ sơ mới - + Enter a profile name: Nhập tên hồ sơ: - - + + Create Input Profile Tạo hồ sơ đầu vào - + The given profile name is not valid! Tên hồ sơ không hợp lệ! - + Failed to create the input profile "%1" Thất bại khi tạo hồ sơ đầu vào "%1" - + Delete Input Profile Xoá hồ sơ đầu vào - + Failed to delete the input profile "%1" Thất bại khi xoá hồ sơ đầu vào "%1" - + Load Input Profile Nạp hồ sơ đầu vào - + Failed to load the input profile "%1" Thất bại khi nạp hồ sơ đầu vào "%1" - + Save Input Profile Lưu hồ sơ đầu vào - + Failed to save the input profile "%1" Thất bại khi lưu hồ sơ dầu vào "%1" @@ -4412,7 +4363,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Configure Cấu hình @@ -4443,7 +4394,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Test Thử nghiệm @@ -4458,77 +4409,77 @@ To invert the axes, first move your joystick vertically, and then horizontally.< Loại bỏ máy chủ - + %1:%2 %1:%2 - - - - - - + + + + + + Eden - + Port number has invalid characters Cổng có kí tự không hợp lệ - + Port has to be in range 0 and 65353 Cổng phải từ 0 đến 65353 - + IP address is not valid Địa chỉ IP không hợp lệ - + This UDP server already exists Máy chủ UDP này đã tồn tại - + Unable to add more than 8 servers Không thể thêm quá 8 máy chủ - + Testing Thử nghiệm - + Configuring Cấu hình - + Test Successful Thử nghiệm thành công - + Successfully received data from the server. Thành công nhận dữ liệu từ máy chủ. - + Test Failed Thử nghiệm thất bại - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Không thể nhận được dữ liệu hợp lệ từ máy chủ.<br>Hãy chắc chắn máy chủ được thiết lập chính xác và địa chỉ lẫn cổng đều đúng. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. Cấu hình kiểm tra hoặc hiệu chuẩn UDP đang được tiến hành.<br>Vui lòng chờ cho đến khi nó hoàn thành. @@ -4713,57 +4664,57 @@ Các giá trị hiện tại lần lượt là %1% và %2%. Một số cài đặt chỉ khả dụng khi game không chạy. - + Add-Ons Add-Ons - + System Hệ thống - + CPU CPU - + Graphics Đồ hoạ - + Adv. Graphics Đồ hoạ nâng cao - + Ext. Graphics - + Audio Âm thanh - + Input Profiles Hồ sơ đầu vào - + Network - + Applets - + Properties Thuộc tính @@ -4886,6 +4837,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5453,91 +5528,86 @@ Kéo điểm để thay đổi vị trí, hoặc nhấp đúp chuột vào ô tr - Show Compatibility List - Hiện danh sách tương thích - - - Show Add-Ons Column Hiện cột Add-ons - + Show Size Column Hiện cột Kích thước - + Show File Types Column Hiện cột Loại tập tin - + Show Play Time Column - + Folder Icon Size: Kích thước biểu tượng thư mục: - + Row 1 Text: Dòng chữ hàng 1: - + Row 2 Text: Dòng chữ hàng 2: - + Screenshots Ảnh chụp màn hình - + Ask Where To Save Screenshots (Windows Only) Hỏi nơi lưu ảnh chụp màn hình (chỉ cho Windows) - + Screenshots Path: Đường dẫn cho ảnh chụp màn hình: - + ... ... - + TextLabel NhãnVănBản - + Resolution: Độ phân giải: - + Select Screenshots Path... Chọn đường dẫn cho ảnh chụp màn hình... - + <System> <System> - + English Tiếng Việt - + Auto (%1 x %2, %3 x %4) Screenshot width value Tự động (%1 x %2, %3 x %4) @@ -5774,7 +5844,7 @@ Kéo điểm để thay đổi vị trí, hoặc nhấp đúp chuột vào ô tr - + Calculating... @@ -5976,50 +6046,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL không khả dụng! - + OpenGL shared contexts are not supported. Các ngữ cảnh OpenGL chung không được hỗ trợ. - + Eden has not been compiled with OpenGL support. - - - + + + Error while initializing OpenGL! Lỗi khi khởi tạo OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. GPU của bạn có thể không hỗ trợ OpenGL, hoặc bạn không có driver đồ hoạ mới nhất. - + Error while initializing OpenGL 4.6! Lỗi khi khởi tạo OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 GPU của bạn có thể không hỗ trợ OpenGL 4.6, hoặc bạn không có driver đồ hoạ mới nhất.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 GPU của bạn có thể không hỗ trợ một hoặc nhiều tiện ích OpenGL cần thiết. Vui lòng đảm bảo bạn có driver đồ hoạ mới nhất.<br><br>GL Renderer:<br>%1<br><br>Tiện ích không hỗ trợ:<br>%2 - + This build doesn't have OpenGL support. @@ -6027,279 +6097,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory - + Favorite Ưa thích - + Start Game Bắt đầu game - + Start Game without Custom Configuration Bắt đầu game mà không có cấu hình tuỳ chỉnh - + Open Save Data Location Mở vị trí dữ liệu save - + Open Mod Data Location Mở vị trí chứa dữ liệu mod - + Open Transferable Pipeline Cache Mở thư mục chứa bộ nhớ đệm pipeline - + Link to Ryujinx - + Remove Loại bỏ - + Remove Installed Update Loại bỏ bản cập nhật đã cài - + Remove All Installed DLC Loại bỏ tất cả DLC đã cài đặt - + Remove Custom Configuration Loại bỏ cấu hình tuỳ chỉnh - + Remove Cache Storage Loại bỏ bộ nhớ đệm - + Remove OpenGL Pipeline Cache Loại bỏ bộ nhớ đệm pipeline OpenGL - + Remove Vulkan Pipeline Cache Loại bỏ bộ nhớ đệm pipeline Vulkan - + Remove All Pipeline Caches Loại bỏ tất cả bộ nhớ đệm shader - + Remove All Installed Contents Loại bỏ tất cả nội dung đã cài đặt - + Manage Play Time - + Edit Play Time Data - + Remove Play Time Data - - + + Dump RomFS Trích xuất RomFS - + Dump RomFS to SDMC Trích xuất RomFS tới SDMC - + Verify Integrity Kiểm tra tính toàn vẹn - + Copy Title ID to Clipboard Sao chép ID title vào bộ nhớ tạm - - Navigate to GameDB entry - Điều hướng đến mục GameDB - - - + Create Shortcut Tạo lối tắt - + Add to Desktop Thêm vào desktop - + Add to Applications Menu Thêm vào menu ứng dụng - + Configure Game - + Scan Subfolders Quét các thư mục con - + Remove Game Directory Loại bỏ thư mục game - + ▲ Move Up ▲ Di chuyển lên - + ▼ Move Down ▼ Di chuyển xuống - + Open Directory Location Mở vị trí thư mục - + Clear Xóa - - - Name - Tên - - - - Compatibility - Độ tương thích - - - - Add-ons - Add-ons - - - - File type - Loại tập tin - - - - Size - Kích thước - - - - Play time - - GameListItemCompat - + Ingame Trong game - + Game starts, but crashes or major glitches prevent it from being completed. Game khởi động, nhưng bị crash hoặc lỗi nghiêm trọng dẫn đến việc không thể hoàn thành nó. - + Perfect Hoàn hảo - + Game can be played without issues. Game có thể chơi mà không gặp vấn đề. - + Playable Có thể chơi - + Game functions with minor graphical or audio glitches and is playable from start to finish. Game hoạt động với lỗi hình ảnh hoặc âm thanh nhẹ và có thể chơi từ đầu tới cuối. - + Intro/Menu Phần mở đầu/Menu - + Game loads, but is unable to progress past the Start Screen. Game đã tải, nhưng không thể qua được màn hình bắt đầu. - + Won't Boot Không khởi động - + The game crashes when attempting to startup. Game crash khi đang khởi động. - + Not Tested Chưa ai thử - + The game has not yet been tested. Game này chưa được thử nghiệm. + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list Nhấp đúp chuột để thêm một thư mục mới vào danh sách game @@ -6307,17 +6370,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) - + Filter: Lọc: - + Enter pattern to filter Nhập mẫu để lọc @@ -6812,1139 +6875,1112 @@ Debug Message: - + Game &Icon Size - + Reset Window Size to &720p Đặt lại kích thước cửa sổ về &720p - + Reset Window Size to 720p Đặt lại kích thước cửa sổ về 720p - + Reset Window Size to &900p Đặt lại kích thước cửa sổ về &900p - + Reset Window Size to 900p Đặt lại kích thước cửa sổ về 900p - + Reset Window Size to &1080p Đặt lại kích thước cửa sổ về &1080p - + Reset Window Size to 1080p Đặt lại kích thước cửa sổ về 1080p - + &Multiplayer &Nhiều người chơi - + &Tools &Công cụ - + Am&iibo - + Launch &Applet - + &TAS &TAS - + &Create Home Menu Shortcut - + Install &Firmware - + &Help &Trợ giúp - + &Install Files to NAND... &Cài đặt tập tin vào NAND... - + L&oad File... N&ạp tập tin... - + Load &Folder... Nạp &thư mục... - + E&xit T&hoát - - + + &Pause &Tạm dừng - + &Stop &Dừng - + &Verify Installed Contents - + &About Eden - + Single &Window Mode Chế độ &cửa sổ đơn - + Con&figure... Cấu &hình... - + Ctrl+, - + Enable Overlay Display Applet - + Show &Filter Bar Hiện thanh &lọc - + Show &Status Bar Hiện thanh &trạng thái - + Show Status Bar Hiện thanh trạng thái - + &Browse Public Game Lobby &Duyệt phòng game công khai - + &Create Room &Tạo phòng - + &Leave Room &Rời phòng - + &Direct Connect to Room &Kết nối trực tiếp tới phòng - + &Show Current Room &Hiện phòng hiện tại - + F&ullscreen T&oàn màn hình - + &Restart &Khởi động lại - + Load/Remove &Amiibo... Nạp/Loại bỏ &Amiibo... - - &Report Compatibility - &Báo cáo độ tương thích - - - + Open &Mods Page Mở trang &mods - - Open &Quickstart Guide - Mở &Hướng dẫn nhanh + + &User Handbook + - - &FAQ - &FAQ - - - + &Capture Screenshot &Chụp ảnh màn hình - + &Album - + &Set Nickname and Owner - + &Delete Game Data - + &Restore Amiibo - + &Format Amiibo - + &Mii Editor - + &Configure TAS... &Cấu hình TAS... - + Configure C&urrent Game... Cấu hình game h&iện tại... - - + + &Start &Bắt đầu - + &Reset &Đặt lại - - + + R&ecord G&hi lại - + Open &Controller Menu - + Install Decryption &Keys - + &Home Menu - + &Desktop - + &Application Menu - + &Root Data Folder - + &NAND Folder - + &SDMC Folder - + &Mod Folder - + &Log Folder - + From Folder - + From ZIP - + &Eden Dependencies - + &Data Manager - + &Tree View - + &Grid View - + Game Icon Size - - + + None - + Show Game &Name - + Show &Performance Overlay - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) - + Standard (64x64) - + Large (128x128) - + Full Size (256x256) - + Broken Vulkan Installation Detected - + Vulkan initialization failed during boot. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping - + Loading Web Applet... - - + + Disable Web Applet - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + The amount of shaders currently being built - + The current selected resolution scaling multiplier. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Unmute - + Mute - + Reset Volume - + + Configure Effects... + + + + &Clear Recent Files - + &Continue - + Warning: Outdated Game Format - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - - + + Error while loading ROM! - + The ROM format is not supported. - + An error occurred initializing the video core. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + An unknown error occurred. Please see the log for more details. - + + (64-bit) - + + (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + Closing software... - + Save Data - + Mod Data - + Error Opening %1 Folder - - + + Folder does not exist! - + Remove Installed Game Contents? - + Remove Installed Game Update? - + Remove Installed Game DLC? - + Remove Entry - + Delete OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? - + Remove Custom Game Configuration? - + Remove Cache Storage? - + Remove File - + Remove Play Time Data - + Reset play time? - - + + RomFS Extraction Failed! - + There was an error copying the RomFS files or the user cancelled the operation. - + Full - + Skeleton - + Select RomFS Dump Mode - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Extracting RomFS... - - + + Cancel - + RomFS Extraction Succeeded! - + The operation completed successfully. - + Error Opening %1 - + Select Directory - + Properties - + The game properties could not be loaded. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Load File - + Open Extracted ROM Directory - + Invalid Directory Selected - + The directory you have selected does not contain a 'main' file. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files - + %n file(s) remaining - + Installing file "%1"... - - + + Install Results - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + %n file(s) were newly installed - + %n file(s) were overwritten - + %n file(s) failed to install - + System Application - + System Archive - + System Application Update - + Firmware Package (Type A) - + Firmware Package (Type B) - + Game - + Game Update - + Game DLC - + Delta Title - + Select NCA Install Type... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Failed to Install - + The title type you selected for the NCA is invalid. - + File not found - + File "%1" not found - + OK - + Function Disabled - + Compatibility list reporting is currently disabled. Check back later! - + Error opening URL - + Unable to open the URL "%1". - + TAS Recording - + Overwrite file of player 1? - + Invalid config detected - + Handheld controller can't be used on docked mode. Pro controller will be selected. - - + + Amiibo - - + + The current amiibo has been removed - + Error - - + + The current game is not looking for amiibos - + Amiibo File (%1);; All Files (*.*) - + Load Amiibo - + Error loading Amiibo data - + The selected file is not a valid amiibo - + The selected file is already on use - + An unknown error occurred - - - Keys not installed - - - - - - Install decryption keys and restart Eden before attempting to install firmware. - - - - - Select Dumped Firmware Source Location - - - - - Select Dumped Firmware ZIP - - - - - Zipped Archives (*.zip) - - - - - Firmware cleanup failed - - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available - + Firmware Corrupted - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot - + PNG Image (*.png) - + TAS state: Running %1/%2 - + TAS state: Recording %1 - + TAS state: Idle %1/%2 - + TAS State: Invalid - + &Stop Running - + Stop R&ecording - + Building: %n shader(s) - + Scale: %1x %1 is the resolution scaling factor - + Speed: %1% / %2% - + Speed: %1% - + Game: %1 FPS - + Frame: %1 ms - - - FSR - - - - + NO AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE - + VOLUME: %1% Volume percentage (e.g. 50%) - + Derivation Components Missing - + Decryption keys are missing. Install them now? - + Wayland Detected! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -7952,74 +7988,74 @@ Would you like to force it for future launches? - + Use X11 - + Continue with Wayland - + Don't show again - + Restart Required - + Restart Eden to apply the X11 backend. - + Slow - + Turbo - + Unlocked - + Select RomFS Dump Target - + Please select which RomFS you would like to dump. - + Are you sure you want to close Eden? - - - + + + Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8085,6 +8121,11 @@ Would you like to bypass this and exit anyway? ScaleForce + + + FSR + + Area @@ -8096,52 +8137,57 @@ Would you like to bypass this and exit anyway? - + + SGSR + + + + + SGSR EdgeDir + + + + Docked - + Handheld - + Fast - - Balanced - - - - + Accurate - + Vulkan - + OpenGL GLSL - + OpenGL SPIRV - + OpenGL GLASM - + Null @@ -8174,7 +8220,7 @@ If you wish to clean up the files which were left in the old data location, you - + Data was migrated successfully. @@ -8664,8 +8710,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8678,10 +8724,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8696,9 +8742,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8860,50 +8906,50 @@ p, li { white-space: pre-wrap; } [không xác định] - + %1%2 %1%2 - + [invalid] [không hợp lệ] - - + + %1%2Hat %3 %1%2Mũ %3 - - - + + + %1%2Axis %3 %1%2Trục %3 - + %1%2Axis %3,%4,%5 %1%2Trục %3,%4,%5 - + %1%2Motion %3 %1%2Chuyển động %3 - - + + %1%2Button %3 %1%2Nút %3 - + [unused] [không sử dụng] @@ -9029,47 +9075,47 @@ p, li { white-space: pre-wrap; } %1 đang chơi %2 - + Play Time: %1 - + Never Played - + Version: %1 - + Version: 1.0.0 - + Installed SD Titles Các title đã cài đặt trên thẻ SD - + Installed NAND Titles Các title đã cài đặt trên NAND - + System Titles Titles hệ thống - + Add New Game Directory Thêm thư mục game - + Favorites Ưa thích @@ -9190,13 +9236,13 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. @@ -9206,10 +9252,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9230,213 +9276,246 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9680,72 +9759,72 @@ Would you like to manually select a portable folder to use? - + Create Shortcut - + Do you want to launch the game in fullscreen? - + Shortcut Created - + Successfully created a shortcut to %1 - + Shortcut may be Volatile! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? - + Failed to Create Shortcut - + Failed to create a shortcut to %1 - + Create Icon - + Cannot create icon file. Path "%1" does not exist and cannot be created. - + No firmware available - + Please install firmware to use the home menu. - + Home Menu Applet - + Home Menu is not available. Please reinstall firmware. @@ -9753,55 +9832,55 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name - + What should this mod be called? - + RomFS - + ExeFS/Patch - + Cheat - + Mod Type - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty @@ -10495,65 +10574,65 @@ By selecting "From Eden", previous save data stored in Ryujinx will be - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/vi_VN.ts b/dist/languages/vi_VN.ts index 8fa5494c73..51156ca9c8 100644 --- a/dist/languages/vi_VN.ts +++ b/dist/languages/vi_VN.ts @@ -211,155 +211,6 @@ This would ban both their forum username and their IP address. %1 - %2 (%3/%4 members) - đã kết nối - - CompatDB - - - Report Compatibility - Báo cáo tương thích - - - - - - - - - - Report Game Compatibility - Báo cáo trò chơi tương thích - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>Game có chạy lên thành công hay không?</p></body></html> - - - - Yes The game starts to output video or audio - Có Game có xuất ra hình và âm thanh - - - - No The game doesn't get past the "Launching..." screen - Không Game không thể qua khỏi được khúc màn hình "Launching..." - - - - Yes The game gets past the intro/menu and into gameplay - Có Game có thể qua được khúc intro/menu và vô được game - - - - No The game crashes or freezes while loading or using the menu - Không Game crash hoặc đơ khi đang loading hoặc sử dụng menu - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>Game có chạy được tới vô bên trong hay không?</p></body></html> - - - - Yes The game works without crashes - Có Game chạy ổn định, không bị crash - - - - No The game crashes or freezes during gameplay - Không Game crash hoặc đơ trong lúc chơi - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>Game chạy có ổn định với không crash, đơ hoặc bị kẹt trong lúc chơi hay không?</p></body></html> - - - - Yes The game can be finished without any workarounds - Có Game có thể hoàn thành mà không cần phải làm gì thêm - - - - No The game can't progress past a certain area - Không Game không thể qua được mốt số khúc - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>Game có chơi được từ đầu đến cuối hay không?</p></body></html> - - - - Major The game has major graphical errors - Lỗi nặng Game bị lỗi hình ảnh nặng - - - - Minor The game has minor graphical errors - Lỗi nhẹ Game bị lỗi hình ảnh nhẹ - - - - None Everything is rendered as it looks on the Nintendo Switch - Không lỗi Game nhìn y hệt như trên Nintendo Switch - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>Game có bị lỗi gì về hình ảnh không?</p></body></html> - - - - Major The game has major audio errors - Lỗi nặng Game bị lỗi âm thanh nặng - - - - Minor The game has minor audio errors - Lỗi nhẹ Game bị lỗi âm thanh nhẹ - - - - None Audio is played perfectly - Không lỗi Âm thanh hoạt động hoàn hảo - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>Game có bị lỗi âm thanh hay lỗi hiệu ứng hay không?</p></body></html> - - - - Thank you for your submission! - Cảm ơn bạn đã gửi đến cho chúng tôi! - - - - Submitting - Đang gửi - - - - Communication error - Đã xảy ra lỗi giao tiếp với máy chủ - - - - An error occurred while sending the Testcase - Có lỗi xảy ra khi gửi Testcase - - - - Next - Tiếp theo - - ConfigurationShared @@ -559,231 +410,241 @@ Can help reduce stuttering at lower framerates. - + Backend: - CPU Overclock + CPU Clocks - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. - + Custom CPU Ticks - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. - + Enable Host MMU Emulation (fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. - + Unfuse FMA (improve performance on CPUs without FMA) Không dùng FMA (tăng hiệu suất cho các dòng CPU không hỗ trợ FMA) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. - + Faster FRSQRTE and FRECPE Chạy FRSQRTE và FRECPE nhanh hơn - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. - + Faster ASIMD instructions (32 bits only) Các lệnh ASIMD nhanh hơn (chỉ áp dụng cho 32 bit) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. - + Inaccurate NaN handling Xử lí NaN gặp lỗi - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. - + Disable address space checks Tắt kiểm tra không gian địa chỉ - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. - + Ignore global monitor Bỏ qua màn hình chung - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. - + API: API đồ hoạ: - + Changes the output graphics API. Vulkan is recommended. - + Device: Thiết bị đồ hoạ: - + This setting selects the GPU to use (Vulkan only). - + Resolution: Độ phân giải: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. - + Window Adapting Filter: Bộ lọc điều chỉnh cửa sổ: - + FSR Sharpness: Độ sắc nét FSR: - - Determines how sharpened the image will look using FSR's dynamic contrast. + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. - + Anti-Aliasing Method: Phương pháp khử răng cưa: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: Chế độ Toàn màn hình: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. - + Aspect Ratio: Tỉ lệ khung hình: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. - + Use persistent pipeline cache - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. - + Use asynchronous GPU emulation - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + NVDEC emulation: Giả lập NVDEC - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. - + ASTC Decoding Method: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -792,55 +653,75 @@ stuttering but may present artifacts. - + ASTC Recompression Method: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. - + Skip CPU Inner Invalidation - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: Chế độ Vsync: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -848,1361 +729,1371 @@ Immediate (no synchronization) presents whatever is available and can exhibit te - + Sync Memory Operations - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. - + Enable asynchronous presentation (Vulkan only) Bật hiển thị bất đồng bộ (chỉ dành cho Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. - + Force maximum clocks (Vulkan only) Buộc chạy ở xung nhịp tối đa (chỉ Vulkan) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. Chạy các công việc trong nền trong khi đang chờ lệnh đồ họa để giữ cho GPU không giảm xung nhịp. - + Anisotropic Filtering: Bộ lọc góc nghiêng: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. - + GPU Mode: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - + DMA Accuracy: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. - + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation - + May reduce shader stutter. - - Fast GPU Time + + GPU Clocks - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. - + GPU Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache Dùng Vulkan pipeline cache - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. - + Enable Compute Pipelines (Intel Vulkan Only) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. - + Enable Reactive Flushing Bật xả tương ứng - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. - + Sync to framerate of video playback Đồng bộ hóa với tốc độ khung hình khi phát video - + Run the game at normal speed during video playback, even when the framerate is unlocked. Chạy game với tốc độ bình thường trong quá trình phát video, ngay cả khi tốc độ khung hình được mở khóa. - + Barrier feedback loops Vòng lặp phản hồi rào cản - + Improves rendering of transparency effects in specific games. Cải thiện hiệu quả hiển thị của hiệu ứng trong suốt trong một số trò chơi. - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. - + Vertex Input Dynamic State - + Enables vertex input dynamic state feature for better quality and performance. - + Sample Shading - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. - + RNG Seed Hạt giống RNG - + Controls the seed of the random number generator. Mainly used for speedrunning. - + Device Name Tên thiết bị - + The name of the console. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: - + This option allows to change the clock of the console. Can be used to manipulate time in games. - + The number of seconds from the current unix time - + Language: - + This option can be overridden when region setting is auto-select - + Region: Vùng: - + The region of the console. - + Time Zone: Múi giờ: - + The time zone of the console. - + Sound Output Mode: Chế độ đầu ra âm thanh - + Console Mode: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. - + Prompt for user profile on boot - + Useful if multiple people use the same PC. - + Pause when not in focus - + Pauses emulation when focusing on other windows. - + Confirm before stopping emulation - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. - + Hide mouse on inactivity Ẩn con trỏ chuột khi không dùng - + Hides the mouse after 2.5s of inactivity. - - Disable controller applet - Vô hiệu hoá applet tay cầm - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - - - - + Check for updates - + Whether or not to check for updates upon startup. - + Enable Gamemode - + Force X11 as Graphics Backend - + Custom frontend - + Real applet - + + + Disabled + + + + Never - + On Load - + Always - + CPU CPU - + GPU - + CPU Asynchronous - + Uncompressed (Best quality) Không nén (Chất lượng tốt nhất) - + BC1 (Low quality) BC1 (Chất lượng thấp) - + BC3 (Medium quality) BC3 (Chất lượng trung bình) - - + + Auto Tự động - + 30 FPS - + 60 FPS - + 90 FPS - + 120 FPS - + Conservative - + Aggressive - + Vulkan Vulkan - + OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) - + Null Null - + Fast - - Balanced - - - - - + + + Accurate Tuyệt đối - - + + + Default Mặc định - + Unsafe (fast) - + Safe (stable) - + + Immediate + + + + + Balanced + + + + Unsafe Tương đối - + Paranoid (disables most optimizations) Paranoid (vô hiệu hoá hầu hết sự tối ưu) - + Debugging - + Dynarmic - + NCE - + Borderless Windowed Cửa sổ không viền - + Exclusive Fullscreen Toàn màn hình - + No Video Output Không Video Đầu Ra - + CPU Video Decoding Giải mã video bằng CPU - + GPU Video Decoding (Default) Giải mã video bằng GPU (Mặc định) - + 0.25X (180p/270p) [EXPERIMENTAL] - + 0.5X (360p/540p) [EXPERIMENTAL] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [THỬ NGHIỆM] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [THỬ NGHIỆM] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor Nearest Neighbor - + Bilinear Bilinear - + Bicubic Bicubic - + Gaussian ScaleForce - + Lanczos - + ScaleForce ScaleForce - + AMD FidelityFX Super Resolution - + Area - + MMPX - + Zero-Tangent - + B-Spline - + Mitchell - + Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None Trống - + FXAA FXAA - + SMAA SMAA - + Default (16:9) Mặc định (16:9) - + Force 4:3 Dùng 4:3 - + Force 21:9 Dùng 21:9 - + Force 16:10 Dung 16:10 - + Stretch to Window Kéo dãn đến cửa sổ phần mềm - + Automatic Tự động - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - - - - - 64x - - - - + Japanese (日本語) Tiếng Nhật (日本語) - + American English Tiếng Anh Mỹ - + French (français) Tiếng Pháp (French) - + German (Deutsch) Tiếng Đức (Deutsch) - + Italian (italiano) Tiếng Ý (italiano) - + Spanish (español) Tiếng Tây Ban Nha (Spanish) - + Chinese Tiếng Trung - + Korean (한국어) Tiếng Hàn (한국어) - + Dutch (Nederlands) Tiếng Hà Lan (Dutch) - + Portuguese (português) Tiếng Bồ Đào Nha (Portuguese) - + Russian (Русский) Tiếng Nga (Русский) - + Taiwanese Tiếng Đài Loan - + British English Tiếng Anh UK (British English) - + Canadian French Tiếng Pháp Canada - + Latin American Spanish Tiếng Mỹ La-tinh - + Simplified Chinese Tiếng Trung giản thể - + Traditional Chinese (正體中文) Tiếng Trung phồn thể (正體中文) - + Brazilian Portuguese (português do Brasil) Tiếng Bồ Đào Nha của người Brazil (Português do Brasil) - + Polish (polska) - + Thai (แบบไทย) - - + + Japan Nhật Bản - + USA Hoa Kỳ - + Europe Châu Âu - + Australia Châu Úc - + China Trung Quốc - + Korea Hàn Quốc - + Taiwan Đài Loan - + Auto (%1) Auto select time zone Tự động (%1) - + Default (%1) Default time zone Mặc định (%1) - + CET CET - + CST6CDT CST6CDT - + Cuba Cuba - + EET EET - + Egypt Ai Cập - + Eire Eire - + EST EST - + EST5EDT EST5EDT - + GB GB - + GB-Eire GB-Eire - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich Greenwich - + Hongkong Hồng Kông - + HST HST - + Iceland Iceland - + Iran Iran - + Israel Israel - + Jamaica Jamaica - + Kwajalein Kwajalein - + Libya Libya - + MET MET - + MST MST - + MST7MDT MST7MDT - + Navajo Navajo - + NZ NZ - + NZ-CHAT NZ-CHAT - + Poland Ba Lan - + Portugal Bồ Đào Nha - + PRC PRC - + PST8PDT PST8PDT - + ROC ROC - + ROK ROK - + Singapore Singapore - + Turkey Thổ Nhĩ Kỳ - + UCT UCT - + Universal Quốc tế - + UTC UTC - + W-SU W-SU - + WET WET - + Zulu Zulu - + Mono Mono - + Stereo Stereo - + Surround Surround - + 4GB DRAM (Default) - + 6GB DRAM (Unsafe) - + 8GB DRAM - + 10GB DRAM (Unsafe) - + 12GB DRAM (Unsafe) - + Docked Chế độ cắm TV - + Handheld Cầm tay - - - Off + + + Normal - - Boost (1700MHz) + + + Boost - - Fast (2000MHz) + + + Overclock - + Always ask (Default) - + Only if game specifies not to stop - + Never ask - - - Medium (256) - - - - - - High (512) - - - - + Very Small (16 MB) - + Small (32 MB) - + Normal (128 MB) - + Large (256 MB) - + Very Large (512 MB) - + Very Low (4 MB) - + Low (8 MB) - + Normal (16 MB) - + Medium (32 MB) - + High (64 MB) - + Very Low (32) - + Low (64) - + Normal (128) - - Disabled + + Medium (256) - + + High (512) + + + + ExtendedDynamicState 1 - + ExtendedDynamicState 2 - + ExtendedDynamicState 3 - + Tree View - + Grid View @@ -2562,252 +2453,297 @@ When a program attempts to open the controller applet, it is immediately closed. Bộ lọc sổ ghi chép - - When checked, the max size of the log increases from 100 MB to 1 GB - Khi tích vào, dung lượng tối đa cho file log chuyển từ 100 MB lên 1 GB + + GPU Logging/Level + - - Enable Extended Logging** - Bật ghi nhật ký mở rộng** + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console Hiện nhật ký trên trong console - + + When checked, the max size of the log increases from 100 MB to 1 GB + Khi tích vào, dung lượng tối đa cho file log chuyển từ 100 MB lên 1 GB + + + + Enable Extended Logging** + Bật ghi nhật ký mở rộng** + + + Open Log Location Mở vị trí sổ ghi chép - + Homebrew Homebrew - + Arguments String Chuỗi đối số - + Graphics Đồ hoạ - + When checked, it executes shaders without loop logic changes Khi kích hoạt, nó thực thi shaders mà không thay đổi logic vòng lặp. - + Disable Loop safety checks Tắt kiểm tra an toàn vòng lặp - + When checked, it will dump all the macro programs of the GPU Khi kích hoạt, nó sẽ trích xuất tất cả chương trình macro của GPU - + Dump Maxwell Macros Trích xuất Maxwell Macros - + When checked, it enables Nsight Aftermath crash dumps Khi kích hoạt, nó cho phép ghi nhận lỗi Nsight Aftermath. - + Enable Nsight Aftermath Bật Nsight Aftermath - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - Khi kích hoạt, nó sẽ trích xuất tất cả shader của trình hợp dịch từ bộ nhớ đệm shader trên ổ cứng hoặc từ game khi tìm thấy - - - - Dump Game Shaders - Trích xuất shader game - - - - Enable Renderdoc Hotkey + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. - - When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower - Khi chọn, nó sẽ tắt trình biên dịch Just In Time cho macro. Bật tính năng này sẽ làm cho các trò chơi chạy chậm hơn - - - - Disable Macro JIT - Không dùng Macro JIT - - - - When checked, it disables the macro HLE functions. Enabling this makes games run slower - Khi chọn, nó sẽ tắt các chức năng Macro HLE. Bật tính năng này sẽ làm cho các trò chơi chạy chậm hơn - - - - Disable Macro HLE - Tắt Macro HLE - - - - When checked, the graphics API enters a slower debugging mode - Khi được kích hoạt, API đồ hoạ sẽ chuyển sang chế độ gỡ lỗi chậm hơn. - - - - Enable Graphics Debugging - Kích hoạt chế độ gỡ lỗi đồ hoạ - - - - When checked, yuzu will log statistics about the compiled pipeline cache - Khi chọn, yuzu sẽ ghi lại các thống kê về bộ nhớ cache pipeline đã được biên dịch - - - - Enable Shader Feedback - Bật phản hồi shader - - - - <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + Dump Guest (Maxwell) Shaders - Disable Buffer Reorder + Enable Renderdoc Hotkey + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower + Khi chọn, nó sẽ tắt trình biên dịch Just In Time cho macro. Bật tính năng này sẽ làm cho các trò chơi chạy chậm hơn + + + + Disable Macro JIT + Không dùng Macro JIT + + + + When checked, it disables the macro HLE functions. Enabling this makes games run slower + Khi chọn, nó sẽ tắt các chức năng Macro HLE. Bật tính năng này sẽ làm cho các trò chơi chạy chậm hơn + + + + Disable Macro HLE + Tắt Macro HLE + + + + When checked, the graphics API enters a slower debugging mode + Khi được kích hoạt, API đồ hoạ sẽ chuyển sang chế độ gỡ lỗi chậm hơn. + + + + Enable Graphics Debugging + Kích hoạt chế độ gỡ lỗi đồ hoạ + + + + When checked, yuzu will log statistics about the compiled pipeline cache + Khi chọn, yuzu sẽ ghi lại các thống kê về bộ nhớ cache pipeline đã được biên dịch + + + + Enable Shader Feedback + Bật phản hồi shader + + + + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> + + + + + Disable Buffer Reorder + + + + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced Nâng Cao - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. Cho phép yuzu kiểm tra môi trường Vulkan hoạt động khi chương trình bắt đầu. Vô hiệu hóa tính năng này nếu nó gây ra vấn đề cho các chương trình bên ngoài khi nhìn thấy yuzu. - + Perform Startup Vulkan Check Thực hiện kiểm tra Vulkan khi khởi động - + Disable Web Applet Tắt applet web - + Enable All Controller Types Kích hoạt tất cả các loại tay cầm - + Enable Auto-Stub - + Kiosk (Quest) Mode Chế độ Kiosk (Khách) - + Use dev.keys - + Enable Debug Asserts Bật kiểm tra lỗi gỡ lỗi - + Debugging Vá lỗi - + Battery Serial: - + + Debug Knobs: + + + + Bitmask for quick development toggles - + Set debug knobs (bitmask) - + 16-bit debug knob set for quick development toggles - + (bitmask) - - Debug Knobs: - - - - + Unit Serial: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. Bật tính năng này để đưa ra danh sách lệnh âm thanh mới nhất đã tạo ra đến console. Chỉ ảnh hưởng đến các game sử dụng bộ mã hóa âm thanh. - + Dump Audio Commands To Console** Trích xuất các lệnh âm thanh đến console** - + Flush log output on each line - + Enable FS Access Log Bật log truy cập FS - + Enable Verbose Reporting Services** Bật dịch vụ báo cáo chi tiết** - + Censor username in logs - + **This will be reset automatically when Eden closes. @@ -3257,33 +3193,38 @@ Would you like to delete the old save data? Màu nền: - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off Tắt - + VSync Off Tắt Vsync - + Recommended Đề xuất - + On Bật - + VSync On Bật Vsync @@ -3804,6 +3745,16 @@ Would you like to delete the old save data? Motion / Touch Chuyển động / Cảm ứng + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4313,58 +4264,58 @@ Nếu muốn đảo ngược hướng cần điều khiển, di chuyển cần s [Chờ] - + New Profile Hồ sơ mới - + Enter a profile name: Nhập tên hồ sơ: - - + + Create Input Profile Tạo Hồ Sơ Phím - + The given profile name is not valid! Tên hồ sơ không hợp lệ! - + Failed to create the input profile "%1" Quá trình tạo hồ sơ phím "%1" thất bại - + Delete Input Profile Xóa Hồ Sơ Phím - + Failed to delete the input profile "%1" Quá trình xóa hồ sơ phím "%1" thất bại - + Load Input Profile Nạp Hồ Sơ Phím - + Failed to load the input profile "%1" Quá trình nạp hồ sơ phím "%1" thất bại - + Save Input Profile Lưu Hồ Sơ Phím - + Failed to save the input profile "%1" Quá trình lưu hồ sơ phím "%1" thất bại @@ -4412,7 +4363,7 @@ Nếu muốn đảo ngược hướng cần điều khiển, di chuyển cần s - + Configure Cài đặt @@ -4443,7 +4394,7 @@ Nếu muốn đảo ngược hướng cần điều khiển, di chuyển cần s - + Test Thử nghiệm @@ -4458,77 +4409,77 @@ Nếu muốn đảo ngược hướng cần điều khiển, di chuyển cần s Xóa Server - + %1:%2 %1:%2 - - - - - - + + + + + + Eden - + Port number has invalid characters Cổng có kí tự không hợp lệ - + Port has to be in range 0 and 65353 Cổng phải từ 0 đến 65353 - + IP address is not valid Địa chỉ IP không hợp lệ - + This UDP server already exists Server UDP đã tồn tại - + Unable to add more than 8 servers Không thể vượt quá 8 server - + Testing Thử nghiệm - + Configuring Cài đặt - + Test Successful Thử Nghiệm Thành Công - + Successfully received data from the server. Nhận được dữ liệu từ server! - + Test Failed Thử Nghiệm Thất Bại - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. Không thể nhận được dữ liệu hợp lệ từ server. <br>Hãy chắc chắn server được thiết lập chính xác, từ địa chỉ lẫn cổng phải được thiết lập đúng. - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. Cấu hình kiểm tra hoặc hiệu chuẩn UDP đang được tiến hành.<br>Vui lòng chờ cho đến khi nó hoàn thành. @@ -4713,57 +4664,57 @@ Các giá trị hiện tại lần lượt là %1% và %2%. Một số cài đặt chỉ khả dụng khi game không chạy. - + Add-Ons Bổ Sung - + System Hệ Thống - + CPU CPU - + Graphics Đồ Họa - + Adv. Graphics Đồ Họa Nâng Cao - + Ext. Graphics - + Audio Âm Thanh - + Input Profiles Hồ sơ đầu vào - + Network - + Applets - + Properties Thuộc tính @@ -4886,6 +4837,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5453,91 +5528,86 @@ Kéo điểm để thay đổi vị trí, hoặc nhấp đúp chuột vào ô tr - Show Compatibility List - Hiện danh sách tương thích - - - Show Add-Ons Column Hiện thị cột Tiện ích ngoài - + Show Size Column Hiện cột Kích thước - + Show File Types Column Hiện cột Loại tệp - + Show Play Time Column - + Folder Icon Size: Kích thước icon thư mục: - + Row 1 Text: Dòng chữ hàng 1: - + Row 2 Text: Dòng chữ hàng 2: - + Screenshots Ảnh chụp màn hình - + Ask Where To Save Screenshots (Windows Only) Hỏi nơi lưu ảnh chụp màn hình (chỉ Windows) - + Screenshots Path: Đường dẫn cho ảnh chụp màn hình: - + ... ... - + TextLabel NhãnVănBản - + Resolution: Độ phân giải: - + Select Screenshots Path... Chọn đường dẫn cho ảnh chụp màn hình... - + <System> <System> - + English Tiếng Anh - + Auto (%1 x %2, %3 x %4) Screenshot width value Tự động (%1 x %2, %3 x %4) @@ -5774,7 +5844,7 @@ Kéo điểm để thay đổi vị trí, hoặc nhấp đúp chuột vào ô tr - + Calculating... @@ -5976,50 +6046,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! Không có sẵn OpenGL! - + OpenGL shared contexts are not supported. Các ngữ cảnh OpenGL chung không được hỗ trợ. - + Eden has not been compiled with OpenGL support. - - - + + + Error while initializing OpenGL! Đã xảy ra lỗi khi khởi tạo OpenGL! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. GPU của bạn có thể không hỗ trợ OpenGL, hoặc bạn không có driver đồ hoạ mới nhất. - + Error while initializing OpenGL 4.6! Lỗi khi khởi tạo OpenGL 4.6! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 GPU của bạn có thể không hỗ trợ OpenGL 4.6, hoặc bạn không có driver đồ hoạ mới nhất.<br><br>GL Renderer:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 GPU của bạn có thể không hỗ trợ một hoặc nhiều tiện ích OpenGL cần thiết. Vui lòng đảm bảo bạn có driver đồ hoạ mới nhất.<br><br>GL Renderer:<br>%1<br><br>Tiện ích không hỗ trợ:<br>%2 - + This build doesn't have OpenGL support. @@ -6027,279 +6097,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory - + Favorite Ưa thích - + Start Game Bắt đầu game - + Start Game without Custom Configuration Bắt đầu game mà không có cấu hình tuỳ chỉnh - + Open Save Data Location Mở vị trí lưu dữ liệu - + Open Mod Data Location Mở vị trí chỉnh sửa dữ liệu - + Open Transferable Pipeline Cache Mở thư mục chứa bộ nhớ cache pipeline - + Link to Ryujinx - + Remove Gỡ Bỏ - + Remove Installed Update Loại bỏ bản cập nhật đã cài - + Remove All Installed DLC Loại bỏ tất cả DLC đã cài đặt - + Remove Custom Configuration Loại bỏ cấu hình tuỳ chỉnh - + Remove Cache Storage Xoá bộ nhớ cache - + Remove OpenGL Pipeline Cache Loại bỏ OpenGL Pipeline Cache - + Remove Vulkan Pipeline Cache Loại bỏ bộ nhớ cache pipeline Vulkan - + Remove All Pipeline Caches Loại bỏ tất cả bộ nhớ cache shader - + Remove All Installed Contents Loại bỏ tất cả nội dung đã cài đặt - + Manage Play Time - + Edit Play Time Data - + Remove Play Time Data - - + + Dump RomFS Kết xuất RomFS - + Dump RomFS to SDMC Trích xuất RomFS tới SDMC - + Verify Integrity Kiểm tra tính toàn vẹn - + Copy Title ID to Clipboard Sao chép ID tiêu đề vào bộ nhớ tạm - - Navigate to GameDB entry - Điều hướng đến mục cơ sở dữ liệu trò chơi - - - + Create Shortcut Tạo lối tắt - + Add to Desktop Thêm vào Desktop - + Add to Applications Menu Thêm vào menu ứng dụng - + Configure Game - + Scan Subfolders Quét các thư mục con - + Remove Game Directory Loại bỏ thư mục game - + ▲ Move Up ▲ Di chuyển lên - + ▼ Move Down ▼ Di chuyển xuống - + Open Directory Location Mở vị trí thư mục - + Clear Bỏ trống - - - Name - Tên - - - - Compatibility - Tương thích - - - - Add-ons - Tiện ích ngoài - - - - File type - Loại tệp tin - - - - Size - Kích cỡ - - - - Play time - - GameListItemCompat - + Ingame Trong game - + Game starts, but crashes or major glitches prevent it from being completed. Game khởi động, nhưng gặp vấn đề hoặc lỗi nghiêm trọng đến việc không thể hoàn thành trò chơi. - + Perfect Tốt nhất - + Game can be played without issues. Game có thể chơi mà không gặp vấn đề. - + Playable Có thể chơi - + Game functions with minor graphical or audio glitches and is playable from start to finish. Game hoạt động với lỗi hình ảnh hoặc âm thanh nhẹ và có thể chơi từ đầu tới cuối. - + Intro/Menu Phần mở đầu/Menu - + Game loads, but is unable to progress past the Start Screen. Trò chơi đã tải, nhưng không thể qua Màn hình Bắt đầu. - + Won't Boot Không hoạt động - + The game crashes when attempting to startup. Trò chơi sẽ thoát đột ngột khi khởi động. - + Not Tested Chưa ai thử - + The game has not yet been tested. Trò chơi này chưa có ai thử cả. + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list Nháy đúp chuột để thêm một thư mục mới vào danh sách trò chơi game @@ -6307,17 +6370,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) - + Filter: Bộ lọc: - + Enter pattern to filter Nhập khuôn để lọc @@ -6812,1139 +6875,1112 @@ Debug Message: - + Game &Icon Size - + Reset Window Size to &720p Đặt lại kích thước cửa sổ về &720p - + Reset Window Size to 720p Đặt lại kích thước cửa sổ về 720p - + Reset Window Size to &900p Đặt lại kích thước cửa sổ về &900p - + Reset Window Size to 900p Đặt lại kích thước cửa sổ về 900p - + Reset Window Size to &1080p Đặt lại kích thước cửa sổ về &1080p - + Reset Window Size to 1080p Đặt lại kích thước cửa sổ về 1080p - + &Multiplayer &Nhiều người chơi - + &Tools &Công cụ - + Am&iibo - + Launch &Applet - + &TAS &TAS - + &Create Home Menu Shortcut - + Install &Firmware - + &Help &Trợ giúp - + &Install Files to NAND... &Cài đặt tập tin vào NAND... - + L&oad File... N&ạp tập tin... - + Load &Folder... Nạp &Thư mục - + E&xit Th&oát - - + + &Pause &Tạm dừng - + &Stop &Dừng - + &Verify Installed Contents - + &About Eden - + Single &Window Mode &Chế độ cửa sổ đơn - + Con&figure... Cấu& hình - + Ctrl+, - + Enable Overlay Display Applet - + Show &Filter Bar Hiện thanh &lọc - + Show &Status Bar Hiện thanh &trạng thái - + Show Status Bar Hiển thị thanh trạng thái - + &Browse Public Game Lobby &Duyệt phòng game công khai - + &Create Room &Tạo phòng - + &Leave Room &Rời phòng - + &Direct Connect to Room &Kết nối trực tiếp tới phòng - + &Show Current Room &Hiện phòng hiện tại - + F&ullscreen T&oàn màn hình - + &Restart &Khởi động lại - + Load/Remove &Amiibo... Tải/Loại bỏ &Amiibo - - &Report Compatibility - &Báo cáo tương thích - - - + Open &Mods Page Mở trang &mods - - Open &Quickstart Guide - Mở &Hướng dẫn nhanh + + &User Handbook + - - &FAQ - &FAQ - - - + &Capture Screenshot &Chụp ảnh màn hình - + &Album - + &Set Nickname and Owner - + &Delete Game Data - + &Restore Amiibo - + &Format Amiibo - + &Mii Editor - + &Configure TAS... &Cấu hình TAS... - + Configure C&urrent Game... Cấu hình game hiện tại... - - + + &Start &Bắt đầu - + &Reset &Đặt lại - - + + R&ecord G&hi - + Open &Controller Menu - + Install Decryption &Keys - + &Home Menu - + &Desktop - + &Application Menu - + &Root Data Folder - + &NAND Folder - + &SDMC Folder - + &Mod Folder - + &Log Folder - + From Folder - + From ZIP - + &Eden Dependencies - + &Data Manager - + &Tree View - + &Grid View - + Game Icon Size - - + + None - + Show Game &Name - + Show &Performance Overlay - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) - + Standard (64x64) - + Large (128x128) - + Full Size (256x256) - + Broken Vulkan Installation Detected - + Vulkan initialization failed during boot. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping - + Loading Web Applet... - - + + Disable Web Applet - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + The amount of shaders currently being built - + The current selected resolution scaling multiplier. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Unmute - + Mute - + Reset Volume - + + Configure Effects... + + + + &Clear Recent Files - + &Continue - + Warning: Outdated Game Format - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - - + + Error while loading ROM! - + The ROM format is not supported. - + An error occurred initializing the video core. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + An unknown error occurred. Please see the log for more details. - + + (64-bit) - + + (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + Closing software... - + Save Data - + Mod Data - + Error Opening %1 Folder - - + + Folder does not exist! - + Remove Installed Game Contents? - + Remove Installed Game Update? - + Remove Installed Game DLC? - + Remove Entry - + Delete OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? - + Remove Custom Game Configuration? - + Remove Cache Storage? - + Remove File - + Remove Play Time Data - + Reset play time? - - + + RomFS Extraction Failed! - + There was an error copying the RomFS files or the user cancelled the operation. - + Full - + Skeleton - + Select RomFS Dump Mode - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Extracting RomFS... - - + + Cancel - + RomFS Extraction Succeeded! - + The operation completed successfully. - + Error Opening %1 - + Select Directory - + Properties - + The game properties could not be loaded. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Load File - + Open Extracted ROM Directory - + Invalid Directory Selected - + The directory you have selected does not contain a 'main' file. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files - + %n file(s) remaining - + Installing file "%1"... - - + + Install Results - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + %n file(s) were newly installed - + %n file(s) were overwritten - + %n file(s) failed to install - + System Application - + System Archive - + System Application Update - + Firmware Package (Type A) - + Firmware Package (Type B) - + Game - + Game Update - + Game DLC - + Delta Title - + Select NCA Install Type... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Failed to Install - + The title type you selected for the NCA is invalid. - + File not found - + File "%1" not found - + OK - + Function Disabled - + Compatibility list reporting is currently disabled. Check back later! - + Error opening URL - + Unable to open the URL "%1". - + TAS Recording - + Overwrite file of player 1? - + Invalid config detected - + Handheld controller can't be used on docked mode. Pro controller will be selected. - - + + Amiibo - - + + The current amiibo has been removed - + Error - - + + The current game is not looking for amiibos - + Amiibo File (%1);; All Files (*.*) - + Load Amiibo - + Error loading Amiibo data - + The selected file is not a valid amiibo - + The selected file is already on use - + An unknown error occurred - - - Keys not installed - - - - - - Install decryption keys and restart Eden before attempting to install firmware. - - - - - Select Dumped Firmware Source Location - - - - - Select Dumped Firmware ZIP - - - - - Zipped Archives (*.zip) - - - - - Firmware cleanup failed - - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available - + Firmware Corrupted - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot - + PNG Image (*.png) - + TAS state: Running %1/%2 - + TAS state: Recording %1 - + TAS state: Idle %1/%2 - + TAS State: Invalid - + &Stop Running - + Stop R&ecording - + Building: %n shader(s) - + Scale: %1x %1 is the resolution scaling factor - + Speed: %1% / %2% - + Speed: %1% - + Game: %1 FPS - + Frame: %1 ms - - - FSR - - - - + NO AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE - + VOLUME: %1% Volume percentage (e.g. 50%) - + Derivation Components Missing - + Decryption keys are missing. Install them now? - + Wayland Detected! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -7952,74 +7988,74 @@ Would you like to force it for future launches? - + Use X11 - + Continue with Wayland - + Don't show again - + Restart Required - + Restart Eden to apply the X11 backend. - + Slow - + Turbo - + Unlocked - + Select RomFS Dump Target - + Please select which RomFS you would like to dump. - + Are you sure you want to close Eden? - - - + + + Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8085,6 +8121,11 @@ Would you like to bypass this and exit anyway? ScaleForce + + + FSR + + Area @@ -8096,52 +8137,57 @@ Would you like to bypass this and exit anyway? - + + SGSR + + + + + SGSR EdgeDir + + + + Docked - + Handheld - + Fast - - Balanced - - - - + Accurate - + Vulkan - + OpenGL GLSL - + OpenGL SPIRV - + OpenGL GLASM - + Null @@ -8174,7 +8220,7 @@ If you wish to clean up the files which were left in the old data location, you - + Data was migrated successfully. @@ -8664,8 +8710,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8678,10 +8724,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8696,9 +8742,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8860,50 +8906,50 @@ p, li { white-space: pre-wrap; } [không xác định] - + %1%2 %1%2 - + [invalid] [không hợp lệ] - - + + %1%2Hat %3 %1%2Mũ %3 - - - + + + %1%2Axis %3 %1%2Trục %3 - + %1%2Axis %3,%4,%5 %1%2Trục %3,%4,%5 - + %1%2Motion %3 %1%2Chuyển động %3 - - + + %1%2Button %3 %1%2Nút %3 - + [unused] [không sử dụng] @@ -9029,47 +9075,47 @@ p, li { white-space: pre-wrap; } %1 đang chơi %2 - + Play Time: %1 - + Never Played - + Version: %1 - + Version: 1.0.0 - + Installed SD Titles Các title đã cài đặt trên thẻ SD - + Installed NAND Titles Các title đã cài đặt trên NAND - + System Titles Titles hệ thống - + Add New Game Directory Thêm thư mục game - + Favorites Ưa thích @@ -9190,13 +9236,13 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. @@ -9206,10 +9252,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9230,213 +9276,246 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9680,72 +9759,72 @@ Would you like to manually select a portable folder to use? - + Create Shortcut Tạo lối tắt - + Do you want to launch the game in fullscreen? Bạn có muốn khởi chạy trò chơi ở chế độ toàn màn hình không? - + Shortcut Created Lối tắt đã được tạo - + Successfully created a shortcut to %1 Đã tạo thành công lối tắt tới %1 - + Shortcut may be Volatile! Lối tắt có thể không ổn định! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? Thao tác này sẽ tạo một lối tắt đến AppImage hiện tại. Việc này có thể không hoạt động tốt nếu bạn cập nhật. Bạn có muốn tiếp tục không? - + Failed to Create Shortcut Không thể tạo lối tắt - + Failed to create a shortcut to %1 Không thể tạo lối tắt tới %1 - + Create Icon Tạo icon - + Cannot create icon file. Path "%1" does not exist and cannot be created. Không thể tạo icon. Đường dẫn "%1" không tồn tại và không thể tạo được. - + No firmware available Không có firmware khả dụng - + Please install firmware to use the home menu. - + Home Menu Applet - + Home Menu is not available. Please reinstall firmware. @@ -9753,55 +9832,55 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name - + What should this mod be called? - + RomFS - + ExeFS/Patch - + Cheat - + Mod Type - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty @@ -10495,65 +10574,65 @@ By selecting "From Eden", previous save data stored in Ryujinx will be - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/languages/zh_CN.ts b/dist/languages/zh_CN.ts index aa5cda0fcc..2b86a73843 100644 --- a/dist/languages/zh_CN.ts +++ b/dist/languages/zh_CN.ts @@ -218,155 +218,6 @@ This would ban both their forum username and their IP address. %1 - %2 (%3/%4 个成员) - 已连接 - - CompatDB - - - Report Compatibility - 报告兼容性 - - - - - - - - - - Report Game Compatibility - 报告游戏兼容性 - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - <html><head/><body><p><span style=" font-size:10pt;">如果您选择向 </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">Eden 兼容性列表</span></a><span style=" font-size:10pt;">提交测试结果,我们将收集并在网页上展示以下信息:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">硬件信息(CPU / GPU / 操作系统)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">您使用的 Eden 版本</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">已连接的 Eden 账户</li></ul></body></html> - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>游戏成功启动了吗?</p></body></html> - - - - Yes The game starts to output video or audio - 是的,游戏开始输出视频或音频 - - - - No The game doesn't get past the "Launching..." screen - 不,游戏无法通过“启动中…”页面 - - - - Yes The game gets past the intro/menu and into gameplay - 是的,游戏出现开场/菜单页面并进入游戏 - - - - No The game crashes or freezes while loading or using the menu - 不,加载或使用菜单时游戏崩溃或卡死 - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>游戏是否可玩?</p></body></html> - - - - Yes The game works without crashes - 没有,游戏运行时没有崩溃 - - - - No The game crashes or freezes during gameplay - 有,游戏运行时出现卡死或崩溃 - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>游戏在运行时有没有崩溃、卡死或出现软锁?</p></body></html> - - - - Yes The game can be finished without any workarounds - 是的,可以顺利地完成游戏过程 - - - - No The game can't progress past a certain area - 不,游戏在特定区段无法继续 - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>游戏从头到尾都可以顺利运行吗?</p></body></html> - - - - Major The game has major graphical errors - 严重 游戏在运行时有严重的图形错误 - - - - Minor The game has minor graphical errors - 轻微 游戏在运行时有轻微的图形错误 - - - - None Everything is rendered as it looks on the Nintendo Switch - 完美 媲美实机的游戏体验 - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>游戏运行时出现了图形问题吗?</p></body></html> - - - - Major The game has major audio errors - 严重 游戏运行时出现严重的音频错误 - - - - Minor The game has minor audio errors - 轻微 游戏运行时出现轻微的音频错误 - - - - None Audio is played perfectly - 完美 游戏运行时音频未出现任何问题 - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>游戏是否出现音频故障/效果缺失?</p></body></html> - - - - Thank you for your submission! - 感谢您向我们提交信息! - - - - Submitting - 提交中 - - - - Communication error - 网络错误 - - - - An error occurred while sending the Testcase - 在提交测试用例时发生错误。 - - - - Next - 下一步 - - ConfigurationShared @@ -571,42 +422,39 @@ Can help reduce stuttering at lower framerates. - + Backend: 后端: - CPU Overclock - CPU 超频 + CPU Clocks + CPU 时钟频率 - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. - 对模拟的 CPU 进行超频,以移除部分 FPS 限制。性能较弱的 CPU 可能会出现性能下降,某些游戏也可能会出现异常行为。 - -您可以选择: -Boost(1700MHz):运行在 Switch 的最高原生频率。 -Fast(2000MHz):运行在 2 倍频率。 + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + 提高模拟 CPU 报告的时钟频率,从而消除部分帧率限制。 +性能较弱的 CPU 可能会出现可见性能下降,甚至会导致某些游戏运行异常。 - + Custom CPU Ticks 自定义 CPU 时钟周期 - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. 设置自定义的 CPU 时钟周期。较高的数值可能提升性能,但也可能导致游戏卡死。推荐范围:77–21000。 - + Enable Host MMU Emulation (fastmem) 启用主机 MMU 模拟(快速内存存取) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. @@ -615,100 +463,100 @@ Disabling this forces all memory accesses to use Software MMU Emulation. - + Unfuse FMA (improve performance on CPUs without FMA) 低精度 FMA (在 CPU 不支持 FMA 指令集的情况下提高性能) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. 该选项通过降低积和熔加运算的精度来提高模拟器在不支持 FMA 指令集 CPU 上的运行速度。 - + Faster FRSQRTE and FRECPE 快速 FRSQRTE 和 FRECPE - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. 该选项通过使用精度较低的近似值来提高某些浮点函数的运算速度。 - + Faster ASIMD instructions (32 bits only) 加速 ASIMD 指令执行(仅限 32 位) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. 该选项通过不正确的舍入模式来提高 32 位 ASIMD 浮点函数的运行速度。 - + Inaccurate NaN handling 低精度非数处理 - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. 该选项通过取消非数检查来提高速度。 请注意,这也会降低某些浮点指令的精确度。 - + Disable address space checks 禁用地址空间检查 - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. 此选项通过在每次内存操作前取消安全检查来提高速度。 禁用它可能允许执行任意代码。 - + Ignore global monitor 忽略全局监视器 - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. 此选项仅通过 cmpxchg 指令来提高速度,以确保独占访问指令的安全性。 请注意,这可能会导致死锁和其他问题。 - + API: API: - + Changes the output graphics API. Vulkan is recommended. 更改图形输出的 API。 推荐使用 Vulkan。 - + Device: 设备: - + This setting selects the GPU to use (Vulkan only). 设置Vulkan下使用的GPU。 - + Resolution: 分辨率: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. @@ -717,27 +565,27 @@ Options lower than 1X can cause artifacts. 低于 1X 的选项可能造成渲染问题。 - + Window Adapting Filter: 窗口滤镜: - + FSR Sharpness: FSR 锐化度: - - Determines how sharpened the image will look using FSR's dynamic contrast. - 确定使用 FSR 的动态对比度后的图像锐化度。 + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. + 确定使用 FSR 或 SGSR 的动态对比度时图像看起来有多锐利。 - + Anti-Aliasing Method: 抗锯齿方式: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. @@ -745,13 +593,23 @@ FXAA can produce a more stable picture in lower resolutions. SMAA 提供最佳质量。 FXAA 可以在较低分辨率下产生更稳定的画面。 + + + Enable post-processing effects + 开启后处理效果 + + Applies post-processing effects to the final image. + 给最终图像加上后处理效果。 + + + Fullscreen Mode: 全屏模式: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. @@ -760,12 +618,12 @@ Exclusive fullscreen may offer better performance and better Freesync/Gsync supp 独占全屏提供更好的性能和 Freesync/Gsync 支持。 - + Aspect Ratio: 屏幕纵横比: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. @@ -774,36 +632,36 @@ Also controls the aspect ratio of captured screenshots. 此设置同时控制捕获截图的高宽比。 - + Use persistent pipeline cache 使用持久管道缓存 - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. 将生成的着色器保存到硬盘,提高后续游戏过程中的着色器加载速度。 请仅在调试时禁用此项。 - + Use asynchronous GPU emulation 使用异步 GPU 模拟 - + Uses an extra CPU thread for rendering. This option should always remain enabled. 使用额外的 CPU 线程进行渲染。 此选项应始终保持启用状态。 - + NVDEC emulation: NVDEC 模拟方式: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. @@ -812,12 +670,12 @@ In most cases, GPU decoding provides the best performance. 大多数情况下,使用 GPU 解码将提供最好的性能。 - + ASTC Decoding Method: ASTC 纹理解码方式: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -830,12 +688,12 @@ CPU 异步模拟:使用 CPU 在 ASTC 纹理到达时对其进行解码。 消除 ASTC 解码带来的卡顿,但在解码时可能出现渲染问题。 - + ASTC Recompression Method: ASTC 纹理重压缩方式: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. @@ -844,44 +702,64 @@ BC1/BC3: 中间格式将被重新压缩为 BC1 或 BC3 格式,从而节省显存 但会降低图像质量。 - + Frame Pacing Mode (Vulkan only) 帧同步模式 (仅限 Vulkan) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. 控制模拟器如何管理帧同步,以减少卡顿,使帧率表现更加平稳顺滑。 - + VRAM Usage Mode: VRAM 使用模式: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. 选择模拟器是应优先节省内存还是最大限度地使用可用视频内存以提高性能。 激进模式可能会影响诸如录屏软件等其他应用程序的性能,。 - + Skip CPU Inner Invalidation 跳过 CPU 内部失效处理 - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. 在内存更新期间跳过某些缓存失效,从而降低 CPU 使用率并改善延迟。这可能导致软件崩溃。 - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: 垂直同步模式: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -892,12 +770,12 @@ Mailbox 的延迟可能比 FIFO 低且不会导致撕裂,但可能会丢帧。 Immediate (不同步) 会呈现全部可用内容,并可能出现撕裂。 - + Sync Memory Operations 同步内存操作 - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. @@ -906,98 +784,116 @@ Unreal Engine 4 games often see the most significant changes thereof. 虚幻 4 引擎的游戏通常会看到最显著的变化。 - + Enable asynchronous presentation (Vulkan only) 启用异步帧提交 (仅限 Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. 将帧提交移动到单独的 CPU 线程,略微提高性能。 - + Force maximum clocks (Vulkan only) 强制最大时钟 (仅限 Vulkan) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. 在后台运行的同时等待图形命令,以防止 GPU 降低时钟速度。 - + Anisotropic Filtering: 各向异性过滤: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. 控制在斜角下纹理渲染的质量。 大多数 GPU 上设置为 16 倍是安全的。 - + GPU Mode: GPU 模式: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - 控制 GPU 模拟的精确度。大部分游戏在性能或平衡模式下可以正常渲染,但部分游戏需要设置为精确。粒子效果通常只有在精确模式下才能正确显示。 + 控制 GPU 模拟模式。 +大多数游戏在“快速”模式下渲染效果良好,但有些游戏仍需使用“精确”模式。而粒子效果通常只有在“精确”模式下才能正确渲染。 - + DMA Accuracy: DMA 精度: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. - 控制 DMA 精度。安全精度可修复某些游戏中的问题,但可能会降低性能。 + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. + 控制 DMA 读取模式。 +不安全模式速度更快,而安全模式更稳定,并且可以修复某些游戏中的问题。 +默认使用 GPU 精度设置。 - + + GPU Fence Behavior: + GPU 围栏行为: + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation 开启异步着色器编译 - + May reduce shader stutter. 可能减少着色器卡顿。 - - Fast GPU Time - GPU 超频频率 + + GPU Clocks + GPU 时钟频率 - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. - 将模拟的 GPU 超频,以提高动态分辨率和渲染距离。使用 256 可获得最大性能,使用 512 可获得最高的图形保真度。 + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. + 通过让游戏误以为 GPU 的处理速度比实际更快,以避免游戏为了适应 Switch 的时钟频率而降低分辨率和渲染距离。 - + GPU Unswizzle GPU 还原 - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. 利用 GPU 计算来加速 BCn 格式 3D 纹理的解码。如果遇到崩溃或画面花屏,请禁用此项。 - + GPU Unswizzle Max Texture Size GPU 还原最大纹理尺寸 - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. @@ -1006,48 +902,48 @@ Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size GPU 还原流大小 - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. 设置每帧处理的纹理数据最大量(单位:MiB)。 较高的数值可以减少纹理加载时的卡顿,但可能会影响帧率的稳定性(即造成帧时间波动)。 - + GPU Unswizzle Chunk Size GPU 还原块大小 - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. 确定在单次调度(Dispatch)中处理的深度切片(Depth Slices)数量。 增加此数值可以提高高端 GPU 的吞吐量(处理效率),但在性能较弱的硬件上可能会引发 TDR(驱动程序重置)或驱动超时。 - + Use Vulkan pipeline cache 启用 Vulkan 管线缓存 - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. 启用 GPU 供应商专用的管线缓存。 在 Vulkan 驱动程序内部不存储管线缓存的情况下,此选项可显著提高着色器加载速度。 - + Enable Compute Pipelines (Intel Vulkan Only) 启用计算管线 (仅限 Intel 显卡 Vulkan 模式) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. @@ -1056,183 +952,181 @@ Compute pipelines are always enabled on all other drivers. 在所有其他驱动程序上始终启用计算管线。 - + Enable Reactive Flushing 启用反应性刷新 - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. 使用反应性刷新取代预测性刷新,从而更精确地同步内存。 - + Sync to framerate of video playback 播放视频时帧率同步 - + Run the game at normal speed during video playback, even when the framerate is unlocked. 在视频播放期间以正常速度运行游戏,即使帧率未锁定。 - + Barrier feedback loops 屏障反馈环路 - + Improves rendering of transparency effects in specific games. 改进某些游戏中透明效果的渲染。 - + Enable buffer history 启用缓冲区历史 - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. 允许访问之前的缓冲状态。 这个选项可能会提升某些游戏的渲染质量和性能一致性。 - + Fix bloom effects 修复泛光效果 - + Removes bloom in Burnout. 去除《火爆狂飙》中的泛光特效。 - - Enable Legacy Rescale Pass - 启用旧版缩放 - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - 通过依赖之前实现的行为,可能会修复部分游戏的缩放重叠问题。 -修复 AMD 和 Intel 显卡上的线条伪影,以及《路易斯洋楼3》中 Nvidia 显卡的灰色纹理闪烁的遗留行为变通方法。 - - - + Extended Dynamic State 扩展动态状态 - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. 控制在扩展动态状态中可使用的函数数量。更高的数值允许启用更多功能,并可能提升性能,但同时也可能导致额外的图形问题。 - + Vertex Input Dynamic State 顶点输入动态状态 - + Enables vertex input dynamic state feature for better quality and performance. 开启顶点输入动态状态功能来获得更好的质量和性能。 - + Sample Shading 采样着色 - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. 允许片段着色器在多重采样的片段中每个样本执行一次而不是每个片段执行一次。可以提高图形质量,但会降低性能。 更高的值可以提高质量但会降低性能。 - + RNG Seed 随机数生成器种子 - + Controls the seed of the random number generator. Mainly used for speedrunning. 控制随机数生成器的种子。 主要用于竞速游戏。 - + Device Name 设备名称 - + The name of the console. 主机的数量。 - + + Homebrew Args + 自制软件参数 + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + 启动 homebrew 时需要传递的命令行参数 (例如 -noglsl)。 + + + Custom RTC Date: 自定义系统时间: - + This option allows to change the clock of the console. Can be used to manipulate time in games. 此选项允许更改控制台的时钟。 可用于操纵游戏中的时间。 - + The number of seconds from the current unix time 来自当前 unix 时间的秒数。 - + Language: 语言: - + This option can be overridden when region setting is auto-select 当区域设置为自动选择时可以使用此选项替代。 - + Region: 地区: - + The region of the console. 主机的区域。 - + Time Zone: 时区: - + The time zone of the console. 主机的时区。 - + Sound Output Mode: 声音输出模式: - + Console Mode: 控制台模式: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. @@ -1241,1031 +1135,1026 @@ Setting to Handheld can help improve performance for low end systems. 将设置为掌机模式可以帮助低端系统提高性能。 - + Prompt for user profile on boot 启动时提示选择用户账户 - + Useful if multiple people use the same PC. 在多人使用相同的 PC 时有效。 - + Pause when not in focus 在丢失焦点时暂停 - + Pauses emulation when focusing on other windows. 当焦点位于其它窗口时暂停模拟器。 - + Confirm before stopping emulation 停止模拟时需要确认 - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. 替代提示以确认停止模拟。 启用它将绕过此类提示并直接退出模拟。 - + Hide mouse on inactivity 自动隐藏鼠标光标 - + Hides the mouse after 2.5s of inactivity. 在 2.5 秒无活动后隐藏鼠标。 - - Disable controller applet - 禁用控制器小程序 - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - 强制禁止在模拟程序中使用控制器小程序。 - 当程序尝试打开控制器小程序时,它会立即被关闭。 - - - + Check for updates 检查更新 - + Whether or not to check for updates upon startup. 在启动时是否检查更新。 - + Enable Gamemode 启用游戏模式 - + Force X11 as Graphics Backend 强制使用 X11 作为图形后端 - + Custom frontend 自定义前端 - + Real applet 真实的小程序 - + + + Disabled + 禁用 + + + Never 永不 - + On Load 加载时 - + Always 总是 - + CPU CPU - + GPU GPU - + CPU Asynchronous CPU 异步模拟 - + Uncompressed (Best quality) 不压缩 (最高质量) - + BC1 (Low quality) BC1 (低质量) - + BC3 (Medium quality) BC3 (中等质量) - - + + Auto 自动 - + 30 FPS 30 FPS - + 60 FPS 60 FPS - + 90 FPS 90 FPS - + 120 FPS 120 FPS - + Conservative 保守模式 - + Aggressive 激进模式 - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) OpenGL GLASM (汇编着色器,仅限 NVIDIA 显卡) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) OpenGL SPIR-V (实验性,仅限 AMD/Mesa) - + Null 无 - + Fast 高速 - - Balanced - 平衡 - - - - + + + Accurate 高精度 - - + + + Default 系统默认 - + Unsafe (fast) 不安全(快速) - + Safe (stable) 安全(稳定) - + + Immediate + 即时 + + + + Balanced + 平衡 + + + Unsafe 低精度 - + Paranoid (disables most optimizations) 偏执模式 (禁用绝大多数优化项) - + Debugging 调试 - + Dynarmic 动态编译 - + NCE 本机代码执行 - + Borderless Windowed 无边框窗口 - + Exclusive Fullscreen 独占全屏 - + No Video Output 无视频输出 - + CPU Video Decoding CPU 视频解码 - + GPU Video Decoding (Default) GPU 视频解码 (默认) - + 0.25X (180p/270p) [EXPERIMENTAL] 0.25X (180p/270p) [实验性] - + 0.5X (360p/540p) [EXPERIMENTAL] 0.5X (360p/540p) [实验性] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [实验性] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] 1.25X (900p/1350p) [实验性] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [实验性] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor 近邻取样 - + Bilinear 双线性过滤 - + Bicubic 双三线过滤 - + Gaussian 高斯模糊 - + Lanczos Lanczos - + ScaleForce 强制缩放 - + AMD FidelityFX Super Resolution AMD FidelityFX 超级分辨率 - + Area 区域 - + MMPX MMPX - + Zero-Tangent 零切线 - + B-Spline B-Spline - + Mitchell Mitchell - + Spline-1 Spline-1 - - + + Snapdragon Game Super Resolution + 骁龙游戏超级分辨率 + + + + Snapdragon Game Super Resolution EdgeDir + 骁龙游戏超级分辨率 EdgeDir + + + None 无 - + FXAA 快速近似抗锯齿 - + SMAA 子像素形态学抗锯齿 - + Default (16:9) 默认 (16:9) - + Force 4:3 强制 4:3 - + Force 21:9 强制 21:9 - + Force 16:10 强制 16:10 - + Stretch to Window 拉伸窗口 - + Automatic 自动 - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - 32x - - - - 64x - 64x - - - + Japanese (日本語) 日语 (日本語) - + American English 美式英语 - + French (français) 法语 (français) - + German (Deutsch) 德语 (Deutsch) - + Italian (italiano) 意大利语 (italiano) - + Spanish (español) 西班牙语 (español) - + Chinese 中文 - + Korean (한국어) 韩语 (한국어) - + Dutch (Nederlands) 荷兰语 (Nederlands) - + Portuguese (português) 葡萄牙语 (português) - + Russian (Русский) 俄语 (Русский) - + Taiwanese 台湾中文 - + British English 英式英语 - + Canadian French 加拿大法语 - + Latin American Spanish 拉美西班牙语 - + Simplified Chinese 简体中文 - + Traditional Chinese (正體中文) 繁体中文 (正體中文) - + Brazilian Portuguese (português do Brasil) 巴西-葡萄牙语 (português do Brasil) - + Polish (polska) 波兰语(波兰语) - + Thai (แบบไทย) 泰语 - - + + Japan 日本 - + USA 美国 - + Europe 欧洲 - + Australia 澳大利亚 - + China 中国 - + Korea 韩国 - + Taiwan 台湾地区 - + Auto (%1) Auto select time zone 自动 (%1) - + Default (%1) Default time zone 默认 (%1) - + CET 欧洲中部时间 - + CST6CDT 古巴标准时间&古巴夏令时 - + Cuba 古巴 - + EET 东欧时间 - + Egypt 埃及 - + Eire 爱尔兰 - + EST 东部标准时间 - + EST5EDT 东部标准时间&东部夏令时 - + GB 英国 - + GB-Eire 英国-爱尔兰时间 - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich 格林威治 - + Hongkong 香港 - + HST 美国夏威夷时间 - + Iceland 冰岛 - + Iran 伊朗 - + Israel 以色列 - + Jamaica 牙买加 - + Kwajalein 夸贾林环礁 - + Libya 利比亚 - + MET 中欧时间 - + MST 山区标准时间 (北美) - + MST7MDT 山区标准时间&山区夏令时 (北美) - + Navajo 纳瓦霍 - + NZ 新西兰时间 - + NZ-CHAT NZ-CHAT - + Poland 波兰 - + Portugal 葡萄牙 - + PRC 中国标准时间 - + PST8PDT 太平洋标准时间&太平洋夏令时 - + ROC 台湾时间 - + ROK 韩国时间 - + Singapore 新加坡 - + Turkey 土耳其 - + UCT UCT - + Universal 世界时间 - + UTC 协调世界时 - + W-SU 欧洲-莫斯科时间 - + WET 西欧时间 - + Zulu 祖鲁 - + Mono 单声道 - + Stereo 立体声 - + Surround 环绕声 - + 4GB DRAM (Default) 4GB DRAM (默认) - + 6GB DRAM (Unsafe) 6GB DRAM (不安全) - + 8GB DRAM 8GB DRAM - + 10GB DRAM (Unsafe) 10GB DRAM (不安全) - + 12GB DRAM (Unsafe) 12GB DRAM (不安全) - + Docked 主机模式 - + Handheld 掌机模式 - - - Off - 关闭 + + + Normal + 正常 - - Boost (1700MHz) - 加速 (1700MHz) + + + Boost + 升频 - - Fast (2000MHz) - 快速 (2000MHz) + + + Overclock + 超频 - + Always ask (Default) 总是询问 (默认) - + Only if game specifies not to stop 仅当游戏不希望停止时 - + Never ask 从不询问 - - - Medium (256) - 中(256) - - - - - High (512) - 高(512) - - - + Very Small (16 MB) 很小 (16 MB) - + Small (32 MB) 较小 (32 MB) - + Normal (128 MB) 正常 (128 MB) - + Large (256 MB) 较大 (256 MB) - + Very Large (512 MB) 很大 (512 MB) - + Very Low (4 MB) 很低 (4 MB) - + Low (8 MB) 低 (8 MB) - + Normal (16 MB) 正常 (16 MB) - + Medium (32 MB) 中 (32 MB) - + High (64 MB) 高 (64 MB) - + Very Low (32) 很低 (32) - + Low (64) 低 (64) - + Normal (128) 正常 (128) - - Disabled - 禁用 + + Medium (256) + 中(256) - + + High (512) + 高(512) + + + ExtendedDynamicState 1 扩展动态状态 1 - + ExtendedDynamicState 2 扩展动态状态 2 - + ExtendedDynamicState 3 扩展动态状态 3 - + Tree View 树景视图 - + Grid View 网格视图 @@ -2625,252 +2514,297 @@ When a program attempts to open the controller applet, it is immediately closed. 全局日志过滤器 - - When checked, the max size of the log increases from 100 MB to 1 GB - 选中此项后,日志文件的最大大小从 100MB 增加到 1GB + + GPU Logging/Level + GPU 日志/级别 - - Enable Extended Logging** - 启用扩展的日志记录** + + Detail level for GPU logs. Off disables logging entirely. + GPU 日志的详细级别。关闭会完全禁用日志记录。 - + + Off + 关 + + + + Errors + 错误 + + + + Standard + 标准 + + + + Verbose + 详细 + + + + All + 全部 + + + Show Log in Console 显示日志窗口 - + + When checked, the max size of the log increases from 100 MB to 1 GB + 选中此项后,日志文件的最大大小从 100MB 增加到 1GB + + + + Enable Extended Logging** + 启用扩展的日志记录** + + + Open Log Location 打开日志位置 - + Homebrew 自制游戏 - + Arguments String 参数字符串 - + Graphics 图形 - + When checked, it executes shaders without loop logic changes 启用后,yuzu 在执行着色器时,不会修改循环结构的条件判断 - + Disable Loop safety checks 禁用循环体安全检查 - + When checked, it will dump all the macro programs of the GPU 选中后,将转储 GPU 的所有宏程序 - + Dump Maxwell Macros 转储 Maxwell 宏 - + When checked, it enables Nsight Aftermath crash dumps 启用后,yuzu 将会保存 Nsight Aftermath 格式的崩溃转储文件 - + Enable Nsight Aftermath 启用 Nsight Aftermath - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - 启用时,将从磁盘着色器缓存或游戏中转储所有的着色器文件。 + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + 选中后,它会将原始的 Maxwell 客户端着色器字节码 (重编译器的输入) 以 .ash 文件形式转储到 DumpDir/shaders/ 下。对于使用 nvdisasm 检查很有用。 - - Dump Game Shaders - 转储着色器文件 + + Dump Guest (Maxwell) Shaders + 转储客户 (Maxwell) 的着色器 - + Enable Renderdoc Hotkey 启用 Renderdoc 热键 - + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower 启用时,将禁用宏即时编译器。这会降低游戏运行速度。 - + Disable Macro JIT 禁用宏即时编译 - + When checked, it disables the macro HLE functions. Enabling this makes games run slower 启用时,将禁用宏高阶模拟。这会降低游戏运行速度。 - + Disable Macro HLE 禁用宏高阶模拟 - + When checked, the graphics API enters a slower debugging mode 启用时,图形 API 将进入较慢的调试模式。 - + Enable Graphics Debugging 启用图形调试 - + When checked, yuzu will log statistics about the compiled pipeline cache 选中时,yuzu 将记录有关已编译着色器缓存的统计信息。 - + Enable Shader Feedback 启用着色器反馈 - + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> <html><head/><body><p>选中时,禁用映射内存上载的重新排序,从而允许将上载与特定绘图关联起来。某些情况下可能会降低性能。</p></body></html> - + Disable Buffer Reorder 禁用缓存重排序 - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + 选中后,它会把重编译器生成的 SPIR-V 二进制文件 (.spv) 放到 LogDir/shaders/ 下。可以用 SPIRV-Tools(spirv-dis / spirv-cross / spirv-val)或者 RenderDoc 查看。 + + + + Dump SPIR-V Shaders + 转储 SPIR-V 着色器 + + + Advanced 高级选项 - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. 允许 yuzu 在启动时检查 Vulkan 环境是否正常工作。如果是其他程序导致 yuzu 出现相关问题,请禁用此选项。 - + Perform Startup Vulkan Check 启动时进行 Vulkan 检测 - + Disable Web Applet 禁用 Web 小程序 - + Enable All Controller Types 启用其他类型的控制器 - + Enable Auto-Stub 开启自动存根 - + Kiosk (Quest) Mode Kiosk (Quest) 模式 - + Use dev.keys 使用 dev.keys - + Enable Debug Asserts 启用调试断言 - + Debugging 调试选项 - + Battery Serial: 电池序列号: - + + Debug Knobs: + 调试参数: + + + Bitmask for quick development toggles 用于快速开发切换的位掩码 - + Set debug knobs (bitmask) 设置调试开关 (位掩码) - + 16-bit debug knob set for quick development toggles 用于快速开发切换的 16 位调试开关组 - + (bitmask) (位掩码) - - Debug Knobs: - 调试开关: - - - + Unit Serial: 单元序列号: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. 启用此选项会将最新的音频命令列表输出到控制台。只影响使用音频渲染器的游戏。 - + Dump Audio Commands To Console** 将音频命令转储至控制台** - + Flush log output on each line 每行刷新日志输出 - + Enable FS Access Log 启用文件系统访问记录 - + Enable Verbose Reporting Services** 启用详细报告服务** - + Censor username in logs 审查日志中的用户名 - + **This will be reset automatically when Eden closes. **这会在 Eden 关闭时自动重置。 @@ -3320,33 +3254,38 @@ Would you like to delete the old save data? 背景颜色: - + % - FSR sharpening percentage (e.g. 50%) + FSR/SGSR sharpening percentage (e.g. 50%) % - + + Configure Effects... + 配置效果... + + + Off 关闭 - + VSync Off 垂直同步关 - + Recommended 推荐 - + On 开启 - + VSync On 垂直同步开 @@ -3867,6 +3806,16 @@ Would you like to delete the old save data? Motion / Touch 体感/触摸 + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + 旨在禁用 SDL GUIDE 按钮作弊:当按下 SELECT(减号) + START(加号) 时,会触发合成的 GUIDE(HOME) 事件。可能影响 Windows 相关的触发/震动等功能 + + + + Disable SDL WGI/XInput (Requires restart) + 禁用 SDL WGI/XInput (需要重启) + ConfigureInputPerGame @@ -4376,58 +4325,58 @@ To invert the axes, first move your joystick vertically, and then horizontally.< [等待中] - + New Profile 新建自定义设置 - + Enter a profile name: 输入配置文件名称: - - + + Create Input Profile 新建输入配置文件 - + The given profile name is not valid! 输入的配置文件名称无效! - + Failed to create the input profile "%1" 新建输入配置文件 "%1" 失败 - + Delete Input Profile 删除输入配置文件 - + Failed to delete the input profile "%1" 删除输入配置文件 "%1" 失败 - + Load Input Profile 加载输入配置文件 - + Failed to load the input profile "%1" 加载输入配置文件 "%1" 失败 - + Save Input Profile 保存输入配置文件 - + Failed to save the input profile "%1" 保存输入配置文件 "%1" 失败 @@ -4475,7 +4424,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Configure 设置 @@ -4506,7 +4455,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Test 测试 @@ -4521,77 +4470,77 @@ To invert the axes, first move your joystick vertically, and then horizontally.< 移除服务器 - + %1:%2 %1:%2 - - - - - - + + + + + + Eden Eden - + Port number has invalid characters 端口号中包含无效字符 - + Port has to be in range 0 and 65353 端口必须为 0 到 65353 之间 - + IP address is not valid 无效的 IP 地址 - + This UDP server already exists 此 UDP 服务器已存在 - + Unable to add more than 8 servers 最多只能添加 8 个服务器 - + Testing 测试中 - + Configuring 配置中 - + Test Successful 测试成功 - + Successfully received data from the server. 已成功地从服务器获取数据。 - + Test Failed 测试失败 - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. 无法从服务器获取数据。<br>请验证服务器是否正在运行,以及地址和端口是否配置正确。 - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. UDP 测试或触摸校准正在进行中。<br>请耐心等待。 @@ -4776,57 +4725,57 @@ Current values are %1% and %2% respectively. 只有当游戏不在运行时,某些设置项才可用。 - + Add-Ons 附加项 - + System 系统 - + CPU CPU - + Graphics 图形 - + Adv. Graphics 高级图形 - + Ext. Graphics 扩展图形 - + Audio 声音 - + Input Profiles 输入配置文件 - + Network 网络 - + Applets 小程序 - + Properties 属性 @@ -4953,6 +4902,131 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t 在文件管理器中打开(&O) + + ConfigurePostProcessing + + + Post-Processing Effects + 后处理效果 + + + + Changes apply immediately while a game is running. + 更改会在游戏运行时立即生效。 + + + + Enable post-processing + 开启后处理 + + + + Preset: + 预设: + + + + Apply + 应用 + + + + Reset Values + 重置值 + + + + Return every effect to the values the preset ships with. + 把每个效果恢复到预设的默认值 + + + + Save As... + 另存为... + + + + Save Preset + 保存预设 + + + + Preset name: + 预设名称: + + + + Clear + 清除 + + + + Remove every effect in use and deselect the preset. + 移除所有使用中的效果并取消选择预设。 + + + + Add Effect + 添加效果 + + + + None + 无 + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + 没有使用中的效果。请选择一个预设并点击应用,或一个一个地添加效果。 + + + + Custom chain. Pick a preset above and press Apply to replace it. + 自定义链。选择上面的预设并点击应用替换它。 + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + 活动的预设: %1 (已修改)。按重置值可以恢复出厂设置。 + + + + Active preset: %1 + 活动的预设: %1 + + + + Enabled + 开启 + + + + Disabled + 关闭 + + + + Reset to defaults + 重置为默认值 + + + + Effect '%1' was not found. + 找不到效果 '%1'。 + + + + Effect failed to compile: +%1 + 编译效果失败: +%1 + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + 找不到可用的 ReShade FX 效果。请将 .fx 文件放到 post_shaders 文件夹中。 + + ConfigureProfileManager @@ -5520,91 +5594,86 @@ Drag points to change position, or double-click table cells to edit values. - Show Compatibility List - 显示兼容性列表 - - - Show Add-Ons Column 显示“附加项”列 - + Show Size Column 显示“文件大小”列 - + Show File Types Column 显示“文件类型”列 - + Show Play Time Column 显示“游玩时间”列 - + Folder Icon Size: 文件夹图标大小: - + Row 1 Text: 第一行: - + Row 2 Text: 第二行: - + Screenshots 捕获截图 - + Ask Where To Save Screenshots (Windows Only) 询问保存截图的位置 (仅限 Windows ) - + Screenshots Path: 截图保存位置: - + ... ... - + TextLabel 文本水印 - + Resolution: 分辨率: - + Select Screenshots Path... 选择截图保存位置... - + <System> <System> - + English 英语 - + Auto (%1 x %2, %3 x %4) Screenshot width value 自动 (%1 x %2, %3 x %4) @@ -5841,7 +5910,7 @@ Drag points to change position, or double-click table cells to edit values.导入此目录的数据。这可能需要一些时间,并且会删除所有现有数据! - + Calculating... 正在计算... @@ -6044,330 +6113,323 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! OpenGL 模式不可用! - + OpenGL shared contexts are not supported. 不支持 OpenGL 共享上下文。 - + Eden has not been compiled with OpenGL support. Eden 尚未编译为支持 OpenGL。 - - - + + + Error while initializing OpenGL! 初始化 OpenGL 时出错! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. 您的 GPU 可能不支持 OpenGL ,或者您没有安装最新的显卡驱动。 - + Error while initializing OpenGL 4.6! 初始化 OpenGL 4.6 时出错! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 您的 GPU 可能不支持 OpenGL 4.6 ,或者您没有安装最新的显卡驱动。<br><br>GL 渲染器:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 您的 GPU 可能不支持某些必需的 OpenGL 扩展。请确保您已经安装最新的显卡驱动。<br><br>GL 渲染器:<br>%1<br><br>不支持的扩展:<br>%2 - + This build doesn't have OpenGL support. - + 此版本不支持 OpenGL。 GameList - + &Add New Game Directory 添加新游戏目录 (&A) - + Favorite 收藏 - + Start Game 开始游戏 - + Start Game without Custom Configuration 使用公共设置项进行游戏 - + Open Save Data Location 打开存档位置 - + Open Mod Data Location 打开 MOD 数据位置 - + Open Transferable Pipeline Cache 打开可转移着色器缓存 - + Link to Ryujinx 链接到 Ryujinx - + Remove 删除 - + Remove Installed Update 删除已安装的游戏更新 - + Remove All Installed DLC 删除所有已安装 DLC - + Remove Custom Configuration 删除自定义设置 - + Remove Cache Storage 移除缓存 - + Remove OpenGL Pipeline Cache 删除 OpenGL 着色器缓存 - + Remove Vulkan Pipeline Cache 删除 Vulkan 着色器缓存 - + Remove All Pipeline Caches 删除所有着色器缓存 - + Remove All Installed Contents 删除所有安装的项目 - + Manage Play Time 管理游戏时间 - + Edit Play Time Data 编辑游戏时间数据 - + Remove Play Time Data 清除游玩时间 - - + + Dump RomFS 转储 RomFS - + Dump RomFS to SDMC 转储 RomFS 到 SDMC - + Verify Integrity 完整性验证 - + Copy Title ID to Clipboard 复制游戏 ID 到剪贴板 - - Navigate to GameDB entry - 查看兼容性报告 - - - + Create Shortcut 创建快捷方式 - + Add to Desktop 添加到桌面 - + Add to Applications Menu 添加到应用程序菜单 - + Configure Game 配置游戏 - + Scan Subfolders 扫描子文件夹 - + Remove Game Directory 移除游戏目录 - + ▲ Move Up ▲ 向上移动 - + ▼ Move Down ▼ 向下移动 - + Open Directory Location 打开目录位置 - + Clear 清除 - - - Name - 名称 - - - - Compatibility - 兼容性 - - - - Add-ons - 附加项 - - - - File type - 文件类型 - - - - Size - 大小 - - - - Play time - 游玩时间 - GameListItemCompat - + Ingame 可进游戏 - + Game starts, but crashes or major glitches prevent it from being completed. 游戏可以开始,但会出现崩溃或严重故障导致游戏无法继续。 - + Perfect 完美 - + Game can be played without issues. 游戏可以毫无问题地运行。 - + Playable 可运行 - + Game functions with minor graphical or audio glitches and is playable from start to finish. 游戏可以从头到尾完整地运行,但可能出现轻微的图形或音频故障。 - + Intro/Menu 开场/菜单 - + Game loads, but is unable to progress past the Start Screen. 游戏可以加载,但无法通过标题页面。 - + Won't Boot 无法启动 - + The game crashes when attempting to startup. 在启动游戏时直接崩溃。 - + Not Tested 未测试 - + The game has not yet been tested. 游戏尚未经过测试。 + + GameListModel + + + Name + 名称 + + + + Add-ons + 附加内容 + + + + File type + 文件类型 + + + + Size + 大小 + + + + Play time + 游戏时间 + + GameListPlaceholder - + Double-click to add a new folder to the game list 双击添加新的游戏文件夹 @@ -6375,17 +6437,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) %n个结果中的第%1个 - + Filter: 搜索: - + Enter pattern to filter 搜索游戏 @@ -6880,1146 +6942,1117 @@ Debug Message: 游戏列表模式 (&G) - + Game &Icon Size 游戏图标大小 (&I) - + Reset Window Size to &720p 重置窗口大小为720p (&7) - + Reset Window Size to 720p 重置窗口大小为720p - + Reset Window Size to &900p 重置窗口大小为900p (&9) - + Reset Window Size to 900p 重置窗口大小为900p - + Reset Window Size to &1080p 重置窗口大小为1080p (&1) - + Reset Window Size to 1080p 重置窗口大小为1080p - + &Multiplayer 多人游戏 (&M) - + &Tools 工具 (&T) - + Am&iibo Am&iibo - + Launch &Applet 启动小程序 (&A) - + &TAS TAS (&T) - + &Create Home Menu Shortcut 创建主页菜单快捷方式(&C) - + Install &Firmware 安装固件(&F) - + &Help 帮助 (&H) - + &Install Files to NAND... 安装文件到 NAND... (&I) - + L&oad File... 加载文件... (&O) - + Load &Folder... 加载文件夹... (&F) - + E&xit 退出 (&X) - - + + &Pause 暂停 (&P) - + &Stop 停止 (&S) - + &Verify Installed Contents 验证已安装内容的完整性 (&V) - + &About Eden 关于 Eden(&A) - + Single &Window Mode 单窗口模式 (&W) - + Con&figure... 设置... (&F) - + Ctrl+, Ctrl+, - + Enable Overlay Display Applet 开启覆盖层显示小程序 - + Show &Filter Bar 显示搜索栏 (&F) - + Show &Status Bar 显示状态栏 (&S) - + Show Status Bar 显示状态栏 - + &Browse Public Game Lobby 浏览公共游戏大厅 (&B) - + &Create Room 创建房间 (&C) - + &Leave Room 离开房间 (&L) - + &Direct Connect to Room 直接连接到房间 (&D) - + &Show Current Room 显示当前房间 (&S) - + F&ullscreen 全屏 (&U) - + &Restart 重新启动 (&R) - + Load/Remove &Amiibo... 加载/移除 Amiibo... (&A) - - &Report Compatibility - 报告兼容性 (&R) - - - + Open &Mods Page 打开 Mod 页面 (&M) - - Open &Quickstart Guide - 查看快速导航 (&Q) + + &User Handbook + 用户手册(&U) - - &FAQ - FAQ (&F) - - - + &Capture Screenshot 捕获截图 (&C) - + &Album 相册 (&A) - + &Set Nickname and Owner 设置昵称及所有者 (&S) - + &Delete Game Data 删除游戏数据 (&D) - + &Restore Amiibo 重置 Amiibo (&R) - + &Format Amiibo 格式化 Amiibo (&F) - + &Mii Editor Mii 编辑器 (&M) - + &Configure TAS... 配置 TAS... (&C) - + Configure C&urrent Game... 配置当前游戏... (&U) - - + + &Start 开始 (&S) - + &Reset 重置 (&R) - - + + R&ecord 录制 (&E) - + Open &Controller Menu 打开控制器菜单 (&C) - + Install Decryption &Keys 安装解密密钥(&K) - + &Home Menu 主页 (&H) - + &Desktop 桌面(&D) - + &Application Menu 应用程序菜单(&A) - + &Root Data Folder 根数据文件夹(&R) - + &NAND Folder &NAND 文件夹 - + &SDMC Folder &SDMC 文件夹 - + &Mod Folder &Mod 文件夹 - + &Log Folder &Log 文件夹 - + From Folder 从文件夹 - + From ZIP 从 ZIP - + &Eden Dependencies &Eden 依赖项 - + &Data Manager 数据管理器(&D) - + &Tree View 树景视图 (&T) - + &Grid View 网格视图 (&G) - + Game Icon Size 游戏图标大小 - - + + None 无 - + Show Game &Name 显示游戏名称 (&N) - + Show &Performance Overlay 显示性能覆盖层 - + + Post-Processing &Shaders... + 后处理效果着色器(&S)... + + + + &Carousel View + 轮播视图(&C) + + + Small (32x32) 小 (32x32) - + Standard (64x64) 标准 (64x64) - + Large (128x128) 大 (128x128) - + Full Size (256x256) 最大 (256x256) - + Broken Vulkan Installation Detected 检测到损坏的 Vulkan 安装 - + Vulkan initialization failed during boot. 在启动时初始化 Vulkan 失败。 - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping 运行游戏 - + Loading Web Applet... 正在加载 Web 小程序... - - + + Disable Web Applet 禁用 Web 小程序 - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) 禁用网页小程序可能会导致未定义的行为并且应仅在 超级马里奥 3D 全明星中使用。您确定要禁用网页小程序吗? (这可以在调试设置中重新启用。) - + The amount of shaders currently being built 当前正在构建的着色器数量 - + The current selected resolution scaling multiplier. 当前选择的分辨率缩放倍数。 - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. 当前模拟速度。高于或低于 100% 的数值表示模拟运行比 Switch 快或慢。 - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. 游戏当前显示的每秒帧数。这个数值会因游戏和场景的不同而有所变化。 - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. 模拟 Switch 一帧所需的时间,不包括帧限制或垂直同步。为了全速模拟这个时间最多应为 16.67 毫秒。 - + Unmute 取消静音 - + Mute 静音 - + Reset Volume 重置音量 - + + Configure Effects... + 配置效果... + + + &Clear Recent Files 清除最近的文件(&C) - + &Continue 继续(&C) - + Warning: Outdated Game Format 警告: 游戏格式过时 - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. 您正在为此游戏使用解包 ROM 目录格式,这是一种已过时的格式,它已被 NCA、NAX、XCI 或 NSP 等其他格式取代。解包 ROM 目录缺少图标、元数据和更新支持。<br>有关 Eden 支持的各种 Switch 格式的说明请查阅我们的用户手册。此消息将不再显示。 - - + + Error while loading ROM! 加载 ROM 时出错! - + The ROM format is not supported. 不支持该 ROM 格式。 - + An error occurred initializing the video core. 初始化视频核心时发生错误。 - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. - Eden 在运行视频核心时遇到了错误。通常这是由于 GPU 驱动程序过时引起的,包括集成显卡驱动程序。有关详细信息,请查看日志。有关如何访问日志的更多信息,请参阅以下页面:<a href="https://yuzu-mirror.github.io/help/reference/log-files/">如何上传日志文件</a>。 + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. + 这通常是因为过时的 GPU 驱动所导致的。具体情况请查看日志。请查阅: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>如何访问日志文件</a>。 - + Error while loading ROM! %1 %1 signifies a numeric error code. 加载 ROM 时出错! %1 - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. %1<br>请重新导出您的文件,或在 Discord/Stoat 上寻求帮助。 - + An unknown error occurred. Please see the log for more details. 发生未知错误。请查看日志以获取更多详情。 - + + (64-bit) (64 位) - + + (32-bit) (32 位) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit %1 %2 - + Closing software... 正在关闭软件... - + Save Data 存档数据 - + Mod Data Mod 数据 - + Error Opening %1 Folder 打开 %1 文件夹出错 - - + + Folder does not exist! 文件夹不存在! - + Remove Installed Game Contents? 是否移除已安装的游戏内容? - + Remove Installed Game Update? 是否移除已安装的游戏更新? - + Remove Installed Game DLC? 是否移除已安装的游戏 DLC? - + Remove Entry 删除条目 - + Delete OpenGL Transferable Shader Cache? 要删除 OpenGL 可传输着色器缓存吗? - + Delete Vulkan Transferable Shader Cache? 要删除 Vulkan 可传输着色器缓存吗? - + Delete All Transferable Shader Caches? 删除所有可传输的着色器缓存? - + Remove Custom Game Configuration? 是否移除自定义游戏配置? - + Remove Cache Storage? 要清除缓存存储吗? - + Remove File 删除文件 - + Remove Play Time Data 删除游戏时间数据 - + Reset play time? 要重置播放时间吗? - - + + RomFS Extraction Failed! RomFS 提取失败! - + There was an error copying the RomFS files or the user cancelled the operation. 复制 RomFS 文件时出错或用户取消了操作。 - + Full 完整 - + Skeleton 结构 - + Select RomFS Dump Mode 选择 RomFS 转储模式 - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. 请选择您希望如何导出 RomFS。<br>&quot;完整&quot; 将把所有文件复制到新的目录中,而<br>&quot;结构&quot; 仅会创建目录结构。</br></br> - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root %1 的可用空间不足,无法提取 RomFS。请释放空间或在模拟 > 配置 > 系统 > 文件系统 > 转储根目录,中选择其它目录 - + Extracting RomFS... 正在提取 RomFS... - - + + Cancel 取消 - + RomFS Extraction Succeeded! RomFS 提取成功! - + The operation completed successfully. 操作已成功完成。 - + Error Opening %1 打开 %1 时出错 - + Select Directory 选择目录 - + Properties 属性 - + The game properties could not be loaded. 无法加载游戏属性。 - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. Switch 可执行文件 (%1);;所有文件 (*.*) - + Load File 加载文件 - + Open Extracted ROM Directory 打开已提取的 ROM 目录 - + Invalid Directory Selected 选择的目录无效 - + The directory you have selected does not contain a 'main' file. 您选择的目录不包含 'main' 文件。 - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) 可安装的 Switch 文件 (*.nca *.nsp *.xci);;任天堂内容档案 (*.nca);;任天堂提交包 (*.nsp);;NX 卡带镜像 (*.xci) - + Install Files 安装文件 - + %n file(s) remaining 剩余 %n 个文件 - + Installing file "%1"... 正在安装文件 "%1"... - - + + Install Results 安装结果 - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. 为了避免可能的冲突,我们不鼓励用户将基础游戏安装到 NAND。 请仅使用此功能来安装更新和 DLC。 - + %n file(s) were newly installed 已新安装 %n 个文件 - + %n file(s) were overwritten 已覆盖了 %n 个文件 - + %n file(s) failed to install %n 个文件安装失败 - + System Application 系统应用 - + System Archive 系统档案 - + System Application Update 系统应用更新 - + Firmware Package (Type A) 固件包 (类型 A) - + Firmware Package (Type B) 固件包 (类型 B) - + Game 游戏 - + Game Update 游戏更新 - + Game DLC 游戏可下载内容 - + Delta Title Delta 标题 - + Select NCA Install Type... 选择 NCA 安装类型... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) 请选择您希望将此 NCA 安装为的标题类型: (在大多数情况下,默认的 '游戏' 就可以。) - + Failed to Install 安装失败 - + The title type you selected for the NCA is invalid. 您为 NCA 选择的标题类型无效。 - + File not found 找不到文件 - + File "%1" not found 未找到文件 "%1" - + OK 确定 - + Function Disabled 功能已被关闭 - + Compatibility list reporting is currently disabled. Check back later! 兼容性列表报告目前已被禁用。请稍后再查看! - + Error opening URL 打开网址出错 - + Unable to open the URL "%1". 无法打开 URL "%1"。 - + TAS Recording TAS 录像 - + Overwrite file of player 1? 要覆盖玩家 1 的文件吗? - + Invalid config detected 检测到无效配置 - + Handheld controller can't be used on docked mode. Pro controller will be selected. 手柄在主机模式下无法使用。将选择 Pro 手柄。 - - + + Amiibo Amiibo - - + + The current amiibo has been removed 当前的 amiibo 已被移除 - + Error 错误 - - + + The current game is not looking for amiibos 当前游戏不支持寻找 amiibo - + Amiibo File (%1);; All Files (*.*) Amiibo 文件 (%1);; 所有文件 (*.*) - + Load Amiibo 读取 Amiibo - + Error loading Amiibo data 加载 Amiibo 数据出错 - + The selected file is not a valid amiibo 所选文件不是有效的 amiibo - + The selected file is already on use 所选文件正在使用中 - + An unknown error occurred 发生未知错误 - - - Keys not installed - 未安装密钥 - - - - - Install decryption keys and restart Eden before attempting to install firmware. - 在尝试安装固件之前请先安装解密密钥并重启 Eden。 - - - - Select Dumped Firmware Source Location - 选择已转储固件源位置 - - - - Select Dumped Firmware ZIP - 选择已转储的固件 ZIP - - - - Zipped Archives (*.zip) - 压缩文件 (*.zip) - - - - Firmware cleanup failed - 固件清理失败 - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - 清理提取的固件缓存失败。 -检查系统临时目录的写入权限然后重试。 -操作系统报告错误: %1 - - - + No firmware available 没有可用的固件 - + Firmware Corrupted 固件已损坏 - + Unknown applet 未知小程序 - + Applet doesn't map to a known value. 无法识别该小程序对应的值。 - + Record not found 找不到记录程序 - + Applet not found. Please reinstall firmware. 找不到小程序。请重新安装固件。 - + Capture Screenshot 截取屏幕截图 - + PNG Image (*.png) PNG 图像 (*.png) - + TAS state: Running %1/%2 TAS 状态: 正在运行 %1/%2 - + TAS state: Recording %1 TAS 状态: 正在录制 %1 - + TAS state: Idle %1/%2 TAS 状态: 空闲 %1/%2 - + TAS State: Invalid TAS 状态: 无效 - + &Stop Running 停止运行(&S) - + Stop R&ecording 停止录制(&A) - + Building: %n shader(s) 正在编译:%n 个着色器 - + Scale: %1x %1 is the resolution scaling factor 缩放: %1x - + Speed: %1% / %2% 速度: %1% / %2% - + Speed: %1% 速度: %1% - + Game: %1 FPS 游戏: %1 FPS - + Frame: %1 ms 帧: %1 ms - - - FSR - FSR - - - + NO AA 无 AA - + + NO FX + 无 FX + + + + FX + FX + + + VOLUME: MUTE 音量: 静音 - + VOLUME: %1% Volume percentage (e.g. 50%) 音量: %1% - + Derivation Components Missing 缺少派生组件 - + Decryption keys are missing. Install them now? 缺少解密密钥。现在安装吗? - + Wayland Detected! 检测到 Wayland! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -8030,74 +8063,74 @@ Would you like to force it for future launches? 您想要在未来的启动中强制执行吗? - + Use X11 使用 X11 - + Continue with Wayland 继续使用 Wayland - + Don't show again 不再显示 - + Restart Required 需要重新启动 - + Restart Eden to apply the X11 backend. 重新启动 Eden 以应用 X11 后端。 - + Slow 慢速 - + Turbo 加速 - + Unlocked 解锁 - + Select RomFS Dump Target 选择 RomFS 转储目标 - + Please select which RomFS you would like to dump. 请选择您想要转储的 RomFS。 - + Are you sure you want to close Eden? 您确实要关闭 Eden 吗? - - - + + + Eden Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. 您确定要停止模拟吗?任何未保存的进度将会丢失。 - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8165,6 +8198,11 @@ Would you like to bypass this and exit anyway? ScaleForce ScaleForce + + + FSR + FSR + Area @@ -8176,52 +8214,57 @@ Would you like to bypass this and exit anyway? MMPX - + + SGSR + SGSR + + + + SGSR EdgeDir + SGSR EdgeDir + + + Docked 主机模式 - + Handheld 掌机模式 - + Fast 加速 - - Balanced - 平衡 - - - + Accurate 精确 - + Vulkan Vulkan - + OpenGL GLSL OpenGL GLSL - + OpenGL SPIRV OpenGL SPIRV - + OpenGL GLASM OpenGL GLASM - + Null 空 @@ -8264,7 +8307,7 @@ If you wish to clean up the files which were left in the old data location, you %1 - + Data was migrated successfully. 数据已成功迁移。 @@ -8757,8 +8800,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8771,10 +8814,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8789,9 +8832,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8953,50 +8996,50 @@ p, li { white-space: pre-wrap; } [未指定] - + %1%2 %1%2 - + [invalid] [无效] - - + + %1%2Hat %3 %1%2方向键 %3 - - - + + + %1%2Axis %3 %1%2轴 %3 - + %1%2Axis %3,%4,%5 %1%2轴 %3,%4,%5 - + %1%2Motion %3 %1%2体感 %3 - - + + %1%2Button %3 %1%2按键 %3 - + [unused] [未使用] @@ -9122,47 +9165,47 @@ p, li { white-space: pre-wrap; } %1 正在玩 %2 - + Play Time: %1 游玩时间:%1 - + Never Played 未曾游玩 - + Version: %1 版本:%1 - + Version: 1.0.0 版本:1.0.0 - + Installed SD Titles SD 卡中安装的项目 - + Installed NAND Titles NAND 中安装的项目 - + System Titles 系统项目 - + Add New Game Directory 添加游戏目录 - + Favorites 收藏 @@ -9283,14 +9326,14 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware 游戏需要固件 - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. - 您正尝试启动的游戏需要固件才能启动或通过启动画面打开菜单。请 <a href='https://yuzu-mirror.github.io/help/quickstart'>转储并安装固件</a>, 或点击 "确定" 继续启动。 + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. + 这个游戏可能需要固件才能正常运行,但您还没有安装任何固件。请在启动前安装固件,或者按 “确定” 继续启动。 @@ -9299,10 +9342,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel 取消 @@ -9323,7 +9366,7 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 @@ -9332,207 +9375,242 @@ p, li { white-space: pre-wrap; } %1 - - + + Verifying integrity... 正在验证完整性... - - + + Integrity verification succeeded! 完整性验证成功! - - + + The operation completed successfully. 操作成功完成。 - - + + Integrity verification failed! 完整性验证失败! - + File contents may be corrupt or missing. 文件内容可能缺失或已损坏。 - + Integrity verification couldn't be performed 无法执行完整性验证 - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. 固件安装失败。固件可能处于异常状态或已损坏,无法验证文件内容的有效性。 - + Select Dumped Keys Location 选择导出的密钥文件位置 - + Decryption Keys install succeeded 密钥文件安装成功 - + Decryption Keys install failed 密钥文件安装失败 - + Orphaned Profiles Detected! 检测到孤立的配置文件! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> 如果您不阅读此内容,可能会发生意想不到的糟糕情况!<br>Eden 检测到以下存档目录没有附加的配置文件:<br>%1<br><br>下列配置是有效的:<br>%2<br><br>点击“确定”以打开您的存档文件夹并修复配置文件。<br>提示: 将最大或最近修改的文件夹内容复制到其他地方,删除所有孤立的配置文件,然后将复制的内容移到正确的配置文件中。<br><br>还是感到疑惑? 请查看 <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>帮助页</a>。<br> - + Really clear data? 确实要清除数据吗? - + Important data may be lost! 可能会丢失重要的数据! - + Are you REALLY sure? 您真的确定吗? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. 在删除后,您将无法找回数据! 仅在您 100% 确认要删除此数据时才这样做。 - + Clearing... 正在清除... - + Select Export Location 选择导出位置 - + %1.zip %1.zip - - + + + Zipped Archives (*.zip) 压缩档案 (*.zip) - + Exporting data. This may take a while... 正在导出数据。这可能需要一些时间... - + Exporting 正在导出 - + Exported Successfully 导出成功 - + Data was exported successfully. 数据已成功导出。 - + Export Cancelled 导出已被取消 - + Export was cancelled by the user. 导出已被用户取消。 - + Export Failed 导出失败 - + Ensure you have write permissions on the targeted directory and try again. 请确认您是否具有目标目录的写入权限然后再次尝试。 - + Select Import Location 选择导入位置 - + Import Warning 导入警告 - + All previous data in this directory will be deleted. Are you sure you wish to proceed? 此目录中的所有先前数据将被删除。您确定要继续吗? - + Importing data. This may take a while... 正在导入数据。这需要一些时间... - + Importing 正在导入 - + Imported Successfully 导入成功 - + Data was imported successfully. 数据已导入成功。 - + Import Cancelled 导入已被取消 - + Import was cancelled by the user. 导入已被用户取消。 - + Import Failed 导入失败 - + Ensure you have read permissions on the targeted directory and try again. 请确认是否您具有目标目录的读取权限然后再次尝试。 + + + Keys not installed + 未安装密钥 + + + + Install decryption keys and restart Eden before attempting to install firmware. + 在尝试安装固件之前先安装解密密钥并重启 Eden。 + + + + Select Dumped Firmware Source Location + 选择已转储的固件源位置 + + + + Select Dumped Firmware ZIP + 选择已转储的固件 ZIP + + + + Firmware cleanup failed + 清理固件失败 + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + 清理提取的固件缓存失败。 +请检查系统临时目录的写入权限然后重试。 +OS 报告的错误: %1 + QtCommon::FS @@ -9779,72 +9857,72 @@ Would you like to manually select a portable folder to use? 缓存数据删除失败。它可能不存在或正在被使用。 - + Create Shortcut 创建快捷方式 - + Do you want to launch the game in fullscreen? 您想以全屏模式启动游戏吗? - + Shortcut Created 已创建快捷方式 - + Successfully created a shortcut to %1 %1 的快捷方式创建成功 - + Shortcut may be Volatile! 快捷方式可能不稳定! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? 这回创建到当前 AppImage 的快捷方式。它可能会在您更新后失效。要继续吗? - + Failed to Create Shortcut 创建快捷方式失败 - + Failed to create a shortcut to %1 %1 的快捷方式创建失败 - + Create Icon 创建图标 - + Cannot create icon file. Path "%1" does not exist and cannot be created. 无法创建图标文件。路径“ %1 ”不存在且无法被创建。 - + No firmware available 无可用固件 - + Please install firmware to use the home menu. 请先安装固件才能使用主页菜单。 - + Home Menu Applet 主页菜单小程序 - + Home Menu is not available. Please reinstall firmware. 主页菜单不可用。请重新安装固件。 @@ -9852,37 +9930,37 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name 模组名称 - + What should this mod be called? 这个模组应该叫什么名字? - + RomFS RomFS - + ExeFS/Patch ExeFS/Patch - + Cheat 作弊 - + Mod Type 模组类型 - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. @@ -9891,18 +9969,18 @@ Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed 模组提取失败 - + Failed to create temporary directory %1 未能创建临时 %1 目录 - + Zip file %1 is empty 压缩文件 %1 是空的。 @@ -10604,66 +10682,66 @@ By selecting "From Eden", previous save data stored in Ryujinx will be %1 可用于下载。 - + New Version Location 新版本位置 - + All Files (*.*) 所有文件 (*.*) - - - + + + Failed to save file 保存文件失败 - + Could not open file %1 for writing. 无法打开要写入的 %1 文件。 - + Downloading... 正在下载... - + Cancel 取消 - + Could not write to file %1. 无法写入到文件 %1。 - + Could not commit to file %1. 无法提交到文件 %1。 - + Failed to download file 下载文件失败 - + Could not download from %1%2 Error code: %3 无法从 %1%2 下载 错误代码: %3 - + Download Complete 下载完成 - + Successfully downloaded %1. Would you like to open it? 已成功下载 %1。您要打开它吗? diff --git a/dist/languages/zh_TW.ts b/dist/languages/zh_TW.ts index b09469591b..463d7fa2ed 100644 --- a/dist/languages/zh_TW.ts +++ b/dist/languages/zh_TW.ts @@ -218,155 +218,6 @@ This would ban both their forum username and their IP address. %1 - %2 (%3/%4 個成員) - 已連線 - - CompatDB - - - Report Compatibility - 回報相容性 - - - - - - - - - - Report Game Compatibility - 回報遊戲相容性 - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - <html><head/><body><span style=" font-size:10pt;">若您選擇要傳送測試範本到</span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">Eden相容性清單</span></a><span style=" font-size:10pt;">,以下的資訊將會被收集而且會顯示在Eden相容性清單的網站上:</span><p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">硬體資訊(CPU/GPU/作業系統)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">您正在使用的Eden版本</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">已連接的Eden帳號</li></ul></body></html> - - - - <html><head/><body><p>Does the game boot?</p></body></html> - <html><head/><body><p>遊戲啟動了嗎?</p></body></html> - - - - Yes The game starts to output video or audio - 是,遊戲開始輸出影像或音訊 - - - - No The game doesn't get past the "Launching..." screen - 否,遊戲無法通過“啟動中…”畫面 - - - - Yes The game gets past the intro/menu and into gameplay - 是的,遊戲可以通過開場動畫/主選單頁面並且可進行遊戲 - - - - No The game crashes or freezes while loading or using the menu - 不,加載或使用選單時遊戲崩潰或卡死 - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - <html><head/><body><p>是否可以進行遊戲?</p></body></html> - - - - Yes The game works without crashes - 是,遊戲正常運作,沒有崩潰 - - - - No The game crashes or freezes during gameplay - 否,遊玩過程中會崩潰或卡死 - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - <html><head/><body><p>遊戲在遊玩時有沒有崩潰、卡死或當機?</p></body></html> - - - - Yes The game can be finished without any workarounds - 沒有,可以順利地完成整個遊戲過程 - - - - No The game can't progress past a certain area - 有,遊戲在進行到特定區域後無法繼續遊玩 - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - <html><head/><body><p>遊戲從頭到尾都可以順利運行嗎?</p></body></html> - - - - Major The game has major graphical errors - 嚴重 遊戲在運行時有嚴重的圖形錯誤 - - - - Minor The game has minor graphical errors - 輕微 遊戲在運行時有輕微的圖形錯誤 - - - - None Everything is rendered as it looks on the Nintendo Switch - 完美 媲美實機的遊戲體驗 - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - <html><head/><body><p>遊戲運行時有出現圖形問題嗎?</p></body></html> - - - - Major The game has major audio errors - 嚴重 遊戲運行時出現嚴重的音訊錯誤 - - - - Minor The game has minor audio errors - 輕微 遊戲運行時出現輕微的音訊錯誤 - - - - None Audio is played perfectly - 完美 遊戲運行時音訊未出現任何問題 - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - <html><head/><body><p>遊戲是否出現音訊問題/特效消失?</p></body></html> - - - - Thank you for your submission! - 感謝您的回報! - - - - Submitting - 上傳中 - - - - Communication error - 連線錯誤 - - - - An error occurred while sending the Testcase - 在上傳測試結果時發生錯誤 - - - - Next - 下一步 - - ConfigurationShared @@ -570,39 +421,38 @@ Can help reduce stuttering at lower framerates. - + Backend: 後端: - CPU Overclock + CPU Clocks - Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. -Use Boost (1700MHz) to run at the Switch's highest native clock, or Fast (2000MHz) to run at 2x clock. - 強制模擬 CPU 以更高的時脈運行,減少某些 FPS 限制。較弱的 CPU 可能會看到效能下降,特定遊戲可能會出現問題。 -使用 Boost (1700MHz) 以 Switch 的最高原生時脈運行,或 Fast (2000MHz) 以雙倍時脈運行。 + Raises the clock the emulated CPU reports, which removes some FPS limiters. +Weaker CPUs may see reduced performance, and certain games may behave improperly. + - + Custom CPU Ticks 自訂CPU時脈 - + Set a custom value of CPU ticks. Higher values can increase performance, but may cause deadlocks. A range of 77-21000 is recommended. 自訂CPU時脈。更高的值可能提高效能,但也可能導致遊戲卡死。建議範圍為77-21000。 - + Enable Host MMU Emulation (fastmem) 啟用主機 MMU 模擬(fastmem) - + This optimization speeds up memory accesses by the guest program. Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU. Disabling this forces all memory accesses to use Software MMU Emulation. @@ -611,100 +461,100 @@ Disabling this forces all memory accesses to use Software MMU Emulation. - + Unfuse FMA (improve performance on CPUs without FMA) 解除融合FMA(能讓不支援 FMA 指令集的 CPU 提高效能) - + This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support. 透過降低積和熔加運算的準確度來提高模擬器在不支援FMA指令集CPU上的運行速度 - + Faster FRSQRTE and FRECPE 更快的 FRSQRTE 和 FRECPE - + This option improves the speed of some approximate floating-point functions by using less accurate native approximations. 透過使用準確度較低的近似值來提高某些浮點函數的運算速度 - + Faster ASIMD instructions (32 bits only) 快速 ASIMD 指令(僅限 32 位元) - + This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes. 透過使用不準確的捨入模式來提高32位元ASIMD浮點函數的運算速度 - + Inaccurate NaN handling 低準確度NaN處理 - + This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions. 透過取消NaN檢查來提高速度。 請注意,啟用後會降低些浮點指令的準確度 - + Disable address space checks 停用位址空間檢查 - + This option improves speed by eliminating a safety check before every memory operation. Disabling it may allow arbitrary code execution. 透過省略在每次記憶體操作前執行的安全檢查來提高速度 停用此功能可能會允許任意程式碼執行 - + Ignore global monitor 忽略全局監視器 - + This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions. 透過僅依賴cmpxchg指令來確保獨佔存取指令的安全性並提高速度。 請注意,這可能會導致死鎖或其它問題 - + API: API: - + Changes the output graphics API. Vulkan is recommended. 更改使用的圖形API 推薦使用Vulkan - + Device: 裝置: - + This setting selects the GPU to use (Vulkan only). 選擇要使用的GPU(僅限Vulkan) - + Resolution: 解析度: - + Forces to render at a different resolution. Higher resolutions require more VRAM and bandwidth. Options lower than 1X can cause artifacts. @@ -713,27 +563,27 @@ Options lower than 1X can cause artifacts. 選擇低於1X的解析度可能會導致畫面異常 - + Window Adapting Filter: 視窗適應過濾器: - + FSR Sharpness: FSR 銳化度: - - Determines how sharpened the image will look using FSR's dynamic contrast. - 調整使用FSR的動態對比時影像的銳利度 + + Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast. + - + Anti-Aliasing Method: 抗鋸齒: - + The anti-aliasing method to use. SMAA offers the best quality. FXAA can produce a more stable picture in lower resolutions. @@ -741,13 +591,23 @@ FXAA can produce a more stable picture in lower resolutions. SMAA的品質最佳 FXAA在低解析度下可以產生較穩定的畫面 + + + Enable post-processing effects + + + Applies post-processing effects to the final image. + + + + Fullscreen Mode: 全螢幕模式: - + The method used to render the window in fullscreen. Borderless offers the best compatibility with the on-screen keyboard that some games request for input. Exclusive fullscreen may offer better performance and better Freesync/Gsync support. @@ -756,47 +616,47 @@ Exclusive fullscreen may offer better performance and better Freesync/Gsync supp 獨佔全螢幕將提供更好的性能以及更佳的FreeSync/G-Sync支援 - + Aspect Ratio: 長寬比: - + Stretches the renderer to fit the specified aspect ratio. Most games only support 16:9, so modifications are required to get other ratios. Also controls the aspect ratio of captured screenshots. - + Use persistent pipeline cache - + Allows saving shaders to storage for faster loading on following game boots. Disabling it is only intended for debugging. 將產生的著色器快取儲存至硬碟,讓遊戲在後續過程中不需再次產生以提高速度 建議僅在偵錯時才停用此選項 - + Use asynchronous GPU emulation - + Uses an extra CPU thread for rendering. This option should always remain enabled. - + NVDEC emulation: NVDEC 模擬: - + Specifies how videos should be decoded. It can either use the CPU or the GPU for decoding, or perform no decoding at all (black screen on videos). In most cases, GPU decoding provides the best performance. @@ -805,12 +665,12 @@ In most cases, GPU decoding provides the best performance. GPU解碼在大多數情況下提供最好的性能 - + ASTC Decoding Method: ASTC解碼方式: - + This option controls how ASTC textures should be decoded. CPU: Use the CPU for decoding. GPU: Use the GPU's compute shaders to decode ASTC textures (recommended). @@ -819,55 +679,75 @@ stuttering but may present artifacts. - + ASTC Recompression Method: ASTC重新壓縮方式: - + Most GPUs lack support for ASTC textures and must decompress to anintermediate format: RGBA8. BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format, saving VRAM but degrading image quality. - + Frame Pacing Mode (Vulkan only) - + Controls how the emulator manages frame pacing to reduce stuttering and make the frame rate smoother and more consistent. - + VRAM Usage Mode: VRAM 使用模式: - + Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance. Aggressive mode may impact performance of other applications such as recording software. - + Skip CPU Inner Invalidation 跳過CPU內部失效處理 - + Skips certain cache invalidations during memory updates, reducing CPU usage and improving latency. This may cause soft-crashes. - + + GPU Readback Before NCE Invalidation + + + + + Flushes GPU buffer data back to guest memory before NCE invalidates the whole guest page, preserving it while the CPU store proceeds. + + + + + Patch Runtime NROs for NCE + + + + + Applies NCE code patches to runtime-loaded NRO modules.This is required for some games to run correctly. + + + + VSync Mode: 垂直同步: - + FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate. FIFO Relaxed allows tearing as it recovers from a slow down. Mailbox can have lower latency than FIFO and does not tear but may drop frames. @@ -875,1362 +755,1372 @@ Immediate (no synchronization) presents whatever is available and can exhibit te - + Sync Memory Operations 同步記憶體操作 - + Ensures data consistency between compute and memory operations. This option fixes issues in games, but may degrade performance. Unreal Engine 4 games often see the most significant changes thereof. - + Enable asynchronous presentation (Vulkan only) 啟用非同步顯示(僅限Vulkan) - + Slightly improves performance by moving presentation to a separate CPU thread. 透過將畫面顯示移至獨立的CPU執行緒來略微提升性能。 - + Force maximum clocks (Vulkan only) 強制使用最大時脈(僅限Vulkan) - + Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed. 在等待圖形命令時於背景執行任務以防GPU降低時脈 - + Anisotropic Filtering: 各向異性過濾: - + Controls the quality of texture rendering at oblique angles. Safe to set at 16x on most GPUs. - + GPU Mode: - + Controls the GPU emulation mode. -Most games render fine with Fast or Balanced modes, but Accurate is still required for some. +Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. - + DMA Accuracy: - - Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance. + + Controls the DMA read mode. +Unsafe is faster, while Safe is more stable and can fix issues in some games. +Default follows the GPU Accuracy setting. - + + GPU Fence Behavior: + + + + + Controls the GPU fence synchronization behavior. +Immediate is the fastest option, but can introduce some issues. +Balanced offers better compatibility and may fix issues in some games. +Accurate further improves compatibility at the cost of some performance. +Default follows the GPU Mode setting. + + + + Enable asynchronous shader compilation - + May reduce shader stutter. - - Fast GPU Time + + GPU Clocks - - Overclocks the emulated GPU to increase dynamic resolution and render distance. -Use 256 for maximal performance and 512 for maximal graphics fidelity. + + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch's clocks. - + GPU Unswizzle - + Accelerates BCn 3D texture decoding using GPU compute. Disable if experiencing crashes or graphical glitches. - + GPU Unswizzle Max Texture Size - + Sets the maximum size (MiB) for GPU-based texture unswizzling. While the GPU is faster for medium and large textures, the CPU may be more efficient for very small ones. Adjust this to find the balance between GPU acceleration and CPU overhead. - + GPU Unswizzle Stream Size - + Sets the maximum amount of texture data (in MiB) processed per frame. Higher values can reduce stutter during texture loading but may impact frame consistency. - + GPU Unswizzle Chunk Size - + Determines the number of depth slices processed in a single dispatch. Increasing this can improve throughput on high-end GPUs but may cause TDR or driver timeouts on weaker hardware. - + Use Vulkan pipeline cache 启用 Vulkan 管线缓存 - + Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally. 启用 GPU 供应商专用的管线缓存。 在 Vulkan 驱动程序内部不存储管线缓存的情况下,此选项可显著提高着色器加载速度。 - + Enable Compute Pipelines (Intel Vulkan Only) 启用计算管线 (仅限 Intel 显卡 Vulkan 模式) - + Required by some games. This setting only exists for Intel proprietary drivers and may crash if enabled. Compute pipelines are always enabled on all other drivers. - + Enable Reactive Flushing 启用反应性刷新 - + Uses reactive flushing instead of predictive flushing, allowing more accurate memory syncing. 使用反应性刷新取代预测性刷新,从而更精确地同步内存。 - + Sync to framerate of video playback 播放视频时帧率同步 - + Run the game at normal speed during video playback, even when the framerate is unlocked. 在视频播放期间以正常速度运行游戏,即使帧率未锁定。 - + Barrier feedback loops 屏障反馈循环 - + Improves rendering of transparency effects in specific games. 改进某些游戏中透明效果的渲染。 - + Enable buffer history - + Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. - + Fix bloom effects - + Removes bloom in Burnout. - - Enable Legacy Rescale Pass - - - - - May fix rescale issues in some games by relying on behavior from the previous implementation. -Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and grey texture flicker on Nvidia GPUs in Luigis Mansion 3. - - - - + Extended Dynamic State - + Controls the number of features that can be used in Extended Dynamic State. Higher states allow for more features and can increase performance, but may cause additional graphical issues. - + Vertex Input Dynamic State - + Enables vertex input dynamic state feature for better quality and performance. - + Sample Shading - + Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance. - + RNG Seed 隨機種子 - + Controls the seed of the random number generator. Mainly used for speedrunning. - + Device Name 裝置名稱 - + The name of the console. - + + Homebrew Args + + + + + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + + + + Custom RTC Date: 自定义系统时间: - + This option allows to change the clock of the console. Can be used to manipulate time in games. - + The number of seconds from the current unix time - + Language: 语言: - + This option can be overridden when region setting is auto-select - + Region: 區域: - + The region of the console. - + Time Zone: 時區: - + The time zone of the console. - + Sound Output Mode: 音訊輸出模式: - + Console Mode: 控制台模式: - + Selects if the console is in Docked or Handheld mode. Games will change their resolution, details and supported controllers and depending on this setting. Setting to Handheld can help improve performance for low end systems. - + Prompt for user profile on boot - + Useful if multiple people use the same PC. - + Pause when not in focus - + Pauses emulation when focusing on other windows. - + Confirm before stopping emulation 停止模拟时需要确认 - + Overrides prompts asking to confirm stopping the emulation. Enabling it bypasses such prompts and directly exits the emulation. - + Hide mouse on inactivity 滑鼠閒置時自動隱藏 - + Hides the mouse after 2.5s of inactivity. - - Disable controller applet - 禁用控制器程序 - - - - Forcibly disables the use of the controller applet in emulated programs. -When a program attempts to open the controller applet, it is immediately closed. - - - - + Check for updates - + Whether or not to check for updates upon startup. - + Enable Gamemode 启用游戏模式 - + Force X11 as Graphics Backend - + Custom frontend 自定义前端 - + Real applet 真实的小程序 - + + + Disabled + + + + Never - + On Load - + Always - + CPU CPU - + GPU GPU - + CPU Asynchronous CPU 异步模拟 - + Uncompressed (Best quality) 不壓縮 (最高品質) - + BC1 (Low quality) BC1 (低品質) - + BC3 (Medium quality) BC3 (中品質) - - + + Auto 自動 - + 30 FPS - + 60 FPS - + 90 FPS - + 120 FPS - + Conservative 保守模式(节省 VRAM) - + Aggressive 激进模式 - + Vulkan Vulkan - + OpenGL GLSL - + OpenGL GLASM (Assembly Shaders, NVIDIA Only) - + OpenGL SPIR-V (Experimental, AMD/Mesa Only) - + Null 無 - + Fast - - Balanced - - - - - + + + Accurate 高精度 - - + + + Default 預設 - + Unsafe (fast) - + Safe (stable) - + + Immediate + + + + + Balanced + + + + Unsafe 低精度 - + Paranoid (disables most optimizations) 偏执模式 (禁用绝大多数优化项) - + Debugging - + Dynarmic Dynarmic - + NCE NCE - + Borderless Windowed 無邊框視窗 - + Exclusive Fullscreen 全螢幕獨占 - + No Video Output 無視訊輸出 - + CPU Video Decoding CPU 視訊解碼 - + GPU Video Decoding (Default) GPU 視訊解碼(預設) - + 0.25X (180p/270p) [EXPERIMENTAL] - + 0.5X (360p/540p) [EXPERIMENTAL] 0.5X (360p/540p) [实验性] - + 0.75X (540p/810p) [EXPERIMENTAL] 0.75X (540p/810p) [實驗性] - + 1X (720p/1080p) 1X (720p/1080p) - + 1.25X (900p/1350p) [EXPERIMENTAL] - + 1.5X (1080p/1620p) [EXPERIMENTAL] 1.5X (1080p/1620p) [實驗性] - + 2X (1440p/2160p) 2X (1440p/2160p) - + 3X (2160p/3240p) 3X (2160p/3240p) - + 4X (2880p/4320p) 4X (2880p/4320p) - + 5X (3600p/5400p) 5X (3600p/5400p) - + 6X (4320p/6480p) 6X (4320p/6480p) - + 7X (5040p/7560p) 7X (5040p/7560p) - + 8X (5760p/8640p) 8X (5760p/8640p) - + Nearest Neighbor 最近鄰 - + Bilinear 雙線性 - + Bicubic 雙立方 - + Gaussian 高斯 - + Lanczos - + ScaleForce 強制縮放 - + AMD FidelityFX Super Resolution - + Area - + MMPX - + Zero-Tangent - + B-Spline - + Mitchell - + Spline-1 - - + + Snapdragon Game Super Resolution + + + + + Snapdragon Game Super Resolution EdgeDir + + + + None 無 - + FXAA FXAA - + SMAA SMAA - + Default (16:9) 預設 (16:9) - + Force 4:3 強制 4:3 - + Force 21:9 強制 21:9 - + Force 16:10 強制 16:10 - + Stretch to Window 延伸視窗 - + Automatic 自動 - + 2x 2x - + 4x 4x - + 8x 8x - + 16x 16x - - 32x - - - - - 64x - - - - + Japanese (日本語) 日文 (日本語) - + American English 美式英语 - + French (français) 法文 (français) - + German (Deutsch) 德文 (Deutsch) - + Italian (italiano) 義大利文 (italiano) - + Spanish (español) 西班牙文 (español) - + Chinese 中文 - + Korean (한국어) 韓文 (한국어) - + Dutch (Nederlands) 荷蘭文 (Nederlands) - + Portuguese (português) 葡萄牙文 (português) - + Russian (Русский) 俄文 (Русский) - + Taiwanese 台灣中文 - + British English 英式英文 - + Canadian French 加拿大法文 - + Latin American Spanish 拉丁美洲西班牙文 - + Simplified Chinese 簡體中文 - + Traditional Chinese (正體中文) 正體中文 (正體中文) - + Brazilian Portuguese (português do Brasil) 巴西-葡萄牙語 (português do Brasil) - + Polish (polska) - + Thai (แบบไทย) - - + + Japan 日本 - + USA 美國 - + Europe 歐洲 - + Australia 澳洲 - + China 中國 - + Korea 南韓 - + Taiwan 台灣 - + Auto (%1) Auto select time zone 自動 (%1) - + Default (%1) Default time zone 預設 (%1) - + CET 中歐 - + CST6CDT CST6CDT - + Cuba 古巴 - + EET EET - + Egypt 埃及 - + Eire 愛爾蘭 - + EST 北美東部 - + EST5EDT EST5EDT - + GB GB - + GB-Eire 英國-愛爾蘭 - + GMT GMT - + GMT+0 GMT+0 - + GMT-0 GMT-0 - + GMT0 GMT0 - + Greenwich 格林威治 - + Hongkong 香港 - + HST 夏威夷 - + Iceland 冰島 - + Iran 伊朗 - + Israel 以色列 - + Jamaica 牙買加 - + Kwajalein 瓜加林環礁 - + Libya 利比亞 - + MET 中歐 - + MST 北美山區 - + MST7MDT MST7MDT - + Navajo 納瓦霍 - + NZ 紐西蘭 - + NZ-CHAT 紐西蘭-查塔姆群島 - + Poland 波蘭 - + Portugal 葡萄牙 - + PRC 中國 - + PST8PDT 太平洋 - + ROC 臺灣 - + ROK 韓國 - + Singapore 新加坡 - + Turkey 土耳其 - + UCT UCT - + Universal 世界 - + UTC UTC - + W-SU 莫斯科 - + WET 西歐 - + Zulu 協調世界時 - + Mono 單聲道 - + Stereo 立體聲 - + Surround 環繞音效 - + 4GB DRAM (Default) 4GB DRAM (默认) - + 6GB DRAM (Unsafe) 6GB DRAM (不安全) - + 8GB DRAM - + 10GB DRAM (Unsafe) - + 12GB DRAM (Unsafe) - + Docked TV - + Handheld 掌機模式 - - - Off + + + Normal - - Boost (1700MHz) + + + Boost - - Fast (2000MHz) + + + Overclock - + Always ask (Default) 总是询问 (默认) - + Only if game specifies not to stop 仅当游戏不希望停止时 - + Never ask 从不询问 - - - Medium (256) - - - - - - High (512) - - - - + Very Small (16 MB) - + Small (32 MB) - + Normal (128 MB) - + Large (256 MB) - + Very Large (512 MB) - + Very Low (4 MB) - + Low (8 MB) - + Normal (16 MB) - + Medium (32 MB) - + High (64 MB) - + Very Low (32) - + Low (64) - + Normal (128) - - Disabled + + Medium (256) - + + High (512) + + + + ExtendedDynamicState 1 - + ExtendedDynamicState 2 - + ExtendedDynamicState 3 - + Tree View - + Grid View @@ -2589,252 +2479,297 @@ When a program attempts to open the controller applet, it is immediately closed. 全域紀錄篩選器 - - When checked, the max size of the log increases from 100 MB to 1 GB - 啟用後紀錄檔案大小上限從 100MB 增加到 1GB + + GPU Logging/Level + - - Enable Extended Logging** - 啟用延伸紀錄** + + Detail level for GPU logs. Off disables logging entirely. + - + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + Show Log in Console 在終端機中顯示紀錄 - + + When checked, the max size of the log increases from 100 MB to 1 GB + 啟用後紀錄檔案大小上限從 100MB 增加到 1GB + + + + Enable Extended Logging** + 啟用延伸紀錄** + + + Open Log Location 開啟紀錄位置 - + Homebrew Homebrew - + Arguments String 參數字串 - + Graphics 圖形 - + When checked, it executes shaders without loop logic changes 啟用時 yuzu 在執行著色器時,不會修改循環結構的條件判斷。 - + Disable Loop safety checks 停用循環安全檢查 - + When checked, it will dump all the macro programs of the GPU 選取後,將傾印 GPU 的所有巨集程式 - + Dump Maxwell Macros 傾印 Maxwell 巨集 - + When checked, it enables Nsight Aftermath crash dumps 啟用時 yuzu 將會儲存 Nsight Aftermath 格式的錯誤傾印檔案。 - + Enable Nsight Aftermath 啟用 Nsight Aftermath - - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found - 啟用時,將從磁碟著色器快取或遊戲中轉儲所有的著色器檔案。 + + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. + - - Dump Game Shaders - 傾印遊戲著色器 + + Dump Guest (Maxwell) Shaders + - + Enable Renderdoc Hotkey 啟用 Renderdoc 快捷鍵 - + When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower 啟用時將停用 Macro Just In Time 編譯器,會使得效能降低。 - + Disable Macro JIT 停用 Macro JIT - + When checked, it disables the macro HLE functions. Enabling this makes games run slower 停用macro HLE,將會降低遊戲效能。 - + Disable Macro HLE 停用macro HLE - + When checked, the graphics API enters a slower debugging mode 啟用時圖形 API 會進入較慢的偵錯模式。 - + Enable Graphics Debugging 啟用圖形偵錯 - + When checked, yuzu will log statistics about the compiled pipeline cache 啟用時 yuzu 將記錄有關編譯著色器快取的統計資訊。 - + Enable Shader Feedback 啟用著色器回饋 - + <html><head/><body><p>When checked, disables reordering of mapped memory uploads which allows to associate uploads with specific draws. May reduce performance in some cases.</p></body></html> <html><head/><body><p>选中时,禁用映射内存上载的重新排序,从而允许将上载与特定绘图关联起来。某些情况下可能会降低性能。</p></body></html> - + Disable Buffer Reorder 禁用缓存重排序 - + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + + + Dump SPIR-V Shaders + + + + Advanced 進階 - + Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu. 讓 yuzu 在啟動時檢查 Vulkan 是否正常工作。如果其他程式導致 yuzu 出現此問題,請停用此選項。 - + Perform Startup Vulkan Check 啟動時檢查 Vulkan - + Disable Web Applet 停用 Web 小程式 - + Enable All Controller Types 啟用所有控制器類型 - + Enable Auto-Stub - + Kiosk (Quest) Mode Kiosk (Quest) 模式 - + Use dev.keys - + Enable Debug Asserts 啟用偵錯 - + Debugging 偵錯 - + Battery Serial: - + + Debug Knobs: + + + + Bitmask for quick development toggles - + Set debug knobs (bitmask) - + 16-bit debug knob set for quick development toggles - + (bitmask) - - Debug Knobs: - - - - + Unit Serial: - + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. 啟用此選項會將最新的音訊指令列表輸出到控制台。只影響使用音訊渲染器的遊戲。 - + Dump Audio Commands To Console** 將音訊指令傾印至控制台** - + Flush log output on each line - + Enable FS Access Log 啟用檔案系統存取記錄 - + Enable Verbose Reporting Services** 啟用詳細報告服務 - + Censor username in logs - + **This will be reset automatically when Eden closes. @@ -3284,33 +3219,38 @@ Would you like to delete the old save data? 背景顏色: - + % - FSR sharpening percentage (e.g. 50%) - % + FSR/SGSR sharpening percentage (e.g. 50%) + - + + Configure Effects... + + + + Off 關閉 - + VSync Off 垂直同步關 - + Recommended 推薦 - + On 開啟 - + VSync On 垂直同步開 @@ -3831,6 +3771,16 @@ Would you like to delete the old save data? Motion / Touch 體感/觸控 + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + + Disable SDL WGI/XInput (Requires restart) + + ConfigureInputPerGame @@ -4340,58 +4290,58 @@ To invert the axes, first move your joystick vertically, and then horizontally.< [等待中] - + New Profile 新增設定檔 - + Enter a profile name: 輸入設定檔名稱: - - + + Create Input Profile 建立輸入設定檔 - + The given profile name is not valid! 輸入的設定檔名稱無效! - + Failed to create the input profile "%1" 建立輸入設定檔「%1」失敗 - + Delete Input Profile 刪除輸入設定檔 - + Failed to delete the input profile "%1" 刪除輸入設定檔「%1」失敗 - + Load Input Profile 載入輸入設定檔 - + Failed to load the input profile "%1" 載入輸入設定檔「%1」失敗 - + Save Input Profile 儲存輸入設定檔 - + Failed to save the input profile "%1" 儲存輸入設定檔「%1」失敗 @@ -4439,7 +4389,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Configure 設定 @@ -4470,7 +4420,7 @@ To invert the axes, first move your joystick vertically, and then horizontally.< - + Test 測試 @@ -4485,77 +4435,77 @@ To invert the axes, first move your joystick vertically, and then horizontally.< 移除伺服器 - + %1:%2 %1:%2 - - - - - - + + + + + + Eden - + Port number has invalid characters 連線埠中包含無效字元 - + Port has to be in range 0 and 65353 連線埠必須為 0 到 65353 之間 - + IP address is not valid 無效的 IP 位址 - + This UDP server already exists 此 UDP 伺服器已存在 - + Unable to add more than 8 servers 最多只能新增 8 個伺服器 - + Testing 測試中 - + Configuring 設定中 - + Test Successful 測試成功 - + Successfully received data from the server. 已成功從伺服器取得資料 - + Test Failed 測試失敗 - + Could not receive valid data from the server.<br>Please verify that the server is set up correctly and the address and port are correct. 無法從伺服器取得有效的資料。<br>請檢查伺服器是否正確設定以及位址和連接埠是否正確。 - + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. UDP 測試或觸控校正進行中。<br>請耐心等候。 @@ -4740,57 +4690,57 @@ Current values are %1% and %2% respectively. 某些設定僅在遊戲未執行時才能修改 - + Add-Ons 延伸模組 - + System 系統 - + CPU CPU - + Graphics 圖形 - + Adv. Graphics 進階圖形 - + Ext. Graphics - + Audio 音訊 - + Input Profiles 輸入設定檔 - + Network - + Applets - + Properties 屬性 @@ -4913,6 +4863,130 @@ Once deleted, these can NOT be recovered. Are you 100% sure you want to delete t + + ConfigurePostProcessing + + + Post-Processing Effects + + + + + Changes apply immediately while a game is running. + + + + + Enable post-processing + + + + + Preset: + + + + + Apply + + + + + Reset Values + + + + + Return every effect to the values the preset ships with. + + + + + Save As... + + + + + Save Preset + + + + + Preset name: + + + + + Clear + + + + + Remove every effect in use and deselect the preset. + + + + + Add Effect + + + + + None + + + + + No effects in use. Pick a preset and press Apply, or add effects one by one. + + + + + Custom chain. Pick a preset above and press Apply to replace it. + + + + + Active preset: %1 (modified). Press Reset Values to go back to how it ships. + + + + + Active preset: %1 + + + + + Enabled + + + + + Disabled + + + + + Reset to defaults + + + + + Effect '%1' was not found. + + + + + Effect failed to compile: +%1 + + + + + No usable ReShade FX effects were found. Place .fx files in the post_shaders folder. + + + ConfigureProfileManager @@ -5480,91 +5554,86 @@ Drag points to change position, or double-click table cells to edit values. - Show Compatibility List - 顯示相容性列表 - - - Show Add-Ons Column 顯示延伸模組欄位 - + Show Size Column 顯示檔案大小欄位 - + Show File Types Column 顯示檔案格式欄位 - + Show Play Time Column 顯示遊玩時間欄位 - + Folder Icon Size: 資料夾圖示大小: - + Row 1 Text: 第一行顯示文字: - + Row 2 Text: 第二行顯示文字: - + Screenshots 螢幕截圖 - + Ask Where To Save Screenshots (Windows Only) 詢問儲存螢幕截圖的位置(僅限 Windows) - + Screenshots Path: 螢幕截圖位置 - + ... ... - + TextLabel 文字標籤 - + Resolution: 解析度: - + Select Screenshots Path... 選擇儲存螢幕截圖位置... - + <System> <System> - + English English - + Auto (%1 x %2, %3 x %4) Screenshot width value 自動 (%1 x %2, %3 x %4) @@ -5801,7 +5870,7 @@ Drag points to change position, or double-click table cells to edit values. - + Calculating... @@ -6003,50 +6072,50 @@ Please go to Configure -> System -> Network and make a selection. GRenderWindow - - + + OpenGL not available! 無法使用 OpenGL 模式! - + OpenGL shared contexts are not supported. 不支援 OpenGL 共用的上下文。 - + Eden has not been compiled with OpenGL support. - - - + + + Error while initializing OpenGL! 初始化 OpenGL 時發生錯誤! - + Your GPU may not support OpenGL, or you do not have the latest graphics driver. 您的 GPU 可能不支援 OpenGL,或是未安裝最新的圖形驅動程式 - + Error while initializing OpenGL 4.6! 初始化 OpenGL 4.6 時發生錯誤! - + Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.<br><br>GL Renderer:<br>%1 您的 GPU 可能不支援 OpenGL 4.6,或是未安裝最新的圖形驅動程式<br><br>GL 渲染器:<br>%1 - + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported extensions:<br>%2 您的 GPU 可能不支援某些必需的 OpenGL 功能。請確保您已安裝最新的圖形驅動程式。<br><br>GL 渲染器:<br>%1<br><br>不支援的功能:<br>%2 - + This build doesn't have OpenGL support. @@ -6054,279 +6123,272 @@ Please go to Configure -> System -> Network and make a selection. GameList - + &Add New Game Directory - + Favorite 我的最愛 - + Start Game 開始遊戲 - + Start Game without Custom Configuration 開始遊戲(不使用額外設定) - + Open Save Data Location 開啟存檔位置 - + Open Mod Data Location 開啟模組位置 - + Open Transferable Pipeline Cache 開啟通用著色器管線快取位置 - + Link to Ryujinx - + Remove 移除 - + Remove Installed Update 移除已安裝的遊戲更新 - + Remove All Installed DLC 移除所有安裝的DLC - + Remove Custom Configuration 移除額外設定 - + Remove Cache Storage 移除快取儲存空間 - + Remove OpenGL Pipeline Cache 刪除 OpenGL 著色器管線快取 - + Remove Vulkan Pipeline Cache 刪除 Vulkan 著色器管線快取 - + Remove All Pipeline Caches 刪除所有著色器管線快取 - + Remove All Installed Contents 移除所有安裝項目 - + Manage Play Time - + Edit Play Time Data - + Remove Play Time Data 清除遊玩時間 - - + + Dump RomFS 傾印 RomFS - + Dump RomFS to SDMC 傾印 RomFS 到 SDMC - + Verify Integrity 完整性驗證 - + Copy Title ID to Clipboard 複製遊戲 ID 到剪貼簿 - - Navigate to GameDB entry - 檢視遊戲相容性報告 - - - + Create Shortcut 建立捷徑 - + Add to Desktop 新增至桌面 - + Add to Applications Menu 新增至應用程式選單 - + Configure Game - + Scan Subfolders 包含子資料夾 - + Remove Game Directory 移除遊戲資料夾 - + ▲ Move Up ▲ 向上移動 - + ▼ Move Down ▼ 向下移動 - + Open Directory Location 開啟資料夾位置 - + Clear 清除 - - - Name - 名稱 - - - - Compatibility - 相容性 - - - - Add-ons - 延伸模組 - - - - File type - 檔案格式 - - - - Size - 大小 - - - - Play time - 遊玩時間 - GameListItemCompat - + Ingame 遊戲內 - + Game starts, but crashes or major glitches prevent it from being completed. 遊戲可以執行,但可能會出現當機或故障導致遊戲無法正常運作。 - + Perfect 完美 - + Game can be played without issues. 遊戲可以毫無問題的遊玩。 - + Playable 可遊玩 - + Game functions with minor graphical or audio glitches and is playable from start to finish. 遊戲自始至終可以正常遊玩,但可能會有一些輕微的圖形或音訊故障。 - + Intro/Menu 開始畫面/選單 - + Game loads, but is unable to progress past the Start Screen. 遊戲可以載入,但無法通過開始畫面。 - + Won't Boot 無法啟動 - + The game crashes when attempting to startup. 啟動遊戲時異常關閉 - + Not Tested 未測試 - + The game has not yet been tested. 此遊戲尚未經過測試 + + GameListModel + + + Name + + + + + Add-ons + + + + + File type + + + + + Size + + + + + Play time + + + GameListPlaceholder - + Double-click to add a new folder to the game list 連點兩下以新增資料夾至遊戲清單 @@ -6334,17 +6396,17 @@ Please go to Configure -> System -> Network and make a selection. GameListSearchField - + %1 of %n result(s) - + Filter: 搜尋: - + Enter pattern to filter 輸入文字以搜尋 @@ -6838,1139 +6900,1112 @@ Debug Message: - + Game &Icon Size - + Reset Window Size to &720p 重設視窗大小為 &720p - + Reset Window Size to 720p 重設視窗大小為 720p - + Reset Window Size to &900p 重設視窗大小為 &900p - + Reset Window Size to 900p 重設視窗大小為 900p - + Reset Window Size to &1080p 重設視窗大小為 &1080p - + Reset Window Size to 1080p 重設視窗大小為 1080p - + &Multiplayer 多人遊戲 (&M) - + &Tools 工具 (&T) - + Am&iibo - + Launch &Applet - + &TAS TAS (&T) - + &Create Home Menu Shortcut - + Install &Firmware - + &Help 說明 (&H) - + &Install Files to NAND... &安裝檔案至內部儲存空間 - + L&oad File... 開啟檔案(&O)... - + Load &Folder... 開啟資料夾(&F)... - + E&xit 結束(&X) - - + + &Pause 暫停(&P) - + &Stop 停止(&S) - + &Verify Installed Contents 驗證已安裝內容的完整性 (&V) - + &About Eden - + Single &Window Mode 單一視窗模式(&W) - + Con&figure... 設定 (&F) - + Ctrl+, - + Enable Overlay Display Applet - + Show &Filter Bar 顯示搜尋列(&F) - + Show &Status Bar 顯示狀態列(&S) - + Show Status Bar 顯示狀態列 - + &Browse Public Game Lobby 瀏覽公用遊戲大廳 (&B) - + &Create Room 建立房間 (&C) - + &Leave Room 離開房間 (&L) - + &Direct Connect to Room 直接連線到房間 (&D) - + &Show Current Room 顯示目前的房間 (&S) - + F&ullscreen 全螢幕(&U) - + &Restart 重新啟動(&R) - + Load/Remove &Amiibo... 載入/移除 Amiibo... (&A) - - &Report Compatibility - 回報相容性(&R) - - - + Open &Mods Page 模組資訊 (&M) - - Open &Quickstart Guide - 快速入門 (&Q) + + &User Handbook + - - &FAQ - 常見問題 (&F) - - - + &Capture Screenshot 截圖 (&C) - + &Album - + &Set Nickname and Owner 登錄持有者和暱稱 (&S) - + &Delete Game Data 清除遊戲資料 (&D) - + &Restore Amiibo 復原資料 (&R) - + &Format Amiibo 初始化 Amiibo (&F) - + &Mii Editor - + &Configure TAS... 設定 &TAS… - + Configure C&urrent Game... 目前遊戲設定...(&U) - - + + &Start 開始(&S) - + &Reset 重設 (&R) - - + + R&ecord 錄製 (&E) - + Open &Controller Menu 打开控制器菜单 (&C) - + Install Decryption &Keys - + &Home Menu - + &Desktop - + &Application Menu - + &Root Data Folder - + &NAND Folder - + &SDMC Folder - + &Mod Folder - + &Log Folder - + From Folder - + From ZIP - + &Eden Dependencies - + &Data Manager - + &Tree View - + &Grid View - + Game Icon Size - - + + None - + Show Game &Name - + Show &Performance Overlay - + + Post-Processing &Shaders... + + + + + &Carousel View + + + + Small (32x32) - + Standard (64x64) - + Large (128x128) - + Full Size (256x256) - + Broken Vulkan Installation Detected - + Vulkan initialization failed during boot. - + Running a game TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping - + Loading Web Applet... - - + + Disable Web Applet - + Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet? (This can be re-enabled in the Debug settings.) - + The amount of shaders currently being built - + The current selected resolution scaling multiplier. - + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. - + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. - + Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms. - + Unmute - + Mute - + Reset Volume - + + Configure Effects... + + + + &Clear Recent Files - + &Continue - + Warning: Outdated Game Format - + You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.<br>For an explanation of the various Switch formats Eden supports, out our user handbook. This message will not be shown again. - - + + Error while loading ROM! - + The ROM format is not supported. - + An error occurred initializing the video core. - - Eden has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://yuzu-mirror.github.io/help/reference/log-files/'>How to Upload the Log File</a>. + + This is usually caused by outdated GPU drivers. Please see the log for more details. See: <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md'>How to access log files</a>. - + Error while loading ROM! %1 %1 signifies a numeric error code. - + %1<br>Please redump your files or ask on Discord/Stoat for help. %1 signifies an error string. - + An unknown error occurred. Please see the log for more details. - + + (64-bit) - + + (32-bit) - + + %1 %2 %1 is the title name. %2 indicates if the title is 64-bit or 32-bit - + Closing software... - + Save Data - + Mod Data - + Error Opening %1 Folder - - + + Folder does not exist! - + Remove Installed Game Contents? - + Remove Installed Game Update? - + Remove Installed Game DLC? - + Remove Entry - + Delete OpenGL Transferable Shader Cache? - + Delete Vulkan Transferable Shader Cache? - + Delete All Transferable Shader Caches? - + Remove Custom Game Configuration? - + Remove Cache Storage? - + Remove File - + Remove Play Time Data - + Reset play time? - - + + RomFS Extraction Failed! - + There was an error copying the RomFS files or the user cancelled the operation. - + Full - + Skeleton - + Select RomFS Dump Mode - + Please select the how you would like the RomFS dumped.<br>Full will copy all of the files into the new directory while <br>skeleton will only create the directory structure. - + There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation > Configure > System > Filesystem > Dump Root - + Extracting RomFS... - - + + Cancel - + RomFS Extraction Succeeded! - + The operation completed successfully. - + Error Opening %1 - + Select Directory - + Properties - + The game properties could not be loaded. - + Switch Executable (%1);;All Files (*.*) %1 is an identifier for the Switch executable file extensions. - + Load File - + Open Extracted ROM Directory - + Invalid Directory Selected - + The directory you have selected does not contain a 'main' file. - + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) - + Install Files - + %n file(s) remaining - + Installing file "%1"... - - + + Install Results - + To avoid possible conflicts, we discourage users from installing base games to the NAND. Please, only use this feature to install updates and DLC. - + %n file(s) were newly installed - + %n file(s) were overwritten - + %n file(s) failed to install - + System Application - + System Archive - + System Application Update - + Firmware Package (Type A) - + Firmware Package (Type B) - + Game - + Game Update - + Game DLC - + Delta Title - + Select NCA Install Type... - + Please select the type of title you would like to install this NCA as: (In most instances, the default 'Game' is fine.) - + Failed to Install - + The title type you selected for the NCA is invalid. - + File not found - + File "%1" not found - + OK - + Function Disabled - + Compatibility list reporting is currently disabled. Check back later! - + Error opening URL - + Unable to open the URL "%1". - + TAS Recording - + Overwrite file of player 1? - + Invalid config detected - + Handheld controller can't be used on docked mode. Pro controller will be selected. - - + + Amiibo - - + + The current amiibo has been removed - + Error - - + + The current game is not looking for amiibos - + Amiibo File (%1);; All Files (*.*) - + Load Amiibo - + Error loading Amiibo data - + The selected file is not a valid amiibo - + The selected file is already on use - + An unknown error occurred - - - Keys not installed - - - - - - Install decryption keys and restart Eden before attempting to install firmware. - - - - - Select Dumped Firmware Source Location - - - - - Select Dumped Firmware ZIP - - - - - Zipped Archives (*.zip) - - - - - Firmware cleanup failed - - - - - Failed to clean up extracted firmware cache. -Check write permissions in the system temp directory and try again. -OS reported error: %1 - - - - + No firmware available - + Firmware Corrupted - + Unknown applet - + Applet doesn't map to a known value. - + Record not found - + Applet not found. Please reinstall firmware. - + Capture Screenshot - + PNG Image (*.png) - + TAS state: Running %1/%2 - + TAS state: Recording %1 - + TAS state: Idle %1/%2 - + TAS State: Invalid - + &Stop Running - + Stop R&ecording - + Building: %n shader(s) - + Scale: %1x %1 is the resolution scaling factor - + Speed: %1% / %2% - + Speed: %1% - + Game: %1 FPS - + Frame: %1 ms - - - FSR - - - - + NO AA - + + NO FX + + + + + FX + + + + VOLUME: MUTE - + VOLUME: %1% Volume percentage (e.g. 50%) - + Derivation Components Missing - + Decryption keys are missing. Install them now? - + Wayland Detected! - + Wayland is known to have significant performance issues and mysterious bugs. It's recommended to use X11 instead. @@ -7978,74 +8013,74 @@ Would you like to force it for future launches? - + Use X11 - + Continue with Wayland - + Don't show again - + Restart Required - + Restart Eden to apply the X11 backend. - + Slow - + Turbo - + Unlocked - + Select RomFS Dump Target - + Please select which RomFS you would like to dump. - + Are you sure you want to close Eden? - - - + + + Eden - + Are you sure you want to stop the emulation? Any unsaved progress will be lost. - + The currently running application has requested Eden to not exit. Would you like to bypass this and exit anyway? @@ -8111,6 +8146,11 @@ Would you like to bypass this and exit anyway? ScaleForce + + + FSR + + Area @@ -8122,52 +8162,57 @@ Would you like to bypass this and exit anyway? - + + SGSR + + + + + SGSR EdgeDir + + + + Docked - + Handheld - + Fast - - Balanced - - - - + Accurate - + Vulkan - + OpenGL GLSL - + OpenGL SPIRV - + OpenGL GLASM - + Null @@ -8200,7 +8245,7 @@ If you wish to clean up the files which were left in the old data location, you - + Data was migrated successfully. @@ -8690,8 +8735,8 @@ p, li { white-space: pre-wrap; } - - + + [not set] @@ -8704,10 +8749,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + @@ -8722,9 +8767,9 @@ p, li { white-space: pre-wrap; } - - - + + + @@ -8886,50 +8931,50 @@ p, li { white-space: pre-wrap; } [未指定] - + %1%2 %1%2 - + [invalid] [無效] - - + + %1%2Hat %3 %1%2Hat 控制器 %3 - - - + + + %1%2Axis %3 %1%2軸 %3 - + %1%2Axis %3,%4,%5 %1%2軸 %3,%4,%5 - + %1%2Motion %3 %1%2體感 %3 - - + + %1%2Button %3 %1%2按鈕 %3 - + [unused] [未使用] @@ -9055,47 +9100,47 @@ p, li { white-space: pre-wrap; } %1 正在玩 %2 - + Play Time: %1 - + Never Played - + Version: %1 - + Version: 1.0.0 - + Installed SD Titles 安裝在 SD 卡中的遊戲 - + Installed NAND Titles 安裝在內部儲存空間中的遊戲 - + System Titles 系統項目 - + Add New Game Directory 加入遊戲資料夾 - + Favorites 我的最愛 @@ -9216,13 +9261,13 @@ p, li { white-space: pre-wrap; } QtCommon::Content - + Game Requires Firmware - - The game you are trying to launch requires firmware to boot or to get past the opening menu. Please <a href='https://yuzu-mirror.github.io/help/quickstart'>dump and install firmware</a>, or press "OK" to launch anyways. + + This game may require firmware to function properly, and you don't have any installed. Please install firmware before launching, or press "OK" to launch anyways. @@ -9232,10 +9277,10 @@ p, li { white-space: pre-wrap; } - - - - + + + + Cancel @@ -9256,213 +9301,246 @@ p, li { white-space: pre-wrap; } - + Verification failed for the following files: %1 - - + + Verifying integrity... - - + + Integrity verification succeeded! - - + + The operation completed successfully. - - + + Integrity verification failed! - + File contents may be corrupt or missing. - + Integrity verification couldn't be performed - + Firmware installation cancelled, firmware may be in a bad state or corrupted. File contents could not be checked for validity. - + Select Dumped Keys Location - + Decryption Keys install succeeded - + Decryption Keys install failed - + Orphaned Profiles Detected! - + UNEXPECTED BAD THINGS MAY HAPPEN IF YOU DON'T READ THIS!<br>Eden has detected the following save directories with no attached profile:<br>%1<br><br>The following profiles are valid:<br>%2<br><br>Click "OK" to open your save folder and fix up your profiles.<br>Hint: copy the contents of the largest or last-modified folder elsewhere, delete all orphaned profiles, and move your copied contents to the good profile.<br><br>Still confused? See the <a href='https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/Orphaned.md'>help page</a>.<br> - + Really clear data? - + Important data may be lost! - + Are you REALLY sure? - + Once deleted, your data will NOT come back! Only do this if you're 100% sure you want to delete this data. - + Clearing... - + Select Export Location - + %1.zip - - + + + Zipped Archives (*.zip) - + Exporting data. This may take a while... - + Exporting - + Exported Successfully - + Data was exported successfully. - + Export Cancelled - + Export was cancelled by the user. - + Export Failed - + Ensure you have write permissions on the targeted directory and try again. - + Select Import Location - + Import Warning - + All previous data in this directory will be deleted. Are you sure you wish to proceed? - + Importing data. This may take a while... - + Importing - + Imported Successfully - + Data was imported successfully. - + Import Cancelled - + Import was cancelled by the user. - + Import Failed - + Ensure you have read permissions on the targeted directory and try again. + + + Keys not installed + + + + + Install decryption keys and restart Eden before attempting to install firmware. + + + + + Select Dumped Firmware Source Location + + + + + Select Dumped Firmware ZIP + + + + + Firmware cleanup failed + + + + + Failed to clean up extracted firmware cache. +Check write permissions in the system temp directory and try again. +OS reported error: %1 + + QtCommon::FS @@ -9706,72 +9784,72 @@ Would you like to manually select a portable folder to use? - + Create Shortcut - + Do you want to launch the game in fullscreen? - + Shortcut Created - + Successfully created a shortcut to %1 - + Shortcut may be Volatile! - + This will create a shortcut to the current AppImage. This may not work well if you update. Continue? - + Failed to Create Shortcut - + Failed to create a shortcut to %1 - + Create Icon - + Cannot create icon file. Path "%1" does not exist and cannot be created. - + No firmware available - + Please install firmware to use the home menu. - + Home Menu Applet - + Home Menu is not available. Please reinstall firmware. @@ -9779,55 +9857,55 @@ Would you like to manually select a portable folder to use? QtCommon::Mod - + Mod Name - + What should this mod be called? - + RomFS - + ExeFS/Patch - + Cheat - + Mod Type - + Could not detect mod type automatically. Please manually specify the type of mod you downloaded. Most mods are RomFS mods, but patches (.pchtxt) are typically ExeFS mods. - - + + Mod Extract Failed - + Failed to create temporary directory %1 - + Zip file %1 is empty @@ -10521,65 +10599,65 @@ By selecting "From Eden", previous save data stored in Ryujinx will be - + New Version Location - + All Files (*.*) - - - + + + Failed to save file - + Could not open file %1 for writing. - + Downloading... - + Cancel - + Could not write to file %1. - + Could not commit to file %1. - + Failed to download file - + Could not download from %1%2 Error code: %3 - + Download Complete - + Successfully downloaded %1. Would you like to open it? diff --git a/dist/post_presets/Anime.fxp b/dist/post_presets/Anime.fxp new file mode 100644 index 0000000000..a0019905ce --- /dev/null +++ b/dist/post_presets/Anime.fxp @@ -0,0 +1,3 @@ +name=Anime +description=Cel shaded look: the picture is cleaned first, then the lighting is collapsed into flat bands with inked edges, finished with a small glow. +chain=Denoise.fx|Denoise|Strength=0.18,Radius=1.2;CelShading.fx|CelShading|Bands=5,BandContrast=0.35,BandEdge=0.2,Detail=0.7,OutlineStrength=0.7,OutlineThreshold=0.2,Saturation=1.3;Bloom.fx|Bloom|Radius=1.2,Amount=0.25 diff --git a/dist/post_presets/Cinematic.fxp b/dist/post_presets/Cinematic.fxp new file mode 100644 index 0000000000..582226f2da --- /dev/null +++ b/dist/post_presets/Cinematic.fxp @@ -0,0 +1,3 @@ +name=Cinematic +description=Film look: soft glow on the highlights, a filmic contrast curve, cool shadows against warm highlights, fine grain and a gentle vignette. +chain=Bloom.fx|Bloom|Radius=1.6,Amount=0.35;FilmicCurve.fx|FilmicCurve|Toe=1.35,Shoulder=1.3,Amount=0.85;SplitToning.fx|SplitToning|ShadowHue=210,ShadowStrength=0.25,HighlightHue=38,HighlightStrength=0.3;FilmGrain.fx|FilmGrain|Intensity=0.022;Vignette.fx|Vignette|Strength=0.5 diff --git a/dist/post_presets/Clean.fxp b/dist/post_presets/Clean.fxp new file mode 100644 index 0000000000..c44e519e0f --- /dev/null +++ b/dist/post_presets/Clean.fxp @@ -0,0 +1,3 @@ +name=Clean +description=Cleans up a low internal resolution: removes dithering and compression noise, smooths banded skies and brings edge detail back. The safe starting point if you are not sure what you want. +chain=Denoise.fx|Denoise|Strength=0.14,Curve=1.2;Deband.fx|Deband|;Sharpen.fx|Sharpen|Amount=0.75 diff --git a/dist/post_presets/Dreamy.fxp b/dist/post_presets/Dreamy.fxp new file mode 100644 index 0000000000..cbf56c77af --- /dev/null +++ b/dist/post_presets/Dreamy.fxp @@ -0,0 +1,3 @@ +name=Dreamy +description=Soft focus: the centre of the screen stays sharp while everything around it blurs, with a wide glow over the highlights. Good for slower games and cutscenes. +chain=Blur.fx|Blur|Radius=6,Strength=0.85,FocusSize=0.28,FocusSoftness=0.5;Bloom.fx|Bloom|Radius=2.0,Amount=0.55 diff --git a/dist/post_presets/Retro.fxp b/dist/post_presets/Retro.fxp new file mode 100644 index 0000000000..ac4059e641 --- /dev/null +++ b/dist/post_presets/Retro.fxp @@ -0,0 +1,3 @@ +name=Retro +description=Old television look: colour fringing at the edges of the screen, curved scanlines with a phosphor mask, and darkened corners. The scanline roll is switched off so it does not crawl. +chain=ChromaticAberration.fx|ChromaticAberration|Strength=1.0;CRT.fx|CRT|RollSpeed=0,Bleed=1.2;Vignette.fx|Vignette|Strength=0.7 diff --git a/dist/post_presets/Vivid.fxp b/dist/post_presets/Vivid.fxp new file mode 100644 index 0000000000..6a69a8e27f --- /dev/null +++ b/dist/post_presets/Vivid.fxp @@ -0,0 +1,3 @@ +name=Vivid +description=Punchier colour without stylising the image: warmer and more saturated, blacks and whites pushed to the ends of the range, and a light sharpen. +chain=NaturalColors.fx|NaturalColors|Luma=1.15,Chroma=1.35;Levels.fx|Levels|InputBlack=0.02,InputWhite=0.98;Sharpen.fx|Sharpen|Amount=0.5 diff --git a/dist/post_shaders/Bloom.fx b/dist/post_shaders/Bloom.fx new file mode 100644 index 0000000000..a450b21331 --- /dev/null +++ b/dist/post_shaders/Bloom.fx @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project +// SPDX-License-Identifier: GPL-2.0-or-later +// Ported from bloomnoblur.fsh in PPSSPP. + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Radius < + ui_type = "slider"; + ui_label = "Radius"; + ui_tooltip = "How far the glow spreads from bright areas."; + ui_min = 0.0; ui_max = 4.0; ui_step = 0.05; +> = 1.0; + +uniform float Amount < + ui_type = "slider"; + ui_label = "Amount"; + ui_tooltip = "Strength of the glow added on top of the image."; + ui_min = 0.0; ui_max = 2.0; ui_step = 0.05; +> = 0.6; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float Weight(float3 color) +{ + float gray = (color.r + color.g + color.b) / 3.0; + float saturation = (abs(color.r - gray) + abs(color.g - gray) + abs(color.b - gray)) / 3.0; + return gray * gray / max(saturation, 0.25); +} + +float4 PS_Bloom(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float3 color = tex2D(BackBuffer, uv).rgb; + + float gray = (color.r + color.g + color.b) / 3.0; + float saturation = (abs(color.r - gray) + abs(color.g - gray) + abs(color.b - gray)) / 3.0; + float spread = 0.002 * gray / max(saturation, 0.25) * Radius; + + float3 sum = float3(0.0, 0.0, 0.0); + [unroll] for (int x = -3; x <= 3; x += 2) + { + [unroll] for (int y = -3; y <= 3; y += 2) + { + float3 tap = tex2D(BackBuffer, uv + float2(x, y) * spread).rgb; + sum += tap * Weight(tap); + } + } + sum /= 16.0; + + return float4(saturate(color + sum * Amount), 1.0); +} + +technique Bloom < + ui_label = "Bloom"; + ui_tooltip = "Bleeds light out of the brightest areas into a soft halo, the way a camera does when pointed at something too bright."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_Bloom; + } +} diff --git a/dist/post_shaders/Blur.fx b/dist/post_shaders/Blur.fx new file mode 100644 index 0000000000..aa17d03c2e --- /dev/null +++ b/dist/post_shaders/Blur.fx @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Radius < + ui_type = "slider"; + ui_label = "Radius"; + ui_tooltip = "How far the blur reaches, in pixels."; + ui_min = 1.0; ui_max = 12.0; ui_step = 0.5; +> = 4.0; + +uniform float Strength < + ui_type = "slider"; + ui_label = "Strength"; + ui_tooltip = "Blend between the original image and the blurred one."; + ui_min = 0.0; ui_max = 1.0; ui_step = 0.05; +> = 1.0; + +uniform float FocusSize < + ui_type = "slider"; + ui_label = "Sharp Centre"; + ui_tooltip = "Size of the region left in focus at the centre of the screen. At zero the whole image is blurred evenly."; + ui_min = 0.0; ui_max = 1.0; ui_step = 0.05; +> = 0.0; + +uniform float FocusSoftness < + ui_type = "slider"; + ui_label = "Focus Falloff"; + ui_tooltip = "How gradually the sharp centre gives way to the blur."; + ui_min = 0.05; ui_max = 1.0; ui_step = 0.05; +> = 0.4; + +static const int TAPS = 17; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float4 PS_Blur(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT); + float3 original = tex2D(BackBuffer, uv).rgb; + + float2 centred = (uv - 0.5) * float2(BUFFER_WIDTH * BUFFER_RCP_HEIGHT, 1.0) * 2.0; + float distance_from_centre = length(centred); + float no_focus = 1.0 - step(0.001, FocusSize); + float focus = max(smoothstep(FocusSize, FocusSize + FocusSoftness, distance_from_centre), no_focus); + float amount = Strength * focus; + + float angle = frac(sin(dot(pos.xy, float2(12.9898, 78.233))) * 43758.5453) * 6.2831853; + float2 spoke = float2(cos(angle), sin(angle)); + + const float2 turn = float2(cos(2.39996323), sin(2.39996323)); + const float decay = exp(-2.0 / float(TAPS)); + float weight = exp(-1.0 / float(TAPS)); + + float3 sum = float3(0.0, 0.0, 0.0); + float total = 0.0; + [unroll] for (int i = 0; i < TAPS; ++i) + { + float reach = sqrt((float(i) + 0.5) / float(TAPS)); + sum += tex2D(BackBuffer, uv + spoke * reach * texel * Radius).rgb * weight; + total += weight; + weight *= decay; + spoke = float2(spoke.x * turn.x - spoke.y * turn.y, + spoke.x * turn.y + spoke.y * turn.x); + } + + float3 blurred = sum / total; + return float4(lerp(original, blurred, amount), 1.0); +} + +technique Blur < + ui_label = "Blur"; + ui_tooltip = "Gaussian blur with an optional sharp centre, for softening the picture or faking depth of field."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_Blur; + } +} diff --git a/dist/post_shaders/CRT.fx b/dist/post_shaders/CRT.fx new file mode 100644 index 0000000000..51bb810e9f --- /dev/null +++ b/dist/post_shaders/CRT.fx @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project +// SPDX-License-Identifier: GPL-2.0-or-later +// Ported from crt.fsh in PPSSPP, by KillaMaaki. + + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Timer < source = "timer"; > = 0.0; + +uniform float Density < + ui_type = "slider"; + ui_label = "Line Density"; + ui_min = 60.0; ui_max = 720.0; ui_step = 10.0; +> = 272.0; + +uniform float RollSpeed < + ui_type = "slider"; + ui_label = "Roll Speed"; + ui_tooltip = "Speed of the rolling bar. Zero disables it."; + ui_min = 0.0; ui_max = 2.0; ui_step = 0.05; +> = 1.0; + +uniform float Bleed < + ui_type = "slider"; + ui_label = "Colour Bleed"; + ui_tooltip = "Horizontal separation of the red and green channels."; + ui_min = 0.0; ui_max = 4.0; ui_step = 0.1; +> = 1.0; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float4 PS_CRT(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float seconds = Timer * 0.001; + float scan = floor((uv.y + seconds * RollSpeed * 0.5) * Density); + float line_intensity = frac(scan * 0.5) * 2.0; + + float2 shift = float2(line_intensity * 0.0005, 0.0); + float2 bleed = float2(BUFFER_RCP_WIDTH * Bleed, 0.0); + + float r = tex2D(BackBuffer, uv + bleed + shift).r; + float g = tex2D(BackBuffer, uv - bleed + shift).g; + float b = tex2D(BackBuffer, uv).b; + + float3 color = float3(r, g * 0.99, b) * clamp(line_intensity, 0.85, 1.0); + + if (RollSpeed > 0.0) + { + float rollbar = sin((uv.y + seconds * RollSpeed) * 4.0); + color += rollbar * 0.02; + } + + return float4(saturate(color), 1.0); +} + +technique CRT < + ui_label = "CRT"; + ui_tooltip = "Curved scanlines and the phosphor mask of a CRT television, for games that were drawn with that kind of screen in mind."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_CRT; + } +} diff --git a/dist/post_shaders/Cartoon.fx b/dist/post_shaders/Cartoon.fx new file mode 100644 index 0000000000..3dbe9d77b0 --- /dev/null +++ b/dist/post_shaders/Cartoon.fx @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project +// SPDX-FileCopyrightText: guest(r) +// SPDX-License-Identifier: GPL-2.0-or-later +// Ported from cartoon.fsh in PPSSPP, Advanced Cartoon shader I by guest(r). + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float EdgeStrength < + ui_type = "slider"; + ui_label = "Edge Strength"; + ui_tooltip = "Darkness of the ink outline drawn around detected edges."; + ui_min = 0.0; ui_max = 2.0; ui_step = 0.05; +> = 0.5; + +uniform float Levels < + ui_type = "slider"; + ui_label = "Colour Levels"; + ui_tooltip = "How many bands the colours are quantised into."; + ui_min = 2.0; ui_max = 16.0; ui_step = 1.0; +> = 4.0; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float4 PS_Cartoon(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT); + + float3 c00 = tex2D(BackBuffer, uv + texel * float2(-1.0, -1.0)).rgb; + float3 c10 = tex2D(BackBuffer, uv + texel * float2( 0.0, -1.0)).rgb; + float3 c20 = tex2D(BackBuffer, uv + texel * float2( 1.0, -1.0)).rgb; + float3 c01 = tex2D(BackBuffer, uv + texel * float2(-1.0, 0.0)).rgb; + float3 c11 = tex2D(BackBuffer, uv).rgb; + float3 c21 = tex2D(BackBuffer, uv + texel * float2( 1.0, 0.0)).rgb; + float3 c02 = tex2D(BackBuffer, uv + texel * float2(-1.0, 1.0)).rgb; + float3 c12 = tex2D(BackBuffer, uv + texel * float2( 0.0, 1.0)).rgb; + float3 c22 = tex2D(BackBuffer, uv + texel * float2( 1.0, 1.0)).rgb; + + const float3 dt = float3(1.0, 1.0, 1.0); + + float d1 = dot(abs(c00 - c22), dt); + float d2 = dot(abs(c20 - c02), dt); + float hl = dot(abs(c01 - c21), dt); + float vl = dot(abs(c10 - c12), dt); + float edge = EdgeStrength * (d1 + d2 + hl + vl) / (dot(c11, dt) + 0.15); + + float lc = Levels * length(c11); + float f = frac(lc); + f *= f; + lc = (floor(lc) + f * f) / Levels + 0.05; + + float3 unit = normalize(max(c11, 0.0001)); + float3 quant = Levels * unit; + float3 frct = frac(quant); + frct *= frct; + quant = floor(quant) + 0.05 * dt + frct * frct; + + float3 color = lc * (1.1 - edge * sqrt(edge)) * quant / Levels; + return float4(saturate(color), 1.0); +} + +technique Cartoon < + ui_label = "Cartoon"; + ui_tooltip = "Ink outlines and flat colour bands. Faithful port of the PPSSPP shader; the outline gets heavy in dark scenes, so try Cartoon Soft or Cel Shading if the blacks smear."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_Cartoon; + } +} diff --git a/dist/post_shaders/CartoonSoft.fx b/dist/post_shaders/CartoonSoft.fx new file mode 100644 index 0000000000..bee08d8b43 --- /dev/null +++ b/dist/post_shaders/CartoonSoft.fx @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project +// SPDX-FileCopyrightText: guest(r) +// SPDX-License-Identifier: GPL-2.0-or-later + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float EdgeStrength < + ui_type = "slider"; + ui_label = "Edge Strength"; + ui_tooltip = "Darkness of the ink outline drawn around detected edges."; + ui_min = 0.0; ui_max = 2.0; ui_step = 0.05; +> = 0.45; + +uniform float ShadowGuard < + ui_type = "slider"; + ui_label = "Shadow Guard"; + ui_tooltip = "Holds the outline back in dark areas. Raise it if shadows turn into black blobs."; + ui_min = 0.1; ui_max = 2.0; ui_step = 0.05; +> = 0.8; + +uniform float Levels < + ui_type = "slider"; + ui_label = "Colour Levels"; + ui_tooltip = "How many bands the colours are quantised into."; + ui_min = 2.0; ui_max = 16.0; ui_step = 1.0; +> = 6.0; + +uniform float Smoothing < + ui_type = "slider"; + ui_label = "Banding"; + ui_tooltip = "How far the picture is pushed towards flat bands."; + ui_min = 0.0; ui_max = 1.0; ui_step = 0.05; +> = 0.75; + +uniform float Saturation < + ui_type = "slider"; + ui_label = "Saturation"; + ui_min = 0.0; ui_max = 2.0; ui_step = 0.05; +> = 1.15; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float4 PS_CartoonSoft(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT); + + float3 c00 = tex2D(BackBuffer, uv + texel * float2(-1.0, -1.0)).rgb; + float3 c10 = tex2D(BackBuffer, uv + texel * float2( 0.0, -1.0)).rgb; + float3 c20 = tex2D(BackBuffer, uv + texel * float2( 1.0, -1.0)).rgb; + float3 c01 = tex2D(BackBuffer, uv + texel * float2(-1.0, 0.0)).rgb; + float3 c11 = tex2D(BackBuffer, uv).rgb; + float3 c21 = tex2D(BackBuffer, uv + texel * float2( 1.0, 0.0)).rgb; + float3 c02 = tex2D(BackBuffer, uv + texel * float2(-1.0, 1.0)).rgb; + float3 c12 = tex2D(BackBuffer, uv + texel * float2( 0.0, 1.0)).rgb; + float3 c22 = tex2D(BackBuffer, uv + texel * float2( 1.0, 1.0)).rgb; + + const float3 dt = float3(1.0, 1.0, 1.0); + const float3 luma_weights = float3(0.299, 0.587, 0.114); + + float d1 = dot(abs(c00 - c22), dt); + float d2 = dot(abs(c20 - c02), dt); + float hl = dot(abs(c01 - c21), dt); + float vl = dot(abs(c10 - c12), dt); + + float luma = dot(c11, luma_weights); + float response = (d1 + d2 + hl + vl) / (luma * 2.0 + ShadowGuard); + float ink = 1.0 - saturate(response * EdgeStrength); + + float scaled = luma * Levels; + float step_position = frac(scaled); + float eased = step_position * step_position * (3.0 - 2.0 * step_position); + float banded = (floor(scaled) + eased) / Levels; + float target = lerp(luma, banded, Smoothing); + + float3 tinted = c11 * (target / max(luma, 0.001)); + float3 grey = dot(tinted, luma_weights); + float3 color = lerp(grey, tinted, Saturation) * ink; + + return float4(saturate(color), 1.0); +} + +technique CartoonSoft < + ui_label = "Cartoon Soft"; + ui_tooltip = "Ink outlines and flat colour bands, with the outline held back in the shadows so dark scenes do not turn into black blobs."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_CartoonSoft; + } +} diff --git a/dist/post_shaders/CelShading.fx b/dist/post_shaders/CelShading.fx new file mode 100644 index 0000000000..fa1cae452c --- /dev/null +++ b/dist/post_shaders/CelShading.fx @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Bands < + ui_type = "slider"; + ui_label = "Shading Bands"; + ui_tooltip = "How many flat steps the lighting is collapsed into. Three or four gives the classic look."; + ui_min = 2.0; ui_max = 8.0; ui_step = 1.0; +> = 4.0; + +uniform float BandContrast < + ui_type = "slider"; + ui_label = "Band Contrast"; + ui_tooltip = "Spreads the bands towards pure black and white. Lower it if the shadows crush."; + ui_min = 0.0; ui_max = 1.0; ui_step = 0.05; +> = 0.5; + +uniform float BandEdge < + ui_type = "slider"; + ui_label = "Band Edge"; + ui_tooltip = "Width of the transition between bands. Low values give hard cel steps."; + ui_min = 0.02; ui_max = 1.0; ui_step = 0.02; +> = 0.15; + +uniform float Detail < + ui_type = "slider"; + ui_label = "Texture Detail"; + ui_tooltip = "How much surface texture survives the flattening. At zero the bands are completely flat."; + ui_min = 0.0; ui_max = 1.0; ui_step = 0.05; +> = 0.6; + +uniform float OutlineStrength < + ui_type = "slider"; + ui_label = "Outline Strength"; + ui_tooltip = "Opacity of the ink line drawn along detected edges."; + ui_min = 0.0; ui_max = 1.0; ui_step = 0.05; +> = 0.8; + +uniform float OutlineThreshold < + ui_type = "slider"; + ui_label = "Outline Threshold"; + ui_tooltip = "How strong an edge has to be before it is inked. Raise it to keep ink off textures and specular highlights."; + ui_min = 0.01; ui_max = 0.4; ui_step = 0.01; +> = 0.18; + +uniform float Saturation < + ui_type = "slider"; + ui_label = "Saturation"; + ui_tooltip = "Colour intensity of the flat regions."; + ui_min = 0.0; ui_max = 2.0; ui_step = 0.05; +> = 1.25; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float4 PS_CelShading(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + const float3 luma_weights = float3(0.2126, 0.7152, 0.0722); + float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT); + + float3 centre = tex2D(BackBuffer, uv).rgb; + float luma = dot(centre, luma_weights); + + float2 near = texel * 1.5; + float2 far = texel * 3.5; + + float a00 = dot(tex2D(BackBuffer, uv + near * float2(-1.0, -1.0)).rgb, luma_weights); + float a20 = dot(tex2D(BackBuffer, uv + near * float2( 1.0, -1.0)).rgb, luma_weights); + float a02 = dot(tex2D(BackBuffer, uv + near * float2(-1.0, 1.0)).rgb, luma_weights); + float a22 = dot(tex2D(BackBuffer, uv + near * float2( 1.0, 1.0)).rgb, luma_weights); + + float b00 = dot(tex2D(BackBuffer, uv + far * float2(-1.0, -1.0)).rgb, luma_weights); + float b20 = dot(tex2D(BackBuffer, uv + far * float2( 1.0, -1.0)).rgb, luma_weights); + float b02 = dot(tex2D(BackBuffer, uv + far * float2(-1.0, 1.0)).rgb, luma_weights); + float b22 = dot(tex2D(BackBuffer, uv + far * float2( 1.0, 1.0)).rgb, luma_weights); + + float gx = (a00 + a02) - (a20 + a22); + float gy = (a00 + a20) - (a02 + a22); + float gradient = sqrt(gx * gx + gy * gy); + float ink = smoothstep(OutlineThreshold, OutlineThreshold + 0.04, gradient); + + float lighting = (a00 + a20 + a02 + a22 + b00 + b20 + b02 + b22) * 0.125; + float detail = luma - lighting; + + float softness = max(BandEdge, 0.001); + float coord = lighting * Bands - softness * 0.5; + float index = floor(coord) + smoothstep(1.0 - softness, 1.0, frac(coord)); + + float centred = (index + 0.5) / Bands; + float stretched = index / max(Bands - 1.0, 1.0); + float banded = saturate(lerp(centred, stretched, BandContrast)); + + float target = saturate(banded + detail * Detail); + + float gain = min(target / max(luma, 0.001), 4.0); + float3 shaded = centre * gain; + float3 grey = dot(shaded, luma_weights); + float3 color = lerp(grey, shaded, Saturation); + color *= 1.0 - ink * OutlineStrength; + + return float4(saturate(color), 1.0); +} + +technique CelShading < + ui_label = "Cel Shading"; + ui_tooltip = "Collapses the lighting into a few hard steps and inks the edges, keeping texture detail and hue intact."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_CelShading; + } +} diff --git a/dist/post_shaders/ChromaticAberration.fx b/dist/post_shaders/ChromaticAberration.fx new file mode 100644 index 0000000000..e051a038ef --- /dev/null +++ b/dist/post_shaders/ChromaticAberration.fx @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Strength < + ui_type = "slider"; + ui_label = "Strength"; + ui_tooltip = "Channel separation in pixels, measured at the edge of the screen."; + ui_min = 0.0; ui_max = 8.0; ui_step = 0.1; +> = 1.5; + +uniform float Falloff < + ui_type = "slider"; + ui_label = "Falloff"; + ui_tooltip = "How fast the separation grows away from the centre. Higher keeps the middle clean."; + ui_min = 1.0; ui_max = 4.0; ui_step = 0.1; +> = 2.0; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float4 PS_ChromaticAberration(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT); + + float2 direction = uv - float2(0.5, 0.5); + float radius = length(direction); + float2 unit = direction / max(radius, 0.0001); + float2 offset = unit * Strength * pow(radius * 2.0, Falloff) * texel; + + float red = tex2D(BackBuffer, uv + offset).r; + float green = tex2D(BackBuffer, uv).g; + float blue = tex2D(BackBuffer, uv - offset).b; + + return float4(red, green, blue, 1.0); +} + +technique ChromaticAberration < + ui_label = "Chromatic Aberration"; + ui_tooltip = "Splits the colour channels apart towards the edges of the screen, the way a cheap lens fails to focus every colour on the same spot."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_ChromaticAberration; + } +} diff --git a/dist/post_shaders/ColorGrade.fx b/dist/post_shaders/ColorGrade.fx new file mode 100644 index 0000000000..cf1f10ecad --- /dev/null +++ b/dist/post_shaders/ColorGrade.fx @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project +// SPDX-License-Identifier: GPL-2.0-or-later +// Based on colorcorrection.fsh in PPSSPP. + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Saturation < + ui_type = "slider"; + ui_label = "Saturation"; + ui_min = 0.0; ui_max = 2.0; ui_step = 0.01; +> = 1.0; + +uniform float Brightness < + ui_type = "slider"; + ui_label = "Brightness"; + ui_min = 0.0; ui_max = 2.0; ui_step = 0.01; +> = 1.0; + +uniform float Contrast < + ui_type = "slider"; + ui_label = "Contrast"; + ui_min = 0.0; ui_max = 2.0; ui_step = 0.01; +> = 1.0; + +uniform float Gamma < + ui_type = "slider"; + ui_label = "Gamma"; + ui_min = 0.5; ui_max = 2.0; ui_step = 0.01; +> = 1.0; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float4 PS_ColorGrade(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float3 rgb = tex2D(BackBuffer, uv).rgb; + + float luma = dot(rgb, float3(0.2126, 0.7152, 0.0722)); + rgb = lerp(float3(luma, luma, luma), rgb, Saturation); + rgb *= Brightness; + rgb = (rgb - 0.5) * Contrast + 0.5; + rgb = pow(max(rgb, 0.0), 1.0 / max(Gamma, 0.0001)); + + return float4(saturate(rgb), 1.0); +} + +technique ColorGrade < + ui_label = "Colour Grade"; + ui_tooltip = "The four basic colour controls in one pass: saturation, brightness, contrast and gamma. Reach for this before anything more specialised."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_ColorGrade; + } +} diff --git a/dist/post_shaders/Deband.fx b/dist/post_shaders/Deband.fx new file mode 100644 index 0000000000..96086a9c6c --- /dev/null +++ b/dist/post_shaders/Deband.fx @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2015 Niklas Haas +// SPDX-License-Identifier: MIT + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Threshold < + ui_type = "slider"; + ui_label = "Threshold"; + ui_tooltip = "How flat a neighbourhood must be before it gets smoothed. Raise it to catch wider bands, lower it to keep more detail."; + ui_min = 0.002; ui_max = 0.05; ui_step = 0.001; +> = 0.012; + +uniform float Radius < + ui_type = "slider"; + ui_label = "Radius"; + ui_tooltip = "How far the sampling reaches, in pixels. Wider gradients need a larger radius."; + ui_min = 1.0; ui_max = 32.0; ui_step = 1.0; +> = 8.0; + +uniform float Grain < + ui_type = "slider"; + ui_label = "Dither"; + ui_tooltip = "Noise added to break up whatever banding survives the smoothing."; + ui_min = 0.0; ui_max = 0.02; ui_step = 0.001; +> = 0.004; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float Hash(float2 p) +{ + float3 scattered = frac(float3(p.x, p.y, p.x) * 0.1031); + scattered += dot(scattered, scattered.yzx + 33.33); + return frac((scattered.x + scattered.y) * scattered.z); +} + +float4 PS_Deband(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT); + + float3 centre = tex2D(BackBuffer, uv).rgb; + float base = Hash(pos.xy) * 6.2831853; + + float3 total = float3(0.0, 0.0, 0.0); + float3 deviation = float3(0.0, 0.0, 0.0); + + [unroll] for (int ring = 1; ring <= 2; ++ring) + { + float angle = base + float(ring) * 2.3999632; + float reach = Radius * float(ring) * 0.5; + float2 along = float2(cos(angle), sin(angle)) * reach; + float2 across = float2(-along.y, along.x); + + float3 s0 = tex2D(BackBuffer, uv + along * texel).rgb; + float3 s1 = tex2D(BackBuffer, uv - along * texel).rgb; + float3 s2 = tex2D(BackBuffer, uv + across * texel).rgb; + float3 s3 = tex2D(BackBuffer, uv - across * texel).rgb; + + total += s0 + s1 + s2 + s3; + deviation = max(deviation, max(max(abs(s0 - centre), abs(s1 - centre)), + max(abs(s2 - centre), abs(s3 - centre)))); + } + + float3 average = total * 0.125; + float3 flatness = float3(1.0, 1.0, 1.0) - + smoothstep(Threshold * 0.5, Threshold, deviation); + float3 result = lerp(centre, average, flatness); + + float dither = (Hash(pos.xy + float2(71.3, 41.7)) - 0.5) * Grain; + + return float4(saturate(result + dither), 1.0); +} + +technique Deband < + ui_label = "Deband"; + ui_tooltip = "Smooths the visible steps in gradients such as skies, then dithers whatever survives. Worth adding whenever large flat areas show rings."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_Deband; + } +} diff --git a/dist/post_shaders/Denoise.fx b/dist/post_shaders/Denoise.fx new file mode 100644 index 0000000000..c87e33a446 --- /dev/null +++ b/dist/post_shaders/Denoise.fx @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2019-2021 bloc97 +// SPDX-License-Identifier: MIT + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Strength < + ui_type = "slider"; + ui_label = "Strength"; + ui_min = 0.01; ui_max = 0.5; ui_step = 0.01; +> = 0.1; + +uniform float Radius < + ui_type = "slider"; + ui_label = "Radius"; + ui_min = 0.3; ui_max = 2.0; ui_step = 0.05; +> = 1.0; + +uniform float Curve < + ui_type = "slider"; + ui_label = "Shadow Bias"; + ui_min = 0.0; ui_max = 2.0; ui_step = 0.05; +> = 1.0; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float3 IntensityWeight(float3 value, float3 sigma, float3 centre) +{ + float3 scaled = (value - centre) / sigma; + return exp(-0.5 * scaled * scaled); +} + +float SpatialWeight(float distance, float sigma) +{ + float scaled = distance / sigma; + return exp(-0.5 * scaled * scaled); +} + +float4 PS_Denoise(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT); + + float3 centre = tex2D(BackBuffer, uv).rgb; + float3 intensity_sigma = max(pow(centre + 0.0001, Curve) * Strength, 0.0001); + float spatial_sigma = max(Radius, 0.05); + + float3 sum = float3(0.0, 0.0, 0.0); + float3 total = float3(0.0, 0.0, 0.0); + + [unroll] for (int y = -2; y <= 2; ++y) + { + [unroll] for (int x = -2; x <= 2; ++x) + { + float2 offset = float2(x, y); + float3 tap = tex2D(BackBuffer, uv + offset * texel).rgb; + float3 weight = IntensityWeight(tap, intensity_sigma, centre) * + SpatialWeight(length(offset), spatial_sigma); + sum += weight * tap; + total += weight; + } + } + + return float4(sum / total, 1.0); +} + +technique Denoise < + ui_label = "Denoise"; + ui_tooltip = "Edge preserving blur that clears dithering and compression noise while leaving outlines sharp. Ported from Anime4K."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_Denoise; + } +} diff --git a/dist/post_shaders/FilmGrain.fx b/dist/post_shaders/FilmGrain.fx new file mode 100644 index 0000000000..4e21c18b20 --- /dev/null +++ b/dist/post_shaders/FilmGrain.fx @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Intensity < + ui_type = "slider"; + ui_label = "Intensity"; + ui_min = 0.0; ui_max = 0.2; ui_step = 0.005; +> = 0.03; + +uniform float Size < + ui_type = "slider"; + ui_label = "Size"; + ui_tooltip = "Grain cell size in pixels."; + ui_min = 1.0; ui_max = 4.0; ui_step = 1.0; +> = 1.0; + +uniform float Colored < + ui_type = "slider"; + ui_label = "Colour"; + ui_tooltip = "Zero gives monochrome grain, one gives independent noise per channel."; + ui_min = 0.0; ui_max = 1.0; ui_step = 0.01; +> = 0.0; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float Hash(float2 p) +{ + float3 scattered = frac(float3(p.x, p.y, p.x) * 0.1031); + scattered += dot(scattered, scattered.yzx + 33.33); + return frac((scattered.x + scattered.y) * scattered.z); +} + +float4 PS_FilmGrain(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float3 rgb = tex2D(BackBuffer, uv).rgb; + + float2 cell = floor(pos.xy / max(Size, 1.0)); + float mono = Hash(cell) - 0.5; + float3 chroma = float3(Hash(cell + 11.7), Hash(cell + 23.1), Hash(cell + 37.5)) - 0.5; + float3 noise = lerp(float3(mono, mono, mono), chroma, Colored); + + float luma = dot(rgb, float3(0.2126, 0.7152, 0.0722)); + float response = 1.0 - abs(luma * 2.0 - 1.0); + + return float4(saturate(rgb + noise * Intensity * response), 1.0); +} + +technique FilmGrain < + ui_label = "Film Grain"; + ui_tooltip = "Adds photographic grain, strongest in the midtones and fading out in blacks and whites."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_FilmGrain; + } +} diff --git a/dist/post_shaders/FilmicCurve.fx b/dist/post_shaders/FilmicCurve.fx new file mode 100644 index 0000000000..2b1b1e0773 --- /dev/null +++ b/dist/post_shaders/FilmicCurve.fx @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Exposure < + ui_type = "slider"; + ui_label = "Exposure"; + ui_min = 0.5; ui_max = 2.0; ui_step = 0.01; +> = 1.0; + +uniform float Toe < + ui_type = "slider"; + ui_label = "Toe"; + ui_tooltip = "Above 1.0 deepens the shadows, below 1.0 lifts them."; + ui_min = 0.5; ui_max = 2.0; ui_step = 0.01; +> = 1.2; + +uniform float Shoulder < + ui_type = "slider"; + ui_label = "Shoulder"; + ui_tooltip = "Above 1.0 opens up the highlights, below 1.0 compresses them."; + ui_min = 0.5; ui_max = 2.0; ui_step = 0.01; +> = 1.2; + +uniform float Amount < + ui_type = "slider"; + ui_label = "Amount"; + ui_min = 0.0; ui_max = 1.0; ui_step = 0.01; +> = 0.7; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float4 PS_FilmicCurve(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float3 rgb = tex2D(BackBuffer, uv).rgb; + + float3 curved = saturate(rgb * Exposure); + curved = pow(max(curved, 0.0), Toe); + curved = 1.0 - pow(max(1.0 - curved, 0.0), Shoulder); + + return float4(saturate(lerp(rgb, curved, Amount)), 1.0); +} + +technique FilmicCurve < + ui_label = "Filmic Curve"; + ui_tooltip = "Filmic contrast curve. Deepens the shadows and opens the highlights without clipping either end."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_FilmicCurve; + } +} diff --git a/dist/post_shaders/LensDistortion.fx b/dist/post_shaders/LensDistortion.fx new file mode 100644 index 0000000000..8e0649bfe7 --- /dev/null +++ b/dist/post_shaders/LensDistortion.fx @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Distortion < + ui_type = "slider"; + ui_label = "Distortion"; + ui_tooltip = "Positive bulges the picture outwards, negative pinches it inwards."; + ui_min = -0.5; ui_max = 0.5; ui_step = 0.01; +> = 0.1; + +uniform float Zoom < + ui_type = "slider"; + ui_label = "Zoom"; + ui_tooltip = "Scales the picture to hide the edges the warp pulls in."; + ui_min = 0.5; ui_max = 1.5; ui_step = 0.01; +> = 1.0; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float4 PS_LensDistortion(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float aspect = BUFFER_WIDTH * BUFFER_RCP_HEIGHT; + float2 half_size = float2(aspect, 1.0); + float2 unit = half_size / length(half_size); + + float2 centred = (uv - 0.5) * 2.0 * unit; + float r2 = dot(centred, centred); + centred *= 1.0 + Distortion * r2; + centred /= max(Zoom, 0.001); + + float2 source = centred / (2.0 * unit) + 0.5; + + return float4(tex2D(BackBuffer, source).rgb, 1.0); +} + +technique LensDistortion < + ui_label = "Lens Distortion"; + ui_tooltip = "Barrel or pincushion warp, like looking through a wide angle lens."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_LensDistortion; + } +} diff --git a/dist/post_shaders/Levels.fx b/dist/post_shaders/Levels.fx new file mode 100644 index 0000000000..510fd3d070 --- /dev/null +++ b/dist/post_shaders/Levels.fx @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float InputBlack < + ui_type = "slider"; + ui_label = "Input Black"; + ui_tooltip = "Input level mapped to black. Raise it to deepen washed out shadows."; + ui_min = 0.0; ui_max = 0.5; ui_step = 0.005; +> = 0.0; + +uniform float InputWhite < + ui_type = "slider"; + ui_label = "Input White"; + ui_min = 0.5; ui_max = 1.0; ui_step = 0.005; +> = 1.0; + +uniform float Gamma < + ui_type = "slider"; + ui_label = "Gamma"; + ui_min = 0.2; ui_max = 3.0; ui_step = 0.01; +> = 1.0; + +uniform float OutputBlack < + ui_type = "slider"; + ui_label = "Output Black"; + ui_tooltip = "Lifts crushed shadows so detail stops collapsing into one flat black."; + ui_min = 0.0; ui_max = 0.5; ui_step = 0.005; +> = 0.0; + +uniform float OutputWhite < + ui_type = "slider"; + ui_label = "Output White"; + ui_min = 0.5; ui_max = 1.0; ui_step = 0.005; +> = 1.0; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float4 PS_Levels(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float3 rgb = tex2D(BackBuffer, uv).rgb; + + rgb = saturate((rgb - InputBlack) / max(InputWhite - InputBlack, 0.001)); + rgb = pow(max(rgb, 0.0), 1.0 / max(Gamma, 0.001)); + rgb = lerp(OutputBlack, OutputWhite, rgb); + + return float4(saturate(rgb), 1.0); +} + +technique Levels < + ui_label = "Levels"; + ui_tooltip = "Black point, white point, gamma and output range. Use it to fix crushed or washed out shadows."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_Levels; + } +} diff --git a/dist/post_shaders/MotionBlur.fx b/dist/post_shaders/MotionBlur.fx new file mode 100644 index 0000000000..d541b5db9d --- /dev/null +++ b/dist/post_shaders/MotionBlur.fx @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Length < + ui_type = "slider"; + ui_label = "Length"; + ui_tooltip = "How far the streaks reach, as a percentage of screen height."; + ui_min = 0.0; ui_max = 20.0; ui_step = 0.5; +> = 5.0; + +uniform float Zoom < + ui_type = "slider"; + ui_label = "Zoom"; + ui_tooltip = "Streaks running out from the centre of the screen, as if rushing forward. The centre stays sharp."; + ui_min = 0.0; ui_max = 1.0; ui_step = 0.05; +> = 1.0; + +uniform float Pan < + ui_type = "slider"; + ui_label = "Pan"; + ui_tooltip = "Streaks running in one fixed direction, as if the camera were sweeping across."; + ui_min = 0.0; ui_max = 1.0; ui_step = 0.05; +> = 0.0; + +uniform float Angle < + ui_type = "slider"; + ui_label = "Pan Angle"; + ui_tooltip = "Direction of the sweep, in degrees."; + ui_min = 0.0; ui_max = 360.0; ui_step = 5.0; +> = 0.0; + +uniform float Spin < + ui_type = "slider"; + ui_label = "Spin"; + ui_tooltip = "Streaks curling around the centre, as if the camera were rolling. Negative turns the other way."; + ui_min = -1.0; ui_max = 1.0; ui_step = 0.05; +> = 0.0; + +static const int TAPS = 24; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float4 PS_MotionBlur(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float aspect = BUFFER_WIDTH * BUFFER_RCP_HEIGHT; + float2 to_screen = float2(aspect, 1.0); + + float2 centred = (uv - 0.5) * to_screen * 2.0; + float2 outward = centred; + float2 around = float2(-centred.y, centred.x); + + float radians_angle = Angle * 0.01745329; + float2 sweep = float2(cos(radians_angle), sin(radians_angle)); + + float2 velocity = sweep * Pan + outward * Zoom + around * Spin; + velocity *= Length * 0.01; + velocity /= to_screen; + + float jitter = frac(sin(dot(pos.xy, float2(12.9898, 78.233))) * 43758.5453); + + float3 sum = float3(0.0, 0.0, 0.0); + [unroll] for (int i = 0; i < TAPS; ++i) + { + float t = (float(i) + jitter) / float(TAPS) - 0.5; + sum += tex2D(BackBuffer, uv + velocity * t).rgb; + } + + return float4(sum / float(TAPS), 1.0); +} + +technique MotionBlur < + ui_label = "Motion Blur"; + ui_tooltip = "Camera style motion blur: streaks the picture outwards, sideways or around, without touching still detail at the centre."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_MotionBlur; + } +} diff --git a/dist/post_shaders/NaturalColors.fx b/dist/post_shaders/NaturalColors.fx new file mode 100644 index 0000000000..0a35ad4ff2 --- /dev/null +++ b/dist/post_shaders/NaturalColors.fx @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project +// SPDX-License-Identifier: GPL-2.0-or-later +// Ported from naturalA.fsh in PPSSPP, by ShadX, modified by SimoneT. + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Luma < + ui_type = "slider"; + ui_label = "Luma Curve"; + ui_tooltip = "Gamma applied to the luminance channel in YIQ space."; + ui_min = 0.5; ui_max = 2.0; ui_step = 0.01; +> = 1.2; + +uniform float Chroma < + ui_type = "slider"; + ui_label = "Chroma Gain"; + ui_tooltip = "Boost applied to the two colour difference channels."; + ui_min = 0.0; ui_max = 2.0; ui_step = 0.01; +> = 1.2; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float4 PS_Natural(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + const float3x3 RGBtoYIQ = float3x3(0.299, 0.587, 0.114, + 0.596, -0.275, -0.321, + 0.212, -0.523, 0.311); + + const float3x3 YIQtoRGB = float3x3(1.0, 0.95568806, 0.61985809, + 1.0, -0.27158180, -0.64687382, + 1.0, -1.10817733, 1.70506456); + + float3 rgb = tex2D(BackBuffer, uv).rgb; + float3 yiq = mul(RGBtoYIQ, rgb); + + yiq.x = pow(max(yiq.x, 0.0), Luma); + yiq.yz *= Chroma; + + return float4(saturate(mul(YIQtoRGB, yiq)), 1.0); +} + +technique NaturalColors < + ui_label = "Natural Colours"; + ui_tooltip = "Warms the picture and lifts saturation for a less washed out look. Ported from the PPSSPP shader."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_Natural; + } +} diff --git a/dist/post_shaders/Reflections.fx b/dist/post_shaders/Reflections.fx new file mode 100644 index 0000000000..1ab193481e --- /dev/null +++ b/dist/post_shaders/Reflections.fx @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Timer < source = "timer"; > = 0.0; + +uniform float Horizon < + ui_type = "slider"; + ui_label = "Reflection Line"; + ui_tooltip = "Height on screen where the reflective floor begins. Everything above it is left untouched."; + ui_min = 0.0; ui_max = 1.0; ui_step = 0.01; +> = 0.55; + +uniform float Amount < + ui_type = "slider"; + ui_label = "Amount"; + ui_tooltip = "How strongly the reflection shows through the floor."; + ui_min = 0.0; ui_max = 1.0; ui_step = 0.05; +> = 0.35; + +uniform float Falloff < + ui_type = "slider"; + ui_label = "Falloff"; + ui_tooltip = "How quickly the reflection fades as the floor comes towards the viewer. At zero it stays even."; + ui_min = 0.0; ui_max = 4.0; ui_step = 0.1; +> = 1.2; + +uniform float Perspective < + ui_type = "slider"; + ui_label = "Perspective"; + ui_tooltip = "Stretches or squashes the mirrored image. One is a true mirror, higher values pull it towards the line."; + ui_min = 0.2; ui_max = 2.0; ui_step = 0.05; +> = 1.0; + +uniform float Ripple < + ui_type = "slider"; + ui_label = "Ripple"; + ui_tooltip = "Amplitude of the waves disturbing the reflection. At zero the mirror is perfectly still."; + ui_min = 0.0; ui_max = 1.0; ui_step = 0.05; +> = 0.0; + +uniform float RippleSpeed < + ui_type = "slider"; + ui_label = "Ripple Speed"; + ui_tooltip = "How fast the waves travel."; + ui_min = 0.0; ui_max = 4.0; ui_step = 0.1; +> = 1.0; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float4 PS_Reflections(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float3 color = tex2D(BackBuffer, uv).rgb; + + float depth = uv.y - Horizon; + float on_floor = step(0.0, depth); + float span = max(1.0 - Horizon, 0.001); + float distance_down = saturate(depth / span); + + float seconds = Timer * 0.001; + float wave = sin(uv.x * 38.0 + seconds * RippleSpeed * 2.0) * + sin(uv.y * 21.0 - seconds * RippleSpeed * 1.3); + float2 disturbance = float2(wave * 0.004, wave * 0.002) * Ripple * distance_down; + + float2 mirrored = float2(uv.x, Horizon - depth * Perspective) + disturbance; + float3 reflection = tex2D(BackBuffer, saturate(mirrored)).rgb; + + float fade = pow(max(1.0 - distance_down, 0.0001), Falloff); + float strength = Amount * fade * on_floor; + + return float4(lerp(color, reflection, strength), 1.0); +} + +technique Reflections < + ui_label = "Reflections"; + ui_tooltip = "Mirrors the picture into a reflective floor below a line you choose, with optional water ripples."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_Reflections; + } +} diff --git a/dist/post_shaders/Scanlines.fx b/dist/post_shaders/Scanlines.fx new file mode 100644 index 0000000000..2f24dc5ed5 --- /dev/null +++ b/dist/post_shaders/Scanlines.fx @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project +// SPDX-License-Identifier: GPL-2.0-or-later +// Ported from scanlines.fsh in PPSSPP. + + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Density < + ui_type = "slider"; + ui_label = "Line Density"; + ui_tooltip = "Number of scanline pairs across the screen."; + ui_min = 60.0; ui_max = 720.0; ui_step = 10.0; +> = 340.0; + +uniform float Intensity < + ui_type = "slider"; + ui_label = "Intensity"; + ui_tooltip = "How dark the gaps between lines become."; + ui_min = 0.0; ui_max = 1.0; ui_step = 0.05; +> = 0.5; + +uniform float Tint < + ui_type = "slider"; + ui_label = "Phosphor Tint"; + ui_tooltip = "Strength of the green-warm phosphor cast."; + ui_min = 0.0; ui_max = 1.0; ui_step = 0.05; +> = 1.0; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float4 PS_Scanlines(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float line_pos = uv.y * Density * 0.5; + float gate = cos((frac(line_pos) - 0.5) * 3.1415926 * Intensity) * 1.5; + + float3 rgb = tex2D(BackBuffer, uv).rgb; + float3 color = rgb * 0.5 + 0.5 * rgb * rgb * 1.2; + + float3 phosphor = lerp(float3(1.0, 1.0, 1.0), float3(0.9, 1.0, 0.7), Tint); + color *= phosphor; + + float2 diff = uv - 0.5; + color *= 1.1 - 0.6 * (dot(diff, diff) * 2.0); + + return float4(saturate(color * saturate(gate)), 1.0); +} + +technique Scanlines < + ui_label = "Scanlines"; + ui_tooltip = "Darkens alternating rows of pixels to imitate the horizontal scanlines of a CRT. Cheaper than the full CRT effect when you only want the lines."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_Scanlines; + } +} diff --git a/dist/post_shaders/Sharpen.fx b/dist/post_shaders/Sharpen.fx new file mode 100644 index 0000000000..36850e818b --- /dev/null +++ b/dist/post_shaders/Sharpen.fx @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Amount < + ui_type = "slider"; + ui_label = "Amount"; + ui_min = 0.0; ui_max = 3.0; ui_step = 0.01; +> = 0.6; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float4 PS_Sharpen(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float2 texel = float2(BUFFER_RCP_WIDTH, BUFFER_RCP_HEIGHT); + + float3 centre = tex2D(BackBuffer, uv).rgb; + float3 blur = tex2D(BackBuffer, uv + float2(-texel.x, 0.0)).rgb; + blur += tex2D(BackBuffer, uv + float2(texel.x, 0.0)).rgb; + blur += tex2D(BackBuffer, uv + float2(0.0, -texel.y)).rgb; + blur += tex2D(BackBuffer, uv + float2(0.0, texel.y)).rgb; + blur *= 0.25; + + return float4(centre + (centre - blur) * Amount, 1.0); +} + +technique Sharpen < + ui_label = "Sharpen"; + ui_tooltip = "Unsharp mask that brings back edge detail lost to scaling. Most useful after upscaling a low internal resolution."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_Sharpen; + } +} diff --git a/dist/post_shaders/SplitToning.fx b/dist/post_shaders/SplitToning.fx new file mode 100644 index 0000000000..682ee5cf69 --- /dev/null +++ b/dist/post_shaders/SplitToning.fx @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float ShadowHue < + ui_type = "slider"; + ui_label = "Shadow Hue"; + ui_min = 0.0; ui_max = 360.0; ui_step = 1.0; +> = 210.0; + +uniform float ShadowStrength < + ui_type = "slider"; + ui_label = "Shadow Strength"; + ui_min = 0.0; ui_max = 1.0; ui_step = 0.01; +> = 0.0; + +uniform float HighlightHue < + ui_type = "slider"; + ui_label = "Highlight Hue"; + ui_min = 0.0; ui_max = 360.0; ui_step = 1.0; +> = 45.0; + +uniform float HighlightStrength < + ui_type = "slider"; + ui_label = "Highlight Strength"; + ui_min = 0.0; ui_max = 1.0; ui_step = 0.01; +> = 0.0; + +uniform float Balance < + ui_type = "slider"; + ui_label = "Balance"; + ui_tooltip = "Moves the split between what counts as shadow and what counts as highlight."; + ui_min = -0.5; ui_max = 0.5; ui_step = 0.01; +> = 0.0; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float3 HueToRGB(float hue) +{ + float h = frac(hue / 360.0) * 6.0; + return saturate(float3(abs(h - 3.0) - 1.0, 2.0 - abs(h - 2.0), 2.0 - abs(h - 4.0))); +} + +float4 PS_SplitToning(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float3 rgb = tex2D(BackBuffer, uv).rgb; + + float luma = saturate(dot(rgb, float3(0.2126, 0.7152, 0.0722)) + Balance); + + float3 shadow_tint = HueToRGB(ShadowHue) - 0.5; + float3 highlight_tint = HueToRGB(HighlightHue) - 0.5; + + rgb += shadow_tint * ShadowStrength * 0.25 * (1.0 - luma); + rgb += highlight_tint * HighlightStrength * 0.25 * luma; + + return float4(saturate(rgb), 1.0); +} + +technique SplitToning < + ui_label = "Split Toning"; + ui_tooltip = "Tints the shadows and the highlights towards two different hues."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_SplitToning; + } +} diff --git a/dist/post_shaders/Vignette.fx b/dist/post_shaders/Vignette.fx new file mode 100644 index 0000000000..351990a43e --- /dev/null +++ b/dist/post_shaders/Vignette.fx @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2012 PPSSPP Project +// SPDX-License-Identifier: GPL-2.0-or-later +// Ported from vignette.fsh in PPSSPP, by Henrik Rydgard. + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Strength < + ui_type = "slider"; + ui_label = "Strength"; + ui_tooltip = "How dark the corners become."; + ui_min = 0.0; ui_max = 2.0; ui_step = 0.01; +> = 0.6; + +uniform float Aspect < + ui_type = "slider"; + ui_label = "Aspect"; + ui_min = 0.5; ui_max = 2.0; ui_step = 0.01; +> = 1.0; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float4 PS_Vignette(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float2 diff = uv - 0.5; + diff.x *= Aspect; + diff.y /= max(Aspect, 0.0001); + + float falloff = 1.0 - min(1.0, Strength * dot(diff, diff) * 2.0); + float3 rgb = tex2D(BackBuffer, uv).rgb; + + return float4(rgb * falloff, 1.0); +} + +technique Vignette < + ui_label = "Vignette"; + ui_tooltip = "Darkens the corners of the screen to pull the eye towards the middle, the way a camera lens falls off at its edges."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_Vignette; + } +} diff --git a/dist/post_shaders/WhiteBalance.fx b/dist/post_shaders/WhiteBalance.fx new file mode 100644 index 0000000000..29aea52fc2 --- /dev/null +++ b/dist/post_shaders/WhiteBalance.fx @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +texture BackBufferTex : COLOR; +sampler BackBuffer { Texture = BackBufferTex; }; + +uniform float Temperature < + ui_type = "slider"; + ui_label = "Temperature"; + ui_tooltip = "Negative cools the picture towards blue, positive warms it towards orange."; + ui_min = -100.0; ui_max = 100.0; ui_step = 1.0; +> = 0.0; + +uniform float Tint < + ui_type = "slider"; + ui_label = "Tint"; + ui_tooltip = "Negative shifts towards green, positive towards magenta."; + ui_min = -100.0; ui_max = 100.0; ui_step = 1.0; +> = 0.0; + +void VS_PostProcess(in uint id : SV_VertexID, out float4 pos : SV_Position, out float2 uv : TEXCOORD) +{ + uv = float2(float(id & 2), float((id & 1) << 1)); + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} + +float3 WhitePointLMS(float t1, float t2) +{ + float shift = 0.05; + if (t1 < 0.0) + { + shift = 0.10; + } + float x = 0.31271 - t1 * shift; + float y = 2.87 * x - 3.0 * x * x - 0.27509507 + t2 * 0.05; + + float big_y = 1.0; + float big_x = big_y * x / y; + float big_z = big_y * (1.0 - x - y) / y; + + return float3( 0.7328 * big_x + 0.4296 * big_y - 0.1624 * big_z, + -0.7036 * big_x + 1.6975 * big_y + 0.0061 * big_z, + 0.0030 * big_x + 0.0136 * big_y + 0.9834 * big_z); +} + +float4 PS_WhiteBalance(float4 pos : SV_Position, float2 uv : TEXCOORD) : SV_Target +{ + float3 rgb = pow(max(tex2D(BackBuffer, uv).rgb, 0.0), 2.2); + + float3 balance = float3(0.949237, 1.03542, 1.08728) / + WhitePointLMS(Temperature / 65.0, Tint / 65.0); + + const float3x3 rgb_to_lms = float3x3(0.390405, 0.549941, 0.008926, + 0.070841, 0.963172, 0.001358, + 0.023108, 0.128021, 0.936245); + const float3x3 lms_to_rgb = float3x3( 2.858470, -1.628790, -0.024891, + -0.210182, 1.158200, 0.000324, + -0.041812, -0.118169, 1.068670); + + float3 lms = mul(rgb_to_lms, rgb) * balance; + rgb = mul(lms_to_rgb, lms); + + return float4(saturate(pow(max(rgb, 0.0), 1.0 / 2.2)), 1.0); +} + +technique WhiteBalance < + ui_label = "White Balance"; + ui_tooltip = "Corrects a picture that looks too cool, too warm or tinted."; +> +{ + pass + { + VertexShader = VS_PostProcess; + PixelShader = PS_WhiteBalance; + } +} diff --git a/dist/qt_themes/default/style.qss b/dist/qt_themes/default/style.qss index c56ca54ce3..8f3115b3af 100644 --- a/dist/qt_themes/default/style.qss +++ b/dist/qt_themes/default/style.qss @@ -509,6 +509,8 @@ QWidget#contentRichDialog QLabel#label_title_rich { } QWidget#contentDialog QLabel#label_dialog { + background: #2E2E2E; + padding: 20px 65px; } diff --git a/docs/Build.md b/docs/Build.md index 394fe4871d..84a3784fc0 100644 --- a/docs/Build.md +++ b/docs/Build.md @@ -5,6 +5,15 @@ This is a full-fledged guide to build Eden on all supported platforms. +If you already have a development environment set up: + +```sh +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -G Ninja +cmake --build build +``` + +On Linux, macOS, and MinGW/MSYS2, make sure to read the [dependencies guide](Deps.md). On Android, read its [dedicated page](build/Android.md). + ## Dependencies First, you must [install some dependencies](Deps.md). @@ -125,17 +134,27 @@ cmake -S . -B build -G "" -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COM -- 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 +- 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 -- Now run by clicking the play button or pressing Shift+F10, and eden will auto-launch once built. +- Now run by clicking the play button or pressing Shift+F10, and Eden will auto-launch once built. +#### Option D: Visual Studio with clang-cl + +
+1. Install `"x64 Native Tools Command Prompt"` for VS from the installer and also install `cmake-gui`. +2. Open `"x64 Native Tools Command Prompt"` and type `cmake-gui`. +3. Click configure choose ninja generator > specify native compilers. +4. Put `"C:/Program Files/Microsoft Visual Studio/18/Community/VC/Tools/Llvm/x64/bin/clang-cl.exe"` as your C/C++ compiler path. +5. Open `Visual studio > Open project` or Solution > Change to search for the CMake project file (`CMakeList.txt`) file on the cloned directory, and then build. +
+ ## Troubleshooting If your initial configure failed: @@ -143,7 +162,7 @@ If your initial configure failed: - *Carefully* re-read the [dependencies guide](Deps.md) - Clear the CPM cache (`.cache/cpm`) and CMake cache (`/CMakeCache.txt`) - Evaluate the error and find any related settings -- See the [CPM docs](CPM.md) to see if you may need to forcefully bundle any packages +- See the [CPM docs](CPMUtil.md) to see if you may need to forcefully bundle any packages Otherwise, feel free to ask for help in Stoat or Discord. diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index 8da1f3d949..3bfc2314b8 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -1,5 +1,5 @@ # ui stuff -/src/android @AleksandrPopovich @kleidis @Producdevity +/src/android @AleksandrPopovich @Producdevity /src/yuzu @crueter /src/eden @crueter /src/frontend_common @crueter diff --git a/docs/CPMUtil.md b/docs/CPMUtil.md new file mode 100644 index 0000000000..8cebaaee42 --- /dev/null +++ b/docs/CPMUtil.md @@ -0,0 +1,372 @@ +# CPMUtil + +CPMUtil is a wrapper around CPM that aims to reduce boilerplate and add useful utility functions to make dependency management a piece of cake. + +- [CPMUtil](#cpmutil) + - [Global Options](#global-options) + - [About](#about) + - [Common Properties](#common-properties) + - [Standard Packages](#standard-packages) + - [Versioning](#versioning) + - [Artifact Naming Errata](#artifact-naming-errata) + - [Patches](#patches) + - [Pre-built CI Packages](#pre-built-ci-packages) + - [Usage](#usage) + - [Addendum: Cache Storage](#addendum-cache-storage) + - [Addendum: Making Patches](#addendum-making-patches) + - [Addendum: Package Identification Lists](#addendum-package-identification-lists) + - [Addendum: Notes for Packagers](#addendum-notes-for-packagers) + - [Network Sandbox](#network-sandbox) + - [Unsandboxed](#unsandboxed) + - [Addendum: Dependent Packages](#addendum-dependent-packages) + - [Example: Vulkan](#example-vulkan) + - [Addendum: Module Path Packages](#addendum-module-path-packages) + - [Example: OpenSSL](#example-openssl) + - [Addendum: Adding Qt](#addendum-adding-qt) + - [Addendum: Package-Specific Overrides](#addendum-package-specific-overrides) + - [Addendum: Supply-Chain Security](#addendum-supply-chain-security) + - [Checksumming](#checksumming) + - [Caching](#caching) + - [Immutable Commit Hashes](#immutable-commit-hashes) + +## Global Options + +- `CPMUTIL_FORCE_SYSTEM` (default `OFF`): Require all CPM dependencies to use system packages. + - You may optionally override this for each package: [Package-Specific Overrides](#addendum-package-specific-overrides) +- `CPMUTIL_FORCE_BUNDLED` (default `ON` on MSVC and Android, `OFF` elsewhere): Require all CPM dependencies to use bundled packages. + - You may optionally override this for each package: [Package-Specific Overrides](#addendum-package-specific-overrides) +- `CPMUTIL_PATCH_DIR` (default `${PROJECT_SOURCE_DIR}/.patch`): Path to patches used in packages. Stored as `/json-package-name/0001-patch-name.patch`, etc. +- `CPM_SOURCE_CACHE` (default `${PROJECT_SOURCE_DIR}/.cache/cpm`): Where downloaded dependencies get stored. + +## About + +CPMUtil works by defining dependencies in a JSON file, `cpmfile.json`, and calling `AddJsonPackage`. These dependencies generally must define, at minimum: + +- The repository and Git host +- A release artifact, commit, or tag archive to download +- A SHA512 sum for the downloaded artifact + +And may optionally define other properties like: + +- The minimum version for system packages +- The package name used for system packages (this defaults to the json key if undefined) +- In-tree source patches +- Options passed to CMake +- Options passed to find_package + +For instance: + +```json +{ + "fmt": { + "repo": "fmtlib/fmt", + "version": "12.1.0", + "hash": "f0da8...23f2", + "min_version": "8", + "options": [ + "FMT_TEST ON" + ], + "patches": [ + "0001-disable-reference-copy.patch" + ] + } +} +``` + +Calling `AddJsonPackage(fmt)`: + +- Searches for a system package named `fmt` of version 8 or higher (`find_package(fmt 8)`) +- If found, uses the system package and caches it for future use +- If not found: + - Downloads fmt 12.1.0 from the GitHub Archive into `.cache/cpm/fmt/12.1.0` + - Verifies the hash + - Applies the `0001-disable-reference-copy.patch` patch to the source tree + - Sets `FMT_TEST` to `ON` + - Adds the downloaded directory to CMake + - Now, future `find_package(fmt)` calls will use the downloaded package + +There are two types of packages CPMUtil can define: standard and prebuilt CI packages. Some properties are common to both types, however. + +## Common Properties + +These JSON properties are used by standard and CI packages alike. + +- `package`: The package name used by `find_package` to check for the existence of a system package. + - If unset, defaults to the JSON key +- `repo`: The Git repository the package is stored in, if applicable. +- `version`: The version of the package. This is required. + - Generally, this should be a 10-wide Git commit hash or Git tag. + - Tags must be fully qualified and include prefixes and suffixes, e.g. `boost-1.88.0` +- `git_host`: The Git host the package is stored in, if applicable. Defaults to `github.com`. + +## Standard Packages + +Normal packages, like the prior `fmt` example, *must* also define: + +- `hash`: The SHA512 hash of the downloaded artifact. CPMUtil generally computes this for you--if not, use `tools/cpmutil.sh package hash ` +- `min_version`: The minimum required version of the package, if a system package is desired. + +And may optionally define: + +- `url`: Download from a raw URL. +- `artifact`: A GitHub/Forgejo/Gitea release artifact. Requires `repo` to be set and valid. +- `numeric_version`: Replaces `%NUMERIC_VERSION%` in artifact and version definitions; see [Artifact Naming Errata](#artifact-naming-errata). + +See [Versioning](#versioning) for version/artifact information. + +The following are optional to define: + +- `source_subdir`: A subdirectory containing the `CMakeLists.txt` to configure a project. Useful for projects like `zstd`. +- `bundled`: Force the usage of a bundled package. Useful for packages where the system package is broken or nonexistent; e.g. external fragment shaders or data archives. + - Note that this will conflict with `CPMUTIL_FORCE_SYSTEM`; for this reason, when using non-library archives, it may be best to allow the user to download and extract the archive manually and specify a local directory to it. +- `find_args`: Additional arguments passed to `find_package`, e.g. `MODULE` +- `patches`: Array of in-tree patches to apply to the downloaded source code. See [#Patches](TODO). +- `options`: Array of CMake options to apply before configuring the package, e.g. `"FMT_TEST ON"`. + +### Versioning + +When fetching from a Git repository, there are generally three methods of versioning: + +- Commit hashes +- Git tags +- Release artifacts + +When `repo` is set, `version` field can be set to any commitish value, including commit hashes or Git tags. In the case of artifacts, `version` must be a Git tag, and `artifact` must be set to a release artifact attached to that tag. Many repositories intentionally version the filenames of their release artifacts; for this purpose, CPMUtil allows you to implant the version number into the artifact name. To do so, add `%VERSION%` to the artifact name; CPMUtil will then automatically replace `%VERSION%` with the `version` field. This means that when changing versions, you only need to update the version, not the artifact! + +Take Boost as an example. The artifact for Boost 1.90.0 is `boost-1.90.0-cmake.tar.xz`, and the tag is `boost-1.90.0`. Thus, we can set the artifact to `%VERSION%-cmake.tar.xz`: + +```json +"boost": { + "repo": "boostorg/boost", + "version": "boost-1.90.0", + "artifact": "%VERSION%-cmake.tar.xz" +} +``` + +The artifact will then evaluate as `boost-1.90.0-cmake.tar.xz`. + +### Artifact Naming Errata + +While `%VERSION%` replacement is generally good enough for well-packaged projects, occasionally there may be some problematic packages. Take, for instance, Vulkan Validation Layers: + +- Tag (`version`): `vulkan-sdk-1.4.341.0` +- Artifact: `android-binaries-1.4.341.0.zip` + +Attempting to add version replacement to the artifact definition **would not work here!** In this case, you must utilize the `numeric_version` field described earlier; we would set `numeric_version` to `1.4.341.0` and add `%NUMERIC_VERSION%` replacements into our artifact and version fields: + +```json +"vulkan-validation-layers": { + "artifact": "android-binaries-%NUMERIC_VERSION%.zip", + "repo": "KhronosGroup/Vulkan-ValidationLayers", + "version": "vulkan-sdk-%NUMERIC_VERSION%", + "numeric_version": "1.4.341.0" +}, +``` + +`artifact` will thus evaluate to `android-binaries-1.4.341.0.zip`, and `version` to `vulkan-sdk-1.4.341.0`. CPMUtil's auto-updater will also account for this and only update `numeric_version`! + +### Patches + +CPMUtil is able to apply in-place source tree patches to downloaded packages. These are defined in JSON as an array of names, preferably using `git-format-patch`'s scheme of `<4 digit number>-patch-name.patch`. + +They are stored in `/` (remember that `CPMUTIL_PATCH_DIR` defaults to `$ROOT/.patch`); e.g. `boost` patches would be in `.patch/boost`. Let's say we've made three patches and want to add them; in the Boost JSON definition, we would add: + +```json +"patches": [ + "0001-fix-clang-cl-compilation.patch", + "0002-fix-msvc-arm64-compilation.patch", + "0003-fix-bsd-linking.patch" +] +``` + +Then, when Boost is downloaded, it will apply these patches to the source tree in the order they are defined (compound/dependent patches are okay!). Note that when you add, remove, or modify patches, CPMUtil will invalidate your downloaded cache and re-fetch the source. + +To learn how to make patches, see [Addendum: Making Patches](#addendum-making-patches). + +## Pre-built CI Packages + +The definition and usage of CI packages is subject to change in the very near future. + +CI packages are, in essence, prebuilt binary distributions for libraries. They exist for a few reasons: + +- Creating static libraries for system packages that normally lack them, e.g. Qt/SDL +- Reducing duplicated compilation effort on rarely-changing externals, e.g. SDL +- Creating debloated prebuilt packages specifically for your project to reduce binary size, e.g. FFmpeg + +CPMUtil is specifically designed to work with a small subset of prebuilt CI packages; namely, those that follow the format of the [crueter-ci spec](https://github.com/crueter-ci/spec/blob/master/README.md). + +To use them, you must add `ci: true` to your package definition. Alongside the common properties, CI packages define the following: + +- `name`: The artifacts' name prefix (required), e.g. `openssl` +- `extension`: The artifacts' extension (default `tar.zst`) +- `disabled_platforms`: CPMUtil-supported platforms that are not built into this repository. + - This is subject to change. + +**Note that `package` is subject to removal here.** + +Example: + +```json +"sdl2": { + "repo": "crueter-ci/SDL2", + "package": "SDL2", + "min_version": "2.26.4", + "ci": true, + "version": "2.32.10-a65111bd2d", + "artifact": "SDL2" +}, +``` + +## Usage + +Once you've defined your package in `cpmfile.json`, simply call `AddJsonPackage()` and go from there! Specific instructions differ between individual packages, so you're on your own from here. + +If you're only concerned with basic usage, you can stop reading. For more advanced use cases and package management, read these addenda. + +## Addendum: Cache Storage + +CPMUtil stores downloaded packages within `.cache/cpm` by default (see `CPM_SOURCE_CACHE`). Subdirectories stored within are lowercase representations of the `find_package` name for the package; for instance, a `vulkan-headers` definition with `package: "VulkanHeaders"` would be stored in `.cache/cpm/vulkanheaders`. + +Within these subdirectories, additional directories are created for each individual version, corresponding directly to their `version` field. CI packages use `--` unconditionally. + +To see the cache directory for a given package, use `tools/cpmutil.sh package dir `. + +## Addendum: Making Patches + +CPMUtil has a dedicated command for making patches. You're recommended to have Git and a command line editor installed, but CPMUtil is able to work without either. To do so, follow these steps, noting your package's JSON key: + +- Clean-fetch your package: `tools/cpmutil.sh package reset ` +- Make any necessary modifications to the package source. + - You can access the package source directory via `tools/cpmutil.sh package dir `. +- Create the patch: `tools/cpmutil.sh package patch ` + - Follow the on-screen prompts. If you have Git installed, an editor will be opened so you can type your commit message. If not, just type a one-line description. + +And you're done! CPMUtil will automatically create and name the patch, and add it to the list of patches in the JSON definition. + +## Addendum: Package Identification Lists + +CPMUtil will create three lists of dependencies where `AddPackage` or similar was used. Each is in order of addition. + +- `CPM_PACKAGE_NAMES`: The names of packages included by CPMUtil +- `CPM_PACKAGE_URLS`: The URLs to project/repo pages of packages +- `CPM_PACKAGE_SHAS`: Short version identifiers for each package + - If the package was included as a system package, `(system)` is appended thereafter + - Packages whose versions can't be deduced will be left as `unknown`. + +For an example of how this might be implemented in an application, see Eden's implementation: + +- [`dep_hashes.h.in`](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/src/dep_hashes.h.in) +- [`GenerateDepHashes.cmake`](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CMakeModules/GenerateDepHashes.cmake) +- [`deps_dialog.cpp`](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/src/yuzu/deps_dialog.cpp) + +## Addendum: Notes for Packagers + +If you are packaging a project that uses CPMUtil, read this! + +### Network Sandbox + +For sandboxed environments (e.g. Gentoo, nixOS) you must install all dependencies to the system beforehand and set `-DCPMUTIL_FORCE_SYSTEM=ON`. If a dependency is missing, get creating! + +Alternatively, if CPMUtil pulls in a package that has no suitable way to install or use a system version, download it separately and pass `-D_CUSTOM_DIR=/path/to/downloaded/dir`. + +### Unsandboxed + +For others (AUR, MPR, etc). CPMUtil will handle everything for you, including if some of the project's dependencies are missing from your distribution's repositories. See [`eden-git`](https://aur.archlinux.org/cgit/aur.git/tree/PKGBUILD?h=eden-git) for an example. + +## Addendum: Dependent Packages + +Consider the following scenario: the Vulkan Headers and Vulkan Utility libraries are both pulled in by my project. In order for both to compile cleanly, their versions *must* match. However, a user may have the Vulkan Headers installed, but *not* the Vulkan Utility Libraries! This can cause a version mismatch where the Utility Libraries expect a much newer version of the Vulkan Headers than the user has installed. + +To solve this, CPMUtil has an `AddDependentPackages` command. This takes a list of JSON package keys that *must* either ALL be installed to the system, or ALL be bundled. + +### Example: Vulkan + +Using the prior Vulkan example: + +```json +"vulkan-headers": { + "repo": "KhronosGroup/Vulkan-Headers", + "package": "VulkanHeaders", + "min_version": "1.4.317", + "version": "v1.4.342" +}, +"vulkan-utility-libraries": { + "repo": "KhronosGroup/Vulkan-Utility-Libraries", + "package": "VulkanUtilityLibraries", + "version": "v1.4.342" +} +``` + +In CMake: + +```cmake +AddDependentPackages(vulkan-headers vulkan-utility-libraries) +``` + +Possible scenarios: + +- The user has both Vulkan Headers and Vulkan Utility Libraries installed to the system, and both are new enough. + - Configuration proceeds without issue. +- The user has neither installed, or has a too-old version of Vulkan Headers installed + - Configuration proceeds without issue. +- The user has a valid Vulkan Headers installed, but not Vulkan Utility Libraries. + - CPMUtil instructs the user to either force bundled Vulkan Headers, or install Vulkan Utility Libraries. +- The user has both installed, but Vulkan Headers are too old. + - CPMUtil instructs the user to install a valid version of Vulkan Headers, or force bundled Vulkan Utility Libraries. + +## Addendum: Module Path Packages + +Sometimes, a prebuilt CI package may be packed in such a way that it's meant to be used in the context of a system install (e.g. pkgconfig or CMakeConfig files). In this case, CPMUtil normally will be unable to configure the downloaded subdirectory. To solve this, you can use `AddJsonPackage`'s `MODULE_PATH` mode, which adds the downloaded source directory to the `CMAKE_MODULE_PATH`. + +### Example: OpenSSL + +Say an OpenSSL CI package is packed to contain its CMake config files rather than a root `CMakeLists.txt`; in this case, you would call: + +```cmake +AddJsonPackage(NAME openssl MODULE_PATH) +``` + +The `NAME` argument is also required, as the parsing is different from the standard single-argument function signature. + +From here, calling `find_package(OpenSSL)` will use the bundled OpenSSL. + +## Addendum: Adding Qt + +If you'd like to use customized Qt builds, CPMUtil provides a convenience function that allows you to add Qt builds. This usage and setup is subject to change. + +See [crueter-ci/Qt](https://github.com/crueter-ci/Qt) for an example of how one might build customized Qt. To add a Qt build to your project, use `AddQt( )`, e.g.: + +```cmake +AddQt(QDash-CI/Qt 6.11.1) +``` + +Then, call `find_package(Qt6 ...)` and it will pull Qt from your downloaded source. + +## Addendum: Package-Specific Overrides + +There are three variables that CPMUtil defines for each package; these can be overriden by the user or in your CMake. `package` refers either to the `package` value in the JSON, or the package's JSON key if unset (see `package` in [Common Properties](#common-properties)): + +- `_FORCE_BUNDLED`: Forcefully bundle the package. This has the same effect as `CPMUTIL_FORCE_BUNDLED`, but only for this package. +- `_FORCE_BUNDLED`: Forcefully use the system package, failing if it can't be found. This has the same effect as `SYSTEM`, but only for this package. +- `_CUSTOM_DIR`: Path to an extracted copy of the package. CPMUtil will not attempt to download the package and will instead use the custom directory. + - For an example, see [CPMUtil's test case](https://git.crueter.xyz/CMake/CPMUtil/src/branch/master/tests/dir/CMakeLists.txt) + +Additionally, in CMake, you can add `FORCE_BUNDLED_PACKAGE ON` to your `AddJsonPackage` command--note that you will have to use the `NAME ` syntax as described in [Module Path Packages](#addendum-module-path-packages). This will overrule *all* other overrides, including `CPMUTIL_FORCE_SYSTEM` and `_FORCE_SYSTEM`--use with caution! + +## Addendum: Supply-Chain Security + +Many package managers suffer from the issue of supply chain security, specifically in regards to silent overwrites of existing packages or archives, e.g. tag sliding and artifact overwriting. CPMUtil has three methods to protect against this. + +### Checksumming + +CPMUtil *requires* SHA512 checksums for standard packages, and soon will for CI packages as well. If an attacker or compromised account slides a tag, overwrites a release artifact, or otherwise attempts to compromise anything that CPMUtil may fetch, **CPMUtil will not allow the download to continue!** This means that consumers of your build system can **only** download an artifact if the contents of the artifact are *exactly* indentical to what it was when it was configured--any changes at all will be rejected by CPMUtil. + +### Caching + +CPMUtil uses a mutable cache system, stored by default in `.cache/cpm`. Dependencies are downloaded and extracted here, and can be reused infinitely. This means that, for instance, if a package is compromised but you already have a cached local copy, you won't have to worry at all! + +### Immutable Commit Hashes + +CPMUtil is capable of using immutable Git commit hashes for its artifacts. These are (barring SHA1 collisions) completely immune to supply chain attacks--that is, unless the entire root server gets compromised to serve infected artifacts/source code; at which point there are much larger issues to worry about. This means that once you set a package to use a Git commit hash for its version, **it will stay the same forever**. This is useful if you want to ensure that consumers are never faced with download failures stemming from hash mismatches in case of compromised artifacts. + +Do note, however, that this will render the package incompatible with CPMUtil's built-in auto-updater, so you will have to manually update the package. diff --git a/docs/CPMUtil/AddCIPackage.md b/docs/CPMUtil/AddCIPackage.md deleted file mode 100644 index bc7c1ccfad..0000000000 --- a/docs/CPMUtil/AddCIPackage.md +++ /dev/null @@ -1,20 +0,0 @@ -# AddPackage - -- `VERSION` (required): The version to get (the tag will be `v${VERSION}`) -- `NAME` (required): Name used within the artifacts -- `REPO` (required): CI repository, e.g. `crueter-ci/OpenSSL` -- `PACKAGE` (required): `find_package` package name -- `EXTENSION`: Artifact extension (default `tar.zst`) -- `MIN_VERSION`: Minimum version for `find_package`. Only used if platform does not support this package as a bundled artifact -- `DISABLED_PLATFORMS`: List of platforms that lack artifacts for this package. Options: - - `windows-amd64` - - `windows-arm64` - - `mingw-amd64` - - `mingw-arm64` - - `android-x86_64` - - `android-aarch64` - - `solaris-amd64` - - `freebsd-amd64` - - `linux-amd64` - - `linux-aarch64` - - `macos-universal` diff --git a/docs/CPMUtil/AddDependentPackage.md b/docs/CPMUtil/AddDependentPackage.md deleted file mode 100644 index bb6651e4b8..0000000000 --- a/docs/CPMUtil/AddDependentPackage.md +++ /dev/null @@ -1,41 +0,0 @@ -# AddDependentPackage - -Use `AddDependentPackage` when you have multiple packages that are required to all be from the system, OR bundled. This is useful in cases where e.g. versions must absolutely match. - -## Versioning - -Versioning must be handled by the package itself. - -## Examples - -### Vulkan - -`cpmfile.json` - -```json -{ - "vulkan-headers": { - "repo": "KhronosGroup/Vulkan-Headers", - "package": "VulkanHeaders", - "version": "1.4.317", - "hash": "26e0ad8fa34ab65a91ca62ddc54cc4410d209a94f64f2817dcdb8061dc621539a4262eab6387e9b9aa421db3dbf2cf8e2a4b041b696d0d03746bae1f25191272", - "git_version": "1.4.342", - "tag": "v%VERSION%" - }, - "vulkan-utility-libraries": { - "repo": "KhronosGroup/Vulkan-Utility-Libraries", - "package": "VulkanUtilityLibraries", - "hash": "8147370f964fd82c315d6bb89adeda30186098427bf3efaa641d36282d42a263f31e96e4586bfd7ae0410ff015379c19aa4512ba160630444d3d8553afd1ec14", - "git_version": "1.4.342", - "tag": "v%VERSION%" - } -} -``` - -`CMakeLists.txt`: - -```cmake -AddDependentPackages(vulkan-headers vulkan-utility-libraries) -``` - -If Vulkan Headers are installed, but NOT Vulkan Utility Libraries, then CPMUtil will throw an error. diff --git a/docs/CPMUtil/AddJsonPackage.md b/docs/CPMUtil/AddJsonPackage.md deleted file mode 100644 index 464cd1731b..0000000000 --- a/docs/CPMUtil/AddJsonPackage.md +++ /dev/null @@ -1,104 +0,0 @@ -# AddJsonPackage - -In each directory that utilizes `CPMUtil`, there must be a `cpmfile.json` that defines dependencies in a similar manner to the individual calls. - -The cpmfile is an object of objects, with each sub-object being named according to the package's identifier, e.g. `openssl`, which can then be fetched with `AddJsonPackage()`. Options are designed to map closely to the argument names, and are always strings unless otherwise specified. - -- [Options](#options) -- [Examples](#examples) - - -## Options - -- `package` -> `NAME` (`PACKAGE` for CI), defaults to the object key -- `repo` -> `REPO` -- `version` -> `VERSION` -- `ci` (bool) - -If `ci` is `false`: - -- `hash` -> `HASH` -- `hash_suffix` -> `HASH_SUFFIX` -- `sha` -> `SHA` -- `key` -> `KEY` -- `tag` -> `TAG` - - If the tag contains `%VERSION%`, that part will be replaced by the `git_version`, OR `version` if `git_version` is not specified -- `url` -> `URL` -- `artifact` -> `ARTIFACT` - - If the artifact contains `%VERSION%`, that part will be replaced by the `git_version`, OR `version` if `git_version` is not specified - - If the artifact contains `%TAG%`, that part will be replaced by the `tag` (with its replacement already done) -- `git_version` -> `GIT_VERSION` -- `git_host` -> `GIT_HOST` -- `source_subdir` -> `SOURCE_SUBDIR` -- `bundled` -> `BUNDLED_PACKAGE` -- `find_args` -> `FIND_PACKAGE_ARGUMENTS` -- `download_only` -> `DOWNLOAD_ONLY` -- `patches` -> `PATCHES` (array) -- `options` -> `OPTIONS` (array) -- `skip_updates`: Tells `check-updates.sh` to not check for new updates on this package. - -Other arguments aren't currently supported. If you wish to add them, see the `AddJsonPackage` function in `CMakeModules/CPMUtil.cmake`. - -If `ci` is `true`: - -- `name` -> `NAME`, defaults to the object key -- `extension` -> `EXTENSION`, defaults to `tar.zst` -- `min_version` -> `MIN_VERSION` -- `extension` -> `EXTENSION` -- `disabled_platforms` -> `DISABLED_PLATFORMS` (array) - -## Examples - -In order: OpenSSL CI, Boost (tag + artifact), Opus (options + find_args), discord-rpc (sha + options + patches). - -```json -{ - "openssl": { - "ci": true, - "package": "OpenSSL", - "name": "openssl", - "repo": "crueter-ci/OpenSSL", - "version": "3.6.0", - "min_version": "1.1.1", - "disabled_platforms": [ - "macos-universal" - ] - }, - "boost": { - "package": "Boost", - "repo": "boostorg/boost", - "tag": "boost-%VERSION%", - "artifact": "%TAG%-cmake.7z", - "hash": "e5b049e5b61964480ca816395f63f95621e66cb9bcf616a8b10e441e0e69f129e22443acb11e77bc1e8170f8e4171b9b7719891efc43699782bfcd4b3a365f01", - "git_version": "1.88.0", - "version": "1.57" - }, - "opus": { - "package": "Opus", - "repo": "xiph/opus", - "sha": "5ded705cf4", - "hash": "0dc89e58ddda1f3bc6a7037963994770c5806c10e66f5cc55c59286fc76d0544fe4eca7626772b888fd719f434bc8a92f792bdb350c807968b2ac14cfc04b203", - "version": "1.3", - "find_args": "MODULE", - "options": [ - "OPUS_BUILD_TESTING OFF", - "OPUS_BUILD_PROGRAMS OFF", - "OPUS_INSTALL_PKG_CONFIG_MODULE OFF", - "OPUS_INSTALL_CMAKE_CONFIG_MODULE OFF" - ] - }, - "discord-rpc": { - "repo": "discord/discord-rpc", - "sha": "963aa9f3e5", - "hash": "386e1344e9a666d730f2d335ee3aef1fd05b1039febefd51aa751b705009cc764411397f3ca08dffd46205c72f75b235c870c737b2091a4ed0c3b061f5919bde", - "options": [ - "BUILD_EXAMPLES OFF" - ], - "patches": [ - "0001-cmake-version.patch", - "0002-no-clang-format.patch", - "0003-fix-cpp17.patch" - ] - } -} -``` diff --git a/docs/CPMUtil/AddPackage.md b/docs/CPMUtil/AddPackage.md deleted file mode 100644 index 6e9ae1b775..0000000000 --- a/docs/CPMUtil/AddPackage.md +++ /dev/null @@ -1,118 +0,0 @@ -# `AddPackage` - - -- [Identification/Fetching](#identificationfetching) -- [Hashing](#hashing) -- [Other Options](#other-options) -- [Extra Variables](#extra-variables) -- [System/Bundled Packages](#systembundled-packages) -- [Identification](#identification) - - -## Identification/Fetching - -- `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 -- `URL`: The URL to fetch. -- `REPO`: The repo to use (`owner/repo`). -- `GIT_HOST`: The Git host to use - - Defaults to `github.com`. Do not include the protocol, as HTTPS is enforced. -- `TAG`: The tag to fetch, if applicable. -- `ARTIFACT`: The name of the artifact, if applicable. -- `SHA`: Commit sha to fetch, if applicable. -- `BRANCH`: Branch to fetch, if applicable. - -The following configurations are supported, in descending order of precedence: - -- `URL`: Bare URL download, useful for custom artifacts - - If this is set, `GIT_URL` or `REPO` should be set to allow the dependency viewer to link to the project's Git repository. - - If this is NOT set, `REPO` must be defined. -- `REPO + TAG + ARTIFACT`: GitHub release artifact - - The final download URL will be `https://github.com/${REPO}/releases/download/${TAG}/${ARTIFACT}` - - Useful for prebuilt libraries and prefetched archives -- `REPO + TAG`: GitHub tag archive - - The final download URL will be `https://github.com/${REPO}/archive/refs/tags/${TAG}.tar.gz` - - Useful for pinning to a specific tag, better for build identification -- `REPO + SHA`: GitHub commit archive - - The final download URL will be `https://github.com/${REPO}/archive/${SHA}.zip` - - Useful for pinning to a specific commit -- `REPO + BRANCH`: GitHub branch archive - - The final download URL will be `https://github.com/${REPO}/archive/refs/heads/${BRANCH}.zip` - - Generally not recommended unless the branch is frozen -- `REPO`: GitHub master archive - - The final download URL will be `https://github.com/${REPO}/archive/refs/heads/master.zip` - - Generally not recommended unless the project is dead - -## Hashing - -Hashing is used for verifying downloads. It's highly recommended to use these. - -- `HASH_ALGO` (default `SHA512`): Hash algorithm to use - -Hashing strategies, descending order of precedence: - -- `HASH`: Bare hash verification, useful for static downloads e.g. commit archives -- `HASH_SUFFIX`: Download the hash as `${DOWNLOAD_URL}.${HASH_SUFFIX}` - - The downloaded hash *must* match the hash algorithm and contain nothing but the hash; no filenames or extra content. -- `HASH_URL`: Download the hash from a separate URL - -## Other Options - -- `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` - - 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 -- `SOURCE_SUBDIR`: Subdirectory of the project containing a CMakeLists.txt file -- `FIND_PACKAGE_ARGUMENTS`: Arguments to pass to the `find_package` call -- `BUNDLED_PACKAGE`: Set to `ON` to default to the bundled package -- `FORCE_BUNDLED_PACKAGE`: Set to `ON` to force the usage of the bundled package, regardless of CPMUTIL_FORCE_SYSTEM or `_FORCE_SYSTEM` -- `OPTIONS`: Options to pass to the configuration of the package -- `PATCHES`: Patches to apply to the package, stored in `.patch/${packagename_lower}/0001-patch-name.patch` and so on -- Other arguments can be passed to CPM as well - -## Extra Variables - -For each added package, users may additionally force usage of the system/bundled package. - -- `${package}_DIR`: Path to a separately-downloaded copy of the package. Note that versioning is not checked! -- `${package}_FORCE_SYSTEM`: Require the package to be installed on the system -- `${package}_FORCE_BUNDLED`: Force the package to be fetched and use the bundled version - -## System/Bundled Packages - -Descending order of precedence: - -- If `${package}_FORCE_SYSTEM` is true, requires the package to be on the system -- If `${package}_FORCE_BUNDLED` is true, forcefully uses the bundled package -- If `CPMUTIL_FORCE_SYSTEM` is true, requires the package to be on the system -- If `CPMUTIL_FORCE_BUNDLED` is true, forcefully uses the bundled package -- If the `BUNDLED_PACKAGE` argument is true, forcefully uses the bundled package -- Otherwise, CPM will search for the package first, and if not found, will use the bundled package - -## Identification - -All dependencies must be identifiable in some way for usage in the dependency viewer. Lists are provided in descending order of precedence. - -URLs: - -- `GIT_URL` -- `REPO` as a Git repository - - You may optionally specify `GIT_HOST` to use a custom host, e.g. `GIT_HOST git.crueter.xyz`. Note that the git host MUST be GitHub-like in its artifact/archive downloads, e.g. Forgejo - - If `GIT_HOST` is unspecified, defaults to `github.com` -- `URL` - -Versions (bundled): - -- `SHA` -- `GIT_VERSION` -- `VERSION` -- `TAG` -- "unknown" - -If the package is a system package, AddPackage will attempt to determine the package version and append `(system)` to the identifier. Otherwise, it will be marked as `unknown (system)` diff --git a/docs/CPMUtil/AddQt.md b/docs/CPMUtil/AddQt.md deleted file mode 100644 index e9595b8004..0000000000 --- a/docs/CPMUtil/AddQt.md +++ /dev/null @@ -1,28 +0,0 @@ -# AddQt - -Simply call `AddQt()` before any Qt `find_package` calls and everything will be set up for you. On Linux, the bundled Qt library is built as a shared library, and provided you have OpenSSL and X11, everything should just work. - -On Windows, MinGW, and MacOS, Qt is bundled as a static library. No further action is needed, as the provided libraries automatically integrate the Windows/Cocoa plugins, alongside the corresponding Multimedia and Network plugins. - -## Modules - -The following modules are bundled into these Qt builds: - -- Base (Gui, Core, Widgets, Network) -- Multimedia -- Declarative (Quick, QML) -- Linux: Wayland client - -Each platform has the corresponding QPA built in and set as the default as well. This means you don't need to add `Q_IMPORT_PLUGIN`! - -## Example - -See an example in the [`tests/qt`](https://git.crueter.xyz/CMake/CPMUtil/src/branch/master/tests/qt/CMakeLists.txt) directory. - -## Versions - -The following versions have available builds: - -- 6.9.3 - -See [`crueter-ci/Qt`](https://github.com/crueter-ci/Qt) for an updated list at any time. diff --git a/docs/CPMUtil/README.md b/docs/CPMUtil/README.md deleted file mode 100644 index dbc5f0922a..0000000000 --- a/docs/CPMUtil/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# CPMUtil - -CPMUtil is a wrapper around CPM that aims to reduce boilerplate and add useful utility functions to make dependency management a piece of cake. - -Global Options: - -- `CPMUTIL_FORCE_SYSTEM` (default `OFF`): Require all CPM dependencies to use system packages. NOT RECOMMENDED! - - You may optionally override this (section) -- `CPMUTIL_FORCE_BUNDLED` (default `ON` on MSVC and Android, `OFF` elsewhere): Require all CPM dependencies to use bundled packages. - -You are highly encouraged to read AddPackage first, even if you plan to only interact with CPMUtil via `AddJsonPackage`. - -- [AddPackage](#addpackage) -- [AddCIPackage](#addcipackage) -- [AddJsonPackage](#addjsonpackage) -- [AddQt](#addqt) -- [Lists](#lists) -- [For Packagers](#for-packagers) - - [Network Sandbox](#network-sandbox) - - [Unsandboxed](#unsandboxed) - -## AddPackage - -The core of CPMUtil is the [`AddPackage`](./AddPackage.md) function. [`AddPackage`](./AddPackage.md) itself is fully CMake-based, and largely serves as an interface between CPM and the rest of CPMUtil. - -## AddCIPackage - -[`AddCIPackage`](./AddCIPackage.md) adds a package that follows [crueter's CI repository spec](https://github.com/crueter-ci). - -## AddJsonPackage - -[`AddJsonPackage`](./AddJsonPackage.md) is the recommended method of usage for CPMUtil. - -## AddDependentPackage - -[`AddDependentPackage`](./AddDependentPackage.md) allows you to add multiple packages such that all of them must be from the system OR bundled. - -## AddQt - -[`AddQt`](./AddQt.md) adds a specific version of Qt to your project. - -## Lists - -CPMUtil will create three lists of dependencies where `AddPackage` or similar was used. Each is in order of addition. - -- `CPM_PACKAGE_NAMES`: The names of packages included by CPMUtil -- `CPM_PACKAGE_URLS`: The URLs to project/repo pages of packages -- `CPM_PACKAGE_SHAS`: Short version identifiers for each package - - If the package was included as a system package, `(system)` is appended thereafter - - Packages whose versions can't be deduced will be left as `unknown`. - -For an example of how this might be implemented in an application, see Eden's implementation: - -- [`dep_hashes.h.in`](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/src/dep_hashes.h.in) -- [`GenerateDepHashes.cmake`](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CMakeModules/GenerateDepHashes.cmake) -- [`deps_dialog.cpp`](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/src/yuzu/deps_dialog.cpp) - -## For Packagers - -If you are packaging a project that uses CPMUtil, read this! - -### Network Sandbox - -For sandboxed environments (e.g. Gentoo, nixOS) you must install all dependencies to the system beforehand and set `-DCPMUTIL_FORCE_SYSTEM=ON`. If a dependency is missing, get creating! - -Alternatively, if CPMUtil pulls in a package that has no suitable way to install or use a system version, download it separately and pass `-DPackageName_DIR=/path/to/downloaded/dir` (e.g. shaders) - -### Unsandboxed - -For others (AUR, MPR, etc). CPMUtil will handle everything for you, including if some of the project's dependencies are missing from your distribution's repositories. That is pretty much half the reason I created this behemoth, after all. diff --git a/docs/Caveats.md b/docs/Caveats.md index 8c752cf79d..b86edf4d75 100644 --- a/docs/Caveats.md +++ b/docs/Caveats.md @@ -12,10 +12,6 @@ - [NetBSD](#netbsd) - [MSYS2](#msys2) - [RedoxOS](#redoxos) -- [Windows](#windows) - - [Windows 7, Windows 8 and Windows 8.1](#windows-7-windows-8-and-windows-81) - - [Windows Vista and below](#windows-vista-and-below) - - [Windows on ARM](#windows-on-arm) ## Arch Linux @@ -46,7 +42,7 @@ Qt Widgets appears to be broken. For now, add `-DENABLE_QT=OFF` to your configur This is needed for some dependencies that call cc directly (tz): ```sh -echo '#!/bin/sh' >cc +echo '#!/bin/sh -e' >cc echo 'gcc $@' >>cc chmod +x cc export PATH="$PATH:$PWD" @@ -65,14 +61,13 @@ export LIBGL_ALWAYS_SOFTWARE=1 ``` - 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's CMake configuration, audio driver defaults to SunOS ``, which does not exist on OpenIndiana. Using external or bundled SDL2 may solve this. - System OpenSSL generally does not work. Instead, use `-DYUZU_USE_BUNDLED_OPENSSL=ON` to use a bundled static OpenSSL, or build a system dependency from source. ## OmniOS Install `developer/gcc14` on OmniOS using pkgsrc. -Since so many dependencies are missing on `OmniOS`, you may wish to use `-DCPMUTIL_FORCE_BUNDLED=ON -DYUZU_USE_EXTERNAL_SDL2=ON` +Since so many dependencies are missing on `OmniOS`, you may wish to use `-DCPMUTIL_FORCE_BUNDLED=ON` and `-DYUZU_USE_BUNDLED_OPENSSL=OFF`. For OmniOS you are required to build glslang yourself: ```sh @@ -87,11 +82,13 @@ cmake --install build It may be tempting to specify `-t glslang`, but this will cause installation to fail. So don't. -Using `--parallel` on CMake incorrectly passes `dmake ... -jn` instead of `dmake ... -j n`, this is a bug with OmniOS's CMake, and as such it's recommended to not use this option until it's fixed. +Using `--parallel` on CMake incorrectly passes `dmake ... -jn` instead of `dmake ... -j n`, this is a bug with OmniOS's CMake, it's recommended to not use this option until it's fixed. You may also need to install `gmake` in order to properly build FFmpeg, this is provided by the `build-essential` package. -If it wasn't obvious already, you require a X11 server to properly run the emulator within OmniOS, [this guide](https://web.archive.org/web/20260424200928/https://geekblood.wordpress.com/2017/10/26/installing-x11-and-a-desktop-environment-on-omnios/) is a great starting point for that, the links to pkgsrc are outdated so follow [this exemplar](https://pkgsrc.smartos.org/install-on-illumos/) as well: +If it wasn't obvious already, you require a X11 server to properly run the emulator within OmniOS, [this guide](https://web.archive.org/web/20260424200928/https://geekblood.wordpress.com/2017/10/26/installing-x11-and-a-desktop-environment-on-omnios/) is a great starting point for that, the links to pkgsrc are outdated so follow [this exemplar](https://pkgsrc.smartos.org/install-on-illumos/) as well. + +For Solaris based OSes, `${CMAKE_SYSTEM_NAME}` isn't properly set on CMake (it's set to i686 on AMD64), you may find issues when building OpenSSL from `openssl-cmake`. ## HaikuOS @@ -245,19 +242,3 @@ find ./*/ -name "*.dll" | while read -r dll; do deps "$dll"; done The package install may randomly hang at times, in which case it has to be restarted. ALWAYS do a `sudo pkg update` or the chances of it hanging will be close to 90%. If "multiple" installs fail at once, try installing 1 by 1 the packages. When CMake invokes certain file syscalls - it may sometimes cause crashes or corruptions on the (kernel?) address space - so reboot the system if there is a "hang" in CMake. - -## Windows - -### Windows 7, Windows 8 and Windows 8.1 - -DirectX 12 is not available - simply copy and paste a random DLL and name it `d3d12.dll`. - -Install [Qt6 compatibility libraries](github.com/ANightly/qt6windows7) specifically Qt 6.9.5. - -### Windows Vista and below - -No support for Windows Vista (or below) is present at the moment. Check back later. - -### Windows on ARM - -If you're using Snapdragon X or 8CX, use the [the Vulkan translation layer](https://apps.microsoft.com/detail/9nqpsl29bfff?hl=en-us&gl=USE) only if the stock drivers do not work. And of course always keep your system up-to-date. diff --git a/docs/CrossCompile.md b/docs/CrossCompile.md index 1f6ef447e6..06bac5d9db 100644 --- a/docs/CrossCompile.md +++ b/docs/CrossCompile.md @@ -10,3 +10,81 @@ A painless guide for cross compilation (or to test NCE) from a x86_64 system wit - Download Debian 13: `wget https://cdimage.debian.org/debian-cd/current/arm64/iso-cd/debian-13.0.0-arm64-netinst.iso` - Create a system disk: `qemu-img create -f qcow2 debian-13-arm64-ci.qcow2 30G` - Run the VM: `qemu-system-aarch64 -M virt -m 2G -cpu max -bios /usr/local/share/qemu/edk2-aarch64-code.fd -drive if=none,file=debian-13.0.0-arm64-netinst.iso,format=raw,id=cdrom -device scsi-cd,drive=cdrom -drive if=none,file=debian-13-arm64-ci.qcow2,id=hd0,format=qcow2 -device virtio-blk-device,drive=hd0 -device virtio-gpu-pci -device usb-ehci -device usb-kbd -device intel-hda -device hda-output -nic user,model=virtio-net-pci` + +## Gentoo + +Gentoo's cross-compilation setup is relatively easy, provided you're already familiar with portage. A [cross toolchain file](../CMakeModules/toolchains/GentooCross.cmake) is provided. Throughout this section, replace `aarch64` with whatever target architecture you desire. + +### Crossdev + +First, emerge crossdev via `sudo emerge -a sys-devel/crossdev`. + +Now, set up the environment depending on the target architecture; e.g. + +```sh +sudo crossdev aarch64 +``` + +### QEMU + +If you don't have a host Gentoo system of your target architecture, you should install a QEMU user setup for testing. To do so, enable the relevant USE flags for `app-emulation/qemu`: + +```txt +app-emulation/qemu static-user qemu_user_targets_aarch64 +``` + +To use cross-emerged shared libraries, you will also need to tell qemu where the sysroot is. You can do this with an alias: + +```sh +alias qemu-aarch64="qemu-aarch64 -L /usr/aarch64-unknown-linux-gnu" +``` + +### Dependencies + +Dependencies are the same [as normal Gentoo](./Deps.md#Commands); simply replace the `emerge` command with `emerge--unknown-linux-gnu` (e.g. `emerge-aarch64-unknown-linux-gnu`). However, there are a few caveats: + +#### Enabling GURU + +Since Crossdev sysroots are effectively isolated from the system w.r.t Portage, you must manually enable GURU in your sysroot. Run the following as root: + +```sh +mkdir -p /usr/aarch64-unknown-linux-gnu/etc/portage/repos.conf +cat << EOF > /usr/aarch64-unknown-linux-gnu/etc/portage/repos.conf/guru.conf +[guru] +location = /var/db/repos/guru +auto-sync = no +priority = 1 +EOF +``` + +#### Package Errata + +Crossdev is not perfect, and you may face some challenges with package that are not properly keyworded or have issues on specific architectures. These behaviors are, unfortunately, not well documented, and certain build systems such as Meson--and certain troublesome packages like GTK--are generally unfriendly towards cross-compilation. + +Thus, it may be desirable to emerge a minimal set of dependencies and allow Eden's build system to handle the rest for you. At a minimum, you *only* need standard system libraries (Crossdev does this for you) and Qt: + +```sh +sudo emerge-aarch64-unknown-linux-gnu dev-qt/qtbase:6 dev-qt/qtcharts:6 +``` + +From here, CPMUtil will take care of everything else. For extra insurance, you may want to set `-DCPMUTIL_FORCE_BUNDLED=ON` in your configure command. + +### Building + +From here, building is relatively standard. The [cross toolchain file](../CMakeModules/toolchains/GentooCross.cmake) contains a few additional configurations, but generally all you need to do is set `CROSS_TARGET` and `CMAKE_TOOLCHAIN_FILE`. Disabling OpenGL is strongly recommended as well. + +```sh +cmake -S . -B build/aarch64 -DCMAKE_TOOLCHAIN_FILE=CMakeModules/toolchains/GentooCross.cmake -GNinja -DCROSS_TARGET=aarch64 -DENABLE_OPENGL=OFF +``` + +With that done, you can build as normal: + +```sh +cmake --build build/aarch64 +``` + +And finally, run the compiled executable with QEMU! + +```sh +qemu-aarch64 build/aarch64/bin/eden +``` diff --git a/docs/Debug.md b/docs/Debug.md index 335806d52b..ec668d5dfd 100644 --- a/docs/Debug.md +++ b/docs/Debug.md @@ -6,7 +6,7 @@ When reporting issues or finding bugs, we often need backtraces, debug logs, or ### Graphics Debugging -If your bug is related to a graphical issue--e.g. mismatched colors, vertex explosions, flickering, etc.--then you are required to include graphical debugging logs in your issue reports. +If your bug is related to a graphical issue -- e.g. mismatched colors, vertex explosions, flickering, etc. -- then you are required to include graphical debugging logs in your issue reports. Graphics Debugging is found in General -> Debug on desktop, and Advanced Settings -> Debug on Android. Android users are all set; however, desktop users may need to install the Vulkan Validation Layers: @@ -17,7 +17,7 @@ Once Graphics Debugging is enabled, run the problematic game again and continue. ### Debug Logs -Debug logs can be found in General -> Debug -> Open Log Location on desktop, and Share Debug Logs on Android. This MUST be included in all bug reports, except for certain UI bugs--but we still highly recommend them even for UI bugs. +Debug logs can be found in `General -> Debug -> Open Log Location` on desktop, and `Share Debug Logs` on Android. This MUST be included in all bug reports, except for certain UI bugs -- but we still highly recommend them even for UI bugs. ## Debugging (host code) @@ -38,7 +38,7 @@ You must have GDB installed for aarch64 to debug the target. Install it through - `sudo emerge --ask crossdev` - `sudo crossdev -t aarch64-unknown-linux-gnu --ex-gdb` -Run `./build/bin/eden-cli -c -d -g `, or `Enable GDB Stub` at General > Debug, then hook up an aarch64-gdb: +Run `./build/bin/eden-cli -c -d -g `, or `Enable GDB Stub` at General > Debug, then hook up an aarch64-gdb: - `target remote localhost:6543` @@ -69,12 +69,38 @@ Expressions can be `variable_names` or `1234` (numbers) or `*var` (dereference o For more information type `info gdb` and read [the man page](https://man7.org/linux/man-pages/man1/gdb.1.html). +# RenderDoc + +RenderDoc is a free, cross platform, multi-graphics API debugger. It is an invaluable tool for diagnosing issues with graphics applications, and includes support for Vulkan. Get it from [renderdoc.org](https://renderdoc.org). + +RenderDoc can capture Eden's Vulkan output when its Vulkan layer is loaded before Eden creates the Vulkan device. Before using RenderDoc to diagnose issues, it's always good to make sure there are no validation errors. Any errors means the behavior of the application is undefined. That said, RenderDoc can help debug validation errors if you do have them. + +## Usage on Windows + +You can either use RenderDoc UI to launch Eden, or you can make Eden attach it internally: + +On Windows PowerShell: +```powershell +$env:ENABLE_VULKAN_RENDERDOC_CAPTURE='1' +.\eden.exe +``` +When RenderDoc is attached, Eden logs the default Windows capture folder: +```text +%LOCALAPPDATA%\Temp\RenderDoc +``` + +Press RenderDoc's capture hotkey, usually `F12`, to capture a frame. To stop using RenderDoc, close Eden and launch it again without `ENABLE_VULKAN_RENDERDOC_CAPTURE`. + +## Eden Hotkey + +Eden also has a separate `Toggle Renderdoc Capture` hotkey behind the debug setting `renderdoc_hotkey`. +That hotkey does not load or unload RenderDoc. It only toggles Eden's own manual capture through RenderDoc's API: + +- First press: Starts a capture +- Second press: Ends that capture + ## Simple checklist for debugging black screens using Renderdoc -Renderdoc is a free, cross platform, multi-graphics API debugger. It is an invaluable tool for diagnosing issues with graphics applications, and includes support for Vulkan. Get it at [renderdoc.org](https://renderdoc.org). - -Before using renderdoc to diagnose issues, it is always good to make sure there are no validation errors. Any errors means the behavior of the application is undefined. That said, renderdoc can help debug validation errors if you do have them. - When debugging a black screen, there are many ways the application could have setup Vulkan wrong. Here is a short checklist of items to look at to make sure are appropriate: diff --git a/docs/Deps.md b/docs/Deps.md index 744713d4f0..2b73ecb4ac 100644 --- a/docs/Deps.md +++ b/docs/Deps.md @@ -16,7 +16,7 @@ To build Eden, you MUST have a C++ compiler. The following additional tools are also required: -* **[CMake](https://www.cmake.org/)** 3.22+ - already included with the Android SDK +* **[CMake](https://www.cmake.org/)** 3.31+ - already included with the Android SDK * **[Git](https://git-scm.com/)** for version control * **[Windows installer](https://gitforwindows.org)** * **[Python3](https://www.python.org/downloads/)** 3.10+ - necessary to download external repositories @@ -35,6 +35,11 @@ If you are on desktop and plan to use the Qt frontend, you *must* install Qt 6, * For help setting up Qt Creator, run `./install.sh -h qtcreator` +* If you're using clang-cl and want to still use MSVC + * Check the option to add "C++ clang compiler for Windows" on Visual Studio installer and uncheck "x64/x86 build tool for MSVC" while selecting "C++ desktop developement tools" and change Visual Studio to 2026, from 2022. + * At qt creator section generator tab change Visual Studio 17 2022 to 2026. + * Finally, to use clang-cl: `cmake -S . -B build -G "Visual Studio 17 2026" -T ClangCL` + If you are on **Windows** and building with **MSVC** or **clang-cl**, you may go [back home](Build.md) and continue. ## Externals @@ -42,7 +47,7 @@ If you are on **Windows** and building with **MSVC** or **clang-cl**, you may go The following are handled by Eden's externals: * [FFmpeg](https://ffmpeg.org/) (should use `-DYUZU_USE_EXTERNAL_FFMPEG=ON`) -* [SDL2](https://www.libsdl.org/download-2.0.php) 2.0.18+ (should use `-DYUZU_USE_EXTERNAL_SDL2=ON` OR `-DYUZU_USE_BUNDLED_SDL2=ON` to reduce compile time) +* [SDL3](https://www.libsdl.org/download-2.0.php) 3.2.10+ (Use `-DYUZU_USE_BUNDLED_SDL2=ON` to reduce compile time) 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 (UNIX-like only): @@ -75,7 +80,6 @@ Certain other dependencies will be fetched by CPM regardless. System packages *c * [httplib](https://github.com/yhirose/cpp-httplib) - if `ENABLE_UPDATE_CHECKER` or `ENABLE_WEB_SERVICE` are on * This package is known to be broken on the AUR. * [cpp-jwt](https://github.com/arun11299/cpp-jwt) 1.4+ - if `ENABLE_WEB_SERVICE` is on -* [unordered-dense](https://github.com/martinus/unordered_dense) On amd64: @@ -114,11 +118,11 @@ Now, install all deps: sudo emerge -a \ app-arch/lz4 app-arch/zstd app-arch/unzip \ dev-libs/libfmt dev-libs/libusb dev-libs/mcl dev-libs/sirit \ - dev-libs/unordered_dense dev-libs/boost dev-libs/openssl dev-libs/discord-rpc \ + dev-libs/boost dev-libs/openssl dev-libs/discord-rpc \ dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \ dev-util/vulkan-utility-libraries dev-util/glslang \ media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \ - media-libs/VulkanMemoryAllocator media-libs/libsdl2 media-libs/cubeb \ + media-libs/VulkanMemoryAllocator media-libs/libsdl3 media-libs/cubeb \ net-libs/enet \ sys-libs/zlib \ dev-cpp/nlohmann_json dev-cpp/simpleini dev-cpp/cpp-httplib dev-cpp/cpp-jwt \ @@ -137,7 +141,8 @@ Required USE flags: * `dev-qt/qtbase network concurrent dbus gui widgets` * `dev-libs/quazip qt6` -* `media-libs/libsdl2 haptic joystick sound video` +* `media-libs/libsdl3 haptic joystick sound video` + * Adding `X vulkan udev opengl` is recommended but not required * `dev-cpp/cpp-httplib ssl` [Caveats](./Caveats.md#gentoo-linux) @@ -148,7 +153,7 @@ Required USE flags: Arch Linux ```sh -sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl opus qt6-base qt6-multimedia qt6-charts sdl2 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers +sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl opus qt6-base qt6-multimedia qt6-charts sdl3 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers ``` * Building with QT Web Engine requires `qt6-webengine` as well. @@ -161,10 +166,10 @@ sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glsl Ubuntu, Debian, Mint Linux ```sh -sudo apt-get install autoconf cmake g++ gcc git glslang-tools 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 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 qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl2-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev +sudo apt-get install autoconf cmake g++ gcc git glslang-tools 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 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 qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl3-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev ``` -* Ubuntu 22.04, Linux Mint 20, or Debian 12 or later is required. +* Ubuntu 26.04, Linux Mint 22.3, or Debian 13 or later is required. * To enable QT Web Engine, add `-DYUZU_USE_QT_WEB_ENGINE=ON` when running CMake. @@ -185,13 +190,13 @@ AlmaLinux (use `YUZU_USE_CPM=ON`): sudo dnf install epel-release dnf-utils # (run rpmfusion installation afterwards) # vvv - This will work for most systems -sudo dnf install autoconf cmake libtool libudev cmake gcc gcc-c++ qt6-qtbase-devel zlib-devel openssl-devel boost SDL2 ffmpeg-devel libdrm glslang jq patch +sudo dnf install autoconf cmake libtool libudev cmake gcc gcc-c++ qt6-qtbase-devel zlib-devel openssl-devel boost SDL3 ffmpeg-devel libdrm glslang jq patch # Qt6 private GUI must be taken from CRB repos sudo dnf config-manager --enable crb sudo dnf install qt6-qtbase-private-devel ``` -For systems like OpenEuler or derivates, don't forget to also install: `SDL2-devel pkg-config fmt-dev nlohmann-json-dev`. +For systems like OpenEuler or derivates, don't forget to also install: `SDL3-devel pkg-config fmt-dev nlohmann-json-dev`. * [RPM Fusion](https://rpmfusion.org/Configuration) is required for `ffmpeg-devel` * Fedora 32 or later is required. @@ -208,7 +213,7 @@ First, enable the community repository; [see here](https://wiki.alpinelinux.org/ # Enable the community repository setup-apkrepos -c # Install -apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl2-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev opus-dev jq patch +apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl3-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev opus-dev jq patch ``` @@ -216,7 +221,7 @@ apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libqua Void Linux ```sh -xbps-install -Su git make cmake clang pkg-config patch SPIRV-Tools-devel SPIRV-Headers lz4 liblz4-devel boost-devel ffmpeg6-devel catch2 Vulkan-Utility-Libraries Vulkan-Headers glslang openssl-devel SDL2-devel quazip-qt6-devel qt6-base-devel qt6-qt5compat-devel qt6-charts-devel fmt-devel json-c++ libenet-devel libusb-devel +xbps-install -Su git make cmake clang pkg-config patch SPIRV-Tools-devel SPIRV-Headers lz4 liblz4-devel boost-devel ffmpeg6-devel catch2 Vulkan-Utility-Libraries Vulkan-Headers glslang openssl-devel SDL3-devel quazip-qt6-devel qt6-base-devel qt6-qt5compat-devel qt6-charts-devel fmt-devel json-c++ libenet-devel libusb-devel ``` Yes, `nlohmann-json` is just named `json-c++`. Why? @@ -237,7 +242,7 @@ If you're going for a pure build (i.e no downloaded deps), use `-DYUZU_USE_CPM=O Install dependencies from **[Homebrew](https://brew.sh/)** ```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 +brew install autoconf automake boost ffmpeg fmt glslang hidapi libtool libusb lz4 ninja nlohmann-json openssl pkg-config qt@6 sdl3 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`. @@ -254,7 +259,10 @@ brew install molten-vk
FreeBSD -As root run: `pkg install 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 devel/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib devel/unordered-dense vulkan-headers quazip-qt6` +As root run: +```sh +pkg install devel/cmake devel/sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base x11-toolkits/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib graphics/vulkan-utility-libraries graphics/vulkan-headers graphics/spirv-headers quazip-qt6 +``` If using FreeBSD 12 or prior, use `devel/pkg-config` instead. @@ -264,7 +272,11 @@ If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
NetBSD -For NetBSD +10.1: `pkgin install git cmake boost fmtlib SDL2 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq libopus qt6 cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx`. +For NetBSD +10.1: + +```sh +pkgin install git cmake boost fmtlib SDL3 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq libopus qt6-qtbase qt6-qtcharts qt6-qtmultimedia qt6-qttools cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx frozen +``` [Caveats](./Caveats.md#netbsd). @@ -274,7 +286,7 @@ For NetBSD +10.1: `pkgin install git cmake boost fmtlib SDL2 catch2 libjwt spirv ```sh pkg_add -u -pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gmake qt6 jq fmt nlohmann-json enet boost vulkan-utility-libraries vulkan-headers spirv-headers spirv-tools catch2 sdl2 libusb1-1.0.29 +pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gmake qt6 jq fmt nlohmann-json enet boost vulkan-utility-libraries vulkan-headers spirv-headers spirv-tools catch2 sdl3 libusb1-1.0.29 quazip-qt6 ``` [Caveats](./Caveats.md#openbsd). @@ -284,7 +296,7 @@ pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gm DragonFlyBSD ```sh -pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmake jq nlohmann-json enet spirv-tools sdl2 vulkan-utility-libraries vulkan-headers catch2 libfmt openssl liblz4 boost-libs cpp-httplib qt6-base qt6-charts quazip-qt6 unordered-dense libva-vdpau-driver libva-utils libva-intel-driver +pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmake jq nlohmann-json enet spirv-tools sdl3 vulkan-utility-libraries vulkan-headers catch2 libfmt openssl liblz4 boost-libs cpp-httplib qt6-base qt6-charts quazip-qt6 libva-vdpau-driver libva-utils libva-intel-driver ``` [Caveats](./Caveats.md#dragonflybsd). @@ -294,7 +306,7 @@ pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmak OpenIndiana ```sh -sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl opus sdl2 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt +sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl opus sdl3 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt ``` [Caveats](./Caveats.md#openindiana). @@ -318,7 +330,7 @@ sudo pkgin install git cmake autoconf build-essential libusb-1 nasm gcc13 ```sh BASE="git make autoconf libtool automake-wrapper jq patch" -MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus libusb unordered_dense openssl SDL2" +MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus libusb openssl SDL3" # Either x86_64 or clang-aarch64 (Windows on ARM) packages="$BASE" for pkg in $MINGW; do @@ -344,7 +356,7 @@ pacman -Syuu --needed --noconfirm $packages HaikuOS ```sh -pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl2_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel +pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel ``` [Caveats](./Caveats.md#haikuos). @@ -355,9 +367,11 @@ pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel b ```sh sudo pkg update -sudo pkg install git cmake ffmpeg6 sdl2 zlib llvm18 +sudo pkg install git cmake ffmpeg6 zlib llvm18 ``` +RedoxOS currently does not support SDL3. You will have to compile it yourself and pray. + [Caveats](./Caveats.md#redoxos).
diff --git a/docs/DesignOverview.md b/docs/DesignOverview.md new file mode 100644 index 0000000000..c6a328cb9c --- /dev/null +++ b/docs/DesignOverview.md @@ -0,0 +1,47 @@ +# Design Overview + +Modern game consoles require heavy power to be emulated appropriately. This is why the emulator uses an approach known as HLE (High-Level-Emulation), in a nutshell: Instead of accurately emulating every subsystem that forms part of a component, emulate the resulting visible I/O interface instead. + +For example, take a disk write, instead of emulating a proper SD card we instead use the C++ standard library for I/O. Additionally we use the abstractions provided by the `fs` service to "lie" to programs about certain SD card properties. Notably this includes making up sizes for the fake SD card, giving "realistic" values or expected outputs for a given card, and so on. And instead of writing to an actual SD card, the emulator simply writes to a file. + +This also means grand part of the emulator consists of just re-implementing firmware but using HLE primitives; for example audio doesn't go to an emulated audio device, but rather gets processed on the fly by a dedicated service and then passed to SDL3/cubeb/etc. + +As such, many of the systems implemented are not 100% accurate to the original software, but they're "good enough" to pass as being so. While we do strive to maintain high compatibility (especially with homebrew), there are realistic limitations to these approaches. + +No formal fuzzing or formal verification has been done on the emulator as a whole, this means there may be hundreds of bugs hiding in each subsystem. As such, output may also not correctly match due to those aforementioned issues. + +## src/android/ + +Entire Android frontend, written mostly in Kotlin and generally having asinine hacks (thanks Android) due to the particularly horrific (and particular way) to do things. + +## src/audio_core/ + +Handles everything related to audio, this is where most of the filtering and processing occurs (CPU intensive task!). + +## src/common/ + +The [common](../src/common) folder contains just your basic polyfills for whatever missing functionality. We heavily encourage new PRs to make use of one of the dependencies, or the standard C++ library. Minimizing the amount of things we reinvent the wheel for is always a good thing. + +## src/core/ + +The [core folder](../src/core) is the main heart of the emulator, while we don't officially support having other frontends out of the box (RetroArch, for example); any prospective developer can reuse this library (compiled as one by CMake) to stitch up their own RetroArch core, for example. + +## src/dynarmic/ + +See [Dynarmic](./dynarmic). + +## src/hid_core/ + +Input, joysticks, controllers and everything related to input devices. + +## src/shader_recompiler/ + +Dedicated shader recompiler to translate Maxwell assembly code to either SPIR-V, GLSL or GLASM. + +## src/video_core/ + +Most of the things here have their own dedicated section. In short this is basically the entire Tegra NVIDIA Maxwell GPU emulation. Additionally it includes some [extra effects](../src/video_core/host_shaders/opengl_smaa.glsl) to emulate MSAA, D24 copies or as polyfill. + +Available backends are: Null, Vulkan, and OpenGL. + +See [NVIDIA GPU](./NvidiaGpu.md). diff --git a/docs/Development.md b/docs/Development.md index 0c7543c156..ddffb0cbb4 100644 --- a/docs/Development.md +++ b/docs/Development.md @@ -91,7 +91,7 @@ You may additionally need the `Qt Extension Pack` extension if building Qt. # Build speedup -If you have an HDD, use ramdisk (build in RAM), approximatedly you need 4GB for a full build with debug symbols: +If you have an HDD, use ramdisk (build in RAM), approximately you need 4GB for a full build with debug symbols: ```sh mkdir /tmp/ramdisk @@ -110,7 +110,7 @@ A general rule of thumb, before uploading files: - PNG files: Use [optipng](https://web.archive.org/web/20240325055059/https://optipng.sourceforge.net/). - SVG files: Use [svgo](https://github.com/svg/svgo). -May not be used but worth mentioning nonethless: +May not be used but worth mentioning nonetheless: - OGG files: Use [OptiVorbis](https://github.com/OptiVorbis/OptiVorbis). - Video files: Use ffmpeg, preferably re-encode as AV1. diff --git a/docs/HosKernel.md b/docs/HosKernel.md new file mode 100644 index 0000000000..87df2072a5 --- /dev/null +++ b/docs/HosKernel.md @@ -0,0 +1,31 @@ +# HOS Kernel + +In brief, the HOS kernel is a microkernel, all services and programs run in userspace, the primary way to do communication between these is via `HIPC` (not covered here); otherwise most of the primitives reside in the forms of syscalls invoked via `svc #imm`. The kernel supports both 32-bit and 64-bit programs, and has the capacity to use 32, 36 and 39 bits of address space for spawned processes. Most of the networking stack is based off FreeBSD's network stack. + +The emulator implements the majority of the syscalls pertaining to the HOS kernel itself. When we talk about the HOS Kernel (in the context of the emulator) we are strictly speaking about the mechanisms from which syscalls are handled (and it's subsequent side effects, such as the page table book-keeping). The emulator at it's current state is unable to load a custom low-level kernel and do supervisor-level emulation. + +Most programs in NX eventually invoke an `svc`, which, depending on it's immediate value, will go on to be dispatched into one of the specific syscall handlers. + +These can be seen in [svc.cpp](/src/core/hle/kernel/svc.cpp). All of these correspond to syscalls which userspace programs may perform. + +In turn, these syscalls create the mechanisms that allows programs to use CMIF/TIPC as their primary IPC form to contact other services/processes running on the system, the details of which will not be covered here, but you can consult the relevant [SwitchBrew article: 'HIPC'](https://switchbrew.org/wiki/HIPC). + +From the point of view of the programs, no special devices (such as PCIE, Realtek drivers, Bluetooth or USB) has to be handled by the emulator; this is because most of the fun occurs in specialized services such as `usb:u` or `pcie` services. Which aren't emulated (yet). + +Due to the nature of syscalls, many of them interact with memory. The emulated kernel has an internal tree-like structure, borrowed from FreeBSD's intrusive red-black tree; this is used to track and find mappings added or removed. Thus most of the process space is emulated in this way. + +The kernel keeps it's own separate pagetable, in a traditional sense, each process has it's own pagetable, this is true for HOS as well. + +Every process keeps it's own tracking of the following structures: +- Name (13 characters) +- 64-bit ID +- A handle table +- Exclusive monitor +- Threads +- Held locks +- Thread local pages +- A page table for each process + +The emulator willingly restricts itself to only use 4 threads (to emulate 4 cores), this is because most existing applications do not benefit greatly from the added core count, and in fact can be detrimental due to extra contention. This translates equitatively to about 4 `ArmInterface` slots for each process, these are then redirected to whatever is the last `pc` of the last thread running on the core is meant to be; proceed to run it, then when returning (due to halt or interruption), proceed to reschedule the thread. + +The scheduler as-is isn't 100% faithful to the original (for example the original is cooperative and not preemptive), and has great timing variance (especially due to the fact the emulator can run in systems with wildly different timings). diff --git a/docs/NvidiaGpu.md b/docs/NvidiaGpu.md index d7fe3d86d3..a595f61a66 100644 --- a/docs/NvidiaGpu.md +++ b/docs/NvidiaGpu.md @@ -853,6 +853,8 @@ Texture Query. Vote Across SIMD Thread Group +`VOTE_vtg` is a kepler leftover. + # VSET `0100 000- ---- ----` diff --git a/docs/Options.md b/docs/Options.md index 3eb6effe92..ece6bdeba6 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -29,8 +29,7 @@ These options control dependencies. - `YUZU_TZDB_PATH` (string) Path to a pre-downloaded timezone database (useful for nixOS and Gentoo) - `YUZU_USE_BUNDLED_MOLTENVK` (ON, macOS only) Download bundled MoltenVK lib - `YUZU_USE_BUNDLED_OPENSSL` (ON for MSVC, Android, Solaris, and OpenBSD) Download bundled OpenSSL build -- `YUZU_USE_EXTERNAL_SDL2` (OFF) Compiles SDL2 from source -- `YUZU_USE_BUNDLED_SDL2` (ON for MSVC) Download a prebuilt SDL2 +- `YUZU_USE_BUNDLED_SDL3` (ON for MSVC) Download a prebuilt SDL3 ### Miscellaneous @@ -44,6 +43,11 @@ These options control dependencies. - UNIX may be better off appending `-flto=thin` to compiler args - `USE_FASTER_LINKER` (OFF) Check if a faster linker is available - Not recommended outside of Linux +- `YUZU_INSTALL_UDEV_RULES` (OFF) Install udev rules to enable hidraw access + - Needed for gyroscopes + - Only available on Linux +- `ENABLE_DEBUG_TOOLS` (OFF) Enables debugging and development tools, see [tools](../tools/README.md). +- `ENABLE_WERROR` (ON) Enables warnings as errors (-Werror). ### Flavors @@ -60,7 +64,7 @@ These options control executables and build flavors. **Desktop only**: -- `YUZU_CMD` (ON) Compile the SDL2 frontend (eden-cli) +- `YUZU_CMD` (ON) Compile the SDL-based frontend (eden-cli) - `YUZU_ROOM` (OFF) Compile dedicated room functionality into the main executable - `YUZU_ROOM_STANDALONE` (OFF) Compile a separate executable for room functionality - `YUZU_STATIC_ROOM` (OFF) Compile the room executable *only* as a static, portable executable @@ -72,7 +76,7 @@ The following options are desktop only. - `ENABLE_LIBUSB` (ON) Enable the use of the libusb input backend (HIGHLY RECOMMENDED) - `ENABLE_OPENGL` (ON) Enable the OpenGL graphics backend - - Unavailable on Windows/ARM64 + - Unavailable on Windows/ARM64 and on Android - You probably shouldn't turn this off. ### Qt @@ -96,5 +100,6 @@ The following options were a part of Eden at one point, but have since been reti - `ENABLE_SDL2` - While technically possible to *not* use SDL2 on desktop, this is **NOT** a supported configuration under any means, and adding this matrix to our build system was not worth the effort. - `YUZU_USE_CPM` - This option once had a purpose, but that purpose has long since passed us by. *All* builds use CPMUtil to manage dependencies now. - If you want to *force* the usage of system dependencies, use `-DCPMUTIL_FORCE_SYSTEM=ON`. +- `YUZU_USE_EXTERNAL_SDL` - This is now handled automatically. It was included even after CPM for purposes that have not applied for a very long time. See `src/dynarmic/CMakeLists.txt` for additional options--usually, these don't need changed diff --git a/docs/README.md b/docs/README.md index 68775f99d8..5bcaf30239 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,26 +2,40 @@ Are you just a casual user? Take a look at our [User Handbook](./user) then! -This contains documentation created by developers. This contains build instructions, guidelines, instructions/layouts for [cool stuff we made](./CPMUtil), and more. +If you want to register/signup as a contributor, take a gander at [CONTRIBUTING.md](../CONTRIBUTING.md). -- **[General Build Instructions](Build.md)** -- **[CMake Options](Options.md)** -- **[Cross Compiling](CrossCompile.md)** -- **[Development Guidelines](Development.md)** -- **[Dependencies](Deps.md)** +This contains documentation created by developers, build instructions, guidelines, instructions/layouts for [cool stuff we made](./CPMUtil.md), and more. + +- **[General Build Instructions](./Build.md)** +- **[CMake Options](./Options.md)** +- **[Cross Compiling](./CrossCompile.md)** +- **[Development Guidelines](./Development.md)** +- **[Dependencies](./Deps.md)** - **[Debug Guidelines](./Debug.md)** -- **[CPM - CMake Package Manager](./CPMUtil)** -- **[Platform-Specific Caveats](Caveats.md)** + - **[RenderDoc](./Debug.md#renderdoc)** +- **[CPM - CMake Package Manager](./CPMUtil.md)** +- **[Platform-Specific Caveats](./Caveats.md)** - **[The NVIDIA SM86 (Maxwell) GPU](./NvidiaGpu.md)** -- **[Dynarmic](./dynarmic)** -- **[Cross compilation](./CrossCompile.md)** - **[Driver Bugs](./DriverBugs.md)** - **[Building Older Commits](./build/OlderCommits.md)** +- Subsystems: + - **[Design Overview](./DesignOverview.md)** + - **[Dynarmic](./dynarmic/README.md)** + - **[HOS Kernel](./HosKernel.md)** + - **[Settings](./Settings.md)** ## Policies Policies and information on development. - **[AI and LLM Usage](./policies/AI.md)** -- **[Release Policy](./policies/Release.md)** - **[Coding guidelines](./policies/Coding.md)** +- **[Contributing](../CONTRIBUTING.md)** + +## Externals + +Other useful resources in general, take a quick read if you need. + +- **[SwitchBrew](https://switchbrew.org/wiki/Main_Page)** +- **[IPS file format](https://zerosoft.zophar.net/ips.php)** +- **[IPSwitch file format](https://github.com/3096/ipswitch)** diff --git a/docs/SIGNUP.md b/docs/SIGNUP.md deleted file mode 100644 index 5e5f7cebf6..0000000000 --- a/docs/SIGNUP.md +++ /dev/null @@ -1,76 +0,0 @@ -# Signup - -To prevent spam and reduce bandwidth usage, registration is closed, and will likely remain this way. - -## Valid Reasons - -First of all, you MUST have a valid reason to sign up for our Git. Valid reasons include (but are not limited to): - -- I want to add feature XYZ... -- I want to improve the macOS version... -- I want to improve the Vulkan backend... -- I want to fix bug XYZ... -- I have experience in XYZ... -- I can provide insight on XYZ... - -## Invalid Reasons - -The following are not valid reasons to sign up: - -- I want to contribute to Eden. - * Be at least somewhat specific! We always welcome contributors and developers, but generic "I want to contribute" messages don't give us enough information. -- I want to support Eden. - * If you wish to support us through development, be more specific; otherwise, to support us, check out our [donations page](https://eden-emu.dev/donations). -- I want to report issues. - * Most of our issue tracking is handled on [GitHub](https://github.com/eden-emulator/Issue-Reports) for the time being. This is subject to change. -- I want to play/use Eden. - * To download and use Eden, see our [Releases page](https://github.com/eden-emulator/Releases/releases)! -- I want to see the source code. - * To see Eden's source code, go [here](https://git.eden-emu.dev/eden-emu/eden). - -## Other Information - -Requests that appear suspicious, automated, OR blank will generally be automatically filtered. In cases of suspicion, or any of the invalid reasons listed above, you may receive an email back asking for clarification. - -You MUST use the following format: - -``` -Subject: [Eden Git] Registration Request -Username: -Email: -I wish to sign up because... -``` - -Email notifications are disabled for the time being, so you don't have to use a real email. If you wish to remain anonymous, either send a separate email asking for access to a shared anonymous account, *or* create a fake username and email. Do note that the email you sign up with is used to accredit commits on the web UI, and *must* match your configured GPG key. - -## Patches - -In general, PRs are the preferred method of tracking patches, as they allow us to go through our standard triage, CI, and testing process without having to deal with the minutiae of incremental patches. However, we also understand that many people prefer to use raw patches, and that's totally okay! While we currently don't have a mailing list, we do accept email patches. To do so: - -1. Make your changes on a clean copy of the master branch -2. Commit your changes with a descriptive, well-formed message (see the [commit message docs](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/Development.md#pull-requests)), and a proper description thoroughly explaining your changes. - * Note that we don't need to know all the individual details about your code. A description explaining the motivation and general implementation of your changes is enough, alongside caveats and any potential blockers. -3. Format your patch with `git format-patch -1 HEAD`. -4. Email us with the subject `[Eden] [PATCH] `, with a brief description of your patch, and the previously-formatted patch file as an attachment. - * If you don't include the first two bracketed parts, your email may be lost! - -The following emails are currently set up to receive and process patches: - -- [eden@eden-emu.dev](mailto:eden@eden-emu.dev] -- [crueter@eden-emu.dev](mailto:eden@eden-emu.dev) - -## Instructions - -If you have read everything above and affirm that you will not abuse your access, click the summary below to get the email to send your request to. - -
-I affirm that I have read ALL of the information above, and will not abuse my access to Eden, nor will I send unnecessary spam to the following email. - -Email [crueter@crueter.xyz](mailto:crueter@crueter.xyz) with the format above. - -Once your request is processed, you should receive a confirmation email from crueter with your password alongside a link to a repository containing instructions on SSH, etc. Note that you are required to change your password. If your request is rejected, you will receive a notice as such, asking for clarification if needed. If you do not receive a response in 48 hours, you may send another email. - -> [!WARNING] -> Some email providers may place the response email in your spam/junk folder; notable offenders include Gmail and Outlook. *Always* ensure to check your Spam/Junk folder, until Google/Microsoft finally end their vendetta against the great evil of my `.xyz` domain. - -
diff --git a/docs/Settings.md b/docs/Settings.md new file mode 100644 index 0000000000..94fbc7e440 --- /dev/null +++ b/docs/Settings.md @@ -0,0 +1,343 @@ +# Settings + +> [!WARNING] +> This guide is intended for developers ONLY. If you're looking for configuring the emulator itself, read **[the user handbook](./user/README.md)**. + +Settings on the emulator are very important, toggles and such can be used to guard and/or add branches to paths where some games may crash while others won't, and viceversa. + +However, this process can be tedious for those unfamiliar; this document serves as an outline/documentation for the settings subsystem. + +## Index + +* [Adding Debug Knobs](#adding-debug-knobs) + * [Advantages](#advantages) + * [Usage](#usage) + * [Accessing Debug Knobs (dev side)](#accessing-debug-knobs-dev-side) + * [Setting Debug Knobs (user side)](#setting-debug-knobs-user-side) + * [Bit Manipulation Examples](#bit-manipulation-examples) + * [Terminology and user communication](#terminology-and-user-communication) + * [Examples](#examples) + * [Example 1: Conditional Debug Logging](#example-1-conditional-debug-logging) + * [Example 2: Performance Tuning](#example-2-performance-tuning) + * [Example 3: Feature Gating](#example-3-feature-gating) + * [Best Practices](#best-practices) +* [Adding Boolean Settings Toggles](#adding-boolean-settings-toggles) + * [Step 1 - Common Setting](#step-1-common-setting) + * [Step 2 - Qt Toggle](#step-2-qt-toggle) + * [Step 3 - Kotlin (Android)](#step-3-kotlin-android) + * [Step 3.1 - BooleanSetting.kt](#step-3-1-booleansetting-kt) + * [Step 3.2 - SettingsItem.kt](#step-3-2-settingsitem-kt) + * [Step 3.3 - SettingsFragmentPresenter.kt](#step-3-3-settingsfragmentpresenter-kt) + * [Step 3.4 - Localization](#step-3-4-localization) + * [Step 4 - Use Your Toggle](#step-4-use-your-toggle) + * [Best Practices](#best-practices) + +## Adding Boolean Settings Toggles + +This guide will walk you through adding a new boolean toggle setting to Eden's configuration across both Qt's (PC) and Kotlin's (Android) UIs. + +--- + +### Step 1 - Common Setting + +Firstly add your desired toggle: + +Example: `src/common/setting.h` + +```cpp +SwitchableSetting your_setting_name{linkage, false, "your_setting_name", Category::RendererExtensions}; +``` + +Remember to add your toggle to the appropriate category, for example: + +Common Categories: + +* Category::Renderer +* Category::RendererAdvanced +* Category::RendererExtensions +* Category::System +* Category::Core + +> [!WARNING] +> If you wish for your toggle to be `on by default` then change `false` to `true` after `linkage,`. + +--- + +### Step 2 - Qt Toggle + +Add the toggle to the Qt UI, where you wish for it to appear and place it there. + +Example: `src/qt_common/config/shared_translation.cpp` + +```cpp +INSERT(Settings, + your_setting_name, + tr("Your Setting Display Name"), + tr("Detailed description of what this setting does.\n" + "You can use multiple lines.\n" + "Explain any caveats or requirements.")); +``` + +#### Make sure to: + +* Keep display naming consistent +* Put detailed info in the description +* Use `\n` for line breaks in descriptions + +--- + +### Step 3 - Kotlin (Android) + +#### Step 3.1 - BooleanSetting.kt + +Add where it should be in the settings. + +Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt` + +```kts +RENDERER_YOUR_SETTING_NAME("your_setting_name"), +``` + +#### Make sure to: + +* Ensure the prefix naming matches the intended category. + +--- + +#### Step 3.2 - SettingsItem.kt + +Add the toggle to the Kotlin (Android) UI + +Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt` + +```kts +put( + SwitchSetting( + BooleanSetting.RENDERER_YOUR_SETTING_NAME, + titleId = R.string.your_setting_name, + descriptionId = R.string.your_setting_name_description + ) +) +``` + +--- + +#### Step 3.3 - SettingsFragmentPresenter.kt + +Add your setting within the right category. + +Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt` + +```kts +add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key) +``` + +> [!WARNING] +> Remember, placing matters! Settings appear in the order of where you add them. + +--- + +#### Step 3.4 - Localization + +Add your setting and description in the appropriate place. + +Example: `src/android/app/src/main/res/values/strings.xml` + +```xml +Your Setting Display Name +Detailed description of what this setting does. Explain any caveats, requirements, or warnings here. +``` + +--- + +### Step 4 - Use Your Toggle! + +Now the UI part is done find a place in the code for the toggle, +And use it to your heart's desire! + +Example: + +```cpp +const bool your_value = Settings::values.your_setting_name.GetValue(); + +if (your_value) { + // Do something when enabled +} +``` + +If you wish to do something only when the toggle is disabled, +Use `if (!your_value) {` instead of `if (your_value) {`. + +--- + +### Best Practices + +* Naming - Use clear, descriptive names. Something for both the devs and the users. +* Defaults - Choose safe default values (usually false for new features). +* Documentation - Write clear descriptions explaining when and why to use the setting. +* Categories - Put settings in the appropriate category. +* Order - Place related settings near each other. +* Testing - Always test on both PC and Android before committing when possible. + +Thank you for reading, I hope this guide helped you making your toggle! + +## Adding Debug Knobs + +Debug Knobs is a 16-bit integer setting (`debug_knobs`) in the Eden Emulator that serves as a bitmask for gating various testing and debugging features. This allows developers and advanced users to enable or disable specific debug behaviors without requiring deploying of complete but temporary toggles. + +The setting ranges from 0 to 65535 (0x0000 to 0xFFFF), where each bit represents a different debug feature flag. + +--- + +### Advantages + +The main advantage is to avoid deploying new disposable toggles (those made only for testing stage, and are disposed once new feature gets good to merge). This empowers devs to be free of all frontend bureaucracy and hassle of new toggles. + +Common advantages recap: + +* **Fine-Grained Control**: Enable or disable up to 16 individual debug features independently using bit manipulation on a single build +* **Runtime Configuration**: Change debug behavior at runtime the same way as new toggles would do +* **Safe incremental development**: New debug features can be added while impact can be isolated from previous deployments + +### Usage + +#### Accessing Debug Knobs (dev side) + +Use the `Settings::GetDebugKnobAt(u8 i)` function to check if a specific bit is set: + +```cpp +//cpp side +#include "common/settings.h" + +//To use it as a general purpose uint var: +unsigned int debug_knobs = Settings::values.debug_knobs.GetValue(); + +// Check if bit 0 is set +bool feature_enabled = Settings::GetDebugKnobAt(0); + +// Check if bit 15 is set +bool another_feature = Settings::GetDebugKnobAt(15); +``` + +```kts +//kotlin side +import org.yuzu.yuzu_emu.features.settings.model.Settings + +//To use it as a general purpose uint var +val debug_knobs: Int = UShortSetting.DEBUG_KNOBS.getInt() + +// Check if bit x is set +bool feature_enabled = Settings.GetDebugKnobAt(x); //x as integer from 0 to 15 +``` + +The function returns `true` if the specified bit (0-15) is set in the `debug_knobs` value, `false` otherwise. + +#### Setting Debug Knobs (user side) + +Developers must inform which knobs are tied to each functionality to be tested. + +The debug knobs value can be set through: + +1. **Desktop UI**: In the Debug configuration tab, there's a spinbox for "Debug knobs" (0-65535) +2. **Android UI**: Available as an integer setting in the Debug section +3. **Configuration Files**: Set the `debug_knobs` value in the emulator's configuration + +#### Bit Manipulation Examples + +To enable specific features, calculate the decimal value by setting the appropriate bits: + +* **Enable only bit 0**: Value = 1 (2^0) +* **Enable only bit 1**: Value = 2 (2^1) +* **Enable bits 0 and 1**: Value = 3 (2^0 + 2^1) +* **Enable bit 15**: Value = 32768 (2^15) + +### Terminology and user communication + +There are two main confusions when talking about knobs: + +#### Whether it's zero-based or one-based + +Sometimes when an user reports: knobs 1 and 2 gets better performance, dev may get confuse whether he means the knobs 1 and 2 literally, or the 1st and 2nd knobs (knobs 0 and 1). + +Debug knobs are **zero-based**, which means: + +* The first knob is the knob(0) (or knob0 henceforth), and the last one is the 15 (knob15, likewise) +* You can talk: "knob0 is enabled/disabled", "In this video i was using only knobs 0 and 2", etc. + +#### Whether one is talking about the knob itself or about the entire parameter value (which represents all knobs) + +Sometimes when an user reports: knob 3 results, it's unclear whether he's referring to knob setting with value 3 (which means both knob 0 and 1 are enabled), or to knob(3) specifically. +Whenever you're instructing tests or reporting results, be precise about whether one you're talking to avoid confusion: + +#### Setting based terminology + +ALWAYS use the word in PLURAL (knobs), without mentioning which one, to refer to the setting, aka multiple knobs at once: +Examples: + +- **knobs=0**: no knobs enabled +- **knobs=1**: knob0 enabled, others disabled +- **knobs=2**: knob1 enabled, others disabled +- **knobs=3**: knobs 0 and 1 enabled, others disabled + +... + +#### Knob based terminology + +Use the word in SINGULAR (knob), or in plural but referring which ones, when meaning multiple knobs at once: +Examples: + +- **knob0**: knob 0 enabled, others disabled +- **knob1**: knob 1 enabled, others disabled +- **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled + +... + +### Examples + +#### Example 1: Conditional Debug Logging + +```cpp +void SomeFunction() { + if (Settings::GetDebugKnobAt(0)) { + LOG_DEBUG(Common, "Debug feature 0 is enabled"); + // Additional debug code here + } + + if (Settings::GetDebugKnobAt(1)) { + LOG_DEBUG(Common, "Debug feature 1 is enabled"); + // Different debug behavior + } +} +``` + +#### Example 2: Performance Tuning + +```cpp +bool UseOptimizedPath() { + // Skip optimization if debug bit 2 is set for testing + return !Settings::GetDebugKnobAt(2); +} +``` + +#### Example 3: Feature Gating + +```cpp +void ExperimentalFeature() { + static constexpr u8 EXPERIMENTAL_FEATURE_BIT = 3; + + if (!Settings::GetDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) { + // Fallback to stable implementation + StableImplementation(); + return; + } + + // Experimental implementation + ExperimentalImplementation(); +} +``` + +### Best Practices + +* This setting is intended for development and testing purposes only +* Knobs must be unwired before PR creation +* The setting is per-game configurable, allowing different debug setups for different titles diff --git a/docs/dynarmic/Design.md b/docs/dynarmic/Design.md index 020cfd65bd..77272e2220 100644 --- a/docs/dynarmic/Design.md +++ b/docs/dynarmic/Design.md @@ -1,44 +1,44 @@ # Dynarmic Design Documentation -Dynarmic is a dynamic recompiler for the ARMv6K architecture. Future plans for dynarmic include +Dynarmic is a dynamic recompiler for the ARMv6K architecture. Future plans for Dynarmic include support for other versions of the ARM architecture, having a interpreter mode, and adding support for other architectures. Users of this library interact with it primarily through the interface provided in -[`src/dynarmic/interface`](../src/dynarmic/interface). Users specify how dynarmic's CPU core interacts with +[`src/dynarmic/interface`](../../src/dynarmic/src/dynarmic/interface). Users specify how Dynarmic's CPU core interacts with the rest of their system providing an implementation of the relevant `UserCallbacks` interface. Users setup the CPU state using member functions of `Jit`, then call `Jit::Execute` to start CPU execution. The callbacks defined on `UserCallbacks` may be called from dynamically generated code, so users of the library should not depend on the stack being in a walkable state for unwinding. -* A32: [`Jit`](../src/dynarmic/interface/A32/a32.h), [`UserCallbacks`](../src/dynarmic/interface/A32/config.h) -* A64: [`Jit`](../src/dynarmic/interface/A64/a64.h), [`UserCallbacks`](../src/dynarmic/interface/A64/config.h) +* A32: [`Jit`](../../src/dynarmic/src/dynarmic/interface/A32/a32.h), [`UserCallbacks`](../../src/dynarmic/src/dynarmic/interface/A32/config.h) +* A64: [`Jit`](../../src/dynarmic/src/dynarmic/interface/A64/a64.h), [`UserCallbacks`](../../src/dynarmic/src/dynarmic/interface/A64/config.h) Dynarmic reads instructions from memory by calling `UserCallbacks::MemoryReadCode`. These instructions then pass through several stages: 1. Decoding (Identifying what type of instruction it is and breaking it up into fields) 2. Translation (Generation of high-level IR from the instruction) -3. Optimization (Eliminiation of redundant microinstructions, other speed improvements) +3. Optimization (Elimination of redundant microinstructions, other speed improvements) 4. Emission (Generation of host-executable code into memory) 5. Execution (Host CPU jumps to the start of emitted code and runs it) Using the A32 frontend with the x64 backend as an example: * Decoding is done by [double dispatch](https://en.wikipedia.org/wiki/Visitor_pattern) in - [`src/frontend/A32/decoder/{arm.h,thumb16.h,thumb32.h}`](../src/dynarmic/frontend/A32/decoder/). -* Translation is done by the visitors in [`src/dynarmic/frontend/A32/translate/translate_{arm,thumb}.cpp`](../src/dynarmic/frontend/A32/translate/). - The function [`Translate`](../src/dynarmic/frontend/A32/translate/translate.h) takes a starting memory location, + [`src/frontend/A32/decoder/{arm.h,thumb16.h,thumb32.h}`](../../src/dynarmic/src/dynarmic/frontend/A32/decoder/). +* Translation is done by the visitors in [`src/dynarmic/frontend/A32/translate/translate_{arm,thumb}.cpp`](../../src/dynarmic/src/dynarmic/frontend/A32/translate/). + The function [`Translate`](../../src/dynarmic/src/dynarmic/frontend/A32/translate/a32_translate.cpp) takes a starting memory location, some CPU state, and memory reader callback and returns a basic block of IR. -* The IR can be found under [`src/frontend/ir/`](../src/dynarmic/ir/). -* Optimizations can be found under [`src/ir_opt/`](../src/dynarmic/ir/opt/). -* Emission is done by `EmitX64` which can be found in [`src/dynarmic/backend/x64/emit_x64.{h,cpp}`](../src/dynarmic/backend/x64/). -* Execution is performed by calling `BlockOfCode::RunCode` in [`src/dynarmic/backend/x64/block_of_code.{h,cpp}`](../src/dynarmic/backend/x64/). +* The IR can be found under [`src/frontend/ir/`](../../src/dynarmic/src/dynarmic/ir/). +* Optimizations can be found under [`src/ir/opt_passes.cpp`](../../src/dynarmic/src/dynarmic/ir/opt_passes.cpp). +* Emission is done by `EmitX64` which can be found in [`src/dynarmic/backend/x64/emit_x64.{h,cpp}`](../../src/dynarmic/src/dynarmic/backend/x64/). +* Execution is performed by calling `BlockOfCode::RunCode` in [`src/dynarmic/backend/x64/block_of_code.{h,cpp}`](../../src/dynarmic/src/dynarmic/backend/x64/). ## Decoder The decoder is a double dispatch decoder. Each instruction is represented by a line in the relevant -instruction table. Here is an example line from [`arm.h`](../src/dynarmic/frontend/A32/decoder/arm.h): +instruction table. Here is an example line from [`arm.h`](../../src/dynarmic/src/dynarmic/frontend/A32/decoder/arm.h): INST(&V::arm_ADC_imm, "ADC (imm)", "cccc0010101Snnnnddddrrrrvvvvvvvv") @@ -61,7 +61,7 @@ error results. ## Translator The translator is a visitor that uses the decoder to decode instructions. The translator generates IR code with the -help of the [`IREmitter` class](../src/dynarmic/ir/ir_emitter.h). An example of a translation function follows: +help of the [`IREmitter` class](../../src/dynarmic/src/dynarmic/ir/ir_emitter.h). An example of a translation function follows: bool ArmTranslatorVisitor::arm_ADC_imm(Cond cond, bool S, Reg n, Reg d, int rotate, Imm8 imm8) { u32 imm32 = ArmExpandImm(rotate, imm8); @@ -107,7 +107,7 @@ function analyser in the medium-term future. Dynarmic's intermediate representation is typed. Each microinstruction may take zero or more arguments and may return zero or more arguments. A subset of the microinstructions available is documented below. -A complete list of microinstructions can be found in [src/dynarmic/ir/opcodes.inc](../src/dynarmic/ir/opcodes.inc). +A complete list of microinstructions can be found in [src/dynarmic/ir/opcodes.inc](../../src/dynarmic/src/dynarmic/ir/opcodes.inc). The below lists some commonly used microinstructions. @@ -273,7 +273,7 @@ Exclusive OR (i.e.: XOR) ### Callback: {Read,Write}Memory{8,16,32,64} -```c++ +```cpp ReadMemory8( vaddr) ReadMemory16( vaddr) ReadMemory32( vaddr) @@ -288,7 +288,7 @@ Memory access. ### Terminal: ReturnToDispatch -```c++ +```cpp SetTerm(IR::Term::ReturnToDispatch{}) ``` @@ -297,7 +297,7 @@ The dispatcher will use the value in R15 to determine what comes next. ### Terminal: LinkBlock -```c++ +```cpp SetTerm(IR::Term::LinkBlock{next}) ``` @@ -307,7 +307,7 @@ dispatcher, which will return control to the host. ### Terminal: LinkBlockFast -```c++ +```cpp SetTerm(IR::Term::LinkBlockFast{next}) ``` @@ -316,7 +316,7 @@ This promises guarantees that must be held at runtime - i.e that the program won ### Terminal: PopRSBHint -```c++ +```cpp SetTerm(IR::Term::PopRSBHint{}) ``` @@ -328,7 +328,7 @@ this optimization or doesn't have a RSB may choose to implement this exactly as ### Terminal: If -```c++ +```cpp SetTerm(IR::Term::If{cond, term_then, term_else}) ``` @@ -366,7 +366,7 @@ Do NEVER modify `%r15`, we must make it clear that this register is "immutable" ### `Scratch` -```c++ +```cpp Xbyak::Reg64 ScratchGpr(HostLocList desired_locations = any_gpr); Xbyak::Xmm ScratchXmm(HostLocList desired_locations = any_xmm); ``` @@ -375,7 +375,7 @@ At runtime, allocate one of the registers in `desired_locations`. You are free t ### Pure `Use` -```c++ +```cpp Xbyak::Reg64 UseGpr(Argument& arg); Xbyak::Xmm UseXmm(Argument& arg); OpArg UseOpArg(Argument& arg); @@ -391,7 +391,7 @@ This register **must not** have it's value changed. ### `UseScratch` -```c++ +```cpp Xbyak::Reg64 UseScratchGpr(Argument& arg); Xbyak::Xmm UseScratchXmm(Argument& arg); void UseScratch(Argument& arg, HostLoc host_loc); @@ -409,7 +409,7 @@ You are free to modify the value in the register. The register is discarded at t A `Define` is the defintion of a value. This is the only time when a value may be set. -```c++ +```cpp void DefineValue(IR::Inst* inst, const Xbyak::Reg& reg); ``` @@ -420,7 +420,7 @@ value to the specified register `reg`. Adding a `Define` to an existing value. -```c++ +```cpp void DefineValue(IR::Inst* inst, Argument& arg); ``` @@ -458,7 +458,7 @@ One complication dynarmic has is that a compiled block is not uniquely identifia the PC alone, but bits in the FPSCR and CPSR are also relevant. We resolve this by computing a 64-bit `UniqueHash` that is guaranteed to uniquely identify a block. -```c++ +```cpp u64 LocationDescriptor::UniqueHash() const { // This value MUST BE UNIQUE. // This calculation has to match up with EmitX64::EmitTerminalPopRSBHint @@ -482,18 +482,18 @@ point. Each element in `rsb_location_descriptors` is a `UniqueHash` and they each correspond to an element in `rsb_codeptrs`. `rsb_codeptrs` contains the host addresses for the corresponding the compiled blocks. -`RSBSize` was chosen by performance testing. Note that this is bigger than the +`RSB_SIZE` was chosen by performance testing. Note that this is bigger than the size of the real RSB in hardware (which has 3 entries). Larger RSBs than 8 showed degraded performance. -```c++ +```cpp struct JitState { // ... - static constexpr size_t RSBSize = 8; // MUST be a power of 2. + static constexpr size_t RSB_SIZE = 8; // MUST be a power of 2. u32 rsb_ptr = 0; - std::array rsb_location_descriptors; - std::array rsb_codeptrs; + std::array rsb_location_descriptors; + std::array rsb_codeptrs; void ResetRSB(); // ... @@ -505,7 +505,7 @@ struct JitState { We insert our prediction at the insertion point iff the RSB doesn't already contain a prediction with the same `UniqueHash`. -```c++ +```cpp void EmitX64::EmitPushRSB(IR::Block&, IR::Inst* inst) { using namespace Xbyak::util; @@ -521,7 +521,7 @@ void EmitX64::EmitPushRSB(IR::Block&, IR::Inst* inst) { code->mov(index_reg, dword[code.ABI_JIT_PTR + offsetof(JitState, rsb_ptr)]); code->add(index_reg, 1); - code->and_(index_reg, u32(JitState::RSBSize - 1)); + code->and_(index_reg, u32(JitState::RSB_SIZE - 1)); code->mov(loc_desc_reg, u64(imm64)); CodePtr patch_location = code->getCurr(); @@ -530,7 +530,7 @@ void EmitX64::EmitPushRSB(IR::Block&, IR::Inst* inst) { code->EnsurePatchLocationSize(patch_location, 10); Xbyak::Label label; - for (size_t i = 0; i < JitState::RSBSize; ++i) { + for (size_t i = 0; i < JitState::RSB_SIZE; ++i) { code->cmp(loc_desc_reg, qword[code.ABI_JIT_PTR + offsetof(JitState, rsb_location_descriptors) + i * sizeof(u64)]); code->je(label, code->T_SHORT); } @@ -544,12 +544,12 @@ void EmitX64::EmitPushRSB(IR::Block&, IR::Inst* inst) { In pseudocode: -```c++ - for (i := 0 .. RSBSize-1) +```cpp + for (i := 0 .. RSB_SIZE-1) if (rsb_location_descriptors[i] == imm64) goto label; rsb_ptr++; - rsb_ptr %= RSBSize; + rsb_ptr %= RSB_SIZE; rsb_location_desciptors[rsb_ptr] = imm64; //< The UniqueHash rsb_codeptr[rsb_ptr] = /* codeptr corresponding to the UniqueHash */; label: @@ -559,7 +559,7 @@ label: To check if a predicition is in the RSB, we linearly scan the RSB. -```c++ +```cpp void EmitX64::EmitTerminalPopRSBHint(IR::Term::PopRSBHint, IR::LocationDescriptor initial_location) { using namespace Xbyak::util; @@ -571,7 +571,7 @@ void EmitX64::EmitTerminalPopRSBHint(IR::Term::PopRSBHint, IR::LocationDescripto code->or_(rbx, rcx); code->mov(rax, u64(code->GetReturnFromRunCodeAddress())); - for (size_t i = 0; i < JitState::RSBSize; ++i) { + for (size_t i = 0; i < JitState::RSB_SIZE; ++i) { code->cmp(rbx, qword[code.ABI_JIT_PTR + offsetof(JitState, rsb_location_descriptors) + i * sizeof(u64)]); code->cmove(rax, qword[code.ABI_JIT_PTR + offsetof(JitState, rsb_codeptrs) + i * sizeof(u64)]); } @@ -582,10 +582,10 @@ void EmitX64::EmitTerminalPopRSBHint(IR::Term::PopRSBHint, IR::LocationDescripto In pseudocode: -```c++ +```cpp rbx := ComputeUniqueHash() rax := ReturnToDispatch -for (i := 0 .. RSBSize-1) +for (i := 0 .. RSB_SIZE-1) if (rbx == rsb_location_descriptors[i]) rax = rsb_codeptrs[i] goto rax diff --git a/docs/dynarmic/README.md b/docs/dynarmic/README.md index bfba7c9cb0..0fa893d550 100644 --- a/docs/dynarmic/README.md +++ b/docs/dynarmic/README.md @@ -3,175 +3,20 @@ Dynarmic A dynamic recompiler for ARM. -Highlight features: +*Note that an adversarial guest program [can determine if it's being ran under Dynarmic](#disadvantages-of-dynarmic). Preventing this is not a goal of this project.* -- Fast dynamic binary translation via Just-in-Time compilation -- Clean API -- Implemented in modern C++20 -- Hooks exposed for easy code instrumentation -- Code injection support for very fine-grained instrumentation -- Support for unusual address space setups (bring-your-own memory system) -- Native support for most popular operating systems (Windows, macOS, Linux, FreeBSD, OpenBSD, NetBSD, Android) +Cortex-A57 (32 and 64 bit) is the emulated guest target. See [ArchVersion](../../src/dynarmic/src/dynarmic/interface/A32/arch_version.h). -*Please note that an adversarial guest program [can determine if it is being run under dynarmic](#disadvantages-of-dynarmic). Preventing this is not a goal of this project.* +The only supported host architectures are x86-64, and AArch64. There are no plans to support any 32-bit architectures. -### Supported guest architectures - -* v3 -* v4 -* v4T -* v5TE -* v6K -* v6T2 -* v7A -* 32-bit v8 -* 64-bit v8 - -You can specify the specific guest version using [ArchVersion](src/dynarmic/interface/A32/arch_version.h). - -There are no plans to support v1 or v2. - -### Supported host architectures - -* x86-64 -* AArch64 - -There are no plans to support any 32-bit architecture. - -Important API Changes in v6.x Series ------------------------------------- - -* **v6.7.0** - * To support use cases where one wants to have the guest to have the same address space as the host, `nullptr` is now a valid value for `fastmem_pointer`. - **This change is not backwards-compatible.** If you were previously using `nullptr` to represent an invalid fastmem arena, you will now have to use `std::nullopt`. - - -Documentation -------------- +See [an example usage](../../src/dynarmic/tests/print_info.cpp). Design documentation can be found at [./Design.md](./Design.md). - -Usage Example -------------- - -The below is a minimal example. Bring-your-own memory system. - -```cpp -#include -#include -#include -#include - -#include "dynarmic/interface/A32/a32.h" -#include "dynarmic/interface/A32/config.h" - -using u8 = std::uint8_t; -using u16 = std::uint16_t; -using u32 = std::uint32_t; -using u64 = std::uint64_t; - -class MyEnvironment final : public Dynarmic::A32::UserCallbacks { -public: - u64 ticks_left = 0; - std::array memory{}; - - u8 MemoryRead8(u32 vaddr) override { - if (vaddr >= memory.size()) { - return 0; - } - return memory[vaddr]; - } - - u16 MemoryRead16(u32 vaddr) override { - return u16(MemoryRead8(vaddr)) | u16(MemoryRead8(vaddr + 1)) << 8; - } - - u32 MemoryRead32(u32 vaddr) override { - return u32(MemoryRead16(vaddr)) | u32(MemoryRead16(vaddr + 2)) << 16; - } - - u64 MemoryRead64(u32 vaddr) override { - return u64(MemoryRead32(vaddr)) | u64(MemoryRead32(vaddr + 4)) << 32; - } - - void MemoryWrite8(u32 vaddr, u8 value) override { - if (vaddr >= memory.size()) { - return; - } - memory[vaddr] = value; - } - - void MemoryWrite16(u32 vaddr, u16 value) override { - MemoryWrite8(vaddr, u8(value)); - MemoryWrite8(vaddr + 1, u8(value >> 8)); - } - - void MemoryWrite32(u32 vaddr, u32 value) override { - MemoryWrite16(vaddr, u16(value)); - MemoryWrite16(vaddr + 2, u16(value >> 16)); - } - - void MemoryWrite64(u32 vaddr, u64 value) override { - MemoryWrite32(vaddr, u32(value)); - MemoryWrite32(vaddr + 4, u32(value >> 32)); - } - - void CallSVC(u32 swi) override { - // Do something. - } - - void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override { - // Do something. - } - - void AddTicks(u64 ticks) override { - if (ticks > ticks_left) { - ticks_left = 0; - return; - } - ticks_left -= ticks; - } - - u64 GetTicksRemaining() override { - return ticks_left; - } -}; - -int main(int argc, char** argv) { - MyEnvironment env; - Dynarmic::A32::UserConfig user_config; - user_config.callbacks = &env; - Dynarmic::A32::Jit cpu{user_config}; - - // Execute at least 1 instruction. - // (Note: More than one instruction may be executed.) - env.ticks_left = 1; - - // Write some code to memory. - env.MemoryWrite16(0, 0x0088); // lsls r0, r1, #2 - env.MemoryWrite16(2, 0xE7FE); // b +#0 (infinite loop) - - // Setup registers. - cpu.Regs()[0] = 1; - cpu.Regs()[1] = 2; - cpu.Regs()[15] = 0; // PC = 0 - cpu.SetCpsr(0x00000030); // Thumb mode - - // Execute! - cpu.Run(); - - // Here we would expect cpu.Regs()[0] == 8 - printf("R0: %u\n", cpu.Regs()[0]); - - return 0; -} -``` - Alternatives to Dynarmic ------------------------ -Here are some projects with the same goals as dynarmic: +Here are some projects with the same goals as Dynarmic: * [Unicorn](https://www.unicorn-engine.org/) - Recompiling multi-architecture CPU emulator, based on QEMU * [SkyEye](http://skyeye.sourceforge.net) - Cached interpreter for ARM @@ -189,8 +34,8 @@ More general alternatives: Disadvantages of Dynarmic ------------------------- -In the pursuit of speed, some behavior not commonly depended upon is elided. Therefore this emulator does not match spec. -Please note that this would mean that a guest application can easily determine if it is being run under instrumentation. +In the pursuit of speed, some behavior not commonly depended upon is elided. Hence this emulator doesn't match spec. +Note that this would mean that a guest application can easily determine if it's being ran under instrumentation. Known examples: @@ -205,182 +50,6 @@ Use this code base at your own risk. Legal ----- -dynarmic is under a 0BSD license. See LICENSE.txt for more details. +Dynarmic is under a GPLv3 license, check the relevant file headers for more information. -dynarmic uses several other libraries, whose licenses are included below: - -### biscuit - -``` -Copyright 2021 Lioncash/Lioncache - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. -``` - -### catch - -``` -Boost Software License - Version 1.0 - August 17th, 2003 - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -``` - -### fmt - -``` -Copyright (c) 2012 - 2016, Victor Zverovich - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -``` - -### mcl & oaknut - -``` -MIT License - -Copyright (c) 2022 merryhime - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -``` - -### unordered_dense - -``` -MIT License - -Copyright (c) 2022 Martin Leitner-Ankerl - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -``` - -### xbyak - -``` -Copyright (c) 2007 MITSUNARI Shigeo -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. -Neither the name of the copyright owner nor the names of its contributors may -be used to endorse or promote products derived from this software without -specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF -THE POSSIBILITY OF SUCH DAMAGE. ------------------------------------------------------------------------------ -ソースコード形式かバイナリ形式か、変更するかしないかを問わず、以下の条件を満た -す場合に限り、再頒布および使用が許可されます。 - -ソースコードを再頒布する場合、上記の著作権表示、本条件一覧、および下記免責条項 -を含めること。 -バイナリ形式で再頒布する場合、頒布物に付属のドキュメント等の資料に、上記の著作 -権表示、本条件一覧、および下記免責条項を含めること。 -書面による特別の許可なしに、本ソフトウェアから派生した製品の宣伝または販売促進 -に、著作権者の名前またはコントリビューターの名前を使用してはならない。 -本ソフトウェアは、著作権者およびコントリビューターによって「現状のまま」提供さ -れており、明示黙示を問わず、商業的な使用可能性、および特定の目的に対する適合性 -に関する暗黙の保証も含め、またそれに限定されない、いかなる保証もありません。 -著作権者もコントリビューターも、事由のいかんを問わず、 損害発生の原因いかんを -問わず、かつ責任の根拠が契約であるか厳格責任であるか(過失その他の)不法行為で -あるかを問わず、仮にそのような損害が発生する可能性を知らされていたとしても、 -本ソフトウェアの使用によって発生した(代替品または代用サービスの調達、使用の -喪失、データの喪失、利益の喪失、業務の中断も含め、またそれに限定されない)直接 -損害、間接損害、偶発的な損害、特別損害、懲罰的損害、または結果損害について、 -一切責任を負わないものとします。 -``` +Dynarmic uses several open-source libraries, whose licenses are included inside thereof. diff --git a/docs/policies/AI.md b/docs/policies/AI.md deleted file mode 100644 index 719548f4c8..0000000000 --- a/docs/policies/AI.md +++ /dev/null @@ -1,112 +0,0 @@ -# AI Policy - -Use at your peril. - -AI is a *tool*, not a replacement or catch-all solution. It is generally okay at a few *very specific* use cases: - -- Automation of tedious changes where you have already made the pattern clear and done the necessary groundwork. -- Conversion of code from one paradigm to another. - -For everything else, AI is subpar at best, and actively harmful at worst. In general, you are **heavily** encouraged to not use AI at all. - -## Why? - -AI is notorious for hallucinating facts out of thin air and sometimes outright lying to users. Additionally, code written by LLMs is often needlessly verbose and horrifically inefficient (not to mention the rather ridiculous level of over-commenting). The end result is often one of three things: - -- Completely nonfunctional code -- Code that works, but is extraordinarily verbose or not nearly as efficient as it can be -- Code that works well and is written well, but solves a different problem than was intended, or solves the same problem but in a completely incorrect way that will break other things horribly. - -Human-written code will, without exception, always be of infinitely higher quality when properly researched and implemented by someone familiar with *both* the surrounding code and the programming language in use. LLMs may produce a "good enough" result, but this result is often subpar. - -**All code is held under a STRICT STANDARD OF EXCELLENCE**. AI code is no different, and since it often produces subpar or outright terrible code, it will often fail to meet this excellence standard. - -On a lesser-known note, LLM outputs often contain unicode symbols such as emojis or the arrow symbol. Please don't put Unicode symbols in your code. It messes with many an IDE, and the three people viewing your code on Lynx will be very unhappy. - -**Learn to code**. It's worth it, we promise! - -## Acceptable Use - -As stated previously, AI is good in a few *very specific* cases. In these cases, it's usually fine to use AI, as long as you **explicitly provide notice that it was used**. - -- Anything directly outside of the realm of the code written in your PR or patch is none of our business. - - This primarily covers research. - - However, we *still* strongly discourage this for the reasons mentioned above. -- Assistance with cleanups, and minor nitpicks/optimizations. - - This is still discouraged, but it's okay to occasionally use LLMs to catch any minor mistakes you made in your code. -- Debugging - - In general, LLMs are not good at debugging, but if you solve a bug you're facing with help from an AI, and said fix **works properly**, then that's fine. - -## Unacceptable Use - -Well, everything else. But here are a few examples: - -- Commit messages - - LLMs are absolutely horrible at this. They are needlessly verbose, almost never catch the actual intent of the commit, and will almost always hallucinate false information about said changes. - - See the addendum for an example -- Solving problems - - Slapping a few files and a "please fix bug XYZ" into an LLM is a recipe for disaster that will pretty much never work. -- Fully AI-generated code - - This shouldn't need explaining. Do not do this under any circumstance, especially if you don't actually understand what's going on. -- Writing code based on pseudo-instructions - - If you don't know how to write code, don't. If you've figured out the root cause (preferably without feeding random files into an LLM) and actively know what's going on, provide information to other developers or friends of yours who have knowledge of the language and/or the codebase. - -## Addendum: Commit Messages - -The patchset for pull request [#3422](https://git.eden-emu.dev/eden-emu/eden/pulls/3422) was fed into several LLMs to generate a commit message. All of them sucked, and not a single one caught on to what the commit actually did. For example: - -```txt -profile_manager: Refactor user deletion to use index instead of UUID - -This commit improves the user profile deletion flow in the following ways: - -1. **UI layer (ConfigureProfileManager)**: - - Replaces UUID-based deletion with index-based deletion - - Passes the selected row index from the tree view to the confirmation dialog - - Emits deleteUser(int index) signal from ConfigureProfileManagerDeleteDialog - - Removes lambda capture and std::function callback in favor of direct signal/slot connection - -2. **Delete dialog**: - - Changes SetInfo() signature to accept index instead of accept callback - - Stores index internally and emits it via deleteUser signal on acceptance - -3. **Core ProfileManager**: - - Refactors RemoveUser(UUID) → RemoveProfileAtIndex(index) - - Removes duplicated stable_partition + save logic from RemoveUser() - - Centralizes profile removal and save-file writing in RemoveProfileAtIndex() - -4. **Minor follow-up**: - - Adds profile_manager.ResetUserSaveFile() call in PopulateUserList() to ensure consistent state when refreshing the user list - -These changes eliminate redundant code, reduce the risk of UUID → index mismatch bugs, and make the deletion path more straightforward and maintainable. - -Fixes: potential inconsistency when user order changes between UI and backend -``` - -This is: - -- Needlessly verbose (nobody cares about most of these details) -- Doesn't address the actual purpose of the PR (fixing a double-deletion bug in the profile manager) -- Has unicode arrows -- Uses overly corporate and, well, robotic language - -As another example: - -```txt -profile(manager/ui): switch to index-based deletion and unify removal logic - -- Make ConfigureProfileManagerDeleteDialog emit a deleteUser(int) signal instead of taking a callback. Store the target index in the dialog (m_index) and emit it on acceptance. -- Change ConfigureProfileManager to pass an index to SetInfo and to accept an index in DeleteUser. Wire the dialog signal to the new DeleteUser slot. -- Update headers: add the signal and m_index, add TODO to move dialog to a .ui file, and update slot/signature declarations. -- Add ProfileManager::RemoveProfileAtIndex(std::size_t) and refactor RemoveUser(UUID) to call RemoveProfileAtIndex to avoid duplicated removal logic. Ensure the removal path marks saves as needed and writes the user save file. -- Ensure the profile list updates immediately after deletes by calling profile_manager.ResetUserSaveFile() when populating the user list (qlaunch fix). -- Misc: update SPDX copyright year and fix build breakages caused by the API changes. - -This consolidates profile removal behavior, fixes potential race conditions in the profile dialog, and removes duplicated removal code. -``` - -This has all of the same problems as the other one. Needlessly verbose, doesn't address *what* it actually fixes ("consolidates profile removal behavior"... okay, why? What does it fix?), etc. It even has the bonus of totally hallucinating the addition of a method! - -On a more "philosophical" note, LLMs tend to be geared towards *corporate language*, as that's what they're trained on. This is why AI-generated commit messages feel like "word salad", and typically pad out the commit message to make it *look* like a lot of things were changed (trust me, it's like that in the corporate world). They typically also drift towards unneeded buzzwords and useless implementation details. - -**Don't use AI for commit messages**. diff --git a/docs/policies/Coding.md b/docs/policies/Coding.md index c50f93b142..afc195d86c 100644 --- a/docs/policies/Coding.md +++ b/docs/policies/Coding.md @@ -1,126 +1,71 @@ # Coding guidelines -These are mostly "suggestions", if you feel like your code is readable, comprehensible to others; and most importantly doesn't result in unreadable spaghetti you're fine to go. +These are **not** stylistic guidelines, they're, for the most part, suggestions on how to architecture new systems or improve upon the existing codebase. -But for new developers you may find that following these guidelines will make everything x10 easier. +## Foreword -## Naming conventions +Keep your code simple, efficient, and readable. -Simply put, types/classes are named as `PascalCase`, same for methods and functions like `AddElement`. Variables are named `like_this_snake_case` and constants are `IN_SCREAMING_CASE`. +## C++ guidelines -Except for Qt MOC where `functionName` is preferred. +Everyone has their own way of viewing good/bad C++ practices, my general outline: -Template typenames prefer short names like `T`, `I`, `U`, if a longer name is required either `Iterator` or `perform_action` are fine as well. Do not use names like `SS` as systems like solaris define it for registers, in general do not use any of the following for short names: +- At your disposal you may use `boost::container::static_vector<>` (beware it has a ctor/initialization cost which goes up the more elements you add). + - Or you may use `boost::container::small_vector<>` (which has an initialization cost as well, and will use extra book-keeping for heap, try to keep a balance). +- Don't use `[[likely]]` or `[[unlikely]]`; PGO builds exist for that. +- Don't use inline assembly to try to outsmart the compiler unless you're 100% sure the assembly you're writing is actually good. + - And if so, try to restructure your C++ code so the compiler vectorizes it/makes it better + - Or if that fails, use intrinsics instead of raw `asm volatile`. +- Use `std::optional<>` instead of `std::unique_ptr<>` if possible. + - `std::unique_ptr<>` carries indirection cost due to it being memory allocated on the heap. + - It isn't often that objects that contain `std::unique_ptr<>`, are allocated on the heap themselves, allocating even more things on the heap seems redundant. +- Avoid `std::recursive_mutex` at all costs. + - It's basically implemented as a linked list most of the time and has HEAVY performance penalties. +- Exploit the fact `std::atomic/std::atomic` is basically free on most arches that matter. + - In x86_64, an atomic `uint32_t` is basically `mov [m32], r32`, which is essentially free/cheap. +- Avoid template parameters unless you really need them. + - For small inline-able functions this is fine, for more complex ones, consider the generated assembly. +- Don't make your own memcpy/memset/strcpy/strncpy/etc. + - Seriously DON'T DO THIS. You will NOT beat the compiler. + - Nor 30 years of writing optimized `mem*`. + - If your code is slow, don't blame `mem*`, blame your code. +- Try to avoid using `virtual` since vtable indirection has a cost +- Avoid `dynamic_cast` and `typeid` at all costs. + - The reason is because the project has `-fno-rtti` disabled by default, due to the costs of dynamic polymorphism. +- Always copy-on-value for objects with `sizeof(void *) >= sizeof(T) * 2`, i.e objects sized as 2 pointers or less, for bigger objects you can use ref/pointer as usual. +- Try using move semantics instead of references, whenever possible. +- Function parameters are cheap. Don't be afraid to use as many as needed (API usability permitting). +- Don't save a reference in structures of a parent object, i.e: -- `SS`, `DS`, `GS`, `FS`: Segment registers, defined by Solaris `` -- `EAX`, `EBX`, `ECX`, `EDX`, `ESI`, `EDI`, `ESP`, `EBP`, `EIP`: Registers, defined by Solaris. -- `X`: Defined by some utility headers, avoid. -- `_`: Defined by gettext, avoid. -- `N`, `M`, `S`: Preferably don't use this for types, use it for numeric constants. -- `TR`: Used by some weird `` whom define the Task Register as a logical register to provide to the user... (Need to remember which OS in specific). + ```cpp + struct Child { + Parent& parent; + void Mehod() { + parent.Something(); + } + }; + ``` -Macros must always be in `SCREAMING_CASE`. Do not use short letter macros as systems like Solaris will conflict with them; a good rule of thumb is >5 characters per macro - i.e `THIS_MACRO_IS_GOOD`, `AND_ALSO_THIS_ONE`. + - Instead you can do the following: -Try not using hungarian notation, if you're able. + ```cpp + struct Child { + void Mehod(Parent& parent) { + parent.Something(); + } + }; + ``` -## Formatting + - This reduces the amount of pointers you have lying around, and also works better because of the aforementioned cheapness of parameter functions. -Formatting is extremelly lax, the general rule of thumb is: Don't add new lines just to increase line count. The less lines we have to look at, the better. This means also packing densely your code while not making it a clusterfuck. Strike a balance of "this is a short and comprehensible piece of code" and "my eyes are actually happy to see this!". Don't just drop the entire thing in a single line and call it "dense code", that's just spaghetti posing as code. In general, be mindful of what other devs need to look at. +## Engineering guidelines -Do not put if/while/etc braces after lines: +Programming, alongside the physical act of writing code, also consists of architecting the code you write into a coherent, maintainable system. -```c++ -// no dont do this -// this is more lines of code for no good reason (why braces need their separate lines?) -// and those take space in someone's screen, cumulatively -if (thing) -{ //<-- - some(); // ... -} //<-- 2 lines of code for basically "opening" and "closing" an statment - -// do this -if (thing) { //<-- [...] and with your brain you can deduce it's this piece of code - // that's being closed - some(); // ... -} //<-- only one line, and it's clearer since you know its closing something [...] - -// or this, albeit the extra line isn't needed (at your discretion of course) -if (thing) - some(); // ... - -// this is also ok, keeps things in one line and makes it extremely clear -if (thing) some(); - -// NOT ok, don't be "clever" and use the comma operator to stash a bunch of statments -// in a single line, doing this will definitely ruin someone's day - just do the thing below -// vvv -if (thing) some(), thing(), a2(a1(), y1(), j1()), do_complex_shit(wa(), wo(), ploo()); -// ... and in general don't use the comma operator for "multiple statments", EXCEPT if you think -// that it makes the code more readable (the situation may be rare however) - -// Wow so much clearer! Now I can actually see what each statment is meant to do! -if (thing) { - some(); - thing(); - a2(a1(), y1(), j1()); - do_complex_shit(wa(), wo(), ploo()); -} -``` - -Brace rules are lax, if you can get the point across, do it: - -```c++ -// this is fine -do { - if (thing) { - return 0; - } -} while (other); - -// this is also ok --- albeit a bit more dense -do if (thing) return 0; while (other); - -// ok as well -do { - if (thing) return 0; -} while (other); -``` - -There is no 80-column limit but preferably be mindful of other developer's readability (like don't just put everything onto one line). - -```c++ -// someone is going to be mad due to this -SDL_AudioSpec obtained; -device_name.empty() ? device = SDL_OpenAudioDevice(nullptr, capture, &spec, &obtained, false) : device = SDL_OpenAudioDevice(device_name.c_str(), capture, &spec, &obtained, false); - -// maybe consider this -SDL_AudioSpec obtained; -if (device_name.empty()) { - device = SDL_OpenAudioDevice(nullptr, capture, &spec, &obtained, false); -} else { - device = SDL_OpenAudioDevice(device_name.c_str(), capture, &spec, &obtained, false); -} - -// or this is fine as well -SDL_AudioSpec obtained; -device = SDL_OpenAudioDevice(device_name.empty() ? nullptr : device_name.c_str(), capture, &spec, &obtained, false); -``` - -A note about operators: Use them sparingly, yes, the language is lax on them, but some usages can be... tripping to say the least. - -```c++ -a, b, c; //<-- NOT OK multiple statments with comma operator is definitely a recipe for disaster -return c ? a : b; //<-- OK ternaries at end of return statments are clear and fine -return a, b; //<-- NOT OK return will take value of `b` but also evaluate `a`, just use a separate statment -void f(int a[]) //<-- OK? if you intend to use the pointer as an array, otherwise just mark it as * -``` - -And about templates, use them sparingly, don't just do meta-templating for the sake of it, do it when you actually need it. This isn't a competition to see who can make the most complicated and robust meta-templating system. Just use what works, and preferably stick to the standard libary instead of reinventing the wheel. Additionally: - -```c++ -// NOT OK This will create (T * N * C * P) versions of the same function. DO. NOT. DO. THIS. -template inline void what() const noexcept; - -// OK use parameters like a normal person, don't be afraid to use them :) -template inline void what(size_t n, size_t c, size_t p) const noexcept; -``` +- Try to reduce your usage of dependencies + - Dependencies that are legitimately useful to have are few and far between. + - At the same time, NIHing your own implementations of widely adopted algorithms or standards can be quite subpar. + - For dependencies that are very large but contain something you need, consider cherry-picking the individual files it needs (or writing a smaller version of it) +- Try to rely less on indirection for architecting systems + - If the underlying HLE kernel emulation requires it, try making a solution that keeps things local + - For example, there isn't a need for file descriptors to each be a pointer, when they could be a fixed table size with elements that may be emplaced at will. diff --git a/docs/policies/Release.md b/docs/policies/Release.md deleted file mode 100644 index 89b37bc630..0000000000 --- a/docs/policies/Release.md +++ /dev/null @@ -1,10 +0,0 @@ -# Release Policy - -Release when lots of new changes and fixes. Hotfix if more bugs. Release candidate if lot of things to test. Simple as. - -## Checklist - -- [ ] Update Transifex -- [ ] Test for regressions and bugs -- [ ] Write a changelog -- [ ] Ensure all platforms work diff --git a/docs/user/AddingBooleanToggles.md b/docs/user/AddingBooleanToggles.md deleted file mode 100644 index 1af5fd932d..0000000000 --- a/docs/user/AddingBooleanToggles.md +++ /dev/null @@ -1,159 +0,0 @@ -# User Handbook - Adding Boolean Settings Toggles - -> [!WARNING] -> This guide is intended for developers ONLY. If you are not a developer, this likely irrelevant to yourself. -> -> If you want to add temporary toggles, please refer to **[Adding Debug Knobs](AddingDebugKnobs.md)** - -This guide will walk you through adding a new boolean toggle setting to Eden's configuration across both Qt's (PC) and Kotlin's (Android) UIs. - -## Index - -1. [Step 1 - Common Setting](#step-1-common-setting) -2. [Step 2 - Qt Toggle](#step-2-qt-toggle) -3. [Step 3 - Kotlin (Android)](#step-3-kotlin-android) - - * [Step 3.1 - BooleanSetting.kt](#step-3-1-booleansetting-kt) - * [Step 3.2 - SettingsItem.kt](#step-3-2-settingsitem-kt) - * [Step 3.3 - SettingsFragmentPresenter.kt](#step-3-3-settingsfragmentpresenter-kt) - * [Step 3.4 - Localization](#step-3-4-localization) -4. [Step 4 - Use Your Toggle](#step-4-use-your-toggle) -5. [Best Practices](#best-practices) - ---- - -## Step 1 - Common Setting - -Firstly add your desired toggle: - -Example: `src/common/setting.h` -```cpp -SwitchableSetting your_setting_name{linkage, false, "your_setting_name", Category::RendererExtensions}; -``` - -### Remember to add your toggle to the appropriate category, for example: - -Common Categories: - -* Category::Renderer -* Category::RendererAdvanced -* Category::RendererExtensions -* Category::System -* Category::Core - -> [!WARNING] -> If you wish for your toggle to be `on by default` then change `false` to `true` after `linkage,`. - ---- - -## Step 2 - Qt Toggle - -Add the toggle to the Qt UI, where you wish for it to appear and place it there. - -Example: `src/qt_common/config/shared_translation.cpp` -```cpp -INSERT(Settings, - your_setting_name, - tr("Your Setting Display Name"), - tr("Detailed description of what this setting does.\n" - "You can use multiple lines.\n" - "Explain any caveats or requirements.")); -``` - -### Make sure to: - -* Keep display naming consistant -* Put detailed info in the description -* Use `\n` for line breaks in descriptions - ---- - -## Step 3 - Kotlin (Android) - -### Step 3.1 - BooleanSetting.kt - -Add where it should be in the settings. - -Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt` -```kts -RENDERER_YOUR_SETTING_NAME("your_setting_name"), -``` - -### Make sure to: - -* Ensure the prefix naming matches the intended category. - ---- - -### Step 3.2 - SettingsItem.kt - -Add the toggle to the Kotlin (Android) UI - -Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt` -```kts -put( - SwitchSetting( - BooleanSetting.RENDERER_YOUR_SETTING_NAME, - titleId = R.string.your_setting_name, - descriptionId = R.string.your_setting_name_description - ) -) -``` - ---- - -### Step 3.3 - SettingsFragmentPresenter.kt - -Add your setting within the right category. - -Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt` -```kts -add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key) -``` - -> [!WARNING] -> Remember, placing matters! Settings appear in the order of where you add them. - ---- - -### Step 3.4 - Localization - -Add your setting and description in the appropriate place. - -Example: `src/android/app/src/main/res/values/strings.xml` -```xml -Your Setting Display Name -Detailed description of what this setting does. Explain any caveats, requirements, or warnings here. -``` - ---- - -## Step 4 - Use Your Toggle! - -Now the UI part is done find a place in the code for the toggle, -And use it to your heart's desire! - -Example: -```cpp -const bool your_value = Settings::values.your_setting_name.GetValue(); - -if (your_value) { - // Do something when enabled -} -``` - -If you wish to do something only when the toggle is disabled, -Use `if (!your_value) {` instead of `if (your_value) {`. - ---- - -## Best Practices - -* Naming - Use clear, descriptive names. Something for both the devs and the users. -* Defaults - Choose safe default values (usually false for new features). -* Documentation - Write clear descriptions explaining when and why to use the setting. -* Categories - Put settings in the appropriate category. -* Order - Place related settings near each other. -* Testing - Always test on both PC and Android before committing when possible. - -### Thank you for reading, I hope this guide helped you making your toggle! diff --git a/docs/user/AddingDebugKnobs.md b/docs/user/AddingDebugKnobs.md deleted file mode 100644 index 83db52b375..0000000000 --- a/docs/user/AddingDebugKnobs.md +++ /dev/null @@ -1,167 +0,0 @@ -# User Handbook - Adding Debug Knobs - -Debug Knobs is a 16-bit integer setting (`debug_knobs`) in the Eden Emulator that serves as a bitmask for gating various testing and debugging features. This allows developers and advanced users to enable or disable specific debug behaviors without requiring deploying of complete but temporary toggles. - -The setting ranges from 0 to 65535 (0x0000 to 0xFFFF), where each bit represents a different debug feature flag. - -## Index - -1. [Advantages](#advantages) -2. [Usage](#usage) - - * [Accessing Debug Knobs (dev side)](#accessing-debug-knobs-dev-side) - * [Setting Debug Knobs (user side)](#setting-debug-knobs-user-side) - * [Bit Manipulation Examples](#bit-manipulation-examples) -3. [Terminology and user communication](#terminology-and-user-communication) -4. [Examples](#examples) - - * [Example 1: Conditional Debug Logging](#example-1-conditional-debug-logging) - * [Example 2: Performance Tuning](#example-2-performance-tuning) - * [Example 3: Feature Gating](#example-3-feature-gating) -5. [Best Practices](#best-practices) - ---- - -## Advantages - -The main advantage is to avoid deploying new disposable toggles (those made only for testing stage, and are disposed once new feature gets good to merge). This empowers devs to be free of all frontend burocracy and hassle of new toggles. - -Common advantages recap: - -* **Fine-Grained Control**: Enable or disable up to 16 individual debug features independently using bit manipulation on a single build -* **Runtime Configuration**: Change debug behavior at runtime the same way as new toggles would do -* **Safe incremental development**: New debug features can be added while impact can be isolated from previous deployments - -## Usage - -### Accessing Debug Knobs (dev side) - -Use the `Settings::getDebugKnobAt(u8 i)` function to check if a specific bit is set: - -```cpp -//cpp side -#include "common/settings.h" - -// Check if bit 0 is set -bool feature_enabled = Settings::getDebugKnobAt(0); - -// Check if bit 15 is set -bool another_feature = Settings::getDebugKnobAt(15); -``` - -```kts -//kotlin side -import org.yuzu.yuzu_emu.features.settings.model.Settings - -// Check if bit x is set -bool feature_enabled = Settings.getDebugKnobAt(x); //x as integer from 0 to 15 -``` - -The function returns `true` if the specified bit (0-15) is set in the `debug_knobs` value, `false` otherwise. - -### Setting Debug Knobs (user side) - -Developers must inform which knobs are tied to each functionality to be tested. - -The debug knobs value can be set through: - -1. **Desktop UI**: In the Debug configuration tab, there's a spinbox for "Debug knobs" (0-65535) -2. **Android UI**: Available as an integer setting in the Debug section -3. **Configuration Files**: Set the `debug_knobs` value in the emulator's configuration - -### Bit Manipulation Examples - -To enable specific features, calculate the decimal value by setting the appropriate bits: - -* **Enable only bit 0**: Value = 1 (2^0) -* **Enable only bit 1**: Value = 2 (2^1) -* **Enable bits 0 and 1**: Value = 3 (2^0 + 2^1) -* **Enable bit 15**: Value = 32768 (2^15) - -## Terminology and user communication - -There are two main confusions when talking about knobs: - -### Whether it's zero-based or one-based - -Sometimes when an user reports: knobs 1 and 2 gets better performance, dev may get confuse whether he means the knobs 1 and 2 literally, or the 1st and 2nd knobs (knobs 0 and 1). - -Debug knobs are **zero-based**, which means: -* The first knob is the knob(0) (or knob0 henceforth), and the last one is the 15 (knob15, likewise) -* You can talk: "knob0 is enabled/disabled", "In this video i was using only knobs 0 and 2", etc. - -### Whether one is talking about the knob itself or about the entire parameter value (which represents all knobs) - -Sometimes when an user reports: knob 3 results, it's unclear whether he's referring to knob setting with value 3 (which means both knob 0 and 1 are enabled), or to knob(3) specifically. -Whenever you're instructing tests or reporting results, be precise about whether one you're talking to avoid confusion: - -### Setting based terminology - -ALWAYS use the word in PLURAL (knobs), without mentioning which one, to refer to the setting, aka multiple knobs at once: -Examples: -- **knobs=0**: no knobs enabled -- **knobs=1**: knob0 enabled, others disabled -- **knobs=2**: knob1 enabled, others disabled -- **knobs=3**: knobs 0 and 1 enabled, others disabled - -... - -### Knob based terminology - -Use the word in SINGULAR (knob), or in plural but referring which ones, when meaning multiple knobs at once: -Examples: -- **knob0**: knob 0 enabled, others disabled -- **knob1**: knob 1 enabled, others disabled -- **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled - -... - -## Examples - -### Example 1: Conditional Debug Logging - -```cpp -void SomeFunction() { - if (Settings::getDebugKnobAt(0)) { - LOG_DEBUG(Common, "Debug feature 0 is enabled"); - // Additional debug code here - } - - if (Settings::getDebugKnobAt(1)) { - LOG_DEBUG(Common, "Debug feature 1 is enabled"); - // Different debug behavior - } -} -``` - -### Example 2: Performance Tuning - -```cpp -bool UseOptimizedPath() { - // Skip optimization if debug bit 2 is set for testing - return !Settings::getDebugKnobAt(2); -} -``` - -### Example 3: Feature Gating - -```cpp -void ExperimentalFeature() { - static constexpr u8 EXPERIMENTAL_FEATURE_BIT = 3; - - if (!Settings::getDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) { - // Fallback to stable implementation - StableImplementation(); - return; - } - - // Experimental implementation - ExperimentalImplementation(); -} -``` - -## Best Practices - -* This setting is intended for development and testing purposes only -* Knobs must be unwired before PR creation -* The setting is per-game configurable, allowing different debug setups for different titles diff --git a/docs/user/AlterDateTime.md b/docs/user/AlterDateTime.md index aeffa1a548..f28df3df54 100644 --- a/docs/user/AlterDateTime.md +++ b/docs/user/AlterDateTime.md @@ -2,7 +2,7 @@ Use this guide whenever you want to modify the Date or Time that Eden reports to games. This can be useful for modifying RNG elements, skipping wait times in games, etc. -**Click [Here](https://evilperson1337.notion.site/Setting-a-Custom-Date-Time-in-Eden-2b357c2edaf680acb8d4e63ccc126564) for a version of this guide with images & visual elements.** +**[See here](https://evilperson1337.notion.site/Setting-a-Custom-Date-Time-in-Eden-2b357c2edaf680acb8d4e63ccc126564) for an illustrated guide.** --- @@ -14,7 +14,7 @@ Use this guide whenever you want to modify the Date or Time that Eden reports to ## Steps -1. Navigate to *Emulation → Configure*. +1. Navigate to *Emulation > Configure*. 2. Click on the **System** item on the left-hand side navigation, then check the *Custom RTC Date* box. 3. The Date/Time option now becomes editable. Set it to the value you want and hit **OK**. 4. GREAT SCOTT! We have time traveled! You can of course go forward or backward in time (as long as it is not before the year 1970) and your game should update accordingly (e.g. certain *Super Mario Odyssey* moons that take time for flowers to grow will now be fully grown.). \ No newline at end of file diff --git a/docs/user/Architectures.md b/docs/user/Architectures.md index 45f9e85c4f..d5f9acd74c 100644 --- a/docs/user/Architectures.md +++ b/docs/user/Architectures.md @@ -40,11 +40,11 @@ Windows/riscv64 doesn't exist, and may never (until corporate greed no longer co Android/riscv64 is interesting. While support for it may be added if and when RISC-V phones/handhelds ever go mainstream, arm64 devices will always be preferred due to NCE. -Only Fedora/riscv64 has been tested, but in theory, every riscv64 distribution that has *at least* the standard build tools, Qt, FFmpeg, and SDL2 should work. +Only Fedora/riscv64 has been tested, but in theory, every riscv64 distribution that has *at least* the standard build tools, Qt, FFmpeg, and SDL3 should work. ## Other -Other architectures, such as SPARC, MIPS, PowerPC, Loong, and all 32-bit architectures are completely unsupported, as there is no JIT backend or emitter thereof. If you want support for it--submit patches! +Other architectures, such as SPARC, MIPS, PowerPC, Loong, and all 32-bit architectures are completely unsupported, as there is no JIT backend or emitter thereof. If you want support for it -- submit patches! IA-64 (Itanium) support is completely unknown. Existing amd64 packages will not run on IA-64 (assuming you can even find a supported Windows/Linux distribution) @@ -52,28 +52,32 @@ IA-64 (Itanium) support is completely unknown. Existing amd64 packages will not The vast majority of Eden's testing is done on Windows, Linux, and Android. However, first-class support is also provided for: -- HaikuOS -- FreeBSD -- OpenBSD -- NetBSD -- OpenIndiana (Solaris) -- macOS +- FreeBSD (amd64, aarch64) +- HaikuOS (amd64) +- OpenBSD (amd64) +- NetBSD (amd64) +- macOS (aarch64) +- OpenIndiana aka. OpenSolaris (amd64) ## Linux While all modern Linux distributions are supported (Fedora >40, Ubuntu >24.04, Debian >12, Arch, Gentoo, etc.), the vast majority of testing and development for Linux is on Arch and Gentoo. Most major build system changes are tested on Gentoo first and foremost, so if builds fail on any modern distribution no matter what you do, it's likely a bug and should be reported. -Intel and Nvidia GPU support is limited. AMD (RADV) drivers receive first-class testing and are known to provide the most stable Eden experience possible. +Intel and NVIDIA GPU support is limited. AMD (RADV) drivers receive first-class testing and are known to provide the most stable Eden experience possible. Wayland is not recommended. Testing has shown significantly worse performance on most Wayland compositors compared to X11, alongside mysterious bugs and compatibility errors. For now, set `QT_QPA_PLATFORM=xcb` when running Eden, or pass `-platform xcb` to the launch arguments. ## Windows -Windows 10 and 11 are supported. Support for Windows 8.x is unknown, and Windows 7 support is unlikely to ever be added. +Windows 10 and 11 are supported. Anything below is unsupported. -In order to run Eden, you will probably need to install the [Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170). +To run Eden, you'll need to install [Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170). -Neither AMD nor Nvidia drivers work nearly as well as Linux's RADV drivers. Compatibility is still largely the same, but performance and some hard-to-run games may suffer compared to Linux. +Neither AMD/NVIDIA drivers work nearly as well as Linux's RADV drivers. Compatibility is still largely the same, but performance and some hard-to-run games may suffer compared to Linux. + +### Windows on ARM + +If you're using Snapdragon X or 8CX, install the [Vulkan translation layer](https://apps.microsoft.com/detail/9nqpsl29bfff?hl=en-us&gl=USE) only if the stock drivers don't work. And of course always keep your system up-to-date. ## Android @@ -123,13 +127,13 @@ Do note that building the GUI version with Qt versions higher than 6.7.3 will ca ## *BSD, Solaris -BSD and Solaris distributions tend to lag behind Linux in terms of Vulkan and other library compatibility. For example, OpenIndiana (Solaris) does not properly package Qt, meaning the recommended method of usage is to use `eden-cli` only for now. Solaris also generally works better with OpenGL. +BSD and Solaris distributions tend to lag behind Linux in terms of Vulkan and ports. For example, OpenIndiana (Solaris) does not properly package Qt, meaning the recommended method of usage is to use `eden-cli` for now. Solaris also generally works better with OpenGL. -AMD GPU support on these platforms is limited or nonexistent. +AMD GPU support on these platforms is limited or nonexistent, bar for OpenBSD which has a dedicated AMD GPU stack. ## HaikuOS -HaikuOS supports (see below) Vulkan 1.3 and has Mesa 24.0. Because OpenGL ES is used instead of the desktop flavour of OpenGL the OpenGL backend is actually worse than the Vulkan one in terms of stability and system support. OpenGL is highly not recommended due to it being: out of tree builds of Mesa and generally unstable ones at that. Users are advised to use Vulkan whenever possible. +HaikuOS supports (see below) Vulkan 1.3 and has Mesa 24.0. Because OpenGL ES is used instead of the desktop flavour of OpenGL the OpenGL backend is actually worse than the Vulkan one in terms of stability and system support. OpenGL is highly not recommended due to it being out of tree builds of Mesa and generally unstable ones at that. Users are advised to use Vulkan instead. - Additionally system drivers for NVIDIA and Intel iGPUs exist and provide a native Vulkan ICD with the `Xcb` interface as opposed to the native `BView` - In order to obtain Vulkan 1.3 support with native `BView` support; Swiftshader can be compiled from source [see this thread](https://discuss.haiku-os.org/t/swiftshader-vulkan-software-renderer-on-haiku/11526/6). diff --git a/docs/user/Basics.md b/docs/user/Basics.md index 794a0935d5..2b7a99c8d0 100644 --- a/docs/user/Basics.md +++ b/docs/user/Basics.md @@ -2,13 +2,13 @@ ## Introduction -Eden is a very complicated piece of software, and as such there are many knobs and toggles that can be configured. Most of these are invisible to normal users, however power users may be able to leverage them to their advantage. +Eden is a very complicated piece of software, there are many knobs and toggles that can be configured. Most of these are invisible to normal users, however, power users may be able to leverage them to their advantage. This handbook primarily describes such knobs and toggles. Normal configuration options are described within the emulator itself and will not be covered in detail. ## Requirements -The emulator is very demanding on hardware, and as such requires a decent mid-range computer/cellphone. +The emulator is very demanding on hardware and requires a decent mid-range computer/cellphone. See [the requirements page](https://archive.is/sv83h) for recommended and minimum specs. @@ -22,8 +22,8 @@ If your GPU doesn't support or is just behind by a minor version, see Mesa envir - RC releases: Release candidate, generally "less stable but still stable" versions. - Full release: "The stablest possible you could get". - Nightly: Builds done around 2PM UTC (if there are any changes), generally stable, but not recommended for the average user. These contain daily updates and may contain critical fixes for some games. -- Master: Unstable builds, can lead from a game working exceptionally fine to absolute crashing in some systems because someone forgot to check if NixOS or Solaris worked. These contain straight from the oven fixes, please don't use them unless you plan to contribute something! They're very experimental! Still 95% of the time it will work just fine. -- PR builds: Highly experimental builds, testers may grab from these. The average user should treat them the same as master builds, except sometimes they straight up don't build/work. +- Master: Unstable builds, can range from games working exceptionally well to instantly crashing. These contain straight from the oven fixes, don't use them unless you plan to contribute something! They're very experimental! Still 95% of the time they will work just fine. +- PR builds: Highly experimental builds, testers may grab from these. The average user should treat them the same as master builds. ## User configuration @@ -36,7 +36,7 @@ Eden will store configuration files in the following directories: - **Linux, macOS, FreeBSD, Solaris, OpenBSD**: `$XDG_DATA_HOME`, `$XDG_CACHE_HOME`, `$XDG_CONFIG_HOME`. - **HaikuOS**: `/boot/home/config/settings/eden` -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. +If a `user` directory is present in the current working directory, that will override all global configuration directories and the emulator will use the `user` directory instead. ### Environment variables @@ -64,4 +64,4 @@ Then just running `chmod +x script.sh && source script.sh`. ## Compatibility list -Eden doesn't mantain a compatibility list. However, [EmuReady](https://www.emuready.com/) has a more fine-grained compatibility information for multiple emulators/forks as well. +Eden doesn't maintain a compatibility list. However, [EmuReady](https://www.emuready.com/) has a more fine-grained compatibility information for multiple emulators/forks as well. diff --git a/docs/user/CFW.md b/docs/user/CFW.md index ea224d3d36..e9213e5dac 100644 --- a/docs/user/CFW.md +++ b/docs/user/CFW.md @@ -9,3 +9,11 @@ At the moment of writing, we do not support CFW such as Atmosphere, due to: We do however, maintain HLE compatibility with the former mentioned CFW, applications that require Atmosphere to run will run fine in the emulator without any adjustments. If they don't run - then that's a bug! + +## Atmosphere + +Fusée Gelée, the bootloader and other low-level mechanisms are not emulated at the moment. + +Having OFW is recommended, but may not be required (untested). + +Extract the contents of Atmosphere into `sdmc`. Then to launch simply use `-hlaunch` instead (orthogonal to `-qlaunch`). diff --git a/docs/user/CommandLine.md b/docs/user/CommandLine.md index cd98d88b19..a63a41a27c 100644 --- a/docs/user/CommandLine.md +++ b/docs/user/CommandLine.md @@ -1,24 +1,44 @@ # User Handbook - Command Line -There are two main applications, an SDL2 based app (`eden-cli`) and a Qt based app (`eden`); both accept command line arguments. +There are two main applications, an SDL-based app (`eden-cli`) and a Qt based app (`eden`); both accept the same command line arguments. -## eden -- `./eden `: Running with a single argument and nothing else, will make the emulator look for the given file and load it, this behaviour is similar to `eden-cli`; allows dragging and dropping games into the application. -- `-g `: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument. -- `-f`: Use fullscreen. -- `-u `: Select the index of the user to load as. -- `-input-profile `: Specifies input profile name to use (for player #0 only). -- `-qlaunch`: Launch QLaunch. -- `-setup`: Launch setup applet. - -## eden-cli +- `./eden `: Running with a single argument and nothing else, will make the emulator look for the given file and load it, this behavior is similar to `eden-cli`; allows dragging and dropping games into the application. - `--debug/-d`: Enter debug mode, allow gdb stub at port `1234` - `--config/-c`: Specify alternate configuration file. -- `--fullscreen/-f`: Set fullscreen. +- `--fullscreen/-f`: Use fullscreen. - `--help/-h`: Display help. -- `--game/-g`: Specify the game to run. +- `--game/-g `: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument. - `--multiplayer/-m`: Specify multiplayer options. - `--program/-p`: Specify the program arguments to pass (optional). -- `--user/-u`: Specify the user index. +- `--user/-u `: Specify the user index. - `--version/-v`: Display version and quit. -- `--input-profile/-i`: Specifies input profile name to use (for player #0 only). +- `--input-profile/-i `: Specifies input profile name to use (for player #0 only). +- `--null-render/-n`: Forces the usage of the "Null" render backend irrespective of settings. +- `--filter/-x`: Sets the debug log filter irrespective of settings. +- `--singlecore/-s`: Forces single-core regardless of settings. + +Only the Qt frontend supports the following arguments: + +- `-qlaunch`: Launch QLaunch. +- `-hlaunch`: Launch homebrew launcher `nx-hbloader`. + - Requires a copy of Atmosphere to be extracted onto `sdmc`. + - This is a shorthand for `/sdmc/atmosphere/hbl.nsp`. +- `-setup`: Launch setup applet. + +`eden` (provided with `--room`), and `eden-room` supports the following options as well: + +- `-n/--room-name`: The name of the room. +- `-d/--room-description`: The room description. +- `-s/--bind-address`: The bind address for the room. +- `-p/--port`: The port used for the room. +- `-m/--max-members`: The maximum number of players for this room. +- `-w/--password`: The password for the room. +- `-g/--preferred-game`: The preferred game for this room. +- `-i/--preferred-game-id`: The preferred game-id for this room. +- `-u/--username`: The username used for announce. +- `-t/--token`: The token used for announce. +- `-a/--web-api-url`: yuzu Web API url. +- `-b/--ban-list-file`: The file for storing the room ban list. +- `-l/--log-file`: The file for storing the room log. +- `-h/--help`: Display this help and exit. +- `-v/--version`: Output version information and exit. diff --git a/docs/user/Controllers.md b/docs/user/Controllers.md index 6aac9056ff..9f9a94878c 100644 --- a/docs/user/Controllers.md +++ b/docs/user/Controllers.md @@ -1,12 +1,12 @@ # User Handbook - Controllers -Most of the controls should work out of the box. If not, please use a joystick calibrator to ensure it's not an issue with your own controller, for example: +Most of the controls should work out of the box. If not, use a joystick calibrator to ensure it's not an issue with your own controller, for example: - https://github.com/dkosmari/calibrate-joystick ## Using external controllers on the Steamdeck -In desktop mode ignore your pro controller/xbox contoller external controller and use **Steam Virtual Gamepad 0 as Player 1**. If you have multiple external controllers set **Player 2 to Steam Virtual Gamepad 1**. Steam app must not be closed on desktop mode. +In desktop mode ignore your Pro controller/XBOX controller external controller and use **Steam Virtual Gamepad 0 as Player 1**. If you have multiple external controllers set **Player 2 to Steam Virtual Gamepad 1**. Steam app must not be closed on desktop mode. Here's the annoying part of it. When waking up the steam deck from sleep try not to touch any button on the Steamdeck and turn on your external controller. Then open the Eden.AppImage. If you're lucky you can get your external controller to be position 0 and also Steam Virtual Gamepad 0 in desktop mode. If not that is ok too unless you need to configure player 1 to have gyro. You might need to repeat this to get your external controller as Steam Virtual Gamepad 0 so you can config Player 1 having gyro. You might be able to config player 1 to have gyro with the Steamdeck itself. Or you can also config player 1, 2, 3, etc, to have gyro somehow. Make sure they are all using Virtual Gamepads though. @@ -18,7 +18,7 @@ Basically the Steamdeck or the external controller is fighting for position 0 an Use this guide for when you want to configure specific controller settings to be reused. -**Click [Here](https://evilperson1337.notion.site/Configuring-Controller-Profiles-2be57c2edaf680eabc3ac8c333ec75c4) for a version of this guide with images & visual elements.** +**[See here](https://evilperson1337.notion.site/Configuring-Controller-Profiles-2be57c2edaf680eabc3ac8c333ec75c4) for an illustrated guide.** --- @@ -30,8 +30,8 @@ Use this guide for when you want to configure specific controller settings to be #### Steps 1. Launch Eden and wait for it to load. -2. Navigate to *Emulation > Configure…* -3. Select **Controls** from the left-hand menu and configure your controller for the way you want it to be in game. +2. Navigate to `Emulation > Configure...` +3. Select **Controls** from the left-hand menu and configure your controller for the way you want it to be in-game. 4. Select **New** and enter a name for the profile in the box that appears. Press **OK** to save the profile settings. 5. Select **OK** to close the settings menu. @@ -39,7 +39,7 @@ Use this guide for when you want to configure specific controller settings to be Use this guide when you want to set up specific controller profiles for specific games. This can be useful for certain games like *Captain Toad Treasure Tracker* where a blue dot appears in the middle of the screen when you have docked mode enabled, but not handheld mode. -**Click [Here](https://evilperson1337.notion.site/Setting-Controller-Profiles-By-Game-2b057c2edaf681658a57f0c199cb6083) for a version of this guide with images & visual elements.** +**[See here](https://evilperson1337.notion.site/Setting-Controller-Profiles-By-Game-2b057c2edaf681658a57f0c199cb6083) for an illustrated guide.** --- @@ -47,7 +47,6 @@ Use this guide when you want to set up specific controller profiles for specific - Eden Emulator set up and fully configured - Controller Profile Created - - See [*Configuring Controller Profiles*](./ControllerProfiles.md) for instructions on how to do this if needed. --- diff --git a/docs/user/Graphics.md b/docs/user/Graphics.md index ad359b9049..4928eb47ff 100644 --- a/docs/user/Graphics.md +++ b/docs/user/Graphics.md @@ -44,6 +44,10 @@ Various graphical filters exist - each of them aimed at a specific target/image - **MMPX**: Nearest-neighbour filter aimed at providing higher pixel-art quality. - **Pros**: Offers decent pixel-art upscaling. - **Cons**: Only works for pixel-art. +- **SGSR**: Uses Snapdragon Studios Game Super Resolution to enhance image quality (similar to FSR, but for Adreno devices). + - **Pros**: Optimized for Adreno devices. + - **Cons**: Doesn't play nicely with non-Adreno devices. +- **SGSR Edge**: Almost the same pipeline as SGSR, but with improved edge detection. ### Anisotropy values @@ -96,7 +100,7 @@ HaikuOS bundles a Mesa library that doesn't support full core OpenGL 4.6 (requir ### Fixes for Windows 10 and above having "Device loss" Run the following batch script *inside* the Eden folder: -```cmd +```bat @echo off pushd "%~dp0" if exist "%temp%\FixFullScreen.reg" ( diff --git a/docs/user/GyroControls.md b/docs/user/GyroControls.md index 0df9088c1e..7d7eee6872 100644 --- a/docs/user/GyroControls.md +++ b/docs/user/GyroControls.md @@ -1,7 +1,7 @@ # Getting Gyro/Motion Controls Working on Steam Deck Use this guide when you want to use the Steam Deck's native gyro functionality for motion controls in Eden. -**Click [Here](https://evilperson1337.notion.site/Getting-Gyro-Motion-Controls-Working-on-Steam-Deck-2b057c2edaf681a1aaade35db6e0fd1b) for a version of this guide with images & visual elements.** +**[See here](https://evilperson1337.notion.site/Getting-Gyro-Motion-Controls-Working-on-Steam-Deck-2b057c2edaf681a1aaade35db6e0fd1b) for an illustrated guide.** ## Steamdeck @@ -16,14 +16,14 @@ Use this guide when you want to use the Steam Deck's native gyro functionality f ### Steps 1. Go into Steam Deck's Desktop Mode, and use the shortcut to launch EmuDeck. -2. Install [SteamDeckGyroDSU](https://github.com/kmicki/SteamDeckGyroDSU/releases) by going to *3rd Party Tools > Gyroscope* and clicking **Install.** +2. Install [SteamDeckGyroDSU](https://github.com/kmicki/SteamDeckGyroDSU/releases) by going to *3rd Party Tools > Gyroscope* and clicking **Install.** a. Alternatively you can install [SteamDeckGyroDSU](https://github.com/kmicki/SteamDeckGyroDSU/releases) manually following the GitHub page instructions. 3. Upon completion of the installation. You will need to reboot your Steam Deck. Do so before continuing on. 4. Go back into the Steam Deck Desktop Mode and open the Dolphin File Explorer. -5. Navigate to the following directory to see you controller configuration: `/home/deck/.config/Eden` -6. *Right-Click* the **qt-config.ini** file and open it with ***Kate*** -7. Look for the following line: `player_0_motionleft=[empty]`. -8. Change the line to now say: `player_0_motionleft="motion:0,pad:0,port:26760,guid:0000000000000000000000007f000001,engine:cemuhookudp"` +5. Navigate to the following directory to see you controller configuration: `/home/deck/.config/Eden` +6. *Right-Click* the **qt-config.ini** file and open it with ***Kate*** +7. Look for the following line: `player_0_motionleft=[empty]`. +8. Change the line to now say: `player_0_motionleft="motion:0,pad:0,port:26760,guid:0000000000000000000000007f000001,engine:cemuhookudp"` 9. Save the file and open Eden. -10. Launch a compatible title, like *The Legend of Zelda: Breath of the Wild*. -11. Test the gyro capabilities, for the above mentioned title, it is accessed by holding down the **R Trigger** and moving the Steam Deck around. \ No newline at end of file +10. Launch a compatible title, like *The Legend of Zelda: Breath of the Wild*. +11. Test the gyro capabilities, for the above mentioned title, it is accessed by holding down the **R Trigger** and moving the Steam Deck around. \ No newline at end of file diff --git a/docs/user/HowToAccessLogs.md b/docs/user/HowToAccessLogs.md index 826ead382c..6019e59bfc 100644 --- a/docs/user/HowToAccessLogs.md +++ b/docs/user/HowToAccessLogs.md @@ -2,7 +2,7 @@ Use this when you need to review the logs to determine an issue or provide them to a member of the Eden team. -**Click [Here](https://evilperson1337.notion.site/How-to-Access-Logs-2b057c2edaf68105a281fe1688a332d4) for a version of this guide with images & visual elements.** +**[See here](https://evilperson1337.notion.site/How-to-Access-Logs-2b057c2edaf68105a281fe1688a332d4) for an illustrated guide.** --- diff --git a/docs/user/ImportingSaves.md b/docs/user/ImportingSaves.md index 428609f69c..b936e2d32b 100644 --- a/docs/user/ImportingSaves.md +++ b/docs/user/ImportingSaves.md @@ -2,7 +2,7 @@ Use this guide when you want to manually import save files for use in the Eden emulator. -**Click [Here](https://evilperson1337.notion.site/Importing-Saves-Into-Eden-2b057c2edaf681fe968df8d63821ccae) for a version of this guide with images & visual elements.** +**[See here](https://evilperson1337.notion.site/Importing-Saves-Into-Eden-2b057c2edaf681fe968df8d63821ccae) for an illustrated guide.** ### Pre-Requisites - Eden emulator already set up and configured. diff --git a/docs/user/InstallingAtmosphereMods.md b/docs/user/InstallingAtmosphereMods.md index ae8e4b37d2..689ed88e16 100644 --- a/docs/user/InstallingAtmosphereMods.md +++ b/docs/user/InstallingAtmosphereMods.md @@ -2,7 +2,7 @@ Use this guide for when you want to install an Atmosphere-based mod for use in Eden. -**Click [Here](https://evilperson1337.notion.site/Installing-Atmosphere-Mods-2b057c2edaf681fe8d39cbfc2d0cc799) for a version of this guide with images & visual elements.** +**[See here](https://evilperson1337.notion.site/Installing-Atmosphere-Mods-2b057c2edaf681fe8d39cbfc2d0cc799) for an illustrated guide.** --- diff --git a/docs/user/InstallingUpdatesDLC.md b/docs/user/InstallingUpdatesDLC.md index 2fdf40dde9..e012a58ca5 100644 --- a/docs/user/InstallingUpdatesDLC.md +++ b/docs/user/InstallingUpdatesDLC.md @@ -4,11 +4,11 @@ Use this guide when you want to install Updates or DLC for your games in Eden. -**Click [Here](https://evilperson1337.notion.site/Working-with-Updates-DLC-in-Eden-2b057c2edaf681dfb65dfc4dd96980c0) for a version of this guide with images & visual elements.** +**[See here](https://evilperson1337.notion.site/Working-with-Updates-DLC-in-Eden-2b057c2edaf681dfb65dfc4dd96980c0) for an illustrated guide.** --- diff --git a/docs/user/Multiplayer.md b/docs/user/Multiplayer.md index 57c4495f46..37581240cf 100644 --- a/docs/user/Multiplayer.md +++ b/docs/user/Multiplayer.md @@ -1,16 +1,16 @@ -# Multiplayer +# User Handbook - Multiplayer Use this guide to answer questions regarding and to start using the multiplayer functionality of Eden. ## Multiplayer FAQ This FAQ will serve as a general quick question and answer simple questions. -**Click [Here](https://evilperson1337.notion.site/Multiplayer-FAQ-2c357c2edaf680fca2e9ce59969a220f) for a version of this guide with images & visual elements.** +**[See here](https://evilperson1337.notion.site/Multiplayer-FAQ-2c357c2edaf680fca2e9ce59969a220f) for an illustrated guide.** ### Can Eden Play Games with a Switch Console? No - The only emulator that has this kind of functionality is *Ryujinx* and it's forks. This solution requires loading a custom module on a modded switch console to work. ### Can I Play Online Games? -No - This would require hijacking requests to Nintendo's official servers to a custom server infrastructure built to emulate that functionality. This is how services like [*Pretendo*](https://pretendo.network/) operate. As such, you would not be able to play “Online”, you can however play multiplayer games. +No - This would require hijacking requests to Nintendo's official servers to a custom server infrastructure built to emulate that functionality. This is how services like [*Pretendo*](https://pretendo.network/) operate. As such, you would not be able to play "Online", you can however play multiplayer games. ### What's the Difference Between Online and Multiplayer? I have chosen the wording carefully here for a reason. @@ -21,7 +21,7 @@ I have chosen the wording carefully here for a reason. The rule of thumb here is simple: If a game supports the ability to communicate without a server (Local Wireless, LAN, etc.) you will be able to play with other users. If it requires a server to function - it will not. You will need to look up if your title support Local Wireless/LAN play as an option. ### How Does Multiplayer Work on Eden Exactly? -Eden's multiplayer works by emulating the Switch's local wireless (LDN) system, then tunneling that traffic over the internet through “rooms” that act like lobbies. Each player runs their own instance of the emulator, and as long as everyone joins the same room and the game supports local wireless multiplayer, the emulated consoles see each other as if they were on the same local network. This design avoids typical one-save netplay issues because every user keeps an independent save and console state while only the in-game wireless packets are forwarded through the room server. In practice, you pick or host a room, configure your network interface/port forwarding if needed, then launch any LDN-capable game; from the game's perspective it is just doing standard local wireless, while the emulator handles discovery and communication over the internet or LAN. +Eden's multiplayer works by emulating the Switch's local wireless (LDN) system, then tunneling that traffic over the internet through "rooms" that act like lobbies. Each player runs their own instance of the emulator, and as long as everyone joins the same room and the game supports local wireless multiplayer, the emulated consoles see each other as if they were on the same local network. This design avoids typical one-save netplay issues because every user keeps an independent save and console state while only the in-game wireless packets are forwarded through the room server. In practice, you pick or host a room, configure your network interface/port forwarding if needed, then launch any LDN-capable game; from the game's perspective it is just doing standard local wireless, while the emulator handles discovery and communication over the internet or LAN. ### What Do I Need to Do? That depends entirely on what your goal is and your level of technical ability, you have a 2 options on how to proceed. @@ -40,7 +40,7 @@ While it would be nice if everything always worked perfectly - that is not reali 1. Emulator Version Mismatches 1. Occasionally updates to the emulator of choice alter how the LDN functionality is handled. In these situations, unexpected behavior can occur when trying to establish LDN connections. This is a good first step to check if you are having issues playing a game together, but can join the same lobby without issue. 2. Game Version Mismatches - 1. It is best practice to have the game version be identical to each other in order to ensure that there is no difference in how the programs are handling the LDN logic. Games are black boxes that the dev team cannot see into to ensure the logic handling operates the same way. For this reason, it is highly advised that the game versions match across all the players. This would be a good 2nd step to check if you are having issues playing a game together, but can join the same lobby without issue. + 1. It is best practice to have the game version be identical to each other in order to ensure that there is no difference in how the programs are handling the LDN logic. Games are black boxes that the dev team can't see into to ensure the logic handling operates the same way. For this reason, it is highly advised that the game versions match across all the players. This would be a good 2nd step to check if you are having issues playing a game together, but can join the same lobby without issue. 3. Latency 1. Because this implementation is emulating a LAN/Local Wireless connection - it is extremely sensitive to network latency and drops. Eden has done a good job of trying to account for this and not immediately drop users out - but it is not infallible. If latency is a concern or becomes an issue - consider hosting a room. @@ -49,7 +49,7 @@ While it would be nice if everything always worked perfectly - that is not reali ## Joining a Multiplayer Room Use this when you need to connect to a multiplayer room for LDN functionality inside of Eden. This does not cover how to host a room, only joining existing ones. -**Click [Here](https://evilperson1337.notion.site/Access-Your-Multiplayer-Room-Externally-2c357c2edaf681c0ab2ce2ee624d809d) for a version of this guide with images & visual elements.** +**[See here](https://evilperson1337.notion.site/Access-Your-Multiplayer-Room-Externally-2c357c2edaf681c0ab2ce2ee624d809d) for an illustrated guide.** ### Pre-Requisites - Eden set up and functioning @@ -66,12 +66,12 @@ There are 2 primary methods that you can use to connect to an existing room, dep ## Joining a Public Lobby -1. Open Eden and navigate to *Multiplayer → Browse Public Game Lobby*. +1. Open Eden and navigate to *Multiplayer > Browse Public Game Lobby*. 2. The **Public Room Browser** will now open and display a list of publicly accessible rooms. Find one you want to connect to and double click it. -13. *Syncthing* will now pull all the files from the Parent and store them in your local save directory. At this point the files are in sync and alterations to one will affect the other and both can be considered “*Parents*” for other devices you want to add. Repeat these steps for as many devices you want. +13. *Syncthing* will now pull all the files from the Parent and store them in your local save directory. At this point the files are in sync and alterations to one will affect the other and both can be considered "*Parents*" for other devices you want to add. Repeat these steps for as many devices you want. ## Linux @@ -166,9 +166,9 @@ Use this when you want to set this machine up as a child (pull files from the ot Use this when you want to set this machine as the initial source of truth (push files out to all the other devices). Afterwards they will all be equal partners, not a parent/child relationship, this just helps with initial setup. 1. Right-Click the *Syncthing* Tray icon in your taskbar and select **Open Syncthing.** - 1. If you don’t have a taskbar in your distro, you can also reach it directly by opening a web browser to: *http://127.0.0.1:8384/.* + 1. If you don't have a taskbar in your distro, you can also reach it directly by opening a web browser to: *http://127.0.0.1:8384/.* 2. You will now have a browser window open up to a web GUI to configure *Syncthing*. You will get a pop up about allowing anonymous usage and setting a password, make your selections to close them. -3. We’ll start by adding the folder with our save files that we want to sync by Pressing **+ Add Folder**. +3. We'll start by adding the folder with our save files that we want to sync by Pressing **+ Add Folder**. 4. A pop-up window will appear, fill in the Folder label field with whatever you want to call it, like Switch Saves. 5. Enter the Full folder path to where your save files are stored on this machine. @@ -187,7 +187,7 @@ Use this when you want to set this machine as the initial source of truth (push Use this when you want to set this machine up as a child (pull files from the other devices). Afterwards they will all be equal partners, not a parent/child relationship, this just helps with initial setup. -1. Install Syncthing Tray on the client device following the section above. Copy the child’s ID and store it so it is accessible to the Parent. +1. Install Syncthing Tray on the client device following the section above. Copy the child's ID and store it so it is accessible to the Parent. 2. ***ON THE PARENT***: Right-Click the *Syncthing* Tray icon in your taskbar and select **Open Syncthing** if it is not open already**.** 3. You will now have a browser window open up to a web GUI to configure *Syncthing*. You will get a pop up about allowing anonymous usage and setting a password, make your selections to close them. 4. Navigate down to **+ Add Remote Device**, we are going to add our Child device, so I hope you have its ID handy. If not, go back and get it. @@ -208,4 +208,4 @@ Use this when you want to set this machine up as a child (pull files from the ot -13. *Syncthing* will now pull all the files from the Parent and store them in your local save directory. At this point the files are in sync and alterations to one will affect the other and both can be considered “*Parents*” for other devices you want to add. Repeat these steps for as many devices you want. +13. *Syncthing* will now pull all the files from the Parent and store them in your local save directory. At this point the files are in sync and alterations to one will affect the other and both can be considered "*Parents*" for other devices you want to add. Repeat these steps for as many devices you want. diff --git a/docs/user/Testing.md b/docs/user/Testing.md index eb3beeb37c..b1af94147d 100644 --- a/docs/user/Testing.md +++ b/docs/user/Testing.md @@ -41,7 +41,7 @@ For regressions/bugs from PRs or commits: - [ ] Bisected PR? (if it has commits) - [ ] Found bisected commit? -If an issue sporadically appears, try to do multiple runs, try if possible, to count the number of times it has failed and the number of times it has "worked just fine"; say it worked 3 times but failed 1. then there is a 1/4th chance every run that the issue is replicated - so every bisect step would require 4 runs to ensure there is atleast a chance of triggering the bug. +If an issue sporadically appears, try to do multiple runs, try if possible, to count the number of times it has failed and the number of times it has "worked just fine"; say it worked 3 times but failed 1. then there is a 1/4th chance every run that the issue is replicated - so every bisect step would require 4 runs to ensure there is at least a chance of triggering the bug. ## What to do when something seems off @@ -96,4 +96,4 @@ The faulty commit then, is 6th of Jan. This is called bisection https://git-scm. - PR's marked with **WIP** do NOT need to be tested unless explicitly asked (check the git in case) - Sometimes license checks may fail, hover over the build icon to see if builds did succeed, as the CI will push builds even if license checks fail. - All open PRs can be viewed [here](https://git.eden-emu.dev/eden-emu/eden/pulls/). -- If site is down use one of the [mirrors](./user/ThirdParty.md#mirrors). +- If site is down use one of the [mirrors](./ThirdParty.md#mirrors). diff --git a/docs/user/ThirdParty.md b/docs/user/ThirdParty.md index 5bd72ebe72..2e7bc63f6f 100644 --- a/docs/user/ThirdParty.md +++ b/docs/user/ThirdParty.md @@ -1,29 +1,30 @@ # User Handbook - Third party tools and extras -The Eden emulator by itself lacks some functionality - or otherwise requires external files (such as packaging) to operate correctly in a given OS. Addendum to that some repositories provide nightly or specialised builds of the emulator. +The Eden emulator by itself lacks some functionality - or otherwise requires external files (such as packaging) to operate correctly in a given OS. Addendum to that some repositories provide nightly or specialized builds of the emulator. -While most of the links mentioned in this guide are relatively "safe"; we urge users to use their due diligence and appropriatedly verify the integrity of all files downloaded and ensure they're not compromised. +While most of the links mentioned in this guide are relatively "safe"; we urge users to use their due diligence and appropriately verify the integrity of all files downloaded and ensure they're not compromised. - [NixOS Eden Flake](https://github.com/Grantimatter/eden-flake) - [ES-DE Frontend Support](https://github.com/GlazedBelmont/es-de-android-custom-systems) ## Mirrors -The main origin repository is always at https://git.eden-emu.dev/eden-emu/eden. +The main origin repository is always at . -- https://github.com/eden-emulator/mirror -- https://git.crueter.xyz/mirror/eden -- https://collective.taymaerz.de/eden/eden +- +- +- +- Other mirrors obviously exist on the internet, but we can't guarantee their reliability and/or availability. -If you're someone wanting to make a mirror, simply setup forgejo and automatically mirror from the origin repository. Or you could mirror a mirror to save us bandwidth... your choice! +If you're someone wanting to make a mirror, simply setup Forgejo and automatically mirror from the origin repository. Or you could mirror a mirror to save us bandwidth... your choice! ## Configuring Obtainium Very nice handy app, here's a quick rundown how to configure: -1. Copy the URL: https://git.eden-emu.dev/eden-emu/eden/ (or one of your favourite mirrors) +1. Copy the URL: (or one of your favorite mirrors) 2. Open Obtainium and tap `Add App`. 3. Paste the URL into the `App Source URL` field. 4. Override Source: Look for the `Override Source` dropdown menu and select `Forgejo (Codeberg)`. @@ -35,7 +36,7 @@ Note: Even though the site isn't Codeberg, it uses the same Forgejo/Gitea backen ### Method 1 -1. Download ZIP from [here](https://github.com/GlazedBelmont/es-de-android-custom-systems) +1. Download the ZIP from [GitHub](https://github.com/GlazedBelmont/es-de-android-custom-systems) 2. Unzip the file and extract `es_systems.xml` and `es_find_rules.xml` to `\Odin2\Internal shared storage\ES-DE\custom_systems`. 3. Press `Start -> Other Settings -> Alternative Emulators` and set it to Eden (Standalone). @@ -60,7 +61,7 @@ Note: Even though the site isn't Codeberg, it uses the same Forgejo/Gitea backen ``` -3. Add this line of text to your `es_systems.xml` underneath where the rest of your switch system entries are: +1. Add this line of text to your `es_systems.xml` underneath where the rest of your switch system entries are: ```xml %EMULATOR_EDEN% %ACTION%=android.nfc.action.TECH_DISCOVERED %DATA%=%ROMPROVIDER% diff --git a/docs/user/Troubleshoot.md b/docs/user/Troubleshoot.md index 0aebfc5b96..76d69e38f9 100644 --- a/docs/user/Troubleshoot.md +++ b/docs/user/Troubleshoot.md @@ -14,7 +14,7 @@ ## Eden Fails to Launch and Does Not Leave Any Logs -**Click [Here](https://evilperson1337.notion.site/Windows-Eden-Fails-to-Launch-and-Does-Not-Leave-Any-Logs-2b057c2edaf68156b640cf1ac549870a) for a version of this guide with images & visual elements.** +**[See here](https://evilperson1337.notion.site/Windows-Eden-Fails-to-Launch-and-Does-Not-Leave-Any-Logs-2b057c2edaf68156b640cf1ac549870a) for an illustrated guide.** ### Error Details @@ -24,11 +24,11 @@ **Error Log Entries:** -``` +```text None ``` **Example Error Message Entry in Windows Event Viewer** -``` +```text Faulting application name: eden.exe, version: 0.0.0.0, time stamp: 0x6795dc3c Faulting module name: ntdll.dll, version: 10.0.26100.3037, time stamp: 0x95e6c489 Exception code: 0xc0000005 @@ -68,12 +68,12 @@ Faulting package-relative application ID: 2. Confirm there are no logs created in the log directory. 1. See the [How to Access Logs](./HowToAccessLogs.md) page for the log location if you need it. 2. If there are any entries in here since you tried step 1, this is likely not your issue. -3. Navigate to your *Windows Event Viewer* (Start Menu → **eventvwr.msc)**. +3. Navigate to your *Windows Event Viewer* (Start Menu > **eventvwr.msc)**. 4. Expand **Windows Logs** and select **Application.** 5. Look for an entry with the Level of Error, and look for a message similar to the following - ``` + ```text Faulting application name: Eden.exe, version: 0.0.0.0, time stamp: 0x6795dc3c Faulting module name: ntdll.dll, version: 10.0.26100.3037, time stamp: 0x95e6c489 Exception code: 0xc0000005 @@ -90,7 +90,7 @@ Faulting package-relative application ID: 6. Run a Command Prompt terminal Window as Administrator. 7. Enter the following command and wait for it to complete. It will take a while, just be patient and do other things while it completes. - ``` + ```bat DISM /Online /Cleanup-Image /RestoreHealth ``` diff --git a/docs/user/UsingAmiibo.md b/docs/user/UsingAmiibo.md index 4288517bec..112693ec79 100644 --- a/docs/user/UsingAmiibo.md +++ b/docs/user/UsingAmiibo.md @@ -2,7 +2,7 @@ Use this guide when you want to load Amiibo into your games for use with the Eden emulator. -**Click [Here](https://evilperson1337.notion.site/Using-Amiibo-with-Eden-2b057c2edaf681b1b28ec6be600c6d3e) for a version of this guide with images & visual elements.** +**[See here](https://evilperson1337.notion.site/Using-Amiibo-with-Eden-2b057c2edaf681b1b28ec6be600c6d3e) for an illustrated guide.** ## Android @@ -17,7 +17,7 @@ TBD @@ -30,14 +30,14 @@ TBD -1. Navigate to the Amiibo section of the game. The method for initiating the scanning varies from game to game, for *Captain Toad’s Treasure Tracker*, you need to go to the press the **+** button when on the level select. You will need to look up how to do so with your specific game. +1. Navigate to the Amiibo section of the game. The method for initiating the scanning varies from game to game, for *Captain Toad's Treasure Tracker*, you need to go to the press the **+** button when on the level select. You will need to look up how to do so with your specific game. 2. Upon activating the Amiibo scan functionality, you should get a Scan page. Eden is now looking for an Amiibo file to be loaded, which emulates scanning an Amiibo on actual hardware. -3. Navigate to **File > Load/Remove Amiibo…**, or press the hotkey to do the same (**F2** on keyboard by default). +3. Navigate to **File > Load/Remove Amiibo...**, or press the hotkey to do the same (**F2** on keyboard by default). 4. In the file explorer that opens, navigate to the amiibo file you want to use. diff --git a/docs/user/UsingCheats.md b/docs/user/UsingCheats.md index f0da068582..d6ad7b634a 100644 --- a/docs/user/UsingCheats.md +++ b/docs/user/UsingCheats.md @@ -2,7 +2,7 @@ Use this guide when you want to add cheats into a game to alter gameplay for use with the Eden emulator. -**Click [Here](https://evilperson1337.notion.site/Using-Cheats-with-Eden-2b057c2edaf6818fab66c276e2304bb4) for a version of this guide with images & visual elements.** +**[See here](https://evilperson1337.notion.site/Using-Cheats-with-Eden-2b057c2edaf6818fab66c276e2304bb4) for an illustrated guide.** ## Android @@ -58,24 +58,24 @@ Community Member [Ninjistix](https://github.com/Ninjistix) created a utility (Wi ``` [Super Mario Bros. Wonder - Various] <- Optional -[♯ 1. Always Star Power] +[# 1. Always Star Power] 040E0000 00880580 52800035 -[♯ 2. Star Power + Bubble Mode (Invincible)] +[# 2. Star Power + Bubble Mode (Invincible)] 040E0000 00880580 52800075 -[♯ 3. Can Fast Travel to Any Course and World] +[# 3. Can Fast Travel to Any Course and World] 040E0000 00935E10 52800036 040E0000 0048A528 52800028 040E0000 005D9F58 52800028 -[♯ 4. Got All Top of Flag Poles] +[# 4. Got All Top of Flag Poles] 040E0000 0048A818 52800028 ``` ### Step 3: Enabling/Disabling Cheats -Cheats are enabled by default, but can be disabled so they don’t affect gameplay fairly easily using the game properties. +Cheats are enabled by default, but can be disabled so they don't affect gameplay fairly easily using the game properties. 1. Open Eden and press and hold the game you want to apply the cheat to. 2. Scroll down on the properties until you see **Add-ons**, select this option. @@ -130,24 +130,24 @@ Community Member [Ninjistix](https://github.com/Ninjistix) created a utility (Wi ``` [Super Mario Bros. Wonder - Various] <- Optional -[♯ 1. Always Star Power] +[# 1. Always Star Power] 040E0000 00880580 52800035 -[♯ 2. Star Power + Bubble Mode (Invincible)] +[# 2. Star Power + Bubble Mode (Invincible)] 040E0000 00880580 52800075 -[♯ 3. Can Fast Travel to Any Course and World] +[# 3. Can Fast Travel to Any Course and World] 040E0000 00935E10 52800036 040E0000 0048A528 52800028 040E0000 005D9F58 52800028 -[♯ 4. Got All Top of Flag Poles] +[# 4. Got All Top of Flag Poles] 040E0000 0048A818 52800028 ``` ### Step 3: Enabling/Disabling Cheats -Cheats are enabled by default, but can be disabled so they don’t affect gameplay fairly easily using the game properties. +Cheats are enabled by default, but can be disabled so they don't affect gameplay fairly easily using the game properties. 1. *Right-Click* the game and select *Configure Game*. 2. In the **Add-Ons** section, locate the cheat you wish to enable. diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index e230f709fa..cb1deceb96 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -6,8 +6,7 @@ # TODO(crueter): A lot of this should be moved to the root. # otherwise we have to do weird shenanigans with library linking and stuff - -include(CPMUtil) +# Or just add a CPMUtil thing to propagate packages # Explicitly declare this option here to propagate to the oaknut CPM call option(DYNARMIC_TESTS "Build tests" ${BUILD_TESTING}) @@ -49,8 +48,13 @@ if (NOT TARGET stb::headers) add_library(stb::headers ALIAS stb) endif() -# ItaniumDemangle -if (NOT TARGET LLVM::Demangle) +AddJsonPackage(NAME zbic DOWNLOAD_ONLY) +set(ZBIC_INCLUDE_DIR + "${zbic_SOURCE_DIR}/src" + PARENT_SCOPE) + +# ItaniumDemangle (Windows only) +if (WIN32 AND NOT TARGET LLVM::Demangle) add_library(demangle demangle/ItaniumDemangle.cpp) target_include_directories(demangle PUBLIC ./demangle) if (NOT MSVC) @@ -59,9 +63,6 @@ if (NOT TARGET LLVM::Demangle) add_library(LLVM::Demangle ALIAS demangle) endif() -# unordered_dense -AddJsonPackage(unordered-dense) - # httplib if (IOS) set(HTTPLIB_USE_BROTLI_IF_AVAILABLE OFF) @@ -82,6 +83,11 @@ if (ARCHITECTURE_riscv64) AddJsonPackage(biscuit) endif() +# Lagoon +if (ARCHITECTURE_loongarch64) + AddJsonPackage(lagoon) +endif() + # Vulkan stuff AddDependentPackages(vulkan-headers vulkan-utility-libraries) @@ -135,47 +141,49 @@ if(ENABLE_CUBEB) endif() endif() -if (NOT ANDROID) - if (YUZU_USE_EXTERNAL_SDL2) - message(STATUS "Using SDL2 from externals.") - if (NOT WIN32) - # Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers - # Since 2.0.18 Atomic+Threads required for HIDAPI/libusb (see https://github.com/libsdl-org/SDL/issues/5095) - # Yuzu-cmd also needs: Video (depends on Loadso/Dlopen) - # CPUinfo also required for SDL Audio, at least until 2.28.0 (see https://github.com/libsdl-org/SDL/issues/7809) - set(SDL_UNUSED_SUBSYSTEMS - File Filesystem - Locale Power Render) - foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS}) - string(TOUPPER ${_SUB} _OPT) - set(SDL_${_OPT} OFF) - endforeach() +if (NOT YUZU_USE_BUNDLED_SDL3) + if (NOT WIN32) + # Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers + # Since 2.0.18 Atomic+Threads required for HIDAPI/libusb (see https://github.com/libsdl-org/SDL/issues/5095) + # Yuzu-cmd also needs: Video (depends on Loadso/Dlopen) + # CPUinfo also required for SDL Audio, at least until 2.28.0 (see https://github.com/libsdl-org/SDL/issues/7809) + set(SDL_UNUSED_SUBSYSTEMS + File Filesystem + Locale Power Render) + foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS}) + string(TOUPPER ${_SUB} _OPT) + set(SDL_${_OPT} OFF) + endforeach() - set(HIDAPI ON) - endif() - - if (APPLE) - set(SDL_FILE ON) - endif() - - if ("${YUZU_SYSTEM_PROFILE}" STREQUAL "steamdeck") - set(SDL_PIPEWIRE OFF) # build errors out with this on - AddJsonPackage("sdl2_steamdeck") - else() - AddJsonPackage("sdl2_generic") - endif() - elseif (YUZU_USE_BUNDLED_SDL2) - message(STATUS "Using bundled SDL2") - if (PLATFORM_FREEBSD) - set(BUILD_SHARED_LIBS ON) - endif() - AddJsonPackage(sdl2) + set(HIDAPI ON) endif() - find_package(SDL2 2.26.4 REQUIRED) + if (APPLE) + set(SDL_FILE ON) + endif() + + AddJsonPackage(sdl3) +else() + message(STATUS "Using bundled SDL3") + AddJsonPackage(sdl3-ci) + + # copy sdl java sources for consumption by Gradle + if (ANDROID) + file(COPY ${SDL3_SOURCE_DIR}/java DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/generated/sdl) + endif() endif() -set(BUILD_SHARED_LIBS OFF) +# Normalize SDL3 link target across package variants. +# Some SDL3 packages export only SDL3::SDL3-shared or SDL3::SDL3-static. +if (NOT TARGET SDL3::SDL3) + if (TARGET SDL3::SDL3-shared) + add_library(SDL3::SDL3 ALIAS SDL3::SDL3-shared) + elseif (TARGET SDL3::SDL3-static) + add_library(SDL3::SDL3 ALIAS SDL3::SDL3-static) + else() + message(FATAL_ERROR "SDL3 package found, but no usable SDL3 target was exported") + endif() +endif() # SPIRV Headers AddJsonPackage(spirv-headers) @@ -192,6 +200,50 @@ else() endif() endif() +# reshadefx +if (ENABLE_RESHADE) + AddJsonPackage(NAME reshade DOWNLOAD_ONLY) + + set(RESHADEFX_SHIM_DIR ${CMAKE_CURRENT_BINARY_DIR}/reshadefx_shim) + file(WRITE ${RESHADEFX_SHIM_DIR}/spirv.hpp "#include \n") + file(WRITE ${RESHADEFX_SHIM_DIR}/GLSL.std.450.h "#include \n") + if (APPLE) + file(WRITE ${RESHADEFX_SHIM_DIR}/malloc.h "#include \n#include \n") + endif() + + add_library(reshadefx STATIC + ${reshade_SOURCE_DIR}/source/effect_codegen_spirv.cpp + ${reshade_SOURCE_DIR}/source/effect_expression.cpp + ${reshade_SOURCE_DIR}/source/effect_lexer.cpp + ${reshade_SOURCE_DIR}/source/effect_parser_exp.cpp + ${reshade_SOURCE_DIR}/source/effect_parser_stmt.cpp + ${reshade_SOURCE_DIR}/source/effect_preprocessor.cpp + ${reshade_SOURCE_DIR}/source/effect_symbol_table.cpp + ) + + target_include_directories(reshadefx SYSTEM PUBLIC ${reshade_SOURCE_DIR}/source) + target_include_directories(reshadefx PRIVATE ${RESHADEFX_SHIM_DIR}) + target_link_libraries(reshadefx PUBLIC SPIRV-Headers::SPIRV-Headers) + + if (NOT MSVC) + target_compile_options(reshadefx PRIVATE -w -fno-char8_t) + else() + target_compile_options(reshadefx PRIVATE /w /Zc:char8_t-) + endif() + + if (WIN32) + if (NOT MSVC) + target_compile_options(reshadefx PRIVATE -include share.h) + else() + target_compile_options(reshadefx PRIVATE /FIshare.h) + endif() + endif() + + if (NOT TARGET reshadefx::reshadefx) + add_library(reshadefx::reshadefx ALIAS reshadefx) + endif() +endif() + # Catch2 if (YUZU_TESTS OR DYNARMIC_TESTS) AddJsonPackage(catch2) @@ -383,13 +435,6 @@ if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client) endif() endif() -# oboe -if (ANDROID) - AddJsonPackage(oboe) - - add_library(oboe::oboe ALIAS oboe) -endif() - if (APPLE) # moltenvk if (NOT YUZU_USE_BUNDLED_MOLTENVK) diff --git a/externals/cmake-modules/DetectPlatform.cmake b/externals/cmake-modules/DetectPlatform.cmake index de1a85b793..0f7cdfcf46 100644 --- a/externals/cmake-modules/DetectPlatform.cmake +++ b/externals/cmake-modules/DetectPlatform.cmake @@ -1,45 +1,46 @@ -# SPDX-FileCopyrightText: Copyright 2025 crueter +# SPDX-FileCopyrightText: Copyright 2026 crueter # SPDX-License-Identifier: LGPL-3.0-or-later ## DetectPlatform ## -# This is a small helper that sets PLATFORM_ variables for various +# This is a small helper that sets platform variables for various # operating systems and distributions. Note that Apple, Windows, Android, etc. # are not covered, as CMake already does that for us. # It also sets CXX_ for the C++ compiler. -# Furthermore, some platforms have really silly requirements/quirks, so this -# also does a few of those. - # This module contains contributions from the Eden Emulator Project, # notably from crueter and Lizzie. if (${CMAKE_SYSTEM_NAME} STREQUAL "SunOS") - set(PLATFORM_SUN ON) + set(SOLARIS ON) elseif (${CMAKE_SYSTEM_NAME} STREQUAL "OpenOrbis") - set(PLATFORM_PS4 ON) + set(OPENORBIS ON) elseif (${CMAKE_SYSTEM_NAME} STREQUAL "managarm") - set(PLATFORM_MANAGARM ON) -elseif (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") - set(PLATFORM_FREEBSD ON) -elseif (${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") - set(PLATFORM_OPENBSD ON) -elseif (${CMAKE_SYSTEM_NAME} STREQUAL "NetBSD") - set(PLATFORM_NETBSD ON) -elseif (${CMAKE_SYSTEM_NAME} STREQUAL "DragonFly") - set(PLATFORM_DRAGONFLYBSD ON) + set(MANAGARM ON) elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Haiku") - set(PLATFORM_HAIKU ON) -elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - set(PLATFORM_LINUX ON) + set(HAIKUOS ON) +endif() + +# BSD +if (DEFINED BSD) + if ("${BSD}" STREQUAL "DragonFlyBSD") + set(DRAGONFLYBSD ON) + elseif ("${BSD}" STREQUAL "FreeBSD") + set(FREEBSD ON) + elseif ("${BSD}" STREQUAL "OpenBSD") + set(OPENBSD ON) + elseif ("${BSD}" STREQUAL "NetBSD") + set(NETBSD ON) + endif() endif() # dumb heuristic to detect msys2 if (CMAKE_COMMAND MATCHES "msys64") - set(PLATFORM_MSYS ON) + set(MSYS2 ON) endif() +# compiler checks if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set(CXX_CLANG ON) if (MSVC) @@ -47,8 +48,6 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") endif() elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(CXX_GCC ON) -elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - set(CXX_CL ON) elseif (CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") set(CXX_ICC ON) elseif (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") @@ -61,37 +60,13 @@ if(MINGW AND CXX_CLANG) set(CMAKE_SYSTEM_VERSION 10.0.0) endif() -# NB: this does not account for SPARC -if (PLATFORM_SUN) - # Terrific OpenIndiana pkg shenanigans - list(APPEND CMAKE_PREFIX_PATH - "${CMAKE_SYSROOT}/usr/lib/qt/6.6/lib/amd64/cmake") - list(APPEND CMAKE_MODULE_PATH - "${CMAKE_SYSROOT}/usr/lib/qt/6.6/lib/amd64/cmake") - - # Amazing - absolutely incredible - list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SYSROOT}/usr/lib/amd64/cmake") - list(APPEND CMAKE_MODULE_PATH "${CMAKE_SYSROOT}/usr/lib/amd64/cmake") - - # For some mighty reason, doing a normal release build sometimes - # may not trigger the proper -O3 switch to materialize - if (CMAKE_BUILD_TYPE MATCHES "Release") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") - endif() - if (CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2") - endif() -endif() - # MSYS2 utilities # Sometimes, PkgConfig modules will incorrectly reference / when CMake # wants you to reference it as C:/msys64/. This function corrects that. # Example in a Find module: #[[ - if (PLATFORM_MSYS) + if (MSYS2) FixMsysPath(PkgConfig::OPUS) endif() ]] @@ -116,41 +91,9 @@ function(FixMsysPath target) INTERFACE_INCLUDE_DIRECTORIES ${include_dir}) endfunction() -# MSYSTEM handling + program_path -if (PLATFORM_MSYS) - # really, really dumb heuristic to detect what environment we are in - macro(system var) - if (CMAKE_COMMAND MATCHES ${var}) - set(MSYSTEM ${var}) - endif() - endmacro() - - system(mingw64) - system(clang64) - system(clangarm64) - system(ucrt64) - - if (NOT DEFINED MSYSTEM) - set(MSYSTEM msys2) - endif() - - # We generally want to prioritize environment-specific binaries if possible - # some, like autoconf, are not present on environments besides msys2 though - set(CMAKE_PROGRAM_PATH C:/msys64/${MSYSTEM}/bin C:/msys64/usr/bin) - set(ENV{PKG_CONFIG_PATH} C:/msys64/${MSYSTEM}/lib/pkgconfig) -endif() - -# This saves a truly ridiculous amount of time during linking -# In my tests, without this, Eden takes 2 mins, with this, it takes 3-5 seconds -# or on GitHub Actions, 10 minutes -> 3 seconds +# Saves linking time if (MINGW) set(MINGW_FLAGS "-Wl,--strip-all -Wl,--gc-sections") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${MINGW_FLAGS}") endif() - -# awesome -if (PLATFORM_FREEBSD OR PLATFORM_DRAGONFLYBSD) - set(CMAKE_EXE_LINKER_FLAGS - "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/local/lib") -endif() diff --git a/externals/cpmfile.json b/externals/cpmfile.json deleted file mode 100644 index 0f8d34230c..0000000000 --- a/externals/cpmfile.json +++ /dev/null @@ -1,254 +0,0 @@ -{ - "vulkan-memory-allocator": { - "package": "VulkanMemoryAllocator", - "repo": "GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator", - "tag": "v%VERSION%", - "hash": "deb5902ef8db0e329fbd5f3f4385eb0e26bdd9f14f3a2334823fb3fe18f36bc5d235d620d6e5f6fe3551ec3ea7038638899db8778c09f6d5c278f5ff95c3344b", - "find_args": "CONFIG", - "git_version": "3.3.0" - }, - "sirit": { - "repo": "eden-emulator/sirit", - "git_version": "1.0.4", - "tag": "v%VERSION%", - "artifact": "sirit-source-%VERSION%.tar.zst", - "hash_suffix": "sha512sum", - "find_args": "CONFIG", - "options": [ - "SIRIT_USE_SYSTEM_SPIRV_HEADERS ON" - ] - }, - "sirit-ci": { - "ci": true, - "package": "sirit", - "name": "sirit", - "repo": "eden-emulator/sirit", - "version": "1.0.4" - }, - "httplib": { - "repo": "yhirose/cpp-httplib", - "tag": "v%VERSION%", - "hash": "5efa8140aadffe105dcf39935b732476e95755f6c7473ada3d0b64df2bc02c557633ae3948a25b45e1cf67e89a3ff6329fb30362e4ac033b9a1d1e453aa2eded", - "git_version": "0.37.0", - "version": "0.18.7", - "find_args": "MODULE GLOBAL", - "patches": [ - "0001-mingw.patch", - "0002-fix-zstd.patch" - ], - "options": [ - "HTTPLIB_REQUIRE_OPENSSL ON", - "HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES ON" - ] - }, - "cpp-jwt": { - "version": "1.4", - "repo": "arun11299/cpp-jwt", - "sha": "7f24eb4c32", - "hash": "d11cbd5ddb3197b4c5ca15679bcd76a49963e7b530b7dd132db91e042925efa20dfb2c24ccfbe7ef82a7012af80deff0f72ee25851312ae80381a462df8534b8", - "find_args": "CONFIG", - "options": [ - "CPP_JWT_USE_VENDORED_NLOHMANN_JSON OFF" - ], - "patches": [ - "0001-fix-missing-decl.patch" - ] - }, - "xbyak": { - "package": "xbyak", - "repo": "herumi/xbyak", - "tag": "v%VERSION%", - "hash": "b6475276b2faaeb315734ea8f4f8bd87ededcee768961b39679bee547e7f3e98884d8b7851e176d861dab30a80a76e6ea302f8c111483607dde969b4797ea95a", - "git_version": "7.35.2" - }, - "oaknut": { - "repo": "eden-emulator/oaknut", - "version": "2.0.1", - "git_version": "2.0.3", - "tag": "v%VERSION%", - "hash": "9697e80a7d5d9bcb3ce51051a9a24962fb90ca79d215f1f03ae6b58da8ba13a63b5dda1b4dde3d26ac6445029696b8ef2883f4e5a777b342bba01283ed293856" - }, - "libadrenotools": { - "repo": "eden-emulator/libadrenotools", - "sha": "8ba23b42d7", - "hash": "f6526620cb752876edc5ed4c0925d57b873a8218ee09ad10859ee476e9333259784f61c1dcc55a2bcba597352d18aff22cd2e4c1925ec2ae94074e09d7da2265", - "patches": [ - "0001-linkerns-cpm.patch" - ] - }, - "oboe": { - "repo": "google/oboe", - "tag": "%VERSION%", - "hash": "ce4011afe7345370d4ead3b891cd69a5ef224b129535783586c0ca75051d303ed446e6c7f10bde8da31fff58d6e307f1732a3ffd03b249f9ef1fd48fd4132715", - "git_version": "1.10.0", - "bundled": true - }, - "unordered-dense": { - "package": "unordered_dense", - "repo": "martinus/unordered_dense", - "sha": "7b55cab841", - "hash": "d2106f6640f6bfb81755e4b8bfb64982e46ec4a507cacdb38f940123212ccf35a20b43c70c6f01d7bfb8c246d1a16f7845d8052971949cea9def1475e3fa02c8", - "find_args": "CONFIG", - "bundled": true, - "patches": [ - "0001-avoid-memset-when-clearing-an-empty-table.patch" - ] - }, - "enet": { - "repo": "lsalzman/enet", - "tag": "v%VERSION%", - "hash": "a0d2fa8c957704dd49e00a726284ac5ca034b50b00d2b20a94fa1bbfbb80841467834bfdc84aa0ed0d6aab894608fd6c86c3b94eee46343f0e6d9c22e391dbf9", - "version": "1.3", - "git_version": "1.3.18", - "find_args": "MODULE" - }, - "spirv-headers": { - "package": "SPIRV-Headers", - "repo": "KhronosGroup/SPIRV-Headers", - "sha": "04f10f650d", - "hash": "cae8cd179c9013068876908fecc1d158168310ad6ac250398a41f0f5206ceff6469e2aaeab9c820bce9d1b08950c725c89c46e94b89a692be9805432cf749396", - "options": [ - "SPIRV_WERROR OFF" - ] - }, - "cubeb": { - "repo": "mozilla/cubeb", - "sha": "fa02160712", - "hash": "8a4bcb2f83ba590f52c66626e895304a73eb61928dbc57777e1822e55378e3568366f17f9da4b80036cc2ef4ea9723c32abf6e7d9bbe00fb03654f0991596ab0", - "find_args": "CONFIG", - "options": [ - "USE_SANITIZERS OFF", - "BUILD_TESTS OFF", - "BUILD_TOOLS OFF", - "BUNDLE_SPEEX ON" - ] - }, - "sdl2": { - "ci": true, - "package": "SDL2", - "name": "SDL2", - "repo": "crueter-ci/SDL2", - "version": "2.32.10-3c28e8ecc0", - "min_version": "2.26.4" - }, - "catch2": { - "package": "Catch2", - "repo": "catchorg/Catch2", - "tag": "v%VERSION%", - "hash": "7eea385d79d88a5690cde131fe7ccda97d5c54ea09d6f515000d7bf07c828809d61c1ac99912c1ee507cf933f61c1c47ecdcc45df7850ffa82714034b0fccf35", - "version": "3.0.1", - "git_version": "3.13.0", - "patches": [ - "0001-solaris-isnan-fix.patch" - ] - }, - "discord-rpc": { - "package": "DiscordRPC", - "repo": "eden-emulator/discord-rpc", - "sha": "0d8b2d6a37", - "hash": "8213c43dcb0f7d479f5861091d111ed12fbdec1e62e6d729d65a4bc181d82f48a35d5fd3cd5c291f2393ac7c9681eabc6b76609755f55376284c8a8d67e148f3", - "find_args": "MODULE" - }, - "simpleini": { - "package": "SimpleIni", - "repo": "brofield/simpleini", - "tag": "v%VERSION%", - "hash": "b937c18a7b6277d77ca7ebfb216af4984810f77af4c32d101b7685369a4bd5eb61406223f82698e167e6311a728d07415ab59639fdf19eff71ad6dc2abfda989", - "find_args": "MODULE", - "git_version": "4.25" - }, - "sdl2_generic": { - "package": "SDL2", - "repo": "libsdl-org/SDL", - "tag": "release-%VERSION%", - "hash": "d5622d6bb7266f7942a7b8ad43e8a22524893bf0c2ea1af91204838d9b78d32768843f6faa248757427b8404b8c6443776d4afa6b672cd8571a4e0c03a829383", - "bundled": true, - "git_version": "2.32.10", - "skip_updates": true - }, - "sdl2_steamdeck": { - "package": "SDL2", - "repo": "libsdl-org/SDL", - "sha": "cc016b0046", - "hash": "b8d9873446cdb922387471df9968e078714683046674ef0d0edddf8e25da65a539a3bae83d635496b970237f90b07b36a69f8d7855d450de59311d6d6e8c3dbc", - "bundled": true, - "skip_updates": "true" - }, - "moltenvk": { - "repo": "V380-Ori/Ryujinx.MoltenVK", - "tag": "v%VERSION%-ryujinx", - "git_version": "1.4.1", - "artifact": "MoltenVK-macOS.tar", - "hash": "5695b36ca5775819a71791557fcb40a4a5ee4495be6b8442e0b666d0c436bec02aae68cc6210183f7a5c986bdbec0e117aecfad5396e496e9c2fd5c89133a347", - "bundled": true - }, - "gamemode": { - "repo": "FeralInteractive/gamemode", - "sha": "ce6fe122f3", - "hash": "e87ec14ed3e826d578ebf095c41580069dda603792ba91efa84f45f4571a28f4d91889675055fd6f042d7dc25b0b9443daf70963ae463e38b11bcba95f4c65a9", - "version": "1.7", - "find_args": "MODULE" - }, - "biscuit": { - "repo": "lioncash/biscuit", - "tag": "v%VERSION%", - "hash": "1229f345b014f7ca544dedb4edb3311e41ba736f9aa9a67f88b5f26f3c983288c6bb6cdedcfb0b8a02c63088a37e6a0d7ba97d9c2a4d721b213916327cffe28a", - "version": "0.9.1", - "git_version": "0.19.0" - }, - "libusb": { - "repo": "libusb/libusb", - "tag": "v%VERSION%", - "hash": "98c5f7940ff06b25c9aa65aa98e23de4c79a4c1067595f4c73cc145af23a1c286639e1ba11185cd91bab702081f307b973f08a4c9746576dc8d01b3620a3aeb5", - "find_args": "MODULE", - "git_version": "1.0.29", - "patches": [ - "0001-netbsd-gettime.patch" - ] - }, - "ffmpeg": { - "repo": "FFmpeg/FFmpeg", - "sha": "c7b5f1537d", - "hash": "ed177621176b3961bdcaa339187d3a7688c1c8b060b79c4bb0257cbc67ad7021ae5d5adca5303b45625abbbe3d9aafdd87ce777b8690ac295290d744c875489a", - "bundled": true - }, - "ffmpeg-ci": { - "ci": true, - "package": "FFmpeg", - "name": "ffmpeg", - "repo": "crueter-ci/FFmpeg", - "version": "8.0.1-c7b5f1537d", - "min_version": "4.1" - }, - "tzdb": { - "package": "nx_tzdb", - "repo": "eden-emu/tzdb_to_nx", - "git_host": "git.eden-emu.dev", - "artifact": "%VERSION%.tar.gz", - "tag": "%VERSION%", - "hash": "cce65a12bf90f4ead43b24a0b95dfad77ac3d9bfbaaf66c55e6701346e7a1e44ca5d2f23f47ee35ee02271eb1082bf1762af207aad9fb236f1c8476812d008ed", - "version": "121125", - "git_version": "230326" - }, - "vulkan-headers": { - "repo": "KhronosGroup/Vulkan-Headers", - "package": "VulkanHeaders", - "version": "1.4.317", - "hash": "d2846ea228415772645eea4b52a9efd33e6a563043dd3de059e798be6391a8f0ca089f455ae420ff22574939ed0f48ed7c6ff3d5a9987d5231dbf3b3f89b484b", - "git_version": "1.4.345", - "tag": "v%VERSION%" - }, - "vulkan-utility-libraries": { - "repo": "KhronosGroup/Vulkan-Utility-Libraries", - "package": "VulkanUtilityLibraries", - "hash": "114f6b237a6dcba923ccc576befb5dea3f1c9b3a30de7dc741f234a831d1c2d52d8a224afb37dd57dffca67ac0df461eaaab6a5ab5e503b393f91c166680c3e1", - "git_version": "1.4.345", - "tag": "v%VERSION%" - }, - "frozen": { - "package": "frozen", - "repo": "serge-sans-paille/frozen", - "sha": "61dce5ae18", - "hash": "b8dfe741c82bc178dfc9749d4ab5a130cee718d9ee7b71d9b547cf5f7f23027ed0152ad250012a8546399fcc1e12187efc68d89d6731256c4d2df7d04eef8d5c" - } -} diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt index 2d85876ad2..f1bf099c10 100644 --- a/externals/ffmpeg/CMakeLists.txt +++ b/externals/ffmpeg/CMakeLists.txt @@ -4,8 +4,6 @@ # SPDX-FileCopyrightText: 2021 yuzu Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later -# TODO(crueter, MaranBr): Externals FFmpeg 8.0 - set(FFmpeg_HWACCEL_LIBRARIES) set(FFmpeg_HWACCEL_FLAGS) set(FFmpeg_HWACCEL_INCLUDE_DIRS) @@ -56,10 +54,15 @@ if (NOT YUZU_USE_BUNDLED_FFMPEG) endif() endif() -if (PLATFORM_PS4 OR PLATFORM_MANAGARM) +if (OPENORBIS OR MANAGARM) # Doesn't support VA-API, don't go thru the embarrassment of trying to enable it list(APPEND FFmpeg_HWACCEL_FLAGS --disable-vaapi) -elseif (UNIX AND NOT DEFINED FFmpeg_IS_CROSS_COMPILING) +elseif (ANDROID) + list(APPEND FFmpeg_HWACCEL_FLAGS + --enable-mediacodec + --enable-jni + ) +elseif (UNIX AND NOT DEFINED FFmpeg_IS_CROSS_COMPILING AND NOT ANDROID) find_package(PkgConfig REQUIRED) pkg_check_modules(LIBVA libva) pkg_check_modules(CUDA cuda) @@ -70,7 +73,7 @@ elseif (UNIX AND NOT DEFINED FFmpeg_IS_CROSS_COMPILING) if(X11_FOUND) if (NOT APPLE) # In Solaris needs explicit linking for ffmpeg which links to /lib/amd64/libX11.so - if(PLATFORM_SUN) + if(SOLARIS) list(APPEND FFmpeg_HWACCEL_LIBRARIES X11 "${CMAKE_SYSROOT}/usr/lib/xorg/amd64/libdrm.so") @@ -153,7 +156,7 @@ elseif (UNIX AND NOT DEFINED FFmpeg_IS_CROSS_COMPILING) endif() endif() -if (PLATFORM_PS4) +if (OPENORBIS) list(APPEND FFmpeg_CROSS_COMPILE_LIBS -lkernel -lSceUserService @@ -167,9 +170,9 @@ if (PLATFORM_PS4) --extra-cxxflags=${CMAKE_SYSROOT}/usr/include --extra-libs="${FFmpeg_CROSS_COMPILE_LIBS}" ) -elseif (PLATFORM_MANAGARM) +elseif (MANAGARM) # Required for proper stuff - list(APPEND FFmpeg_CROSS_COMPILE_FLAGS + list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --disable-pthreads --extra-libs="${FFmpeg_CROSS_COMPILE_LIBS}" ) @@ -294,7 +297,7 @@ else() set(FFmpeg_BUILD_LIBRARIES ${FFmpeg_LIBRARIES}) # BSD make or Solaris make don't support ffmpeg make-j8 - if (PLATFORM_LINUX OR ANDROID OR APPLE OR WIN32 OR PLATFORM_FREEBSD) + if (LINUX OR ANDROID OR APPLE OR WIN32 OR FREEBSD) set(FFmpeg_MAKE_ARGS -j${SYSTEM_THREADS}) else() set(FFmpeg_MAKE_ARGS "") diff --git a/externals/libusb/CMakeLists.txt b/externals/libusb/CMakeLists.txt index 47b54f43cc..f33b5d1646 100644 --- a/externals/libusb/CMakeLists.txt +++ b/externals/libusb/CMakeLists.txt @@ -11,7 +11,7 @@ if (NOT libusb_ADDED) endif() # TODO: *BSD fails to compile--may need different configs/symbols -if (MINGW OR PLATFORM_LINUX OR APPLE) +if (MINGW OR LINUX OR APPLE) set(LIBUSB_FOUND ON CACHE BOOL "libusb is present" FORCE) set(LIBUSB_VERSION "1.0.24" CACHE STRING "libusb version string" FORCE) diff --git a/externals/nx_tzdb/NxTzdbCreateHeader.cmake b/externals/nx_tzdb/NxTzdbCreateHeader.cmake index 95606d8629..229c8aa43d 100644 --- a/externals/nx_tzdb/NxTzdbCreateHeader.cmake +++ b/externals/nx_tzdb/NxTzdbCreateHeader.cmake @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright 2026 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 @@ -16,15 +19,24 @@ if (NOT FILE_LIST) endif() set(DIRECTORY_NAME ${HEADER_NAME}) - set(FILE_DATA "") + +string(APPEND FILE_DATA "[[nodiscard]] static inline std::vector CollectFiles_${DIRECTORY_NAME}() {\n") +string(APPEND FILE_DATA [[ + std::vector vfs_files; + auto const fn = [&](std::string_view name, std::span data) { + vfs_files.push_back(std::make_shared( + std::vector(data.begin(), data.end()), + std::string{name} + )); + }; +]]) foreach(ZONE_FILE ${FILE_LIST}) if (ZONE_FILE STREQUAL "\n") continue() endif() - - string(APPEND FILE_DATA "{\"${ZONE_FILE}\",\n{") - + string(APPEND FILE_DATA " {\n") + string(APPEND FILE_DATA " constexpr uint8_t tzdb_data[] = {\n") file(READ ${ZONE_PATH}/${ZONE_FILE} ZONE_DATA HEX) string(LENGTH "${ZONE_DATA}" ZONE_DATA_LEN) foreach(I RANGE 0 ${ZONE_DATA_LEN} 2) @@ -42,9 +54,12 @@ foreach(ZONE_FILE ${FILE_LIST}) string(APPEND FILE_DATA " ") endif() endforeach() - - string(APPEND FILE_DATA "}},\n") + string(APPEND FILE_DATA " };\n") + string(APPEND FILE_DATA " fn(\"${ZONE_FILE}\", tzdb_data);\n") + string(APPEND FILE_DATA " }\n") endforeach() +string(APPEND FILE_DATA " return vfs_files;\n") +string(APPEND FILE_DATA "}\n") file(READ ${NX_TZDB_SOURCE_DIR}/tzdb_template.h.in NX_TZDB_TEMPLATE_H_IN) file(CONFIGURE OUTPUT ${NX_TZDB_INCLUDE_DIR}/nx_tzdb/${HEADER_NAME}.h CONTENT "${NX_TZDB_TEMPLATE_H_IN}") diff --git a/externals/nx_tzdb/tzdb_template.h.in b/externals/nx_tzdb/tzdb_template.h.in index 289d002ea8..c349a143d5 100644 --- a/externals/nx_tzdb/tzdb_template.h.in +++ b/externals/nx_tzdb/tzdb_template.h.in @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -9,10 +12,10 @@ namespace NxTzdb { +// @DIRECTORY_NAME@ + // clang-format off -const static std::map> @DIRECTORY_NAME@ = -{ -@FILE_DATA@}; +@FILE_DATA@ // clang-format on } // namespace NxTzdb diff --git a/externals/stb/stb_image.h b/externals/stb/stb_image.h index 5e807a0a6e..2a97c1a2a7 100644 --- a/externals/stb/stb_image.h +++ b/externals/stb/stb_image.h @@ -1177,7 +1177,7 @@ static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int re #endif #ifndef STBI_NO_TGA - // test tga last because it's a crappy test! + // test tga last because it's a bad test! if (stbi__tga_test(s)) return stbi__tga_load(s,x,y,comp,req_comp, ri); #endif @@ -7662,7 +7662,7 @@ static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) if (stbi__hdr_info(s, x, y, comp)) return 1; #endif - // test tga last because it's a crappy test! + // test tga last because it's a bad test! #ifndef STBI_NO_TGA if (stbi__tga_info(s, x, y, comp)) return 1; diff --git a/externals/tz/tz/tz.h b/externals/tz/tz/tz.h index e70092a987..821d60708a 100644 --- a/externals/tz/tz/tz.h +++ b/externals/tz/tz/tz.h @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace Tz { diff --git a/hooks/pre-commit b/hooks/pre-commit index 484eef1762..b543ff36ab 100755 --- a/hooks/pre-commit +++ b/hooks/pre-commit @@ -1,5 +1,7 @@ -#!/bin/sh +#!/bin/sh -e +# SPDX-FileCopyrightText: Copyright 2026 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 @@ -10,20 +12,19 @@ paths_to_check="src/ CMakeLists.txt" # If there are whitespace errors, print the offending file names and fail. if ! git diff --cached --check -- $paths_to_check ; then - cat<:-fno-rtti>) endif() + if (ENABLE_WERROR) + add_compile_options( + $<$:-Werror=all> + $<$:-Werror=extra> + $<$:-Werror=missing-declarations> + $<$:-Werror=shadow> + $<$:-Werror=unused>) + else() + add_compile_options( + $<$:-Wall> + $<$:-Wextra> + $<$:-Wmissing-declarations> + $<$:-Wshadow> + $<$:-Wunused> + # Some compilers could particularly misbehave :) + $<$:-Wno-error-all>) + endif() add_compile_options( - $<$:-Werror=all> - $<$:-Werror=extra> - $<$:-Werror=missing-declarations> - $<$:-Werror=shadow> - $<$:-Werror=unused> $<$:-Wno-attributes> $<$:-Wno-invalid-offsetof> $<$:-Wno-unused-parameter> @@ -148,9 +181,9 @@ else() if (CXX_CLANG OR CXX_ICC OR CXX_APPLE) # Clang, AppleClang, or Intel C++ if (NOT MSVC) add_compile_options( - $<$:-Werror=shadow-uncaptured-local> - $<$:-Werror=implicit-fallthrough> - $<$:-Werror=type-limits>) + $<$:-Wshadow-uncaptured-local> + $<$:-Wimplicit-fallthrough> + $<$:-Wtype-limits>) endif() add_compile_options( $<$:-Wno-braced-scalar-init> @@ -159,7 +192,10 @@ else() endif() if (ARCHITECTURE_x86_64) - add_compile_options(-mcx16) + add_compile_options($<$:-mcx16>) + if (LINUX OR FREEBSD) + add_compile_options($<$:-mtls-dialect=gnu2>) + endif() endif() if (APPLE AND CXX_CLANG) diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index ccc5b3cabe..69dd6a8e4f 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -188,6 +188,7 @@ android { create("mainline") { dimension = "version" isDefault = true + minSdk = 33 manifestPlaceholders += mapOf("appNameBase" to "Eden") resValue("string", "app_name_suffixed", "Eden") @@ -199,6 +200,7 @@ android { create("genshinSpoof") { dimension = "version" + minSdk = 35 manifestPlaceholders += mapOf("appNameBase" to "Eden Optimized") resValue("string", "app_name_suffixed", "Eden Optimized") applicationId = "com.miHoYo.Yuanshen" @@ -216,6 +218,7 @@ android { create("legacy") { dimension = "version" + minSdk = 29 manifestPlaceholders += mapOf("appNameBase" to "Eden Legacy") resValue("string", "app_name_suffixed", "Eden Legacy") applicationId = "dev.legacy.eden_emulator" @@ -256,7 +259,7 @@ android { externalNativeBuild { cmake { - version = "3.22.1" + version = "3.31.5" path = file("${edenDir}/CMakeLists.txt") } } @@ -269,6 +272,13 @@ android { resValue("string", "app_name_suffixed", "$currentName$suffix") resValue("string", "app_name", "Eden$suffix") } + + sourceSets { + named("main") { + java.srcDir("${edenDir}/externals/generated/sdl/java") + kotlin.srcDir("${edenDir}/externals/generated/sdl/java") + } + } } idea { diff --git a/src/android/app/proguard-rules.pro b/src/android/app/proguard-rules.pro index 691e08fd03..91f63bbb84 100644 --- a/src/android/app/proguard-rules.pro +++ b/src/android/app/proguard-rules.pro @@ -9,6 +9,9 @@ -keep class org.ini4j.spi.IniBuilder -keep class org.ini4j.spi.IniFormatter +-keep class org.libsdl.app.** { *; } +-keepclassmembers class org.libsdl.app.** { *; } + # Suppress warnings for R8 -dontwarn org.bouncycastle.jsse.BCSSLParameters -dontwarn org.bouncycastle.jsse.BCSSLSocket diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index a8d6eaf6e1..77222884c6 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -16,6 +16,7 @@ import android.widget.TextView import androidx.annotation.Keep import androidx.core.net.toUri import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.libsdl.app.SDL import java.lang.ref.WeakReference import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.fragments.CoreErrorDialogFragment @@ -52,6 +53,8 @@ object NativeLibrary { init { try { System.loadLibrary("yuzu-android") + SDL.setupJNI() + initJvm() } catch (ex: UnsatisfiedLinkError) { error("[NativeLibrary] $ex") } @@ -218,7 +221,9 @@ object NativeLibrary { external fun logSettings() - external fun getDebugKnobAt(index: Int): Boolean + external fun refreshThreadPolicies() + + external fun GetDebugKnobAt(index: Int): Boolean /** * Set the current speed limit to the configured turbo speed. @@ -365,6 +370,7 @@ object NativeLibrary { NetPlayManager.clearChat() } + external fun initJvm() external fun initMultiplayer() @Keep @@ -537,6 +543,40 @@ object NativeLibrary { */ external fun installKeys(path: String, ext: String): Int + /** + * @return Whether this GPU can run the Lossless Scaling frame generation shaders, + * which are built against the Vulkan memory model. + */ + external fun supportsFrameGeneration(): Boolean + + /** + * @return Path the user-supplied Lossless Scaling library is expected at. + */ + external fun getLosslessDllPath(): String + + /** + * Parses the installed Lossless Scaling library and checks that every shader the + * frame generation chain needs is present. + * + * @return The result code, matching the losslessDllResults array. + */ + external fun validateLosslessDll(): Int + + /** + * Translates the frame generation shaders out of the installed Lossless Scaling library + * and writes them to the SPIR-V cache. Slow, so call it off the main thread. + * + * @return The result code, matching the losslessDllResults array. + */ + external fun prepareLosslessDll(): Int + + /** + * Deletes the installed Lossless Scaling library. + * + * @return Whether the library is gone after the call. + */ + external fun removeLosslessDll(): Boolean + /** * Checks the PatchManager for any addons that are available * diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index eab0fac705..aaabcc95be 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt @@ -15,7 +15,6 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter -import android.content.pm.PackageManager import android.content.res.Configuration import android.graphics.Rect import android.graphics.drawable.Icon @@ -101,7 +100,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager private var romSwapGeneration = 0 private var hasEmulationSession = processHasEmulationSession private val romSwapStopTimeoutRunnable = Runnable { onRomSwapStopTimeout() } - private val pictureInPictureFailureActions: MutableSet = mutableSetOf() private fun onRomSwapStopTimeout() { if (!isWaitingForRomSwapStop) { @@ -127,6 +125,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager super.onCreate(savedInstanceState) + NativeConfig.reloadGlobalConfig() + InputHandler.updateControllerData() val players = NativeConfig.getInputSettings(true) var hasConfiguredControllers = false @@ -268,18 +268,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager } override fun onUserLeaveHint() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S || - !isPictureInPictureSupported() || - !BooleanSetting.PICTURE_IN_PICTURE.getBoolean() || - isInPictureInPictureMode - ) { - return - } - - val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() - .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() - runPictureInPictureAction("enter picture-in-picture mode") { - enterPictureInPictureMode(pictureInPictureParamsBuilder.build()) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) { + val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() + .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() + enterPictureInPictureMode(pictureInPictureParamsBuilder.build()) + } } } @@ -565,6 +559,15 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager private fun enableFullscreenImmersive() { WindowCompat.setDecorFitsSystemWindows(window, false) + @Suppress("DEPRECATION") + window.decorView.systemUiVisibility = + View.SYSTEM_UI_FLAG_FULLSCREEN or + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + WindowInsetsControllerCompat(window, window.decorView).let { controller -> controller.hide(WindowInsetsCompat.Type.systemBars()) controller.systemBarsBehavior = @@ -659,29 +662,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager return this.apply { setActions(pictureInPictureActions) } } - private fun isPictureInPictureSupported() = - Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && - packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) - - private fun runPictureInPictureAction(actionName: String, action: () -> Unit) { - try { - action() - } catch (e: IllegalStateException) { - if (pictureInPictureFailureActions.add(actionName)) { - Log.warning("[PiP] Failed to $actionName: ${e.message}") - } - } catch (e: UnsupportedOperationException) { - if (pictureInPictureFailureActions.add(actionName)) { - Log.warning("[PiP] Failed to $actionName: ${e.message}") - } - } - } - fun buildPictureInPictureParams() { - if (!isPictureInPictureSupported()) { - return - } - val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { @@ -691,9 +672,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && isEmulationActive ) } - runPictureInPictureAction("set picture-in-picture params") { - setPictureInPictureParams(pictureInPictureParamsBuilder.build()) - } + setPictureInPictureParams(pictureInPictureParamsBuilder.build()) } fun displayMultiplayerDialog() { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/QuickSettings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/QuickSettings.kt index 7ba7772bd7..c2ef683cfd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/QuickSettings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/dialogs/QuickSettings.kt @@ -3,10 +3,13 @@ package org.yuzu.yuzu_emu.dialogs +import android.content.res.ColorStateList +import android.graphics.Rect import android.view.LayoutInflater import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import android.widget.LinearLayout import android.widget.RadioGroup import android.widget.TextView import androidx.drawerlayout.widget.DrawerLayout @@ -18,11 +21,28 @@ import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.fragments.EmulationFragment import org.yuzu.yuzu_emu.utils.NativeConfig +import org.yuzu.yuzu_emu.utils.NativePostProcessing import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting class QuickSettings(val emulationFragment: EmulationFragment) { + private val expandedShaders = mutableSetOf() + + private fun forgetShaderSlot(index: Int) { + val shifted = mutableSetOf() + for (slot in expandedShaders) { + if (slot < index) { + shifted.add(slot) + } + if (slot > index) { + shifted.add(slot - 1) + } + } + expandedShaders.clear() + expandedShaders.addAll(shifted) + } + private fun saveSettings() { if (emulationFragment.shouldUseCustom) { NativeConfig.savePerGameConfig() @@ -232,6 +252,589 @@ class QuickSettings(val emulationFragment: EmulationFragment) { container.addView(itemView) } + fun addChoice( + title: String, + container: ViewGroup, + choices: List, + selectedIndex: Int, + onSelected: (Int) -> Unit + ) { + val inflater = LayoutInflater.from(emulationFragment.requireContext()) + val itemView = inflater.inflate(R.layout.item_quick_settings_menu, container, false) + val headerView = itemView.findViewById(R.id.setting_header) + val titleView = itemView.findViewById(R.id.setting_title) + val valueView = itemView.findViewById(R.id.setting_value) + val expandIcon = itemView.findViewById(R.id.expand_icon) + val radioGroup = itemView.findViewById(R.id.radio_group) + + titleView.text = title + + var current = "" + if (selectedIndex in choices.indices) { + current = choices[selectedIndex] + } + valueView.text = current + headerView.visibility = View.VISIBLE + + var isExpanded = false + choices.forEachIndexed { index, name -> + val radioButton = com.google.android.material.radiobutton.MaterialRadioButton( + emulationFragment.requireContext() + ) + radioButton.text = name + radioButton.id = View.generateViewId() + radioButton.isChecked = index == selectedIndex + radioButton.setPadding(16, 8, 16, 8) + + radioButton.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + valueView.text = name + onSelected(index) + } + } + radioGroup.addView(radioButton) + } + + headerView.setOnClickListener { + isExpanded = !isExpanded + if (isExpanded) { + radioGroup.visibility = View.VISIBLE + expandIcon.animate().rotation(180f).setDuration(200).start() + } else { + radioGroup.visibility = View.GONE + expandIcon.animate().rotation(0f).setDuration(200).start() + } + } + + container.addView(itemView) + } + + fun addStepSlider( + title: String, + container: ViewGroup, + steps: Int, + selectedStep: Int, + describe: (Int) -> String, + onCommitted: (Int) -> Unit, + onChanged: (Int) -> Unit + ) { + val inflater = LayoutInflater.from(emulationFragment.requireContext()) + val itemView = inflater.inflate(R.layout.item_quick_settings_menu, container, false) + + val sliderContainer = itemView.findViewById(R.id.slider_container) + val titleView = itemView.findViewById(R.id.slider_title) + val valueDisplay = itemView.findViewById(R.id.slider_value_display) + val slider = itemView.findViewById( + R.id.setting_slider + ) + + titleView.text = title + sliderContainer.visibility = View.VISIBLE + + slider.valueFrom = 0f + slider.valueTo = steps.toFloat() + slider.stepSize = 1f + slider.value = selectedStep.toFloat().coerceIn(0f, steps.toFloat()) + valueDisplay.text = describe(slider.value.toInt()) + + slider.addOnChangeListener { _, value, fromUser -> + if (fromUser) { + val step = value.toInt() + onChanged(step) + valueDisplay.text = describe(step) + } + } + + var pressedValue = slider.value + + slider.setOnTouchListener { _, event -> + val drawer = emulationFragment.view?.findViewById(R.id.drawer_layout) + when (event.action) { + MotionEvent.ACTION_DOWN -> { + drawer?.requestDisallowInterceptTouchEvent(true) + pressedValue = slider.value + } + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + drawer?.requestDisallowInterceptTouchEvent(false) + if (slider.value != pressedValue) { + onCommitted(slider.value.toInt()) + } + } + } + false + } + + container.addView(itemView) + } + + fun addShaderCard( + index: Int, + title: String, + summary: String, + container: ViewGroup, + onRemove: () -> Unit + ): ViewGroup { + val inflater = LayoutInflater.from(emulationFragment.requireContext()) + val itemView = inflater.inflate(R.layout.item_quick_settings_shader, container, false) + + val headerView = itemView.findViewById(R.id.shader_header) + val titleView = itemView.findViewById(R.id.shader_title) + val summaryView = itemView.findViewById(R.id.shader_summary) + val removeView = itemView.findViewById(R.id.shader_remove) + val expandIcon = itemView.findViewById(R.id.shader_expand) + val bodyView = itemView.findViewById(R.id.shader_body) + + titleView.text = title + if (summary.isEmpty()) { + summaryView.visibility = View.GONE + } else { + summaryView.text = summary + } + + var isExpanded = expandedShaders.contains(index) + if (isExpanded) { + bodyView.visibility = View.VISIBLE + expandIcon.rotation = 180f + } + + headerView.setOnClickListener { + isExpanded = !isExpanded + if (isExpanded) { + expandedShaders.add(index) + bodyView.visibility = View.VISIBLE + expandIcon.animate().rotation(180f).setDuration(200).start() + } else { + expandedShaders.remove(index) + bodyView.visibility = View.GONE + expandIcon.animate().rotation(0f).setDuration(200).start() + } + } + + removeView.setOnClickListener { + onRemove() + } + + container.addView(itemView) + return bodyView + } + + fun addEffectPicker( + container: ViewGroup, + choices: List, + hasEffects: Boolean, + onRemoveAll: () -> Unit, + onPicked: (Int) -> Unit + ) { + val context = emulationFragment.requireContext() + val inflater = LayoutInflater.from(context) + val itemView = inflater.inflate(R.layout.item_quick_settings_add, container, false) + + val button = itemView.findViewById( + R.id.add_button + ) + val removeButton = + itemView.findViewById( + R.id.remove_all_button + ) + val choiceGroup = itemView.findViewById(R.id.add_choices) + + choices.forEachIndexed { index, name -> + val radioButton = com.google.android.material.radiobutton.MaterialRadioButton(context) + radioButton.text = name + radioButton.id = View.generateViewId() + radioButton.setPadding(16, 8, 16, 8) + radioButton.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + onPicked(index) + } + } + choiceGroup.addView(radioButton) + } + + var closedLabel = R.string.post_processing_add + if (hasEffects) { + closedLabel = R.string.post_processing_open_list + removeButton.visibility = View.VISIBLE + } + button.setText(closedLabel) + + val removeBackground = MaterialColors.getColor( + removeButton, + com.google.android.material.R.attr.colorErrorContainer + ) + val removeForeground = MaterialColors.getColor( + removeButton, + com.google.android.material.R.attr.colorOnErrorContainer + ) + removeButton.backgroundTintList = ColorStateList.valueOf(removeBackground) + removeButton.setTextColor(removeForeground) + removeButton.iconTint = ColorStateList.valueOf(removeForeground) + removeButton.setOnClickListener { + onRemoveAll() + } + + val slide = context.resources.displayMetrics.density * 24.0f + + var isOpen = false + button.setOnClickListener { + isOpen = !isOpen + if (isOpen) { + choiceGroup.alpha = 0.0f + choiceGroup.translationY = slide + choiceGroup.visibility = View.VISIBLE + choiceGroup.animate() + .alpha(1.0f) + .translationY(0.0f) + .setDuration(220) + .withEndAction { + choiceGroup.requestRectangleOnScreen( + Rect(0, 0, choiceGroup.width, choiceGroup.height), + false + ) + } + .start() + + button.setText(R.string.post_processing_close_list) + button.setIconResource(R.drawable.ic_clear) + } else { + choiceGroup.animate() + .alpha(0.0f) + .translationY(slide) + .setDuration(160) + .withEndAction { + choiceGroup.visibility = View.GONE + } + .start() + + button.setText(closedLabel) + button.setIconResource(R.drawable.ic_add) + } + } + + container.addView(itemView) + } + + fun addPresetBand(container: ViewGroup, name: String, summary: String) { + val inflater = LayoutInflater.from(emulationFragment.requireContext()) + val itemView = inflater.inflate(R.layout.item_quick_settings_preset, container, false) + + val titleView = itemView.findViewById(R.id.preset_title) + val summaryView = itemView.findViewById(R.id.preset_summary) + val switchView = itemView.findViewById(R.id.preset_switch) + + titleView.text = name + if (summary.isEmpty()) { + summaryView.visibility = View.GONE + } else { + summaryView.text = summary + } + + switchView.isChecked = NativePostProcessing.isEnabled() + switchView.setOnCheckedChangeListener { _, checked -> + emulationFragment.editPostProcessing { + NativePostProcessing.setEnabled(checked) + } + } + + container.addView(itemView) + } + + fun addPostProcessing(container: ViewGroup, onStructureChanged: () -> Unit) { + val usable = NativePostProcessing.catalog().filter { it.valid } + if (usable.isEmpty()) { + return + } + + val preset = NativePostProcessing.getActivePreset() + if (preset.isNotEmpty()) { + addDivider(container) + + var summary = "" + val described = NativePostProcessing.presets().firstOrNull { it.name == preset } + if (described != null) { + summary = described.description + } + if (summary.isEmpty()) { + summary = + YuzuApplication.appContext.getString(R.string.post_processing_preset_locked) + } + if (NativePostProcessing.isPresetModified()) { + summary = summary + "\n" + + YuzuApplication.appContext.getString(R.string.post_processing_preset_modified) + } + + addPresetBand(container, preset, summary) + return + } + + val labels = mutableListOf() + val files = mutableListOf() + val techniques = mutableListOf() + + for (effect in usable) { + for (technique in effect.techniques) { + var label = effect.label + if (effect.techniques.size > 1) { + label = effect.label + " \u00b7 " + technique + } + labels.add(label) + files.add(effect.file) + techniques.add(technique) + } + } + + addDivider(container) + + val chain = NativePostProcessing.chain() + chain.forEachIndexed { index, entry -> + val effect = usable.firstOrNull { it.file == entry.file } + + var title = entry.file + var summary = "" + if (effect != null) { + title = effect.label + if (effect.techniques.size > 1) { + title = effect.label + " \u00b7 " + entry.technique + } + summary = effect.description + } + + val body = addShaderCard(index, title, summary, container) { + emulationFragment.editPostProcessing { + NativePostProcessing.remove(index) + } + forgetShaderSlot(index) + onStructureChanged() + } + + if (effect != null) { + for (uniform in effect.uniforms) { + addUniformSliders(body, index, uniform) + } + } + } + + addEffectPicker( + container, + labels, + chain.isNotEmpty(), + { + emulationFragment.editPostProcessing { + NativePostProcessing.clearChain() + } + expandedShaders.clear() + onStructureChanged() + } + ) { picked -> + emulationFragment.editPostProcessing { + NativePostProcessing.append(files[picked], techniques[picked]) + } + onStructureChanged() + } + } + + private fun addUniformSliders( + container: ViewGroup, + index: Int, + uniform: NativePostProcessing.Uniform + ) { + if (uniform.uiType == NativePostProcessing.UI_HIDDEN) { + return + } + + for (component in 0 until uniform.components) { + var title = uniform.label + if (uniform.components > 1) { + title = uniform.label + " [" + component + "]" + } + + var value = uniform.defaultAt(component) + if (NativePostProcessing.hasValue(index, uniform.name)) { + value = NativePostProcessing.getValue(index, uniform.name, component) + } + + val steps = uniform.steps + val step = Math.round((value - uniform.min) / uniform.step) + + addStepSlider( + title, + container, + steps, + step, + { position -> uniform.describe(position) }, + { emulationFragment.persistPostProcessing() } + ) { position -> + NativePostProcessing.setValue( + index, + uniform.name, + component, + uniform.min + position * uniform.step + ) + } + } + } + + private fun addFrameGenCell(inflater: LayoutInflater, row: ViewGroup): TextView { + val cell = inflater.inflate(R.layout.item_quick_settings_frame_gen_cell, row, false) + as TextView + row.addView(cell) + return cell + } + + fun addFrameGen(container: ViewGroup) { + val inflater = LayoutInflater.from(emulationFragment.requireContext()) + val itemView = inflater.inflate(R.layout.item_quick_settings_frame_gen, container, false) + + val multiplierRow = itemView.findViewById(R.id.frame_gen_multipliers) + val targetRow = itemView.findViewById(R.id.frame_gen_targets) + val targetSection = itemView.findViewById(R.id.frame_gen_target_section) + val flowRow = itemView.findViewById(R.id.frame_gen_flow) + val flowSection = itemView.findViewById(R.id.frame_gen_flow_section) + + val multiplierNames = + emulationFragment.resources.getStringArray(R.array.frameGenMultiplierNames) + val multiplierValues = + emulationFragment.resources.getIntArray(R.array.frameGenMultiplierValues) + val targetValues = + emulationFragment.resources.getIntArray(R.array.frameGenTargetRateValues) + val flowValues = + emulationFragment.resources.getIntArray(R.array.frameGenFlowScaleValues) + + val columns = maxOf( + multiplierValues.size + 1, + targetValues.size, + flowValues.size + 1 + ).toFloat() + multiplierRow.weightSum = columns + targetRow.weightSum = columns + flowRow.weightSum = columns + + val powerCell = addFrameGenCell(inflater, multiplierRow) + + val multiplierCells = mutableListOf() + for (name in multiplierNames) { + val cell = addFrameGenCell(inflater, multiplierRow) + cell.text = name + multiplierCells.add(cell) + } + + val targetCells = mutableListOf() + for (value in targetValues) { + val cell = addFrameGenCell(inflater, targetRow) + if (value == 0) { + cell.setText(R.string.frame_gen_fixed) + } else { + cell.text = value.toString() + } + targetCells.add(cell) + } + + val autoCell = addFrameGenCell(inflater, flowRow) + autoCell.setText(R.string.frame_gen_flow_auto) + + val flowCells = mutableListOf() + for (value in flowValues) { + val cell = addFrameGenCell(inflater, flowRow) + cell.text = "$value%" + flowCells.add(cell) + } + + val inactiveAlpha = 0.38f + + fun refresh() { + val enabled = BooleanSetting.RENDERER_FRAME_GEN.getBoolean(needsGlobal = false) + val multiplier = IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.getInt(needsGlobal = false) + val target = IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.getInt(needsGlobal = false) + val fixed = target == 0 + val flowAuto = + BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.getBoolean(needsGlobal = false) + val flowScale = IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.getInt(needsGlobal = false) + + var powerLabel = R.string.frame_gen_off + var rowAlpha = inactiveAlpha + if (enabled) { + powerLabel = R.string.frame_gen_on + rowAlpha = 1.0f + } + + var multiplierAlpha = inactiveAlpha + if (enabled && fixed) { + multiplierAlpha = 1.0f + } + + powerCell.setText(powerLabel) + powerCell.isSelected = enabled + + multiplierCells.forEachIndexed { index, cell -> + cell.isEnabled = enabled + cell.isSelected = enabled && fixed && multiplierValues[index] == multiplier + cell.alpha = multiplierAlpha + } + + targetSection.alpha = rowAlpha + targetCells.forEachIndexed { index, cell -> + cell.isEnabled = enabled + cell.isSelected = enabled && targetValues[index] == target + } + + flowSection.alpha = rowAlpha + autoCell.isEnabled = enabled + autoCell.isSelected = enabled && flowAuto + + flowCells.forEachIndexed { index, cell -> + cell.isEnabled = enabled + cell.isSelected = enabled && !flowAuto && flowValues[index] == flowScale + } + } + + powerCell.setOnClickListener { + if (BooleanSetting.RENDERER_FRAME_GEN.getBoolean(needsGlobal = false)) { + BooleanSetting.RENDERER_FRAME_GEN.setBoolean(false) + } else { + IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.setInt(multiplierValues.first()) + IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.setInt(0) + BooleanSetting.RENDERER_FRAME_GEN.setBoolean(true) + } + saveSettings() + refresh() + } + + multiplierCells.forEachIndexed { index, cell -> + cell.setOnClickListener { + IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.setInt(multiplierValues[index]) + IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.setInt(0) + saveSettings() + refresh() + } + } + + targetCells.forEachIndexed { index, cell -> + cell.setOnClickListener { + IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.setInt(targetValues[index]) + saveSettings() + refresh() + } + } + + autoCell.setOnClickListener { + BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.setBoolean(true) + saveSettings() + refresh() + } + + flowCells.forEachIndexed { index, cell -> + cell.setOnClickListener { + IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.setInt(flowValues[index]) + BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.setBoolean(false) + saveSettings() + refresh() + } + } + + refresh() + container.addView(itemView) + } + fun addDivider(container: ViewGroup) { val inflater = LayoutInflater.from(emulationFragment.requireContext()) val dividerView = inflater.inflate(R.layout.item_quick_settings_divider, container, false) 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 eccf0549e7..6c33fc7812 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 @@ -25,7 +25,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache"), RENDERER_FORCE_MAX_CLOCK("force_max_clock"), RENDERER_ASYNCHRONOUS_GPU_EMULATION("use_asynchronous_gpu_emulation"), - RENDERER_ASYNC_PRESENTATION("async_presentation"), RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"), RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"), ENABLE_BUFFER_HISTORY("enable_buffer_history"), @@ -36,6 +35,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"), RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"), RENDERER_SAMPLE_SHADING("sample_shading"), + RENDERER_FRAME_GEN("frame_gen"), + RENDERER_FRAME_GEN_FLOW_SCALE_AUTO("frame_gen_flow_scale_auto"), GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"), PICTURE_IN_PICTURE("picture_in_picture"), USE_CUSTOM_RTC("custom_rtc_enabled"), @@ -78,19 +79,21 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { SHOW_SHADERS_BUILDING("show_shaders_building"), DEBUG_FLUSH_BY_LINE("flush_line"), + EXTENDED_LOGGING("extended_logging"), DONT_SHOW_DRIVER_SHADER_WARNING("dont_show_driver_shader_warning"), ENABLE_OVERLAY("enable_overlay"), // GPU Logging - GPU_LOGGING_ENABLED("gpu_logging_enabled"), GPU_LOG_VULKAN_CALLS("gpu_log_vulkan_calls"), GPU_LOG_SHADER_DUMPS("gpu_log_shader_dumps"), + DUMP_GUEST_SHADERS("dump_guest_shaders"), + DUMP_MACROS("dump_macros"), GPU_LOG_MEMORY_TRACKING("gpu_log_memory_tracking"), GPU_LOG_DRIVER_DEBUG("gpu_log_driver_debug"), ENABLE_QUICK_SETTINGS("enable_quick_settings"); -// external fun isFrameSkippingEnabled(): Boolean + //external fun isFrameSkippingEnabled(): Boolean external fun isFrameInterpolationEnabled(): Boolean override fun getBoolean(needsGlobal: Boolean): Boolean = diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FxPresetNameSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FxPresetNameSetting.kt new file mode 100644 index 0000000000..496cb5390a --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FxPresetNameSetting.kt @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.features.settings.model + +class FxPresetNameSetting(private val onNamed: (String) -> Unit) : AbstractStringSetting { + override val key: String + get() = "fx_preset_name" + + override val defaultValue: Any + get() = "" + + override val isRuntimeModifiable: Boolean + get() = true + + override val pairedSettingKey: String + get() = "" + + override val isSwitchable: Boolean + get() = false + + override val isSaveable: Boolean + get() = true + + override var global: Boolean + get() = true + set(_) {} + + override fun getString(needsGlobal: Boolean): String = "" + + override fun setString(value: String) = onNamed(value) + + override fun getValueAsString(needsGlobal: Boolean): String = "" + + override fun reset() {} +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FxUniformSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FxUniformSetting.kt new file mode 100644 index 0000000000..23c7837e12 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FxUniformSetting.kt @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.features.settings.model + +import org.yuzu.yuzu_emu.utils.NativePostProcessing +import kotlin.math.roundToInt + +abstract class FxUniformSetting( + protected val index: Int, + protected val uniform: NativePostProcessing.Uniform, + protected val component: Int +) : AbstractSetting { + override val key: String + get() = "fx_${index}_${uniform.name}_$component" + + override val isRuntimeModifiable: Boolean + get() = true + + override val pairedSettingKey: String + get() = "" + + override val isSwitchable: Boolean + get() = false + + override val isSaveable: Boolean + get() = true + + override var global: Boolean + get() = true + set(_) {} + + protected fun currentValue(): Float { + if (NativePostProcessing.hasValue(index, uniform.name)) { + return NativePostProcessing.getValue(index, uniform.name, component) + } + return uniform.defaultAt(component) + } + + protected fun commit(value: Float) { + NativePostProcessing.setValue(index, uniform.name, component, value) + NativePostProcessing.store() + } + + override fun reset() = commit(uniform.defaultAt(component)) +} + +class FxUniformSliderSetting( + index: Int, + uniform: NativePostProcessing.Uniform, + component: Int +) : FxUniformSetting(index, uniform, component), AbstractIntSetting { + override val defaultValue: Any + get() = ((uniform.defaultAt(component) - uniform.min) / uniform.step).roundToInt() + + override fun getInt(needsGlobal: Boolean): Int = + ((currentValue() - uniform.min) / uniform.step).roundToInt() + + override fun setInt(value: Int) = commit(uniform.min + value * uniform.step) + + override fun getValueAsString(needsGlobal: Boolean): String { + if (uniform.kind == NativePostProcessing.KIND_FLOAT) { + return String.format("%.3f", currentValue()) + } + return currentValue().roundToInt().toString() + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt index d1702b8140..ea31692fbf 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt @@ -19,6 +19,10 @@ enum class IntSetting(override val key: String) : AbstractIntSetting { RENDERER_ASTC_DECODE_METHOD("accelerate_astc"), RENDERER_ACCURACY("gpu_accuracy"), RENDERER_RESOLUTION("resolution_setup"), + RENDERER_FRAME_GEN_MULTIPLIER("frame_gen_multiplier"), + RENDERER_FRAME_GEN_TARGET_RATE("frame_gen_target_rate"), + RENDERER_FRAME_GEN_QUEUE_TARGET("frame_gen_queue_target"), + RENDERER_FRAME_GEN_FLOW_SCALE("frame_gen_flow_scale"), RENDERER_VSYNC("use_vsync"), RENDERER_SCALING_FILTER("scaling_filter"), RENDERER_ANTI_ALIASING("anti_aliasing"), @@ -27,6 +31,7 @@ enum class IntSetting(override val key: String) : AbstractIntSetting { RENDERER_DYNA_STATE("dyna_state"), DMA_ACCURACY("dma_accuracy"), + GPU_FENCE_BEHAVIOR("gpu_fence_behavior"), FRAME_PACING_MODE("frame_pacing_mode"), AUDIO_OUTPUT_ENGINE("output_engine"), MAX_ANISOTROPY("max_anisotropy"), @@ -67,7 +72,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting { MY_PAGE_APPLET("my_page_applet_mode"), INPUT_OVERLAY_AUTO_HIDE("input_overlay_auto_hide"), OVERLAY_GRID_SIZE("overlay_grid_size"), - GPU_LOG_RING_BUFFER_SIZE("gpu_log_ring_buffer_size") + GPU_LOG_RING_BUFFER_SIZE("gpu_log_ring_buffer_size"), + ANDROID_PIPELINE_WORKERS("pipeline_worker_count") ; override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal) 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 b438812d58..23c5ceb88b 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 @@ -11,6 +11,8 @@ object Settings { SECTION_ROOT(R.string.advanced_settings), SECTION_SYSTEM(R.string.preferences_system), SECTION_RENDERER(R.string.preferences_graphics), + SECTION_FRAME_GEN(R.string.frame_gen), + SECTION_POST_PROCESSING(R.string.post_processing), SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options), SECTION_INPUT_OVERLAY(R.string.input_overlay_options), SECTION_SOC_OVERLAY(R.string.soc_overlay_options), @@ -34,8 +36,8 @@ object Settings { fun getPlayerString(player: Int): String = YuzuApplication.appContext.getString(R.string.preferences_player, player) - fun getDebugKnobAt(index: Int): Boolean { - return org.yuzu.yuzu_emu.NativeLibrary.getDebugKnobAt(index) + fun GetDebugKnobAt(index: Int): Boolean { + return org.yuzu.yuzu_emu.NativeLibrary.GetDebugKnobAt(index) } const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt index 88c3615bdb..5f1dbe6372 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt @@ -11,8 +11,7 @@ import org.yuzu.yuzu_emu.utils.NativeConfig enum class ShortSetting(override val key: String) : AbstractShortSetting { RENDERER_SPEED_LIMIT("speed_limit"), RENDERER_TURBO_SPEED_LIMIT("turbo_speed_limit"), - RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit"), - DEBUG_KNOBS("debug_knobs") + RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit") ; override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal) @@ -29,4 +28,4 @@ enum class ShortSetting(override val key: String) : AbstractShortSetting { override fun getValueAsString(needsGlobal: Boolean): String = getShort(needsGlobal).toString() override fun reset() = NativeConfig.setShort(key, defaultValue) -} \ No newline at end of file +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt index 55ddd5950c..e3ccf9e998 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2023 yuzu Emulator Project @@ -11,6 +11,9 @@ import org.yuzu.yuzu_emu.utils.NativeConfig enum class StringSetting(override val key: String) : AbstractStringSetting { DRIVER_PATH("driver_path"), DEVICE_NAME("device_name"), + LOG_FILTER("log_filter"), + PROGRAM_ARGS("program_args"), + POST_SHADER_CHAIN("post_shader_chain"), WEB_TOKEN("eden_token"), WEB_USERNAME("eden_username") diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/UShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/UShortSetting.kt new file mode 100644 index 0000000000..b55b4dd019 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/UShortSetting.kt @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.features.settings.model + +import org.yuzu.yuzu_emu.utils.NativeConfig + +enum class UShortSetting(override val key: String) : AbstractIntSetting { + DEBUG_KNOBS("debug_knobs") + ; + + override fun getInt(needsGlobal: Boolean): Int = + NativeConfig.getUnsignedShort(key, needsGlobal) + + override fun setInt(value: Int) { + if (NativeConfig.isPerGameConfigLoaded()) { + global = false + } + NativeConfig.setUnsignedShort(key, value) + } + + override val defaultValue: Int by lazy { NativeConfig.getDefaultToString(key).toInt() } + + override fun getValueAsString(needsGlobal: Boolean): String = getInt(needsGlobal).toString() + + override fun reset() = NativeConfig.setUnsignedShort(key, defaultValue) +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/FxButtonSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/FxButtonSetting.kt new file mode 100644 index 0000000000..3fc999e267 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/FxButtonSetting.kt @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.features.settings.model.view + +import androidx.annotation.StringRes + +class FxButtonSetting( + @StringRes titleId: Int, + val onClick: () -> Unit +) : SettingsItem(emptySetting, titleId, "", 0, "") { + override val type = TYPE_FX_BUTTON +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/FxPresetSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/FxPresetSetting.kt new file mode 100644 index 0000000000..9873b1c396 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/FxPresetSetting.kt @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.features.settings.model.view + +class FxPresetSetting( + titleString: String, + descriptionString: String = "", + val deletable: Boolean = false, + val onApply: () -> Unit, + val onDelete: () -> Unit = {} +) : SettingsItem(emptySetting, 0, titleString, 0, descriptionString) { + override val type = TYPE_FX_PRESET +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/FxShaderCardSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/FxShaderCardSetting.kt new file mode 100644 index 0000000000..e54ac3b5a0 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/FxShaderCardSetting.kt @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.features.settings.model.view + +import org.yuzu.yuzu_emu.utils.NativePostProcessing + +class FxShaderCardSetting( + titleString: String, + descriptionString: String, + val index: Int, + val expanded: Boolean, + val uniforms: List, + val onToggle: () -> Unit, + val onRemove: () -> Unit, + val onReset: () -> Unit +) : SettingsItem(emptySetting, 0, titleString, 0, descriptionString) { + override val type = TYPE_FX_SHADER +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/FxToolbarSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/FxToolbarSetting.kt new file mode 100644 index 0000000000..e62b0b914b --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/FxToolbarSetting.kt @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.features.settings.model.view + +import androidx.annotation.StringRes + +class FxToolbarSetting( + @StringRes val addLabelId: Int, + val listOpen: Boolean, + val presetLabel: String, + val hasEffects: Boolean, + val createPreset: StringInputSetting, + val onAdd: () -> Unit, + val onPresets: () -> Unit, + val onRemoveAll: () -> Unit +) : SettingsItem(emptySetting, 0, "", 0, "") { + override val type = TYPE_FX_TOOLBAR +} 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 3a5f466f07..f968a0ae18 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 @@ -20,7 +20,9 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.features.settings.model.LongSetting import org.yuzu.yuzu_emu.features.settings.model.ShortSetting import org.yuzu.yuzu_emu.features.settings.model.StringSetting +import org.yuzu.yuzu_emu.features.settings.model.UShortSetting import org.yuzu.yuzu_emu.network.NetDataValidators +import org.yuzu.yuzu_emu.utils.LosslessScalingHelper import org.yuzu.yuzu_emu.utils.NativeConfig /** @@ -65,6 +67,19 @@ abstract class SettingsItem( return NativeLibrary.isFirmwareAvailable() } + if (setting.key in frameGenKeys && + !(LosslessScalingHelper.isInstalled() && LosslessScalingHelper.isSupportedByGpu()) + ) { + return false + } + + // A frame rate target moves the multiplier on its own + if (setting.key == IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key && + frameGenTargetRate != 0 + ) { + return false + } + // Can't edit settings that aren't saveable in per-game config even if they are switchable if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) { return false @@ -88,7 +103,29 @@ abstract class SettingsItem( val clearable: Boolean get() = !setting.global && NativeConfig.isPerGameConfigLoaded() + private val frameGenTargetRate: Int + get() { + val key = IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key + val needsGlobal = if (NativeLibrary.isRunning() && + !NativeConfig.isPerGameConfigLoaded() + ) { + !NativeConfig.usingGlobal(key) + } else { + NativeConfig.usingGlobal(key) + } + return IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.getInt(needsGlobal) + } + companion object { + private val frameGenKeys = setOf( + BooleanSetting.RENDERER_FRAME_GEN.key, + IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key, + IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key, + IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key, + BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key, + IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key + ) + const val TYPE_HEADER = 0 const val TYPE_SWITCH = 1 const val TYPE_SINGLE_CHOICE = 2 @@ -105,6 +142,10 @@ abstract class SettingsItem( const val TYPE_LAUNCHABLE = 13 const val TYPE_PATH = 14 const val TYPE_GPU_UNSWIZZLE = 15 + const val TYPE_FX_TOOLBAR = 16 + const val TYPE_FX_PRESET = 17 + const val TYPE_FX_SHADER = 18 + const val TYPE_FX_BUTTON = 19 const val FASTMEM_COMBINED = "fastmem_combined" const val GPU_UNSWIZZLE_COMBINED = "gpu_unswizzle_combined" @@ -125,6 +166,13 @@ abstract class SettingsItem( // List of all general val settingsItems = HashMap().apply { put(StringInputSetting(StringSetting.DEVICE_NAME, titleId = R.string.device_name)) + put( + StringInputSetting( + StringSetting.PROGRAM_ARGS, + titleId = R.string.program_args, + descriptionId = R.string.program_args_description + ) + ) put( SwitchSetting( BooleanSetting.RENDERER_USE_SPEED_LIMIT, @@ -216,6 +264,13 @@ abstract class SettingsItem( descriptionId = R.string.flush_by_line_description ) ) + put( + SwitchSetting( + BooleanSetting.EXTENDED_LOGGING, + titleId = R.string.extended_logging, + descriptionId = R.string.extended_logging_description + ) + ) val dockedModeSetting = object : AbstractBooleanSetting { override val key = BooleanSetting.USE_DOCKED_MODE.key @@ -579,9 +634,20 @@ abstract class SettingsItem( IntSetting.FSR_SHARPENING_SLIDER, titleId = R.string.fsr_sharpness, descriptionId = R.string.fsr_sharpness_description, + max = 200, units = "%" ) ) + put( + SliderSetting( + IntSetting.ANDROID_PIPELINE_WORKERS, + titleId = R.string.pipeline_worker_cores, + descriptionId = R.string.pipeline_worker_cores_description, + min = 2, + max = 8, + units = "cores" + ) + ) put( SingleChoiceSetting( IntSetting.RENDERER_ANTI_ALIASING, @@ -590,6 +656,57 @@ abstract class SettingsItem( valuesId = R.array.rendererAntiAliasingValues ) ) + put( + SwitchSetting( + BooleanSetting.RENDERER_FRAME_GEN, + titleId = R.string.frame_gen, + descriptionId = R.string.frame_gen_description + ) + ) + put( + SingleChoiceSetting( + IntSetting.RENDERER_FRAME_GEN_MULTIPLIER, + titleId = R.string.frame_gen_multiplier, + descriptionId = R.string.frame_gen_multiplier_description, + choicesId = R.array.frameGenMultiplierNames, + valuesId = R.array.frameGenMultiplierValues + ) + ) + put( + SingleChoiceSetting( + IntSetting.RENDERER_FRAME_GEN_TARGET_RATE, + titleId = R.string.frame_gen_target_rate, + descriptionId = R.string.frame_gen_target_rate_description, + choicesId = R.array.frameGenTargetRateNames, + valuesId = R.array.frameGenTargetRateValues + ) + ) + put( + SingleChoiceSetting( + IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET, + titleId = R.string.frame_gen_queue_target, + descriptionId = R.string.frame_gen_queue_target_description, + choicesId = R.array.frameGenQueueTargetNames, + valuesId = R.array.frameGenQueueTargetValues + ) + ) + put( + SwitchSetting( + BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO, + titleId = R.string.frame_gen_flow_scale_auto, + descriptionId = R.string.frame_gen_flow_scale_auto_description + ) + ) + put( + SliderSetting( + IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE, + titleId = R.string.frame_gen_flow_scale, + descriptionId = R.string.frame_gen_flow_scale_description, + min = 25, + max = 100, + units = "%" + ) + ) put( SingleChoiceSetting( IntSetting.RENDERER_SCREEN_LAYOUT, @@ -636,13 +753,6 @@ abstract class SettingsItem( descriptionId = R.string.renderer_asynchronous_gpu_emulation_description ) ) - put( - SwitchSetting( - BooleanSetting.RENDERER_ASYNC_PRESENTATION, - titleId = R.string.renderer_async_presentation, - descriptionId = R.string.renderer_async_presentation_description - ) - ) put( SingleChoiceSetting( IntSetting.DMA_ACCURACY, @@ -652,6 +762,15 @@ abstract class SettingsItem( valuesId = R.array.dmaAccuracyValues ) ) + put( + SingleChoiceSetting( + IntSetting.GPU_FENCE_BEHAVIOR, + titleId = R.string.gpu_fence_behavior, + descriptionId = R.string.gpu_fence_behavior_description, + choicesId = R.array.gpuFenceBehaviorNames, + valuesId = R.array.gpuFenceBehaviorValues + ) + ) put( SwitchSetting( BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS, @@ -895,9 +1014,16 @@ abstract class SettingsItem( descriptionId = R.string.use_auto_stub_description ) ) + put( + StringInputSetting( + StringSetting.LOG_FILTER, + titleId = R.string.log_filter, + descriptionId = R.string.log_filter_description + ) + ) put( SpinBoxSetting( - ShortSetting.DEBUG_KNOBS, + UShortSetting.DEBUG_KNOBS, titleId = R.string.debug_knobs, descriptionId = R.string.debug_knobs_description, valueHint = R.string.debug_knobs_hint, @@ -907,13 +1033,6 @@ abstract class SettingsItem( ) // GPU Logging settings - put( - SwitchSetting( - BooleanSetting.GPU_LOGGING_ENABLED, - titleId = R.string.gpu_logging_enabled, - descriptionId = R.string.gpu_logging_enabled_description - ) - ) put( SingleChoiceSetting( ByteSetting.GPU_LOG_LEVEL, @@ -930,6 +1049,13 @@ abstract class SettingsItem( descriptionId = R.string.gpu_log_vulkan_calls_description ) ) + put( + SwitchSetting( + BooleanSetting.DUMP_GUEST_SHADERS, + titleId = R.string.dump_guest_shaders, + descriptionId = R.string.dump_guest_shaders_description + ) + ) put( SwitchSetting( BooleanSetting.GPU_LOG_SHADER_DUMPS, @@ -937,6 +1063,13 @@ abstract class SettingsItem( descriptionId = R.string.gpu_log_shader_dumps_description ) ) + put( + SwitchSetting( + BooleanSetting.DUMP_MACROS, + titleId = R.string.dump_macros, + descriptionId = R.string.dump_macros_description + ) + ) put( SwitchSetting( BooleanSetting.GPU_LOG_MEMORY_TRACKING, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputDialogFragment.kt index d07a6e6aea..0e2f78c49f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/InputDialogFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2024 yuzu Emulator Project @@ -169,7 +169,7 @@ class InputDialogFragment : DialogFragment() { NativeInput.onGamePadButtonEvent( controllerData.getGUID(), controllerData.getPort(), - event.keyCode, + InputHandler.getButtonIdFromEvent(event), action ) onInputReceived(event.device) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt index d7311eded5..8d47db94e0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt @@ -23,6 +23,10 @@ import com.google.android.material.timepicker.TimeFormat import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.SettingsNavigationDirections import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding +import org.yuzu.yuzu_emu.databinding.ListItemSettingFxButtonBinding +import org.yuzu.yuzu_emu.databinding.ListItemSettingFxPresetBinding +import org.yuzu.yuzu_emu.databinding.ListItemSettingFxShaderBinding +import org.yuzu.yuzu_emu.databinding.ListItemSettingFxToolbarBinding import org.yuzu.yuzu_emu.databinding.ListItemSettingInputBinding import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding @@ -106,6 +110,34 @@ class SettingsAdapter( GpuUnswizzleViewHolder(ListItemSettingBinding.inflate(inflater), this) } + SettingsItem.TYPE_FX_TOOLBAR -> { + FxToolbarViewHolder( + ListItemSettingFxToolbarBinding.inflate(inflater, parent, false), + this + ) + } + + SettingsItem.TYPE_FX_PRESET -> { + FxPresetViewHolder( + ListItemSettingFxPresetBinding.inflate(inflater, parent, false), + this + ) + } + + SettingsItem.TYPE_FX_SHADER -> { + FxShaderCardViewHolder( + ListItemSettingFxShaderBinding.inflate(inflater, parent, false), + this + ) + } + + SettingsItem.TYPE_FX_BUTTON -> { + FxButtonViewHolder( + ListItemSettingFxButtonBinding.inflate(inflater, parent, false), + this + ) + } + else -> { HeaderViewHolder(ListItemSettingsHeaderBinding.inflate(inflater), this) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt index 4e4ac47ea2..3ab864f906 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt @@ -382,7 +382,9 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener } scSetting.setSelectedValue(value) - if (scSetting.setting.key == IntSetting.RENDERER_SCALING_FILTER.key) { + if (scSetting.setting.key == IntSetting.RENDERER_SCALING_FILTER.key || + scSetting.setting.key == IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key + ) { settingsViewModel.setShouldReloadSettingsList(true) } 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 9f0e409cf4..824a54181e 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 @@ -18,6 +18,7 @@ import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting +import org.yuzu.yuzu_emu.features.settings.model.FxPresetNameSetting import org.yuzu.yuzu_emu.features.settings.model.ByteSetting import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.features.settings.model.LongSetting @@ -25,9 +26,12 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings.MenuTag import org.yuzu.yuzu_emu.features.settings.model.ShortSetting import org.yuzu.yuzu_emu.features.settings.model.StringSetting +import org.yuzu.yuzu_emu.features.settings.model.UShortSetting import org.yuzu.yuzu_emu.features.settings.model.view.* import org.yuzu.yuzu_emu.utils.InputHandler +import org.yuzu.yuzu_emu.utils.LosslessScalingHelper import org.yuzu.yuzu_emu.utils.NativeConfig +import org.yuzu.yuzu_emu.utils.NativePostProcessing import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.FullscreenHelper import androidx.core.content.edit @@ -42,6 +46,14 @@ class SettingsFragmentPresenter( ) { private var settingsList = ArrayList() + private val expandedShaderSlots = mutableSetOf() + + private var shaderPickerOpen = false + + private var presetPickerOpen = false + + private var postProcessingSynced = false + private val context get() = YuzuApplication.appContext // Extension for altering settings list based on each setting's properties @@ -76,18 +88,64 @@ class SettingsFragmentPresenter( } } - private fun isFsrScalingFilterSelected(): Boolean { - val fsrFilterValue = resolveFsrScalingFilterValue() ?: return false - val needsGlobal = getNeedsGlobalForKey(IntSetting.RENDERER_SCALING_FILTER.key) - val selectedFilter = IntSetting.RENDERER_SCALING_FILTER.getInt(needsGlobal) - return selectedFilter == fsrFilterValue + private fun addFrameGenSettings(sl: ArrayList) { + sl.apply { + if (!LosslessScalingHelper.isSupportedByGpu()) { + add( + RunnableSetting( + titleId = R.string.frame_gen_unsupported, + descriptionId = R.string.frame_gen_unsupported_description, + isRunnable = false + ) {} + ) + } else if (!LosslessScalingHelper.isInstalled()) { + add( + RunnableSetting( + titleId = R.string.lossless_scaling_missing, + descriptionId = R.string.lossless_scaling_missing_description, + isRunnable = false + ) {} + ) + } + + add(BooleanSetting.RENDERER_FRAME_GEN.key) + add(IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key) + if (IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.getInt( + getNeedsGlobalForKey(IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key) + ) == 0 + ) { + add(IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.key) + } + add(IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key) + add(BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key) + if (!BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.getBoolean( + getNeedsGlobalForKey(BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key) + ) + ) { + add(IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key) + } + } } - private fun resolveFsrScalingFilterValue(): Int? { + private fun isSharpnessScalingFilterSelected(): Boolean { + val needsGlobal = getNeedsGlobalForKey(IntSetting.RENDERER_SCALING_FILTER.key) + val selectedFilter = IntSetting.RENDERER_SCALING_FILTER.getInt(needsGlobal) + return selectedFilter in resolveSharpnessScalingFilterValues() + } + + private fun resolveSharpnessScalingFilterValues(): Set { val names = context.resources.getStringArray(R.array.rendererScalingFilterNames) val values = context.resources.getIntArray(R.array.rendererScalingFilterValues) - val fsrIndex = names.indexOf(context.getString(R.string.scaling_filter_fsr)) - return if (fsrIndex in values.indices) values[fsrIndex] else null + val sharpnessFilterNames = setOf( + context.getString(R.string.scaling_filter_fsr), + context.getString(R.string.scaling_filter_sgsr), + context.getString(R.string.scaling_filter_sgsr_edge), + ) + return names.asSequence() + .mapIndexedNotNull { index, name -> + if (name in sharpnessFilterNames && index in values.indices) values[index] else null + } + .toSet() } // Allows you to show/hide abstract settings based on the paired setting key @@ -113,6 +171,8 @@ class SettingsFragmentPresenter( MenuTag.SECTION_ROOT -> addConfigSettings(sl) MenuTag.SECTION_SYSTEM -> addSystemSettings(sl) MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl) + MenuTag.SECTION_FRAME_GEN -> addFrameGenSettings(sl) + MenuTag.SECTION_POST_PROCESSING -> addPostProcessingSettings(sl) MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl) MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl) MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl) @@ -140,6 +200,209 @@ class SettingsFragmentPresenter( } } + private fun addPostProcessingSettings(sl: ArrayList) { + if (!postProcessingSynced) { + postProcessingSynced = true + NativePostProcessing.reload() + } + + val usable = NativePostProcessing.catalog().filter { it.valid } + + sl.apply { + if (usable.isEmpty()) { + add( + RunnableSetting( + titleId = R.string.post_processing_empty, + descriptionString = NativePostProcessing.getShaderDirectory(), + isRunnable = false + ) {} + ) + return@apply + } + + val labels = mutableListOf() + val summaries = mutableListOf() + val files = mutableListOf() + val techniques = mutableListOf() + for (effect in usable) { + for (technique in effect.techniques) { + if (effect.techniques.size == 1) { + labels.add(effect.label) + } else { + labels.add(effect.label + " \u00b7 " + technique) + } + summaries.add(effect.description) + files.add(effect.file) + techniques.add(technique) + } + } + + val chain = NativePostProcessing.chain() + val active = NativePostProcessing.getActivePreset() + + var addLabel = R.string.post_processing_add + if (chain.isNotEmpty()) { + addLabel = R.string.post_processing_open_list + } + if (shaderPickerOpen) { + addLabel = R.string.post_processing_close_list + } + + var presetLabel = context.getString(R.string.post_processing_presets) + if (active.isNotEmpty()) { + presetLabel = active + } + + val createPreset = StringInputSetting( + setting = FxPresetNameSetting { name -> + NativePostProcessing.savePreset(name, "") + settingsViewModel.setReloadListAndNotifyDataset(true) + }, + titleId = R.string.post_processing_preset_new, + descriptionId = R.string.post_processing_preset_new_description, + validator = { it != null && it.isNotBlank() && !it.contains('=') }, + errorId = R.string.post_processing_preset_name_invalid + ) + + add( + FxToolbarSetting( + addLabelId = addLabel, + listOpen = shaderPickerOpen, + presetLabel = presetLabel, + hasEffects = chain.isNotEmpty(), + createPreset = createPreset, + onAdd = { + shaderPickerOpen = !shaderPickerOpen + presetPickerOpen = false + settingsViewModel.setReloadListAndNotifyDataset(true) + }, + onPresets = { + presetPickerOpen = !presetPickerOpen + shaderPickerOpen = false + settingsViewModel.setReloadListAndNotifyDataset(true) + }, + onRemoveAll = { + NativePostProcessing.clearChain() + NativePostProcessing.clearPreset() + NativePostProcessing.store() + expandedShaderSlots.clear() + shaderPickerOpen = false + settingsViewModel.setReloadListAndNotifyDataset(true) + } + ) + ) + + if (shaderPickerOpen) { + for (choice in labels.indices) { + add( + RunnableSetting( + titleString = labels[choice], + descriptionString = summaries[choice], + isRunnable = true + ) { + NativePostProcessing.append(files[choice], techniques[choice]) + NativePostProcessing.store() + shaderPickerOpen = false + settingsViewModel.setReloadListAndNotifyDataset(true) + } + ) + } + } + + if (presetPickerOpen) { + add( + FxPresetSetting( + titleString = context.getString(R.string.post_processing_preset_none), + onApply = { + NativePostProcessing.clearPreset() + presetPickerOpen = false + settingsViewModel.setReloadListAndNotifyDataset(true) + } + ) + ) + for (preset in NativePostProcessing.presets()) { + add( + FxPresetSetting( + titleString = preset.name, + descriptionString = preset.description, + deletable = !preset.bundled, + onApply = { + NativePostProcessing.applyPreset(preset.name) + NativePostProcessing.store() + presetPickerOpen = false + expandedShaderSlots.clear() + settingsViewModel.setReloadListAndNotifyDataset(true) + }, + onDelete = { + NativePostProcessing.deletePreset(preset.name) + settingsViewModel.setReloadListAndNotifyDataset(true) + } + ) + ) + } + } + + if (active.isNotEmpty()) { + add( + FxButtonSetting(titleId = R.string.post_processing_preset_reset) { + NativePostProcessing.applyPreset(active) + NativePostProcessing.store() + expandedShaderSlots.clear() + settingsViewModel.setReloadListAndNotifyDataset(true) + } + ) + } + + for (index in chain.indices) { + val entry = chain[index] + val effect = usable.firstOrNull { it.file == entry.file } + + var header = entry.file + var summary = "" + var uniforms = emptyList() + if (effect != null) { + header = effect.label + if (effect.techniques.size > 1) { + header = effect.label + " \u00b7 " + entry.technique + } + summary = effect.description + uniforms = effect.uniforms + } + + val isOpen = expandedShaderSlots.contains(index) + + add( + FxShaderCardSetting( + titleString = header, + descriptionString = summary, + index = index, + expanded = isOpen, + uniforms = uniforms, + onToggle = { + if (isOpen) { + expandedShaderSlots.remove(index) + } else { + expandedShaderSlots.add(index) + } + settingsViewModel.setReloadListAndNotifyDataset(true) + }, + onRemove = { + NativePostProcessing.remove(index) + NativePostProcessing.store() + expandedShaderSlots.clear() + settingsViewModel.setReloadListAndNotifyDataset(true) + }, + onReset = { + NativePostProcessing.resetValues(index) + NativePostProcessing.store() + settingsViewModel.setReloadListAndNotifyDataset(true) + } + ) + ) + } + } + } + private fun addConfigSettings(sl: ArrayList) { sl.apply { add( @@ -243,8 +506,9 @@ class SettingsFragmentPresenter( add(BooleanSetting.USE_CUSTOM_RTC.key) add(LongSetting.CUSTOM_RTC.key) - add(HeaderSetting(R.string.cpu)) + add(HeaderSetting(R.string.clocks)) add(IntSetting.FAST_CPU_TIME.key) + add(IntSetting.FAST_GPU_TIME.key) add(BooleanSetting.CORE_SYNC_CORE_SPEED.key) add(IntSetting.MEMORY_LAYOUT.key) @@ -262,12 +526,10 @@ class SettingsFragmentPresenter( // TODO(crueter): sub-submenus? private fun addGraphicsSettings(sl: ArrayList) { sl.apply { - // add(IntSetting.RENDERER_NVDEC_EMULATION.key) - add(IntSetting.RENDERER_RESOLUTION.key) add(IntSetting.RENDERER_VSYNC.key) add(IntSetting.RENDERER_SCALING_FILTER.key) - if (isFsrScalingFilterSelected()) { + if (isSharpnessScalingFilterSelected()) { add(IntSetting.FSR_SHARPENING_SLIDER.key) } add(IntSetting.RENDERER_ANTI_ALIASING.key) @@ -276,9 +538,11 @@ class SettingsFragmentPresenter( add(IntSetting.RENDERER_ACCURACY.key) add(IntSetting.DMA_ACCURACY.key) + add(IntSetting.GPU_FENCE_BEHAVIOR.key) add(IntSetting.MAX_ANISOTROPY.key) add(IntSetting.RENDERER_VRAM_USAGE_MODE.key) add(IntSetting.RENDERER_ASTC_DECODE_METHOD.key) + add(IntSetting.RENDERER_NVDEC_EMULATION.key) add(BooleanSetting.SYNC_MEMORY_OPERATIONS.key) add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key) @@ -289,14 +553,12 @@ class SettingsFragmentPresenter( add(HeaderSetting(R.string.hacks)) - add(IntSetting.FAST_GPU_TIME.key) add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key) add(BooleanSetting.FIX_BLOOM_EFFECTS.key) add(BooleanSetting.EMULATE_BGR565.key) - add(BooleanSetting.RESCALE_HACK.key) add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key) + add(IntSetting.ANDROID_PIPELINE_WORKERS.key) add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key) - add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key) add(SettingsItem.GPU_UNSWIZZLE_COMBINED) add(HeaderSetting(R.string.extensions)) @@ -1272,20 +1534,26 @@ class SettingsFragmentPresenter( add(HeaderSetting(R.string.log)) add(BooleanSetting.DEBUG_FLUSH_BY_LINE.key) + add(BooleanSetting.EXTENDED_LOGGING.key) + add(StringSetting.LOG_FILTER.key) } add(HeaderSetting(R.string.general)) - add(ShortSetting.DEBUG_KNOBS.key) + add(UShortSetting.DEBUG_KNOBS.key) + add(StringSetting.PROGRAM_ARGS.key) - add(HeaderSetting(R.string.gpu_logging_header)) - add(BooleanSetting.GPU_LOGGING_ENABLED.key) - add(ByteSetting.GPU_LOG_LEVEL.key) - add(BooleanSetting.GPU_LOG_VULKAN_CALLS.key) - add(BooleanSetting.GPU_LOG_SHADER_DUMPS.key) - add(BooleanSetting.GPU_LOG_MEMORY_TRACKING.key) - add(BooleanSetting.GPU_LOG_DRIVER_DEBUG.key) - add(IntSetting.GPU_LOG_RING_BUFFER_SIZE.key) + if (!NativeConfig.isPerGameConfigLoaded()) { + add(HeaderSetting(R.string.gpu_logging_header)) + add(ByteSetting.GPU_LOG_LEVEL.key) + add(BooleanSetting.GPU_LOG_VULKAN_CALLS.key) + add(BooleanSetting.DUMP_GUEST_SHADERS.key) + add(BooleanSetting.GPU_LOG_SHADER_DUMPS.key) + add(BooleanSetting.DUMP_MACROS.key) + add(BooleanSetting.GPU_LOG_MEMORY_TRACKING.key) + add(BooleanSetting.GPU_LOG_DRIVER_DEBUG.key) + add(IntSetting.GPU_LOG_RING_BUFFER_SIZE.key) + } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSubscreenActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSubscreenActivity.kt index 91888dce12..8c0d6d055e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSubscreenActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsSubscreenActivity.kt @@ -29,6 +29,7 @@ enum class SettingsSubscreen { DRIVER_MANAGER, DRIVER_FETCHER, FREEDRENO_SETTINGS, + LOSSLESS_MANAGER, APPLET_LAUNCHER, INSTALLABLE, GAME_FOLDERS, @@ -126,6 +127,7 @@ class SettingsSubscreenActivity : AppCompatActivity() { SettingsSubscreen.DRIVER_MANAGER -> R.id.driverManagerFragment SettingsSubscreen.DRIVER_FETCHER -> R.id.driverFetcherFragment SettingsSubscreen.FREEDRENO_SETTINGS -> R.id.freedrenoSettingsFragment + SettingsSubscreen.LOSSLESS_MANAGER -> R.id.losslessManagerFragment SettingsSubscreen.APPLET_LAUNCHER -> R.id.appletLauncherFragment SettingsSubscreen.INSTALLABLE -> R.id.installableFragment SettingsSubscreen.GAME_FOLDERS -> R.id.gameFoldersFragment diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/FxButtonViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/FxButtonViewHolder.kt new file mode 100644 index 0000000000..914f96085a --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/FxButtonViewHolder.kt @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.features.settings.ui.viewholder + +import android.view.View +import org.yuzu.yuzu_emu.databinding.ListItemSettingFxButtonBinding +import org.yuzu.yuzu_emu.features.settings.model.view.FxButtonSetting +import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem +import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter + +class FxButtonViewHolder( + val binding: ListItemSettingFxButtonBinding, + adapter: SettingsAdapter +) : SettingViewHolder(binding.root, adapter) { + private lateinit var setting: FxButtonSetting + + override fun bind(item: SettingsItem) { + setting = item as FxButtonSetting + + binding.fxButton.text = item.title + binding.fxButton.setOnClickListener { setting.onClick.invoke() } + } + + override fun onClick(clicked: View) {} + + override fun onLongClick(clicked: View): Boolean = true +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/FxPresetViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/FxPresetViewHolder.kt new file mode 100644 index 0000000000..d8634c1002 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/FxPresetViewHolder.kt @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.features.settings.ui.viewholder + +import android.view.View +import org.yuzu.yuzu_emu.databinding.ListItemSettingFxPresetBinding +import org.yuzu.yuzu_emu.features.settings.model.view.FxPresetSetting +import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem +import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter +import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible + +class FxPresetViewHolder( + val binding: ListItemSettingFxPresetBinding, + adapter: SettingsAdapter +) : SettingViewHolder(binding.root, adapter) { + private lateinit var setting: FxPresetSetting + + override fun bind(item: SettingsItem) { + setting = item as FxPresetSetting + + binding.presetName.text = item.title + binding.presetDescription.text = item.description + binding.presetDescription.setVisible(item.description.isNotEmpty()) + + binding.presetRow.setOnClickListener { setting.onApply.invoke() } + + binding.presetDelete.setVisible(setting.deletable) + binding.presetDelete.setOnClickListener { setting.onDelete.invoke() } + } + + override fun onClick(clicked: View) {} + + override fun onLongClick(clicked: View): Boolean = true +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/FxShaderCardViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/FxShaderCardViewHolder.kt new file mode 100644 index 0000000000..fc72f04225 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/FxShaderCardViewHolder.kt @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.features.settings.ui.viewholder + +import android.view.LayoutInflater +import android.view.View +import org.yuzu.yuzu_emu.databinding.ItemSettingFxActionsBinding +import org.yuzu.yuzu_emu.databinding.ItemSettingFxSliderBinding +import org.yuzu.yuzu_emu.databinding.ListItemSettingFxShaderBinding +import org.yuzu.yuzu_emu.features.settings.model.FxUniformSliderSetting +import org.yuzu.yuzu_emu.features.settings.model.view.FxShaderCardSetting +import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem +import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter +import org.yuzu.yuzu_emu.utils.NativePostProcessing +import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible + +class FxShaderCardViewHolder( + val binding: ListItemSettingFxShaderBinding, + adapter: SettingsAdapter +) : SettingViewHolder(binding.root, adapter) { + private lateinit var setting: FxShaderCardSetting + + override fun bind(item: SettingsItem) { + setting = item as FxShaderCardSetting + + binding.shaderTitle.text = item.title + binding.shaderSummary.text = item.description + binding.shaderSummary.setVisible(item.description.isNotEmpty()) + + var rotation = 0f + if (setting.expanded) { + rotation = 180f + } + binding.shaderExpand.rotation = rotation + + binding.shaderHeader.setOnClickListener { setting.onToggle.invoke() } + binding.shaderRemove.setOnClickListener { setting.onRemove.invoke() } + + binding.shaderBody.removeAllViews() + binding.shaderBody.setVisible(setting.expanded) + if (!setting.expanded) { + return + } + + val inflater = LayoutInflater.from(binding.root.context) + for (uniform in setting.uniforms) { + if (uniform.uiType == NativePostProcessing.UI_HIDDEN) { + continue + } + for (component in 0 until uniform.components) { + addSlider(inflater, uniform, component) + } + } + addActions(inflater) + } + + private fun addSlider( + inflater: LayoutInflater, + uniform: NativePostProcessing.Uniform, + component: Int + ) { + val row = ItemSettingFxSliderBinding.inflate(inflater, binding.shaderBody, false) + val value = FxUniformSliderSetting(setting.index, uniform, component) + + var title = uniform.label + if (uniform.components > 1) { + title = uniform.label + " [" + component + "]" + } + row.fxSliderTitle.text = title + + val steps = uniform.steps.toFloat() + row.fxSlider.valueFrom = 0f + row.fxSlider.valueTo = steps + row.fxSlider.stepSize = 1f + row.fxSlider.value = value.getInt(false).toFloat().coerceIn(0f, steps) + row.fxSliderValue.text = uniform.describe(row.fxSlider.value.toInt()) + + row.fxSlider.addOnChangeListener { _, position, fromUser -> + if (fromUser) { + value.setInt(position.toInt()) + row.fxSliderValue.text = uniform.describe(position.toInt()) + } + } + + binding.shaderBody.addView(row.root) + } + + private fun addActions(inflater: LayoutInflater) { + val row = ItemSettingFxActionsBinding.inflate(inflater, binding.shaderBody, false) + + row.fxReset.setOnClickListener { setting.onReset.invoke() } + + binding.shaderBody.addView(row.root) + } + + override fun onClick(clicked: View) {} + + override fun onLongClick(clicked: View): Boolean = true +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/FxToolbarViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/FxToolbarViewHolder.kt new file mode 100644 index 0000000000..21bad70040 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/FxToolbarViewHolder.kt @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.features.settings.ui.viewholder + +import android.content.res.ColorStateList +import android.view.View +import com.google.android.material.color.MaterialColors +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.databinding.ListItemSettingFxToolbarBinding +import org.yuzu.yuzu_emu.features.settings.model.view.FxToolbarSetting +import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem +import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter + +class FxToolbarViewHolder( + val binding: ListItemSettingFxToolbarBinding, + adapter: SettingsAdapter +) : SettingViewHolder(binding.root, adapter) { + private lateinit var setting: FxToolbarSetting + + override fun bind(item: SettingsItem) { + setting = item as FxToolbarSetting + + binding.fxAdd.setText(setting.addLabelId) + var addIcon = R.drawable.ic_add + if (setting.listOpen) { + addIcon = R.drawable.ic_clear + } + binding.fxAdd.setIconResource(addIcon) + binding.fxAdd.setOnClickListener { setting.onAdd.invoke() } + + binding.fxPresets.text = setting.presetLabel + binding.fxPresets.setOnClickListener { setting.onPresets.invoke() } + + binding.fxCreatePreset.isEnabled = setting.hasEffects + binding.fxCreatePreset.setOnClickListener { + adapter.onStringInputClick(setting.createPreset, bindingAdapterPosition) + } + + val removeBackground = MaterialColors.getColor( + binding.fxRemoveAll, + com.google.android.material.R.attr.colorErrorContainer + ) + val removeForeground = MaterialColors.getColor( + binding.fxRemoveAll, + com.google.android.material.R.attr.colorOnErrorContainer + ) + binding.fxRemoveAll.backgroundTintList = ColorStateList.valueOf(removeBackground) + binding.fxRemoveAll.iconTint = ColorStateList.valueOf(removeForeground) + binding.fxRemoveAll.isEnabled = setting.hasEffects + binding.fxRemoveAll.setOnClickListener { setting.onRemoveAll.invoke() } + } + + override fun onClick(clicked: View) {} + + override fun onLongClick(clicked: View): Boolean = true +} 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 120bafdd14..3323b3cf9a 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 @@ -92,8 +92,10 @@ import org.yuzu.yuzu_emu.utils.GameIconUtils import org.yuzu.yuzu_emu.utils.GpuDriverHelper import org.yuzu.yuzu_emu.utils.InputHandler import org.yuzu.yuzu_emu.utils.Log +import org.yuzu.yuzu_emu.utils.LosslessScalingHelper import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.NativeFreedrenoConfig +import org.yuzu.yuzu_emu.utils.NativePostProcessing import org.yuzu.yuzu_emu.utils.ViewUtils import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible import org.yuzu.yuzu_emu.utils.collect @@ -883,6 +885,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { if (shouldUseCustom) { SettingsFile.loadCustomConfig(game!!) } + refreshPostProcessing() + addQuickSettings() } } @@ -1087,10 +1091,38 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } + private fun withPerGameConfig(create: Boolean, action: () -> Unit) { + val target = game + var owned = false + if (target != null && !NativeConfig.isPerGameConfigLoaded()) { + if (create || SettingsFile.getCustomSettingsFile(target).exists()) { + SettingsFile.loadCustomConfig(target) + owned = true + } + } + action() + if (owned) { + NativeConfig.unloadPerGameConfig() + } + } + + fun refreshPostProcessing() = withPerGameConfig(false) { + NativePostProcessing.reload() + } + + fun persistPostProcessing() = withPerGameConfig(true) { + NativePostProcessing.persist() + } + + fun editPostProcessing(action: () -> Unit) = withPerGameConfig(true) { + action() + NativePostProcessing.persist() + } + private fun addQuickSettings() { binding.quickSettingsSheet.apply { val container = binding.quickSettingsSheet.findViewById(R.id.quick_settings_container) - val isFsrSelected = isFsrScalingFilterSelected() + val isSharpnessFilterSelected = isSharpnessScalingFilterSelected() container.removeAllViews() @@ -1157,6 +1189,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { quickSettings.addDivider(container) + if (LosslessScalingHelper.isInstalled() && LosslessScalingHelper.isSupportedByGpu()) { + quickSettings.addFrameGen(container) + quickSettings.addDivider(container) + } + quickSettings.addIntSetting( R.string.renderer_accuracy, container, @@ -1176,13 +1213,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { addQuickSettings() } - if (isFsrSelected) { + if (isSharpnessFilterSelected) { quickSettings.addSliderSetting( R.string.fsr_sharpness, container, IntSetting.FSR_SHARPENING_SLIDER, minValue = 0, - maxValue = 100, + maxValue = 200, units = "%" ) } @@ -1194,20 +1231,31 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { R.array.rendererAntiAliasingNames, R.array.rendererAntiAliasingValues ) + + quickSettings.addPostProcessing(container) { + addQuickSettings() + } } } - private fun isFsrScalingFilterSelected(): Boolean { - val fsrFilterValue = resolveFsrScalingFilterValue() ?: return false + private fun isSharpnessScalingFilterSelected(): Boolean { val selectedFilter = IntSetting.RENDERER_SCALING_FILTER.getInt(needsGlobal = false) - return selectedFilter == fsrFilterValue + return selectedFilter in resolveSharpnessScalingFilterValues() } - private fun resolveFsrScalingFilterValue(): Int? { + private fun resolveSharpnessScalingFilterValues(): Set { val names = resources.getStringArray(R.array.rendererScalingFilterNames) val values = resources.getIntArray(R.array.rendererScalingFilterValues) - val fsrIndex = names.indexOf(getString(R.string.scaling_filter_fsr)) - return if (fsrIndex in values.indices) values[fsrIndex] else null + val sharpnessFilterNames = setOf( + getString(R.string.scaling_filter_fsr), + getString(R.string.scaling_filter_sgsr), + getString(R.string.scaling_filter_sgsr_edge), + ) + return names.asSequence() + .mapIndexedNotNull { index, name -> + if (name in sharpnessFilterNames && index in values.indices) values[index] else null + } + .toSet() } private fun openQuickSettingsMenu() { @@ -1444,6 +1492,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { override fun onResume() { super.onResume() + NativeLibrary.refreshThreadPolicies() val b = _binding ?: return updateStatsPosition(IntSetting.PERF_OVERLAY_POSITION.getInt()) updateSocPosition(IntSetting.SOC_OVERLAY_POSITION.getInt()) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt index c3dea79bae..d2505c5f61 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt @@ -369,6 +369,35 @@ class GamePropertiesFragment : Fragment() { ) ) } + add( + SubmenuProperty( + R.string.frame_gen, + R.string.frame_gen_per_game_description, + R.drawable.ic_duck, + action = { + val action = HomeNavigationDirections.actionGlobalSettingsActivity( + args.game, + Settings.MenuTag.SECTION_FRAME_GEN + ) + binding.root.findNavController().navigate(action) + } + ) + ) + add( + SubmenuProperty( + R.string.post_processing, + R.string.post_processing_per_game_description, + R.drawable.ic_post_processing, + action = { + val action = HomeNavigationDirections.actionGlobalSettingsActivity( + args.game, + Settings.MenuTag.SECTION_POST_PROCESSING + ) + binding.root.findNavController().navigate(action) + } + ) + ) + if (GpuDriverHelper.isAdrenoGpu()) { add( SubmenuProperty( @@ -440,7 +469,7 @@ class GamePropertiesFragment : Fragment() { val shaderCacheDir = File( DirectoryInitialization.userDirectory + - "/shader/" + args.game.settingsName.lowercase() + "/cache/shader/" + args.game.settingsName.lowercase() ) if (shaderCacheDir.exists()) { add( diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index 37eda22c69..2b4cfc5ca8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt @@ -44,6 +44,7 @@ import org.yuzu.yuzu_emu.ui.main.MainActivity import org.yuzu.yuzu_emu.utils.FileUtil import org.yuzu.yuzu_emu.utils.GpuDriverHelper import org.yuzu.yuzu_emu.utils.Log +import org.yuzu.yuzu_emu.utils.LosslessScalingHelper import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins class HomeSettingsFragment : Fragment() { @@ -170,6 +171,38 @@ class HomeSettingsFragment : Fragment() { ) ) } + add( + HomeSetting( + R.string.post_processing, + R.string.post_processing_description, + R.drawable.ic_post_processing, + { + val action = HomeNavigationDirections.actionGlobalSettingsActivity( + null, + Settings.MenuTag.SECTION_POST_PROCESSING + ) + binding.root.findNavController().navigate(action) + } + ) + ) + add( + HomeSetting( + R.string.lossless_scaling, + R.string.lossless_scaling_description, + R.drawable.ic_duck, + { + val action = HomeNavigationDirections.actionGlobalSettingsSubscreenActivity( + SettingsSubscreen.LOSSLESS_MANAGER, + null + ) + binding.root.findNavController().navigate(action) + }, + { true }, + 0, + 0, + LosslessScalingHelper.statusText + ) + ) add( HomeSetting( R.string.multiplayer, @@ -337,6 +370,7 @@ class HomeSettingsFragment : Fragment() { override fun onResume() { super.onResume() driverViewModel.updateDriverNameForGame(null) + LosslessScalingHelper.refreshStatus() } override fun onDestroyView() { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LosslessManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LosslessManagerFragment.kt new file mode 100644 index 0000000000..52cb887fdb --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LosslessManagerFragment.kt @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.GridLayoutManager +import com.google.android.material.transition.MaterialSharedAxis +import org.yuzu.yuzu_emu.NativeLibrary +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter +import org.yuzu.yuzu_emu.databinding.FragmentLosslessManagerBinding +import org.yuzu.yuzu_emu.features.fetcher.SpacingItemDecoration +import org.yuzu.yuzu_emu.model.HomeSetting +import org.yuzu.yuzu_emu.utils.LosslessScalingHelper +import org.yuzu.yuzu_emu.utils.ViewUtils.updateMargins +import org.yuzu.yuzu_emu.utils.collect + +class LosslessManagerFragment : Fragment() { + private var _binding: FragmentLosslessManagerBinding? = null + private val binding get() = _binding!! + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) + exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentLosslessManagerBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.toolbarLossless.setNavigationOnClickListener { + requireActivity().onBackPressedDispatcher.onBackPressed() + } + + binding.losslessOptionsList.apply { + layoutManager = + GridLayoutManager(requireContext(), resources.getInteger(R.integer.grid_columns)) + addItemDecoration( + SpacingItemDecoration(resources.getDimensionPixelSize(R.dimen.spacing_small)) + ) + } + + LosslessScalingHelper.statusText.collect(viewLifecycleOwner) { refreshOptions() } + + setInsets() + } + + private fun refreshOptions() { + binding.losslessOptionsList.adapter = HomeSettingAdapter( + requireActivity() as AppCompatActivity, + viewLifecycleOwner, + buildOptions() + ) + } + + private fun buildOptions(): List { + val installed = LosslessScalingHelper.isInstalled() + return listOf( + HomeSetting( + if (installed) R.string.lossless_scaling_replace else R.string.lossless_scaling_install, + if (installed) { + R.string.lossless_scaling_replace_description + } else { + R.string.lossless_scaling_install_description + }, + R.drawable.ic_install, + { dllPickerLauncher.launch(arrayOf("*/*")) }, + { !NativeLibrary.isRunning() }, + R.string.lossless_scaling_locked, + R.string.lossless_scaling_locked_description, + LosslessScalingHelper.statusText + ), + HomeSetting( + R.string.lossless_scaling_remove, + R.string.lossless_scaling_remove_description, + R.drawable.ic_delete, + { confirmRemoval() }, + { installed && !NativeLibrary.isRunning() }, + if (installed) { + R.string.lossless_scaling_locked + } else { + R.string.lossless_scaling_remove_unavailable + }, + if (installed) { + R.string.lossless_scaling_locked_description + } else { + R.string.lossless_scaling_remove_unavailable_description + } + ) + ) + } + + private fun confirmRemoval() { + MessageDialogFragment.newInstance( + requireActivity(), + titleId = R.string.lossless_scaling_remove, + descriptionId = R.string.lossless_scaling_remove_confirmation, + positiveButtonTitleId = R.string.lossless_scaling_remove, + positiveAction = { LosslessScalingHelper.remove() }, + showNegativeButton = true, + negativeAction = {} + ).show(parentFragmentManager, MessageDialogFragment.TAG) + } + + private val dllPickerLauncher = + registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> + if (result == null) { + return@registerForActivityResult + } + + val resultStrings = resources.getStringArray(R.array.losslessDllResults) + ProgressDialogFragment.newInstance( + requireActivity(), + R.string.lossless_scaling_installing, + false + ) { _, _ -> + val installResult = LosslessScalingHelper.install(result) + if (installResult == LosslessScalingHelper.RESULT_OK) { + getString(R.string.lossless_scaling_install_success) + } else { + MessageDialogFragment.newInstance( + titleId = R.string.lossless_scaling_install_failed, + descriptionString = resultStrings[installResult] + ) + } + }.show(parentFragmentManager, ProgressDialogFragment.TAG) + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + private fun setInsets() = + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, windowInsets -> + val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) + + binding.appbarLossless.updateMargins( + left = barInsets.left + cutoutInsets.left, + right = barInsets.right + cutoutInsets.right + ) + + binding.scrollViewLossless.updatePadding(bottom = barInsets.bottom) + + binding.losslessOptionsList.updatePadding( + left = barInsets.left + cutoutInsets.left, + right = barInsets.right + cutoutInsets.right + ) + + windowInsets + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt index 41f2da08a7..fd59304ebb 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.fragments @@ -42,6 +42,7 @@ import org.yuzu.yuzu_emu.model.SetupPage import org.yuzu.yuzu_emu.model.PageState import org.yuzu.yuzu_emu.ui.main.MainActivity import org.yuzu.yuzu_emu.utils.DirectoryInitialization +import org.yuzu.yuzu_emu.utils.LosslessScalingHelper import org.yuzu.yuzu_emu.utils.NativeConfig import org.yuzu.yuzu_emu.utils.ViewUtils import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible @@ -202,6 +203,24 @@ class SetupFragment : Fragment() { R.string.install_firmware_warning_help, ) ) + add( + PageButton( + R.drawable.ic_duck, + R.string.lossless_scaling, + R.string.lossless_scaling_setup_description, + { + pageButtonCallback = it + getLosslessDll.launch(arrayOf("*/*")) + }, + { + if (LosslessScalingHelper.isInstalled()) { + ButtonState.BUTTON_ACTION_COMPLETE + } else { + ButtonState.BUTTON_ACTION_INCOMPLETE + } + } + ) + ) add( PageButton( R.drawable.ic_controller, @@ -446,6 +465,32 @@ class SetupFragment : Fragment() { } } + val getLosslessDll = + registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> + if (result == null) { + return@registerForActivityResult + } + + val resultStrings = resources.getStringArray(R.array.losslessDllResults) + ProgressDialogFragment.newInstance( + requireActivity(), + R.string.lossless_scaling_installing, + false + ) { _, _ -> + val installResult = LosslessScalingHelper.install(result) + if (installResult == LosslessScalingHelper.RESULT_OK) { + getString(R.string.lossless_scaling_install_success) + } else { + MessageDialogFragment.newInstance( + titleId = R.string.lossless_scaling_install_failed, + descriptionString = resultStrings[installResult] + ) + } + }.apply { + onDialogComplete = { checkForButtonState.invoke() } + }.show(parentFragmentManager, ProgressDialogFragment.TAG) + } + val getGamesDirectory = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> if (result != null) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SystemInfoDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SystemInfoDialogFragment.kt index 5c914eeb16..2a691e7bf8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SystemInfoDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SystemInfoDialogFragment.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.fragments @@ -72,6 +72,18 @@ class SystemInfoDialogFragment : DialogFragment() { val vulkanDriver = NativeLibrary.getVulkanDriverVersion() appendLine("${getString(R.string.vulkan_driver_version)}: $vulkanDriver") + + val frameGen = NativeLibrary.supportsFrameGeneration() + appendLine( + "${getString(R.string.frame_generation_support)}: " + + getString( + if (frameGen) { + R.string.frame_generation_supported + } else { + R.string.frame_generation_unsupported + } + ) + ) } catch (e: Exception) { appendLine("${getString(R.string.error_getting_emulator_info)}: ${e.message}") } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt index 41f03c2488..fe53a99e3e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt @@ -23,6 +23,7 @@ import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver import org.yuzu.yuzu_emu.utils.GpuDriverHelper import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.utils.GpuDriverMetadata +import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.NativeConfig import java.io.File @@ -146,11 +147,11 @@ class DriverViewModel : ViewModel() { private fun wipeGameShaders(game: Game) { viewModelScope.launch { withContext(Dispatchers.IO) { - val externalFilesDir = YuzuApplication.appContext.getExternalFilesDir(null) + val userDirectory = DirectoryInitialization.userDirectory ?: return@withContext val shaderDir = File( - externalFilesDir.absolutePath + - "/shader/" + game.settingsName.lowercase() + userDirectory + + "/cache/shader/" + game.settingsName.lowercase() ) if (shaderDir.exists()) { shaderDir.deleteRecursively() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt index 799708dfa7..f5f0cda0e9 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2023 yuzu Emulator Project @@ -37,7 +37,7 @@ class Game( val settingsName: String get() { - val programIdLong = programId.toLong() + val programIdLong = programId.toLongOrNull() ?: 0L return if (programIdLong == 0L) { FileUtil.getFilename(Uri.parse(path)) } else { @@ -47,7 +47,7 @@ class Game( val programIdHex: String get() { - val programIdLong = programId.toLong() + val programIdLong = programId.toLongOrNull() ?: 0L return if (programIdLong == 0L) { "0" } else { @@ -100,7 +100,7 @@ class Game( companion object { val extensions: Set = HashSet( - listOf("xci", "nsp", "nca", "nro") + listOf("xci", "nsp", "nca", "nro", "nsz", "xcz") ) } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt index 6931a1f9d5..a71978b4b8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt @@ -18,6 +18,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +import androidx.core.view.doOnPreDraw import androidx.core.view.updatePadding import androidx.core.widget.doOnTextChanged import androidx.fragment.app.Fragment @@ -46,7 +47,6 @@ import info.debatty.java.stringsimilarity.Jaccard import info.debatty.java.stringsimilarity.JaroWinkler import java.util.Locale import androidx.core.content.edit -import androidx.core.view.doOnNextLayout class GamesFragment : Fragment() { private var _binding: FragmentGamesBinding? = null @@ -58,7 +58,10 @@ class GamesFragment : Fragment() { private var originalHeaderLeftMargin: Int? = null private var lastViewType: Int = GameAdapter.VIEW_TYPE_GRID - private var fallbackBottomInset: Int = 0 + private var pendingPostReloadListSettle = false + private var pendingPostReloadListSettleGeneration = 0 + private var gameListSubmitGeneration = 0 + private var committedGameListSubmitGeneration = 0 companion object { private const val SEARCH_TEXT = "SearchText" @@ -168,10 +171,9 @@ class GamesFragment : Fragment() { gamesViewModel.shouldScrollAfterReload.collect(viewLifecycleOwner) { shouldScroll -> if (shouldScroll) { - binding.gridGames.post { - (binding.gridGames as? CarouselRecyclerView)?.pendingScrollAfterReload = true - gameAdapter.notifyDataSetChanged() - } + pendingPostReloadListSettle = true + pendingPostReloadListSettleGeneration = gameListSubmitGeneration + schedulePostReloadListSettle() gamesViewModel.setShouldScrollAfterReload(false) } } @@ -223,12 +225,7 @@ class GamesFragment : Fragment() { } else -> throw IllegalArgumentException("Invalid view type: $savedViewType") } - if (savedViewType == GameAdapter.VIEW_TYPE_CAROUSEL) { - (binding.gridGames as? View)?.let { it -> ViewCompat.requestApplyInsets(it)} - doOnNextLayout { //Carousel: important to avoid overlap issues - (this as? CarouselRecyclerView)?.notifyLaidOut(fallbackBottomInset) - } - } else { + if (savedViewType != GameAdapter.VIEW_TYPE_CAROUSEL) { (this as? CarouselRecyclerView)?.setupCarousel(false) } adapter = gameAdapter @@ -273,11 +270,42 @@ class GamesFragment : Fragment() { lastSearchText = currentSearchText lastFilter = currentFilter } else { - ((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList(games) + submitGameList(games) gamesViewModel.setFilteredGames(games) } } + private fun submitGameList(games: List) { + val adapter = (binding.gridGames as? RecyclerView)?.adapter as? GameAdapter + if (adapter == null) { + schedulePostReloadListSettle() + return + } + + val submitGeneration = ++gameListSubmitGeneration + adapter.submitList(games) { + if (committedGameListSubmitGeneration < submitGeneration) { + committedGameListSubmitGeneration = submitGeneration + } + schedulePostReloadListSettle() + } + } + + + private fun schedulePostReloadListSettle() { + if (!pendingPostReloadListSettle || _binding == null) return + + binding.gridGames.doOnPreDraw { + if (!pendingPostReloadListSettle || _binding == null) return@doOnPreDraw + if (committedGameListSubmitGeneration < pendingPostReloadListSettleGeneration) { + schedulePostReloadListSettle() + return@doOnPreDraw + } + pendingPostReloadListSettle = false + + (binding.gridGames as? CarouselRecyclerView)?.refreshView() + } + } private fun setupTopView() { binding.searchText.doOnTextChanged() { text: CharSequence?, _: Int, _: Int, _: Int -> if (text.toString().isNotEmpty()) { @@ -414,9 +442,7 @@ class GamesFragment : Fragment() { val searchTerm = binding.searchText.text.toString().lowercase(Locale.getDefault()) if (searchTerm.isEmpty()) { - ((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList( - filteredList - ) + submitGameList(filteredList) gamesViewModel.setFilteredGames(filteredList) return } @@ -432,7 +458,7 @@ class GamesFragment : Fragment() { } }.sortedByDescending { it.score }.map { it.item } - ((binding.gridGames as? RecyclerView)?.adapter as? GameAdapter)?.submitList(sortedList) + submitGameList(sortedList) gamesViewModel.setFilteredGames(sortedList) } @@ -557,11 +583,6 @@ class GamesFragment : Fragment() { qlaunchButton.layoutParams = mlpQLaunch } - val navInsets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()) - val gestureInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures()) - val bottomInset = maxOf(navInsets.bottom, gestureInsets.bottom, cutoutInsets.bottom) - fallbackBottomInset = bottomInset - (binding.gridGames as? CarouselRecyclerView)?.notifyInsetsReady(bottomInset) windowInsets } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/AddonUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/AddonUtil.kt index b5613a7a76..60bdaefe9b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/AddonUtil.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/AddonUtil.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2023 yuzu Emulator Project @@ -7,5 +7,5 @@ package org.yuzu.yuzu_emu.utils object AddonUtil { - val validAddonDirectories = listOf("cheats", "exefs", "romfs", "romfslite") + val validAddonDirectories = listOf("cheats", "exefs", "romfs", "romfslite", "romfs_ext") } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt index 64e035afbe..bb2337a310 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt @@ -31,6 +31,7 @@ object GameHelper { fun getGames(): List { val games = mutableListOf() + val gamesByProgramId = mutableMapOf() val context = YuzuApplication.appContext preferences = PreferenceManager.getDefaultSharedPreferences(context) @@ -63,6 +64,7 @@ object GameHelper { addGamesRecursive( games, + gamesByProgramId, FileUtil.listFiles(gameDirUri), scanDepth, mountedContainerUris @@ -107,7 +109,7 @@ object GameHelper { // File extensions considered as external content, buuut should // be done better imo. - private val externalContentExtensions = setOf("nsp", "xci") + private val externalContentExtensions = setOf("nsp", "xci", "nsz", "xcz") private fun scanContentContainersRecursive( files: Array, @@ -136,6 +138,7 @@ object GameHelper { private fun addGamesRecursive( games: MutableList, + gamesByProgramId: MutableMap, files: Array, depth: Int, mountedContainerUris: MutableSet @@ -148,6 +151,7 @@ object GameHelper { if (it.isDirectory) { addGamesRecursive( games, + gamesByProgramId, FileUtil.listFiles(it.uri), depth - 1, mountedContainerUris @@ -156,8 +160,9 @@ object GameHelper { val extension = FileUtil.getExtension(it.uri).lowercase() val filePath = it.uri.toString() - if (externalContentExtensions.contains(extension) && - mountedContainerUris.add(filePath)) { + val mountedContainer = externalContentExtensions.contains(extension) && + mountedContainerUris.add(filePath) + if (mountedContainer) { NativeLibrary.addGameFolderFileToFilesystemProvider(filePath) } @@ -165,6 +170,20 @@ object GameHelper { val game = getGame(it.uri, true, false) if (game != null) { games.add(game) + if (game.programId != "0") { + gamesByProgramId[game.programId] = game + } + } else if (mountedContainer) { + GameMetadata.getProgramId(filePath).toLongOrNull()?.let { programId -> + gamesByProgramId[(programId and 0x800L.inv()).toString()] + }?.let { existingGame -> + NativeLibrary.getPatchesForFile(existingGame.path, existingGame.programId) + existingGame.version = GameMetadata.getVersion( + existingGame.path, + true + ) + GameIconUtils.refreshGameIcon(existingGame) + } } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt index d05020560a..efe68a4ecf 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -24,6 +27,15 @@ import coil.request.Options import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.model.Game +import java.util.Collections +import java.util.WeakHashMap + +private val gameIconHashes = Collections.synchronizedMap(mutableMapOf()) +private val gameIconTargets = Collections.synchronizedMap(WeakHashMap()) + +private fun Game.iconCacheKey(): String = "$path|$version" + +private data class GameIconTarget(val game: Game, var iconHash: Int? = null) class GameIconFetcher( private val game: Game, @@ -31,14 +43,15 @@ class GameIconFetcher( ) : Fetcher { override suspend fun fetch(): FetchResult { return DrawableResult( - drawable = decodeGameIcon(game.path)!!.toDrawable(options.context.resources), + drawable = decodeGameIcon(game)!!.toDrawable(options.context.resources), isSampled = false, dataSource = DataSource.DISK ) } - private fun decodeGameIcon(uri: String): Bitmap? { - val data = GameMetadata.getIcon(uri) + private fun decodeGameIcon(game: Game): Bitmap? { + val data = GameMetadata.getIcon(game.path) + gameIconHashes[game.iconCacheKey()] = data.contentHashCode() return BitmapFactory.decodeByteArray( data, 0, @@ -54,7 +67,7 @@ class GameIconFetcher( } class GameIconKeyer : Keyer { - override fun key(data: Game, options: Options): String = data.path + override fun key(data: Game, options: Options): String = data.iconCacheKey() } object GameIconUtils { @@ -71,14 +84,58 @@ object GameIconUtils { .build() fun loadGameIcon(game: Game, imageView: ImageView) { + gameIconTargets[imageView] = GameIconTarget(game) val request = ImageRequest.Builder(YuzuApplication.appContext) .data(game) .target(imageView) .error(R.drawable.default_icon) + .listener( + onSuccess = { _, _ -> + val target = gameIconTargets[imageView] + if (target?.game?.iconCacheKey() == game.iconCacheKey()) { + gameIconHashes[game.iconCacheKey()]?.let { + target.iconHash = it + } + } + }, + onError = { _, _ -> + gameIconTargets[imageView]?.iconHash = null + } + ) .build() imageLoader.enqueue(request) } + fun refreshGameIcon(game: Game) { + val targets = synchronized(gameIconTargets) { + gameIconTargets + .filterValues { it.game.path == game.path && it.game.programId == game.programId } + .keys + .toList() + } + if (targets.isEmpty()) { + return + } + + val iconHash = GameMetadata.getIcon(game.path).contentHashCode() + val targetsToRefresh = targets.filter { gameIconTargets[it]?.iconHash != iconHash } + if (targetsToRefresh.isEmpty()) { + return + } + + imageLoader.memoryCache?.remove(MemoryCache.Key(game.iconCacheKey())) + targetsToRefresh.forEach { imageView -> + imageView.post { + val target = gameIconTargets[imageView] ?: return@post + if (target.game.path == game.path && target.game.programId == game.programId) { + if (target.iconHash != iconHash) { + loadGameIcon(game, imageView) + } + } + } + } + } + suspend fun getGameIcon(lifecycleOwner: LifecycleOwner, game: Game): Bitmap { val request = ImageRequest.Builder(YuzuApplication.appContext) .data(game) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt index 1909a73319..cde084c68f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt @@ -49,6 +49,12 @@ object InputHandler { MotionEvent.AXIS_RTRIGGER ) + // Currently, Android doesn't support Joy-Con D-pad buttons. We fall back to the scan code + private const val LINUX_BUTTON_DPAD_UP = 0x220 + private const val LINUX_BUTTON_DPAD_DOWN = 0x221 + private const val LINUX_BUTTON_DPAD_LEFT = 0x222 + private const val LINUX_BUTTON_DPAD_RIGHT = 0x223 + fun isPhysicalGameController(device: InputDevice?): Boolean { device ?: return false @@ -87,12 +93,25 @@ object InputHandler { NativeInput.onGamePadButtonEvent( controllerData.getGUID(), controllerData.getPort(), - event.keyCode, + getButtonIdFromEvent(event), action ) return true } + fun getButtonIdFromEvent(event: KeyEvent): Int { + if (event.keyCode == 0) { + return when (event.scanCode) { + LINUX_BUTTON_DPAD_UP -> KeyEvent.KEYCODE_DPAD_UP + LINUX_BUTTON_DPAD_DOWN -> KeyEvent.KEYCODE_DPAD_DOWN + LINUX_BUTTON_DPAD_LEFT -> KeyEvent.KEYCODE_DPAD_LEFT + LINUX_BUTTON_DPAD_RIGHT -> KeyEvent.KEYCODE_DPAD_RIGHT + else -> return 0 + } + } + return event.keyCode + } + fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { val controllerData = androidControllers[event.device.controllerNumber] ?: return false diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/LosslessScalingHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/LosslessScalingHelper.kt new file mode 100644 index 0000000000..61f594a124 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/LosslessScalingHelper.kt @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.utils + +import android.net.Uri +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import org.yuzu.yuzu_emu.NativeLibrary +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.YuzuApplication +import java.io.File + +object LosslessScalingHelper { + const val RESULT_OK = 0 + const val RESULT_NOT_INSTALLED = 1 + + private val _statusText = MutableStateFlow("") + val statusText: StateFlow = _statusText.asStateFlow() + + private var installed: Boolean? = null + private var gpuSupported: Boolean? = null + + fun isInstalled(): Boolean = installed ?: refreshStatus() + + fun isSupportedByGpu(): Boolean { + val cached = gpuSupported + if (cached != null) { + return cached + } + val result = NativeLibrary.supportsFrameGeneration() + gpuSupported = result + return result + } + + fun refreshStatus(): Boolean { + val result = NativeLibrary.validateLosslessDll() == RESULT_OK + installed = result + + val context = YuzuApplication.appContext + _statusText.value = if (result) { + context.getString(R.string.lossless_scaling_installed) + } else { + context.getString(R.string.lossless_scaling_not_installed) + } + return result + } + + fun install(source: Uri): Int { + val destination = File(NativeLibrary.getLosslessDllPath()) + destination.parentFile?.mkdirs() + + val copied = FileUtil.copyUriToInternalStorage( + source, + destination.parent!!, + destination.name + ) + if (copied == null) { + refreshStatus() + return RESULT_NOT_INSTALLED + } + + val result = NativeLibrary.prepareLosslessDll() + if (result != RESULT_OK) { + NativeLibrary.removeLosslessDll() + } + refreshStatus() + return result + } + + fun remove(): Boolean { + val removed = NativeLibrary.removeLosslessDll() + refreshStatus() + return removed + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt index d1b5b1373d..10c8998d5c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt @@ -80,6 +80,12 @@ object NativeConfig { @Synchronized external fun setShort(key: String, value: Short) + @Synchronized + external fun getUnsignedShort(key: String, needsGlobal: Boolean): Int + + @Synchronized + external fun setUnsignedShort(key: String, value: Int) + @Synchronized external fun getInt(key: String, needsGlobal: Boolean): Int diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativePostProcessing.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativePostProcessing.kt new file mode 100644 index 0000000000..2c04900064 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativePostProcessing.kt @@ -0,0 +1,242 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.utils + +import org.json.JSONArray +import org.json.JSONObject + +object NativePostProcessing { + const val KIND_BOOL = 0 + const val KIND_INT = 1 + const val KIND_FLOAT = 2 + + const val UI_HIDDEN = 0 + const val UI_SLIDER = 1 + const val UI_DRAG = 2 + const val UI_COMBO = 3 + const val UI_RADIO = 4 + const val UI_CHECKBOX = 5 + const val UI_COLOR = 6 + const val UI_INPUT_BOX = 7 + + external fun getCatalogJson(): String + + external fun getChainJson(): String + + external fun append(file: String, technique: String) + + external fun replace(index: Int, file: String, technique: String) + + external fun remove(index: Int) + + external fun move(index: Int, delta: Int) + + external fun resetValues(index: Int) + + external fun getValue(index: Int, uniform: String, component: Int): Float + + external fun hasValue(index: Int, uniform: String): Boolean + + external fun setValue(index: Int, uniform: String, component: Int, value: Float) + + external fun store() + + external fun reload() + + external fun clearChain() + + external fun getPresetsJson(): String + + external fun getActivePreset(): String + + external fun isPresetModified(): Boolean + + external fun applyPreset(name: String): Boolean + + external fun savePreset(name: String, description: String): Boolean + + external fun deletePreset(name: String): Boolean + + external fun clearPreset() + + external fun isEnabled(): Boolean + + external fun setEnabled(enabled: Boolean) + + external fun getPresetDirectory(): String + + fun persist() { + val perGame = NativeConfig.isPerGameConfigLoaded() + store() + if (perGame) { + NativeConfig.savePerGameConfig() + } else { + NativeConfig.saveGlobalConfig() + } + } + + + external fun getShaderDirectory(): String + + data class Uniform( + val name: String, + val label: String, + val tooltip: String, + val category: String, + val kind: Int, + val uiType: Int, + val components: Int, + val min: Float, + val max: Float, + val step: Float, + val items: List, + val defaults: List + ) { + val steps: Int + get() { + val span = max - min + if (step <= 0f) { + return 1 + } + val count = Math.round(span / step) + if (count < 1) { + return 1 + } + return count + } + + fun describe(position: Int): String { + val value = min + position * step + if (kind == NativePostProcessing.KIND_FLOAT) { + return String.format("%.3f", value) + } + return Math.round(value).toString() + } + + fun defaultAt(component: Int): Float { + if (component < defaults.size) { + return defaults[component] + } + return 0f + } + } + + data class Effect( + val file: String, + val name: String, + val label: String, + val description: String, + val error: String, + val techniques: List, + val uniforms: List + ) { + val valid: Boolean + get() = error.isEmpty() && techniques.isNotEmpty() + } + + data class ChainEntry(val file: String, val technique: String) + + data class Preset( + val name: String, + val description: String, + val bundled: Boolean + ) + + fun presets(): List { + val out = mutableListOf() + val array = JSONArray(getPresetsJson()) + for (i in 0 until array.length()) { + val obj = array.getJSONObject(i) + out.add( + Preset( + name = obj.optString("name"), + description = obj.optString("description"), + bundled = obj.optBoolean("bundled") + ) + ) + } + return out + } + + fun catalog(): List { + val out = mutableListOf() + val array = JSONArray(getCatalogJson()) + for (i in 0 until array.length()) { + val obj = array.getJSONObject(i) + out.add( + Effect( + file = obj.optString("file"), + name = obj.optString("name"), + label = obj.optString("label"), + description = obj.optString("description"), + error = obj.optString("error"), + techniques = obj.optJSONArray("techniques").toStringList(), + uniforms = obj.optJSONArray("uniforms").toUniformList() + ) + ) + } + return out + } + + fun chain(): List { + val out = mutableListOf() + val array = JSONArray(getChainJson()) + for (i in 0 until array.length()) { + val obj = array.getJSONObject(i) + out.add(ChainEntry(obj.optString("file"), obj.optString("technique"))) + } + return out + } + + fun findEffect(file: String): Effect? = catalog().firstOrNull { it.file == file } + + private fun JSONArray?.toStringList(): List { + if (this == null) { + return emptyList() + } + val out = mutableListOf() + for (i in 0 until length()) { + out.add(optString(i)) + } + return out + } + + private fun JSONArray?.toFloatList(): List { + if (this == null) { + return emptyList() + } + val out = mutableListOf() + for (i in 0 until length()) { + out.add(optDouble(i, 0.0).toFloat()) + } + return out + } + + private fun JSONArray?.toUniformList(): List { + if (this == null) { + return emptyList() + } + val out = mutableListOf() + for (i in 0 until length()) { + val obj: JSONObject = optJSONObject(i) ?: continue + out.add( + Uniform( + name = obj.optString("name"), + label = obj.optString("label"), + tooltip = obj.optString("tooltip"), + category = obj.optString("category"), + kind = obj.optInt("kind", KIND_FLOAT), + uiType = obj.optInt("uiType", UI_HIDDEN), + components = obj.optInt("components", 1), + min = obj.optDouble("min", 0.0).toFloat(), + max = obj.optDouble("max", 1.0).toFloat(), + step = obj.optDouble("step", 0.01).toFloat(), + items = obj.optJSONArray("items").toStringList(), + defaults = obj.optJSONArray("defaults").toFloatList() + ) + ) + } + return out + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/CarouselRecyclerView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/CarouselRecyclerView.kt index 24dafbcd32..0c63f1e121 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/CarouselRecyclerView.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/CarouselRecyclerView.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.ui @@ -11,12 +11,17 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.PagerSnapHelper import androidx.recyclerview.widget.RecyclerView import kotlin.math.abs +import kotlin.math.cos +import kotlin.math.pow +import kotlin.math.sin import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.adapters.GameAdapter import androidx.core.view.doOnNextLayout +import androidx.core.view.ViewCompat import org.yuzu.yuzu_emu.YuzuApplication import androidx.preference.PreferenceManager import androidx.core.view.WindowInsetsCompat +import org.yuzu.yuzu_emu.utils.FullscreenHelper /** * CarouselRecyclerView encapsulates all carousel content for the games UI. * It manages overlapping cards, center snapping, custom drawing order, @@ -30,10 +35,13 @@ class CarouselRecyclerView @JvmOverloads constructor( private var overlapFactor: Float = 0f private var overlapPx: Int = 0 - private var bottomInset: Int = -1 + private var bottomInset: Int = 0 + private var latestWindowInsets: WindowInsetsCompat? = null + private var cardGeometryInitialized: Boolean = false private var overlapDecoration: OverlappingDecoration? = null private var pagerSnapHelper: PagerSnapHelper? = null private var scalingScrollListener: OnScrollListener? = null + private var savedItemAnimator: RecyclerView.ItemAnimator? = null companion object { private const val CAROUSEL_CARD_SIZE_FACTOR = "CarouselCardSizeMultiplier" @@ -42,8 +50,13 @@ class CarouselRecyclerView @JvmOverloads constructor( private const val CAROUSEL_OVERLAP_FACTOR = "CarouselOverlapFactor" private const val CAROUSEL_MAX_FLING_COUNT = "CarouselMaxFlingCount" private const val CAROUSEL_FLING_MULTIPLIER = "CarouselFlingMultiplier" - private const val CAROUSEL_CARDS_SCALING_SHAPE = "CarouselCardsScalingShape" - private const val CAROUSEL_CARDS_ALPHA_SHAPE = "CarouselCardsAlphaShape" + private const val CAROUSEL_ARC_ANGLE_STEP_DEGREES = 15.0 + private const val CAROUSEL_ARC_MAX_ANGLE_DEGREES = 165.0 + private const val CAROUSEL_ARC_DEPTH_MAX_ANGLE_DEGREES = 85.0 + private const val CAROUSEL_ARC_DEPTH_STRETCH = 5.0f + private const val CAROUSEL_ARC_X_DEPTH_FACTOR = 0.55f + private const val CAROUSEL_ARC_FADE_OUT_START_DEGREES = 60.0 + private const val CAROUSEL_ARC_FADE_OUT_END_DEGREES = 95.0 const val CAROUSEL_LAST_SCROLL_POSITION = "CarouselLastScrollPosition" const val CAROUSEL_VIEW_TYPE_PORTRAIT = "GamesViewTypePortrait" const val CAROUSEL_VIEW_TYPE_LANDSCAPE = "GamesViewTypeLandscape" @@ -83,6 +96,38 @@ class CarouselRecyclerView @JvmOverloads constructor( init { setChildrenDrawingOrderEnabled(true) + ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets -> + latestWindowInsets = insets + updateCardGeometry() + applyCarouselPadding() + insets + } + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + ViewCompat.requestApplyInsets(this) + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + if (w != oldw || h != oldh) { + updateCardGeometry() + applyCarouselPadding() + } + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + if (isCarouselMode) updateChildScalesAndAlpha() + } + + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + if (hasFocus) { + ViewCompat.requestApplyInsets(this) + post { updateCardGeometry() } + } } override fun setAdapter(adapter: Adapter<*>?) { @@ -95,6 +140,8 @@ class CarouselRecyclerView @JvmOverloads constructor( super.setAdapter(adapter) (adapter as? GameAdapter)?.registerAdapterDataObserver(carouselAdapterObserver) + updateCardGeometry() + applyCarouselPadding() } private fun calculateCenter(width: Int, paddingStart: Int, paddingEnd: Int): Int { @@ -160,46 +207,52 @@ class CarouselRecyclerView @JvmOverloads constructor( } } - fun shapingFunction(x: Float, option: Int = 0): Float { - return when (option) { - 0 -> 1f // Off - 1 -> 1f - x // linear descending - 2 -> (1f - x) * (1f - x) // Ease out - 3 -> if (x < 0.05f) 1f else (1f - x) * 0.8f - 4 -> kotlin.math.cos(x * Math.PI).toFloat() // Cosine - 5 -> kotlin.math.cos((1.5f * x).coerceIn(0f, 1f) * Math.PI).toFloat() // Cosine 1.5x trimmed - else -> 1f // Default to Off - } - } - fun updateChildScaleAndAlphaForPosition(child: View) { val cardSize = (adapter as? GameAdapter ?: return).cardSize val position = getChildViewHolder(child).bindingAdapterPosition if (position == RecyclerView.NO_POSITION || cardSize <= 0) { return // No valid position or card size } - child.layoutParams.width = cardSize - child.layoutParams.height = cardSize + val layoutParams = child.layoutParams + if (layoutParams.width != cardSize || layoutParams.height != cardSize) { + child.layoutParams = layoutParams.apply { + width = cardSize + height = cardSize + } + } + + val signedDistance = getChildDistanceToCenter(child) + val itemStep = (cardSize - overlapPx).toFloat().coerceAtLeast(1f) + val angleStep = Math.toRadians(CAROUSEL_ARC_ANGLE_STEP_DEGREES).toFloat() + val maxAngle = Math.toRadians(CAROUSEL_ARC_MAX_ANGLE_DEGREES).toFloat() + val depthMaxAngle = Math.toRadians(CAROUSEL_ARC_DEPTH_MAX_ANGLE_DEGREES).toFloat() + val fadeOutStartAngle = Math.toRadians(CAROUSEL_ARC_FADE_OUT_START_DEGREES).toFloat() + val fadeOutEndAngle = Math.toRadians(CAROUSEL_ARC_FADE_OUT_END_DEGREES).toFloat() + val angle = (signedDistance / itemStep * angleStep).coerceIn(-maxAngle, maxAngle) + val arcRadius = itemStep / angleStep + val arcX = sin(angle) * arcRadius + val absoluteAngle = abs(angle) + val rawDepthInput = ((1f - cos(absoluteAngle)) / (1f - cos(depthMaxAngle))) + .coerceIn(0f, 1f) + val easedDepthTail = Math.pow( + (1f - rawDepthInput).toDouble(), + CAROUSEL_ARC_DEPTH_STRETCH.toDouble() + ).toFloat() + val depthInput = (1f - easedDepthTail).coerceIn(0f, 1f) + val projectedArcX = arcX * (1f - rawDepthInput * CAROUSEL_ARC_X_DEPTH_FACTOR) + + child.animate().cancel() + child.translationX = projectedArcX - signedDistance - val center = getRecyclerViewCenter() - val distance = abs(getChildDistanceToCenter(child)) val internalBorderScale = resources.getFraction(R.fraction.carousel_bordercards_scale, 1, 1) val borderScale = preferences.getFloat(CAROUSEL_BORDERCARDS_SCALE, internalBorderScale).coerceIn( 0f, 1f ) - val shapeInput = (distance / center).coerceIn(0f, 1f) - val internalShapeSetting = resources.getInteger(R.integer.carousel_cards_scaling_shape) - val scalingShapeSetting = preferences.getInt( - CAROUSEL_CARDS_SCALING_SHAPE, - internalShapeSetting - ) - val shapedScaling = shapingFunction(shapeInput, scalingShapeSetting) + val shapedScaling = 1f - depthInput val scale = (borderScale + (1f - borderScale) * shapedScaling).coerceIn(0f, 1f) - val maxDistance = width / 2f - val alphaInput = (distance / maxDistance).coerceIn(0f, 1f) val internalBordersAlpha = resources.getFraction( R.fraction.carousel_bordercards_alpha, 1, @@ -209,15 +262,12 @@ class CarouselRecyclerView @JvmOverloads constructor( 0f, 1f ) - val internalAlphaShapeSetting = resources.getInteger(R.integer.carousel_cards_alpha_shape) - val alphaShapeSetting = preferences.getInt( - CAROUSEL_CARDS_ALPHA_SHAPE, - internalAlphaShapeSetting - ) - val shapedAlpha = shapingFunction(alphaInput, alphaShapeSetting) - val alpha = (borderAlpha + (1f - borderAlpha) * shapedAlpha).coerceIn(0f, 1f) + val shapedAlpha = cos(depthInput * Math.PI).toFloat() + val baseAlpha = (borderAlpha + (1f - borderAlpha) * shapedAlpha).coerceIn(0f, 1f) + val rearPresence = (1f - (absoluteAngle - fadeOutStartAngle) / + (fadeOutEndAngle - fadeOutStartAngle)).coerceIn(0f, 1f) + val alpha = (baseAlpha * rearPresence).coerceIn(0f, 1f) - child.animate().cancel() child.alpha = alpha child.scaleX = scale child.scaleY = scale @@ -242,38 +292,71 @@ class CarouselRecyclerView @JvmOverloads constructor( } } - fun notifyInsetsReady(newBottomInset: Int) { - if (bottomInset != newBottomInset) { - bottomInset = newBottomInset - } - - if (isCarouselMode) { - setupCarousel(true) + private fun resolveBottomInset(windowInsets: WindowInsetsCompat): Int { + val navigationBottom = if (FullscreenHelper.isFullscreenEnabled(context)) { + 0 } else { - setupCarousel(false) + windowInsets.getInsetsIgnoringVisibility(WindowInsetsCompat.Type.navigationBars()).bottom } + val gestureInsets = windowInsets.getInsetsIgnoringVisibility( + WindowInsetsCompat.Type.systemGestures() + ) + val cutoutInsets = windowInsets.getInsetsIgnoringVisibility( + WindowInsetsCompat.Type.displayCutout() + ) + return maxOf(navigationBottom, gestureInsets.bottom, cutoutInsets.bottom) } - fun notifyLaidOut(fallBackBottomInset: Int) { - if (bottomInset < 0) bottomInset = fallBackBottomInset - var gameAdapter = adapter as? GameAdapter ?: return - var newCardSize = cardSize(bottomInset) - if (gameAdapter.cardSize != newCardSize) { - gameAdapter.setCardSize(newCardSize) - } + private fun updateCardGeometry() { + if (!isCarouselMode || height <= 0) return - if (isCarouselMode) { - setupCarousel(true) - } - } + val gameAdapter = adapter as? GameAdapter ?: return + val windowInsets = latestWindowInsets ?: ViewCompat.getRootWindowInsets(this) ?: return - fun cardSize(bottomInset: Int): Int { + if (cardGeometryInitialized && !hasWindowFocus()) return + + val newBottomInset = resolveBottomInset(windowInsets).coerceIn(0, height) val internalFactor = resources.getFraction(R.fraction.carousel_card_size_factor, 1, 1) val userFactor = preferences.getFloat(CAROUSEL_CARD_SIZE_FACTOR, internalFactor).coerceIn( 0f, 1f ) - return (userFactor * (height - bottomInset)).toInt() + val screenWidth = resources.displayMetrics.widthPixels.toFloat() + val screenHeight = resources.displayMetrics.heightPixels.toFloat() + val aspectFactor = ((screenWidth / screenHeight) / (20f / 9f)) + .pow(0.75f) + .coerceIn(0.5f, 1f) + val newCardSize = minOf( + (height * userFactor).toInt(), + height - newBottomInset, + (height * aspectFactor).toInt() + ) + if (newCardSize <= 0) return + val insetChanged = bottomInset != newBottomInset + val cardSizeChanged = gameAdapter.cardSize != newCardSize + + bottomInset = newBottomInset + cardGeometryInitialized = true + + if (cardSizeChanged) gameAdapter.setCardSize(newCardSize) + if (insetChanged || cardSizeChanged) setupCarousel(true) + } + + private fun applyCarouselPadding() { + if (!isCarouselMode) return + + val gameAdapter = adapter as? GameAdapter ?: return + val cardSize = gameAdapter.cardSize + if (cardSize <= 0 || bottomInset < 0) return + + val topPadding = ((height - bottomInset - cardSize) / 2).coerceAtLeast(0) + val sidePadding = (width - cardSize) / 2 + if (paddingLeft != sidePadding || paddingTop != topPadding || + paddingRight != sidePadding || paddingBottom != 0 + ) { + setPadding(sidePadding, topPadding, sidePadding, 0) + } + clipToPadding = false } fun setupCarousel(enabled: Boolean) { @@ -282,6 +365,13 @@ class CarouselRecyclerView @JvmOverloads constructor( if (gameAdapter.cardSize == 0) return if (bottomInset < 0) return + itemAnimator?.let { + if (savedItemAnimator == null) { + savedItemAnimator = it + } + itemAnimator = null + } + useCustomDrawingOrder = true val cardSize = gameAdapter.cardSize @@ -295,9 +385,6 @@ class CarouselRecyclerView @JvmOverloads constructor( internalFlingMultiplier ).coerceIn(1f, 5f) - // Detach SnapHelper during setup - pagerSnapHelper?.attachToRecyclerView(null) - // Add overlap decoration if not present if (overlapDecoration == null) { overlapDecoration = OverlappingDecoration(overlapPx) @@ -315,12 +402,7 @@ class CarouselRecyclerView @JvmOverloads constructor( addOnScrollListener(scalingScrollListener!!) } - if (cardSize > 0) { - val topPadding = ((height - bottomInset - cardSize) / 2).coerceAtLeast(0) // Center vertically - val sidePadding = (width - cardSize) / 2 // Center first/last card - setPadding(sidePadding, topPadding, sidePadding, 0) - clipToPadding = false - } + applyCarouselPadding() if (pagerSnapHelper == null) { pagerSnapHelper = CenterPagerSnapHelper() @@ -336,6 +418,13 @@ class CarouselRecyclerView @JvmOverloads constructor( // Detach PagerSnapHelper pagerSnapHelper?.attachToRecyclerView(null) pagerSnapHelper = null + savedItemAnimator?.let { + if (itemAnimator == null) { + itemAnimator = it + } + savedItemAnimator = null + } + cardGeometryInitialized = false useCustomDrawingOrder = false // Reset padding and fling setPadding(0, 0, 0, 0) @@ -344,6 +433,7 @@ class CarouselRecyclerView @JvmOverloads constructor( // Reset scaling for (i in 0 until childCount) { val child = getChildAt(i) + child?.translationX = 0f child?.scaleX = 1f child?.scaleY = 1f child?.alpha = 1f diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt index c68e206d24..337d9fea1b 100644 --- a/src/android/app/src/main/jni/CMakeLists.txt +++ b/src/android/app/src/main/jni/CMakeLists.txt @@ -17,19 +17,22 @@ add_library(yuzu-android SHARED android_config.cpp android_config.h native_input.cpp + native_post_processing.cpp ) 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 video_core) -target_link_libraries(yuzu-android PRIVATE android camera2ndk EGL glad jnigraphics log) +target_link_libraries(yuzu-android PRIVATE android camera2ndk EGL glad jnigraphics log GLESv2) if (ARCHITECTURE_arm64) target_link_libraries(yuzu-android PRIVATE adrenotools) endif() -target_link_libraries(yuzu-android PRIVATE OpenSSL::SSL cpp-jwt::cpp-jwt) +target_link_libraries(yuzu-android PRIVATE ${FFmpeg_LIBRARIES} OpenSSL::SSL cpp-jwt::cpp-jwt) if (ENABLE_UPDATE_CHECKER) target_compile_definitions(yuzu-android PUBLIC ENABLE_UPDATE_CHECKER) endif() set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} yuzu-android) + +target_link_options(yuzu-android PRIVATE "-Wl,-Bsymbolic") diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp index f697084348..6548667de4 100644 --- a/src/android/app/src/main/jni/android_config.cpp +++ b/src/android/app/src/main/jni/android_config.cpp @@ -19,6 +19,9 @@ AndroidConfig::AndroidConfig(const std::string& config_name, ConfigType config_t } void AndroidConfig::ReloadAllValues() { + // Ensure the INI file is current before reloading values. + SetUpIni(); + Reload(); ReadAndroidValues(); SaveAndroidValues(); @@ -30,8 +33,8 @@ void AndroidConfig::SaveAllValues() { } void AndroidConfig::ReadAndroidValues() { + ReadAndroidUIValues(); if (global) { - ReadAndroidUIValues(); ReadUIValues(); BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage)); Settings::values.ext_content_from_game_dirs = ReadBooleanSetting( @@ -220,8 +223,8 @@ void AndroidConfig::ReadAndroidControlValues() { } void AndroidConfig::SaveAndroidValues() { + SaveAndroidUIValues(); if (global) { - SaveAndroidUIValues(); SaveUIValues(); SaveOverlayValues(); } diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h index 37da651519..7cc3ad22d5 100644 --- a/src/android/app/src/main/jni/android_settings.h +++ b/src/android/app/src/main/jni/android_settings.h @@ -147,6 +147,13 @@ namespace AndroidSettings { &show_performance_overlay}; + Settings::SwitchableSetting pipeline_worker_count{linkage, 2, "pipeline_worker_count", + Settings::Category::Android, + Settings::Specialization::Default, + true, + true}; + + Settings::Setting show_input_overlay{linkage, true, "show_input_overlay", Settings::Category::Overlay}; Settings::Setting overlay_snap_to_grid{linkage, false, "overlay_snap_to_grid", diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp index 6886dac172..8dfea40693 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.cpp +++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp @@ -124,9 +124,18 @@ float EmuWindow_Android::GetFrameTimeVerifiedHint() const { return QuantizeFrameRateHint(verified_rate); } +float EmuWindow_Android::GetPresentedFrameMultiplier() { + if (!Settings::values.frame_gen.GetValue()) { + return 1.0f; + } + return static_cast(std::clamp(Settings::values.frame_gen_multiplier.GetValue(), 2, 4)); +} + float EmuWindow_Android::GetFrameRateHint() const { - const float observed_rate = std::clamp(m_smoothed_present_rate, 0.0f, 240.0f); - const float frame_time_verified_hint = GetFrameTimeVerifiedHint(); + const float presented_multiplier = GetPresentedFrameMultiplier(); + const float observed_rate = + std::clamp(m_smoothed_present_rate * presented_multiplier, 0.0f, 240.0f); + const float frame_time_verified_hint = GetFrameTimeVerifiedHint() * presented_multiplier; if (m_last_frame_rate_hint > 0.0f && observed_rate > 0.0f) { const float tolerance = std::max(m_last_frame_rate_hint * 0.12f, 4.0f); @@ -150,9 +159,9 @@ float EmuWindow_Android::GetFrameRateHint() const { return frame_time_verified_hint; } - constexpr float NominalFrameRate = 60.0f; + const float nominal_rate = 60.0f * presented_multiplier; if (!Settings::values.use_speed_limit.GetValue()) { - return NominalFrameRate; + return QuantizeFrameRateHint(nominal_rate); } const u16 speed_limit = Settings::SpeedLimit(); @@ -161,7 +170,7 @@ float EmuWindow_Android::GetFrameRateHint() const { } const float speed_limited_rate = - NominalFrameRate * (static_cast(std::min(speed_limit, 100)) / 100.0f); + nominal_rate * (static_cast(std::min(speed_limit, 100)) / 100.0f); return QuantizeFrameRateHint(speed_limited_rate); } diff --git a/src/android/app/src/main/jni/emu_window/emu_window.h b/src/android/app/src/main/jni/emu_window/emu_window.h index b73e8b9b4d..fca83b91fa 100644 --- a/src/android/app/src/main/jni/emu_window/emu_window.h +++ b/src/android/app/src/main/jni/emu_window/emu_window.h @@ -61,6 +61,7 @@ private: void UpdateObservedFrameRate(); [[nodiscard]] float GetFrameRateHint() const; [[nodiscard]] float GetFrameTimeVerifiedHint() const; + [[nodiscard]] static float GetPresentedFrameMultiplier(); [[nodiscard]] static float QuantizeFrameRateHint(float frame_rate); float m_window_width{}; diff --git a/src/android/app/src/main/jni/game_metadata.cpp b/src/android/app/src/main/jni/game_metadata.cpp index 9acba456f1..9b6cd44e9f 100644 --- a/src/android/app/src/main/jni/game_metadata.cpp +++ b/src/android/app/src/main/jni/game_metadata.cpp @@ -20,132 +20,98 @@ struct RomMetadata { std::vector icon; bool isHomebrew; }; +static ::Common::unordered_map m_rom_metadata_cache; -ankerl::unordered_dense::map m_rom_metadata_cache; +static RomMetadata CacheRomMetadata(const std::string& path) { + auto& instance = EmulationSession::GetInstance(); + const auto file = Core::GetGameFileFromPath(instance.System().GetFilesystem(), path); + if (auto loader = Loader::GetLoader(instance.System(), file, 0, 0); loader) { + RomMetadata entry; + loader->ReadTitle(entry.title); + loader->ReadProgramId(entry.programId); + loader->ReadIcon(entry.icon); -RomMetadata CacheRomMetadata(const std::string& path) { - const auto file = - Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path); - auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0); + const FileSys::PatchManager pm{ + entry.programId, + instance.System().GetFileSystemController(), + instance.System().GetContentProvider() + }; + const auto control = pm.GetControlMetadata(); - RomMetadata entry; - loader->ReadTitle(entry.title); - loader->ReadProgramId(entry.programId); - loader->ReadIcon(entry.icon); - - const FileSys::PatchManager pm{ - entry.programId, EmulationSession::GetInstance().System().GetFileSystemController(), - EmulationSession::GetInstance().System().GetContentProvider()}; - const auto control = pm.GetControlMetadata(); - - if (control.first != nullptr) { - entry.developer = control.first->GetDeveloperName(); - entry.version = control.first->GetVersionString(); - } else { - FileSys::NACP nacp; - if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) { - entry.developer = nacp.GetDeveloperName(); + if (control.first != nullptr) { + entry.developer = control.first->GetDeveloperName(); + entry.version = control.first->GetVersionString(); } else { - entry.developer = ""; + FileSys::NACP nacp{}; + entry.developer = loader->ReadControlData(nacp) == Loader::ResultStatus::Success + ? nacp.GetDeveloperName() + : ""; + entry.version = "1.0.0"; } - - entry.version = "1.0.0"; + if (loader->GetFileType() == Loader::FileType::NRO) { + auto loader_nro = reinterpret_cast(loader.get()); + entry.isHomebrew = loader_nro->IsHomebrew(); + } else { + entry.isHomebrew = false; + } + m_rom_metadata_cache[path] = entry; + return entry; } - - if (loader->GetFileType() == Loader::FileType::NRO) { - auto loader_nro = reinterpret_cast(loader.get()); - entry.isHomebrew = loader_nro->IsHomebrew(); - } else { - entry.isHomebrew = false; - } - - m_rom_metadata_cache[path] = entry; - - return entry; + return {}; } -RomMetadata GetRomMetadata(const std::string& path, bool reload = false) { - if (reload) { +static RomMetadata GetRomMetadata(const std::string& path, bool reload = false) { + if (reload) return CacheRomMetadata(path); - } - - if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) { - return search->second; - } - + if (auto it = m_rom_metadata_cache.find(path); it != m_rom_metadata_cache.end()) + return it->second; return CacheRomMetadata(path); } extern "C" { -jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobject obj, - jstring jpath) { - const auto file = EmulationSession::GetInstance().System().GetFilesystem()->OpenFile( - Common::Android::GetJString(env, jpath), FileSys::OpenMode::Read); - if (!file) { - return false; +jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobject obj, jstring jpath) { + if (auto const file = EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(Common::Android::GetJString(env, jpath), FileSys::OpenMode::Read); file) { + if (auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file); loader) { + auto const file_type = loader->GetFileType(); + if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) + return false; + if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI || + file_type == Loader::FileType::NSZ || file_type == Loader::FileType::XCZ) && + !Loader::IsBootableGameContainer(file, file_type)) + return false; + u64 program_id = 0; + return loader->ReadProgramId(program_id) == Loader::ResultStatus::Success; + } } - - auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file); - if (!loader) { - return false; - } - - const auto file_type = loader->GetFileType(); - if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { - return false; - } - - if ((file_type == Loader::FileType::NSP || file_type == Loader::FileType::XCI) && - !Loader::IsBootableGameContainer(file, file_type)) { - return false; - } - - u64 program_id = 0; - Loader::ResultStatus res = loader->ReadProgramId(program_id); - if (res != Loader::ResultStatus::Success) { - return false; - } - return true; + return false; } -jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj, - jstring jpath) { - return Common::Android::ToJString( - env, GetRomMetadata(Common::Android::GetJString(env, jpath)).title); +jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj, jstring jpath) { + return Common::Android::ToJString(env, GetRomMetadata(Common::Android::GetJString(env, jpath)).title); } -jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getProgramId(JNIEnv* env, jobject obj, - jstring jpath) { - return Common::Android::ToJString( - env, std::to_string(GetRomMetadata(Common::Android::GetJString(env, jpath)).programId)); +jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getProgramId(JNIEnv* env, jobject obj, jstring jpath) { + return Common::Android::ToJString(env, std::to_string(GetRomMetadata(Common::Android::GetJString(env, jpath)).programId)); } -jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, jobject obj, - jstring jpath) { - return Common::Android::ToJString( - env, GetRomMetadata(Common::Android::GetJString(env, jpath)).developer); +jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, jobject obj, jstring jpath) { + return Common::Android::ToJString(env, GetRomMetadata(Common::Android::GetJString(env, jpath)).developer); } -jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj, - jstring jpath, jboolean jreload) { - return Common::Android::ToJString( - env, GetRomMetadata(Common::Android::GetJString(env, jpath), jreload).version); +jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj, jstring jpath, jboolean jreload) { + return Common::Android::ToJString(env, GetRomMetadata(Common::Android::GetJString(env, jpath), jreload).version); } -jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj, - jstring jpath) { +jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj, jstring jpath) { auto icon_data = GetRomMetadata(Common::Android::GetJString(env, jpath)).icon; - jbyteArray icon = env->NewByteArray(static_cast(icon_data.size())); - env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon), - reinterpret_cast(icon_data.data())); + jbyteArray icon = env->NewByteArray(jsize(icon_data.size())); + env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon), reinterpret_cast(icon_data.data())); return icon; } -jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, jobject obj, - jstring jpath) { - return static_cast( - GetRomMetadata(Common::Android::GetJString(env, jpath)).isHomebrew); +jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, jobject obj, jstring jpath) { + return jboolean(GetRomMetadata(Common::Android::GetJString(env, jpath)).isHomebrew); } void Java_org_yuzu_yuzu_1emu_utils_GameMetadata_resetMetadata(JNIEnv* env, jobject obj) { diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index abfc428919..d4f7689934 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -36,17 +36,22 @@ #include #include +extern "C" { +#include +} + #include "common/android/multiplayer/multiplayer.h" #include "common/android/android_common.h" #include "common/android/id_cache.h" -#include "common/detached_tasks.h" #include "common/dynamic_library.h" +#include "common/fs/fs_util.h" #include "common/fs/path_util.h" #include "common/logging.h" #include "common/scm_rev.h" #include "common/scope_exit.h" #include "common/settings.h" #include "common/string_util.h" +#include "common/thread.h" #include "frontend_common/play_time_manager.h" #include "core/constants.h" #include "core/core.h" @@ -71,6 +76,7 @@ #include "core/frontend/applets/software_keyboard.h" #include "core/frontend/applets/web_browser.h" #include "common/android/applets/web_browser.h" +#include "core/file_sys/common_funcs.h" #include "core/hle/service/am/applet_manager.h" #include "core/hle/service/am/frontend/applets.h" #include "core/hle/service/filesystem/filesystem.h" @@ -86,6 +92,7 @@ #include "hid_core/hid_types.h" #include "input_common/drivers/virtual_amiibo.h" #include "jni/native.h" +#include "video_core/frame_gen/lossless_dll.h" #include "video_core/renderer_base.h" #include "video_core/renderer_vulkan/renderer_vulkan.h" #include "video_core/capture.h" @@ -305,11 +312,23 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string ConfigureFilesystemProvider(filepath); // Load the ROM. + const u64 previous_program_id = + program_index != 0 && m_next_program_id.load() > + static_cast(Service::AM::AppletProgramId::MaxProgramId) + ? m_next_program_id.load() + : 0; + Service::AM::FrontendAppletParameters params{ + .program_id = previous_program_id, .applet_id = static_cast(m_applet_id), .launch_type = frontend_initiated ? Service::AM::LaunchType::FrontendInitiated : Service::AM::LaunchType::ApplicationInitiated, .program_index = static_cast(program_index), + .previous_program_index = + previous_program_id != 0 + ? static_cast(previous_program_id - + FileSys::GetBaseTitleID(previous_program_id)) + : -1, }; m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath, params); @@ -322,8 +341,12 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string m_system.GetCpuManager().OnGpuReady(); m_system.RegisterExitCallback([&] { HaltEmulation(); }); + m_system.RegisterApplicationChangedCallback( + [&](u64 changed_program_id) { RequestDiskShaderCacheReload(changed_program_id); }); + // Register an ExecuteProgram callback such that Core can execute a sub-program m_system.RegisterExecuteProgramCallback([&](std::size_t program_index_) { + m_next_program_id = m_system.GetApplicationProcessProgramID(); m_next_program_index = program_index_; EmulationSession::GetInstance().HaltEmulation(); }); @@ -352,7 +375,6 @@ void EmulationSession::ShutdownEmulation() { if (m_load_result == Core::SystemResultStatus::Success) { m_system.DetachDebugger(); m_system.ShutdownMainProcess(); - m_detached_tasks.WaitForAllTasks(); m_load_result = Core::SystemResultStatus::ErrorNotInitialized; m_window.reset(); OnEmulationStopped(Core::SystemResultStatus::Success); @@ -402,20 +424,58 @@ void EmulationSession::RunEmulation() { } while (true) { + std::optional reload_title; { [[maybe_unused]] std::unique_lock lock(m_mutex); - if (m_cv.wait_for(lock, std::chrono::milliseconds(800), - [&]() { return !m_is_running; })) { - // Emulation halted. - break; + if (m_cv.wait_for(lock, std::chrono::milliseconds(800), [&]() { + return !m_is_running || m_pending_shader_cache_title.has_value(); + })) { + if (!m_is_running) { + break; + } + reload_title = std::exchange(m_pending_shader_cache_title, std::nullopt); } } + + if (reload_title.has_value()) + ReloadDiskShaderCache(*reload_title); } // Reset current applet ID. m_applet_id = static_cast(Service::AM::AppletId::Application); } +void EmulationSession::RequestDiskShaderCacheReload(u64 program_id) { + { + std::scoped_lock lock(m_mutex); + m_pending_shader_cache_title = program_id; + } + m_cv.notify_one(); +} + +void EmulationSession::ReloadDiskShaderCache(u64 program_id) { + if (!Settings::values.use_disk_shader_cache.GetValue()) + return; + + LOG_INFO(Frontend, "Reloading disk shader cache for {:016X}", program_id); + + const bool was_paused = m_is_paused; + + m_system.Pause(); + m_system.GPU().WaitForIdle(); + m_system.GPU().ObtainContext(); + + LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); + m_system.Renderer().ReadRasterizer()->LoadDiskResources(program_id, std::stop_token{}, + LoadDiskCacheProgress); + LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); + + m_system.GPU().ReleaseContext(); + + if (!was_paused) + m_system.Run(); +} + Common::Android::SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() { return m_software_keyboard; } @@ -680,6 +740,13 @@ const char* fallback_cpu_detection() { } // namespace +extern "C" { +jint InitFFmpegOnLoad(JavaVM* vm) { + av_jni_set_java_vm(vm, nullptr); + return 0; +} +} + extern "C" { void Java_org_yuzu_yuzu_1emu_NativeLibrary_surfaceChanged(JNIEnv* env, jobject instance, @@ -721,7 +788,7 @@ int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj, jstring jprogramId, jstring jupdatePath) { - u64 program_id = EmulationSession::GetProgramId(env, jprogramId); + const u64 program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId)); std::string updatePath = Common::Android::GetJString(env, jupdatePath); std::shared_ptr nsp = std::make_shared( EmulationSession::GetInstance().System().GetFilesystem()->OpenFile( @@ -729,7 +796,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* en for (const auto& item : nsp->GetNCAs()) { for (const auto& nca_details : item.second) { if (nca_details.second->GetName().ends_with(".cnmt.nca")) { - auto update_id = nca_details.second->GetTitleId() & ~0xFFFULL; + const auto update_id = FileSys::GetBaseTitleID(nca_details.second->GetTitleId()); if (update_id == program_id) { return true; } @@ -988,7 +1055,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuSummary(JNIEnv* env, jobject FILE* f = std::fopen(CPUINFO_PATH, "r"); if (!f) return Common::Android::ToJString(env, result); - char buf[512]; + char buf[4096]; if (f) { std::set feature_set; @@ -1019,7 +1086,13 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuSummary(JNIEnv* env, jobject bool has_dotprod = feature_set.count("asimddp") || feature_set.count("dotprod"); bool has_i8mm = feature_set.count("i8mm"); bool has_bf16 = feature_set.count("bf16"); + bool has_fp16 = feature_set.count("fphp") || feature_set.count("asimdhp"); bool has_atomics = feature_set.count("atomics") || feature_set.count("lse"); + bool has_lse2 = feature_set.count("uscat"); + bool has_rcpc = feature_set.count("lrcpc"); + bool has_rcpc2 = feature_set.count("ilrcpc"); + bool has_flagm = feature_set.count("flagm"); + bool has_flagm2 = feature_set.count("flagm2"); std::string features; if (has_neon || has_fp) { @@ -1027,6 +1100,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuSummary(JNIEnv* env, jobject if (has_dotprod) features += "+DP"; if (has_i8mm) features += "+I8MM"; if (has_bf16) features += "+BF16"; + if (has_fp16) features += "+FP16"; } if (has_sve) { @@ -1043,6 +1117,19 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuSummary(JNIEnv* env, jobject if (has_atomics) { if (!features.empty()) features += " | "; features += "LSE"; + if (has_lse2) features += "2"; + } + + if (has_rcpc) { + if (!features.empty()) features += " | "; + features += "RCpc"; + if (has_rcpc2) features += "2"; + } + + if (has_flagm) { + if (!features.empty()) features += " | "; + features += "FlagM"; + if (has_flagm2) features += "2"; } if (!features.empty()) { @@ -1080,6 +1167,38 @@ VkPhysicalDeviceProperties GetVulkanDeviceProperties() { const Vulkan::vk::PhysicalDevice physical_device(physical_devices[0], dld); return physical_device.GetProperties(); } + +bool GetFrameGenerationSupport() { + Common::DynamicLibrary library; + if (!library.Open("libvulkan.so")) { + return false; + } + + Vulkan::vk::InstanceDispatch dld; + const auto instance = Vulkan::CreateInstance(library, dld, VK_API_VERSION_1_1); + const auto physical_devices = instance.EnumeratePhysicalDevices(); + if (physical_devices.empty()) { + return false; + } + + const Vulkan::vk::PhysicalDevice physical_device(physical_devices[0], dld); + + VkPhysicalDeviceShaderFloat16Int8Features float16_int8{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES, + .pNext = nullptr, + }; + VkPhysicalDeviceVulkanMemoryModelFeatures memory_model{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES, + .pNext = &float16_int8, + }; + VkPhysicalDeviceFeatures2 features{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, + .pNext = &memory_model, + }; + physical_device.GetFeatures2(features); + + return memory_model.vulkanMemoryModel == VK_TRUE && float16_int8.shaderFloat16 == VK_TRUE; +} } // namespace jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getVulkanDriverVersion(JNIEnv* env, jobject jobj) { @@ -1155,6 +1274,14 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getVulkanApiVersion(JNIEnv* env, j } } +jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_supportsFrameGeneration(JNIEnv* env, jobject jobj) { + try { + return static_cast(GetFrameGenerationSupport()); + } catch (...) { + return static_cast(false); + } +} + jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGpuModel(JNIEnv* env, jobject jobj) { const auto props = GetVulkanDeviceProperties(); if (props.deviceID == 0) { @@ -1173,8 +1300,12 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj Settings::LogSettings(); } -jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobject jobj, jint index) { - return static_cast(Settings::getDebugKnobAt(static_cast(index))); +void Java_org_yuzu_yuzu_1emu_NativeLibrary_refreshThreadPolicies(JNIEnv* env, jobject jobj) { + Common::RefreshThreadPolicies(); +} + +jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDebugKnobAt(JNIEnv* env, jobject jobj, jint index) { + return static_cast(Settings::GetDebugKnobAt(static_cast(index))); } void Java_org_yuzu_yuzu_1emu_NativeLibrary_setTurboSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) { @@ -1376,6 +1507,39 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_installKeys(JNIEnv* env, jclass clazz return static_cast(FirmwareManager::InstallKeys(path, ext)); } +jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getLosslessDllPath(JNIEnv* env, jclass clazz) { +#ifdef HAS_LSFG + const auto path = VideoCore::FrameGen::GetLosslessDllPath(); + return Common::Android::ToJString(env, Common::FS::PathToUTF8String(path)); +#else + return Common::Android::ToJString(env, ""); +#endif +} + +jint Java_org_yuzu_yuzu_1emu_NativeLibrary_validateLosslessDll(JNIEnv* env, jclass clazz) { +#ifdef HAS_LSFG + return static_cast(VideoCore::FrameGen::GetInstalledLosslessStatus()); +#else + return static_cast(VideoCore::FrameGen::LosslessStatus::NotInstalled); +#endif +} + +jint Java_org_yuzu_yuzu_1emu_NativeLibrary_prepareLosslessDll(JNIEnv* env, jclass clazz) { +#ifdef HAS_LSFG + return static_cast(VideoCore::FrameGen::BuildShaderCache()); +#else + return static_cast(VideoCore::FrameGen::LosslessStatus::NotInstalled); +#endif +} + +jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_removeLosslessDll(JNIEnv* env, jclass clazz) { +#ifdef HAS_LSFG + return static_cast(VideoCore::FrameGen::RemoveInstalledLosslessDll()); +#else + return static_cast(false); +#endif +} + jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env, jobject jobj, jstring jpath, jstring jprogramId) { @@ -1570,6 +1734,15 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_loadAmiibo(JNIEnv* env, jobject jobj, return static_cast(info); } +extern "C" JNIEXPORT void JNICALL +Java_org_yuzu_yuzu_1emu_NativeLibrary_initJvm(JNIEnv *env, jclass clazz) { + JavaVM *vm; + if (env->GetJavaVM(&vm) != JNI_OK) + return; + + Common::Android::Initialize(vm, env); +} + JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initMultiplayer( JNIEnv* env, [[maybe_unused]] jobject obj) { diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index f2e5c2cfd6..5794660bbd 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -4,9 +4,10 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + #include #include "common/android/applets/software_keyboard.h" -#include "common/detached_tasks.h" #include "core/core.h" #include "core/file_sys/registered_cache.h" #include "core/hle/service/acc/profile_manager.h" @@ -45,6 +46,7 @@ public: void HaltEmulation(); void RunEmulation(); void ShutdownEmulation(); + void RequestDiskShaderCacheReload(u64 program_id); const Core::PerfStatsResults& PerfStats(); int ShadersBuilding(); @@ -66,6 +68,7 @@ private: static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max); static void OnEmulationStopped(Core::SystemResultStatus result); static void ChangeProgram(std::size_t program_index); + void ReloadDiskShaderCache(u64 program_id); private: // Window management @@ -75,7 +78,6 @@ private: // Core emulation Core::System m_system; InputCommon::InputSubsystem m_input_subsystem; - Common::DetachedTasks m_detached_tasks; Core::PerfStatsResults m_perf_stats{}; int m_shaders_building{0}; std::shared_ptr m_vfs; @@ -85,6 +87,7 @@ private: Common::Android::SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{}; std::unique_ptr m_manual_provider; int m_applet_id{1}; + std::optional m_pending_shader_cache_title; // GPU driver parameters std::shared_ptr m_vulkan_library; @@ -95,4 +98,5 @@ private: // Program index for next boot std::atomic m_next_program_index = -1; + std::atomic m_next_program_id = 0; }; diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp index 647fdbb997..1b8e343986 100644 --- a/src/android/app/src/main/jni/native_config.cpp +++ b/src/android/app/src/main/jni/native_config.cpp @@ -15,10 +15,22 @@ #include "frontend_common/config.h" #include "frontend_common/settings_generator.h" #include "native.h" +#ifdef HAS_RESHADE +#include "video_core/post_processing/fx_chain.h" +#endif std::unique_ptr global_config; std::unique_ptr per_game_config; +#ifdef HAS_RESHADE +static void ResetFxChainToGlobal() { + VideoCore::UseGlobalFxSettings(); + VideoCore::FxChain::Instance().LoadFromSettings(); +} +#else +static void ResetFxChainToGlobal() {} +#endif + template Settings::Setting* getSetting(JNIEnv* env, jstring jkey) { auto key = Common::Android::GetJString(env, jkey); @@ -39,6 +51,7 @@ extern "C" { void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeGlobalConfig(JNIEnv* env, jobject obj) { global_config = std::make_unique(); FrontendCommon::GenerateSettings(); + ResetFxChainToGlobal(); } void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, jobject obj) { @@ -47,6 +60,7 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadGlobalConfig(JNIEnv* env, jobject obj) { global_config->AndroidConfig::ReloadAllValues(); + ResetFxChainToGlobal(); } void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jobject obj) { @@ -130,6 +144,25 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject ob setting->SetValue(value); } +jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getUnsignedShort(JNIEnv* env, jobject obj, + jstring jkey, + jboolean needGlobal) { + auto setting = getSetting(env, jkey); + if (setting == nullptr) { + return -1; + } + return static_cast(setting->GetValue(static_cast(needGlobal))); +} + +void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setUnsignedShort(JNIEnv* env, jobject obj, + jstring jkey, jint value) { + auto setting = getSetting(env, jkey); + if (setting == nullptr) { + return; + } + setting->SetValue(static_cast(value)); +} + jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey, jboolean needGlobal) { auto setting = getSetting(env, jkey); diff --git a/src/android/app/src/main/jni/native_input.cpp b/src/android/app/src/main/jni/native_input.cpp index 730d4f0a27..e05ab2f209 100644 --- a/src/android/app/src/main/jni/native_input.cpp +++ b/src/android/app/src/main/jni/native_input.cpp @@ -21,7 +21,7 @@ #include "input_common/drivers/virtual_gamepad.h" #include "native.h" -ankerl::unordered_dense::map> map_profiles; +::Common::unordered_map> map_profiles; bool IsHandheldOnly() { const auto npad_style_set = diff --git a/src/android/app/src/main/jni/native_post_processing.cpp b/src/android/app/src/main/jni/native_post_processing.cpp new file mode 100644 index 0000000000..7fb04e7f9f --- /dev/null +++ b/src/android/app/src/main/jni/native_post_processing.cpp @@ -0,0 +1,350 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include + +#include +#include + +#include "common/android/android_common.h" +#ifdef HAS_RESHADE +#include "android_config.h" +#include "video_core/post_processing/fx_chain.h" +#include "common/settings.h" +#include "video_core/post_processing/fx_effect.h" +#include "video_core/post_processing/fx_preset.h" + +extern std::unique_ptr per_game_config; +#endif + +namespace { + +#ifdef HAS_RESHADE +bool EditingPerGame() { + return per_game_config != nullptr; +} + +void BeginFxEdit() { + if (!EditingPerGame()) { + return; + } + VideoCore::UsePerGameFxSettings(); +} + +nlohmann::json SerializeUniform(const VideoCore::FxUniformDesc& uniform) { + nlohmann::json out; + out["name"] = uniform.name; + out["label"] = uniform.label; + out["tooltip"] = uniform.tooltip; + out["category"] = uniform.category; + out["kind"] = static_cast(uniform.kind); + out["uiType"] = static_cast(uniform.ui_type); + out["components"] = uniform.components; + out["min"] = uniform.ui_min; + out["max"] = uniform.ui_max; + out["step"] = uniform.ui_step; + out["items"] = uniform.items; + + nlohmann::json defaults = nlohmann::json::array(); + for (u32 i = 0; i < uniform.components; ++i) { + defaults.push_back(uniform.default_value[i]); + } + out["defaults"] = defaults; + + return out; +} + +std::array DefaultValueOf(size_t index, const std::string& uniform) { + const auto entries = VideoCore::FxChain::Instance().Entries(); + if (index >= entries.size()) { + return {}; + } + const VideoCore::FxEffectDesc* effect = VideoCore::FindFxEffect(entries[index].file); + if (effect == nullptr) { + return {}; + } + const VideoCore::FxUniformDesc* desc = VideoCore::FindFxUniform(*effect, uniform); + if (desc == nullptr) { + return {}; + } + return desc->default_value; +} +#endif + +} // Anonymous namespace + +extern "C" { + +jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getCatalogJson(JNIEnv* env, + jobject obj) { + nlohmann::json out = nlohmann::json::array(); + +#ifdef HAS_RESHADE + VideoCore::FxChain::Instance().DropUnknownEntries(); + + for (const auto& effect : VideoCore::GetFxCatalog()) { + nlohmann::json entry; + entry["file"] = effect.file; + entry["name"] = effect.name; + entry["label"] = effect.label; + entry["description"] = effect.description; + entry["error"] = effect.error; + entry["techniques"] = effect.techniques; + + nlohmann::json uniforms = nlohmann::json::array(); + for (const auto& uniform : effect.uniforms) { + uniforms.push_back(SerializeUniform(uniform)); + } + entry["uniforms"] = uniforms; + + out.push_back(entry); + } +#endif + + return Common::Android::ToJString(env, out.dump()); +} + +jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getChainJson(JNIEnv* env, jobject obj) { + nlohmann::json out = nlohmann::json::array(); + +#ifdef HAS_RESHADE + for (const auto& entry : VideoCore::FxChain::Instance().Entries()) { + nlohmann::json item; + item["file"] = entry.file; + item["technique"] = entry.technique; + + nlohmann::json values = nlohmann::json::object(); + for (const auto& [name, value] : entry.values) { + values[name] = {value[0], value[1], value[2], value[3]}; + } + item["values"] = values; + + out.push_back(item); + } +#endif + + return Common::Android::ToJString(env, out.dump()); +} + +void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_append(JNIEnv* env, jobject obj, + jstring jfile, jstring jtechnique) { +#ifdef HAS_RESHADE + VideoCore::FxChain::Instance().Append(Common::Android::GetJString(env, jfile), + Common::Android::GetJString(env, jtechnique)); +#endif +} + +void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_replace(JNIEnv* env, jobject obj, + jint index, jstring jfile, + jstring jtechnique) { +#ifdef HAS_RESHADE + VideoCore::FxChain::Instance().Replace(static_cast(index), + Common::Android::GetJString(env, jfile), + Common::Android::GetJString(env, jtechnique)); +#endif +} + +void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_remove(JNIEnv* env, jobject obj, + jint index) { +#ifdef HAS_RESHADE + VideoCore::FxChain::Instance().Remove(static_cast(index)); +#endif +} + +void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_move(JNIEnv* env, jobject obj, jint index, + jint delta) { +#ifdef HAS_RESHADE + VideoCore::FxChain::Instance().Move(static_cast(index), delta); +#endif +} + +void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_resetValues(JNIEnv* env, jobject obj, + jint index) { +#ifdef HAS_RESHADE + VideoCore::FxChain::Instance().ResetValues(static_cast(index)); +#endif +} + +jfloat Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getValue(JNIEnv* env, jobject obj, + jint index, jstring juniform, + jint component) { +#ifdef HAS_RESHADE + if (component < 0 || component >= 4) { + return 0.0f; + } + const auto value = VideoCore::FxChain::Instance().GetValue( + static_cast(index), Common::Android::GetJString(env, juniform)); + return value[static_cast(component)]; +#else + return 0.0f; +#endif +} + +jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_hasValue(JNIEnv* env, jobject obj, + jint index, + jstring juniform) { +#ifdef HAS_RESHADE + return static_cast(VideoCore::FxChain::Instance().HasValue( + static_cast(index), Common::Android::GetJString(env, juniform))); +#else + return static_cast(false); +#endif +} + +void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_setValue(JNIEnv* env, jobject obj, + jint index, jstring juniform, + jint component, jfloat value) { +#ifdef HAS_RESHADE + if (component < 0 || component >= 4) { + return; + } + const std::string uniform = Common::Android::GetJString(env, juniform); + auto& chain = VideoCore::FxChain::Instance(); + const auto slot = static_cast(index); + + auto current = chain.GetValue(slot, uniform); + if (!chain.HasValue(slot, uniform)) { + current = DefaultValueOf(slot, uniform); + } + + current[static_cast(component)] = value; + chain.SetValue(slot, uniform, current); +#endif +} + +void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_store(JNIEnv* env, jobject obj) { +#ifdef HAS_RESHADE + BeginFxEdit(); + VideoCore::FxChain::Instance().StoreToSettings(); +#endif +} + +void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_reload(JNIEnv* env, jobject obj) { +#ifdef HAS_RESHADE + if (!EditingPerGame()) { + VideoCore::UseGlobalFxSettings(); + } + VideoCore::FxChain::Instance().LoadFromSettings(); +#endif +} + +void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_clearChain(JNIEnv* env, jobject obj) { +#ifdef HAS_RESHADE + VideoCore::FxChain::Instance().Clear(); +#endif +} + +jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getShaderDirectory(JNIEnv* env, + jobject obj) { +#ifdef HAS_RESHADE + return Common::Android::ToJString(env, VideoCore::GetFxRootDirectory().string()); +#else + return Common::Android::ToJString(env, ""); +#endif +} + +jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getPresetsJson(JNIEnv* env, + jobject obj) { + nlohmann::json out = nlohmann::json::array(); +#ifdef HAS_RESHADE + VideoCore::ReloadFxPresetCatalog(); + + for (const auto& preset : VideoCore::GetFxPresetCatalog()) { + nlohmann::json entry; + entry["name"] = preset.name; + entry["description"] = preset.description; + entry["bundled"] = preset.bundled; + out.push_back(entry); + } +#endif + return Common::Android::ToJString(env, out.dump()); +} + +jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getActivePreset(JNIEnv* env, + jobject obj) { +#ifdef HAS_RESHADE + return Common::Android::ToJString(env, VideoCore::GetActiveFxPreset()); +#else + return Common::Android::ToJString(env, ""); +#endif +} + +jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_isPresetModified(JNIEnv* env, + jobject obj) { +#ifdef HAS_RESHADE + return static_cast(VideoCore::IsActiveFxPresetModified()); +#else + return static_cast(false); +#endif +} + +jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_applyPreset(JNIEnv* env, jobject obj, + jstring jname) { +#ifdef HAS_RESHADE + BeginFxEdit(); + return static_cast( + VideoCore::ApplyFxPreset(Common::Android::GetJString(env, jname))); +#else + return static_cast(false); +#endif +} + +jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_savePreset(JNIEnv* env, jobject obj, + jstring jname, + jstring jdescription) { +#ifdef HAS_RESHADE + BeginFxEdit(); + return static_cast( + VideoCore::SaveFxPreset(Common::Android::GetJString(env, jname), + Common::Android::GetJString(env, jdescription))); +#else + return static_cast(false); +#endif +} + +jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_deletePreset(JNIEnv* env, jobject obj, + jstring jname) { +#ifdef HAS_RESHADE + BeginFxEdit(); + return static_cast( + VideoCore::DeleteFxPreset(Common::Android::GetJString(env, jname))); +#else + return static_cast(false); +#endif +} + +void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_clearPreset(JNIEnv* env, jobject obj) { +#ifdef HAS_RESHADE + BeginFxEdit(); + VideoCore::SetActiveFxPreset(std::string_view()); +#endif +} + +jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_isEnabled(JNIEnv* env, jobject obj) { +#ifdef HAS_RESHADE + return static_cast(Settings::values.post_shader_enabled.GetValue()); +#else + return static_cast(false); +#endif +} + +void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_setEnabled(JNIEnv* env, jobject obj, + jboolean enabled) { +#ifdef HAS_RESHADE + BeginFxEdit(); + Settings::values.post_shader_enabled.SetValue(enabled != JNI_FALSE); +#endif +} + +jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getPresetDirectory(JNIEnv* env, + jobject obj) { +#ifdef HAS_RESHADE + return Common::Android::ToJString(env, VideoCore::GetFxPresetDirectory().string()); +#else + return Common::Android::ToJString(env, ""); +#endif +} + +} // extern "C" diff --git a/src/android/app/src/main/res/color/frame_gen_cell_text.xml b/src/android/app/src/main/res/color/frame_gen_cell_text.xml new file mode 100644 index 0000000000..6e671681bf --- /dev/null +++ b/src/android/app/src/main/res/color/frame_gen_cell_text.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/android/app/src/main/res/color/game_card_ripple.xml b/src/android/app/src/main/res/color/game_card_ripple.xml new file mode 100644 index 0000000000..84a5dab29e --- /dev/null +++ b/src/android/app/src/main/res/color/game_card_ripple.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/android/app/src/main/res/drawable/frame_gen_cell_background.xml b/src/android/app/src/main/res/drawable/frame_gen_cell_background.xml new file mode 100644 index 0000000000..6f76a37718 --- /dev/null +++ b/src/android/app/src/main/res/drawable/frame_gen_cell_background.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/drawable/ic_duck.xml b/src/android/app/src/main/res/drawable/ic_duck.xml new file mode 100644 index 0000000000..601e1e6c0f --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_duck.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/src/android/app/src/main/res/drawable/ic_post_processing.xml b/src/android/app/src/main/res/drawable/ic_post_processing.xml new file mode 100644 index 0000000000..06ce58d40e --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_post_processing.xml @@ -0,0 +1,10 @@ + + + diff --git a/src/android/app/src/main/res/drawable/shader_card_background.xml b/src/android/app/src/main/res/drawable/shader_card_background.xml new file mode 100644 index 0000000000..40e840695a --- /dev/null +++ b/src/android/app/src/main/res/drawable/shader_card_background.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/src/android/app/src/main/res/layout-land/card_game_carousel.xml b/src/android/app/src/main/res/layout-land/card_game_carousel.xml index 4d8e5fd148..866b189042 100644 --- a/src/android/app/src/main/res/layout-land/card_game_carousel.xml +++ b/src/android/app/src/main/res/layout-land/card_game_carousel.xml @@ -10,6 +10,7 @@ android:clipChildren="true" android:layout_margin="0dp" app:cardBackgroundColor="@color/eden_card_background" + app:rippleColor="@color/game_card_ripple" app:strokeWidth="1dp" app:strokeColor="@color/eden_border"> diff --git a/src/android/app/src/main/res/layout/card_game_carousel.xml b/src/android/app/src/main/res/layout/card_game_carousel.xml index 424d6cadde..4e92a3e209 100644 --- a/src/android/app/src/main/res/layout/card_game_carousel.xml +++ b/src/android/app/src/main/res/layout/card_game_carousel.xml @@ -11,6 +11,7 @@ app:cardCornerRadius="16dp" app:cardPreventCornerOverlap="true" android:clipChildren="true" + app:rippleColor="@color/game_card_ripple" android:layout_margin="4dp"> + + + + + + + + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/layout/item_quick_settings_add.xml b/src/android/app/src/main/res/layout/item_quick_settings_add.xml new file mode 100644 index 0000000000..1af62f917b --- /dev/null +++ b/src/android/app/src/main/res/layout/item_quick_settings_add.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/layout/item_quick_settings_frame_gen.xml b/src/android/app/src/main/res/layout/item_quick_settings_frame_gen.xml new file mode 100644 index 0000000000..e5f8baa3d3 --- /dev/null +++ b/src/android/app/src/main/res/layout/item_quick_settings_frame_gen.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/layout/item_quick_settings_frame_gen_cell.xml b/src/android/app/src/main/res/layout/item_quick_settings_frame_gen_cell.xml new file mode 100644 index 0000000000..0d4d0af3a8 --- /dev/null +++ b/src/android/app/src/main/res/layout/item_quick_settings_frame_gen_cell.xml @@ -0,0 +1,13 @@ + + diff --git a/src/android/app/src/main/res/layout/item_quick_settings_preset.xml b/src/android/app/src/main/res/layout/item_quick_settings_preset.xml new file mode 100644 index 0000000000..935ce4d45d --- /dev/null +++ b/src/android/app/src/main/res/layout/item_quick_settings_preset.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/layout/item_quick_settings_shader.xml b/src/android/app/src/main/res/layout/item_quick_settings_shader.xml new file mode 100644 index 0000000000..dac3bb4de8 --- /dev/null +++ b/src/android/app/src/main/res/layout/item_quick_settings_shader.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/layout/item_setting_fx_actions.xml b/src/android/app/src/main/res/layout/item_setting_fx_actions.xml new file mode 100644 index 0000000000..c7df946416 --- /dev/null +++ b/src/android/app/src/main/res/layout/item_setting_fx_actions.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/src/android/app/src/main/res/layout/item_setting_fx_slider.xml b/src/android/app/src/main/res/layout/item_setting_fx_slider.xml new file mode 100644 index 0000000000..1e2989195f --- /dev/null +++ b/src/android/app/src/main/res/layout/item_setting_fx_slider.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/layout/list_item_setting_fx_button.xml b/src/android/app/src/main/res/layout/list_item_setting_fx_button.xml new file mode 100644 index 0000000000..8d207888cb --- /dev/null +++ b/src/android/app/src/main/res/layout/list_item_setting_fx_button.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/src/android/app/src/main/res/layout/list_item_setting_fx_preset.xml b/src/android/app/src/main/res/layout/list_item_setting_fx_preset.xml new file mode 100644 index 0000000000..a212f67f4b --- /dev/null +++ b/src/android/app/src/main/res/layout/list_item_setting_fx_preset.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/layout/list_item_setting_fx_shader.xml b/src/android/app/src/main/res/layout/list_item_setting_fx_shader.xml new file mode 100644 index 0000000000..e6f7b12eb4 --- /dev/null +++ b/src/android/app/src/main/res/layout/list_item_setting_fx_shader.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/layout/list_item_setting_fx_toolbar.xml b/src/android/app/src/main/res/layout/list_item_setting_fx_toolbar.xml new file mode 100644 index 0000000000..074c5a0662 --- /dev/null +++ b/src/android/app/src/main/res/layout/list_item_setting_fx_toolbar.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + diff --git a/src/android/app/src/main/res/navigation/settings_subscreen_navigation.xml b/src/android/app/src/main/res/navigation/settings_subscreen_navigation.xml index f03024e7f6..fff7cdb2d4 100644 --- a/src/android/app/src/main/res/navigation/settings_subscreen_navigation.xml +++ b/src/android/app/src/main/res/navigation/settings_subscreen_navigation.xml @@ -47,6 +47,12 @@ android:defaultValue="@null" /> + + - سيشغل هذا البرنامج ألعاب جهاز Nintendo Switch. لا يتضمن البرنامج أي ألعاب أو مفاتيح.

قبل أن تبدأ، يرجى تحديد موقع ملف prod.keys ]]> على وحدة تخزين جهازك.

اعرف المزيد]]>
+ سيعمل هذا البرنامج على تشغيل ألعاب منصة ألعاب «نينتندو سويتش». لا يتضمن البرنامج أي ألعاب أو مفاتيح تفعيل.

قبل البدء، يرجى تحديد موقع ملف prod.keys ]]> في مساحة التخزين بجهازك.

مزيد من المعلومات]]>
الإشعارات والأخطاء عرض الإشعارات عند حدوث خطأ ما. لم يتم منح إذن الإشعار! @@ -28,7 +28,7 @@ السلوك إخفاء الطبقة تلقائيًا إخفاء طبقة ذراع التحكم باللمس تلقائيًا بعد مرور الوقت المحدد من عدم النشاط. - تمكين إخفاء الطبقة تلقائيًا + تفعيل إخفاء الطبقة تلقائيًا إخفاء الطبقة عند إدخال ذراع التحكم إخفاء تلقائي لطبقة عناصر التحكم باللمس عند استخدام ذراع تحكم فعلية. تظهر الطبقة مرة أخرى عند فصل ذراع التحكم. عكس أزرار التأكيد/الرجوع في وحدة التحكم @@ -51,7 +51,7 @@ التخصيص الرؤية طبقة الأداء - تمكين طبقة إحصائيات الأداء + تفعيل طبقة إحصائيات الأداء ضبط المعلومات التي يتم عرضها في طبقة إحصائيات الأداء عرض معدل الإطارات عرض معدل الإطارات الحالي @@ -66,6 +66,8 @@ عرض استهلاك الطاقة الحالي والسعة المتبقية في البطارية عرض بناء التظليل عرض عدد التظليل التي يتم بناؤها + خيوط عامل خط الأنابيب + قم بإدارة عدد النوى المستخدمة لبناء خطوط أنابيب Vulkan، فالقيمة الأعلى ستحسن أداء تجميع خط الأنابيب ولكن درجات الحرارة سترتفع أيضًا. موضع الطبقة حدد مكان عرض الطبقة على الشاشة أعلى اليمين @@ -79,7 +81,7 @@ عرض طبقة معلومات الجهاز - تمكين طبقة الجهاز + تفعيل طبقة الجهاز طبقة الجهاز ضبط المعلومات التي يتم عرضها في طبقة الجهاز @@ -96,15 +98,15 @@ مزامنة سرعة النواة قم بمزامنة سرعة النواة مع النسبة المئوية للسرعة القصوى لتحسين الأداء دون تغيير السرعة الفعلية للعبة. - تمكين محاكاة MMU المضيف - يعمل هذا التحسين على تسريع وصول الذاكرة بواسطة البرنامج الضيف. يؤدي تمكينه إلى إجراء عمليات قراءة/كتابة ذاكرة الضيف مباشرة في الذاكرة والاستفادة من MMU المضيف. يؤدي تعطيل هذا إلى إجبار جميع عمليات الوصول إلى الذاكرة على استخدام محاكاة MMU البرمجية. + تفعيل محاكاة MMU المضيف + يعمل هذا التحسين على تسريع وصول الذاكرة بواسطة البرنامج الضيف. يؤدي تفعيله إلى إجراء عمليات قراءة/كتابة ذاكرة الضيف مباشرة في الذاكرة والاستفادة من MMU المضيف. يؤدي تعطيل هذا إلى إجبار جميع عمليات الوصول إلى الذاكرة على استخدام محاكاة MMU البرمجية. مقابض تصحيح الأخطاء للإستخدام في التطوير فقط. 0 إلى 65535 محاكاة NVDEC - حدد كيفية التعامل مع فك تشفير الفيديو (NVDEC) خلال المشاهد التمهيدية والمقدمة. + قم بتغيير المعالج المركزي في حالة حدوث عطل أثناء المشاهد السينمائية. لا شيء @@ -252,7 +254,7 @@ إعداد بيانات المحاكي يلزم وجود مفاتيح حتى يعمل المحاكي، ويوصى باستخدام الفيرموير ويكون ضروريًا لاستخدام تطبيق القائمة الرئيسية. منح الأذونات - منح أذونات اختيارية لاستخدام ميزات معينة للمحاكي + منح أذونات اختيارية لاستخدام ميزات محددة في المحاكي العديد من الألعاب تتطلب الوصول إلى الفيرموير لتشغيل بشكل صحيح. https://yuzu-mirror.github.io/help/quickstart/#guide-introduction الإشعارات @@ -289,6 +291,87 @@ أداة جلب برامج تشغيل وحدة المعالجة الرسومية إدارة برامج تشغيل وحدة معالجة الرسومات تثبيت برامج تشغيل بديلة لأداء أو دقة أفضل + تأثيرات ما بعد المعالجة + تأثيرات ReShade FX التي يتم تطبيقها بعد عملية العرض + ضبط تسلسل التأثيرات لهذه اللعبة + إضافة تأثير + إزالة + فتح القائمة + إغلاق القائمة + إزالة التأثيرات + الإعداد مسبقًا نشط. قم بتعديله في «تأثيرات ما بعد المعالجة» قبل بدء اللعبة. + تم تغيير القيم عن الإعداد الأصلي. + الإعدادات المسبقة + إعداد مسبق جديد + يحفظ التأثيرات التي قمت بتحميلها، مع قيمها الحالية، كإعداد مسبق يمكنك اختياره لاحقًا. + قم بتسمية الإعداد المسبق باسم لا يحتوي على علامة يساوي. + لا يوجد إعداد مسبق + حذف الإعداد المسبق + إعادة تعيين القيم المحددة مسبقًا + إعادة التعيين إلى الإعدادات الافتراضية + لم يتم العثور على أي ملفات. ضع ملفات .fx في هذا المجلد: + توليد الإطار + ضبط إعدادات إنشاء الإطارات لهذه اللعبة + قم بإدراج الإطارات المُستكملة بين الإطارات المُعالجة باستخدام تقنية التحجيم بدون فقدان الجودة. يُفرض هذا الخيار عرض الإطارات وفقًا لترتيب FIFO عند تفعيله. + مضاعف الإطارات + عدد الإطارات المطلوب عرضها لكل إطار مُعالَج. تتطلب القيم الأعلى وقت معالجة رسوميات أكبر. طلب عدد إطارات يفوق قدرة الشاشة على عرضه سيؤدي إلى إبطاء المحاكاة. + 2x + 3x + 4x + معدل الإطارات المستهدف + اختر المعدل الذي يمكن لشاشتك عرضه فعليًّا. عندئذٍ يرتفع المضاعف أو ينخفض تلقائيًّا للحفاظ على هذا المعدل، ويقوم بالتراجع عن أي خطوة تؤدي إلى إبطاء سير اللعبة نفسها. + استخدم مضاعفًا ثابتًا + 60 إطارًا في الثانية + 90 إطارًا في الثانية + 120 إطارًا في الثانية + تشغيل + إيقاف + مستقر + تلقائي + قم بتشغيل أو إيقاف توليد الإطارات واختر مضاعف الإطارات. + توليد إطارات بمعدل إطارات مستهدف. ويتم ضبط المضاعف تلقائيًا. + الدقة المستخدمة لتقدير الحركة بين الإطارات. تقلل القيم المنخفضة من الوقت الذي تستهلكه وحدة معالجة الرسومات. + هدف قائمة انتظار الإطارات + كم عدد الإطارات المكتملة التي قد تنتظر قبل عرضها؟ تعمل قوائم الانتظار الأكبر حجمًا على امتصاص الارتفاعات المفاجئة في حمل وحدة معالجة الرسومات على حساب زمن انتقال الإدخال. + أقل زمن انتقال (بدون تخزين مؤقت) + متوازن (1 إطار) + الأكثر سلاسة (2 إطارات) + تكييف تقدير الحركة مع اللعبة + قم بتقدير الحركة بناءً على الدقة التي تعرضها اللعبة فعليًّا، بدلاً من الإخراج الذي تم رفع دقته. ولا يؤثر ذلك على الدقة بأي شكل، لأن رفع الدقة لا يضيف أي تفاصيل تتعلق بالحركة. + دقة تقدير الحركة + دقة مسار التدفق البصري، كجزء من الناتج. ويُعد خفض هذه القيمة أرخص طريقة لاستعادة الأداء. + توليد الإطار غير متاح + يفتقر برنامج تشغيل وحدة معالجة الرسومات هذا إلى نموذج ذاكرة Vulkan أو دعم الدقة النصفية (float16) التي تتطلبها برامج التظليل الخاصة بـ «Lossless Scaling». + اختياري. قم بتوفير ملف Lossless.dll الخاص بك لتفعيل إنشاء الإطارات لاحقًا + تثبيت ملف Lossless.dll + يتطلب إنشاء الإطارات أن تكون لديك نسخة قانونية خاصة بك من ملف Lossless.dll المأخوذ من برنامج Lossless Scaling + اختر نسخة أخرى من ملف Lossless.dll + توليد الإطارات + مدعوم + غير مدعوم (لا يتوفر نموذج ذاكرة Vulkan أو float16) + Lossless Scaling + قم بتوفير نسختك الخاصة من ملف Lossless.dll لتفعيل توليد الإطارات + مثبت + غير مثبت + استبدل + إزالة + احذف ملف Lossless.dll المثبت ووحدات التظليل المعدة له + سيتوقف توليد الإطارات عن العمل إلى أن تقوم بتثبيت ملف Lossless.dll مرة أخرى. ولن يتأثر ملفك الأصلي بذلك. + ملف Lossless.dll غير مثبت + قم بتثبيته من الإعدادات › Lossless Scaling لاستخدام ميزة توليد الإطارات. + أغلق اللعبة أولاً + لا يمكن تعديل ملف Lossless.dll أثناء تشغيل اللعبة. + لا يوجد شيء يجب إزالته + لم يتم تثبيت ملف Lossless.dll بعد. + جاري التحضير لإنشاء مظلّلات الإطارات… + تم تثبيت ملف Lossless.dll بنجاح + تعذر تثبيت ملف Lossless.dll + تعذر نسخ الملف المحدد. + تعذر قراءة الملف المحدد. + الملف المحدد ليس مكتبة Windows. حدد ملف Lossless.dll من تثبيت برنامج Lossless Scaling. + لا تحتوي هذه النسخة من ملف Lossless.dll على برامج التظليل الخاصة بتوليد الإطارات. قم بتحديث ميزة «Lossless Scaling» وحاول مرة أخرى. + تعذر ترجمة برامج التظليل الخاصة بتوليد الإطارات. هذا الإصدار من «Lossless Scaling» غير مدعوم حتى الآن. + تعذر كتابة برامج التظليل المترجمة إلى وحدة التخزين. تأكد من توفر مساحة خالية. الإعدادات المتقدمة ضبط إعدادات المحاكي تم تشغيلها مؤخرًا @@ -419,13 +502,16 @@ نسبة الحد الأقصى للسرعة يحدد النسبة المئوية للحد من سرعة المحاكاة. %100 هي السرعة الطبيعية. القيم الأعلى أو الأقل ستزيد أو تقلل من الحد الأقصى للسرعة. سرعة تيربو - عند تمكين وضع التيربو، سيتم تشغيل المحاكاة بهذه السرعة. + عند تفعيل وضع التيربو، سيتم تشغيل المحاكاة بهذه السرعة. سرعة بطيئة - عند تمكين الوضع البطيء، سيتم تشغيل المحاكاة بهذه السرعة. + عند تفعيل الوضع البطيء، سيتم تشغيل المحاكاة بهذه السرعة. خلفية وحدة المعالجة المركزية دقة وحدة المعالجة المركزية %1$s%2$s + وسائط Homebrew + سطر الأوامر الوسائط المُمررة إلى Homebrew عند التشغيل (مثل -noglsl). + اسم الجهاز وضع الإرساء @@ -439,8 +525,8 @@ تعيين ساعة مخصصة في الوقت الحقيقي - رفع تردد المعالج - يجبر وحدة المعالجة المركزية المحاكاة على العمل بتردد أعلى، مما يقلل بعض محددات معدل الإطارات. استخدم وضع التعزيز (1700 ميجاهرتز) لتشغيلها عند أعلى تردد أصلي لجهاز Switch، أو وضع السرعة (2000 ميجاهرتز) لتشغيلها بتردد مضاعف. + ترددات وحدة المعالجة المركزية + يرفع هذا الإعداد تردد المعالج المُحاكى، مما يُزيل بعض مُحددات معدل الإطارات. قد تُلاحظ المعالجات الأضعف انخفاضًا في الأداء، وقد تعمل بعض الألعاب بشكل غير صحيح. تخصيص دورات المعالج قم بتعيين قيمة مخصصة لدورات المعالج. القيم الأعلى يمكن أن تزيد من الأداء، ولكنها قد تتسبب أيضًا في تجميد اللعبة. يوصى باستخدام نطاق 77-21000. دورات @@ -460,17 +546,19 @@ الدقة (الإرساء/محمول) VSync وضع مرشح ملائم للنافذة - حدة FSR - يحدد مدى وضوح الصورة عند استخدام التباين الديناميكي لـ FSR + حدة FSR/SGSR + يحدد مدى وضوح الصورة عند استخدام مرشحات FSR أو SGSR طريقة مضاد التعرج متقدم وضع وحدة معالجة الرسومات - يتحكم في وضع محاكاة وحدة معالجة الرسومات. تعمل معظم الألعاب بشكل جيد مع وضعي سريع أو متوازن، لكن الوضع الدقيق لا يزال مطلوبًا لبعض الألعاب. تميل الجسيمات إلى العرض بشكل صحيح فقط عند استخدام الوضع الدقيق. + يتحكم هذا الخيار في وضع محاكاة وحدة معالجة الرسومات. تعمل معظم الألعاب بشكل جيد مع الوضع السريع، لكن الوضع الدقيق لا يزال ضروريًا لبعضها. تميل الجسيمات إلى الظهور بشكل صحيح فقط مع الوضع الدقيق. دقة DMA يتحكم في دقة DMA. يمكن أن تؤدي الدقة الآمنة إلى حل المشكلات في بعض الألعاب، ولكنها قد تؤثر أيضًا على الأداء في بعض الحالات. إذا لم تكن متأكدًا، فاترك هذا الخيار على الإعداد الافتراضي. + سلوك حاجز وحدة معالجة الرسومات + يتحكم في سلوك تزامن حاجز وحدة معالجة الرسوميات. الخيار الفوري هو الأسرع، لكنه قد يسبب بعض المشاكل. الخيار المتوازن يقدم توافقًا أفضل وقد يصلح مشاكل في بعض الألعاب. الخيار الدقيق يحسن التوافق أكثر لكنه قد يقلل الأداء قليلاً. الخيار الصارم هو الأبطأ، لكنه قد يصلح المشاكل التي تتطلب تزامنًا أكثر صرامة. الإعداد الافتراضي يتبع إعداد دقة وحدة معالجة الرسوميات. تصفية متباينة الخواص يحسن جودة الأنسجة عند عرضها بزوايا مائلة وضع استخدام ذاكرة VRAM @@ -485,26 +573,26 @@ إجبار السرعة القصوى (لأجهزة Adreno فقط) يجبر وحدة معالجة الرسومات على العمل بأقصى سرعة ممكنة (سيظل يتم تطبيق القيود الحرارية). محاكاة غير متزامنة لوحدة معالجة الرسومات - يُشغّل هذا الخيار محاكاة وحدة معالجة الرسومات بشكل غير متزامن لتقليل توقف وحدة المعالجة المركزية وتحسين الإنتاجية. عطّل هذا الخيار فقط في حال واجهت مشاكل متعلقة بالتوقيت. - عرض غير متزامن - يحسّن الأداء بشكل طفيف عن طريق نقل عملية العرض إلى خيط معالجة منفصل لوحدة المعالجة المركزية. + يمكن لهذه الحيلة أن تزيد الأداء عن طريق تشغيل محاكاة وحدة معالجة الرسومات بشكل غير متزامن على حساب مشاكل الرسومات وزيادة معدلات الأعطال بسبب العمليات المتعلقة بالتوقيت. استخدم التنظيف التفاعلي يحسن دقة العرض في بعض الألعاب على حساب الأداء. - تمكين سجل التخزين المؤقت + تفعيل سجل التخزين المؤقت يُتيح هذا الخيار الوصول إلى حالات التخزين المؤقت السابقة. وقد يُحسّن جودة العرض وثبات الأداء في بعض الألعاب. مخازن الرؤوس المُحسّنة - يتيح ربط مخزن الرؤوس المُحسّن لتحسين الأداء. يتطلب برامج تشغيل Turnip/QCOM من إصدار Mesa 26.0 أو أحدث. سيؤدي إلى تعطل النظام عند استخدام برامج تشغيل Turnip الأقدم. + يُتيح ربطًا مُحسَّنًا لمخازن الرؤوس لتحسين الأداء. يتطلب برامج تشغيل Mesa 26.0+ Turnip/ برامج تشغيل QCOM. قد يتعطل على برامج تشغيل Turnip القديمة (25.3 وما دون). اختراقات - وقت معالجة الرسومات السريع - يُجبر هذا الخيار معظم الألعاب على العمل بأعلى دقة عرض أصلية. استخدم 256 للحصول على أقصى أداء و512 للحصول على أعلى جودة رسومات. + سرعات معالج الرسوميات + يجعل اللعبة تعتقد أن عمل وحدة معالجة الرسومات ينتهي بشكل أسرع مما هو عليه في الواقع، لذلك تتوقف عن خفض الدقة ومسافة العرض لتتناسب مع سرعات جهاز Switch. تخطي إبطال صلاحية وحدة المعالجة المركزية الداخلية يتخطى بعض عمليات إبطال ذاكرة التخزين المؤقتة من جانب وحدة المعالجة المركزية أثناء تحديثات الذاكرة، مما يقلل من استخدام وحدة المعالجة المركزية ويحسن أداءها. قد يتسبب ذلك في حدوث أعطال أو تعطل في بعض الألعاب. إصلاح تأثيرات التوهج يقلل من ضبابية التوهج في LA/EOW (Adreno A6XX - A7XX/ Turnip)، ويزيل التوهج في Burnout. تحذير: قد يسبب تشوهات رسومية في ألعاب أخرى. محاكاة BGR565 يُصلح مشاكل انعكاس الألوان في الألعاب أو ظهور تشوهات غريبة أو ظلال غريبة. + تفعيل ميزة إعادة التحجيم القديمة + يُمكّن هذا الخيار من التعامل مع عملية إعادة تحجيم الألعاب بطريقة تقليدية باستخدام مسار إعادة التحجيم السريع استخدم تظليل غير متزامن يقوم بتجميع التظليل بشكل غير متزامن. قد يقلل ذلك من التقطعات ولكنه قد يؤدي أيضًا إلى حدوث أخطاء. إعدادات إلغاء ترتيب بيانات وحدة معالجة الرسومات @@ -523,10 +611,10 @@ إضافات الحالة الديناميكية الموسعة - يتحكم هذا الخيار في عدد الميزات التي يمكن استخدامها في حالة الديناميكية الموسعة. تسمح الأرقام الأعلى بمزيد من الميزات ويمكن أن تزيد من الأداء، ولكنها قد تسبب مشاكل مع بعض برامج التشغيل والأجهزة. + يتحكم هذا الخيار في عدد الميزات التي يمكن استخدامها في ExtendedDynamicState (EDS). كلما زادت القيمة، قلّ عدد عمليات تجميع خط الأنابيب بناءً على الحالة الديناميكية التي يدعمها برنامج التشغيل. معطل حالة ديناميكية لإدخال الرأس - يتيح ميزة الحالة الديناميكية لإدخال الرأس لتحسين الجودة والأداء. + يتيح تفعيل هذه الميزة معالجة أكثر مرونة لمدخلات الرؤوس، مما قد يقلل من وقت تجميع خط الأنابيب في vertex/buffer. تظليل العينة يسمح هذا الخيار بتنفيذ مُظلل الأجزاء لكل عينة في جزء متعدد العينات بدلاً من تنفيذه مرة واحدة لكل جزء. يُحسّن هذا من جودة الرسومات على حساب بعض الأداء. @@ -543,6 +631,7 @@ وحدة المعالج المركزية + ترددات استخدم الملحق التلقائي استبدال الخدمات والوظائف المفقودة تلقائيًا. قد يؤدي ذلك إلى تحسين التوافق، ولكنه قد يتسبب في حدوث أعطال ومشكلات في الاستقرار. @@ -557,17 +646,23 @@ السجلات تفريغ سجلات التصحيح حسب السطر يفرغ سجلات التصحيح عند كتابة كل سطر، مما يجعل التصحيح أسهل في حالات التوقف أو التجميد. + تفعيل التسجيل الموسع + يزيد الحد الأقصى لحجم ملف السجل من 100 ميجابايت إلى 1 جيجابايت. + مرشح السجلات + يتحكم في فئات سجلات Eden. مثال: *:Info Service.LM:Debug تسجيل وحدة معالجة الرسومات - تمكين تسجيل وحدة معالجة الرسومات - تسجيل عمليات وحدة معالجة الرسومات في ملف eden_gpu.log لتصحيح أخطاء برامج تشغيل Adreno مستوى السجل مستوى التفاصيل لسجلات وحدة معالجة الرسومات (كلما زاد المستوى، زادت التفاصيل وزادت التكاليف الإضافية) تسجيل استدعاءات واجهة برمجة تطبيقات Vulkan تتبع جميع استدعاءات واجهة برمجة تطبيقات Vulkan في المخزن المؤقت الحلقي - تفريغ التظليل - حفظ تظليل SPIR-V المجمع في ملفات + تفريغ مظللات SPIR-V + احفظ ملفات SPIR-V الثنائية المُعاد تجميعها (.spv) في مجلد التفريغ. افحصها باستخدام spirv-dis/spirv-cross/spirv-val. + تظليلات ضيف التفريغ (ماكسويل) + احفظ ملفات بايت كود برنامج التظليل الضيف الخاص بـ «ماكسويل» (*.ash) في مجلد «dump». افحصها باستخدام nvdisasm. + تفريغ ماكرو ماكسويل + احفظ ملفات برامج ماكرو ماكسويل (*.macro) في مجلد «dump». افحصها باستخدام برنامج «envydis». تتبع ذاكرة وحدة معالجة الرسومات مراقبة تخصيصات ذاكرة وحدة معالجة الرسومات وإلغاء تخصيصها معلومات تصحيح أخطاء برنامج التشغيل @@ -631,10 +726,10 @@ ذراع تحكم GameCube عكس المحور عكس الزر - زر تشغيل/إيقاف + زر تفعيل/تعطيل زر التوربو تعيين الحد الفاصل - تشغيل/إيقاف المحور + تفعيل/تعطيل المحور متصل استخدم هزاز النظام طبقة الإدخال @@ -728,9 +823,9 @@ النظام وضع الإرساء، المنطقة، اللغة الرسومات - مستوى الدقة، الدقة، ذاكرة التخزين المؤقتة للتظليل + مستوى دقة المحاكاة، الدقة، ذاكرة التخزين المؤقتة للتظليل الإعدادات السريعة - تمكين الإعدادات السريعة + تفعيل الإعدادات السريعة السماح بالوصول إلى قائمة الإعدادات السريعة عن طريق التمرير و زر القائمة الصوت محرك الإخراج، مستوى الصوت @@ -773,7 +868,7 @@ الإصدار نسخ التفاصيل الإضافات - التعديلات، التحديثات، المحتوى القابل للتنزيل + تفعيل/تعطيل التعديلات، التحديثات، المحتوى القابل للتنزيل زمن اللعب: مسح زمن اللعب إعادة تعيين زمن اللعب للعبة الحالية إلى 0 ثانية @@ -839,7 +934,7 @@ ملف ROM غير موجود اللعبة تتطلب الفيرموير - نسخ وتثبيت الفيرموير، أو اضغط \"موافق\" للمتابعة على أي حال.]]> + جارٍ البحث عن اللعبة... @@ -878,13 +973,13 @@ خروج من المحاكاة إنهاء - تشغيل/إيقاف أزرار التحكم + تفعيل/تعطيل أزرار التحكم مركز العصا النسبي انزلاق الأسهم الاهتزازات الديناميكية عرض ذراع التحكم إخفاء ذراع التحكم - تشغيل/إيقاف الكل + تفعيل/تعطيل الكل ضبط الطبقة الحجم الشفافية @@ -940,14 +1035,15 @@ 6 جيجابايت (غير آمن) 8 جيجابايت (غير آمن) - - تعزيز (1700MHz) - سريع (2000MHz) + + طبيعي + طبيعي + كسر السرعة - - تعطيل - متوسط (256) - مرتفع (512) + + طبيعي + تعزيز + كسر السرعة صغير جدًا (16 ميغابايت) @@ -987,7 +1083,6 @@ سريع - متوازن دقيق @@ -995,6 +1090,12 @@ غير آمن آمن + + افتراضي + فوري + متوازن + دقيق + محافظ عدواني @@ -1042,7 +1143,7 @@ حول إعدادات Freedreno قم بإعداد خيارات برنامج تشغيل Freedreno/Turnip لوحدة معالجة الرسومات لأغراض التصحيح، والتحليل، وتحسين الأداء. يتم حفظ التغييرات تلقائيًا. راجع https://docs.mesa3d.org/drivers/freedreno.html للحصول على الوثائق التفصيلية. إعدادات Freedreno - قم بضبط إعدادات برنامج تشغيل وحدة معالجة الرسومات لهذه اللعبة + ضبط إعدادات برنامج تشغيل وحدة معالجة الرسومات لهذه اللعبة تم حفظ إعدادات Freedreno @@ -1070,8 +1171,6 @@ فاتح داكن - لا شيء - خلفيات سوداء عند استخدام السمة الداكنة، قم بتطبيق خلفيات سوداء. @@ -1122,8 +1221,8 @@ وضع الطيران يمرر وضع الطيران إلى نظام تشغيل Switch - تمكين التطبيق الصغير للطبقة - تمكين التطبيق الصغير المدمج في Horizon. اضغط مع الاستمرار على زر الشاشة الرئيسية لمدة 1 ثانية لإظهاره. + تفعيل التطبيق الصغير للطبقة + تفعيل التطبيق الصغير المدمج في Horizon. اضغط مع الاستمرار على زر الشاشة الرئيسية لمدة 1 ثانية لإظهاره. إدارة ملف التعريف 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 c310b88d25..c4b59984c5 100644 --- a/src/android/app/src/main/res/values-ckb/strings.xml +++ b/src/android/app/src/main/res/values-ckb/strings.xml @@ -62,7 +62,6 @@ ئەم باشکردنە خێرایی دەستکەوتنی بیرگە لەلایەن پرۆگرامی میوانەکە زیاد دەکات. چالاککردنی وای لێدەکات کە خوێندنەوە/نووسینەکانی بیرگەی میوانەکە ڕاستەوخۆ لە بیرگە ئەنجام بدرێت و میمیکردنی MMU میواندە بەکاربهێنێت. ناچالاککردنی ئەمە هەموو دەستکەوتنەکانی بیرگە ڕەت دەکاتەوە لە بەکارهێنانی میمیکردنی MMU نەرمەکاڵا. ئیمولەیشنی NVDEC - هەڵبژاردنی ڕێگای دیکۆدکردنی ڤیدیۆ هیچ @@ -329,8 +328,6 @@ ڕوونی (دۆخی دەستی/دۆخی دۆک) دۆخی VSync فلتەری گونجاندنی پەنجەرە - تیژی FSR - دیاریکردنی تیژی وێنە لە کاتی بەکارهێنانی FSR شێوازی دژە-خاوڕۆیی @@ -381,7 +378,6 @@ تۆمارکردن خاوکردنەوەی تۆمارەکانی دیباگ بە هێڵ تۆمارەکانی دیباگ لە هەر هێڵێکدا دەنوسرێت خاو دەکاتەوە، ئەمە وا دەکات دیباگکردن ئاسانتر بێت لە کاتی کرشکردن یان پێکەنین. - بزوێنری دەرچوونی دەنگ دەنگ @@ -478,8 +474,6 @@ فایلی ڕۆم بوونی نییە یارییەکە فریموێر پێویستە - فریموێر دامپ بکە و دابنێ, یان پەنجە بنێ سەر \"باشە\" بۆ بەردەوامبوون هەرچۆنێک بێت.]]> - گەڕان بە دوای یارییە... یاری نەدۆزرایەوە بۆ ناسنامەی ناونیشان: %1$s @@ -555,10 +549,6 @@ 6GB (نائاسایش) 8GB (نائاسایش) - - زۆرکردن (1700MHz) - خێرا (2000MHz) - سیلیزی فارینهایت 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 4f7d98057c..a8e859cb45 100644 --- a/src/android/app/src/main/res/values-cs/strings.xml +++ b/src/android/app/src/main/res/values-cs/strings.xml @@ -1,7 +1,6 @@ - Tento software umožňuje spouštět hry pro herní konzoli Nintendo Switch. Neobsahuje žádné herní tituly ani klíče.

Nejdříve prosím vyhledejte soubor prod.keys ]]> v úložišti vašeho zařízení.

Další informace]]>
Upozornění a chyby Zobrazí oznámení v případě chyby. Oprávnění k zobrazení oznámení nebylo uděleno! @@ -97,7 +96,6 @@ Emulace NVDEC - Určuje, jakým způsobem se zpracovává dekódování videa (NVDEC). Žádné @@ -417,9 +415,6 @@ Umožňuje nastavit vlastní hodiny reálného času nezávisle na systému. Nastavit vlastní RTC - - Přetaktování CPU - Vynutí běh CPU na vyšší frekvenci, což může pomoci obejít některé omezovače FPS. Režim Boost (1700 MHz) odpovídá maximální taktu konzole Switch, režim Fast (2000 MHz) je dvojnásobek základního taktu konzole. Vlastní hodnota CPU cyků Nastavte vlastní hodnotu cyklů CPU. Vyšší hodnoty mohou zvýšit výkon, ale mohou také způsobit zamrznutí hry. Doporučený rozsah hodnot je 77–21000. Cykly @@ -439,15 +434,12 @@ Rozlišení (Handheld/V doku) Režim VSync Škálovací filtr - Ostrost FSR - Určuje jak ostře bude obraz vypadat při použití dynamického kontrastu FSR. Metoda anti-aliasingu Pokročilé Režim GPU - Určuje režim emulovaného GPU. Většina her běží bez problémů v rychlém, nebo vyváženém režimu, ale některé stále vyžadují přesný režim. Částicové efekty se většinou zobrazují korektně pouze v přesném režimu. Přesnost DMA Ovládá přesnost DMA. Bezpečná přesnost může vyřešit problémy v některých hrách, ale v některých případech může také ovlivnit výkon. Pokud si nejste jisti, použijte výchozí nastavení. Anizotropní filtrování @@ -469,8 +461,6 @@ Umožňuje přístup k předchozím stavům bufferu. Tato volba může u některých her zvýšit kvalitu vykreslování a zlepšit stabilitu výkonu. Hacky - Rychlé časování GPU - Nutí většinu her běžet v nejvyšším nativním rozlišení. Použijte hodnotu 256 pro maximální výkon a 512 pro nejlepší kvalitu. Přeskočit vnitřní invalidaci CPU Přeskočí určité invalidace mezipaměti procesoru při aktualizacích paměti, čímž snižuje vytížení CPU a zvyšuje jeho výkon. Může způsobit chyby nebo pády některých hrách. Asynchronní kompilace shaderů @@ -478,10 +468,8 @@ Rozšíření Úroveň EDS - Určuje počet funkcí využívaných v rámci rozšířeného dynamického stavu API Vulkan (Extended Dynamic State). Vyšší hodnoty umožňují využít více funkcí a mohou zvýšit výkon, ale u některých ovladačů a výrobců grafických karet mohou způsobovat problémy s kompatibilitou. Vypnuto Dynamický stav vstupu vrcholů (Vertex Input) - Aktivuje funkci dynamického stavu vstupu vrcholů (Vertex Input Dynamic State) pro lepší kvalitu a výkon. Zobrazení Orientace @@ -506,7 +494,6 @@ Protokolování Vypisovat ladicí záznamy po řádcích Vypisuje ladicí záznamy po každém napsaném řádku, což usnadňuje ladění v případě pádu nebo zamrznutí. - Výstupní engine Hlasitost @@ -617,8 +604,6 @@ ROM neexistuje Hra vyžaduje firmware - převezměte a nainstalujte firmware, nebo stiskněte \"OK\" pro pokračování v každém případě.]]> - Hledání hry... Hra nebyla nalezena pro ID titulu: %1$s @@ -699,10 +684,6 @@ 6GB (Nebezpečné) 8GB (Nebezpečné) - - Boost (1700MHz) - Rychlé (2000MHz) - Celsia Fahrenheita @@ -711,7 +692,6 @@ Rychlý - Vyvážený Přesný 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 d2988d2b27..8f6b691176 100644 --- a/src/android/app/src/main/res/values-de/strings.xml +++ b/src/android/app/src/main/res/values-de/strings.xml @@ -1,7 +1,6 @@ - Diese Software führt Spiele für die Nintendo Switch-Konsole aus. Es sind keine Spiele oder Keys enthalten. Suche bevor du beginnst deine prod.keys ]]>-Datei auf deinem Gerät.

Mehr Erfahren]]>
Hinweise und Fehler Zeigt Benachrichtigungen an, wenn etwas schief läuft. Berechtigung für Benachrichtigungen nicht zugelassen! @@ -16,22 +15,27 @@ Wert darf höchstens %1$d betragen Ungültiger Wert + Verwendung spielspezifischer Konfigurationen + - Eingabe-Overlay anzeigen + Eingabeüberlagerung anzeigen Touch-Bedienelemente während der Emulation anzeigen Am Raster ausrichten - Beim Bearbeiten die Overlay-Bedienelemente am Raster ausrichten + Überlagerungs-Steuerelemente beim Bearbeiten an einem Raster ausrichten Rastergröße - Rastergröße in Pixeln + Größe der Rasterzellen in Pixeln Verhalten - Automatisches Ausblenden des Overlays - Blende die Touch-Bedienelemente nach Ablauf der angegebenen Inaktivitätszeit automatisch aus - Automatisches Ausblenden des Overlays aktivieren - Overlay bei Controller-Eingabe ausblenden - Blende die Touch-Bedienelemente automatisch aus wenn ein physischer Controller benutzt wird. Das Overlay wird wieder eingeblendet, wenn die Verbindung zum Controller getrennt wird. - Eingabe-Overlay + Automatisches Ausblenden der Überlagerung + Überlagerung der Touch-Bedienelementen automatisch nach der festgelegten Zeit der Inaktivität ausblenden. + Automatisches Ausblenden der Überlagerung aktivieren + Überlagerung bei Controller-Eingabe ausblenden + Touch-Bedienelemente automatisch ausblenden, wenn ein physischer Controller benutzt wird. Die Überlagerung wird wieder eingeblendet, wenn die Verbindung zum Controller getrennt wird. + Controller-Tasten für Bestätigen/Zurück vertauschen + Belegung der Android-Tasten „Bestätigen“ und „Zurück“ so tauschen, dass sie bei der Nutzung der App-Benutzeroberfläche sowohl dem Switch- als auch dem Xbox-Schema entsprechen. + + Eingabeüberlagerung Bedienelemente auf dem Bildschirm konfigurieren - Overlay-Layout bearbeiten + Überlagerungs-Layout bearbeiten Position und Skalierung der Bedienelemente auf dem Bildschirm anpassen @@ -45,12 +49,12 @@ Leistungsstatistik Overlay anzeigen Anpassung Sichtbarkeit - Leistungs Overlay - Leistungsstatistik Overlay aktivieren - Konfiguriere angezeigte Informationen im Overlay - FPS anzeigen + Leistungsüberlagerung + Leistungsstatistik-Überlagerung aktivieren + Angezeigte Informationen in der Überlagerung konfigurieren + BpS anzeigen Aktuelle Bilder pro Sekunde - Frametime anzeigen + Frame-Zeit anzeigen App-RAM-Nutzung anzeigen Zeigt den RAM-Verbrauch des Emulators an System-RAM-Nutzung anzeigen @@ -59,25 +63,28 @@ Batterietemperatur-Einheiten Batterieinfo anzeigen Aktuellen Stromverbrauch und verbleibende Kapazität der Batterie anzeigen - Shader-Bau anzeigen + Shader-Erstellung anzeigen Aktuelle Anzahl der erstellten Shader anzeigen - Overlay-Position - Position des Overlays auf dem Bildschirm + Lege die Anzahl der Kerne fest, die für die Erstellung von Vulkan-Rohrleitungen verwendet werden sollen. Ein höherer Wert verbessert die Kompilierungsleistung der Rohrleitung, führt jedoch auch zu einem Anstieg der Temperaturen. + Überlagerungs-Position + Wähle aus, wo die Überlagerung auf dem Bildschirm angezeigt wird Oben links Oben rechts Unten links Unten rechts Mitte oben Mitte unten - Overlay-Hintergrund + Überlagerungs-Hintergrund Hintergrund für bessere Lesbarkeit - Geräteinfo-Overlay anzeigen - Geräte-Overlay aktivieren - Geräte-Overlay - Konfigurieren Sie, welche Informationen im Geräte-Overlay angezeigt werden + Geräteinfo-Überlagerung anzeigen + Geräteüberlagerung aktivieren + Geräteüberlagerung + Konfiguriere, welche Informationen in der Geräteüberlagerung angezeigt werden + Build-ID anzeigen + Treiberversion anzeigen Gerätemodell anzeigen GPU-Modell anzeigen SoC-Modell anzeigen @@ -85,19 +92,19 @@ Puffer-Neuanordnung deaktivieren - Wenn aktiviert, wird die Neuanordnung von gemappten Speicher-Uploads deaktiviert, was die Zuordnung von Uploads zu bestimmten Zeichenvorgängen ermöglicht. Kann in einigen Fällen die Leistung verringern. + Wenn aktiviert, wird die Neuanordnung von zugeordneten Speicher-Uploads deaktiviert, wodurch Uploads bestimmten Abrufen zugeordnet werden können. Dies kann in einigen Fällen zu Leistungseinbußen führen. Kern-Geschwindigkeit synchronisieren Synchronisiert die Taktrate des Kerns mit der maximalen Geschwindigkeit, um die Leistung zu verbessern, ohne die tatsächliche Spielgeschwindigkeit zu verändern. Host-MMU-Emulation aktivieren Diese Optimierung beschleunigt Speicherzugriffe durch das Gastprogramm. Wenn aktiviert, erfolgen Speicherlese- und -schreibvorgänge des Gastes direkt im Speicher und nutzen die MMU des Hosts. Das Deaktivieren erzwingt die Verwendung der Software-MMU-Emulation für alle Speicherzugriffe. - Debug-Regler + Fehlerbehebungs-Regler Nur für Entwicklungszwecke 0 bis 65535 NVDEC-Emulation - Methode zur Videodekodierung + Wechsle auf CPU, falls ein Absturz bei einem Cinematic auftritt. Keine @@ -126,7 +133,7 @@ Falsches Passwort Verbindung fehlgeschlagen Raum ist voll - Sie sind von diesem Raum gebannt + Du bist von diesem Raum gebannt Zugriff verweigert Benutzer existiert nicht Bereits im Raum @@ -139,7 +146,7 @@ Raum beigetreten Raum-Moderator %1$s ist beigetreten - %1$s hat verlassen + %1$s ist gegangen %1$s wurde entfernt %1$s wurde gebannt Adresse entbannt @@ -157,11 +164,11 @@ Nachricht senden Moderation Bannliste - Keine gebannten Benutzer - Entbannen + Keine gebannten Nutzer + Nutzer entbannen Entbannen %1$s wirklich entbannen? - Benutzer bannen + Nutzer bannen Öffentliche Räume Keine öffentlichen Räume gefunden Passwort erforderlich @@ -172,15 +179,15 @@ Volle Räume ausblenden Leere Räume ausblenden Zum Aktualisieren tippen - Räume suchen… + Lobbys suchen… Bevorzugtes Spiel Lobby-Typ Muss 3-20 Zeichen lang sein Erforderlich - Web-Token erforderlich, gehen Sie zu Erweiterte Einstellungen -> System -> Netzwerk + Web-Token erforderlich, gehe zu Erweiterte Einstellungen -> System -> Netzwerk Ungültiges IP-Format Muss zwischen 4–20 Zeichen lang sein und nur alphanumerische Zeichen, Punkte, Bindestriche, Unterstriche und Leerzeichen enthalten - Ungültiger Benutzername, stellen Sie sicher, dass er in System → Netzwerk korrekt eingestellt ist + Ungültiger Benutzername; stelle sicher, dass er unter System -> Netzwerk korrekt konfiguriert ist. Muss 48 Zeichen lang sein und nur Kleinbuchstaben a-z enthalten Muss zwischen 1-65535 liegen Abbrechen @@ -188,20 +195,20 @@ Aktualisieren Raumliste Öffentlich - Unlisted + Nicht gelistet Willkommen! - Erfahre wie man <b>Eden</b> einrichtet und beginne mit der Emulation. + Erfahre, wie du Eden einrichtest, und tauche in die Welt der Emulation ein. Erste Schritte Schlüssel - Wähle deine <b>prod.keys</b> Datei mit dem Button unten aus. + Wähle deine prod.keys -Datei über die untenstehende Schaltfläche aus. Firmware - Wähle deine firmware.zip-Datei mit dem Button unten aus + Wähle deine firmware.zip-Datei über die untenstehende Schaltfläche aus. Spiele - Wähle mit dem Knopf unten den <b>Spiele</b>-Ordner aus. + Wähle deinen Spiele-Ordner mit der untenstehenden Schaltfläche aus. Fertig - Du bist start klar.\nViel Spaß mit deinen Spielen! + Alles bereit.\nViel Spaß beim Spielen! Weiter Zurück Spiele hinzufügen @@ -214,23 +221,23 @@ Raster Kompaktes Gitter Karussell - Scrennshot für %1$s + Bildschirmfoto für %1$s Ordner Nicht mehr anzeigen Neues Spieleverzeichnis erfolgreich hinzugefügt. - Auf Updates überprüfen - Beim Start auf Updates überprüfen und optional das neue Update herunterladen und installieren - Update verfügbar - Eine Version ist verfügbar: %1$s\n\nWillst du sie herunterladen\? - Update wird heruntergeladen - Herunterladen des Updates fehlgeschlagen - Update erfolgreich installiert - Installieren des Updates fehlgeschlagen: %1$s + Auf Aktualisierungen prüfen + Beim Start auf Aktualisierungen prüfen und optional die neue Aktualisierung herunterladen und installieren + Aktualisierung verfügbar + Eine neue Version ist verfügbar: %1$s\n\nWillst du sie herunterladen\? + Aktualisierung wird heruntergeladen + Herunterladen der Aktualisierung fehlgeschlagen + Aktualisierung erfolgreich installiert + Installieren der Aktualisierung fehlgeschlagen: %1$s Suche Einstellungen Es wurden keine Dateien gefunden oder es wurde noch kein Spielverzeichnis ausgewählt. - Spiele-Ordner verwalten - Erlaubt Eden die Spieleliste zu füllen + Spielordner verwalten + Ermöglicht es Eden, die Spieleliste zu füllen. Auswahl des Spieleverzeichnisses überspringen? Spiele werden in der Spieleliste nicht angezeigt, wenn kein Ordner ausgewählt ist. https://yuzu-mirror.github.io/help/quickstart/#dumping-games @@ -241,33 +248,33 @@ Hinzufügen der Schlüssel überspringen? Für die Emulation von Spielen sind gültige Schlüssel erforderlich. Wenn du fortfährst, funktionieren nur Homebrew-Anwendungen. https://yuzu-mirror.github.io/help/quickstart/#guide-introduction - Firmware nicht hinzufügen? + Das Hinzufügen der Firmware überspringen\? Emulatordaten einrichten Für die Funktion des Emulators werden Schlüssel benötigt, und Firmware ist empfohlen und für die Verwendung des QLaunch-Applets erforderlich. - Berechtigung erteilen - Optionale Berechtigungen erteilen um bestimmte Features des Emulators zu nutzen + Berechtigungen erteilen + Erteile optionale Berechtigungen zur Nutzung bestimmter Funktionen des Emulators. Viele Spiele benötigen Zugriff auf die Firmware, um richtig zu funktionieren. https://yuzu-mirror.github.io/help/quickstart/#guide-introduction Benachrichtigungen - Erteile mit dem Knopf unten die Berechtigung, Benachrichtigungen zu senden. + Erteile die Berechtigung für Benachrichtigungen über die untenstehende Schaltfläche. Zugriff verweigert Du hast diese Berechtigung zu oft verweigert und musst sie nun manuell in den Systemeinstellungen erteilen. Über - Build-Version, Credits und mehr + Build-Version, Mitwirkende und mehr Systeminformationen Detaillierte Systeminformationen anzeigen Hersteller Modell Produkt Android-Version - Sicherheitspatch + Sicherheits-Patch Build-ID Allgemeine Informationen Hardware Unterstützte ABIs CPU-Informationen GPU-Informationen - Vulkan Treiberversion + Vulkan-Treiberversion Fehler beim Abrufen der Emulatorinformationen Speicherinformation Gesamtspeicher @@ -278,10 +285,38 @@ Überspringen Abbrechen Amiibo-Schlüssel installieren - Benötigt um Amiibos im Spiel zu verwenden + Benötigt, um Amiibos im Spiel zu verwenden GPU-Treiber-Hersteller GPU-Treiber Verwaltung Alternative Treiber für eventuell bessere Leistung oder Genauigkeit installieren + Frame-Generation + Konfiguriere Frame-Generation für dieses Spiel + Füge zwischen den gerenderten Bildern interpolierte Bilder mithilfe von Lossless Scaling ein. + Frame-Multiplikator + Wie viele Bilder für jedes gerenderte angezeigt werden soll. Höhere Werte kosten proportional mehr GPU-Zeit. Nach mehr zu fragen, als dein Bildschirm darstellen kann, wird die Emulation verlangsamen. + 2x + 3x + 4x + Ziel-Bildrate + Wähle die Rate aus die dein Bildschirm tatsächlich darstellen kann. Der Multiplikator steigt oder fällt dann alleine um sie zu halten, und rollt jeden Schritt zurück, der das Spiel selber langsamer macht. + Nutze einen fixierten Multiplikator + 60 FPS + 90 FPS + 120 FPS + Ausbalanciert (1 Bild) + Flüssigste (2 Bilder) + Frame-Generation nicht verfügbar + Optional. Stelle deine eigene Lossless.dll zur Verfügung, um Frame-Generation später zu aktivieren. + Installiere Lossless.dll + Wähle eine andere Kopie von Lossless.dll + Unterstützt + Lossless Scaling + Installiert + Nicht installiert + Ersetzen + Entfernen + Schließe zuerst das Spiel + Nichts zum Entfernen Erweiterte Einstellungen Emulatoreinstellungen konfigurieren Kürzlich gespielt @@ -289,36 +324,39 @@ Eden-Ordner öffnen Eden\'s interne Dateien verwalten Verhalten und Aussehen der App verändern - Kein Dateimanager gefunden + Kein Datei-Manager gefunden Eden-Verzeichnis konnte nicht geöffnet werden - Bitte suche den Benutzerordner manuell über die Seitenleiste des Dateimanagers. + Bitte suche den Benutzerordner manuell über die Seitenleiste des Datei-Managers. Speicherdaten verwalten Speicherdaten gefunden. Bitte wähle unten eine Option aus. Speicherdaten importieren Das überschreibt alle existierenden Speicherdaten für dieses Spiel mit der ausgewählten Datei. Wirklich fortfahren? - Importiere Speicherdaten... - Exportiere Speicherdaten... + Speicherstände werden importiert... + Speicherstände werden exportiert... Erfolgreich importiert Ungültige Speicherverzeichnisstruktur Der erste Unterordnername muss die Titel-ID des Spiels sein. Firmware installieren - Die Firmware muss in einem ZIP-Archiv vorliegen und wird zum Booten einiger Spiele benötigt + Die Firmware muss in einem ZIP-Archiv vorliegen und wird zum Starten einiger Spiele benötigt Firmware wird installiert - Bei der Firmware installation ist etwas fehlgeschlagen. - Stellen Sie sicher, dass sich die Firmware-NCA-Dateien im Stammverzeichnis der ZIP-Datei befinden, und versuchen Sie es erneut. - Die Deinstallation der Firmware ist fehlgeschlagen - Debug-Logs teilen - Debug-Logs an Eden zur Untersuchung absenden - Keine Log-Datei gefunden + Firmware-Installation fehlgeschlagen + Stelle sicher, dass sich die Firmware-NCA-Dateien im Stammverzeichnis der ZIP-Datei befinden, und versuche es erneut. + Deinstallation der Firmware fehlgeschlagen + Fehlerbehebungs-Protokolle teilen + Fehlerbehebungs-Protokolle an Eden zur Fehlerbehebung absenden + Keine Protokolldatei gefunden + GPU-Protokolle teilen + Teile Edens GPU-Protokolldatei, um Grafikprobleme zu beheben + Keine GPU-Protokolldatei gefunden Spiel installieren - Spiel-Updates oder DLCs installieren - Installiere... + Spielaktualisierungen oder DLCs installieren + Inhalt wird installiert... Fehler beim Installieren der Datei(en) auf NAND - Bitte stellen Sie sicher, dass der/die Inhalt(e) gültig sind und dass die Datei prod.keys installiert ist. + Bitte stelle sicher, dass der/die Inhalt(e) gültig sind und dass die Datei prod.keys installiert ist. Um mögliche Konflikte zu vermeiden, ist die Installation von Basisspielen nicht gestattet. %1$d Installationsfehler - Game content(s) installed successfully + Spielinhalt(e) erfolgreich installiert %1$d erfolgreich installiert %1$d erfolgreich überschrieben https://yuzu-mirror.github.io/help/quickstart/#dumping-installed-updates @@ -326,20 +364,20 @@ Wirklich fortfahren?
Das Laden von benutzerdefinierten Treibern wird für dieses Gerät momentan nicht unterstützt.\nSchau später einfach nochmal nach, ob die Unterstützung hinzugefügt wurde! Eden-Daten verwalten Importieren/Exportieren Sie Firmware, Schlüssel, Benutzerdaten und mehr! - Spiele-Ordner + Spielordner Tiefer Scan - Spiele-Ordner hinzufügen + Spielordner hinzufügen Ordner bereits vorhanden Ordner-Eigenschaften %d Spielstand konnte nicht importiert werden - %d Spielstände konnten nicht importiert werden + %d-Spielstände konnten nicht importiert werden %d Spielstand erfolgreich importiert - %d Spielstände erfolgreich importiert + %d -Spielstände erfolgreich importiert - Keine Spielstände gefunden + Keine Speicherdaten gefunden Inhalt überprüfen Überprüft installierte Inhalte auf Fehler @@ -356,14 +394,15 @@ Wirklich fortfahren?
Schlüsselinstallation fehlgeschlagen Schlüssel erfolgreich installiert Ein oder mehrere Schlüssel konnten nicht kopiert werden. - Stellen Sie sicher, dass Ihre Schlüsseldatei die Erweiterung .keys hat, und versuchen Sie es erneut. - Schlüssel konnten nicht initialisiert werden. Stellen Sie sicher, dass Ihre Dumping-Tools auf dem neuesten Stand sind, und dumpen Sie die Schlüssel erneut. + Stelle sicher, dass deine Schlüsseldatei die Erweiterung .keys hat, und versuche es erneut. + Schlüssel konnten nicht initialisiert werden. Stelle sicher, dass deine Auslese-Werkzeuge auf dem neuesten Stand sind, und lies dann die Schlüssel erneut aus. Qlaunch Anwendungen vom Systemstartbildschirm aus starten Applet-Launcher System-Applets mit Firmware starten + Firmware nicht installiert Applet nicht verfügbar Album Bilder im Screenshot-Ordner anzeigen @@ -382,6 +421,7 @@ Wirklich fortfahren?
In die Zwischenablage kopiert Ein quelloffener Switch-Emulator Beitragende + Personen, die Eden für Android möglich gemacht haben Projekte, die Eden für Android möglich machen Build Nutzerdaten @@ -418,8 +458,7 @@ Wird der Handheld-Modus verwendet, verringert es die Auflösung und erhöht die Legen Sie eine benutzerdefinierte Echtzeituhr fest - CPU-Übertaktung - Zwingt die emulierte CPU, mit höherer Taktrate zu laufen, wodurch bestimmte FPS-Begrenzungen reduziert werden. Verwende Boost (1700MHz), um mit der höchsten nativen Taktrate der Switch zu laufen, oder Fast (2000MHz), um mit der doppelten Taktrate zu laufen. + CPU-Takte Benutzerdefinierte CPU-Ticks Legen Sie einen benutzerdefinierten Wert für CPU-Ticks fest. Höhere Werte können die Leistung steigern, aber auch zum Einfrieren des Spiels führen. Ein Bereich von 77–21000 wird empfohlen. Ticks @@ -439,15 +478,12 @@ Wird der Handheld-Modus verwendet, verringert es die Auflösung und erhöht die Auflösung (Handheld/Gedockt) VSync-Modus Skalierungsfilter - FSR-Schärfe - Bestimmt die Schärfe bei FSR-Nutzung. Kantenglättung Erweitert GPU-Modus - Steuert den GPU-Emulationsmodus. Die meisten Spiele werden im Modus \"Fast\" oder \"Balanced\" gut gerendert, für einige ist jedoch weiterhin der Modus \"Accurate\" erforderlich. Partikel werden in der Regel nur im Modus \"Accurate\" korrekt gerendert. DMA-Genauigkeit Steuert die DMA-Präzisionsgenauigkeit. Sichere Präzision kann Probleme in einigen Spielen beheben, kann aber in einigen Fällen auch die Leistung beeinträchtigen. Im Zweifel lassen Sie dies auf Standard stehen. Anisotrope Filterung @@ -467,19 +503,19 @@ Wird der Handheld-Modus verwendet, verringert es die Auflösung und erhöht die Verbessert die Genauigkeit in einigen Spielen. Hacks - Schnelle GPU-Zeit - Erzwingt bei den meisten Spielen die höchste native Auflösung. Verwende 256 für maximale Leistung und 512 für maximale Grafikqualität + GPU-Takte CPU-interne Invalidierung überspringen Überspringt bestimmte Cache-Invalidierungen auf CPU-Seite während Speicherupdates, reduziert die CPU-Auslastung und verbessert die Leistung. Kann in einigen Spielen zu Fehlern oder Abstürzen führen. Asynchrone Shader Kompiliert Shader asynchron. Dies kann Ruckler reduzieren, aber auch Grafikfehler verursachen. + Standard + + Erweiterungen Erweiterter dynamischer Status - Steuert die Anzahl der Funktionen, die im \"Vertex Input Dynamic State\" werden können. Höhere Werte ermöglichen mehr Funktionen und können die Leistung steigern, können aber bei einigen Treibern und Anbietern zu Problemen führen. Deaktiviert Vertex Input Dynamic State - Aktiviert die Funktion \"Vertex Input Dynamic State\" für bessere Qualität und Leistung. Sample Shading Ermöglicht es dem Fragment-Shader, in einem Multisample-Fragment pro Sample anstatt einmal pro Fragment ausgeführt zu werden. Verbessert die Grafikqualität auf Kosten der Leistung. @@ -496,6 +532,7 @@ Wird der Handheld-Modus verwendet, verringert es die Auflösung und erhöht die CPU + Takte Auto-Stub verwenden Ergänzt automatisch fehlende Dienste und Funktionen. Kann die Kompatibilität verbessern, aber auch zu Abstürzen und Stabilitätsproblemen führen. @@ -510,7 +547,6 @@ Wird der Handheld-Modus verwendet, verringert es die Auflösung und erhöht die Protokollierung Debug-Protokolle zeilenweise leeren Leert Debug-Protokolle bei jeder geschriebenen Zeile, was das Debuggen bei Abstürzen oder Einfrieren erleichtert. - Allgemein @@ -751,6 +787,7 @@ Wirklich fortfahren?
Bestätigen Sie die Deinstallation Möchten Sie dieses Add-on wirklich deinstallieren\? Integrität prüfen + Verifiziere... Integritätsüberprüfung erfolgreich! Integritätsüberprüfung fehlgeschlagen! Der Dateiinhalt ist möglicherweise beschädigt @@ -771,8 +808,6 @@ Wirklich fortfahren?
ROM-Datei existiert nicht Spiel erfordert Firmware - dumpen und installieren Sie Firmware, oder drücken Sie \"OK\", um trotzdem zu starten.]]> - Suche nach Spiel... Spiel nicht gefunden für Titel-ID: %1$s @@ -872,14 +907,15 @@ Wirklich fortfahren?
6 GB (Unsicher) 8 GB (Unsicher) - - Boost (1700MHz) - Schnell (2000MHz) + + Normal + Beschleunigung + Übertaktung - - Aus - Mittel (256) - Hoch (512) + + Normal + Beschleunigung + Übertaktung Sehr klein (16 MB) @@ -919,7 +955,6 @@ Wirklich fortfahren?
Schnell - Ausgeglichen Genau @@ -927,6 +962,12 @@ Wirklich fortfahren?
Unsicher Sicher + + Standard + Direkt + Ausgewogen + Genau + Konservativ Aggressiv @@ -993,28 +1034,32 @@ Wirklich fortfahren?
Material You App-Einstellungen Theme und Farben + Vollbild-Modus Design System folgen Hell Dunkel - Keine - Schwarze Hintergründe Bei Verwendung des dunklen Designs, schwarze Hintergründe verwenden. + + Ordner + QLaunch App-Sprache Sprache der App-Oberfläche ändern System folgen Designfarbe + Eden Violett Blau Cyan Rot + Grün Gelb Orange Rosa @@ -1046,6 +1091,10 @@ Wirklich fortfahren?
Applet-Overlay aktivieren Aktiviert Horizons eingebautes Overlay-Applet. Halte die Home-Taste eine Sekunde lang gedrückt, um es anzuzeigen. + + Nutzerverwaltung + Fehler + Lizenzen Hochwertiges Upscaling von AMD 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 35257c2beb..05135b515e 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -1,7 +1,6 @@ - Este software arrancará juegos para la consola Nintendo Switch. No se incluyen títulos ni claves de juego.

Antes de continuar, por favor localiza tu archivo en el almacenamiento de tu dispositivo.

Aprender más]]><b>
Avisos y errores Mostrar notificaciones cuando algo vaya mal. ¡Permiso de notificación no concedido! @@ -66,6 +65,8 @@ Muestra el consumo de energía actual y la capacidad restante de la batería Mostrar construcción de sombreadores Muestra el número actual de sombreadores que se están construyendo + Hilos de trabajo de canalización + Gestiona la cantidad de núcleos usados para la construcción de canalizaciones de Vulkan; un valor más alto mejorará el rendimiento de la contrucción de canalización, pero también aumentarán las temperaturas. Posición de la superposición Elige dónde se muestra la superposición en la pantalla Superior izquierda @@ -104,7 +105,6 @@ Emulación NVDEC - Seleccione cómo se maneja la decodificación de vídeo (NVDEC) durante las escenas y las introducciones. Ninguno @@ -289,6 +289,42 @@ Obtenedor de controladores de la GPU Gestor de controladores de la GPU Instale los controladores alternativos para obtener un posible mejor rendimiento o precisión + Añadir efecto + Abrir lista + Cerrar lista + Restablecer a predeterminado + Generación de fotograma + Configurar la generación de fotogramas para este juego + Multiplicador de fotograma + 2x + 3x + 4x + Objetivo de tasa de fotogramas + Usar un multiplicador fijo + 60 FPS + 90 FPS + 120 FPS + Latencia más baja (Sin búfer) + Equilibrado (1 fotograma) + Más suave (2 fotogramas) + Generación de fotogramas no disponbile + Instalar Lossless.dll + Seleccionar una copia diferente de Lossless.dll + Generación de fotograma + Soportado + Escalado sin pérdidas + Instalado + No instalado + Reemplazar + Borrar + Borrar Lossless.dll instalado y sus sombreadores preparados + Lossless.dll no instalado + Primero cierra el juego + Nada que borrar + Lossless.dll instalado correctamente + No se pudo instalar Lossless.dll + No se pudo copiar el archivo seleccionado. + No se pudo leer el archivo seleccionado. Ajustes avanzados Configurar los ajustes del emulador Jugado recientemente @@ -374,7 +410,7 @@ Qlaunch Iniciar aplicaciones desde la pantalla de inicio del sistema Lanzador de Applet - Ejecutar applets de sistema usando el firmware instalado + Ejecutar applets del sistema usando el firmware instalado El firmware no está instalado Applet no disponible prod.keys y el firmware están instalados e inténtelo de nuevo.]]> @@ -420,6 +456,9 @@ Precisión de la CPU %1$s%2$s + Argumentos Homebrew + Argumentos en la línea de comandos pasados al homebrew al ser lanzado (p.e. -noglsl). + Nombre del dispositivo Modo sobremesa @@ -433,8 +472,7 @@ Configurar RTC personalizado - Overclock de la CPU - Fuerza a la CPU emulada a ejecutarser a una velocidad de reloj más alta, lo cual reduce ciertos limitadores de fotogramas por segundo. Usa Boost (1700 MHz) para ejecutar a la velocidad de reloj nativa más alta de la Switch, o Fast (2000 MHz) para ejecutar a una velocidad doble de reloj. + Relojes de la CPU Ticks de CPU personalizados Establezca un valor personalizado de los ciclos de la CPU. Los valores más altos pueden aumentar el rendimiento, pero también pueden hacer que el juego se congele. Se recomienda un rango de 77–21000. Ciclos @@ -454,17 +492,17 @@ Resolución (Portátil/Sobremesa) Modo de sincronización vertical Filtro de adaptación de ventana - Nitidez FSR - Ajusta la intensidad del filtro de enfoque al usar el contraste dinámico de FSR. + Nitidez FSR/SGSR + Determina el grado de nitidez de la imagen al usar filtros FSR o SGSR Método de suavizado de bordes Avanzado Modo de la GPU - Controla el modo de la emulación de la GPU. La mayoría de los juegos se renderizan correctamente en los modos Rápido o Equilibrado, pero algunos requieren Preciso. Las partículas tienden a renderizarse correctamente solo con el modo Preciso. Precisión de DMA Controla la precisión de DMA. La precisión segura puede solucionar problemas en algunos juegos, pero también puede afectar al rendimiento en algunos casos. Si no está seguro, déjelo en Predeterminado. + Comportamiento de vallado de la GPU Filtrado anisotrópico Mejora la calidad de las texturas al ser observadas desde ángulos oblicuos Modo de uso de VRAM @@ -479,26 +517,25 @@ Forzar velocidad al máximo (solo Adreno) Fuerza a la GPU a ejecutarse a la velocidad máxima de reloj posible (se seguirán aplicando restricciones térmicas). Emulación de GPU asíncrona - Ejecuta la emulación de la GPU de forma asíncrona para reducir los bloqueos de la CPU y mejorar el rendimiento. Desactiva esta opción solo si experimentas problemas de sincronización. - Presentación asíncrona - Mejora ligeramente el rendimiento al mover la presentación a un hilo independiente de la CPU. + Este hack puede aumentar el rendimiento ejecutando la emulación de la GPU de forma asíncrona, a costa de problemas gráficos y un aumento en la tasa de fallos debido a operaciones relacionadas con la sincronización. Usar limpieza reactiva Mejora la precisión de renderizado en algunos juegos, pero reduce el rendimiento. Activar el historial del búfer Permite el acceso al estado del búfer anterior. Esta opción puede mejorar la calidad de renderizado y la consistencia en el rendimiento de algunos juegos. Búferes de vértices optimizados - Permite la optimización del enlace del búfer de vértices para un mejor rendimiento. Requiere controladores Mesa 26.0+ Turnip/ controladores QCOM. Causará fallos con controladores Turnip más antiguos. + Permite la optimización del enlace del búfer de vértices para un mejor rendimiento. Requiere controladores Mesa 26.0+ Turnip/ controladores QCOM. Fallará con controladores Turnip más antiguos (versión 25.3 o inferior). Hacks - Tiempo rápido de la GPU - Fuerza a la mayoría de los juegos a ejecutarse a su resolución nativa más alta. Usa 256 para un máximo rendimiento y 512 para una fidelidad gráfica óptima. + Relojes de la GPU Omitir invalidación interna de la CPU Omite ciertas invalidaciones de caché de la CPU durante las actualizaciones de memoria, lo que reduce el uso de la CPU y mejora su rendimiento. Esto puede causar fallos o bloqueos en algunos juegos. Arreglar los efectos de resplandor Reduce el efecto de resplandor en LA/EOW (Adreno A6XX - A7XX/ Turnip), elimina el resplandor en Burnout. Advertencia: puede causar artefactos gráficos en otros juegos. Emular BGR565 Soluciona problemas con colores invertidos en juegos, artefactos o sombras extrañas. + Activar la pasada de reescalado heredada + Permite el manejo de versiones anteriores para el paso de configuración de reescalado para juegos mediante el uso de una ruta de reescalado rápida. Usar sombreadores asíncronos Compila los sombreadores de forma asíncrona. Esto puede reducir los tirones, pero también puede introducir errores gráficos. Ajustes de desentrelazado de la GPU @@ -517,10 +554,10 @@ Extensiones Estado dinámico extendido - Controla la cantidad de funciones que se pueden usar en el Estado Dinámico Extendido. Un número mayor permite más funciones y puede aumentar el rendimiento, pero puede causar problemas con algunos controladores y proveedores. + Controla la cantidad de características que se pueden usar en ExtendedDynamicState (EDS). Un valor más alto permitirá reducir la cantidad de compilaciones de canalización basadas en el estado dinámico compatible con el controlador. Desactivado Estado dinámico de entrada de vértices - Activa la función de estado dinámico de entrada de vértices para una mejor calidad y rendimiento. + La activación de esta función permite un manejo más flexible de la entrada de vértices, lo que podría reducir el tiempo de compilación de la canalización en vértices/búfer. Muestreo de sombreado Permite que el sombreador de fragmentos se ejecute por muestra en un fragmento multimuestreado, en lugar de una sola vez por fragmento. Mejora la calidad de los gráficos a coste de algo de rendimiento. @@ -537,6 +574,7 @@ CPU + Relojes Usar Auto Stub Rellena automáticamente servicios y funciones ausentes. Puede mejorar la compatibilidad pero puede causar cierres inesperados. @@ -551,17 +589,21 @@ Registro Vaciar los registros de depuración por línea Vacía los registros de depuración en cada línea escrita, facilitando la depuración en casos de bloqueos o congelamientos. + Filtro de registros + Controla las categorias de registros de Eden. Por ejemplo: *:Info Service.LM:Debug Registros de la GPU - Activar los registros de la GPU - Registra las operaciones de la GPU en eden_gpu.log para la depuración de los controladores de Adreno Nivel de registros Nivel de detalle de los registros de la GPU (más alto = más detalles, más sobrecarga) Registros de llamadas del API de Vulkan Rastrear todas las llamadas del API de Vulkan en el búfer circular - Volcar sombreadores - Guardar sombreadores compilados de SPIR-V en archivos + Volcar sombreadores SPIR-V + Guardar binarios recompilados SPIR-V (.spv) a la carpeta de volcado. Inspeccionar con spirv-dis/spirv-cross/spirv-val. + Volcar sombreadores huesped (Maxwell) + Guardar archivos bytecode del sombreador huesped Maxwell (*.ash) a la carpeta de volcado. Inspeccionar con nvdisasm. + Volcar macros Maxwell + Guardar archivos de programa macro Maxwell (*.macro) a la carpeta de volcado. Inspeccionar con envydis. Rastrear memoria de la GPU Monitorizar las asignaciones y desasignaciones de memoria de la GPU Información de depuración del controlador @@ -739,7 +781,7 @@ Directorio de los datos de guardado Establecer una ruta personalizada para el almacenamiento de los datos de guardado - Restaurar valores predeterminados + Restaurar los valores predeterminados Migrar datos de guardado ¿Desea migrar los datos de guardado existentes a la nueva ubicación\? Datos de guardado migrados con éxito @@ -799,7 +841,7 @@ Tipo de contenido Actualizaciones y contenido descargable Mods y trucos - Aviso importante de complementos + Aviso importante sobre los complementos Para instalar mods y trucos, debe seleccionar una carpeta que contenga los directorios cheats/, romfs/, o exefs/ . ¡No podemos confirmar si éstos serán compatibles con su juego, así que tenga cuidado! Directorio no válido @@ -810,7 +852,7 @@ Aviso importante de contenido El contenido seleccionado no es de este juego.\n¿Instalar aun que\? Confirmar desinstalación - ¿Está seguro de que quiere desinstalar este complemento\? + ¿Estás seguro de que quieres desinstalar este complemento\? Verificar integridad Verificando... ¡La verificación de integridad ha sido un éxito! @@ -833,8 +875,6 @@ Archivo ROM no existe El juego requiere firmware - vuelque e instale el firmware, o pulse \"Aceptar\" para continuar de todos modos.]]> - Buscando juego... Juego no encontrado para el ID de título: %1$s @@ -934,14 +974,15 @@ 6GB (Inseguro) 8GB (Inseguro) - - Impulsado (1700MHz) - Rápido (2000MHz) + + Normal + Impulso + Overclock - - Desactivado - Medio (256) - Alto (512) + + Normal + Impulso + Overclock Muy pequeño (16 MB) @@ -981,7 +1022,6 @@ Rápido - Equilibrado Preciso @@ -989,6 +1029,12 @@ Inseguro Seguro + + Predeterminado + Inmediato + Equilibrado + Preciso + Conservador Agresivo @@ -1064,11 +1110,9 @@ Claro Oscuro - Nada - Fondos oscuros - Cuando utilice el modo oscuro, aplique fondos negros. + Cuando se usa el modo oscuro, aplicar fondos de pantalla negros. Carpeta 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 39f0432d8e..6b40d97f2a 100644 --- a/src/android/app/src/main/res/values-fa/strings.xml +++ b/src/android/app/src/main/res/values-fa/strings.xml @@ -800,7 +800,6 @@ تاریک - oboe cubeb @@ -808,9 +807,6 @@ x4 x8 x16 - x32 - x64 - None پس‌زمینه مشکی 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 0f41841fb5..7507051dd7 100644 --- a/src/android/app/src/main/res/values-fr/strings.xml +++ b/src/android/app/src/main/res/values-fr/strings.xml @@ -1,7 +1,6 @@ - Ce logiciel permet de jouer à des jeux de la console Nintendo Switch. Aucun titre ni clé de jeu n\'est inclus.

Avant de commencer, veuillez localiser votre fichier prod.keys ]]> sur le stockage de votre appareil.

En savoir plus]]>
Avis et erreurs Affiche des notifications en cas de problème. Permission de notification non accordée ! @@ -104,7 +103,6 @@ Émulation NVDEC - Sélectionnez la manière dont le décodage vidéo (NVDEC) est géré pendant les cinématiques et les intros. Aucun @@ -432,9 +430,6 @@ 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 - - Overclock du CPU - Force le CPU émulé à fonctionner à une fréquence plus élevée, en contournant certaines limitations de FPS. Utilisez Boost (1700 MHz) pour atteindre la fréquence native maximale de la Nintendo Switch, ou Fast (2000 MHz) pour doubler la fréquence. C’est un “hack”. Certains chipsets moins puissants peuvent voir leurs performances réduites et les jeux peuvent se comporter différemment. Ticks CPU personnalisés Définissez une valeur personnalisée de ticks CPU. Des valeurs plus élevées peuvent améliorer les performances, mais peuvent aussi provoquer des gels du jeu. Une plage de 77 à 21000 est recommandée. Ticks @@ -454,11 +449,12 @@ Résolution (Mode Portable/Mode TV) Mode VSync Filtre de fenêtre adaptatif - Netteté FSR - Détermine à quel point l\'image sera affinée lors de l\'utilisation du contraste dynamique FSR. Méthode d\'anticrénelage + Avancé + + Mode GPU Précision DMA Contrôle la précision du DMA. Une précision sûre peut résoudre les problèmes dans certains jeux, mais peut aussi affecter les performances dans certains cas. Si vous n\'êtes pas sûr, laissez ce paramètre sur Par défaut. Filtrage anisotropique @@ -474,16 +470,28 @@ Réduire les saccades en stockant et en chargeant localement les shaders générés Forcer les fréquences maximales (Adreno uniquement) Forcer le GPU à fonctionner à ses fréquences maximales possibles (les contraintes thermiques seront toujours appliquées). + Émulation GPU asynchrone + Ce contournement peut améliorer les performances en faisant tourner l\'émulation GPU de manière asynchrone au détriment de la fidélité graphique et de la stabilité (plantages plus fréquents) dus à des erreurs de cadence. Utiliser le vidage réactif Améliore la précision du rendu dans certains jeux au détriment des performances. + Activer l\'historique du tampon + Contournements + Ignorer l\'invalidation interne du CPU Ignore certaines invalidations de cache côté CPU lors des mises à jour mémoire, réduisant l\'utilisation du CPU et améliorant ses performances. Peut causer des bugs ou plantages sur certains jeux. + Emuler BGR565 Utiliser les shaders asynchrones Compile les shaders de manière asynchrone. Cela peut réduire les saccades mais peut aussi provoquer des problèmes graphiques. + Désactivé + Par défaut + + + Extensions + État dynamique étendu Désactivé État dynamique d\'entrée de sommet - Active la fonctionnalité d\'état dynamique des entrées de sommets pour une meilleure qualité et de meilleures performances. + Échantillonnage de shading Affichage Orientation @@ -508,6 +516,17 @@ Journalisation Vider les journaux de débogage ligne par ligne Vide les journaux de débogage à chaque ligne écrite, facilitant le débogage en cas de plantage ou de gel. + + Journalisation GPU + Niveau de journalisation + Journaliser les appels API Vulkan + Monitorer la mémoire GPU + Monitorer les allocations et désallocations de la mémoire GPU + Informations de débogage du pilote + Nombre d\'appels Vulkans récents à monitorer (par défaut : 512) + 64 à 4096 entrées + + Général Moteur de sortie @@ -591,6 +610,7 @@ Par défaut + Par défaut Chargement... Extinction en cours... Voulez-vous réinitialiser ce paramètre à sa valeur par défaut ? @@ -629,6 +649,7 @@ Par défaut Pilote non valide sélectionné Pilote déjà installé + %1$s (Installé) Pilote du GPU du système Installation du pilote... @@ -648,6 +669,7 @@ Installation en cours… Dernière Pilote recommandé : + Modèle GPU GPU non pris en charge Votre GPU ne prend pas en charge l\'injection de pilotes. Il n\'est pas recommandé de définir des pilotes personnalisés. @@ -657,6 +679,9 @@ Mode TV, région, langue Vidéo Niveau de précision, résolution, cache de shaders + Paramètres rapides + Activer les paramètres rapides + Autoriser l\'accès aux paramètres rapides par le balayage de l\'écran et le bouton du menu Audio Moteur de sortie, volume Contrôles @@ -664,6 +689,25 @@ Joueur %d Débogage Débogage CPU/GPU, API graphique, fastmem + Chemins personnalisés + Sauvegarder le répertoire des données + + + Sauvegarder le répertoire des données + Définir un chemin personnalisé pour les sauvegardes + Réinitialiser par défaut + Migrer les données de sauvegarde + Données de sauvegarde supprimées avec succès + Échec de la migration des données de sauvegarde + La destination contient déjà des données. Voulez-vous les écraser \? + Accorder la permission + Dossier NAND + Définir un chemin personnalisé pour le stockage NAND + Répertoire de carte SD + Définir un chemin personnalisé pour le stockage de la carte SD virtuelle + Chemin défini avec succès + Sauter + Info ID du programme, développeur, version @@ -677,6 +721,7 @@ Copier les détails Extensions Activer les mods, mises à jour et DLC + Temps de jeu : Réinitialiser le Temps de Jeu Réinitialiser le temps de jeu du jeu actuel à 0 seconde Cela effacera les données de temps de jeu du jeu actuel. Êtes-vous sûr \? @@ -684,6 +729,9 @@ Modifier le Temps de Jeu Heures Minutes + h + m + s Les heures doivent être comprises entre 0 et 9999 Les minutes doivent être comprises entre 0 et 59 Les secondes doivent être comprises entre 0 et 59 @@ -715,6 +763,7 @@ Confirmer la désinstallation Êtes-vous sûr de vouloir désinstaller cette extension ? Vérifier l\'intégrité + Vérification... La vérification de l\'intégrité a réussi ! La vérification de l\'intégrité a échoué ! Le contenu d\'un fichier peut être corrompu @@ -735,8 +784,6 @@ Le fichier ROM n\'existe pas Jeu nécessite un firmware - dumper et installer le firmware ou appuyez sur \"OK\" pour continuer quand même.]]> - Recherche du jeu... Jeu non trouvé pour l\'ID de titre : %1$s @@ -786,6 +833,7 @@ Opacité Réinitialiser l\'overlay Modifier l\'overlay + Aimanter à la grille Mettre en pause l\'émulation Reprendre l\'émulation Options de l\'overlay @@ -835,9 +883,26 @@ 6 Go (Dangereux) 8 Go (Dangereux) - - Boost (1700MHz) - Rapide (2000MHz) + + Très petit (16 Mo) + Petit (32 Mo) + Normal (128 Mo) + Large (256 Mo) + Très large (512 Mo) + + + Très faible (4 Mo) + Faible (8 Mo) + Normal (16 Mo) + Moyen (32 Mo) + Élevé (64 Mo) + + + Très faible (32) + Faible (64) + Normal (128) + Moyen (256) + Élevé (512) Celsius @@ -854,6 +919,10 @@ Aucune + + Rapide + Précis + Défaut Dangereux @@ -887,6 +956,26 @@ Paranoïaque Débogage + + Paramètres de Freedreno + Paramètres du pilote GPU + Préréglages rapides + Paramètres actuels + Paramètres avancés + Valeur de la variable + Ajouter la variable + Effacer tout + Configuration Freedreno sauvegardée + Toutes les variables Freedreno effacées + Variable %1$s ajouté + Préréglage \'%1$s\' appliqué + Le nom de la variable ne peut pas être vide + Échec de l\'assignation de la variable + À propos de la configuration Freedreno + Paramètres de Freedreno + Configurer les paramètres du pilote GPU pour ce jeu + Configuration Freedreno sauvegardée + Pavé directionnel Stick gauche @@ -903,6 +992,7 @@ Material You Paramètres de l’App Thème et Couleur + Mode plein écran Changer le mode de thème Automatique @@ -913,16 +1003,24 @@ Arrière-plan noir Lorsque vous utilisez le thème sombre, appliquer un arrière-plan noir. + + Dossier + Afficher le bouton pour ajouter les dossiers de jeu + QLaunch + Afficher le bouton pour lancer QLaunch + Language de l\'application Changer la langue de l\'interface Suivre le système Couleur du thème + Eden Violet (Par défaut) Bleu Cyan Rouge + Vert Jaune Orange Rose @@ -941,6 +1039,8 @@ Applets + Modifier les frontends et paramètres des applets + Frontend personnalisé Applet réel @@ -949,7 +1049,39 @@ Mode avion Passe le mode avion au système d\'exploitation Switch + Activer l\'applet d\'overlay + + Gestionnaire de profil + Gérer les profils utilisateurs + Ajouter un utilisateur + Nouvel utilisateur + Modifier l\'utilisateur + Éditer + Supprimer + Nom d\'utilisateur + ID de l\'utilisateur (UUID) + Il s\'agit de l\'identifiant unique de ce profil utilisateur. Il ne peut pas être changé après sa création. + Générer + Avatar de l\'utilisateur + Sélectionner l\'image + Avatars du firmware + Avatars du firmware non disponibles. Veuillez installer le firmware pour utiliser cette fonctionnalité. + Restaurer à la valeur par défaut + Utilisateur actuel + Nombre d\'Utilisateurs Maximum Atteint + Vous ne pouvez pas créer plus du 8 profils utilisateurs. Veuillez supprimer un des profils existants pour en créer un nouveau. + Supprimer le profil \? + Échec de la création du profil utilisateur + Échec de la mise à jour du profil utilisateur + Échec du chargement de l\'image : %1$s + Échec de la sauvegarde de l\'image : %1$s + Erreur + Licences Mise à l\'échelle de haute qualité par AMD. -
+ Contenu externe + Ajouter un dossier + %1$d%% + +
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 4cfd89217e..265c38fa82 100644 --- a/src/android/app/src/main/res/values-he/strings.xml +++ b/src/android/app/src/main/res/values-he/strings.xml @@ -67,7 +67,6 @@ לשימוש בפיתוח בלבד. אמולציית NVDEC - בחר כיצד לטפל בפענוח וידאו ללא @@ -359,8 +358,6 @@ רזולוציה (מעוגן/נייד) מצב VSync פילטר מתאם חלון - חדות FSR - קובע את מידת החדות בעת שימוש ב-FSR. שיטת Anti-aliasing @@ -410,7 +407,6 @@ רישום רוקן יומני ניפוי שגיאות לפי שורה מרוקן יומני ניפוי שגיאות בכל שורה שנכתבת, מה שמקל על ניפוי שגיאות במקרים של קריסה או קיפאון. - מנוע פלט עוצמת שמע @@ -514,8 +510,6 @@ קובץ המשחק לא קיים המשחק דורש קושחה - שמור והתקן קושחה או לחץ \"אישור\" כדי להמשיך בכל מקרה.]]> - מחפש משחק... משחק לא נמצא למזהה כותרת: %1$s @@ -594,10 +588,6 @@ 6GB (לא בטוח) 8GB (לא בטוח) - - Boost (1700MHz) - מהיר (2000MHz) - צלזיוס פרנהייט 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 a42bac6779..daa9539756 100644 --- a/src/android/app/src/main/res/values-hu/strings.xml +++ b/src/android/app/src/main/res/values-hu/strings.xml @@ -62,7 +62,6 @@ Ez az optimalizáció gyorsítja a vendégprogram memória-hozzáférését. Engedélyezése esetén a vendég memóriaolvasási/írási műveletei közvetlenül a memóriában történnek, és kihasználják a gazda MMU-ját. Letiltás esetén minden memória-hozzáférés a szoftveres MMU emulációt használja. NVDEC emuláció - Videódekódolás kezelése Nincs @@ -348,8 +347,6 @@ Felbontás (Kézi/Dockolt) VSync mód Ablakhoz alkalmazkodó szűrő - FSR élesség - Meghatározza, milyen éles lesz a kép az FSR dinamikus kontraszt használata közben. Élsimítási módszer @@ -398,7 +395,6 @@ Naplózás Hibakeresési naplók soronkénti kiürítése Kiüríti a hibakeresési naplókat minden írt sor után, megkönnyítve a hibakeresést összeomlás vagy fagyás esetén. - Kimeneti motor Hangerő @@ -602,8 +598,6 @@ ROM fájl nem létezik A játék firmware-t igényel - dumpolja és telepítse a firmware-t, vagy nyomja meg az \"OK\" gombot a folytatáshoz.]]> - Játék keresése... A játék nem található a következő címazonosítóhoz: %1$s @@ -683,10 +677,6 @@ 6GB (Nem biztonságos) 8GB (Nem biztonságos) - - Boost (1700MHz) - Gyors (2000MHz) - Celsius Fahrenheit 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 2c575dcc8f..6dcb323579 100644 --- a/src/android/app/src/main/res/values-id/strings.xml +++ b/src/android/app/src/main/res/values-id/strings.xml @@ -1,7 +1,6 @@ - Perangkat lunak ini akan menjalankan permainan dari konsol Nintendo Switch. Permainan maupun keys tidak disediakan.

Sebelum memulai, tolong temukan berkas prod.keys ]]> didalam tempat penyimpanan mu.

Pelajari lebih lanjut]]>
Pemberitahuan dan error Menampilkan pemberitahuan ketika terjadi kesalahan. Izin notifikasi tidak diberikan! @@ -81,7 +80,6 @@ Optimasi ini mempercepat akses memori oleh program tamu. Mengaktifkannya menyebabkan pembacaan/penulisan memori tamu dilakukan langsung ke memori dan memanfaatkan MMU Host. Menonaktifkan ini memaksa semua akses memori menggunakan Emulasi MMU Perangkat Lunak. Emulasi NVDEC - Pilih cara decoding video (NVDEC) ditangani selama cutscene dan intro. Tidak Ada @@ -380,8 +378,6 @@ Resolusi (Handheld/Docked) Mode Sinkronisasi Vertikal Filter penyesuaian jendela - Ketajaman FSR - Menentukan seberapa tajam gambar akan terlihat saat menggunakan kontras dinamis FSR Metode anti-aliasing @@ -432,7 +428,6 @@ Pencatatan Buang log debug per baris Membuang log debug pada setiap baris yang ditulis, memudahkan debugging dalam kasus crash atau freeze. - Output audio Volume @@ -642,8 +637,6 @@ Berkas Tidak Ditemukan Game memerlukan firmware - dump dan instal firmware, atau tekan \"OK\" untuk melanjutkan.]]> - Mencari game... Game tidak ditemukan untuk ID Judul: %1$s @@ -729,10 +722,6 @@ 6GB (Tidak Aman) 8GB (Tidak Aman) - - Boost (1700MHz) - Cepat (2000MHz) - Celsius Fahrenheit 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 e3b3009385..0b74a74aed 100644 --- a/src/android/app/src/main/res/values-it/strings.xml +++ b/src/android/app/src/main/res/values-it/strings.xml @@ -1,7 +1,6 @@ - Questo software esegue giochi per la console Nintendo Switch. Non sono inclusi titoli di giochi o chiavi.

Prima di iniziare, individua il file prod.keys]]> nella memoria del tuo dispositivo.

Ulteriori informazioni]]>
Avvisi ed errori Mostra le notifiche quando qualcosa va storto. Autorizzazione di notifica non concessa! @@ -79,7 +78,6 @@ Questa ottimizzazione accelera gli accessi alla memoria da parte del programma guest. Abilitandola, le letture/scritture della memoria guest vengono eseguite direttamente in memoria e sfruttano la MMU host. Disabilitandola, tutti gli accessi alla memoria sono costretti a utilizzare l\'emulazione software della MMU. Emulazione NVDEC - Scegli come gestire la decodifica video Nessuna @@ -387,8 +385,6 @@ Risoluzione (Portatile/Docked) Modalità VSync Filtro adattivo della finestra - Nitidezza FSR - Determina quanto sarà nitida l\'immagine utilizzando il contrasto dinamico di FSR Metodo di anti-aliasing @@ -439,7 +435,6 @@ Registrazione Svuota i log di debug per riga Svuota i log di debug su ogni riga scritta, facilitando il debug in caso di crash o blocco. - Motore di Output Volume @@ -666,8 +661,6 @@ Il file della ROM non esiste Firmware richiesto - Esegui il dump del firmware e installalo, o premi \"OK\" per continuare lo stesso.]]> - Ricerca del gioco in corso... Gioco non trovato per l\'ID titolo: %1$s @@ -766,10 +759,6 @@ 6GB (Non sicuro) 8GB (Non sicuro) - - Boost (1700MHz) - Veloce (2000MHz) - Celsius Fahrenheit 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 03bfcf3e41..4b84e82b3d 100644 --- a/src/android/app/src/main/res/values-ja/strings.xml +++ b/src/android/app/src/main/res/values-ja/strings.xml @@ -62,7 +62,6 @@ この最適化により、ゲストプログラムによるメモリアクセスが高速化されます。有効にすると、ゲストのメモリ読み書きが直接メモリ内で実行され、ホストのMMUを利用します。無効にすると、すべてのメモリアクセスでソフトウェアMMUエミュレーションが使用されます。 NVDECエミュレーション - ビデオデコード方法 無効 @@ -398,7 +397,6 @@ ロギング デバッグログを行ごとにフラッシュ デバッグログを行ごとにフラッシュし、クラッシュやフリーズ時のデバッグを容易にします。 - 出力エンジン 音量 @@ -510,8 +508,6 @@ ROMファイルが存在しません ゲームにはファームウェアが必要です - ファームウェアをダンプしてインストールするか、\"OK\"を押して続行してください。]]> - ゲームを検索中... タイトルID: %1$s のゲームが見つかりません @@ -590,10 +586,6 @@ 6GB (安全でない) 8GB (安全でない) - - ブースト (1700MHz) - 高速 (2000MHz) - 摂氏 華氏 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 2f32665356..e9f79165e5 100644 --- a/src/android/app/src/main/res/values-ko/strings.xml +++ b/src/android/app/src/main/res/values-ko/strings.xml @@ -6,8 +6,8 @@ 알림 권한이 부여되지 않았습니다! 프로세스 RAM: %1$d MB - 셰이더 - 빌드 중 + 구축 중 + 개 셰이더 (충전 중) 시스템: @@ -62,7 +62,6 @@ 이 최적화는 게스트 프로그램의 메모리 접근 속도를 높입니다. 활성화하면 게스트의 메모리 읽기/쓰기가 메모리에서 직접 수행되고 호스트의 MMU를 활용합니다. 비활성화하면 모든 메모리 접근에 소프트웨어 MMU 에뮬레이션을 사용하게 됩니다. NVDEC 에뮬레이션 - 비디오 디코딩 처리 방식 선택 없음 @@ -398,7 +397,6 @@ 로깅 디버그 로그를 줄별로 플러시 디버그 로그를 각 줄마다 플러시하여 충돌 또는 정지 시 디버깅을 용이하게 합니다. - 출력 엔진 볼륨 @@ -560,8 +558,6 @@ 롬 파일이 존재하지 않음 게임에 펌웨어가 필요합니다 - 펌웨어를 덤프하여 설치하거나 \"확인\"을 눌러 계속 진행하세요.]]> - 게임 검색 중... 타이틀 ID에 대한 게임을 찾을 수 없음: %1$s @@ -642,10 +638,6 @@ 6GB (안전하지 않음) 8GB (안전하지 않음) - - 부스트 (1700MHz) - 빠름 (2000MHz) - 섭씨 화씨 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 8fbc008bb1..a3fc8bad7c 100644 --- a/src/android/app/src/main/res/values-nb/strings.xml +++ b/src/android/app/src/main/res/values-nb/strings.xml @@ -62,7 +62,6 @@ Denne optimaliseringen fremskynder minnetilgang av gjesteprogrammet. Hvis aktivert, utføres gjestens minnelesing/skriving direkte i minnet og bruker vertens MMU. Deaktivering tvinger alle minnetilganger til å bruke programvarebasert MMU-emulering. NVDEC-emulering - Velg hvordan videodekoding håndteres Ingen @@ -329,8 +328,6 @@ Oppløsning (håndholdt/dokket) VSync-modus Filter for vindustilpasning - FSR-skarphet - Bestemmer bildekvalitet med FSR Anti-aliasing-metode @@ -381,7 +378,6 @@ Logging Tøm feilsøkingslogger per linje Tømmer feilsøkingslogger for hver linje som skrives, noe som gjør feilsøking enklere ved krasj eller frysing. - Lydmotor Volum @@ -491,8 +487,6 @@ ROM-filen finnes ikke Spillet krever fastvare - dump og installer fastvare, eller trykk \"OK\" for å fortsette likevel.]]> - Søker etter spill... Spill ikke funnet for tittel-ID: %1$s @@ -568,10 +562,6 @@ 6GB (Usikkert) 8GB (Usikkert) - - Boost (1700MHz) - Rask (2000MHz) - Celsius Fahrenheit 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 ac09657d6c..9f49ae5dd0 100644 --- a/src/android/app/src/main/res/values-pl/strings.xml +++ b/src/android/app/src/main/res/values-pl/strings.xml @@ -1,7 +1,6 @@ - To oprogramowanie uruchamia gry na konsoli Nintendo Switch. Żadne tytuły gier ani klucze nie są dołączone.

Zanim zaczniesz, zlokalizuj plik prod.keys ]]> na swoim urządzeniu.

Więcej informacji]]>
Powiadomienia błędy Pokaż powiadomienie gdy coś pójdzie źle Nie zezwolono na powiadomienia! @@ -97,7 +96,6 @@ Emulacja NVDEC - Wybierz metodę dekodowania wideo (NVDEC). Brak @@ -417,9 +415,6 @@ Ta opcja pozwala na wybranie własnych ustawień czasu używanych w czasie emulacji, innych niż czas systemu Android. Ustaw niestandardowy czas RTC - - Podkręcanie CPU - Wymusza działanie emulowanego CPU z wyższym taktowaniem, ograniczając niektóre blokady FPS. Użyj Boost (1700 MHz), aby działać z najwyższym natywnym taktowaniem Switcha, lub Fast (2000 MHz), aby działać z taktowaniem 2×. Niestandardowe takty CPU Ustaw niestandardową wartość taktów CPU. Wyższe wartości mogą zwiększyć wydajność, ale mogą też powodować zawieszanie się gry. Zalecany zakres to 77–21000. Takty @@ -439,15 +434,12 @@ Rozdzielczość (Handheld/Zadokowany) Synchronizacja pionowa VSync Filtr adaptacji rozdzielczości - Ostrość FSR - Kontroluje ostrość obrazu w FSR. Metoda wygładzania krawędzi Zaawansowane Tryb GPU - Steruje trybem emulacji GPU. Większość gier renderuje się poprawnie w trybach Szybki lub Zrównoważony, ale dla niektórych nadal wymagany jest tryb Dokładny. Efekty cząsteczkowe zwykle renderują się poprawnie tylko w trybie Dokładnym. Dokładność DMA Kontroluje dokładność precyzji DMA. Bezpieczna precyzja może naprawić problemy w niektórych grach, ale w niektórych przypadkach może również wpłynąć na wydajność. Jeśli nie jesteś pewien, pozostaw wartość Domyślną. Filtrowanie anizotropowe @@ -467,8 +459,6 @@ Poprawia jakość renderowania w kilku grach, kosztem wydajności. Hacki - Szybki czas GPU - Wymusza działanie większości gier w ich najwyższej natywnej rozdzielczości. Ustaw 256 dla maksymalnej wydajności, a 512 dla maksymalnej jakości grafiki. Pomiń wewnętrzne unieważnienie CPU Pomija niektóre unieważnienia pamięci podręcznej po stronie CPU podczas aktualizacji pamięci, zmniejszając użycie CPU i poprawiając jego wydajność. Może powodować błędy lub awarie w niektórych grach. Wyłącz synchronizację shaderów @@ -476,10 +466,8 @@ Rozszerzenia Rozszerzony stan dynamiczny - Kontroluje liczbę funkcji, które mogą być używane w Extended Dynamic State. Wyższe wartości pozwalają na użycie większej liczby funkcji i mogą zwiększyć wydajność, ale mogą powodować problemy z niektórymi sterownikami i u niektórych producentów. Wyłączone Dynamiczny stan wejścia wierzchołków - Włącza funkcję dynamicznego stanu wejścia wierzchołków, poprawiając jakość i wydajność. Cieniowanie próbkowane Pozwala uruchamiać shader fragmentów dla każdej próbki w wielopróbkowanym fragmencie zamiast raz na fragment. Poprawia jakość grafiki kosztem części wydajności. @@ -510,7 +498,6 @@ Rejestrowanie Opróżniaj dzienniki debugowania linia po linii Opróżnia dzienniki debugowania po każdej napisanej linii, ułatwiając debugowanie w przypadku awarii lub zawieszenia. - Ogólne @@ -770,8 +757,6 @@ Plik ROM nie istnieje Gra wymaga oprogramowania sprzętowego - zrzuć i zainstaluj oprogramowanie sprzętowe, lub naciśnij \"OK\", aby kontynuować mimo to.]]> - Wyszukiwanie gry... Nie znaleziono gry dla ID tytułu: %1$s @@ -871,15 +856,6 @@ 6GB (Niebezpieczne) 8GB (Niebezpieczne) - - Boost (1700MHz) - Szybkie (2000MHz) - - - Wyłączone - Średnie (256) - Wysokie (512) - Celsjusza Fahrenheita @@ -897,7 +873,6 @@ Szybkie - Zrównoważony Dokładny @@ -955,8 +930,6 @@ Jasny Ciemny - Brak - Czarne tła Kiedy używany ciemny motyw, tła zostają zastąpione czernią. 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 d87d6a15e6..533ccbcdf2 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 @@ -1,7 +1,6 @@ - Este programa serve para rodar jogos do Nintendo Switch. Nenhum jogo ou chaves do sistema são inclusos

Antes de começar, localize o seu prod.keys ]]> salvo no seu sistema.

Saiba mais]]>
Notificações e erros Mostra notificações quando acontecer um erro. Acesso às notificações não foi permitido! @@ -91,7 +90,6 @@ Decodificação de Vídeo (NVDEC) - Selecione como a decodificação de vídeo é realizada durante cutscenes e intros. Nenhum @@ -409,8 +407,6 @@ 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 - - Overclock da CPU Ciclos da CPU Personalizados Defina um valor personalizado de ciclos da CPU. Valores mais altos podem aumentar o desempenho, mas também podem fazer o jogo travar. Recomenda-se usar valores entre 77 e 21000. Ciclos @@ -430,8 +426,6 @@ Resolução (Portátil/Modo TV) Modo de VSync Filtro de Adaptação da Janela - Nitidez do FSR - Determina a nitidez da imagem ao utilizar o contraste dinâmico do FSR Método de Anti-aliasing @@ -463,7 +457,6 @@ Extended Dynamic State Desativado Vertex Input Dynamic State - Ativa o recurso de vertex input dynamic state para melhor qualidade e desempenho. Tela Orientação @@ -488,7 +481,6 @@ Registro Liberar logs de depuração por linha Libera logs de depuração em cada linha escrita, facilitando a depuração em casos de travamento ou congelamento. - Geral @@ -724,8 +716,6 @@ O arquivo ROM não existe O jogo requer firmware - faça dump e instale o firmware, ou pressione \"OK\" para continuar mesmo assim.]]> - Procurando jogo... Jogo não encontrado para o ID do Título: %1$s @@ -824,10 +814,6 @@ 6GB (Inseguro) 8GB (Inseguro) - - Boost (1700MHz) - Rápido (2000MHz) - Celsius Fahrenheit @@ -843,7 +829,6 @@ Nenhum - Média Alta @@ -901,8 +886,6 @@ Claro Escuro - Nenhum - Planos de fundo pretos Quando usar o tema escuro, aplicar fundos pretos 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 51283f93a0..598c80985a 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 @@ -62,7 +62,6 @@ Esta otimização acelera os acessos à memória pelo programa convidado. Ativar faz com que as leituras/escritas de memória do convidado sejam efetuadas diretamente na memória e utilizem a MMU do Anfitrião. Desativar força todos os acessos à memória a usar a Emulação de MMU por Software. Emulação NVDEC - Método de decodificação de vídeo. Nenhum @@ -352,8 +351,6 @@ Resolução (Portátil/Ancorado) Modo VSync Filtro de Adaptação da Janela - Nitidez do FSR - Determina a nitidez da imagem ao usar contraste dinâmico do FSR Método de Anti-Serrilhado @@ -404,7 +401,6 @@ Registo Libertar registos de depuração por linha Liberta registos de depuração em cada linha escrita, facilitando a depuração em casos de falha ou congelamento. - Motor de saída Volume @@ -613,8 +609,6 @@ uma tentativa de mapeamento automático
O ficheiro da ROM não existe O jogo requer firmware - faça dump e instale o firmware, ou pressione \"OK\" para continuar mesmo assim.]]> - A procurar jogo... Jogo não encontrado para o ID do Título: %1$s @@ -695,10 +689,6 @@ uma tentativa de mapeamento automático
6GB (Inseguro) 8GB (Inseguro) - - Boost (1700MHz) - Rápido (2000MHz) - Celsius Fahrenheit 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 dbce577e13..c291a9276b 100644 --- a/src/android/app/src/main/res/values-ru/strings.xml +++ b/src/android/app/src/main/res/values-ru/strings.xml @@ -1,7 +1,7 @@ - Это программное обеспечение предназначено для запуска игр для игровой консоли Nintendo Switch. Игры и ключи в комплект не входят.

Прежде чем начать, пожалуйста, найдите файл prod.keys]]> на вашем устройстве.

Узнать больше]]>
+ Это программное обеспечение предназначено для запуска игр консоли Nintendo Switch. Никакие игры или ключи не входят в комплект.

Перед началом работы найдите файл prod.keys ]]> во внутреннем хранилище вашего устройства.

Узнать больше]]>
Уведомления и ошибки Показывает уведомления, когда что-то пошло не так Вы не предоставили разрешение на уведомления! @@ -66,6 +66,8 @@ Показать текущее энергопотребление и оставшуюся емкость аккумулятора Показать компиляцию шейдеров Отображает текущее количество шейдеров, которые компилируются + Рабочие потоки конвейера + Позволяет настроить количество ядер, задействованных при построении конвейеров Vulkan. Чем выше значение, тем быстрее будет компиляция конвейеров, однако это приведёт к повышению температуры устройства. Позиция оверлея Расположение оверлея на экране Сверху слева @@ -104,7 +106,7 @@ Эмуляция NVDEC - Обработка видео (ролики, интро) + Переключите на CPU, если происходит вылет на кат-сценах. Отключено @@ -289,6 +291,80 @@ Получение драйверов ГПУ Менеджер драйверов ГПУ Установите альтернативные драйверы для потенциально лучшей производительности и/или точности + Эффекты постобработки + Эффекты ReShade FX, применяемые после рендеринга + Настроить цепочку эффектов для этой игры + Добавить эффект + Удалить + Открыть список + Закрыть список + Удалить эффекты + Пресет активен. Отредактируйте его в разделе \"Эффекты постобработки\" перед запуском игры. + Значения изменены относительно исходного пресета. + Пресеты + Новый пресет + Сохраняет загруженные вами эффекты вместе с их текущими значениями в виде пресета, который можно будет выбрать позже. + Дайте пресету имя без знака равенства. + Нет пресета + Удалить пресет + Сбросить значения пресета + Сбросить до значений по умолчанию + Эффекты не найдены. Поместите файлы .fx в эту папку: + Генерация кадров + Настройка генерации кадров для данной игры + Вставляет промежуточные кадры между отрендеренными с помощью Lossless Scaling. При включении принудительно устанавливает режим вывода FIFO. + Множитель кадров + Количество кадров, отображаемых на каждый отрисованный кадр. Повышение значения пропорционально увеличивает затраты ресурсов ГПУ. Если запрашивать больше, чем может отобразить ваш экран, эмуляция будет замедляться. + Целевая частота кадров + Выберите значение под ваш дисплей. Множитель подстроится сам, чтобы его держать, и откатит изменения, если игра начнёт тормозить. + Использовать фиксированный множитель + Вкл. + Выкл. + Фикс. + Авто + Включите или выключите генерацию кадров и выберите множитель кадров. + Генерация кадров с заданной частотой кадров. Множитель регулируется автоматически. + Разрешение, используемое для оценки движения между кадрами. Меньшие значения экономят время ГПУ. + Целевой размер очереди кадров + Сколько готовых кадров может ожидать перед выводом на экран. Более длинные очереди сглаживают скачки ГПУ ценой задержки ввода. + Минимальная задержка (Без буферизации) + Сбалансированный (1 кадр) + Наиболее плавный (2 кадра) + Подстроить оценку движения под игру + Оценивать движение в разрешении, которое игра действительно рендерит, вместо масштабированного вывода. Ничего не стоит в точности, так как масштабирование не добавляет деталей движения. + Разрешение оценки движения + Разрешение прохода оптического потока в долях от выходного разрешения. Его понижение — самый дешёвый способ вернуть производительность. + Генерация кадров недоступна + В этом ГПУ драйвере отсутствует поддержка модели памяти Vulkan или половинной точности (float16), которая требуется шейдерам Lossless Scaling. + Опционально. Укажите свой Lossless.dll для включения генерации кадров позже. + Установить Lossless.dll + Для генерации кадров требуется ваша собственная легальная копия Lossless.dll из Lossless Scaling + Выбрать другой файл Lossless.dll + Генерация кадров + Поддерживается + Не поддерживается (нет модели памяти Vulkan или float16) + Предоставьте свою копию Lossless.dll для включения генерации кадров + Установлена + Не установлена + Заменить + Удалить + Удалить установленный Lossless.dll и его подготовленные шейдеры + Генерация кадров перестанет работать, пока вы снова не установите Lossless.dll. Ваш исходный файл не пострадает. + Lossless.dll не установлен + Установите его через Настройки › Lossless Scaling для использования генерации кадров. + Сначала закройте игру + Lossless.dll нельзя изменить, пока запущена игра. + Нечего удалять. + Lossless.dll ещё не установлен. + Подготовка шейдеров генерации кадров… + Lossless.dll успешно установлен + Не удалось установить Lossless.dll + Не удалось скопировать выбранный файл. + Не удалось прочитать выбранный файл. + Выбранный файл не является библиотекой Windows. Выберите Lossless.dll из установки Lossless Scaling. + Эта копия Lossless.dll не содержит шейдеров генерации кадров. Обновите Lossless Scaling и попробуйте снова. + Не удалось транслировать шейдеры генерации кадров. Эта версия Lossless Scaling пока не поддерживается. + Не удалось записать транслированные шейдеры в хранилище. Проверьте, есть ли свободное место. Расширенные настройки Настройка параметров эмулятора Недавно сыгранные @@ -422,6 +498,9 @@ Точность ЦП %1$s%2$s + Аргументы Homebrew + Аргументы командной строки, переданные Homebrew при запуске (например, -noglsl) + Название устройства Режим док-станции @@ -435,8 +514,8 @@ Установить пользовательский RTC - Разгон ЦП - Принудительно повышает тактовую частоту эмулируемого ЦП, снижая влияние некоторых ограничителей FPS. Используйте Разгон (1700 МГц) для работы на максимальной штатной частоте Switch или Быстрая (2000 МГц) для удвоенной частоты. + Тактовая частота ЦП + Повышает тактовую частоту, которую сообщает эмулируемый процессор, что убирает некоторые ограничители FPS. На более слабых процессорах производительность может снизиться, а в некоторых играх возможно некорректное поведение. Пользовательские такты ЦП Установите пользовательское значение тактов ЦП. Более высокие значения могут увеличить производительность, но также могут вызвать зависание игры. Рекомендуется диапазон 77–21000. Такты @@ -456,17 +535,19 @@ Разрешение (портативное/в док-станции) Режим верт. синхронизации Фильтр адаптации окна - Резкость FSR - Определяет, насколько чётким будет изображение при использовании динамического контраста FSR. + Резкость FSR/SGSR + Определяет, насколько чётким будет изображение при использовании динамического контраста FSR или SGSR фильтров Метод сглаживания Расширенные Режим ГПУ - Управляет режимом эмуляции графического процессора. Большинство игр нормально отображаются в режимах «Быстрый» или «Сбалансированный», но для некоторых требуется режим «Точный». Частицы обычно корректно отображаются только в режиме «Точный». + Управляет режимом эмуляции ГПУ. Большинство игр нормально отображаются в режиме Быстрый, но для некоторых требуется режим Точный. Частицы обычно корректно отображаются только в режиме Точный. Точность DMA Управляет точностью DMA. Безопасная точность может исправить проблемы в некоторых играх, но в некоторых случаях также может повлиять на производительность. Если не уверены, оставьте значение По умолчанию. + Поведение барьеров ГПУ + Управляет поведением синхронизации через барьеры ГПУ. Мгновенный — самый быстрый вариант, но может вызывать некоторые проблемы. Сбалансированный обеспечивает лучшую совместимость и может исправлять проблемы в некоторых играх. Точный ещё больше повышает совместимость ценой некоторого снижения производительности. Строгий — самый медленный вариант, но может исправлять проблемы, требующие более строгой синхронизации. По умолчанию соответствует настройке точности ГПУ. Анизотропная фильтрация Улучшает качество текстур под углом Режим VRAM @@ -481,26 +562,26 @@ Принудительная максимальная тактовая частота (только для Adreno) Заставляет ГПУ работать на максимально возможных тактовых частотах (тепловые ограничения все равно будут применяться). Асинхронная эмуляция ГПУ - Выполняет эмуляцию ГПУ асинхронно для снижения задержек ЦП и увеличения производительности. Отключайте только при возникновении проблем с таймингами. - Асинхронная презентация - Немного улучшает производительность, перемещая презентацию в отдельный поток ЦП. + Может повысить производительность за счёт асинхронного запуска эмуляции ГПУ, но ценой появления графических ошибок и увеличения частоты вылетов из-за операций, зависящих от синхронизации. Реактивная очистка Повышение точности рендеринга в некоторых играх за счет снижения производительности. Включить историю буфера Позволяет обращаться к предыдущим состояниям буфера. Эта опция может повысить качество рендеринга и стабильность производительности в некоторых играх. Оптимизированные вершинные буферы - Включает оптимизированную привязку вершинного буфера для повышения производительности. Требует Mesa Turnip 26.0+ / QCOM драйверы. Приводит к вылету на старых версиях Turnip. + Включает оптимизированную привязку вершинного буфера для повышения производительности. Требует Mesa Turnip 26.0+ / QCOM. Приводит к вылету на старых версиях драйверов Turnip (25.3 и ниже). Хаки - Быстрое время ГПУ - Принудительно запускает большинство игр в их максимальном нативном разрешении. Используйте значение 256 для максимальной производительности и 512 для максимального качества графики. + Тактовая частота ГПУ + Заставляет игру думать, что работа ГПУ завершается быстрее, чем на самом деле, поэтому она перестаёт снижать разрешение и дистанцию прорисовки, чтобы подстраиваться под тактовые частоты Switch. Пропустить внутреннюю инвалидацию ЦП Пропускает некоторые инвалидации кэша на стороне ЦП при обновлениях памяти, уменьшая нагрузку на процессор и повышая производительность. Может вызывать сбои в некоторых играх. Исправить эффекты размытия Частично убирает размытие в LA/EOW (Adreno A6XX - A7XX/ Turnip), полностью отключает его в Burnout. Внимание: может вызывать графические артефакты в других играх. Эмулировать BGR565 Исправляет проблемы с инвертированными цветами в играх, а также со странными артефактами или некорректными тенями. + Включить старый метод изменения разрешения + Включает старый метод обработки этапа перенастройки масштабирования для игр за счёт использования быстрого алгоритма перемасштабирования. Использовать асинхронные шейдеры Компилирует шейдеры асинхронно. Это может уменьшить подтормаживания, но также может вызвать графические артефакты. Настройки распаковки текстур (Unswizzle) @@ -519,10 +600,10 @@ Расширения Расширенное динамическое состояние - Управляет количеством функций, доступных в режиме «Расширенное динамическое состояние». Большее число позволяет задействовать больше функций и может повысить производительность, но способно вызывать проблемы с некоторыми драйверами и графикой. + Управляет количеством функций, доступных для использования в расширенном динамическом состояние. Более высокое значение позволит сократить количество компиляций конвейеров за счёт поддержки динамического состояния драйвером. Отключено Динамическое состояние ввода вершин - Включает функцию динамического состояния ввода вершин для повышения качества и производительности + Включение этой функции обеспечивает более гибкую обработку входных данных вершин, что потенциально сокращает время компиляции конвейера на этапе работы с вершинами и буферами. Сэмпловое затенение Позволяет шейдеру фрагментов выполняться для каждого сэмпла в многосэмпловом фрагменте, а не один раз на фрагмент. Улучшает качество графики ценой некоторого падения производительности. @@ -539,6 +620,7 @@ ЦП + Тактовая частота Использовать Auto Stub Автоматически заглушает отсутствующие сервисы и функции. Может улучшить совместимость, но вызывать сбои и проблемы стабильности. @@ -553,17 +635,23 @@ Логирование Сбрасывать логи отладки построчно Сбрасывает логи отладки после каждой написанной строки, упрощая отладку в случае сбоев или зависаний. + Включить расширенное логирование + Увеличивает максимальный размер файла журнала со 100 МБ до 1 ГБ. + Фильтр журнала + Управляет категориями журнала Eden. Пример: :Info Service.LM:Debug Ведение журнала ГПУ - Включить ведение журнала ГПУ - Записывать операции ГПУ в файл eden_gpu.log для отладки драйверов Adreno Уровень журналирования Уровень детализации логов ГПУ (больше значение = больше деталей, выше нагрузка) Записывать вызовы Vulkan API Отслеживать все вызовы Vulkan API в кольцевом буфере - Выгрузить шейдеры - Сохранять скомпилированные SPIR-V шейдеры в файлы + Выгрузить SPIR-V шейдеры + Сохранять перекомпилированные SPIR-V бинарные файлы (.spv) в папку дампа. Проверять с помощью spirv-dis/spirv-cross/spirv-val. + Выгрузить гостевые (Maxwell) шейдеры + Сохранять файлы байткода гостевых шейдеров Maxwell (*.ash) в папку дампа. Проверять с помощью nvdisasm. + Выгрузить макросы Maxwell + Сохранять файлы макропрограмм Maxwell (*.macro) в папку дампа. Проверять с помощью envydis. Отслеживать память ГПУ Мониторить выделение и освобождение памяти ГПУ Отладочная информация драйвера @@ -835,7 +923,7 @@ Файл ROM не существует Игре требуется прошивка - сохраните и установите прошивку или нажмите \"OK\" для запуска в любом случае.]]> + Поиск игры... @@ -936,14 +1024,15 @@ 6 ГБ (Небезопасно) 8 ГБ (Небезопасно) - - Разгон (1700MHz) - Быстрая (2000MHz) + + Обычная + Турбо + Разгон - - Выкл. - Среднее (256) - Высокое (512) + + Обычная + Турбо + Разгон Очень малый (16 МБ) @@ -983,7 +1072,6 @@ Быстрый - Сбалансированный Точный @@ -991,6 +1079,12 @@ Небезопасно Безопасный + + По умолчанию + Мгновенный + Сбалансированный + Точный + Консервативный Агрессивный @@ -1066,8 +1160,6 @@ Светлая Темная - Отключено - Чёрный фон При использовании темной темы применяйте черный фон. @@ -1155,4 +1247,6 @@ Высококачественное масштабирование от AMD Дополнительный контент Добавить папку -
+ %1$d %% + + 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 74b04d4c8f..49641692d9 100644 --- a/src/android/app/src/main/res/values-sr/strings.xml +++ b/src/android/app/src/main/res/values-sr/strings.xml @@ -60,7 +60,6 @@ Ова оптимизација убрзава приступ меморији од стране гостујућег програма. Укључивање изазива да се читања/уписа меморије госта обављају директно у меморији и користе MMU домаћина. Искључивање присиљава све приступе меморији да користе софтверску емулацију MMU. НВДЕЦ Емулација - Изаберите како се видео декодирање (НВДЕЦ) обрађује током секс и увозних интросија. Ниједан @@ -351,8 +350,6 @@ Резолуција (ручно / прикључено) Всинц мод Филтер прилагођавања прозора - ФСР оштрина - Одређује колико ће се слика наоштрен трајати док користи \"ФСР\" динамички контраст Метода против алиасирања @@ -403,7 +400,6 @@ Сечеља Записници за уклањање погрешака по линији Испушта ознаке за уклањање погрешака на сваком писменом линијском линији, олакшавање уклањања погрешака у случајевима пада или замрзавања. - Излазни мотор Запремина @@ -610,8 +606,6 @@ РОМ датотека не постоји Игра захтева firmware - направите дамп и инсталирајте firmware, или притисните \"OK\" да бисте наставили у сваком случају.]]> - Тражење игре... Игра није пронађена за ID наслова: %1$s @@ -692,10 +686,6 @@ 6ГБ (несигуран) 8ГБ (несигурно) - - Боост (1700МХз) - Брзи (2000МХз) - Целзијус Фаренхајт 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 3338d48d68..b49e320806 100644 --- a/src/android/app/src/main/res/values-uk/strings.xml +++ b/src/android/app/src/main/res/values-uk/strings.xml @@ -1,9 +1,8 @@ - Цей застосунок запускає ігри для ігрової консолі Nintendo Switch. Він не містить ігор чи ключів.

Перш ніж почати, укажіть розташування файлу prod.keys ]]> у пам’яті вашого пристрою.

Дізнатися більше]]>
Сповіщення та помилки - Виводить сповіщення у разі виникнення проблем. + Показує сповіщення у разі виникнення проблем. Дозвіл на сповіщення не надано! Сповіщення емулятора Switch Eden Eden працює @@ -66,6 +65,8 @@ Показати поточне споживання енергії та залишкову ємність акумулятора Показати побудову шейдерів Показати поточну кількість шейдерів, які наразі компілюються + Потоки обробника конвеєра + Керує кількістю ядер, що використовуються для збірки конвеєрів Vulkan. Вище значення покращить продуктивність компіляції конвеєра, але також підвищить температуру. Позиція оверлею Обрати розташування виводу статистики на екрані Вгорі ліворуч @@ -104,7 +105,6 @@ Емуляція NVDEC - Обробка відео під час катсцен Вимкнено @@ -422,6 +422,9 @@ Точність CPU %1$s%2$s + Параметри запуску Homebrew + Параметри командного рядка, що передаються Homebrew при запуску (як-от noglsl). + Назва пристрою Режим док-станції @@ -434,9 +437,6 @@ Дозволяє встановити власний час (Real-time clock, або RTC), відмінний від системного. Встановити RTC - - Розгін ЦП - Примушує емульований ЦП працювати з вищою тактовою частотою, послаблюючи деякі обмежувачі частоти кадрів. Використовуйте «Підвищення (1700 МГц)», щоб емулювати максимальну тактову частоту справжнього Switch, або «Швидко (2000 МГц)», щоб подвоїти частоту. Користувацькі такти CPU Встановіть користувацьке значення тактів CPU. Вищі значення можуть покращити продуктивність, але також можуть спричинити зависання гри. Рекомендується діапазон 77–21000. Такти @@ -456,15 +456,14 @@ Роздільна здатність (Портативний/Док) Режим верт. синхронізації Фільтр масштабування вікна - Різкість FSR - Визначає різкість зображення при використанні FSR. + Різкість FSR/SGSR + Визначає різкість зображення при використанні фільтрів FSR або SGSR. Згладжування Додаткові Режим ГП - Керує режимом емуляції ГП. Більшість ігор добре візуалізуються з режимами «Швидко» або «Збалансовано», але деякі ігри можуть потребувати режиму «Точно». Частинки зазвичай правильно візуалізуються лише з режимом «Точно». Точність DMA Керує точністю DMA. Безпечна точність може виправити проблеми в деяких іграх, але в деяких випадках також може вплинути на продуктивність. Якщо не впевнені, залиште це значення за замовчуванням. Анізотропне фільтрування @@ -481,26 +480,24 @@ Максимальна тактова частота (тільки Adreno) Змушує GPU працювати на максимальній тактовій частоті. Асинхронна емуляція ГП - Емуляція ГП виконується асинхронно для зменшення затримок ЦП й покращення пропускної здатності. Вимкніть лише у випадку виникнення проблем із таймінгами. - Асинхронне подання - Трохи покращує продуктивність завдяки переміщенню подання на окремий потік ЦП. + Це обхідне рішення може покращити продуктивність завдяки асинхронному виконанню емуляції ГП, але спричинить проблеми з графікою та збільшить частоту збоїв чутливих до таймінгів операцій. Реактивне очищення Покращує точність рендерингу в деяких іграх. Увімкнути історію буфера Вмикає доступ до попередніх станів буфера. Цей параметр може покращити якість візуалізації та стабільну продуктивність у деяких іграх. Оптимізовані буфери вершин - Застосовує оптимізований буфер вершин, щоб покращити продуктивність. Потребує драйверів Mesa 26.0+ Turnip / QCOM. На старіших драйверах Turnip виникатиме збій. + Застосовує оптимізований буфер вершин, щоб покращити продуктивність. Потребує драйверів Mesa 26.0+ Turnip / QCOM. На старіших драйверах Turnip виникатиме збій (25.3 і нижче). Обхідні рішення - Швидкий час роботи ГП - Примушує більшість ігор працювати на їхній максимальній нативній роздільності. Використовуйте 256 для максимальної продуктивності та 512 для найкращої якості. Пропустити внутрішнє інвалідування CPU Пропускає деякі інвалідації кешу на стороні CPU під час оновлення пам\'яті, зменшуючи навантаження на процесор і покращуючи продуктивність. Може спричинити збої в деяких іграх. Виправити ефекти світіння Зменшує розмиття світіння в LA/EOW (Adreno A6XX–A7XX / Turnip), прибирає світіння в Burnout. Увага: може спричинити графічні артефакти в інших іграх. Емулювати BGR565 Виправляє проблеми з інвертованими кольорами в іграх або дивними артефактами чи тінями. + Увімкнути застаріле масштабування + Вмикає застарілу обробку масштабування для ігор, використовуючи швидкий шлях масштабування Асинхронні шейдери Компілює шейдери асинхронно. Це може зменшити затримки, але також може спричинити графічні баги. Налаштування розпакування за допомогою ГП @@ -519,10 +516,10 @@ Розширення Розширений динамічний стан - Керує кількістю функцій, які можна використовувати в «Розширеному динамічному стані». Вище число дозволяє більше функцій і може покращити продуктивність, але може спричинити проблеми з деякими драйверами й виробниками. + Керує кількістю функцій, які можна використовувати в ExtendedDynamicState (EDS). Вище значення дозволить зменшити кількість компіляцій конвеєра на основі динамічного стану, підтримуваного драйвером. Вимкнено Динамічний стан введення вершин - Вмикає можливість динамічного стану введення вершин для кращих якості й продуктивності. + Увімкнення цієї функції дозволить гнучкішу обробку введення вершин. Може зменшити час компіляції конвеєра для вершин/буфера. Простий шейдинг Дозволяє виконувати фрагмент шейдера для кожного зразка в багатозразковому фрагменті замість одного разу для кожного фрагмента. Покращує якість графікі ціною втрати продуктивності. @@ -553,17 +550,12 @@ Журналювання Скидати логи налагодження по рядках Скидає логи налагодження після кожного написаного рядка, полегшуючи налагодження у випадках збоїв або зависань. - Журналювання ГП - Увімкнути журналювання ГП - Журналювати операції ГП до eden_gpu.log для зневадження драйверів Adreno Рівень журналювання Рівень подробиць у журналі ГП (вищий = більше подробиць, більший вплив на швидкодію) Записувати виклики API Vulkan Відстежувати всі виклики API Vulkan у кільцевому буфері - Зберігати шейдери - Зберігати до файлів скомпільовані шейдери SPIR-V Відстежувати пам’ять ГП Відстежувати використання і звільнення пам’яті ГП Зневаджувальна інформація драйверів @@ -835,8 +827,6 @@ ROM файлу не існує Гра вимагає прошивки - зробіть дамп і встановіть прошивку, або натисніть \"OK\", щоб продовжити в будь-якому разі.]]> - Пошук гри... Гру не знайдено для ID заголовку: %1$s @@ -936,15 +926,6 @@ 6 ГБ (Небезпечно) 8 ГБ (Небезпечно) - - Підвищення (1700 МГц) - Швидко (2000 МГц) - - - Вимкнено - Середньо (256) - Високо (512) - Дуже малий (16 МБ) Малий (32 МБ) @@ -983,7 +964,6 @@ Швидко - Збалансовано Точно @@ -1066,8 +1046,6 @@ Світла Темна - Жодного - Чорний фон Використовувати чорний фон у темній темі. 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 5aec3b1219..06e4551f71 100644 --- a/src/android/app/src/main/res/values-vi/strings.xml +++ b/src/android/app/src/main/res/values-vi/strings.xml @@ -62,7 +62,6 @@ Tối ưu hóa này tăng tốc độ truy cập bộ nhớ của chương trình khách. Bật nó lên khiến các thao tác đọc/ghi bộ nhớ khách được thực hiện trực tiếp vào bộ nhớ và sử dụng MMU của Máy chủ. Tắt tính năng này buộc tất cả quyền truy cập bộ nhớ phải sử dụng Giả lập MMU Phần mềm. Giả lập NVDEC - Chọn cách xử lý giải mã video Tắt @@ -327,8 +326,6 @@ Độ phân giải (Handheld/Docked) Chế độ VSync Bộ lọc điều chỉnh cửa sổ - Độ sắc nét FSR - Độ sắc nét khi dùng FSR Phương pháp khử răng cưa @@ -375,7 +372,6 @@ Ghi nhật ký Xả nhật ký gỡ lỗi theo dòng Xả nhật ký gỡ lỗi trên mỗi dòng được viết, giúp gỡ lỗi dễ dàng hơn trong trường hợp bị treo hoặc sập. - Công cụ xuất âm thanh Âm lượng @@ -487,8 +483,6 @@ Tệp ROM không tồn tại Trò chơi yêu cầu firmware - dump và cài đặt firmware, hoặc nhấn \"OK\" để tiếp tục dù sao đi nữa.]]> - Đang tìm kiếm trò chơi... Không tìm thấy trò chơi cho ID Tiêu đề: %1$s @@ -564,10 +558,6 @@ 6GB (Không an toàn) 8GB (Không an toàn) - - Boost (1700MHz) - Nhanh (2000MHz) - Celsius Fahrenheit 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 7777b40767..6ae0f102b2 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 @@ -1,7 +1,7 @@ - 本软件可以运行Nintendo Switch游戏主机的游戏,但自身并不包含任何游戏或密钥。

请于开始之前选定位于你的设备存储空间中的prod.keys]]>。

了解更多]]>
+ 本软件可以运行用于 Nintendo Switch 游戏机的游戏。不包含任何游戏或密钥。

在您开始之前, 请定位您设备存储上的 prod.keys ]]> 文件。

了解更多]]>
错误与注意事项 当发生错误时显示通知。 未授予通知权限! @@ -29,8 +29,8 @@ 触控叠加层自动隐藏 在指定时间内未进行任何操作后,自动隐藏触控叠加层。 启用触控叠加层自动隐藏 - 使用控制器时隐藏触控叠加层 - 在使用实体控制器时自动隐藏触控叠加层,而当控制器断开时触控叠加层则会重新显现。 + 当使用控制器控制输入时隐藏触控叠加层 + 在使用实体控制器时自动隐藏触控叠加层,而当控制器断开连接时,触控叠加层则会重新显现。 切换“确认/返回”控制器按钮功能 在与本应用的界面交互时,交换 Android 的“确认”与“返回”按钮的处理方式,以匹配 Switch 和 Xbox 的风格。 @@ -66,6 +66,8 @@ 显示当前功耗和电池剩余容量 显示着色器编译信息 显示当前正在编译的着色器数量 + 管线工作线程 + 管理用于构建 Vulkan 管线的核心数量,较高的值可提升管线编译性能,但温度也会随之升高。 叠加层位置 选择叠加层在屏幕上显示的位置 左上 @@ -95,7 +97,7 @@ 勾选时,禁用映射内存上传的重排序功能,允许将上传与特定绘制关联。在某些情况下可能会降低性能。 同步核心速度 - 将核心速度与最大速度百分比同步,在不改变游戏实际速度的情况下提高性能。 + 将核心时钟周期速度与最大速度百分比同步,从而在不改变游戏实际速度的情况下提升性能。 启用主机 MMU 模拟 此优化可加速来宾程序的内存访问。启用后,来宾内存读取/写入将直接在内存中执行并利用主机的 MMU。禁用此功能将强制所有内存访问使用软件 MMU 模拟。 调试开关 @@ -104,7 +106,7 @@ NVDEC模拟 - 选择视频解码处理方式 + 如果在过场动画中出现崩溃就切换为 CPU。 禁用 @@ -179,14 +181,14 @@ 隐藏满员房间 隐藏空房间 点击刷新重试 - 搜索房间… + 搜索游戏大厅… 首选游戏 - 大厅类型 + 游戏大厅类型 长度需为3-20个字符 - 必填 + 需要 需要Web令牌,请前往高级设置 -> 系统 -> 网络 IP格式无效 - 必须为4-20个字符(仅字母数字、点号、连字符、下划线和空格) + 必须为4至20个字符,且仅包含字母、数字、点号、连字符、下划线和空格 用户名无效,请在系统→网络中检查设置 必须为48个字符,且仅包含小写字母a-z 端口需为1-65535 @@ -246,7 +248,7 @@ 安装 prod.keys 文件 需要密钥文件来解密游戏 跳过添加密钥文件? - 对于商业游戏,需要有效的密钥文件才能运行。如果没有密钥文件,将只能运行自制软件。 + 模拟零售版游戏需要有效的密钥。如果选择继续,仅有自制软件可以正常运行。 https://yuzu-mirror.github.io/help/quickstart/#guide-introduction 跳过添加固件? 设置模拟器数据 @@ -285,11 +287,92 @@ 跳过 取消 安装 Amiibo 密钥文件 - 在遊戏中使用 Amiibo 时必需 + 在遊戏中使用 Amiibo 时需要 GPU驱动获取器 GPU 驱动管理器 安装替代的驱动程序以获得更好的性能和精度 - 高级选项 + 后处理效果 + 在渲染完成后应用 ReShade FX 效果 + 配置此游戏的效果链 + 添加效果 + 移除 + 打开列表 + 关闭列表 + 移除效果 + 预设已激活。在开始游戏前可以在后处理效果中编辑它。 + 数值已从原始预设被更改。 + 预设 + 新建预设 + 把您加载的效果及当前数值保存为一个预设,以便今后选用。 + 请给预设起一个不含等号的名称。 + 无预设 + 删除预设 + 重置预设值 + 重置为默认值 + 找不到效果。请在此文件中放置 .fx 文件: + 帧生成 + 配置针对此游戏的帧生成设定 + 设定要在使用无损缩放渲染的帧之间应用的插帧。启用后强制采用FIFO呈现模式 。 + 多帧生成 + 对于每个渲染帧,设定应显示的帧。数值越高,所消耗的 GPU 时间就越会成倍增多。如果设定超过显示器能力的帧数,将会降低模拟速度。 + 2x + 3x + 4x + 目标帧率 + 选取一个符合显示器实际能能力的帧率。增幅会自动调整以保持设定的帧率不变,并回调任何会导致游戏运行变慢的设定。 + 使用固定增幅 + 60 FPS + 90 FPS + 120 FPS + 开 + 关 + 固定 + 自动 + 打开或关闭帧生成并选择帧生成倍数。 + 生成帧到目标帧率。倍率会自动调整。 + 用来估算帧间运动的分辨率。较低的值可以节省 GPU 时间。 + 帧队列目标 + 在显示之前有多少已完成渲染的帧正在等待。较大的队列可以缓解 GPU 突发的压力,但会增加输入延迟。 + 最低延迟 (无缓冲) + 平衡 (1 帧) + 最平滑 (2 帧) + 将运动预估与游戏匹配 + 在游戏实际渲染的分辨率下估算运动,而不是在放大后的输出上。不会影响精确性,因为放大并不会增加运动细节。 + 运动预估分辨率 + 光流通道的分辨率,以输出的比例表示。降低它是提升性能最经济的做法。 + 帧生成不可用 + 这个 GPU 驱动缺少 Lossless Scaling 着色器所需的 Vulkan 内存模型或半精度 (float16) 支持。 + 作为可选项。请提供您自己的 Lossless.dll 以便在之后可以启用帧生成。 + 安装 Lossless.dll + 帧生成需要您自己从 Lossless Scaling 获得合法的 Lossless.dll 副本 + 选择其它 Lossless.dll 副本 + 帧生成 + 支持 + 不支持 (无 Vulkan 内存模型或 float16) + 无损缩放 + 提供您自己的 Lossless.dll 文件以启用帧生成 + 已安装 + 未安装 + 替换 + 移除 + 删除已安装的 Lossless.dll 及其准备好的着色器 + 帧生成将停止工作,直到您重新安装 Lossless.dll。您的原始文件不会受到影响。 + 未安装 Lossless.dll + 请从设置 › 无损缩放安装它以使用帧生成。 + 请先关闭游戏 + 无法在游戏运行时更改 Lossless.dll。 + 没有可以移除的项目 + 尚未安装 Lossless.dll。 + 正在准备帧生成着色器... + 已成功安装 Lossless.dll + 无法安装 Lossless.dll  + 无法复制选定的文件。 + 无法读取选定的文件。 + 选定的文件不是一个 Windows 动态链接库。请从您的 Lossless Scaling 安装目录中选择 Lossless.dll。 + 此副本的 Lossless.dll 中尚未包含帧生成着色器。请更新 Lossless Scaling 然后重试。 + 无法翻译帧生成着色器。尚未支持此版本的 Lossless Scaling。 + 已翻译的着色器无法写入到存储中。请确认其是否拥有足够的可用空间。 + 高级设置 更改模拟器设置 最近游玩 最近添加 @@ -416,6 +499,9 @@ CPU 精度 %1$s%2$s + 自制软件参数 + 启动时传递给自制软件的命令行参数(例如 -noglsl)。 + 设备名称 主机模式 @@ -429,96 +515,98 @@ 设置自定义系统时间 - CPU 超频 - 强制模拟的 CPU 以更高的时钟频率运行,从而解除某些 FPS 限制器。使用 Boost (1700MHz) 可让游戏以 Switch 的最高原生时钟运行,或使用 Fast (2000MHz) 以 2 倍时钟运行。 - 自定义CPU时钟 - 设置自定义的CPU时钟值。更高的值可能提高性能,但也可能导致游戏卡顿。建议范围为77-21000。 - 时钟 + CPU 时钟频率 + 提高模拟 CPU 报告的时钟频率,从而消除部分帧率限制。性能较弱的 CPU 可能会出现可见性能下降,甚至会导致某些游戏运行异常。 + 自定义 CPU 时钟周期 + 设定自定义的 CPU 时钟周期值。更高的值可以提升性能,但也可能导致游戏冻结卡死。建议的赋值范围为77-21000。 + 时钟周期 内存布局 - (实验性) 更改模拟内存布局。此设置不会提高性能,但可能有助于通过模组使用高分辨率的游戏。不要在 RAM 为 8GB 或更少的手机上使用。 + (实验性) 更改模拟内存布局。此项设置并不会提升性能,但可能有助于游戏通过 mods 来利用高分辨率。请不要在内存不大于 8GB 的手机上使用。仅适用于 Dynamic(JIT)后端。 生成 网络令牌 - 用于创建公共房间的网络令牌。它是一个48个字符的字符串,仅包含小写字母a-z。 + 用于创建公共游戏大厅的 web token。这是一个仅包含小写字母 a-z 的 48 位字符串。 网络用户名 - 多人游戏房间中显示的用户名。必须为4-20个字符(仅字母数字、连字符、点号、下划线和空格)。 + 在多人游戏大厅中显示的用户名。用户名必须为4至20个字符,且仅可包含字母、数字、连字符、点号、下划线和空格。 网络 分辨率 (掌机模式/主机模式) 垂直同步模式 窗口滤镜 - FSR 锐化度 - 指定使用 FSR 时图像的锐化程度 + FSR/SGSR 锐度 + 确定在使用 FSR 或 SGSR 过滤器时的图像锐度 抗锯齿方式 高级 GPU 模式 - 控制 GPU 模拟的精确度。大部分游戏在性能或平衡模式下可以正常渲染,但部分游戏需要设置为精确。粒子效果通常只有在精确模式下才能正确显示。 + 控制 GPU 模拟模式。大多数游戏在“快速”模式下渲染效果良好,但有些游戏仍需使用“精确”模式。而粒子效果通常只有在“精确”模式下才能正确渲染。 DMA 精度 - 控制 DMA 精度。安全精度可以修复某些游戏中的问题,但在某些情况下也可能影响性能。如果不确定,请保留为“默认”。 + 控制 DMA 的精准度。安全精度可以修复存在于某些游戏中的问题,但在某些情况下也会对性能造成影响。如不确定,请保持“默认”。 + GPU 围栏行为 + 控制 GPU 围栏同步行为。“即时”是速度最快的选项,但可能会引入一些问题。“均衡”提供了更好的兼容性,可能修复某些游戏中的问题。“精确”在牺牲部分性能的前提下进一步提升兼容性。“严格”是速度最慢的选项,但可以修复那些要求更严格同步的问题。默认遵循 GPU “精确”设定。 各向异性过滤 - 提高斜角的纹理质量 + 提升斜视角下的纹理质量 显存使用模式 - 控制显存分配策略 + 控制显存分配与释放策略 ASTC解码方式 - 选择ASTC压缩纹理的解码方式:CPU(慢速、安全)、GPU(快速、推荐)或CPU异步(无卡顿,可能导致问题) + 选择渲染时使用的 ASTC 压缩纹理解码方式:CPU(缓慢,安全),GPU(快速,推荐),或 CPU 异步(无卡顿,但可能导致问题) 同步内存操作 确保计算和内存操作之间的数据一致性。 此选项应能修复某些游戏中的问题,但在某些情况下可能会降低性能。 使用Unreal Engine 4的游戏似乎受影响最大。 磁盘着色器缓存 将生成的着色器缓存于磁盘中并进行读取,以减少卡顿。 强制最大时钟 (仅限 Adreno) - 强制 GPU 以最大时钟运行 (仍被温控限制)。 + 强制 GPU 以最大时钟运行 (温控依然生效)。 GPU 异步模拟 - 异步运行 GPU 模拟,以减少 CPU 停顿并提高吞吐量。仅当遇到与时序相关的问题时才应禁用此功能。 - 异步呈现 - 通过将呈现操作移至单独的 CPU 线程来略微提升性能。 + 此技巧可通过异步运行 GPU 模拟来提升性能,但在执行与时序相关的操作时,可能带来图形显示问题以及增加崩溃概率。 启用反应性刷新 - 通过牺牲性能来提高某些游戏的渲染精度。 + 通过牺牲性能来提升某些游戏的渲染精度。 启用缓冲区历史 启用对先前缓冲区状态的访问。此选项可在某些游戏中提升渲染质量并保持性能的一致性。 优化顶点缓冲区 - 启用经过优化的顶点缓冲区绑定以提升性能。需要 Mesa 26.0 及以上版本的 Turnip 或 QCOM 驱动程序。若使用较旧版本的 Turnip 驱动则会导致崩溃。 + 启用经过优化的顶点缓冲区绑定以提升性能。需要 Mesa 26.0 及以上版本的 Turnip 或 QCOM 驱动程序。若使用较旧版本的 Turnip 驱动 (25.3 及以下版本) 则会导致崩溃。 Hacks - GPU 超频频率 - 强制大多数游戏以其最高原生分辨率运行。设置为 256 可获得最佳性能,设置为 512 可获得最佳画面保真度。 + GPU 时钟频率 + 通过让游戏误以为 GPU 的处理速度比实际更快,以避免游戏为了适应 Switch 的时钟频率而降低分辨率和渲染距离。 跳过CPU内部无效化 - 在内存更新期间跳过某些CPU端缓存无效化,减少CPU使用率并提高其性能。可能会导致某些游戏出现故障或崩溃。 + 在更新内存时跳过某些 CPU 端的缓存失效操作,从而降低 CPU 占用率并提升性能。可能会在某些游戏中引发故障点或崩溃。 修复 Bloom 效果 - 减少《塞尔达传说:智慧的再现》(Adreno A6XX - A7XX/ Turnip)中的 bloom 模糊,并移除《Burnout》中的 bloom 效果。警告:可能会导致在其他游戏中出现图形异常。 + 减少《智慧的再现》和《众神的三角力量2》(Adreno A6XX - A7XX/ Turnip)中的 bloom 模糊,并移除《Burnout》中的 bloom 效果。警告:可能会导致在其他游戏中出现图形异常。 模拟 BGR565 - 修复了游戏中的颜色反转以及出现的异常画面瑕疵或奇怪阴影问题 + 修复游戏中的颜色反转或是异常的画面瑕疵或阴影问题 + 启用旧版缩放处理 + 启用通过使用快速缩放路径,来为游戏提供缩放配置处理的传统处理方式 使用异步着色器 - 异步编译着色器。这可能会减少卡顿,但也可能会导致图形错误。 - GPU 还原设置 - 配置基于 GPU 的纹理还原参数,或将其完全禁用。调整这些设置以平衡性能与纹理加载质量。 - 启用 GPU 还原 + 以异步方式编译着色器。采用此方式或可减少卡顿,但也可能引入故障点。 + GPU Unswizzle 设置 + 配置基于 GPU 的纹理 unswizzling 参数,或完全禁用该功能。通过调整这些设置,以尝试在性能与纹理加载质量之间取得平衡。 + 启用 GPU Unswizzle 禁用 - GPU 还原最大纹理尺寸 - 设置基于 GPU 的纹理还原的最大尺寸(单位:MiB)。\n虽然 GPU 在处理中型和大型纹理时速度更快,但对于非常小的纹理,CPU 的效率可能更高。\n调整此设置,以便在 GPU 加速和 CPU 开销之间找到最佳平衡点。 - GPU 还原流大小 - 设置每帧还原大型纹理的数据限制。较高的数值可以加快纹理加载速度,但代价是增加帧延迟(影响响应速度/平滑度);较低的数值可以减少 GPU 开销,但可能会导致明显的纹理突然出现(Pop-in)现象。 - GPU 还原块大小 - 定义了 3D 纹理在单个批次(Batch)中处理的深度切片(Depth Slices)数量。增加此数值可以提升强力 GPU 的吞吐效率,但在性能较弱的硬件上可能会引起卡顿或驱动程序超时(Driver Timeouts)。 + GPU Unswizzle 最大纹理尺寸 + 设置基于 GPU 的纹理 unswizzling 的最大尺寸(MB)。虽然 GPU 处理中等和大型纹理的速度更快,但对于非常小的纹理,CPU 可能更为高效。通过调节此项设置,以平衡GPU 加速与 CPU 开销。 + GPU Unswizzle 流大小 + 设置用于 unswizzling 大型纹理时的每帧数据限制。较高的数值可以加速纹理的加载过程,但会带来更高的帧延迟。而较低的数值则可以降低 GPU 的开销,但也可能会导致可见的纹理闪现。 + GPU Unswizzle 块大小 + 定义了 3D 纹理每批次处理的深度切片数量。增加此数值可在高性能 GPU 上提升吞吐效率,但在性能较弱的硬件上可能会导致卡顿或驱动超时。 默认 扩展 扩展动态状态 - 控制在扩展动态状态中可使用的函数数量。更高的数值允许启用更多功能,并可能提升性能,但同时也可能导致额外的图形问题。 + 控制扩展动态状态 (EDS) 可使用的功能数目。较高的数值将允许根据驱动程序支持的动态状态来减少管线编译的次数。 已禁用 顶点输入动态状态 - 开启顶点输入动态状态功能来获得更好的质量和性能。 + 启用此功能可实现更灵活的顶点输入处理,可能减少顶点/缓冲区的管线编译时间。 采样着色 - 允许片段着色器在多采样片段中每个样本执行一次,而不是每个片段执行一次。以提高性能为代价改善图形质量。 + 允许片段着色器在多采样片段中对每个采样点执行一次操作,而非对每个片段执行一次。在提升图形显示质量的同时,会牺牲一部分性能。 显示 @@ -533,8 +621,9 @@ CPU - 使用自动存根 - 自动补全缺失的服务和功能。可提高兼容性但可能导致崩溃和稳定性问题。 + 时钟频率 + 使用自动占位处理 + 自动为缺失的服务和功能生成占位代码。这可能会提高兼容性,但也可能导致崩溃和稳定性问题 GPU API @@ -542,22 +631,28 @@ 将图形 API 设置为较慢的调试模式。 BCn 纹理补丁 在 Adreno GPU 上覆盖自动 BCn 纹理格式检测。通常根据 Android 版本自动检测(在 API 28 及以上启用)。 - Fastmem 内存访问 + Fastmem 日志记录 按行刷新调试日志 在每行写入时刷新调试日志,使在崩溃或冻结时调试更容易。 + 开启扩展日志 + 将最大日志文件大小从 100 MiB 提升至 1GiB。 + 日志筛选器 + 控制 Eden 的日志类别。例如: *:Info Service.LM:Debug GPU 日志 - 启用 GPU 日志 - 将 GPU 操作记录至 eden_gpu.log 以供调试 Adreno 驱动 日志等级 GPU 日志的详细级别(数值越高 = 细节越多,开销越大) 记录 Vulkan API 调用 在环形缓冲区中跟踪所有 Vulkan API 调用 - 转储着色器 - 保存已编译的着色器 SPIR-V 到文件中 + 转储 SPIR-V 着色器 + 将经过重编译的 SPIR-V 二进制文件(.spv)保存到 dump 文件夹中。使用 spirv-dis/spirv-cross/spirv-val 进行检查。 + 转储 Guest (Maxwell) 着色器 + 将 Maxwell guest 着色器字节码文件(*.ash)保存到 dump 文件夹。使用 nvdisasm 进行检查。 + 转储 Maxwell 宏 + 将 Maxwell 宏程序文件(*.macro)保存到 dump 文件夹。使用 envydis 进行检查。 追踪 GPU 内存 监控 GPU 内存的分配和释放 驱动调试信息 @@ -609,7 +704,7 @@ 未使用 输入映射过滤器 选择一个设备过滤输入映射 - 控制器自动映射 + 自动映射控制器键位 选择一个设备以尝试自动映射 尝试为 %1$s 自动映射 控制器类型 @@ -655,9 +750,9 @@ 正在关闭… 您要将此设定重置为默认值吗? 恢复默认 - 重置所有高级选项 + 重置所有高级设置 重置所有设置项? - 所有高级选项都将被重置,此动作无法还原。 + 所有高级设置都将重置为默认。此操作无法撤销。 重置设置项 关闭 了解更多 @@ -725,10 +820,10 @@ 声音 输出引擎及音量 控制 - 使用控制器来映射输入 + 映射控制器键位输入 玩家 %d 调试 - CPU/GPU 调试、图形 API 及 fastmem 内存访问 + CPU/GPU 调试、图形 API 以及 fastmem 自定义路径 存档目录 @@ -784,7 +879,7 @@ 由于着色器缓存需要重新生成,您将遇到更多卡顿 着色器缓存清除成功 已清理着色器 - Eden 已自动清除所有着色器缓存以维护 Vulkan 管线验证。这在切换 GPU 驱动程序时是必需的,以防止崩溃和图形损坏。在着色器重建期间您可能会遇到一些卡顿。 + Eden 已自动清空所有着色器缓存,以维持 Vulkan 管线验证流程。在切换 GPU 驱动程序时,此操作是必要的,可防止崩溃和画面异常。在着色器重构期间,您可能会感受到些许卡顿。 附加项: %1$s 保存数据 管理此游戏的保存数据 @@ -829,7 +924,7 @@ ROM 文件不存在 游戏需要固件 - 转储并安装固件,或点击\"确定\"继续。]]> + 正在搜索游戏... @@ -872,15 +967,15 @@ 相对摇杆中心 十字方向键滑动 触觉反馈 - 显示控制器 - 隐藏控制器 + 显示触控叠加层 + 隐藏触控叠加层 全部切换 调整触控叠加层 缩放 不透明度 重置触控叠加层 编辑触控叠加层 - 截图到网格 + 对齐到网格 暂停模拟 继续模拟 触控叠加层选项 @@ -930,14 +1025,15 @@ 6GB (不安全) 8GB (不安全) - - 加速 (1700MHz) - 快速 (2000MHz) + + 正常 + 升频 + 超频 - - 关闭 - 中 (256) - 高 (512) + + 正常 + 升频 + 超频 极小 (16 MB) @@ -976,8 +1072,7 @@ 无 - 性能 - 平衡 + 快速 精确 @@ -985,8 +1080,14 @@ 不安全 安全 - 保守模式 - 激进模式 + + 默认 + 即时 + 均衡 + 精确 + + 保守式 + 主动式 即时 (关闭) @@ -1008,9 +1109,9 @@ 拉伸窗口 - 高精度 - 低精度 - 偏执模式 + 精确 + 不安全 + 极致 调试 @@ -1060,8 +1161,6 @@ 浅色 深色 - 无 - 使用黑色背景 使用深色主题时,套用黑色背景。 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 d45059a3b0..1391f1bc6e 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 @@ -1,12 +1,12 @@ - 本軟體可執行Nintendo Switch主機的遊戲,軟體不提供遊戲和金鑰檔案。<br/> <br/>在開始之前,請先安裝您的 <prod.keys</b> ]]> 檔案,<br /><br />了解更多]]> + 本軟體可執行 Nintendo Switch 主機的遊戲,軟體不提供遊戲和金鑰檔案。

在開始之前,請先安裝您的 prod.keys ]]> 檔案,了解更多]]>
通知和錯誤 - 發生錯誤時顯示通知。 + 發生錯誤時顯示通知 未授予通知權限! - Eden模擬器通知 - Eden正在執行 + Eden 模擬器通知 + Eden 正在執行 秒 @@ -16,7 +16,7 @@ 範圍最大必須為%1$d 無效的範圍 - 使用自定義組態中 + 使用個別遊戲設定中 顯示虛擬按鍵 @@ -25,14 +25,14 @@ 編輯時將虛擬按鍵與網格對齊 網格大小 調整網格格線間距 - 行為模式 + 隱藏 自動隱藏虛擬按鍵 在未使用虛擬按鍵幾秒後自動隱藏 啟用自動隱藏虛擬按鍵 使用控制器時自動隱藏虛擬按鍵 在使用控制器時自動隱藏畫面上的虛擬按鍵,會在控制器斷開連接後重新出現 反轉手把的 確認/返回 按鈕 - 使用Eden時將手把的確認及返回鍵反轉以符合Switch與Xbox的操作方式 + 使用 Eden 時將手把的確認及返回鍵反轉以符合 Switch 與 Xbox 的操作方式 虛擬按鍵設定 調整虛擬按鍵的位置與顯示設定 @@ -46,26 +46,28 @@ 著色器 (充電中) - 系統: + 系統: 顯示效能統計疊加層 自訂 可見項目 疊加層 啟用效能統計疊加層 設定疊加層中顯示的資訊 - 顯示FPS - 顯示當前幀率 - 顯示幀時間 + 顯示影格率 + 顯示目前的影格率 + 顯示影格時間 顯示應用程式的記憶體用量 - 顯示模擬器正在使用的記憶體量 + 顯示模擬器的記憶體用量 顯示系統記憶體用量 - 顯示系統使用的記憶體量 + 顯示系統的記憶體用量 顯示電池溫度 電池溫度單位 顯示電池資訊 顯示當前功耗和電池可續航時間 當著色器在編譯時顯示 顯示正在編譯的著色器數量 + 管線工作執行緒 + 設定用於建置 Vulkan 管線的 CPU 核心數量,設定的數值越大效能越好,但溫度也會上升更快 疊加層位置 選擇疊加層在畫面上的顯示位置 左上 @@ -78,33 +80,33 @@ 為疊加層添加背景以提高可讀性 - 顯示裝置資訊浮層 - 啟用裝置浮層 - 裝置浮層 - 設定裝置浮層中顯示的資訊 + 顯示裝置資訊疊加層 + 啟用裝置疊加層 + 裝置疊加層 + 設定裝置疊加層中顯示的資訊 - 顯示Eden的組建版本 + 顯示 Eden 的組建版本 顯示圖形驅動程式的版本 顯示裝置型號 - 顯示GPU型號 - 顯示CPU型號 + 顯示 GPU 型號 + 顯示 CPU 型號 顯示韌體版本 停用緩衝區重新排序 - 勾選時,停用映射記憶體上傳的重新排序功能,允許將上傳與特定繪製關聯。某些情況下可能會降低效能。 + 勾選時,停用映射記憶體上傳的重新排序功能,允許將上傳與特定繪製關聯。某些情況下可能會降低效能 同步核心速度 - 將核心速度與最大速度百分比同步,在不改變遊戲實際速度的情況下提高效能。 + 將核心速度與最大速度百分比同步,在不改變遊戲實際速度的情況下提高效能 啟用主機 MMU 模擬 - 此最佳化可加速來賓程式的記憶體存取。啟用後,來賓記憶體讀取/寫入將直接在記憶體中執行並利用主機的 MMU。停用此功能將強制所有記憶體存取使用軟體 MMU 模擬。 + 此最佳化可加速客體程式的記憶體存取。啟用後,客體記憶體讀取/寫入將直接在記憶體中執行並利用主機的 MMU。停用此功能將強制所有記憶體存取使用軟體 MMU 模擬 偵錯開關 僅限開發用途 - 0到65535 + 0 到 65535 - NVDEC模擬 - 選擇影片解碼(NVDEC)的方式 + NVDEC 模擬 + 若在過場動畫中當機請切換成 CPU 無 @@ -119,7 +121,7 @@ 加入 瀏覽公開房間 使用者名稱 - IP位址 + IP 位址 埠 房間建立成功 成功加入房間 @@ -127,8 +129,8 @@ 網路錯誤 連線中斷 使用者名稱已被其他人使用 - MAC位址衝突 - 主機ID衝突 + MAC 位址衝突 + 主機 ID 衝突 版本不符 密碼錯誤 無法連接 @@ -141,7 +143,7 @@ 您已被房主踢出 未知錯誤 房間未初始化 - 房間閒置 + 房間閒置中 正在加入房間 已加入房間 房間管理員 @@ -149,7 +151,7 @@ %1$s 離開 %1$s 被踢出 %1$s 被封鎖 - 位址已解封 + IP 位址已解封 踢出成員 密碼 正在加入… @@ -171,7 +173,7 @@ 封鎖使用者 公開房間 未找到公開房間 - 需輸入密碼 + 請輸入密碼 :%1$d/%2$d 遊戲 任意遊戲 @@ -180,16 +182,16 @@ 隱藏空房間 點擊重新整理以重試 搜尋房間… - 首選遊戲 + 遊戲 大廳類型 - 長度需為3-20個字元 - 必填 - 需要Web令牌,請前往進階設定 -> 系統 -> 網路 - IP格式無效 - 必須介於4-20個字元之間,並且僅能使用英文字母、數字、句點、破折號、底線和空格(標點符號須為英文格式) - 使用者名稱無效,請在系統→網路中檢查設定 - 必須為48個字元,且僅包含小寫字母a-z - 埠號需為1-65535 + 長度需為 3-20 個字元 + 請填寫這個欄位 + 需要 Web 令牌,請前往進階設定 -> 系統 -> 網路 + IP 格式無效 + 必須介於 4-20 個字元之間,並且僅能使用英文字母、數字、句點、破折號、底線和空格(標點符號須為英文格式) + 使用者名稱無效,請在系統 -> 網路中檢查設定 + 必須為 48 個字元,且僅包含小寫字母 a-z + 埠號需為 1-65535 取消 確定 重新整理 @@ -199,16 +201,16 @@ 歡迎! - 瞭解如何設定 Eden 並開始模擬。 + 瞭解如何設定 Eden 並開始模擬 開始使用 金鑰 - 使用下方的按鈕安裝您的 prod.keys 檔案。 + 使用下方的按鈕安裝您的 prod.keys 檔案 韌體 使用下方的按鈕安裝您的firmware.zip檔案 遊戲資料夾 使用下方的按鈕選擇您的遊戲資料夾。 完成 - 您已準備就緒。\n盡情遊玩您的遊戲! + 您已準備就緒\n盡情遊玩您的遊戲! 下一步 上一步 新增遊戲 @@ -226,8 +228,8 @@ 不再顯示 遊戲目錄新增成功 檢查更新 - 在Eden啟動時檢查有無新版本並選擇是否要下載與安裝更新 - 偵測到新版本 + 在 Eden 啟動時檢查有無新版本並選擇是否要下載與安裝更新 + 發現新版本 有更新可供安裝:%1$s\n\n您要進行更新嗎? 下載更新中 更新下載失敗 @@ -235,30 +237,30 @@ 更新安裝失敗:%1$s 搜尋 設定 - 找不到檔案,或者尚未選取遊戲目錄。 + 找不到遊戲檔案,或者尚未選取遊戲目錄 管理遊戲資料夾 允許 Eden 尋找您的遊戲檔案 跳過選擇遊戲資料夾? - 如果未選擇遊戲資料夾,遊戲將不會顯示在遊戲清單。 + 如果未選擇遊戲資料夾,遊戲將不會顯示在遊戲清單 https://yuzu-mirror.github.io/help/quickstart/#dumping-games 搜尋遊戲 搜尋設定 安裝 prod.keys - 需要解密零售遊戲 + 需要用來解密零售遊戲 跳過安裝金鑰? - 模擬零售遊戲需要有效的金鑰,若要繼續,將僅有自製遊戲可以運作。 + 模擬零售遊戲需要有效的金鑰,如果不安裝將僅有自製遊戲可以運作 https://yuzu-mirror.github.io/help/quickstart/#guide-introduction 跳過安裝韌體? 設定模擬器資料 - 模擬器需要金鑰才能正常執行,同時建議安裝韌體以啟動QLaunch小程式 + 模擬器需要金鑰才能正常執行,同時建議安裝韌體以啟動 QLaunch 小程式 授予權限 授予權限以使用模擬器的特定功能 - 許多遊戲需要韌體才能正常運作。 + 許多遊戲需要韌體才能正常運作 https://yuzu-mirror.github.io/help/quickstart/#guide-introduction 通知 - 使用下方的按鈕授予通知權限。 + 使用下方的按鈕授予通知權限 權限遭拒 - 您曾多次拒絕了權限要求,現在您需要在系統設定中手動授予權限。 + 您多次拒絕了要求的權限,現在您需要在系統設定中手動授予 關於 組建版本、製作群、以及更多 裝置資訊 @@ -271,10 +273,10 @@ 版本號碼 基本資訊 硬體 - 支援的ABI - CPU資訊 - GPU資訊 - Vulkan驅動程式版本 + 支援的 ABI + CPU 資訊 + GPU 資訊 + Vulkan 驅動程式版本 取得裝置資訊失敗 記憶體資訊 記憶體大小 @@ -285,58 +287,142 @@ 跳過 取消 安裝 Amiibo 金鑰 - 需要在遊戲中使用 Amiibo - GPU驅動程式下載器 + 需要先安裝才能在遊戲中使用 Amiibo + GPU 驅動程式下載器 GPU 驅動程式管理員 安裝替代驅動程式以取得潛在的更佳效能或準確度 + 後製處理效果 + 在渲染完成後套用 ReShade FX 效果 + 為此遊戲設定特殊濾鏡或效果 + 新增效果 + 移除 + 開啟清單 + 收起清單 + 移除效果 + 濾鏡已啟用。請在開啟遊戲前於\"後製處理效果\"中調整 + 數值已變更,與原始濾鏡設定不同 + 濾鏡 + 新增濾鏡 + 將目前載入的效果及數值儲存為設定檔方便日後使用 + 請為此濾鏡設定檔命名,名稱中不得包含等號且不能與預先提供的設定檔名稱相同 + 無濾鏡 + 刪除濾鏡 + 重設此濾鏡的所有設定值 + 重設為預設值 + 找不到設定檔。請將濾鏡效果的 .fx 檔案放置於此資料夾: + 影格生成 + 調整此遊戲的影格生成設定 + 使用Lossless Scaling在已渲染的影格之間插入補間影格。啟用此功能時,會強制採用FIFO垂直同步 + 影格倍率 + 設定在每個已渲染的影格中要顯示的影格數,數值越高所需的GPU運算時間也會按比例增加。若要求的影格數超過裝置顯示器能呈現的數量將會降低模擬速度 + 2x + 3x + 4x + 目標影格率 + 選擇裝置顯示器能實際呈現的影格率,之後倍率器會自動升高或降低以維持影格率。如果某個調整導致遊戲本身運作變慢,則會自動復原該設定 + 使用固定倍率 + 60 FPS + 90 FPS + 120 FPS + 開啟 + 關閉 + 固定 + 自動 + 開啟或關閉影格生成後選擇影格倍率 + 選擇要生成的目標影格率,倍率器會自動調整 + 用來預測影格之間動態的解析度,較低的數值可以減少 GPU 運算時間 + 影格佇列目標 + 最多允許多少個已完成的影格在顯示器前等待,較大的佇列可以吸收 GPU 突發負載,但會增加輸入延遲 + 最低延遲(無緩衝) + 平衡(1 影格) + 最流暢(2 影格) + 配合遊戲調整運動預測 + 以遊戲實際渲染的解析度而非升頻後的輸出進行運動預測,由於升頻後不會增加任何動態細節,因此不會影響準確度 + 運動預測解析度 + 光流處理的解析度,以輸出解析度的比例表示,降低此設定值是減少效能負載最有效的方法 + 無法使用影格生成 + 此 GPU 驅動程式缺少 Lossless Scaling 著色器所需的 Vulkan 記憶體模型或半精度浮點數(float16)支援 + 可選擇安裝自己擁有的 Lossless.dll 以在之後啟用影格生成功能 + 安裝 Lossless.dll + 您需要擁有從合法管道獲得的 Lossless Scaling 並從該軟體的資料夾中取得 Lossless.dll 檔案才能使用影格生成功能 + 安裝不同的 Lossless.dll 檔案 + 影格生成 + 支援 + 不支援(無 Vulkan 記憶體模型或 float16) + Lossless Scaling + 安裝您擁有的 Lossless.dll 檔案以使用影格生成 + 已安裝 + 未安裝 + 更換 + 刪除 + 刪除已安裝的 Lossless.dll 及預先編譯的著色器 + 影格生成功能會停止運作直到您重新安裝Lossless.dll,您的 Lossless.dll原始檔不會被 Eden 修改 + Lossless.dll 未安裝 + 前往 設定 -> Lossless Scaling 安裝以使用影格生成功能 + 請先關閉遊戲 + Lossless.dll 無法在遊戲執行時進行更換 + 沒有東西可以刪除 + 尚未安裝 Lossless.dll + 準備影格生成著色器中... + 成功安裝 Lossless.dll + 無法安裝 Lossless.dll + 無法複製選取的檔案 + 無法讀取選取的檔案 + 選取的檔案並不是 Windows 程式庫。請在 Lossless Scaling 的安裝位置(資料夾)中選擇 Lossless.dll + 這個 Lossless.dll 的副本並未包含影格生成著色器。請更新 Lossless Scaling 後重試 + 無法轉譯影格生成著色器,尚不支援此 Lossless Scaling 版本 + 轉譯的著色器無法寫入到儲存空間,請確認手機容量是否充足 進階設定 - 進行模擬器設定 + 調整模擬器設定 最近遊玩 最近新增 開啟 Eden 資料夾 管理 Eden 的內部檔案 - 自訂Eden的主題與介面 + 自訂 Eden 的主題與介面 找不到檔案管理員 無法開啟 Eden 目錄 - 請使用檔案管理員的側邊面板手動定位到使用者資料夾。 + 請使用檔案管理員的側邊面板手動定位到使用者資料夾 管理儲存資料 - 已找到儲存資料,請選取下方的選項。 - 導入儲存資料 - 這將會以提供的檔案覆寫所有現有的儲存資料,您確定要繼續嗎? - 正在導入儲存資料… - 正在導出儲存資料… - 已成功導入 + 匯入/匯出遊戲的儲存資料\n壓縮檔必須為.zip格式\n壓縮檔中首個子資料夾名稱必須為遊戲ID + 匯入儲存資料 + 這將會以提供的檔案覆蓋現有的該遊戲儲存資料,您確定要繼續嗎? + 正在匯入儲存資料… + 正在匯出儲存資料… + 已成功匯入 無效的儲存目錄結構 - 首個子資料夾名稱必須為遊戲標題 ID。 + 首個子資料夾名稱必須為遊戲 ID 安裝韌體 韌體必須為 ZIP 壓縮檔,將會用於部分遊戲的啟動 正在安裝韌體 韌體安裝失敗 - 請確保韌體 nca 檔案位於 zip 壓縮檔的根目錄,然後再試一次。 + 請確保韌體 nca 檔案位於 zip 壓縮檔的根目錄後再試一次 韌體解除安裝失敗 分享偵錯記錄 分享 Eden 的記錄檔以便對相關問題進行偵錯 找不到日誌檔案 + 分享 GPU 日誌 + 分享 Eden 的 GPU 日誌以對圖形問題進行偵錯 + 找不到 GPU 日誌檔案 安裝遊戲內容 安裝遊戲更新或 DLC 正在安裝內容… 安裝檔案至 NAND 時發生錯誤 - 請確保內容有效並且 prod.keys 檔案已安裝。 - 為避免可能的衝突,無法直接安裝遊戲本體。 + 請確保內容有效並且 prod.keys 檔案已安裝 + 為避免產生衝突,無法直接安裝遊戲本體 %1$d 安裝錯誤 遊戲內容已成功安裝 %1$d 安裝成功 %1$d 覆寫成功 https://yuzu-mirror.github.io/help/quickstart/#dumping-installed-updates 您的裝置不支援自訂驅動程式 - 此裝置不支援注入自訂驅動程式。\n請以後再來查看是否已新增支援! + 此裝置不支援注入自訂驅動程式\n請以後再來查看是否已新增支援! 管理 Eden 資料 - 安裝韌體、金鑰,導入/導出使用者資料及安裝其他項目! + 安裝韌體、金鑰,導入/導出使用者資料及安裝其它項目! 遊戲資料夾 深度掃描 新增遊戲資料夾 這個資料夾已經新增過了! - 遊戲資料夾屬性 + 遊戲資料夾設定 %d 個存檔導入失敗 @@ -347,28 +433,30 @@ 驗證已安裝內容的完整性 檢查所有已安裝的內容是否有損壞 - 缺少解密金鑰 + 缺少 prod.keys 無法解密韌體和零售遊戲 https://yuzu-mirror.github.io/help/quickstart/#dumping-decryption-keys 解除安裝韌體 - 解除安裝韌體將從裝置中刪除它並可能影響遊戲相容性。 + 解除安裝韌體將從裝置中刪除它並可能影響遊戲相容性 正在解除安裝韌體... 韌體解除安裝成功 金鑰安裝失敗 金鑰安裝成功 - 一個或多個金鑰複製失敗。 - 請確保金鑰檔案具有.keys副檔名後重試。 - 金鑰初始化失敗。請檢查您的轉儲工具是否為最新版本並重新轉儲金鑰。 + 一個或多個金鑰安裝失敗 + 請確保金鑰檔案具有 .keys 副檔名後重試 + 金鑰初始化失敗,請檢查您的轉儲工具是否為最新版本並重新轉儲金鑰 Qlaunch - 從系統主畫面啟動應用程式 + 從 Switch 系統主畫面啟動應用程式(目前僅支援英文) 小程式啟動器 使用已安裝的韌體啟動系統小程式 + 未安裝韌體 無法使用小程式 + prod.keys檔案和韌體已安裝並重試]]> 相簿 使用系統相片檢視器查看儲存在使用者螢幕截圖資料夾中的影像 Mii 編輯 @@ -377,96 +465,150 @@ 編輯、刪除儲存在 amiibo 上的資料 Cabinet 啟動器 暱稱和擁有者設定 - 遊戲資料橡皮擦 + 刪除遊戲資料 還原程式 格式化程式 - Gaia不是真的 + Gaia 不存在 已複製到剪貼簿 一個開放原始碼的 Switch 模擬器 參與者 + 這些人讓 Eden Android 版成為可能 這些專案使 Eden Android 版成為可能 組建版本 使用者資料 - 導入/導出所有應用程式資料。\n\n導入使用者資料時,現有的使用者資料將被取代!\n\n直接從 Citron 導入資料可能會出現問題,建議手動導入所有所需資料。 - 正在導出使用者資料… - 正在導入使用者資料… + 匯入/匯出所有應用程式資料\n\n匯入使用者資料時,現有的使用者資料將被取代!\n\n直接從 Citron 匯入使用者資料可能會出現問題,建議手動匯入所有所需資料 + 正在匯出使用者資料… + 正在匯入使用者資料… 無效的 Eden 備份 - 使用者資料導出成功 - 使用者資料導入成功 - 導出已取消 - 請確保使用者資料夾位於 zip 壓縮檔的根目錄,並在 config/config.ini 路徑中包含組態檔案,並再試一次。 + 使用者資料匯出成功 + 使用者資料匯入成功 + 匯出已取消 + 請確保使用者資料夾位於 zip 壓縮檔的根目錄,並在 config/config.ini 路徑中包含組態檔案後重試 - 限制速度 - 將模擬速度限制在標準速度的指定百分比。 + 限制模擬速度 + 將模擬速度限制在標準速度的指定百分比 限制速度百分比 - 指定限制模擬速度的百分比。100% 為標準速度,更高或更低的值將會增加或減少速度限制。 + 指定模擬速度的百分比,100% 為標準速度,更高或更低的值將會減少或增加速度限制 + 加速模式 + 當開啟加速模式時,模擬器將以此速度執行 + 慢速模式 + 當慢速模式開啟時,模擬器將會以此速度執行 CPU 後端 CPU 準確度 %1$s%2$s + Homebrew 參數 + 在啟動時傳遞給 Homebrew 的命令列參數(例如:-noglsl) + 裝置名稱 底座模式 - 提高解析度,降低效能。停用後將會使用手提模式,會降低解析度並提高效能。 + 提高解析度,降低效能。停用後將會使用手提模式,會降低解析度並提高效能 模擬區域 模擬語言 選擇 RTC 日期 選擇 RTC 時間 自訂 RTC - 允許您設定與您的目前系統時間相互獨立的自訂時間。 + 允許您設定與您的目前系統時間相互獨立的自訂時間 設定自訂 RTC - 自訂CPU時脈 - 自訂CPU時脈。更高的值可能提高效能,但也可能導致遊戲卡死。建議範圍為77-21000。 + + CPU 時脈 + 提高模擬 CPU 回報的時脈,可以減少某些 FPS 限制。較弱的 CPU 可能會出現效能下降的情況,部分遊戲可能會出現異常 + 自訂 CPU 時脈 + 自訂 CPU 時脈,更高的值可能提高效能,但也可能導致遊戲卡死。建議範圍為 77-21000 時脈 - 記憶體佈局 - (實驗性)更改模擬記憶體佈局。此設定不會提高效能,但可能有助於使用模組來提高解析度的遊戲的載入。不要在 RAM 為 8GB 或更少的手機上使用。若遊戲崩潰請將進階設定裡偵錯選項中的CPU後端改為 Dynarmic (JIT) + 記憶體布局 + (實驗性)更改模擬記憶體布局,此設定不會提高效能,但可能有助於使用模組來提高解析度的遊戲的載入。不要在 RAM 為 8GB 或更少的手機上使用。若遊戲崩潰請將進階設定裡偵錯選項中的 CPU 後端改為 Dynarmic(JIT) 生成 網路令牌 - 用於建立公開大廳的網路令牌。它是由48個小寫字母a-z組成的字串。 + 用於建立公開大廳的網路令牌,由 48 個小寫字母 a-z 組成 網路使用者名稱 - 多人遊戲房間中顯示的使用者名稱。必須為4-20個字元,僅能使用英文字母、數字、句點、破折號、底線和空格(標點符號須為英文格式)。 + 多人遊戲房間中顯示的使用者名稱。必須為 4-20 個字元,僅能使用英文字母、數字、句點、破折號、底線和空格(標點符號須為英文格式) 網路 解析度 (手提/底座) 垂直同步 - 視窗適應過濾器 - FSR 銳化度 - 使用 FSR 時圖片的銳化程度 + 視窗自適應濾波器 + FSR/SGSR 銳化度 + 使用 FSR/SGSR 時圖片的銳化程度 抗鋸齒 + 進階 + + GPU 模式 + 設定 GPU 模擬的準確度,大多數遊戲在設定為快速時即可正常渲染,但有些需要渲染粒子的遊戲仍需設為準確來避免圖形錯誤 DMA 準確度 - 控制 DMA 準確度。安全準確度可以修復某些遊戲中的問題,但在某些情況下也可能影響效能。如果不確定,請保留為「預設」。 + 控制 DMA 準確度,將準確度設為穩定可以修復某些遊戲中的問題,但在某些情況下也可能影響效能。如果不確定,請保留為預設 + GPU 同步柵欄模式 + 控制 GPU 同步柵欄模式,即時是速度最快的選項,但可能會導致一些問題;使用平衡可以提供更好的相容性並修復某些遊戲中的錯誤;準確則是在犧牲部分效能的情況下進一步提升相容性;嚴格是最慢的選項,但可以修復那些對同步要求更嚴格的遊戲中的問題;預設則會依照 GPU 準確度設定 各向異性過濾 改善斜角檢視時的紋理品質 - VRAM使用模式 - 控制GPU記憶體的分配與釋放策略 - ASTC解碼方式 - 選擇ASTC壓縮紋理的解碼方式:CPU(慢速、安全)、GPU(快速、推薦)或CPU非同步(無卡頓,可能導致問題) + VRAM 使用模式 + 控制 GPU 記憶體的分配與釋放策略 + ASTC 解碼方式 + 選擇 ASTC 壓縮紋理的解碼方式:CPU(慢速、穩定)、GPU(快速、推薦)或 CPU 非同步(無卡頓,可能導致問題) 同步記憶體操作 - 確保計算和記憶體操作之間的資料一致性。 此選項應能修復某些遊戲中的問題,但在某些情況下可能會降低效能。 使用Unreal Engine 4的遊戲似乎受影響最大。 + 確保計算和記憶體操作之間的資料一致性,此選項應能修復某些遊戲中的問題,但在某些情況下可能會降低效能。使用 Unreal Engine 4 的遊戲似乎受影響最大 磁碟著色器快取 - 將產生的著色器快取儲存至硬碟以減少中斷。 - 強制使用最大時脈 (僅限Adreno) - 強制 GPU 以可能的最大時脈執行 (熱溫限制仍會被套用)。 + 將產生的著色器快取儲存至硬碟以減少中斷 + 強制使用最大時脈(僅限 Adreno) + 強制GPU以可能的最大時脈運作(熱溫限制仍會被套用) + GPU 非同步模擬 + 此修改可透過使用 GPU 非同步模擬來提升性能,不過可能會導致圖形問題且會提高在進行時序相關操作時當機的機率 使用重新啟用排清 - 犧牲效能,以改善部分遊戲的轉譯準確度。 - 跳過CPU內部失效處理 - 在記憶體更新期間跳過某些CPU端快取的失效處理,減少CPU使用率並提高其性能。可能會導致某些遊戲出現故障或崩潰。 + 犧牲效能以改善部分遊戲的轉譯準確度 + 啟用緩衝區歷史 + 允許存取先前的緩衝區狀態,此選項可能會改善部分遊戲的渲染品質與效能穩定性 + 最佳化頂點緩衝區 + 啟用最佳化的頂點緩衝區綁定,需要安裝 Mesa 26.0+ Turnip drivers/Qualcomm drivers。使用舊版 Turnip drivers 會導致當機(25.3 版或更低的版本) + + 修改 + + GPU 時脈 + 讓遊戲誤以為 GPU 工作能更快完成,避免遊戲為了配合 Switch 的時脈而降低解析度與渲染距離 + 跳過 CPU 內部失效處理 + 在記憶體更新期間跳過某些 CPU 端快取的失效處理,減少 CPU 使用率並提高其性能。可能會導致某些遊戲出現故障或當機 + 修正光暈特效 + 在 \"薩爾達傳說:織夢島\" 和 \"薩爾達傳說:智慧的再現\" 中減少光暈模糊的問題並移除 \"橫衝直撞:狂飆樂園重製版\" 中的光暈效果(適用於 Adreno A6XX - A7XX/ Turnip)。警告:可能會在其它遊戲中導致圖形異常 + 模擬 BGR565 + 修正遊戲中出現的色彩反轉、圖形異常或陰影異常 + 啟用舊版重新縮放處理 + 透過快速重新縮放處理來啟用遊戲重新縮放設定階段的舊版處理方式 使用非同步著色器 - 非同步編譯著色器。這可能會減少卡頓,但也可能導致圖形錯誤。 + 使用非同步編譯著色器,這可能會減少卡頓,但也可能導致圖形錯誤 + GPU Unswizzle 設定 + 設定基於 GPU 的 Unswizzling 參數或完全停用此功能,調整此設定以在性能與紋理載入品質中取得平衡 + 啟用 GPU Unswizzle + 停用 + GPU Unswizzle 最大紋理尺寸 + 設定 GPU 紋理 Unswizzling 的最大值(MB),雖然 GPU 處理中型和大型紋理的速度較快,但 CPU 處理非常小的紋理可能更有效率。調整此設定以在 GPU 加速與 CPU 負載之間取得平衡 + GPU Unswizzle Stream 大小 + 設定每個影格處理大型紋理 Unswizzling 的資料上限,數值越高載入紋理的速度越快,但會增加畫面延遲;數值較低則能降低 GPU 負載,不過有可能導致至紋理載入不完全甚至突然出現 + GPU Unswizzle Chunk 大小 + 設定 3D 紋理每批次處理的深度切片數量,增加此數值可提升規格較佳 GPU 的處理效率,但在較低規格的硬體上可能導致卡頓或驅動程式逾時 + 預設 + + + 擴充功能 + 擴展動態狀態 - 已停用 + 控制擴展動態狀態中可使用的功能數量,較高的數值可根據驅動程式支援的動態狀態減少管線編譯的次數 + 停用 頂點輸入動態狀態 - 啟用頂點輸入動態狀態以取得更佳的品質及性能 + 啟用此功能可提供更彈性的頂點輸入處理方式,並可能減少頂點/緩衝區的管線編譯時間 + 取樣著色 + 讓片段著色器在多重取樣片段中針對每個取樣執行,而不是每個片段只執行一次。可提升圖形品質但會降低部分效能 + + 顯示 方向 @@ -475,32 +617,59 @@ 子母畫面 位於背景時最小化視窗 - 解析度縮放到2倍或更高可能會導致問題,並可能顯著降低設備速度。 + 解析度縮放到 2 倍或更高可能會導致問題,並可能顯著降低設備速度 CPU + 時脈 使用自動存根 - 自動補上缺少的服務和功能。可提高相容性但可能導致崩潰和穩定性問題。 + 自動補上缺少的服務和功能,可提高相容性但可能導致當機和穩定性問題 GPU API 圖形偵錯 - 將圖形 API 設為慢速偵錯模式。 + 將圖形 API 設為慢速偵錯模式 + BCn 區塊壓縮紋理修補 + 覆寫 Adreno GPU 的 BCn 紋理格式自動偵測。通常會根據 Android 版本自動偵測(會在 API 28 以上的裝置自動啟用) Fastmem - 日誌記錄 + 日誌 按行寫入偵錯日誌 - 在每行寫入時重新整理偵錯日誌,讓程式在崩潰或當機時更容易偵錯。 + 寫入一行日誌就立刻更新偵錯記錄,讓程式在當機或畫面凍結時更容易偵錯 + 啟用更詳細的日誌記錄 + 將日誌檔的最大檔案限制從 100MB 提高到 1GB + 日誌過濾器 + 控制 Eden 日誌的紀錄類別。例如: *:Info Service.LM:Debug + + + GPU 日誌 + 記錄層級 + GPU 日誌的詳細層級(越高日誌越詳細,不過會更容易過熱) + 記錄 Vulkan API 呼叫 + 在環形緩衝區中記錄所有 Vulkan API 呼叫 + 傾印 SPIR-V 著色器 + 將重新編譯後的 SPIR-V 二進位檔案(.spv)儲存至 Eden 資料夾裡的 dump 資料夾中。可使用 spirv-dis/spirv-cross/spirv-val 進行檢查 + 傾印客體(Maxwell)著色器 + 將 Maxwell 客體著色器位元組碼檔案(.ash)儲存至 Eden 資料夾裡的 dump 資料夾中。可使用 nvdisasm 進行檢查 + 傾印 Maxwell 巨集 + 將 Maxwell 巨集程式檔案(.macro)儲存至 Eden 資料夾裡的 dump 資料夾中。可使用 envydis 進行檢查 + 追蹤 GPU 記憶體 + 監控 GPU 記憶體的分配和釋放 + 驅動程式偵錯資料 + 擷取驅動程式的特定偵錯資料(例如 Turnip breadcrumbs) + 環形緩衝區大小 + 追蹤的近期 Vulkan 呼叫數量(預設為 512) + 64 至 4096 個項目 一般 輸出引擎 音量 - 指定音訊輸出音量。 + 指定音訊輸出音量 - 按键 + 按鍵 + - HOME @@ -519,7 +688,7 @@ 靈敏度 無感帶 用按鈕、按鍵來模擬搖桿 - 修改按鈕模擬左/右搖桿的值,視遊戲不同而有所差異\n(模擬左搖桿時,若將此數值設為50,則按下按鈕時遊戲裡的人物可能只會走動而不是奔跑;模擬右搖桿時所使用的值則是會影響到轉視角的速度,數值越高代表使用的模擬值越大) + 修改按鈕模擬左/右搖桿的值,視遊戲不同而有所差異\n(模擬左搖桿時,若將此數值設為 50,則按下按鈕時遊戲裡的人物可能只會走動而不是奔跑;模擬右搖桿時所使用的值則是會影響到轉視角的速度,數值越高代表使用的模擬值越大) 肩部按鍵 左 SL 左 SR @@ -576,12 +745,13 @@ 預設 + 預設 正在載入… 正在關閉… 要將此設定重設回預設值嗎? 重設為預設值 重設所有進階設定 - 重設所有設定? + 確定要重設所有設定嗎? 所有進階設定將被重設,此動作無法復原 設定已重設 關閉 @@ -589,24 +759,24 @@ 自動 提交 無 - 導入 - 導出 - 導出失敗 - 導入失敗 + 匯入 + 匯出 + 匯出失敗 + 匯入失敗 正在取消 安裝 - 獲取 + 下載 刪除 編輯 - 已成功導入 - 已成功導出 + 已成功匯入 + 已成功匯出 開始 - 全域 - 自定義 - 導入完成 + 全域設定 + 個別遊戲設定 + 匯入完成 使用全域設定 操作已成功完成 - 確定 + 確認 載入 儲存 @@ -614,17 +784,18 @@ 預設 選擇的驅動程式無效 驅動程式已安裝 + %1$s(已安裝) 系統 GPU 驅動程式 正在安裝驅動程式… 顯示版本 - 獲取失敗 - 獲取過程中發生錯誤 + 下載失敗 + 下載過程中發生錯誤 顯示下載 隱藏下載 快取目錄不可用 - 回應內容為空 + 未回傳任何資料 %1$s 安裝成功 驅動程式安裝失敗 無法安裝 %1$s 驅動程式,您的系統支援此驅動程式嗎? @@ -633,9 +804,9 @@ 安裝中… 最新 推薦驅動程式: - GPU型號 - 不支援的GPU - 您的GPU不支援驅動程式注入。不建議嘗試設定自訂驅動程式。 + GPU 型號 + 不支援的 GPU + 您的 GPU 不支援驅動程式注入,不建議嘗試設定自訂驅動程式 設定 @@ -643,6 +814,9 @@ 底座模式、區域及語言 圖形 準確度層級、解析度及著色器快取 + 快捷設定 + 啟用快捷設定 + 允許透過滑動和選單按鈕存取快捷設定選單 音訊 輸出引擎及音量 控制 @@ -650,12 +824,34 @@ 玩家 %d 偵錯 CPU/GPU 偵錯、圖形 API 及 fastmem + 自訂路徑 + 自訂檔案的存放路徑 + + + 遊戲儲存資料路徑 + 設定遊戲儲存資料的自訂路徑 + 重設為預設值 + 遷移儲存資料 + 您要將現有的儲存資料移至新位置嗎? + 成功遷移儲存資料 + 儲存資料遷移失敗 + 存檔目錄設定完成 + 所選取的位置已有儲存資料,是否要取代該檔案? + 若要自訂路徑必須授予所有檔案的存取權 + 授予權限 + NAND 路徑 + 設定 NAND 資料的自訂路徑 + 虛擬SD卡路徑 + 設定虛擬SD卡儲存空間的自訂路徑 + 路徑設定成功 + 跳過 + - 資訊 + 軟體資訊 程式 ID、開發人員及版本資訊 個別遊戲設定 - 編輯此遊戲的特定設定 - 啟動組態 + 調整此遊戲的專用設定 + 啟動設定 路徑 程式 ID 開發商 @@ -665,7 +861,7 @@ 切換模組、更新及 DLC 遊玩時間: 重設遊玩時間 - 將此遊戲的遊玩時間重設為0 + 將此遊戲的遊玩時間重設為 0 您確定要重設此遊戲的遊玩時間嗎? 遊玩時間已重設 修改遊玩時間 @@ -674,32 +870,32 @@ 小時 分鐘 秒 - 必須介於0到9999小時之間 - 必須介於0到59分鐘之間 - 必須介於0到59秒之間 - 成功更新遊玩時間 + 必須介於 0 到 9999 小時之間 + 必須介於 0 到 59 分鐘之間 + 必須介於 0 到 59 秒之間 + 成功修改遊玩時間 清除著色器快取 清除此遊戲所有已編譯的著色器快取 重新編譯著色器快取的過程中可能會導致遊戲有些微卡頓 著色器快取已成功清除 已刪除著色器快取 - 為了維持Vulkan管線驗證,Eden已自動清除所有著色器快取。為了預防閃退和圖形錯誤,這是必須的動作。因為著色器需要重新編譯,您在遊戲過程中可能會遇到些微卡頓的情況 + 為了維持Vulkan管線驗證,Eden 已自動清除所有著色器快取,若要預防閃退和圖形錯誤,這是必須的動作。因為著色器需要重新編譯,您在遊戲過程中可能會遇到些微卡頓的情況 附加元件:%1$s 儲存資料 管理此遊戲的儲存資料 刪除儲存資料 刪除此遊戲的所有儲存資料 - 這將會刪除此遊戲的所有儲存資料,且無法復原,您確定要繼續嗎? + 這將會刪除此遊戲的所有儲存資料且無法復原,您確定要繼續嗎? 儲存資料已成功刪除 內容類型 更新及 DLC - 模組及密技 + 模組及金手指 重要的˙附加元件通知 - 若要安裝模組及密技,您必須選擇一個包含 cheats/、romfs/ 或 exefs/ 的目錄。我們無法驗證這些內容是否與您的遊戲相容,所以請謹慎安裝! + 若要安裝模組及金手指,您必須選擇一個包含 cheats/、romfs/ 或 exefs/ 的目錄。我們無法驗證這些內容是否與您的遊戲相容,所以請謹慎安裝! 無效的目錄 - 請確保您選取的目錄包含 cheats/、romfs/ 或 exefs/ 資料夾,然後再試一次。 + 請確保您選取的目錄包含 cheats/、romfs/ 或 exefs/ 資料夾,然後再試一次 附加元件已成功安裝 正在驗證內容… 內容安裝通知 @@ -707,36 +903,37 @@ 確認刪除 您確定要刪除此附加元件嗎? 完整性驗證 + 驗證中... 完整性驗證成功! 完整性驗證失敗! 文件可能已經損壞 - 無法執行完整性驗證 - 未檢查文件的完整性 + 無法正常執行完整性驗證 + 未完成文件完整性檢查 以下文件的完整性驗證失敗:\n %1$s 分享設定 - 導入設定 - 導出設定 + 匯入設定 + 匯出設定 分享設定失敗 您的 ROM 已加密 遊戲卡或數位版遊戲。]]> - prod.keys 檔案已安裝,讓遊戲可以解密。]]> + prod.keys 檔案已安裝,讓遊戲可以正常解密]]> 初始化視訊核心時發生錯誤 這經常是因為不相容的 GPU 驅動程式造成,安裝自訂 GPU 驅動程式可能會解決此問題。 ROM 檔案不存在 遊戲需要韌體 - 轉儲並安裝韌體,或點擊\"確定\"繼續。]]> + 正在搜尋遊戲... - 找不到標題ID的遊戲: %1$s + 找不到標題 ID 的遊戲: %1$s 自訂設定失敗 無法套用自訂設定 %1$s: %2$s 使用預設設定啟動 啟動已取消 - 無法套用請求的設定。這可能是由於缺少GPU驅動程式或設定問題。 + 無法套用請求的設定,這可能是由於缺少 GPU 驅動程式或設定問題 已套用自訂設定 正在啟動 %1$s... 無法初始化遊戲 @@ -749,18 +946,18 @@ 設定寫入失敗 套用設定失敗 設定已存在 - %1$s已存在自訂設定\n\n您要覆寫它嗎?\n\n這個動作無法復原 + %1$s已存在自訂設定\n\n您要取代它嗎?\n\n這個動作無法復原 正在檢查現有設定... - 覆寫已取消 - 覆寫 + 已取消取代該設定 + 取代 - 未安裝所需的驅動程式: %s - 無效的驅動程式檔案: %s - 無可用網路連線。請檢查您的網路連線並重試。 - 需要GPU驅動程式 + 未安裝所需的驅動程式: %s + 無效的驅動程式檔案: %s + 無可用網路連線,請檢查您的網路連線並重試 + 需要 GPU 驅動程式 這個遊戲的設定需要 \"%s\"驅動程式,而它並沒有安裝在您的裝置上\n\n要下載並安裝此驅動程式嗎? - 驅動程式下載已取消。沒有所需的驅動程式無法啟動遊戲。 + 驅動程式下載已取消,沒有所需的驅動程式無法啟動遊戲 下載 @@ -769,7 +966,7 @@ 切換控制 相對搖桿中心 方向鍵滑動 - 觸覺回饋技術 + 觸覺回饋 顯示虛擬按鍵 隱藏虛擬按鍵 全部切換 @@ -778,10 +975,11 @@ 不透明度 重設虛擬按鍵 編輯虛擬按鍵 + 對齊網格 暫停模擬 - 取消暫停模擬 + 繼續模擬 疊加層/虛擬按鍵設定 - 導入Amiibo + 導入 Amiibo 觸控螢幕 鎖定側邊選單 解鎖側邊選單 @@ -789,12 +987,12 @@ Amiibo - 該amiibo已被移除 - 此遊戲目前沒有在尋找amiibo - 您所選擇的並不是正確的amiibo金鑰檔 + 該 amiibo 已被移除 + 此遊戲目前沒有在尋找 amiibo + 您所選擇的並不是正確的 amiibo 金鑰檔 選擇的檔案已在使用中 發生未知的錯誤 - 已導入Amiibo + Amiibo 已匯入 @@ -804,11 +1002,11 @@ 中止 繼續 找不到系統存檔 - 缺少%s,請轉儲您的系統存檔\n繼續模擬可能會導致崩潰 + 缺少%s,請轉儲您的系統存檔\n繼續模擬可能會導致當機 系統存檔 儲存/載入時發生錯誤 嚴重錯誤 - 發生嚴重錯誤。請查看日誌以取得詳細資料\n繼續模擬可能會導致崩潰 + 發生嚴重錯誤。請查看日誌以取得詳細資料\n繼續模擬可能會導致當機 裝置記憶體大小:%1$s\n推薦記憶體大小:%2$s %1$s%2$s 目前沒有可啟動的遊戲! @@ -823,17 +1021,44 @@ 臺灣 - 4GB (推薦) - 6GB (不安全) - 8GB (不安全) + 4GB(推薦) + 6GB(不穩定) + 8GB(不穩定) - - 加速 (1700MHz) - 快速 (2000MHz) + + 正常 + 加速 + 超頻 + + + 正常 + 加速 + 超頻 + + + 極小 (16MB) + 小(32MB) + 正常(128MB) + 大(256MB) + 極大(512MB) + + + 極小(4MB) + 小(8MB) + 正常(16MB) + 中(32MB) + 大(64MB) + + + 極小(32) + 小(64) + 正常(128) + 中(256) + 大(512) - 攝氏度 - 華氏度 + 攝氏 + 華氏 B @@ -846,21 +1071,28 @@ 無 - 平衡 - 準確 + + 效能 + 品質 預設 - 不安全 - 安全 + 不穩定 + 穩定 + + + 預設 + 即時 + 平衡 + 準確 保守 積極 - 即時 (關閉) + 即時(關閉) Mailbox - FIFO (開啟) + FIFO(開啟) FIFO 寬鬆 @@ -878,10 +1110,22 @@ 高準確度 - 低準確度(不安全) + 低準確度(不穩定) 不合理 偵錯 + + Freedreno 設定 + GPU 驅動程式設定 + 預先提供的設定檔 + 目前使用的設定 + 進階設定 + 變數名稱 (例如: TU_DEBUG) + 變數值 + 新增變數 + 全部清除 + Freedreno 設定已儲存 + 已清除所有 Freedreno 變數 方向鍵 左搖桿 @@ -904,8 +1148,6 @@ 淺色 深色 - 無 - 黑色背景 使用深色主題時,套用黑色背景。 @@ -951,4 +1193,4 @@ 授權 來自 AMD 的升級圖像品質 -
+
\ No newline at end of file diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 99838b98cc..be367a2fcf 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -34,7 +34,7 @@ - @string/off + @string/clock_normal @string/clock_boost @string/clock_fast @@ -103,53 +103,46 @@ @string/renderer_accuracy_low - @string/renderer_accuracy_medium @string/renderer_accuracy_high 0 1 - 2 - @string/vram_usage_conservative @string/vram_usage_aggressive - - 0 - 1 + 0 + 1 - @string/accelerate_astc_cpu @string/accelerate_astc_gpu @string/accelerate_astc_async - - 0 - 1 - 2 + 0 + 1 + 2 - + - @string/nvdec_emulation_none - @string/nvdec_emulation_cpu - @string/nvdec_emulation_gpu + @string/nvdec_emulation_none + @string/nvdec_emulation_cpu + @string/nvdec_emulation_gpu - - 3 - 1 - 2 + 0 + 1 + 2 @@ -164,6 +157,50 @@ @string/resolution_four + + @string/frame_gen_multiplier_2x + @string/frame_gen_multiplier_3x + @string/frame_gen_multiplier_4x + + + + 2 + 3 + 4 + + + + @string/frame_gen_target_rate_off + @string/frame_gen_target_rate_60 + @string/frame_gen_target_rate_90 + @string/frame_gen_target_rate_120 + + + + 0 + 60 + 90 + 120 + + + + 50 + 75 + 100 + + + + @string/frame_gen_queue_target_0 + @string/frame_gen_queue_target_1 + @string/frame_gen_queue_target_2 + + + + 0 + 1 + 2 + + @string/renderer_vsync_immediate @string/renderer_vsync_mailbox @@ -240,6 +277,8 @@ @string/scaling_filter_bspline @string/scaling_filter_mitchell @string/scaling_filter_spline1 + @string/scaling_filter_sgsr + @string/scaling_filter_sgsr_edge @@ -256,6 +295,8 @@ 10 11 12 + 13 + 14 @@ -426,13 +467,13 @@ @string/auto - @string/oboe + @string/sdl3 @string/cubeb @string/string_null 0 - 4 + 2 1 3 @@ -469,9 +510,6 @@ @string/multiplier_x4 @string/multiplier_x8 @string/multiplier_x16 - @string/multiplier_x32 - @string/multiplier_x64 - @string/multiplier_none 0 @@ -480,9 +518,6 @@ 3 4 5 - 6 - 7 - 8 @@ -518,6 +553,20 @@ 2 + + @string/gpu_fence_behavior_default + @string/gpu_fence_behavior_immediate + @string/gpu_fence_behavior_balanced + @string/gpu_fence_behavior_accurate + + + 0 + 1 + 2 + 3 + 4 + + @string/applet_hle @@ -530,7 +579,7 @@ - @string/off + @string/fast_gpu_normal @string/fast_gpu_medium @string/fast_gpu_high @@ -621,6 +670,16 @@ @string/error_keys_failed_init + + "" + @string/error_lossless_copy_failed + @string/error_lossless_unreadable + @string/error_lossless_not_pe + @string/error_lossless_missing_shaders + @string/error_lossless_translation_failed + @string/error_lossless_cache_failed + + Off diff --git a/src/android/app/src/main/res/values/integers.xml b/src/android/app/src/main/res/values/integers.xml index cc05478c21..55e885e42e 100644 --- a/src/android/app/src/main/res/values/integers.xml +++ b/src/android/app/src/main/res/values/integers.xml @@ -5,8 +5,6 @@ 2 4 100 - 1 - 4 760 diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 7cdce5339f..33e1eaa340 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -3,7 +3,7 @@ Eden - This software will run games for the Nintendo Switch game console. No game titles or keys are included.<br /><br />Before you begin, please locate your prod.keys ]]> file on your device storage.<br /><br />Learn more]]> + This software will run games for the Nintendo Switch game console. No game titles or keys are included.<br /><br />Before you begin, please locate your prod.keys ]]> file on your device storage.<br /><br />Learn more]]> Notices and errors noticesAndErrors Shows notifications when something goes wrong. @@ -71,6 +71,8 @@ Display current power draw and remaining capacity on battery Show Shaders Building Display current number of shaders being built + Pipeline Worker Threads + Manage the amount of cores used for building Vulkan pipelines, the higher value will improve pipeline compilation performance but temperatures will increase as well. Overlay Position Choose where the overlay is displayed on the screen Top Left @@ -109,7 +111,7 @@ NVDEC Emulation - Select how video decoding (NVDEC) is handled during cutscenes and intros. + Change to CPU if a crash occurs on cinematics. CPU GPU None @@ -296,6 +298,87 @@ GPU driver fetcher GPU driver manager Install alternative drivers for potentially better performance or accuracy + Post-Processing Effects + ReShade FX effects applied after rendering + Configure the effect chain for this game + Add effect + Remove + Open list + Close list + Remove effects + Preset active. Edit it in Post-Processing Effects before starting a game. + Values changed from the original preset. + Presets + New preset + Saves the effects you have loaded, with their current values, as a preset you can pick later. + Give the preset a name without an equals sign. + No preset + Delete preset + Reset preset values + Reset to default + No effects found. Place .fx files in this folder: + Frame generation + Configure frame generation for this game + Insert interpolated frames between rendered ones using Lossless Scaling. Forces FIFO presentation while enabled. + Frame multiplier + How many frames to display for each rendered frame. Higher values cost proportionally more GPU time. Asking for more than your display can present will slow emulation down. + 2x + 3x + 4x + Target frame rate + Pick the rate your display can actually show. The multiplier then rises or falls on its own to hold it, and rolls back any step that makes the game itself run slower. + Use a fixed multiplier + 60 FPS + 90 FPS + 120 FPS + On + Off + Fixed + Auto + Turn frame generation on or off and choose the frame multiplier. + Generate frames up to a target frame rate. The multiplier adjusts on its own. + Resolution used to estimate motion between frames. Lower values save GPU time. + Frame queue target + How many finished frames may wait ahead of the display. Larger queues absorb GPU spikes at the cost of input latency. + Lowest latency (Unbuffered) + Balanced (1 frame) + Smoothest (2 frames) + Match motion estimation to the game + Estimate motion at the resolution the game actually renders instead of the upscaled output. Costs nothing in accuracy, since upscaling adds no motion detail. + Motion estimation resolution + Resolution of the optical flow pass, as a fraction of the output. Lowering it is the cheapest way to reclaim performance. + Frame generation unavailable + This GPU driver lacks the Vulkan memory model or half precision (float16) support that the Lossless Scaling shaders require. + Optional. Provide your own Lossless.dll to enable frame generation later + Install Lossless.dll + Frame generation needs your own legal copy of Lossless.dll from Lossless Scaling + Select a different copy of Lossless.dll + Frame generation + Supported + Unsupported (no Vulkan memory model or float16) + Lossless Scaling + Provide your own copy of Lossless.dll to enable frame generation + Installed + Not installed + Replace + Remove + Delete the installed Lossless.dll and its prepared shaders + Frame generation will stop working until you install Lossless.dll again. Your original file is not affected. + Lossless.dll not installed + Install it from Settings › Lossless Scaling to use frame generation. + Close the game first + Lossless.dll cannot be changed while a game is running. + Nothing to remove + Lossless.dll is not installed yet. + Preparing frame generation shaders… + Lossless.dll installed successfully + Could not install Lossless.dll + The selected file could not be copied. + The selected file could not be read. + The selected file is not a Windows library. Select Lossless.dll from your Lossless Scaling installation. + This copy of Lossless.dll does not contain the frame generation shaders. Update Lossless Scaling and try again. + The frame generation shaders could not be translated. This version of Lossless Scaling is not supported yet. + The translated shaders could not be written to storage. Check that there is free space available. Advanced settings Configure emulator settings Recently played @@ -432,6 +515,9 @@ CPU accuracy %1$s%2$s + Homebrew Args + Command-line arguments passed to homebrew at launch (e.g. -noglsl). + Device name Docked Mode @@ -445,8 +531,8 @@ Set custom RTC - CPU Overclock - Forces the emulated CPU to run at a higher clock, reducing certain FPS limiters. Use Boost (1700MHz) to run at the Switch\'s highest native clock, or Fast (2000MHz) to run at 2x clock. + CPU Clocks + Raises the clock the emulated CPU reports, which removes some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. Custom CPU Ticks Set a custom value of CPU ticks. Higher values can increase performance, but may also cause the game to freeze. A range of 77–21000 is recommended. Ticks @@ -466,17 +552,19 @@ Resolution (Handheld/Docked) VSync mode Window adapting filter - FSR sharpness - Determines how sharpened the image will look while using FSR\'s dynamic contrast + FSR/SGSR sharpness + Determines how sharpened the image will look while using FSR or SGSR filters Anti-aliasing method Advanced GPU Mode - Controls the GPU emulation mode. Most games render fine with Fast or Balanced modes, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. + Controls the GPU emulation mode. Most games render fine with Fast, but Accurate is still required for some. Particles tend to only render correctly with Accurate mode. DMA Accuracy Controls the DMA precision accuracy. Safe precision can fix issues in some games, but it can also impact performance in some cases. If unsure, leave this on Default. + GPU Fence Behavior + Controls the GPU fence synchronization behavior. Immediate is the fastest option, but can introduce some issues. Balanced offers better compatibility and may fix issues in some games. Accurate further improves compatibility at the cost of some performance. Strict is the slowest option, but can fix issues that require stricter synchronization. Default follows the GPU Accuracy setting. Anisotropic filtering Improves the quality of textures when viewed at oblique angles VRAM Usage Mode @@ -491,20 +579,18 @@ Force maximum clocks (Adreno only) Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied). GPU async emulation - Runs GPU emulation asynchronously to reduce CPU stalls and improve throughput. Disable this only if you run into timing-related issues. - Asynchronous presentation - Slightly improves performance by moving presentation to a separate CPU thread. + This hack can increase performance by running GPU emulation asynchronously at the cost of graphical issues and increased crash rates by timing-related operations. Use reactive flushing Improves rendering accuracy in some games at the cost of performance. Enable buffer history Enables access to previous buffer states. This option may improve rendering quality and performance consistency in some games. Optimized Vertex Buffers - Enables optimized vertex buffer binding for improved performance. Requires Mesa 26.0+ Turnip drivers/ QCOM drivers. Will crash on older Turnip drivers. + Enables optimized vertex buffer binding for improved performance. Requires Mesa 26.0+ Turnip drivers/ QCOM drivers. Will crash on older Turnip drivers (25.3 and below). Hacks - Fast GPU Time - Forces most games to run at their highest native resolution. Use 256 for maximal performance and 512 for maximal graphics fidelity. + GPU Clocks + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch\'s clocks. Skip CPU Inner Invalidation Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games. Fix Bloom Effects @@ -531,10 +617,10 @@ Extensions Extended Dynamic State - Controls the number of features that can be used in Extended Dynamic State. Higher numbers allow for more features and can increase performance, but may cause issues with some drivers and vendors. + Controls the number of features that can be used in ExtendedDynamicState (EDS). The higher value will allow to reduce the amount of pipeline compilations based on the dynamic state supported by driver. Disabled Vertex Input Dynamic State - Enables vertex input dynamic state feature for better quality and performance. + Enabling this feature allows for more flexible vertex input handling, potentially reducing pipeline compilation time in vertex/buffer. Sample Shading Allows the fragment shader to execute per sample in a multi-sampled fragment instead once per fragment. Improves graphics quality at the cost of some performance. @@ -551,6 +637,7 @@ CPU + Clocks Use Auto Stub Automatically stub missing services and functions. This may improve compatibility but can cause crashes and stability issues. @@ -565,17 +652,23 @@ Logging Flush debug logs by line Flushes debugging logs on each line written, making debugging easier in cases of crashing or freezing. + Enable extended logging + Increases the maximum log file size from 100 MiB to 1 GiB. + Log filter + Controls Eden\'s log categories. Example: *:Info Service.LM:Debug GPU Logging - Enable GPU Logging - Log GPU operations to eden_gpu.log for debugging Adreno drivers Log Level Detail level for GPU logs (higher = more detail, more overhead) Log Vulkan API Calls Track all Vulkan API calls in ring buffer - Dump Shaders - Save compiled shader SPIR-V to files + Dump SPIR-V Shaders + Save recompiled SPIR-V binaries (.spv) to dump folder. Inspect with spirv-dis/spirv-cross/spirv-val. + Dump Guest (Maxwell) Shaders + Save Maxwell guest shader bytecode files (*.ash) to dump folder. Inspect with nvdisasm. + Dump Maxwell Macros + Save Maxwell macro program files (*.macro) to dump folder. Inspect with envydis. Track GPU Memory Monitor GPU memory allocations and deallocations Driver Debug Info @@ -856,7 +949,7 @@ ROM file does not exist Game Requires Firmware - dump and install firmware, or press \"OK\" to launch anyways.]]> + Searching for game... @@ -957,14 +1050,15 @@ 6GB (Unsafe) 8GB (Unsafe) - - Boost (1700MHz) - Fast (2000MHz) + + Normal + Boost + Overclock - - Off - Medium (256) - High (512) + + Normal + Boost + Overclock Very Small (16 MB) @@ -1028,7 +1122,6 @@ Fast - Balanced Accurate @@ -1036,6 +1129,12 @@ Unsafe Safe + + Default + Immediate + Balanced + Accurate + CPU GPU @@ -1076,6 +1175,8 @@ B-Spline Mitchell MMPX + Snapdragon GSR + Snapdragon GSR EdgeDir None @@ -1156,7 +1257,7 @@ Dark - oboe + SDL3 cubeb @@ -1164,9 +1265,6 @@ x4 x8 x16 - x32 - x64 - None Black backgrounds diff --git a/src/android/app/src/main/res/xml/game_mode_config.xml b/src/android/app/src/main/res/xml/game_mode_config.xml index 8f6e90d0d2..69e038cb5e 100644 --- a/src/android/app/src/main/res/xml/game_mode_config.xml +++ b/src/android/app/src/main/res/xml/game_mode_config.xml @@ -1,7 +1,7 @@ \ No newline at end of file diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index e31fd2c5b9..b244d657a9 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -222,7 +222,7 @@ if (MSVC) ) else() target_compile_options(audio_core PRIVATE - $<$:-Werror=conversion> + $<$:-Wconversion> $<$:-Wno-sign-conversion>) endif() @@ -238,20 +238,11 @@ if (ENABLE_CUBEB) target_compile_definitions(audio_core PRIVATE HAVE_CUBEB=1) endif() -if(ANDROID) - target_sources(audio_core PRIVATE - sink/oboe_sink.cpp - sink/oboe_sink.h) +target_sources(audio_core PRIVATE + sink/sdl3_sink.cpp + sink/sdl3_sink.h) - target_link_libraries(audio_core PRIVATE oboe) - target_compile_definitions(audio_core PUBLIC HAVE_OBOE) -else() - target_sources(audio_core PRIVATE - sink/sdl2_sink.cpp - sink/sdl2_sink.h) - - target_link_libraries(audio_core PRIVATE SDL2::SDL2) - target_compile_definitions(audio_core PRIVATE HAVE_SDL2) -endif() +target_link_libraries(audio_core PRIVATE SDL3::SDL3) +target_compile_definitions(audio_core PRIVATE HAVE_SDL3) create_target_directory_groups(audio_core) diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h index db6b893d43..4e128187a7 100644 --- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h +++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project diff --git a/src/audio_core/adsp/apps/opus/opus_decode_object.cpp b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp index e2b9eb566c..a47a7f8527 100644 --- a/src/audio_core/adsp/apps/opus/opus_decode_object.cpp +++ b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/audio_core/adsp/apps/opus/opus_decode_object.h b/src/audio_core/adsp/apps/opus/opus_decode_object.h index 6425f987c6..feee9587ce 100644 --- a/src/audio_core/adsp/apps/opus/opus_decode_object.h +++ b/src/audio_core/adsp/apps/opus/opus_decode_object.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/audio_core/adsp/apps/opus/opus_decoder.h b/src/audio_core/adsp/apps/opus/opus_decoder.h index fcb89bb40e..5b3f22bf4c 100644 --- a/src/audio_core/adsp/apps/opus/opus_decoder.h +++ b/src/audio_core/adsp/apps/opus/opus_decoder.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp index 05cf3975db..6fecd63309 100644 --- a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp +++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -7,13 +10,12 @@ namespace AudioCore::ADSP::OpusDecoder { namespace { -bool IsValidChannelCount(u32 channel_count) { - return channel_count == 1 || channel_count == 2; -} +constexpr u32 OpusStreamCountMax = 255; bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) { - return total_stream_count > 0 && static_cast(stereo_stream_count) >= 0 && - stereo_stream_count <= total_stream_count && IsValidChannelCount(total_stream_count); + return total_stream_count > 0 && total_stream_count <= OpusStreamCountMax && + static_cast(stereo_stream_count) >= 0 && + stereo_stream_count <= total_stream_count; } } // namespace diff --git a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h index 93558ded5d..baad58385e 100644 --- a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h +++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/audio_core/adsp/apps/opus/shared_memory.h b/src/audio_core/adsp/apps/opus/shared_memory.h index c696731ed8..b15a1837cc 100644 --- a/src/audio_core/adsp/apps/opus/shared_memory.h +++ b/src/audio_core/adsp/apps/opus/shared_memory.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp index fcaab2b320..234c831ac0 100644 --- a/src/audio_core/audio_core.cpp +++ b/src/audio_core/audio_core.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -8,10 +11,11 @@ namespace AudioCore { -AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique()} { +AudioCore::AudioCore(Core::System& system) { + audio_manager.emplace(); CreateSinks(); // Must be created after the sinks - adsp = std::make_unique(system, *output_sink); + adsp.emplace(system, *output_sink); } AudioCore ::~AudioCore() { diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h index e4e27fc661..ababd780b1 100644 --- a/src/audio_core/audio_core.h +++ b/src/audio_core/audio_core.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -15,10 +18,7 @@ class System; namespace AudioCore { -class AudioManager; -/** - * Main audio class, stored inside the core, and holding the audio manager, all sinks, and the ADSP. - */ +/// @brief Main audio class, stored inside the core, and holding the audio manager, all sinks, and the ADSP. class AudioCore { public: explicit AudioCore(Core::System& system); @@ -50,27 +50,22 @@ public: */ Sink::Sink& GetInputSink(); - /** - * Get the ADSP. - * - * @return Ref to the ADSP. - */ + /// @brief Get the ADSP. + /// @return Ref to the ADSP. ADSP::ADSP& ADSP(); private: - /** - * Create the sinks on startup. - */ + /// @brief Create the sinks on startup. void CreateSinks(); /// Main audio manager for audio in/out - std::unique_ptr audio_manager; + std::optional audio_manager; /// Sink used for audio renderer and audio out std::unique_ptr output_sink; /// Sink used for audio input std::unique_ptr input_sink; /// The ADSP in the sysmodule - std::unique_ptr adsp; + std::optional adsp; }; } // namespace AudioCore diff --git a/src/audio_core/audio_in_manager.cpp b/src/audio_core/audio_in_manager.cpp index 63b064922a..6b528e9db0 100644 --- a/src/audio_core/audio_in_manager.cpp +++ b/src/audio_core/audio_in_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -41,8 +44,7 @@ void Manager::ReleaseSessionId(const size_t session_id) { Result Manager::LinkToManager() { std::scoped_lock l{mutex}; if (!linked_to_manager) { - AudioManager& manager{system.AudioCore().GetAudioManager()}; - manager.SetInManager(std::bind(&Manager::BufferReleaseAndRegister, this)); + system.AudioCore().GetAudioManager().SetInManager(std::bind(&Manager::BufferReleaseAndRegister, this)); linked_to_manager = true; } diff --git a/src/audio_core/audio_manager.cpp b/src/audio_core/audio_manager.cpp index 10b56f2140..93142a53fb 100644 --- a/src/audio_core/audio_manager.cpp +++ b/src/audio_core/audio_manager.cpp @@ -1,81 +1,77 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 #include "audio_core/audio_manager.h" +#include "common/thread.h" #include "core/core.h" #include "core/hle/service/audio/errors.h" namespace AudioCore { AudioManager::AudioManager() { - thread = std::jthread([this]() { ThreadFunc(); }); + thread = std::jthread([this](std::stop_token stop_token) { + Common::SetCurrentThreadName("AudioManager"); + std::unique_lock l{events.GetAudioEventLock()}; + events.ClearEvents(); + while (!stop_token.stop_requested()) { + const auto timed_out{events.Wait(l, std::chrono::seconds(2))}; + if (events.CheckAudioEventSet(Event::Type::Max)) { + break; + } + for (size_t i = 0; i < buffer_events.size(); i++) { + const auto event_type = Event::Type(i); + if (events.CheckAudioEventSet(event_type) || timed_out) { + if (buffer_events[i]) { + buffer_events[i](); + } + } + events.SetAudioEvent(event_type, false); + } + } + }); } void AudioManager::Shutdown() { - running = false; events.SetAudioEvent(Event::Type::Max, true); - thread.join(); + if (thread.joinable()) { + thread.request_stop(); + thread.join(); + } } Result AudioManager::SetOutManager(BufferEventFunc buffer_func) { - if (!running) { - return Service::Audio::ResultOperationFailed; + if (thread.joinable()) { + std::scoped_lock l{lock}; + const auto index{events.GetManagerIndex(Event::Type::AudioOutManager)}; + if (buffer_events[index] == nullptr) { + buffer_events[index] = std::move(buffer_func); + needs_update = true; + events.SetAudioEvent(Event::Type::AudioOutManager, true); + } + return ResultSuccess; } - - std::scoped_lock l{lock}; - - const auto index{events.GetManagerIndex(Event::Type::AudioOutManager)}; - if (buffer_events[index] == nullptr) { - buffer_events[index] = std::move(buffer_func); - needs_update = true; - events.SetAudioEvent(Event::Type::AudioOutManager, true); - } - return ResultSuccess; + return Service::Audio::ResultOperationFailed; } Result AudioManager::SetInManager(BufferEventFunc buffer_func) { - if (!running) { - return Service::Audio::ResultOperationFailed; + if (thread.joinable()) { + std::scoped_lock l{lock}; + const auto index{events.GetManagerIndex(Event::Type::AudioInManager)}; + if (buffer_events[index] == nullptr) { + buffer_events[index] = std::move(buffer_func); + needs_update = true; + events.SetAudioEvent(Event::Type::AudioInManager, true); + } + return ResultSuccess; } - - std::scoped_lock l{lock}; - - const auto index{events.GetManagerIndex(Event::Type::AudioInManager)}; - if (buffer_events[index] == nullptr) { - buffer_events[index] = std::move(buffer_func); - needs_update = true; - events.SetAudioEvent(Event::Type::AudioInManager, true); - } - return ResultSuccess; + return Service::Audio::ResultOperationFailed; } void AudioManager::SetEvent(const Event::Type type, const bool signalled) { events.SetAudioEvent(type, signalled); } -void AudioManager::ThreadFunc() { - std::unique_lock l{events.GetAudioEventLock()}; - events.ClearEvents(); - running = true; - - while (running) { - const auto timed_out{events.Wait(l, std::chrono::seconds(2))}; - - if (events.CheckAudioEventSet(Event::Type::Max)) { - break; - } - - for (size_t i = 0; i < buffer_events.size(); i++) { - const auto event_type = static_cast(i); - - if (events.CheckAudioEventSet(event_type) || timed_out) { - if (buffer_events[i]) { - buffer_events[i](); - } - } - events.SetAudioEvent(event_type, false); - } - } -} - } // namespace AudioCore diff --git a/src/audio_core/audio_manager.h b/src/audio_core/audio_manager.h index 02270242ac..0194aa16db 100644 --- a/src/audio_core/audio_manager.h +++ b/src/audio_core/audio_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -66,13 +69,6 @@ public: void SetEvent(Event::Type type, bool signalled); private: - /** - * Main thread, waiting on a manager signal and calling the registered function. - */ - void ThreadFunc(); - - /// Is the main thread running? - std::atomic running{}; /// Unused bool needs_update{}; /// Events to be set and signalled diff --git a/src/audio_core/audio_out_manager.cpp b/src/audio_core/audio_out_manager.cpp index 316ea7c817..569df8d1e0 100644 --- a/src/audio_core/audio_out_manager.cpp +++ b/src/audio_core/audio_out_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -40,8 +43,7 @@ void Manager::ReleaseSessionId(const size_t session_id) { Result Manager::LinkToManager() { std::scoped_lock l{mutex}; if (!linked_to_manager) { - AudioManager& manager{system.AudioCore().GetAudioManager()}; - manager.SetOutManager(std::bind(&Manager::BufferReleaseAndRegister, this)); + system.AudioCore().GetAudioManager().SetOutManager(std::bind(&Manager::BufferReleaseAndRegister, this)); linked_to_manager = true; } diff --git a/src/audio_core/audio_render_manager.cpp b/src/audio_core/audio_render_manager.cpp index 3c53e3afde..8c4e74c4be 100644 --- a/src/audio_core/audio_render_manager.cpp +++ b/src/audio_core/audio_render_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -9,7 +12,9 @@ namespace AudioCore::Renderer { Manager::Manager(Core::System& system_) - : system{system_}, system_manager{std::make_unique(system)} { + : system{system_} + , system_manager{std::make_unique(system)} +{ std::iota(session_ids.begin(), session_ids.end(), 0); } @@ -38,14 +43,12 @@ Result Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params, s32 Manager::GetSessionId() { std::scoped_lock l{session_lock}; - auto session_id{session_ids[session_count]}; - - if (session_id == -1) { - return -1; + ASSERT(session_count <= session_ids.size()); + auto const session_id = session_ids[session_count]; + if (session_id >= 0) { + session_ids[session_count] = -1; + session_count++; } - - session_ids[session_count] = -1; - session_count++; return session_id; } diff --git a/src/audio_core/device/device_session.cpp b/src/audio_core/device/device_session.cpp index 2a1ae1bb3f..2e9da7876f 100644 --- a/src/audio_core/device/device_session.cpp +++ b/src/audio_core/device/device_session.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -39,7 +42,7 @@ Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_for channel_count = channel_count_; session_id = session_id_; handle = handle_; - handle->Open(); + handle->Open(system.Kernel()); applet_resource_user_id = applet_resource_user_id_; if (type == Sink::StreamType::In) { @@ -60,7 +63,7 @@ void DeviceSession::Finalize() { } if (handle) { - handle->Close(); + handle->Close(system.Kernel()); handle = nullptr; } } diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp index ec1f565736..f8946cc0f6 100644 --- a/src/audio_core/in/audio_in_system.cpp +++ b/src/audio_core/in/audio_in_system.cpp @@ -14,21 +14,6 @@ #include "core/core_timing.h" #include "core/hle/kernel/k_event.h" -// See texture_cache/util.h -template -#if BOOST_VERSION >= 108100 || __GNUC__ > 12 -[[nodiscard]] boost::container::static_vector FixStaticVectorADL(const boost::container::static_vector& v) { - return v; -} -#else -[[nodiscard]] std::vector FixStaticVectorADL(const boost::container::static_vector& v) { - std::vector u; - for (auto const& e : v) - u.push_back(e); - return u; -} -#endif - namespace AudioCore::AudioIn { System::System(Core::System& system_, Kernel::KEvent* event_, const size_t session_id_) @@ -110,7 +95,7 @@ Result System::Start() { boost::container::static_vector buffers_to_flush{}; buffers.RegisterBuffers(buffers_to_flush); - session->AppendBuffers(FixStaticVectorADL(buffers_to_flush)); + session->AppendBuffers(buffers_to_flush); session->SetRingSize(static_cast(buffers_to_flush.size())); return ResultSuccess; @@ -122,7 +107,7 @@ Result System::Stop() { session->SetVolume(0.0f); session->ClearBuffers(); if (buffers.ReleaseBuffers(system.CoreTiming(), *session, true)) { - buffer_event->Signal(); + buffer_event->Signal(system.Kernel()); } state = State::Stopped; } @@ -155,7 +140,7 @@ void System::RegisterBuffers() { if (state == State::Started) { boost::container::static_vector registered_buffers{}; buffers.RegisterBuffers(registered_buffers); - session->AppendBuffers(FixStaticVectorADL(registered_buffers)); + session->AppendBuffers(registered_buffers); } } @@ -164,7 +149,7 @@ void System::ReleaseBuffers() { if (signal) { // Signal if any buffer was released, or if none are registered, we need more. - buffer_event->Signal(); + buffer_event->Signal(system.Kernel()); } } @@ -181,7 +166,7 @@ bool System::FlushAudioInBuffers() { buffers.FlushBuffers(buffers_released); if (buffers_released > 0) { - buffer_event->Signal(); + buffer_event->Signal(system.Kernel()); } return true; } diff --git a/src/audio_core/opus/decoder.cpp b/src/audio_core/opus/decoder.cpp index 7d0cce74db..1c13cb60e9 100644 --- a/src/audio_core/opus/decoder.cpp +++ b/src/audio_core/opus/decoder.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -8,6 +8,7 @@ #include "audio_core/opus/hardware_opus.h" #include "audio_core/opus/parameters.h" #include "common/alignment.h" +#include "common/scope_exit.h" #include "common/swap.h" #include "core/core.h" @@ -28,10 +29,18 @@ OpusDecoder::OpusDecoder(Core::System& system_, HardwareOpus& hardware_opus_) OpusDecoder::~OpusDecoder() { if (decode_object_initialized) { hardware_opus.ShutdownDecodeObject(shared_buffer.data(), shared_buffer.size()); + hardware_opus.UnregisterDecoder(this); } } Result OpusDecoder::Initialize(const OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) { + R_TRY(hardware_opus.RegisterDecoder(this)); + SCOPE_EXIT { + if (!decode_object_initialized) { + hardware_opus.UnregisterDecoder(this); + } + }; + auto frame_size{params.use_large_frame_size ? 5760 : 1920}; shared_buffer.resize(transfer_memory_size); shared_memory_mapped = true; @@ -61,6 +70,13 @@ Result OpusDecoder::Initialize(const OpusParametersEx& params, Kernel::KTransfer } Result OpusDecoder::Initialize(const OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) { + R_TRY(hardware_opus.RegisterDecoder(this)); + SCOPE_EXIT { + if (!decode_object_initialized) { + hardware_opus.UnregisterDecoder(this); + } + }; + auto frame_size{params.use_large_frame_size ? 5760 : 1920}; shared_buffer.resize(transfer_memory_size, 0); shared_memory_mapped = true; @@ -147,7 +163,7 @@ Result OpusDecoder::DecodeInterleavedForMultiStream(u32* out_data_size, u64* out auto* header_p{reinterpret_cast(input_data.data())}; OpusPacketHeader header{ReverseHeader(*header_p)}; - LOG_TRACE(Service_Audio, "header size {:#X} input data size 0x{:X} in_data size 0x{:X}", + LOG_TRACE(Service_Audio, "header size {:#x} input data size {:#x} in_data size {:#x}", header.size, input_data.size_bytes(), in_data.size_bytes()); R_UNLESS(in_data.size_bytes() >= header.size && diff --git a/src/audio_core/opus/hardware_opus.cpp b/src/audio_core/opus/hardware_opus.cpp index 30805f4a36..04591c10b6 100644 --- a/src/audio_core/opus/hardware_opus.cpp +++ b/src/audio_core/opus/hardware_opus.cpp @@ -1,6 +1,10 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 #include #include "audio_core/audio_core.h" @@ -42,6 +46,25 @@ HardwareOpus::HardwareOpus(Core::System& system_) opus_decoder.SetSharedMemory(shared_memory); } +Result HardwareOpus::RegisterDecoder(OpusDecoder* decoder) { + std::scoped_lock l{mutex}; + const auto slot = std::ranges::find(decoders, nullptr); + if (slot == decoders.end()) { + R_THROW(ResultOutOfOpusDecoders); + } + *slot = decoder; + R_SUCCEED(); +} + +void HardwareOpus::UnregisterDecoder(OpusDecoder* decoder) { + std::scoped_lock l{mutex}; + const auto slot = std::ranges::find(decoders, decoder); + if (slot == decoders.end()) { + return; + } + *slot = nullptr; +} + u32 HardwareOpus::GetWorkBufferSize(u32 channel) { if (!opus_decoder.IsRunning()) { return 0; @@ -105,7 +128,7 @@ Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 chan shared_memory.host_send_data[4] = total_stream_count; shared_memory.host_send_data[5] = stereo_stream_count; - ASSERT(channel_count <= MaxChannels); + ASSERT(channel_count <= shared_memory.channel_mapping.size()); std::memcpy(shared_memory.channel_mapping.data(), mappings, channel_count * sizeof(u8)); opus_decoder.Send(ADSP::Direction::DSP, diff --git a/src/audio_core/opus/hardware_opus.h b/src/audio_core/opus/hardware_opus.h index caa7468401..b6c7fbe6c8 100644 --- a/src/audio_core/opus/hardware_opus.h +++ b/src/audio_core/opus/hardware_opus.h @@ -1,8 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -12,9 +16,12 @@ #include "core/hle/service/audio/errors.h" namespace AudioCore::OpusDecoder { +class OpusDecoder; class HardwareOpus { public: HardwareOpus(Core::System& system); + Result RegisterDecoder(OpusDecoder* decoder); + void UnregisterDecoder(OpusDecoder* decoder); u32 GetWorkBufferSize(u32 channel); u32 GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count); @@ -39,6 +46,7 @@ public: private: Core::System& system; std::mutex mutex; + std::array decoders{}; ADSP::OpusDecoder::OpusDecoder& opus_decoder; ADSP::OpusDecoder::SharedMemory shared_memory; }; diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp index dcb0d0694e..46f30f19c6 100644 --- a/src/audio_core/out/audio_out_system.cpp +++ b/src/audio_core/out/audio_out_system.cpp @@ -121,7 +121,7 @@ Result System::Stop() { session->SetVolume(0.0f); session->ClearBuffers(); if (buffers.ReleaseBuffers(system.CoreTiming(), *session, true)) { - buffer_event->Signal(); + buffer_event->Signal(system.Kernel()); } state = State::Stopped; } @@ -162,7 +162,7 @@ void System::ReleaseBuffers() { bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session, false)}; if (signal) { // Signal if any buffer was released, or if none are registered, we need more. - buffer_event->Signal(); + buffer_event->Signal(system.Kernel()); } } @@ -179,7 +179,7 @@ bool System::FlushAudioOutBuffers() { buffers.FlushBuffers(buffers_released); if (buffers_released > 0) { - buffer_event->Signal(); + buffer_event->Signal(system.Kernel()); } return true; } diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp index 1da737e5fb..a88eae91ad 100644 --- a/src/audio_core/renderer/behavior/info_updater.cpp +++ b/src/audio_core/renderer/behavior/info_updater.cpp @@ -369,7 +369,7 @@ Result InfoUpdater::UpdateMixes(MixContext& mix_context, const u32 mix_buffer_co if (mix_count < 0 || mix_count > 0x100) { LOG_ERROR( Service_Audio, - "Invalid mix count from dirty parameter: count={}, magic=0x{:X}, expected_size={}", + "Invalid mix count from dirty parameter: count={}, magic={:#x}, expected_size={}", mix_count, in_dirty_params->magic, in_header->mix_size); return Service::Audio::ResultInvalidUpdateInfo; } diff --git a/src/audio_core/renderer/command/data_source/decode.cpp b/src/audio_core/renderer/command/data_source/decode.cpp index a11d73cac6..0f6f78253c 100644 --- a/src/audio_core/renderer/command/data_source/decode.cpp +++ b/src/audio_core/renderer/command/data_source/decode.cpp @@ -18,20 +18,15 @@ namespace AudioCore::Renderer { constexpr u32 TempBufferSize = 0x3F00; -constexpr std::array PitchBySrcQuality = {4, 8, 4}; -/** - * Decode PCM data. Only s16 or f32 is supported. - * - * @tparam T - Type to decode. Only s16 and f32 are supported. - * @param memory - Core memory for reading samples. - * @param out_buffer - Output mix buffer to receive the samples. - * @param req - Information for how to decode. - * @return Number of samples decoded. - */ +/// @brief Decode PCM data. Only s16 or f32 is supported. +/// @tparam T - Type to decode. Only s16 and f32 are supported. +/// @param memory - Core memory for reading samples. +/// @param out_buffer - Output mix buffer to receive the samples. +/// @param req - Information for how to decode. +/// @return Number of samples decoded. template -static u32 DecodePcm(Core::Memory::Memory& memory, std::span out_buffer, - const DecodeArg& req) { +static u32 DecodePcm(Core::Memory::Memory& memory, std::span out_buffer, const DecodeArg& req) { constexpr s32 min{(std::numeric_limits::min)()}; constexpr s32 max{(std::numeric_limits::max)()}; @@ -94,16 +89,12 @@ static u32 DecodePcm(Core::Memory::Memory& memory, std::span out_buffer, return samples_to_decode; } -/** - * Decode ADPCM data. - * - * @param memory - Core memory for reading samples. - * @param out_buffer - Output mix buffer to receive the samples. - * @param req - Information for how to decode. - * @return Number of samples decoded. - */ -static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span out_buffer, - const DecodeArg& req) { +/// @brief Decode ADPCM data. +/// @param memory - Core memory for reading samples. +/// @param out_buffer - Output mix buffer to receive the samples. +/// @param req - Information for how to decode. +/// @return Number of samples decoded. +static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span out_buffer, const DecodeArg& req) { constexpr u32 SamplesPerFrame{14}; constexpr u32 NibblesPerFrame{16}; @@ -115,8 +106,7 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span out_buffer, return 0; } - auto end{(req.end_offset % SamplesPerFrame) + - NibblesPerFrame * (req.end_offset / SamplesPerFrame)}; + auto end{(req.end_offset % SamplesPerFrame) + NibblesPerFrame * (req.end_offset / SamplesPerFrame)}; if (req.end_offset % SamplesPerFrame) { end += 3; } else { @@ -133,52 +123,49 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span out_buffer, return 0; } - auto samples_to_read{samples_to_process}; - auto samples_remaining_in_frame{start_pos % SamplesPerFrame}; - auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame + - samples_remaining_in_frame}; - + auto samples_to_read = samples_to_process; + auto samples_remaining_in_frame = start_pos % SamplesPerFrame; + auto position_in_frame = (start_pos / SamplesPerFrame) * NibblesPerFrame + samples_remaining_in_frame; if (samples_remaining_in_frame) { position_in_frame += 2; } const auto size{(std::max)((samples_to_process / 8U) * SamplesPerFrame, 8U)}; - Core::Memory::CpuGuestMemory wavebuffer( - memory, req.buffer + position_in_frame / 2, size); + Core::Memory::CpuGuestMemory wavebuffer(memory, req.buffer + position_in_frame / 2, size); - auto context{req.adpcm_context}; - auto header{context->header}; - u8 coeff_index{static_cast((header >> 4U) & 0xFU)}; - u8 scale{static_cast(header & 0xFU)}; - s32 coeff0{req.coefficients[coeff_index * 2 + 0]}; - s32 coeff1{req.coefficients[coeff_index * 2 + 1]}; + auto context = req.adpcm_context; + auto header = context->header; + u8 scale = u8(header & 0xfU); + u8 coeff_index = u8((header >> 4U) & 0x7u); + s32 coeff0 = req.coefficients[coeff_index * 2 + 0]; + s32 coeff1 = req.coefficients[coeff_index * 2 + 1]; - auto yn0{context->yn0}; - auto yn1{context->yn1}; - - static constexpr std::array Steps{ - 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1, + auto yn0 = context->yn0; + auto yn1 = context->yn1; + auto const get_step = [](u32 index) { + // Emulates the following table + // 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1, + constexpr u64 steps_table = 0x1234567876543210ull; + constexpr u64 steps_sign = 0b1111111100000000ull; + auto const r = s32((steps_table >> (index * 4)) & 0xf); + return ((steps_sign >> index) & 1) == 0 ? -r : r; }; - - const auto decode_sample = [&](const s32 code) -> s16 { + auto const decode_sample = [&](const s32 code) -> s16 { const auto xn = code * (1 << scale); const auto prediction = coeff0 * yn0 + coeff1 * yn1; const auto sample = ((xn << 11) + 0x400 + prediction) >> 11; const auto saturated = std::clamp(sample, -0x8000, 0x7FFF); yn1 = yn0; - yn0 = static_cast(saturated); - return yn0; + return yn0 = s16(saturated); }; - u32 read_index{0}; - u32 write_index{0}; - - while (samples_to_read > 0) { + u32 read_index = 0; + for (u32 write_index = 0; samples_to_read > 0 && write_index < out_buffer.size(); ) { // Are we at a new frame? if ((position_in_frame % NibblesPerFrame) == 0) { header = wavebuffer[read_index++]; - coeff_index = (header >> 4) & 0xF; - scale = header & 0xF; + scale = header & 0xFu; + coeff_index = (header >> 4) & 0x7u; coeff0 = req.coefficients[coeff_index * 2 + 0]; coeff1 = req.coefficients[coeff_index * 2 + 1]; position_in_frame += 2; @@ -187,14 +174,12 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span out_buffer, if (samples_to_read >= SamplesPerFrame) { // Can grab all samples until the next header for (u32 i = 0; i < SamplesPerFrame / 2; i++) { - auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]}; - auto code1{Steps[wavebuffer[read_index] & 0xF]}; - read_index++; - + auto code0 = get_step((wavebuffer[read_index + i] >> 4) & 0xF); + auto code1 = get_step(wavebuffer[read_index + i] & 0xF); out_buffer[write_index++] = decode_sample(code0); out_buffer[write_index++] = decode_sample(code1); } - + read_index += SamplesPerFrame / 2; position_in_frame += SamplesPerFrame; samples_to_read -= SamplesPerFrame; continue; @@ -202,15 +187,14 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span out_buffer, } // Decode a single sample - auto code{wavebuffer[read_index]}; + auto code = wavebuffer[read_index]; if (position_in_frame & 1) { code &= 0xF; read_index++; } else { code >>= 4; } - - out_buffer[write_index++] = decode_sample(Steps[code]); + out_buffer[write_index++] = decode_sample(get_step(code)); position_in_frame++; samples_to_read--; @@ -219,27 +203,21 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span out_buffer, context->header = header; context->yn0 = yn0; context->yn1 = yn1; - return samples_to_process; } -/** - * Decode implementation. - * Decode wavebuffers according to the given args. - * - * @param memory - Core memory to read data from. - * @param args - The wavebuffer data, and information for how to decode it. - */ +/// @brief Decode implementation. +/// Decode wavebuffers according to the given args. +/// +/// @param memory - Core memory to read data from. +/// @param args - The wavebuffer data, and information for how to decode it. void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) { - static constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index, - auto& played_samples, auto& consumed) -> void { + constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index, auto& played_samples, auto& consumed) -> void { voice_state.wave_buffer_valid[index] = false; voice_state.loop_count = 0; - if (wavebuffer.stream_ended) { played_samples = 0; } - index = (index + 1) % MaxWaveBuffers; consumed++; }; @@ -255,8 +233,9 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf return; } - auto pitch{PitchBySrcQuality[static_cast(args.src_quality)]}; - if (static_cast(pitch + size_required.to_int_floor()) > TempBufferSize) { + // 0 -> 4, 1 -> 8, 2 -> 4 + auto pitch = u32((0x040804ul >> (u32(args.src_quality) * 8)) & 0xfful); + if (u32(pitch + size_required.to_int_floor()) > TempBufferSize) { return; } @@ -272,7 +251,7 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf bool is_buffer_starved{false}; u32 offset{voice_state.offset}; - auto output_buffer{args.output}; + auto output_buffer = args.output; std::array temp_buffer{}; while (remaining_sample_count > 0) { @@ -294,8 +273,8 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf if (wavebuffer_index >= MaxWaveBuffers) { LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index); wavebuffer_index = 0; - voice_state.wave_buffer_valid.fill(false); wavebuffers_consumed = MaxWaveBuffers; + voice_state.wave_buffer_valid.fill(false); } if (!voice_state.wave_buffer_valid[wavebuffer_index]) { @@ -303,12 +282,9 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf break; } - auto& wavebuffer{args.wave_buffers[wavebuffer_index]}; - - if (offset == 0 && args.sample_format == SampleFormat::Adpcm && - wavebuffer.context != 0) { - memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context, - wavebuffer.context_size); + auto& wavebuffer = args.wave_buffers[wavebuffer_index]; + if (offset == 0 && args.sample_format == SampleFormat::Adpcm && wavebuffer.context != 0) { + memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context, wavebuffer.context_size); } auto start_offset{wavebuffer.start_offset}; @@ -351,9 +327,7 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf case SampleFormat::Adpcm: { decode_arg.adpcm_context = &voice_state.adpcm_context; memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size); - samples_decoded = DecodeAdpcm( - memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, - decode_arg); + samples_decoded = DecodeAdpcm( memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, decode_arg); } break; default: diff --git a/src/audio_core/renderer/command/resample/resample.cpp b/src/audio_core/renderer/command/resample/resample.cpp index 51f4ba39eb..c20b5aa4a8 100644 --- a/src/audio_core/renderer/command/resample/resample.cpp +++ b/src/audio_core/renderer/command/resample/resample.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -5,21 +8,13 @@ namespace AudioCore::Renderer { -static void ResampleLowQuality(std::span output, std::span input, - const Common::FixedPoint<49, 15>& sample_rate_ratio, - Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) { - if (sample_rate_ratio == 1.0f) { - for (u32 i = 0; i < samples_to_write; i++) { - output[i] = input[i]; - } - } else { - u32 read_index{0}; - for (u32 i = 0; i < samples_to_write; i++) { - output[i] = input[read_index + (fraction >= 0.5f)]; - fraction += sample_rate_ratio; - read_index += static_cast(fraction.to_int_floor()); - fraction.clear_int(); - } +static void ResampleLowQuality(std::span output, std::span input, const Common::FixedPoint<49, 15>& sample_rate_ratio, Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) { + u32 read_index{0}; + for (u32 i = 0; i < samples_to_write; i++) { + output[i] = input[read_index + (fraction >= 0.5f)]; + fraction += sample_rate_ratio; + read_index += u32(fraction.to_int_floor()); + fraction.clear_int(); } } @@ -295,7 +290,7 @@ static void ResampleNormalQuality(std::span output, std::span in auto lut{get_lut()}; u32 read_index{0}; for (u32 i = 0; i < samples_to_write; i++) { - const auto lut_index{(fraction.get_frac() >> 8) * 4}; + const auto lut_index = ((fraction.get_frac() >> 8) << 2) & 511; const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]}; const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]}; const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]}; @@ -845,7 +840,7 @@ static void ResampleHighQuality(std::span output, std::span inpu auto lut{get_lut()}; u32 read_index{0}; for (u32 i = 0; i < samples_to_write; i++) { - const auto lut_index{(fraction.get_frac() >> 8) * 8}; + const auto lut_index = ((fraction.get_frac() >> 8) << 3) & 1023; const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]}; const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]}; const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]}; diff --git a/src/audio_core/renderer/memory/pool_mapper.cpp b/src/audio_core/renderer/memory/pool_mapper.cpp index e47eb66d51..fcd923d35a 100644 --- a/src/audio_core/renderer/memory/pool_mapper.cpp +++ b/src/audio_core/renderer/memory/pool_mapper.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -181,8 +184,9 @@ MemoryPoolInfo::ResultState PoolMapper::Update(MemoryPoolInfo& pool, return MemoryPoolInfo::ResultState::Success; } - if (in_params.address == 0 || in_params.size == 0 || !Common::Is4KBAligned(in_params.address) || - !Common::Is4KBAligned(in_params.size)) { + if (in_params.address == 0 || in_params.size == 0 + || !Common::IsAligned(in_params.address, Core::Memory::YUZU_PAGESIZE) + || !Common::IsAligned(in_params.size, Core::Memory::YUZU_PAGESIZE)) { return MemoryPoolInfo::ResultState::BadParam; } diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp index 2053ec6dd4..b7675ff5de 100644 --- a/src/audio_core/renderer/system.cpp +++ b/src/audio_core/renderer/system.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project @@ -540,7 +540,7 @@ Result System::Update(std::span input, std::span performance, std: return result; } - adsp_rendered_event->Clear(); + adsp_rendered_event->Clear(core.Kernel()); num_times_updated++; const auto end_time{core.CoreTiming().GetGlobalTimeNs().count()}; @@ -624,7 +624,7 @@ void System::SendCommandToDsp() { reset_command_buffers = false; command_buffer_size = command_size; if (remaining_command_count == 0) { - adsp_rendered_event->Signal(); + adsp_rendered_event->Signal(core.Kernel()); } } else { audio_renderer.ClearRemainCommandCount(session_id); diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp index ca42810345..c8c4cb2253 100644 --- a/src/audio_core/sink/cubeb_sink.cpp +++ b/src/audio_core/sink/cubeb_sink.cpp @@ -261,6 +261,7 @@ SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channe system_channels = system_channels_; SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique( ctx, device_channels, system_channels, output_device, input_device, name, type, system)); + stream->SetDeviceVolume(device_volume); return stream.get(); } @@ -280,14 +281,11 @@ void CubebSink::CloseStreams() { } f32 CubebSink::GetDeviceVolume() const { - if (sink_streams.empty()) { - return 1.0f; - } - - return sink_streams[0]->GetDeviceVolume(); + return device_volume; } void CubebSink::SetDeviceVolume(f32 volume) { + device_volume = volume; for (auto& stream : sink_streams) { stream->SetDeviceVolume(volume); } diff --git a/src/audio_core/sink/null_sink.h b/src/audio_core/sink/null_sink.h index b6b43c93e1..556456c206 100644 --- a/src/audio_core/sink/null_sink.h +++ b/src/audio_core/sink/null_sink.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -38,6 +41,7 @@ public: StreamType type) override { if (null_sink == nullptr) { null_sink = std::make_unique(system, type); + null_sink->SetDeviceVolume(device_volume); } return null_sink.get(); } @@ -45,9 +49,14 @@ public: void CloseStream(SinkStream*) override {} void CloseStreams() override {} f32 GetDeviceVolume() const override { - return 1.0f; + return device_volume; + } + void SetDeviceVolume(f32 volume) override { + device_volume = volume; + if (null_sink != nullptr) { + null_sink->SetDeviceVolume(volume); + } } - void SetDeviceVolume(f32 volume) override {} void SetSystemVolume(f32 volume) override {} private: diff --git a/src/audio_core/sink/oboe_sink.cpp b/src/audio_core/sink/oboe_sink.cpp deleted file mode 100644 index d1ead7d880..0000000000 --- a/src/audio_core/sink/oboe_sink.cpp +++ /dev/null @@ -1,230 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 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 -#include - -#include - -#include "audio_core/common/common.h" -#include "audio_core/sink/oboe_sink.h" -#include "audio_core/sink/sink_stream.h" -#include "common/logging.h" -#include "common/scope_exit.h" -#include "core/core.h" - -namespace AudioCore::Sink { - -class OboeSinkStream final : public SinkStream, - public oboe::AudioStreamDataCallback, - public oboe::AudioStreamErrorCallback { -public: - explicit OboeSinkStream(Core::System& system_, StreamType type_, const std::string& name_, - u32 system_channels_) - : SinkStream(system_, type_) { - name = name_; - system_channels = system_channels_; - - this->OpenStream(); - } - - ~OboeSinkStream() override { - LOG_INFO(Audio_Sink, "Destroyed Oboe stream"); - } - - void Finalize() override { - this->Stop(); - m_stream.reset(); - } - - void Start(bool resume = false) override { - if (!m_stream || !paused) { - return; - } - - paused = false; - - if (m_stream->start() != oboe::Result::OK) { - LOG_CRITICAL(Audio_Sink, "Error starting Oboe stream"); - } - } - - void Stop() override { - if (!m_stream || paused) { - return; - } - - this->SignalPause(); - - if (m_stream->stop() != oboe::Result::OK) { - LOG_CRITICAL(Audio_Sink, "Error stopping Oboe stream"); - } - } - -public: - static s32 QueryChannelCount(oboe::Direction direction) { - std::shared_ptr temp_stream; - oboe::AudioStreamBuilder builder; - - const auto result = ConfigureBuilder(builder, direction)->openStream(temp_stream); - if (result == oboe::Result::OK) { - return temp_stream->getChannelCount() >= 6 ? 6 : 2; - } - - LOG_ERROR(Audio_Sink, "Failed to open {} stream. Using default channel count 2", - direction == oboe::Direction::Output ? "output" : "input"); - return 2; - } - -protected: - oboe::DataCallbackResult onAudioReady(oboe::AudioStream*, void* audio_data, - s32 num_buffer_frames) override { - const size_t num_channels = this->GetDeviceChannels(); - const size_t frame_size = num_channels; - const size_t num_frames = static_cast(num_buffer_frames); - - if (type == StreamType::In) { - std::span input_buffer{reinterpret_cast(audio_data), - num_frames * frame_size}; - this->ProcessAudioIn(input_buffer, num_frames); - } else { - std::span output_buffer{reinterpret_cast(audio_data), - num_frames * frame_size}; - this->ProcessAudioOutAndRender(output_buffer, num_frames); - } - - return oboe::DataCallbackResult::Continue; - } - - void onErrorAfterClose(oboe::AudioStream*, oboe::Result) override { - LOG_INFO(Audio_Sink, "Audio stream closed, reinitializing"); - - if (this->OpenStream()) { - m_stream->start(); - } - } - -private: - static oboe::AudioStreamBuilder* ConfigureBuilder(oboe::AudioStreamBuilder& builder, - oboe::Direction direction) { - // TODO: investigate callback delay issues when using AAudio - return builder.setPerformanceMode(oboe::PerformanceMode::LowLatency) - ->setAudioApi(oboe::AudioApi::OpenSLES) - ->setDirection(direction) - ->setSampleRate(TargetSampleRate) - ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High) - ->setFormat(oboe::AudioFormat::I16) - ->setFormatConversionAllowed(true) - ->setUsage(oboe::Usage::Game) - ->setBufferCapacityInFrames(TargetSampleCount * 2); - } - - bool OpenStream() { - const auto direction = [&]() { - switch (type) { - case StreamType::In: - return oboe::Direction::Input; - case StreamType::Out: - case StreamType::Render: - return oboe::Direction::Output; - default: - ASSERT(false); - return oboe::Direction::Output; - } - }(); - - const auto expected_channels = QueryChannelCount(direction); - const auto expected_mask = [&]() { - switch (expected_channels) { - case 1: - return oboe::ChannelMask::Mono; - case 2: - return oboe::ChannelMask::Stereo; - case 6: - return oboe::ChannelMask::CM5Point1; - default: - ASSERT(false); - return oboe::ChannelMask::Unspecified; - } - }(); - - oboe::AudioStreamBuilder builder; - const auto result = ConfigureBuilder(builder, direction) - ->setChannelCount(expected_channels) - ->setChannelMask(expected_mask) - ->setChannelConversionAllowed(true) - ->setDataCallback(this) - ->setErrorCallback(this) - ->openStream(m_stream); - ASSERT(result == oboe::Result::OK); - return result == oboe::Result::OK && this->SetStreamProperties(); - } - - bool SetStreamProperties() { - ASSERT(m_stream); - - m_stream->setBufferSizeInFrames(TargetSampleCount * 2); - device_channels = m_stream->getChannelCount(); - - const auto sample_rate = m_stream->getSampleRate(); - const auto buffer_capacity = m_stream->getBufferCapacityInFrames(); - const auto stream_backend = - m_stream->getAudioApi() == oboe::AudioApi::AAudio ? "AAudio" : "OpenSLES"; - - LOG_INFO(Audio_Sink, "Opened Oboe {} stream with {} channels sample rate {} capacity {}", - stream_backend, device_channels, sample_rate, buffer_capacity); - - return true; - } - - std::shared_ptr m_stream{}; -}; - -OboeSink::OboeSink() { - // TODO: This is not generally knowable - // The channel count is distinct based on direction and can change - device_channels = OboeSinkStream::QueryChannelCount(oboe::Direction::Output); -} - -OboeSink::~OboeSink() = default; - -SinkStream* OboeSink::AcquireSinkStream(Core::System& system, u32 system_channels, - const std::string& name, StreamType type) { - SinkStreamPtr& stream = sink_streams.emplace_back( - std::make_unique(system, type, name, system_channels)); - - return stream.get(); -} - -void OboeSink::CloseStream(SinkStream* to_remove) { - sink_streams.remove_if([&](auto& stream) { return stream.get() == to_remove; }); -} - -void OboeSink::CloseStreams() { - sink_streams.clear(); -} - -f32 OboeSink::GetDeviceVolume() const { - if (sink_streams.empty()) { - return 1.0f; - } - - return sink_streams.front()->GetDeviceVolume(); -} - -void OboeSink::SetDeviceVolume(f32 volume) { - for (auto& stream : sink_streams) { - stream->SetDeviceVolume(volume); - } -} - -void OboeSink::SetSystemVolume(f32 volume) { - for (auto& stream : sink_streams) { - stream->SetSystemVolume(volume); - } -} - -} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/oboe_sink.h b/src/audio_core/sink/oboe_sink.h deleted file mode 100644 index 8f6f54ab53..0000000000 --- a/src/audio_core/sink/oboe_sink.h +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#include "audio_core/sink/sink.h" - -namespace Core { -class System; -} - -namespace AudioCore::Sink { -class SinkStream; - -class OboeSink final : public Sink { -public: - explicit OboeSink(); - ~OboeSink() override; - - /** - * Create a new sink stream. - * - * @param system - Core system. - * @param system_channels - Number of channels the audio system expects. - * May differ from the device's channel count. - * @param name - Name of this stream. - * @param type - Type of this stream, render/in/out. - * - * @return A pointer to the created SinkStream - */ - SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, - const std::string& name, StreamType type) override; - - /** - * Close a given stream. - * - * @param stream - The stream to close. - */ - void CloseStream(SinkStream* stream) override; - - /** - * Close all streams. - */ - void CloseStreams() override; - - /** - * Get the device volume. Set from calls to the IAudioDevice service. - * - * @return Volume of the device. - */ - f32 GetDeviceVolume() const override; - - /** - * Set the device volume. Set from calls to the IAudioDevice service. - * - * @param volume - New volume of the device. - */ - void SetDeviceVolume(f32 volume) override; - - /** - * Set the system volume. Comes from the audio system using this stream. - * - * @param volume - New volume of the system. - */ - void SetSystemVolume(f32 volume) override; - -private: - /// List of streams managed by this sink - std::list sink_streams{}; -}; - -} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl3_sink.cpp similarity index 53% rename from src/audio_core/sink/sdl2_sink.cpp rename to src/audio_core/sink/sdl3_sink.cpp index b016bf2222..02eee9bec2 100644 --- a/src/audio_core/sink/sdl2_sink.cpp +++ b/src/audio_core/sink/sdl3_sink.cpp @@ -7,16 +7,65 @@ #include #include -#include +#include #include "audio_core/common/common.h" -#include "audio_core/sink/sdl2_sink.h" +#include "audio_core/sink/sdl3_sink.h" #include "audio_core/sink/sink_stream.h" #include "common/logging.h" #include "common/scope_exit.h" #include "core/core.h" namespace AudioCore::Sink { + +namespace { + +[[nodiscard]] bool InitializeAudio() { + if (!SDL_WasInit(SDL_INIT_AUDIO)) { + // See https://github.com/PCSX2/pcsx2/pull/12312 + // "SDL and cubeb backends previously resulted in different names for the output which + // caused them be identified as different applications by the OS." + // + // Keep in sync with cubeb_sink.cpp name. + SDL_SetHint("SDL_AUDIO_DEVICE_APP_NAME", "yuzu Latency Getter"); +#ifdef __ANDROID__ + SDL_SetHintWithPriority(SDL_HINT_AUDIO_DRIVER, "openslES", SDL_HINT_OVERRIDE); + if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { + LOG_WARNING(Audio_Sink, "OpenSL ES audio initialization failed: {}; retrying default drivers", SDL_GetError()); + SDL_ResetHint(SDL_HINT_AUDIO_DRIVER); + } +#endif + if (!SDL_WasInit(SDL_INIT_AUDIO) && !SDL_InitSubSystem(SDL_INIT_AUDIO)) { + LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); + return false; + } + LOG_INFO(Audio_Sink, "SDL audio driver: {}", SDL_GetCurrentAudioDriver()); + } + return true; +} + +SDL_AudioDeviceID FindAudioDeviceByName(const std::string& device_name, bool capture) { + int device_count = 0; + SDL_AudioDeviceID* devices = capture ? SDL_GetAudioRecordingDevices(&device_count) + : SDL_GetAudioPlaybackDevices(&device_count); + if (devices == nullptr) { + return capture ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING : SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + } + + SDL_AudioDeviceID selected = capture ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING + : SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; + for (int i = 0; i < device_count; ++i) { + const char* current_name = SDL_GetAudioDeviceName(devices[i]); + if (current_name != nullptr && device_name == current_name) { + selected = devices[i]; + break; + } + } + SDL_free(devices); + return selected; +} +} // Anonymous namespace + /** * SDL sink stream, responsible for sinking samples to hardware. */ @@ -39,13 +88,10 @@ public: system_channels = system_channels_; device_channels = device_channels_; - SDL_AudioSpec spec; + SDL_AudioSpec spec{}; spec.freq = TargetSampleRate; spec.channels = static_cast(device_channels); - spec.format = AUDIO_S16SYS; - spec.samples = TargetSampleCount * 2; - spec.callback = &SDLSinkStream::DataCallback; - spec.userdata = this; + spec.format = SDL_AUDIO_S16; std::string device_name{output_device}; bool capture{false}; @@ -54,22 +100,28 @@ public: capture = true; } - SDL_AudioSpec obtained; - if (device_name.empty()) { - device = SDL_OpenAudioDevice(nullptr, capture, &spec, &obtained, false); - } else { - device = SDL_OpenAudioDevice(device_name.c_str(), capture, &spec, &obtained, false); - } + const SDL_AudioDeviceID audio_device = + device_name.empty() ? (capture ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING + : SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) + : FindAudioDeviceByName(device_name, capture); - if (device == 0) { + stream = SDL_OpenAudioDeviceStream(audio_device, &spec, &SDLSinkStream::DataCallback, + this); + + if (stream == nullptr) { LOG_CRITICAL(Audio_Sink, "Error opening SDL audio device: {}", SDL_GetError()); return; } + SDL_AudioSpec stream_in{}; + SDL_AudioSpec stream_out{}; + static_cast(SDL_GetAudioStreamFormat(stream, &stream_in, &stream_out)); + LOG_INFO(Service_Audio, "Opening SDL stream {} with: rate {} channels {} (system channels {}) " - " samples {}", - device, obtained.freq, obtained.channels, system_channels, obtained.samples); + " format {}", + static_cast(stream), stream_out.freq, stream_out.channels, + system_channels, static_cast(stream_out.format)); } /** @@ -84,13 +136,14 @@ public: * Finalize the sink stream. */ void Finalize() override { - if (device == 0) { + if (stream == nullptr) { return; } Stop(); - SDL_ClearQueuedAudio(device); - SDL_CloseAudioDevice(device); + SDL_ClearAudioStream(stream); + SDL_DestroyAudioStream(stream); + stream = nullptr; } /** @@ -100,23 +153,23 @@ public: * Default false. */ void Start(bool resume = false) override { - if (device == 0 || !paused) { + if (stream == nullptr || !paused) { return; } paused = false; - SDL_PauseAudioDevice(device, 0); + static_cast(SDL_ResumeAudioStreamDevice(stream)); } /** * Stop the sink stream. */ void Stop() override { - if (device == 0 || paused) { + if (stream == nullptr || paused) { return; } SignalPause(); - SDL_PauseAudioDevice(device, 1); + static_cast(SDL_PauseAudioStreamDevice(stream)); } private: @@ -128,7 +181,8 @@ private: * @param stream - Buffer of samples to be filled or read. * @param len - Length of the stream in bytes. */ - static void DataCallback(void* userdata, Uint8* stream, int len) { + static void DataCallback(void* userdata, SDL_AudioStream* stream, int additional_amount, + int total_amount) { auto* impl = static_cast(userdata); if (!impl) { @@ -137,37 +191,52 @@ private: const std::size_t num_channels = impl->GetDeviceChannels(); const std::size_t frame_size = num_channels; - const std::size_t num_frames{len / num_channels / sizeof(s16)}; if (impl->type == StreamType::In) { - std::span input_buffer{reinterpret_cast(stream), - num_frames * frame_size}; + const int bytes_available = SDL_GetAudioStreamAvailable(stream); + if (bytes_available <= 0) { + return; + } + + std::vector input(bytes_available / static_cast(sizeof(s16))); + const int bytes_read = SDL_GetAudioStreamData(stream, input.data(), bytes_available); + if (bytes_read <= 0) { + return; + } + + const std::size_t num_frames = + static_cast(bytes_read) / sizeof(s16) / frame_size; + std::span input_buffer{input.data(), + static_cast(bytes_read) / sizeof(s16)}; impl->ProcessAudioIn(input_buffer, num_frames); } else { - std::span output_buffer{reinterpret_cast(stream), num_frames * frame_size}; + if (additional_amount <= 0 && total_amount <= 0) { + return; + } + + const int bytes_requested = additional_amount > 0 ? additional_amount : total_amount; + std::vector output(bytes_requested / static_cast(sizeof(s16))); + const std::size_t num_frames = + static_cast(bytes_requested) / sizeof(s16) / frame_size; + std::span output_buffer{output.data(), output.size()}; impl->ProcessAudioOutAndRender(output_buffer, num_frames); + static_cast(SDL_PutAudioStreamData(stream, output.data(), bytes_requested)); } } - /// SDL device id of the opened input/output device - SDL_AudioDeviceID device{}; + /// SDL stream attached to an opened input/output device + SDL_AudioStream* stream{}; }; SDLSink::SDLSink(std::string_view target_device_name) { - if (!SDL_WasInit(SDL_INIT_AUDIO)) { - if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { - LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); - return; + if (InitializeAudio()) { + if (target_device_name != auto_device_name && !target_device_name.empty()) { + output_device = target_device_name; + } else { + output_device.clear(); } + device_channels = 2; } - - if (target_device_name != auto_device_name && !target_device_name.empty()) { - output_device = target_device_name; - } else { - output_device.clear(); - } - - device_channels = 2; } SDLSink::~SDLSink() = default; @@ -177,6 +246,7 @@ SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels system_channels = system_channels_; SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique( device_channels, system_channels, output_device, input_device, type, system)); + stream->SetDeviceVolume(device_volume); return stream.get(); } @@ -195,14 +265,11 @@ void SDLSink::CloseStreams() { } f32 SDLSink::GetDeviceVolume() const { - if (sink_streams.empty()) { - return 1.0f; - } - - return sink_streams[0]->GetDeviceVolume(); + return device_volume; } void SDLSink::SetDeviceVolume(f32 volume) { + device_volume = volume; for (auto& stream : sink_streams) { stream->SetDeviceVolume(volume); } @@ -215,21 +282,24 @@ void SDLSink::SetSystemVolume(f32 volume) { } std::vector ListSDLSinkDevices(bool capture) { - std::vector device_list; + if (!InitializeAudio()) + return {}; //no devices - if (!SDL_WasInit(SDL_INIT_AUDIO)) { - if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { - LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); - return {}; - } + std::vector device_list; + int device_count = 0; + SDL_AudioDeviceID* devices = + capture ? SDL_GetAudioRecordingDevices(&device_count) + : SDL_GetAudioPlaybackDevices(&device_count); + if (devices == nullptr) { + return device_list; } - const int device_count = SDL_GetNumAudioDevices(capture); for (int i = 0; i < device_count; ++i) { - if (const char* name = SDL_GetAudioDeviceName(i, capture)) { + if (const char* name = SDL_GetAudioDeviceName(devices[i])) { device_list.emplace_back(name); } } + SDL_free(devices); return device_list; } @@ -242,17 +312,12 @@ u32 GetSDLLatency() { // REVERTED back to 3833 - Below function IsSDLSuitable() removed, reverting to GetSDLLatency() above. - DIABLO 3 FIX /* bool IsSDLSuitable() { -#if !defined(HAVE_SDL2) +#if !defined(HAVE_SDL3) return false; #else // Check SDL can init - if (!SDL_WasInit(SDL_INIT_AUDIO)) { - if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { - LOG_ERROR(Audio_Sink, "SDL failed to init, it is not suitable. Error: {}", - SDL_GetError()); - return false; - } - } + if (!InitializeAudio()! + return false; // We can set any latency frequency we want with SDL, so no need to check that. diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl3_sink.h similarity index 97% rename from src/audio_core/sink/sdl2_sink.h rename to src/audio_core/sink/sdl3_sink.h index 19ea32595f..d0f4586f53 100644 --- a/src/audio_core/sink/sdl2_sink.h +++ b/src/audio_core/sink/sdl3_sink.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h index e22e8c3e56..2b7069e1a3 100644 --- a/src/audio_core/sink/sink.h +++ b/src/audio_core/sink/sink.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -96,6 +99,8 @@ public: } protected: + /// Master volume, persists stream lifetimes + f32 device_volume{1.0f}; /// Number of device channels supported by the hardware u32 device_channels{2}; /// Number of channels the game is sending diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp index 4420560d22..efd8fceb32 100644 --- a/src/audio_core/sink/sink_details.cpp +++ b/src/audio_core/sink/sink_details.cpp @@ -10,14 +10,11 @@ #include #include "audio_core/sink/sink_details.h" -#ifdef HAVE_OBOE -#include "audio_core/sink/oboe_sink.h" -#endif #ifdef HAVE_CUBEB #include "audio_core/sink/cubeb_sink.h" #endif -#ifdef HAVE_SDL2 -#include "audio_core/sink/sdl2_sink.h" +#ifdef HAVE_SDL3 +#include "audio_core/sink/sdl3_sink.h" #endif #include "audio_core/sink/null_sink.h" #include "common/logging.h" @@ -43,24 +40,8 @@ struct SinkDetails { /// SuitableFn is_suitable; // REVERTED FOR LatencyFn latency ABOVE - DIABLO 3 FIX }; -// NOTE TO PROBABLY FIX LATER FOR ANDROID - the return value "0u" for the first HAVE_OBOE -// section below was just copied from the null section so there's a somewhat valid value -// being returned, since the previous "true" value probably isn't compatible with the -// previous EA-3833 code. (HAVE_OBOE was introduced in a later release.) Eventually need -// to change "0u" for something else directly from the oboe_sink.cpp functions. - // sink_details is ordered in terms of desirability, with the best choice at the top. constexpr SinkDetails sink_details[] = { -#ifdef HAVE_OBOE - SinkDetails{ - Settings::AudioEngine::Oboe, - [](std::string_view device_id) -> std::unique_ptr { - return std::make_unique(); - }, - [](bool capture) { return std::vector{"Default"}; }, - []() { return 0u; }, - }, -#endif #ifdef HAVE_CUBEB SinkDetails{ Settings::AudioEngine::Cubeb, @@ -71,9 +52,9 @@ constexpr SinkDetails sink_details[] = { &GetCubebLatency, }, #endif -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 SinkDetails{ - Settings::AudioEngine::Sdl2, + Settings::AudioEngine::Sdl3, [](std::string_view device_id) -> std::unique_ptr { return std::make_unique(device_id); }, @@ -115,10 +96,10 @@ const SinkDetails& GetOutputSinkDetails(Settings::AudioEngine sink_id) { // BEGIN REINTRODUCED FROM 3833 - REPLACED CODE BLOCK ABOVE - DIABLO 3 FIX // Auto-select a backend. Prefer CubeB, but it may report a large minimum latency which // causes audio issues, in that case go with SDL. -#if defined(HAVE_CUBEB) && defined(HAVE_SDL2) +#if defined(HAVE_CUBEB) && defined(HAVE_SDL3) iter = find_backend(Settings::AudioEngine::Cubeb); if (iter->latency() > TargetSampleCount * 3) { - iter = find_backend(Settings::AudioEngine::Sdl2); + iter = find_backend(Settings::AudioEngine::Sdl3); } #else iter = std::begin(sink_details); diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp index 2c41e7f2c8..c70020e9da 100644 --- a/src/audio_core/sink/sink_stream.cpp +++ b/src/audio_core/sink/sink_stream.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -155,7 +155,7 @@ void SinkStream::ProcessAudioIn(std::span input_buffer, std::size_t n std::memcpy(&last_frame[0], &input_buffer[(frames_written - 1) * frame_size], frame_size_bytes); } - // update sample counts für audio-ins + // update sample counts fuer audio-ins { std::scoped_lock lk{sample_count_lock}; last_sample_count_update_time = system.CoreTiming().GetGlobalTimeNs(); @@ -177,10 +177,8 @@ void SinkStream::ProcessAudioOutAndRender(std::span output_buffer, std::siz queued_buffers.store(0); release_cv.notify_one(); } - - static constexpr std::array silence{}; for (size_t i = 0; i < num_frames; i++) - std::memcpy(&output_buffer[i * frame_size], silence.data(), frame_size_bytes); + std::memset(&output_buffer[i * frame_size], 0, frame_size_bytes); return; } diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index fb2bdda146..5d1b30a549 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -40,8 +40,6 @@ add_library( container_hash.h demangle.cpp demangle.h - detached_tasks.cpp - detached_tasks.h device_power_state.cpp device_power_state.h div_ceil.h @@ -50,7 +48,6 @@ add_library( elf.h error.cpp error.h - expected.h fiber.cpp fiber.h fixed_point.h @@ -79,7 +76,6 @@ add_library( logging.h lz4_compression.cpp lz4_compression.h - make_unique_for_overwrite.h math_util.h memory_detect.cpp memory_detect.h @@ -92,7 +88,6 @@ add_library( param_package.h parent_of_member.h point.h - quaternion.h range_map.h range_mutex.h range_sets.h @@ -112,7 +107,8 @@ add_library( settings_setting.h slot_vector.h socket_types.h - spin_lock.h + sparse_large_vector.cpp + sparse_large_vector.h stb.cpp stb.h steady_clock.cpp @@ -124,6 +120,8 @@ add_library( swap.h thread.cpp thread.h + adpf.cpp + adpf.h thread_queue_list.h thread_worker.h threadsafe_queue.h @@ -139,16 +137,21 @@ add_library( uuid.cpp uuid.h vector_math.h - virtual_buffer.cpp - virtual_buffer.h - wall_clock.cpp - wall_clock.h + zbic_compression.cpp + zbic_compression.h zstd_compression.cpp zstd_compression.h fs/ryujinx_compat.h fs/ryujinx_compat.cpp fs/symlink.h fs/symlink.cpp + cpu_features.cpp + cpu_features.h httplib.h - net/net.h net/net.cpp) + net/net.h net/net.cpp + container/unordered_map.h container/unordered_set.h) + +set_source_files_properties(zbic_compression.cpp PROPERTIES + INCLUDE_DIRECTORIES "${ZBIC_INCLUDE_DIR}" + COMPILE_OPTIONS "$<$:-Wno-unused-function;-Wno-missing-declarations;-Wno-shadow>") if(WIN32) target_sources(common PRIVATE windows/timer_resolution.cpp @@ -180,23 +183,13 @@ endif() if(ARCHITECTURE_x86_64) target_sources( common - PRIVATE x64/cpu_detect.cpp - x64/cpu_detect.h - x64/cpu_wait.cpp - x64/cpu_wait.h - x64/native_clock.cpp - x64/native_clock.h + PRIVATE x64/rdtsc.cpp x64/rdtsc.h - x64/xbyak_abi.h - x64/xbyak_util.h) + x64/xbyak.h) target_link_libraries(common PRIVATE xbyak::xbyak) endif() -if(HAS_NCE) - target_sources(common PRIVATE arm64/native_clock.cpp arm64/native_clock.h) -endif() - if(MSVC) target_compile_definitions( common @@ -231,7 +224,7 @@ endif() if(CXX_CLANG) target_compile_options(common PRIVATE $<$:-fsized-deallocation> - $<$:-Werror=unreachable-code-aggressive>) + $<$:-Wunreachable-code-aggressive>) target_compile_definitions( common PRIVATE @@ -241,19 +234,24 @@ if(CXX_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::heap Boost::icl Boost::pool) else() target_link_libraries(common PUBLIC Boost::headers) endif() - +target_link_libraries(common PRIVATE OpenSSL::SSL) target_link_libraries(common PUBLIC Boost::filesystem Boost::context httplib::httplib nlohmann_json::nlohmann_json) if (lz4_ADDED) target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib) endif() -target_link_libraries(common PUBLIC fmt::fmt stb::headers Threads::Threads unordered_dense::unordered_dense) -target_link_libraries(common PRIVATE lz4::lz4 LLVM::Demangle zstd::zstd) +target_link_libraries(common PUBLIC fmt::fmt stb::headers Threads::Threads) +target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd) + +# Please refer to src/common/demangle.cpp +if (WIN32) + target_link_libraries(common PRIVATE LLVM::Demangle) +endif() if(ANDROID) # For ASharedMemory_create diff --git a/src/common/address_space.inc b/src/common/address_space.inc index 1ee82df53b..4fce338661 100644 --- a/src/common/address_space.inc +++ b/src/common/address_space.inc @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2021 Skyline Team and Contributors // SPDX-License-Identifier: GPL-3.0-or-later @@ -43,13 +46,13 @@ MAP_MEMBER(void)::MapLocked(VaType virt, PaType phys, VaType size, ExtraBlockInf if (virt_end > va_limit) { ASSERT_MSG(false, - "Trying to map a block past the VA limit: virt_end: 0x{:X}, va_limit: 0x{:X}", + "Trying to map a block past the VA limit: virt_end: {:#x}, va_limit: {:#x}", virt_end, va_limit); } auto block_end_successor{std::lower_bound(blocks.begin(), blocks.end(), virt_end)}; if (block_end_successor == blocks.begin()) { - ASSERT_MSG(false, "Trying to map a block before the VA start: virt_end: 0x{:X}", virt_end); + ASSERT_MSG(false, "Trying to map a block before the VA start: virt_end: {:#x}", virt_end); } auto block_end_predecessor{std::prev(block_end_successor)}; @@ -124,7 +127,7 @@ MAP_MEMBER(void)::MapLocked(VaType virt, PaType phys, VaType size, ExtraBlockInf // Check that the start successor is either the end block or something in between if (block_start_successor->virt > virt_end) { - ASSERT_MSG(false, "Unsorted block in AS map: virt: 0x{:X}", block_start_successor->virt); + ASSERT_MSG(false, "Unsorted block in AS map: virt: {:#x}", block_start_successor->virt); } else if (block_start_successor->virt == virt_end) { // We need to create a new block as there are none spare that we would overwrite blocks.insert(block_start_successor, Block(virt, phys, extra_info)); @@ -150,13 +153,13 @@ MAP_MEMBER(void)::UnmapLocked(VaType virt, VaType size) { if (virt_end > va_limit) { ASSERT_MSG(false, - "Trying to map a block past the VA limit: virt_end: 0x{:X}, va_limit: 0x{:X}", + "Trying to map a block past the VA limit: virt_end: {:#x}, va_limit: {:#x}", virt_end, va_limit); } auto block_end_successor{std::lower_bound(blocks.begin(), blocks.end(), virt_end)}; if (block_end_successor == blocks.begin()) { - ASSERT_MSG(false, "Trying to unmap a block before the VA start: virt_end: 0x{:X}", + ASSERT_MSG(false, "Trying to unmap a block before the VA start: virt_end: {:#x}", virt_end); } @@ -257,7 +260,7 @@ MAP_MEMBER(void)::UnmapLocked(VaType virt, VaType size) { auto block_start_successor{std::next(block_start_predecessor)}; if (block_start_successor->virt > virt_end) { - ASSERT_MSG(false, "Unsorted block in AS map: virt: 0x{:X}", block_start_successor->virt); + ASSERT_MSG(false, "Unsorted block in AS map: virt: {:#x}", block_start_successor->virt); } else if (block_start_successor->virt == virt_end) { // There are no blocks between the start and the end that would let us skip inserting a new // one for head diff --git a/src/common/adpf.cpp b/src/common/adpf.cpp new file mode 100644 index 0000000000..8fa2996173 --- /dev/null +++ b/src/common/adpf.cpp @@ -0,0 +1,317 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "common/adpf.h" + +#ifdef __ANDROID__ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "common/logging.h" + +namespace Common::ADPF { + +namespace { + +constexpr std::chrono::nanoseconds DEFAULT_TARGET = std::chrono::nanoseconds{16'666'667}; + +struct AHintManager; +struct AHintSession; + +using PFN_GetManager = AHintManager* (*)(); +using PFN_CreateSession = AHintSession* (*)(AHintManager*, const s32*, size_t, s64); +using PFN_CloseSession = void (*)(AHintSession*); +using PFN_UpdateTarget = int (*)(AHintSession*, s64); +using PFN_ReportActual = int (*)(AHintSession*, s64); +using PFN_SetThreads = int (*)(AHintSession*, const pid_t*, size_t); +using PFN_SetPowerEfficiency = int (*)(AHintSession*, bool); + +struct Api { + PFN_GetManager get_manager = nullptr; + PFN_CreateSession create_session = nullptr; + PFN_CloseSession close_session = nullptr; + PFN_UpdateTarget update_target = nullptr; + PFN_ReportActual report_actual = nullptr; + PFN_SetThreads set_threads = nullptr; + PFN_SetPowerEfficiency set_power_efficiency = nullptr; + AHintManager* manager = nullptr; + bool usable = false; +}; + +const Api& Resolve() { + static const Api api = [] { + Api resolved; + void* library = dlopen("libandroid.so", RTLD_NOW); + if (library == nullptr) { + LOG_INFO(Common, "libandroid.so unavailable, ADPF is disabled"); + return resolved; + } + const auto load = [library](const char* name) { return dlsym(library, name); }; + + resolved.get_manager = reinterpret_cast(load("APerformanceHint_getManager")); + resolved.create_session = + reinterpret_cast(load("APerformanceHint_createSession")); + resolved.close_session = + reinterpret_cast(load("APerformanceHint_closeSession")); + resolved.update_target = + reinterpret_cast(load("APerformanceHint_updateTargetWorkDuration")); + resolved.report_actual = + reinterpret_cast(load("APerformanceHint_reportActualWorkDuration")); + resolved.set_threads = + reinterpret_cast(load("APerformanceHint_setThreads")); + resolved.set_power_efficiency = reinterpret_cast( + load("APerformanceHint_setPreferPowerEfficiency")); + + if (resolved.get_manager == nullptr || resolved.create_session == nullptr || + resolved.close_session == nullptr || resolved.update_target == nullptr || + resolved.report_actual == nullptr) { + LOG_INFO(Common, "Performance hint API not exported, ADPF is disabled"); + return resolved; + } + + resolved.manager = resolved.get_manager(); + if (resolved.manager == nullptr) { + LOG_INFO(Common, "Device does not provide a performance hint manager"); + return resolved; + } + + resolved.usable = true; + LOG_INFO(Common, "ADPF available, setThreads {}, power efficiency {}", + resolved.set_threads != nullptr ? "yes" : "no", + resolved.set_power_efficiency != nullptr ? "yes" : "no"); + return resolved; + }(); + return api; +} + +struct SessionState { + AHintSession* handle = nullptr; + std::vector threads; + bool unsupported = false; +}; + +std::mutex g_mutex; +std::array g_sessions; + +constexpr s64 MAX_REPORTED_TARGETS = 4; + +std::atomic g_target_ns{DEFAULT_TARGET.count()}; +thread_local std::chrono::steady_clock::time_point t_last_frame{}; + +SessionState& StateOf(Session session) { + return g_sessions[static_cast(session)]; +} + +bool IsBackgroundUsable(const Api& api) { + return api.set_power_efficiency != nullptr; +} + +void CloseLocked(SessionState& state) { + if (state.handle != nullptr) { + Resolve().close_session(state.handle); + state.handle = nullptr; + } +} + +AHintSession* CreateSessionFor(Session session, const std::vector& threads) { + const Api& api = Resolve(); + const s64 target = session == Session::Render ? g_target_ns.load(std::memory_order_relaxed) : 0; + + std::vector ids; + ids.reserve(threads.size()); + for (const pid_t tid : threads) { + ids.push_back(static_cast(tid)); + } + + AHintSession* handle = api.create_session(api.manager, ids.data(), ids.size(), target); + if (handle == nullptr && target == 0) { + handle = api.create_session(api.manager, ids.data(), ids.size(), DEFAULT_TARGET.count()); + } + if (handle == nullptr) { + return nullptr; + } + if (session == Session::Background && api.set_power_efficiency != nullptr) { + api.set_power_efficiency(handle, true); + } + return handle; +} + +bool SyncLocked(Session session, SessionState& state) { + if (state.threads.empty()) { + CloseLocked(state); + return false; + } + + const Api& api = Resolve(); + if (state.handle != nullptr && api.set_threads != nullptr) { + std::vector ids = state.threads; + if (api.set_threads(state.handle, ids.data(), ids.size()) == 0) { + return true; + } + } + + AHintSession* const replacement = CreateSessionFor(session, state.threads); + if (replacement == nullptr) { + if (state.handle == nullptr) { + state.unsupported = true; + } + LOG_WARNING(Common, "Could not open a performance hint session for {} threads, falling back", + state.threads.size()); + return false; + } + CloseLocked(state); + state.handle = replacement; + return true; +} + +} // Anonymous namespace + +bool IsSessionSupported(Session session) { + const Api& api = Resolve(); + if (!api.usable) { + return false; + } + if (session == Session::Background && !IsBackgroundUsable(api)) { + return false; + } + std::scoped_lock lock{g_mutex}; + return !StateOf(session).unsupported; +} + +bool AddCurrentThread(Session session) { + if (!IsSessionSupported(session)) { + return false; + } + + const pid_t tid = gettid(); + std::scoped_lock lock{g_mutex}; + + for (size_t i = 0; i < g_sessions.size(); ++i) { + SessionState& state = g_sessions[i]; + if (static_cast(session) == i) { + continue; + } + const auto it = std::find(state.threads.begin(), state.threads.end(), tid); + if (it != state.threads.end()) { + state.threads.erase(it); + SyncLocked(static_cast(i), state); + } + } + + SessionState& state = StateOf(session); + const bool added = + std::find(state.threads.begin(), state.threads.end(), tid) == state.threads.end(); + if (added) { + state.threads.push_back(tid); + } + if (!SyncLocked(session, state)) { + if (added) { + std::erase(state.threads, tid); + } + return false; + } + return true; +} + +void RemoveCurrentThread() { + if (!Resolve().usable) { + return; + } + const pid_t tid = gettid(); + std::scoped_lock lock{g_mutex}; + for (size_t i = 0; i < g_sessions.size(); ++i) { + SessionState& state = g_sessions[i]; + if (std::erase(state.threads, tid) != 0) { + SyncLocked(static_cast(i), state); + } + } +} + +void SetTargetWorkDuration(std::chrono::nanoseconds target) { + const Api& api = Resolve(); + if (!api.usable || target.count() <= 0) { + return; + } + if (g_target_ns.exchange(target.count(), std::memory_order_relaxed) == target.count()) { + return; + } + std::scoped_lock lock{g_mutex}; + SessionState& state = StateOf(Session::Render); + if (state.handle != nullptr) { + api.update_target(state.handle, target.count()); + } +} + +void ReportFrameInterval() { + const Api& api = Resolve(); + if (!api.usable) { + return; + } + + const auto now = std::chrono::steady_clock::now(); + const auto previous = t_last_frame; + t_last_frame = now; + if (previous.time_since_epoch().count() == 0) { + return; + } + + s64 actual = std::chrono::duration_cast(now - previous).count(); + if (actual <= 0) { + return; + } + + const s64 ceiling = g_target_ns.load(std::memory_order_relaxed) * MAX_REPORTED_TARGETS; + actual = (std::min)(actual, ceiling); + + std::scoped_lock lock{g_mutex}; + SessionState& state = StateOf(Session::Render); + if (state.handle != nullptr) { + api.report_actual(state.handle, actual); + } +} + +void Shutdown() { + if (!Resolve().usable) { + return; + } + std::scoped_lock lock{g_mutex}; + for (SessionState& state : g_sessions) { + CloseLocked(state); + state.threads.clear(); + state.unsupported = false; + } +} + +} // namespace Common::ADPF + +#else + +namespace Common::ADPF { + +bool IsSessionSupported(Session) { + return false; +} + +bool AddCurrentThread(Session) { + return false; +} + +void RemoveCurrentThread() {} + +void SetTargetWorkDuration(std::chrono::nanoseconds) {} + +void ReportFrameInterval() {} + +void Shutdown() {} + +} // namespace Common::ADPF + +#endif diff --git a/src/common/adpf.h b/src/common/adpf.h new file mode 100644 index 0000000000..6f14a0bee4 --- /dev/null +++ b/src/common/adpf.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +namespace Common::ADPF { + +enum class Session { + Render, + Background, +}; + +bool IsSessionSupported(Session session); + +bool AddCurrentThread(Session session); +void RemoveCurrentThread(); + +void SetTargetWorkDuration(std::chrono::nanoseconds target); + +void ReportFrameInterval(); + +void Shutdown(); + +} // namespace Common::ADPF diff --git a/src/common/alignment.h b/src/common/alignment.h index fc5c26898a..3612ab0a86 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2014 Jannik Vogel // SPDX-License-Identifier: CC0-1.0 @@ -34,12 +37,6 @@ template return static_cast(value - value % size); } -template - requires std::is_unsigned_v -[[nodiscard]] constexpr bool Is4KBAligned(T value) { - return (value & 0xFFF) == 0; -} - template requires std::is_unsigned_v [[nodiscard]] constexpr bool IsWordAligned(T value) { @@ -48,9 +45,9 @@ template template requires std::is_integral_v -[[nodiscard]] constexpr bool IsAligned(T value, size_t alignment) { +[[nodiscard]] constexpr bool IsAligned(T value, size_t alignment) noexcept { using U = typename std::make_unsigned_t; - const U mask = static_cast(alignment - 1); + const U mask = U(alignment - 1); return (value & mask) == 0; } diff --git a/src/common/android/android_common.cpp b/src/common/android/android_common.cpp index a1cf081695..5041579e0e 100644 --- a/src/common/android/android_common.cpp +++ b/src/common/android/android_common.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -// SPDX-FileCopyrightText: 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "android_common.h" @@ -51,7 +51,7 @@ jstring ToJString(JNIEnv* env, std::u16string_view str) { } double GetJDouble(JNIEnv* env, jobject jdouble) { - return env->GetDoubleField(jdouble, GetDoubleValueField()); + return env->CallDoubleMethod(jdouble, GetDoubleValueMethod()); } jobject ToJDouble(JNIEnv* env, double value) { @@ -59,7 +59,7 @@ jobject ToJDouble(JNIEnv* env, double value) { } s32 GetJInteger(JNIEnv* env, jobject jinteger) { - return env->GetIntField(jinteger, GetIntegerValueField()); + return env->CallIntMethod(jinteger, GetIntegerValueMethod()); } jobject ToJInteger(JNIEnv* env, s32 value) { @@ -67,7 +67,7 @@ jobject ToJInteger(JNIEnv* env, s32 value) { } bool GetJBoolean(JNIEnv* env, jobject jboolean) { - return env->GetBooleanField(jboolean, GetBooleanValueField()); + return env->CallBooleanMethod(jboolean, GetBooleanValueMethod()); } jobject ToJBoolean(JNIEnv* env, bool value) { diff --git a/src/common/android/id_cache.cpp b/src/common/android/id_cache.cpp index 76af1e0fb2..3da532703a 100644 --- a/src/common/android/id_cache.cpp +++ b/src/common/android/id_cache.cpp @@ -63,15 +63,15 @@ static jfieldID s_patch_title_id_field; static jclass s_double_class; static jmethodID s_double_constructor; -static jfieldID s_double_value_field; +static jmethodID s_double_value_method; static jclass s_integer_class; static jmethodID s_integer_constructor; -static jfieldID s_integer_value_field; +static jmethodID s_integer_value_method; static jclass s_boolean_class; static jmethodID s_boolean_constructor; -static jfieldID s_boolean_value_field; +static jmethodID s_boolean_value_method; static jclass s_player_input_class; static jmethodID s_player_input_constructor; @@ -103,7 +103,6 @@ static jmethodID s_clear_chat; static constexpr jint JNI_VERSION = JNI_VERSION_1_6; namespace Common::Android { - JNIEnv *GetEnvForThread() { thread_local static struct OwnedEnv { OwnedEnv() { @@ -299,8 +298,8 @@ namespace Common::Android { return s_double_constructor; } - jfieldID GetDoubleValueField() { - return s_double_value_field; + jmethodID GetDoubleValueMethod() { + return s_double_value_method; } jclass GetIntegerClass() { @@ -311,8 +310,8 @@ namespace Common::Android { return s_integer_constructor; } - jfieldID GetIntegerValueField() { - return s_integer_value_field; + jmethodID GetIntegerValueMethod() { + return s_integer_value_method; } jclass GetBooleanClass() { @@ -323,8 +322,8 @@ namespace Common::Android { return s_boolean_constructor; } - jfieldID GetBooleanValueField() { - return s_boolean_value_field; + jmethodID GetBooleanValueMethod() { + return s_boolean_value_method; } jclass GetPlayerInputClass() { @@ -427,186 +426,7 @@ namespace Common::Android { extern "C" { #endif - jint JNI_OnLoad(JavaVM *vm, void *reserved) { - s_java_vm = vm; - - JNIEnv *env; - if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK) - return JNI_ERR; - - // Initialize Java classes - const jclass native_library_class = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary"); - s_native_library_class = reinterpret_cast(env->NewGlobalRef(native_library_class)); - s_disk_cache_progress_class = reinterpret_cast(env->NewGlobalRef( - env->FindClass("org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress"))); - s_load_callback_stage_class = reinterpret_cast(env->NewGlobalRef(env->FindClass( - "org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage"))); - - const jclass game_dir_class = env->FindClass("org/yuzu/yuzu_emu/model/GameDir"); - s_game_dir_class = reinterpret_cast(env->NewGlobalRef(game_dir_class)); - s_game_dir_constructor = env->GetMethodID(game_dir_class, "", - "(Ljava/lang/String;Z)V"); - env->DeleteLocalRef(game_dir_class); - - // Initialize methods - s_exit_emulation_activity = - env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); - s_disk_cache_load_progress = - env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V"); - s_copy_to_storage = env->GetStaticMethodID(s_native_library_class, "copyFileToStorage", - "(Ljava/lang/String;Ljava/lang/String;)Z"); - s_file_exists = env->GetStaticMethodID(s_native_library_class, "exists", - "(Ljava/lang/String;)Z"); - s_file_extension = env->GetStaticMethodID(s_native_library_class, "getFileExtension", - "(Ljava/lang/String;)Ljava/lang/String;"); - s_on_emulation_started = - env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V"); - s_on_emulation_stopped = - env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V"); - s_on_program_changed = - env->GetStaticMethodID(s_native_library_class, "onProgramChanged", "(I)V"); - - const jclass game_class = env->FindClass("org/yuzu/yuzu_emu/model/Game"); - s_game_class = reinterpret_cast(env->NewGlobalRef(game_class)); - s_game_constructor = env->GetMethodID(game_class, "", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/" - "String;Ljava/lang/String;Ljava/lang/String;Z)V"); - s_game_title_field = env->GetFieldID(game_class, "title", "Ljava/lang/String;"); - s_game_path_field = env->GetFieldID(game_class, "path", "Ljava/lang/String;"); - s_game_program_id_field = env->GetFieldID(game_class, "programId", "Ljava/lang/String;"); - s_game_developer_field = env->GetFieldID(game_class, "developer", "Ljava/lang/String;"); - s_game_version_field = env->GetFieldID(game_class, "version", "Ljava/lang/String;"); - s_game_is_homebrew_field = env->GetFieldID(game_class, "isHomebrew", "Z"); - env->DeleteLocalRef(game_class); - - const jclass string_class = env->FindClass("java/lang/String"); - s_string_class = reinterpret_cast(env->NewGlobalRef(string_class)); - env->DeleteLocalRef(string_class); - - const jclass pair_class = env->FindClass("kotlin/Pair"); - s_pair_class = reinterpret_cast(env->NewGlobalRef(pair_class)); - s_pair_constructor = - env->GetMethodID(pair_class, "", "(Ljava/lang/Object;Ljava/lang/Object;)V"); - s_pair_first_field = env->GetFieldID(pair_class, "first", "Ljava/lang/Object;"); - s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;"); - env->DeleteLocalRef(pair_class); - - const jclass overlay_control_data_class = - env->FindClass("org/yuzu/yuzu_emu/overlay/model/OverlayControlData"); - s_overlay_control_data_class = - reinterpret_cast(env->NewGlobalRef(overlay_control_data_class)); - s_overlay_control_data_constructor = - env->GetMethodID(overlay_control_data_class, "", - "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;F)V"); - s_overlay_control_data_id_field = - env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;"); - s_overlay_control_data_enabled_field = - env->GetFieldID(overlay_control_data_class, "enabled", "Z"); - s_overlay_control_data_landscape_position_field = - env->GetFieldID(overlay_control_data_class, "landscapePosition", "Lkotlin/Pair;"); - s_overlay_control_data_portrait_position_field = - env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;"); - s_overlay_control_data_foldable_position_field = - env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;"); - s_overlay_control_data_individual_scale_field = - env->GetFieldID(overlay_control_data_class, "individualScale", "F"); - env->DeleteLocalRef(overlay_control_data_class); - - const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch"); - s_patch_class = reinterpret_cast(env->NewGlobalRef(patch_class)); - s_patch_constructor = env->GetMethodID( - patch_class, "", - "(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;JI)V"); - s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z"); - s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;"); - s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;"); - s_patch_type_field = env->GetFieldID(patch_class, "type", "I"); - s_patch_program_id_field = env->GetFieldID(patch_class, "programId", "Ljava/lang/String;"); - s_patch_title_id_field = env->GetFieldID(patch_class, "titleId", "Ljava/lang/String;"); - env->DeleteLocalRef(patch_class); - - const jclass double_class = env->FindClass("java/lang/Double"); - s_double_class = reinterpret_cast(env->NewGlobalRef(double_class)); - s_double_constructor = env->GetMethodID(double_class, "", "(D)V"); - s_double_value_field = env->GetFieldID(double_class, "value", "D"); - env->DeleteLocalRef(double_class); - - const jclass int_class = env->FindClass("java/lang/Integer"); - s_integer_class = reinterpret_cast(env->NewGlobalRef(int_class)); - s_integer_constructor = env->GetMethodID(int_class, "", "(I)V"); - s_integer_value_field = env->GetFieldID(int_class, "value", "I"); - env->DeleteLocalRef(int_class); - - const jclass boolean_class = env->FindClass("java/lang/Boolean"); - s_boolean_class = reinterpret_cast(env->NewGlobalRef(boolean_class)); - s_boolean_constructor = env->GetMethodID(boolean_class, "", "(Z)V"); - s_boolean_value_field = env->GetFieldID(boolean_class, "value", "Z"); - env->DeleteLocalRef(boolean_class); - - const jclass player_input_class = - env->FindClass("org/yuzu/yuzu_emu/features/input/model/PlayerInput"); - s_player_input_class = reinterpret_cast(env->NewGlobalRef(player_input_class)); - s_player_input_constructor = env->GetMethodID( - player_input_class, "", - "(Z[Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;ZIJJJJLjava/lang/String;Z)V"); - s_player_input_connected_field = env->GetFieldID(player_input_class, "connected", "Z"); - s_player_input_buttons_field = - env->GetFieldID(player_input_class, "buttons", "[Ljava/lang/String;"); - s_player_input_analogs_field = - env->GetFieldID(player_input_class, "analogs", "[Ljava/lang/String;"); - s_player_input_motions_field = - env->GetFieldID(player_input_class, "motions", "[Ljava/lang/String;"); - s_player_input_vibration_enabled_field = - env->GetFieldID(player_input_class, "vibrationEnabled", "Z"); - s_player_input_vibration_strength_field = - env->GetFieldID(player_input_class, "vibrationStrength", "I"); - s_player_input_body_color_left_field = - env->GetFieldID(player_input_class, "bodyColorLeft", "J"); - s_player_input_body_color_right_field = - env->GetFieldID(player_input_class, "bodyColorRight", "J"); - s_player_input_button_color_left_field = - env->GetFieldID(player_input_class, "buttonColorLeft", "J"); - s_player_input_button_color_right_field = - env->GetFieldID(player_input_class, "buttonColorRight", "J"); - s_player_input_profile_name_field = - env->GetFieldID(player_input_class, "profileName", "Ljava/lang/String;"); - s_player_input_use_system_vibrator_field = - env->GetFieldID(player_input_class, "useSystemVibrator", "Z"); - env->DeleteLocalRef(player_input_class); - - const jclass yuzu_input_device_interface = - env->FindClass("org/yuzu/yuzu_emu/features/input/YuzuInputDevice"); - s_yuzu_input_device_interface = - reinterpret_cast(env->NewGlobalRef(yuzu_input_device_interface)); - s_yuzu_input_device_get_name = - env->GetMethodID(yuzu_input_device_interface, "getName", "()Ljava/lang/String;"); - s_yuzu_input_device_get_guid = - env->GetMethodID(yuzu_input_device_interface, "getGUID", "()Ljava/lang/String;"); - s_yuzu_input_device_get_port = env->GetMethodID(yuzu_input_device_interface, "getPort", - "()I"); - s_yuzu_input_device_get_supports_vibration = - env->GetMethodID(yuzu_input_device_interface, "getSupportsVibration", "()Z"); - s_yuzu_input_device_vibrate = env->GetMethodID(yuzu_input_device_interface, "vibrate", - "(F)V"); - s_yuzu_input_device_get_axes = - env->GetMethodID(yuzu_input_device_interface, "getAxes", "()[Ljava/lang/Integer;"); - s_yuzu_input_device_has_keys = - env->GetMethodID(yuzu_input_device_interface, "hasKeys", "([I)[Z"); - env->DeleteLocalRef(yuzu_input_device_interface); - s_add_netplay_message = env->GetStaticMethodID(s_native_library_class, "addNetPlayMessage", - "(ILjava/lang/String;)V"); - s_clear_chat = env->GetStaticMethodID(s_native_library_class, "clearChat", "()V"); - - - // Initialize Android Storage - Common::FS::Android::RegisterCallbacks(env, s_native_library_class); - - // Initialize applets - Common::Android::SoftwareKeyboard::InitJNI(env); - Common::Android::WebBrowser::InitJNI(env); - - return JNI_VERSION; - } + jint InitFFmpegOnLoad(JavaVM *vm); void JNI_OnUnload(JavaVM *vm, void *reserved) { JNIEnv *env; @@ -642,4 +462,183 @@ namespace Common::Android { } #endif +void Initialize(JavaVM* vm, JNIEnv *env) { + s_java_vm = vm; + InitFFmpegOnLoad(vm); + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + } + + // Initialize Java classes + const jclass native_library_class = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary"); + s_native_library_class = reinterpret_cast(env->NewGlobalRef(native_library_class)); + s_disk_cache_progress_class = reinterpret_cast(env->NewGlobalRef( + env->FindClass("org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress"))); + s_load_callback_stage_class = reinterpret_cast(env->NewGlobalRef(env->FindClass( + "org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage"))); + + const jclass game_dir_class = env->FindClass("org/yuzu/yuzu_emu/model/GameDir"); + s_game_dir_class = reinterpret_cast(env->NewGlobalRef(game_dir_class)); + s_game_dir_constructor = env->GetMethodID(game_dir_class, "", + "(Ljava/lang/String;Z)V"); + env->DeleteLocalRef(game_dir_class); + + // Initialize methods + s_exit_emulation_activity = + env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); + s_disk_cache_load_progress = + env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V"); + s_copy_to_storage = env->GetStaticMethodID(s_native_library_class, "copyFileToStorage", + "(Ljava/lang/String;Ljava/lang/String;)Z"); + s_file_exists = env->GetStaticMethodID(s_native_library_class, "exists", + "(Ljava/lang/String;)Z"); + s_file_extension = env->GetStaticMethodID(s_native_library_class, "getFileExtension", + "(Ljava/lang/String;)Ljava/lang/String;"); + s_on_emulation_started = + env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V"); + s_on_emulation_stopped = + env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V"); + s_on_program_changed = + env->GetStaticMethodID(s_native_library_class, "onProgramChanged", "(I)V"); + + const jclass game_class = env->FindClass("org/yuzu/yuzu_emu/model/Game"); + s_game_class = reinterpret_cast(env->NewGlobalRef(game_class)); + s_game_constructor = env->GetMethodID(game_class, "", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/" + "String;Ljava/lang/String;Ljava/lang/String;Z)V"); + s_game_title_field = env->GetFieldID(game_class, "title", "Ljava/lang/String;"); + s_game_path_field = env->GetFieldID(game_class, "path", "Ljava/lang/String;"); + s_game_program_id_field = env->GetFieldID(game_class, "programId", "Ljava/lang/String;"); + s_game_developer_field = env->GetFieldID(game_class, "developer", "Ljava/lang/String;"); + s_game_version_field = env->GetFieldID(game_class, "version", "Ljava/lang/String;"); + s_game_is_homebrew_field = env->GetFieldID(game_class, "isHomebrew", "Z"); + env->DeleteLocalRef(game_class); + + const jclass string_class = env->FindClass("java/lang/String"); + s_string_class = reinterpret_cast(env->NewGlobalRef(string_class)); + env->DeleteLocalRef(string_class); + + const jclass pair_class = env->FindClass("kotlin/Pair"); + s_pair_class = reinterpret_cast(env->NewGlobalRef(pair_class)); + s_pair_constructor = + env->GetMethodID(pair_class, "", "(Ljava/lang/Object;Ljava/lang/Object;)V"); + s_pair_first_field = env->GetFieldID(pair_class, "first", "Ljava/lang/Object;"); + s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;"); + env->DeleteLocalRef(pair_class); + + const jclass overlay_control_data_class = + env->FindClass("org/yuzu/yuzu_emu/overlay/model/OverlayControlData"); + s_overlay_control_data_class = + reinterpret_cast(env->NewGlobalRef(overlay_control_data_class)); + s_overlay_control_data_constructor = + env->GetMethodID(overlay_control_data_class, "", + "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;F)V"); + s_overlay_control_data_id_field = + env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;"); + s_overlay_control_data_enabled_field = + env->GetFieldID(overlay_control_data_class, "enabled", "Z"); + s_overlay_control_data_landscape_position_field = + env->GetFieldID(overlay_control_data_class, "landscapePosition", "Lkotlin/Pair;"); + s_overlay_control_data_portrait_position_field = + env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;"); + s_overlay_control_data_foldable_position_field = + env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;"); + s_overlay_control_data_individual_scale_field = + env->GetFieldID(overlay_control_data_class, "individualScale", "F"); + env->DeleteLocalRef(overlay_control_data_class); + + const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch"); + s_patch_class = reinterpret_cast(env->NewGlobalRef(patch_class)); + s_patch_constructor = env->GetMethodID( + patch_class, "", + "(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;JI)V"); + s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z"); + s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;"); + s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;"); + s_patch_type_field = env->GetFieldID(patch_class, "type", "I"); + s_patch_program_id_field = env->GetFieldID(patch_class, "programId", "Ljava/lang/String;"); + s_patch_title_id_field = env->GetFieldID(patch_class, "titleId", "Ljava/lang/String;"); + env->DeleteLocalRef(patch_class); + + const jclass double_class = env->FindClass("java/lang/Double"); + s_double_class = reinterpret_cast(env->NewGlobalRef(double_class)); + s_double_constructor = env->GetMethodID(double_class, "", "(D)V"); + s_double_value_method = env->GetMethodID(double_class, "doubleValue", "()D"); + env->DeleteLocalRef(double_class); + + const jclass int_class = env->FindClass("java/lang/Integer"); + s_integer_class = reinterpret_cast(env->NewGlobalRef(int_class)); + s_integer_constructor = env->GetMethodID(int_class, "", "(I)V"); + s_integer_value_method = env->GetMethodID(int_class, "intValue", "()I"); + env->DeleteLocalRef(int_class); + + const jclass boolean_class = env->FindClass("java/lang/Boolean"); + s_boolean_class = reinterpret_cast(env->NewGlobalRef(boolean_class)); + s_boolean_constructor = env->GetMethodID(boolean_class, "", "(Z)V"); + s_boolean_value_method = env->GetMethodID(boolean_class, "booleanValue", "()Z"); + env->DeleteLocalRef(boolean_class); + + const jclass player_input_class = + env->FindClass("org/yuzu/yuzu_emu/features/input/model/PlayerInput"); + s_player_input_class = reinterpret_cast(env->NewGlobalRef(player_input_class)); + s_player_input_constructor = env->GetMethodID( + player_input_class, "", + "(Z[Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;ZIJJJJLjava/lang/String;Z)V"); + s_player_input_connected_field = env->GetFieldID(player_input_class, "connected", "Z"); + s_player_input_buttons_field = + env->GetFieldID(player_input_class, "buttons", "[Ljava/lang/String;"); + s_player_input_analogs_field = + env->GetFieldID(player_input_class, "analogs", "[Ljava/lang/String;"); + s_player_input_motions_field = + env->GetFieldID(player_input_class, "motions", "[Ljava/lang/String;"); + s_player_input_vibration_enabled_field = + env->GetFieldID(player_input_class, "vibrationEnabled", "Z"); + s_player_input_vibration_strength_field = + env->GetFieldID(player_input_class, "vibrationStrength", "I"); + s_player_input_body_color_left_field = + env->GetFieldID(player_input_class, "bodyColorLeft", "J"); + s_player_input_body_color_right_field = + env->GetFieldID(player_input_class, "bodyColorRight", "J"); + s_player_input_button_color_left_field = + env->GetFieldID(player_input_class, "buttonColorLeft", "J"); + s_player_input_button_color_right_field = + env->GetFieldID(player_input_class, "buttonColorRight", "J"); + s_player_input_profile_name_field = + env->GetFieldID(player_input_class, "profileName", "Ljava/lang/String;"); + s_player_input_use_system_vibrator_field = + env->GetFieldID(player_input_class, "useSystemVibrator", "Z"); + env->DeleteLocalRef(player_input_class); + + const jclass yuzu_input_device_interface = + env->FindClass("org/yuzu/yuzu_emu/features/input/YuzuInputDevice"); + s_yuzu_input_device_interface = + reinterpret_cast(env->NewGlobalRef(yuzu_input_device_interface)); + s_yuzu_input_device_get_name = + env->GetMethodID(yuzu_input_device_interface, "getName", "()Ljava/lang/String;"); + s_yuzu_input_device_get_guid = + env->GetMethodID(yuzu_input_device_interface, "getGUID", "()Ljava/lang/String;"); + s_yuzu_input_device_get_port = env->GetMethodID(yuzu_input_device_interface, "getPort", + "()I"); + s_yuzu_input_device_get_supports_vibration = + env->GetMethodID(yuzu_input_device_interface, "getSupportsVibration", "()Z"); + s_yuzu_input_device_vibrate = env->GetMethodID(yuzu_input_device_interface, "vibrate", + "(F)V"); + s_yuzu_input_device_get_axes = + env->GetMethodID(yuzu_input_device_interface, "getAxes", "()[Ljava/lang/Integer;"); + s_yuzu_input_device_has_keys = + env->GetMethodID(yuzu_input_device_interface, "hasKeys", "([I)[Z"); + env->DeleteLocalRef(yuzu_input_device_interface); + s_add_netplay_message = env->GetStaticMethodID(s_native_library_class, "addNetPlayMessage", + "(ILjava/lang/String;)V"); + s_clear_chat = env->GetStaticMethodID(s_native_library_class, "clearChat", "()V"); + + // Initialize Android Storage + Common::FS::Android::RegisterCallbacks(env, s_native_library_class); + + // Initialize applets + Common::Android::SoftwareKeyboard::InitJNI(env); + Common::Android::WebBrowser::InitJNI(env); +} + } // namespace Common::Android diff --git a/src/common/android/id_cache.h b/src/common/android/id_cache.h index 6b48f471b7..6da385d521 100644 --- a/src/common/android/id_cache.h +++ b/src/common/android/id_cache.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -11,6 +11,7 @@ namespace Common::Android { +void Initialize(JavaVM* vm, JNIEnv *env); JNIEnv* GetEnvForThread(); /** @@ -80,15 +81,15 @@ jfieldID GetPatchTitleIdField(); jclass GetDoubleClass(); jmethodID GetDoubleConstructor(); -jfieldID GetDoubleValueField(); +jmethodID GetDoubleValueMethod(); jclass GetIntegerClass(); jmethodID GetIntegerConstructor(); -jfieldID GetIntegerValueField(); +jmethodID GetIntegerValueMethod(); jclass GetBooleanClass(); jmethodID GetBooleanConstructor(); -jfieldID GetBooleanValueField(); +jmethodID GetBooleanValueMethod(); jclass GetPlayerInputClass(); jmethodID GetPlayerInputConstructor(); diff --git a/src/common/arm64/native_clock.cpp b/src/common/arm64/native_clock.cpp deleted file mode 100644 index 76ffb74bab..0000000000 --- a/src/common/arm64/native_clock.cpp +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#ifdef ANDROID -#include -#endif -#include "common/arm64/native_clock.h" - -namespace Common::Arm64 { - -namespace { - -NativeClock::FactorType GetFixedPointFactor(u64 num, u64 den) { - return (static_cast(num) << 64) / den; -} - -u64 MultiplyHigh(u64 m, NativeClock::FactorType factor) { - return static_cast((m * factor) >> 64); -} - -} // namespace - -NativeClock::NativeClock() { - const u64 host_cntfrq = GetHostCNTFRQ(); - ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq); - us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq); - ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq); - guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq); - gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq); -} - -std::chrono::nanoseconds NativeClock::GetTimeNS() const { - return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_cntfrq_factor)}; -} - -std::chrono::microseconds NativeClock::GetTimeUS() const { - return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_cntfrq_factor)}; -} - -std::chrono::milliseconds NativeClock::GetTimeMS() const { - return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_cntfrq_factor)}; -} - -s64 NativeClock::GetCNTPCT() const { - return MultiplyHigh(GetUptime(), guest_cntfrq_factor); -} - -s64 NativeClock::GetGPUTick() const { - return MultiplyHigh(GetUptime(), gputick_cntfrq_factor); -} - -s64 NativeClock::GetUptime() const { - s64 cntvct_el0 = 0; - asm volatile("dsb ish\n\t" - "mrs %[cntvct_el0], cntvct_el0\n\t" - "dsb ish\n\t" - : [cntvct_el0] "=r"(cntvct_el0)); - return cntvct_el0; -} - -bool NativeClock::IsNative() const { - return true; -} - -s64 NativeClock::GetHostCNTFRQ() { - u64 cntfrq_el0 = 0; - std::string_view board{""}; -#ifdef ANDROID - char buffer[PROP_VALUE_MAX]; - int len{__system_property_get("ro.product.board", buffer)}; - board = std::string_view(buffer, static_cast(len)); -#endif - if (board == "s5e9925") { // Exynos 2200 - cntfrq_el0 = 25600000; - } else if (board == "exynos2100") { // Exynos 2100 - cntfrq_el0 = 26000000; - } else if (board == "exynos9810") { // Exynos 9810 - cntfrq_el0 = 26000000; - } else if (board == "s5e8825") { // Exynos 1280 - cntfrq_el0 = 26000000; - } else { - asm("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0)); - } - return cntfrq_el0; -} - -} // namespace Common::Arm64 diff --git a/src/common/arm64/native_clock.h b/src/common/arm64/native_clock.h deleted file mode 100644 index 94bc1882e0..0000000000 --- a/src/common/arm64/native_clock.h +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/wall_clock.h" - -namespace Common::Arm64 { - -class NativeClock final : public WallClock { -public: - explicit NativeClock(); - - std::chrono::nanoseconds GetTimeNS() const override; - - std::chrono::microseconds GetTimeUS() const override; - - std::chrono::milliseconds GetTimeMS() const override; - - s64 GetCNTPCT() const override; - - s64 GetGPUTick() const override; - - s64 GetUptime() const override; - - bool IsNative() const override; - - static s64 GetHostCNTFRQ(); - -public: - using FactorType = unsigned __int128; - - FactorType GetGuestCNTFRQFactor() const { - return guest_cntfrq_factor; - } - -private: - FactorType ns_cntfrq_factor; - FactorType us_cntfrq_factor; - FactorType ms_cntfrq_factor; - FactorType guest_cntfrq_factor; - FactorType gputick_cntfrq_factor; -}; - -} // namespace Common::Arm64 diff --git a/src/common/bit_field.h b/src/common/bit_field.h index 0168ff9cbf..fbc71590d5 100644 --- a/src/common/bit_field.h +++ b/src/common/bit_field.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2014 Tony Wasserka // SPDX-FileCopyrightText: 2014 Dolphin Emulator Project // SPDX-License-Identifier: BSD-3-Clause AND GPL-2.0-or-later @@ -7,6 +10,7 @@ #include #include #include +#include #include "common/swap.h" /* @@ -83,25 +87,22 @@ */ #pragma pack(1) template + requires std::is_trivially_copyable_v struct BitField { private: // UnderlyingType is T for non-enum types and the underlying type of T if // T is an enumeration. Note that T is wrapped within an enable_if in the // former case to workaround compile errors which arise when using // std::underlying_type::type directly. - using UnderlyingType = typename std::conditional_t, std::underlying_type, - std::enable_if>::type; - + using UnderlyingType = typename std::conditional_t, std::underlying_type, std::enable_if>::type; // We store the value as the unsigned type to avoid undefined behaviour on value shifting using StorageType = std::make_unsigned_t; - using StorageTypeWithEndian = typename AddEndian::type; - public: /// Constants to allow limited introspection of fields if needed static constexpr std::size_t position = Position; static constexpr std::size_t bits = Bits; - static constexpr StorageType mask = (((StorageType)~0) >> (8 * sizeof(T) - bits)) << position; + static constexpr StorageType mask = (StorageType(~0) >> (CHAR_BIT * sizeof(T) - bits)) << position; /** * Formats a value by masking and shifting it according to the field parameters. A value @@ -109,21 +110,18 @@ public: * the results together. */ [[nodiscard]] static constexpr StorageType FormatValue(const T& value) { - return (static_cast(value) << position) & mask; + return (StorageType(value) << position) & mask; } - /** - * Extracts a value from the passed storage. In most situations prefer use the member functions - * (such as Value() or operator T), but this can be used to extract a value from a bitfield - * union in a constexpr context. - */ + /// @brief Extracts a value from the passed storage. In most situations prefer use the member functions + /// (such as Value() or operator T), but this can be used to extract a value from a bitfield + /// union in a constexpr context. [[nodiscard]] static constexpr T ExtractValue(const StorageType& storage) { if constexpr (std::numeric_limits::is_signed) { - std::size_t shift = 8 * sizeof(T) - bits; - return static_cast(static_cast(storage << (shift - position)) >> - shift); + std::size_t shift = CHAR_BIT * sizeof(T) - bits; + return T(UnderlyingType(storage << (shift - position)) >> shift); } else { - return static_cast((storage & mask) >> position); + return T((storage & mask) >> position); } } @@ -141,30 +139,14 @@ public: constexpr BitField(BitField&&) noexcept = default; constexpr BitField& operator=(BitField&&) noexcept = default; - constexpr void Assign(const T& value) { -#ifdef _MSC_VER - storage = static_cast((storage & ~mask) | FormatValue(value)); -#else - // Explicitly reload with memcpy to avoid compiler aliasing quirks - // regarding optimization: GCC/Clang clobber chained stores to - // different bitfields in the same struct with the last value. - StorageTypeWithEndian storage_; - std::memcpy(&storage_, &storage, sizeof(storage_)); - storage = static_cast((storage_ & ~mask) | FormatValue(value)); -#endif + constexpr void Assign(const T value) { + storage = StorageType((storage & ~mask) | FormatValue(value)); } [[nodiscard]] constexpr T Value() const { return ExtractValue(storage); } - template - [[nodiscard]] constexpr ConvertedToType As() const { - static_assert(!std::is_same_v, - "Unnecessary cast. Use Value() instead."); - return static_cast(Value()); - } - [[nodiscard]] constexpr operator T() const { return Value(); } @@ -176,13 +158,11 @@ public: private: StorageTypeWithEndian storage; - static_assert(bits + position <= 8 * sizeof(T), "Bitfield out of range"); - + static_assert(bits + position <= CHAR_BIT * sizeof(T), "Bitfield out of range"); // And, you know, just in case people specify something stupid like bits=position=0x80000000 - static_assert(position < 8 * sizeof(T), "Invalid position"); - static_assert(bits <= 8 * sizeof(T), "Invalid number of bits"); + static_assert(position < CHAR_BIT * sizeof(T), "Invalid position"); + static_assert(bits <= CHAR_BIT * sizeof(T), "Invalid number of bits"); static_assert(bits > 0, "Invalid number of bits"); - static_assert(std::is_trivially_copyable_v, "T must be trivially copyable in a BitField"); }; #pragma pack() diff --git a/src/common/bit_util.h b/src/common/bit_util.h index 13368b4392..ce916e6162 100644 --- a/src/common/bit_util.h +++ b/src/common/bit_util.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -14,46 +17,34 @@ namespace Common { /// Gets the size of a specified type T in bits. template + requires std::is_integral_v [[nodiscard]] constexpr std::size_t BitSize() { - return sizeof(T) * CHAR_BIT; + return std::size_t(sizeof(T) * CHAR_BIT); } -[[nodiscard]] constexpr u32 MostSignificantBit32(const u32 value) { - return 31U - static_cast(std::countl_zero(value)); +template + requires std::is_integral_v +[[nodiscard]] constexpr u32 MostSignificantBit(const T value) { + return u32(sizeof(T) * CHAR_BIT - 1 - std::countl_zero(value)); } -[[nodiscard]] constexpr u32 MostSignificantBit64(const u64 value) { - return 63U - static_cast(std::countl_zero(value)); +template + requires std::is_integral_v +[[nodiscard]] constexpr T Log2Floor(const T value) { + return T(MostSignificantBit(value)); } -[[nodiscard]] constexpr u32 Log2Floor32(const u32 value) { - return MostSignificantBit32(value); -} - -[[nodiscard]] constexpr u32 Log2Floor64(const u64 value) { - return MostSignificantBit64(value); -} - -[[nodiscard]] constexpr u32 Log2Ceil32(const u32 value) { - const u32 log2_f = Log2Floor32(value); - return log2_f + static_cast((value ^ (1U << log2_f)) != 0U); -} - -[[nodiscard]] constexpr u32 Log2Ceil64(const u64 value) { - const u64 log2_f = Log2Floor64(value); - return static_cast(log2_f + static_cast((value ^ (1ULL << log2_f)) != 0ULL)); -} - -template - requires std::is_unsigned_v -[[nodiscard]] constexpr bool IsPow2(T value) { - return std::has_single_bit(value); +template + requires std::is_integral_v +[[nodiscard]] constexpr T Log2Ceil(const T value) { + const T log2_f = Log2Floor(value); + return T(log2_f + T((value ^ (T(1ULL) << log2_f)) != T(0ULL))); } template requires std::is_integral_v [[nodiscard]] T NextPow2(T value) { - return static_cast(1ULL << ((8U * sizeof(T)) - std::countl_zero(value - 1U))); + return T(1ULL << (sizeof(T) * CHAR_BIT - std::countl_zero(value - 1U))); } template diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index 639c2d0e0d..18e83d3381 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -8,12 +8,21 @@ #include #include - -#if !defined(ARCHITECTURE_x86_64) +#include #include // for exit -#endif #include "common/common_types.h" +#ifndef __cpp_lib_to_underlying +namespace std { +// https://en.cppreference.com/cpp/utility/to_underlying +template + requires std::is_enum_v +constexpr std::underlying_type_t to_underlying(T e) noexcept { + return std::underlying_type_t(e); +} +} +#endif + /// Textually concatenates two tokens. The double-expansion is required by the C preprocessor. #define CONCAT2(x, y) DO_CONCAT2(x, y) #define DO_CONCAT2(x, y) x##y @@ -38,56 +47,43 @@ #define DECLARE_ENUM_FLAG_OPERATORS(type) \ [[nodiscard]] constexpr type operator|(type a, type b) noexcept { \ - using T = std::underlying_type_t; \ - return static_cast(static_cast(a) | static_cast(b)); \ + return type(std::to_underlying(a) | std::to_underlying(b)); \ } \ [[nodiscard]] constexpr type operator&(type a, type b) noexcept { \ - using T = std::underlying_type_t; \ - return static_cast(static_cast(a) & static_cast(b)); \ + return type(std::to_underlying(a) & std::to_underlying(b)); \ } \ [[nodiscard]] constexpr type operator^(type a, type b) noexcept { \ - using T = std::underlying_type_t; \ - return static_cast(static_cast(a) ^ static_cast(b)); \ + return type(std::to_underlying(a) ^ std::to_underlying(b)); \ } \ [[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \ - using T = std::underlying_type_t; \ - return static_cast(static_cast(a) << static_cast(b)); \ + return type(std::to_underlying(a) << std::to_underlying(b)); \ } \ [[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \ - using T = std::underlying_type_t; \ - return static_cast(static_cast(a) >> static_cast(b)); \ + return type(std::to_underlying(a) >> std::to_underlying(b)); \ } \ - constexpr type& operator|=(type& a, type b) noexcept { \ - a = a | b; \ - return a; \ + constexpr type operator|=(type& a, type b) noexcept { \ + return a = a | b; \ } \ - constexpr type& operator&=(type& a, type b) noexcept { \ - a = a & b; \ - return a; \ + constexpr type operator&=(type& a, type b) noexcept { \ + return a = a & b; \ } \ - constexpr type& operator^=(type& a, type b) noexcept { \ - a = a ^ b; \ - return a; \ + constexpr type operator^=(type& a, type b) noexcept { \ + return a = a ^ b; \ } \ - constexpr type& operator<<=(type& a, type b) noexcept { \ - a = a << b; \ - return a; \ + constexpr type operator<<=(type& a, type b) noexcept { \ + return a = a << b; \ } \ - constexpr type& operator>>=(type& a, type b) noexcept { \ - a = a >> b; \ - return a; \ + constexpr type operator>>=(type& a, type b) noexcept { \ + return a = a >> b; \ } \ [[nodiscard]] constexpr type operator~(type key) noexcept { \ - using T = std::underlying_type_t; \ - return static_cast(~static_cast(key)); \ + return type(~std::to_underlying(key)); \ } \ [[nodiscard]] constexpr bool True(type key) noexcept { \ - using T = std::underlying_type_t; \ - return static_cast(key) != 0; \ + return std::to_underlying(key) != 0; \ } \ [[nodiscard]] constexpr bool False(type key) noexcept { \ - using T = std::underlying_type_t; \ - return static_cast(key) == 0; \ + return std::to_underlying(key) == 0; \ } #define YUZU_NON_COPYABLE(cls) \ @@ -104,10 +100,8 @@ namespace Common { return u32(a) | u32(b) << 8 | u32(c) << 16 | u32(d) << 24; } -[[nodiscard]] constexpr u64 MakeMagic(char a, char b, char c, char d, char e, char f, char g, - char h) { - return u64(a) << 0 | u64(b) << 8 | u64(c) << 16 | u64(d) << 24 | u64(e) << 32 | u64(f) << 40 | - u64(g) << 48 | u64(h) << 56; +[[nodiscard]] constexpr u64 MakeMagic(char a, char b, char c, char d, char e, char f, char g, char h) { + return u64(a) << 0 | u64(b) << 8 | u64(c) << 16 | u64(d) << 24 | u64(e) << 32 | u64(f) << 40 | u64(g) << 48 | u64(h) << 56; } } // namespace Common diff --git a/src/common/container/unordered_map.h b/src/common/container/unordered_map.h new file mode 100644 index 0000000000..7d9e49fd80 --- /dev/null +++ b/src/common/container/unordered_map.h @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/container_hash.h" + +#include + +namespace Common { + +template , class Pred = std::equal_to, + class Allocator = std::allocator>> +using unordered_map = boost::unordered::unordered_flat_map; + +} diff --git a/src/common/container/unordered_set.h b/src/common/container/unordered_set.h new file mode 100644 index 0000000000..54d71406a1 --- /dev/null +++ b/src/common/container/unordered_set.h @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/container_hash.h" + +#include + +namespace Common { + +template , class Pred = std::equal_to, + class Allocator = std::allocator> +using unordered_set = boost::unordered::unordered_flat_set; + +} diff --git a/src/common/container_hash.h b/src/common/container_hash.h index a5e3577452..d7477f7e4f 100644 --- a/src/common/container_hash.h +++ b/src/common/container_hash.h @@ -1,12 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2005-2014 Daniel James // SPDX-FileCopyrightText: 2016 Austin Appleby // SPDX-License-Identifier: BSL-1.0 +#pragma once + #include #include #include +#include #include +#include #include +#include #include namespace Common { @@ -64,10 +72,17 @@ struct HashCombineImpl<64> { } // namespace detail template + requires std::is_unsigned_v inline void HashCombine(std::size_t& seed, const T& v) { seed = detail::HashCombineImpl::fn(seed, detail::HashValue(v)); } +template + requires (!std::is_unsigned_v) +inline void HashCombine(std::size_t& seed, const T& v) { + seed = detail::HashCombineImpl::fn(seed, std::hash{}(v)); +} + template inline std::size_t HashRange(It first, It last) { std::size_t seed = 0; @@ -90,3 +105,26 @@ std::size_t HashValue(const std::vector& v) { } } // namespace Common + +namespace std { + +template +struct hash> { + std::size_t operator()(const std::tuple& t) const noexcept { + std::size_t seed = 0; + std::apply([&seed](const Args&... args) { (Common::HashCombine(seed, args), ...); }, t); + return seed; + } +}; + +template +struct hash> { + std::size_t operator()(const std::pair& p) const noexcept { + std::size_t seed = 0; + Common::HashCombine(seed, p.first); + Common::HashCombine(seed, p.second); + return seed; + } +}; + +} // namespace std diff --git a/src/common/x64/cpu_detect.cpp b/src/common/cpu_features.cpp similarity index 50% rename from src/common/x64/cpu_detect.cpp rename to src/common/cpu_features.cpp index 4cc42ccbd5..8ccb6847f7 100644 --- a/src/common/x64/cpu_detect.cpp +++ b/src/common/cpu_features.cpp @@ -10,34 +10,32 @@ #include #include #include +#include #include #include #include -#include "common/bit_util.h" -#include "common/common_types.h" -#include "common/logging.h" -#include "common/x64/cpu_detect.h" -#include "common/x64/rdtsc.h" - #ifdef _WIN32 #include -#endif - -#ifdef _MSC_VER -#include - -static inline u64 xgetbv(u32 index) { - return _xgetbv(index); -} -#else - -#if defined(__DragonFly__) || defined(__FreeBSD__) -// clang-format off +#elif defined(__DragonFly__) || defined(__FreeBSD__) #include #include -// clang-format on +#elif defined(__ANDROID__) +#include #endif +#include "common/steady_clock.h" +#include "common/uint128.h" +#include "common/bit_util.h" +#include "common/common_types.h" +#include "common/cpu_features.h" +#include "common/logging.h" + +#ifdef ARCHITECTURE_x86_64 +#include "common/x64/rdtsc.h" +#ifdef _MSC_VER +#include +static inline u64 xgetbv(u32 index) { return _xgetbv(index); } +#else static inline void __cpuidex(int info[4], u32 function_id, u32 subfunction_id) { #if defined(__DragonFly__) || defined(__FreeBSD__) // Despite the name, this is just do_cpuid() with ECX as second input. @@ -50,11 +48,7 @@ static inline void __cpuidex(int info[4], u32 function_id, u32 subfunction_id) { : "a"(function_id), "c"(subfunction_id)); #endif } - -static inline void __cpuid(int info[4], u32 function_id) { - return __cpuidex(info, function_id, 0); -} - +static inline void __cpuid(int info[4], u32 function_id) { return __cpuidex(info, function_id, 0); } #define _XCR_XFEATURE_ENABLED_MASK 0 static inline u64 xgetbv(u32 index) { u32 eax, edx; @@ -62,9 +56,10 @@ static inline u64 xgetbv(u32 index) { return ((u64)edx << 32) | eax; } #endif // _MSC_VER +#endif namespace Common { - +#ifdef ARCHITECTURE_x86_64 CPUCaps::Manufacturer CPUCaps::ParseManufacturer(std::string_view brand_string) { if (brand_string == "GenuineIntel") { return Manufacturer::Intel; @@ -76,142 +71,6 @@ CPUCaps::Manufacturer CPUCaps::ParseManufacturer(std::string_view brand_string) return Manufacturer::Unknown; } -// Detects the various CPU features -static CPUCaps Detect() { - CPUCaps caps = {}; - - // Assumes the CPU supports the CPUID instruction. Those that don't would likely not support - // yuzu at all anyway - - int cpu_id[4]; - - // Detect CPU's CPUID capabilities and grab manufacturer string - __cpuid(cpu_id, 0x00000000); - const u32 max_std_fn = cpu_id[0]; // EAX - - std::memset(caps.brand_string, 0, std::size(caps.brand_string)); - std::memcpy(&caps.brand_string[0], &cpu_id[1], sizeof(u32)); - std::memcpy(&caps.brand_string[4], &cpu_id[3], sizeof(u32)); - std::memcpy(&caps.brand_string[8], &cpu_id[2], sizeof(u32)); - - caps.manufacturer = CPUCaps::ParseManufacturer(caps.brand_string); - - // Set reasonable default cpu string even if brand string not available - std::strncpy(caps.cpu_string, caps.brand_string, std::size(caps.brand_string)); - - __cpuid(cpu_id, 0x80000000); - - const u32 max_ex_fn = cpu_id[0]; - - // Detect family and other miscellaneous features - if (max_std_fn >= 1) { - __cpuid(cpu_id, 0x00000001); - caps.sse = Common::Bit<25>(cpu_id[3]); - caps.sse2 = Common::Bit<26>(cpu_id[3]); - caps.sse3 = Common::Bit<0>(cpu_id[2]); - caps.pclmulqdq = Common::Bit<1>(cpu_id[2]); - caps.ssse3 = Common::Bit<9>(cpu_id[2]); - caps.sse4_1 = Common::Bit<19>(cpu_id[2]); - caps.sse4_2 = Common::Bit<20>(cpu_id[2]); - caps.movbe = Common::Bit<22>(cpu_id[2]); - caps.popcnt = Common::Bit<23>(cpu_id[2]); - caps.aes = Common::Bit<25>(cpu_id[2]); - caps.f16c = Common::Bit<29>(cpu_id[2]); - - // AVX support requires 3 separate checks: - // - Is the AVX bit set in CPUID? - // - Is the XSAVE bit set in CPUID? - // - XGETBV result has the XCR bit set. - if (Common::Bit<28>(cpu_id[2]) && Common::Bit<27>(cpu_id[2])) { - if ((xgetbv(_XCR_XFEATURE_ENABLED_MASK) & 0x6) == 0x6) { - caps.avx = true; - if (Common::Bit<12>(cpu_id[2])) - caps.fma = true; - } - } - - if (max_std_fn >= 7) { - __cpuidex(cpu_id, 0x00000007, 0x00000000); - // Can't enable AVX{2,512} unless the XSAVE/XGETBV checks above passed - if (caps.avx) { - caps.avx2 = Common::Bit<5>(cpu_id[1]); - caps.avx512f = Common::Bit<16>(cpu_id[1]); - caps.avx512dq = Common::Bit<17>(cpu_id[1]); - caps.avx512cd = Common::Bit<28>(cpu_id[1]); - caps.avx512bw = Common::Bit<30>(cpu_id[1]); - caps.avx512vl = Common::Bit<31>(cpu_id[1]); - caps.avx512vbmi = Common::Bit<1>(cpu_id[2]); - caps.avx512bitalg = Common::Bit<12>(cpu_id[2]); - } - - caps.bmi1 = Common::Bit<3>(cpu_id[1]); - caps.bmi2 = Common::Bit<8>(cpu_id[1]); - caps.sha = Common::Bit<29>(cpu_id[1]); - - caps.waitpkg = Common::Bit<5>(cpu_id[2]); - caps.gfni = Common::Bit<8>(cpu_id[2]); - - __cpuidex(cpu_id, 0x00000007, 0x00000001); - caps.avx_vnni = caps.avx && Common::Bit<4>(cpu_id[0]); - } - } - - if (max_ex_fn >= 0x80000004) { - // Extract CPU model string - __cpuid(cpu_id, 0x80000002); - std::memcpy(caps.cpu_string, cpu_id, sizeof(cpu_id)); - __cpuid(cpu_id, 0x80000003); - std::memcpy(caps.cpu_string + 16, cpu_id, sizeof(cpu_id)); - __cpuid(cpu_id, 0x80000004); - std::memcpy(caps.cpu_string + 32, cpu_id, sizeof(cpu_id)); - } - - if (max_ex_fn >= 0x80000001) { - // Check for more features - __cpuid(cpu_id, 0x80000001); - caps.lzcnt = Common::Bit<5>(cpu_id[2]); - caps.fma4 = Common::Bit<16>(cpu_id[2]); - caps.monitorx = Common::Bit<29>(cpu_id[2]); - } - - if (max_ex_fn >= 0x80000007) { - __cpuid(cpu_id, 0x80000007); - caps.invariant_tsc = Common::Bit<8>(cpu_id[3]); - } - - if (max_std_fn >= 0x15) { - __cpuid(cpu_id, 0x15); - caps.tsc_crystal_ratio_denominator = cpu_id[0]; - caps.tsc_crystal_ratio_numerator = cpu_id[1]; - caps.crystal_frequency = cpu_id[2]; - // Some CPU models might not return a crystal frequency. - // The CPU model can be detected to use the values from turbostat - // https://github.com/torvalds/linux/blob/master/tools/power/x86/turbostat/turbostat.c#L5569 - // but it's easier to just estimate the TSC tick rate for these cases. - if (caps.tsc_crystal_ratio_denominator) { - caps.tsc_frequency = static_cast(caps.crystal_frequency) * - caps.tsc_crystal_ratio_numerator / - caps.tsc_crystal_ratio_denominator; - } else { - caps.tsc_frequency = X64::EstimateRDTSCFrequency(); - } - } - - if (max_std_fn >= 0x16) { - __cpuid(cpu_id, 0x16); - caps.base_frequency = cpu_id[0]; - caps.max_frequency = cpu_id[1]; - caps.bus_frequency = cpu_id[2]; - } - - return caps; -} - -const CPUCaps& GetCPUCaps() { - static CPUCaps caps = Detect(); - return caps; -} - std::optional GetProcessorCount() { #if defined(_WIN32) // Get the buffer length. @@ -253,4 +112,316 @@ std::optional GetProcessorCount() { #endif } +/// @brief Detects the various CPU features +const CPUCaps g_cpu_caps = [] { + CPUCaps caps = {}; + + // Assumes the CPU supports the CPUID instruction. Those that don't would likely not support + // yuzu at all anyway + int cpu_id[4]; + + // Detect CPU's CPUID capabilities and grab manufacturer string + __cpuid(cpu_id, 0x00000000); + const u32 max_std_fn = cpu_id[0]; // EAX + + std::memset(caps.brand_string, 0, std::size(caps.brand_string)); + std::memcpy(&caps.brand_string[0], &cpu_id[1], sizeof(u32)); + std::memcpy(&caps.brand_string[4], &cpu_id[3], sizeof(u32)); + std::memcpy(&caps.brand_string[8], &cpu_id[2], sizeof(u32)); + + caps.manufacturer = CPUCaps::ParseManufacturer(caps.brand_string); + + // Set reasonable default cpu string even if brand string not available + std::strncpy(caps.cpu_string, caps.brand_string, std::size(caps.brand_string)); + + __cpuid(cpu_id, 0x80000000); + + const u32 max_ex_fn = cpu_id[0]; + + // Detect family and other miscellaneous features + if (max_std_fn >= 1) { + __cpuid(cpu_id, 0x00000001); + caps.sse3 = Common::Bit<0>(cpu_id[2]); + caps.pclmulqdq = Common::Bit<1>(cpu_id[2]); + caps.ssse3 = Common::Bit<9>(cpu_id[2]); + caps.sse4_1 = Common::Bit<19>(cpu_id[2]); + caps.sse4_2 = Common::Bit<20>(cpu_id[2]); + caps.movbe = Common::Bit<22>(cpu_id[2]); + caps.popcnt = Common::Bit<23>(cpu_id[2]); + caps.aes = Common::Bit<25>(cpu_id[2]); + caps.f16c = Common::Bit<29>(cpu_id[2]); + + // AVX support requires 3 separate checks: + // - Is the AVX bit set in CPUID? + // - Is the XSAVE bit set in CPUID? + // - XGETBV result has the XCR bit set. + if (Common::Bit<28>(cpu_id[2]) && Common::Bit<27>(cpu_id[2])) { + if ((xgetbv(_XCR_XFEATURE_ENABLED_MASK) & 0x6) == 0x6) { + caps.avx = true; + if (Common::Bit<12>(cpu_id[2])) + caps.fma = true; + } + } + + if (max_std_fn >= 7) { + __cpuidex(cpu_id, 0x00000007, 0x00000000); + // Can't enable AVX{2,512} unless the XSAVE/XGETBV checks above passed + if (caps.avx) { + caps.avx2 = Common::Bit<5>(cpu_id[1]); + caps.avx512f = Common::Bit<16>(cpu_id[1]); + caps.avx512dq = Common::Bit<17>(cpu_id[1]); + caps.avx512cd = Common::Bit<28>(cpu_id[1]); + caps.avx512bw = Common::Bit<30>(cpu_id[1]); + caps.avx512vl = Common::Bit<31>(cpu_id[1]); + caps.avx512vbmi = Common::Bit<1>(cpu_id[2]); + caps.avx512bitalg = Common::Bit<12>(cpu_id[2]); + } + + caps.bmi1 = Common::Bit<3>(cpu_id[1]); + caps.bmi2 = Common::Bit<8>(cpu_id[1]); + caps.sha = Common::Bit<29>(cpu_id[1]); + + caps.waitpkg = Common::Bit<5>(cpu_id[2]); + caps.gfni = Common::Bit<8>(cpu_id[2]); + } + } + + if (max_ex_fn >= 0x80000004) { + // Extract CPU model string + __cpuid(cpu_id, 0x80000002); + std::memcpy(caps.cpu_string, cpu_id, sizeof(cpu_id)); + __cpuid(cpu_id, 0x80000003); + std::memcpy(caps.cpu_string + 16, cpu_id, sizeof(cpu_id)); + __cpuid(cpu_id, 0x80000004); + std::memcpy(caps.cpu_string + 32, cpu_id, sizeof(cpu_id)); + } + + if (max_ex_fn >= 0x80000001) { + // Check for more features + __cpuid(cpu_id, 0x80000001); + caps.lzcnt = Common::Bit<5>(cpu_id[2]); + caps.monitorx = Common::Bit<29>(cpu_id[2]); + } + + if (max_ex_fn >= 0x80000007) { + __cpuid(cpu_id, 0x80000007); + caps.invariant_tsc = Common::Bit<8>(cpu_id[3]); + } + + if (max_std_fn >= 0x15) { + __cpuid(cpu_id, 0x15); + caps.tsc_crystal_ratio_denominator = cpu_id[0]; + caps.tsc_crystal_ratio_numerator = cpu_id[1]; + caps.crystal_frequency = cpu_id[2]; + // Some CPU models might not return a crystal frequency. + // The CPU model can be detected to use the values from turbostat + // https://github.com/torvalds/linux/blob/master/tools/power/x86/turbostat/turbostat.c#L5569 + // but it's easier to just estimate the TSC tick rate for these cases. + if (caps.tsc_crystal_ratio_denominator) { + caps.tsc_frequency = u64(caps.crystal_frequency) + * caps.tsc_crystal_ratio_numerator / caps.tsc_crystal_ratio_denominator; + } else { + caps.tsc_frequency = X64::EstimateRDTSCFrequency(); + } + } + + if (max_std_fn >= 0x16) { + __cpuid(cpu_id, 0x16); + caps.base_frequency = cpu_id[0]; + caps.max_frequency = cpu_id[1]; + caps.bus_frequency = cpu_id[2]; + } + return caps; +}(); + +#else + +#endif + +#if defined(ARCHITECTURE_x86_64) +WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept + : rdtsc_frequency{rdtsc_frequency_} + , ns_rdtsc_factor{invariant_ ? GetFixedPoint64Factor(NsRatio::den, rdtsc_frequency_) : 0} + , us_rdtsc_factor{invariant_ ? GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency_) : 0} + , ms_rdtsc_factor{invariant_ ? GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency_) : 0} + , rdtsc_ns_integer{invariant_ ? rdtsc_frequency_ / NsRatio::den : 1} + , rdtsc_ns_factor{invariant_ ? GetFixedPoint64Factor(rdtsc_frequency_ % NsRatio::den, NsRatio::den) : 0} + , cntpct_rdtsc_factor{invariant_ ? GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency_) : 0} + , gputick_rdtsc_factor{invariant_ ? GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency_) : 0} + , invariant{invariant_} +{} + +std::chrono::nanoseconds WallClock::GetTimeNS() const { + if (!invariant) + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()); + return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_rdtsc_factor)}; +} + +std::chrono::microseconds WallClock::GetTimeUS() const { + if (!invariant) + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()); + return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_rdtsc_factor)}; +} + +std::chrono::milliseconds WallClock::GetTimeMS() const { + if (!invariant) + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()); + return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_rdtsc_factor)}; +} + +s64 WallClock::GetCNTPCT() const { + if (!invariant) + return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den; + return MultiplyHigh(GetUptime(), cntpct_rdtsc_factor); +} + +s64 WallClock::GetGPUTick() const { + if (!invariant) + return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den; + return MultiplyHigh(GetUptime(), gputick_rdtsc_factor); +} + +s64 WallClock::GetUptime() const { + if (!invariant) + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); + return s64(Common::X64::FencedRDTSC()); +} + +bool WallClock::IsNative() const { + return invariant; +} + +u64 WallClock::NsToTicks(std::chrono::nanoseconds ns) const { + return ns.count() * rdtsc_ns_integer + MultiplyHigh(ns.count(), rdtsc_ns_factor); +} +#elif defined(HAS_NCE) +namespace { +[[nodiscard]] Common::WallClock::FactorType GetFixedPointFactor(u64 num, u64 den) noexcept { + return (Common::WallClock::FactorType(num) << 64) / den; +} +[[nodiscard]] u64 MultiplyHigh(u64 m, Common::WallClock::FactorType factor) noexcept { + return static_cast((m * factor) >> 64); +} +[[nodiscard]] s64 GetHostCNTFRQ() noexcept { + u64 cntfrq_el0 = 0; +#ifdef __ANDROID__ + std::string_view board{""}; + char buffer[PROP_VALUE_MAX]; + int len{__system_property_get("ro.product.board", buffer)}; + board = std::string_view(buffer, static_cast(len)); + if (board == "s5e9925") { // Exynos 2200 + cntfrq_el0 = 25600000; + } else if (board == "exynos2100") { // Exynos 2100 + cntfrq_el0 = 26000000; + } else if (board == "exynos9810") { // Exynos 9810 + cntfrq_el0 = 26000000; + } else if (board == "s5e8825") { // Exynos 1280 + cntfrq_el0 = 26000000; + } else { + asm volatile("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0)); + } + return cntfrq_el0; +#else + asm volatile("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0)); + return cntfrq_el0; +#endif +} +} // namespace + +WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept { + const u64 host_cntfrq = std::max(GetHostCNTFRQ(), 1); + ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq); + us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq); + ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq); + cntfrq_ns_factor = GetFixedPointFactor(host_cntfrq, NsRatio::den); + guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq); + gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq); +} + +std::chrono::nanoseconds WallClock::GetTimeNS() const { + return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_cntfrq_factor)}; +} + +std::chrono::microseconds WallClock::GetTimeUS() const { + return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_cntfrq_factor)}; +} + +std::chrono::milliseconds WallClock::GetTimeMS() const { + return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_cntfrq_factor)}; +} + +s64 WallClock::GetCNTPCT() const { + return MultiplyHigh(GetUptime(), guest_cntfrq_factor); +} + +s64 WallClock::GetGPUTick() const { + return MultiplyHigh(GetUptime(), gputick_cntfrq_factor); +} + +s64 WallClock::GetUptime() const { + s64 cntvct_el0 = 0; + asm volatile( + "dsb ish\n\t" + "mrs %[cntvct_el0], cntvct_el0\n\t" + "dsb ish\n\t" + : [cntvct_el0] "=r"(cntvct_el0) + ); + return cntvct_el0; +} + +bool WallClock::IsNative() const { + return true; +} + +u64 WallClock::NsToTicks(std::chrono::nanoseconds ns) const { + return MultiplyHigh(ns.count(), cntfrq_ns_factor); +} +#else +WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept {} + +std::chrono::nanoseconds WallClock::GetTimeNS() const { + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()); +} + +std::chrono::microseconds WallClock::GetTimeUS() const { + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()); +} + +std::chrono::milliseconds WallClock::GetTimeMS() const { + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()); +} + +s64 WallClock::GetCNTPCT() const { + return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den; +} + +s64 WallClock::GetGPUTick() const { + return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den; +} + +s64 WallClock::GetUptime() const { + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count(); +} + +bool WallClock::IsNative() const { + return false; +} + +u64 WallClock::NsToTicks(std::chrono::nanoseconds ns) const { + return ns.count(); +} +#endif + +// Wall clock MUST be initialized AFTER g_cpu_caps +// C++ only guarantees ctor init in the order they appear in TU +const WallClock g_wall_clock = [] { +#if defined(ARCHITECTURE_x86_64) + auto const& caps = Common::g_cpu_caps; + return WallClock(caps.invariant_tsc && caps.tsc_frequency > std::nano::den, caps.tsc_frequency); +#elif defined(HAS_NCE) + return WallClock(false, 1); +#else + return WallClock(true, 1); +#endif +}(); } // namespace Common diff --git a/src/common/cpu_features.h b/src/common/cpu_features.h new file mode 100644 index 0000000000..61aa2767ab --- /dev/null +++ b/src/common/cpu_features.h @@ -0,0 +1,188 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2013 Dolphin Emulator Project / 2015 Citra Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "common/common_types.h" + +namespace Common { + +class WallClock { +public: + static constexpr u64 CNTFRQ = 19'200'000; // CNTPCT_EL0 Frequency = 19.2 MHz + static constexpr u64 GPUTickFreq = 614'400'000; // GM20B GPU Tick Frequency = 614.4 MHz + static constexpr u64 CPUTickFreq = 1'020'000'000; // T210/4 A57 CPU Tick Frequency = 1020.0 MHz + + explicit WallClock(bool invariant, u64 rdtsc_frequency_) noexcept; + + /// @returns The time in nanoseconds since the construction of this clock. + std::chrono::nanoseconds GetTimeNS() const; + + /// @returns The time in microseconds since the construction of this clock. + std::chrono::microseconds GetTimeUS() const; + + /// @returns The time in milliseconds since the construction of this clock. + std::chrono::milliseconds GetTimeMS() const; + + /// @returns The guest CNTPCT ticks since the construction of this clock. + s64 GetCNTPCT() const; + + /// @returns The guest GPU ticks since the construction of this clock. + s64 GetGPUTick() const; + + /// @returns The raw host timer ticks since an indeterminate epoch. + s64 GetUptime() const; + + /// @returns Whether the clock directly uses the host's hardware clock. + bool IsNative() const; + + // @returns Nanoseconds to native ticks + u64 NsToTicks(std::chrono::nanoseconds ns) const; + + static inline u64 NSToCNTPCT(u64 ns) { + return ns * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den; + } + + static inline u64 NSToGPUTick(u64 ns) { + return ns * NsToGPUTickRatio::num / NsToGPUTickRatio::den; + } + + // Cycle Timing + + static inline u64 CPUTickToNS(u64 cpu_tick) { + return cpu_tick * CPUTickToNsRatio::num / CPUTickToNsRatio::den; + } + + static inline u64 CPUTickToUS(u64 cpu_tick) { + return cpu_tick * CPUTickToUsRatio::num / CPUTickToUsRatio::den; + } + + static inline u64 CPUTickToCNTPCT(u64 cpu_tick) { + return cpu_tick * CPUTickToCNTPCTRatio::num / CPUTickToCNTPCTRatio::den; + } + + static inline u64 CPUTickToGPUTick(u64 cpu_tick) { + return cpu_tick * CPUTickToGPUTickRatio::num / CPUTickToGPUTickRatio::den; + } + + using NsRatio = std::nano; + using UsRatio = std::micro; + using MsRatio = std::milli; + + using NsToUsRatio = std::ratio_divide; + using NsToMsRatio = std::ratio_divide; + using NsToCNTPCTRatio = std::ratio; + using NsToGPUTickRatio = std::ratio; + + // Cycle Timing + + using CPUTickToNsRatio = std::ratio; + using CPUTickToUsRatio = std::ratio; + using CPUTickToCNTPCTRatio = std::ratio; + using CPUTickToGPUTickRatio = std::ratio; + +#if defined(ARCHITECTURE_x86_64) + u64 rdtsc_frequency; + u64 ns_rdtsc_factor; + u64 us_rdtsc_factor; + u64 ms_rdtsc_factor; + u64 rdtsc_ns_integer; + u64 rdtsc_ns_factor; + u64 cntpct_rdtsc_factor; + u64 gputick_rdtsc_factor; + bool invariant; +#elif defined(HAS_NCE) + using FactorType = unsigned __int128; + [[nodiscard]] inline FactorType GetGuestCNTFRQFactor() const { + return guest_cntfrq_factor; + } + FactorType ns_cntfrq_factor; + FactorType us_cntfrq_factor; + FactorType ms_cntfrq_factor; + FactorType cntfrq_ns_factor; + FactorType guest_cntfrq_factor; + FactorType gputick_cntfrq_factor; +#endif +}; + +#ifdef ARCHITECTURE_x86_64 +/// x86/x64 CPU capabilities that may be detected by this module +struct CPUCaps { + enum class Manufacturer : u8 { + Unknown = 0, + Intel = 1, + AMD = 2, + Hygon = 3, + }; + + static Manufacturer ParseManufacturer(std::string_view brand_string); + + Manufacturer manufacturer; + char brand_string[13]; + + char cpu_string[48]; + + u32 base_frequency; + u32 max_frequency; + u32 bus_frequency; + + u32 tsc_crystal_ratio_denominator; + u32 tsc_crystal_ratio_numerator; + u32 crystal_frequency; + u64 tsc_frequency; // Derived from the above three values + + bool sse3 : 1; + bool ssse3 : 1; + bool sse4_1 : 1; + bool sse4_2 : 1; + + bool avx : 1; + bool avx2 : 1; + bool avx512f : 1; + bool avx512dq : 1; + bool avx512cd : 1; + bool avx512bw : 1; + bool avx512vl : 1; + bool avx512vbmi : 1; + bool avx512bitalg : 1; + + bool aes : 1; + bool bmi1 : 1; + bool bmi2 : 1; + bool f16c : 1; + bool fma : 1; + bool gfni : 1; + bool invariant_tsc : 1; + bool lzcnt : 1; + bool monitorx : 1; + bool movbe : 1; + bool pclmulqdq : 1; + bool popcnt : 1; + bool sha : 1; + bool waitpkg : 1; +}; +#else +struct CPUCaps { + bool padding; +}; +#endif + +/// Detects CPU core count +std::optional GetProcessorCount(); + +/// @brief Global cpu caps +extern const CPUCaps g_cpu_caps; +/// @brief Global wall clock +extern const WallClock g_wall_clock; + +} // namespace Common diff --git a/src/common/demangle.cpp b/src/common/demangle.cpp index 8cb8c35515..d5ebc23c0a 100644 --- a/src/common/demangle.cpp +++ b/src/common/demangle.cpp @@ -1,35 +1,50 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include +#ifdef _WIN32 #include - +#else +#include +#endif #include "common/demangle.h" -#include "common/scope_exit.h" + +static bool IsItanium(std::string_view name) { + // A valid Itanium encoding requires 1-4 leading underscores, followed by 'Z'. + auto const pos = name.find_first_not_of('_'); + return pos > 0 && pos <= 4 && pos < name.size() && name[pos] == 'Z'; +} namespace Common { - std::string DemangleSymbol(const std::string& mangled) { if (mangled.size() > 0) { - auto const is_itanium = [](std::string_view name) -> bool { - // A valid Itanium encoding requires 1-4 leading underscores, followed by 'Z'. - auto const pos = name.find_first_not_of('_'); - return pos > 0 && pos <= 4 && pos < name.size() && name[pos] == 'Z'; - }; - std::string ret = mangled; - if (is_itanium(mangled)) { + if (IsItanium(mangled)) { +#ifdef _WIN32 + // requires the use of llvm if (char* p = llvm::itaniumDemangle(mangled); p != nullptr) { - ret = std::string{p}; + std::string ret = std::string{p}; std::free(p); + return ret; } +#else + // can safely use libc++ and glibcxx provided demangling functions + // it's available since 2008(!) so no system should have issues with it + // see https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html + int status; + if (char* p = abi::__cxa_demangle(mangled.c_str(), NULL, NULL, &status); p != nullptr) { + std::string ret = std::string{p}; + std::free(p); + return ret; + } +#endif } - return ret; + return mangled; } return std::string{}; } - } // namespace Common diff --git a/src/common/demangle.h b/src/common/demangle.h index f072d22f33..c96f14ff42 100644 --- a/src/common/demangle.h +++ b/src/common/demangle.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/common/detached_tasks.cpp b/src/common/detached_tasks.cpp deleted file mode 100644 index f2ed795cc2..0000000000 --- a/src/common/detached_tasks.cpp +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include "common/assert.h" -#include "common/detached_tasks.h" - -namespace Common { - -DetachedTasks* DetachedTasks::instance = nullptr; - -DetachedTasks::DetachedTasks() { - ASSERT(instance == nullptr); - instance = this; -} - -void DetachedTasks::WaitForAllTasks() { - std::unique_lock lock{mutex}; - cv.wait(lock, [this]() { return count == 0; }); -} - -DetachedTasks::~DetachedTasks() { - WaitForAllTasks(); - - std::unique_lock lock{mutex}; - ASSERT(count == 0); - instance = nullptr; -} - -void DetachedTasks::AddTask(std::function task) { - std::unique_lock lock{instance->mutex}; - ++instance->count; - std::thread([task_{std::move(task)}]() { - task_(); - std::unique_lock thread_lock{instance->mutex}; - --instance->count; - std::notify_all_at_thread_exit(instance->cv, std::move(thread_lock)); - }).detach(); -} - -} // namespace Common diff --git a/src/common/detached_tasks.h b/src/common/detached_tasks.h deleted file mode 100644 index 416a2d7f3d..0000000000 --- a/src/common/detached_tasks.h +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -namespace Common { - -/** - * A background manager which ensures that all detached task is finished before program exits. - * - * Some tasks, telemetry submission for example, prefer executing asynchronously and don't care - * about the result. These tasks are suitable for std::thread::detach(). However, this is unsafe if - * the task is launched just before the program exits (which is a common case for telemetry), so we - * need to block on these tasks on program exit. - * - * To make detached task safe, a single DetachedTasks object should be placed in the main(), and - * call WaitForAllTasks() after all program execution but before global/static variable destruction. - * Any potentially unsafe detached task should be executed via DetachedTasks::AddTask. - */ -class DetachedTasks { -public: - DetachedTasks(); - ~DetachedTasks(); - void WaitForAllTasks(); - - static void AddTask(std::function task); - -private: - static DetachedTasks* instance; - - std::condition_variable cv; - std::mutex mutex; - int count = 0; -}; - -} // namespace Common diff --git a/src/common/dynamic_library.cpp b/src/common/dynamic_library.cpp index 566fbed631..462faa673d 100644 --- a/src/common/dynamic_library.cpp +++ b/src/common/dynamic_library.cpp @@ -1,7 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2019 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include + +#ifndef _WIN32 +#include +#endif + #include #include diff --git a/src/common/error.cpp b/src/common/error.cpp index 1b2009db71..1d67dad6a9 100644 --- a/src/common/error.cpp +++ b/src/common/error.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2013 Dolphin Emulator Project // SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -14,35 +17,33 @@ namespace Common { +// glibc, mlibc, musl, and newlib all define their own variants of strerror_r +// We don't need to use the preprocessor, we can just select depending on return type +template std::string HandleStrerrorR(T r, char *err_str); +template<> std::string HandleStrerrorR(char* r, char *) { return std::string{r}; } +template<> std::string HandleStrerrorR(const char* r, char *) { return std::string{r}; } +template<> std::string HandleStrerrorR(int r, char *err_str) { + return std::string{r != 0 + ? "(strerror_r failed to format error)" + : err_str}; +} + std::string NativeErrorToString(int e) { #ifdef _WIN32 LPSTR err_str; - DWORD res = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, e, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(&err_str), 1, nullptr); - if (!res) { - return "(FormatMessageA failed to format error)"; + LPSTR(&err_str), 1, nullptr); + if (res) { + std::string ret(err_str); + LocalFree(err_str); + return ret; } - std::string ret(err_str); - LocalFree(err_str); - return ret; + return "(FormatMessageA failed to format error)"; #else char err_str[255]; -#if defined(ANDROID) || \ - (defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600))) - // Thread safe (GNU-specific) - const char* str = strerror_r(e, err_str, sizeof(err_str)); - return std::string(str); -#else - // Thread safe (XSI-compliant) - int second_err = strerror_r(e, err_str, sizeof(err_str)); - if (second_err != 0) { - return "(strerror_r failed to format error)"; - } - return std::string(err_str); -#endif // GLIBC etc. + return HandleStrerrorR(strerror_r(e, err_str, sizeof(err_str)), err_str); #endif // _WIN32 } diff --git a/src/common/expected.h b/src/common/expected.h deleted file mode 100644 index 5fccfbcbdd..0000000000 --- a/src/common/expected.h +++ /dev/null @@ -1,986 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -// This is based on the proposed implementation of std::expected (P0323) -// https://github.com/TartanLlama/expected/blob/master/include/tl/expected.hpp - -#pragma once - -#include -#include - -namespace Common { - -template -class Expected; - -template -class Unexpected { -public: - Unexpected() = delete; - - constexpr explicit Unexpected(const E& e) : m_val{e} {} - - constexpr explicit Unexpected(E&& e) : m_val{std::move(e)} {} - - constexpr E& value() & { - return m_val; - } - - constexpr const E& value() const& { - return m_val; - } - - constexpr E&& value() && { - return std::move(m_val); - } - - constexpr const E&& value() const&& { - return std::move(m_val); - } - -private: - E m_val; -}; - -template -constexpr auto operator<=>(const Unexpected& lhs, const Unexpected& rhs) { - return lhs.value() <=> rhs.value(); -} - -struct unexpect_t { - constexpr explicit unexpect_t() = default; -}; - -namespace detail { - -struct no_init_t { - constexpr explicit no_init_t() = default; -}; - -/** - * This specialization is for when T is not trivially destructible, - * so the destructor must be called on destruction of `expected' - * Additionally, this requires E to be trivially destructible - */ -template > - requires std::is_trivially_destructible_v -struct expected_storage_base { - constexpr expected_storage_base() : m_val{T{}}, m_has_val{true} {} - - constexpr expected_storage_base(no_init_t) : m_has_val{false} {} - - template >* = nullptr> - constexpr expected_storage_base(std::in_place_t, Args&&... args) - : m_val{std::forward(args)...}, m_has_val{true} {} - - template &, Args&&...>>* = - nullptr> - constexpr expected_storage_base(std::in_place_t, std::initializer_list il, Args&&... args) - : m_val{il, std::forward(args)...}, m_has_val{true} {} - - template >* = nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args&&... args) - : m_unexpect{std::forward(args)...}, m_has_val{false} {} - - template &, Args&&...>>* = - nullptr> - constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, - Args&&... args) - : m_unexpect{il, std::forward(args)...}, m_has_val{false} {} - - ~expected_storage_base() { - if (m_has_val) { - m_val.~T(); - } - } - - union { - T m_val; - Unexpected m_unexpect; - }; - - bool m_has_val; -}; - -/** - * This specialization is for when T is trivially destructible, - * so the destructor of `expected` can be trivial - * Additionally, this requires E to be trivially destructible - */ -template - requires std::is_trivially_destructible_v -struct expected_storage_base { - constexpr expected_storage_base() : m_val{T{}}, m_has_val{true} {} - - constexpr expected_storage_base(no_init_t) : m_has_val{false} {} - - template >* = nullptr> - constexpr expected_storage_base(std::in_place_t, Args&&... args) - : m_val{std::forward(args)...}, m_has_val{true} {} - - template &, Args&&...>>* = - nullptr> - constexpr expected_storage_base(std::in_place_t, std::initializer_list il, Args&&... args) - : m_val{il, std::forward(args)...}, m_has_val{true} {} - - template >* = nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args&&... args) - : m_unexpect{std::forward(args)...}, m_has_val{false} {} - - template &, Args&&...>>* = - nullptr> - constexpr explicit expected_storage_base(unexpect_t, std::initializer_list il, - Args&&... args) - : m_unexpect{il, std::forward(args)...}, m_has_val{false} {} - - ~expected_storage_base() = default; - - union { - T m_val; - Unexpected m_unexpect; - }; - - bool m_has_val; -}; - -template -struct expected_operations_base : expected_storage_base { - using expected_storage_base::expected_storage_base; - - template - void construct(Args&&... args) noexcept { - new (std::addressof(this->m_val)) T{std::forward(args)...}; - this->m_has_val = true; - } - - template - void construct_with(Rhs&& rhs) noexcept { - new (std::addressof(this->m_val)) T{std::forward(rhs).get()}; - this->m_has_val = true; - } - - template - void construct_error(Args&&... args) noexcept { - new (std::addressof(this->m_unexpect)) Unexpected{std::forward(args)...}; - this->m_has_val = false; - } - - void assign(const expected_operations_base& rhs) noexcept { - if (!this->m_has_val && rhs.m_has_val) { - geterr().~Unexpected(); - construct(rhs.get()); - } else { - assign_common(rhs); - } - } - - void assign(expected_operations_base&& rhs) noexcept { - if (!this->m_has_val && rhs.m_has_val) { - geterr().~Unexpected(); - construct(std::move(rhs).get()); - } else { - assign_common(rhs); - } - } - - template - void assign_common(Rhs&& rhs) { - if (this->m_has_val) { - if (rhs.m_has_val) { - get() = std::forward(rhs).get(); - } else { - destroy_val(); - construct_error(std::forward(rhs).geterr()); - } - } else { - if (!rhs.m_has_val) { - geterr() = std::forward(rhs).geterr(); - } - } - } - - bool has_value() const { - return this->m_has_val; - } - - constexpr T& get() & { - return this->m_val; - } - - constexpr const T& get() const& { - return this->m_val; - } - - constexpr T&& get() && { - return std::move(this->m_val); - } - - constexpr const T&& get() const&& { - return std::move(this->m_val); - } - - constexpr Unexpected& geterr() & { - return this->m_unexpect; - } - - constexpr const Unexpected& geterr() const& { - return this->m_unexpect; - } - - constexpr Unexpected&& geterr() && { - return std::move(this->m_unexpect); - } - - constexpr const Unexpected&& geterr() const&& { - return std::move(this->m_unexpect); - } - - constexpr void destroy_val() { - get().~T(); - } -}; - -/** - * This manages conditionally having a trivial copy constructor - * This specialization is for when T is trivially copy constructible - * Additionally, this requires E to be trivially copy constructible - */ -template > - requires std::is_trivially_copy_constructible_v -struct expected_copy_base : expected_operations_base { - using expected_operations_base::expected_operations_base; -}; - -/** - * This specialization is for when T is not trivially copy constructible - * Additionally, this requires E to be trivially copy constructible - */ -template - requires std::is_trivially_copy_constructible_v -struct expected_copy_base : expected_operations_base { - using expected_operations_base::expected_operations_base; - - expected_copy_base() = default; - - expected_copy_base(const expected_copy_base& rhs) - : expected_operations_base{no_init_t{}} { - if (rhs.has_value()) { - this->construct_with(rhs); - } else { - this->construct_error(rhs.geterr()); - } - } - - expected_copy_base(expected_copy_base&&) = default; - - expected_copy_base& operator=(const expected_copy_base&) = default; - - expected_copy_base& operator=(expected_copy_base&&) = default; -}; - -/** - * This manages conditionally having a trivial move constructor - * This specialization is for when T is trivially move constructible - * Additionally, this requires E to be trivially move constructible - */ -template > - requires std::is_trivially_move_constructible_v -struct expected_move_base : expected_copy_base { - using expected_copy_base::expected_copy_base; -}; - -/** - * This specialization is for when T is not trivially move constructible - * Additionally, this requires E to be trivially move constructible - */ -template - requires std::is_trivially_move_constructible_v -struct expected_move_base : expected_copy_base { - using expected_copy_base::expected_copy_base; - - expected_move_base() = default; - - expected_move_base(const expected_move_base&) = default; - - expected_move_base(expected_move_base&& rhs) noexcept(std::is_nothrow_move_constructible_v) - : expected_copy_base{no_init_t{}} { - if (rhs.has_value()) { - this->construct_with(std::move(rhs)); - } else { - this->construct_error(std::move(rhs.geterr())); - } - } - - expected_move_base& operator=(const expected_move_base&) = default; - - expected_move_base& operator=(expected_move_base&&) = default; -}; - -/** - * This manages conditionally having a trivial copy assignment operator - * This specialization is for when T is trivially copy assignable - * Additionally, this requires E to be trivially copy assignable - */ -template , - std::is_trivially_copy_constructible, - std::is_trivially_destructible>> - requires std::conjunction_v, - std::is_trivially_copy_constructible, - std::is_trivially_destructible> -struct expected_copy_assign_base : expected_move_base { - using expected_move_base::expected_move_base; -}; - -/** - * This specialization is for when T is not trivially copy assignable - * Additionally, this requires E to be trivially copy assignable - */ -template - requires std::conjunction_v, - std::is_trivially_copy_constructible, - std::is_trivially_destructible> -struct expected_copy_assign_base : expected_move_base { - using expected_move_base::expected_move_base; - - expected_copy_assign_base() = default; - - expected_copy_assign_base(const expected_copy_assign_base&) = default; - - expected_copy_assign_base(expected_copy_assign_base&&) = default; - - expected_copy_assign_base& operator=(const expected_copy_assign_base& rhs) { - this->assign(rhs); - return *this; - } - - expected_copy_assign_base& operator=(expected_copy_assign_base&&) = default; -}; - -/** - * This manages conditionally having a trivial move assignment operator - * This specialization is for when T is trivially move assignable - * Additionally, this requires E to be trivially move assignable - */ -template , - std::is_trivially_move_constructible, - std::is_trivially_destructible>> - requires std::conjunction_v, - std::is_trivially_move_constructible, - std::is_trivially_destructible> -struct expected_move_assign_base : expected_copy_assign_base { - using expected_copy_assign_base::expected_copy_assign_base; -}; - -/** - * This specialization is for when T is not trivially move assignable - * Additionally, this requires E to be trivially move assignable - */ -template - requires std::conjunction_v, - std::is_trivially_move_constructible, - std::is_trivially_destructible> -struct expected_move_assign_base : expected_copy_assign_base { - using expected_copy_assign_base::expected_copy_assign_base; - - expected_move_assign_base() = default; - - expected_move_assign_base(const expected_move_assign_base&) = default; - - expected_move_assign_base(expected_move_assign_base&&) = default; - - expected_move_assign_base& operator=(const expected_move_assign_base&) = default; - - expected_move_assign_base& operator=(expected_move_assign_base&& rhs) noexcept( - std::conjunction_v, - std::is_nothrow_move_assignable>) { - this->assign(std::move(rhs)); - return *this; - } -}; - -/** - * expected_delete_ctor_base will conditionally delete copy and move constructors - * depending on whether T is copy/move constructible - * Additionally, this requires E to be copy/move constructible - */ -template , - bool EnableMove = std::is_move_constructible_v> - requires std::conjunction_v, std::is_move_constructible> -struct expected_delete_ctor_base { - expected_delete_ctor_base() = default; - expected_delete_ctor_base(const expected_delete_ctor_base&) = default; - expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = default; - expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = default; - expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = default; -}; - -template - requires std::conjunction_v, std::is_move_constructible> -struct expected_delete_ctor_base { - expected_delete_ctor_base() = default; - expected_delete_ctor_base(const expected_delete_ctor_base&) = default; - expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = delete; - expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = default; - expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = default; -}; - -template - requires std::conjunction_v, std::is_move_constructible> -struct expected_delete_ctor_base { - expected_delete_ctor_base() = default; - expected_delete_ctor_base(const expected_delete_ctor_base&) = delete; - expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = default; - expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = default; - expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = default; -}; - -template - requires std::conjunction_v, std::is_move_constructible> -struct expected_delete_ctor_base { - expected_delete_ctor_base() = default; - expected_delete_ctor_base(const expected_delete_ctor_base&) = delete; - expected_delete_ctor_base(expected_delete_ctor_base&&) noexcept = delete; - expected_delete_ctor_base& operator=(const expected_delete_ctor_base&) = default; - expected_delete_ctor_base& operator=(expected_delete_ctor_base&&) noexcept = default; -}; - -/** - * expected_delete_assign_base will conditionally delete copy and move assignment operators - * depending on whether T is copy/move constructible + assignable - * Additionally, this requires E to be copy/move constructible + assignable - */ -template < - typename T, typename E, - bool EnableCopy = std::conjunction_v, std::is_copy_assignable>, - bool EnableMove = std::conjunction_v, std::is_move_assignable>> - requires std::conjunction_v, std::is_move_constructible, - std::is_copy_assignable, std::is_move_assignable> -struct expected_delete_assign_base { - expected_delete_assign_base() = default; - expected_delete_assign_base(const expected_delete_assign_base&) = default; - expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default; - expected_delete_assign_base& operator=(const expected_delete_assign_base&) = default; - expected_delete_assign_base& operator=(expected_delete_assign_base&&) noexcept = default; -}; - -template - requires std::conjunction_v, std::is_move_constructible, - std::is_copy_assignable, std::is_move_assignable> -struct expected_delete_assign_base { - expected_delete_assign_base() = default; - expected_delete_assign_base(const expected_delete_assign_base&) = default; - expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default; - expected_delete_assign_base& operator=(const expected_delete_assign_base&) = default; - expected_delete_assign_base& operator=(expected_delete_assign_base&&) noexcept = delete; -}; - -template - requires std::conjunction_v, std::is_move_constructible, - std::is_copy_assignable, std::is_move_assignable> -struct expected_delete_assign_base { - expected_delete_assign_base() = default; - expected_delete_assign_base(const expected_delete_assign_base&) = default; - expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default; - expected_delete_assign_base& operator=(const expected_delete_assign_base&) = delete; - expected_delete_assign_base& operator=(expected_delete_assign_base&&) noexcept = default; -}; - -template - requires std::conjunction_v, std::is_move_constructible, - std::is_copy_assignable, std::is_move_assignable> -struct expected_delete_assign_base { - expected_delete_assign_base() = default; - expected_delete_assign_base(const expected_delete_assign_base&) = default; - expected_delete_assign_base(expected_delete_assign_base&&) noexcept = default; - expected_delete_assign_base& operator=(const expected_delete_assign_base&) = delete; - expected_delete_assign_base& operator=(expected_delete_assign_base&&) noexcept = delete; -}; - -/** - * This is needed to be able to construct the expected_default_ctor_base which follows, - * while still conditionally deleting the default constructor. - */ -struct default_constructor_tag { - constexpr explicit default_constructor_tag() = default; -}; - -/** - * expected_default_ctor_base will ensure that expected - * has a deleted default constructor if T is not default constructible - * This specialization is for when T is default constructible - */ -template > -struct expected_default_ctor_base { - constexpr expected_default_ctor_base() noexcept = default; - constexpr expected_default_ctor_base(expected_default_ctor_base const&) noexcept = default; - constexpr expected_default_ctor_base(expected_default_ctor_base&&) noexcept = default; - expected_default_ctor_base& operator=(expected_default_ctor_base const&) noexcept = default; - expected_default_ctor_base& operator=(expected_default_ctor_base&&) noexcept = default; - - constexpr explicit expected_default_ctor_base(default_constructor_tag) {} -}; - -template -struct expected_default_ctor_base { - constexpr expected_default_ctor_base() noexcept = delete; - constexpr expected_default_ctor_base(expected_default_ctor_base const&) noexcept = default; - constexpr expected_default_ctor_base(expected_default_ctor_base&&) noexcept = default; - expected_default_ctor_base& operator=(expected_default_ctor_base const&) noexcept = default; - expected_default_ctor_base& operator=(expected_default_ctor_base&&) noexcept = default; - - constexpr explicit expected_default_ctor_base(default_constructor_tag) {} -}; - -template -using expected_enable_forward_value = - std::enable_if_t && - !std::is_same_v, std::in_place_t> && - !std::is_same_v, std::remove_cvref_t> && - !std::is_same_v, std::remove_cvref_t>>; - -template -using expected_enable_from_other = std::enable_if_t< - std::is_constructible_v && std::is_constructible_v && - !std::is_constructible_v&> && !std::is_constructible_v&&> && - !std::is_constructible_v&> && - !std::is_constructible_v&&> && - !std::is_convertible_v&, T> && !std::is_convertible_v&&, T> && - !std::is_convertible_v&, T> && - !std::is_convertible_v&&, T>>; - -} // namespace detail - -template -class Expected : private detail::expected_move_assign_base, - private detail::expected_delete_ctor_base, - private detail::expected_delete_assign_base, - private detail::expected_default_ctor_base { -public: - using value_type = T; - using error_type = E; - using unexpected_type = Unexpected; - - constexpr Expected() = default; - constexpr Expected(const Expected&) = default; - constexpr Expected(Expected&&) = default; - Expected& operator=(const Expected&) = default; - Expected& operator=(Expected&&) = default; - - template >* = nullptr> - constexpr Expected(std::in_place_t, Args&&... args) - : impl_base{std::in_place, std::forward(args)...}, - ctor_base{detail::default_constructor_tag{}} {} - - template &, Args&&...>>* = - nullptr> - constexpr Expected(std::in_place_t, std::initializer_list il, Args&&... args) - : impl_base{std::in_place, il, std::forward(args)...}, - ctor_base{detail::default_constructor_tag{}} {} - - template >* = nullptr, - std::enable_if_t>* = nullptr> - constexpr explicit Expected(const Unexpected& e) - : impl_base{unexpect_t{}, e.value()}, ctor_base{detail::default_constructor_tag{}} {} - - template >* = nullptr, - std::enable_if_t>* = nullptr> - constexpr Expected(Unexpected const& e) - : impl_base{unexpect_t{}, e.value()}, ctor_base{detail::default_constructor_tag{}} {} - - template >* = nullptr, - std::enable_if_t>* = nullptr> - constexpr explicit Expected(Unexpected&& e) noexcept(std::is_nothrow_constructible_v) - : impl_base{unexpect_t{}, std::move(e.value())}, ctor_base{ - detail::default_constructor_tag{}} {} - - template >* = nullptr, - std::enable_if_t>* = nullptr> - constexpr Expected(Unexpected&& e) noexcept(std::is_nothrow_constructible_v) - : impl_base{unexpect_t{}, std::move(e.value())}, ctor_base{ - detail::default_constructor_tag{}} {} - - template >* = nullptr> - constexpr explicit Expected(unexpect_t, Args&&... args) - : impl_base{unexpect_t{}, std::forward(args)...}, - ctor_base{detail::default_constructor_tag{}} {} - - template &, Args&&...>>* = - nullptr> - constexpr explicit Expected(unexpect_t, std::initializer_list il, Args&&... args) - : impl_base{unexpect_t{}, il, std::forward(args)...}, - ctor_base{detail::default_constructor_tag{}} {} - - template && - std::is_convertible_v)>* = nullptr, - detail::expected_enable_from_other* = nullptr> - constexpr explicit Expected(const Expected& rhs) - : ctor_base{detail::default_constructor_tag{}} { - if (rhs.has_value()) { - this->construct(*rhs); - } else { - this->construct_error(rhs.error()); - } - } - - template && - std::is_convertible_v)>* = nullptr, - detail::expected_enable_from_other* = nullptr> - constexpr Expected(const Expected& rhs) : ctor_base{detail::default_constructor_tag{}} { - if (rhs.has_value()) { - this->construct(*rhs); - } else { - this->construct_error(rhs.error()); - } - } - - template && std::is_convertible_v)>* = - nullptr, - detail::expected_enable_from_other* = nullptr> - constexpr explicit Expected(Expected&& rhs) - : ctor_base{detail::default_constructor_tag{}} { - if (rhs.has_value()) { - this->construct(std::move(*rhs)); - } else { - this->construct_error(std::move(rhs.error())); - } - } - - template && std::is_convertible_v)>* = - nullptr, - detail::expected_enable_from_other* = nullptr> - constexpr Expected(Expected&& rhs) : ctor_base{detail::default_constructor_tag{}} { - if (rhs.has_value()) { - this->construct(std::move(*rhs)); - } else { - this->construct_error(std::move(rhs.error())); - } - } - - template >* = nullptr, - detail::expected_enable_forward_value* = nullptr> - constexpr explicit Expected(U&& v) : Expected{std::in_place, std::forward(v)} {} - - template >* = nullptr, - detail::expected_enable_forward_value* = nullptr> - constexpr Expected(U&& v) : Expected{std::in_place, std::forward(v)} {} - - template >* = nullptr, - std::enable_if_t<( - !std::is_same_v, std::remove_cvref_t> && - !std::conjunction_v, std::is_same>> && - std::is_constructible_v && std::is_assignable_v && - std::is_nothrow_move_constructible_v)>* = nullptr> - Expected& operator=(U&& v) { - if (has_value()) { - val() = std::forward(v); - } else { - err().~Unexpected(); - new (valptr()) T{std::forward(v)}; - this->m_has_val = true; - } - - return *this; - } - - template >* = nullptr, - std::enable_if_t<( - !std::is_same_v, std::remove_cvref_t> && - !std::conjunction_v, std::is_same>> && - std::is_constructible_v && std::is_assignable_v && - std::is_nothrow_move_constructible_v)>* = nullptr> - Expected& operator=(U&& v) { - if (has_value()) { - val() = std::forward(v); - } else { - auto tmp = std::move(err()); - err().~Unexpected(); - new (valptr()) T{std::forward(v)}; - this->m_has_val = true; - } - - return *this; - } - - template && - std::is_assignable_v>* = nullptr> - Expected& operator=(const Unexpected& rhs) { - if (!has_value()) { - err() = rhs; - } else { - this->destroy_val(); - new (errptr()) Unexpected{rhs}; - this->m_has_val = false; - } - - return *this; - } - - template && - std::is_move_assignable_v>* = nullptr> - Expected& operator=(Unexpected&& rhs) noexcept { - if (!has_value()) { - err() = std::move(rhs); - } else { - this->destroy_val(); - new (errptr()) Unexpected{std::move(rhs)}; - this->m_has_val = false; - } - - return *this; - } - - template >* = nullptr> - void emplace(Args&&... args) { - if (has_value()) { - val() = T{std::forward(args)...}; - } else { - err().~Unexpected(); - new (valptr()) T{std::forward(args)...}; - this->m_has_val = true; - } - } - - template >* = nullptr> - void emplace(Args&&... args) { - if (has_value()) { - val() = T{std::forward(args)...}; - } else { - auto tmp = std::move(err()); - err().~Unexpected(); - new (valptr()) T{std::forward(args)...}; - this->m_has_val = true; - } - } - - template &, - Args&&...>>* = nullptr> - void emplace(std::initializer_list il, Args&&... args) { - if (has_value()) { - T t{il, std::forward(args)...}; - val() = std::move(t); - } else { - err().~Unexpected(); - new (valptr()) T{il, std::forward(args)...}; - this->m_has_val = true; - } - } - - template &, - Args&&...>>* = nullptr> - void emplace(std::initializer_list il, Args&&... args) { - if (has_value()) { - T t{il, std::forward(args)...}; - val() = std::move(t); - } else { - auto tmp = std::move(err()); - err().~Unexpected(); - new (valptr()) T{il, std::forward(args)...}; - this->m_has_val = true; - } - } - - constexpr T* operator->() { - return valptr(); - } - - constexpr const T* operator->() const { - return valptr(); - } - - template - constexpr U& operator*() & { - return val(); - } - - template - constexpr const U& operator*() const& { - return val(); - } - - template - constexpr U&& operator*() && { - return std::move(val()); - } - - template - constexpr const U&& operator*() const&& { - return std::move(val()); - } - - constexpr bool has_value() const noexcept { - return this->m_has_val; - } - - constexpr explicit operator bool() const noexcept { - return this->m_has_val; - } - - template - constexpr U& value() & { - return val(); - } - - template - constexpr const U& value() const& { - return val(); - } - - template - constexpr U&& value() && { - return std::move(val()); - } - - template - constexpr const U&& value() const&& { - return std::move(val()); - } - - constexpr E& error() & { - return err().value(); - } - - constexpr const E& error() const& { - return err().value(); - } - - constexpr E&& error() && { - return std::move(err().value()); - } - - constexpr const E&& error() const&& { - return std::move(err().value()); - } - - template - constexpr T value_or(U&& v) const& { - static_assert(std::is_copy_constructible_v && std::is_convertible_v, - "T must be copy-constructible and convertible from U&&"); - return bool(*this) ? **this : static_cast(std::forward(v)); - } - - template - constexpr T value_or(U&& v) && { - static_assert(std::is_move_constructible_v && std::is_convertible_v, - "T must be move-constructible and convertible from U&&"); - return bool(*this) ? std::move(**this) : static_cast(std::forward(v)); - } - -private: - static_assert(!std::is_reference_v, "T must not be a reference"); - static_assert(!std::is_same_v>, - "T must not be std::in_place_t"); - static_assert(!std::is_same_v>, "T must not be unexpect_t"); - static_assert(!std::is_same_v>>, - "T must not be Unexpected"); - static_assert(!std::is_reference_v, "E must not be a reference"); - - T* valptr() { - return std::addressof(this->m_val); - } - - const T* valptr() const { - return std::addressof(this->m_val); - } - - Unexpected* errptr() { - return std::addressof(this->m_unexpect); - } - - const Unexpected* errptr() const { - return std::addressof(this->m_unexpect); - } - - template - constexpr U& val() { - return this->m_val; - } - - template - constexpr const U& val() const { - return this->m_val; - } - - constexpr Unexpected& err() { - return this->m_unexpect; - } - - constexpr const Unexpected& err() const { - return this->m_unexpect; - } - - using impl_base = detail::expected_move_assign_base; - using ctor_base = detail::expected_default_ctor_base; -}; - -template -constexpr bool operator==(const Expected& lhs, const Expected& rhs) { - return (lhs.has_value() != rhs.has_value()) - ? false - : (!lhs.has_value() ? lhs.error() == rhs.error() : *lhs == *rhs); -} - -template -constexpr bool operator!=(const Expected& lhs, const Expected& rhs) { - return !operator==(lhs, rhs); -} - -template -constexpr bool operator==(const Expected& x, const U& v) { - return x.has_value() ? *x == v : false; -} - -template -constexpr bool operator==(const U& v, const Expected& x) { - return x.has_value() ? *x == v : false; -} - -template -constexpr bool operator!=(const Expected& x, const U& v) { - return !operator==(x, v); -} - -template -constexpr bool operator!=(const U& v, const Expected& x) { - return !operator==(v, x); -} - -template -constexpr bool operator==(const Expected& x, const Unexpected& e) { - return x.has_value() ? false : x.error() == e.value(); -} - -template -constexpr bool operator==(const Unexpected& e, const Expected& x) { - return x.has_value() ? false : x.error() == e.value(); -} - -template -constexpr bool operator!=(const Expected& x, const Unexpected& e) { - return !operator==(x, e); -} - -template -constexpr bool operator!=(const Unexpected& e, const Expected& x) { - return !operator==(e, x); -} - -} // namespace Common diff --git a/src/common/fiber.cpp b/src/common/fiber.cpp index 69eca732eb..0e7f28c5b9 100644 --- a/src/common/fiber.cpp +++ b/src/common/fiber.cpp @@ -9,7 +9,6 @@ #include "common/assert.h" #include "common/fiber.h" -#include "common/virtual_buffer.h" #include diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp index 854090e7b2..461a5eee35 100644 --- a/src/common/fs/file.cpp +++ b/src/common/fs/file.cpp @@ -9,7 +9,7 @@ #include "common/assert.h" #include "common/fs/file.h" #include "common/fs/fs.h" -#ifdef ANDROID +#ifdef __ANDROID__ #include "common/fs/fs_android.h" #endif #include "common/logging.h" @@ -259,7 +259,7 @@ void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, File } else { _wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type)); } -#elif ANDROID +#elif __ANDROID__ if (Android::IsContentUri(path)) { ASSERT_MSG(mode == FileAccessMode::Read, "Content URI file access is for read-only!"); const auto fd = Android::OpenContentUri(path, Android::OpenMode::Read); @@ -396,7 +396,7 @@ u64 IOFile::GetSize() const { // Flush any unwritten buffered data into the file prior to retrieving the file size. std::fflush(file); -#if ANDROID +#ifdef __ANDROID__ u64 file_size = 0; if (Android::IsContentUri(file_path)) { file_size = Android::GetSize(file_path); diff --git a/src/common/fs/file.h b/src/common/fs/file.h index 246c3db5d6..b1ccfbd18a 100644 --- a/src/common/fs/file.h +++ b/src/common/fs/file.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -277,13 +280,11 @@ public: * @returns Count of T data successfully read. */ template + requires std::is_trivially_copyable_v [[nodiscard]] size_t ReadSpan(std::span data) const { - static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); - if (!IsOpen()) { return 0; } - return std::fread(data.data(), sizeof(T), data.size(), file); } diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp index 3858b0f0c3..90cef15001 100644 --- a/src/common/fs/fs.cpp +++ b/src/common/fs/fs.cpp @@ -6,7 +6,7 @@ #include "common/fs/file.h" #include "common/fs/fs.h" -#ifdef ANDROID +#ifdef __ANDROID__ #include "common/fs/fs_android.h" #endif #include "common/fs/path_util.h" @@ -409,130 +409,75 @@ bool RenameDir(const fs::path& old_path, const fs::path& new_path) { return true; } -void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable& callback, - DirEntryFilter filter) { +void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable& callback, DirEntryFilter filter) { if (!ValidatePath(path)) { LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); return; } - if (!Exists(path)) { - LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist", - PathToUTF8String(path)); + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist", PathToUTF8String(path)); return; } - if (!IsDir(path)) { - LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", - PathToUTF8String(path)); + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", PathToUTF8String(path)); return; } - bool callback_error = false; - std::error_code ec; - - for (const auto& entry : fs::directory_iterator(path, ec)) { - if (ec) { - break; - } - - if (True(filter & DirEntryFilter::File) && - entry.status().type() == fs::file_type::regular) { + bool callback_error = false; + for (auto const& entry : fs::directory_iterator(path, ec)) { + if ((True(filter & DirEntryFilter::File) && entry.status().type() == fs::file_type::regular) + || (True(filter & DirEntryFilter::Directory) && entry.status().type() == fs::file_type::directory)) { if (!callback(entry)) { callback_error = true; - break; - } - } - - if (True(filter & DirEntryFilter::Directory) && - entry.status().type() == fs::file_type::directory) { - if (!callback(entry)) { - callback_error = true; - break; } } } if (callback_error || ec) { - LOG_ERROR(Common_Filesystem, - "Failed to visit all the directory entries of path={}, ec_message={}", - PathToUTF8String(path), ec.message()); - return; + LOG_ERROR(Common_Filesystem, "Failed to visit all the directory entries of path={}, ec_message={}, callback_error={}", PathToUTF8String(path), ec.message(), callback_error); + } else { + LOG_DEBUG(Common_Filesystem, "Visited all the directory entries of path={}", PathToUTF8String(path)); } - - LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}", - PathToUTF8String(path)); } -void IterateDirEntriesRecursively(const std::filesystem::path& path, - const DirEntryCallable& callback, DirEntryFilter filter) { +void IterateDirEntriesRecursively(const std::filesystem::path& path, const DirEntryCallable& callback, DirEntryFilter filter) { if (!ValidatePath(path)) { LOG_ERROR(Common_Filesystem, "Input path is not valid, path={}", PathToUTF8String(path)); return; } - if (!Exists(path)) { - LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist", - PathToUTF8String(path)); + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} does not exist", PathToUTF8String(path)); return; } - if (!IsDir(path)) { - LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", - PathToUTF8String(path)); + LOG_ERROR(Common_Filesystem, "Filesystem object at path={} is not a directory", PathToUTF8String(path)); return; } - bool callback_error = false; - std::error_code ec; - - // TODO (Morph): Replace this with recursive_directory_iterator once it's fixed in MSVC. - for (const auto& entry : fs::directory_iterator(path, ec)) { - if (ec) { - break; - } - - if (True(filter & DirEntryFilter::File) && - entry.status().type() == fs::file_type::regular) { + bool callback_error = false; + // MSVC should now be fixed... right... right?!?!?! + for (const auto& entry : fs::recursive_directory_iterator(path, ec)) { + if ((True(filter & DirEntryFilter::File) && entry.status().type() == fs::file_type::regular) + || (True(filter & DirEntryFilter::Directory) && entry.status().type() == fs::file_type::directory)) { if (!callback(entry)) { callback_error = true; - break; } } - - if (True(filter & DirEntryFilter::Directory) && - entry.status().type() == fs::file_type::directory) { - if (!callback(entry)) { - callback_error = true; - break; - } - } - - // TODO (Morph): Remove this when MSVC fixes recursive_directory_iterator. - // recursive_directory_iterator throws an exception despite passing in a std::error_code. - if (entry.status().type() == fs::file_type::directory) { - IterateDirEntriesRecursively(entry.path(), callback, filter); - } } - if (callback_error || ec) { - LOG_ERROR(Common_Filesystem, - "Failed to visit all the directory entries of path={}, ec_message={}", - PathToUTF8String(path), ec.message()); - return; + LOG_ERROR(Common_Filesystem, "Failed to visit all the directory entries of path={}, ec_message={}, callback_error={}", PathToUTF8String(path), ec.message(), callback_error); + } else { + LOG_DEBUG(Common_Filesystem, "Visited all the directory entries of path={}", PathToUTF8String(path)); } - - LOG_DEBUG(Common_Filesystem, "Successfully visited all the directory entries of path={}", - PathToUTF8String(path)); } // Generic Filesystem Operations bool Exists(const fs::path& path) { std::error_code ec; -#ifdef ANDROID +#ifdef __ANDROID__ if (Android::IsContentUri(path)) { return Android::Exists(path); } else { @@ -545,7 +490,7 @@ bool Exists(const fs::path& path) { bool IsFile(const fs::path& path) { std::error_code ec; -#ifdef ANDROID +#ifdef __ANDROID__ if (Android::IsContentUri(path)) { return !Android::IsDirectory(path); } else { @@ -558,7 +503,7 @@ bool IsFile(const fs::path& path) { bool IsDir(const fs::path& path) { std::error_code ec; -#ifdef ANDROID +#ifdef __ANDROID__ if (Android::IsContentUri(path)) { return Android::IsDirectory(path); } else { @@ -611,7 +556,7 @@ fs::file_type GetEntryType(const fs::path& path) { } u64 GetSize(const fs::path& path) { -#ifdef ANDROID +#ifdef __ANDROID__ if (Android::IsContentUri(path)) { return Android::GetSize(path); } diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h index 640a83c44b..ae25846fc2 100644 --- a/src/common/fs/fs_paths.h +++ b/src/common/fs/fs_paths.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -20,8 +20,11 @@ #define KEYS_DIR "keys" #define LOAD_DIR "load" #define LOG_DIR "log" +#define LOSSLESS_DIR "lossless" #define NAND_DIR "nand" #define PLAY_TIME_DIR "play_time" +#define POST_PRESET_DIR "post_presets" +#define POST_SHADER_DIR "post_shaders" #define SCREENSHOTS_DIR "screenshots" #define SDMC_DIR "sdmc" #define SHADER_DIR "shader" @@ -37,3 +40,5 @@ // yuzu-specific files #define LOG_FILE "eden_log.txt" +#define LOSSLESS_DLL_FILE "Lossless.dll" +#define LOSSLESS_CACHE_FILE "lsfg_spirv.cache" diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index 714e393293..82ea8f6cb2 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -5,13 +5,14 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include -#include +#include "common/container/unordered_map.h" #include "common/assert.h" #include "common/fs/fs.h" -#ifdef ANDROID +#ifdef __ANDROID__ #include "common/fs/fs_android.h" #endif #include "common/fs/fs_paths.h" @@ -90,7 +91,7 @@ public: void CreateEdenPaths() { std::for_each(eden_paths.begin(), eden_paths.end(), [](auto &path) { - void(FS::CreateDir(path.second)); + void(FS::CreateDirs(path.second)); }); } @@ -126,7 +127,7 @@ public: LEGACY_PATH(Yuzu, YUZU) LEGACY_PATH(Suyu, SUYU) #undef LEGACY_PATH -#elif ANDROID +#elif __ANDROID__ ASSERT(!eden_path.empty()); eden_path_cache = eden_path / CACHE_DIR; eden_path_config = eden_path / CONFIG_DIR; @@ -149,24 +150,29 @@ public: LEGACY_PATH(Suyu, SUYU) #undef LEGACY_PATH #endif + // data GenerateEdenPath(EdenPath::EdenDir, eden_path); GenerateEdenPath(EdenPath::AmiiboDir, eden_path / AMIIBO_DIR); - GenerateEdenPath(EdenPath::CacheDir, eden_path_cache); - GenerateEdenPath(EdenPath::ConfigDir, eden_path_config); GenerateEdenPath(EdenPath::CrashDumpsDir, eden_path / CRASH_DUMPS_DIR); GenerateEdenPath(EdenPath::DumpDir, eden_path / DUMP_DIR); GenerateEdenPath(EdenPath::KeysDir, eden_path / KEYS_DIR); GenerateEdenPath(EdenPath::LoadDir, eden_path / LOAD_DIR); GenerateEdenPath(EdenPath::LogDir, eden_path / LOG_DIR); + GenerateEdenPath(EdenPath::LosslessDir, eden_path / LOSSLESS_DIR); GenerateEdenPath(EdenPath::NANDDir, eden_path / NAND_DIR); GenerateEdenPath(EdenPath::PlayTimeDir, eden_path / PLAY_TIME_DIR); + GenerateEdenPath(EdenPath::PostPresetDir, eden_path / POST_PRESET_DIR); + GenerateEdenPath(EdenPath::PostShaderDir, eden_path / POST_SHADER_DIR); GenerateEdenPath(EdenPath::SaveDir, eden_path / NAND_DIR); GenerateEdenPath(EdenPath::ScreenshotsDir, eden_path / SCREENSHOTS_DIR); GenerateEdenPath(EdenPath::SDMCDir, eden_path / SDMC_DIR); - GenerateEdenPath(EdenPath::ShaderDir, eden_path / SHADER_DIR); GenerateEdenPath(EdenPath::TASDir, eden_path / TAS_DIR); GenerateEdenPath(EdenPath::IconsDir, eden_path / ICONS_DIR); - + // config + GenerateEdenPath(EdenPath::ConfigDir, eden_path_config); + // cache + GenerateEdenPath(EdenPath::CacheDir, eden_path_cache); + GenerateEdenPath(EdenPath::ShaderDir, eden_path_cache / SHADER_DIR); #ifdef _WIN32 GenerateLegacyPath(EmuPath::RyujinxDir, GetAppDataRoamingDirectory() / RYUJINX_DIR); #else @@ -193,8 +199,8 @@ private: SetLegacyPathImpl(legacy_path, new_path); } - ankerl::unordered_dense::map eden_paths; - ankerl::unordered_dense::map legacy_paths; + ::Common::unordered_map eden_paths; + ::Common::unordered_map legacy_paths; }; bool ValidatePath(const fs::path& path) { @@ -447,11 +453,11 @@ std::vector SplitPathComponentsCopy(std::string_view filename) { std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) { std::string path(path_); -#ifdef ANDROID +#ifdef __ANDROID__ if (Android::IsContentUri(path)) { return path; } -#endif // ANDROID +#endif // __ANDROID__ char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\'; char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/'; @@ -474,6 +480,81 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se path.erase(std::unique(start, path.end(), [type2](char c1, char c2) { return c1 == type2 && c2 == type2; }), path.end()); + + std::string root; + std::string_view components{path}; + bool drive_relative = false; + +#ifdef _WIN32 + const bool network = path.size() > 1 && path[0] == type2 && path[1] == type2; + const bool drive = + path.size() > 1 && std::isalpha(static_cast(path[0])) && path[1] == ':'; + + if (network) { + root.assign(2, type2); + components.remove_prefix(2); + } else if (drive) { + root.assign(path.data(), 2); + components.remove_prefix(2); + if (!components.empty() && components.front() == type2) { + root += type2; + components.remove_prefix(1); + } else { + drive_relative = true; + } + } +#endif + + if (root.empty() && !components.empty() && components.front() == type2) { + root += type2; + components.remove_prefix(1); + } + + const auto path_parts = SplitPathComponents(components); + std::size_t root_component_count = 0; +#ifdef _WIN32 + if (network) { + root_component_count = 2; + + const auto is_unc = [](std::string_view part) { + return part.size() == 3 && (part[0] == 'U' || part[0] == 'u') && + (part[1] == 'N' || part[1] == 'n') && (part[2] == 'C' || part[2] == 'c'); + }; + if (path_parts.size() >= 2 && path_parts[0] == "?" && is_unc(path_parts[1])) { + root_component_count = 4; + } + } +#endif + + std::vector parts; + for (std::size_t i = 0; i < path_parts.size(); ++i) { + const auto part = path_parts[i]; + if (i < root_component_count) { + parts.push_back(part); + } else if (part.empty() || part == ".") { + continue; + } else if (part == "..") { + if (parts.size() > root_component_count) { + parts.pop_back(); + } + } else { + parts.push_back(part); + } + } + + const std::size_t root_length = root.size(); + std::string resolved = std::move(root); + for (std::size_t i = 0; i < parts.size(); ++i) { + if (i != 0 || (!resolved.empty() && resolved.back() != type2 && !drive_relative)) + resolved += type2; + resolved.append(parts[i].data(), parts[i].size()); + } + + path = std::move(resolved); + + if (!path.empty() && path.size() == root_length) { + return path; + } return std::string(RemoveTrailingSlash(path)); } @@ -482,7 +563,7 @@ std::string GetParentPath(std::string_view path) { return std::string(path); } -#ifdef ANDROID +#ifdef __ANDROID__ if (path[0] != '/') { std::string path_string{path}; return FS::Android::GetParentDirectory(path_string); diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h index 9f597232a5..e8fa5614ae 100644 --- a/src/common/fs/path_util.h +++ b/src/common/fs/path_util.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -23,8 +23,11 @@ enum class EdenPath { KeysDir, // Where key files are stored. LoadDir, // Where cheat/mod files are stored. LogDir, // Where log files are stored. + LosslessDir, // Where the user-supplied Lossless Scaling library is stored. NANDDir, // Where the emulated NAND is stored. PlayTimeDir, // Where play time data is stored. + PostPresetDir, + PostShaderDir, // Where user post-processing shaders are stored. SaveDir, // Where save data is stored. ScreenshotsDir, // Where yuzu screenshots are stored. SDMCDir, // Where the emulated SDMC is stored. @@ -344,8 +347,9 @@ enum class DirectorySeparator { // i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" } [[nodiscard]] std::vector SplitPathComponentsCopy(std::string_view filename); -// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\' -// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows +// Normalizes directory separators, removes duplicate and non-root trailing separators, and resolves +// '.' and '..' components without traversing above the path root. Windows drive and UNC roots are +// preserved. [[nodiscard]] std::string SanitizePath( std::string_view path, DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash); diff --git a/src/common/hex_util.cpp b/src/common/hex_util.cpp index 07053295c0..3f7cd21dd6 100644 --- a/src/common/hex_util.cpp +++ b/src/common/hex_util.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2013 Dolphin Emulator Project // SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -12,7 +15,7 @@ std::vector HexStringToVector(std::string_view str, bool little_endian) { for (std::size_t i = str.size() - 2; i <= str.size(); i -= 2) out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]); } else { - for (std::size_t i = 0; i < str.size(); i += 2) + for (std::size_t i = 0; i + 1 < str.size(); i += 2) out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]); } return out; diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp index 04f3a65778..75a9da80e9 100644 --- a/src/common/host_memory.cpp +++ b/src/common/host_memory.cpp @@ -7,7 +7,7 @@ #ifdef _WIN32 #include -#include +#include "common/container/unordered_map.h" #include #include #include "common/dynamic_library.h" @@ -32,6 +32,8 @@ #include #elif defined(__FreeBSD__) #include +#elif defined(__OPENORBIS__) +#include #endif // FreeBSD @@ -115,67 +117,75 @@ template static void GetFuncAddress(Common::DynamicLibrary& dll, const char* name, T& pfn) { if (!dll.GetSymbol(name, &pfn)) { LOG_CRITICAL(HW_Memory, "Failed to load {}", name); - throw std::bad_alloc{}; } } class HostMemory::Impl { public: explicit Impl(size_t backing_size_, size_t virtual_size_) - : backing_size{backing_size_}, virtual_size{virtual_size_}, process{GetCurrentProcess()}, - kernelbase_dll("Kernelbase") { + : backing_size{backing_size_} + , virtual_size{virtual_size_} + , process{GetCurrentProcess()} + , kernelbase_dll("Kernelbase") + {} + + bool Init() { if (!kernelbase_dll.IsOpen()) { LOG_CRITICAL(HW_Memory, "Failed to load Kernelbase.dll"); - throw std::bad_alloc{}; + return false; } GetFuncAddress(kernelbase_dll, "CreateFileMapping2", pfn_CreateFileMapping2); GetFuncAddress(kernelbase_dll, "VirtualAlloc2", pfn_VirtualAlloc2); GetFuncAddress(kernelbase_dll, "MapViewOfFile3", pfn_MapViewOfFile3); GetFuncAddress(kernelbase_dll, "UnmapViewOfFile2", pfn_UnmapViewOfFile2); + if (!pfn_CreateFileMapping2 || !pfn_VirtualAlloc2 || !pfn_MapViewOfFile3 || !pfn_UnmapViewOfFile2) { + LOG_CRITICAL(HW_Memory, "Failed to find functions for virtual allocs"); + return false; + } + // Allocate backing file map - backing_handle = - pfn_CreateFileMapping2(INVALID_HANDLE_VALUE, nullptr, FILE_MAP_WRITE | FILE_MAP_READ, - PAGE_READWRITE, SEC_COMMIT, backing_size, nullptr, nullptr, 0); + backing_handle = pfn_CreateFileMapping2(INVALID_HANDLE_VALUE, nullptr, FILE_MAP_WRITE | FILE_MAP_READ, PAGE_READWRITE, SEC_COMMIT, backing_size, nullptr, nullptr, 0); if (!backing_handle) { - LOG_CRITICAL(HW_Memory, "Failed to allocate {} MiB of backing memory", - backing_size >> 20); - throw std::bad_alloc{}; + LOG_CRITICAL(HW_Memory, "Failed to allocate {} MiB of backing memory", backing_size >> 20); + return false; } // Allocate a virtual memory for the backing file map as placeholder - backing_base = static_cast(pfn_VirtualAlloc2(process, nullptr, backing_size, - MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, - PAGE_NOACCESS, nullptr, 0)); + backing_base = static_cast(pfn_VirtualAlloc2(process, nullptr, backing_size, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, nullptr, 0)); if (!backing_base) { Release(); - LOG_CRITICAL(HW_Memory, "Failed to reserve {} MiB of virtual memory", - backing_size >> 20); - throw std::bad_alloc{}; + LOG_CRITICAL(HW_Memory, "Failed to reserve {} MiB of virtual memory", backing_size >> 20); + return false; } // Map backing placeholder - void* const ret = pfn_MapViewOfFile3(backing_handle, process, backing_base, 0, backing_size, - MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0); + void* const ret = pfn_MapViewOfFile3(backing_handle, process, backing_base, 0, backing_size, MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0); if (ret != backing_base) { Release(); LOG_CRITICAL(HW_Memory, "Failed to map {} MiB of virtual memory", backing_size >> 20); - throw std::bad_alloc{}; + return false; } // Allocate virtual address placeholder - virtual_base = static_cast(pfn_VirtualAlloc2(process, nullptr, virtual_size, - MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, - PAGE_NOACCESS, nullptr, 0)); + virtual_base = static_cast(pfn_VirtualAlloc2(process, nullptr, virtual_size, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, nullptr, 0)); if (!virtual_base) { Release(); - LOG_CRITICAL(HW_Memory, "Failed to reserve {} GiB of virtual memory", - virtual_size >> 30); - throw std::bad_alloc{}; + LOG_CRITICAL(HW_Memory, "Failed to reserve {} GiB of virtual memory", virtual_size >> 30); + return false; } + return true; } ~Impl() { Release(); } + void* Allocate(size_t size) { + auto* ptr = VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + if (ptr == nullptr) { + LOG_CRITICAL(HW_Memory, "Failed to allocate fallback buffer with size {:#x}, error {}", size, GetLastError()); + } + return ptr; + } + void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms) { std::unique_lock lock{placeholder_mutex}; if (!IsNiechePlaceholder(virtual_offset, length)) { @@ -388,11 +398,18 @@ private: std::mutex placeholder_mutex; ///< Mutex for placeholders boost::icl::separate_interval_set placeholders; ///< Mapped placeholders - ankerl::unordered_dense::map placeholder_host_pointers; ///< Placeholder backing offset + ::Common::unordered_map placeholder_host_pointers; ///< Placeholder backing offset }; +#elif defined(__OPENORBIS__) || defined(__managarm__) +// None of the luxuries of POSIX, all of the suffering +// For managarm: see https://github.com/managarm/managarm/issues/1370 #else // ^^^ Windows ^^^ vvv POSIX vvv +#ifndef MAP_NOCORE +#define MAP_NOCORE 0 +#endif + #ifdef ARCHITECTURE_arm64 static void* ChooseVirtualBase(size_t virtual_size) { @@ -417,7 +434,7 @@ static void* ChooseVirtualBase(size_t virtual_size) { // Note: we may be able to take advantage of MAP_FIXED_NOREPLACE here. void* map_pointer = mmap(reinterpret_cast(hint_address), virtual_size, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_NOCORE, -1, 0); // If we successfully mapped, we're done. if (reinterpret_cast(map_pointer) == hint_address) { @@ -437,11 +454,11 @@ static void* ChooseVirtualBase(size_t virtual_size) { static void* ChooseVirtualBase(size_t virtual_size) { #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); + void* virtual_base = mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_ALIGNED_SUPER | MAP_NOCORE, -1, 0); 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); + return mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_NOCORE, -1, 0); } #endif @@ -496,10 +513,13 @@ static int shm_open_anon(int flags, mode_t mode) { class HostMemory::Impl { public: explicit Impl(size_t backing_size_, size_t virtual_size_) - : backing_size{backing_size_}, virtual_size{virtual_size_} { + : backing_size{backing_size_} + , virtual_size{virtual_size_} + {} + + bool Init() { long page_size = sysconf(_SC_PAGESIZE); - ASSERT_MSG(page_size == 0x1000, "page size {:#x} is incompatible with 4K paging", - page_size); + ASSERT_MSG(page_size == 0x1000, "page size {:#x} is incompatible with 4K paging", page_size); // Backing memory initialization #if defined(__sun__) || defined(__HAIKU__) || defined(__NetBSD__) || defined(__DragonFly__) fd = shm_open_anon(O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, 0600); @@ -507,7 +527,7 @@ public: fd = shm_open_anon(O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, 0600); #elif defined(__FreeBSD__) fd = shm_open(SHM_ANON, O_RDWR, 0600); -#elif defined(__APPLE__) +#elif defined(__APPLE__) || defined(__managarm__) // macOS doesn't have memfd_create, use anonymous temporary file char template_path[] = "/tmp/eden_mem_XXXXXX"; fd = mkstemp(template_path); @@ -532,29 +552,44 @@ public: } if (use_anon) { LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones"); - backing_base = static_cast(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)); + backing_base = static_cast(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_NOCORE, -1, 0)); if (fd > 0) { fd = -1; close(fd); } } else { - backing_base = static_cast(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); + backing_base = static_cast(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NOCORE, fd, 0)); + } + if (backing_base == MAP_FAILED) { + LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno)); + return false; } - ASSERT_MSG(backing_base != MAP_FAILED, "mmap failed: {}", strerror(errno)); // Virtual memory initialization virtual_base = virtual_map_base = static_cast(ChooseVirtualBase(virtual_size)); - ASSERT_MSG(virtual_base != MAP_FAILED, "mmap failed: {}", strerror(errno)); + if (virtual_base == MAP_FAILED) { + LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno)); + return false; + } #if defined(__linux__) madvise(virtual_base, virtual_size, MADV_HUGEPAGE); #endif free_manager.SetAddressSpace(virtual_base, virtual_size); + return true; } ~Impl() { Release(); } + void* Allocate(size_t size) { + auto* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (ptr == MAP_FAILED) { + LOG_CRITICAL(HW_Memory, "Failed to allocate fallback buffer with size {:#x}, {}", size, strerror(errno)); + } + return ptr; + } + void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms) { // Intersect the range with our address space. AdjustMap(&virtual_offset, &length); @@ -669,27 +704,53 @@ private: #endif // ^^^ POSIX ^^^ -HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_) : backing_size(backing_size_), virtual_size(virtual_size_) { +HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_) + : backing_size(backing_size_) + , virtual_size(virtual_size_) +{ +#if defined(__OPENORBIS__) || defined(__managarm__) + LOG_WARNING(HW_Memory, "Platform doesn't support fastmem"); + backing_base = static_cast(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + virtual_base = nullptr; +#else // Try to allocate a fastmem arena. - // The implementation will fail with std::bad_alloc on errors. impl = std::make_unique(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize); - backing_base = impl->backing_base; - virtual_base = impl->virtual_base; - if (virtual_base) { - // Ensure the virtual base is aligned to the L2 block size. - virtual_base = reinterpret_cast(Common::AlignUp(uintptr_t(virtual_base), HugePageSize)); - virtual_base_offset = virtual_base - impl->virtual_base; + if (impl->Init()) { + backing_base = impl->backing_base; + virtual_base = impl->virtual_base; + if (virtual_base) { + // Ensure the virtual base is aligned to the L2 block size. + virtual_base = reinterpret_cast(Common::AlignUp(uintptr_t(virtual_base), HugePageSize)); + virtual_base_offset = virtual_base - impl->virtual_base; + } + } else { + LOG_WARNING(HW_Memory, "Platform can support fastmem, but can't create it"); + fallback_buffer = true; + backing_base = static_cast(impl->Allocate(backing_size)); + virtual_base = nullptr; + impl.reset(); } +#endif } -HostMemory::~HostMemory() = default; +HostMemory::~HostMemory() { +#ifdef _WIN32 + if (fallback_buffer) { + VirtualFree(backing_base, backing_size, MEM_RELEASE); + } +#else + if (fallback_buffer) { + munmap(backing_base, backing_size); + } +#endif +} HostMemory::HostMemory(HostMemory&&) noexcept = default; HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default; -void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length, - MemoryPermission perms, bool separate_heap) { +void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms, bool separate_heap) { +#if !(defined(__OPENORBIS__) || defined(__managarm__)) ASSERT(virtual_offset % PageAlignment == 0); ASSERT(host_offset % PageAlignment == 0); ASSERT(length % PageAlignment == 0); @@ -699,9 +760,11 @@ void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length, return; } impl->Map(virtual_offset + virtual_base_offset, host_offset, length, perms); +#endif } void HostMemory::Unmap(size_t virtual_offset, size_t length, bool separate_heap) { +#if !(defined(__OPENORBIS__) || defined(__managarm__)) ASSERT(virtual_offset % PageAlignment == 0); ASSERT(length % PageAlignment == 0); ASSERT(virtual_offset + length <= virtual_size); @@ -709,9 +772,11 @@ void HostMemory::Unmap(size_t virtual_offset, size_t length, bool separate_heap) return; } impl->Unmap(virtual_offset + virtual_base_offset, length); +#endif } void HostMemory::Protect(size_t virtual_offset, size_t length, MemoryPermission perm) { +#if !(defined(__OPENORBIS__) || defined(__managarm__)) ASSERT(virtual_offset % PageAlignment == 0); ASSERT(length % PageAlignment == 0); ASSERT(virtual_offset + length <= virtual_size); @@ -722,6 +787,7 @@ void HostMemory::Protect(size_t virtual_offset, size_t length, MemoryPermission const bool write = True(perm & MemoryPermission::Write); const bool execute = True(perm & MemoryPermission::Execute); impl->Protect(virtual_offset + virtual_base_offset, length, read, write, execute); +#endif } void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 fill_value) { @@ -729,10 +795,12 @@ void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 f } void HostMemory::EnableDirectMappedAddress() { +#if !(defined(__OPENORBIS__) || defined(__managarm__)) if (impl) { impl->EnableDirectMappedAddress(); virtual_size += reinterpret_cast(virtual_base); } +#endif } } // namespace Common diff --git a/src/common/host_memory.h b/src/common/host_memory.h index 8dd30aa9de..a7fd0532b7 100644 --- a/src/common/host_memory.h +++ b/src/common/host_memory.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -7,9 +7,9 @@ #pragma once #include +#include #include "common/common_funcs.h" #include "common/common_types.h" -#include "common/virtual_buffer.h" namespace Common { @@ -76,12 +76,16 @@ private: size_t backing_size{}; size_t virtual_size{}; +#if !(defined(__OPENORBIS__) || defined(__managarm__)) // Low level handler for the platform dependent memory routines class Impl; std::unique_ptr impl; +#endif u8* backing_base{}; u8* virtual_base{}; size_t virtual_base_offset{}; + // Windows requires it for kernels whom lack proper support for some functions! + bool fallback_buffer{false}; }; } // namespace Common diff --git a/src/common/input.h b/src/common/input.h index bd9b2d1656..514f93b494 100644 --- a/src/common/input.h +++ b/src/common/input.h @@ -9,7 +9,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include #include "common/logging.h" @@ -412,7 +412,7 @@ public: namespace Impl { template -using FactoryListType = ankerl::unordered_dense::map>>; +using FactoryListType = ::Common::unordered_map>>; template struct FactoryList { diff --git a/src/common/logging.cpp b/src/common/logging.cpp index 68e033b129..ed12d52532 100644 --- a/src/common/logging.cpp +++ b/src/common/logging.cpp @@ -39,6 +39,19 @@ namespace Common::Log { +/// @brief A log entry. Log entries are store in a structured format to permit more varied output +/// formatting on different frontends, as well as facilitating filtering and aggregation. +struct Entry { + char const* message = nullptr; + size_t message_len = 0; + std::chrono::microseconds timestamp; + Class log_class{}; + Level log_level{}; + const char* filename = nullptr; + const char* function = nullptr; + uint32_t line_num = 0; +}; + namespace { /// @brief Returns the name of the passed log class as a C-string. Subclasses are separated by periods @@ -70,8 +83,6 @@ const char* GetLevelName(Level log_level) { } } -} - // Some IDEs prefer : instead, so let's just do that :) std::string FormatLogMessage(const Entry& entry) noexcept { if (!entry.filename) return ""; @@ -79,10 +90,9 @@ std::string FormatLogMessage(const Entry& entry) noexcept { auto const time_fractional = uint32_t(entry.timestamp.count() % 1000000); auto const class_name = GetLogClassName(entry.log_class); auto const level_name = GetLevelName(entry.log_level); - return fmt::format("[{:4d}.{:06d}] {} <{}> {}:{}:{}: {}", time_seconds, time_fractional, class_name, level_name, entry.filename, entry.line_num, entry.function, entry.message); + return fmt::format("[{:4d}.{:06d}] {} <{}> {}:{}:{}: {}\n", time_seconds, time_fractional, class_name, level_name, entry.filename, entry.line_num, entry.function, entry.message); } -namespace { template Level GetLevelByName(const It begin, const It end) { for (u32 i = 0; i < u32(Level::Count); ++i) { @@ -127,25 +137,6 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { instance.SetClassLevel(log_class, level); return true; } -} // Anonymous namespace - -void Filter::ParseFilterString(std::string_view filter_view) { - auto clause_begin = filter_view.cbegin(); - while (clause_begin != filter_view.cend()) { - auto clause_end = std::find(clause_begin, filter_view.cend(), ' '); - // If clause isn't empty - if (clause_end != clause_begin) { - ParseFilterRule(*this, clause_begin, clause_end); - } - if (clause_end != filter_view.cend()) { - // Skip over the whitespace - ++clause_end; - } - clause_begin = clause_end; - } -} - -namespace { /// @brief Trims up to and including the last of ../, ..\, src/, src\ in a string /// do not be fooled this isn't generating new strings on .rodata :) @@ -208,7 +199,7 @@ struct ColorConsoleBackend final : public Backend { }()); SetConsoleTextAttribute(console_handle, color); auto const df = GetDirectFormatArgs(entry); - std::fprintf(stdout, CCB_PRINTF_FMT "\n", df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message.c_str()); + std::fprintf(stdout, CCB_PRINTF_FMT "\n", df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message); } } void Flush() noexcept override {} @@ -220,22 +211,24 @@ struct ColorConsoleBackend final : public Backend { ~ColorConsoleBackend() noexcept override {} void Write(const Entry& entry) noexcept override { if (enabled) { -#define ESC "\x1b" auto const color_str = [&entry]() -> const char* { switch (entry.log_level) { -#define CCB_MAKE_COLOR_FMT(X) ESC X CCB_PRINTF_FMT ESC "[0m\n" - case Level::Debug: return CCB_MAKE_COLOR_FMT("[0;36m"); // Cyan - case Level::Info: return CCB_MAKE_COLOR_FMT("[0;37m"); // Bright gray - case Level::Warning: return CCB_MAKE_COLOR_FMT("[1;33m"); // Bright yellow - case Level::Error: return CCB_MAKE_COLOR_FMT("[1;31m"); // Bright red - case Level::Critical: return CCB_MAKE_COLOR_FMT("[1;35m"); // Bright magenta - default: return CCB_MAKE_COLOR_FMT("[1;30m"); // Grey -#undef CCB_MAKE_COLOR_FMT + case Level::Debug: return "[0;36m"; // Cyan + case Level::Info: return "[0;37m"; // Bright gray + case Level::Warning: return "[1;33m"; // Bright yellow + case Level::Error: return "[1;31m"; // Bright red + case Level::Critical: return "[1;35m"; // Bright magenta + default: return "[1;30m"; // Grey } }(); auto const df = GetDirectFormatArgs(entry); - std::fprintf(stdout, color_str, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message.c_str()); -#undef ESC + // more restrictive, because take for example this simple prelude: + // [ 50.872256] Config common/settings.cpp:142:LogSettings: + char buffer[256]; + auto result = fmt::format_to_n(buffer, sizeof(buffer) - 1, "\x1b{}[{:4d}.{:06d}] {} <{}> {}:{}:{}: ", color_str, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message); + std::fwrite(buffer, 1, (std::min)(sizeof(buffer) - 1, result.size), stdout); + std::fwrite(entry.message, 1, entry.message_len, stdout); + std::fwrite("\x1b[0m\n", 1, sizeof("\x1b[0m\n"), stdout); } } void Flush() noexcept override {} @@ -246,7 +239,7 @@ struct ColorConsoleBackend final : public Backend { #ifndef __OPENORBIS__ /// @brief Backend that writes to a file passed into the constructor struct FileBackend final : public Backend { - explicit FileBackend(const std::filesystem::path& filename) noexcept { + explicit FileBackend(const std::filesystem::path filename) noexcept { auto old_filename = filename; old_filename += ".old.txt"; // Existence checks are done within the functions themselves. @@ -261,7 +254,7 @@ struct FileBackend final : public Backend { if (!enabled) return; - auto message = FormatLogMessage(entry).append(1, '\n'); + auto message = FormatLogMessage(entry); #ifndef __ANDROID__ if (Settings::values.censor_username.GetValue()) { // This must be a static otherwise it would get checked on EVERY @@ -269,8 +262,7 @@ struct FileBackend final : public Backend { static std::string username = []() -> std::string { // in order of precedence // LOGNAME usually works on UNIX, USERNAME on Windows - // Some UNIX systems suck and don't use LOGNAME so we also - // need USER :( + // Some UNIX systems suck and don't use LOGNAME so we also need USER :( for (auto const var : { "LOGNAME", "USERNAME", "USER", }) if (auto const s = ::getenv(var); s != nullptr) return std::string{s}; @@ -280,7 +272,7 @@ struct FileBackend final : public Backend { boost::replace_all(message, username, "user"); } #endif - bytes_written += file->WriteString(message); + bytes_written += file->WriteSpan(std::span{message.begin(), message.end()}); // Option to log each line rather than 4k buffers if (Settings::values.log_flush_line.GetValue()) @@ -308,19 +300,18 @@ private: bool enabled = true; }; #endif - #ifdef _WIN32 /// @brief Backend that writes to Visual Studio's output window struct DebuggerBackend final : public Backend { explicit DebuggerBackend() noexcept = default; ~DebuggerBackend() noexcept override = default; void Write(const Entry& entry) noexcept override { - ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str()); + ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry)).c_str()); } void Flush() noexcept override {} }; #endif -#ifdef ANDROID +#ifdef __ANDROID__ /// @brief Backend that writes to the Android logcat struct LogcatBackend : public Backend { explicit LogcatBackend() noexcept = default; @@ -338,7 +329,7 @@ struct LogcatBackend : public Backend { } }(); auto const df = GetDirectFormatArgs(entry); - __android_log_print(android_log_priority, "YuzuNative", CCB_PRINTF_FMT, df.time_seconds, df.time_fractional, df.class_name, df.level_name, entry.filename, entry.line_num, entry.function, entry.message.c_str()); + __android_log_print(android_log_priority, "YuzuNative", "%s %s:%u:%s: %s", df.class_name, entry.filename, entry.line_num, entry.function, entry.message); } void Flush() noexcept override {} }; @@ -359,7 +350,7 @@ struct Impl { #ifdef _WIN32 lambda(static_cast(debugger_backend)); #endif -#ifdef ANDROID +#ifdef __ANDROID__ lambda(static_cast(lc_backend)); #endif } @@ -372,12 +363,28 @@ struct Impl { #ifdef _WIN32 DebuggerBackend debugger_backend{}; #endif -#ifdef ANDROID +#ifdef __ANDROID__ LogcatBackend lc_backend{}; #endif std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; }; -} // namespace +} // Anonymous namespace + +void Filter::ParseFilterString(std::string_view filter_view) { + auto clause_begin = filter_view.cbegin(); + while (clause_begin < filter_view.cend()) { + auto clause_end = std::find(clause_begin, filter_view.cend(), ' '); + // If clause isn't empty + if (clause_end != clause_begin) { + ParseFilterRule(*this, clause_begin, clause_end); + } + if (clause_end != filter_view.cend()) { + // Skip over the whitespace + ++clause_end; + } + clause_begin = clause_end; + } +} // Constructor shall NOT depend upon Settings() or whatever // it's ran at global static ctor() time... so BE CAREFUL MFER! @@ -419,19 +426,35 @@ void SetColorConsoleBackendEnabled(bool enabled) { void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::string_view format, const fmt::format_args& args) { if (logging_instance && logging_instance->filter.CheckMessage(log_class, log_level)) { auto const flush = ::Settings::values.log_flush_line.GetValue(); - logging_instance->ForEachBackend([=](Backend& backend) { - backend.Write(Entry{ - .message = fmt::vformat(format, args), - .timestamp = std::chrono::duration_cast(std::chrono::steady_clock::now() - logging_instance->time_origin), - .log_class = log_class, - .log_level = log_level, - .filename = TrimSourcePath(filename), - .function = function, - .line_num = line_num, + char buffer[BUFSIZ]; + auto result = fmt::vformat_to_n(buffer, sizeof(buffer) - 1, format, args); + Entry e{ + .message = nullptr, + .message_len = 0, + .timestamp = std::chrono::duration_cast(std::chrono::steady_clock::now() - logging_instance->time_origin), + .log_class = log_class, + .log_level = log_level, + .filename = TrimSourcePath(filename), + .function = function, + .line_num = line_num, + }; + if (result.size <= sizeof(buffer) - 1) { + buffer[(std::min)(result.size, sizeof(buffer) - 1)] = '\0'; + e.message = buffer; + e.message_len = (std::min)(result.size, sizeof(buffer) - 1); + logging_instance->ForEachBackend([=](Backend& backend) { + backend.Write(e); + if (flush) backend.Flush(); }); - if (flush) - backend.Flush(); - }); + } else { + std::string s = fmt::vformat(format, args); + e.message = s.c_str(); + e.message_len = s.size(); + logging_instance->ForEachBackend([=](Backend& backend) { + backend.Write(e); + if (flush) backend.Flush(); + }); + } } } } // namespace Common::Log diff --git a/src/common/logging.h b/src/common/logging.h index eabc683f63..d273eb0c49 100644 --- a/src/common/logging.h +++ b/src/common/logging.h @@ -89,7 +89,7 @@ void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, u template void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::format_string format, const Args&... args) { - FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format, fmt::make_format_args(args...)); + FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format.get(), fmt::make_format_args(args...)); } /// Implements a log message filter which allows different log classes to have different minimum @@ -140,25 +140,4 @@ void Stop(); void SetGlobalFilter(const Filter& filter); void SetColorConsoleBackendEnabled(bool enabled); -/// @brief A log entry. Log entries are store in a structured format to permit more varied output -/// formatting on different frontends, as well as facilitating filtering and aggregation. -struct Entry { - std::string message; - std::chrono::microseconds timestamp; - Class log_class{}; - Level log_level{}; - const char* filename = nullptr; - const char* function = nullptr; - unsigned int line_num = 0; -}; - -/// Formats a log entry into the provided text buffer. -std::string FormatLogMessage(const Entry& entry) noexcept; - -/// Prints the same message as `PrintMessage`, but colored according to the severity level. -void PrintColoredMessage(const Entry& entry) noexcept; - -/// Formats and prints a log entry to the android logcat. -void PrintMessageToLogcat(const Entry& entry) noexcept; - } // namespace Common::Log diff --git a/src/common/lru_cache.h b/src/common/lru_cache.h index 36cea5d27e..952ba0eb8b 100644 --- a/src/common/lru_cache.h +++ b/src/common/lru_cache.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -60,7 +63,7 @@ public: template void ForEachItemBelow(TickType tick, Func&& func) { static constexpr bool RETURNS_BOOL = - std::is_same_v, bool>; + std::is_same_v, bool>; Item* iterator = first_item; while (iterator) { if (static_cast(tick) - static_cast(iterator->tick) < 0) { diff --git a/src/common/make_unique_for_overwrite.h b/src/common/make_unique_for_overwrite.h deleted file mode 100644 index 17f81bba4a..0000000000 --- a/src/common/make_unique_for_overwrite.h +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -namespace Common { - -template - requires(!std::is_array_v) -std::unique_ptr make_unique_for_overwrite() { - return std::unique_ptr(new T); -} - -template - requires std::is_unbounded_array_v -std::unique_ptr make_unique_for_overwrite(std::size_t n) { - return std::unique_ptr(new std::remove_extent_t[n]); -} - -template - requires std::is_bounded_array_v -void make_unique_for_overwrite(Args&&...) = delete; - -} // namespace Common diff --git a/src/common/math_util.h b/src/common/math_util.h index 967d755f53..0f100b270c 100644 --- a/src/common/math_util.h +++ b/src/common/math_util.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2013 Dolphin Emulator Project @@ -13,8 +13,6 @@ namespace Common { -constexpr float PI = 3.1415926535f; - template struct Rectangle { T left{}; diff --git a/src/common/net/net.cpp b/src/common/net/net.cpp index 1d836cbd1d..5c45e13701 100644 --- a/src/common/net/net.cpp +++ b/src/common/net/net.cpp @@ -62,16 +62,21 @@ std::vector Release::GetPlatformAssets() const { #ifdef _WIN32 #ifdef ARCHITECTURE_x86_64 - find_asset("Standard", {"amd64-msvc-standard.exe", "amd64-msvc-standard.zip", "mingw-amd64-gcc-standard.exe", "mingw-amd64-gcc-standard.zip"}); - find_asset("PGO", {"mingw-amd64-clang-pgo.exe", "mingw-amd64-clang-pgo.zip"}); +#ifdef _MSC_VER + find_asset("Standard", {"amd64-msvc-standard.exe", "amd64-msvc-standard.zip"}); +#else // _MSC_VER + find_asset("Standard", {BUILD_ID "-gcc-standard.exe", BUILD_ID "-gcc-standard.zip"}); + find_asset("PGO", {BUILD_ID "-clang-pgo.exe", BUILD_ID "-clang-pgo.zip"}); +#endif // _MSC_VER #elif defined(ARCHITECTURE_arm64) - find_asset("Standard", {"mingw-arm64-clang-standard.exe", "mingw-arm64-clang-standard.zip"}); - find_asset("PGO", {"mingw-arm64-clang-pgo.exe", "mingw-arm64-clang-pgo.zip"}); -#endif + find_asset("Standard", {"arm64-clang-standard.exe", "arm64-clang-standard.zip"}); + find_asset("PGO", {"arm64-clang-pgo.exe", "arm64-clang-pgo.zip"}); +#endif // ARCHITECTURE_arm64 #elif defined(__APPLE__) #ifdef ARCHITECTURE_arm64 - find_asset("Standard", {".dmg", ".tar.gz"}); -#endif + find_asset("Standard", {"standard.dmg", "standard.tar.gz", ".dmg", ".tar.gz"}); + find_asset("PGO", {"pgo.dmg", "pgo.tar.gz"}); +#endif // ARCHITECTURE_arm64 #elif defined(__ANDROID__) #ifdef ARCHITECTURE_x86_64 find_asset("Standard", {"chromeos.apk"}); @@ -82,9 +87,9 @@ std::vector Release::GetPlatformAssets() const { find_asset("Standard", {"optimized.apk"}); #else find_asset("Standard", {"standard.apk"}); -#endif -#endif -#endif +#endif // GENSHIN_SPOOF +#endif // ARCHITECTURE_arm64 +#endif // __APPLE__ return found_assets; } @@ -236,7 +241,7 @@ std::optional MakeRequest(const std::string& url, const std::string response.status); return {}; } - if (!response.headers.contains("content-type")) { + if (!response.has_header("content-type")) { LOG_ERROR(Common, "GET to {}{} returned no content", url, path); return {}; } diff --git a/src/common/net/net.h b/src/common/net/net.h index b07caabe20..77c80d9dc3 100644 --- a/src/common/net/net.h +++ b/src/common/net/net.h @@ -11,14 +11,14 @@ namespace Common::Net { -typedef struct { +struct Asset { std::string name; std::string url; std::string path; std::string filename; -} Asset; +}; -typedef struct Release { +struct Release { std::string title; std::string body; std::string tag; @@ -39,7 +39,7 @@ typedef struct Release { static std::optional FromJson(const std::string_view& json, const std::string &host, const std::string& repo); static std::vector ListFromJson(const nlohmann::json &json, const std::string &host, const std::string &repo); static std::vector ListFromJson(const std::string_view &json, const std::string &host, const std::string &repo); -} Release; +}; // Make a request via httplib, and return the response body if applicable. std::optional MakeRequest(const std::string &url, const std::string &path); diff --git a/src/common/page_table.cpp b/src/common/page_table.cpp index d34ba89993..73278cfc59 100644 --- a/src/common/page_table.cpp +++ b/src/common/page_table.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -13,39 +13,11 @@ PageTable::PageTable() = default; PageTable::~PageTable() noexcept = default; -bool PageTable::BeginTraversal(TraversalEntry* out_entry, TraversalContext* out_context, - Common::ProcessAddress address) const { - out_context->next_offset = GetInteger(address); - out_context->next_page = address / page_size; - - return this->ContinueTraversal(out_entry, out_context); -} - -bool PageTable::ContinueTraversal(TraversalEntry* out_entry, TraversalContext* context) const { - // Setup invalid defaults. - out_entry->phys_addr = 0; - out_entry->block_size = page_size; - // Validate that we can read the actual entry. - if (auto const page = context->next_page; page < entries.size()) { - // Validate that the entry is mapped. - if (auto const paddr = entries[page].addr; paddr != 0) { - // Populate the results. - out_entry->phys_addr = paddr + context->next_offset; - context->next_page += 1; - context->next_offset += page_size; - return true; - } - } - context->next_page += 1; - context->next_offset += page_size; - return false; -} - -void PageTable::Resize(std::size_t address_space_width_in_bits, std::size_t page_size_in_bits) { - auto const num_page_table_entries = 1ULL << (address_space_width_in_bits - page_size_in_bits); - entries.resize(num_page_table_entries); +void PageTable::Resize(std::size_t address_space_width_in_bits, std::size_t page_bits) { + auto const num_page_table_entries = 1ULL << (address_space_width_in_bits - page_bits); + entries.ResizeAndClear(num_page_table_entries); current_address_space_width_in_bits = address_space_width_in_bits; - page_size = 1ULL << page_size_in_bits; + current_page_bits = page_bits; } } // namespace Common diff --git a/src/common/page_table.h b/src/common/page_table.h index 0dfd152331..a21fe5311b 100644 --- a/src/common/page_table.h +++ b/src/common/page_table.h @@ -9,22 +9,22 @@ #include #include "common/common_types.h" +#include "common/sparse_large_vector.h" #include "common/typed_address.h" -#include "common/virtual_buffer.h" namespace Common { enum class PageType : u8 { /// Page is unmapped and should cause an access error. - Unmapped, + Unmapped = 0b00, /// Page is mapped to regular memory. This is the only type you can get pointers to. - Memory, + Memory = 0b01, /// Page is mapped to regular memory, but inaccessible from CPU fastmem and must use /// the callbacks. - DebugMemory, + DebugMemory = 0b10, /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and /// invalidation - RasterizerCachedMemory, + RasterizerCachedMemory = 0b11, }; /** @@ -42,57 +42,86 @@ struct PageTable { u64 next_offset{}; }; - /// Number of bits reserved for attribute tagging. - /// This can be at most the guaranteed alignment of the pointers in the page table. - static constexpr int ATTRIBUTE_BITS = 2; + /// Masks out bits reserved for attribute tagging. + static constexpr u64 ATTRIBUTE_MASK = ((1ULL << 44) - 1) << 12; + + /// Specifies sign bit for page table entries. + static constexpr u64 SIGN_BIT = 45 + 12; // 44 bits of data + page offset /** - * Pair of host pointer and page type attribute. - * This uses the lower bits of a given pointer to store the attribute tag. + * Atomic tuple of host pointer, page type, and block id. + * This uses the lower bits of a given pointer to store the attributes. * Writing and reading the pointer attribute pair is guaranteed to be atomic for the same method * call. In other words, they are guaranteed to be synchronized at all times. */ - class PageInfo { + class PageEntryData { public: + struct Data { + Data(bool marked_, PageType type_, u16 block_, u64 page_) + : marked(static_cast(marked_) & 0b1) + , type(static_cast(type_) & ((1ULL << 2) - 1)) + , block(static_cast(block_) & ((1ULL << 9) - 1)) + , page((page_ >> 12) & ((1ULL << 45) - 1)) + , block2((static_cast(block_) >> 9) & ((1ULL << 7) - 1)) {} + u64 marked : 1; + u64 type : 2; + u64 block : 9; + u64 page : 45; // 44 bits of actual data (64 - page offset (12) - reserved (8)) + a sign bit + u64 block2 : 7; + }; + + [[nodiscard]] Data Raw() const noexcept { + return std::bit_cast(data_raw.load(std::memory_order_relaxed)); + } + /// Returns the page pointer - [[nodiscard]] uintptr_t Pointer() const noexcept { - return ExtractPointer(raw.load(std::memory_order_relaxed)); + [[nodiscard]] uintptr_t Pointer(bool ignored_marked = false) const noexcept { + return ExtractPointer(std::bit_cast(data_raw.load(std::memory_order_relaxed)), ignored_marked); } /// Returns the page type attribute [[nodiscard]] PageType Type() const noexcept { - return ExtractType(raw.load(std::memory_order_relaxed)); + return static_cast(std::bit_cast(data_raw.load(std::memory_order_relaxed)).type); + } + + /// Returns the block identifier. + [[nodiscard]] u16 Block() const noexcept { + return ExtractBlock(std::bit_cast(data_raw.load(std::memory_order_relaxed))); } /// Returns the page pointer and attribute pair, extracted from the same atomic read - [[nodiscard]] std::pair PointerType() const noexcept { - const uintptr_t non_atomic_raw = raw.load(std::memory_order_relaxed); - return {ExtractPointer(non_atomic_raw), ExtractType(non_atomic_raw)}; + [[nodiscard]] std::tuple PointerTypeBlock(bool ignore_marked = false) const noexcept { + const auto non_atomic_raw = std::bit_cast(data_raw.load(std::memory_order_relaxed)); + return {ExtractPointer(non_atomic_raw, ignore_marked), static_cast(non_atomic_raw.type), ExtractBlock(non_atomic_raw)}; } - /// Returns the raw representation of the page information. - /// Use ExtractPointer and ExtractType to unpack the value. - [[nodiscard]] uintptr_t Raw() const noexcept { - return raw.load(std::memory_order_relaxed); + /// Write page info atomically + inline void Store(bool marked, PageType type, u16 block, uintptr_t pointer) noexcept { + data_raw.store(std::bit_cast(Data{marked, type, block, pointer})); } - /// Write a page pointer and type pair atomically - void Store(uintptr_t pointer, PageType type) noexcept { - raw.store(pointer | uintptr_t(type)); + inline void MarkRasterizerCached() noexcept { + data_raw.fetch_or(0b111); + } + + inline void MarkDebug(u64 ptr, u16 block) noexcept { + Store(true, PageType::DebugMemory, block, ptr); } /// Unpack a pointer from a page info raw representation - [[nodiscard]] static uintptr_t ExtractPointer(uintptr_t raw) noexcept { - return raw & (~uintptr_t{0} << ATTRIBUTE_BITS); + [[nodiscard]] static uintptr_t ExtractPointer(Data raw, bool ignore_marked = false) noexcept { + return raw.marked && !ignore_marked ? 0 + // shift raw.page's fake sign bit to the actual sign bit, then sign extend + : ((s64)(raw.page << (64 - 44))) >> (64 - 44 - 12); } - /// Unpack a page type from a page info raw representation - [[nodiscard]] static PageType ExtractType(uintptr_t raw) noexcept { - return static_cast(raw & ((uintptr_t{1} << ATTRIBUTE_BITS) - 1)); + [[nodiscard]] static u16 ExtractBlock(Data raw) noexcept { + return static_cast(raw.block | (raw.block2 << 9)); } private: - std::atomic raw; + std::atomic data_raw; + static_assert(sizeof(Data) == sizeof(std::atomic)); }; PageTable(); @@ -100,13 +129,8 @@ struct PageTable { PageTable(const PageTable&) = delete; PageTable& operator=(const PageTable&) = delete; - - PageTable(PageTable&&) noexcept = default; - PageTable& operator=(PageTable&&) noexcept = default; - - bool BeginTraversal(TraversalEntry* out_entry, TraversalContext* out_context, - Common::ProcessAddress address) const; - bool ContinueTraversal(TraversalEntry* out_entry, TraversalContext* context) const; + PageTable(PageTable&&) noexcept = delete; + PageTable& operator=(PageTable&&) noexcept = delete; /** * Resizes the page table to be able to accommodate enough pages within @@ -121,30 +145,14 @@ struct PageTable { return current_address_space_width_in_bits; } - bool GetPhysicalAddress(Common::PhysicalAddress* out_phys_addr, - Common::ProcessAddress virt_addr) const { - if (virt_addr > (1ULL << this->GetAddressSpaceBits())) { - return false; - } - - *out_phys_addr = entries[virt_addr / page_size].addr + GetInteger(virt_addr); - return true; - } - /// Vector of memory pointers backing each page. An entry can only be non-null if the /// corresponding attribute element is of type `Memory`. - struct PageEntryData { - PageInfo ptr; - u64 block; - u64 addr; - u64 padding; - }; - VirtualBuffer entries; - static_assert(sizeof(PageEntryData) == 32); + SparseLargeVector entries; + static_assert(sizeof(PageEntryData) == 8); u8* fastmem_arena{}; std::size_t current_address_space_width_in_bits{}; - std::size_t page_size{}; + std::size_t current_page_bits{}; }; } // namespace Common diff --git a/src/common/param_package.h b/src/common/param_package.h index 0216863a39..5561dc1b7b 100644 --- a/src/common/param_package.h +++ b/src/common/param_package.h @@ -8,14 +8,14 @@ #include #include -#include +#include "common/container/unordered_map.h" namespace Common { /// A string-based key-value container supporting serializing to and deserializing from a string class ParamPackage { public: - using DataType = ankerl::unordered_dense::map; + using DataType = ::Common::unordered_map; ParamPackage() = default; explicit ParamPackage(const std::string& serialized); diff --git a/src/common/quaternion.h b/src/common/quaternion.h deleted file mode 100644 index 5bb5f2af08..0000000000 --- a/src/common/quaternion.h +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-FileCopyrightText: 2016 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/vector_math.h" - -namespace Common { - -template -class Quaternion { -public: - Vec3 xyz; - T w{}; - - [[nodiscard]] Quaternion Inverse() const { - return {-xyz, w}; - } - - [[nodiscard]] Quaternion operator+(const Quaternion& other) const { - return {xyz + other.xyz, w + other.w}; - } - - [[nodiscard]] Quaternion operator-(const Quaternion& other) const { - return {xyz - other.xyz, w - other.w}; - } - - [[nodiscard]] Quaternion operator*( - const Quaternion& other) const { - return {xyz * other.w + other.xyz * w + Cross(xyz, other.xyz), - w * other.w - Dot(xyz, other.xyz)}; - } - - [[nodiscard]] Quaternion Normalized() const { - T length = std::sqrt(xyz.Length2() + w * w); - return {xyz / length, w / length}; - } - - [[nodiscard]] std::array ToMatrix() const { - const T x2 = xyz[0] * xyz[0]; - const T y2 = xyz[1] * xyz[1]; - const T z2 = xyz[2] * xyz[2]; - - const T xy = xyz[0] * xyz[1]; - const T wz = w * xyz[2]; - const T xz = xyz[0] * xyz[2]; - const T wy = w * xyz[1]; - const T yz = xyz[1] * xyz[2]; - const T wx = w * xyz[0]; - - return {1.0f - 2.0f * (y2 + z2), - 2.0f * (xy + wz), - 2.0f * (xz - wy), - 0.0f, - 2.0f * (xy - wz), - 1.0f - 2.0f * (x2 + z2), - 2.0f * (yz + wx), - 0.0f, - 2.0f * (xz + wy), - 2.0f * (yz - wx), - 1.0f - 2.0f * (x2 + y2), - 0.0f, - 0.0f, - 0.0f, - 0.0f, - 1.0f}; - } -}; - -template -[[nodiscard]] auto QuaternionRotate(const Quaternion& q, const Vec3& v) { - return v + 2 * Cross(q.xyz, Cross(q.xyz, v) + v * q.w); -} - -[[nodiscard]] inline Quaternion MakeQuaternion(const Vec3& axis, float angle) { - return {axis * std::sin(angle / 2), std::cos(angle / 2)}; -} - -} // namespace Common diff --git a/src/common/ring_buffer.h b/src/common/ring_buffer.h index e97854f514..10e53d3018 100644 --- a/src/common/ring_buffer.h +++ b/src/common/ring_buffer.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -23,17 +23,15 @@ namespace Common { /// @tparam T Element type /// @tparam capacity Number of slots in ring buffer template + requires std::is_trivial_v class RingBuffer { /// A "slot" is made of a single `T`. static constexpr std::size_t slot_size = sizeof(T); - // T must be safely memcpy-able and have a trivial default constructor. - static_assert(std::is_trivial_v); // Ensure capacity is sensible. static_assert(capacity < (std::numeric_limits::max)() / 2); static_assert((capacity & (capacity - 1)) == 0, "capacity must be a power of two"); // Ensure lock-free. static_assert(std::atomic_size_t::is_always_lock_free); - public: /// Pushes slots into the ring buffer /// @param new_slots Pointer to the slots to push diff --git a/src/common/scratch_buffer.h b/src/common/scratch_buffer.h index 2a98cda539..929a65609a 100644 --- a/src/common/scratch_buffer.h +++ b/src/common/scratch_buffer.h @@ -1,11 +1,14 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 #pragma once #include - -#include "common/make_unique_for_overwrite.h" +#include +#include namespace Common { @@ -34,8 +37,9 @@ public: ScratchBuffer() = default; explicit ScratchBuffer(size_type initial_capacity) - : last_requested_size{initial_capacity}, buffer_capacity{initial_capacity}, - buffer{Common::make_unique_for_overwrite(initial_capacity)} {} + : last_requested_size{initial_capacity} + , buffer_capacity{initial_capacity} + , buffer{std::make_unique_for_overwrite(initial_capacity)} {} ~ScratchBuffer() = default; ScratchBuffer(const ScratchBuffer&) = delete; @@ -60,8 +64,8 @@ public: /// The previously held data will remain intact. void resize(size_type size) { if (size > buffer_capacity) { - auto new_buffer = Common::make_unique_for_overwrite(size); - std::move(buffer.get(), buffer.get() + buffer_capacity, new_buffer.get()); + auto new_buffer = std::make_unique_for_overwrite(size); + std::memcpy(new_buffer.get(), buffer.get(), buffer_capacity * sizeof(T)); buffer = std::move(new_buffer); buffer_capacity = size; } @@ -73,7 +77,7 @@ public: void resize_destructive(size_type size) { if (size > buffer_capacity) { buffer_capacity = size; - buffer = Common::make_unique_for_overwrite(buffer_capacity); + buffer = std::make_unique_for_overwrite(buffer_capacity); } last_requested_size = size; } diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 54848c4dd1..3f5154026e 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -54,7 +54,6 @@ SWITCHABLE(CpuBackend, true); SWITCHABLE(CpuAccuracy, true); SWITCHABLE(FullscreenMode, true); SWITCHABLE(GpuAccuracy, true); -SWITCHABLE(GpuLogLevel, true); SWITCHABLE(Language, true); SWITCHABLE(MemoryLayout, true); SWITCHABLE(NvdecEmulation, false); @@ -127,17 +126,15 @@ void LogSettings() { setting->UsingGlobal() ? '-' : 'C', TranslateCategory(category), setting->GetLabel()); if (is_default) - settings_list.push_back(fmt::format("{}: {}\n", name, setting->Canonicalize())); + settings_list.push_back(fmt::format("{}: {}", name, setting->Canonicalize())); else - settings_list.push_front(fmt::format("{}: {}\n", name, setting->Canonicalize())); + settings_list.push_front(fmt::format("{}: {}", name, setting->Canonicalize())); } } } - - std::string settings_str{}; + LOG_INFO(Config, "Eden Configuration:"); for (auto const& e : settings_list) - settings_str += e; - LOG_INFO(Config, "Eden Configuration:\n{}", settings_str); + LOG_INFO(Config, "{}", e); #define LOG_PATH(NAME) \ LOG_INFO(Config, #NAME ": {}", Common::FS::PathToUTF8String(Common::FS::GetEdenPath(Common::FS::EdenPath::NAME))) LOG_PATH(CacheDir); @@ -149,7 +146,7 @@ void LogSettings() { #undef LOG_PATH } -bool getDebugKnobAt(u8 i) { +bool GetDebugKnobAt(u8 i) { return (values.debug_knobs.GetValue() & (1 << (i & 0xF))) != 0; } @@ -157,14 +154,6 @@ void UpdateGPUAccuracy() { values.current_gpu_accuracy = values.gpu_accuracy.GetValue(); } -bool IsGPULevelLow() { - return values.current_gpu_accuracy == GpuAccuracy::Low; -} - -bool IsGPULevelMedium() { - return values.current_gpu_accuracy == GpuAccuracy::Medium; -} - bool IsGPULevelHigh() { return values.current_gpu_accuracy == GpuAccuracy::High; } @@ -177,6 +166,18 @@ bool IsDMALevelSafe() { return values.dma_accuracy.GetValue() == DmaAccuracy::Safe; } +bool IsGPUFenceBehaviorDefault() { + return values.gpu_fence_behavior.GetValue() == GpuFenceBehavior::Default; +} + +bool IsGPUFenceBehaviorBalanced() { + return values.gpu_fence_behavior.GetValue() == GpuFenceBehavior::Balanced; +} + +bool IsGPUFenceBehaviorAccurate() { + return values.gpu_fence_behavior.GetValue() == GpuFenceBehavior::Accurate; +} + bool IsFastmemEnabled() { if (values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Debugging) return bool(values.cpuopt_fastmem); @@ -213,6 +214,16 @@ bool IsNceEnabled() { return is_nce_enabled; } +static u64 current_program_id = 0; + +void SetCurrentProgramID(u64 program_id) { + current_program_id = program_id; +} + +u64 GetCurrentProgramID() { + return current_program_id; +} + bool IsDockedMode() { return values.use_docked_mode.GetValue() == Settings::ConsoleMode::Docked; } @@ -363,6 +374,28 @@ void UpdateRescalingInfo() { TranslateResolutionInfo(setup, info); } +u32 FrameGenMultiplier() { + return std::clamp(values.frame_gen_multiplier.GetValue(), MIN_FRAME_GEN_MULTIPLIER, + MAX_FRAME_GEN_MULTIPLIER); +} + +size_t FrameGenGenerations() { + if (!values.frame_gen.GetValue()) { + return 0; + } + return FrameGenMultiplier() - 1; +} + +size_t FrameGenMaxGenerations() { + if (!values.frame_gen.GetValue()) { + return 0; + } + if (values.frame_gen_target_rate.GetValue() != 0) { + return MAX_FRAME_GEN_MULTIPLIER - 1; + } + return FrameGenMultiplier() - 1; +} + void RestoreGlobalState(bool is_powered_on) { // If a game is running, DO NOT restore the global settings state if (is_powered_on) { @@ -435,4 +468,16 @@ void ToggleSlowMode() { SetSpeedMode(SpeedMode::Standard); } +bool IsOpenGL() { + const auto backend = Settings::values.renderer_backend.GetValue(); + switch (backend) { + case RendererBackend::OpenGL_GLSL: + case RendererBackend::OpenGL_GLASM: + case RendererBackend::OpenGL_SPIRV: + return true; + default: + return false; + } +} + } // namespace Settings diff --git a/src/common/settings.h b/src/common/settings.h index d9e1f09dcc..b57bec5c12 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -171,8 +171,13 @@ struct Values { SwitchableSetting enable_overlay{linkage, false, "enable_overlay", Category::LibraryApplet}; // Audio - SwitchableSetting sink_id{linkage, AudioEngine::Auto, "output_engine", - Category::Audio, Specialization::RuntimeList}; + SwitchableSetting sink_id{linkage, +#ifdef __ANDROID__ + AudioEngine::Sdl3, +#else + AudioEngine::Auto, +#endif + "output_engine", Category::Audio, Specialization::RuntimeList}; SwitchableSetting audio_output_device_id{ linkage, "auto", "output_device", Category::Audio, Specialization::RuntimeList}; SwitchableSetting audio_input_device_id{ @@ -260,10 +265,10 @@ struct Values { Category::Cpu}; SwitchableSetting cpu_accuracy{linkage, CpuAccuracy::Auto, "cpu_accuracy", Category::Cpu}; - SwitchableSetting fast_cpu_time{linkage, - CpuClock::Off, + SwitchableSetting cpu_clock{linkage, + CpuClock::Normal, "fast_cpu_time", - Category::Cpu, + Category::System, Specialization::Default, true, true}; @@ -336,7 +341,7 @@ struct Values { RendererBackend::Vulkan, #endif "backend", Category::Renderer}; - SwitchableSetting vulkan_device{linkage, 0, "vulkan_device", Category::Renderer, Specialization::RuntimeList}; + SwitchableSetting vulkan_device{linkage, 0, "vulkan_device", Category::Renderer, Specialization::RuntimeList}; // Graphics Settings ResolutionScalingInfo resolution_info{}; @@ -352,14 +357,14 @@ struct Values { true}; SwitchableSetting scaling_filter{linkage, - ScalingFilter::Bilinear, + ScalingFilter::NearestNeighbor, "scaling_filter", Category::Renderer, Specialization::Default, true, true}; SwitchableSetting fsr_sharpening_slider{linkage, -#ifdef ANDROID +#ifdef __ANDROID__ 0, #else 25, @@ -388,8 +393,92 @@ struct Values { true, true}; - SwitchableSetting use_asynchronous_gpu_emulation{ - linkage, true, "use_asynchronous_gpu_emulation", Category::Renderer}; + SwitchableSetting post_shader_chain{linkage, + std::string(), + "post_shader_chain", + Category::Renderer, + Specialization::Default, + true, + true}; + + SwitchableSetting post_shader_preset{linkage, + std::string(), + "post_shader_preset", + Category::Renderer, + Specialization::Default, + true, + true}; + + SwitchableSetting post_shader_enabled{linkage, + true, + "post_shader_enabled", + Category::Renderer, + Specialization::Default, + true, + true}; + + SwitchableSetting frame_gen{linkage, false, "frame_gen", Category::Renderer, + Specialization::Default, true, false}; + + SwitchableSetting frame_gen_multiplier{linkage, + 2, + 2, + 4, + "frame_gen_multiplier", + Category::Renderer, + Specialization::Countable, + true, + false, + &frame_gen}; + + SwitchableSetting frame_gen_target_rate{linkage, + 0, + 0, + 240, + "frame_gen_target_rate", + Category::Renderer, + Specialization::Countable, + true, + true, + &frame_gen}; + + SwitchableSetting frame_gen_flow_scale_auto{linkage, + true, + "frame_gen_flow_scale_auto", + Category::Renderer, + Specialization::Default, + true, + false, + &frame_gen}; + + SwitchableSetting frame_gen_flow_scale{linkage, + 75, + 25, + 100, + "frame_gen_flow_scale", + Category::Renderer, + Specialization::Countable | + Specialization::Percentage, + true, + true, + &frame_gen}; + + SwitchableSetting frame_gen_queue_target{linkage, + 0, + 0, + 2, + "frame_gen_queue_target", + Category::Renderer, + Specialization::Countable, + true, + false, + &frame_gen}; + + SwitchableSetting frame_gen_dump_flow{linkage, false, "frame_gen_dump_flow", + Category::Renderer}; + + SwitchableSetting use_asynchronous_gpu_emulation{linkage, true, "use_asynchronous_gpu_emulation", + Category::Renderer}; // *nix platforms may have issues with the borderless windowed fullscreen mode. // Default to exclusive fullscreen on these platforms for now. SwitchableSetting fullscreen_mode{linkage, @@ -412,10 +501,10 @@ struct Values { linkage, 0, "bg_blue", Category::Renderer, Specialization::Default, true, true}; SwitchableSetting gpu_accuracy{linkage, -#ifdef ANDROID +#ifdef __ANDROID__ GpuAccuracy::Low, #else - GpuAccuracy::Medium, + GpuAccuracy::High, #endif "gpu_accuracy", Category::RendererAdvanced, @@ -423,7 +512,7 @@ struct Values { true, true}; - GpuAccuracy current_gpu_accuracy{GpuAccuracy::Medium}; + GpuAccuracy current_gpu_accuracy{GpuAccuracy::High}; SwitchableSetting dma_accuracy{linkage, DmaAccuracy::Default, @@ -433,16 +522,26 @@ struct Values { true, true}; + SwitchableSetting gpu_fence_behavior{linkage, + GpuFenceBehavior::Default, + GpuFenceBehavior::Default, + GpuFenceBehavior::Accurate, + "gpu_fence_behavior", + Category::RendererAdvanced, + Specialization::Default, + true, + true}; + SwitchableSetting vram_usage_mode{linkage, VramUsageMode::Conservative, "vram_usage_mode", Category::RendererAdvanced}; - SwitchableSetting nvdec_emulation{linkage, NvdecEmulation::Gpu, + SwitchableSetting nvdec_emulation{linkage, NvdecEmulation::Cpu, "nvdec_emulation", Category::RendererAdvanced}; SwitchableSetting max_anisotropy{linkage, -#ifdef ANDROID +#ifdef __ANDROID__ AnisotropyMode::Default, #else AnisotropyMode::Automatic, @@ -495,7 +594,7 @@ struct Values { Category::RendererAdvanced}; SwitchableSetting use_reactive_flushing{linkage, -#ifdef ANDROID +#ifdef __ANDROID__ false, #else true, @@ -514,7 +613,7 @@ struct Values { true, true}; -#ifdef ANDROID +#ifdef __ANDROID__ SwitchableSetting use_optimized_vertex_buffers{linkage, false, "use_optimized_vertex_buffers", @@ -525,13 +624,13 @@ struct Values { #endif // Renderer Hacks // - SwitchableSetting fast_gpu_time{linkage, - GpuOverclock::Medium, - "fast_gpu_time", - Category::RendererHacks, - Specialization::Default, - true, - true}; + SwitchableSetting gpu_clock{linkage, + GpuClock::Boost, + "fast_gpu_time", + Category::System, + Specialization::Default, + true, + true}; SwitchableSetting skip_cpu_inner_invalidation{linkage, false, @@ -541,7 +640,7 @@ struct Values { true, true}; SwitchableSetting async_presentation{linkage, -#ifdef ANDROID +#ifdef __ANDROID__ true, #else false, @@ -554,14 +653,6 @@ struct Values { SwitchableSetting emulate_bgr565{linkage, false, "emulate_bgr565", Category::RendererHacks}; - SwitchableSetting rescale_hack{linkage, -#ifdef __ANDROID__ - true, -#else - false, -#endif - "rescale_hack", Category::RendererHacks}; - SwitchableSetting use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders", Category::RendererHacks}; @@ -587,7 +678,7 @@ struct Values { Category::RendererHacks}; SwitchableSetting dyna_state{linkage, -#if defined(ANDROID) +#if defined(__ANDROID__) ExtendedDynamicState::Disabled, #elif defined(__APPLE__) ExtendedDynamicState::Disabled, @@ -606,7 +697,7 @@ struct Values { Specialization::Scalar}; SwitchableSetting vertex_input_dynamic_state{linkage, -#if defined (ANDROID) +#ifdef __ANDROID__ false, #else true, @@ -622,7 +713,7 @@ struct Values { linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug}; Setting enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey", Category::RendererDebug}; -#if defined(ANDROID) && defined(ARCHITECTURE_arm64) +#if defined(__ANDROID__) && defined(ARCHITECTURE_arm64) // Debug override for automatic BCn patching detection Setting patch_old_qcom_drivers{linkage, false, "patch_old_qcom_drivers", Category::RendererDebug}; @@ -649,8 +740,8 @@ struct Values { false, true, &custom_rtc_enabled}; SwitchableSetting custom_rtc_offset{linkage, 0, - (std::numeric_limits::min)(), - (std::numeric_limits::max)(), + (std::numeric_limits::min)(), + (std::numeric_limits::max)(), "custom_rtc_offset", Category::System, Specialization::Countable, @@ -667,7 +758,7 @@ struct Values { Setting current_user{linkage, 0, "current_user", Category::System}; SwitchableSetting use_docked_mode{linkage, -#ifdef ANDROID +#ifdef __ANDROID__ ConsoleMode::Handheld, #else ConsoleMode::Docked, @@ -680,7 +771,15 @@ struct Values { // Controls InputSetting> players; - + Setting disable_wgi_xinput{ + linkage, false, "disable_wgi_xinput", Category::Controls, Specialization::Default, +// Only read/write disable_wgi_xinput on Windows platforms +#ifdef _WIN32 + true +#else + false +#endif + }; Setting enable_raw_input{ linkage, false, "enable_raw_input", Category::Controls, Specialization::Default, // Only read/write enable_raw_input on Windows platforms @@ -739,7 +838,7 @@ struct Values { Setting touch_device{linkage, "min_x:100,min_y:50,max_x:1800,max_y:850", "touch_device", Category::Controls}; - Setting touch_from_button_map_index{linkage, 0, "touch_from_button_map", + Setting touch_from_button_map_index{linkage, 0, "touch_from_button_map", Category::Controls}; std::vector touch_from_button_maps; @@ -767,11 +866,17 @@ struct Values { bool record_frame_times; Setting use_gdbstub{linkage, false, "use_gdbstub", Category::Debugging}; Setting gdbstub_port{linkage, 6543, "gdbstub_port", Category::Debugging}; - Setting program_args{linkage, std::string(), "program_args", Category::Debugging}; + SwitchableSetting program_args{linkage, + std::string(), + "program_args", + Category::Debugging, + Specialization::Default, + true, // save_ - persist in config file + false}; // runtime_modifiable_ - startup-only Setting dump_exefs{linkage, false, "dump_exefs", Category::Debugging}; Setting dump_nso{linkage, false, "dump_nso", Category::Debugging}; - Setting dump_shaders{ - linkage, false, "dump_shaders", Category::DebuggingGraphics, Specialization::Default, + Setting dump_guest_shaders{ + linkage, false, "dump_guest_shaders", Category::DebuggingGraphics, Specialization::Default, false}; Setting dump_macros{ linkage, false, "dump_macros", Category::DebuggingGraphics, Specialization::Default, false}; @@ -795,9 +900,8 @@ struct Values { Setting disable_web_applet{linkage, true, "disable_web_applet", Category::Debugging}; // GPU Logging - Setting gpu_logging_enabled{linkage, false, "gpu_logging_enabled", Category::Debugging}; - SwitchableSetting gpu_log_level{linkage, GpuLogLevel::Standard, "gpu_log_level", - Category::Debugging}; + Setting gpu_log_level{linkage, GpuLogLevel::Off, "gpu_log_level", + Category::Debugging}; Setting gpu_log_vulkan_calls{linkage, true, "gpu_log_vulkan_calls", Category::Debugging}; Setting gpu_log_shader_dumps{linkage, false, "gpu_log_shader_dumps", Category::Debugging}; Setting gpu_log_memory_tracking{linkage, true, "gpu_log_memory_tracking", @@ -825,7 +929,7 @@ struct Values { // Network Setting network_interface{linkage, std::string(), "network_interface", Category::Network}; - SwitchableSetting airplane_mode{linkage, false, "airplane_mode", Category::Network}; + SwitchableSetting airplane_mode{linkage, true, "airplane_mode", Category::Network}; // WebService Setting web_api_url{linkage, "api.ynet-fun.xyz", "web_api_url", @@ -840,24 +944,41 @@ struct Values { // Per-game overrides bool use_squashed_iterated_blend; + }; extern Values values; -bool getDebugKnobAt(u8 i); +constexpr u32 MIN_FRAME_GEN_MULTIPLIER = 2; +constexpr u32 MAX_FRAME_GEN_MULTIPLIER = 4; + +[[nodiscard]] u32 FrameGenMultiplier(); + +[[nodiscard]] size_t FrameGenGenerations(); + +[[nodiscard]] size_t FrameGenMaxGenerations(); + +bool GetDebugKnobAt(u8 i); void UpdateGPUAccuracy(); -bool IsGPULevelLow(); -bool IsGPULevelMedium(); bool IsGPULevelHigh(); bool IsDMALevelDefault(); bool IsDMALevelSafe(); +bool IsGPUFenceBehaviorDefault(); +bool IsGPUFenceBehaviorBalanced(); +bool IsGPUFenceBehaviorAccurate(); + bool IsFastmemEnabled(); void SetNceEnabled(bool is_64bit); bool IsNceEnabled(); +void SetCurrentProgramID(u64 program_id); +u64 GetCurrentProgramID(); + +bool IsOpenGL(); + bool IsDockedMode(); float Volume(); diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 638be4127f..eaf3ec541d 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -92,12 +92,14 @@ struct EnumMetadata { // AudioEngine must be specified discretely due to having existing but slightly different // canonicalizations // TODO (lat9nq): Remove explicit definition of AudioEngine/sink_id -enum class AudioEngine : u32 { Auto, Cubeb, Sdl2, Null, Oboe, }; +enum class AudioEngine : u32 { Auto, Cubeb, Sdl3, Null, }; template<> inline std::vector> EnumMetadata::Canonicalizations() { return { - {"auto", AudioEngine::Auto}, {"cubeb", AudioEngine::Cubeb}, {"sdl2", AudioEngine::Sdl2}, - {"null", AudioEngine::Null}, {"oboe", AudioEngine::Oboe}, + {"auto", AudioEngine::Auto}, + {"cubeb", AudioEngine::Cubeb}, + {"sdl3", AudioEngine::Sdl3}, + {"null", AudioEngine::Null}, }; } /// @brief This is just a sufficiently large number that is more than the number of other enums declared here @@ -111,7 +113,7 @@ inline AudioEngine EnumMetadata::GetFirst() { } template<> inline AudioEngine EnumMetadata::GetLast() { - return AudioEngine::Oboe; + return AudioEngine::Null; } ENUM(AudioMode, Mono, Stereo, Surround); @@ -126,37 +128,38 @@ ENUM(TimeZone, Auto, Default, Cet, Cst6Cdt, Cuba, Eet, Egypt, Eire, Est, Est5Edt GmtPlusZero, GmtMinusZero, GmtZero, Greenwich, Hongkong, Hst, Iceland, Iran, Israel, Jamaica, Japan, Kwajalein, Libya, Met, Mst, Mst7Mdt, Navajo, Nz, NzChat, Poland, Portugal, Prc, Pst8Pdt, Roc, Rok, Singapore, Turkey, Uct, Universal, Utc, WSu, Wet, Zulu); -ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16, X32, X64, None); +ENUM(AnisotropyMode, Automatic, Default, X2, X4, X8, X16); ENUM(AstcDecodeMode, Cpu, Gpu, CpuAsynchronous); ENUM(AstcRecompression, Uncompressed, Bc1, Bc3); ENUM(FramePacingMode, Target_Auto, Target_30, Target_60, Target_90, Target_120); ENUM(VSyncMode, Immediate, Mailbox, Fifo, FifoRelaxed); ENUM(VramUsageMode, Conservative, Aggressive); ENUM(RendererBackend, OpenGL_GLSL, Vulkan, Null, OpenGL_GLASM, OpenGL_SPIRV); -ENUM(GpuAccuracy, Low, Medium, High); +ENUM(GpuAccuracy, Low, High); ENUM(DmaAccuracy, Default, Unsafe, Safe); +ENUM(GpuFenceBehavior, Default, Immediate, Balanced, Accurate); ENUM(CpuBackend, Dynarmic, Nce); ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid, Debugging); -ENUM(CpuClock, Off, Boost, Fast) +ENUM(CpuClock, Normal, Boost, Overclock) ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb, Memory_10Gb, Memory_12Gb); ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never); ENUM(FullscreenMode, Borderless, Exclusive); ENUM(NvdecEmulation, Off, Cpu, Gpu); ENUM(ResolutionSetup, Res1_4X, Res1_2X, Res3_4X, Res1X, Res5_4X, Res3_2X, Res2X, Res3X, Res4X, Res5X, Res6X, Res7X, Res8X); -ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Gaussian, Lanczos, ScaleForce, Fsr, Area, ZeroTangent, BSpline, Mitchell, Spline1, Mmpx, MaxEnum); -ENUM(AntiAliasing, None, Fxaa, Smaa, MaxEnum); +ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Gaussian, Lanczos, ScaleForce, Fsr, Area, ZeroTangent, BSpline, Mitchell, Spline1, Mmpx, Sgsr, SgsrEdge); +ENUM(AntiAliasing, None, Fxaa, Smaa); ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch); ENUM(ConsoleMode, Handheld, Docked); -ENUM(AppletMode, HLE, LLE); +ENUM(AppletMode, HLE, LLE, Disabled); ENUM(SpirvOptimizeMode, Never, OnLoad, Always); -ENUM(GpuOverclock, Normal, Medium, High) +ENUM(GpuClock, Normal, Boost, Overclock) ENUM(GpuUnswizzleSize, VerySmall, Small, Normal, Large, VeryLarge) ENUM(GpuUnswizzle, VeryLow, Low, Normal, Medium, High) ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High) ENUM(TemperatureUnits, Celsius, Fahrenheit) ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3); ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All) -ENUM(GameListMode, TreeView, GridView); +ENUM(GameListMode, TreeView, GridView, CarouselView); ENUM(SpeedMode, Standard, Turbo, Slow); template diff --git a/src/common/settings_setting.h b/src/common/settings_setting.h index 076aceef29..0a9243cc0d 100644 --- a/src/common/settings_setting.h +++ b/src/common/settings_setting.h @@ -6,14 +6,13 @@ #pragma once +#include #include -#include #include #include #include -#include -#include -#include +#include +#include #include "common/common_types.h" #include "common/settings_common.h" #include "common/settings_enums.h" @@ -101,7 +100,15 @@ public: * @param val The desired value */ virtual void SetValue(const Type& val) { - Type temp{ranged ? std::clamp(val, minimum, maximum) : val}; + // Enums have a maximal range which they're allowed + Type temp{}; + if constexpr (std::is_enum_v) { + auto const r_min = std::underlying_type_t(0); + auto const r_max = std::underlying_type_t(EnumMetadata::GetLast()); + temp = Type(std::clamp(std::underlying_type_t(val), r_min, r_max)); + } else { + temp = ranged ? std::clamp(val, this->minimum, this->maximum) : val; + } std::swap(value, temp); } @@ -129,7 +136,7 @@ protected: } else if constexpr (std::is_floating_point_v) { return fmt::format("{:f}", value_); } else if constexpr (std::is_enum_v) { - return std::to_string(u32(value_)); + return std::to_string(std::underlying_type_t(value_)); } else { return std::to_string(value_); } @@ -371,7 +378,15 @@ public: * @param val The new value */ void SetValue(const Type& val) override final { - Type temp{ranged ? std::clamp(val, this->minimum, this->maximum) : val}; + // Enums have a maximal range which they're allowed + Type temp{}; + if constexpr (std::is_enum_v) { + auto const r_min = std::underlying_type_t(0); + auto const r_max = std::underlying_type_t(EnumMetadata::GetLast()); + temp = Type(std::clamp(std::underlying_type_t(val), r_min, r_max)); + } else { + temp = ranged ? std::clamp(val, this->minimum, this->maximum) : val; + } if (use_global) { std::swap(this->value, temp); } else { diff --git a/src/common/slot_vector.h b/src/common/slot_vector.h index e464d3d948..f6da7a59d1 100644 --- a/src/common/slot_vector.h +++ b/src/common/slot_vector.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -20,10 +20,14 @@ namespace Common { struct SlotId { + static constexpr u32 TAGGED_MASK = 0x7fffffff; + static constexpr u32 TAGGED_VALUE = 0x80000000; static constexpr u32 INVALID_INDEX = (std::numeric_limits::max)(); + constexpr u32 Value() const noexcept { + return index & (~TAGGED_VALUE); + } constexpr auto operator<=>(const SlotId&) const noexcept = default; - constexpr explicit operator bool() const noexcept { return index != INVALID_INDEX; } @@ -47,12 +51,12 @@ public: Iterator& operator++() noexcept { const u64* const bitset = slot_vector->stored_bitset.data(); const u32 size = static_cast(slot_vector->stored_bitset.size()) * 64; - if (id.index < size) { + if (id.Value() < size) { do { ++id.index; - } while (id.index < size && !IsValid(bitset)); - if (id.index == size) { - id.index = SlotId::INVALID_INDEX; + } while (id.Value() < size && !IsValid(bitset)); + if (id.Value() == size) { + id = SlotId{}; } } return *this; @@ -85,7 +89,7 @@ public: : slot_vector{slot_vector_}, id{id_} {} bool IsValid(const u64* bitset) const noexcept { - return ((bitset[id.index / 64] >> (id.index % 64)) & 1) != 0; + return ((bitset[id.Value() / 64] >> (id.Value() % 64)) & 1) != 0; } SlotVector* slot_vector; @@ -107,12 +111,12 @@ public: [[nodiscard]] T& operator[](SlotId id) noexcept { ValidateIndex(id); - return values[id.index].object; + return values[id.Value()].object; } [[nodiscard]] const T& operator[](SlotId id) const noexcept { ValidateIndex(id); - return values[id.index].object; + return values[id.Value()].object; } template @@ -125,9 +129,9 @@ public: } void erase(SlotId id) noexcept { - values[id.index].object.~T(); - free_list.push_back(id.index); - ResetStorageBit(id.index); + values[id.Value()].object.~T(); + free_list.push_back(id.Value()); + ResetStorageBit(id.Value()); } [[nodiscard]] Iterator begin() noexcept { @@ -141,7 +145,7 @@ public: } [[nodiscard]] Iterator end() noexcept { - return Iterator(this, SlotId{SlotId::INVALID_INDEX}); + return Iterator(this, SlotId{}); } [[nodiscard]] size_t size() const noexcept { @@ -175,8 +179,8 @@ private: void ValidateIndex(SlotId id) const noexcept { DEBUG_ASSERT(id); - DEBUG_ASSERT(id.index / 64 < stored_bitset.size()); - DEBUG_ASSERT(((stored_bitset[id.index / 64] >> (id.index % 64)) & 1) != 0); + DEBUG_ASSERT(id.Value() / 64 < stored_bitset.size()); + DEBUG_ASSERT(((stored_bitset[id.Value() / 64] >> (id.Value() % 64)) & 1) != 0); } [[nodiscard]] u32 FreeValueIndex() noexcept { @@ -208,9 +212,7 @@ private: const size_t old_free_size = free_list.size(); free_list.resize(old_free_size + (new_capacity - values_capacity)); - std::iota(free_list.begin() + old_free_size, free_list.end(), - static_cast(values_capacity)); - + std::iota(free_list.begin() + old_free_size, free_list.end(), u32(values_capacity)); delete[] values; values = new_values; values_capacity = new_capacity; diff --git a/src/common/socket_types.h b/src/common/socket_types.h index 63824a5c48..5b1e4d7b1d 100644 --- a/src/common/socket_types.h +++ b/src/common/socket_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -28,9 +31,118 @@ enum class Type { /// Protocol values for sockets enum class Protocol : u8 { Unspecified, ///< Represents 0, usable in various places + IP, ICMP, TCP, UDP, + IPV6, + RAW, + IGMP, + GGP, + IPV4, + ST, + EGP, + PIGP, + RCCMON, + NVPII, + PUP, + ARGUS, + EMCON, + XNET, + CHAOS, + MUX, + MEAS, + HMP, + PRM, + IDP, + TRUNK1, + TRUNK2, + LEAF1, + LEAF2, + RDP, + IRTP, + TP, + BLT, + NSP, + INP, + DCCP, + //TODO: 3PC, + IDPR, + XTP, + DDP, + CMTP, + TPXX, + IL, + SDRP, + ROUTING, + FRAGMENT, + IDRP, + RSVP, + GRE, + MHRP, + BHA, + ESP, + AH, + INLSP, + SWIPE, + NHRP, + MOBILE, + TLSP, + SKIP, + ICMPV6, + NONE, + DSTOPTS, + AHIP, + CFTP, + HELLO, + SATEXPAK, + KRYPTOLAN, + RVD, + IPPC, + ADFS, + SATMON, + VISA, + IPCV, + CPNX, + CPHB, + WSN, + PVP, + BRSATMON, + ND, + WBMON, + WBEXPAK, + EON, + VMTP, + SVMTP, + VINES, + TTP, + IGP, + DGP, + TCF, + IGRP, + OSPFIGP, + SRPC, + LARP, + MTP, + AX25, + IPEIP, + MICP, + SCCSP, + ETHERIP, + ENCAP, + APES, + GMTP, + IPCOMP, + SCTP, + MH, + UDPLITE, + HIP, + SHIM6, + PIM, + CARP, + PGM, + MPLS, + PFSYNC }; /// Shutdown mode diff --git a/src/common/sparse_large_vector.cpp b/src/common/sparse_large_vector.cpp new file mode 100644 index 0000000000..eee8ff6d18 --- /dev/null +++ b/src/common/sparse_large_vector.cpp @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +/* virtual_buffer.cpp */ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifdef _WIN32 +#include +#include +#include +#else +#include +#endif + +#include "common/alignment.h" +#include "common/assert.h" +#include "common/sparse_large_vector.h" + +namespace Common { + +#ifdef _WIN32 +static std::vector> vector_regions {}; +static std::mutex vector_regions_mutex {}; + +// Workaround for handling non-commited memory accessed by Dynarmic; usually result of an error +static LONG WINAPI FakePageFaultHandler(PEXCEPTION_POINTERS info) { + DWORD code = info->ExceptionRecord->ExceptionCode; + u64 exception_addr = reinterpret_cast(info->ExceptionRecord->ExceptionInformation[1]); + + if (code != EXCEPTION_ACCESS_VIOLATION || info->ExceptionRecord->ExceptionInformation[0] == 1) { + // Not our problem + return EXCEPTION_CONTINUE_SEARCH; + } + + u64 addr = 0, addr2 = 0; + + { + std::lock_guard lock(vector_regions_mutex); + for (auto region: vector_regions) { + auto addr_shifted = exception_addr >> HostPageBits; + if (region.first <= addr_shifted && addr_shifted <= region.second) { + addr = addr_shifted; + } + + // Page-boundary accesses + if (auto addr_ = (exception_addr + 0x40) >> HostPageBits; addr_ != addr_shifted && region.first <= addr_ && addr_ <= region.second) { + addr2 = addr_; + } + + if (addr != 0 || addr2 != 0) { + break; + } + } + } + + if (addr == 0 && addr2 == 0) { + // Not our problem + return EXCEPTION_CONTINUE_SEARCH; + } + + LOG_ERROR(HW_Memory, "Accessing an unallocated region of a SparseLargeVector at {:#x}; this shouldn't happen and is likely a Dynarmic error!", exception_addr); + + // Commit this region + if (addr != 0) { + if (!CommitVectorPage(addr << HostPageBits, false)) { + return EXCEPTION_CONTINUE_SEARCH; + } + } + // Commit next region if needed + if (addr2 != 0) { + if (!CommitVectorPage(addr2 << HostPageBits, false)) { + return EXCEPTION_CONTINUE_SEARCH; + } + } + + return EXCEPTION_CONTINUE_EXECUTION; +} + +bool CommitVectorPage(uintptr_t addr, bool write) noexcept { + MEMORY_BASIC_INFORMATION info {}; + auto res = VirtualQuery(reinterpret_cast(addr), &info, sizeof(info)); + if (res == 0) { + LOG_CRITICAL(HW_Memory, "Failed to query large buffer region at {:#x} with error {}, will try committing anyway", addr, GetLastError()); + } else if (info.State == MEM_COMMIT) { + DWORD old_protect {}; + auto perm = write ? PAGE_READWRITE : PAGE_READONLY; + if (!VirtualProtect(reinterpret_cast(addr), HostPageSize, perm, &old_protect)) { + LOG_ERROR(HW_Memory, "Failed to change permissions of large buffer region at {:#x}, error {}", addr, GetLastError()); + return false; + } + return true; + } else if (info.State != MEM_RESERVE) { + LOG_ERROR(HW_Memory, "Tried to commit an unreserved large buffer region at {:#x} that is not mapped (state {:#x})", addr, info.State); + return false; + } + + auto perm = write ? PAGE_READWRITE : PAGE_READONLY; + void* res2 = VirtualAlloc(reinterpret_cast(addr), HostPageSize, MEM_COMMIT, perm); + if (res2 == nullptr) { + LOG_ERROR(HW_Memory, "Failed to commit large buffer region at {:#x}, error {}", addr, GetLastError()); + return false; + } + + return true; +} +#endif + +#ifndef MAP_NOCORE +#define MAP_NOCORE 0 +#endif +#ifndef MADV_FREE +#define MADV_FREE MADV_DONTNEED +#endif + +void DecommitVectorPage(uintptr_t base) noexcept { +#if defined(_WIN32) + VirtualFree(reinterpret_cast(base), HostPageSize, MEM_DECOMMIT); +#elif defined(__linux__) + // Linux's MADV_DONTNEED zeros out pages for us + madvise(reinterpret_cast(base), HostPageSize, MADV_DONTNEED); +#else + madvise(reinterpret_cast(base), HostPageSize, MADV_FREE); + std::memset(reinterpret_cast(base), 0, HostPageSize); +#endif +} + +void* AllocateMemoryPages(std::size_t size) noexcept { + if (auto page = HostPageSize; size % page != 0) { + LOG_WARNING(HW_Memory, "Allocating unaligned large vector with size {:#x}; aligning to {} page size", size, page); + size = AlignUp(size, page); + } + +#ifdef _WIN32 + // We will never use this memory entirely so instead of committing it up front let's just reserve it and commit each page individually + void* base = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE); + + if (base != nullptr) { + std::lock_guard lock(vector_regions_mutex); + vector_regions.emplace_back(reinterpret_cast(base) >> HostPageBits, (reinterpret_cast(base) + size) >> HostPageBits); + + static std::once_flag flag; + std::call_once(flag, []() { AddVectoredExceptionHandler(1, FakePageFaultHandler); }); + } else { + // Try committing everything instead?? + LOG_WARNING(HW_Memory, "Failed to reserve large vector region with error {}, trying to commit instead..", GetLastError()); + base = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE); + } + ASSERT_MSG(base, "Failed to reserve {:#x} sized region with error {}", size, GetLastError()); +#else + void* base = mmap(nullptr, size, PROT_READ, MAP_ANON | MAP_PRIVATE | MAP_NOCORE, -1, 0); + if (base == MAP_FAILED) + base = nullptr; + ASSERT_MSG(base, "Failed to allocate {:#x} sized region with error {}", size, strerror(errno)); +#endif + return base; +} + +void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept { + if (auto page = HostPageSize; size % page != 0) { + size = AlignUp(size, page); + } + if (!base) + return; +#ifdef _WIN32 + std::lock_guard lock(vector_regions_mutex); + std::erase_if(vector_regions, [base](const auto& r) {return r.first == reinterpret_cast(base); }); + ASSERT(VirtualFree(base, 0, MEM_RELEASE)); +#else + ASSERT(munmap(base, size) == 0); +#endif +} + +} // namespace Common diff --git a/src/common/sparse_large_vector.h b/src/common/sparse_large_vector.h new file mode 100644 index 0000000000..8496c28cef --- /dev/null +++ b/src/common/sparse_large_vector.h @@ -0,0 +1,214 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +/* virtual_buffer.h */ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "common/alignment.h" +#include "common/assert.h" + +namespace Common { + +#ifdef _WIN32 +constexpr u64 HostPageSize = 0x1000; +constexpr u64 HostPageBits = 12; +constexpr u64 HostPageMask = ~(HostPageSize - 1); +bool CommitVectorPage(uintptr_t addr, bool write) noexcept; +#else +inline const u64 HostPageSize = sysconf(_SC_PAGESIZE); +inline const u64 HostPageBits = std::countr_zero(HostPageSize); +inline const u64 HostPageMask = ~(HostPageSize - 1); +#endif + +void* AllocateMemoryPages(std::size_t size) noexcept; +void FreeMemoryPages(void* base, std::size_t size) noexcept; +void DecommitVectorPage(uintptr_t base) noexcept; + +/// A large page-aligned buffer that has optimized memory usage for zero-writes. +template + // MSVC doesn't regard structs with atomics as trivially copyable + // requires std::is_trivially_copyable_v +class SparseLargeVector final { +public: + constexpr SparseLargeVector() = default; + + explicit SparseLargeVector(std::size_t count) noexcept + : alloc_size{count * sizeof(T)} + { + base_ptr = static_cast(AllocateMemoryPages(alloc_size)); + + // each item in vector holds information for 64 pages + auto denom = HostPageSize * 64; + committed_pages = std::vector>((alloc_size + denom - 1) / denom); + } + + ~SparseLargeVector() noexcept { + FreeMemoryPages(base_ptr, alloc_size); + } + + SparseLargeVector(const SparseLargeVector&) = delete; + SparseLargeVector& operator=(const SparseLargeVector&) = delete; + SparseLargeVector(SparseLargeVector&& other) = delete; + SparseLargeVector& operator=(SparseLargeVector&& other) = delete; + + void ResizeAndClear(std::size_t count) noexcept { + if (auto const new_size = count * sizeof(T); new_size != alloc_size) { + FreeMemoryPages(base_ptr, alloc_size); + alloc_size = new_size; + base_ptr = static_cast(AllocateMemoryPages(alloc_size)); + + auto denom = HostPageSize * 64; + committed_pages = std::vector>((alloc_size + denom - 1) / denom); + } + } + + /// Returns a reference to the value of the requested index and allocates memory if needed. + T& GetAndFault(std::size_t index) noexcept { + if (index > alloc_size / sizeof(T)) { + UNREACHABLE_MSG("Out of bounds RW access on SparseLargeVector @ {}", index); + } + + if (!IsCommittedPage(index) && !CommitPage(index)) { + UNREACHABLE_MSG("Cannot access SparseLargeVector index {} with RW permission", index); + } + return base_ptr[index]; + } + + /// Returns a reference to the value of the requested index if initialized, or will otherwise return a zero-initialized object. + const T& GetOrDefault(std::size_t index) const { +#ifdef _WIN32 + if (!IsCommittedPage(index)) { + return *reinterpret_cast(&default_val); + } +#endif + // On non-Windows, OS page table should optimize this by pointing to a zero page if unallocated. + return base_ptr[index]; + } + + void Set(std::size_t index, const T& value) noexcept { + if (index > alloc_size / sizeof(T)) { + LOG_CRITICAL(Common_Memory, "Out of bounds write on SparseLargeVector @ {}", index); + return; + } + if (IsCommittedPage(index) || CommitPage(index)) + base_ptr[index] = value; + } + + void ZeroRegion(std::size_t start, std::size_t end_) noexcept { + u64 base = reinterpret_cast(&base_ptr[start]); + const u64 end = reinterpret_cast(&base_ptr[end_]); + + const u64 end_page = AlignUp(base, HostPageSize); + const u64 first_size = (std::min)(end_page, end) - base; + + if (IsCommittedPage(start)) { + std::memset(reinterpret_cast(base), 0, first_size); + } + + if (end <= end_page) + return; + + base = end_page; + + for (u64 page = base; page < end; page += HostPageSize) { + auto index = (page - reinterpret_cast(base_ptr)) / sizeof(T); + if (!IsCommittedPage(index)) { + continue; + } + + if (end - page >= HostPageSize) { + DecommitPage(index); + } else { + std::memset(reinterpret_cast(page), 0, end - page); + } + } + } + + constexpr void CommitRegion(size_t index, size_t end_) { + const u64 base = static_cast(index) * sizeof(T); + const u64 end = static_cast(end_) * sizeof(T); + + for (u64 page = AlignDown(base, HostPageSize); page < end; page += HostPageSize) { + if (!IsCommittedPage(page / sizeof(T))) { + CommitPage(page / sizeof(T)); + } + } + } + + constexpr T& GetUnchecked(size_t index) { + return base_ptr[index]; + } + + [[nodiscard]] constexpr const T& operator[](std::size_t index) const noexcept { + return GetOrDefault(index); + } + + [[nodiscard]] constexpr const T* data() const noexcept { + return base_ptr; + } + + [[nodiscard]] constexpr std::size_t size() const noexcept { + return alloc_size / sizeof(T); + } + +private: + [[nodiscard]] constexpr bool IsCommittedPage(std::size_t index) const noexcept { + if (index > alloc_size / sizeof(T)) { + LOG_CRITICAL(Common_Memory, "Out of bounds access on large vector @ {}", index); + return false; + } + + auto page = (index * sizeof(T)) >> HostPageBits; + auto val = committed_pages[page >> 6].load(std::memory_order_acquire); + return (val >> (page & 63)) & 1; + } + + constexpr bool CommitPage(std::size_t index) noexcept { + auto page_index = (index * sizeof(T)) >> HostPageBits; + auto page = reinterpret_cast(base_ptr + index) & HostPageMask; +#if defined(_WIN32) + if (!CommitVectorPage(page, true)) { + return false; + } +#else + if (mprotect(reinterpret_cast(page), HostPageSize, PROT_READ | PROT_WRITE) != 0) { + LOG_ERROR(Common_Memory, "Failed to commit large buffer region at index {}, error {}", index, strerror(errno)); + return false; + } +#endif + + committed_pages[page_index >> 6].fetch_or(1ULL << (page_index & 63), std::memory_order_release); + return true; + } + + constexpr void DecommitPage(std::size_t index) noexcept { + auto page_index = (index * sizeof(T)) >> HostPageBits; + auto page = reinterpret_cast(base_ptr + index) & HostPageMask; + + committed_pages[page_index >> 6].fetch_and(~(1ULL << (page_index & 63)), std::memory_order_release); + DecommitVectorPage(page); + } + + std::size_t alloc_size{}; + T* base_ptr{}; + + std::vector> committed_pages{}; +#ifdef _WIN32 + const std::array default_val{}; +#endif +}; + +} // namespace Common diff --git a/src/common/spin_lock.h b/src/common/spin_lock.h deleted file mode 100644 index 93d507ae80..0000000000 --- a/src/common/spin_lock.h +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#ifdef _MSC_VER -#include -#elif defined(ARCHITECTURE_x86_64) -#include -#endif -#include - -namespace Common { - -/// @brief A lock similar to mutex that forces a thread to spin wait instead calling the -/// supervisor. Should be used on short sequences of code. -struct SpinLock { - SpinLock() noexcept = default; - SpinLock(const SpinLock&) noexcept = delete; - SpinLock& operator=(const SpinLock&) noexcept = delete; - SpinLock(SpinLock&&) noexcept = delete; - SpinLock& operator=(SpinLock&&) noexcept = delete; - - inline void lock() noexcept { - while (lck.test_and_set(std::memory_order_acquire)) { -#if defined(ARCHITECTURE_x86_64) - _mm_pause(); -#elif defined(ARCHITECTURE_arm64) && defined(_MSC_VER) - __yield(); -#elif defined(ARCHITECTURE_arm64) - asm("yield"); -#endif - } - } - - inline void unlock() noexcept { - lck.clear(std::memory_order_release); - } - - [[nodiscard]] inline bool try_lock() noexcept { - return !lck.test_and_set(std::memory_order_acquire); - } - - std::atomic_flag lck = ATOMIC_FLAG_INIT; -}; - -} // namespace Common diff --git a/src/common/stb.cpp b/src/common/stb.cpp index d3b16665d4..59e709e57d 100644 --- a/src/common/stb.cpp +++ b/src/common/stb.cpp @@ -1,8 +1,10 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 -#define STB_IMAGE_IMPLEMENTATION -#define STB_IMAGE_RESIZE_IMPLEMENTATION -#define STB_IMAGE_WRITE_IMPLEMENTATION - +#define STB_IMAGE_IMPLEMENTATION 1 +#define STB_IMAGE_RESIZE_IMPLEMENTATION 1 +#define STB_IMAGE_WRITE_IMPLEMENTATION 1 #include "common/stb.h" diff --git a/src/common/stb.h b/src/common/stb.h index e5c197c11d..bc8f651426 100644 --- a/src/common/stb.h +++ b/src/common/stb.h @@ -1,8 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 +#define STBI_ONLY_JPEG 1 +#define STBI_WRITE_NO_STDIO 1 #include #include #include diff --git a/src/common/steady_clock.cpp b/src/common/steady_clock.cpp index 9c3d73659e..c516a12d65 100644 --- a/src/common/steady_clock.cpp +++ b/src/common/steady_clock.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -48,22 +48,10 @@ static s64 GetSystemTimeNS() { SteadyClock::time_point SteadyClock::Now() noexcept { #if defined(_WIN32) - static const auto freq = WindowsQueryPerformanceFrequency(); + static const auto timer_freq = WindowsQueryPerformanceFrequency(); const auto counter = WindowsQueryPerformanceCounter(); - - // 10 MHz is a very common QPC frequency on modern PCs. - // Optimizing for this specific frequency can double the performance of - // this function by avoiding the expensive frequency conversion path. - static constexpr s64 TenMHz = 10'000'000; - - if (freq == TenMHz) [[likely]] { - static_assert(period::den % TenMHz == 0); - static constexpr s64 Multiplier = period::den / TenMHz; - return time_point{duration{counter * Multiplier}}; - } - - const auto whole = (counter / freq) * period::den; - const auto part = (counter % freq) * period::den / freq; + const auto whole = (counter / timer_freq) * period::den; + const auto part = (counter % timer_freq) * period::den / timer_freq; return time_point{duration{whole + part}}; #elif defined(__APPLE__) return time_point{duration{clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW)}}; diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 7bcbe737b6..9543a62343 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2013 Dolphin Emulator Project @@ -18,7 +18,7 @@ #include #endif -#ifdef ANDROID +#ifdef __ANDROID__ #include #endif @@ -45,7 +45,7 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _ if (full_path.empty()) return false; -#ifdef ANDROID +#ifdef __ANDROID__ if (full_path[0] != '/') { *_pPath = Common::FS::Android::GetParentDirectory(full_path); *_pFilename = Common::FS::Android::GetFilename(full_path); diff --git a/src/common/swap.h b/src/common/swap.h index 1cf8a6ba39..c7646ea24d 100644 --- a/src/common/swap.h +++ b/src/common/swap.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2012 PPSSPP Project @@ -10,12 +10,10 @@ #pragma once -#if defined(_MSC_VER) #include -#endif -#include #include #include +#include #include "common/common_types.h" namespace Common { diff --git a/src/common/thread.cpp b/src/common/thread.cpp index f4bdb3f7c0..7b4dec3dce 100644 --- a/src/common/thread.cpp +++ b/src/common/thread.cpp @@ -1,12 +1,16 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2013 Dolphin Emulator Project // SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include #include #include +#include "common/adpf.h" #include "common/error.h" #include "common/logging.h" #include "common/assert.h" @@ -18,23 +22,429 @@ #elif defined(_WIN32) #include #include "common/string_util.h" +#include "common/windows/timer_resolution.h" #else #if defined(__FreeBSD__) #include #include #include +// Compatibility with CPUset +#define cpu_set_t cpuset_t #elif defined(__DragonFly__) || defined(__OpenBSD__) || defined(__Bitrig__) #include #endif #include #include #endif + #ifndef _WIN32 #include #endif -#ifdef __FreeBSD__ -# define cpu_set_t cpuset_t +#ifdef __linux__ +#include +#include + +namespace { +constexpr int NICE_AUDIO = -16; +constexpr int NICE_URGENT_DISPLAY = -8; +constexpr int NICE_DISPLAY = -4; +constexpr int NICE_DEFAULT = 0; +constexpr int NICE_BACKGROUND = 10; + +int LowestAllowedNice() { + static const int lowest = [] { + rlimit limit{}; + if (getrlimit(RLIMIT_NICE, &limit) != 0) { + return 0; + } + if (limit.rlim_cur >= 40) { + return -20; + } + return 20 - static_cast(limit.rlim_cur); + }(); + return lowest; +} + +int NiceValueForPriority(Common::ThreadPriority priority) { + const int wanted = [priority] { + switch (priority) { + case Common::ThreadPriority::Low: return NICE_BACKGROUND; + case Common::ThreadPriority::Normal: return NICE_DEFAULT; + case Common::ThreadPriority::High: return NICE_DISPLAY; + case Common::ThreadPriority::VeryHigh: return NICE_URGENT_DISPLAY; + case Common::ThreadPriority::Critical: return NICE_AUDIO; + default: return NICE_DEFAULT; + } + }(); + return (std::max)(wanted, (std::min)(NICE_DEFAULT, LowestAllowedNice())); +} +} // Anonymous namespace +#endif + +#ifdef __ANDROID__ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +constexpr size_t ANDROID_MINIMUM_PERFORMANCE_CORES = 4; + +// A core counts as a performance core while it is within this much of the fastest one. +constexpr s64 ANDROID_PERFORMANCE_CAPACITY_PERCENT = 50; + +constexpr std::chrono::nanoseconds ANDROID_POLICY_POLL_INTERVAL = std::chrono::milliseconds{500}; + +enum class CoreGroup { + Unrestricted, + Performance, + Efficiency, +}; + +struct ThreadPolicy { + pid_t tid; + CoreGroup group; + s32 nice_value; + bool has_nice; +}; + +struct CoreInfo { + s64 weight; + u64 midr; + s32 cpu; +}; + +struct CpuTopologyState { + std::mutex topology_mutex; + cpu_set_t allowed{}; + cpu_set_t performance{}; + cpu_set_t efficiency{}; + bool separated = false; + bool initialized = false; + + pid_t canary_tid = 0; + cpu_set_t canary_mask{}; + bool canary_valid = false; + + std::atomic next_poll_ns{0}; + + std::mutex policy_mutex; + std::vector policies; +}; + +CpuTopologyState& State() { + static CpuTopologyState* const state = new CpuTopologyState(); + return *state; +} + +struct PolicyRegistration { + ~PolicyRegistration() { + const pid_t tid = gettid(); + ::Common::ADPF::RemoveCurrentThread(); + CpuTopologyState& state = State(); + std::scoped_lock lock{state.policy_mutex}; + std::erase_if(state.policies, + [tid](const ThreadPolicy& policy) { return policy.tid == tid; }); + } +}; + +thread_local PolicyRegistration t_policy_registration; + +s32 PossibleCpuCount() { + std::ifstream file("/sys/devices/system/cpu/possible"); + std::string list; + if (file && std::getline(file, list) && !list.empty()) { + s64 highest = -1; + const char* cursor = list.c_str(); + while (*cursor != '\0') { + char* end = nullptr; + const s64 value = std::strtol(cursor, &end, 10); + if (end == cursor) { + break; + } + highest = (std::max)(highest, value); + cursor = end; + while (*cursor == '-' || *cursor == ',') { + ++cursor; + } + } + if (highest >= 0) { + return static_cast((std::min)(highest + 1, CPU_SETSIZE)); + } + } + const s64 configured = sysconf(_SC_NPROCESSORS_CONF); + if (configured > 0) { + return static_cast((std::min)(configured, CPU_SETSIZE)); + } + return static_cast((std::min)(std::thread::hardware_concurrency(), CPU_SETSIZE)); +} + +s64 ReadCpuScalar(s32 cpu, const char* node) { + s64 value = 0; + std::ifstream file("/sys/devices/system/cpu/cpu" + std::to_string(cpu) + "/" + node); + if (!file || !(file >> value) || value <= 0) { + return 0; + } + return value; +} + +u64 ReadCpuMidr(s32 cpu) { + u64 midr = 0; + std::ifstream file("/sys/devices/system/cpu/cpu" + std::to_string(cpu) + + "/regs/identification/midr_el1"); + if (!file || !(file >> std::hex >> midr)) { + return 0; + } + return midr; +} + +std::vector CollectCores(const cpu_set_t& allowed, s32 total, const char* node, + bool require_all) { + std::vector cores; + for (s32 cpu = 0; cpu < total; ++cpu) { + if (!CPU_ISSET(cpu, &allowed)) { + continue; + } + const s64 weight = ReadCpuScalar(cpu, node); + if (weight <= 0) { + if (require_all) { + return {}; + } + LOG_WARNING(Common, "Could not read {} for CPU {}, treating it as an efficiency core", + node, cpu); + continue; + } + cores.push_back(CoreInfo{weight, ReadCpuMidr(cpu), cpu}); + } + return cores; +} + +bool WeightsAreUniform(const std::vector& cores) { + return std::all_of(cores.begin(), cores.end(), + [&](const CoreInfo& core) { return core.weight == cores.front().weight; }); +} + +bool MidrsAreDistinct(const std::vector& cores) { + return std::none_of(cores.begin(), cores.end(), + [](const CoreInfo& core) { return core.midr == 0; }) && + std::any_of(cores.begin(), cores.end(), + [&](const CoreInfo& core) { return core.midr != cores.front().midr; }); +} + +void ComputeTopologyLocked(CpuTopologyState& state) { + state.initialized = true; + state.separated = false; + CPU_ZERO(&state.allowed); + CPU_ZERO(&state.performance); + CPU_ZERO(&state.efficiency); + + if (sched_getaffinity(getpid(), sizeof(state.allowed), &state.allowed) != 0) { + LOG_WARNING(Common, "Could not query process CPU affinity: {}", + ::Common::GetLastErrorMsg()); + return; + } + + const s32 total = PossibleCpuCount(); + auto cores = CollectCores(state.allowed, total, "cpu_capacity", true); + if (cores.empty() || (WeightsAreUniform(cores) && MidrsAreDistinct(cores))) { + auto by_frequency = CollectCores(state.allowed, total, "cpufreq/cpuinfo_max_freq", false); + if (!by_frequency.empty()) { + cores = std::move(by_frequency); + } + } + if (cores.empty()) { + LOG_WARNING(Common, "Could not determine CPU topology, thread placement is disabled"); + return; + } + + if (WeightsAreUniform(cores)) { + if (MidrsAreDistinct(cores)) { + LOG_WARNING(Common, "CPU clusters differ but rank identically, thread placement is " + "disabled"); + } else { + LOG_INFO(Common, "CPU cores are symmetric, thread placement is disabled"); + } + return; + } + + std::sort(cores.begin(), cores.end(), [](const CoreInfo& lhs, const CoreInfo& rhs) { + if (lhs.weight != rhs.weight) { + return lhs.weight > rhs.weight; + } + return lhs.cpu < rhs.cpu; + }); + + const s64 fastest = cores.front().weight; + size_t taken = 0; + for (const auto& core : cores) { + const bool fast_enough = + core.weight * 100 >= fastest * ANDROID_PERFORMANCE_CAPACITY_PERCENT; + if (!fast_enough && taken >= ANDROID_MINIMUM_PERFORMANCE_CORES) { + break; + } + CPU_SET(core.cpu, &state.performance); + ++taken; + } + if (taken == 0) { + return; + } + + for (s32 cpu = 0; cpu < total; ++cpu) { + if (CPU_ISSET(cpu, &state.allowed) && !CPU_ISSET(cpu, &state.performance)) { + CPU_SET(cpu, &state.efficiency); + } + } + + state.separated = CPU_COUNT(&state.efficiency) > 0; + LOG_INFO(Common, "CPU topology: {} performance cores, {} efficiency cores, separation {}", + CPU_COUNT(&state.performance), CPU_COUNT(&state.efficiency), + state.separated ? "enabled" : "unavailable"); +} + +void EnsureTopologyLocked(CpuTopologyState& state) { + if (!state.initialized) { + ComputeTopologyLocked(state); + } +} + +void RefreshTopologyLocked(CpuTopologyState& state) { + if (!state.initialized) { + ComputeTopologyLocked(state); + return; + } + cpu_set_t current; + CPU_ZERO(¤t); + if (sched_getaffinity(getpid(), sizeof(current), ¤t) != 0) { + return; + } + if (std::memcmp(¤t, &state.allowed, sizeof(current)) != 0) { + ComputeTopologyLocked(state); + } +} + +bool ApplyCoreGroupLocked(CpuTopologyState& state, pid_t tid, CoreGroup group, + bool* gone = nullptr) { + const bool restrict_group = group != CoreGroup::Unrestricted && state.separated; + const cpu_set_t* mask = &state.allowed; + if (restrict_group) { + mask = group == CoreGroup::Performance ? &state.performance : &state.efficiency; + } + if (CPU_COUNT(mask) == 0) { + return false; + } + if (sched_setaffinity(tid, sizeof(*mask), mask) != 0) { + if (gone != nullptr && errno == ESRCH) { + *gone = true; + return false; + } + LOG_WARNING(Common, "Could not restrict thread {} to its core group: {}", tid, + ::Common::GetLastErrorMsg()); + return false; + } + return true; +} + +bool KernelPreservesRequestedAffinity() { + utsname info{}; + if (uname(&info) != 0) { + return false; + } + s32 major = 0; + s32 minor = 0; + if (std::sscanf(info.release, "%d.%d", &major, &minor) != 2) { + return false; + } + return major > 6 || (major == 6 && minor >= 2); +} + +void SnapshotCanaryLocked(CpuTopologyState& state) { + state.canary_valid = false; + if (!state.separated) { + return; + } + for (const auto& policy : state.policies) { + if (policy.group == CoreGroup::Unrestricted) { + continue; + } + cpu_set_t mask; + CPU_ZERO(&mask); + if (sched_getaffinity(policy.tid, sizeof(mask), &mask) != 0) { + continue; + } + state.canary_tid = policy.tid; + state.canary_mask = mask; + state.canary_valid = true; + return; + } +} + +bool DueForPoll(CpuTopologyState& state) { + const auto now = std::chrono::steady_clock::now().time_since_epoch(); + const s64 now_ns = std::chrono::duration_cast(now).count(); + s64 next = state.next_poll_ns.load(std::memory_order_relaxed); + if (now_ns < next) { + return false; + } + return state.next_poll_ns.compare_exchange_strong( + next, now_ns + ANDROID_POLICY_POLL_INTERVAL.count(), std::memory_order_relaxed); +} + +ThreadPolicy& AcquirePolicyLocked(CpuTopologyState& state, pid_t tid) { + for (auto& policy : state.policies) { + if (policy.tid == tid) { + return policy; + } + } + return state.policies.emplace_back(ThreadPolicy{tid, CoreGroup::Unrestricted, 0, false}); +} + +void SetCurrentThreadCoreGroup(CoreGroup group) { + const pid_t tid = gettid(); + (void)&t_policy_registration; + + CoreGroup effective = group; + if (tid == getpid() && group != CoreGroup::Unrestricted) { + LOG_WARNING(Common, "Refusing to place the main thread: the CPU topology is read from it"); + effective = CoreGroup::Unrestricted; + } + + CpuTopologyState& state = State(); + std::scoped_lock topology_lock{state.topology_mutex}; + EnsureTopologyLocked(state); + ApplyCoreGroupLocked(state, tid, effective); + + std::scoped_lock policy_lock{state.policy_mutex}; + AcquirePolicyLocked(state, tid).group = effective; + if (!state.canary_valid) { + SnapshotCanaryLocked(state); + } +} + +void RememberCurrentThreadNice(pid_t tid, s32 nice_value) { + (void)&t_policy_registration; + CpuTopologyState& state = State(); + std::scoped_lock lock{state.policy_mutex}; + ThreadPolicy& policy = AcquirePolicyLocked(state, tid); + policy.nice_value = nice_value; + policy.has_nice = true; +} +} // Anonymous namespace +#endif + +#include "common/cpu_features.h" +#ifdef ARCHITECTURE_x86_64 +#ifdef _MSC_VER +#include +#else +#include +#endif +#include "common/x64/rdtsc.h" #endif namespace Common { @@ -65,21 +475,31 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) { } }(); set_thread_priority(find_thread(NULL), priority); -#else - pthread_t this_thread = pthread_self(); - const auto scheduling_type = SCHED_OTHER; - s32 max_prio = sched_get_priority_max(scheduling_type); - s32 min_prio = sched_get_priority_min(scheduling_type); - u32 level = (std::max)(u32(new_priority) + 1, 4U); - - struct sched_param params; - if (max_prio > min_prio) { - params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4; - } else { - params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4; +#elif defined(__ANDROID__) + const int nice_value = NiceValueForPriority(new_priority); + const pid_t tid = gettid(); + if (setpriority(PRIO_PROCESS, static_cast(tid), nice_value) != 0) { + LOG_WARNING(Common, "Could not set thread nice value to {}: {}", nice_value, + GetLastErrorMsg()); + return; + } + RememberCurrentThreadNice(tid, nice_value); +#elif defined(__linux__) + const int nice_value = NiceValueForPriority(new_priority); + if (setpriority(PRIO_PROCESS, 0, nice_value) != 0) { + LOG_DEBUG(Common, "Could not set thread nice value to {}: {}", nice_value, + GetLastErrorMsg()); + } +#else + const s32 max_prio = sched_get_priority_max(SCHED_OTHER); + const s32 min_prio = sched_get_priority_min(SCHED_OTHER); + if (max_prio > min_prio) { + const u32 level = (std::min)(static_cast(new_priority), 4U); + sched_param params{}; + params.sched_priority = + min_prio + static_cast(static_cast(max_prio - min_prio) * level) / 4; + pthread_setschedparam(pthread_self(), SCHED_OTHER, ¶ms); } - - pthread_setschedparam(this_thread, scheduling_type, ¶ms); #endif } @@ -119,29 +539,192 @@ void SetCurrentThreadName(const char* name) { #endif } -void PinCurrentThreadToPerformanceCore(size_t core_id) { - ASSERT(core_id < 4); - // If we set a flag for a CPU that doesn't exist, the thread may not be allowed to - // run in ANY processor! - auto const total_cores = std::thread::hardware_concurrency(); - if (core_id < total_cores) { +void SetCurrentThreadToPerformanceCores() { #if defined(__ANDROID__) - cpu_set_t set; - CPU_ZERO(&set); - CPU_SET(core_id, &set); - sched_setaffinity(pthread_self(), sizeof(set), &set); -#elif defined(__linux__) || defined(__FreeBSD__) - cpu_set_t set; - CPU_ZERO(&set); - CPU_SET(core_id, &set); - pthread_setaffinity_np(pthread_self(), sizeof(set), &set); -#elif defined(_WIN32) - DWORD set = 1UL << core_id; - SetThreadAffinityMask(GetCurrentThread(), set); -#else - // No pin functionality implemented -#endif + if (ADPF::AddCurrentThread(ADPF::Session::Render)) { + SetCurrentThreadCoreGroup(CoreGroup::Unrestricted); + return; } + SetCurrentThreadCoreGroup(CoreGroup::Performance); +#endif } +void SetCurrentThreadToEfficiencyCores() { +#if defined(__ANDROID__) + if (ADPF::AddCurrentThread(ADPF::Session::Background)) { + SetCurrentThreadCoreGroup(CoreGroup::Unrestricted); + return; + } + SetCurrentThreadCoreGroup(CoreGroup::Efficiency); +#endif +} + +void SetCurrentThreadToBackgroundWork() { +#if defined(__ANDROID__) + ADPF::AddCurrentThread(ADPF::Session::Background); + SetCurrentThreadCoreGroup(CoreGroup::Unrestricted); +#endif +} + +void SetCurrentThreadToAllCores() { +#if defined(__ANDROID__) + ADPF::RemoveCurrentThread(); + SetCurrentThreadCoreGroup(CoreGroup::Unrestricted); +#endif +} + +void RefreshThreadPolicies() { +#if defined(__ANDROID__) + CpuTopologyState& state = State(); + std::scoped_lock topology_lock{state.topology_mutex}; + RefreshTopologyLocked(state); + + std::scoped_lock policy_lock{state.policy_mutex}; + std::erase_if(state.policies, [&state](const ThreadPolicy& policy) { + bool gone = false; + if (policy.has_nice && + setpriority(PRIO_PROCESS, static_cast(policy.tid), policy.nice_value) != 0 && + errno == ESRCH) { + gone = true; + } + if (!gone) { + ApplyCoreGroupLocked(state, policy.tid, policy.group, &gone); + } + return gone; + }); + SnapshotCanaryLocked(state); +#endif +} + +void PollThreadPolicies() { +#if defined(__ANDROID__) + static const bool needed = !KernelPreservesRequestedAffinity(); + if (!needed) { + return; + } + + CpuTopologyState& state = State(); + if (!DueForPoll(state)) { + return; + } + + pid_t tid; + cpu_set_t expected; + { + std::scoped_lock lock{state.topology_mutex}; + if (!state.canary_valid) { + return; + } + tid = state.canary_tid; + expected = state.canary_mask; + } + + cpu_set_t current; + CPU_ZERO(¤t); + if (sched_getaffinity(tid, sizeof(current), ¤t) == 0 && + std::memcmp(¤t, &expected, sizeof(current)) == 0) { + return; + } + RefreshThreadPolicies(); +#endif +} + +#ifdef ARCHITECTURE_x86_64 +// On Linux and UNIX systems, a futex would nominally be used to cover the costs +// the idea is that it's intuitivelly cheaper to use a direct instruction as opposed to a full futex call +// the underlying libc++ implementation uses pthread_cond_timedwait which MAY invoke a futex +// Let's pretend the OS is too expensive to jump into, and avoid ANY context switches +// this should *IN THEORY* lower CPU usage while just waiting for stuff effectively +// For windows the minimal quanta resolution is about 500us, and normal CRT cond var is 1.5ms(?) +// so may as well avoid that too +// Let's just give ALL platforms the same mechanisms (almost) for when they have umonitor OR waitpkg +#ifdef __clang__ +__attribute__((target("waitpkg,mwaitx"))) +#elif defined(__GNUC__) +#pragma GCC target("waitpkg") +#pragma GCC target("mwaitx") +#endif +bool Event::WaitFor(const std::chrono::nanoseconds time) { +#ifdef _WIN32 + auto const start = Common::X64::FencedRDTSC(); + auto const& caps = Common::g_cpu_caps; + [[maybe_unused]] auto const end = start + Common::g_wall_clock.NsToTicks(time); + if (caps.monitorx) { + while (true) { + // Armed monitor, as per manual, MWAITX must be conditional if the condition isn't satisfied + // to prevent a race condition. + _mm_monitorx(reinterpret_cast(std::addressof(is_set)), 0, 0); + if (!is_set.load()) { + // RDTSC may be fenced here due to atomic load +#ifdef _MSC_VER + auto const now = __rdtsc(); +#else + auto const now = _rdtsc(); +#endif + if (end > now) { + u32 const cycles = std::min((std::numeric_limits::max)(), s64(end) - s64(now)); + // See here: https://github.com/torvalds/linux/blob/948a64995aca6820abefd17f1a4258f5835c5ad9/arch/x86/lib/delay.c#L93 + // MWAITX accepts a 32-bit input timer which determines the total number of cycles to wait for + // NOT THE TOTAL ABSOLUTE TSC VALUE, it's just a delta + // BIT[1] = use a timer + // Hint = 0: Use C1 state when sleepy (means slower wakeup but better savings) + _mm_mwaitx(1 << 1, 0u, cycles); + if (!is_set.load()) + return false; + } else + return false; //timeout + } + bool expected = true; + if (is_set.compare_exchange_weak(expected, false, std::memory_order_release)) + return true; + } + } else if (caps.waitpkg) { + // #UD If CPUID.7.0:ECX.WAITPKG[bit 5]=0. + while (true) { + _umonitor(std::addressof(is_set)); + if (!is_set.load() && !_umwait(0, end)) //umwait is absolute time!!! + return false; + bool expected = true; + if (is_set.compare_exchange_weak(expected, false, std::memory_order_release)) + return true; + } + } else { +#ifdef _MSC_VER + while (!is_set.load() && end > __rdtsc()) + Common::Windows::SleepForOneTick(); +#else + while (!is_set.load() && end > _rdtsc()) + Common::Windows::SleepForOneTick(); +#endif + if (is_set.load()) + Reset(); + return true; + } +#else + std::unique_lock lk{mutex}; + if (!condvar.wait_for(lk, time, [this] { return is_set.load(); })) + return false; + is_set = false; + return true; +#endif +} +#else +bool Event::WaitFor(const std::chrono::nanoseconds time) { +#ifdef _WIN32 + auto const end = Common::g_wall_clock.GetTimeNS() + time; + while (!is_set.load() && end > Common::g_wall_clock.GetTimeNS()) + Common::Windows::SleepForOneTick(); + if (is_set.load()) + Reset(); + return true; +#else + std::unique_lock lk{mutex}; + if (!condvar.wait_for(lk, time, [this] { return is_set.load(); })) + return false; + is_set = false; + return true; +#endif +} +#endif + } // namespace Common diff --git a/src/common/thread.h b/src/common/thread.h index ea6f5d6b3b..4a36295931 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -34,16 +34,10 @@ public: is_set = false; } - bool WaitFor(const std::chrono::nanoseconds& time) { - std::unique_lock lk{mutex}; - if (!condvar.wait_for(lk, time, [this] { return is_set.load(); })) - return false; - is_set = false; - return true; - } + bool WaitFor(const std::chrono::nanoseconds time); - template - bool WaitUntil(const std::chrono::time_point& time) { + template + bool WaitUntil(const std::chrono::time_point time) { std::unique_lock lk{mutex}; if (!condvar.wait_until(lk, time, [this] { return is_set.load(); })) return false; @@ -63,9 +57,9 @@ public: } private: + alignas(64) std::atomic is_set{false}; std::condition_variable condvar; std::mutex mutex; - std::atomic_bool is_set{false}; }; class Barrier { @@ -105,8 +99,20 @@ enum class ThreadPriority : u32 { Critical = 4, }; +enum class ThreadPlacement : u32 { + Default = 0, + Background = 1, + Efficiency = 2, +}; + void SetCurrentThreadPriority(ThreadPriority new_priority); void SetCurrentThreadName(const char* name); -void PinCurrentThreadToPerformanceCore(size_t core_id); +void SetCurrentThreadToPerformanceCores(); +void SetCurrentThreadToEfficiencyCores(); +void SetCurrentThreadToBackgroundWork(); +void SetCurrentThreadToAllCores(); + +void RefreshThreadPolicies(); +void PollThreadPolicies(); } // namespace Common diff --git a/src/common/thread_worker.h b/src/common/thread_worker.h index 6ec9d6a2bd..ec3ca0af49 100644 --- a/src/common/thread_worker.h +++ b/src/common/thread_worker.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -37,10 +37,25 @@ class StatefulThreadWorker { using StateMaker = std::conditional_t, DummyCallable>; public: - explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {}) + explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {}, + ThreadPlacement placement = ThreadPlacement::Default) : workers_queued{num_workers}, thread_name{std::move(name)} { - const auto lambda = [this, func](std::stop_token stop_token) { + const auto lambda = [this, func, placement](std::stop_token stop_token) { Common::SetCurrentThreadName(thread_name.c_str()); + if (placement != ThreadPlacement::Default) { + Common::SetCurrentThreadPriority(ThreadPriority::Low); + } + switch (placement) { + case ThreadPlacement::Efficiency: + Common::SetCurrentThreadToEfficiencyCores(); + break; + case ThreadPlacement::Background: + Common::SetCurrentThreadToBackgroundWork(); + break; + default: + Common::SetCurrentThreadToAllCores(); + break; + } { [[maybe_unused]] std::conditional_t state{func()}; while (!stop_token.stop_requested()) { diff --git a/src/common/typed_address.h b/src/common/typed_address.h index 4df48a99fb..f716accd03 100644 --- a/src/common/typed_address.h +++ b/src/common/typed_address.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -30,8 +33,8 @@ public: // Arithmetic operators. template + requires std::is_integral_v constexpr inline TypedAddress operator+(I rhs) const { - static_assert(std::is_integral_v); return m_address + rhs; } @@ -48,8 +51,8 @@ public: } template + requires std::is_integral_v constexpr inline TypedAddress operator-(I rhs) const { - static_assert(std::is_integral_v); return m_address - rhs; } @@ -66,15 +69,15 @@ public: } template + requires std::is_integral_v constexpr inline TypedAddress operator+=(I rhs) { - static_assert(std::is_integral_v); m_address += rhs; return *this; } template + requires std::is_integral_v constexpr inline TypedAddress operator-=(I rhs) { - static_assert(std::is_integral_v); m_address -= rhs; return *this; } @@ -89,8 +92,8 @@ public: } template + requires std::is_integral_v constexpr inline TypedAddress operator|=(I rhs) { - static_assert(std::is_integral_v); m_address |= rhs; return *this; } diff --git a/src/common/uuid.cpp b/src/common/uuid.cpp index d4a5733c26..4b992c5df8 100644 --- a/src/common/uuid.cpp +++ b/src/common/uuid.cpp @@ -208,4 +208,12 @@ UUID UUID::MakeRandomRFC4122V4() { return uuid; } +UUID UUID::MakeRFC4122V5(std::span sha1) { + UUID uuid{}; + std::memcpy(&uuid.uuid, sha1.data(), sha1.size()); + uuid.uuid[8] = 0x80 | (uuid.uuid[8] & 0x3F); + uuid.uuid[6] = 0x50 | (uuid.uuid[6] & 0xF); + return uuid; +} + } // namespace Common diff --git a/src/common/uuid.h b/src/common/uuid.h index 60c5d75f12..465b5be58d 100644 --- a/src/common/uuid.h +++ b/src/common/uuid.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -5,7 +8,9 @@ #include #include +#include #include +#include #include "common/common_types.h" @@ -86,28 +91,20 @@ struct UUID { }; } - /** - * Creates a random UUID. - * - * @returns A random UUID. - */ - static UUID MakeRandom(); + /// @brief Creates a random UUID. + /// @returns A random UUID. + [[nodiscard]] static UUID MakeRandom(); - /** - * Creates a random UUID with a seed. - * - * @param seed A seed to initialize the Mersenne-Twister RNG - * - * @returns A random UUID. - */ - static UUID MakeRandomWithSeed(u32 seed); + /// @brief Creates a random UUID with a seed. + /// @param seed A seed to initialize the Mersenne-Twister RNG + /// @returns A random UUID. + [[nodiscard]] static UUID MakeRandomWithSeed(u32 seed); - /** - * Creates a random UUID. The generated UUID is RFC 4122 Version 4 compliant. - * - * @returns A random UUID that is RFC 4122 Version 4 compliant. - */ - static UUID MakeRandomRFC4122V4(); + /// @brief Creates a random UUID. The generated UUID is RFC 4122 Version 4 compliant. + /// @returns A random UUID that is RFC 4122 Version 4 compliant. + [[nodiscard]] static UUID MakeRandomRFC4122V4(); + + [[nodiscard]] static UUID MakeRFC4122V5(std::span sha1); friend constexpr bool operator==(const UUID& lhs, const UUID& rhs) = default; }; diff --git a/src/common/vector_math.h b/src/common/vector_math.h index 829a79ce5a..f99fdcb01e 100644 --- a/src/common/vector_math.h +++ b/src/common/vector_math.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2014 Tony Wasserka @@ -7,752 +7,128 @@ #pragma once -#ifdef __ARM_NEON -#include -#endif - #include #include namespace Common { -template -class Vec2; -template -class Vec3; -template -class Vec4; - -template -class Vec2 { +template +class Vec { public: - T x{}; - T y{}; + std::array elems{}; - constexpr Vec2() = default; - constexpr Vec2(const T& x_, const T& y_) : x(x_), y(y_) {} + constexpr Vec() = default; + constexpr Vec(T e0) noexcept : elems{e0} {} + constexpr Vec(T e0, T e1) noexcept : elems{e0, e1} {} + constexpr Vec(T e0, T e1, T e2) noexcept : elems{e0, e1, e2} {} + constexpr Vec(T e0, T e1, T e2, T e4) noexcept : elems{e0, e1, e2, e4} {} + //explicit constexpr Vec(const std::initializer_list elems_) noexcept : elems{elems_} {} - template - [[nodiscard]] constexpr Vec2 Cast() const { - return Vec2(static_cast(x), static_cast(y)); + [[nodiscard]] constexpr Vec operator+(const Vec o) const noexcept { + Vec r{}; + for (size_t i = 0; i < N; ++i) + r.elems[i] = elems[i] + o.elems[i]; + return r; } + constexpr Vec operator+=(const Vec o) noexcept { return *this = *this + o; } - [[nodiscard]] static constexpr Vec2 AssignToAll(const T& f) { - return Vec2{f, f}; - } - - [[nodiscard]] constexpr Vec2 operator+(const Vec2& other) const { - return {x + other.x, y + other.y}; - } - constexpr Vec2& operator+=(const Vec2& other) { - x += other.x; - y += other.y; - return *this; - } - [[nodiscard]] constexpr Vec2 operator-(const Vec2& other) const { - return {x - other.x, y - other.y}; - } - constexpr Vec2& operator-=(const Vec2& other) { - x -= other.x; - y -= other.y; - return *this; + [[nodiscard]] constexpr Vec operator-(const Vec o) const noexcept { + Vec r{}; + for (size_t i = 0; i < N; ++i) + r.elems[i] = elems[i] - o.elems[i]; + return r; } + constexpr Vec operator-=(const Vec o) noexcept { return *this = *this - o; } template - [[nodiscard]] constexpr Vec2, U>> operator-() const { - return {-x, -y}; - } - [[nodiscard]] constexpr Vec2 operator*(const Vec2& other) const { - return {x * other.x, y * other.y}; + [[nodiscard]] constexpr Vec, U>, N> operator-() const noexcept { + Vec r{}; + for (size_t i = 0; i < N; ++i) + r.elems[i] = -elems[i]; + return r; } + [[nodiscard]] constexpr Vec operator*(const Vec o) const noexcept { + Vec r{}; + for (size_t i = 0; i < N; ++i) + r.elems[i] = elems[i] * o.elems[i]; + return r; + } template - [[nodiscard]] constexpr Vec2 operator*(const V& f) const { + [[nodiscard]] constexpr Vec operator*(const V f) const noexcept { using TV = decltype(T{} * V{}); using C = std::common_type_t; - - return { - static_cast(static_cast(x) * static_cast(f)), - static_cast(static_cast(y) * static_cast(f)), - }; + Vec r{}; + for (size_t i = 0; i < N; ++i) + r.elems[i] = TV(C(elems[i]) * C(f)); + return r; } + template + constexpr Vec operator*=(const V f) noexcept { return *this = *this * f; } template - constexpr Vec2& operator*=(const V& f) { - *this = *this * f; - return *this; - } - - template - [[nodiscard]] constexpr Vec2 operator/(const V& f) const { + [[nodiscard]] constexpr Vec operator/(const V f) const noexcept { using TV = decltype(T{} / V{}); using C = std::common_type_t; - - return { - static_cast(static_cast(x) / static_cast(f)), - static_cast(static_cast(y) / static_cast(f)), - }; + Vec r{}; + for (size_t i = 0; i < N; ++i) + r.elems[i] = TV(C(elems[i]) / C(f)); + return r; } - template - constexpr Vec2& operator/=(const V& f) { - *this = *this / f; - return *this; - } + constexpr Vec operator/=(const V f) noexcept { return *this = *this / f; } - [[nodiscard]] constexpr T Length2() const { - return x * x + y * y; + [[nodiscard]] constexpr T Length2() const noexcept { + T r{}; + for (size_t i = 0; i < N; ++i) + r += elems[i] * elems[i]; + return r; } - // Only implemented for T=float - [[nodiscard]] float Length() const; - [[nodiscard]] float Normalize(); // returns the previous length, which is often useful + [[nodiscard]] T Length() const { return T(std::sqrt(float(Length2()))); } + [[nodiscard]] Vec Normalized() const { return *this / Length(); } + [[nodiscard]] constexpr T& operator[](std::size_t i) noexcept { return elems[i]; } + [[nodiscard]] constexpr const T& operator[](std::size_t i) const noexcept { return elems[i]; } - [[nodiscard]] constexpr T& operator[](std::size_t i) { - return *((&x) + i); - } - [[nodiscard]] constexpr const T& operator[](std::size_t i) const { - return *((&x) + i); - } + [[nodiscard]] std::array ToMatrix() const { + const T x2 = elems[0] * elems[0]; + const T y2 = elems[1] * elems[1]; + const T z2 = elems[2] * elems[2]; - constexpr void SetZero() { - x = 0; - y = 0; - } - - // Common aliases: UV (texel coordinates), ST (texture coordinates) - [[nodiscard]] constexpr T& u() { - return x; - } - [[nodiscard]] constexpr T& v() { - return y; - } - [[nodiscard]] constexpr T& s() { - return x; - } - [[nodiscard]] constexpr T& t() { - return y; - } - - [[nodiscard]] constexpr const T& u() const { - return x; - } - [[nodiscard]] constexpr const T& v() const { - return y; - } - [[nodiscard]] constexpr const T& s() const { - return x; - } - [[nodiscard]] constexpr const T& t() const { - return y; - } - - // swizzlers - create a subvector of specific components - [[nodiscard]] constexpr Vec2 yx() const { - return Vec2(y, x); - } - [[nodiscard]] constexpr Vec2 vu() const { - return Vec2(y, x); - } - [[nodiscard]] constexpr Vec2 ts() const { - return Vec2(y, x); + const T xy = elems[0] * elems[1]; + const T wz = elems[3] * elems[2]; + const T xz = elems[0] * elems[2]; + const T wy = elems[3] * elems[1]; + const T yz = elems[1] * elems[2]; + const T wx = elems[3] * elems[0]; + return { + 1.0f - 2.0f * (y2 + z2), + 2.0f * (xy + wz), + 2.0f * (xz - wy), + 0.0f, + 2.0f * (xy - wz), + 1.0f - 2.0f * (x2 + z2), + 2.0f * (yz + wx), + 0.0f, + 2.0f * (xz + wy), + 2.0f * (yz - wx), + 1.0f - 2.0f * (x2 + y2), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 1.0f + }; } }; -template -[[nodiscard]] constexpr Vec2 operator*(const V& f, const Vec2& vec) { +template +[[nodiscard]] constexpr Vec operator*(const V f, const Vec v) noexcept { using C = std::common_type_t; - - return Vec2(static_cast(static_cast(f) * static_cast(vec.x)), - static_cast(static_cast(f) * static_cast(vec.y))); -} - -using Vec2f = Vec2; - -template <> -inline float Vec2::Length() const { - return std::sqrt(x * x + y * y); -} - -template <> -inline float Vec2::Normalize() { - float length = Length(); - *this /= length; - return length; -} - -template -class Vec3 { -public: - T x{}; - T y{}; - T z{}; - - constexpr Vec3() = default; - constexpr Vec3(const T& x_, const T& y_, const T& z_) : x(x_), y(y_), z(z_) {} - - template - [[nodiscard]] constexpr Vec3 Cast() const { - return Vec3(static_cast(x), static_cast(y), static_cast(z)); - } - - [[nodiscard]] static constexpr Vec3 AssignToAll(const T& f) { - return Vec3(f, f, f); - } - - [[nodiscard]] constexpr Vec3 operator+(const Vec3& other) const { - return {x + other.x, y + other.y, z + other.z}; - } - - constexpr Vec3& operator+=(const Vec3& other) { - x += other.x; - y += other.y; - z += other.z; - return *this; - } - - [[nodiscard]] constexpr Vec3 operator-(const Vec3& other) const { - return {x - other.x, y - other.y, z - other.z}; - } - - constexpr Vec3& operator-=(const Vec3& other) { - x -= other.x; - y -= other.y; - z -= other.z; - return *this; - } - - template - [[nodiscard]] constexpr Vec3, U>> operator-() const { - return {-x, -y, -z}; - } - - [[nodiscard]] constexpr Vec3 operator*(const Vec3& other) const { - return {x * other.x, y * other.y, z * other.z}; - } - - template - [[nodiscard]] constexpr Vec3 operator*(const V& f) const { - using TV = decltype(T{} * V{}); - using C = std::common_type_t; - - return { - static_cast(static_cast(x) * static_cast(f)), - static_cast(static_cast(y) * static_cast(f)), - static_cast(static_cast(z) * static_cast(f)), - }; - } - - template - constexpr Vec3& operator*=(const V& f) { - *this = *this * f; - return *this; - } - template - [[nodiscard]] constexpr Vec3 operator/(const V& f) const { - using TV = decltype(T{} / V{}); - using C = std::common_type_t; - - return { - static_cast(static_cast(x) / static_cast(f)), - static_cast(static_cast(y) / static_cast(f)), - static_cast(static_cast(z) / static_cast(f)), - }; - } - - template - constexpr Vec3& operator/=(const V& f) { - *this = *this / f; - return *this; - } - - void RotateFromOrigin(float roll, float pitch, float yaw) { - float temp = y; - y = std::cos(roll) * y - std::sin(roll) * z; - z = std::sin(roll) * temp + std::cos(roll) * z; - - temp = x; - x = std::cos(pitch) * x + std::sin(pitch) * z; - z = -std::sin(pitch) * temp + std::cos(pitch) * z; - - temp = x; - x = std::cos(yaw) * x - std::sin(yaw) * y; - y = std::sin(yaw) * temp + std::cos(yaw) * y; - } - - [[nodiscard]] constexpr T Length2() const { - return x * x + y * y + z * z; - } - - // Only implemented for T=float - [[nodiscard]] float Length() const; - [[nodiscard]] Vec3 Normalized() const; - [[nodiscard]] float Normalize(); // returns the previous length, which is often useful - - [[nodiscard]] constexpr T& operator[](std::size_t i) { - return *((&x) + i); - } - - [[nodiscard]] constexpr const T& operator[](std::size_t i) const { - return *((&x) + i); - } - - constexpr void SetZero() { - x = 0; - y = 0; - z = 0; - } - - // Common aliases: UVW (texel coordinates), RGB (colors), STQ (texture coordinates) - [[nodiscard]] constexpr T& u() { - return x; - } - [[nodiscard]] constexpr T& v() { - return y; - } - [[nodiscard]] constexpr T& w() { - return z; - } - - [[nodiscard]] constexpr T& r() { - return x; - } - [[nodiscard]] constexpr T& g() { - return y; - } - [[nodiscard]] constexpr T& b() { - return z; - } - - [[nodiscard]] constexpr T& s() { - return x; - } - [[nodiscard]] constexpr T& t() { - return y; - } - [[nodiscard]] constexpr T& q() { - return z; - } - - [[nodiscard]] constexpr const T& u() const { - return x; - } - [[nodiscard]] constexpr const T& v() const { - return y; - } - [[nodiscard]] constexpr const T& w() const { - return z; - } - - [[nodiscard]] constexpr const T& r() const { - return x; - } - [[nodiscard]] constexpr const T& g() const { - return y; - } - [[nodiscard]] constexpr const T& b() const { - return z; - } - - [[nodiscard]] constexpr const T& s() const { - return x; - } - [[nodiscard]] constexpr const T& t() const { - return y; - } - [[nodiscard]] constexpr const T& q() const { - return z; - } - -// swizzlers - create a subvector of specific components -// e.g. Vec2 uv() { return Vec2(x,y); } -// _DEFINE_SWIZZLER2 defines a single such function, DEFINE_SWIZZLER2 defines all of them for all -// component names (x<->r) and permutations (xy<->yx) -#define _DEFINE_SWIZZLER2(a, b, name) \ - [[nodiscard]] constexpr Vec2 name() const { return Vec2(a, b); } -#define DEFINE_SWIZZLER2(a, b, a2, b2, a3, b3, a4, b4) \ - _DEFINE_SWIZZLER2(a, b, a##b); \ - _DEFINE_SWIZZLER2(a, b, a2##b2); \ - _DEFINE_SWIZZLER2(a, b, a3##b3); \ - _DEFINE_SWIZZLER2(a, b, a4##b4); \ - _DEFINE_SWIZZLER2(b, a, b##a); \ - _DEFINE_SWIZZLER2(b, a, b2##a2); \ - _DEFINE_SWIZZLER2(b, a, b3##a3); \ - _DEFINE_SWIZZLER2(b, a, b4##a4) - - DEFINE_SWIZZLER2(x, y, r, g, u, v, s, t); - DEFINE_SWIZZLER2(x, z, r, b, u, w, s, q); - DEFINE_SWIZZLER2(y, z, g, b, v, w, t, q); -#undef DEFINE_SWIZZLER2 -#undef _DEFINE_SWIZZLER2 -}; - -template -[[nodiscard]] constexpr Vec3 operator*(const V& f, const Vec3& vec) { - using C = std::common_type_t; - - return Vec3(static_cast(static_cast(f) * static_cast(vec.x)), - static_cast(static_cast(f) * static_cast(vec.y)), - static_cast(static_cast(f) * static_cast(vec.z))); -} - -template <> -inline float Vec3::Length() const { - return std::sqrt(x * x + y * y + z * z); -} - -template <> -inline Vec3 Vec3::Normalized() const { - return *this / Length(); -} - -template <> -inline float Vec3::Normalize() { - float length = Length(); - *this /= length; - return length; -} - -using Vec3f = Vec3; - -template -class Vec4 { -public: - T x{}; - T y{}; - T z{}; - T w{}; - - constexpr Vec4() = default; - constexpr Vec4(const T& x_, const T& y_, const T& z_, const T& w_) - : x(x_), y(y_), z(z_), w(w_) {} - - template - [[nodiscard]] constexpr Vec4 Cast() const { - return Vec4(static_cast(x), static_cast(y), static_cast(z), - static_cast(w)); - } - - [[nodiscard]] static constexpr Vec4 AssignToAll(const T& f) { - return Vec4(f, f, f, f); - } - - [[nodiscard]] constexpr Vec4 operator+(const Vec4& other) const { - return {x + other.x, y + other.y, z + other.z, w + other.w}; - } - - constexpr Vec4& operator+=(const Vec4& other) { - x += other.x; - y += other.y; - z += other.z; - w += other.w; - return *this; - } - - [[nodiscard]] constexpr Vec4 operator-(const Vec4& other) const { - return {x - other.x, y - other.y, z - other.z, w - other.w}; - } - - constexpr Vec4& operator-=(const Vec4& other) { - x -= other.x; - y -= other.y; - z -= other.z; - w -= other.w; - return *this; - } - - template - [[nodiscard]] constexpr Vec4, U>> operator-() const { - return {-x, -y, -z, -w}; - } - - [[nodiscard]] constexpr Vec4 operator*(const Vec4& other) const { - return {x * other.x, y * other.y, z * other.z, w * other.w}; - } - - template - [[nodiscard]] constexpr Vec4 operator*(const V& f) const { - using TV = decltype(T{} * V{}); - using C = std::common_type_t; - - return { - static_cast(static_cast(x) * static_cast(f)), - static_cast(static_cast(y) * static_cast(f)), - static_cast(static_cast(z) * static_cast(f)), - static_cast(static_cast(w) * static_cast(f)), - }; - } - - template - constexpr Vec4& operator*=(const V& f) { - *this = *this * f; - return *this; - } - - template - [[nodiscard]] constexpr Vec4 operator/(const V& f) const { - using TV = decltype(T{} / V{}); - using C = std::common_type_t; - - return { - static_cast(static_cast(x) / static_cast(f)), - static_cast(static_cast(y) / static_cast(f)), - static_cast(static_cast(z) / static_cast(f)), - static_cast(static_cast(w) / static_cast(f)), - }; - } - - template - constexpr Vec4& operator/=(const V& f) { - *this = *this / f; - return *this; - } - - [[nodiscard]] constexpr T Length2() const { - return x * x + y * y + z * z + w * w; - } - - [[nodiscard]] constexpr T& operator[](std::size_t i) { - return *((&x) + i); - } - - [[nodiscard]] constexpr const T& operator[](std::size_t i) const { - return *((&x) + i); - } - - constexpr void SetZero() { - x = 0; - y = 0; - z = 0; - w = 0; - } - - // Common alias: RGBA (colors) - [[nodiscard]] constexpr T& r() { - return x; - } - [[nodiscard]] constexpr T& g() { - return y; - } - [[nodiscard]] constexpr T& b() { - return z; - } - [[nodiscard]] constexpr T& a() { - return w; - } - - [[nodiscard]] constexpr const T& r() const { - return x; - } - [[nodiscard]] constexpr const T& g() const { - return y; - } - [[nodiscard]] constexpr const T& b() const { - return z; - } - [[nodiscard]] constexpr const T& a() const { - return w; - } - -// Swizzlers - Create a subvector of specific components -// e.g. Vec2 uv() { return Vec2(x,y); } - -// _DEFINE_SWIZZLER2 defines a single such function -// DEFINE_SWIZZLER2_COMP1 defines one-component functions for all component names (x<->r) -// DEFINE_SWIZZLER2_COMP2 defines two component functions for all component names (x<->r) and -// permutations (xy<->yx) -#define _DEFINE_SWIZZLER2(a, b, name) \ - [[nodiscard]] constexpr Vec2 name() const { return Vec2(a, b); } -#define DEFINE_SWIZZLER2_COMP1(a, a2) \ - _DEFINE_SWIZZLER2(a, a, a##a); \ - _DEFINE_SWIZZLER2(a, a, a2##a2) -#define DEFINE_SWIZZLER2_COMP2(a, b, a2, b2) \ - _DEFINE_SWIZZLER2(a, b, a##b); \ - _DEFINE_SWIZZLER2(a, b, a2##b2); \ - _DEFINE_SWIZZLER2(b, a, b##a); \ - _DEFINE_SWIZZLER2(b, a, b2##a2) - - DEFINE_SWIZZLER2_COMP2(x, y, r, g); - DEFINE_SWIZZLER2_COMP2(x, z, r, b); - DEFINE_SWIZZLER2_COMP2(x, w, r, a); - DEFINE_SWIZZLER2_COMP2(y, z, g, b); - DEFINE_SWIZZLER2_COMP2(y, w, g, a); - DEFINE_SWIZZLER2_COMP2(z, w, b, a); - DEFINE_SWIZZLER2_COMP1(x, r); - DEFINE_SWIZZLER2_COMP1(y, g); - DEFINE_SWIZZLER2_COMP1(z, b); - DEFINE_SWIZZLER2_COMP1(w, a); -#undef DEFINE_SWIZZLER2_COMP1 -#undef DEFINE_SWIZZLER2_COMP2 -#undef _DEFINE_SWIZZLER2 - -#define _DEFINE_SWIZZLER3(a, b, c, name) \ - [[nodiscard]] constexpr Vec3 name() const { return Vec3(a, b, c); } -#define DEFINE_SWIZZLER3_COMP1(a, a2) \ - _DEFINE_SWIZZLER3(a, a, a, a##a##a); \ - _DEFINE_SWIZZLER3(a, a, a, a2##a2##a2) -#define DEFINE_SWIZZLER3_COMP3(a, b, c, a2, b2, c2) \ - _DEFINE_SWIZZLER3(a, b, c, a##b##c); \ - _DEFINE_SWIZZLER3(a, c, b, a##c##b); \ - _DEFINE_SWIZZLER3(b, a, c, b##a##c); \ - _DEFINE_SWIZZLER3(b, c, a, b##c##a); \ - _DEFINE_SWIZZLER3(c, a, b, c##a##b); \ - _DEFINE_SWIZZLER3(c, b, a, c##b##a); \ - _DEFINE_SWIZZLER3(a, b, c, a2##b2##c2); \ - _DEFINE_SWIZZLER3(a, c, b, a2##c2##b2); \ - _DEFINE_SWIZZLER3(b, a, c, b2##a2##c2); \ - _DEFINE_SWIZZLER3(b, c, a, b2##c2##a2); \ - _DEFINE_SWIZZLER3(c, a, b, c2##a2##b2); \ - _DEFINE_SWIZZLER3(c, b, a, c2##b2##a2) - - DEFINE_SWIZZLER3_COMP3(x, y, z, r, g, b); - DEFINE_SWIZZLER3_COMP3(x, y, w, r, g, a); - DEFINE_SWIZZLER3_COMP3(x, z, w, r, b, a); - DEFINE_SWIZZLER3_COMP3(y, z, w, g, b, a); - DEFINE_SWIZZLER3_COMP1(x, r); - DEFINE_SWIZZLER3_COMP1(y, g); - DEFINE_SWIZZLER3_COMP1(z, b); - DEFINE_SWIZZLER3_COMP1(w, a); -#undef DEFINE_SWIZZLER3_COMP1 -#undef DEFINE_SWIZZLER3_COMP3 -#undef _DEFINE_SWIZZLER3 -}; - -template -[[nodiscard]] constexpr Vec4 operator*(const V& f, const Vec4& vec) { - using TV = decltype(V{} * T{}); - using C = std::common_type_t; - - return { - static_cast(static_cast(f) * static_cast(vec.x)), - static_cast(static_cast(f) * static_cast(vec.y)), - static_cast(static_cast(f) * static_cast(vec.z)), - static_cast(static_cast(f) * static_cast(vec.w)), - }; -} - -using Vec4f = Vec4; - -template -constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec2& a, const Vec2& b) { - return a.x * b.x + a.y * b.y; -} - -template -[[nodiscard]] constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec3& a, const Vec3& b) { - return a.x * b.x + a.y * b.y + a.z * b.z; -} - -template -[[nodiscard]] constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec4& a, const Vec4& b) { - return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; -} - -template <> -[[nodiscard]] inline float Dot(const Vec4& a, const Vec4& b) { -#ifdef __ARM_NEON - float32x4_t va = vld1q_f32(&a.x); - float32x4_t vb = vld1q_f32(&b.x); - float32x4_t result = vmulq_f32(va, vb); -#if defined(__aarch64__) // Use vaddvq_f32 in ARMv8 architectures - return vaddvq_f32(result); -#else // Use manual addition for older architectures - float32x2_t sum2 = vadd_f32(vget_high_f32(result), vget_low_f32(result)); - return vget_lane_f32(vpadd_f32(sum2, sum2), 0); -#endif -#else - return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; -#endif -} - -template -[[nodiscard]] constexpr Vec3 Cross(const Vec3& a, - const Vec3& b) { - return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x}; -} - -// linear interpolation via float: 0.0=begin, 1.0=end -template -[[nodiscard]] constexpr decltype(X{} * float{} + X{} * float{}) Lerp(const X& begin, const X& end, - const float t) { - return begin * (1.f - t) + end * t; -} - -// linear interpolation via int: 0=begin, base=end -template -[[nodiscard]] constexpr decltype((X{} * int{} + X{} * int{}) / base) LerpInt(const X& begin, - const X& end, - const int t) { - return (begin * (base - t) + end * t) / base; -} - -// bilinear interpolation. s is for interpolating x00-x01 and x10-x11, and t is for the second -// interpolation. -template -[[nodiscard]] constexpr auto BilinearInterp(const X& x00, const X& x01, const X& x10, const X& x11, - const float s, const float t) { - auto y0 = Lerp(x00, x01, s); - auto y1 = Lerp(x10, x11, s); - return Lerp(y0, y1, t); -} - -// Utility vector factories -template -[[nodiscard]] constexpr Vec2 MakeVec(const T& x, const T& y) { - return Vec2{x, y}; -} - -template -[[nodiscard]] constexpr Vec3 MakeVec(const T& x, const T& y, const T& z) { - return Vec3{x, y, z}; -} - -template -[[nodiscard]] constexpr Vec4 MakeVec(const T& x, const T& y, const Vec2& zw) { - return MakeVec(x, y, zw[0], zw[1]); -} - -template -[[nodiscard]] constexpr Vec3 MakeVec(const Vec2& xy, const T& z) { - return MakeVec(xy[0], xy[1], z); -} - -template -[[nodiscard]] constexpr Vec3 MakeVec(const T& x, const Vec2& yz) { - return MakeVec(x, yz[0], yz[1]); -} - -template -[[nodiscard]] constexpr Vec4 MakeVec(const T& x, const T& y, const T& z, const T& w) { - return Vec4{x, y, z, w}; -} - -template -[[nodiscard]] constexpr Vec4 MakeVec(const Vec2& xy, const T& z, const T& w) { - return MakeVec(xy[0], xy[1], z, w); -} - -template -[[nodiscard]] constexpr Vec4 MakeVec(const T& x, const Vec2& yz, const T& w) { - return MakeVec(x, yz[0], yz[1], w); -} - -// NOTE: This has priority over "Vec2> MakeVec(const Vec2& x, const Vec2& y)". -// Even if someone wanted to use an odd object like Vec2>, the compiler would error -// out soon enough due to misuse of the returned structure. -template -[[nodiscard]] constexpr Vec4 MakeVec(const Vec2& xy, const Vec2& zw) { - return MakeVec(xy[0], xy[1], zw[0], zw[1]); -} - -template -[[nodiscard]] constexpr Vec4 MakeVec(const Vec3& xyz, const T& w) { - return MakeVec(xyz[0], xyz[1], xyz[2], w); -} - -template -[[nodiscard]] constexpr Vec4 MakeVec(const T& x, const Vec3& yzw) { - return MakeVec(x, yzw[0], yzw[1], yzw[2]); + Vec r{}; + for (size_t i = 0; i < N; ++i) + r.elems[i] = T(C(f) * C(v.elems[i])); + return r; } } // namespace Common diff --git a/src/common/virtual_buffer.cpp b/src/common/virtual_buffer.cpp deleted file mode 100644 index 55ddfc243a..0000000000 --- a/src/common/virtual_buffer.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#ifdef _WIN32 -#include -#else -#include -#endif - -#include "common/assert.h" -#include "common/virtual_buffer.h" - -namespace Common { - -void* AllocateMemoryPages(std::size_t size) noexcept { -#ifdef _WIN32 - void* base = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE); -#else - void* base = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); - if (base == MAP_FAILED) - base = nullptr; -#endif - ASSERT(base); - return base; -} - -void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept { - if (!base) - return; -#ifdef _WIN32 - ASSERT(VirtualFree(base, 0, MEM_RELEASE)); -#else - ASSERT(munmap(base, size) == 0); -#endif -} - -} // namespace Common diff --git a/src/common/virtual_buffer.h b/src/common/virtual_buffer.h deleted file mode 100644 index af96317288..0000000000 --- a/src/common/virtual_buffer.h +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -namespace Common { - -void* AllocateMemoryPages(std::size_t size) noexcept; -void FreeMemoryPages(void* base, std::size_t size) noexcept; - -template -class VirtualBuffer final { -public: - // TODO: Uncomment this and change Common::PageTable::PageInfo to be trivially constructible - // using std::atomic_ref once libc++ has support for it - // static_assert( - // std::is_trivially_constructible_v, - // "T must be trivially constructible, as non-trivial constructors will not be executed " - // "with the current allocator"); - - constexpr VirtualBuffer() = default; - explicit VirtualBuffer(std::size_t count) noexcept - : alloc_size{count * sizeof(T)} - { - base_ptr = reinterpret_cast(AllocateMemoryPages(alloc_size)); - } - - ~VirtualBuffer() noexcept { - FreeMemoryPages(base_ptr, alloc_size); - } - - VirtualBuffer(const VirtualBuffer&) = delete; - VirtualBuffer& operator=(const VirtualBuffer&) = delete; - - VirtualBuffer(VirtualBuffer&& other) noexcept - : alloc_size{std::exchange(other.alloc_size, 0)}, base_ptr{std::exchange(other.base_ptr), nullptr} - {} - - VirtualBuffer& operator=(VirtualBuffer&& other) noexcept { - alloc_size = std::exchange(other.alloc_size, 0); - base_ptr = std::exchange(other.base_ptr, nullptr); - return *this; - } - - void resize(std::size_t count) noexcept { - if (auto const new_size = count * sizeof(T); new_size != alloc_size) { - FreeMemoryPages(base_ptr, alloc_size); - alloc_size = new_size; - base_ptr = reinterpret_cast(AllocateMemoryPages(alloc_size)); - } - } - - [[nodiscard]] constexpr const T& operator[](std::size_t index) const noexcept { - return base_ptr[index]; - } - - [[nodiscard]] constexpr T& operator[](std::size_t index) noexcept { - return base_ptr[index]; - } - - [[nodiscard]] constexpr T* data() noexcept { - return base_ptr; - } - - [[nodiscard]] constexpr const T* data() const noexcept { - return base_ptr; - } - - [[nodiscard]] constexpr std::size_t size() const noexcept { - return alloc_size / sizeof(T); - } - -private: - std::size_t alloc_size{}; - T* base_ptr{}; -}; - -} // namespace Common diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp deleted file mode 100644 index 4f9c240905..0000000000 --- a/src/common/wall_clock.cpp +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/steady_clock.h" -#include "common/wall_clock.h" - -#ifdef ARCHITECTURE_x86_64 -#include "common/x64/cpu_detect.h" -#include "common/x64/native_clock.h" -#include "common/x64/rdtsc.h" -#endif -#ifdef HAS_NCE -#include "common/arm64/native_clock.h" -#endif - -namespace Common { - -class StandardWallClock final : public WallClock { -public: - explicit StandardWallClock() {} - - std::chrono::nanoseconds GetTimeNS() const override { - return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()); - } - - std::chrono::microseconds GetTimeUS() const override { - return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()); - } - - std::chrono::milliseconds GetTimeMS() const override { - return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()); - } - - s64 GetCNTPCT() const override { - return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den; - } - - s64 GetGPUTick() const override { - return GetUptime() * NsToGPUTickRatio::num / NsToGPUTickRatio::den; - } - - s64 GetUptime() const override { - return std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()) - .count(); - } - - bool IsNative() const override { - return false; - } -}; - -std::unique_ptr CreateOptimalClock() { -#if defined(ARCHITECTURE_x86_64) - const auto& caps = GetCPUCaps(); - - if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) { - return std::make_unique(caps.tsc_frequency); - } else { - // Fallback to StandardWallClock if the hardware TSC - // - Is not invariant - // - Is not more precise than 1 GHz (1ns resolution) - return std::make_unique(); - } -#elif defined(HAS_NCE) - return std::make_unique(); -#else - return std::make_unique(); -#endif -} - -} // namespace Common diff --git a/src/common/wall_clock.h b/src/common/wall_clock.h deleted file mode 100644 index 7ad6536930..0000000000 --- a/src/common/wall_clock.h +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include - -#include "common/common_types.h" - -namespace Common { - -class WallClock { -public: - static constexpr u64 CNTFRQ = 19'200'000; // CNTPCT_EL0 Frequency = 19.2 MHz - static constexpr u64 GPUTickFreq = 614'400'000; // GM20B GPU Tick Frequency = 614.4 MHz - static constexpr u64 CPUTickFreq = 1'020'000'000; // T210/4 A57 CPU Tick Frequency = 1020.0 MHz - - virtual ~WallClock() = default; - - /// @returns The time in nanoseconds since the construction of this clock. - virtual std::chrono::nanoseconds GetTimeNS() const = 0; - - /// @returns The time in microseconds since the construction of this clock. - virtual std::chrono::microseconds GetTimeUS() const = 0; - - /// @returns The time in milliseconds since the construction of this clock. - virtual std::chrono::milliseconds GetTimeMS() const = 0; - - /// @returns The guest CNTPCT ticks since the construction of this clock. - virtual s64 GetCNTPCT() const = 0; - - /// @returns The guest GPU ticks since the construction of this clock. - virtual s64 GetGPUTick() const = 0; - - /// @returns The raw host timer ticks since an indeterminate epoch. - virtual s64 GetUptime() const = 0; - - /// @returns Whether the clock directly uses the host's hardware clock. - virtual bool IsNative() const = 0; - - static inline u64 NSToCNTPCT(u64 ns) { - return ns * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den; - } - - static inline u64 NSToGPUTick(u64 ns) { - return ns * NsToGPUTickRatio::num / NsToGPUTickRatio::den; - } - - // Cycle Timing - - static inline u64 CPUTickToNS(u64 cpu_tick) { - return cpu_tick * CPUTickToNsRatio::num / CPUTickToNsRatio::den; - } - - static inline u64 CPUTickToUS(u64 cpu_tick) { - return cpu_tick * CPUTickToUsRatio::num / CPUTickToUsRatio::den; - } - - static inline u64 CPUTickToCNTPCT(u64 cpu_tick) { - return cpu_tick * CPUTickToCNTPCTRatio::num / CPUTickToCNTPCTRatio::den; - } - - static inline u64 CPUTickToGPUTick(u64 cpu_tick) { - return cpu_tick * CPUTickToGPUTickRatio::num / CPUTickToGPUTickRatio::den; - } - -protected: - using NsRatio = std::nano; - using UsRatio = std::micro; - using MsRatio = std::milli; - - using NsToUsRatio = std::ratio_divide; - using NsToMsRatio = std::ratio_divide; - using NsToCNTPCTRatio = std::ratio; - using NsToGPUTickRatio = std::ratio; - - // Cycle Timing - - using CPUTickToNsRatio = std::ratio; - using CPUTickToUsRatio = std::ratio; - using CPUTickToCNTPCTRatio = std::ratio; - using CPUTickToGPUTickRatio = std::ratio; -}; - -[[nodiscard]] std::unique_ptr CreateOptimalClock(); - -} // namespace Common diff --git a/src/common/x64/cpu_detect.h b/src/common/x64/cpu_detect.h deleted file mode 100644 index 756459417f..0000000000 --- a/src/common/x64/cpu_detect.h +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2013 Dolphin Emulator Project / 2015 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include "common/common_types.h" - -namespace Common { - -/// x86/x64 CPU capabilities that may be detected by this module -struct CPUCaps { - - enum class Manufacturer : u8 { - Unknown = 0, - Intel = 1, - AMD = 2, - Hygon = 3, - }; - - static Manufacturer ParseManufacturer(std::string_view brand_string); - - Manufacturer manufacturer; - char brand_string[13]; - - char cpu_string[48]; - - u32 base_frequency; - u32 max_frequency; - u32 bus_frequency; - - u32 tsc_crystal_ratio_denominator; - u32 tsc_crystal_ratio_numerator; - u32 crystal_frequency; - u64 tsc_frequency; // Derived from the above three values - - bool sse : 1; - bool sse2 : 1; - bool sse3 : 1; - bool ssse3 : 1; - bool sse4_1 : 1; - bool sse4_2 : 1; - - bool avx : 1; - bool avx_vnni : 1; - bool avx2 : 1; - bool avx512f : 1; - bool avx512dq : 1; - bool avx512cd : 1; - bool avx512bw : 1; - bool avx512vl : 1; - bool avx512vbmi : 1; - bool avx512bitalg : 1; - - bool aes : 1; - bool bmi1 : 1; - bool bmi2 : 1; - bool f16c : 1; - bool fma : 1; - bool fma4 : 1; - bool gfni : 1; - bool invariant_tsc : 1; - bool lzcnt : 1; - bool monitorx : 1; - bool movbe : 1; - bool pclmulqdq : 1; - bool popcnt : 1; - bool sha : 1; - bool waitpkg : 1; -}; - -/** - * Gets the supported capabilities of the host CPU - * @return Reference to a CPUCaps struct with the detected host CPU capabilities - */ -const CPUCaps& GetCPUCaps(); - -/// Detects CPU core count -std::optional GetProcessorCount(); - -} // namespace Common diff --git a/src/common/x64/cpu_wait.cpp b/src/common/x64/cpu_wait.cpp deleted file mode 100644 index 85d27161ba..0000000000 --- a/src/common/x64/cpu_wait.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -#include - -#ifdef _MSC_VER -#include -#endif - -#include "common/x64/cpu_detect.h" -#include "common/x64/cpu_wait.h" -#include "common/x64/rdtsc.h" - -namespace Common::X64 { - -namespace { - -// 100,000 cycles is a reasonable amount of time to wait to save on CPU resources. -// For reference: -// At 1 GHz, 100K cycles is 100us -// At 2 GHz, 100K cycles is 50us -// At 4 GHz, 100K cycles is 25us -constexpr auto PauseCycles = 100'000U; - -} // Anonymous namespace - -#if defined(_MSC_VER) && !defined(__clang__) -__forceinline static void TPAUSE() { - static constexpr auto RequestC02State = 0U; - _tpause(RequestC02State, FencedRDTSC() + PauseCycles); -} - -__forceinline static void MWAITX() { - static constexpr auto EnableWaitTimeFlag = 1U << 1; - static constexpr auto RequestC1State = 0U; - - // monitor_var should be aligned to a cache line. - alignas(64) u64 monitor_var{}; - _mm_monitorx(&monitor_var, 0, 0); - _mm_mwaitx(EnableWaitTimeFlag, RequestC1State, PauseCycles); -} -#else -static void TPAUSE() { - static constexpr auto RequestC02State = 0U; - const auto tsc = FencedRDTSC() + PauseCycles; - const auto eax = static_cast(tsc & 0xFFFFFFFF); - const auto edx = static_cast(tsc >> 32); - asm volatile("tpause %0" : : "r"(RequestC02State), "d"(edx), "a"(eax)); -} - -static void MWAITX() { - static constexpr auto EnableWaitTimeFlag = 1U << 1; - static constexpr auto RequestC1State = 0U; - - // monitor_var should be aligned to a cache line. - alignas(64) u64 monitor_var{}; - asm volatile("monitorx" : : "a"(&monitor_var), "c"(0), "d"(0)); - asm volatile("mwaitx" : : "a"(RequestC1State), "b"(PauseCycles), "c"(EnableWaitTimeFlag)); -} -#endif - -void MicroSleep() { - static const bool has_waitpkg = GetCPUCaps().waitpkg; - static const bool has_monitorx = GetCPUCaps().monitorx; - - if (has_waitpkg) { - TPAUSE(); - } else if (has_monitorx) { - MWAITX(); - } else { - std::this_thread::yield(); - } -} - -} // namespace Common::X64 diff --git a/src/common/x64/native_clock.cpp b/src/common/x64/native_clock.cpp deleted file mode 100644 index d2d27fafea..0000000000 --- a/src/common/x64/native_clock.cpp +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/uint128.h" -#include "common/x64/native_clock.h" -#include "common/x64/rdtsc.h" - -namespace Common::X64 { - -NativeClock::NativeClock(u64 rdtsc_frequency_) - : rdtsc_frequency{rdtsc_frequency_}, ns_rdtsc_factor{GetFixedPoint64Factor(NsRatio::den, - rdtsc_frequency)}, - us_rdtsc_factor{GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency)}, - ms_rdtsc_factor{GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency)}, - cntpct_rdtsc_factor{GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency)}, - gputick_rdtsc_factor{GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency)} {} - -std::chrono::nanoseconds NativeClock::GetTimeNS() const { - return std::chrono::nanoseconds{MultiplyHigh(GetUptime(), ns_rdtsc_factor)}; -} - -std::chrono::microseconds NativeClock::GetTimeUS() const { - return std::chrono::microseconds{MultiplyHigh(GetUptime(), us_rdtsc_factor)}; -} - -std::chrono::milliseconds NativeClock::GetTimeMS() const { - return std::chrono::milliseconds{MultiplyHigh(GetUptime(), ms_rdtsc_factor)}; -} - -s64 NativeClock::GetCNTPCT() const { - return MultiplyHigh(GetUptime(), cntpct_rdtsc_factor); -} - -s64 NativeClock::GetGPUTick() const { - return MultiplyHigh(GetUptime(), gputick_rdtsc_factor); -} - -s64 NativeClock::GetUptime() const { - return static_cast(FencedRDTSC()); -} - -bool NativeClock::IsNative() const { - return true; -} - -} // namespace Common::X64 diff --git a/src/common/x64/native_clock.h b/src/common/x64/native_clock.h deleted file mode 100644 index b2629b0311..0000000000 --- a/src/common/x64/native_clock.h +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/wall_clock.h" - -namespace Common::X64 { - -class NativeClock final : public WallClock { -public: - explicit NativeClock(u64 rdtsc_frequency_); - - std::chrono::nanoseconds GetTimeNS() const override; - - std::chrono::microseconds GetTimeUS() const override; - - std::chrono::milliseconds GetTimeMS() const override; - - s64 GetCNTPCT() const override; - - s64 GetGPUTick() const override; - - s64 GetUptime() const override; - - bool IsNative() const override; - -private: - u64 rdtsc_frequency; - - u64 ns_rdtsc_factor; - u64 us_rdtsc_factor; - u64 ms_rdtsc_factor; - u64 cntpct_rdtsc_factor; - u64 gputick_rdtsc_factor; -}; - -} // namespace Common::X64 diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak.h similarity index 77% rename from src/common/x64/xbyak_abi.h rename to src/common/x64/xbyak.h index 8aea5db583..11d3e2b184 100644 --- a/src/common/x64/xbyak_abi.h +++ b/src/common/x64/xbyak.h @@ -1,13 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include #include #include -#include #include "common/assert.h" +// xbyak hates human beings +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wshadow" +#endif +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wconversion" +#pragma clang diagnostic ignored "-Wshadow" +#endif + +// You must ensure this matches with src/common/x64/xbyak.h on root dir +#include "common/container/unordered_map.h" +#include "common/container/unordered_set.h" +#include +#define XBYAK_STD_UNORDERED_SET ::Common::unordered_set +#define XBYAK_STD_UNORDERED_MAP ::Common::unordered_map +#define XBYAK_STD_UNORDERED_MULTIMAP boost::unordered_multimap +#include +#include + +#include + namespace Common::X64 { constexpr size_t RegToIndex(const Xbyak::Reg& reg) { @@ -174,12 +199,13 @@ inline ABIFrameInfo ABI_CalculateFrameSize(std::bitset<32> regs, size_t rsp_alig rsp_alignment -= subtraction; subtraction += rsp_alignment & 0xF; - return ABIFrameInfo{static_cast(subtraction), - static_cast(subtraction - xmm_base_subtraction)}; + return ABIFrameInfo{ + s32(subtraction), + s32(subtraction - xmm_base_subtraction) + }; } -inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bitset<32> regs, - size_t rsp_alignment, size_t needed_frame_size = 0) { +inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bitset<32> regs, size_t rsp_alignment, size_t needed_frame_size = 0) { auto frame_info = ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size); for (size_t i = 0; i < regs.size(); ++i) { @@ -202,8 +228,7 @@ inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::b return ABI_SHADOW_SPACE; } -inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bitset<32> regs, - size_t rsp_alignment, size_t needed_frame_size = 0) { +inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bitset<32> regs, size_t rsp_alignment, size_t needed_frame_size = 0) { auto frame_info = ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size); for (size_t i = 0; i < regs.size(); ++i) { @@ -226,4 +251,34 @@ inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bits } } +// Constants for use with cmpps/cmpss +enum { + CMP_EQ = 0, + CMP_LT = 1, + CMP_LE = 2, + CMP_UNORD = 3, + CMP_NEQ = 4, + CMP_NLT = 5, + CMP_NLE = 6, + CMP_ORD = 7, +}; + +constexpr bool IsWithin2G(uintptr_t ref, uintptr_t target) noexcept { + u64 const distance = target - (ref + 5); + return (distance & 0xffff'ffff) == distance; +} + +template + requires std::is_pointer_v +inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) { + uintptr_t addr = uintptr_t(f); + if (IsWithin2G(uintptr_t(code.getCurr()), addr)) { + code.call(f); + } else { + // ABI_RETURN is a safe temp register to use before a call + code.mov(ABI_RETURN, addr); + code.call(ABI_RETURN); + } +} + } // namespace Common::X64 diff --git a/src/common/x64/xbyak_util.h b/src/common/x64/xbyak_util.h deleted file mode 100644 index 250e5cddb9..0000000000 --- a/src/common/x64/xbyak_util.h +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: 2016 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include "common/x64/xbyak_abi.h" - -namespace Common::X64 { - -// Constants for use with cmpps/cmpss -enum { - CMP_EQ = 0, - CMP_LT = 1, - CMP_LE = 2, - CMP_UNORD = 3, - CMP_NEQ = 4, - CMP_NLT = 5, - CMP_NLE = 6, - CMP_ORD = 7, -}; - -constexpr bool IsWithin2G(uintptr_t ref, uintptr_t target) { - const u64 distance = target - (ref + 5); - return !(distance >= 0x8000'0000ULL && distance <= ~0x8000'0000ULL); -} - -inline bool IsWithin2G(const Xbyak::CodeGenerator& code, uintptr_t target) { - return IsWithin2G(reinterpret_cast(code.getCurr()), target); -} - -template -inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) { - static_assert(std::is_pointer_v, "Argument must be a (function) pointer."); - size_t addr = reinterpret_cast(f); - if (IsWithin2G(code, addr)) { - code.call(f); - } else { - // ABI_RETURN is a safe temp register to use before a call - code.mov(ABI_RETURN, addr); - code.call(ABI_RETURN); - } -} - -} // namespace Common::X64 diff --git a/src/common/zbic_compression.cpp b/src/common/zbic_compression.cpp new file mode 100644 index 0000000000..5e0e4d947b --- /dev/null +++ b/src/common/zbic_compression.cpp @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "common/zbic_compression.h" + +#define ZSTD_ZBIC_SUPPORT 1 +#define ZSTDLIB_VISIBLE static +#define ZSTDLIB_HIDDEN static +#define ZSTDERRORLIB_VISIBLE static +#define ZSTDERRORLIB_HIDDEN static +#undef ZSTD_MULTITHREAD + +#if defined(__ANDROID__) +#undef _GNU_SOURCE +#endif + +#include "zstd.h" +#define g_ZSTD_threading_useless_symbol g_ZSTD_zbic_threading_useless_symbol +#include "zstd.c" +#undef g_ZSTD_threading_useless_symbol + +namespace Common::Compression { + +bool IsZBIC(std::span src) { + if (src.size() < sizeof(u32)) { + return false; + } + u32 magic = 0; + std::memcpy(&magic, src.data(), sizeof(u32)); + return magic == ZSTD_MAGICNUMBER; // 0x4349425A ("ZBIC") +} + +int DecompressDataZBIC(std::span dst, std::span src) { + if (dst.empty() || src.empty()) { + return -1; + } + const size_t res = ZSTD_decompress(dst.data(), dst.size(), src.data(), src.size()); + if (ZSTD_isError(res)) { + return -1; + } + return static_cast(res); +} + +} // namespace Common::Compression diff --git a/src/common/zbic_compression.h b/src/common/zbic_compression.h new file mode 100644 index 0000000000..c36a559388 --- /dev/null +++ b/src/common/zbic_compression.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include "common/common_types.h" + +namespace Common::Compression { + +[[nodiscard]] bool IsZBIC(std::span src); + +[[nodiscard]] int DecompressDataZBIC(std::span dst, std::span src); + +} // namespace Common::Compression diff --git a/src/common/zstd_compression.cpp b/src/common/zstd_compression.cpp index cb6ec171b5..7665482ee6 100644 --- a/src/common/zstd_compression.cpp +++ b/src/common/zstd_compression.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -46,4 +49,10 @@ std::vector DecompressDataZSTD(std::span compressed) { return decompressed; } +std::size_t DecompressDataZSTD(std::span compressed, std::span output) { + const std::size_t result = + ZSTD_decompress(output.data(), output.size(), compressed.data(), compressed.size()); + return ZSTD_isError(result) ? 0 : result; +} + } // namespace Common::Compression diff --git a/src/common/zstd_compression.h b/src/common/zstd_compression.h index a5ab2d05bd..819d7d201a 100644 --- a/src/common/zstd_compression.h +++ b/src/common/zstd_compression.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -42,4 +45,15 @@ namespace Common::Compression { */ [[nodiscard]] std::vector DecompressDataZSTD(std::span compressed); +/** + * Decompresses a single Zstandard frame into a caller-provided buffer of known size. + * + * @param compressed the compressed source memory region. + * @param output the destination buffer. Its size is the maximum number of bytes to write. + * + * @return the number of bytes written, or 0 if the frame is invalid or doesn't fit. An empty + * frame also returns 0. + */ +[[nodiscard]] std::size_t DecompressDataZSTD(std::span compressed, std::span output); + } // namespace Common::Compression diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 39aebd5f48..1b578f67ba 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -23,6 +23,8 @@ add_library(core STATIC core_timing.h cpu_manager.cpp cpu_manager.h + launch_params.cpp + launch_params.h crypto/aes_util.cpp crypto/aes_util.h crypto/ctr_encryption_layer.cpp @@ -31,6 +33,8 @@ add_library(core STATIC crypto/encryption_layer.h crypto/key_manager.cpp crypto/key_manager.h + crypto/ncz_decompressor.cpp + crypto/ncz_decompressor.h crypto/partition_data_manager.cpp crypto/partition_data_manager.h crypto/xts_encryption_layer.cpp @@ -388,14 +392,6 @@ add_library(core STATIC hle/result.h hle/service/acc/acc.cpp hle/service/acc/acc.h - hle/service/acc/acc_aa.cpp - hle/service/acc/acc_aa.h - hle/service/acc/acc_su.cpp - hle/service/acc/acc_su.h - hle/service/acc/acc_u0.cpp - hle/service/acc/acc_u0.h - hle/service/acc/acc_u1.cpp - hle/service/acc/acc_u1.h hle/service/acc/async_context.cpp hle/service/acc/async_context.h hle/service/acc/errors.h @@ -719,8 +715,6 @@ add_library(core STATIC hle/service/kernel_helpers.h hle/service/lbl/lbl.cpp hle/service/lbl/lbl.h - hle/service/ldn/client_process_monitor.cpp - hle/service/ldn/client_process_monitor.h hle/service/ldn/lan_discovery.cpp hle/service/ldn/lan_discovery.h hle/service/ldn/ldn.cpp @@ -768,8 +762,8 @@ add_library(core STATIC hle/service/mii/types/ver3_store_data.h hle/service/mm/mm_u.cpp hle/service/mm/mm_u.h - hle/service/mnpp/mnpp_app.cpp - hle/service/mnpp/mnpp_app.h + hle/service/mnpp/mnpp.cpp + hle/service/mnpp/mnpp.h hle/service/ncm/ncm.cpp hle/service/ncm/ncm.h hle/service/nfc/common/amiibo_crypto.cpp @@ -806,6 +800,8 @@ add_library(core STATIC hle/service/ns/application_manager_interface.h hle/service/ns/application_version_interface.cpp hle/service/ns/application_version_interface.h + hle/service/ns/async_result.cpp + hle/service/ns/async_result.h hle/service/ns/content_management_interface.cpp hle/service/ns/content_management_interface.h hle/service/ns/develop_interface.cpp @@ -840,8 +836,6 @@ add_library(core STATIC hle/service/ns/system_update_control.h hle/service/ns/system_update_interface.cpp hle/service/ns/system_update_interface.h - hle/service/ns/vulnerability_manager_interface.cpp - hle/service/ns/vulnerability_manager_interface.h hle/service/nvdrv/core/container.cpp hle/service/nvdrv/core/container.h hle/service/nvdrv/core/heap_mapper.cpp @@ -1075,8 +1069,6 @@ add_library(core STATIC hle/service/sockets/sockets.h hle/service/sockets/sockets_translate.cpp hle/service/sockets/sockets_translate.h - hle/service/spl/csrng.cpp - hle/service/spl/csrng.h hle/service/spl/spl.cpp hle/service/spl/spl.h hle/service/spl/spl_module.cpp @@ -1089,6 +1081,8 @@ add_library(core STATIC hle/service/ssl/ssl.h hle/service/ssl/ssl_backend.h hle/service/ssl/ssl_types.h + hle/service/tma/tma.cpp + hle/service/tma/tma.h hle/service/usb/usb.cpp hle/service/usb/usb.h hle/service/vi/application_display_service.cpp @@ -1121,6 +1115,12 @@ add_library(core STATIC hle/service/vi/vi_types.h hle/service/vi/vsync_manager.cpp hle/service/vi/vsync_manager.h + hle/service/gpio/gpio.cpp + hle/service/gpio/gpio.h + hle/service/i2c/i2c.cpp + hle/service/i2c/i2c.h + hle/service/wlan/wlan.cpp + hle/service/wlan/wlan.h internal_network/emu_net_state.cpp internal_network/emu_net_state.h internal_network/network.cpp @@ -1169,7 +1169,7 @@ add_library(core STATIC if (ENABLE_WIFI_SCAN) target_sources(core PRIVATE internal_network/wifi_scanner.cpp) - if (PLATFORM_LINUX) + if (LINUX) target_link_libraries(core PRIVATE iw) endif() else() @@ -1195,18 +1195,21 @@ if (MSVC) ) else() target_compile_options(core PRIVATE - $<$:-Werror=conversion> + $<$:-Wconversion> $<$:-Wno-sign-conversion> $<$:-Wno-cast-function-type> $<$:-fsized-deallocation>) # pre-clang19 will spam with "OH DID YOU MEAN THIS?" otherwise... - if (CXX_CLANG AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19) + if (CXX_CLANG AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 20) target_compile_options(core PRIVATE $<$:-Wno-cast-function-type-mismatch>) endif() endif() target_include_directories(core PRIVATE ${OPUS_INCLUDE_DIRS}) target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz) +if (MSVC) + target_link_libraries(core PRIVATE getopt) +endif() if (BOOST_NO_HEADERS) target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::asio Boost::process Boost::crc) @@ -1215,6 +1218,7 @@ else() endif() target_link_libraries(core PRIVATE + OpenSSL::SSL fmt::fmt nlohmann_json::nlohmann_json RenderDoc::API @@ -1246,7 +1250,7 @@ if (HAS_NCE) target_link_libraries(core PRIVATE merry::oaknut) endif() -if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64) +if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64 OR ARCHITECTURE_loongarch64) target_sources(core PRIVATE arm/dynarmic/arm_dynarmic.h arm/dynarmic/arm_dynarmic_64.cpp @@ -1266,23 +1270,11 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64) target_link_libraries(core PRIVATE dynarmic::dynarmic) endif() -target_sources(core PRIVATE hle/service/ssl/ssl_backend_openssl.cpp) - -target_link_libraries(core PRIVATE OpenSSL::SSL OpenSSL::Crypto) - -# TODO - -# elseif (APPLE) -# target_sources(core PRIVATE -# hle/service/ssl/ssl_backend_securetransport.cpp) -# target_link_libraries(core PRIVATE "-framework Security") -# elseif (WIN32) -# target_sources(core PRIVATE -# hle/service/ssl/ssl_backend_schannel.cpp) -# target_link_libraries(core PRIVATE crypt32 secur32) -# else() -# target_sources(core PRIVATE -# hle/service/ssl/ssl_backend_none.cpp) -# endif() +if (TARGET OpenSSL::SSL) + target_sources(core PRIVATE hle/service/ssl/ssl_backend_openssl.cpp) + target_link_libraries(core PRIVATE OpenSSL::SSL OpenSSL::Crypto) +else() + target_sources(core PRIVATE hle/service/ssl/ssl_backend_none.cpp) +endif() create_target_directory_groups(core) diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h index 46384f7e6d..8e6b1d9766 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.h +++ b/src/core/arm/dynarmic/arm_dynarmic.h @@ -1,9 +1,11 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 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 "core/arm/arm_interface.h" diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index c0d6ff9516..a59aa3cac3 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -22,21 +22,15 @@ DynarmicCallbacks32::DynarmicCallbacks32(ArmDynarmic32& parent, Kernel::KProcess , m_check_memory_access{m_debugger_enabled || !Settings::values.cpuopt_ignore_memory_aborts.GetValue()} {} -u8 DynarmicCallbacks32::MemoryRead8(u32 vaddr) { - CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read); - return m_memory.Read8(vaddr); -} -u16 DynarmicCallbacks32::MemoryRead16(u32 vaddr) { - CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read); - return m_memory.Read16(vaddr); -} -u32 DynarmicCallbacks32::MemoryRead32(u32 vaddr) { - CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read); - return m_memory.Read32(vaddr); -} -u64 DynarmicCallbacks32::MemoryRead64(u32 vaddr) { - CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read); - return m_memory.Read64(vaddr); +u64 DynarmicCallbacks32::MemoryRead(u32 vaddr, size_t size) { + CheckMemoryAccess(vaddr, size, Kernel::DebugWatchpointType::Read); + switch (size) { + case sizeof(u64): return m_memory.Read64(vaddr); + case sizeof(u32): return m_memory.Read32(vaddr); + case sizeof(u16): return m_memory.Read16(vaddr); + case sizeof(u8): return m_memory.Read8(vaddr); + default: UNREACHABLE(); + } } std::optional DynarmicCallbacks32::MemoryReadCode(u32 vaddr) { @@ -50,27 +44,17 @@ std::optional DynarmicCallbacks32::MemoryReadCode(u32 vaddr) { return cached_code_page.inst[(vaddr & Core::Memory::YUZU_PAGEMASK) / sizeof(u32)]; } -void DynarmicCallbacks32::MemoryWrite8(u32 vaddr, u8 value) { - if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) { - m_memory.Write8(vaddr, value); +void DynarmicCallbacks32::MemoryWrite(Dynarmic::A32::VAddr vaddr, u64 value, size_t size) { + if (CheckMemoryAccess(vaddr, size, Kernel::DebugWatchpointType::Write)) { + switch (size) { + case sizeof(u64): return m_memory.Write64(vaddr, value); + case sizeof(u32): return m_memory.Write32(vaddr, u32(value)); + case sizeof(u16): return m_memory.Write16(vaddr, u16(value)); + case sizeof(u8): return m_memory.Write8(vaddr, u8(value)); + default: UNREACHABLE(); + } } } -void DynarmicCallbacks32::MemoryWrite16(u32 vaddr, u16 value) { - if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) { - m_memory.Write16(vaddr, value); - } -} -void DynarmicCallbacks32::MemoryWrite32(u32 vaddr, u32 value) { - if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) { - m_memory.Write32(vaddr, value); - } -} -void DynarmicCallbacks32::MemoryWrite64(u32 vaddr, u64 value) { - if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) { - m_memory.Write64(vaddr, value); - } -} - bool DynarmicCallbacks32::MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) { return CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write) && m_memory.WriteExclusive8(vaddr, value, expected); @@ -113,8 +97,7 @@ void DynarmicCallbacks32::CallSVC(u32 swi) { } void DynarmicCallbacks32::AddTicks(u64 ticks) { - ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled"); - + ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled"); // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a // rough approximation of the amount of executed ticks in the system, it may be thrown off // if not all cores are doing a similar amount of work. Instead of doing this, we should @@ -123,14 +106,12 @@ void DynarmicCallbacks32::AddTicks(u64 ticks) { u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES; // Always execute at least one tick. amortized_ticks = std::max(amortized_ticks, 1); - m_parent.m_system.CoreTiming().AddTicks(amortized_ticks); } u64 DynarmicCallbacks32::GetTicksRemaining() { - ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled"); - - return std::max(m_parent.m_system.CoreTiming().GetDowncount(), 0); + ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled"); + return std::max(m_parent.m_system.CoreTiming().downcount, 0); } bool DynarmicCallbacks32::CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type) { @@ -175,12 +156,12 @@ void ArmDynarmic32::MakeJit(Common::PageTable* page_table) { if (page_table) { constexpr size_t PageBits = 12; constexpr size_t NumPageTableEntries = 1 << (32 - PageBits); - constexpr size_t PageLog2Stride = 5; - static_assert(1 << PageLog2Stride == sizeof(Common::PageTable::PageEntryData)); - config.page_table = reinterpret_cast*>(page_table->entries.data()); - config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS; - config.page_table_log2_stride = PageLog2Stride; + // Dynarmic will not write to the page table, const_cast is safe here + config.page_table = reinterpret_cast*>( + const_cast(page_table->entries.data())); + config.page_table_pointer_mask = Common::PageTable::ATTRIBUTE_MASK; + config.page_table_marked_bit = uint8_t(0); config.absolute_offset_page_table = true; config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128; config.only_detect_misalignment_via_page_table_on_page_boundary = true; @@ -191,6 +172,13 @@ void ArmDynarmic32::MakeJit(Common::PageTable* page_table) { config.fastmem_exclusive_access = config.fastmem_pointer != std::nullopt; config.recompile_on_exclusive_fastmem_failure = true; + + if (reinterpret_cast(m_system.DeviceMemory().buffer.BackingBasePointer() + + Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize()) < (1ULL << 39)) { + // Systems like FreeBSD allocate memory really low by default, and since we pack our page table entries, + // we have to manually sign extend when our actual pointer is negative. + config.page_table_sign_extension = std::uint8_t(Common::PageTable::SIGN_BIT); + } } // Multi-process state @@ -289,6 +277,7 @@ void ArmDynarmic32::MakeJit(Common::PageTable* page_table) { config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN; + config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor; break; // Paranoia mode for debugging optimizations case Settings::CpuAccuracy::Paranoid: @@ -422,6 +411,7 @@ void ArmDynarmic32::SignalInterrupt(Kernel::KThread* thread) { } void ArmDynarmic32::ClearInstructionCache() { + m_cb->last_code_addr = u64(-1); m_jit->ClearCache(); } diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.h b/src/core/arm/dynarmic/arm_dynarmic_32.h index d93cc1cfc9..22655dff47 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.h +++ b/src/core/arm/dynarmic/arm_dynarmic_32.h @@ -30,18 +30,12 @@ class System; class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks { public: explicit DynarmicCallbacks32(ArmDynarmic32& parent, Kernel::KProcess* process); - u8 MemoryRead8(u32 vaddr) override; - u16 MemoryRead16(u32 vaddr) override; - u32 MemoryRead32(u32 vaddr) override; - u64 MemoryRead64(u32 vaddr) override; + u64 MemoryRead(u32 vaddr, size_t size) override; std::optional MemoryReadCode(u32 vaddr) override; void InstructionSynchronizationBarrierRaised() override { - last_code_addr = 0; //reset back, force refetch + last_code_addr = u64(-1); //reset back, force refetch } - void MemoryWrite8(u32 vaddr, u8 value) override; - void MemoryWrite16(u32 vaddr, u16 value) override; - void MemoryWrite32(u32 vaddr, u32 value) override; - void MemoryWrite64(u32 vaddr, u64 value) override; + void MemoryWrite(Dynarmic::A32::VAddr vaddr, u64 value, size_t size) override; bool MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) override; bool MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) override; bool MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) override; @@ -54,7 +48,7 @@ public: void ReturnException(u32 pc, Dynarmic::HaltReason hr); // Dynarmic::CodePage cached_code_page; - u64 last_code_addr = 0; + u64 last_code_addr = u64(-1); ArmDynarmic32& m_parent; Core::Memory::Memory& m_memory; Kernel::KProcess* m_process{}; diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index db8f4424eb..894857a86d 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -10,6 +10,7 @@ #include "core/arm/dynarmic/dynarmic_exclusive_monitor.h" #include "core/core_timing.h" #include "core/hle/kernel/k_process.h" +#include "dynarmic/interface/A64/config.h" namespace Core { @@ -21,21 +22,15 @@ DynarmicCallbacks64::DynarmicCallbacks64(ArmDynarmic64& parent, Kernel::KProcess , m_check_memory_access{m_debugger_enabled || !Settings::values.cpuopt_ignore_memory_aborts.GetValue()} {} -u8 DynarmicCallbacks64::MemoryRead8(u64 vaddr) { - CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read); - return m_memory.Read8(vaddr); -} -u16 DynarmicCallbacks64::MemoryRead16(u64 vaddr) { - CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read); - return m_memory.Read16(vaddr); -} -u32 DynarmicCallbacks64::MemoryRead32(u64 vaddr) { - CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read); - return m_memory.Read32(vaddr); -} -u64 DynarmicCallbacks64::MemoryRead64(u64 vaddr) { - CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read); - return m_memory.Read64(vaddr); +u64 DynarmicCallbacks64::MemoryRead(u64 vaddr, size_t size) { + CheckMemoryAccess(vaddr, size, Kernel::DebugWatchpointType::Read); + switch (size) { + case sizeof(u64): return m_memory.Read64(vaddr); + case sizeof(u32): return m_memory.Read32(vaddr); + case sizeof(u16): return m_memory.Read16(vaddr); + case sizeof(u8): return m_memory.Read8(vaddr); + default: UNREACHABLE(); + } } Dynarmic::A64::Vector DynarmicCallbacks64::MemoryRead128(u64 vaddr) { CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Read); @@ -45,7 +40,6 @@ Dynarmic::A64::Vector DynarmicCallbacks64::MemoryRead128(u64 vaddr) { std::optional DynarmicCallbacks64::MemoryReadCode(u64 vaddr) { if (!m_memory.IsValidVirtualAddressRange(vaddr, sizeof(u32))) return std::nullopt; -// return m_memory.Read32(vaddr); auto const aligned_vaddr = vaddr & ~Core::Memory::YUZU_PAGEMASK; if (last_code_addr != aligned_vaddr) { m_memory.ReadBlock(aligned_vaddr, &cached_code_page, sizeof(cached_code_page)); @@ -54,24 +48,15 @@ std::optional DynarmicCallbacks64::MemoryReadCode(u64 vaddr) { return cached_code_page.inst[(vaddr & Core::Memory::YUZU_PAGEMASK) / sizeof(u32)]; } -void DynarmicCallbacks64::MemoryWrite8(u64 vaddr, u8 value) { - if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) { - m_memory.Write8(vaddr, value); - } -} -void DynarmicCallbacks64::MemoryWrite16(u64 vaddr, u16 value) { - if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) { - m_memory.Write16(vaddr, value); - } -} -void DynarmicCallbacks64::MemoryWrite32(u64 vaddr, u32 value) { - if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) { - m_memory.Write32(vaddr, value); - } -} -void DynarmicCallbacks64::MemoryWrite64(u64 vaddr, u64 value) { - if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) { - m_memory.Write64(vaddr, value); +void DynarmicCallbacks64::MemoryWrite(Dynarmic::A64::VAddr vaddr, u64 value, std::size_t size) { + if (CheckMemoryAccess(vaddr, size, Kernel::DebugWatchpointType::Write)) { + switch (size) { + case sizeof(u64): return m_memory.Write64(vaddr, u64(value)); + case sizeof(u32): return m_memory.Write32(vaddr, u32(value)); + case sizeof(u16): return m_memory.Write16(vaddr, u16(value)); + case sizeof(u8): return m_memory.Write8(vaddr, u8(value)); + default: UNREACHABLE(); + } } } void DynarmicCallbacks64::MemoryWrite128(u64 vaddr, Dynarmic::A64::Vector value) { @@ -103,6 +88,7 @@ bool DynarmicCallbacks64::MemoryWriteExclusive128(u64 vaddr, Dynarmic::A64::Vect } void DynarmicCallbacks64::InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op, u64 value) { + last_code_addr = u64(-1); //invalidate cached page switch (op) { case Dynarmic::A64::InstructionCacheOperation::InvalidateByVAToPoU: { static constexpr u64 ICACHE_LINE_SIZE = 64; @@ -128,7 +114,7 @@ void DynarmicCallbacks64::ExceptionRaised(u64 pc, Dynarmic::A64::Exception excep case Dynarmic::A64::Exception::SendEvent: case Dynarmic::A64::Exception::SendEventLocal: case Dynarmic::A64::Exception::Yield: - LOG_TRACE(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", static_cast(exception), pc, m_memory.Read32(pc)); + LOG_TRACE(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, cached = {:08X})", std::size_t(exception), pc, m_memory.Read32(pc), MemoryReadCode(pc).value_or(0)); return; case Dynarmic::A64::Exception::NoExecuteFault: LOG_CRITICAL(Core_ARM, "Cannot execute instruction at unmapped address {:#016x}", pc); @@ -150,8 +136,7 @@ void DynarmicCallbacks64::CallSVC(u32 svc) { } void DynarmicCallbacks64::AddTicks(u64 ticks) { - ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled"); - + ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled"); // Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a // rough approximation of the amount of executed ticks in the system, it may be thrown off // if not all cores are doing a similar amount of work. Instead of doing this, we should @@ -160,13 +145,12 @@ void DynarmicCallbacks64::AddTicks(u64 ticks) { u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES; // Always execute at least one tick. amortized_ticks = std::max(amortized_ticks, 1); - m_parent.m_system.CoreTiming().AddTicks(amortized_ticks); } u64 DynarmicCallbacks64::GetTicksRemaining() { ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled"); - return std::max(m_parent.m_system.CoreTiming().GetDowncount(), 0); + return std::max(m_parent.m_system.CoreTiming().downcount, 0); } u64 DynarmicCallbacks64::GetCNTPCT() { @@ -213,13 +197,12 @@ void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_s // Memory if (page_table) { - constexpr size_t PageLog2Stride = 5; - static_assert(1 << PageLog2Stride == sizeof(Common::PageTable::PageEntryData)); - - config.page_table = reinterpret_cast(page_table->entries.data()); + // Dynarmic will not write to the page table, const_cast is safe here + config.page_table = reinterpret_cast( + const_cast(page_table->entries.data())); config.page_table_address_space_bits = std::uint32_t(address_space_bits); - config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS; - config.page_table_log2_stride = PageLog2Stride; + config.page_table_pointer_mask = Common::PageTable::ATTRIBUTE_MASK; + config.page_table_marked_bit = uint8_t(0); config.silently_mirror_page_table = false; config.absolute_offset_page_table = true; config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128; @@ -233,6 +216,13 @@ void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_s config.fastmem_exclusive_access = config.fastmem_pointer != std::nullopt; config.recompile_on_exclusive_fastmem_failure = true; + + if (reinterpret_cast(m_system.DeviceMemory().buffer.BackingBasePointer() + + Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize()) < (1ULL << 39)) { + // Systems like FreeBSD allocate memory really low by default, and since we pack our page table entries, + // we have to manually sign extend when our actual pointer is negative. + config.page_table_sign_extension = std::uint8_t(Common::PageTable::SIGN_BIT); + } } // Multi-process state @@ -340,6 +330,7 @@ void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_s config.unsafe_optimizations = true; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; config.fastmem_address_space_bits = 64; + config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor; break; // Paranoia mode for debugging optimizations case Settings::CpuAccuracy::Paranoid: @@ -448,6 +439,7 @@ void ArmDynarmic64::SignalInterrupt(Kernel::KThread* thread) { } void ArmDynarmic64::ClearInstructionCache() { + m_cb->last_code_addr = u64(-1); m_jit->ClearCache(); } diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h index 9eff17c5fe..5ea5dde8a6 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.h +++ b/src/core/arm/dynarmic/arm_dynarmic_64.h @@ -8,7 +8,7 @@ #include #include -#include +#include "common/container/unordered_map.h" #include #include @@ -16,6 +16,7 @@ #include "common/hash.h" #include "core/arm/arm_interface.h" #include "core/arm/dynarmic/dynarmic_exclusive_monitor.h" +#include "dynarmic/interface/A64/config.h" namespace Core::Memory { class Memory; @@ -36,19 +37,13 @@ class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks { public: explicit DynarmicCallbacks64(ArmDynarmic64& parent, Kernel::KProcess* process); - u8 MemoryRead8(u64 vaddr) override; - u16 MemoryRead16(u64 vaddr) override; - u32 MemoryRead32(u64 vaddr) override; - u64 MemoryRead64(u64 vaddr) override; + u64 MemoryRead(u64 vaddr, size_t size) override; Dynarmic::A64::Vector MemoryRead128(u64 vaddr) override; std::optional MemoryReadCode(u64 vaddr) override; void InstructionSynchronizationBarrierRaised() override { - last_code_addr = 0; //reset back, force refetch + last_code_addr = u64(-1); //reset back, force refetch } - void MemoryWrite8(u64 vaddr, u8 value) override; - void MemoryWrite16(u64 vaddr, u16 value) override; - void MemoryWrite32(u64 vaddr, u32 value) override; - void MemoryWrite64(u64 vaddr, u64 value) override; + void MemoryWrite(Dynarmic::A64::VAddr vaddr, u64 value, std::size_t size) override; void MemoryWrite128(u64 vaddr, Dynarmic::A64::Vector value) override; bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, std::uint8_t expected) override; bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, std::uint16_t expected) override; @@ -65,7 +60,7 @@ public: void ReturnException(u64 pc, Dynarmic::HaltReason hr); Dynarmic::CodePage cached_code_page; - u64 last_code_addr = 0; + u64 last_code_addr = u64(-1); ArmDynarmic64& m_parent; Core::Memory::Memory& m_memory; u64 m_tpidrro_el0{}; diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp index bbff9f2829..438dd4d981 100644 --- a/src/core/arm/nce/arm_nce.cpp +++ b/src/core/arm/nce/arm_nce.cpp @@ -369,8 +369,8 @@ void ArmNce::SetContext(const Kernel::Svc::ThreadContext& ctx) { } void ArmNce::SignalInterrupt(Kernel::KThread* thread) { - // Add break loop condition. - m_guest_ctx.esr_el1.fetch_or(static_cast(HaltReason::BreakLoop)); + constexpr u64 break_loop = static_cast(HaltReason::BreakLoop); + if ((m_guest_ctx.esr_el1.fetch_or(break_loop) & break_loop) != 0) return; auto* params = &thread->GetNativeExecutionParameters(); LockThreadParameters(params); @@ -388,7 +388,7 @@ void ArmNce::SignalInterrupt(Kernel::KThread* thread) { } } -const std::size_t CACHE_PAGE_SIZE = 4096; +[[maybe_unused]] const std::size_t CACHE_PAGE_SIZE = 4096; void ArmNce::ClearInstructionCache() { #ifdef __aarch64__ diff --git a/src/core/arm/nce/arm_nce_asm_definitions.h b/src/core/arm/nce/arm_nce_asm_definitions.h index 8ea4383f73..ec6abd62d2 100644 --- a/src/core/arm/nce/arm_nce_asm_definitions.h +++ b/src/core/arm/nce/arm_nce_asm_definitions.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 */ @@ -5,9 +8,22 @@ #define __ASSEMBLY__ +// Newer Linux UAPI headers check __ASSEMBLER__ instead of __ASSEMBLY__ to hide C declarations. +// Without it, asm-generic/signal.h redeclares sigset_t and clashes with glibc's. Only define it +// around these includes, since glibc headers also look at __ASSEMBLER__. +#ifndef __ASSEMBLER__ +#define __ASSEMBLER__ +#define EDEN_NCE_UNDEF_ASSEMBLER +#endif + #include #include +#ifdef EDEN_NCE_UNDEF_ASSEMBLER +#undef __ASSEMBLER__ +#undef EDEN_NCE_UNDEF_ASSEMBLER +#endif + #define ReturnToRunCodeByExceptionLevelChangeSignal SIGUSR2 #define BreakFromRunCodeSignal SIGURG #define GuestAccessFaultSignal SIGSEGV diff --git a/src/core/arm/nce/instructions.h b/src/core/arm/nce/instructions.h index ada0da9559..6c71b6acf2 100644 --- a/src/core/arm/nce/instructions.h +++ b/src/core/arm/nce/instructions.h @@ -1,9 +1,11 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 Skyline Team and Contributors // SPDX-License-Identifier: MPL-2.0 +#pragma once + #include "common/bit_field.h" #include "common/common_types.h" @@ -119,7 +121,10 @@ union Exclusive { constexpr explicit Exclusive(u32 raw_) : raw{raw_} {} constexpr bool Verify() { - return this->GetSig() == 0x10; + if (this->GetSig() != 0x10) return false; + const bool pair = decltype(l)::ExtractValue(raw) & 1; + const bool fixed_rt2 = decltype(rt2)::ExtractValue(raw) == 0b11111; + return pair || fixed_rt2; } constexpr u32 GetSig() { diff --git a/src/core/arm/nce/interpreter_visitor.cpp b/src/core/arm/nce/interpreter_visitor.cpp index ec2b155fe4..2ccc176323 100644 --- a/src/core/arm/nce/interpreter_visitor.cpp +++ b/src/core/arm/nce/interpreter_visitor.cpp @@ -12,9 +12,9 @@ namespace Core { namespace { // Prefetch tuning parameters -constexpr size_t CACHE_LINE_SIZE = 64; -constexpr size_t PREFETCH_STRIDE = 128; // 2 cache lines ahead -constexpr size_t SIMD_PREFETCH_THRESHOLD = 32; // Bytes +[[maybe_unused]] constexpr size_t CACHE_LINE_SIZE = 64; +[[maybe_unused]] constexpr size_t PREFETCH_STRIDE = 128; // 2 cache lines ahead +[[maybe_unused]] constexpr size_t SIMD_PREFETCH_THRESHOLD = 32; // Bytes } // namespace template diff --git a/src/core/arm/nce/patcher.cpp b/src/core/arm/nce/patcher.cpp index ea77166645..f21d7cacdc 100644 --- a/src/core/arm/nce/patcher.cpp +++ b/src/core/arm/nce/patcher.cpp @@ -3,7 +3,7 @@ #include #include -#include "common/arm64/native_clock.h" +#include "common/cpu_features.h" #include "common/alignment.h" #include "common/literals.h" #include "core/arm/nce/arm_nce.h" @@ -45,6 +45,23 @@ Patcher::Patcher() : c(m_patch_instructions), c_pre(m_patch_instructions_pre) { WriteLoadContext(c_pre); } +Patcher::Patcher(std::span text) : c(m_patch_instructions), c_pre(m_patch_instructions_pre) { + modules.emplace_back(); + curr_patch = &modules.back(); + const auto words = std::span{reinterpret_cast(text.data()), text.size() / sizeof(u32)}; + for (u32 i = ModuleCodeIndex; i < static_cast(words.size()); ++i) { + const uintptr_t offset = i * sizeof(u32); + if (auto mrs = MRS{words[i]}; mrs.Verify() && mrs.GetRt() != 31 && (mrs.GetSystemReg() == TpidrEl0 || mrs.GetSystemReg() == TpidrroEl0)) { + BranchToPatch(offset); + const auto src_reg = mrs.GetSystemReg() == TpidrroEl0 ? oaknut::SystemReg::TPIDRRO_EL0 : oaknut::SystemReg::TPIDR_EL0; + WriteMrsHandler(offset + sizeof(u32), oaknut::XReg{static_cast(mrs.GetRt())}, src_reg); + } else if (auto msr = MSR{words[i]}; msr.Verify() && msr.GetSystemReg() == TpidrEl0) { + BranchToPatch(offset); + WriteMsrHandler(offset + sizeof(u32), oaknut::XReg{static_cast(msr.GetRt())}); + } + } +} + Patcher::~Patcher() = default; bool Patcher::PatchText(std::span program_image, const Kernel::CodeSet::Segment& code) { @@ -145,7 +162,14 @@ bool Patcher::PatchText(std::span program_image, const Kernel::CodeSet // MRS Xn, CNTFRQ_EL0 if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntfrqEl0) { - UNREACHABLE(); + bool pre_buffer = false; + auto ret = AddRelocations(pre_buffer); + if (pre_buffer) { + WriteCntfrqHandler(ret, oaknut::XReg{static_cast(mrs.GetRt())}, c_pre); + } else { + WriteCntfrqHandler(ret, oaknut::XReg{static_cast(mrs.GetRt())}, c); + } + continue; } // MSR TPIDR_EL0, Xn @@ -175,6 +199,31 @@ bool Patcher::PatchText(std::span program_image, const Kernel::CodeSet return true; } +std::span Patcher::RelocateRuntimeTls( + s64 patch_offset, std::vector& out_patches) { + const auto& patch = modules.front(); + const auto can_branch = [](s64 offset) { + return offset % 4 == 0 && offset >= -static_cast(MaxRelativeBranch) && offset < static_cast(MaxRelativeBranch); + }; + out_patches.clear(); + out_patches.reserve(patch.m_branch_to_patch_relocations.size()); + for (const auto& rel : patch.m_branch_to_patch_relocations) { + const s64 offset = patch_offset + rel.patch_offset - static_cast(rel.module_offset); + if (!can_branch(offset)) {return {};} + u32 replacement; + oaknut::CodeGenerator rc{&replacement}; + rc.B(offset); + out_patches.push_back({rel.module_offset, replacement}); + } + for (const auto& rel : patch.m_branch_to_module_relocations) { + const s64 offset = static_cast(rel.module_offset) - patch_offset - rel.patch_offset; + if (!can_branch(offset)) {return {};} + oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)}; + rc.B(offset); + } + return m_patch_instructions; +} + bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, std::vector& program_image, EntryTrampolines* out_trampolines) { const size_t patch_size = GetSectionSize(); const size_t pre_patch_size = GetPreSectionSize(); @@ -577,8 +626,22 @@ void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg, this->BranchToModule(module_dest); } +void Patcher::WriteCntfrqHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& cg) { + cg.MOV(dest_reg, Common::WallClock::CNTFRQ); + + // Jump back to the instruction after the emulated MRS. + if (&cg == &c_pre) + this->BranchToModulePre(module_dest); + else + this->BranchToModule(module_dest); +} + void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& cg) { - static Common::Arm64::NativeClock clock{}; +#if defined(HAS_NCE) + static Common::WallClock clock(false, 1); +#else + static Common::WallClock clock(true, 1); +#endif const auto factor = clock.GetGuestCNTFRQFactor(); const auto raw_factor = std::bit_cast>(factor); diff --git a/src/core/arm/nce/patcher.h b/src/core/arm/nce/patcher.h index 980a0c81e0..695161cd18 100644 --- a/src/core/arm/nce/patcher.h +++ b/src/core/arm/nce/patcher.h @@ -4,7 +4,7 @@ #pragma once #include -#include +#include "common/container/unordered_map.h" #include #include #include @@ -46,7 +46,12 @@ enum class PatchMode : u32 { using ModuleTextAddress = u64; using PatchTextAddress = u64; -using EntryTrampolines = ankerl::unordered_dense::map; +using EntryTrampolines = ::Common::unordered_map; + +struct InstructionPatch { + u64 offset; + u32 replacement; +}; class Patcher { public: @@ -54,9 +59,11 @@ public: module_id = id; } explicit Patcher(); + explicit Patcher(std::span text); ~Patcher(); bool PatchText(std::span program_image, const Kernel::CodeSet::Segment& code); bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, std::vector& program_image, EntryTrampolines* out_trampolines); + std::span RelocateRuntimeTls(s64 patch_offset, std::vector& out_patches); size_t GetSectionSize() const noexcept; size_t GetPreSectionSize() const noexcept; @@ -80,6 +87,7 @@ private: void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id, oaknut::VectorCodeGenerator& code, oaknut::Label& save_ctx, oaknut::Label& load_ctx); void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::SystemReg src_reg, oaknut::VectorCodeGenerator& code); void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg, oaknut::VectorCodeGenerator& code); + void WriteCntfrqHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& code); void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& code); // Convenience wrappers using default code generator @@ -90,6 +98,7 @@ private: void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) { WriteSvcTrampoline(module_dest, svc_id, c, m_save_context, m_load_context); } void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::SystemReg src_reg) { WriteMrsHandler(module_dest, dest_reg, src_reg, c); } void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg) { WriteMsrHandler(module_dest, src_reg, c); } + void WriteCntfrqHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg) { WriteCntfrqHandler(module_dest, dest_reg, c); } void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg) { WriteCntpctHandler(module_dest, dest_reg, c); } private: diff --git a/src/core/arm/symbols.cpp b/src/core/arm/symbols.cpp index 0259c7ea27..4e270c319e 100644 --- a/src/core/arm/symbols.cpp +++ b/src/core/arm/symbols.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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,117 +16,111 @@ using namespace Common::ELF; namespace Core { namespace Symbols { -template -static Symbols GetSymbols(ByteReader ReadBytes) { - const auto Read8{[&](u64 index) { +struct ModuleHeaderLocation { + u32 version; + u32 header_offset; + u32 version_offset; +}; +static_assert(sizeof(ModuleHeaderLocation) == 0x0C); +struct ModuleHeader { + u32 signature; + u32 dynamic_offset; + u32 bss_start_offset; + u32 bss_end_offset; + u32 exception_info_start_offset; + u32 exception_info_end_offset; + u32 module_offset; + u32 relro_start_offset; + u32 full_relro_end_offset; + u32 nx_debug_link_start_offset; + u32 nx_debug_link_end_offset; + u32 note_gnu_build_id_start_offset; + u32 note_gnu_build_id_end_offset; +}; +static_assert(sizeof(ModuleHeader) == 0x34); + +struct Mod32 { + using Sym = Elf32_Sym; + using Dyn = Elf32_Dyn; +}; +struct Mod64 { + using Sym = Elf64_Sym; + using Dyn = Elf64_Dyn; +}; + +template +static Symbols GetSymbols(F&& ReadBytes) { + const auto Read8 = [&](u64 index) { u8 ret; ReadBytes(&ret, index, sizeof(u8)); return ret; - }}; - - const auto Read32{[&](u64 index) { - u32 ret; - ReadBytes(&ret, index, sizeof(u32)); - return ret; - }}; - - const auto ReadWord{[&](u64 index) { - Word ret; - ReadBytes(&ret, index, sizeof(Word)); - return ret; - }}; - - const u32 mod_offset = Read32(4); - - if (Read32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) { + }; + ModuleHeaderLocation loc{}; + ReadBytes(&loc, 0, sizeof(ModuleHeaderLocation)); + ModuleHeader hdr; + ReadBytes(&hdr, loc.header_offset, sizeof(ModuleHeader)); + if (hdr.signature != Common::MakeMagic('M', 'O', 'D', '0')) { return {}; } - VAddr string_table_offset{}; - VAddr symbol_table_offset{}; - u64 symbol_entry_size{}; - - const auto dynamic_offset = Read32(mod_offset + 0x4) + mod_offset; - - VAddr dynamic_index = dynamic_offset; + VAddr strtab_offs{}; + VAddr symtab_offs{}; + u64 syment_size = sizeof(typename M::Sym); + VAddr dynamic_offset = loc.header_offset + hdr.dynamic_offset; while (true) { - const Word tag = ReadWord(dynamic_index); - const Word value = ReadWord(dynamic_index + sizeof(Word)); - dynamic_index += 2 * sizeof(Word); - - if (tag == ElfDtNull) { + typename M::Dyn dyn; + ReadBytes(&dyn, dynamic_offset, sizeof(typename M::Dyn)); + dynamic_offset += sizeof(typename M::Dyn); + if (dyn.d_tag == ElfDtNull) { break; } - - if (tag == ElfDtStrtab) { - string_table_offset = value; - } else if (tag == ElfDtSymtab) { - symbol_table_offset = value; - } else if (tag == ElfDtSyment) { - symbol_entry_size = value; + if (dyn.d_tag == ElfDtStrtab) { + strtab_offs = dyn.d_un.d_ptr; + } else if (dyn.d_tag == ElfDtSymtab) { + symtab_offs = dyn.d_un.d_ptr; + } else if (dyn.d_tag == ElfDtSyment) { + syment_size = dyn.d_un.d_val; } } - - if (string_table_offset == 0 || symbol_table_offset == 0 || symbol_entry_size == 0) { - return {}; - } - - Symbols out; - - VAddr symbol_index = symbol_table_offset; - while (symbol_index < string_table_offset) { - ELFSymbol symbol{}; - ReadBytes(&symbol, symbol_index, sizeof(ELFSymbol)); - - VAddr string_offset = string_table_offset + symbol.st_name; - std::string name; - for (u8 c = Read8(string_offset); c != 0; c = Read8(++string_offset)) { - name += static_cast(c); + if (strtab_offs > 0 && symtab_offs > 0) { + Symbols out; + VAddr symbol_index = symtab_offs; + while (symbol_index < strtab_offs) { + typename M::Sym symbol{}; + ReadBytes(&symbol, symbol_index, sizeof(typename M::Sym)); + VAddr offs = strtab_offs + symbol.st_name; + std::string name{}; + for (u8 c = Read8(offs); c != 0; c = Read8(++offs)) + name += char(c); + symbol_index += syment_size; + out[name] = std::make_pair(symbol.st_value, symbol.st_size); } - - symbol_index += symbol_entry_size; - out[name] = std::make_pair(symbol.st_value, symbol.st_size); + return out; } - - return out; + return {}; } Symbols GetSymbols(VAddr base, Core::Memory::Memory& memory, bool is_64) { - const auto ReadBytes{ - [&](void* ptr, size_t offset, size_t size) { memory.ReadBlock(base + offset, ptr, size); }}; - - if (is_64) { - return GetSymbols(ReadBytes); - } else { - return GetSymbols(ReadBytes); - } + const auto f = [base, &memory](void* ptr, size_t offset, size_t size) { + memory.ReadBlock(base + offset, ptr, size); + }; + return is_64 ? GetSymbols(f) : GetSymbols(f); } Symbols GetSymbols(std::span data, bool is_64) { - const auto ReadBytes{[&](void* ptr, size_t offset, size_t size) { + const auto f = [data](void* ptr, size_t offset, size_t size) { std::memcpy(ptr, data.data() + offset, size); - }}; - - if (is_64) { - return GetSymbols(ReadBytes); - } else { - return GetSymbols(ReadBytes); - } + }; + return is_64 ? GetSymbols(f) : GetSymbols(f); } std::optional GetSymbolName(const Symbols& symbols, VAddr addr) { - const auto iter = std::find_if(symbols.cbegin(), symbols.cend(), [addr](const auto& pair) { - const auto& [name, sym_info] = pair; - const auto& [start_address, size] = sym_info; - const auto end_address = start_address + size; - return addr >= start_address && addr < end_address; + const auto it = std::find_if(symbols.cbegin(), symbols.cend(), [addr](const auto& e) { + auto const [start, size] = e.second; + auto const end = start + size; + return addr >= start && addr < end; }); - - if (iter == symbols.cend()) { - return std::nullopt; - } - - return iter->first; + return it != symbols.cend() ? std::optional{it->first} : std::nullopt; } } // namespace Symbols diff --git a/src/core/core.cpp b/src/core/core.cpp index e730b808e0..06543b0833 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -10,6 +10,7 @@ #include "audio_core/audio_core.h" #include "common/fs/fs.h" #include "common/logging.h" +#include "common/adpf.h" #include "common/settings.h" #include "common/settings_enums.h" #include "common/string_util.h" @@ -108,7 +109,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, struct System::Impl { explicit Impl(System& system) - : kernel{system}, fs_controller{system}, hid_core{}, cpu_manager{system}, + : kernel{system}, fs_controller{system}, hid_core{kernel}, cpu_manager{system}, reporter{system}, applet_manager{system}, frontend_applets{system}, profile_manager{} {} u64 program_id; @@ -264,14 +265,12 @@ struct System::Impl { // Setting changes may require a full system reinitialization (e.g., disabling multicore). ReinitializeIfNecessary(system); - kernel.Initialize(); - cpu_manager.Initialize(); } SystemResultStatus SetupForApplicationProcess(System& system, Frontend::EmuWindow& emu_window) { host1x_core.emplace(system); - gpu_core = VideoCore::CreateGPU(emu_window, system); + VideoCore::CreateGPU(gpu_core, emu_window, system); if (!gpu_core) return SystemResultStatus::ErrorVideoCore; @@ -293,11 +292,17 @@ struct System::Impl { return SystemResultStatus::Success; } - SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window, - const std::string& filepath, - Service::AM::FrontendAppletParameters& params) { + SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window, const std::string& filepath, Service::AM::FrontendAppletParameters& params) { + if (params.launch_type == Service::AM::LaunchType::FrontendInitiated) { + fs_controller.InitTempStorage(); + } + InitializeKernel(system); + if (params.applet_type == Service::AM::AppletType::Application) { + current_application_filepath = filepath; + } + const auto file = GetGameFileFromPath(virtual_filesystem, filepath); // Create the application process @@ -326,11 +331,14 @@ struct System::Impl { LOG_INFO(Core, "Loading {} ({:016X}) ...", name, params.program_id); + // Expose program id to dump sites and other global readers. + Settings::SetCurrentProgramID(params.program_id); + // Track launch time for frontend launches LaunchTimestampCache::SaveLaunchTimestamp(params.program_id); // Make the process created be the application - kernel.MakeApplicationProcess(process->GetHandle()); + kernel.SetApplicationProcess(process->GetHandle()); // Set up the rest of the system. SystemResultStatus init_result{SetupForApplicationProcess(system, emu_window)}; @@ -339,6 +347,8 @@ struct System::Impl { ShutdownMainProcess(); return init_result; } + // Waiting for GPU before initializing CPU + cpu_manager.Initialize(); // Initialize cheat engine if (cheat_engine) { @@ -384,6 +394,7 @@ struct System::Impl { void ShutdownMainProcess() { SetShuttingDown(true); + Common::ADPF::Shutdown(); // Reset per-game flags Settings::values.use_squashed_iterated_blend = false; @@ -391,10 +402,8 @@ struct System::Impl { is_powered_on = false; exit_locked = false; exit_requested = false; - - if (gpu_core != nullptr) { + if (gpu_core) gpu_core->NotifyShutdown(); - } stop_event.request_stop(); core_timing.SyncPause(false); @@ -466,6 +475,7 @@ struct System::Impl { Core::SpeedLimiter speed_limiter; ExecuteProgramCallback execute_program_callback; ExitCallback exit_callback; + ApplicationChangedCallback application_changed_callback; std::optional services; std::optional debugger; @@ -478,6 +488,7 @@ struct System::Impl { std::optional cheat_engine; std::optional memory_freezer; std::optional renderdoc_api; + std::optional gpu_core; std::array gpu_dirty_memory_managers; std::vector> user_channel; @@ -486,13 +497,14 @@ struct System::Impl { std::array dynarmic_ticks{}; std::array build_id{}; + std::string current_application_filepath; + /// Service manager std::shared_ptr service_manager; /// ContentProviderUnion instance std::unique_ptr content_provider; /// AppLoader used to load the current executing application std::unique_ptr app_loader; - std::unique_ptr gpu_core; std::stop_source stop_event; mutable std::mutex suspend_guard; @@ -726,7 +738,25 @@ const Core::SpeedLimiter& System::SpeedLimiter() const { } u64 System::GetApplicationProcessProgramID() const { - return impl->kernel.ApplicationProcess()->GetProgramId(); + const auto* const process = impl->kernel.ApplicationProcess(); + return process != nullptr ? process->GetProgramId() : 0; +} + +u64 System::GetProgramIdForProcessId(u64 process_id) const { + auto process = impl->kernel.GetProcessByProcessId(process_id); + return process.IsNull() ? 0 : process->GetProgramId(); +} + +u64 System::ResolveCallerProgramId(u64 process_id) const { + if (const auto program_id = this->GetProgramIdForProcessId(process_id); program_id != 0) { + return program_id; + } + + const auto fallback = this->GetApplicationProcessProgramID(); + LOG_WARNING(Core, + "Could not resolve caller process_id={}, falling back to application {:016X}", + process_id, fallback); + return fallback; } Loader::ResultStatus System::GetGameName(std::string& out) const { @@ -909,6 +939,10 @@ void System::ExecuteProgram(std::size_t program_index) { } } +const std::string& System::GetCurrentApplicationFilePath() const { + return impl->current_application_filepath; +} + /// @brief Gets a reference to the user channel stack. /// It is used to transfer data between programs. std::vector>& System::GetUserChannel() { @@ -925,7 +959,7 @@ void System::PushGeneralChannelData(std::vector&& data) { const bool was_empty = impl->general_channel.empty(); impl->general_channel.push_back(std::move(data)); if (was_empty) { - impl->general_channel_event->Signal(); + impl->general_channel_event->Signal(impl->kernel); } } @@ -937,7 +971,7 @@ bool System::TryPopGeneralChannel(std::vector& out_data) { out_data = std::move(impl->general_channel.back()); impl->general_channel.pop_back(); if (impl->general_channel.empty()) { - impl->general_channel_event->Clear(); + impl->general_channel_event->Clear(impl->kernel); } return true; } @@ -960,6 +994,18 @@ void System::Exit() { } } +void System::RegisterApplicationChangedCallback(ApplicationChangedCallback&& callback) { + impl->application_changed_callback = std::move(callback); +} + +void System::NotifyApplicationChanged(u64 program_id) { + //LOG_DEBUG(Core, "Running application changed to {:016X}", program_id); + + if (impl->application_changed_callback) { + impl->application_changed_callback(program_id); + } +} + void System::ApplySettings() { impl->RefreshTime(*this); diff --git a/src/core/core.h b/src/core/core.h index 012533c1fa..d88ed64151 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -322,6 +322,10 @@ public: [[nodiscard]] u64 GetApplicationProcessProgramID() const; + [[nodiscard]] u64 GetProgramIdForProcessId(u64 process_id) const; + + [[nodiscard]] u64 ResolveCallerProgramId(u64 process_id) const; + /// Gets the name of the current game [[nodiscard]] Loader::ResultStatus GetGameName(std::string& out) const; @@ -422,6 +426,7 @@ public: void PushGeneralChannelData(std::vector&& data); bool TryPopGeneralChannel(std::vector& out_data); [[nodiscard]] Service::Event& GetGeneralChannelEvent(); + [[nodiscard]] const std::string& GetCurrentApplicationFilePath() const; /// Type used for the frontend to designate a callback for System to exit the application. using ExitCallback = std::function; @@ -435,10 +440,13 @@ public: /// Instructs the frontend to exit the application. void Exit(); + using ApplicationChangedCallback = std::function; + void RegisterApplicationChangedCallback(ApplicationChangedCallback&& callback); + void NotifyApplicationChanged(u64 program_id); + /// Applies any changes to settings to this core instance. void ApplySettings(); -private: struct Impl; std::unique_ptr impl; }; diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index b216dc2094..d2ee6769cc 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -8,15 +8,13 @@ #include #include #include +#include "common/cpu_features.h" +#include "common/cpu_features.h" #ifdef _WIN32 #include "common/windows/timer_resolution.h" #endif -#ifdef ARCHITECTURE_x86_64 -#include "common/x64/cpu_wait.h" -#endif - #include "common/settings.h" #include "core/core_timing.h" #include "core/hardware_properties.h" @@ -25,6 +23,21 @@ namespace Core::Timing { constexpr s64 MAX_SLICE_LENGTH = 10000; +constexpr u32 CPU_CLOCK_BASE_MHZ = 1020; +constexpr u32 CPU_CLOCK_BOOST_MHZ = 1734; +constexpr u32 CPU_CLOCK_OVERCLOCK_MHZ = 2040; + +constexpr u32 CpuClockTargetMhz(Settings::CpuClock clock) { + switch (clock) { + case Settings::CpuClock::Boost: + return CPU_CLOCK_BOOST_MHZ; + case Settings::CpuClock::Overclock: + return CPU_CLOCK_OVERCLOCK_MHZ; + default: + return CPU_CLOCK_BASE_MHZ; + } +} + std::shared_ptr CreateEvent(std::string name, TimedCallback&& callback) { return std::make_shared(std::move(callback), std::move(name)); } @@ -47,8 +60,7 @@ struct CoreTiming::Event { } }; -CoreTiming::CoreTiming() : clock{Common::CreateOptimalClock()} {} - +CoreTiming::CoreTiming() = default; CoreTiming::~CoreTiming() { Reset(); } @@ -57,15 +69,37 @@ void CoreTiming::Initialize(std::function&& on_thread_init_) { Reset(); on_thread_init = std::move(on_thread_init_); event_fifo_id = 0; - shutting_down = false; cpu_ticks = 0; if (is_multicore) { - timer_thread.emplace([](CoreTiming& instance) { + timer_thread = std::jthread([this](std::stop_token stop_token) { Common::SetCurrentThreadName("HostTiming"); - Common::SetCurrentThreadPriority(Common::ThreadPriority::High); - instance.on_thread_init(); - instance.ThreadLoop(); - }, std::ref(*this)); + Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh); + Common::SetCurrentThreadToPerformanceCores(); + on_thread_init(); + has_started = true; + + // base frequency in MHz: 1ns (10^-9) = 1GHz (10^9) + while (!stop_token.stop_requested()) { + while (!paused && !stop_token.stop_requested()) { + paused_set = false; + if (auto const next_time = Advance(); next_time) { + // There are more events left in the queue, wait until the next event. + auto const wait_time = *next_time - GetGlobalTimeNs().count(); + if (wait_time > 0) { + event.WaitFor(std::chrono::nanoseconds(wait_time)); + } + } else { + // Queue is empty, wait until another event is scheduled and signals us to + // continue. + wait_set = true; + event.Wait(); + } + wait_set = false; + } + paused_set = true; + pause_event.Wait(); + } + }); } } @@ -90,7 +124,7 @@ void CoreTiming::SyncPause(bool is_paused) { } Pause(is_paused); - if (timer_thread) { + if (timer_thread.joinable()) { if (!is_paused) { pause_event.Set(); } @@ -190,33 +224,23 @@ void CoreTiming::ResetTicks() { } u64 CoreTiming::GetClockTicks() const { - u64 fres; - if (is_multicore) [[likely]] { - fres = clock->GetCNTPCT(); - } else { - fres = Common::WallClock::CPUTickToCNTPCT(cpu_ticks); + u64 fres = is_multicore ? Common::g_wall_clock.GetCNTPCT() : Common::WallClock::CPUTickToCNTPCT(cpu_ticks); + if (const u32 target = CpuClockTargetMhz(Settings::values.cpu_clock.GetValue()); + target != CPU_CLOCK_BASE_MHZ) { + fres = fres * target / CPU_CLOCK_BASE_MHZ; } - - const auto overclock = Settings::values.fast_cpu_time.GetValue(); - - if (overclock != Settings::CpuClock::Off) { - fres = (u64) ((double) fres * (1.7 + 0.3 * u32(overclock))); - } - - if (Settings::values.sync_core_speed.GetValue()) { - const auto ticks = double(fres); - const auto speed_limit = double(Settings::SpeedLimit())*0.01; - return u64(ticks/speed_limit); - } else { - return fres; - } + if (::Settings::values.sync_core_speed.GetValue()) { + auto const ticks = f64(fres); + auto const speed_limit = f64(Settings::SpeedLimit()) * 0.01; + return u64(ticks / speed_limit); + } + return fres; } u64 CoreTiming::GetGPUTicks() const { - if (is_multicore) [[likely]] { - return clock->GetGPUTick(); - } - return Common::WallClock::CPUTickToGPUTick(cpu_ticks); + return is_multicore + ? Common::g_wall_clock.GetGPUTick() + : Common::WallClock::CPUTickToGPUTick(cpu_ticks); } std::optional CoreTiming::Advance() { @@ -278,75 +302,29 @@ std::optional CoreTiming::Advance() { } } -void CoreTiming::ThreadLoop() { - has_started = true; - while (!shutting_down) { - while (!paused) { - paused_set = false; - const auto next_time = Advance(); - if (next_time) { - // There are more events left in the queue, wait until the next event. - auto wait_time = *next_time - GetGlobalTimeNs().count(); - if (wait_time > 0) { -#ifdef _WIN32 - while (!paused && !event.IsSet() && wait_time > 0) { - wait_time = *next_time - GetGlobalTimeNs().count(); - if (wait_time >= timer_resolution_ns) { - Common::Windows::SleepForOneTick(); - } else { -#ifdef ARCHITECTURE_x86_64 - Common::X64::MicroSleep(); -#else - std::this_thread::yield(); -#endif - } - } - - if (event.IsSet()) { - event.Reset(); - } -#else - event.WaitFor(std::chrono::nanoseconds(wait_time)); -#endif - } - } else { - // Queue is empty, wait until another event is scheduled and signals us to - // continue. - wait_set = true; - event.Wait(); - } - wait_set = false; - } - - paused_set = true; - pause_event.Wait(); - } -} - void CoreTiming::Reset() { paused = true; - shutting_down = true; pause_event.Set(); event.Set(); - if (timer_thread) { - timer_thread->join(); + if (timer_thread.joinable()) { + timer_thread.request_stop(); + timer_thread.join(); } - timer_thread.reset(); has_started = false; } -std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const { - if (is_multicore) [[likely]] { - return clock->GetTimeNS(); - } - return std::chrono::nanoseconds{Common::WallClock::CPUTickToNS(cpu_ticks)}; +/// @brief Returns current time in nanoseconds. +std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const noexcept { + return is_multicore + ? Common::g_wall_clock.GetTimeNS() + : std::chrono::nanoseconds{Common::WallClock::CPUTickToNS(cpu_ticks)}; } -std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const { - if (is_multicore) [[likely]] { - return clock->GetTimeUS(); - } - return std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)}; +/// @brief Returns current time in microseconds. +std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const noexcept { + return is_multicore + ? Common::g_wall_clock.GetTimeUS() + : std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)}; } #ifdef _WIN32 diff --git a/src/core/core_timing.h b/src/core/core_timing.h index ae9f56d519..298fc9595d 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -19,7 +19,7 @@ #include "common/common_types.h" #include "common/thread.h" -#include "common/wall_clock.h" +#include "common/cpu_features.h" namespace Core::Timing { @@ -118,7 +118,7 @@ public: void Idle(); - s64 GetDowncount() const { + s64 GetDowncount() const noexcept { return downcount; } @@ -128,11 +128,8 @@ public: /// Returns the current GPU tick value. u64 GetGPUTicks() const; - /// Returns current time in microseconds. - std::chrono::microseconds GetGlobalTimeUs() const; - - /// Returns current time in nanoseconds. - std::chrono::nanoseconds GetGlobalTimeNs() const; + [[nodiscard]] std::chrono::microseconds GetGlobalTimeUs() const noexcept; + [[nodiscard]] std::chrono::nanoseconds GetGlobalTimeNs() const noexcept; /// Checks for events manually and returns time in nanoseconds for next event, threadsafe. std::optional Advance(); @@ -141,44 +138,32 @@ public: void SetTimerResolutionNs(std::chrono::nanoseconds ns); #endif -private: struct Event; - void ThreadLoop(); void Reset(); - std::unique_ptr clock; - + using heap_t = boost::heap::fibonacci_heap>>; + heap_t event_queue; s64 global_timer = 0; - #ifdef _WIN32 s64 timer_resolution_ns; #endif - - using heap_t = - boost::heap::fibonacci_heap>>; - - heap_t event_queue; u64 event_fifo_id = 0; - - Common::Event event{}; - Common::Event pause_event{}; - mutable std::mutex basic_lock; - std::mutex advance_lock; - std::optional timer_thread; - std::atomic paused{}; - std::atomic paused_set{}; - std::atomic wait_set{}; - std::atomic shutting_down{}; - std::atomic has_started{}; - std::function on_thread_init{}; - - bool is_multicore{}; s64 pause_end_time{}; - /// Cycle timing u64 cpu_ticks{}; s64 downcount{}; + Common::Event event{}; + Common::Event pause_event{}; + std::function on_thread_init{}; + std::jthread timer_thread; + mutable std::mutex basic_lock; + std::mutex advance_lock; + std::atomic paused{}; + std::atomic paused_set{}; + std::atomic wait_set{}; + std::atomic has_started{}; + bool is_multicore{}; }; /// Creates a core timing event with the given name and callback. diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp index 10fdcdf8a2..23d5ac3dfd 100644 --- a/src/core/cpu_manager.cpp +++ b/src/core/cpu_manager.cpp @@ -25,9 +25,11 @@ CpuManager::~CpuManager() = default; void CpuManager::Initialize() { num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1; - gpu_barrier = std::make_unique(num_cores + 1); + gpu_barrier.emplace(num_cores + 1); for (std::size_t core = 0; core < num_cores; core++) - core_data[core].host_thread = std::jthread([this, core](std::stop_token token) { RunThread(token, core); }); + core_data[core].host_thread = std::jthread([this, core](std::stop_token token) { + RunThread(token, core); + }); } void CpuManager::Shutdown() { @@ -39,69 +41,61 @@ void CpuManager::Shutdown() { } } -void CpuManager::GuestThreadFunction() { +void CpuManager::GuestThreadFunction(Kernel::KernelCore& kernel) { if (is_multicore) { - MultiCoreRunGuestThread(); + MultiCoreRunGuestThread(kernel); } else { - SingleCoreRunGuestThread(); + SingleCoreRunGuestThread(kernel); } } -void CpuManager::IdleThreadFunction() { +void CpuManager::IdleThreadFunction(Kernel::KernelCore& kernel) { if (is_multicore) { - MultiCoreRunIdleThread(); + MultiCoreRunIdleThread(kernel); } else { - SingleCoreRunIdleThread(); + SingleCoreRunIdleThread(kernel); } } -void CpuManager::ShutdownThreadFunction() { - ShutdownThread(); +void CpuManager::ShutdownThreadFunction(Kernel::KernelCore& kernel) { + ShutdownThread(kernel); } -void CpuManager::HandleInterrupt() { - auto& kernel = system.Kernel(); +void CpuManager::HandleInterrupt(Kernel::KernelCore& kernel) { auto core_index = kernel.CurrentPhysicalCoreIndex(); - - Kernel::KInterruptManager::HandleInterrupt(kernel, static_cast(core_index)); + Kernel::KInterruptManager::HandleInterrupt(kernel, s32(core_index)); } /////////////////////////////////////////////////////////////////////////////// /// MultiCore /// /////////////////////////////////////////////////////////////////////////////// -void CpuManager::MultiCoreRunGuestThread() { +void CpuManager::MultiCoreRunGuestThread(Kernel::KernelCore& kernel) { // Similar to UserModeThreadStarter in HOS - auto& kernel = system.Kernel(); auto* thread = Kernel::GetCurrentThreadPointer(kernel); - kernel.CurrentScheduler()->OnThreadStart(); + kernel.CurrentScheduler()->OnThreadStart(kernel); while (true) { auto* physical_core = &kernel.CurrentPhysicalCore(); while (!physical_core->IsInterrupted()) { - physical_core->RunThread(thread); + physical_core->RunThread(kernel, thread); physical_core = &kernel.CurrentPhysicalCore(); } - - HandleInterrupt(); + HandleInterrupt(kernel); } } -void CpuManager::MultiCoreRunIdleThread() { +void CpuManager::MultiCoreRunIdleThread(Kernel::KernelCore& kernel) { // Not accurate to HOS. Remove this entire method when singlecore is removed. // See notes in KScheduler::ScheduleImpl for more information about why this // is inaccurate. - - auto& kernel = system.Kernel(); - kernel.CurrentScheduler()->OnThreadStart(); - + kernel.CurrentScheduler()->OnThreadStart(kernel); while (true) { auto& physical_core = kernel.CurrentPhysicalCore(); if (!physical_core.IsInterrupted()) { physical_core.Idle(); } - - HandleInterrupt(); + HandleInterrupt(kernel); } } @@ -109,15 +103,14 @@ void CpuManager::MultiCoreRunIdleThread() { /// SingleCore /// /////////////////////////////////////////////////////////////////////////////// -void CpuManager::SingleCoreRunGuestThread() { - auto& kernel = system.Kernel(); +void CpuManager::SingleCoreRunGuestThread(Kernel::KernelCore& kernel) { auto* thread = Kernel::GetCurrentThreadPointer(kernel); - kernel.CurrentScheduler()->OnThreadStart(); + kernel.CurrentScheduler()->OnThreadStart(kernel); while (true) { auto* physical_core = &kernel.CurrentPhysicalCore(); if (!physical_core->IsInterrupted()) { - physical_core->RunThread(thread); + physical_core->RunThread(kernel, thread); physical_core = &kernel.CurrentPhysicalCore(); } @@ -125,26 +118,22 @@ void CpuManager::SingleCoreRunGuestThread() { system.CoreTiming().Advance(); kernel.SetIsPhantomModeForSingleCore(false); - PreemptSingleCore(); - HandleInterrupt(); + PreemptSingleCore(kernel); + HandleInterrupt(kernel); } } -void CpuManager::SingleCoreRunIdleThread() { - auto& kernel = system.Kernel(); - kernel.CurrentScheduler()->OnThreadStart(); - +void CpuManager::SingleCoreRunIdleThread(Kernel::KernelCore& kernel) { + kernel.CurrentScheduler()->OnThreadStart(kernel); while (true) { - PreemptSingleCore(false); + PreemptSingleCore(kernel, false); system.CoreTiming().AddTicks(1000U); idle_count++; - HandleInterrupt(); + HandleInterrupt(kernel); } } -void CpuManager::PreemptSingleCore(bool from_running_environment) { - auto& kernel = system.Kernel(); - +void CpuManager::PreemptSingleCore(Kernel::KernelCore& kernel, bool from_running_environment) { if (idle_count >= 4 || from_running_environment) { if (!from_running_environment) { system.CoreTiming().Idle(); @@ -156,7 +145,7 @@ void CpuManager::PreemptSingleCore(bool from_running_environment) { } current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES); system.CoreTiming().ResetTicks(); - kernel.Scheduler(current_core).PreemptSingleCore(); + kernel.Scheduler(current_core).PreemptSingleCore(kernel); // We've now been scheduled again, and we may have exchanged schedulers. // Reload the scheduler in case it's different. @@ -165,20 +154,16 @@ void CpuManager::PreemptSingleCore(bool from_running_environment) { } } -void CpuManager::GuestActivate() { +void CpuManager::GuestActivate(Kernel::KernelCore& kernel) { // Similar to the HorizonKernelMain callback in HOS - auto& kernel = system.Kernel(); auto* scheduler = kernel.CurrentScheduler(); - - scheduler->Activate(); + scheduler->Activate(kernel); UNREACHABLE(); } -void CpuManager::ShutdownThread() { - auto& kernel = system.Kernel(); +void CpuManager::ShutdownThread(Kernel::KernelCore& kernel) { auto* thread = kernel.GetCurrentEmuThread(); auto core = is_multicore ? kernel.CurrentPhysicalCoreIndex() : 0; - Common::Fiber::YieldTo(thread->GetHostContext(), *core_data[core].host_context); UNREACHABLE(); } @@ -189,12 +174,7 @@ void CpuManager::RunThread(std::stop_token token, std::size_t core) { std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"}; Common::SetCurrentThreadName(name.c_str()); Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical); -#ifdef __ANDROID__ - // Aimed specifically for Snapdragon 8 Elite devices - // This kills performance on desktop, but boosts perf for UMA devices - // like the S8E. Mediatek and Mali likely won't suffer. - Common::PinCurrentThreadToPerformanceCore(core); -#endif + Common::SetCurrentThreadToPerformanceCores(); auto& data = core_data[core]; data.host_context = Common::Fiber::ThreadToFiber(); diff --git a/src/core/cpu_manager.h b/src/core/cpu_manager.h index a249dc5f76..5e97f51bfb 100644 --- a/src/core/cpu_manager.h +++ b/src/core/cpu_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -14,6 +17,10 @@ #include "common/thread.h" #include "core/hardware_properties.h" +namespace Kernel { +class KernelCore; +} + namespace Common { class Event; class Fiber; @@ -51,57 +58,55 @@ public: void Initialize(); void Shutdown(); - std::function GetGuestActivateFunc() { - return [this] { GuestActivate(); }; + std::function GetGuestActivateFunc(Kernel::KernelCore& kernel) { + return [this, &kernel] { GuestActivate(kernel); }; } - std::function GetGuestThreadFunc() { - return [this] { GuestThreadFunction(); }; + std::function GetGuestThreadFunc(Kernel::KernelCore& kernel) { + return [this, &kernel] { GuestThreadFunction(kernel); }; } - std::function GetIdleThreadStartFunc() { - return [this] { IdleThreadFunction(); }; + std::function GetIdleThreadStartFunc(Kernel::KernelCore& kernel) { + return [this, &kernel] { IdleThreadFunction(kernel); }; } - std::function GetShutdownThreadStartFunc() { - return [this] { ShutdownThreadFunction(); }; + std::function GetShutdownThreadStartFunc(Kernel::KernelCore& kernel) { + return [this, &kernel] { ShutdownThreadFunction(kernel); }; } - void PreemptSingleCore(bool from_running_environment = true); + void PreemptSingleCore(Kernel::KernelCore& kernel, bool from_running_environment = true); std::size_t CurrentCore() const { return current_core.load(); } private: - void GuestThreadFunction(); - void IdleThreadFunction(); - void ShutdownThreadFunction(); + void GuestThreadFunction(Kernel::KernelCore& kernel); + void IdleThreadFunction(Kernel::KernelCore& kernel); + void ShutdownThreadFunction(Kernel::KernelCore& kernel); - void MultiCoreRunGuestThread(); - void MultiCoreRunIdleThread(); + void MultiCoreRunGuestThread(Kernel::KernelCore& kernel); + void MultiCoreRunIdleThread(Kernel::KernelCore& kernel); - void SingleCoreRunGuestThread(); - void SingleCoreRunIdleThread(); + void SingleCoreRunGuestThread(Kernel::KernelCore& kernel); + void SingleCoreRunIdleThread(Kernel::KernelCore& kernel); - void GuestActivate(); - void HandleInterrupt(); - void ShutdownThread(); + void GuestActivate(Kernel::KernelCore& kernel); + void HandleInterrupt(Kernel::KernelCore& kernel); + void ShutdownThread(Kernel::KernelCore& kernel); void RunThread(std::stop_token stop_token, std::size_t core); + static constexpr std::size_t max_cycle_runs = 5; + + std::optional gpu_barrier{}; struct CoreData { std::shared_ptr host_context; std::jthread host_thread; }; - - std::unique_ptr gpu_barrier{}; std::array core_data{}; - - bool is_async_gpu{}; - bool is_multicore{}; + Core::System& system; std::atomic current_core{}; std::size_t idle_count{}; std::size_t num_cores{}; - static constexpr std::size_t max_cycle_runs = 5; - - System& system; + bool is_async_gpu{}; + bool is_multicore{}; }; } // namespace Core diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp index 34d66456ee..7eea665e6d 100644 --- a/src/core/crypto/aes_util.cpp +++ b/src/core/crypto/aes_util.cpp @@ -56,10 +56,9 @@ static inline const std::string GetCipherName(Mode mode, u32 key_size) { }; static EVP_CIPHER *GetCipher(Mode mode, u32 key_size) { - static auto fetch_cipher = [](Mode m, u32 k) { + static auto const fetch_cipher = [](Mode m, u32 k) { return EVP_CIPHER_fetch(nullptr, GetCipherName(m, k).c_str(), nullptr); }; - static const struct { EVP_CIPHER* ctr_16 = fetch_cipher(Mode::CTR, 16); EVP_CIPHER* ecb_16 = fetch_cipher(Mode::ECB, 16); @@ -68,7 +67,6 @@ static EVP_CIPHER *GetCipher(Mode mode, u32 key_size) { EVP_CIPHER* ecb_32 = fetch_cipher(Mode::ECB, 32); EVP_CIPHER* xts_32 = fetch_cipher(Mode::XTS, 32); } ciphers = {}; - switch (mode) { case Mode::CTR: return key_size == 16 ? ciphers.ctr_16 : ciphers.ctr_32; @@ -79,17 +77,15 @@ static EVP_CIPHER *GetCipher(Mode mode, u32 key_size) { default: UNIMPLEMENTED(); } - return nullptr; } // TODO: WHY TEMPLATE??????? -template -Crypto::AESCipher::AESCipher(Key key, Mode mode) : ctx(std::make_unique()) { - +template +Crypto::AESCipher::AESCipher(Key key, Mode mode) : ctx(std::make_unique()) { ctx->encryption_context = EVP_CIPHER_CTX_new(); ctx->decryption_context = EVP_CIPHER_CTX_new(); - ctx->cipher = GetCipher(mode, KeySize); + ctx->cipher = GetCipher(mode, sizeof(Key)); if (ctx->cipher) { EVP_CIPHER_up_ref(ctx->cipher); } else { @@ -105,15 +101,15 @@ Crypto::AESCipher::AESCipher(Key key, Mode mode) : ctx(std::make_u EVP_CIPHER_CTX_set_padding(ctx->decryption_context, 0); } -template -AESCipher::~AESCipher() { +template +AESCipher::~AESCipher() { EVP_CIPHER_CTX_free(ctx->encryption_context); EVP_CIPHER_CTX_free(ctx->decryption_context); EVP_CIPHER_free(ctx->cipher); } -template -void AESCipher::Transcode(const u8* src, std::size_t size, u8* dest, Op op) const { +template +void AESCipher::Transcode(const u8* src, std::size_t size, u8* dest, Op op) const { auto* const context = op == Op::Encrypt ? ctx->encryption_context : ctx->decryption_context; if (size == 0) @@ -157,9 +153,8 @@ void AESCipher::Transcode(const u8* src, std::size_t size, u8* des std::memcpy(dest + whole_block_bytes, tail_buffer.data(), tail); } -template -void AESCipher::XTSTranscode(const u8* src, std::size_t size, u8* dest, - std::size_t sector_id, std::size_t sector_size, Op op) { +template +void AESCipher::XTSTranscode(const u8* src, std::size_t size, u8* dest, std::size_t sector_id, std::size_t sector_size, Op op) { ASSERT(size % sector_size == 0 && "XTS decryption size must be a multiple of sector size."); for (std::size_t i = 0; i < size; i += sector_size) { SetIV(CalculateNintendoTweak(sector_id++)); @@ -167,8 +162,8 @@ void AESCipher::XTSTranscode(const u8* src, std::size_t size, u8* } } -template -void AESCipher::SetIV(std::span data) { +template +void AESCipher::SetIV(std::span data) { const int ret_enc = EVP_CipherInit_ex(ctx->encryption_context, nullptr, nullptr, nullptr, data.data(), -1); const int ret_dec = EVP_CipherInit_ex(ctx->decryption_context, nullptr, nullptr, nullptr, data.data(), -1); ASSERT(ret_enc == 1 && ret_dec == 1 && "Failed to set IV on OpenSSL contexts"); diff --git a/src/core/crypto/aes_util.h b/src/core/crypto/aes_util.h index a3de95e5e2..704e175d84 100644 --- a/src/core/crypto/aes_util.h +++ b/src/core/crypto/aes_util.h @@ -26,37 +26,30 @@ enum class Op { Decrypt, }; -template +template class AESCipher { - static_assert(std::is_same_v>, "Key must be std::array of u8."); - static_assert(KeySize == 0x10 || KeySize == 0x20, "KeySize must be 128 or 256."); - public: + static_assert(sizeof(Key) == 0x10 || sizeof(Key) == 0x20); AESCipher(Key key, Mode mode); ~AESCipher(); void SetIV(std::span data); template + requires std::is_trivially_copyable_v && std::is_trivially_copyable_v void Transcode(const Source* src, std::size_t size, Dest* dest, Op op) const { - static_assert(std::is_trivially_copyable_v && std::is_trivially_copyable_v, - "Transcode source and destination types must be trivially copyable."); Transcode(reinterpret_cast(src), size, reinterpret_cast(dest), op); } void Transcode(const u8* src, std::size_t size, u8* dest, Op op) const; template - void XTSTranscode(const Source* src, std::size_t size, Dest* dest, std::size_t sector_id, - std::size_t sector_size, Op op) { - static_assert(std::is_trivially_copyable_v && std::is_trivially_copyable_v, - "XTSTranscode source and destination types must be trivially copyable."); - XTSTranscode(reinterpret_cast(src), size, reinterpret_cast(dest), sector_id, - sector_size, op); + requires std::is_trivially_copyable_v && std::is_trivially_copyable_v + void XTSTranscode(const Source* src, std::size_t size, Dest* dest, std::size_t sector_id, std::size_t sector_size, Op op) { + XTSTranscode(reinterpret_cast(src), size, reinterpret_cast(dest), sector_id, sector_size, op); } - void XTSTranscode(const u8* src, std::size_t size, u8* dest, std::size_t sector_id, - std::size_t sector_size, Op op); + void XTSTranscode(const u8* src, std::size_t size, u8* dest, std::size_t sector_id, std::size_t sector_size, Op op); private: std::unique_ptr ctx; diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 68692c178e..4c5732a155 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -35,70 +35,56 @@ namespace { constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; -using Common::AsArray; - constexpr std::array>, 30> s128_file_id{{ {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}}, - {"eticket_rsa_kek_source", - {S128KeyType::Source, static_cast(SourceKeyType::ETicketKek), 0}}, - {"eticket_rsa_kekek_source", - {S128KeyType::Source, static_cast(SourceKeyType::ETicketKekek), 0}}, - {"rsa_kek_mask_0", {S128KeyType::RSAKek, static_cast(RSAKekType::Mask0), 0}}, - {"rsa_kek_seed_3", {S128KeyType::RSAKek, static_cast(RSAKekType::Seed3), 0}}, - {"rsa_oaep_kek_generation_source", - {S128KeyType::Source, static_cast(SourceKeyType::RSAOaepKekGeneration), 0}}, - {"sd_card_kek_source", {S128KeyType::Source, static_cast(SourceKeyType::SDKek), 0}}, - {"aes_kek_generation_source", - {S128KeyType::Source, static_cast(SourceKeyType::AESKekGeneration), 0}}, - {"aes_key_generation_source", - {S128KeyType::Source, static_cast(SourceKeyType::AESKeyGeneration), 0}}, - {"package2_key_source", {S128KeyType::Source, static_cast(SourceKeyType::Package2), 0}}, - {"master_key_source", {S128KeyType::Source, static_cast(SourceKeyType::Master), 0}}, - {"header_kek_source", {S128KeyType::Source, static_cast(SourceKeyType::HeaderKek), 0}}, - {"key_area_key_application_source", - {S128KeyType::Source, static_cast(SourceKeyType::KeyAreaKey), - static_cast(KeyAreaKeyType::Application)}}, - {"key_area_key_ocean_source", - {S128KeyType::Source, static_cast(SourceKeyType::KeyAreaKey), - static_cast(KeyAreaKeyType::Ocean)}}, - {"key_area_key_system_source", - {S128KeyType::Source, static_cast(SourceKeyType::KeyAreaKey), - static_cast(KeyAreaKeyType::System)}}, - {"titlekek_source", {S128KeyType::Source, static_cast(SourceKeyType::Titlekek), 0}}, - {"keyblob_mac_key_source", - {S128KeyType::Source, static_cast(SourceKeyType::KeyblobMAC), 0}}, + {"eticket_rsa_kek_source", {S128KeyType::Source, u64(SourceKeyType::ETicketKek), 0}}, + {"eticket_rsa_kekek_source", {S128KeyType::Source, u64(SourceKeyType::ETicketKekek), 0}}, + {"rsa_kek_mask_0", {S128KeyType::RSAKek, u64(RSAKekType::Mask0), 0}}, + {"rsa_kek_seed_3", {S128KeyType::RSAKek, u64(RSAKekType::Seed3), 0}}, + {"rsa_oaep_kek_generation_source", {S128KeyType::Source, u64(SourceKeyType::RSAOaepKekGeneration), 0}}, + {"sd_card_kek_source", {S128KeyType::Source, u64(SourceKeyType::SDKek), 0}}, + {"aes_kek_generation_source", {S128KeyType::Source, u64(SourceKeyType::AESKekGeneration), 0}}, + {"aes_key_generation_source", {S128KeyType::Source, u64(SourceKeyType::AESKeyGeneration), 0}}, + {"package2_key_source", {S128KeyType::Source, u64(SourceKeyType::Package2), 0}}, + {"master_key_source", {S128KeyType::Source, u64(SourceKeyType::Master), 0}}, + {"header_kek_source", {S128KeyType::Source, u64(SourceKeyType::HeaderKek), 0}}, + {"key_area_key_application_source", {S128KeyType::Source, u64(SourceKeyType::KeyAreaKey), u64(KeyAreaKeyType::Application)}}, + {"key_area_key_ocean_source", {S128KeyType::Source, u64(SourceKeyType::KeyAreaKey), u64(KeyAreaKeyType::Ocean)}}, + {"key_area_key_system_source", {S128KeyType::Source, u64(SourceKeyType::KeyAreaKey), u64(KeyAreaKeyType::System)}}, + {"titlekek_source", {S128KeyType::Source, u64(SourceKeyType::Titlekek), 0}}, + {"keyblob_mac_key_source", {S128KeyType::Source, u64(SourceKeyType::KeyblobMAC), 0}}, {"tsec_key", {S128KeyType::TSEC, 0, 0}}, {"secure_boot_key", {S128KeyType::SecureBoot, 0, 0}}, {"sd_seed", {S128KeyType::SDSeed, 0, 0}}, - {"bis_key_0_crypt", {S128KeyType::BIS, 0, static_cast(BISKeyType::Crypto)}}, - {"bis_key_0_tweak", {S128KeyType::BIS, 0, static_cast(BISKeyType::Tweak)}}, - {"bis_key_1_crypt", {S128KeyType::BIS, 1, static_cast(BISKeyType::Crypto)}}, - {"bis_key_1_tweak", {S128KeyType::BIS, 1, static_cast(BISKeyType::Tweak)}}, - {"bis_key_2_crypt", {S128KeyType::BIS, 2, static_cast(BISKeyType::Crypto)}}, - {"bis_key_2_tweak", {S128KeyType::BIS, 2, static_cast(BISKeyType::Tweak)}}, - {"bis_key_3_crypt", {S128KeyType::BIS, 3, static_cast(BISKeyType::Crypto)}}, - {"bis_key_3_tweak", {S128KeyType::BIS, 3, static_cast(BISKeyType::Tweak)}}, + {"bis_key_0_crypt", {S128KeyType::BIS, 0, u64(BISKeyType::Crypto)}}, + {"bis_key_0_tweak", {S128KeyType::BIS, 0, u64(BISKeyType::Tweak)}}, + {"bis_key_1_crypt", {S128KeyType::BIS, 1, u64(BISKeyType::Crypto)}}, + {"bis_key_1_tweak", {S128KeyType::BIS, 1, u64(BISKeyType::Tweak)}}, + {"bis_key_2_crypt", {S128KeyType::BIS, 2, u64(BISKeyType::Crypto)}}, + {"bis_key_2_tweak", {S128KeyType::BIS, 2, u64(BISKeyType::Tweak)}}, + {"bis_key_3_crypt", {S128KeyType::BIS, 3, u64(BISKeyType::Crypto)}}, + {"bis_key_3_tweak", {S128KeyType::BIS, 3, u64(BISKeyType::Tweak)}}, {"header_kek", {S128KeyType::HeaderKek, 0, 0}}, {"sd_card_kek", {S128KeyType::SDKek, 0, 0}}, }}; auto Find128ByName(std::string_view name) { - return std::find_if(s128_file_id.begin(), s128_file_id.end(), - [&name](const auto& pair) { return pair.first == name; }); + return std::find_if(s128_file_id.begin(), s128_file_id.end(), [&name](const auto& pair) { + return pair.first == name; + }); } constexpr std::array>, 6> s256_file_id{{ {"header_key", {S256KeyType::Header, 0, 0}}, - {"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast(SDKeyType::Save), 0}}, - {"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast(SDKeyType::NCA), 0}}, + {"sd_card_save_key_source", {S256KeyType::SDKeySource, u64(SDKeyType::Save), 0}}, + {"sd_card_nca_key_source", {S256KeyType::SDKeySource, u64(SDKeyType::NCA), 0}}, {"header_key_source", {S256KeyType::HeaderSource, 0, 0}}, - {"sd_card_save_key", {S256KeyType::SDKey, static_cast(SDKeyType::Save), 0}}, - {"sd_card_nca_key", {S256KeyType::SDKey, static_cast(SDKeyType::NCA), 0}}, + {"sd_card_save_key", {S256KeyType::SDKey, u64(SDKeyType::Save), 0}}, + {"sd_card_nca_key", {S256KeyType::SDKey, u64(SDKeyType::NCA), 0}}, }}; auto Find256ByName(std::string_view name) { - return std::find_if(s256_file_id.begin(), s256_file_id.end(), - [&name](const auto& pair) { return pair.first == name; }); + return std::find_if(s256_file_id.begin(), s256_file_id.end(), [&name](const auto& pair) { return pair.first == name; }); } using KeyArray = std::array, std::string_view>, 7>; @@ -107,7 +93,7 @@ constexpr KeyArray KEYS_VARIABLE_LENGTH{{ {{S128KeyType::Package1, 0}, "package1_key_"}, {{S128KeyType::Package2, 0}, "package2_key_"}, {{S128KeyType::Titlekek, 0}, "titlekek_"}, - {{S128KeyType::Source, static_cast(SourceKeyType::Keyblob)}, "keyblob_key_source_"}, + {{S128KeyType::Source, u64(SourceKeyType::Keyblob)}, "keyblob_key_source_"}, {{S128KeyType::Keyblob, 0}, "keyblob_key_"}, {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"}, }}; @@ -152,49 +138,39 @@ bool Ticket::IsValid() const { } SignatureType Ticket::GetSignatureType() const { - if (const auto* ticket = std::get_if(&data)) { + if (const auto* ticket = std::get_if(&data)) return ticket->sig_type; - } - if (const auto* ticket = std::get_if(&data)) { + if (const auto* ticket = std::get_if(&data)) return ticket->sig_type; - } - if (const auto* ticket = std::get_if(&data)) { + if (const auto* ticket = std::get_if(&data)) return ticket->sig_type; - } - throw std::bad_variant_access{}; + UNREACHABLE(); } TicketData& Ticket::GetData() { - if (auto* ticket = std::get_if(&data)) { + if (auto* ticket = std::get_if(&data)) return ticket->data; - } - if (auto* ticket = std::get_if(&data)) { + if (auto* ticket = std::get_if(&data)) return ticket->data; - } - if (auto* ticket = std::get_if(&data)) { + if (auto* ticket = std::get_if(&data)) return ticket->data; - } - throw std::bad_variant_access{}; + UNREACHABLE(); } const TicketData& Ticket::GetData() const { - if (const auto* ticket = std::get_if(&data)) { + if (const auto* ticket = std::get_if(&data)) return ticket->data; - } - if (const auto* ticket = std::get_if(&data)) { + if (const auto* ticket = std::get_if(&data)) return ticket->data; - } - if (const auto* ticket = std::get_if(&data)) { + if (const auto* ticket = std::get_if(&data)) return ticket->data; - } - throw std::bad_variant_access{}; + UNREACHABLE(); } u64 Ticket::GetSize() const { const auto sig_type = GetSignatureType(); - return sizeof(SignatureType) + GetSignatureTypeDataSize(sig_type) + - GetSignatureTypePaddingSize(sig_type) + sizeof(TicketData); + return sizeof(SignatureType) + GetSignatureTypeDataSize(sig_type) + GetSignatureTypePaddingSize(sig_type) + sizeof(TicketData); } Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array& rights_id) { @@ -222,8 +198,7 @@ Ticket Ticket::Read(std::span raw_data) { // just make sure we have at least the bare minimum of data to work with. SignatureType sig_type; if (raw_data.size() < sizeof(SignatureType)) { - LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid size {}.", - raw_data.size()); + LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid size {}.", raw_data.size()); return Ticket{std::monostate()}; } std::memcpy(&sig_type, raw_data.data(), sizeof(sig_type)); @@ -255,17 +230,14 @@ Ticket Ticket::Read(std::span raw_data) { Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { Key128 out{}; - AESCipher cipher1(master, Mode::ECB); cipher1.Transcode(kek_seed.data(), kek_seed.size(), out.data(), Op::Decrypt); AESCipher cipher2(out, Mode::ECB); cipher2.Transcode(source.data(), source.size(), out.data(), Op::Decrypt); - if (key_seed != Key128{}) { AESCipher cipher3(out, Mode::ECB); cipher3.Transcode(key_seed.data(), key_seed.size(), out.data(), Op::Decrypt); } - return out; } @@ -280,16 +252,13 @@ Key128 DeriveKeyblobKey(const Key128& sbk, const Key128& tsec, Key128 source) { Key128 DeriveMasterKey(const std::array& keyblob, const Key128& master_source) { Key128 master_root; std::memcpy(master_root.data(), keyblob.data(), sizeof(Key128)); - AESCipher master_cipher(master_root, Mode::ECB); - Key128 master{}; master_cipher.Transcode(master_source.data(), master_source.size(), master.data(), Op::Decrypt); return master; } -std::array DecryptKeyblob(const std::array& encrypted_keyblob, - const Key128& key) { +std::array DecryptKeyblob(const std::array& encrypted_keyblob, const Key128& key) { std::array keyblob; AESCipher cipher(key, Mode::CTR); cipher.SetIV(std::vector(encrypted_keyblob.data() + 0x10, encrypted_keyblob.data() + 0x20)); @@ -298,61 +267,39 @@ std::array DecryptKeyblob(const std::array& encrypted_keyblob, } void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) { - const auto kek_generation_source = - GetKey(S128KeyType::Source, static_cast(SourceKeyType::AESKekGeneration)); - const auto key_generation_source = - GetKey(S128KeyType::Source, static_cast(SourceKeyType::AESKeyGeneration)); - + const auto kek_generation_source = GetKey(S128KeyType::Source, u64(SourceKeyType::AESKekGeneration)); + const auto key_generation_source = GetKey(S128KeyType::Source, u64(SourceKeyType::AESKeyGeneration)); if (HasKey(S128KeyType::Master, crypto_revision)) { - for (auto kak_type : - {KeyAreaKeyType::Application, KeyAreaKeyType::Ocean, KeyAreaKeyType::System}) { - if (HasKey(S128KeyType::Source, static_cast(SourceKeyType::KeyAreaKey), - static_cast(kak_type))) { - const auto source = - GetKey(S128KeyType::Source, static_cast(SourceKeyType::KeyAreaKey), - static_cast(kak_type)); - const auto kek = - GenerateKeyEncryptionKey(source, GetKey(S128KeyType::Master, crypto_revision), - kek_generation_source, key_generation_source); - SetKey(S128KeyType::KeyArea, kek, crypto_revision, static_cast(kak_type)); + for (auto kak_type : {KeyAreaKeyType::Application, KeyAreaKeyType::Ocean, KeyAreaKeyType::System}) { + if (HasKey(S128KeyType::Source, u64(SourceKeyType::KeyAreaKey), u64(kak_type))) { + const auto source = GetKey(S128KeyType::Source, u64(SourceKeyType::KeyAreaKey), u64(kak_type)); + const auto kek = GenerateKeyEncryptionKey(source, GetKey(S128KeyType::Master, crypto_revision), kek_generation_source, key_generation_source); + SetKey(S128KeyType::KeyArea, kek, crypto_revision, u64(kak_type)); } } - AESCipher master_cipher(GetKey(S128KeyType::Master, crypto_revision), Mode::ECB); for (auto key_type : {SourceKeyType::Titlekek, SourceKeyType::Package2}) { - if (HasKey(S128KeyType::Source, static_cast(key_type))) { + if (HasKey(S128KeyType::Source, u64(key_type))) { Key128 key{}; - master_cipher.Transcode( - GetKey(S128KeyType::Source, static_cast(key_type)).data(), key.size(), - key.data(), Op::Decrypt); - SetKey(key_type == SourceKeyType::Titlekek ? S128KeyType::Titlekek - : S128KeyType::Package2, - key, crypto_revision); + master_cipher.Transcode(GetKey(S128KeyType::Source, u64(key_type)).data(), key.size(), key.data(), Op::Decrypt); + SetKey(key_type == SourceKeyType::Titlekek ? S128KeyType::Titlekek : S128KeyType::Package2, key, crypto_revision); } } } } void KeyManager::DeriveETicketRSAKey() { - if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) { - return; + if (eticket_extended_kek != std::array{} && HasKey(S128KeyType::ETicketRSAKek)) { + const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); + std::vector extended_iv(eticket_extended_kek.begin(), eticket_extended_kek.begin() + 0x10); + std::array extended_dec{}; + AESCipher rsa_1(eticket_final, Mode::CTR); + rsa_1.SetIV(extended_iv); + rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, extended_dec.data(), Op::Decrypt); + std::memcpy(eticket_rsa_keypair.decryption_key.data(), extended_dec.data(), eticket_rsa_keypair.decryption_key.size()); + std::memcpy(eticket_rsa_keypair.modulus.data(), extended_dec.data() + 0x100, eticket_rsa_keypair.modulus.size()); + std::memcpy(eticket_rsa_keypair.exponent.data(), extended_dec.data() + 0x200, eticket_rsa_keypair.exponent.size()); } - - const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); - - std::vector extended_iv(eticket_extended_kek.begin(), eticket_extended_kek.begin() + 0x10); - std::array extended_dec{}; - AESCipher rsa_1(eticket_final, Mode::CTR); - rsa_1.SetIV(extended_iv); - rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, - extended_dec.data(), Op::Decrypt); - - std::memcpy(eticket_rsa_keypair.decryption_key.data(), extended_dec.data(), - eticket_rsa_keypair.decryption_key.size()); - std::memcpy(eticket_rsa_keypair.modulus.data(), extended_dec.data() + 0x100, - eticket_rsa_keypair.modulus.size()); - std::memcpy(eticket_rsa_keypair.exponent.data(), extended_dec.data() + 0x200, - eticket_rsa_keypair.exponent.size()); } Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { @@ -363,78 +310,57 @@ Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) } std::optional DeriveSDSeed() { - const auto system_save_43_path = - Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) / "system/save/8000000000000043"; - const Common::FS::IOFile save_43{system_save_43_path, Common::FS::FileAccessMode::Read, - Common::FS::FileType::BinaryFile}; + const auto system_save_43_path = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) / "system/save/8000000000000043"; + const Common::FS::IOFile save_43{system_save_43_path, Common::FS::FileAccessMode::Read, Common::FS::FileType::BinaryFile}; + if (save_43.IsOpen()) { + const auto sd_private_path = Common::FS::GetEdenPath(Common::FS::EdenPath::SDMCDir) / "Nintendo/Contents/private"; + const Common::FS::IOFile sd_private{sd_private_path, Common::FS::FileAccessMode::Read, Common::FS::FileType::BinaryFile}; + if (sd_private.IsOpen()) { + std::array private_seed{}; + if (sd_private.Read(private_seed) == private_seed.size()) { + std::array buffer{}; + s64 offset = 0; + for (; offset + 0x10 < s64(save_43.GetSize()); ++offset) { + if (!save_43.Seek(offset)) { + return std::nullopt; + } - if (!save_43.IsOpen()) { - return std::nullopt; - } + if (save_43.Read(buffer) != buffer.size()) { + return std::nullopt; + } - const auto sd_private_path = - Common::FS::GetEdenPath(Common::FS::EdenPath::SDMCDir) / "Nintendo/Contents/private"; - - const Common::FS::IOFile sd_private{sd_private_path, Common::FS::FileAccessMode::Read, - Common::FS::FileType::BinaryFile}; - - if (!sd_private.IsOpen()) { - return std::nullopt; - } - - std::array private_seed{}; - if (sd_private.Read(private_seed) != private_seed.size()) { - return std::nullopt; - } - - std::array buffer{}; - s64 offset = 0; - for (; offset + 0x10 < static_cast(save_43.GetSize()); ++offset) { - if (!save_43.Seek(offset)) { - return std::nullopt; - } - - if (save_43.Read(buffer) != buffer.size()) { - return std::nullopt; - } - - if (buffer == private_seed) { - break; + if (buffer == private_seed) { + break; + } + } + if (save_43.Seek(offset + 0x10)) { + Key128 seed{}; + if (save_43.Read(seed) == seed.size()) { + return seed; + } + } + } } } - - if (!save_43.Seek(offset + 0x10)) { - return std::nullopt; - } - - Key128 seed{}; - if (save_43.Read(seed) != seed.size()) { - return std::nullopt; - } - - return seed; + return std::nullopt; } Loader::ResultStatus DeriveSDKeys(std::array& sd_keys, KeyManager& keys) { - if (!keys.HasKey(S128KeyType::Source, static_cast(SourceKeyType::SDKek))) { + if (!keys.HasKey(S128KeyType::Source, u64(SourceKeyType::SDKek))) { return Loader::ResultStatus::ErrorMissingSDKEKSource; } - if (!keys.HasKey(S128KeyType::Source, static_cast(SourceKeyType::AESKekGeneration))) { + if (!keys.HasKey(S128KeyType::Source, u64(SourceKeyType::AESKekGeneration))) { return Loader::ResultStatus::ErrorMissingAESKEKGenerationSource; } - if (!keys.HasKey(S128KeyType::Source, static_cast(SourceKeyType::AESKeyGeneration))) { + if (!keys.HasKey(S128KeyType::Source, u64(SourceKeyType::AESKeyGeneration))) { return Loader::ResultStatus::ErrorMissingAESKeyGenerationSource; } - const auto sd_kek_source = - keys.GetKey(S128KeyType::Source, static_cast(SourceKeyType::SDKek)); - const auto aes_kek_gen = - keys.GetKey(S128KeyType::Source, static_cast(SourceKeyType::AESKekGeneration)); - const auto aes_key_gen = - keys.GetKey(S128KeyType::Source, static_cast(SourceKeyType::AESKeyGeneration)); + const auto sd_kek_source = keys.GetKey(S128KeyType::Source, u64(SourceKeyType::SDKek)); + const auto aes_kek_gen = keys.GetKey(S128KeyType::Source, u64(SourceKeyType::AESKekGeneration)); + const auto aes_key_gen = keys.GetKey(S128KeyType::Source, u64(SourceKeyType::AESKeyGeneration)); const auto master_00 = keys.GetKey(S128KeyType::Master); - const auto sd_kek = - GenerateKeyEncryptionKey(sd_kek_source, master_00, aes_kek_gen, aes_key_gen); + const auto sd_kek = GenerateKeyEncryptionKey(sd_kek_source, master_00, aes_kek_gen, aes_key_gen); keys.SetKey(S128KeyType::SDKek, sd_kek); if (!keys.HasKey(S128KeyType::SDSeed)) { @@ -442,16 +368,16 @@ Loader::ResultStatus DeriveSDKeys(std::array& sd_keys, KeyManager& ke } const auto sd_seed = keys.GetKey(S128KeyType::SDSeed); - if (!keys.HasKey(S256KeyType::SDKeySource, static_cast(SDKeyType::Save))) { + if (!keys.HasKey(S256KeyType::SDKeySource, u64(SDKeyType::Save))) { return Loader::ResultStatus::ErrorMissingSDSaveKeySource; } - if (!keys.HasKey(S256KeyType::SDKeySource, static_cast(SDKeyType::NCA))) { + if (!keys.HasKey(S256KeyType::SDKeySource, u64(SDKeyType::NCA))) { return Loader::ResultStatus::ErrorMissingSDNCAKeySource; } std::array sd_key_sources{ - keys.GetKey(S256KeyType::SDKeySource, static_cast(SDKeyType::Save)), - keys.GetKey(S256KeyType::SDKeySource, static_cast(SDKeyType::NCA)), + keys.GetKey(S256KeyType::SDKeySource, u64(SDKeyType::Save)), + keys.GetKey(S256KeyType::SDKeySource, u64(SDKeyType::NCA)), }; // Combine sources and seed @@ -464,15 +390,13 @@ Loader::ResultStatus DeriveSDKeys(std::array& sd_keys, KeyManager& ke AESCipher cipher(sd_kek, Mode::ECB); // The transform manipulates sd_keys as part of the Transcode, so the return/output is // unnecessary. This does not alter sd_keys_sources. - std::transform(sd_key_sources.begin(), sd_key_sources.end(), sd_keys.begin(), - sd_key_sources.begin(), [&cipher](const Key256& source, Key256& out) { - cipher.Transcode(source.data(), source.size(), out.data(), Op::Decrypt); - return source; ///< Return unaltered source to satisfy output requirement. - }); - - keys.SetKey(S256KeyType::SDKey, sd_keys[0], static_cast(SDKeyType::Save)); - keys.SetKey(S256KeyType::SDKey, sd_keys[1], static_cast(SDKeyType::NCA)); + std::transform(sd_key_sources.begin(), sd_key_sources.end(), sd_keys.begin(), sd_key_sources.begin(), [&cipher](const Key256& source, Key256& out) { + cipher.Transcode(source.data(), source.size(), out.data(), Op::Decrypt); + return source; ///< Return unaltered source to satisfy output requirement. + }); + keys.SetKey(S256KeyType::SDKey, sd_keys[0], u64(SDKeyType::Save)); + keys.SetKey(S256KeyType::SDKey, sd_keys[1], u64(SDKeyType::NCA)); return Loader::ResultStatus::Success; } @@ -503,11 +427,9 @@ std::vector GetTicketblob(const Common::FS::IOFile& ticket_save) { } template -static std::array operator^(const std::array& lhs, - const std::array& rhs) { +static std::array operator^(const std::array& lhs, const std::array& rhs) { std::array out; - std::transform(lhs.begin(), lhs.end(), rhs.begin(), out.begin(), - [](u8 lhs_elem, u8 rhs_elem) { return u8(lhs_elem ^ rhs_elem); }); + std::transform(lhs.begin(), lhs.end(), rhs.begin(), out.begin(), [](u8 lhs_elem, u8 rhs_elem) { return u8(lhs_elem ^ rhs_elem); }); return out; } @@ -661,8 +583,7 @@ void KeyManager::ReloadKeys() { static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) { if (base.size() < begin + length) return false; - return std::all_of(base.begin() + begin, base.begin() + begin + length, - [](u8 c) { return std::isxdigit(c); }); + return std::all_of(base.begin() + begin, base.begin() + begin + length, [](u8 c) { return std::isxdigit(c); }); } void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys) { @@ -673,101 +594,90 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti std::ifstream file; Common::FS::OpenFileStream(file, file_path, std::ios_base::in); - if (!file.is_open()) { - return; - } + if (file.is_open()) { + std::string line; + while (std::getline(file, line)) { + std::vector out; + std::stringstream stream(line); + std::string item; + while (std::getline(stream, item, '=')) { + out.push_back(std::move(item)); + } - std::string line; - while (std::getline(file, line)) { - std::vector out; - std::stringstream stream(line); - std::string item; - while (std::getline(stream, item, '=')) { - out.push_back(std::move(item)); - } + if (out.size() != 2) { + continue; + } - if (out.size() != 2) { - continue; - } + out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end()); + out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end()); - out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end()); - out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end()); + if (out[0].compare(0, 1, "#") == 0) { + continue; + } - if (out[0].compare(0, 1, "#") == 0) { - continue; - } - - if (is_title_keys) { - auto rights_id_raw = Common::HexStringToArray<16>(out[0]); - u128 rights_id{}; - std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size()); - Key128 key = Common::HexStringToArray<16>(out[1]); - s128_keys[{S128KeyType::Titlekey, rights_id[1], rights_id[0]}] = key; - } else { - out[0] = Common::ToLower(out[0]); - if (const auto iter128 = Find128ByName(out[0]); iter128 != s128_file_id.end()) { - const auto& index = iter128->second; - const Key128 key = Common::HexStringToArray<16>(out[1]); - s128_keys[{index.type, index.field1, index.field2}] = key; - } else if (const auto iter256 = Find256ByName(out[0]); iter256 != s256_file_id.end()) { - const auto& index = iter256->second; - const Key256 key = Common::HexStringToArray<32>(out[1]); - s256_keys[{index.type, index.field1, index.field2}] = key; - } else if (out[0].compare(0, 8, "keyblob_") == 0 && - out[0].compare(0, 9, "keyblob_k") != 0) { - if (!ValidCryptoRevisionString(out[0], 8, 2)) { - continue; - } - - const auto index = std::strtoul(out[0].substr(8, 2).c_str(), nullptr, 16); - keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); - } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { - if (!ValidCryptoRevisionString(out[0], 18, 2)) { - continue; - } - - const auto index = std::strtoul(out[0].substr(18, 2).c_str(), nullptr, 16); - encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); - } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { - eticket_extended_kek = Common::HexStringToArray<576>(out[1]); - } else if (out[0].compare(0, 19, "eticket_rsa_keypair") == 0) { - const auto key_data = Common::HexStringToArray<528>(out[1]); - std::memcpy(eticket_rsa_keypair.decryption_key.data(), key_data.data(), - eticket_rsa_keypair.decryption_key.size()); - std::memcpy(eticket_rsa_keypair.modulus.data(), key_data.data() + 0x100, - eticket_rsa_keypair.modulus.size()); - std::memcpy(eticket_rsa_keypair.exponent.data(), key_data.data() + 0x200, - eticket_rsa_keypair.exponent.size()); + if (is_title_keys) { + auto rights_id_raw = Common::HexStringToArray<16>(out[0]); + u128 rights_id{}; + std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size()); + Key128 key = Common::HexStringToArray<16>(out[1]); + s128_keys[{S128KeyType::Titlekey, rights_id[1], rights_id[0]}] = key; } else { - for (const auto& kv : KEYS_VARIABLE_LENGTH) { - if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) { + out[0] = Common::ToLower(out[0]); + if (const auto iter128 = Find128ByName(out[0]); iter128 != s128_file_id.end()) { + const auto& index = iter128->second; + const Key128 key = Common::HexStringToArray<16>(out[1]); + s128_keys[{index.type, index.field1, index.field2}] = key; + } else if (const auto iter256 = Find256ByName(out[0]); iter256 != s256_file_id.end()) { + const auto& index = iter256->second; + const Key256 key = Common::HexStringToArray<32>(out[1]); + s256_keys[{index.type, index.field1, index.field2}] = key; + } else if (out[0].compare(0, 8, "keyblob_") == 0 && out[0].compare(0, 9, "keyblob_k") != 0) { + if (!ValidCryptoRevisionString(out[0], 8, 2)) { continue; } - if (out[0].compare(0, kv.second.size(), kv.second) == 0) { - const auto index = - std::strtoul(out[0].substr(kv.second.size(), 2).c_str(), nullptr, 16); - const auto sub = kv.first.second; - if (sub == 0) { - s128_keys[{kv.first.first, index, 0}] = - Common::HexStringToArray<16>(out[1]); - } else { - s128_keys[{kv.first.first, kv.first.second, index}] = - Common::HexStringToArray<16>(out[1]); - } - break; + const auto index = std::strtoul(out[0].substr(8, 2).c_str(), nullptr, 16); + keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); + } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { + if (!ValidCryptoRevisionString(out[0], 18, 2)) { + continue; } - } - static constexpr std::array kak_names = { - "key_area_key_application_", "key_area_key_ocean_", "key_area_key_system_"}; - for (size_t j = 0; j < kak_names.size(); ++j) { - const auto& match = kak_names[j]; - if (out[0].compare(0, std::strlen(match), match) == 0) { - const auto index = - std::strtoul(out[0].substr(std::strlen(match), 2).c_str(), nullptr, 16); - s128_keys[{S128KeyType::KeyArea, index, j}] = - Common::HexStringToArray<16>(out[1]); + const auto index = std::strtoul(out[0].substr(18, 2).c_str(), nullptr, 16); + encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); + } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { + eticket_extended_kek = Common::HexStringToArray<576>(out[1]); + } else if (out[0].compare(0, 19, "eticket_rsa_keypair") == 0) { + const auto key_data = Common::HexStringToArray<528>(out[1]); + std::memcpy(eticket_rsa_keypair.decryption_key.data(), key_data.data(), eticket_rsa_keypair.decryption_key.size()); + std::memcpy(eticket_rsa_keypair.modulus.data(), key_data.data() + 0x100, eticket_rsa_keypair.modulus.size()); + std::memcpy(eticket_rsa_keypair.exponent.data(), key_data.data() + 0x200, eticket_rsa_keypair.exponent.size()); + } else { + for (const auto& kv : KEYS_VARIABLE_LENGTH) { + if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) { + continue; + } + if (out[0].compare(0, kv.second.size(), kv.second) == 0) { + const auto index = std::strtoul(out[0].substr(kv.second.size(), 2).c_str(), nullptr, 16); + const auto sub = kv.first.second; + if (sub == 0) { + s128_keys[{kv.first.first, index, 0}] = Common::HexStringToArray<16>(out[1]); + } else { + s128_keys[{kv.first.first, kv.first.second, index}] = Common::HexStringToArray<16>(out[1]); + } + break; + } + } + + constexpr std::array kak_names = { + "key_area_key_application_", "key_area_key_ocean_", "key_area_key_system_" + }; + for (size_t j = 0; j < kak_names.size(); ++j) { + const auto& match = kak_names[j]; + if (out[0].compare(0, std::strlen(match), match) == 0) { + const auto index = std::strtoul(out[0].substr(std::strlen(match), 2).c_str(), nullptr, 16); + s128_keys[{S128KeyType::KeyArea, index, j}] = Common::HexStringToArray<16>(out[1]); + } } } } @@ -789,13 +699,11 @@ bool KeyManager::BaseDeriveNecessary() const { } for (size_t i = 0; i < CURRENT_CRYPTO_REVISION; ++i) { - if (check_key_existence(S128KeyType::Master, i) || - check_key_existence(S128KeyType::KeyArea, i, - static_cast(KeyAreaKeyType::Application)) || - check_key_existence(S128KeyType::KeyArea, i, static_cast(KeyAreaKeyType::Ocean)) || - check_key_existence(S128KeyType::KeyArea, i, - static_cast(KeyAreaKeyType::System)) || - check_key_existence(S128KeyType::Titlekek, i)) + if (check_key_existence(S128KeyType::Master, i) + || check_key_existence(S128KeyType::KeyArea, i, u64(KeyAreaKeyType::Application)) + || check_key_existence(S128KeyType::KeyArea, i, u64(KeyAreaKeyType::Ocean)) + || check_key_existence(S128KeyType::KeyArea, i, u64(KeyAreaKeyType::System)) + || check_key_existence(S128KeyType::Titlekek, i)) return true; } @@ -828,10 +736,10 @@ Key256 KeyManager::GetBISKey(u8 partition_id) const { Key256 out{}; for (const auto& bis_type : {BISKeyType::Crypto, BISKeyType::Tweak}) { - if (HasKey(S128KeyType::BIS, partition_id, static_cast(bis_type))) { + if (HasKey(S128KeyType::BIS, partition_id, u64(bis_type))) { std::memcpy( - out.data() + sizeof(Key128) * static_cast(bis_type), - s128_keys.at({S128KeyType::BIS, partition_id, static_cast(bis_type)}).data(), + out.data() + sizeof(Key128) * u64(bis_type), + s128_keys.at({S128KeyType::BIS, partition_id, u64(bis_type)}).data(), sizeof(Key128)); } } @@ -840,8 +748,7 @@ Key256 KeyManager::GetBISKey(u8 partition_id) const { } template -void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname, - const std::array& key) { +void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname, const std::array& key) { const auto yuzu_keys_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::KeysDir); std::string filename = "title.keys_autogenerated"; @@ -889,11 +796,9 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { category = KeyCategory::Console; } - const auto iter2 = std::find_if( - s128_file_id.begin(), s128_file_id.end(), [&id, &field1, &field2](const auto& elem) { - return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == - std::tie(id, field1, field2); - }); + const auto iter2 = std::find_if(s128_file_id.begin(), s128_file_id.end(), [&id, &field1, &field2](const auto& elem) { + return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == std::tie(id, field1, field2); + }); if (iter2 != s128_file_id.end()) { WriteKeyToFile(category, iter2->first, key); } @@ -918,7 +823,7 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { WriteKeyToFile(category, fmt::format("keyblob_key_{:02X}", field1), key); } else if (id == S128KeyType::KeyblobMAC) { WriteKeyToFile(category, fmt::format("keyblob_mac_key_{:02X}", field1), key); - } else if (id == S128KeyType::Source && field1 == static_cast(SourceKeyType::Keyblob)) { + } else if (id == S128KeyType::Source && field1 == u64(SourceKeyType::Keyblob)) { WriteKeyToFile(category, fmt::format("keyblob_key_source_{:02X}", field2), key); } @@ -929,11 +834,10 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { if (s256_keys.find({id, field1, field2}) != s256_keys.end() || key == Key256{}) { return; } - const auto iter = std::find_if( - s256_file_id.begin(), s256_file_id.end(), [&id, &field1, &field2](const auto& elem) { - return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == - std::tie(id, field1, field2); - }); + const auto iter = std::find_if(s256_file_id.begin(), s256_file_id.end(), [&id, &field1, &field2](const auto& elem) { + return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == + std::tie(id, field1, field2); + }); if (iter != s256_file_id.end()) { WriteKeyToFile(KeyCategory::Standard, iter->first, key); } @@ -988,18 +892,17 @@ void KeyManager::DeriveBase() { } const auto has_bis = [this](u64 id) { - return HasKey(S128KeyType::BIS, id, static_cast(BISKeyType::Crypto)) && - HasKey(S128KeyType::BIS, id, static_cast(BISKeyType::Tweak)); + return HasKey(S128KeyType::BIS, id, u64(BISKeyType::Crypto)) && HasKey(S128KeyType::BIS, id, u64(BISKeyType::Tweak)); }; const auto copy_bis = [this](u64 id_from, u64 id_to) { SetKey(S128KeyType::BIS, - GetKey(S128KeyType::BIS, id_from, static_cast(BISKeyType::Crypto)), id_to, - static_cast(BISKeyType::Crypto)); + GetKey(S128KeyType::BIS, id_from, u64(BISKeyType::Crypto)), id_to, + u64(BISKeyType::Crypto)); SetKey(S128KeyType::BIS, - GetKey(S128KeyType::BIS, id_from, static_cast(BISKeyType::Tweak)), id_to, - static_cast(BISKeyType::Tweak)); + GetKey(S128KeyType::BIS, id_from, u64(BISKeyType::Tweak)), id_to, + u64(BISKeyType::Tweak)); }; if (has_bis(2) && !has_bis(3)) { @@ -1010,7 +913,7 @@ void KeyManager::DeriveBase() { std::bitset<32> revisions(0xFFFFFFFF); for (size_t i = 0; i < revisions.size(); ++i) { - if (!HasKey(S128KeyType::Source, static_cast(SourceKeyType::Keyblob), i) || + if (!HasKey(S128KeyType::Source, u64(SourceKeyType::Keyblob), i) || encrypted_keyblobs[i] == std::array{}) { revisions.reset(i); } @@ -1030,17 +933,17 @@ void KeyManager::DeriveBase() { // Derive keyblob key const auto key = DeriveKeyblobKey( - sbk, tsec, GetKey(S128KeyType::Source, static_cast(SourceKeyType::Keyblob), i)); + sbk, tsec, GetKey(S128KeyType::Source, u64(SourceKeyType::Keyblob), i)); SetKey(S128KeyType::Keyblob, key, i); // Derive keyblob MAC key - if (!HasKey(S128KeyType::Source, static_cast(SourceKeyType::KeyblobMAC))) { + if (!HasKey(S128KeyType::Source, u64(SourceKeyType::KeyblobMAC))) { continue; } const auto mac_key = DeriveKeyblobMACKey( - key, GetKey(S128KeyType::Source, static_cast(SourceKeyType::KeyblobMAC))); + key, GetKey(S128KeyType::Source, u64(SourceKeyType::KeyblobMAC))); SetKey(S128KeyType::KeyblobMAC, mac_key, i); Key128 cmac = CalculateCMAC(encrypted_keyblobs[i].data() + 0x10, 0xA0, mac_key); @@ -1060,10 +963,10 @@ void KeyManager::DeriveBase() { SetKey(S128KeyType::Package1, package1, i); // Derive master key - if (HasKey(S128KeyType::Source, static_cast(SourceKeyType::Master))) { + if (HasKey(S128KeyType::Source, u64(SourceKeyType::Master))) { SetKey(S128KeyType::Master, DeriveMasterKey(keyblobs[i], GetKey(S128KeyType::Source, - static_cast(SourceKeyType::Master))), + u64(SourceKeyType::Master))), i); } } @@ -1089,15 +992,15 @@ void KeyManager::DeriveBase() { } if (HasKey(S128KeyType::Master, 0) && - HasKey(S128KeyType::Source, static_cast(SourceKeyType::AESKeyGeneration)) && - HasKey(S128KeyType::Source, static_cast(SourceKeyType::AESKekGeneration)) && - HasKey(S128KeyType::Source, static_cast(SourceKeyType::HeaderKek)) && + HasKey(S128KeyType::Source, u64(SourceKeyType::AESKeyGeneration)) && + HasKey(S128KeyType::Source, u64(SourceKeyType::AESKekGeneration)) && + HasKey(S128KeyType::Source, u64(SourceKeyType::HeaderKek)) && HasKey(S256KeyType::HeaderSource)) { const auto header_kek = GenerateKeyEncryptionKey( - GetKey(S128KeyType::Source, static_cast(SourceKeyType::HeaderKek)), + GetKey(S128KeyType::Source, u64(SourceKeyType::HeaderKek)), GetKey(S128KeyType::Master, 0), - GetKey(S128KeyType::Source, static_cast(SourceKeyType::AESKekGeneration)), - GetKey(S128KeyType::Source, static_cast(SourceKeyType::AESKeyGeneration))); + GetKey(S128KeyType::Source, u64(SourceKeyType::AESKekGeneration)), + GetKey(S128KeyType::Source, u64(SourceKeyType::AESKeyGeneration))); SetKey(S128KeyType::HeaderKek, header_kek); AESCipher header_cipher(header_kek, Mode::ECB); @@ -1107,8 +1010,7 @@ void KeyManager::DeriveBase() { } } -void KeyManager::DeriveETicket(PartitionDataManager& data, - const FileSys::ContentProvider& provider) { +void KeyManager::DeriveETicket(PartitionDataManager& data, const FileSys::ContentProvider& provider) { // The emulator no longer derives the ETicket RSA Kek. // It is now required for the user to provide this key in their keys file. if (!HasKey(S128KeyType::ETicketRSAKek)) { @@ -1132,110 +1034,90 @@ void KeyManager::DeriveETicket(PartitionDataManager& data, } void KeyManager::PopulateTickets() { - if (ticket_databases_loaded) { - return; - } - ticket_databases_loaded = true; + if (!ticket_databases_loaded) { + ticket_databases_loaded = true; - std::vector tickets; + std::vector tickets; + const auto system_save_e1_path = + Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) / "system/save/80000000000000e1"; + if (Common::FS::Exists(system_save_e1_path)) { + const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read, Common::FS::FileType::BinaryFile}; + const auto blob1 = GetTicketblob(save_e1); + tickets.insert(tickets.end(), blob1.begin(), blob1.end()); + } - const auto system_save_e1_path = - Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) / "system/save/80000000000000e1"; - if (Common::FS::Exists(system_save_e1_path)) { - const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read, - Common::FS::FileType::BinaryFile}; - const auto blob1 = GetTicketblob(save_e1); - tickets.insert(tickets.end(), blob1.begin(), blob1.end()); - } + const auto system_save_e2_path = + Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) / "system/save/80000000000000e2"; + if (Common::FS::Exists(system_save_e2_path)) { + const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read, Common::FS::FileType::BinaryFile}; + const auto blob2 = GetTicketblob(save_e2); + tickets.insert(tickets.end(), blob2.begin(), blob2.end()); + } - const auto system_save_e2_path = - Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) / "system/save/80000000000000e2"; - if (Common::FS::Exists(system_save_e2_path)) { - const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read, - Common::FS::FileType::BinaryFile}; - const auto blob2 = GetTicketblob(save_e2); - tickets.insert(tickets.end(), blob2.begin(), blob2.end()); - } - - for (const auto& ticket : tickets) { - AddTicket(ticket); + for (const auto& ticket : tickets) { + AddTicket(ticket); + } } } void KeyManager::SynthesizeTickets() { for (const auto& key : s128_keys) { - if (key.first.type != S128KeyType::Titlekey) { - continue; + if (key.first.type == S128KeyType::Titlekey) { + u128 rights_id{key.first.field1, key.first.field2}; + Key128 rights_id_2; + std::memcpy(rights_id_2.data(), rights_id.data(), rights_id_2.size()); + const auto ticket = Ticket::SynthesizeCommon(key.second, rights_id_2); + common_tickets.insert_or_assign(rights_id, ticket); } - u128 rights_id{key.first.field1, key.first.field2}; - Key128 rights_id_2; - std::memcpy(rights_id_2.data(), rights_id.data(), rights_id_2.size()); - const auto ticket = Ticket::SynthesizeCommon(key.second, rights_id_2); - common_tickets.insert_or_assign(rights_id, ticket); } } void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) { - if (key == Key128{}) { - return; + if (key != Key128{}) { + SetKey(id, key, field1, field2); } - SetKey(id, key, field1, field2); } void KeyManager::SetKeyWrapped(S256KeyType id, Key256 key, u64 field1, u64 field2) { - if (key == Key256{}) { - return; + if (key != Key256{}) { + SetKey(id, key, field1, field2); } - - SetKey(id, key, field1, field2); } void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) { - if (!BaseDeriveNecessary()) { - return; - } - - if (!data.HasBoot0()) { - return; - } - - for (size_t i = 0; i < encrypted_keyblobs.size(); ++i) { - if (encrypted_keyblobs[i] != std::array{}) { - continue; + if (BaseDeriveNecessary() && data.HasBoot0()) { + for (size_t i = 0; i < encrypted_keyblobs.size(); ++i) { + if (encrypted_keyblobs[i] == std::array{}) { + encrypted_keyblobs[i] = data.GetEncryptedKeyblob(i); + WriteKeyToFile<0xB0>(KeyCategory::Console, fmt::format("encrypted_keyblob_{:02X}", i), encrypted_keyblobs[i]); + } } - encrypted_keyblobs[i] = data.GetEncryptedKeyblob(i); - WriteKeyToFile<0xB0>(KeyCategory::Console, fmt::format("encrypted_keyblob_{:02X}", i), - encrypted_keyblobs[i]); - } - if (data.HasFuses()) { - SetKeyWrapped(S128KeyType::SecureBoot, data.GetSecureBootKey()); - } + if (data.HasFuses()) { + SetKeyWrapped(S128KeyType::SecureBoot, data.GetSecureBootKey()); + } + DeriveBase(); - DeriveBase(); + Key128 latest_master{}; + for (s8 i = 0x1F; i >= 0; --i) { + if (GetKey(S128KeyType::Master, static_cast(i)) != Key128{}) { + latest_master = GetKey(S128KeyType::Master, static_cast(i)); + break; + } + } + DeriveBase(); - Key128 latest_master{}; - for (s8 i = 0x1F; i >= 0; --i) { - if (GetKey(S128KeyType::Master, static_cast(i)) != Key128{}) { - latest_master = GetKey(S128KeyType::Master, static_cast(i)); - break; + if (data.HasPackage2()) { + std::array package2_keys{}; + for (size_t i = 0; i < package2_keys.size(); ++i) { + if (HasKey(S128KeyType::Package2, i)) { + package2_keys[i] = GetKey(S128KeyType::Package2, i); + } + } + data.DecryptPackage2(package2_keys, Package2Type::NormalMain); + DeriveBase(); } } - - DeriveBase(); - - if (!data.HasPackage2()) - return; - - std::array package2_keys{}; - for (size_t i = 0; i < package2_keys.size(); ++i) { - if (HasKey(S128KeyType::Package2, i)) { - package2_keys[i] = GetKey(S128KeyType::Package2, i); - } - } - data.DecryptPackage2(package2_keys, Package2Type::NormalMain); - - DeriveBase(); } const std::map& KeyManager::GetCommonTickets() const { @@ -1263,8 +1145,8 @@ bool KeyManager::AddTicket(const Ticket& ticket) { if (HasKey(S128KeyType::Titlekey, rights_id[1], rights_id[0])) { LOG_DEBUG(Crypto, - "Skipping parsing title key from ticket for known rights ID {:016X}{:016X}.", - rights_id[1], rights_id[0]); + "Skipping parsing title key from ticket for known rights ID {:016X}{:016X}.", + rights_id[1], rights_id[0]); return true; } diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 7ef8375690..4a892a27ed 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -336,8 +336,7 @@ Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, K Key128 DeriveKeyblobKey(const Key128& sbk, const Key128& tsec, Key128 source); Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source); Key128 DeriveMasterKey(const std::array& keyblob, const Key128& master_source); -std::array DecryptKeyblob(const std::array& encrypted_keyblob, - const Key128& key); +std::array DecryptKeyblob(const std::array& encrypted_keyblob, const Key128& key); std::optional DeriveSDSeed(); Loader::ResultStatus DeriveSDKeys(std::array& sd_keys, KeyManager& keys); diff --git a/src/core/crypto/ncz_decompressor.cpp b/src/core/crypto/ncz_decompressor.cpp new file mode 100644 index 0000000000..5f56e55375 --- /dev/null +++ b/src/core/crypto/ncz_decompressor.cpp @@ -0,0 +1,430 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common/common_types.h" +#include "common/logging.h" +#include "common/zstd_compression.h" +#include "core/crypto/aes_util.h" +#include "core/crypto/key_manager.h" +#include "core/crypto/ncz_decompressor.h" +#include "core/file_sys/vfs/vfs.h" + +namespace Core::Crypto { + +namespace { + +constexpr u64 HeaderSize = 0x4000; +constexpr std::size_t SectionWire = 0x40; +constexpr std::size_t BlockFixed = 0x18; +constexpr std::size_t Chunk = 0x100000; +// Upper bound on NCZ section entries (64 bytes each, so the table is at most 4 MiB). A base NCA +// has a handful of sections, but nsz stores an update's patch (BKTR) data one entry per +// subsection, which is thousands of them. +constexpr u64 MaxSectionCount = 0x10000; +constexpr std::array SectionsMagic{'N', 'C', 'Z', 'S', 'E', 'C', 'T', 'N'}; +constexpr std::array BlockMagic{'N', 'C', 'Z', 'B', 'L', 'O', 'C', 'K'}; + +struct Section { + s64 offset, size; + u64 crypto_type; + Key128 key, counter; + bool Ctr() const { return crypto_type == 3 || crypto_type == 4; } +}; + +struct BlockHeader { + u32 exponent; + u64 decompressed_size; + std::vector sizes; +}; + +u64 Le(const u8* p, std::size_t n) { + u64 v = 0; + for (std::size_t i = n; i-- > 0;) v = (v << 8) | p[i]; + return v; +} + +// Mirrors nsz AESCTR.seek(): upper 8 bytes = stored counter, lower 8 = absolute NCA offset / 16. +void SetIv(Key128& iv, const Key128& base, u64 off) { + iv = base; + u64 blk = off >> 4; + for (std::size_t i = 0; i < 8; ++i) { iv[15 - i] = static_cast(blk & 0xFF); blk >>= 8; } +} + +// Parse header + section table + optional block header. Returns payload_start. +std::optional, std::optional>> ParseHeader( + const FileSys::VirtualFile& f, u64& payload_start) { + const u64 fsize = f->GetSize(); + if (fsize < HeaderSize + 16) return std::nullopt; + + const auto head = f->ReadBytes(16, HeaderSize); + if (head.size() != 16 || !std::equal(head.begin(), head.begin() + 8, SectionsMagic.begin())) + return std::nullopt; + const u64 count = Le(head.data() + 8, 8); + if (count == 0 || count > MaxSectionCount || count * SectionWire > fsize - HeaderSize - 16) + return std::nullopt; + + const auto table = f->ReadBytes(count * SectionWire, HeaderSize + 16); + if (table.size() != count * SectionWire) return std::nullopt; + + std::vector
sections; + sections.reserve(count + 1); + for (u64 i = 0; i < count; ++i) { + const u8* e = table.data() + i * SectionWire; + Section s{}; + s.offset = static_cast(Le(e, 8)); + s.size = static_cast(Le(e + 8, 8)); + s.crypto_type = Le(e + 16, 8); + std::memcpy(s.key.data(), e + 32, 16); + std::memcpy(s.counter.data(), e + 48, 16); + sections.push_back(s); + } + + const u64 block_start = HeaderSize + 16 + count * SectionWire; + const auto fixed = f->ReadBytes(BlockFixed, block_start); + std::optional bh; + if (fixed.size() == BlockFixed && + std::equal(fixed.begin(), fixed.begin() + 8, BlockMagic.begin())) { + BlockHeader b{}; + b.exponent = fixed[11]; + const s32 n = static_cast(Le(fixed.data() + 12, 4)); + if (n <= 0) return std::nullopt; + b.decompressed_size = Le(fixed.data() + 16, 8); + const auto sizes = f->ReadBytes(static_cast(n) * 4, block_start + BlockFixed); + if (sizes.size() != static_cast(n) * 4) return std::nullopt; + b.sizes.resize(n); + for (s32 i = 0; i < n; ++i) b.sizes[i] = static_cast(Le(sizes.data() + i * 4, 4)); + payload_start = block_start + BlockFixed + static_cast(n) * 4; + bh = std::move(b); + } else { + payload_start = block_start; + } + + if (!sections.empty() && sections[0].offset > static_cast(HeaderSize)) { + Section fake{}; + fake.offset = static_cast(HeaderSize); + fake.size = sections[0].offset - static_cast(HeaderSize); + sections.insert(sections.begin(), fake); + } + return std::make_pair(std::move(sections), std::move(bh)); +} + +// Sequential reader over independently-compressed blocks, one block cached (like nsz_rs). +class BlockReader { +public: + BlockReader(FileSys::VirtualFile f, const BlockHeader& bh, u64 payload_start) + : source{std::move(f)}, block_size{1ULL << bh.exponent}, sizes{bh.sizes}, + decompressed_size{bh.decompressed_size} { + if (bh.exponent < 14 || bh.exponent > 32 || sizes.empty()) + throw std::runtime_error("bad NCZBLOCK header"); + u64 off = payload_start; + offsets.reserve(sizes.size()); + for (u32 s : sizes) { offsets.push_back(off); off += s; } + } + + // Fills `out` from `position`; returns bytes written. + std::size_t ReadAt(u64 position, std::span out) { + std::size_t done = 0; + while (done < out.size() && position < decompressed_size) { + const u64 id = position / block_size; + if (id >= sizes.size()) break; + if (cache_id != id && !Load(id)) break; + const u64 in = position - id * block_size; + const std::size_t n = static_cast( + std::min(out.size() - done, cache.size() - in)); + std::memcpy(out.data() + done, cache.data() + in, n); + done += n; + position += n; + } + return done; + } + +private: + bool Load(u64 id) { + u64 want = block_size; + if (id + 1 == sizes.size()) { + const u64 rem = decompressed_size % block_size; + if (rem > 0) want = rem; // avoid the 0-size bug from the original nsz + } + try { cache.resize(static_cast(want)); } + catch (const std::bad_alloc&) { return false; } + const u32 csize = sizes[id]; + if (csize < want) { + const auto comp = source->ReadBytes(csize, offsets[id]); + if (comp.size() != csize || + Common::Compression::DecompressDataZSTD(comp, cache) != want) return false; + } else if (source->Read(cache.data(), cache.size(), offsets[id]) != cache.size()) { + return false; + } + cache_id = id; + return true; + } + + FileSys::VirtualFile source; + u64 block_size; + std::vector offsets; + std::vector sizes; + u64 decompressed_size; + u64 cache_id = ~0ULL; + std::vector cache; +}; + +class BlockNczFile final : public FileSys::VfsFile { +public: + BlockNczFile(std::string name_, std::vector hdr_, std::vector
sections_, + std::unique_ptr body_) + : name{std::move(name_)}, hdr{std::move(hdr_)}, sections{std::move(sections_)}, + body{std::move(body_)} { + nca_size = HeaderSize; + for (const auto& s : sections) + nca_size = std::max(nca_size, static_cast(s.offset + s.size)); + ciphers.reserve(sections.size()); + for (const auto& s : sections) + ciphers.push_back(s.Ctr() ? std::make_unique>(s.key, Mode::CTR) + : nullptr); + } + + std::string GetName() const override { return name; } + std::size_t GetSize() const override { return static_cast(nca_size); } + bool Resize(std::size_t) override { return false; } + FileSys::VirtualDir GetContainingDirectory() const override { return nullptr; } + bool IsWritable() const override { return false; } + bool IsReadable() const override { return true; } + + std::size_t Read(u8* data, std::size_t len, std::size_t off) const override { + if (off >= nca_size) return 0; + len = static_cast(std::min(len, nca_size - off)); + const u64 end = off + len; + u64 pos = off; + if (pos < HeaderSize) { + const u64 n = std::min(end, HeaderSize) - pos; + std::memcpy(data, hdr.data() + pos, static_cast(n)); + pos += n; + } + std::scoped_lock lock{mutex}; + for (std::size_t i = 0; i < sections.size() && pos < end; ++i) { + const auto& s = sections[i]; + const u64 begin = std::max(static_cast(s.offset), HeaderSize); + const u64 stop = std::max(static_cast(s.offset + s.size), begin); + if (stop <= pos) continue; + if (pos < begin) { + const u64 g = std::min(end, begin); + std::memset(data + (pos - off), 0, static_cast(g - pos)); + pos = g; + if (pos >= end) break; + } + const u64 pe = std::min(end, stop); + const std::span piece(data + (pos - off), static_cast(pe - pos)); + if (body->ReadAt(pos - HeaderSize, piece) != piece.size()) + return static_cast(pos - off); + if (ciphers[i]) { + Key128 iv{}; + SetIv(iv, s.counter, pos); + ciphers[i]->SetIV(iv); + ciphers[i]->Transcode(piece.data(), piece.size(), piece.data(), Op::Encrypt); + } + pos = pe; + } + return len; + } + + std::size_t Write(const u8*, std::size_t, std::size_t) override { return 0; } + bool Rename(std::string_view) override { return false; } + +private: + const std::string name; + const std::vector hdr; + const std::vector
sections; + const std::unique_ptr body; + std::vector>> ciphers; + mutable std::mutex mutex; + u64 nca_size{}; +}; + +// Solid NCZ: one zstd stream over the whole body. Decoded incrementally into memory, up to the +// requested offset, so sequential install reads are O(n) instead of O(n^2). +class SolidNczFile final : public FileSys::VfsFile { +public: + SolidNczFile(std::string name_, FileSys::VirtualFile source_, + std::vector
sections_, u64 payload_start_) + : name{std::move(name_)}, source{std::move(source_)}, sections{std::move(sections_)}, + payload_start{payload_start_} { + const auto raw_header = source->ReadBytes(HeaderSize, 0); + data.assign(raw_header.begin(), raw_header.end()); + frontier = HeaderSize; + } + + ~SolidNczFile() override { + if (stream != nullptr) ZSTD_freeDStream(stream); + } + + std::string GetName() const override { return name; } + std::size_t GetSize() const override { + u64 size = HeaderSize; + for (const auto& s : sections) + size = std::max(size, static_cast(s.offset + s.size)); + return static_cast(size); + } + bool Resize(std::size_t) override { return false; } + FileSys::VirtualDir GetContainingDirectory() const override { return nullptr; } + bool IsWritable() const override { return false; } + bool IsReadable() const override { return true; } + + std::size_t Read(u8* out, std::size_t len, std::size_t off) const override { + std::scoped_lock lock{mutex}; + const u64 nca_size = GetSize(); + if (off >= nca_size) return 0; + len = static_cast(std::min(len, nca_size - off)); + const u64 needed = off + len; + if (needed > frontier && !DecodeTo(needed)) return 0; + std::memcpy(out, data.data() + off, len); + return len; + } + + std::size_t Write(const u8*, std::size_t, std::size_t) override { return 0; } + bool Rename(std::string_view) override { return false; } + +private: + bool DecodeTo(u64 needed) const { + if (stream == nullptr) { + stream = ZSTD_createDStream(); + if (stream == nullptr) return false; + ZSTD_DCtx_setParameter(stream, ZSTD_d_windowLogMax, 31); + read_pos = payload_start; + } + + const u64 file_size = source->GetSize(); + std::vector out_buf(Chunk); + + std::size_t index = 0; + for (; index < sections.size(); ++index) { + const auto& s = sections[index]; + const u64 begin = std::max(static_cast(s.offset), HeaderSize); + const u64 end = std::max(static_cast(s.offset) + static_cast(s.size), + begin); + if (end > frontier) break; + } + + for (; index < sections.size() && frontier < needed; ++index) { + const auto& s = sections[index]; + const u64 begin = std::max(static_cast(s.offset), HeaderSize); + const u64 end = std::max(static_cast(s.offset) + static_cast(s.size), + begin); + if (end <= frontier) continue; + u64 pos = std::max(begin, frontier); + + std::optional> cipher; + if (s.Ctr()) cipher.emplace(s.key, Mode::CTR); + + while (pos < end && frontier < needed) { + const u64 want = std::min(end - pos, out_buf.size()); + std::size_t produced = 0; + while (produced < want) { + if (in.pos >= in.size) { + const u64 remaining = file_size - read_pos; + const std::size_t chunk = static_cast( + std::min(Chunk, remaining)); + if (chunk == 0) { LOG_ERROR(Crypto, "Solid NCZ: truncated"); return false; } + const auto bytes = source->ReadBytes(chunk, read_pos); + if (bytes.empty()) { LOG_ERROR(Crypto, "Solid NCZ: truncated"); return false; } + read_pos += bytes.size(); + in_buf.assign(bytes.begin(), bytes.end()); + in.src = in_buf.data(); + in.size = in_buf.size(); + in.pos = 0; + } + ZSTD_outBuffer outb{out_buf.data() + produced, want - produced, 0}; + const std::size_t res = ZSTD_decompressStream(stream, &outb, &in); + if (ZSTD_isError(res)) { + LOG_ERROR(Crypto, "Solid NCZ: zstd error"); + return false; + } + produced += outb.pos; + } + if (cipher) { + Key128 iv{}; + SetIv(iv, s.counter, pos); + cipher->SetIV(iv); + cipher->Transcode(out_buf.data(), produced, out_buf.data(), Op::Encrypt); + } + data.insert(data.end(), out_buf.begin(), out_buf.begin() + produced); + pos += produced; + frontier = pos; + } + } + return frontier >= needed; + } + + const std::string name; + const FileSys::VirtualFile source; + const std::vector
sections; + const u64 payload_start; + mutable std::mutex mutex; + mutable std::vector data; + mutable ZSTD_DStream* stream = nullptr; + mutable u64 read_pos = 0; + mutable u64 frontier = 0; + mutable std::vector in_buf; + mutable ZSTD_inBuffer in{nullptr, 0, 0}; +}; + +// Shared opener: block path always; solid only when allow_solid is true. +FileSys::VirtualFile OpenNcz(const FileSys::VirtualFile& f, std::string_view name, + bool allow_solid) { + if (!f) return nullptr; + u64 ps = 0; + auto parsed = ParseHeader(f, ps); + if (!parsed) return nullptr; + auto& [sections, block] = *parsed; + + if (block) { + auto hdr = f->ReadBytes(HeaderSize, 0); + if (hdr.size() != HeaderSize) return nullptr; + std::unique_ptr body; + try { body = std::make_unique(f, *block, ps); } + catch (const std::exception& e) { + LOG_ERROR(Crypto, "Failed to open NCZBLOCK: {}", e.what()); + return nullptr; + } + return std::make_shared(std::string{name}, std::move(hdr), + std::move(sections), std::move(body)); + } + + if (allow_solid) { + return std::make_shared(std::string{name}, f, std::move(sections), ps); + } + + LOG_DEBUG(Crypto, "Solid NCZ {} skipped (only NCZBLOCK is supported for play)", name); + return nullptr; +} + +} // Anonymous namespace + +bool IsBlockCompressedNCZ(const FileSys::VirtualFile& f) { + if (!f) return false; + u64 ps = 0; + const auto p = ParseHeader(f, ps); + return p && p->second.has_value(); +} + +FileSys::VirtualFile DecompressNCZ(const FileSys::VirtualFile& f, std::string_view name) { + return OpenNcz(f, name, /*allow_solid=*/false); +} + +FileSys::VirtualFile DecompressNCZForInstall(const FileSys::VirtualFile& f, + std::string_view name) { + return OpenNcz(f, name, /*allow_solid=*/true); +} + +} // namespace Core::Crypto \ No newline at end of file diff --git a/src/core/crypto/ncz_decompressor.h b/src/core/crypto/ncz_decompressor.h new file mode 100644 index 0000000000..9557f8c517 --- /dev/null +++ b/src/core/crypto/ncz_decompressor.h @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "core/file_sys/vfs/vfs_types.h" + +namespace Core::Crypto { + +/** + * Opens an NCZBLOCK-compressed NCA (nsz -B / .xcz block format) as a VirtualFile backed by + * on-demand block decoding. Nothing is written to disk; each read decodes only the blocks it + * covers and re-encrypts CTR sections. Solid NCZ is not supported. + * + * @param ncz_file The raw NCZ payload (e.g. ".ncz" entry from an NSZ/XCZ PFS0). + * @param output_name Name to give the returned VirtualFile (e.g. ".nca"). + * + * @return a VirtualFile for the NCA, or nullptr if the NCZ is malformed or not NCZBLOCK. + */ +[[nodiscard]] FileSys::VirtualFile DecompressNCZ(const FileSys::VirtualFile& ncz_file, + std::string_view output_name); + +/** + * Returns true if the given file is an NCZ payload in block-compressed (NCZBLOCK) form, + * i.e. one that DecompressNCZ() can open. Solid NCZ returns false. + */ +[[nodiscard]] bool IsBlockCompressedNCZ(const FileSys::VirtualFile& ncz_file); + +// Install to NAND: same as DecompressNCZ, but solid NCZ is decoded on demand too. +[[nodiscard]] FileSys::VirtualFile DecompressNCZForInstall( + const FileSys::VirtualFile& ncz_file, std::string_view output_name); + +} // namespace Core::Crypto \ No newline at end of file diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp index 3f089b7f80..a9c1dcfb47 100644 --- a/src/core/debugger/debugger.cpp +++ b/src/core/debugger/debugger.cpp @@ -6,13 +6,13 @@ #include #include +#include + #include #include - -#if BOOST_VERSION > 108400 && (!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 - #ifdef USE_BOOST_v1 #include #else @@ -81,7 +81,10 @@ namespace Core { class DebuggerImpl : public DebuggerBackend { public: - explicit DebuggerImpl(Core::System& system_, u16 port) : system{system_} { + explicit DebuggerImpl(Core::System& system_, u16 port) + : system{system_} + , debug_process{system_.Kernel()} + { InitializeServer(port); } @@ -121,7 +124,7 @@ public: } void SetActiveThread(Kernel::KThread* thread) override { - state->active_thread = thread; + state->active_thread = {system.Kernel(), thread}; } Kernel::KThread* GetActiveThread() override { @@ -168,14 +171,7 @@ private: frontend = std::make_unique(*this, system, debug_process.GetPointerUnsafe()); // Set the new state. This will tear down any existing state. - state = ConnectionState{ - .client_socket{std::move(peer)}, - .signal_pipe{io_context}, - .info{}, - .active_thread{}, - .client_data{}, - .pipe_data{}, - }; + state.emplace(std::move(peer), io_context, system.Kernel()); // Set up the client signals for new data. AsyncReceiveInto(state->signal_pipe, state->pipe_data, [&](auto d) { PipeData(d); }); @@ -204,7 +200,7 @@ private: PauseEmulation(); // Notify the client. - state->active_thread = state->info.thread; + state->active_thread = {system.Kernel(), state->info.thread}; UpdateActiveThread(); if (state->info.type == SignalType::Watchpoint) { @@ -247,17 +243,19 @@ private: case DebuggerAction::Continue: MarkResumed([&] { ResumeEmulation(); }); break; - case DebuggerAction::StepThreadUnlocked: - MarkResumed([&] { - state->active_thread->SetStepState(Kernel::StepState::StepPending); - state->active_thread->Resume(Kernel::SuspendType::Debug); - ResumeEmulation(state->active_thread.GetPointerUnsafe()); + case DebuggerAction::ContinueThreads: { + auto* gdb = static_cast(frontend.get()); + MarkResumed([this, threads = std::move(gdb->resume_threads)] { + ResumeThreads(threads); }); break; - case DebuggerAction::StepThreadLocked: { - MarkResumed([&] { + } + case DebuggerAction::StepThread: { + auto* gdb = static_cast(frontend.get()); + MarkResumed([this, threads = std::move(gdb->resume_threads)] { state->active_thread->SetStepState(Kernel::StepState::StepPending); - state->active_thread->Resume(Kernel::SuspendType::Debug); + state->active_thread->Resume(system.Kernel(), Kernel::SuspendType::Debug); + ResumeThreads(threads, state->active_thread.GetPointerUnsafe()); }); break; } @@ -279,7 +277,7 @@ private: // Put all threads to sleep on next scheduler round. for (auto& thread : ThreadList()) { - thread.RequestSuspend(Kernel::SuspendType::Debug); + thread.RequestSuspend(system.Kernel(), Kernel::SuspendType::Debug); } } @@ -294,7 +292,23 @@ private: } thread.SetStepState(Kernel::StepState::NotStepping); - thread.Resume(Kernel::SuspendType::Debug); + thread.Resume(system.Kernel(), Kernel::SuspendType::Debug); + } + } + + void ResumeThreads(const std::vector& threads, + Kernel::KThread* except = nullptr) { + Kernel::KScopedLightLock ll{debug_process->GetListLock()}; + Kernel::KScopedSchedulerLock sl{system.Kernel()}; + + // Wake up only the specified threads. + for (auto* thread : threads) { + if (!thread || thread == except) { + continue; + } + + thread->SetStepState(Kernel::StepState::NotStepping); + thread->Resume(system.Kernel(), Kernel::SuspendType::Debug); } } @@ -314,7 +328,7 @@ private: return; } } - state->active_thread = std::addressof(threads.front()); + state->active_thread = {system.Kernel(), std::addressof(threads.front())}; } private: @@ -336,13 +350,20 @@ private: std::mutex connection_lock; struct ConnectionState { - boost::asio::ip::tcp::socket client_socket; #ifdef USE_BOOST_v1 - boost::process::v1::async_pipe signal_pipe; + using async_pipe = boost::process::v1::async_pipe; #else - boost::process::async_pipe signal_pipe; + using async_pipe = boost::process::async_pipe; #endif + ConnectionState(boost::asio::ip::tcp::socket&& client_socket_, async_pipe signal_pipe_, Kernel::KernelCore& kernel) + : client_socket{std::move(client_socket_)} + , signal_pipe{std::move(signal_pipe_)} + , active_thread{kernel, nullptr} + {} + + boost::asio::ip::tcp::socket client_socket; + async_pipe signal_pipe; SignalInfo info; Kernel::KScopedAutoObject active_thread; std::array client_data; diff --git a/src/core/debugger/debugger_interface.h b/src/core/debugger/debugger_interface.h index 4e18749249..d48dd892da 100644 --- a/src/core/debugger/debugger_interface.h +++ b/src/core/debugger/debugger_interface.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project @@ -20,11 +20,11 @@ struct DebugWatchpoint; namespace Core { enum class DebuggerAction { - Interrupt, ///< Stop emulation as soon as possible. - Continue, ///< Resume emulation. - StepThreadLocked, ///< Step the currently-active thread without resuming others. - StepThreadUnlocked, ///< Step the currently-active thread and resume others. - ShutdownEmulation, ///< Shut down the emulator. + Interrupt, ///< Stop emulation as soon as possible. + Continue, ///< Resume emulation. + ContinueThreads, ///< Resume only specific threads (listed in frontend). + StepThread, ///< Step the active thread and resume only threads listed in frontend. + ShutdownEmulation, ///< Shut down the emulator. }; class DebuggerBackend { diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index a5f49f6ff1..44ddf11c39 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp @@ -4,15 +4,15 @@ // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include +#include #include #include #include #include #include -#include - #include "common/hex_util.h" #include "common/logging.h" #include "common/scope_exit.h" @@ -273,10 +273,12 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vectorInsertWatchpoint(addr, size, Kernel::DebugWatchpointType::Write); + success = debug_process->InsertWatchpoint(system.Kernel(), addr, size, Kernel::DebugWatchpointType::Write); break; case BreakpointType::ReadWatch: - success = debug_process->InsertWatchpoint(addr, size, Kernel::DebugWatchpointType::Read); + success = debug_process->InsertWatchpoint(system.Kernel(), addr, size, Kernel::DebugWatchpointType::Read); break; case BreakpointType::AccessWatch: - success = debug_process->InsertWatchpoint(addr, size, Kernel::DebugWatchpointType::ReadOrWrite); + success = debug_process->InsertWatchpoint(system.Kernel(), addr, size, Kernel::DebugWatchpointType::ReadOrWrite); break; case BreakpointType::Hardware: default: @@ -366,13 +368,13 @@ void GDBStub::HandleBreakpointRemove(std::string_view sv) { break; } case BreakpointType::WriteWatch: - success = debug_process->RemoveWatchpoint(addr, size, Kernel::DebugWatchpointType::Write); + success = debug_process->RemoveWatchpoint(system.Kernel(), addr, size, Kernel::DebugWatchpointType::Write); break; case BreakpointType::ReadWatch: - success = debug_process->RemoveWatchpoint(addr, size, Kernel::DebugWatchpointType::Read); + success = debug_process->RemoveWatchpoint(system.Kernel(), addr, size, Kernel::DebugWatchpointType::Read); break; case BreakpointType::AccessWatch: - success = debug_process->RemoveWatchpoint(addr, size, Kernel::DebugWatchpointType::ReadOrWrite); + success = debug_process->RemoveWatchpoint(system.Kernel(), addr, size, Kernel::DebugWatchpointType::ReadOrWrite); break; case BreakpointType::Hardware: default: @@ -467,29 +469,142 @@ void GDBStub::HandleQuery(std::string_view sv) { } void GDBStub::HandleVCont(std::string_view sv, std::vector& actions) { - // Continuing and stepping are supported (signal is ignored, but required for GDB to use vCont) + // Continuing and stepping are supported (signal is ignored, but required for GDB to use vCont). + // Reference: https://sourceware.org/gdb/current/onlinedocs/gdb.html/Packets.html#vCont-packet if (sv == "?") { SendReply("vCont;c;C;s;S"); - } else { - Kernel::KThread* stepped_thread = nullptr; - bool lock_execution = true; - std::vector entries; - boost::split(entries, sv.substr(1), boost::is_any_of(";")); - for (auto const& thread_action : entries) { - std::vector parts; - boost::split(parts, thread_action, boost::is_any_of(":")); - if (parts.size() == 1 && (parts[0] == "c" || parts[0].starts_with("C"))) - lock_execution = false; - if (parts.size() == 2 && (parts[0] == "s" || parts[0].starts_with("S"))) - stepped_thread = GetThreadByID(strtoll(parts[1].data(), nullptr, 16)); + return; + } + if (sv.empty() || sv.front() != ';') { + SendReply(GDB_STUB_REPLY_ERR); + return; + } + + enum class VContAction { + Continue, + Step, + }; + struct VContDirective { + VContAction action; + Kernel::KThread* thread{}; + bool all_threads{}; + + bool Matches(Kernel::KThread* candidate) const { + return all_threads || thread == candidate; + } + }; + + const auto is_hex_byte = [](std::string_view value) { + return value.size() == 2 && std::isxdigit(static_cast(value[0])) && + std::isxdigit(static_cast(value[1])); + }; + const auto is_hex_string = [](std::string_view value) { + return std::ranges::all_of(value, [](auto const c) { return std::isxdigit(int(c)); }); + }; + + resume_threads.clear(); + + std::vector directives; + std::string_view remaining = sv.substr(1); + while (!remaining.empty()) { + const auto entry_end = remaining.find(';'); + const auto entry = remaining.substr(0, entry_end); + remaining = entry_end == std::string_view::npos ? std::string_view{} : remaining.substr(entry_end + 1); + + if (entry.empty()) { + SendReply(GDB_STUB_REPLY_ERR); + return; } - if (stepped_thread) { - backend.SetActiveThread(stepped_thread); - actions.push_back(lock_execution ? DebuggerAction::StepThreadLocked : DebuggerAction::StepThreadUnlocked); - } else { - actions.push_back(DebuggerAction::Continue); + const auto thread_sep = entry.find(':'); + const auto action_token = entry.substr(0, thread_sep); + const auto thread_token = thread_sep == std::string_view::npos ? std::string_view{} : entry.substr(thread_sep + 1); + + if (action_token.empty()) { + SendReply(GDB_STUB_REPLY_ERR); + return; } + + VContDirective directive; + if (action_token == "c") { + directive.action = VContAction::Continue; + } else if (action_token.front() == 'C' && is_hex_byte(action_token.substr(1))) { + directive.action = VContAction::Continue; + } else if (action_token == "s") { + directive.action = VContAction::Step; + } else if (action_token.front() == 'S' && is_hex_byte(action_token.substr(1))) { + directive.action = VContAction::Step; + } else { + SendReply(GDB_STUB_REPLY_ERR); + return; + } + + if (thread_sep == std::string_view::npos || thread_token == "-1") { + directive.all_threads = true; + } else if (thread_token == "0") { + // A thread-id of 0 selects an arbitrary thread. While stopped, use the + // current active thread as that arbitrary choice. + directive.thread = backend.GetActiveThread(); + } else if (thread_token.starts_with('p')) { + // We do not currently support multiprocess thread selectors. + SendReply(GDB_STUB_REPLY_ERR); + return; + } else if (is_hex_string(thread_token)) { + directive.thread = GetThreadByID(strtoull(std::string(thread_token).c_str(), nullptr, 16)); + } else { + SendReply(GDB_STUB_REPLY_ERR); + return; + } + + directives.push_back(directive); + } + + if (directives.empty()) { + SendReply(GDB_STUB_REPLY_ERR); + return; + } + + // Resolve the packet exactly as specified by the protocol: for each thread, + // the leftmost action with a matching thread-id wins. + Kernel::KThread* stepped_thread = nullptr; + std::vector continue_threads; + auto& thread_list = debug_process->GetThreadList(); + for (auto& thread : thread_list) { + const auto directive = std::find_if(directives.begin(), directives.end(), + [&](const VContDirective& candidate) { + return candidate.Matches(std::addressof(thread)); + }); + if (directive == directives.end()) { + continue; + } + + switch (directive->action) { + case VContAction::Continue: + continue_threads.push_back(std::addressof(thread)); + break; + case VContAction::Step: + if (stepped_thread) { + // The core can step at most one thread at a time. + SendReply(GDB_STUB_REPLY_ERR); + return; + } + stepped_thread = std::addressof(thread); + break; + } + } + + if (stepped_thread) { + backend.SetActiveThread(stepped_thread); + resume_threads = std::move(continue_threads); + actions.push_back(DebuggerAction::StepThread); + } else if (continue_threads.size() == thread_list.size()) { + actions.push_back(DebuggerAction::Continue); + } else if (!continue_threads.empty()) { + resume_threads = std::move(continue_threads); + actions.push_back(DebuggerAction::ContinueThreads); + } else { + // A resume packet that leaves all threads stopped is not useful to execute. + SendReply(GDB_STUB_REPLY_ERR); } } diff --git a/src/core/debugger/gdbstub.h b/src/core/debugger/gdbstub.h index 140b0e8e25..5103536338 100644 --- a/src/core/debugger/gdbstub.h +++ b/src/core/debugger/gdbstub.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project @@ -52,6 +52,7 @@ struct GDBStub : public DebuggerFrontend { std::unique_ptr arch; std::vector current_command; std::map replaced_instructions; + std::vector resume_threads; bool no_ack{}; }; diff --git a/src/core/device_memory.h b/src/core/device_memory.h index 11bf0e3268..b5103e23bc 100644 --- a/src/core/device_memory.h +++ b/src/core/device_memory.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -26,8 +29,11 @@ public: template Common::PhysicalAddress GetPhysicalAddr(const T* ptr) const { - return (reinterpret_cast(ptr) - - reinterpret_cast(buffer.BackingBasePointer())) + + return GetPhysicalAddr(reinterpret_cast(ptr)); + } + + Common::PhysicalAddress GetPhysicalAddr(uintptr_t ptr) const { + return (ptr - reinterpret_cast(buffer.BackingBasePointer())) + DramMemoryMap::Base; } diff --git a/src/core/device_memory_manager.h b/src/core/device_memory_manager.h index 3d97fdcc5c..b4b3b46088 100644 --- a/src/core/device_memory_manager.h +++ b/src/core/device_memory_manager.h @@ -18,7 +18,7 @@ #include "common/common_types.h" #include "common/range_mutex.h" #include "common/scratch_buffer.h" -#include "common/virtual_buffer.h" +#include "common/sparse_large_vector.h" namespace Core { @@ -178,8 +178,8 @@ private: u32 continuity_tracker; u32 compressed_physical_ptr; }; - Common::VirtualBuffer compressed_device_addr; - Common::VirtualBuffer tracked_entries; + Common::SparseLargeVector compressed_device_addr; + Common::SparseLargeVector tracked_entries; // Process memory interfaces @@ -200,8 +200,8 @@ private: return std::make_pair(asid, address); } - void InsertCPUBacking(size_t page_index, VAddr address, Asid asid) { - tracked_entries[page_index].cpu_backing_address = address | (asid.id << asid_start_bit); + constexpr void InsertCPUBacking(size_t page_index, VAddr address, Asid asid) { + tracked_entries.GetUnchecked(page_index).cpu_backing_address = address | (asid.id << asid_start_bit); } std::array t_slot{}; diff --git a/src/core/device_memory_manager.inc b/src/core/device_memory_manager.inc index 15e3a1ad52..97c2706abb 100644 --- a/src/core/device_memory_manager.inc +++ b/src/core/device_memory_manager.inc @@ -11,6 +11,8 @@ #include #include +#include + #include "common/address_space.h" #include "common/address_space.inc" #include "common/alignment.h" @@ -33,21 +35,23 @@ public: void GatherValues(u32 start_entry, Common::ScratchBuffer& buffer) { buffer.resize(8); - buffer.resize(0); - size_t index = 0; - const auto add_value = [&](u32 value) { - buffer.resize(index + 1); + const auto add_value = [&buffer](u32 value, size_t index) { + if (buffer.size() < index + 1) + buffer.resize(index + 8); buffer[index++] = value; + return index; }; + size_t index = 0; u32 iter_entry = start_entry; Entry* current = &storage[iter_entry - 1]; - add_value(current->value); + index = add_value(current->value, index); while (current->next_entry != 0) { iter_entry = current->next_entry; current = &storage[iter_entry - 1]; - add_value(current->value); + index = add_value(current->value, index); } + buffer.resize(index); } u32 Register(u32 value) { @@ -124,8 +128,8 @@ private: u32 value{}; }; - std::deque storage; - std::deque free_entries; + std::vector storage; + boost::container::deque free_entries; }; struct EmptyAllocator { @@ -173,17 +177,6 @@ DeviceMemoryManager::DeviceMemoryManager(const DeviceMemory& device_memo { impl = std::make_unique>(); cached_pages = std::make_unique(); - - const size_t total_virtual = device_as_size >> Memory::YUZU_PAGEBITS; - for (size_t i = 0; i < total_virtual; i++) { - tracked_entries[i].compressed_physical_ptr = 0; - tracked_entries[i].continuity_tracker = 1; - tracked_entries[i].cpu_backing_address = 0; - } - const size_t total_phys = 1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS); - for (size_t i = 0; i < total_phys; i++) { - compressed_device_addr[i] = 0; - } } template @@ -216,26 +209,28 @@ void DeviceMemoryManager::Map(DAddr address, VAddr virtual_address, size size_t start_page_d = address >> Memory::YUZU_PAGEBITS; size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS; std::scoped_lock lk(mapping_guard); + + tracked_entries.CommitRegion(start_page_d, start_page_d + num_pages); for (size_t i = 0; i < num_pages; i++) { const VAddr new_vaddress = virtual_address + i * Memory::YUZU_PAGESIZE; auto* ptr = process_memory->GetPointerSilent(Common::ProcessAddress(new_vaddress)); if (ptr == nullptr) [[unlikely]] { - tracked_entries[start_page_d + i].compressed_physical_ptr = 0; + tracked_entries.GetUnchecked(start_page_d + i).compressed_physical_ptr = 0; continue; } auto phys_addr = static_cast(GetRawPhysicalAddr(ptr) >> Memory::YUZU_PAGEBITS) + 1U; - tracked_entries[start_page_d + i].compressed_physical_ptr = phys_addr; + tracked_entries.GetUnchecked(start_page_d + i).compressed_physical_ptr = phys_addr; InsertCPUBacking(start_page_d + i, new_vaddress, asid); const u32 base_dev = compressed_device_addr[phys_addr - 1U]; const u32 new_dev = static_cast(start_page_d + i); if (base_dev == 0) [[likely]] { - compressed_device_addr[phys_addr - 1U] = new_dev; + compressed_device_addr.GetAndFault(phys_addr - 1U) = new_dev; continue; } u32 start_id = base_dev & MULTI_MASK; if ((base_dev >> MULTI_FLAG_BITS) == 0) { start_id = impl->multi_dev_address.Register(base_dev); - compressed_device_addr[phys_addr - 1U] = MULTI_FLAG | start_id; + compressed_device_addr.GetAndFault(phys_addr - 1U) = MULTI_FLAG | start_id; } impl->multi_dev_address.Register(new_dev, start_id); } @@ -251,24 +246,26 @@ void DeviceMemoryManager::Unmap(DAddr address, size_t size) { size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS; device_inter->InvalidateRegion(address, size); std::scoped_lock lk(mapping_guard); + + tracked_entries.CommitRegion(start_page_d, start_page_d + num_pages); // should already be committed, but just in case for (size_t i = 0; i < num_pages; i++) { - auto phys_addr = tracked_entries[start_page_d + i].compressed_physical_ptr; - tracked_entries[start_page_d + i].compressed_physical_ptr = 0; - tracked_entries[start_page_d + i].cpu_backing_address = 0; + auto& entry = tracked_entries.GetUnchecked(start_page_d + i); + auto phys_addr = entry.compressed_physical_ptr; + entry.compressed_physical_ptr = 0; + entry.cpu_backing_address = 0; if (phys_addr != 0) [[likely]] { - const u32 base_dev = compressed_device_addr[phys_addr - 1U]; + u32& base_dev = compressed_device_addr.GetAndFault(phys_addr - 1U); if ((base_dev >> MULTI_FLAG_BITS) == 0) [[likely]] { - compressed_device_addr[phys_addr - 1] = 0; + base_dev = 0; continue; } const auto [more_entries, new_start] = impl->multi_dev_address.Unregister( static_cast(start_page_d + i), base_dev & MULTI_MASK); if (!more_entries) { - compressed_device_addr[phys_addr - 1] = - impl->multi_dev_address.ReleaseEntry(new_start); + base_dev = impl->multi_dev_address.ReleaseEntry(new_start); continue; } - compressed_device_addr[phys_addr - 1] = new_start | MULTI_FLAG; + base_dev = new_start | MULTI_FLAG; } } t_slot = {}; @@ -281,6 +278,8 @@ void DeviceMemoryManager::TrackContinuityImpl(DAddr address, VAddr virtu size_t num_pages = Common::AlignUp(size, Memory::YUZU_PAGESIZE) >> Memory::YUZU_PAGEBITS; uintptr_t last_ptr = 0; size_t page_count = 1; + + tracked_entries.CommitRegion(start_page_d, start_page_d + num_pages); for (size_t i = num_pages; i > 0; i--) { size_t index = i - 1; const VAddr new_vaddress = virtual_address + index * Memory::YUZU_PAGESIZE; @@ -292,14 +291,14 @@ void DeviceMemoryManager::TrackContinuityImpl(DAddr address, VAddr virtu page_count = 1; } last_ptr = new_ptr; - tracked_entries[start_page_d + index].continuity_tracker = static_cast(page_count); + tracked_entries.GetUnchecked(start_page_d + index).continuity_tracker = static_cast(page_count) - 1; } } template u8* DeviceMemoryManager::GetSpan(const DAddr src_addr, const std::size_t size) { size_t page_index = src_addr >> page_bits; size_t subbits = src_addr & page_mask; - if ((static_cast(tracked_entries[page_index].continuity_tracker) << page_bits) >= size + subbits) { + if ((static_cast(tracked_entries[page_index].continuity_tracker+1) << page_bits) >= size + subbits) { return GetPointer(src_addr); } return nullptr; @@ -309,7 +308,7 @@ template const u8* DeviceMemoryManager::GetSpan(const DAddr src_addr, const std::size_t size) const { size_t page_index = src_addr >> page_bits; size_t subbits = src_addr & page_mask; - if ((static_cast(tracked_entries[page_index].continuity_tracker) << page_bits) >= size + subbits) { + if ((static_cast(tracked_entries[page_index].continuity_tracker+1) << page_bits) >= size + subbits) { return GetPointer(src_addr); } return nullptr; @@ -379,7 +378,7 @@ void DeviceMemoryManager::WalkBlock(DAddr addr, std::size_t size, auto o std::size_t page_index = addr >> Memory::YUZU_PAGEBITS; std::size_t page_offset = addr & Memory::YUZU_PAGEMASK; while (remaining_size) { - const size_t next_pages = std::size_t(tracked_entries[page_index].continuity_tracker); + const size_t next_pages = std::size_t(tracked_entries[page_index].continuity_tracker+1); const std::size_t copy_amount = (std::min)((next_pages << Memory::YUZU_PAGEBITS) - page_offset, remaining_size); const auto current_vaddr = u64((page_index << Memory::YUZU_PAGEBITS) + page_offset); SCOPE_EXIT{ @@ -427,7 +426,7 @@ void DeviceMemoryManager::ReadBlock(DAddr address, void* dest_pointer, s [&](size_t copy_amount, DAddr current_vaddr) { LOG_ERROR( HW_Memory, - "Unmapped Device ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", + "Unmapped Device ReadBlock @ {:#016x} (start address = {:#016x}, size = {})", current_vaddr, address, size); std::memset(dest_pointer, 0, copy_amount); }, @@ -446,7 +445,7 @@ void DeviceMemoryManager::WriteBlock(DAddr address, const void* src_poin [&](size_t copy_amount, DAddr current_vaddr) { LOG_ERROR( HW_Memory, - "Unmapped Device WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", + "Unmapped Device WriteBlock @ {:#016x} (start address = {:#016x}, size = {})", current_vaddr, address, size); }, [&](size_t copy_amount, u8* const dst_ptr) { @@ -485,7 +484,7 @@ void DeviceMemoryManager::ReadBlockUnsafe(DAddr address, void* dest_poin [&](size_t copy_amount, DAddr current_vaddr) { LOG_ERROR( HW_Memory, - "Unmapped Device ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", + "Unmapped Device ReadBlock @ {:#016x} (start address = {:#016x}, size = {})", current_vaddr, address, size); std::memset(dest_pointer, 0, copy_amount); }, @@ -505,7 +504,7 @@ void DeviceMemoryManager::WriteBlockUnsafe(DAddr address, const void* sr [&](size_t copy_amount, DAddr current_vaddr) { LOG_ERROR( HW_Memory, - "Unmapped Device WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", + "Unmapped Device WriteBlock @ {:#016x} (start address = {:#016x}, size = {})", current_vaddr, address, size); }, [&](size_t copy_amount, u8* const dst_ptr) { @@ -619,7 +618,7 @@ template void DeviceMemoryManager::UpdatePagesCachedCount(DAddr addr, size_t size, s32 delta) { Common::ScopedRangeLock lk(counter_guard, addr, size); UpdatePagesCachedCountNoLock(addr, size, delta); -} +} template void DeviceMemoryManager::UpdatePagesCachedBatch(std::span> ranges, s32 delta) { diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 2b478d6b99..1ba7bd3de3 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -29,7 +29,7 @@ static u8 MasterKeyIdForKeyGeneration(u8 key_generation) { return std::max(key_generation, 1) - 1; } -NCA::NCA(VirtualFile file_, const NCA* base_nca) +NCA::NCA(VirtualFile file_, const NCA* base_nca, bool allow_missing_base) : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} { if (file == nullptr) { status = Loader::ResultStatus::ErrorNullFile; @@ -45,6 +45,7 @@ NCA::NCA(VirtualFile file_, const NCA* base_nca) // Ensure we have the proper key area keys to continue. const u8 master_key_id = MasterKeyIdForKeyGeneration(reader->GetKeyGeneration()); if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, reader->GetKeyIndex())) { + LOG_ERROR(Loader, "Cannot decrypt {}: keys for gen 0x{:02X}+ required.", file->GetName(), reader->GetKeyGeneration()); status = Loader::ResultStatus::ErrorMissingKeyAreaKey; return; } @@ -110,7 +111,7 @@ NCA::NCA(VirtualFile file_, const NCA* base_nca) } } - if (is_update && base_nca == nullptr) { + if (is_update && base_nca == nullptr && !allow_missing_base) { status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; } else { status = Loader::ResultStatus::Success; diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 8560617f5b..b88fcdc4ee 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -63,7 +66,7 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) { // After construction, use GetStatus to determine if the file is valid and ready to be used. class NCA : public ReadOnlyVfsDirectory { public: - explicit NCA(VirtualFile file, const NCA* base_nca = nullptr); + explicit NCA(VirtualFile file, const NCA* base_nca = nullptr, bool allow_missing_base = false); ~NCA() override; Loader::ResultStatus GetStatus() const; diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index f86d22d627..2e182f1749 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -128,25 +128,32 @@ const LanguageEntry& NACP::GetLanguageEntry() const { case Settings::Language::Russian: return Language::Russian; case Settings::Language::Spanish: return Language::Spanish; case Settings::Language::SpanishLatin: return Language::LatinAmericanSpanish; - case Settings::Language::Taiwanese: return Language::SimplifiedChinese; + case Settings::Language::Taiwanese: return Language::TraditionalChinese; case Settings::Language::Thai: return Language::Thai; case Settings::Language::Polish: return Language::Polish; default: return Language::AmericanEnglish; } }(); - u32 index = u32(language); + const auto index = static_cast(language); - if (index < language_entries.size() && !language_entries[index].GetApplicationName().empty()) { + if (index < language_entries.size() && + !language_entries[index].GetApplicationName().empty()) { return language_entries[index]; } for (const auto& entry : language_entries) { - if (!entry.GetApplicationName().empty()) - return entry; + if (!entry.GetApplicationName().empty()) { + return entry; + } } - return language_entries.at(static_cast(Language::AmericanEnglish)); + if (!language_entries.empty()) { + return language_entries.front(); + } + + static const LanguageEntry empty_entry{}; + return empty_entry; } std::vector NACP::GetApplicationNames() const { diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h index 7b90ba235d..670e8f7389 100644 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -274,23 +274,22 @@ public: explicit NACP(VirtualFile file); ~NACP(); - const LanguageEntry& GetLanguageEntry() const; - std::string GetApplicationName() const; - std::string GetDeveloperName() const; - u64 GetTitleId() const; - u64 GetDLCBaseTitleId() const; - std::string GetVersionString() const; - u64 GetDefaultNormalSaveSize() const; - u64 GetDefaultJournalSaveSize() const; - u32 GetSupportedLanguages() const; - std::vector GetApplicationNames() const; - std::vector GetRawBytes() const; - bool GetUserAccountSwitchLock() const; - u64 GetDeviceSaveDataSize() const; - u32 GetParentalControlFlag() const; - const std::array& GetRatingAge() const; + [[nodiscard]] const LanguageEntry& GetLanguageEntry() const; + [[nodiscard]] std::string GetApplicationName() const; + [[nodiscard]] std::string GetDeveloperName() const; + [[nodiscard]] u64 GetTitleId() const; + [[nodiscard]] u64 GetDLCBaseTitleId() const; + [[nodiscard]] std::string GetVersionString() const; + [[nodiscard]] u64 GetDefaultNormalSaveSize() const; + [[nodiscard]] u64 GetDefaultJournalSaveSize() const; + [[nodiscard]] u32 GetSupportedLanguages() const; + [[nodiscard]] std::vector GetApplicationNames() const; + [[nodiscard]] std::vector GetRawBytes() const; + [[nodiscard]] bool GetUserAccountSwitchLock() const; + [[nodiscard]] u64 GetDeviceSaveDataSize() const; + [[nodiscard]] u32 GetParentalControlFlag() const; + [[nodiscard]] const std::array& GetRatingAge() const; -private: RawNACP raw{}; std::vector language_entries; }; diff --git a/src/core/file_sys/fssystem/fs_types.h b/src/core/file_sys/fssystem/fs_types.h index f11b7f1dae..fb16697c8f 100644 --- a/src/core/file_sys/fssystem/fs_types.h +++ b/src/core/file_sys/fssystem/fs_types.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -6,6 +6,7 @@ #pragma once +#include #include "common/common_funcs.h" namespace FileSys { diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp index e94eb2c882..144ae8e141 100644 --- a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp @@ -242,11 +242,8 @@ size_t AesCtrCounterExtendedStorage::Read(u8* buffer, size_t size, size_t offset return size; } -void SoftwareDecryptor::Decrypt(u8* buf, size_t buf_size, - const std::array& key, - const std::array& iv) { - Core::Crypto::AESCipher cipher( - key, Core::Crypto::Mode::CTR); +void SoftwareDecryptor::Decrypt(u8* buf, size_t buf_size, const std::array& key, const std::array& iv) { + Core::Crypto::AESCipher cipher(key, Core::Crypto::Mode::CTR); cipher.SetIV(iv); cipher.Transcode(buf, buf_size, buf, Core::Crypto::Op::Decrypt); } diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h index b7b004f9e3..b409b3acf3 100644 --- a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include "common/literals.h" #include "core/file_sys/fssystem/fs_i_storage.h" diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp index 07926b7acf..e457f6b239 100644 --- a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp @@ -4,6 +4,7 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #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" diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.h b/src/core/file_sys/fssystem/fssystem_bucket_tree.h index 3a5e21d1a4..66b6a17bbc 100644 --- a/src/core/file_sys/fssystem/fssystem_bucket_tree.h +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.h @@ -1,9 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 "common/alignment.h" #include "common/common_funcs.h" diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h b/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h index fac6c37214..5e7402e71c 100644 --- a/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -6,6 +6,7 @@ #pragma once +#include #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" diff --git a/src/core/file_sys/fssystem/fssystem_compressed_storage.h b/src/core/file_sys/fssystem/fssystem_compressed_storage.h index 3fc0449664..97de0f0c9a 100644 --- a/src/core/file_sys/fssystem/fssystem_compressed_storage.h +++ b/src/core/file_sys/fssystem/fssystem_compressed_storage.h @@ -6,6 +6,8 @@ #pragma once +#include +#include #include "common/literals.h" #include "core/file_sys/errors.h" diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp index 39bb7b8086..f4ec53ad40 100644 --- a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -121,6 +124,7 @@ size_t HierarchicalIntegrityVerificationStorage::Read(u8* buffer, size_t size, } size_t HierarchicalIntegrityVerificationStorage::GetSize() const { + ASSERT(m_data_size >= 0); return m_data_size; } diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h index bd129db47a..ada1656ef7 100644 --- a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h @@ -1,8 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 "common/alignment.h" #include "core/file_sys/fssystem/fs_i_storage.h" #include "core/file_sys/fssystem/fs_types.h" diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp index 41f5b75b6e..e66ce49d9a 100644 --- a/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp +++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp @@ -4,6 +4,7 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/logging.h" #include "core/file_sys/errors.h" #include "core/file_sys/fssystem/fssystem_indirect_storage.h" @@ -99,6 +100,18 @@ Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s R_SUCCEED(); } +void IndirectStorage::ReportMissingOriginal(s64 offset, s64 size) { + if (m_reported_missing_original) { + return; + } + m_reported_missing_original = true; + + LOG_ERROR(Common_Filesystem, + "Patch storage requests {:#x} bytes at {:#x} from a base storage that is not " + "present; this data will read as garbage", + size, offset); +} + size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const { // Validate pre-conditions. ASSERT(this->IsInitialized()); diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.h b/src/core/file_sys/fssystem/fssystem_indirect_storage.h index cc7b5cd718..728616b964 100644 --- a/src/core/file_sys/fssystem/fssystem_indirect_storage.h +++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.h @@ -6,6 +6,9 @@ #pragma once +#include +#include +#include #include "core/file_sys/errors.h" #include "core/file_sys/fssystem/fs_i_storage.h" #include "core/file_sys/fssystem/fssystem_bucket_tree.h" @@ -85,6 +88,9 @@ public: void SetStorage(s32 idx, VirtualFile storage) { ASSERT(0 <= idx && idx < StorageCount); m_data_storage[idx] = storage; + if (idx == 0) { + m_original_missing = storage == nullptr || storage->GetSize() == 0; + } } template @@ -130,6 +136,9 @@ protected: template Result OperatePerEntry(s64 offset, s64 size, F func); + // Launching another game makes the original storage inaccessable. + // This is a helper for multi-nca games. + void ReportMissingOriginal(s64 offset, s64 size); private: struct ContinuousReadingEntry { @@ -154,6 +163,8 @@ private: private: mutable BucketTree m_table; std::array m_data_storage; + bool m_original_missing{false}; + bool m_reported_missing_original{false}; }; template @@ -272,6 +283,10 @@ Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) { if (needs_operate) { const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset(); + if (cur_entry.storage_index == 0 && m_original_missing) { + this->ReportMissingOriginal(cur_offset, cur_size); + } + if constexpr (RangeCheck) { // Get the current data storage's size. s64 cur_data_storage_size = m_data_storage[cur_entry.storage_index]->GetSize(); diff --git a/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h index 09f76799db..b4259419dd 100644 --- a/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h +++ b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h @@ -1,9 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 "core/file_sys/fssystem/fs_i_storage.h" #include "core/file_sys/fssystem/fs_types.h" 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 e09bfc588a..ad8095b6f0 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,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -6,6 +6,8 @@ #pragma once +#include +#include #include "core/file_sys/fssystem/fssystem_compression_common.h" #include "core/file_sys/fssystem/fssystem_nca_header.h" #include "core/file_sys/vfs/vfs.h" diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.h b/src/core/file_sys/fssystem/fssystem_nca_header.h index a02c5d881c..b10bb39e66 100644 --- a/src/core/file_sys/fssystem/fssystem_nca_header.h +++ b/src/core/file_sys/fssystem/fssystem_nca_header.h @@ -1,8 +1,14 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 "common/common_funcs.h" #include "common/common_types.h" #include "common/literals.h" diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp index 1dddd27909..32e9041017 100644 --- a/src/core/file_sys/ips_layer.cpp +++ b/src/core/file_sys/ips_layer.cpp @@ -3,10 +3,12 @@ #include #include -#include #include #include #include +#include +#include +#include "common/container/unordered_map.h" #include "common/hex_util.h" #include "common/logging.h" @@ -22,46 +24,19 @@ enum class IPSFileType { Error, }; -constexpr std::array, 11> ESCAPE_CHARACTER_MAP{{ - {"\\a", "\a"}, - {"\\b", "\b"}, - {"\\f", "\f"}, - {"\\n", "\n"}, - {"\\r", "\r"}, - {"\\t", "\t"}, - {"\\v", "\v"}, - {"\\\\", "\\"}, - {"\\\'", "\'"}, - {"\\\"", "\""}, - {"\\\?", "\?"}, -}}; - -static IPSFileType IdentifyMagic(const std::vector& magic) { - if (magic.size() != 5) { - return IPSFileType::Error; +static IPSFileType IdentifyMagic(std::span magic) { + if (magic.size() >= 5) { + if (std::memcmp(magic.data(), "PATCH", 5) == 0) + return IPSFileType::IPS; + if (std::memcmp(magic.data(), "IPS32", 5) == 0) + return IPSFileType::IPS32; } - - static constexpr std::array patch_magic{{'P', 'A', 'T', 'C', 'H'}}; - if (std::equal(magic.begin(), magic.end(), patch_magic.begin())) { - return IPSFileType::IPS; - } - - static constexpr std::array ips32_magic{{'I', 'P', 'S', '3', '2'}}; - if (std::equal(magic.begin(), magic.end(), ips32_magic.begin())) { - return IPSFileType::IPS32; - } - return IPSFileType::Error; } -static bool IsEOF(IPSFileType type, const std::vector& data) { - static constexpr std::array eof{{'E', 'O', 'F'}}; - if (type == IPSFileType::IPS && std::equal(data.begin(), data.end(), eof.begin())) { - return true; - } - - static constexpr std::array eeof{{'E', 'E', 'O', 'F'}}; - return type == IPSFileType::IPS32 && std::equal(data.begin(), data.end(), eeof.begin()); +static bool IsEOF(IPSFileType type, std::span magic) { + return (type == IPSFileType::IPS && magic.size() >= 3 && std::memcmp(magic.data(), "EOF", 3) == 0) + || (type == IPSFileType::IPS32 && magic.size() >= 4 && std::memcmp(magic.data(), "EEOF", 4) == 0); } VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) { @@ -73,9 +48,8 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) { return nullptr; auto in_data = in->ReadAllBytes(); - if (in_data.size() == 0) { + if (in_data.size() == 0) return nullptr; - } std::vector temp(type == IPSFileType::IPS ? 3 : 4); u64 offset = 5; // After header @@ -85,12 +59,9 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) { break; } - u32 real_offset{}; - if (type == IPSFileType::IPS32) - real_offset = (temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3]; - else - real_offset = (temp[0] << 16) | (temp[1] << 8) | temp[2]; - + u32 real_offset = (type == IPSFileType::IPS32) + ? ((temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3]) + : ((temp[0] << 16) | (temp[1] << 8) | temp[2]); if (real_offset > in_data.size()) { return nullptr; } @@ -113,34 +84,34 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) { return nullptr; if (real_offset + rle_size > in_data.size()) - rle_size = static_cast(in_data.size() - real_offset); + rle_size = u16(in_data.size() - real_offset); std::memset(in_data.data() + real_offset, *data, rle_size); } else { // Standard Patch auto read = data_size; if (real_offset + read > in_data.size()) - read = static_cast(in_data.size() - real_offset); + read = u16(in_data.size() - real_offset); if (ips->Read(in_data.data() + real_offset, read, offset) != data_size) return nullptr; offset += data_size; } } - - if (!IsEOF(type, temp)) { - return nullptr; + if (IsEOF(type, temp)) { + return std::make_shared(std::move(in_data), in->GetName(), in->GetContainingDirectory()); } - - return std::make_shared(std::move(in_data), in->GetName(), - in->GetContainingDirectory()); + return nullptr; } + +struct IPSwitchRecord { + std::vector data; +}; struct IPSwitchCompiler::IPSwitchPatch { - std::string name; + ::Common::unordered_map records; bool enabled; - std::map> records; }; IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) { - Parse(); + Parse(patch_text->ReadAllBytes()); } IPSwitchCompiler::~IPSwitchCompiler() = default; @@ -149,201 +120,193 @@ std::array IPSwitchCompiler::GetBuildID() const { return nso_build_id; } -bool IPSwitchCompiler::IsValid() const { - return valid; -} - -static bool StartsWith(std::string_view base, std::string_view check) { - return base.size() >= check.size() && base.substr(0, check.size()) == check; -} - -static std::string EscapeStringSequences(std::string in) { - for (const auto& seq : ESCAPE_CHARACTER_MAP) { - for (auto index = in.find(seq.first); index != std::string::npos; - index = in.find(seq.first, index)) { - in.replace(index, std::strlen(seq.first), seq.second); - index += std::strlen(seq.second); +static IPSwitchRecord EscapeStringSequences(std::string_view sv) { + IPSwitchRecord r{}; + for (auto it = sv.cbegin(); it < sv.cend(); ) { + if (*it == '\\' && it + 1 < sv.cend()) { + r.data.push_back([it]() { + switch (it[1]) { + case 'a': return '\a'; + case 'b': return '\b'; + case 'e': return '\e'; + case 'f': return '\f'; + case 'n': return '\n'; + case 'r': return '\r'; + case 't': return '\t'; + case 'v': return '\v'; + case '?': return '\?'; + default: return it[1]; + } + }()); + it += 2; + } else { + r.data.push_back(*it); + ++it; } } - - return in; + return r; } -void IPSwitchCompiler::ParseFlag(const std::string& line) { - if (StartsWith(line, "@flag offset_shift ")) { - // Offset Shift Flag - offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0); - } else if (StartsWith(line, "@little-endian")) { - // Set values to read as little endian - is_little_endian = true; - } else if (StartsWith(line, "@big-endian")) { - // Set values to read as big endian - is_little_endian = false; - } else if (StartsWith(line, "@flag print_values")) { - // Force printing of applied values - print_values = true; - } -} - -void IPSwitchCompiler::Parse() { - const auto bytes = patch_text->ReadAllBytes(); - std::stringstream s; - s.write(reinterpret_cast(bytes.data()), bytes.size()); - - std::vector lines; - std::string stream_line; - while (std::getline(s, stream_line)) { - // Remove a trailing \r - if (!stream_line.empty() && stream_line.back() == '\r') - stream_line.pop_back(); - lines.push_back(std::move(stream_line)); - } - - for (std::size_t i = 0; i < lines.size(); ++i) { - auto line = lines[i]; - - // Remove midline comments - std::size_t comment_index = std::string::npos; - bool within_string = false; - for (std::size_t k = 0; k < line.size(); ++k) { - if (line[k] == '\"' && (k > 0 && line[k - 1] != '\\')) { - within_string = !within_string; - } else if (line[k] == '\\' && (k < line.size() - 1 && line[k + 1] == '\\')) { - comment_index = k; +void IPSwitchCompiler::Parse(std::span bytes) { + LOG_INFO(Loader, "IPSwitchCompiler: '{}'", patch_text->GetName()); + bool is_little_endian = true; + s64 offset_shift = 0; + //bool print_values = false; + auto const parse_line = [&](std::string_view const line) { + // Keep in mind lines have trimmed spaces (at the end & start)! + LOG_INFO(Loader, "<{}>", line); + // IPSwitch is case insensitive + // Yes this is how the logic goes for the main reference parsers! + if (line.size() > 2 && line[0] == '@') { + switch (line[1]) { + // yes, @nsobid too -- NSO Build ID Specifier + case 'n': + case 'N': + nso_build_id = Common::HexStringToArray<0x20>(fmt::format("{:0<64}", line.substr(8))); + break; + // @stop + case 's': + case 'S': + return false; + // @enabled + case 'e': + case 'E': + patches.push_back({{}, true}); + break; + // @disabled + case 'd': + case 'D': + patches.push_back({{}, false}); + break; + // @flag + case 'f': + case 'F': { + if (line.starts_with("@flag offset_shift")) { + offset_shift = std::strtoll(line.data() + 19, nullptr, 0); // Offset Shift Flag + } else if (line.starts_with("@flag print_values")) { + //print_values = true; // Force printing of applied values + } break; } - } - - if (!StartsWith(line, "//") && comment_index != std::string::npos) { - last_comment = line.substr(comment_index + 2); - line = line.substr(0, comment_index); - } - - if (StartsWith(line, "@stop")) { - // Force stop - break; - } else if (StartsWith(line, "@nsobid-")) { - // NSO Build ID Specifier - const auto raw_build_id = fmt::format("{:0<64}", line.substr(8)); - nso_build_id = Common::HexStringToArray<0x20>(raw_build_id); - } else if (StartsWith(line, "#")) { - // Mandatory Comment - LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Forced output comment: {}", - patch_text->GetName(), line.substr(1)); - } else if (StartsWith(line, "//")) { - // Normal Comment - last_comment = line.substr(2); - if (last_comment.find_first_not_of(' ') == std::string::npos) - continue; - if (last_comment.find_first_not_of(' ') != 0) - last_comment = last_comment.substr(last_comment.find_first_not_of(' ')); - } else if (StartsWith(line, "@enabled") || StartsWith(line, "@disabled")) { - // Start of patch - const auto enabled = StartsWith(line, "@enabled"); - if (i == 0) - return; - LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Parsing patch '{}' ({})", - patch_text->GetName(), last_comment, line.substr(1)); - - IPSwitchPatch patch{last_comment, enabled, {}}; - - // Read rest of patch - while (true) { - if (i + 1 >= lines.size()) { - break; - } - - const auto& patch_line = lines[++i]; - - // Patch line may contain comments - if (StartsWith(patch_line, "//")) { - continue; - } - - // Start of new patch - if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) { - --i; - break; - } - - // Check for a flag - if (StartsWith(patch_line, "@")) { - ParseFlag(patch_line); - continue; - } - - // 11 - 8 hex digit offset + space + minimum two digit overwrite val - if (patch_line.length() < 11) - break; - auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16); - offset += static_cast(offset_shift); - - std::vector replace; - // 9 - first char of replacement val - if (patch_line[9] == '\"') { - // string replacement - auto end_index = patch_line.find('\"', 10); - if (end_index == std::string::npos || end_index < 10) - return; - while (patch_line[end_index - 1] == '\\') { - end_index = patch_line.find('\"', end_index + 1); - if (end_index == std::string::npos || end_index < 10) - return; - } - - auto value = patch_line.substr(10, end_index - 10); - value = EscapeStringSequences(value); - replace.reserve(value.size()); - std::copy(value.begin(), value.end(), std::back_inserter(replace)); - } else { - // hex replacement - const auto value = - patch_line.substr(9, patch_line.find_first_of(" /\r\n", 9) - 9); - replace = Common::HexStringToVector(value, is_little_endian); - } - - if (print_values) { - LOG_INFO(Loader, - "[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} " - "with byte string '{}'", - patch_text->GetName(), offset, Common::HexToString(replace)); - } - - patch.records.insert_or_assign(static_cast(offset), std::move(replace)); + case 'l': + case 'L': + is_little_endian = true; + break; + // IPS parsers dont support big endian no more, we do due to backcompat + case 'b': + case 'B': + is_little_endian = false; + break; + default: + LOG_WARNING(Loader, "Unknown flag {}", line); + break; } + } else if (patches.empty()) { + LOG_WARNING(Loader, "Invalid line not in a patch {}", line); + } else { + size_t offset = size_t(std::strtoul(line.data(), nullptr, 16)); + offset += size_t(offset_shift); + if (auto const first_quote = line.find_first_of("\"\'"); first_quote != std::string::npos) { + // string replacement + char quote = line[first_quote]; + auto const start = line.cbegin() + first_quote + 1; + auto end = start; + for (; end < line.cend() && *end != quote; ) + end += (*end == '\\') ? 2 : 1; + if (start <= line.cend() && end <= line.cend()) { + LOG_INFO(Loader, "[S] value @ {:#08X} ", offset); + patches.back().records.insert_or_assign(u32(offset), EscapeStringSequences({start, end})); + } else { + LOG_WARNING(Loader, "invalid string"); + } + } else if (auto const first_space = line.find_last_of(" /\t\r\n"); first_space != std::string::npos) { + IPSwitchRecord r{}; // hex replacement + auto const start = line.cbegin() + first_space + 1; + auto const end = line.cend(); + if (start <= line.cend() && end <= line.cend()) { + // Actually IPS wants ordering from {lsb, ..., msb} -- so LE and BE are inverted, fun! + auto const hs = Common::HexStringToVector({start, end}, is_little_endian); + r.data.resize(hs.size()); + std::memcpy(r.data.data(), hs.data(), hs.size()); + LOG_INFO(Loader, "[H] value @ {:#08X}", offset); + patches.back().records.insert_or_assign(u32(offset), std::move(r)); + } else { + LOG_WARNING(Loader, "invalid line"); + } + } else { + LOG_WARNING(Loader, "unhandled line!"); + } + } + return true; //continue + }; - patches.push_back(std::move(patch)); - } else if (StartsWith(line, "@")) { - ParseFlag(line); + for (auto it = bytes.begin(); it < bytes.end(); ) { + auto const start = it; + auto end = start; + for (; end < bytes.end() && *end != '\n' && *end != '\r'; ++end) + ; + it = end + 1; //prepare for next line + std::string_view const sline{ + reinterpret_cast(bytes.data() + std::distance(bytes.begin(), start)), + size_t(std::distance(start, end)) + }; + if (sline.size() > 0) { + auto p = sline.cbegin(); + // skip space off line + for (; p < sline.cend() && std::isspace(*p); ++p) + ; + // now make a nominal preprocessed line: remove comments + char quote = '\0'; + auto const sline_start = p; + auto last_char = p; + for (; p < sline.cend(); ) { + // we dont check for "//", IPS checks for '/' only... + if (std::isspace(*p)) { + ++p; + } else if ((!quote && p[0] == '/') + || (!quote && p[0] == '#')) { + break; + } else if (p[0] == '\"' || p[0] == '\'') { + quote = (p[0] == quote) ? '\0' : p[0]; + ++p; + last_char = p; + } else if (p + 1 < sline.cend() && p[0] == '\\') { + p += 2; + last_char = p; + } else { + ++p; + last_char = p; + } + } + // now we have the preprocessed string ;) + std::string_view const pp_str(sline_start, last_char); + if (pp_str.size() > 0 && !parse_line(pp_str)) + break; } } - - valid = true; } VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const { - if (in == nullptr || !valid) + if (in == nullptr) return nullptr; auto in_data = in->ReadAllBytes(); for (const auto& patch : patches) { - if (!patch.enabled) - continue; - - for (const auto& record : patch.records) { - if (record.first >= in_data.size()) - continue; - auto replace_size = record.second.size(); - if (record.first + replace_size > in_data.size()) - replace_size = in_data.size() - record.first; - for (std::size_t i = 0; i < replace_size; ++i) - in_data[i + record.first] = record.second[i]; + if (patch.enabled) { + for (const auto& record : patch.records) { + if (record.first < in_data.size()) { + auto replace_size = record.second.data.size(); + if (record.first + replace_size > in_data.size()) + replace_size = in_data.size() - record.first; + std::memcpy(in_data.data() + record.first, record.second.data.data(), replace_size); + } else { + LOG_WARNING(Loader, "record offs={:x},size={:x}", record.first, record.second.data.size()); + } + } } } - - return std::make_shared(std::move(in_data), in->GetName(), - in->GetContainingDirectory()); + return std::make_shared(std::move(in_data), in->GetName(), in->GetContainingDirectory()); } } // namespace FileSys diff --git a/src/core/file_sys/ips_layer.h b/src/core/file_sys/ips_layer.h index d81378e8ad..e3c61f161b 100644 --- a/src/core/file_sys/ips_layer.h +++ b/src/core/file_sys/ips_layer.h @@ -1,11 +1,14 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 #pragma once #include -#include #include +#include #include "common/common_types.h" #include "core/file_sys/vfs/vfs.h" @@ -20,24 +23,17 @@ public: ~IPSwitchCompiler(); std::array GetBuildID() const; - bool IsValid() const; VirtualFile Apply(const VirtualFile& in) const; private: struct IPSwitchPatch; void ParseFlag(const std::string& flag); - void Parse(); - - bool valid = false; + void Parse(std::span bytes); VirtualFile patch_text; std::vector patches; std::array nso_build_id{}; - bool is_little_endian = false; - s64 offset_shift = 0; - bool print_values = false; - std::string last_comment = ""; }; } // namespace FileSys diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h index 43437d0dd7..07aae1642e 100644 --- a/src/core/file_sys/nca_metadata.h +++ b/src/core/file_sys/nca_metadata.h @@ -48,11 +48,11 @@ struct ContentRecord { std::array nca_id; std::array size; ContentRecordType type; - INSERT_PADDING_BYTES(1); + u8 id_offset; }; static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size."); -constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, {}}; +constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, 0}; struct MetaRecord { u64_le title_id; diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 6af926eac0..4fd7dc6c71 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -9,6 +9,7 @@ #include #include +#include "common/assert.h" #include "common/hex_util.h" #include "common/logging.h" #include "common/settings.h" @@ -156,12 +157,15 @@ std::string GetUpdateVersionStringFromSlot(const ContentProvider* provider, u64 NACP nacp{nacp_file}; return nacp.GetVersionString(); } +YUZU_NO_INLINE std::optional GetParentApplicationId(const ContentProvider* provider, u64 title_id) { + return provider == nullptr ? std::nullopt : provider->GetParentApplicationId(title_id); +} } // Anonymous namespace PatchManager::PatchManager(u64 title_id_, const Service::FileSystem::FileSystemController& fs_controller_, const ContentProvider& content_provider_) - : title_id{title_id_}, fs_controller{fs_controller_}, content_provider{content_provider_} {} + : title_id{title_id_}, parent_title_id{GetParentApplicationId(std::addressof(content_provider_), title_id_)}, fs_controller{fs_controller_}, content_provider{content_provider_} {} PatchManager::~PatchManager() = default; @@ -169,13 +173,35 @@ u64 PatchManager::GetTitleID() const { return title_id; } +VirtualDir PatchManager::GetModificationLoadRoot(bool sdmc) const { + const auto get_root = [&](u64 id) { + return sdmc ? fs_controller.GetSDMCModificationLoadRoot(id) : fs_controller.GetModificationLoadRoot(id); + }; + auto root = get_root(title_id); + if (!parent_title_id) { + return root; + } + std::vector roots{std::move(root), get_root(*parent_title_id)}; + std::erase(roots, nullptr); + return LayeredVfsDirectory::MakeLayeredDirectory(std::move(roots)); +} + +std::vector PatchManager::GetDisabledAddons() const { + auto disabled = Settings::values.disabled_addons[title_id]; + if (parent_title_id) { + const auto& shared = Settings::values.disabled_addons[*parent_title_id]; + disabled.insert(disabled.end(), shared.begin(), shared.end()); + } + return disabled; +} + VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id); if (exefs == nullptr) return exefs; - const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto disabled = GetDisabledAddons(); bool update_disabled = true; std::optional enabled_version; @@ -303,8 +329,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { } // LayeredExeFS - const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); - const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); + const auto load_dir = GetModificationLoadRoot(); + const auto sdmc_load_dir = GetModificationLoadRoot(true); std::vector patch_dirs = {sdmc_load_dir}; if (load_dir != nullptr) { @@ -345,9 +371,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { return exefs; } -std::vector PatchManager::CollectPatches(const std::vector& patch_dirs, - const std::string& build_id) const { - const auto& disabled = Settings::values.disabled_addons[title_id]; +std::vector PatchManager::CollectPatches(const std::vector& patch_dirs, const std::string& build_id) const { + const auto disabled = GetDisabledAddons(); const auto nso_build_id = fmt::format("{:0<64}", build_id); std::vector out; @@ -361,16 +386,11 @@ std::vector PatchManager::CollectPatches(const std::vectorGetFiles()) { if (file->GetExtension() == "ips") { auto name = file->GetName(); - - const auto this_build_id = - fmt::format("{:0<64}", name.substr(0, name.find('.'))); + const auto this_build_id = fmt::format("{:0<64}", name.substr(0, name.find('.'))); if (nso_build_id == this_build_id) out.push_back(file); } else if (file->GetExtension() == "pchtxt") { IPSwitchCompiler compiler{file}; - if (!compiler.IsValid()) - continue; - const auto this_build_id = Common::HexToString(compiler.GetBuildID()); if (nso_build_id == this_build_id) out.push_back(file); @@ -378,7 +398,6 @@ std::vector PatchManager::CollectPatches(const std::vector PatchManager::PatchNSO(const std::vector& nso, const std::st LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id); - const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); + const auto load_dir = GetModificationLoadRoot(); if (load_dir == nullptr) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); return nso; @@ -455,7 +474,7 @@ bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name); - const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); + const auto load_dir = GetModificationLoadRoot(); if (load_dir == nullptr) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); return false; @@ -469,13 +488,13 @@ bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) } std::vector PatchManager::CreateCheatList(const BuildID& build_id_) const { - const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); + const auto load_dir = GetModificationLoadRoot(); if (load_dir == nullptr) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); return {}; } - const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto disabled = GetDisabledAddons(); auto patch_dirs = load_dir->GetSubdirectories(); std::sort(patch_dirs.begin(), patch_dirs.end(), [](auto const& l, auto const& r) { return l->GetName() < r->GetName(); }); @@ -509,17 +528,16 @@ std::vector PatchManager::CreateCheatList(const BuildI return out; } -static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type, - const Service::FileSystem::FileSystemController& fs_controller) { - const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); - const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); +void PatchManager::ApplyLayeredFS(VirtualFile& romfs, ContentRecordType type) const { + const auto load_dir = GetModificationLoadRoot(); + const auto sdmc_load_dir = GetModificationLoadRoot(true); if ((type != ContentRecordType::Program && type != ContentRecordType::Data && type != ContentRecordType::HtmlDocument) || (load_dir == nullptr && sdmc_load_dir == nullptr)) { return; } - const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto disabled = GetDisabledAddons(); std::vector patch_dirs = load_dir->GetSubdirectories(); if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { patch_dirs.push_back(sdmc_load_dir); @@ -569,12 +587,11 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t layers.emplace_back(std::move(extracted)); auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); - if (layered == nullptr) { + auto layered_ext = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers_ext)); + if (layered == nullptr && layered_ext == nullptr) { return; } - auto layered_ext = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers_ext)); - auto packed = CreateRomFS(std::move(layered), std::move(layered_ext)); if (packed == nullptr) { return; @@ -599,7 +616,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs // Game Updates const auto update_tid = GetUpdateTitleID(title_id); - const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto disabled = GetDisabledAddons(); bool update_disabled = true; std::optional enabled_version; @@ -706,7 +723,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs // LayeredFS if (apply_layeredfs) { - ApplyLayeredFS(romfs, title_id, type, fs_controller); + ApplyLayeredFS(romfs, type); } return romfs; @@ -943,6 +960,8 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfs")) || IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfslite"))) AppendCommaIfNotEmpty(types, "LayeredFS"); + if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "romfs_ext"))) + AppendCommaIfNotEmpty(types, "ExtLayeredFS"); if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(mod, "cheats"))) AppendCommaIfNotEmpty(types, "Cheats"); @@ -965,13 +984,13 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); if (sdmc_mod_dir != nullptr) { std::string types; - if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs"))) { + if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs"))) AppendCommaIfNotEmpty(types, "LayeredExeFS"); - } if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs")) || - IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfslite"))) { + IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfslite"))) AppendCommaIfNotEmpty(types, "LayeredFS"); - } + if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs_ext"))) + AppendCommaIfNotEmpty(types, "ExtLayeredFS"); if (!types.empty()) { const auto mod_disabled = @@ -1078,15 +1097,22 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { std::optional PatchManager::GetGameVersion() const { const auto update_tid = GetUpdateTitleID(title_id); if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) { - return content_provider.GetEntryVersion(update_tid); + const auto version = content_provider.GetEntryVersion(update_tid); + return version || !parent_title_id ? version : content_provider.GetEntryVersion(GetUpdateTitleID(*parent_title_id)); } - return content_provider.GetEntryVersion(title_id); + const auto version = content_provider.GetEntryVersion(title_id); + return version || !parent_title_id ? version : content_provider.GetEntryVersion(*parent_title_id); } PatchManager::Metadata PatchManager::GetControlMetadata() const { const auto base_control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control); if (base_control_nca == nullptr) { + if (parent_title_id) { + const auto control_id = content_provider.HasEntry(*parent_title_id, ContentRecordType::Control) ? *parent_title_id : GetUpdateTitleID(*parent_title_id); + const PatchManager parent{control_id, fs_controller, content_provider}; + return parent.GetControlMetadata(); + } return {}; } @@ -1155,4 +1181,14 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const { return {std::move(nacp), icon_file}; } + +[[nodiscard]] PatchManager::Metadata PatchManager::GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept { + const FileSys::PatchManager pm{application_id, system.GetFileSystemController(), system.GetContentProvider()}; + auto metadata = pm.GetControlMetadata(); + if (metadata.first != nullptr) + return metadata; + const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(application_id), system.GetFileSystemController(), system.GetContentProvider()}; + return pm_update.GetControlMetadata(); +} + } // namespace FileSys diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 755d924fd4..6819a1a3e7 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -105,11 +105,18 @@ public: // Version of GetControlMetadata that takes an arbitrary NCA [[nodiscard]] Metadata ParseControlNCA(const NCA& nca) const; + /// @brief Gets NACP metadata (accounting for any patches or updates) + [[nodiscard]] static PatchManager::Metadata GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept; + private: + [[nodiscard]] VirtualDir GetModificationLoadRoot(bool sdmc = false) const; + [[nodiscard]] std::vector GetDisabledAddons() const; + void ApplyLayeredFS(VirtualFile& romfs, ContentRecordType type) const; [[nodiscard]] std::vector CollectPatches(const std::vector& patch_dirs, const std::string& build_id) const; u64 title_id; + std::optional parent_title_id; const Service::FileSystem::FileSystemController& fs_controller; const ContentProvider& content_provider; }; diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp index 4fc2e7ced3..63ca9d3fe3 100644 --- a/src/core/file_sys/program_metadata.cpp +++ b/src/core/file_sys/program_metadata.cpp @@ -140,7 +140,7 @@ void ProgramMetadata::LoadManual(bool is_64_bit, ProgramAddressSpaceType address } bool ProgramMetadata::Is64BitProgram() const { - return npdm_header.has_64_bit_instructions.As(); + return bool(npdm_header.has_64_bit_instructions); } ProgramAddressSpaceType ProgramMetadata::GetAddressSpaceType() const { @@ -181,11 +181,11 @@ const ProgramMetadata::KernelCapabilityDescriptors& ProgramMetadata::GetKernelCa void ProgramMetadata::Print() const { LOG_DEBUG(Service_FS, "Magic: {:.4}", npdm_header.magic.data()); - LOG_DEBUG(Service_FS, "Main thread priority: 0x{:02X}", npdm_header.main_thread_priority); + LOG_DEBUG(Service_FS, "Main thread priority: {:#02x}", npdm_header.main_thread_priority); LOG_DEBUG(Service_FS, "Main thread core: {}", npdm_header.main_thread_cpu); - LOG_DEBUG(Service_FS, "Main thread stack size: {:#X} bytes", npdm_header.main_stack_size); + LOG_DEBUG(Service_FS, "Main thread stack size: {:#x} bytes", npdm_header.main_stack_size); LOG_DEBUG(Service_FS, "Process category: {}", npdm_header.process_category); - LOG_DEBUG(Service_FS, "Flags: 0x{:02X}", npdm_header.flags); + LOG_DEBUG(Service_FS, "Flags: {:#02x}", npdm_header.flags); LOG_DEBUG(Service_FS, " > 64-bit instructions: {}", npdm_header.has_64_bit_instructions ? "YES" : "NO"); @@ -209,15 +209,15 @@ void ProgramMetadata::Print() const { // Begin ACID printing (potential perms, signed) LOG_DEBUG(Service_FS, "Magic: {:.4}", acid_header.magic.data()); - LOG_DEBUG(Service_FS, "Flags: 0x{:02X}", acid_header.flags); + LOG_DEBUG(Service_FS, "Flags: {:#02x}", acid_header.flags); LOG_DEBUG(Service_FS, " > Is Retail: {}", acid_header.production_flag ? "YES" : "NO"); - LOG_DEBUG(Service_FS, "Title ID Min: 0x{:016X}", acid_header.title_id_min); - LOG_DEBUG(Service_FS, "Title ID Max: 0x{:016X}", acid_header.title_id_max); - LOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", acid_file_access.permissions); + LOG_DEBUG(Service_FS, "Title ID Min: {:#016x}", acid_header.title_id_min); + LOG_DEBUG(Service_FS, "Title ID Max: {:#016x}", acid_header.title_id_max); + LOG_DEBUG(Service_FS, "Filesystem Access: {:#016x}\n", acid_file_access.permissions); // Begin ACI0 printing (actual perms, unsigned) LOG_DEBUG(Service_FS, "Magic: {:.4}", aci_header.magic.data()); - LOG_DEBUG(Service_FS, "Title ID: 0x{:016X}", aci_header.title_id); - LOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", aci_file_access.permissions); + LOG_DEBUG(Service_FS, "Title ID: {:#016x}", aci_header.title_id); + LOG_DEBUG(Service_FS, "Filesystem Access: {:#016x}\n", aci_file_access.permissions); } } // namespace FileSys diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index af41820a36..26e73d260a 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include #include @@ -15,6 +16,7 @@ #include "common/random.h" #include "common/string_util.h" #include "core/crypto/key_manager.h" +#include "core/crypto/ncz_decompressor.h" #include "core/file_sys/card_image.h" #include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" @@ -157,6 +159,25 @@ static std::shared_ptr OpenContainerAsNsp(const VirtualFile& file, Loader:: return nullptr; } +// Checks that every .ncz entry in the container is NCZBLOCK. Solid NCZ has no random access, +// and the whole point of this build is to reject it at scan time so such files never appear +// in the game list. Containers without any .ncz (plain NSP/XCI) pass through. +static bool ContainerIsFullyBlockCompressed(const std::shared_ptr& nsp) { + if (!nsp) { + return false; + } + for (const auto& entry : nsp->GetFiles()) { + const auto name = entry->GetName(); + if (name.size() < 4 || name.substr(name.size() - 4) != ".ncz") { + continue; + } + if (!Core::Crypto::IsBlockCompressedNCZ(entry)) { + return false; // solid NCZ -> reject the whole container + } + } + return true; // no .ncz at all, or all block-compressed +} + template bool ForEachContainerEntry(const std::shared_ptr& nsp, bool only_content, std::optional base_program_id, Callback&& on_entry) { @@ -348,6 +369,22 @@ std::vector ContentProvider::ListEntries() const { return ListEntriesFilter(std::nullopt, std::nullopt, std::nullopt); } +std::optional ContentProvider::GetParentApplicationId(u64 program_id) const { + const auto application_id = GetBaseTitleID(program_id); + const auto program_index = program_id - application_id; + if (program_index == 0 || program_index > std::numeric_limits::max()) { + return std::nullopt; + } + if (!ListEntriesFilter(TitleType::Application, ContentRecordType::Meta, program_id).empty()) { + return std::nullopt; + } + if ((!ListEntriesFilter(TitleType::Application, ContentRecordType::Meta, application_id).empty() && HasEntry(program_id, ContentRecordType::Program)) + || (!ListEntriesFilter(TitleType::Update, ContentRecordType::Meta, GetUpdateTitleID(application_id)).empty() && HasEntry(GetUpdateTitleID(program_id), ContentRecordType::Program))) { + return application_id; + } + return std::nullopt; +} + PlaceholderCache::PlaceholderCache(VirtualDir dir_) : dir(std::move(dir_)) {} bool PlaceholderCache::Create(const NcaID& id, u64 size) const { @@ -503,6 +540,9 @@ NcaID PlaceholderCache::Generate() { VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& open_dir, std::string_view path) const { + if (open_dir == nullptr) { + return nullptr; + } const auto file = open_dir->GetFileRelative(path); if (file != nullptr) { return file; @@ -563,21 +603,46 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const { return file; } -static std::optional CheckMapForContentRecord(const ankerl::unordered_dense::map& map, u64 title_id, ContentRecordType type) { - const auto cmnt_iter = map.find(title_id); +static std::optional CheckMapForContentRecord(const ::Common::unordered_map& map, u64 title_id, ContentRecordType type) { + auto cmnt_iter = map.find(title_id); + u8 id_offset = 0; + if (cmnt_iter == map.cend()) { - return std::nullopt; + const auto program_index = title_id & AOC_TITLE_ID_MASK; + if (program_index == 0) { + return std::nullopt; + } + + cmnt_iter = map.find(title_id & ~AOC_TITLE_ID_MASK); + if (cmnt_iter == map.cend()) { + return std::nullopt; + } + + id_offset = static_cast(program_index); } const auto& cnmt = cmnt_iter->second; const auto& content_records = cnmt.GetContentRecords(); const auto iter = std::find_if(content_records.cbegin(), content_records.cend(), - [type](const ContentRecord& rec) { return rec.type == type; }); - if (iter == content_records.cend()) { + [type, id_offset](const ContentRecord& rec) { + return rec.type == type && rec.id_offset == id_offset; + }); + if (iter != content_records.cend()) { + return std::make_optional(iter->nca_id); + } + + if (id_offset != 0) { return std::nullopt; } - return std::make_optional(iter->nca_id); + const auto fallback_iter = + std::find_if(content_records.cbegin(), content_records.cend(), + [type](const ContentRecord& rec) { return rec.type == type; }); + if (fallback_iter == content_records.cend()) { + return std::nullopt; + } + + return std::make_optional(fallback_iter->nca_id); } std::optional RegisteredCache::GetNcaIDFromMetadata(u64 title_id, @@ -766,7 +831,14 @@ std::vector RegisteredCache::ListEntriesFilter( } static std::shared_ptr GetNCAFromNSPForID(const NSP& nsp, const NcaID& id) { - auto file = nsp.GetFile(fmt::format("{}.nca", Common::HexToString(id, false))); + const auto id_string = Common::HexToString(id, false); + auto file = nsp.GetFile(fmt::format("{}.nca", id_string)); + if (file == nullptr) { + if (auto ncz_file = nsp.GetFile(fmt::format("{}.ncz", id_string))) { + file = Core::Crypto::DecompressNCZForInstall( + ncz_file, fmt::format("{}.nca", id_string)); + } + } if (file == nullptr) { return nullptr; } @@ -981,6 +1053,22 @@ bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { return removed_data; } +bool RegisteredCache::Delete(const NcaID& id) const { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + + const bool is_file = dir->GetFileRelative(path) != nullptr; + const bool is_dir = dir->GetDirectoryRelative(path) != nullptr; + + if (is_file) { + return dir->DeleteFile(path); + } + if (is_dir) { + return dir->DeleteSubdirectoryRecursive(path); + } + + return true; +} + InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy, bool overwrite_if_exists, std::optional override_id) { @@ -1212,6 +1300,12 @@ bool ManualContentProvider::AddEntriesFromContainer(VirtualFile file, bool only_ return false; } + // Reject solid NCZ (.nsz without -B): only block-compressed NCZ is supported. + if (!ContainerIsFullyBlockCompressed(nsp)) { + LOG_DEBUG(Service_FS, "Skipping solid-compressed container: {}", file->GetName()); + return false; + } + return ForEachContainerEntry( nsp, only_content, base_program_id, [this](TitleType title_type, ContentRecordType content_type, u64 title_id, @@ -1354,8 +1448,9 @@ void ExternalContentProvider::ScanDirectory(const VirtualDir& dir) { } const auto extension = Common::ToLower(filename.substr(dot_pos + 1)); - - if (extension == "nsp") { + // .xcz is an XCI container, but its updates only work through the NSP/PFS0 path + // (DecompressNCZ), not ProcessXCI. So route .xcz here on purpose. + if (extension == "nsp" || extension == "nsz" || extension == "xcz") { ProcessNSP(file); } else if (extension == "xci") { ProcessXCI(file); @@ -1373,6 +1468,12 @@ void ExternalContentProvider::ProcessNSP(const VirtualFile& file) { return; } + // Reject solid NCZ (.nsz without -B): Eden only supports block-compressed NCZ. + if (!ContainerIsFullyBlockCompressed(nsp)) { + LOG_DEBUG(Service_FS, "Skipping solid-compressed container: {}", file->GetName()); + return; + } + LOG_DEBUG(Service_FS, "Processing NSP file: {}", file->GetName()); AddExternalEntriesFromContainer(nsp, entries, versions, multi_version_entries); } diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index 32134d1c48..89140a2e95 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -12,7 +12,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include "common/common_types.h" #include "core/crypto/key_manager.h" @@ -100,6 +100,8 @@ public: std::optional title_type = {}, std::optional record_type = {}, std::optional title_id = {}) const = 0; + [[nodiscard]] std::optional GetParentApplicationId(u64 program_id) const; + protected: // A single instance of KeyManager to be used by GetEntry() Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); @@ -188,6 +190,7 @@ public: // Removes an existing entry based on title id bool RemoveExistingEntry(u64 title_id) const; + bool Delete(const NcaID& id) const; private: template @@ -208,11 +211,11 @@ private: ContentProviderParsingFunction parser; // maps tid -> NcaID of meta - ankerl::unordered_dense::map meta_id; + ::Common::unordered_map meta_id; // maps tid -> meta - ankerl::unordered_dense::map meta; + ::Common::unordered_map meta; // maps tid -> meta for CNMT in yuzu_meta - ankerl::unordered_dense::map yuzu_meta; + ::Common::unordered_map yuzu_meta; }; enum class ContentProviderUnionSlot { @@ -312,8 +315,8 @@ private: void ProcessXCI(const VirtualFile& file); std::vector load_dirs; - ankerl::unordered_dense::map, VirtualFile> entries; - ankerl::unordered_dense::map versions; + ::Common::unordered_map, VirtualFile> entries; + ::Common::unordered_map versions; std::vector multi_version_entries; }; diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index 0ba839ec14..c756734a43 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -57,11 +57,7 @@ std::string GetFutureSaveDataPath(SaveDataSpaceId space_id, SaveDataType type, u SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_, VirtualDir save_directory_) - : system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} { - // Delete all temporary storages - // On hardware, it is expected that temporary storage be empty at first use. - dir->DeleteSubdirectoryRecursive("temp"); -} + : system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} {} SaveDataFactory::~SaveDataFactory() = default; diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index 15dd4ec7de..c7899f023d 100644 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index 630bb0b079..1a39d819f6 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp @@ -12,6 +12,8 @@ #include "common/hex_util.h" #include "common/logging.h" #include "core/crypto/key_manager.h" +#include "core/crypto/ncz_decompressor.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/partition_filesystem.h" @@ -65,10 +67,12 @@ u64 NSP::GetProgramTitleID() const { } auto program_id = expected_program_id; - if (program_id == 0) { - if (!program_status.empty()) { - program_id = program_status.begin()->first; - } + if (program_id == 0 && !program_status.empty()) { + program_id = std::min_element(program_status.cbegin(), program_status.cend(), + [](const auto& lhs, const auto& rhs) { + return lhs.first < rhs.first; + }) + ->first; } program_id = program_id + program_index; @@ -243,6 +247,17 @@ void NSP::ReadNCAs(const std::vector& files) { const auto id_string = Common::HexToString(rec.nca_id, false); auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string)); + if (next_file == nullptr) { + auto ncz_file = pfs->GetFile(fmt::format("{}.ncz", id_string)); + if (ncz_file != nullptr) { + next_file = Core::Crypto::DecompressNCZ(ncz_file, fmt::format("{}.nca", id_string)); + if (next_file == nullptr) { + LOG_DEBUG(Service_FS, "Skipping solid or unsupported NCZ content {}.ncz", id_string); + continue; + } + } + } + if (next_file == nullptr) { if (rec.type != ContentRecordType::DeltaFragment) { LOG_WARNING(Service_FS, @@ -273,8 +288,7 @@ void NSP::ReadNCAs(const std::vector& files) { // If the last 3 hexadecimal digits of the NCA's TitleID is between 0x1 and // 0x7FF, this is a multi-program update NCA. Otherwise, this is a regular // update NCA. - if ((next_nca->GetTitleId() & 0x7FF) != 0 && - (next_nca->GetTitleId() & 0x800) == 0) { + if ((next_nca->GetTitleId() & AOC_TITLE_ID_MASK) != 0) { ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] = std::move(next_nca); } else { diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp index bbdf27039b..1743d01b12 100644 --- a/src/core/file_sys/system_archive/system_archive.cpp +++ b/src/core/file_sys/system_archive/system_archive.cpp @@ -76,7 +76,7 @@ constexpr inline SystemArchiveDescriptor GetSystemArchive(u64 title_id) { VirtualFile SynthesizeSystemArchive(const u64 title_id) { auto const desc = GetSystemArchive(title_id); - LOG_INFO(Service_FS, "Synthesizing system archive '{}' (0x{:016X}).", desc.name, title_id); + LOG_INFO(Service_FS, "Synthesizing system archive '{}' ({:#016x}).", desc.name, title_id); if (desc.supplier != nullptr) { if (auto const dir = desc.supplier(); dir != nullptr) { if (auto const romfs = CreateRomFS(dir); romfs != nullptr) { diff --git a/src/core/file_sys/system_archive/system_version.cpp b/src/core/file_sys/system_archive/system_version.cpp index c4ed91ce01..be6cde7275 100644 --- a/src/core/file_sys/system_archive/system_version.cpp +++ b/src/core/file_sys/system_archive/system_version.cpp @@ -11,30 +11,43 @@ namespace FileSys::SystemArchive { -std::string GetLongDisplayVersion() { - return HLE::ApiVersion::DISPLAY_TITLE; -} - VirtualDir SystemVersion() { - LOG_WARNING(Common_Filesystem, "called - Using hardcoded firmware version '{}'", - GetLongDisplayVersion()); + LOG_WARNING(Common_Filesystem, "called, using hardcoded firmware version: {}, {}", HLE::ApiVersion::VERSION_HASH, HLE::ApiVersion::DISPLAY_TITLE); - VirtualFile file = std::make_shared(std::vector(0x100), "file"); - file->WriteObject(HLE::ApiVersion::HOS_VERSION_MAJOR, 0); - file->WriteObject(HLE::ApiVersion::HOS_VERSION_MINOR, 1); - file->WriteObject(HLE::ApiVersion::HOS_VERSION_MICRO, 2); - file->WriteObject(HLE::ApiVersion::SDK_REVISION_MAJOR, 4); - file->WriteObject(HLE::ApiVersion::SDK_REVISION_MINOR, 5); - file->WriteArray(HLE::ApiVersion::PLATFORM_STRING, - std::min(sizeof(HLE::ApiVersion::PLATFORM_STRING), 0x20ULL), 0x8); - file->WriteArray(HLE::ApiVersion::VERSION_HASH, - std::min(sizeof(HLE::ApiVersion::VERSION_HASH), 0x40ULL), 0x28); - file->WriteArray(HLE::ApiVersion::DISPLAY_VERSION, - std::min(sizeof(HLE::ApiVersion::DISPLAY_VERSION), 0x18ULL), 0x68); - file->WriteArray(HLE::ApiVersion::DISPLAY_TITLE, - std::min(sizeof(HLE::ApiVersion::DISPLAY_TITLE), 0x80ULL), 0x80); - return std::make_shared(std::vector{file}, - std::vector{}, "data"); + // the "/file" + struct VersionFileHeader { + u8 version_major = HLE::ApiVersion::HOS_VERSION_MAJOR; + u8 version_minor = HLE::ApiVersion::HOS_VERSION_MINOR; + u8 version_micro = HLE::ApiVersion::HOS_VERSION_MICRO; + std::array padding_1{}; + u8 sdk_revision_major = HLE::ApiVersion::SDK_REVISION_MAJOR; + u8 sdk_revision_minor = HLE::ApiVersion::SDK_REVISION_MINOR; + std::array padding_2{}; + std::array platform_string{}; + std::array version_hash{}; + std::array display_version{}; + std::array display_title{}; + }; + static_assert(sizeof(VersionFileHeader) == 0x100); + VersionFileHeader version_file_header = {}; + std::memcpy(&version_file_header.platform_string, HLE::ApiVersion::PLATFORM_STRING, sizeof(HLE::ApiVersion::PLATFORM_STRING)); + std::memcpy(&version_file_header.version_hash, HLE::ApiVersion::VERSION_HASH, sizeof(HLE::ApiVersion::VERSION_HASH)); + std::memcpy(&version_file_header.display_version, HLE::ApiVersion::DISPLAY_VERSION, sizeof(HLE::ApiVersion::DISPLAY_VERSION)); + std::memcpy(&version_file_header.display_title, HLE::ApiVersion::DISPLAY_TITLE, sizeof(HLE::ApiVersion::DISPLAY_TITLE)); + + std::vector file_data(sizeof(VersionFileHeader)); + std::memcpy(file_data.data(), &version_file_header, sizeof(version_file_header)); + VirtualFile file = std::make_shared(file_data, "file"); + + // the "/digest" + if (HLE::ApiVersion::VERSION_DIGEST[0] == '\0') { + return std::make_shared(std::vector{file}, std::vector{}, "data"); + } else { + std::vector digest_data(sizeof(HLE::ApiVersion::VERSION_DIGEST)); + std::memcpy(digest_data.data(), HLE::ApiVersion::VERSION_DIGEST, sizeof(HLE::ApiVersion::VERSION_DIGEST)); + VirtualFile digest_file = std::make_shared(digest_data, "digest"); + return std::make_shared(std::vector{file, digest_file}, std::vector{}, "data"); + } } } // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/system_archive/system_version.h b/src/core/file_sys/system_archive/system_version.h index e5f7b952e2..f9962d030b 100644 --- a/src/core/file_sys/system_archive/system_version.h +++ b/src/core/file_sys/system_archive/system_version.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -8,8 +11,6 @@ namespace FileSys::SystemArchive { -std::string GetLongDisplayVersion(); - VirtualDir SystemVersion(); } // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/system_archive/time_zone_binary.cpp b/src/core/file_sys/system_archive/time_zone_binary.cpp index 316ff0dc6f..9b79b6fcd3 100644 --- a/src/core/file_sys/system_archive/time_zone_binary.cpp +++ b/src/core/file_sys/system_archive/time_zone_binary.cpp @@ -1,86 +1,48 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 +#include #include #include "common/swap.h" #include "core/file_sys/system_archive/time_zone_binary.h" +#include "core/file_sys/vfs/vfs_static.h" +#include "core/file_sys/vfs/vfs_types.h" #include "core/file_sys/vfs/vfs_vector.h" #include "nx_tzdb.h" namespace FileSys::SystemArchive { -const static std::map>&> - tzdb_zoneinfo_dirs = {{"Africa", NxTzdb::africa}, - {"America", NxTzdb::america}, - {"Antarctica", NxTzdb::antarctica}, - {"Arctic", NxTzdb::arctic}, - {"Asia", NxTzdb::asia}, - {"Atlantic", NxTzdb::atlantic}, - {"Australia", NxTzdb::australia}, - {"Brazil", NxTzdb::brazil}, - {"Canada", NxTzdb::canada}, - {"Chile", NxTzdb::chile}, - {"Etc", NxTzdb::etc}, - {"Europe", NxTzdb::europe}, - {"Indian", NxTzdb::indian}, - {"Mexico", NxTzdb::mexico}, - {"Pacific", NxTzdb::pacific}, - {"US", NxTzdb::us}}; - -const static std::map>&> - tzdb_america_dirs = {{"Argentina", NxTzdb::america_argentina}, - {"Indiana", NxTzdb::america_indiana}, - {"Kentucky", NxTzdb::america_kentucky}, - {"North_Dakota", NxTzdb::america_north_dakota}}; - -static void GenerateFiles(std::vector& directory, - const std::map>& files) { - for (const auto& [filename, data] : files) { - const auto data_copy{data}; - const std::string filename_copy{filename}; - VirtualFile file{ - std::make_shared(std::move(data_copy), std::move(filename_copy))}; - directory.push_back(file); - } -} - -static std::vector GenerateZoneinfoFiles() { - std::vector zoneinfo_files; - GenerateFiles(zoneinfo_files, NxTzdb::zoneinfo); - return zoneinfo_files; -} - VirtualDir TimeZoneBinary() { std::vector america_sub_dirs; - for (const auto& [dir_name, files] : tzdb_america_dirs) { - std::vector vfs_files; - GenerateFiles(vfs_files, files); - america_sub_dirs.push_back(std::make_shared( - std::move(vfs_files), std::vector{}, dir_name)); - } - + america_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_america_argentina(), std::vector{}, "Argentina")); + america_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_america_indiana(), std::vector{}, "Indiana")); + america_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_america_kentucky(), std::vector{}, "Kentucky")); + america_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_america_north_dakota(), std::vector{}, "North_Dakota")); std::vector zoneinfo_sub_dirs; - for (const auto& [dir_name, files] : tzdb_zoneinfo_dirs) { - std::vector vfs_files; - GenerateFiles(vfs_files, files); - if (dir_name == "America") { - zoneinfo_sub_dirs.push_back(std::make_shared( - std::move(vfs_files), std::move(america_sub_dirs), dir_name)); - } else { - zoneinfo_sub_dirs.push_back(std::make_shared( - std::move(vfs_files), std::vector{}, dir_name)); - } - } - - std::vector zoneinfo_dir{std::make_shared( - GenerateZoneinfoFiles(), std::move(zoneinfo_sub_dirs), "zoneinfo")}; - std::vector root_files; - GenerateFiles(root_files, NxTzdb::base); - - return std::make_shared(std::move(root_files), std::move(zoneinfo_dir), - "data"); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_africa(), std::vector{}, "Africa")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_america(), std::move(america_sub_dirs), "America")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_antarctica(), std::vector{}, "Antarctica")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_arctic(), std::vector{}, "Arctic")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_asia(), std::vector{}, "Asia")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_atlantic(), std::vector{}, "Atlantic")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_australia(), std::vector{}, "Australia")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_brazil(), std::vector{}, "Brazil")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_canada(), std::vector{}, "Canada")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_chile(), std::vector{}, "Chile")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_etc(), std::vector{}, "Etc")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_europe(), std::vector{}, "Europe")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_indian(), std::vector{}, "Indian")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_mexico(), std::vector{}, "Mexico")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_pacific(), std::vector{}, "Pacific")); + zoneinfo_sub_dirs.push_back(std::make_shared(NxTzdb::CollectFiles_us(), std::vector{}, "US")); + std::vector zoneinfo_dir{std::make_shared(NxTzdb::CollectFiles_zoneinfo(), std::move(zoneinfo_sub_dirs), "zoneinfo")}; + // last files (root) + return std::make_shared(NxTzdb::CollectFiles_base(), std::move(zoneinfo_dir), "data"); } } // namespace FileSys::SystemArchive diff --git a/src/core/file_sys/vfs/vfs.h b/src/core/file_sys/vfs/vfs.h index f846a9669c..c3d73364bc 100644 --- a/src/core/file_sys/vfs/vfs.h +++ b/src/core/file_sys/vfs/vfs.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -16,6 +19,12 @@ #include "core/file_sys/fs_filesystem.h" #include "core/file_sys/vfs/vfs_types.h" +#undef CreateFile +#undef CopyFile +#undef MoveFile +#undef DeleteFile +#undef CreateDirectory + namespace FileSys { // An enumeration representing what can be at the end of a path in a VfsFilesystem diff --git a/src/core/file_sys/vfs/vfs_layered.cpp b/src/core/file_sys/vfs/vfs_layered.cpp index 8dabe03e16..c650b45f4c 100644 --- a/src/core/file_sys/vfs/vfs_layered.cpp +++ b/src/core/file_sys/vfs/vfs_layered.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include "common/container/unordered_set.h" #include #include "core/file_sys/vfs/vfs_layered.h" @@ -63,7 +63,7 @@ std::string LayeredVfsDirectory::GetFullPath() const { std::vector LayeredVfsDirectory::GetFiles() const { std::vector out; - ankerl::unordered_dense::set out_names; + ::Common::unordered_set out_names; for (const auto& layer : dirs) { for (auto& file : layer->GetFiles()) { @@ -79,7 +79,7 @@ std::vector LayeredVfsDirectory::GetFiles() const { std::vector LayeredVfsDirectory::GetSubdirectories() const { std::vector out; - ankerl::unordered_dense::set out_names; + ::Common::unordered_set out_names; for (const auto& layer : dirs) { for (const auto& sd : layer->GetSubdirectories()) { diff --git a/src/core/file_sys/vfs/vfs_real.cpp b/src/core/file_sys/vfs/vfs_real.cpp index 1eafff6c06..14e0caad3c 100644 --- a/src/core/file_sys/vfs/vfs_real.cpp +++ b/src/core/file_sys/vfs/vfs_real.cpp @@ -23,7 +23,7 @@ #define stat _stat64 #endif -#ifdef ANDROID +#ifdef __ANDROID__ #include "common/fs/fs_android.h" #endif @@ -33,7 +33,17 @@ namespace FS = Common::FS; namespace { -constexpr size_t MaxOpenFiles = 512; +constexpr size_t MaxOpenFiles = 8192; + +bool IsWithinRoot(std::string_view root, std::string_view full_path) { + if (root.empty()) + return true; + + if (full_path.size() < root.size() || full_path.substr(0, root.size()) != root) + return false; + + return full_path.size() == root.size() || full_path[root.size()] == '/' || full_path[root.size()] == '\\'; +} constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(OpenMode mode) { switch (mode) { @@ -99,8 +109,7 @@ VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::op auto reference = std::make_unique(); this->InsertReferenceIntoListLocked(*reference); - auto file = std::shared_ptr( - new RealVfsFile(*this, std::move(reference), path, perms, size, std::move(parent_path))); + auto file = std::make_shared(*this, std::move(reference), path, perms, size, std::move(parent_path)); cache[path] = file; return file; @@ -167,7 +176,7 @@ bool RealVfsFilesystem::DeleteFile(std::string_view path_) { VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, OpenMode perms) { const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); - return std::shared_ptr(new RealVfsDirectory(*this, path, perms)); + return std::make_shared(*this, path, perms); } VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) { @@ -175,7 +184,7 @@ VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, OpenMode p if (!FS::CreateDirs(path)) { return nullptr; } - return std::shared_ptr(new RealVfsDirectory(*this, path, perms)); + return std::make_shared(*this, path, perms); } VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_, @@ -288,7 +297,7 @@ RealVfsFile::~RealVfsFile() { } std::string RealVfsFile::GetName() const { -#ifdef ANDROID +#ifdef __ANDROID__ if (path[0] != '/') { return FS::Android::GetFilename(path); } @@ -403,7 +412,8 @@ RealVfsDirectory::~RealVfsDirectory() = default; VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const { const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path)); - if (!FS::Exists(full_path) || FS::IsDir(full_path)) { + if (!FS::Exists(full_path) || FS::IsDir(full_path) + || !IsWithinRoot(FS::SanitizePath(path), full_path)) { return nullptr; } return base.OpenFile(full_path, perms); @@ -411,7 +421,8 @@ VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) co VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const { const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path)); - if (!FS::Exists(full_path) || !FS::IsDir(full_path)) { + if (!FS::Exists(full_path) || !FS::IsDir(full_path) + || !IsWithinRoot(FS::SanitizePath(path), full_path)) { return nullptr; } return base.OpenDirectory(full_path, perms); @@ -427,7 +438,7 @@ VirtualDir RealVfsDirectory::GetSubdirectory(std::string_view name) const { VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view relative_path) { const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path)); - if (!FS::CreateParentDirs(full_path)) { + if (!FS::CreateParentDirs(full_path) || !IsWithinRoot(FS::SanitizePath(path), full_path)) { return nullptr; } return base.CreateFile(full_path, perms); @@ -440,7 +451,7 @@ VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view relative_p bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) { const auto full_path = FS::SanitizePath(this->path + '/' + std::string(name)); - return base.DeleteDirectory(full_path); + return FS::RemoveDirRecursively(full_path); } std::vector RealVfsDirectory::GetFiles() const { @@ -506,7 +517,7 @@ VirtualFile RealVfsDirectory::CreateFile(std::string_view name) { bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) { const std::string subdir_path = (path + '/').append(name); - return base.DeleteDirectory(subdir_path); + return FS::RemoveDir(subdir_path); } bool RealVfsDirectory::DeleteFile(std::string_view name) { diff --git a/src/core/file_sys/vfs/vfs_real.h b/src/core/file_sys/vfs/vfs_real.h index 672126d7ba..a97f7d32ea 100644 --- a/src/core/file_sys/vfs/vfs_real.h +++ b/src/core/file_sys/vfs/vfs_real.h @@ -82,6 +82,9 @@ class RealVfsFile : public VfsFile { friend class RealVfsFilesystem; public: + RealVfsFile(RealVfsFilesystem& base, std::unique_ptr reference, + const std::string& path, OpenMode perms = OpenMode::Read, + std::optional size = {}, std::optional parent_path = {}); ~RealVfsFile() override; std::string GetName() const override; @@ -95,9 +98,6 @@ public: bool Rename(std::string_view name) override; private: - RealVfsFile(RealVfsFilesystem& base, std::unique_ptr reference, - const std::string& path, OpenMode perms = OpenMode::Read, - std::optional size = {}, std::optional parent_path = {}); RealVfsFilesystem& base; std::unique_ptr reference; @@ -113,6 +113,8 @@ class RealVfsDirectory : public VfsDirectory { friend class RealVfsFilesystem; public: + RealVfsDirectory(RealVfsFilesystem& base, const std::string& path, + OpenMode perms = OpenMode::Read); ~RealVfsDirectory() override; VirtualFile GetFileRelative(std::string_view relative_path) const override; @@ -138,9 +140,6 @@ public: std::map> GetEntries() const override; private: - RealVfsDirectory(RealVfsFilesystem& base, const std::string& path, - OpenMode perms = OpenMode::Read); - template std::vector> IterateEntries() const; diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp index c4292c5a12..e535866855 100644 --- a/src/core/frontend/applets/controller.cpp +++ b/src/core/frontend/applets/controller.cpp @@ -12,6 +12,7 @@ #include "hid_core/frontend/emulated_controller.h" #include "hid_core/hid_core.h" #include "hid_core/hid_types.h" +#include namespace Core::Frontend { @@ -29,23 +30,39 @@ void DefaultControllerApplet::ReconfigureControllers(ReconfigureCallback callbac const std::size_t min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; + using Core::HID::NpadStyleIndex; + const std::size_t max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; + std::size_t num_selected_players = 0; + std::array keep_connected{}; - // Disconnect Handheld first. + // reserve existing AND valid players before filling slots. include Handheld, but not other + for (std::size_t index = 0; index < hid_core.available_controllers - 1; ++index) { + const auto* controller = hid_core.GetEmulatedControllerByIndex(index); + if (!parameters.keep_controllers_connected || !controller->IsConnected() || num_selected_players >= max_supported_players) continue; + + const auto style = controller->GetNpadStyleIndex(); + keep_connected[index] = + (style == NpadStyleIndex::Fullkey && parameters.allow_pro_controller) || + (style == NpadStyleIndex::JoyconDual && parameters.allow_dual_joycons) || + (style == NpadStyleIndex::JoyconLeft && parameters.allow_left_joycon) || + (style == NpadStyleIndex::JoyconRight && parameters.allow_right_joycon) || + (style == NpadStyleIndex::Handheld && parameters.enable_single_mode && parameters.allow_handheld && !Settings::IsDockedMode()) || + (style == NpadStyleIndex::GameCube && parameters.allow_gamecube_controller); + num_selected_players += keep_connected[index]; + } auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld); - handheld->Disconnect(); + if (!keep_connected[hid_core.available_controllers - 2]) handheld->Disconnect(); // Deduce the best configuration based on the input parameters. for (std::size_t index = 0; index < hid_core.available_controllers - 2; ++index) { auto* controller = hid_core.GetEmulatedControllerByIndex(index); - // First, disconnect all controllers regardless of the value of keep_controllers_connected. - // This makes it easy to connect the desired controllers. + if (keep_connected[index]) continue; controller->Disconnect(); - // Only connect the minimum number of required players. - if (index >= min_supported_players) { - continue; - } + // only add players still needed to reach the minimum + if (num_selected_players >= min_supported_players) continue; + ++num_selected_players; // Connect controllers based on the following priority list from highest to lowest priority: // Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld diff --git a/src/core/hle/api_version.h b/src/core/hle/api_version.h index bffd30d863..0aff969296 100644 --- a/src/core/hle/api_version.h +++ b/src/core/hle/api_version.h @@ -6,6 +6,8 @@ #pragma once +#include +#include #include "common/common_types.h" // This file contains yuzu's HLE API version constants. @@ -14,25 +16,25 @@ namespace HLE::ApiVersion { // Horizon OS version constants. -constexpr u8 HOS_VERSION_MAJOR = 22; +constexpr u8 HOS_VERSION_MAJOR = 23; constexpr u8 HOS_VERSION_MINOR = 0; constexpr u8 HOS_VERSION_MICRO = 0; // NintendoSDK version constants. - -constexpr u8 SDK_REVISION_MAJOR = 3; +constexpr u8 SDK_REVISION_MAJOR = 1; constexpr u8 SDK_REVISION_MINOR = 0; -constexpr char PLATFORM_STRING[] = "NX"; -constexpr char VERSION_HASH[] = "da42070c4ad25840c9ee25344bde9d0a8584f5a9"; -constexpr char DISPLAY_VERSION[] = "22.0.0"; -constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 22.0.0-3.0"; +constexpr char PLATFORM_STRING[0x20] = "NX"; +constexpr char VERSION_HASH[0x40] = "c2663accb7490a6ccfe9bc4dfd120ca215490525"; +constexpr char DISPLAY_VERSION[0x18] = "23.0.0"; +constexpr char DISPLAY_TITLE[0x80] = "NintendoSDK Firmware for NX 23.0.0-4.0"; +// Leave empty when there's no digest (N/A) +constexpr char VERSION_DIGEST[0x40] = ""; // Atmosphere version constants. - constexpr u8 ATMOSPHERE_RELEASE_VERSION_MAJOR = 1; -constexpr u8 ATMOSPHERE_RELEASE_VERSION_MINOR = 10; -constexpr u8 ATMOSPHERE_RELEASE_VERSION_MICRO = 2; +constexpr u8 ATMOSPHERE_RELEASE_VERSION_MINOR = 12; +constexpr u8 ATMOSPHERE_RELEASE_VERSION_MICRO = 0; constexpr u32 AtmosphereTargetFirmwareWithRevision(u8 major, u8 minor, u8 micro, u8 rev) { return u32{major} << 24 | u32{minor} << 16 | u32{micro} << 8 | u32{rev}; diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h index 416da15ecb..9cd2908823 100644 --- a/src/core/hle/ipc.h +++ b/src/core/hle/ipc.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -13,6 +16,16 @@ namespace IPC { /// Size of the command buffer area, in 32-bit words. constexpr std::size_t COMMAND_BUFFER_LENGTH = 0x100 / sizeof(u32); +/// Must match bitfields +constexpr std::size_t MAX_BUFFER_DESCRIPTORS = 16; +constexpr std::size_t MAX_INCOMING_MOVE_HANDLERS = 16; +constexpr std::size_t MAX_INCOMING_COPY_HANDLERS = 16; + +/// Doesn't need to match bitfields but usually not big enough +constexpr std::size_t MAX_OUTGOING_COPY_OBJECTS = 16; +constexpr std::size_t MAX_OUTGOING_MOVE_OBJECTS = 16; +constexpr std::size_t MAX_OUTGOING_DOMAIN_OBJECTS = 16; + enum class ControlCommand : u32 { ConvertSessionToDomain = 0, ConvertDomainToSession = 1, diff --git a/src/core/hle/kernel/global_scheduler_context.cpp b/src/core/hle/kernel/global_scheduler_context.cpp index 855a8226ef..6d1cd030e0 100644 --- a/src/core/hle/kernel/global_scheduler_context.cpp +++ b/src/core/hle/kernel/global_scheduler_context.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -17,7 +17,8 @@ namespace Kernel { GlobalSchedulerContext::GlobalSchedulerContext(KernelCore& kernel) - : m_kernel{kernel}, m_scheduler_lock{kernel} {} + : m_scheduler_lock{kernel} +{} GlobalSchedulerContext::~GlobalSchedulerContext() = default; @@ -37,7 +38,7 @@ void GlobalSchedulerContext::RemoveThread(KThread* thread) noexcept { /// and then does some core rebalancing. Preemption priorities can be found /// in the array 'preemption_priorities'. /// @note This operation happens every 10ms. -void GlobalSchedulerContext::PreemptThreads() noexcept { +void GlobalSchedulerContext::PreemptThreads(KernelCore& kernel) noexcept { // The priority levels at which the global scheduler preempts threads every 10 ms. They are // ordered from Core 0 to Core 3. static constexpr std::array per_core{ @@ -46,9 +47,9 @@ void GlobalSchedulerContext::PreemptThreads() noexcept { 59, 63, }; - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); for (u32 core_id = 0; core_id < per_core.size(); core_id++) - KScheduler::RotateScheduledQueue(m_kernel, core_id, per_core[core_id]); + KScheduler::RotateScheduledQueue(kernel, core_id, per_core[core_id]); } /// @brief Returns true if the global scheduler lock is acquired @@ -69,11 +70,11 @@ void GlobalSchedulerContext::UnregisterDummyThreadForWakeup(KThread* thread) noe } } -void GlobalSchedulerContext::WakeupWaitingDummyThreads() noexcept { +void GlobalSchedulerContext::WakeupWaitingDummyThreads(KernelCore& kernel) noexcept { ASSERT(this->IsLocked()); if (m_woken_dummy_threads.size() > 0) { for (auto* thread : m_woken_dummy_threads) - thread->DummyThreadEndWait(); + thread->DummyThreadEndWait(kernel); m_woken_dummy_threads.clear(); } } diff --git a/src/core/hle/kernel/global_scheduler_context.h b/src/core/hle/kernel/global_scheduler_context.h index 64aee86af8..dd53477d2d 100644 --- a/src/core/hle/kernel/global_scheduler_context.h +++ b/src/core/hle/kernel/global_scheduler_context.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -50,17 +50,16 @@ public: } void AddThread(KThread* thread) noexcept; void RemoveThread(KThread* thread) noexcept; - void PreemptThreads() noexcept; + void PreemptThreads(KernelCore& kernel) noexcept; bool IsLocked() const noexcept; void UnregisterDummyThreadForWakeup(KThread* thread) noexcept; void RegisterDummyThreadForWakeup(KThread* thread) noexcept; - void WakeupWaitingDummyThreads() noexcept; + void WakeupWaitingDummyThreads(KernelCore& kernel) noexcept; private: friend class KScopedSchedulerLock; friend class KScopedSchedulerLockAndSleep; - KernelCore& m_kernel; std::atomic_bool m_scheduler_update_needed{}; KSchedulerPriorityQueue m_priority_queue; LockType m_scheduler_lock; diff --git a/src/core/hle/kernel/init/init_slab_setup.cpp b/src/core/hle/kernel/init/init_slab_setup.cpp index a0e20bbbb4..5dd8afaf96 100644 --- a/src/core/hle/kernel/init/init_slab_setup.cpp +++ b/src/core/hle/kernel/init/init_slab_setup.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -281,7 +284,7 @@ void KPageBufferSlabHeap::Initialize(Core::System& system) { // Reserve memory from the system resource limit. ASSERT( - kernel.GetSystemResourceLimit()->Reserve(LimitableResource::PhysicalMemoryMax, slab_size)); + kernel.GetSystemResourceLimit()->Reserve(kernel, LimitableResource::PhysicalMemoryMax, slab_size)); // Allocate memory for the slab. constexpr auto AllocateOption = KMemoryManager::EncodeOption( diff --git a/src/core/hle/kernel/k_address_arbiter.cpp b/src/core/hle/kernel/k_address_arbiter.cpp index 48889253dc..02634f8721 100644 --- a/src/core/hle/kernel/k_address_arbiter.cpp +++ b/src/core/hle/kernel/k_address_arbiter.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -16,8 +19,9 @@ namespace Kernel { -KAddressArbiter::KAddressArbiter(Core::System& system) - : m_system{system}, m_kernel{system.Kernel()} {} +KAddressArbiter::KAddressArbiter(Core::System& system_) + : system{system_} +{} KAddressArbiter::~KAddressArbiter() = default; namespace { @@ -112,7 +116,7 @@ public: explicit ThreadQueueImplForKAddressArbiter(KernelCore& kernel, KAddressArbiter::ThreadTree* t) : KThreadQueue(kernel), m_tree(t) {} - void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { + void CancelWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { // If the thread is waiting on an address arbiter, remove it from the tree. if (waiting_thread->IsWaitingForAddressArbiter()) { m_tree->erase(m_tree->iterator_to(*waiting_thread)); @@ -120,7 +124,7 @@ public: } // Invoke the base cancel wait handler. - KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + KThreadQueue::CancelWait(kernel, waiting_thread, wait_result, cancel_timer_task); } private: @@ -133,14 +137,14 @@ Result KAddressArbiter::Signal(uint64_t addr, s32 count) { // Perform signaling. s32 num_waiters{}; { - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(system.Kernel()); auto it = m_tree.nfind_key({addr, -1}); while ((it != m_tree.end()) && (count <= 0 || num_waiters < count) && (it->GetAddressArbiterKey() == addr)) { // End the thread's wait. KThread* target_thread = std::addressof(*it); - target_thread->EndWait(ResultSuccess); + target_thread->EndWait(system.Kernel(), ResultSuccess); ASSERT(target_thread->IsWaitingForAddressArbiter()); target_thread->ClearAddressArbiter(); @@ -156,11 +160,11 @@ Result KAddressArbiter::SignalAndIncrementIfEqual(uint64_t addr, s32 value, s32 // Perform signaling. s32 num_waiters{}; { - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(system.Kernel()); // Check the userspace value. s32 user_value{}; - R_UNLESS(UpdateIfEqual(m_kernel, std::addressof(user_value), addr, value, value + 1), + R_UNLESS(UpdateIfEqual(system.Kernel(), std::addressof(user_value), addr, value, value + 1), ResultInvalidCurrentMemory); R_UNLESS(user_value == value, ResultInvalidState); @@ -169,7 +173,7 @@ Result KAddressArbiter::SignalAndIncrementIfEqual(uint64_t addr, s32 value, s32 (it->GetAddressArbiterKey() == addr)) { // End the thread's wait. KThread* target_thread = std::addressof(*it); - target_thread->EndWait(ResultSuccess); + target_thread->EndWait(system.Kernel(), ResultSuccess); ASSERT(target_thread->IsWaitingForAddressArbiter()); target_thread->ClearAddressArbiter(); @@ -185,7 +189,7 @@ Result KAddressArbiter::SignalAndModifyByWaitingCountIfEqual(uint64_t addr, s32 // Perform signaling. s32 num_waiters{}; { - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(system.Kernel()); auto it = m_tree.nfind_key({addr, -1}); // Determine the updated value. @@ -220,9 +224,9 @@ Result KAddressArbiter::SignalAndModifyByWaitingCountIfEqual(uint64_t addr, s32 s32 user_value{}; bool succeeded{}; if (value != new_value) { - succeeded = UpdateIfEqual(m_kernel, std::addressof(user_value), addr, value, new_value); + succeeded = UpdateIfEqual(system.Kernel(), std::addressof(user_value), addr, value, new_value); } else { - succeeded = ReadFromUser(m_kernel, std::addressof(user_value), addr); + succeeded = ReadFromUser(system.Kernel(), std::addressof(user_value), addr); } R_UNLESS(succeeded, ResultInvalidCurrentMemory); @@ -232,7 +236,7 @@ Result KAddressArbiter::SignalAndModifyByWaitingCountIfEqual(uint64_t addr, s32 (it->GetAddressArbiterKey() == addr)) { // End the thread's wait. KThread* target_thread = std::addressof(*it); - target_thread->EndWait(ResultSuccess); + target_thread->EndWait(system.Kernel(), ResultSuccess); ASSERT(target_thread->IsWaitingForAddressArbiter()); target_thread->ClearAddressArbiter(); @@ -246,12 +250,12 @@ Result KAddressArbiter::SignalAndModifyByWaitingCountIfEqual(uint64_t addr, s32 Result KAddressArbiter::WaitIfLessThan(uint64_t addr, s32 value, bool decrement, s64 timeout) { // Prepare to wait. - KThread* cur_thread = GetCurrentThreadPointer(m_kernel); + KThread* cur_thread = GetCurrentThreadPointer(system.Kernel()); KHardwareTimer* timer{}; - ThreadQueueImplForKAddressArbiter wait_queue(m_kernel, std::addressof(m_tree)); + ThreadQueueImplForKAddressArbiter wait_queue(system.Kernel(), std::addressof(m_tree)); { - KScopedSchedulerLockAndSleep slp{m_kernel, std::addressof(timer), cur_thread, timeout}; + KScopedSchedulerLockAndSleep slp{system.Kernel(), std::addressof(timer), cur_thread, timeout}; // Check that the thread isn't terminating. if (cur_thread->IsTerminationRequested()) { @@ -263,9 +267,9 @@ Result KAddressArbiter::WaitIfLessThan(uint64_t addr, s32 value, bool decrement, s32 user_value{}; bool succeeded{}; if (decrement) { - succeeded = DecrementIfLessThan(m_kernel, std::addressof(user_value), addr, value); + succeeded = DecrementIfLessThan(system.Kernel(), std::addressof(user_value), addr, value); } else { - succeeded = ReadFromUser(m_kernel, std::addressof(user_value), addr); + succeeded = ReadFromUser(system.Kernel(), std::addressof(user_value), addr); } if (!succeeded) { @@ -291,7 +295,7 @@ Result KAddressArbiter::WaitIfLessThan(uint64_t addr, s32 value, bool decrement, // Wait for the thread to finish. wait_queue.SetHardwareTimer(timer); - cur_thread->BeginWait(std::addressof(wait_queue)); + cur_thread->BeginWait(system.Kernel(), std::addressof(wait_queue)); cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Arbitration); } @@ -301,12 +305,12 @@ Result KAddressArbiter::WaitIfLessThan(uint64_t addr, s32 value, bool decrement, Result KAddressArbiter::WaitIfEqual(uint64_t addr, s32 value, s64 timeout) { // Prepare to wait. - KThread* cur_thread = GetCurrentThreadPointer(m_kernel); + KThread* cur_thread = GetCurrentThreadPointer(system.Kernel()); KHardwareTimer* timer{}; - ThreadQueueImplForKAddressArbiter wait_queue(m_kernel, std::addressof(m_tree)); + ThreadQueueImplForKAddressArbiter wait_queue(system.Kernel(), std::addressof(m_tree)); { - KScopedSchedulerLockAndSleep slp{m_kernel, std::addressof(timer), cur_thread, timeout}; + KScopedSchedulerLockAndSleep slp{system.Kernel(), std::addressof(timer), cur_thread, timeout}; // Check that the thread isn't terminating. if (cur_thread->IsTerminationRequested()) { @@ -316,7 +320,7 @@ Result KAddressArbiter::WaitIfEqual(uint64_t addr, s32 value, s64 timeout) { // Read the value from userspace. s32 user_value{}; - if (!ReadFromUser(m_kernel, std::addressof(user_value), addr)) { + if (!ReadFromUser(system.Kernel(), std::addressof(user_value), addr)) { slp.CancelSleep(); R_THROW(ResultInvalidCurrentMemory); } @@ -339,7 +343,7 @@ Result KAddressArbiter::WaitIfEqual(uint64_t addr, s32 value, s64 timeout) { // Wait for the thread to finish. wait_queue.SetHardwareTimer(timer); - cur_thread->BeginWait(std::addressof(wait_queue)); + cur_thread->BeginWait(system.Kernel(), std::addressof(wait_queue)); cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Arbitration); } diff --git a/src/core/hle/kernel/k_address_arbiter.h b/src/core/hle/kernel/k_address_arbiter.h index 3b70e1ab28..b3afe19476 100644 --- a/src/core/hle/kernel/k_address_arbiter.h +++ b/src/core/hle/kernel/k_address_arbiter.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -60,8 +63,7 @@ private: private: ThreadTree m_tree; - Core::System& m_system; - KernelCore& m_kernel; + Core::System& system; }; } // namespace Kernel diff --git a/src/core/hle/kernel/k_auto_object.cpp b/src/core/hle/kernel/k_auto_object.cpp index a7a46d29bb..14d1c4624d 100644 --- a/src/core/hle/kernel/k_auto_object.cpp +++ b/src/core/hle/kernel/k_auto_object.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -15,8 +15,8 @@ KAutoObject* KAutoObject::Create(KAutoObject* obj) { return obj; } -void KAutoObject::RegisterWithKernel() { - m_kernel.RegisterKernelObject(this); +void KAutoObject::RegisterWithKernel(KernelCore& kernel) { + kernel.RegisterKernelObject(this); } void KAutoObject::UnregisterWithKernel(KernelCore& kernel, KAutoObject* self) { diff --git a/src/core/hle/kernel/k_auto_object.h b/src/core/hle/kernel/k_auto_object.h index 5e8c0030ce..392b9c9f1d 100644 --- a/src/core/hle/kernel/k_auto_object.h +++ b/src/core/hle/kernel/k_auto_object.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project @@ -87,21 +87,21 @@ private: KERNEL_AUTOOBJECT_TRAITS_IMPL(KAutoObject, KAutoObject, const); public: - explicit KAutoObject(KernelCore& kernel) : m_kernel(kernel) { + explicit KAutoObject(KernelCore& kernel) { m_class_token = GetStaticTypeObj().GetClassToken(); - RegisterWithKernel(); + RegisterWithKernel(kernel); } virtual ~KAutoObject() = default; static KAutoObject* Create(KAutoObject* ptr); // Destroy is responsible for destroying the auto object's resources when ref_count hits zero. - virtual void Destroy() { + virtual void Destroy(KernelCore& kernel) { UNIMPLEMENTED(); } // Finalize is responsible for cleaning up resource, but does not destroy the object. - virtual void Finalize() {} + virtual void Finalize(KernelCore& kernel) {} virtual KProcess* GetOwner() const { return nullptr; @@ -123,67 +123,50 @@ public: Derived DynamicCast() { static_assert(std::is_pointer_v); using DerivedType = std::remove_pointer_t; - - if (this->IsDerivedFrom(DerivedType::GetStaticTypeObj())) { - return static_cast(this); - } else { - return nullptr; - } + if (this->IsDerivedFrom(DerivedType::GetStaticTypeObj())) + return Derived(this); + return nullptr; } template const Derived DynamicCast() const { static_assert(std::is_pointer_v); using DerivedType = std::remove_pointer_t; - - if (this->IsDerivedFrom(DerivedType::GetStaticTypeObj())) { - return static_cast(this); - } else { - return nullptr; - } + if (this->IsDerivedFrom(DerivedType::GetStaticTypeObj())) + return Derived(this); + return nullptr; } - bool Open() { + bool Open(KernelCore& kernel) { // Atomically increment the reference count, only if it's positive. u32 cur_ref_count = m_ref_count.load(std::memory_order_acquire); do { - if (cur_ref_count == 0) { + if (cur_ref_count == 0) return false; - } ASSERT(cur_ref_count < cur_ref_count + 1); - } while (!m_ref_count.compare_exchange_weak(cur_ref_count, cur_ref_count + 1, - std::memory_order_relaxed)); - + } while (!m_ref_count.compare_exchange_weak(cur_ref_count, cur_ref_count + 1, std::memory_order_relaxed)); return true; } - void Close() { + void Close(KernelCore& kernel) { // Atomically decrement the reference count, not allowing it to become negative. u32 cur_ref_count = m_ref_count.load(std::memory_order_acquire); do { - if (cur_ref_count == 0) { + if (cur_ref_count == 0) return; - } ASSERT(cur_ref_count > 0); - } while (!m_ref_count.compare_exchange_weak(cur_ref_count, cur_ref_count - 1, - std::memory_order_acq_rel)); - + } while (!m_ref_count.compare_exchange_weak(cur_ref_count, cur_ref_count - 1, std::memory_order_acq_rel)); // If ref count hits 1, destroy the object. if (cur_ref_count == 1) { - KernelCore& kernel = m_kernel; - this->Destroy(); + this->Destroy(kernel); KAutoObject::UnregisterWithKernel(kernel, this); } } private: - void RegisterWithKernel(); + void RegisterWithKernel(KernelCore& kernel); static void UnregisterWithKernel(KernelCore& kernel, KAutoObject* self); -protected: - KernelCore& m_kernel; - -private: std::atomic m_ref_count{}; ClassTokenType m_class_token{}; }; @@ -225,17 +208,22 @@ class KScopedAutoObject { public: YUZU_NON_COPYABLE(KScopedAutoObject); - constexpr KScopedAutoObject() = default; + constexpr KScopedAutoObject(KernelCore& kernel_) + : kernel{kernel_} + {} - constexpr KScopedAutoObject(T* o) : m_obj(o) { + constexpr KScopedAutoObject(KernelCore& kernel_, T* o) + : kernel{kernel_} + , m_obj(o) + { if (m_obj != nullptr) { - m_obj->Open(); + m_obj->Open(kernel); } } ~KScopedAutoObject() { if (m_obj != nullptr) { - m_obj->Close(); + m_obj->Close(kernel); } m_obj = nullptr; } @@ -253,7 +241,7 @@ public: if (rhs.m_obj != nullptr) { derived = rhs.m_obj->template DynamicCast(); if (derived == nullptr) { - rhs.m_obj->Close(); + rhs.m_obj->Close(rhs.kernel); } } @@ -274,8 +262,16 @@ public: return *m_obj; } + constexpr void SetObject(T* o) { + if (m_obj) + m_obj->Close(kernel); + m_obj = o; + if (m_obj) + m_obj->Open(kernel); + } + constexpr void Reset(T* o) { - KScopedAutoObject(o).Swap(*this); + KScopedAutoObject(kernel, o).Swap(*this); } constexpr T* GetPointerUnsafe() { @@ -304,6 +300,7 @@ private: friend class KScopedAutoObject; private: + KernelCore& kernel; T* m_obj{}; private: diff --git a/src/core/hle/kernel/k_capabilities.cpp b/src/core/hle/kernel/k_capabilities.cpp index d2288c30d1..90f41dd1fc 100644 --- a/src/core/hle/kernel/k_capabilities.cpp +++ b/src/core/hle/kernel/k_capabilities.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -268,7 +271,8 @@ Result KCapabilities::SetHandleTableCapability(const u32 cap) { Result KCapabilities::SetDebugFlagsCapability(const u32 cap) { // Validate. const DebugFlags pack{cap}; - R_UNLESS(pack.reserved == 0, ResultReservedUsed); + // TODO: Enabling this breaks compatibility with HBloader and such + //R_UNLESS(pack.reserved == 0, ResultReservedUsed); DebugFlags debug_capabilities{m_debug_capabilities}; debug_capabilities.allow_debug.Assign(pack.allow_debug); diff --git a/src/core/hle/kernel/k_client_port.cpp b/src/core/hle/kernel/k_client_port.cpp index 68cea978a0..d13995988c 100644 --- a/src/core/hle/kernel/k_client_port.cpp +++ b/src/core/hle/kernel/k_client_port.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2021 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -15,7 +18,7 @@ namespace Kernel { KClientPort::KClientPort(KernelCore& kernel) : KSynchronizationObject{kernel} {} KClientPort::~KClientPort() = default; -void KClientPort::Initialize(KPort* parent, s32 max_sessions) { +void KClientPort::Initialize(KernelCore& kernel, KPort* parent, s32 max_sessions) { // Set member variables. m_num_sessions = 0; m_peak_sessions = 0; @@ -23,48 +26,48 @@ void KClientPort::Initialize(KPort* parent, s32 max_sessions) { m_max_sessions = max_sessions; } -void KClientPort::OnSessionFinalized() { - KScopedSchedulerLock sl{m_kernel}; +void KClientPort::OnSessionFinalized(KernelCore& kernel) { + KScopedSchedulerLock sl{kernel}; if (const auto prev = m_num_sessions--; prev == m_max_sessions) { - this->NotifyAvailable(); + this->NotifyAvailable(kernel); } } -void KClientPort::OnServerClosed() {} +void KClientPort::OnServerClosed(KernelCore& kernel) {} -bool KClientPort::IsLight() const { +bool KClientPort::IsLight(KernelCore& kernel) const { return this->GetParent()->IsLight(); } -bool KClientPort::IsServerClosed() const { - return this->GetParent()->IsServerClosed(); +bool KClientPort::IsServerClosed(KernelCore& kernel) const { + return this->GetParent()->IsServerClosed(kernel); } -void KClientPort::Destroy() { +void KClientPort::Destroy(KernelCore& kernel) { // Note with our parent that we're closed. - m_parent->OnClientClosed(); + m_parent->OnClientClosed(kernel); // Close our reference to our parent. - m_parent->Close(); + m_parent->Close(kernel); } -bool KClientPort::IsSignaled() const { +bool KClientPort::IsSignaled(KernelCore& kernel) const { return m_num_sessions.load() < m_max_sessions; } -Result KClientPort::CreateSession(KClientSession** out) { +Result KClientPort::CreateSession(KernelCore& kernel, KClientSession** out) { // Declare the session we're going to allocate. KSession* session{}; // Reserve a new session from the resource limit. - KScopedResourceReservation session_reservation(GetCurrentProcessPointer(m_kernel), + KScopedResourceReservation session_reservation(kernel, GetCurrentProcessPointer(kernel), LimitableResource::SessionCountMax); R_UNLESS(session_reservation.Succeeded(), ResultLimitReached); // Allocate a session normally. // TODO: Dynamic resource limits - session = KSession::Create(m_kernel); + session = KSession::Create(kernel); // Check that we successfully created a session. R_UNLESS(session != nullptr, ResultOutOfResource); @@ -72,7 +75,7 @@ Result KClientPort::CreateSession(KClientSession** out) { // Update the session counts. { ON_RESULT_FAILURE { - session->Close(); + session->Close(kernel); }; // Atomically increment the number of sessions. @@ -100,38 +103,37 @@ Result KClientPort::CreateSession(KClientSession** out) { } // Initialize the session. - session->Initialize(this, m_parent->GetName()); + session->Initialize(kernel, this, m_parent->GetName()); // Commit the session reservation. session_reservation.Commit(); // Register the session. - KSession::Register(m_kernel, session); + KSession::Register(kernel, session); ON_RESULT_FAILURE { - session->GetClientSession().Close(); - session->GetServerSession().Close(); + session->GetClientSession().Close(kernel); + session->GetServerSession().Close(kernel); }; // Enqueue the session with our parent. - R_TRY(m_parent->EnqueueSession(std::addressof(session->GetServerSession()))); + R_TRY(m_parent->EnqueueSession(kernel, std::addressof(session->GetServerSession()))); // We succeeded, so set the output. *out = std::addressof(session->GetClientSession()); R_SUCCEED(); } -Result KClientPort::CreateLightSession(KLightClientSession** out) { +Result KClientPort::CreateLightSession(KernelCore& kernel, KLightClientSession** out) { // Declare the session we're going to allocate. KLightSession* session{}; // Reserve a new session from the resource limit. - KScopedResourceReservation session_reservation(GetCurrentProcessPointer(m_kernel), - Svc::LimitableResource::SessionCountMax); + KScopedResourceReservation session_reservation(kernel, GetCurrentProcessPointer(kernel), Svc::LimitableResource::SessionCountMax); R_UNLESS(session_reservation.Succeeded(), ResultLimitReached); // Allocate a session normally. // TODO: Dynamic resource limits - session = KLightSession::Create(m_kernel); + session = KLightSession::Create(kernel); // Check that we successfully created a session. R_UNLESS(session != nullptr, ResultOutOfResource); @@ -139,7 +141,7 @@ Result KClientPort::CreateLightSession(KLightClientSession** out) { // Update the session counts. { ON_RESULT_FAILURE { - session->Close(); + session->Close(kernel); }; // Atomically increment the number of sessions. @@ -167,20 +169,20 @@ Result KClientPort::CreateLightSession(KLightClientSession** out) { } // Initialize the session. - session->Initialize(this, m_parent->GetName()); + session->Initialize(kernel, this, m_parent->GetName()); // Commit the session reservation. session_reservation.Commit(); // Register the session. - KLightSession::Register(m_kernel, session); + KLightSession::Register(kernel, session); ON_RESULT_FAILURE { - session->GetClientSession().Close(); - session->GetServerSession().Close(); + session->GetClientSession().Close(kernel); + session->GetServerSession().Close(kernel); }; // Enqueue the session with our parent. - R_TRY(m_parent->EnqueueSession(std::addressof(session->GetServerSession()))); + R_TRY(m_parent->EnqueueSession(kernel, std::addressof(session->GetServerSession()))); // We succeeded, so set the output. *out = std::addressof(session->GetClientSession()); diff --git a/src/core/hle/kernel/k_client_port.h b/src/core/hle/kernel/k_client_port.h index 28b3326081..3e48eeaa00 100644 --- a/src/core/hle/kernel/k_client_port.h +++ b/src/core/hle/kernel/k_client_port.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -23,9 +26,9 @@ public: explicit KClientPort(KernelCore& kernel); ~KClientPort() override; - void Initialize(KPort* parent, s32 max_sessions); - void OnSessionFinalized(); - void OnServerClosed(); + void Initialize(KernelCore& kernel, KPort* parent, s32 max_sessions); + void OnSessionFinalized(KernelCore& kernel); + void OnServerClosed(KernelCore& kernel); const KPort* GetParent() const { return m_parent; @@ -44,15 +47,15 @@ public: return m_max_sessions; } - bool IsLight() const; - bool IsServerClosed() const; + bool IsLight(KernelCore& kernel) const; + bool IsServerClosed(KernelCore& kernel) const; // Overridden virtual functions. - void Destroy() override; - bool IsSignaled() const override; + void Destroy(KernelCore& kernel) override; + bool IsSignaled(KernelCore& kernel) const override; - Result CreateSession(KClientSession** out); - Result CreateLightSession(KLightClientSession** out); + Result CreateSession(KernelCore& kernel, KClientSession** out); + Result CreateLightSession(KernelCore& kernel, KLightClientSession** out); private: std::atomic m_num_sessions{}; diff --git a/src/core/hle/kernel/k_client_session.cpp b/src/core/hle/kernel/k_client_session.cpp index 3e01e3b67f..95cb9896b9 100644 --- a/src/core/hle/kernel/k_client_session.cpp +++ b/src/core/hle/kernel/k_client_session.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -13,41 +16,41 @@ namespace Kernel { KClientSession::KClientSession(KernelCore& kernel) : KAutoObject{kernel} {} KClientSession::~KClientSession() = default; -void KClientSession::Destroy() { - m_parent->OnClientClosed(); - m_parent->Close(); +void KClientSession::Destroy(KernelCore& kernel) { + m_parent->OnClientClosed(kernel); + m_parent->Close(kernel); } void KClientSession::OnServerClosed() {} -Result KClientSession::SendSyncRequest(uintptr_t address, size_t size) { +Result KClientSession::SendSyncRequest(KernelCore& kernel, uintptr_t address, size_t size) { // Create a session request. - KSessionRequest* request = KSessionRequest::Create(m_kernel); + KSessionRequest* request = KSessionRequest::Create(kernel); R_UNLESS(request != nullptr, ResultOutOfResource); SCOPE_EXIT { - request->Close(); + request->Close(kernel); }; // Initialize the request. - request->Initialize(nullptr, address, size); + request->Initialize(kernel, nullptr, address, size); // Send the request. - R_RETURN(m_parent->OnRequest(request)); + R_RETURN(m_parent->OnRequest(kernel, request)); } -Result KClientSession::SendAsyncRequest(KEvent* event, uintptr_t address, size_t size) { +Result KClientSession::SendAsyncRequest(KernelCore& kernel, KEvent* event, uintptr_t address, size_t size) { // Create a session request. - KSessionRequest* request = KSessionRequest::Create(m_kernel); + KSessionRequest* request = KSessionRequest::Create(kernel); R_UNLESS(request != nullptr, ResultOutOfResource); SCOPE_EXIT { - request->Close(); + request->Close(kernel); }; // Initialize the request. - request->Initialize(event, address, size); + request->Initialize(kernel, event, address, size); // Send the request. - R_RETURN(m_parent->OnRequest(request)); + R_RETURN(m_parent->OnRequest(kernel, request)); } } // namespace Kernel diff --git a/src/core/hle/kernel/k_client_session.h b/src/core/hle/kernel/k_client_session.h index a39213e17f..876c3fc789 100644 --- a/src/core/hle/kernel/k_client_session.h +++ b/src/core/hle/kernel/k_client_session.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -26,14 +29,14 @@ public: m_parent = parent; } - void Destroy() override; + void Destroy(KernelCore& kernel) override; KSession* GetParent() const { return m_parent; } - Result SendSyncRequest(uintptr_t address, size_t size); - Result SendAsyncRequest(KEvent* event, uintptr_t address, size_t size); + Result SendSyncRequest(KernelCore& kernel, uintptr_t address, size_t size); + Result SendAsyncRequest(KernelCore& kernel, KEvent* event, uintptr_t address, size_t size); void OnServerClosed(); diff --git a/src/core/hle/kernel/k_code_memory.cpp b/src/core/hle/kernel/k_code_memory.cpp index 7454be55c9..aa8edce832 100644 --- a/src/core/hle/kernel/k_code_memory.cpp +++ b/src/core/hle/kernel/k_code_memory.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -19,16 +22,15 @@ namespace Kernel { KCodeMemory::KCodeMemory(KernelCore& kernel) : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock(kernel) {} -Result KCodeMemory::Initialize(Core::DeviceMemory& device_memory, KProcessAddress addr, - size_t size) { +Result KCodeMemory::Initialize(KernelCore& kernel, Core::DeviceMemory& device_memory, KProcessAddress addr, size_t size) { // Set members. - m_owner = GetCurrentProcessPointer(m_kernel); + m_owner = GetCurrentProcessPointer(kernel); // Get the owner page table. auto& page_table = m_owner->GetPageTable(); // Construct the page group. - m_page_group.emplace(m_kernel, page_table.GetBlockInfoManager()); + m_page_group.emplace(kernel, page_table.GetBlockInfoManager()); // Lock the memory. R_TRY(page_table.LockForCodeMemory(std::addressof(*m_page_group), addr, size)) @@ -39,7 +41,7 @@ Result KCodeMemory::Initialize(Core::DeviceMemory& device_memory, KProcessAddres } // Set remaining tracking members. - m_owner->Open(); + m_owner->Open(kernel); m_address = addr; m_is_initialized = true; m_is_owner_mapped = false; @@ -49,7 +51,7 @@ Result KCodeMemory::Initialize(Core::DeviceMemory& device_memory, KProcessAddres R_SUCCEED(); } -void KCodeMemory::Finalize() { +void KCodeMemory::Finalize(KernelCore& kernel) { // Unlock. if (!m_is_mapped && !m_is_owner_mapped) { const size_t size = m_page_group->GetNumPages() * PageSize; @@ -57,14 +59,14 @@ void KCodeMemory::Finalize() { } // Close the page group. - m_page_group->Close(); + m_page_group->Close(kernel); m_page_group->Finalize(); // Close our reference to our owner. - m_owner->Close(); + m_owner->Close(kernel); } -Result KCodeMemory::Map(KProcessAddress address, size_t size) { +Result KCodeMemory::Map(KernelCore& kernel, KProcessAddress address, size_t size) { // Validate the size. R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize); @@ -75,7 +77,7 @@ Result KCodeMemory::Map(KProcessAddress address, size_t size) { R_UNLESS(!m_is_mapped, ResultInvalidState); // Map the memory. - R_TRY(GetCurrentProcess(m_kernel).GetPageTable().MapPageGroup( + R_TRY(GetCurrentProcess(kernel).GetPageTable().MapPageGroup( address, *m_page_group, KMemoryState::CodeOut, KMemoryPermission::UserReadWrite)); // Mark ourselves as mapped. @@ -84,7 +86,7 @@ Result KCodeMemory::Map(KProcessAddress address, size_t size) { R_SUCCEED(); } -Result KCodeMemory::Unmap(KProcessAddress address, size_t size) { +Result KCodeMemory::Unmap(KernelCore& kernel, KProcessAddress address, size_t size) { // Validate the size. R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize); @@ -92,7 +94,7 @@ Result KCodeMemory::Unmap(KProcessAddress address, size_t size) { KScopedLightLock lk(m_lock); // Unmap the memory. - R_TRY(GetCurrentProcess(m_kernel).GetPageTable().UnmapPageGroup(address, *m_page_group, + R_TRY(GetCurrentProcess(kernel).GetPageTable().UnmapPageGroup(address, *m_page_group, KMemoryState::CodeOut)); // Mark ourselves as unmapped. @@ -101,7 +103,7 @@ Result KCodeMemory::Unmap(KProcessAddress address, size_t size) { R_SUCCEED(); } -Result KCodeMemory::MapToOwner(KProcessAddress address, size_t size, Svc::MemoryPermission perm) { +Result KCodeMemory::MapToOwner(KernelCore& kernel, KProcessAddress address, size_t size, Svc::MemoryPermission perm) { // Validate the size. R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize); @@ -135,7 +137,7 @@ Result KCodeMemory::MapToOwner(KProcessAddress address, size_t size, Svc::Memory R_SUCCEED(); } -Result KCodeMemory::UnmapFromOwner(KProcessAddress address, size_t size) { +Result KCodeMemory::UnmapFromOwner(KernelCore& kernel, KProcessAddress address, size_t size) { // Validate the size. R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize); diff --git a/src/core/hle/kernel/k_code_memory.h b/src/core/hle/kernel/k_code_memory.h index 26fe6b3dc6..4f3aab6b4f 100644 --- a/src/core/hle/kernel/k_code_memory.h +++ b/src/core/hle/kernel/k_code_memory.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -31,18 +34,18 @@ class KCodeMemory final public: explicit KCodeMemory(KernelCore& kernel); - Result Initialize(Core::DeviceMemory& device_memory, KProcessAddress address, size_t size); - void Finalize() override; + Result Initialize(KernelCore& kernel, Core::DeviceMemory& device_memory, KProcessAddress address, size_t size); + void Finalize(KernelCore& kernel) override; - Result Map(KProcessAddress address, size_t size); - Result Unmap(KProcessAddress address, size_t size); - Result MapToOwner(KProcessAddress address, size_t size, Svc::MemoryPermission perm); - Result UnmapFromOwner(KProcessAddress address, size_t size); + Result Map(KernelCore& kernel, KProcessAddress address, size_t size); + Result Unmap(KernelCore& kernel, KProcessAddress address, size_t size); + Result MapToOwner(KernelCore& kernel, KProcessAddress address, size_t size, Svc::MemoryPermission perm); + Result UnmapFromOwner(KernelCore& kernel, KProcessAddress address, size_t size); bool IsInitialized() const override { return m_is_initialized; } - static void PostDestroy(uintptr_t arg) {} + static void PostDestroy(KernelCore& kernel, uintptr_t arg) {} KProcess* GetOwner() const override { return m_owner; diff --git a/src/core/hle/kernel/k_condition_variable.cpp b/src/core/hle/kernel/k_condition_variable.cpp index 1a7431df07..3cf8b28e17 100644 --- a/src/core/hle/kernel/k_condition_variable.cpp +++ b/src/core/hle/kernel/k_condition_variable.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -66,14 +66,15 @@ bool UpdateLockAtomic(KernelCore& kernel, u32* out, KProcessAddress address, u32 class ThreadQueueImplForKConditionVariableWaitForAddress final : public KThreadQueue { public: explicit ThreadQueueImplForKConditionVariableWaitForAddress(KernelCore& kernel) - : KThreadQueue(kernel) {} + : KThreadQueue(kernel) + {} - void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { + virtual void CancelWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { // Remove the thread as a waiter from its owner. - waiting_thread->GetLockOwner()->RemoveWaiter(waiting_thread); + waiting_thread->GetLockOwner(kernel)->RemoveWaiter(kernel, waiting_thread); // Invoke the base cancel wait handler. - KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + KThreadQueue::CancelWait(kernel, waiting_thread, wait_result, cancel_timer_task); } }; @@ -82,14 +83,15 @@ private: KConditionVariable::ThreadTree* m_tree; public: - explicit ThreadQueueImplForKConditionVariableWaitConditionVariable( - KernelCore& kernel, KConditionVariable::ThreadTree* t) - : KThreadQueue(kernel), m_tree(t) {} + explicit ThreadQueueImplForKConditionVariableWaitConditionVariable(KernelCore& kernel, KConditionVariable::ThreadTree* t) + : KThreadQueue(kernel) + , m_tree(t) + {} - void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { + void CancelWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { // Remove the thread as a waiter from its owner. - if (KThread* owner = waiting_thread->GetLockOwner(); owner != nullptr) { - owner->RemoveWaiter(waiting_thread); + if (KThread* owner = waiting_thread->GetLockOwner(kernel); owner != nullptr) { + owner->RemoveWaiter(kernel, waiting_thread); } // If the thread is waiting on a condvar, remove it from the tree. @@ -99,14 +101,15 @@ public: } // Invoke the base cancel wait handler. - KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + KThreadQueue::CancelWait(kernel, waiting_thread, wait_result, cancel_timer_task); } }; } // namespace KConditionVariable::KConditionVariable(Core::System& system) - : m_system{system}, m_kernel{system.Kernel()} {} + : m_system{system} +{} KConditionVariable::~KConditionVariable() = default; @@ -119,8 +122,7 @@ Result KConditionVariable::SignalToAddress(KernelCore& kernel, KProcessAddress a // Remove waiter thread. bool has_waiters{}; - KThread* const next_owner_thread = - owner_thread->RemoveUserWaiterByKey(std::addressof(has_waiters), addr); + KThread* const next_owner_thread = owner_thread->RemoveUserWaiterByKey(kernel, std::addressof(has_waiters), addr); // Determine the next tag. u32 next_value{}; @@ -144,15 +146,14 @@ Result KConditionVariable::SignalToAddress(KernelCore& kernel, KProcessAddress a // If necessary, signal the next owner thread. if (next_owner_thread != nullptr) { - next_owner_thread->EndWait(result); + next_owner_thread->EndWait(kernel, result); } R_RETURN(result); } } -Result KConditionVariable::WaitForAddress(KernelCore& kernel, Handle handle, KProcessAddress addr, - u32 value) { +Result KConditionVariable::WaitForAddress(KernelCore& kernel, Handle handle, KProcessAddress addr, u32 value) { KThread* cur_thread = GetCurrentThreadPointer(kernel); ThreadQueueImplForKConditionVariableWaitForAddress wait_queue(kernel); @@ -173,30 +174,30 @@ Result KConditionVariable::WaitForAddress(KernelCore& kernel, Handle handle, KPr // Get the lock owner thread. owner_thread = GetCurrentProcess(kernel) - .GetHandleTable() - .GetObjectWithoutPseudoHandle(handle) - .ReleasePointerUnsafe(); + .GetHandleTable() + .GetObjectWithoutPseudoHandle(kernel, handle) + .ReleasePointerUnsafe(); R_UNLESS(owner_thread != nullptr, ResultInvalidHandle); // Update the lock. cur_thread->SetUserAddressKey(addr, value); - owner_thread->AddWaiter(cur_thread); + owner_thread->AddWaiter(kernel, cur_thread); // Begin waiting. - cur_thread->BeginWait(std::addressof(wait_queue)); + cur_thread->BeginWait(kernel, std::addressof(wait_queue)); cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar); } // Close our reference to the owner thread, now that the wait is over. - owner_thread->Close(); + owner_thread->Close(kernel); // Get the wait result. R_RETURN(cur_thread->GetWaitResult()); } -void KConditionVariable::SignalImpl(KThread* thread) { +void KConditionVariable::SignalImpl(KernelCore& kernel, KThread* thread) { // Check pre-conditions. - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); // Update the tag. KProcessAddress address = thread->GetAddressKey(); @@ -211,35 +212,33 @@ void KConditionVariable::SignalImpl(KThread* thread) { // TODO(bunnei): We should call CanAccessAtomic(..) here. can_access = true; if (can_access) { - UpdateLockAtomic(m_kernel, std::addressof(prev_tag), address, own_tag, - Svc::HandleWaitMask); + UpdateLockAtomic(kernel, std::addressof(prev_tag), address, own_tag, Svc::HandleWaitMask); } } if (can_access) { if (prev_tag == Svc::InvalidHandle) { // If nobody held the lock previously, we're all good. - thread->EndWait(ResultSuccess); + thread->EndWait(kernel, ResultSuccess); } else { // Get the previous owner. - KThread* owner_thread = GetCurrentProcess(m_kernel) - .GetHandleTable() - .GetObjectWithoutPseudoHandle( - static_cast(prev_tag & ~Svc::HandleWaitMask)) - .ReleasePointerUnsafe(); + KThread* owner_thread = GetCurrentProcess(kernel) + .GetHandleTable() + .GetObjectWithoutPseudoHandle(kernel, Handle(prev_tag & ~Svc::HandleWaitMask)) + .ReleasePointerUnsafe(); if (owner_thread) { // Add the thread as a waiter on the owner. - owner_thread->AddWaiter(thread); - owner_thread->Close(); + owner_thread->AddWaiter(kernel, thread); + owner_thread->Close(kernel); } else { // The lock was tagged with a thread that doesn't exist. - thread->EndWait(ResultInvalidState); + thread->EndWait(kernel, ResultInvalidState); } } } else { // If the address wasn't accessible, note so. - thread->EndWait(ResultInvalidCurrentMemory); + thread->EndWait(kernel, ResultInvalidCurrentMemory); } } @@ -247,17 +246,16 @@ void KConditionVariable::Signal(u64 cv_key, s32 count) { // Perform signaling. s32 num_waiters{}; { - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(m_system.Kernel()); auto it = m_tree.nfind_key({cv_key, -1}); - while ((it != m_tree.end()) && (count <= 0 || num_waiters < count) && - (it->GetConditionVariableKey() == cv_key)) { + while ((it != m_tree.end()) && (count <= 0 || num_waiters < count) && (it->GetConditionVariableKey() == cv_key)) { KThread* target_thread = std::addressof(*it); it = m_tree.erase(it); target_thread->ClearConditionVariable(); - this->SignalImpl(target_thread); + this->SignalImpl(m_system.Kernel(), target_thread); ++num_waiters; } @@ -265,20 +263,20 @@ void KConditionVariable::Signal(u64 cv_key, s32 count) { // If we have no waiters, clear the has waiter flag. if (it == m_tree.end() || it->GetConditionVariableKey() != cv_key) { constexpr u32 HasNoWaiterFlag = 0; - WriteToUser(m_kernel, cv_key, HasNoWaiterFlag); + WriteToUser(m_system.Kernel(), cv_key, HasNoWaiterFlag); } } } Result KConditionVariable::Wait(KProcessAddress addr, u64 key, u32 value, s64 timeout) { // Prepare to wait. - KThread* cur_thread = GetCurrentThreadPointer(m_kernel); + KThread* cur_thread = GetCurrentThreadPointer(m_system.Kernel()); KHardwareTimer* timer{}; - ThreadQueueImplForKConditionVariableWaitConditionVariable wait_queue(m_kernel, + ThreadQueueImplForKConditionVariableWaitConditionVariable wait_queue(m_system.Kernel(), std::addressof(m_tree)); { - KScopedSchedulerLockAndSleep slp(m_kernel, std::addressof(timer), cur_thread, timeout); + KScopedSchedulerLockAndSleep slp(m_system.Kernel(), std::addressof(timer), cur_thread, timeout); // Check that the thread isn't terminating. if (cur_thread->IsTerminationRequested()) { @@ -291,7 +289,7 @@ Result KConditionVariable::Wait(KProcessAddress addr, u64 key, u32 value, s64 ti // Remove waiter thread. bool has_waiters{}; KThread* next_owner_thread = - cur_thread->RemoveUserWaiterByKey(std::addressof(has_waiters), addr); + cur_thread->RemoveUserWaiterByKey(m_system.Kernel(), std::addressof(has_waiters), addr); // Update for the next owner thread. u32 next_value{}; @@ -303,18 +301,18 @@ Result KConditionVariable::Wait(KProcessAddress addr, u64 key, u32 value, s64 ti } // Wake up the next owner. - next_owner_thread->EndWait(ResultSuccess); + next_owner_thread->EndWait(m_system.Kernel(), ResultSuccess); } // Write to the cv key. { constexpr u32 HasWaiterFlag = 1; - WriteToUser(m_kernel, key, HasWaiterFlag); + WriteToUser(m_system.Kernel(), key, HasWaiterFlag); std::atomic_thread_fence(std::memory_order_seq_cst); } // Write the value to userspace. - if (!WriteToUser(m_kernel, addr, next_value)) { + if (!WriteToUser(m_system.Kernel(), addr, next_value)) { slp.CancelSleep(); R_THROW(ResultInvalidCurrentMemory); } @@ -329,7 +327,7 @@ Result KConditionVariable::Wait(KProcessAddress addr, u64 key, u32 value, s64 ti // Begin waiting. wait_queue.SetHardwareTimer(timer); - cur_thread->BeginWait(std::addressof(wait_queue)); + cur_thread->BeginWait(m_system.Kernel(), std::addressof(wait_queue)); cur_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::ConditionVar); } diff --git a/src/core/hle/kernel/k_condition_variable.h b/src/core/hle/kernel/k_condition_variable.h index 2620c8e39e..ca80de8837 100644 --- a/src/core/hle/kernel/k_condition_variable.h +++ b/src/core/hle/kernel/k_condition_variable.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -26,19 +29,17 @@ public: // Arbitration. static Result SignalToAddress(KernelCore& kernel, KProcessAddress addr); - static Result WaitForAddress(KernelCore& kernel, Handle handle, KProcessAddress addr, - u32 value); + static Result WaitForAddress(KernelCore& kernel, Handle handle, KProcessAddress addr, u32 value); // Condition variable. void Signal(u64 cv_key, s32 count); Result Wait(KProcessAddress addr, u64 key, u32 value, s64 timeout); private: - void SignalImpl(KThread* thread); + void SignalImpl(KernelCore& kernel, KThread* thread); private: Core::System& m_system; - KernelCore& m_kernel; ThreadTree m_tree{}; }; diff --git a/src/core/hle/kernel/k_debug.h b/src/core/hle/kernel/k_debug.h index 2290e3bcab..92ff70df74 100644 --- a/src/core/hle/kernel/k_debug.h +++ b/src/core/hle/kernel/k_debug.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -14,7 +17,7 @@ class KDebug final : public KAutoObjectWithSlabHeapAndContainerOpen(); + m_owner->Open(kernel); } // Mark initialized. m_initialized = true; } -void KEvent::Finalize() { - KAutoObjectWithSlabHeapAndContainer::Finalize(); +void KEvent::Finalize(KernelCore& kernel) { + KAutoObjectWithSlabHeapAndContainer::Finalize(kernel); } -Result KEvent::Signal() { - KScopedSchedulerLock sl{m_kernel}; +Result KEvent::Signal(KernelCore& kernel) { + KScopedSchedulerLock sl{kernel}; R_SUCCEED_IF(m_readable_event_destroyed); - return m_readable_event.Signal(); + return m_readable_event.Signal(kernel); } -Result KEvent::Clear() { - KScopedSchedulerLock sl{m_kernel}; +Result KEvent::Clear(KernelCore& kernel) { + KScopedSchedulerLock sl{kernel}; R_SUCCEED_IF(m_readable_event_destroyed); - return m_readable_event.Clear(); + return m_readable_event.Clear(kernel); } -void KEvent::PostDestroy(uintptr_t arg) { +void KEvent::PostDestroy(KernelCore& kernel, uintptr_t arg) { // Release the event count resource the owner process holds. KProcess* owner = reinterpret_cast(arg); if (owner != nullptr) { - owner->GetResourceLimit()->Release(LimitableResource::EventCountMax, 1); - owner->Close(); + owner->GetResourceLimit()->Release(kernel, LimitableResource::EventCountMax, 1); + owner->Close(kernel); } } diff --git a/src/core/hle/kernel/k_event.h b/src/core/hle/kernel/k_event.h index f522b0a84c..75b03efa69 100644 --- a/src/core/hle/kernel/k_event.h +++ b/src/core/hle/kernel/k_event.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -19,9 +22,9 @@ public: explicit KEvent(KernelCore& kernel); ~KEvent() override; - void Initialize(KProcess* owner); + void Initialize(KernelCore& kernel, KProcess* owner); - void Finalize() override; + void Finalize(KernelCore& kernel) override; bool IsInitialized() const override { return m_initialized; @@ -39,10 +42,10 @@ public: return m_readable_event; } - static void PostDestroy(uintptr_t arg); + static void PostDestroy(KernelCore& kernel, uintptr_t arg); - Result Signal(); - Result Clear(); + Result Signal(KernelCore& kernel); + Result Clear(KernelCore& kernel); void OnReadableEventDestroyed() { m_readable_event_destroyed = true; diff --git a/src/core/hle/kernel/k_handle_table.cpp b/src/core/hle/kernel/k_handle_table.cpp index 04ca235344..91d805fa79 100644 --- a/src/core/hle/kernel/k_handle_table.cpp +++ b/src/core/hle/kernel/k_handle_table.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -6,28 +6,27 @@ #include "core/hle/kernel/k_handle_table.h" #include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/kernel.h" namespace Kernel { -void KHandleTable::Finalize() { +void KHandleTable::Finalize(KernelCore& kernel) { // Get the table and clear our record of it. u16 saved_table_size = 0; { - KScopedDisableDispatch dd{m_kernel}; + KScopedDisableDispatch dd{kernel}; KScopedSpinLock lk(m_lock); std::swap(m_table_size, saved_table_size); } // Close and free all entries. - for (size_t i = 0; i < saved_table_size; i++) { - if (KAutoObject* obj = m_objects[i]; obj != nullptr) { - obj->Close(); - } - } + for (size_t i = 0; i < saved_table_size; i++) + if (KAutoObject* obj = m_objects[i]; obj != nullptr) + obj->Close(kernel); } -bool KHandleTable::Remove(Handle handle) { +bool KHandleTable::Remove(KernelCore& kernel, Handle handle) { // Don't allow removal of a pseudo-handle. if (Svc::IsPseudoHandle(handle)) [[unlikely]] { return false; @@ -42,7 +41,7 @@ bool KHandleTable::Remove(Handle handle) { // Find the object and free the entry. KAutoObject* obj = nullptr; { - KScopedDisableDispatch dd{m_kernel}; + KScopedDisableDispatch dd{kernel}; KScopedSpinLock lk(m_lock); if (this->IsValidHandle(handle)) [[likely]] { @@ -56,13 +55,13 @@ bool KHandleTable::Remove(Handle handle) { } // Close the object. - m_kernel.UnregisterInUseObject(obj); - obj->Close(); + kernel.UnregisterInUseObject(obj); + obj->Close(kernel); return true; } -Result KHandleTable::Add(Handle* out_handle, KAutoObject* obj) { - KScopedDisableDispatch dd{m_kernel}; +Result KHandleTable::Add(KernelCore& kernel, Handle* out_handle, KAutoObject* obj) { + KScopedDisableDispatch dd{kernel}; KScopedSpinLock lk(m_lock); // Never exceed our capacity. @@ -76,7 +75,7 @@ Result KHandleTable::Add(Handle* out_handle, KAutoObject* obj) { m_entry_infos[index].linear_id = linear_id; m_objects[index] = obj; - obj->Open(); + obj->Open(kernel); *out_handle = EncodeHandle(static_cast(index), linear_id); } @@ -84,24 +83,22 @@ Result KHandleTable::Add(Handle* out_handle, KAutoObject* obj) { R_SUCCEED(); } -KScopedAutoObject KHandleTable::GetObjectForIpc(Handle handle, - KThread* cur_thread) const { +KScopedAutoObject KHandleTable::GetObjectForIpc(KernelCore& kernel, Handle handle, KThread* cur_thread) const { // Handle pseudo-handles. ASSERT(cur_thread != nullptr); if (handle == Svc::PseudoHandle::CurrentProcess) { auto* const cur_process = cur_thread->GetOwnerProcess(); ASSERT(cur_process != nullptr); - return cur_process; + return {kernel, cur_process}; } if (handle == Svc::PseudoHandle::CurrentThread) { - return cur_thread; + return {kernel, cur_thread}; } - - return GetObjectForIpcWithoutPseudoHandle(handle); + return GetObjectForIpcWithoutPseudoHandle(kernel, handle); } -Result KHandleTable::Reserve(Handle* out_handle) { - KScopedDisableDispatch dd{m_kernel}; +Result KHandleTable::Reserve(KernelCore& kernel, Handle* out_handle) { + KScopedDisableDispatch dd{kernel}; KScopedSpinLock lk(m_lock); // Never exceed our capacity. @@ -111,8 +108,8 @@ Result KHandleTable::Reserve(Handle* out_handle) { R_SUCCEED(); } -void KHandleTable::Unreserve(Handle handle) { - KScopedDisableDispatch dd{m_kernel}; +void KHandleTable::Unreserve(KernelCore& kernel, Handle handle) { + KScopedDisableDispatch dd{kernel}; KScopedSpinLock lk(m_lock); // Unpack the handle. @@ -130,8 +127,8 @@ void KHandleTable::Unreserve(Handle handle) { } } -void KHandleTable::Register(Handle handle, KAutoObject* obj) { - KScopedDisableDispatch dd{m_kernel}; +void KHandleTable::Register(KernelCore& kernel, Handle handle, KAutoObject* obj) { + KScopedDisableDispatch dd{kernel}; KScopedSpinLock lk(m_lock); // Unpack the handle. @@ -149,7 +146,7 @@ void KHandleTable::Register(Handle handle, KAutoObject* obj) { m_entry_infos[index].linear_id = static_cast(linear_id); m_objects[index] = obj; - obj->Open(); + obj->Open(kernel); } } diff --git a/src/core/hle/kernel/k_handle_table.h b/src/core/hle/kernel/k_handle_table.h index 731a5284dc..0704fd56ef 100644 --- a/src/core/hle/kernel/k_handle_table.h +++ b/src/core/hle/kernel/k_handle_table.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -31,14 +31,14 @@ public: static constexpr size_t MaxTableSize = 1024; public: - explicit KHandleTable(KernelCore& kernel) : m_kernel(kernel) {} + explicit KHandleTable(KernelCore& kernel) {} - Result Initialize(s32 size) { + Result Initialize(KernelCore& kernel, s32 size) { // Check that the table size is valid. R_UNLESS(size <= static_cast(MaxTableSize), ResultOutOfMemory); // Lock. - KScopedDisableDispatch dd{m_kernel}; + KScopedDisableDispatch dd{kernel}; KScopedSpinLock lk(m_lock); // Initialize all fields. @@ -68,76 +68,72 @@ public: return m_max_count; } - void Finalize(); - bool Remove(Handle handle); + void Finalize(KernelCore& kernel); + bool Remove(KernelCore& kernel, Handle handle); template - KScopedAutoObject GetObjectWithoutPseudoHandle(Handle handle) const { + KScopedAutoObject GetObjectWithoutPseudoHandle(KernelCore& kernel, Handle handle) const { // Lock and look up in table. - KScopedDisableDispatch dd{m_kernel}; + KScopedDisableDispatch dd{kernel}; KScopedSpinLock lk(m_lock); if constexpr (std::is_same_v) { - return this->GetObjectImpl(handle); + return {kernel, this->GetObjectImpl(handle)}; } else { if (auto* obj = this->GetObjectImpl(handle); obj != nullptr) [[likely]] { - return obj->DynamicCast(); + return {kernel, obj->DynamicCast()}; } else { - return nullptr; + return {kernel, nullptr}; } } } template - KScopedAutoObject GetObject(Handle handle) const { + KScopedAutoObject GetObject(KernelCore& kernel, Handle handle) const { // Handle pseudo-handles. if constexpr (std::derived_from) { if (handle == Svc::PseudoHandle::CurrentProcess) { - auto* const cur_process = GetCurrentProcessPointer(m_kernel); + auto* const cur_process = GetCurrentProcessPointer(kernel); ASSERT(cur_process != nullptr); - return cur_process; + return {kernel, cur_process}; } } else if constexpr (std::derived_from) { if (handle == Svc::PseudoHandle::CurrentThread) { - auto* const cur_thread = GetCurrentThreadPointer(m_kernel); + auto* const cur_thread = GetCurrentThreadPointer(kernel); ASSERT(cur_thread != nullptr); - return cur_thread; + return {kernel, cur_thread}; } } - - return this->template GetObjectWithoutPseudoHandle(handle); + return this->template GetObjectWithoutPseudoHandle(kernel, handle); } - KScopedAutoObject GetObjectForIpcWithoutPseudoHandle(Handle handle) const { + KScopedAutoObject GetObjectForIpcWithoutPseudoHandle(KernelCore& kernel, Handle handle) const { // Lock and look up in table. - KScopedDisableDispatch dd{m_kernel}; + KScopedDisableDispatch dd{kernel}; + KScopedSpinLock lk(m_lock); + return {kernel, this->GetObjectImpl(handle)}; + } + KScopedAutoObject GetObjectForIpc(KernelCore& kernel, Handle handle, KThread* cur_thread) const; + KScopedAutoObject GetObjectByIndex(KernelCore& kernel, Handle* out_handle, size_t index) const { + KScopedDisableDispatch dd{kernel}; KScopedSpinLock lk(m_lock); - return this->GetObjectImpl(handle); + return {kernel, this->GetObjectByIndexImpl(out_handle, index)}; } - KScopedAutoObject GetObjectForIpc(Handle handle, KThread* cur_thread) const; + Result Reserve(KernelCore& kernel, Handle* out_handle); + void Unreserve(KernelCore& kernel, Handle handle); - KScopedAutoObject GetObjectByIndex(Handle* out_handle, size_t index) const { - KScopedDisableDispatch dd{m_kernel}; - KScopedSpinLock lk(m_lock); - - return this->GetObjectByIndexImpl(out_handle, index); - } - - Result Reserve(Handle* out_handle); - void Unreserve(Handle handle); - - Result Add(Handle* out_handle, KAutoObject* obj); - void Register(Handle handle, KAutoObject* obj); + Result Add(KernelCore& kernel, Handle* out_handle, KAutoObject* obj); + void Register(KernelCore& kernel, Handle handle, KAutoObject* obj); template - bool GetMultipleObjects(T** out, const Handle* handles, size_t num_handles) const { + bool GetMultipleObjects(KernelCore& kernel, T** out, const Handle* handles, size_t num_handles) const { // Try to convert and open all the handles. size_t num_opened; { // Lock the table. - KScopedDisableDispatch dd{m_kernel}; + KScopedDisableDispatch dd{kernel}; KScopedSpinLock lk(m_lock); for (num_opened = 0; num_opened < num_handles; num_opened++) { // Get the current handle. @@ -150,14 +146,14 @@ public: } // Cast the current object to the desired type. - T* cur_t = cur_object->DynamicCast(); - if (cur_t == nullptr) [[unlikely]] { + T* cur_thread = cur_object->DynamicCast(); + if (cur_thread == nullptr) [[unlikely]] { break; } // Open a reference to the current object. - cur_t->Open(); - out[num_opened] = cur_t; + cur_thread->Open(kernel); + out[num_opened] = cur_thread; } } @@ -168,7 +164,7 @@ public: // If we didn't convert entry object, close the ones we opened. for (size_t i = 0; i < num_opened; i++) { - out[i]->Close(); + out[i]->Close(kernel); } return false; @@ -177,13 +173,9 @@ public: private: s32 AllocateEntry() { ASSERT(m_count < m_table_size); - const auto index = m_free_head_index; - m_free_head_index = m_entry_infos[index].GetNextFreeIndex(); - m_max_count = (std::max)(m_max_count, ++m_count); - return index; } @@ -302,7 +294,6 @@ private: }; private: - KernelCore& m_kernel; std::array m_entry_infos{}; std::array m_objects{}; mutable KSpinLock m_lock; diff --git a/src/core/hle/kernel/k_hardware_timer_base.h b/src/core/hle/kernel/k_hardware_timer_base.h index 6318b35bdd..75a4a54763 100644 --- a/src/core/hle/kernel/k_hardware_timer_base.h +++ b/src/core/hle/kernel/k_hardware_timer_base.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -45,7 +48,7 @@ protected: this->RemoveTaskFromTree(task); // Handle the task. - task->OnTimer(); + task->OnTimer(m_kernel); } } diff --git a/src/core/hle/kernel/k_interrupt_manager.cpp b/src/core/hle/kernel/k_interrupt_manager.cpp index 22d79569a5..9bd182fd9b 100644 --- a/src/core/hle/kernel/k_interrupt_manager.cpp +++ b/src/core/hle/kernel/k_interrupt_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -18,19 +21,19 @@ void HandleInterrupt(KernelCore& kernel, s32 core_id) { if (auto* process = GetCurrentProcessPointer(kernel); process) { // If the user disable count is set, we may need to pin the current thread. - if (current_thread.GetUserDisableCount() && !process->GetPinnedThread(core_id)) { + if (current_thread.GetUserDisableCount(kernel) && !process->GetPinnedThread(core_id)) { KScopedSchedulerLock sl{kernel}; // Pin the current thread. - process->PinCurrentThread(); + process->PinCurrentThread(kernel); // Set the interrupt flag for the thread. - GetCurrentThread(kernel).SetInterruptFlag(); + GetCurrentThread(kernel).SetInterruptFlag(kernel); } } // Request interrupt scheduling. - kernel.CurrentScheduler()->RequestScheduleOnInterrupt(); + kernel.CurrentScheduler()->RequestScheduleOnInterrupt(kernel); } void SendInterProcessorInterrupt(KernelCore& kernel, u64 core_mask) { diff --git a/src/core/hle/kernel/k_light_client_session.cpp b/src/core/hle/kernel/k_light_client_session.cpp index 8ce3e1ae49..23f77deffd 100644 --- a/src/core/hle/kernel/k_light_client_session.cpp +++ b/src/core/hle/kernel/k_light_client_session.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -11,21 +14,21 @@ KLightClientSession::KLightClientSession(KernelCore& kernel) : KAutoObject(kerne KLightClientSession::~KLightClientSession() = default; -void KLightClientSession::Destroy() { - m_parent->OnClientClosed(); +void KLightClientSession::Destroy(KernelCore& kernel) { + m_parent->OnClientClosed(kernel); } -void KLightClientSession::OnServerClosed() {} +void KLightClientSession::OnServerClosed(KernelCore& kernel) {} -Result KLightClientSession::SendSyncRequest(u32* data) { +Result KLightClientSession::SendSyncRequest(KernelCore& kernel, u32* data) { // Get the request thread. - KThread* cur_thread = GetCurrentThreadPointer(m_kernel); + KThread* cur_thread = GetCurrentThreadPointer(kernel); // Set the light data. cur_thread->SetLightSessionData(data); // Send the request. - R_RETURN(m_parent->OnRequest(cur_thread)); + R_RETURN(m_parent->OnRequest(kernel, cur_thread)); } } // namespace Kernel diff --git a/src/core/hle/kernel/k_light_client_session.h b/src/core/hle/kernel/k_light_client_session.h index 881a15cbda..c0f48c295e 100644 --- a/src/core/hle/kernel/k_light_client_session.h +++ b/src/core/hle/kernel/k_light_client_session.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -22,15 +25,15 @@ public: m_parent = parent; } - virtual void Destroy() override; + virtual void Destroy(KernelCore& kernel) override; const KLightSession* GetParent() const { return m_parent; } - Result SendSyncRequest(u32* data); + Result SendSyncRequest(KernelCore& kernel, u32* data); - void OnServerClosed(); + void OnServerClosed(KernelCore& kernel); private: KLightSession* m_parent; diff --git a/src/core/hle/kernel/k_light_condition_variable.cpp b/src/core/hle/kernel/k_light_condition_variable.cpp index 6d5a815aab..2c873162fb 100644 --- a/src/core/hle/kernel/k_light_condition_variable.cpp +++ b/src/core/hle/kernel/k_light_condition_variable.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -13,11 +16,13 @@ namespace { class ThreadQueueImplForKLightConditionVariable final : public KThreadQueue { public: - ThreadQueueImplForKLightConditionVariable(KernelCore& kernel, KThread::WaiterList* wl, - bool term) - : KThreadQueue(kernel), m_wait_list(wl), m_allow_terminating_thread(term) {} + ThreadQueueImplForKLightConditionVariable(KernelCore& kernel, KThread::WaiterList* wl, bool term) + : KThreadQueue(kernel) + , m_wait_list(wl) + , m_allow_terminating_thread(term) + {} - void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { + virtual void CancelWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { // Only process waits if we're allowed to. if (ResultTerminationRequested == wait_result && m_allow_terminating_thread) { return; @@ -27,7 +32,7 @@ public: m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread)); // Invoke the base cancel wait handler. - KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + KThreadQueue::CancelWait(kernel, waiting_thread, wait_result, cancel_timer_task); } private: @@ -61,7 +66,7 @@ void KLightConditionVariable::Wait(KLightLock* lock, s64 timeout, bool allow_ter // Begin waiting. wait_queue.SetHardwareTimer(timer); - owner->BeginWait(std::addressof(wait_queue)); + owner->BeginWait(m_kernel, std::addressof(wait_queue)); } // Re-acquire the lock. @@ -73,7 +78,7 @@ void KLightConditionVariable::Broadcast() { // Signal all threads. for (auto it = m_wait_list.begin(); it != m_wait_list.end(); it = m_wait_list.erase(it)) { - it->EndWait(ResultSuccess); + it->EndWait(m_kernel, ResultSuccess); } } diff --git a/src/core/hle/kernel/k_light_lock.cpp b/src/core/hle/kernel/k_light_lock.cpp index e87ee8b652..614e056d13 100644 --- a/src/core/hle/kernel/k_light_lock.cpp +++ b/src/core/hle/kernel/k_light_lock.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -15,29 +18,25 @@ class ThreadQueueImplForKLightLock final : public KThreadQueue { public: explicit ThreadQueueImplForKLightLock(KernelCore& kernel) : KThreadQueue(kernel) {} - void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { + void CancelWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { // Remove the thread as a waiter from its owner. - if (KThread* owner = waiting_thread->GetLockOwner(); owner != nullptr) { - owner->RemoveWaiter(waiting_thread); + if (KThread* owner = waiting_thread->GetLockOwner(kernel); owner != nullptr) { + owner->RemoveWaiter(kernel, waiting_thread); } // Invoke the base cancel wait handler. - KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + KThreadQueue::CancelWait(kernel, waiting_thread, wait_result, cancel_timer_task); } }; } // namespace void KLightLock::Lock() { - const uintptr_t cur_thread = reinterpret_cast(GetCurrentThreadPointer(m_kernel)); - + const uintptr_t cur_thread = uintptr_t(GetCurrentThreadPointer(m_kernel)); while (true) { uintptr_t old_tag = m_tag.load(std::memory_order_relaxed); - - while (!m_tag.compare_exchange_weak(old_tag, (old_tag == 0) ? cur_thread : (old_tag | 1), - std::memory_order_acquire)) { - } - + while (!m_tag.compare_exchange_weak(old_tag, (old_tag == 0) ? cur_thread : (old_tag | 1), std::memory_order_acquire)) + ; if (old_tag == 0 || this->LockSlowPath(old_tag | 1, cur_thread)) { break; } @@ -69,13 +68,13 @@ bool KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) { // Add the current thread as a waiter on the owner. KThread* owner_thread = reinterpret_cast(_owner & ~1ULL); cur_thread->SetKernelAddressKey(reinterpret_cast(std::addressof(m_tag))); - owner_thread->AddWaiter(cur_thread); + owner_thread->AddWaiter(m_kernel, cur_thread); // Begin waiting to hold the lock. - cur_thread->BeginWait(std::addressof(wait_queue)); + cur_thread->BeginWait(m_kernel, std::addressof(wait_queue)); if (owner_thread->IsSuspended()) { - owner_thread->ContinueIfHasKernelWaiters(); + owner_thread->ContinueIfHasKernelWaiters(m_kernel); } } @@ -91,26 +90,25 @@ void KLightLock::UnlockSlowPath(uintptr_t _cur_thread) { // Get the next owner. bool has_waiters; - KThread* next_owner = owner_thread->RemoveKernelWaiterByKey( - std::addressof(has_waiters), reinterpret_cast(std::addressof(m_tag))); + KThread* next_owner = owner_thread->RemoveKernelWaiterByKey(m_kernel, + std::addressof(has_waiters), uintptr_t(std::addressof(m_tag))); // Pass the lock to the next owner. uintptr_t next_tag = 0; if (next_owner != nullptr) { - next_tag = - reinterpret_cast(next_owner) | static_cast(has_waiters); + next_tag = uintptr_t(next_owner) | uintptr_t(has_waiters); - next_owner->EndWait(ResultSuccess); + next_owner->EndWait(m_kernel, ResultSuccess); if (next_owner->IsSuspended()) { - next_owner->ContinueIfHasKernelWaiters(); + next_owner->ContinueIfHasKernelWaiters(m_kernel); } } // We may have unsuspended in the process of acquiring the lock, so we'll re-suspend now if // so. if (owner_thread->IsSuspended()) { - owner_thread->TrySuspend(); + owner_thread->TrySuspend(m_kernel); } // Write the new tag value. @@ -119,8 +117,7 @@ void KLightLock::UnlockSlowPath(uintptr_t _cur_thread) { } bool KLightLock::IsLockedByCurrentThread() const { - return (m_tag.load() | 1ULL) == - (reinterpret_cast(GetCurrentThreadPointer(m_kernel)) | 1ULL); + return (m_tag.load() | 1ULL) == (uintptr_t(GetCurrentThreadPointer(m_kernel)) | 1ULL); } } // namespace Kernel diff --git a/src/core/hle/kernel/k_light_server_session.cpp b/src/core/hle/kernel/k_light_server_session.cpp index a4d2b11231..6344f418ed 100644 --- a/src/core/hle/kernel/k_light_server_session.cpp +++ b/src/core/hle/kernel/k_light_server_session.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -24,21 +24,20 @@ public: ThreadQueueImplForKLightServerSessionRequest(KernelCore& kernel, KThread::WaiterList* wl) : KThreadQueue(kernel), m_wait_list(wl) {} - virtual void EndWait(KThread* waiting_thread, Result wait_result) override { + virtual void EndWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result) override { // Remove the thread from our wait list. m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread)); // Invoke the base end wait handler. - KThreadQueue::EndWait(waiting_thread, wait_result); + KThreadQueue::EndWait(kernel, waiting_thread, wait_result); } - virtual void CancelWait(KThread* waiting_thread, Result wait_result, - bool cancel_timer_task) override { + virtual void CancelWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { // Remove the thread from our wait list. m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread)); // Invoke the base cancel wait handler. - KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + KThreadQueue::CancelWait(kernel, waiting_thread, wait_result, cancel_timer_task); } }; @@ -48,9 +47,11 @@ private: public: ThreadQueueImplForKLightServerSessionReceive(KernelCore& kernel, KThread** st) - : KThreadQueue(kernel), m_server_thread(st) {} + : KThreadQueue(kernel) + , m_server_thread(st) + {} - virtual void EndWait(KThread* waiting_thread, Result wait_result) override { + virtual void EndWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result) override { // Clear the server thread. *m_server_thread = nullptr; @@ -58,11 +59,10 @@ public: waiting_thread->ClearCancellable(); // Invoke the base end wait handler. - KThreadQueue::EndWait(waiting_thread, wait_result); + KThreadQueue::EndWait(kernel, waiting_thread, wait_result); } - virtual void CancelWait(KThread* waiting_thread, Result wait_result, - bool cancel_timer_task) override { + virtual void CancelWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { // Clear the server thread. *m_server_thread = nullptr; @@ -70,7 +70,7 @@ public: waiting_thread->ClearCancellable(); // Invoke the base cancel wait handler. - KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + KThreadQueue::CancelWait(kernel, waiting_thread, wait_result, cancel_timer_task); } }; @@ -79,24 +79,21 @@ public: KLightServerSession::KLightServerSession(KernelCore& kernel) : KAutoObject(kernel) {} KLightServerSession::~KLightServerSession() = default; -void KLightServerSession::Destroy() { - this->CleanupRequests(); - - m_parent->OnServerClosed(); +void KLightServerSession::Destroy(KernelCore& kernel) { + this->CleanupRequests(kernel); + m_parent->OnServerClosed(kernel); } -void KLightServerSession::OnClientClosed() { - this->CleanupRequests(); +void KLightServerSession::OnClientClosed(KernelCore& kernel) { + this->CleanupRequests(kernel); } -Result KLightServerSession::OnRequest(KThread* request_thread) { - ThreadQueueImplForKLightServerSessionRequest wait_queue(m_kernel, - std::addressof(m_request_list)); - +Result KLightServerSession::OnRequest(KernelCore& kernel, KThread* request_thread) { + ThreadQueueImplForKLightServerSessionRequest wait_queue(kernel, std::addressof(m_request_list)); // Send the request. { // Lock the scheduler. - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); // Check that the server isn't closed. R_UNLESS(!m_parent->IsServerClosed(), ResultSessionClosed); @@ -109,11 +106,11 @@ Result KLightServerSession::OnRequest(KThread* request_thread) { // Begin waiting on the request. request_thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC); - request_thread->BeginWait(std::addressof(wait_queue)); + request_thread->BeginWait(kernel, std::addressof(wait_queue)); // If we have a server thread, end its wait. if (m_server_thread != nullptr) { - m_server_thread->EndWait(ResultSuccess); + m_server_thread->EndWait(kernel, ResultSuccess); } } @@ -123,13 +120,13 @@ Result KLightServerSession::OnRequest(KThread* request_thread) { R_RETURN(request_thread->GetWaitResult()); } -Result KLightServerSession::ReplyAndReceive(u32* data) { +Result KLightServerSession::ReplyAndReceive(KernelCore& kernel, u32* data) { // Set the server context. - GetCurrentThread(m_kernel).SetLightSessionData(data); + GetCurrentThread(kernel).SetLightSessionData(data); // Reply, if we need to. if (data[0] & KLightSession::ReplyFlag) { - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); // Check that we're open. R_UNLESS(!m_parent->IsClientClosed(), ResultSessionClosed); @@ -139,17 +136,16 @@ Result KLightServerSession::ReplyAndReceive(u32* data) { R_UNLESS(m_current_request != nullptr, ResultInvalidState); // Check that the server thread id is correct. - R_UNLESS(m_server_thread_id == GetCurrentThread(m_kernel).GetId(), ResultInvalidState); + R_UNLESS(m_server_thread_id == GetCurrentThread(kernel).GetId(), ResultInvalidState); // If we can reply, do so. if (!m_current_request->IsTerminationRequested()) { - std::memcpy(m_current_request->GetLightSessionData(), - GetCurrentThread(m_kernel).GetLightSessionData(), KLightSession::DataSize); - m_current_request->EndWait(ResultSuccess); + std::memcpy(m_current_request->GetLightSessionData(), GetCurrentThread(kernel).GetLightSessionData(), KLightSession::DataSize); + m_current_request->EndWait(kernel, ResultSuccess); } // Close our current request. - m_current_request->Close(); + m_current_request->Close(kernel); // Clear our current request. m_current_request = nullptr; @@ -157,14 +153,13 @@ Result KLightServerSession::ReplyAndReceive(u32* data) { } // Create the wait queue for our receive. - ThreadQueueImplForKLightServerSessionReceive wait_queue(m_kernel, - std::addressof(m_server_thread)); + ThreadQueueImplForKLightServerSessionReceive wait_queue(kernel, std::addressof(m_server_thread)); // Receive. while (true) { // Try to receive a request. { - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); // Check that we aren't already receiving. R_UNLESS(m_server_thread == nullptr, ResultInvalidState); @@ -175,20 +170,19 @@ Result KLightServerSession::ReplyAndReceive(u32* data) { R_UNLESS(!m_parent->IsServerClosed(), ResultSessionClosed); // Check that we're not terminating. - R_UNLESS(!GetCurrentThread(m_kernel).IsTerminationRequested(), - ResultTerminationRequested); + R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(), ResultTerminationRequested); // If we have a request available, use it. if (auto head = m_request_list.begin(); head != m_request_list.end()) { // Set our current request. m_current_request = std::addressof(*head); - m_current_request->Open(); + m_current_request->Open(kernel); // Set our server thread id. - m_server_thread_id = GetCurrentThread(m_kernel).GetId(); + m_server_thread_id = GetCurrentThread(kernel).GetId(); // Copy the client request data. - std::memcpy(GetCurrentThread(m_kernel).GetLightSessionData(), + std::memcpy(GetCurrentThread(kernel).GetLightSessionData(), m_current_request->GetLightSessionData(), KLightSession::DataSize); // We successfully received. @@ -198,51 +192,51 @@ Result KLightServerSession::ReplyAndReceive(u32* data) { // We need to wait for a request to come in. // Check if we were cancelled. - if (GetCurrentThread(m_kernel).IsWaitCancelled()) { - GetCurrentThread(m_kernel).ClearWaitCancelled(); + if (GetCurrentThread(kernel).IsWaitCancelled()) { + GetCurrentThread(kernel).ClearWaitCancelled(); R_THROW(ResultCancelled); } // Mark ourselves as cancellable. - GetCurrentThread(m_kernel).SetCancellable(); + GetCurrentThread(kernel).SetCancellable(); // Wait for a request to come in. - m_server_thread = GetCurrentThreadPointer(m_kernel); - GetCurrentThread(m_kernel).SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC); - GetCurrentThread(m_kernel).BeginWait(std::addressof(wait_queue)); + m_server_thread = GetCurrentThreadPointer(kernel); + GetCurrentThread(kernel).SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC); + GetCurrentThread(kernel).BeginWait(kernel, std::addressof(wait_queue)); } // We waited to receive a request; if our wait failed, return the failing result. - R_TRY(GetCurrentThread(m_kernel).GetWaitResult()); + R_TRY(GetCurrentThread(kernel).GetWaitResult()); } } -void KLightServerSession::CleanupRequests() { +void KLightServerSession::CleanupRequests(KernelCore& kernel) { // Cleanup all pending requests. { - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); // Handle the current request. if (m_current_request != nullptr) { // Reply to the current request. if (!m_current_request->IsTerminationRequested()) { - m_current_request->EndWait(ResultSessionClosed); + m_current_request->EndWait(kernel, ResultSessionClosed); } // Clear our current request. - m_current_request->Close(); + m_current_request->Close(kernel); m_current_request = nullptr; m_server_thread_id = InvalidThreadId; } // Reply to all other requests. for (auto& thread : m_request_list) { - thread.EndWait(ResultSessionClosed); + thread.EndWait(kernel, ResultSessionClosed); } // Wait up our server thread, if we have one. if (m_server_thread != nullptr) { - m_server_thread->EndWait(ResultSessionClosed); + m_server_thread->EndWait(kernel, ResultSessionClosed); } } } diff --git a/src/core/hle/kernel/k_light_server_session.h b/src/core/hle/kernel/k_light_server_session.h index 67db3f76ca..5849a41c7b 100644 --- a/src/core/hle/kernel/k_light_server_session.h +++ b/src/core/hle/kernel/k_light_server_session.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -34,19 +34,19 @@ public: m_parent = parent; } - virtual void Destroy() override; + virtual void Destroy(KernelCore& kernel) override; constexpr const KLightSession* GetParent() const { return m_parent; } - Result OnRequest(KThread* request_thread); - Result ReplyAndReceive(u32* data); + Result OnRequest(KernelCore& kernel, KThread* request_thread); + Result ReplyAndReceive(KernelCore& kernel, u32* data); - void OnClientClosed(); + void OnClientClosed(KernelCore& kernel); private: - void CleanupRequests(); + void CleanupRequests(KernelCore& kernel); }; } // namespace Kernel diff --git a/src/core/hle/kernel/k_light_session.cpp b/src/core/hle/kernel/k_light_session.cpp index d8b1e69582..add72d404b 100644 --- a/src/core/hle/kernel/k_light_session.cpp +++ b/src/core/hle/kernel/k_light_session.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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,15 +13,18 @@ namespace Kernel { KLightSession::KLightSession(KernelCore& kernel) - : KAutoObjectWithSlabHeapAndContainer(kernel), m_server(kernel), m_client(kernel) {} + : KAutoObjectWithSlabHeapAndContainer(kernel) + , m_server(kernel) + , m_client(kernel) +{} KLightSession::~KLightSession() = default; -void KLightSession::Initialize(KClientPort* client_port, uintptr_t name) { +void KLightSession::Initialize(KernelCore& kernel, KClientPort* client_port, uintptr_t name) { // Increment reference count. // Because reference count is one on creation, this will result // in a reference count of two. Thus, when both server and client are closed // this object will be destroyed. - this->Open(); + this->Open(kernel); // Create our sub sessions. KAutoObject::Create(std::addressof(m_server)); @@ -33,49 +39,47 @@ void KLightSession::Initialize(KClientPort* client_port, uintptr_t name) { m_name = name; // Set our owner process. - m_process = GetCurrentProcessPointer(m_kernel); - m_process->Open(); + m_process = GetCurrentProcessPointer(kernel); + m_process->Open(kernel); // Set our port. m_port = client_port; if (m_port != nullptr) { - m_port->Open(); + m_port->Open(kernel); } // Mark initialized. m_initialized = true; } -void KLightSession::Finalize() { +void KLightSession::Finalize(KernelCore& kernel) { if (m_port != nullptr) { - m_port->OnSessionFinalized(); - m_port->Close(); + m_port->OnSessionFinalized(kernel); + m_port->Close(kernel); } } -void KLightSession::OnServerClosed() { +void KLightSession::OnServerClosed(KernelCore& kernel) { if (m_state == State::Normal) { m_state = State::ServerClosed; - m_client.OnServerClosed(); + m_client.OnServerClosed(kernel); } - - this->Close(); + this->Close(kernel); } -void KLightSession::OnClientClosed() { +void KLightSession::OnClientClosed(KernelCore& kernel) { if (m_state == State::Normal) { m_state = State::ClientClosed; - m_server.OnClientClosed(); + m_server.OnClientClosed(kernel); } - - this->Close(); + this->Close(kernel); } -void KLightSession::PostDestroy(uintptr_t arg) { +void KLightSession::PostDestroy(KernelCore& kernel, uintptr_t arg) { // Release the session count resource the owner process holds. KProcess* owner = reinterpret_cast(arg); - owner->ReleaseResource(Svc::LimitableResource::SessionCountMax, 1); - owner->Close(); + owner->ReleaseResource(kernel, Svc::LimitableResource::SessionCountMax, 1); + owner->Close(kernel); } } // namespace Kernel diff --git a/src/core/hle/kernel/k_light_session.h b/src/core/hle/kernel/k_light_session.h index f78d8e6899..5bbb942b35 100644 --- a/src/core/hle/kernel/k_light_session.h +++ b/src/core/hle/kernel/k_light_session.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -43,20 +46,20 @@ public: explicit KLightSession(KernelCore& kernel); ~KLightSession(); - void Initialize(KClientPort* client_port, uintptr_t name); - void Finalize() override; + void Initialize(KernelCore& kernel, KClientPort* client_port, uintptr_t name); + void Finalize(KernelCore& kernel) override; bool IsInitialized() const override { return m_initialized; } uintptr_t GetPostDestroyArgument() const override { - return reinterpret_cast(m_process); + return uintptr_t(m_process); } - static void PostDestroy(uintptr_t arg); + static void PostDestroy(KernelCore& kernel, uintptr_t arg); - void OnServerClosed(); - void OnClientClosed(); + void OnServerClosed(KernelCore& kernel); + void OnClientClosed(KernelCore& kernel); bool IsServerClosed() const { return m_state != State::Normal; @@ -65,8 +68,8 @@ public: return m_state != State::Normal; } - Result OnRequest(KThread* request_thread) { - R_RETURN(m_server.OnRequest(request_thread)); + Result OnRequest(KernelCore& kernel, KThread* request_thread) { + R_RETURN(m_server.OnRequest(kernel, request_thread)); } KLightClientSession& GetClientSession() { diff --git a/src/core/hle/kernel/k_object_name.cpp b/src/core/hle/kernel/k_object_name.cpp index df3a1c4c56..da0b04b450 100644 --- a/src/core/hle/kernel/k_object_name.cpp +++ b/src/core/hle/kernel/k_object_name.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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,14 +11,14 @@ namespace Kernel { KObjectNameGlobalData::KObjectNameGlobalData(KernelCore& kernel) : m_object_list_lock{kernel} {} KObjectNameGlobalData::~KObjectNameGlobalData() = default; -void KObjectName::Initialize(KAutoObject* obj, const char* name) { +void KObjectName::Initialize(KernelCore& kernel, KAutoObject* obj, const char* name) { // Set member variables. m_object = obj; std::strncpy(m_name.data(), name, sizeof(m_name) - 1); m_name[sizeof(m_name) - 1] = '\x00'; // Open a reference to the object we hold. - m_object->Open(); + m_object->Open(kernel); } bool KObjectName::MatchesName(const char* name) const { @@ -28,7 +31,7 @@ Result KObjectName::NewFromName(KernelCore& kernel, KAutoObject* obj, const char R_UNLESS(new_name != nullptr, ResultOutOfResource); // Initialize the new name. - new_name->Initialize(obj, name); + new_name->Initialize(kernel, obj, name); // Check if there's an existing name. { @@ -47,7 +50,7 @@ Result KObjectName::NewFromName(KernelCore& kernel, KAutoObject* obj, const char } // The object already exists, which is an error condition. Perform cleanup. - obj->Close(); + obj->Close(kernel); KObjectName::Free(kernel, new_name); R_THROW(ResultInvalidState); } @@ -63,7 +66,7 @@ Result KObjectName::Delete(KernelCore& kernel, KAutoObject* obj, const char* com for (auto& name : gd.GetObjectList()) { if (name.MatchesName(compare_name) && obj == name.GetObject()) { // We found a match, clean up its resources. - obj->Close(); + obj->Close(kernel); gd.GetObjectList().erase(gd.GetObjectList().iterator_to(name)); KObjectName::Free(kernel, std::addressof(name)); R_SUCCEED(); @@ -89,14 +92,12 @@ KScopedAutoObject KObjectName::FindImpl(KernelCore& kernel, const c KObjectNameGlobalData& gd{kernel.ObjectNameGlobalData()}; // Try to find a matching object in the global list. - for (const auto& name : gd.GetObjectList()) { - if (name.MatchesName(compare_name)) { - return name.GetObject(); - } - } + for (const auto& name : gd.GetObjectList()) + if (name.MatchesName(compare_name)) + return {kernel, name.GetObject()}; // There's no matching entry in the list. - return nullptr; + return {kernel, nullptr}; } } // namespace Kernel diff --git a/src/core/hle/kernel/k_object_name.h b/src/core/hle/kernel/k_object_name.h index a8876fe370..2ae9edcf51 100644 --- a/src/core/hle/kernel/k_object_name.h +++ b/src/core/hle/kernel/k_object_name.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -41,7 +44,7 @@ public: R_UNLESS(derived != nullptr, ResultNotFound); // Check that the object is closed. - R_UNLESS(derived->IsServerClosed(), ResultInvalidState); + R_UNLESS(derived->IsServerClosed(kernel), ResultInvalidState); R_RETURN(Delete(kernel, obj.GetPointerUnsafe(), name)); } @@ -49,13 +52,13 @@ public: template requires(std::derived_from) static KScopedAutoObject Find(KernelCore& kernel, const char* name) { - return Find(kernel, name); + return {kernel, static_cast(Find(kernel, name).GetPointerUnsafe())}; } private: static KScopedAutoObject FindImpl(KernelCore& kernel, const char* name); - void Initialize(KAutoObject* obj, const char* name); + void Initialize(KernelCore& kernel, KAutoObject* obj, const char* name); bool MatchesName(const char* name) const; KAutoObject* GetObject() const { diff --git a/src/core/hle/kernel/k_page_group.cpp b/src/core/hle/kernel/k_page_group.cpp index d8c644a336..ed7f219718 100644 --- a/src/core/hle/kernel/k_page_group.cpp +++ b/src/core/hle/kernel/k_page_group.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -21,8 +24,8 @@ void KPageGroup::Finalize() { m_last_block = nullptr; } -void KPageGroup::CloseAndReset() { - auto& mm = m_kernel.MemoryManager(); +void KPageGroup::CloseAndReset(KernelCore& kernel) { + auto& mm = kernel.MemoryManager(); KBlockInfo* cur = m_first_block; while (cur != nullptr) { @@ -76,28 +79,22 @@ Result KPageGroup::AddBlock(KPhysicalAddress addr, size_t num_pages) { R_SUCCEED(); } -void KPageGroup::Open() const { - auto& mm = m_kernel.MemoryManager(); - - for (const auto& it : *this) { +void KPageGroup::Open(KernelCore& kernel) const { + auto& mm = kernel.MemoryManager(); + for (const auto& it : *this) mm.Open(it.GetAddress(), it.GetNumPages()); - } } -void KPageGroup::OpenFirst() const { - auto& mm = m_kernel.MemoryManager(); - - for (const auto& it : *this) { +void KPageGroup::OpenFirst(KernelCore& kernel) const { + auto& mm = kernel.MemoryManager(); + for (const auto& it : *this) mm.OpenFirst(it.GetAddress(), it.GetNumPages()); - } } -void KPageGroup::Close() const { - auto& mm = m_kernel.MemoryManager(); - - for (const auto& it : *this) { +void KPageGroup::Close(KernelCore& kernel) const { + auto& mm = kernel.MemoryManager(); + for (const auto& it : *this) mm.Close(it.GetAddress(), it.GetNumPages()); - } } bool KPageGroup::IsEquivalentTo(const KPageGroup& rhs) const { diff --git a/src/core/hle/kernel/k_page_group.h b/src/core/hle/kernel/k_page_group.h index de9d63a8d2..60a6faa2fd 100644 --- a/src/core/hle/kernel/k_page_group.h +++ b/src/core/hle/kernel/k_page_group.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -139,12 +142,12 @@ public: }; explicit KPageGroup(KernelCore& kernel, KBlockInfoManager* m) - : m_kernel{kernel}, m_manager{m} {} + : m_manager{m} {} ~KPageGroup() { this->Finalize(); } - void CloseAndReset(); + void CloseAndReset(KernelCore& kernel); void Finalize(); Iterator begin() const { @@ -158,9 +161,9 @@ public: } Result AddBlock(KPhysicalAddress addr, size_t num_pages); - void Open() const; - void OpenFirst() const; - void Close() const; + void Open(KernelCore& kernel) const; + void OpenFirst(KernelCore& kernel) const; + void Close(KernelCore& kernel) const; size_t GetNumPages() const; @@ -175,7 +178,6 @@ public: } private: - KernelCore& m_kernel; KBlockInfo* m_first_block{}; KBlockInfo* m_last_block{}; KBlockInfoManager* m_manager{}; @@ -183,21 +185,24 @@ private: class KScopedPageGroup { public: - explicit KScopedPageGroup(const KPageGroup* gp, bool not_first = true) : m_pg(gp) { + explicit KScopedPageGroup(KernelCore& kernel, const KPageGroup* gp, bool not_first = true) + : m_kernel{kernel} + , m_pg{gp} + { if (m_pg) { if (not_first) { - m_pg->Open(); + m_pg->Open(kernel); } else { - m_pg->OpenFirst(); + m_pg->OpenFirst(kernel); } } } - explicit KScopedPageGroup(const KPageGroup& gp, bool not_first = true) - : KScopedPageGroup(std::addressof(gp), not_first) {} + explicit KScopedPageGroup(KernelCore& kernel, const KPageGroup& gp, bool not_first = true) + : KScopedPageGroup(kernel, std::addressof(gp), not_first) {} + ~KScopedPageGroup() { - if (m_pg) { - m_pg->Close(); - } + if (m_pg) + m_pg->Close(m_kernel); } void CancelClose() { @@ -205,6 +210,7 @@ public: } private: + KernelCore& m_kernel; const KPageGroup* m_pg{}; }; diff --git a/src/core/hle/kernel/k_page_table_base.cpp b/src/core/hle/kernel/k_page_table_base.cpp index 3696f9f251..2a0a7d49d5 100644 --- a/src/core/hle/kernel/k_page_table_base.cpp +++ b/src/core/hle/kernel/k_page_table_base.cpp @@ -127,22 +127,22 @@ constexpr Common::MemoryPermission ConvertToMemoryPermission(KMemoryPermission p } // namespace -void KPageTableBase::MemoryRange::Open() { +void KPageTableBase::MemoryRange::Open(KernelCore& kernel) { // If the range contains heap pages, open them. if (this->IsHeap()) { - m_kernel.MemoryManager().Open(this->GetAddress(), this->GetSize() / PageSize); + kernel.MemoryManager().Open(this->GetAddress(), this->GetSize() / PageSize); } } -void KPageTableBase::MemoryRange::Close() { +void KPageTableBase::MemoryRange::Close(KernelCore& kernel) { // If the range contains heap pages, close them. if (this->IsHeap()) { - m_kernel.MemoryManager().Close(this->GetAddress(), this->GetSize() / PageSize); + kernel.MemoryManager().Close(this->GetAddress(), this->GetSize() / PageSize); } } KPageTableBase::KPageTableBase(KernelCore& kernel) - : m_kernel(kernel), m_system(kernel.System()), m_general_lock(kernel), + : m_system(kernel.System()), m_general_lock(kernel), m_map_physical_memory_lock(kernel), m_device_map_lock(kernel) {} KPageTableBase::~KPageTableBase() = default; @@ -177,9 +177,9 @@ Result KPageTableBase::InitializeForKernel(bool is_64_bit, KVirtualAddress start m_mapped_ipc_server_memory = 0; m_memory_block_slab_manager = - m_kernel.GetSystemSystemResource().GetMemoryBlockSlabManagerPointer(); - m_block_info_manager = m_kernel.GetSystemSystemResource().GetBlockInfoManagerPointer(); - m_resource_limit = m_kernel.GetSystemResourceLimit(); + m_system.Kernel().GetSystemSystemResource().GetMemoryBlockSlabManagerPointer(); + m_block_info_manager = m_system.Kernel().GetSystemSystemResource().GetBlockInfoManagerPointer(); + m_resource_limit = m_system.Kernel().GetSystemResourceLimit(); m_allocate_option = KMemoryManager::EncodeOption(KMemoryManager::Pool::System, KMemoryManager::Direction::FromFront); @@ -469,11 +469,11 @@ void KPageTableBase::Finalize() { } // Get physical pages. - KPageGroup pg(m_kernel, m_block_info_manager); + KPageGroup pg(m_system.Kernel(), m_block_info_manager); this->MakePageGroup(pg, addr, size / PageSize); // Free the pages. - pg.CloseAndReset(); + pg.CloseAndReset(m_system.Kernel()); }; // Finalize memory blocks. @@ -490,16 +490,16 @@ void KPageTableBase::Finalize() { // Release any insecure mapped memory. if (m_mapped_insecure_memory) { if (auto* const insecure_resource_limit = - KSystemControl::GetInsecureMemoryResourceLimit(m_kernel); + KSystemControl::GetInsecureMemoryResourceLimit(m_system.Kernel()); insecure_resource_limit != nullptr) { - insecure_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, + insecure_resource_limit->Release(m_system.Kernel(), Svc::LimitableResource::PhysicalMemoryMax, m_mapped_insecure_memory); } } // Release any ipc server memory. if (m_mapped_ipc_server_memory) { - m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, + m_resource_limit->Release(m_system.Kernel(), Svc::LimitableResource::PhysicalMemoryMax, m_mapped_ipc_server_memory); } } @@ -635,6 +635,36 @@ Result KPageTableBase::CheckMemoryState(const KMemoryInfo& info, KMemoryState st R_SUCCEED(); } +bool KPageTableBase::BeginTraversal(const Common::PageTable &impl, TraversalEntry *out_entry, TraversalContext *out_context, + Common::ProcessAddress address) const { + out_context->next_offset = GetInteger(address); + out_context->next_page = GetInteger(address) >> PageBits; + + return ContinueTraversal(impl, out_entry, out_context); +} + +bool KPageTableBase::ContinueTraversal(const Common::PageTable &impl, TraversalEntry *out_entry, + TraversalContext *context) const { + // Setup invalid defaults. + out_entry->phys_addr = 0; + out_entry->block_size = PageSize; + // Validate that we can read the actual entry. + if (auto const page = context->next_page; page < impl.entries.size()) { + // Validate that the entry is mapped. + if (auto const paddr = impl.entries[page].Pointer(true); paddr != 0) { + // Populate the results and return true + out_entry->phys_addr = GetInteger(m_system.DeviceMemory().GetPhysicalAddr(paddr + context->next_offset)); + context->next_page += 1; + context->next_offset += PageSize; + return true; + } + } + context->next_page += 1; + context->next_offset += PageSize; + // Otherwise return false + return false; +} + Result KPageTableBase::CheckMemoryStateContiguous(size_t* out_blocks_needed, KProcessAddress addr, size_t size, KMemoryState state_mask, KMemoryState state, KMemoryPermission perm_mask, @@ -833,7 +863,7 @@ Result KPageTableBase::LockMemoryAndOpen(KPageGroup* out_pg, KPhysicalAddress* o // If we have an output group, open. if (out_pg) { - out_pg->Open(); + out_pg->Open(m_system.Kernel()); } R_SUCCEED(); @@ -940,7 +970,7 @@ Result KPageTableBase::QueryMappingImpl(KProcessAddress* out, KPhysicalAddress a size_t tot_size = 0; next_valid = - impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), region_start); + BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), region_start); next_entry.block_size = (next_entry.block_size - (GetInteger(region_start) & (next_entry.block_size - 1))); @@ -976,7 +1006,7 @@ Result KPageTableBase::QueryMappingImpl(KProcessAddress* out, KPhysicalAddress a break; } - next_valid = impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + next_valid = ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context)); } // Check the last entry. @@ -1041,7 +1071,7 @@ Result KPageTableBase::MapMemory(KProcessAddress dst_address, KProcessAddress sr const size_t num_pages = size / PageSize; // Create page groups for the memory being unmapped. - KPageGroup pg(m_kernel, m_block_info_manager); + KPageGroup pg(m_system.Kernel(), m_block_info_manager); // Create the page group representing the source. R_TRY(this->MakePageGroup(pg, src_address, num_pages)); @@ -1129,7 +1159,7 @@ Result KPageTableBase::UnmapMemory(KProcessAddress dst_address, KProcessAddress const size_t num_pages = size / PageSize; // Create page groups for the memory being unmapped. - KPageGroup pg(m_kernel, m_block_info_manager); + KPageGroup pg(m_system.Kernel(), m_block_info_manager); // Create the page group representing the destination. R_TRY(this->MakePageGroup(pg, dst_address, num_pages)); @@ -1217,7 +1247,7 @@ Result KPageTableBase::MapCodeMemory(KProcessAddress dst_address, KProcessAddres const size_t num_pages = size / PageSize; // Create page groups for the memory being unmapped. - KPageGroup pg(m_kernel, m_block_info_manager); + KPageGroup pg(m_system.Kernel(), m_block_info_manager); // Create the page group representing the source. R_TRY(this->MakePageGroup(pg, src_address, num_pages)); @@ -1312,7 +1342,7 @@ Result KPageTableBase::UnmapCodeMemory(KProcessAddress dst_address, KProcessAddr bool reprotected_pages = false; SCOPE_EXIT { if (reprotected_pages && any_code_pages) { - InvalidateInstructionCache(m_kernel, this, dst_address, size); + InvalidateInstructionCache(m_system.Kernel(), this, dst_address, size); } }; @@ -1322,7 +1352,7 @@ Result KPageTableBase::UnmapCodeMemory(KProcessAddress dst_address, KProcessAddr const size_t num_pages = size / PageSize; // Create page groups for the memory being unmapped. - KPageGroup pg(m_kernel, m_block_info_manager); + KPageGroup pg(m_system.Kernel(), m_block_info_manager); // Create the page group representing the destination. R_TRY(this->MakePageGroup(pg, dst_address, num_pages)); @@ -1383,19 +1413,19 @@ Result KPageTableBase::UnmapCodeMemory(KProcessAddress dst_address, KProcessAddr Result KPageTableBase::MapInsecureMemory(KProcessAddress address, size_t size) { // Get the insecure memory resource limit and pool. - auto* const insecure_resource_limit = KSystemControl::GetInsecureMemoryResourceLimit(m_kernel); + auto* const insecure_resource_limit = KSystemControl::GetInsecureMemoryResourceLimit(m_system.Kernel()); const auto insecure_pool = static_cast(KSystemControl::GetInsecureMemoryPool()); // Reserve the insecure memory. // NOTE: ResultOutOfMemory is returned here instead of the usual LimitReached. - KScopedResourceReservation memory_reservation(insecure_resource_limit, + KScopedResourceReservation memory_reservation(m_system.Kernel(), insecure_resource_limit, Svc::LimitableResource::PhysicalMemoryMax, size); R_UNLESS(memory_reservation.Succeeded(), ResultOutOfMemory); // Allocate pages for the insecure memory. - KPageGroup pg(m_kernel, m_block_info_manager); - R_TRY(m_kernel.MemoryManager().AllocateAndOpen( + KPageGroup pg(m_system.Kernel(), m_block_info_manager); + R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpen( std::addressof(pg), size / PageSize, KMemoryManager::EncodeOption(insecure_pool, KMemoryManager::Direction::FromFront))); @@ -1403,7 +1433,7 @@ Result KPageTableBase::MapInsecureMemory(KProcessAddress address, size_t size) { // If the mapping succeeds, each page will gain an extra reference, otherwise they will be freed // automatically. SCOPE_EXIT { - pg.Close(); + pg.Close(m_system.Kernel()); }; // Clear all the newly allocated pages. @@ -1491,9 +1521,9 @@ Result KPageTableBase::UnmapInsecureMemory(KProcessAddress address, size_t size) // Release the insecure memory from the insecure limit. if (auto* const insecure_resource_limit = - KSystemControl::GetInsecureMemoryResourceLimit(m_kernel); + KSystemControl::GetInsecureMemoryResourceLimit(m_system.Kernel()); insecure_resource_limit != nullptr) { - insecure_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, size); + insecure_resource_limit->Release(m_system.Kernel(), Svc::LimitableResource::PhysicalMemoryMax, size); } R_SUCCEED(); @@ -1603,15 +1633,15 @@ Result KPageTableBase::AllocateAndMapPagesImpl(PageLinkedList* page_list, KProce ASSERT(this->IsLockedByCurrentThread()); // Create a page group to hold the pages we allocate. - KPageGroup pg(m_kernel, m_block_info_manager); + KPageGroup pg(m_system.Kernel(), m_block_info_manager); // Allocate the pages. R_TRY( - m_kernel.MemoryManager().AllocateAndOpen(std::addressof(pg), num_pages, m_allocate_option)); + m_system.Kernel().MemoryManager().AllocateAndOpen(std::addressof(pg), num_pages, m_allocate_option)); // Ensure that the page group is closed when we're done working with it. SCOPE_EXIT { - pg.Close(); + pg.Close(m_system.Kernel()); }; // Clear all pages. @@ -1754,7 +1784,7 @@ Result KPageTableBase::MakePageGroup(KPageGroup& pg, KProcessAddress addr, size_ // Begin traversal. TraversalContext context; TraversalEntry next_entry; - R_UNLESS(impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), addr), + R_UNLESS(BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), addr), ResultInvalidCurrentMemory); // Prepare tracking variables. @@ -1764,7 +1794,7 @@ Result KPageTableBase::MakePageGroup(KPageGroup& pg, KProcessAddress addr, size_ // Iterate, adding to group as we go. while (tot_size < size) { - R_UNLESS(impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)), + R_UNLESS(ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context)), ResultInvalidCurrentMemory); if (next_entry.phys_addr != (cur_addr + cur_size)) { @@ -1828,7 +1858,7 @@ bool KPageTableBase::IsValidPageGroup(const KPageGroup& pg, KProcessAddress addr // Begin traversal. TraversalContext context; TraversalEntry next_entry; - if (!impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), addr)) { + if (!BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), addr)) { return false; } @@ -1839,7 +1869,7 @@ bool KPageTableBase::IsValidPageGroup(const KPageGroup& pg, KProcessAddress addr // Iterate, comparing expected to actual. while (tot_size < size) { - if (!impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context))) { + if (!ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context))) { return false; } @@ -1896,7 +1926,7 @@ Result KPageTableBase::GetContiguousMemoryRangeWithState( // Begin a traversal. TraversalContext context; TraversalEntry cur_entry = {.phys_addr = 0, .block_size = 0}; - R_UNLESS(impl.BeginTraversal(std::addressof(cur_entry), std::addressof(context), address), + R_UNLESS(BeginTraversal(impl, std::addressof(cur_entry), std::addressof(context), address), ResultInvalidCurrentMemory); // Traverse until we have enough size or we aren't contiguous any more. @@ -1905,7 +1935,7 @@ Result KPageTableBase::GetContiguousMemoryRangeWithState( for (contig_size = cur_entry.block_size - (GetInteger(phys_address) & (cur_entry.block_size - 1)); contig_size < size; contig_size += cur_entry.block_size) { - if (!impl.ContinueTraversal(std::addressof(cur_entry), std::addressof(context))) { + if (!ContinueTraversal(impl, std::addressof(cur_entry), std::addressof(context))) { break; } if (cur_entry.phys_addr != phys_address + contig_size) { @@ -1992,7 +2022,7 @@ Result KPageTableBase::SetProcessMemoryPermission(KProcessAddress addr, size_t s KMemoryAttribute::All, KMemoryAttribute::None)); // Make a new page group for the region. - KPageGroup pg(m_kernel, m_block_info_manager); + KPageGroup pg(m_system.Kernel(), m_block_info_manager); // Determine new perm/state. const KMemoryPermission new_perm = ConvertToKMemoryPermission(svc_perm); @@ -2048,9 +2078,9 @@ Result KPageTableBase::SetProcessMemoryPermission(KProcessAddress addr, size_t s // Ensure cache coherency, if we're setting pages as executable. if (is_x) { for (const auto& block : pg) { - StoreDataCache(GetHeapVirtualPointer(m_kernel, block.GetAddress()), block.GetSize()); + StoreDataCache(GetHeapVirtualPointer(m_system.Kernel(), block.GetAddress()), block.GetSize()); } - InvalidateInstructionCache(m_kernel, this, addr, size); + InvalidateInstructionCache(m_system.Kernel(), this, addr, size); } R_SUCCEED(); @@ -2158,7 +2188,7 @@ Result KPageTableBase::SetHeapSize(KProcessAddress* out, size_t size) { false, unmap_properties, OperationType::Unmap, false)); // Release the memory from the resource limit. - m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, + m_resource_limit->Release(m_system.Kernel(), Svc::LimitableResource::PhysicalMemoryMax, num_pages * PageSize); // Apply the memory block update. @@ -2188,20 +2218,20 @@ Result KPageTableBase::SetHeapSize(KProcessAddress* out, size_t size) { } // Reserve memory for the heap extension. - KScopedResourceReservation memory_reservation( + KScopedResourceReservation memory_reservation(m_system.Kernel(), m_resource_limit, Svc::LimitableResource::PhysicalMemoryMax, allocation_size); R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached); // Allocate pages for the heap extension. - KPageGroup pg(m_kernel, m_block_info_manager); - R_TRY(m_kernel.MemoryManager().AllocateAndOpen(std::addressof(pg), allocation_size / PageSize, + KPageGroup pg(m_system.Kernel(), m_block_info_manager); + R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpen(std::addressof(pg), allocation_size / PageSize, m_allocate_option)); // Close the opened pages when we're done with them. // If the mapping succeeds, each page will gain an extra reference, otherwise they will be freed // automatically. SCOPE_EXIT { - pg.Close(); + pg.Close(m_system.Kernel()); }; // Clear all the newly allocated pages. @@ -2334,7 +2364,7 @@ Result KPageTableBase::QueryPhysicalAddress(Svc::lp64::PhysicalMemoryInfo* out, TraversalContext context; TraversalEntry next_entry; bool traverse_valid = - m_impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), virt_addr); + BeginTraversal(m_impl, std::addressof(next_entry), std::addressof(context), virt_addr); R_UNLESS(traverse_valid, ResultInvalidCurrentMemory); // Set tracking variables. @@ -2345,7 +2375,7 @@ Result KPageTableBase::QueryPhysicalAddress(Svc::lp64::PhysicalMemoryInfo* out, while (true) { // Continue the traversal. traverse_valid = - m_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + ContinueTraversal(m_impl, std::addressof(next_entry), std::addressof(context)); if (!traverse_valid) { break; } @@ -2406,7 +2436,7 @@ Result KPageTableBase::MapIoImpl(KProcessAddress* out, PageLinkedList* page_list ASSERT(this->CanContain(region_start, region_size, state)); // Locate the memory region. - const KMemoryRegion* region = KMemoryLayout::Find(m_kernel.MemoryLayout(), phys_addr); + const KMemoryRegion* region = KMemoryLayout::Find(m_system.Kernel().MemoryLayout(), phys_addr); R_UNLESS(region != nullptr, ResultInvalidAddress); ASSERT(region->Contains(GetInteger(phys_addr))); @@ -2567,7 +2597,7 @@ Result KPageTableBase::UnmapIoRegion(KProcessAddress dst_address, KPhysicalAddre TraversalContext context; TraversalEntry next_entry; ASSERT( - impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_address)); + BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), dst_address)); // Check that the physical region matches. R_UNLESS(next_entry.phys_addr == phys_addr, ResultInvalidMemoryRegion); @@ -2577,7 +2607,7 @@ Result KPageTableBase::UnmapIoRegion(KProcessAddress dst_address, KPhysicalAddre next_entry.block_size - (GetInteger(phys_addr) & (next_entry.block_size - 1)); checked_size < size; checked_size += next_entry.block_size) { // Continue the traversal. - ASSERT(impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context))); + ASSERT(ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context))); // Check that the physical region matches. R_UNLESS(next_entry.phys_addr == phys_addr + checked_size, ResultInvalidMemoryRegion); @@ -2640,7 +2670,7 @@ Result KPageTableBase::MapStatic(KPhysicalAddress phys_addr, size_t size, KMemor const size_t region_num_pages = region_size / PageSize; // Locate the memory region. - const KMemoryRegion* region = KMemoryLayout::Find(m_kernel.MemoryLayout(), phys_addr); + const KMemoryRegion* region = KMemoryLayout::Find(m_system.Kernel().MemoryLayout(), phys_addr); R_UNLESS(region != nullptr, ResultInvalidAddress); ASSERT(region->Contains(GetInteger(phys_addr))); @@ -2707,7 +2737,7 @@ Result KPageTableBase::MapStatic(KPhysicalAddress phys_addr, size_t size, KMemor Result KPageTableBase::MapRegion(KMemoryRegionType region_type, KMemoryPermission perm) { // Get the memory region. const KMemoryRegion* region = - m_kernel.MemoryLayout().GetPhysicalMemoryRegionTree().FindFirstDerived(region_type); + m_system.Kernel().MemoryLayout().GetPhysicalMemoryRegionTree().FindFirstDerived(region_type); R_UNLESS(region != nullptr, ResultOutOfRange); // Check that the region is valid. @@ -3004,7 +3034,7 @@ Result KPageTableBase::MakeAndOpenPageGroup(KPageGroup* out, KProcessAddress add R_TRY(this->MakePageGroup(*out, address, num_pages)); // Open a new reference to the pages in the group. - out->Open(); + out->Open(m_system.Kernel()); R_SUCCEED(); } @@ -3029,7 +3059,7 @@ Result KPageTableBase::InvalidateProcessDataCache(KProcessAddress address, size_ TraversalContext context; TraversalEntry next_entry; bool traverse_valid = - impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), address); + BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), address); R_UNLESS(traverse_valid, ResultInvalidCurrentMemory); // Prepare tracking variables. @@ -3041,7 +3071,7 @@ Result KPageTableBase::InvalidateProcessDataCache(KProcessAddress address, size_ while (tot_size < size) { // Continue the traversal. traverse_valid = - impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context)); R_UNLESS(traverse_valid, ResultInvalidCurrentMemory); if (next_entry.phys_addr != (cur_addr + cur_size)) { @@ -3051,7 +3081,7 @@ Result KPageTableBase::InvalidateProcessDataCache(KProcessAddress address, size_ // Invalidate the block. if (cur_size > 0) { // NOTE: Nintendo does not check the result of invalidation. - InvalidateDataCache(GetLinearMappedVirtualPointer(m_kernel, cur_addr), cur_size); + InvalidateDataCache(GetLinearMappedVirtualPointer(m_system.Kernel(), cur_addr), cur_size); } // Advance. @@ -3075,7 +3105,7 @@ Result KPageTableBase::InvalidateProcessDataCache(KProcessAddress address, size_ // Invalidate the last block. if (cur_size > 0) { // NOTE: Nintendo does not check the result of invalidation. - InvalidateDataCache(GetLinearMappedVirtualPointer(m_kernel, cur_addr), cur_size); + InvalidateDataCache(GetLinearMappedVirtualPointer(m_system.Kernel(), cur_addr), cur_size); } R_SUCCEED(); @@ -3083,7 +3113,7 @@ Result KPageTableBase::InvalidateProcessDataCache(KProcessAddress address, size_ Result KPageTableBase::InvalidateCurrentProcessDataCache(KProcessAddress address, size_t size) { // Check pre-condition: this is being called on the current process. - ASSERT(this == std::addressof(GetCurrentProcess(m_kernel).GetPageTable().GetBasePageTable())); + ASSERT(this == std::addressof(GetCurrentProcess(m_system.Kernel()).GetPageTable().GetBasePageTable())); // Check that the region is in range. R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory); @@ -3129,7 +3159,7 @@ Result KPageTableBase::ReadDebugMemory(KProcessAddress dst_address, KProcessAddr TraversalContext context; TraversalEntry next_entry; bool traverse_valid = - impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), src_address); + BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), src_address); R_UNLESS(traverse_valid, ResultInvalidCurrentMemory); // Prepare tracking variables. @@ -3144,7 +3174,7 @@ Result KPageTableBase::ReadDebugMemory(KProcessAddress dst_address, KProcessAddr // Copy as much aligned data as we can. if (cur_size >= sizeof(u32)) { const size_t copy_size = Common::AlignDown(cur_size, sizeof(u32)); - const void* copy_src = GetLinearMappedVirtualPointer(m_kernel, cur_addr); + const void* copy_src = GetLinearMappedVirtualPointer(m_system.Kernel(), cur_addr); FlushDataCache(copy_src, copy_size); R_UNLESS(dst_memory.WriteBlock(dst_address, copy_src, copy_size), ResultInvalidPointer); @@ -3155,7 +3185,7 @@ Result KPageTableBase::ReadDebugMemory(KProcessAddress dst_address, KProcessAddr // Copy remaining data. if (cur_size > 0) { - const void* copy_src = GetLinearMappedVirtualPointer(m_kernel, cur_addr); + const void* copy_src = GetLinearMappedVirtualPointer(m_system.Kernel(), cur_addr); FlushDataCache(copy_src, cur_size); R_UNLESS(dst_memory.WriteBlock(dst_address, copy_src, cur_size), ResultInvalidPointer); } @@ -3167,7 +3197,7 @@ Result KPageTableBase::ReadDebugMemory(KProcessAddress dst_address, KProcessAddr while (tot_size < size) { // Continue the traversal. traverse_valid = - impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context)); ASSERT(traverse_valid); if (next_entry.phys_addr != (cur_addr + cur_size)) { @@ -3225,7 +3255,7 @@ Result KPageTableBase::WriteDebugMemory(KProcessAddress dst_address, KProcessAdd TraversalContext context; TraversalEntry next_entry; bool traverse_valid = - impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_address); + BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), dst_address); R_UNLESS(traverse_valid, ResultInvalidCurrentMemory); // Prepare tracking variables. @@ -3240,11 +3270,11 @@ Result KPageTableBase::WriteDebugMemory(KProcessAddress dst_address, KProcessAdd // Copy as much aligned data as we can. if (cur_size >= sizeof(u32)) { const size_t copy_size = Common::AlignDown(cur_size, sizeof(u32)); - void* copy_dst = GetLinearMappedVirtualPointer(m_kernel, cur_addr); + void* copy_dst = GetLinearMappedVirtualPointer(m_system.Kernel(), cur_addr); R_UNLESS(src_memory.ReadBlock(src_address, copy_dst, copy_size), ResultInvalidCurrentMemory); - StoreDataCache(GetLinearMappedVirtualPointer(m_kernel, cur_addr), copy_size); + StoreDataCache(GetLinearMappedVirtualPointer(m_system.Kernel(), cur_addr), copy_size); src_address += copy_size; cur_addr += copy_size; @@ -3253,11 +3283,11 @@ Result KPageTableBase::WriteDebugMemory(KProcessAddress dst_address, KProcessAdd // Copy remaining data. if (cur_size > 0) { - void* copy_dst = GetLinearMappedVirtualPointer(m_kernel, cur_addr); + void* copy_dst = GetLinearMappedVirtualPointer(m_system.Kernel(), cur_addr); R_UNLESS(src_memory.ReadBlock(src_address, copy_dst, cur_size), ResultInvalidCurrentMemory); - StoreDataCache(GetLinearMappedVirtualPointer(m_kernel, cur_addr), cur_size); + StoreDataCache(GetLinearMappedVirtualPointer(m_system.Kernel(), cur_addr), cur_size); } R_SUCCEED(); @@ -3267,7 +3297,7 @@ Result KPageTableBase::WriteDebugMemory(KProcessAddress dst_address, KProcessAdd while (tot_size < size) { // Continue the traversal. traverse_valid = - impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context)); ASSERT(traverse_valid); if (next_entry.phys_addr != (cur_addr + cur_size)) { @@ -3295,7 +3325,7 @@ Result KPageTableBase::WriteDebugMemory(KProcessAddress dst_address, KProcessAdd R_TRY(PerformCopy()); // Invalidate the instruction cache, as this svc allows modifying executable pages. - InvalidateInstructionCache(m_kernel, this, dst_address, size); + InvalidateInstructionCache(m_system.Kernel(), this, dst_address, size); R_SUCCEED(); } @@ -3311,7 +3341,7 @@ Result KPageTableBase::ReadIoMemoryImpl(KProcessAddress dst_addr, KPhysicalAddre const size_t map_size = map_end - map_start; // Get the memory reference to write into. - auto& dst_memory = GetCurrentMemory(m_kernel); + auto& dst_memory = GetCurrentMemory(m_system.Kernel()); // We're going to perform an update, so create a helper. KScopedPageTableUpdater updater(this); @@ -3347,7 +3377,7 @@ Result KPageTableBase::WriteIoMemoryImpl(KPhysicalAddress phys_addr, KProcessAdd const size_t map_size = map_end - map_start; // Get the memory reference to read from. - auto& src_memory = GetCurrentMemory(m_kernel); + auto& src_memory = GetCurrentMemory(m_system.Kernel()); // We're going to perform an update, so create a helper. KScopedPageTableUpdater updater(this); @@ -3379,7 +3409,7 @@ Result KPageTableBase::ReadDebugIoMemory(KProcessAddress dst_address, KProcessAd // We need to lock both this table, and the current process's table, so set up some aliases. KPageTableBase& src_page_table = *this; - KPageTableBase& dst_page_table = GetCurrentProcess(m_kernel).GetPageTable().GetBasePageTable(); + KPageTableBase& dst_page_table = GetCurrentProcess(m_system.Kernel()).GetPageTable().GetBasePageTable(); // Acquire the table locks. KScopedLightLockPair lk(src_page_table.m_general_lock, dst_page_table.m_general_lock); @@ -3421,7 +3451,7 @@ Result KPageTableBase::WriteDebugIoMemory(KProcessAddress dst_address, KProcessA // We need to lock both this table, and the current process's table, so set up some aliases. KPageTableBase& src_page_table = *this; - KPageTableBase& dst_page_table = GetCurrentProcess(m_kernel).GetPageTable().GetBasePageTable(); + KPageTableBase& dst_page_table = GetCurrentProcess(m_system.Kernel()).GetPageTable().GetBasePageTable(); // Acquire the table locks. KScopedLightLockPair lk(src_page_table.m_general_lock, dst_page_table.m_general_lock); @@ -3606,7 +3636,7 @@ Result KPageTableBase::OpenMemoryRangeForMapDeviceAddressSpace(KPageTableBase::M KMemoryAttribute::IpcLocked | KMemoryAttribute::Locked, KMemoryAttribute::None)); // We got the range, so open it. - out->Open(); + out->Open(m_system.Kernel()); R_SUCCEED(); } @@ -3624,7 +3654,7 @@ Result KPageTableBase::OpenMemoryRangeForUnmapDeviceAddressSpace(MemoryRange* ou KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked, KMemoryAttribute::DeviceShared)); // We got the range, so open it. - out->Open(); + out->Open(m_system.Kernel()); R_SUCCEED(); } @@ -3697,7 +3727,7 @@ Result KPageTableBase::OpenMemoryRangeForProcessCacheOperation(MemoryRange* out, KMemoryAttribute::None)); // We got the range, so open it. - out->Open(); + out->Open(m_system.Kernel()); R_SUCCEED(); } @@ -3710,7 +3740,7 @@ Result KPageTableBase::CopyMemoryFromLinearToUser( R_UNLESS(this->Contains(src_addr, size), ResultInvalidCurrentMemory); // Get the destination memory reference. - auto& dst_memory = GetCurrentMemory(m_kernel); + auto& dst_memory = GetCurrentMemory(m_system.Kernel()); // Copy the memory. { @@ -3728,7 +3758,7 @@ Result KPageTableBase::CopyMemoryFromLinearToUser( TraversalContext context; TraversalEntry next_entry; bool traverse_valid = - impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), src_addr); + BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), src_addr); ASSERT(traverse_valid); // Prepare tracking variables. @@ -3745,7 +3775,7 @@ Result KPageTableBase::CopyMemoryFromLinearToUser( if (cur_size >= sizeof(u32)) { const size_t copy_size = Common::AlignDown(cur_size, sizeof(u32)); R_UNLESS(dst_memory.WriteBlock(dst_addr, - GetLinearMappedVirtualPointer(m_kernel, cur_addr), + GetLinearMappedVirtualPointer(m_system.Kernel(), cur_addr), copy_size), ResultInvalidCurrentMemory); @@ -3757,7 +3787,7 @@ Result KPageTableBase::CopyMemoryFromLinearToUser( // Copy remaining data. if (cur_size > 0) { R_UNLESS(dst_memory.WriteBlock( - dst_addr, GetLinearMappedVirtualPointer(m_kernel, cur_addr), cur_size), + dst_addr, GetLinearMappedVirtualPointer(m_system.Kernel(), cur_addr), cur_size), ResultInvalidCurrentMemory); } @@ -3768,7 +3798,7 @@ Result KPageTableBase::CopyMemoryFromLinearToUser( while (tot_size < size) { // Continue the traversal. traverse_valid = - impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context)); ASSERT(traverse_valid); if (next_entry.phys_addr != (cur_addr + cur_size)) { @@ -3822,7 +3852,7 @@ Result KPageTableBase::CopyMemoryFromLinearToKernel( TraversalContext context; TraversalEntry next_entry; bool traverse_valid = - impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), src_addr); + BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), src_addr); ASSERT(traverse_valid); // Prepare tracking variables. @@ -3836,7 +3866,7 @@ Result KPageTableBase::CopyMemoryFromLinearToKernel( R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), ResultInvalidCurrentMemory); // Copy the data. - std::memcpy(buffer, GetLinearMappedVirtualPointer(m_kernel, cur_addr), cur_size); + std::memcpy(buffer, GetLinearMappedVirtualPointer(m_system.Kernel(), cur_addr), cur_size); R_SUCCEED(); }; @@ -3845,7 +3875,7 @@ Result KPageTableBase::CopyMemoryFromLinearToKernel( while (tot_size < size) { // Continue the traversal. traverse_valid = - impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context)); ASSERT(traverse_valid); if (next_entry.phys_addr != (cur_addr + cur_size)) { @@ -3884,7 +3914,7 @@ Result KPageTableBase::CopyMemoryFromUserToLinear( R_UNLESS(this->Contains(dst_addr, size), ResultInvalidCurrentMemory); // Get the source memory reference. - auto& src_memory = GetCurrentMemory(m_kernel); + auto& src_memory = GetCurrentMemory(m_system.Kernel()); // Copy the memory. { @@ -3902,7 +3932,7 @@ Result KPageTableBase::CopyMemoryFromUserToLinear( TraversalContext context; TraversalEntry next_entry; bool traverse_valid = - impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_addr); + BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), dst_addr); ASSERT(traverse_valid); // Prepare tracking variables. @@ -3919,7 +3949,7 @@ Result KPageTableBase::CopyMemoryFromUserToLinear( if (cur_size >= sizeof(u32)) { const size_t copy_size = Common::AlignDown(cur_size, sizeof(u32)); R_UNLESS(src_memory.ReadBlock(src_addr, - GetLinearMappedVirtualPointer(m_kernel, cur_addr), + GetLinearMappedVirtualPointer(m_system.Kernel(), cur_addr), copy_size), ResultInvalidCurrentMemory); src_addr += copy_size; @@ -3930,7 +3960,7 @@ Result KPageTableBase::CopyMemoryFromUserToLinear( // Copy remaining data. if (cur_size > 0) { R_UNLESS(src_memory.ReadBlock( - src_addr, GetLinearMappedVirtualPointer(m_kernel, cur_addr), cur_size), + src_addr, GetLinearMappedVirtualPointer(m_system.Kernel(), cur_addr), cur_size), ResultInvalidCurrentMemory); } @@ -3941,7 +3971,7 @@ Result KPageTableBase::CopyMemoryFromUserToLinear( while (tot_size < size) { // Continue the traversal. traverse_valid = - impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context)); ASSERT(traverse_valid); if (next_entry.phys_addr != (cur_addr + cur_size)) { @@ -3997,7 +4027,7 @@ Result KPageTableBase::CopyMemoryFromKernelToLinear(KProcessAddress dst_addr, si TraversalContext context; TraversalEntry next_entry; bool traverse_valid = - impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_addr); + BeginTraversal(impl, std::addressof(next_entry), std::addressof(context), dst_addr); ASSERT(traverse_valid); // Prepare tracking variables. @@ -4011,7 +4041,7 @@ Result KPageTableBase::CopyMemoryFromKernelToLinear(KProcessAddress dst_addr, si R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), ResultInvalidCurrentMemory); // Copy the data. - std::memcpy(GetLinearMappedVirtualPointer(m_kernel, cur_addr), buffer, cur_size); + std::memcpy(GetLinearMappedVirtualPointer(m_system.Kernel(), cur_addr), buffer, cur_size); R_SUCCEED(); }; @@ -4020,7 +4050,7 @@ Result KPageTableBase::CopyMemoryFromKernelToLinear(KProcessAddress dst_addr, si while (tot_size < size) { // Continue the traversal. traverse_valid = - impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + ContinueTraversal(impl, std::addressof(next_entry), std::addressof(context)); ASSERT(traverse_valid); if (next_entry.phys_addr != (cur_addr + cur_size)) { @@ -4089,10 +4119,10 @@ Result KPageTableBase::CopyMemoryFromHeapToHeap( bool traverse_valid; // Begin traversal. - traverse_valid = src_impl.BeginTraversal(std::addressof(src_next_entry), + traverse_valid = BeginTraversal(src_impl, std::addressof(src_next_entry), std::addressof(src_context), src_addr); ASSERT(traverse_valid); - traverse_valid = dst_impl.BeginTraversal(std::addressof(dst_next_entry), + traverse_valid = BeginTraversal(dst_impl, std::addressof(dst_next_entry), std::addressof(dst_context), dst_addr); ASSERT(traverse_valid); @@ -4127,7 +4157,7 @@ Result KPageTableBase::CopyMemoryFromHeapToHeap( if (ofs + cur_copy_size != size) { if (cur_src_addr + cur_min_size == cur_src_block_addr + cur_src_size) { // Continue the src traversal. - traverse_valid = src_impl.ContinueTraversal(std::addressof(src_next_entry), + traverse_valid = ContinueTraversal(src_impl, std::addressof(src_next_entry), std::addressof(src_context)); ASSERT(traverse_valid); @@ -4138,7 +4168,7 @@ Result KPageTableBase::CopyMemoryFromHeapToHeap( if (cur_dst_addr + cur_min_size == dst_next_entry.phys_addr + dst_next_entry.block_size) { // Continue the dst traversal. - traverse_valid = dst_impl.ContinueTraversal(std::addressof(dst_next_entry), + traverse_valid = ContinueTraversal(dst_impl, std::addressof(dst_next_entry), std::addressof(dst_context)); ASSERT(traverse_valid); @@ -4162,8 +4192,8 @@ Result KPageTableBase::CopyMemoryFromHeapToHeap( R_UNLESS(IsHeapPhysicalAddress(cur_dst_addr), ResultInvalidCurrentMemory); // Copy the data. - std::memcpy(GetHeapVirtualPointer(m_kernel, cur_dst_addr), - GetHeapVirtualPointer(m_kernel, cur_src_addr), cur_copy_size); + std::memcpy(GetHeapVirtualPointer(m_system.Kernel(), cur_dst_addr), + GetHeapVirtualPointer(m_system.Kernel(), cur_src_addr), cur_copy_size); // Update. cur_src_block_addr = src_next_entry.phys_addr; @@ -4223,10 +4253,10 @@ Result KPageTableBase::CopyMemoryFromHeapToHeapWithoutCheckDestination( bool traverse_valid; // Begin traversal. - traverse_valid = src_impl.BeginTraversal(std::addressof(src_next_entry), + traverse_valid = BeginTraversal(src_impl, std::addressof(src_next_entry), std::addressof(src_context), src_addr); ASSERT(traverse_valid); - traverse_valid = dst_impl.BeginTraversal(std::addressof(dst_next_entry), + traverse_valid = BeginTraversal(dst_impl, std::addressof(dst_next_entry), std::addressof(dst_context), dst_addr); ASSERT(traverse_valid); @@ -4261,7 +4291,7 @@ Result KPageTableBase::CopyMemoryFromHeapToHeapWithoutCheckDestination( if (ofs + cur_copy_size != size) { if (cur_src_addr + cur_min_size == cur_src_block_addr + cur_src_size) { // Continue the src traversal. - traverse_valid = src_impl.ContinueTraversal(std::addressof(src_next_entry), + traverse_valid = ContinueTraversal(src_impl, std::addressof(src_next_entry), std::addressof(src_context)); ASSERT(traverse_valid); @@ -4272,7 +4302,7 @@ Result KPageTableBase::CopyMemoryFromHeapToHeapWithoutCheckDestination( if (cur_dst_addr + cur_min_size == dst_next_entry.phys_addr + dst_next_entry.block_size) { // Continue the dst traversal. - traverse_valid = dst_impl.ContinueTraversal(std::addressof(dst_next_entry), + traverse_valid = ContinueTraversal(dst_impl, std::addressof(dst_next_entry), std::addressof(dst_context)); ASSERT(traverse_valid); @@ -4296,8 +4326,8 @@ Result KPageTableBase::CopyMemoryFromHeapToHeapWithoutCheckDestination( R_UNLESS(IsHeapPhysicalAddress(cur_dst_addr), ResultInvalidCurrentMemory); // Copy the data. - std::memcpy(GetHeapVirtualPointer(m_kernel, cur_dst_addr), - GetHeapVirtualPointer(m_kernel, cur_src_addr), cur_copy_size); + std::memcpy(GetHeapVirtualPointer(m_system.Kernel(), cur_dst_addr), + GetHeapVirtualPointer(m_system.Kernel(), cur_src_addr), cur_copy_size); // Update. cur_src_block_addr = src_next_entry.phys_addr; @@ -4493,7 +4523,7 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size, // Reserve space for any partial pages we allocate. const size_t unmapped_size = aligned_src_size - mapping_src_size; - KScopedResourceReservation memory_reservation( + KScopedResourceReservation memory_reservation(m_system.Kernel(), m_resource_limit, Svc::LimitableResource::PhysicalMemoryMax, unmapped_size); R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached); @@ -4506,10 +4536,10 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size, // free on scope exit. SCOPE_EXIT { if (start_partial_page != 0) { - m_kernel.MemoryManager().Close(start_partial_page, 1); + m_system.Kernel().MemoryManager().Close(start_partial_page, 1); } if (end_partial_page != 0) { - m_kernel.MemoryManager().Close(end_partial_page, 1); + m_system.Kernel().MemoryManager().Close(end_partial_page, 1); } }; @@ -4526,7 +4556,7 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size, // Allocate the start page as needed. if (aligned_src_start < mapping_src_start) { start_partial_page = - m_kernel.MemoryManager().AllocateAndOpenContinuous(1, 1, m_allocate_option); + m_system.Kernel().MemoryManager().AllocateAndOpenContinuous(1, 1, m_allocate_option); R_UNLESS(start_partial_page != 0, ResultOutOfMemory); } @@ -4534,7 +4564,7 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size, if (mapping_src_end < aligned_src_end && (aligned_src_start < mapping_src_end || aligned_src_start == mapping_src_start)) { end_partial_page = - m_kernel.MemoryManager().AllocateAndOpenContinuous(1, 1, m_allocate_option); + m_system.Kernel().MemoryManager().AllocateAndOpenContinuous(1, 1, m_allocate_option); R_UNLESS(end_partial_page != 0, ResultOutOfMemory); } @@ -4547,7 +4577,7 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size, // Begin traversal. TraversalContext context; TraversalEntry next_entry; - bool traverse_valid = src_impl.BeginTraversal(std::addressof(next_entry), + bool traverse_valid = BeginTraversal(src_impl, std::addressof(next_entry), std::addressof(context), aligned_src_start); ASSERT(traverse_valid); @@ -4560,7 +4590,7 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size, // Map the start page, if we have one. if (start_partial_page != 0) { // Ensure the page holds correct data. - u8* const start_partial_virt = GetHeapVirtualPointer(m_kernel, start_partial_page); + u8* const start_partial_virt = GetHeapVirtualPointer(m_system.Kernel(), start_partial_page); if (send) { const size_t partial_offset = src_start - aligned_src_start; size_t copy_size, clear_size; @@ -4574,7 +4604,7 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size, std::memset(start_partial_virt, fill_val, partial_offset); std::memcpy(start_partial_virt + partial_offset, - GetHeapVirtualPointer(m_kernel, cur_block_addr) + partial_offset, + GetHeapVirtualPointer(m_system.Kernel(), cur_block_addr) + partial_offset, copy_size); if (clear_size > 0) { std::memset(start_partial_virt + partial_offset + copy_size, fill_val, clear_size); @@ -4597,7 +4627,7 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size, // If the block's size was one page, we may need to continue traversal. if (cur_block_size == 0 && aligned_src_size > PageSize) { traverse_valid = - src_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + ContinueTraversal(src_impl, std::addressof(next_entry), std::addressof(context)); ASSERT(traverse_valid); cur_block_addr = next_entry.phys_addr; @@ -4610,7 +4640,7 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size, while (aligned_src_start + tot_block_size < mapping_src_end) { // Continue the traversal. traverse_valid = - src_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + ContinueTraversal(src_impl, std::addressof(next_entry), std::addressof(context)); ASSERT(traverse_valid); // Process the block. @@ -4653,7 +4683,7 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size, if (mapped_block_end + cur_block_size < aligned_src_end && cur_block_size == last_block_size) { traverse_valid = - src_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + ContinueTraversal(src_impl, std::addressof(next_entry), std::addressof(context)); ASSERT(traverse_valid); cur_block_addr = next_entry.phys_addr; @@ -4663,10 +4693,10 @@ Result KPageTableBase::SetupForIpcServer(KProcessAddress* out_addr, size_t size, // Map the end page, if we have one. if (end_partial_page != 0) { // Ensure the page holds correct data. - u8* const end_partial_virt = GetHeapVirtualPointer(m_kernel, end_partial_page); + u8* const end_partial_virt = GetHeapVirtualPointer(m_system.Kernel(), end_partial_page); if (send) { const size_t copy_size = src_end - mapping_src_end; - std::memcpy(end_partial_virt, GetHeapVirtualPointer(m_kernel, cur_block_addr), + std::memcpy(end_partial_virt, GetHeapVirtualPointer(m_system.Kernel(), cur_block_addr), copy_size); std::memset(end_partial_virt + copy_size, fill_val, PageSize - copy_size); } else { @@ -4799,7 +4829,7 @@ Result KPageTableBase::CleanupForIpcServer(KProcessAddress address, size_t size, const KProcessAddress mapping_start = Common::AlignUp(GetInteger(address), PageSize); const KProcessAddress mapping_end = Common::AlignDown(GetInteger(address) + size, PageSize); const size_t mapping_size = (mapping_start < mapping_end) ? mapping_end - mapping_start : 0; - m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, + m_resource_limit->Release(m_system.Kernel(), Svc::LimitableResource::PhysicalMemoryMax, aligned_size - mapping_size); R_SUCCEED(); @@ -5168,20 +5198,20 @@ Result KPageTableBase::MapPhysicalMemory(KProcessAddress address, size_t size) { // Allocate and map the memory. { // Reserve the memory from the process resource limit. - KScopedResourceReservation memory_reservation( + KScopedResourceReservation memory_reservation(m_system.Kernel(), m_resource_limit, Svc::LimitableResource::PhysicalMemoryMax, size - mapped_size); R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached); // Allocate pages for the new memory. - KPageGroup pg(m_kernel, m_block_info_manager); - R_TRY(m_kernel.MemoryManager().AllocateForProcess( + KPageGroup pg(m_system.Kernel(), m_block_info_manager); + R_TRY(m_system.Kernel().MemoryManager().AllocateForProcess( std::addressof(pg), (size - mapped_size) / PageSize, m_allocate_option, - GetCurrentProcess(m_kernel).GetId(), m_heap_fill_value)); + GetCurrentProcess(m_system.Kernel()).GetId(), m_heap_fill_value)); // If we fail in the next bit (or retry), we need to cleanup the pages. auto pg_guard = SCOPE_GUARD { - pg.OpenFirst(); - pg.Close(); + pg.OpenFirst(m_system.Kernel()); + pg.Close(m_system.Kernel()); }; // Map the memory. @@ -5304,12 +5334,12 @@ Result KPageTableBase::MapPhysicalMemory(KProcessAddress address, size_t size) { } // Release any remaining unmapped memory. - m_kernel.MemoryManager().OpenFirst(pg_phys_addr, pg_pages); - m_kernel.MemoryManager().Close(pg_phys_addr, pg_pages); + m_system.Kernel().MemoryManager().OpenFirst(pg_phys_addr, pg_pages); + m_system.Kernel().MemoryManager().Close(pg_phys_addr, pg_pages); for (++pg_it; pg_it != pg.end(); ++pg_it) { - m_kernel.MemoryManager().OpenFirst(pg_it->GetAddress(), + m_system.Kernel().MemoryManager().OpenFirst(pg_it->GetAddress(), pg_it->GetNumPages()); - m_kernel.MemoryManager().Close(pg_it->GetAddress(), pg_it->GetNumPages()); + m_system.Kernel().MemoryManager().Close(pg_it->GetAddress(), pg_it->GetNumPages()); } }; @@ -5337,11 +5367,11 @@ Result KPageTableBase::MapPhysicalMemory(KProcessAddress address, size_t size) { // While we have pages to map, map them. { // Create a page group for the current mapping range. - KPageGroup cur_pg(m_kernel, m_block_info_manager); + KPageGroup cur_pg(m_system.Kernel(), m_block_info_manager); { ON_RESULT_FAILURE_2 { - cur_pg.OpenFirst(); - cur_pg.Close(); + cur_pg.OpenFirst(m_system.Kernel()); + cur_pg.Close(m_system.Kernel()); }; size_t remain_pages = map_pages; @@ -5541,7 +5571,7 @@ Result KPageTableBase::UnmapPhysicalMemory(KProcessAddress address, size_t size) // Release the memory resource. m_mapped_physical_memory_size -= mapped_size; - m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, mapped_size); + m_resource_limit->Release(m_system.Kernel(), Svc::LimitableResource::PhysicalMemoryMax, mapped_size); // Update memory blocks. m_memory_block_manager.Update(std::addressof(allocator), address, size / PageSize, @@ -5601,7 +5631,7 @@ Result KPageTableBase::UnmapProcessMemory(KProcessAddress dst_address, size_t si ContiguousRangeInfo(KPageTableBase& pt, KProcessAddress address, size_t size) : m_pt(pt), m_remaining_size(size) { // Begin a traversal. - ASSERT(m_pt.GetImpl().BeginTraversal(std::addressof(m_entry), + ASSERT(m_pt.BeginTraversal(m_pt.GetImpl(), std::addressof(m_entry), std::addressof(m_context), address)); // Setup tracking fields. @@ -5632,7 +5662,7 @@ Result KPageTableBase::UnmapProcessMemory(KProcessAddress dst_address, size_t si void DetermineContiguousBlockExtents() { // Continue traversing until we're not contiguous, or we have enough. while (m_cur_size < m_remaining_size) { - ASSERT(m_pt.GetImpl().ContinueTraversal(std::addressof(m_entry), + ASSERT(m_pt.ContinueTraversal(m_pt.GetImpl(), std::addressof(m_entry), std::addressof(m_context))); // If we're not contiguous, we're done. @@ -5706,9 +5736,9 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a const bool separate_heap = operation == OperationType::UnmapPhysical; // Ensure that any pages we track are closed on exit. - KPageGroup pages_to_close(m_kernel, this->GetBlockInfoManager()); + KPageGroup pages_to_close(m_system.Kernel(), this->GetBlockInfoManager()); SCOPE_EXIT { - pages_to_close.CloseAndReset(); + pages_to_close.CloseAndReset(m_system.Kernel()); }; // Make a page group representing the region to unmap. @@ -5727,7 +5757,7 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a // Open references to pages, if we should. if (this->IsHeapPhysicalAddress(phys_addr)) { - m_kernel.MemoryManager().Open(phys_addr, num_pages); + m_system.Kernel().MemoryManager().Open(phys_addr, num_pages); } R_SUCCEED(); @@ -5767,7 +5797,7 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a const bool separate_heap = operation == OperationType::MapFirstGroupPhysical; // We want to maintain a new reference to every page in the group. - KScopedPageGroup spg(page_group, operation == OperationType::MapGroup); + KScopedPageGroup spg(m_system.Kernel(), page_group, operation == OperationType::MapGroup); for (const auto& node : page_group) { const size_t size{node.GetNumPages() * PageSize}; diff --git a/src/core/hle/kernel/k_page_table_base.h b/src/core/hle/kernel/k_page_table_base.h index 726fad989f..0c222910fc 100644 --- a/src/core/hle/kernel/k_page_table_base.h +++ b/src/core/hle/kernel/k_page_table_base.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include "common/common_funcs.h" #include "common/page_table.h" @@ -61,14 +62,12 @@ public: class MemoryRange { private: - KernelCore& m_kernel; - KPhysicalAddress m_address; - size_t m_size; - bool m_heap; + KPhysicalAddress m_address = 0; + size_t m_size = 0; + bool m_heap = false; public: - explicit MemoryRange(KernelCore& kernel) - : m_kernel(kernel), m_address(0), m_size(0), m_heap(false) {} + explicit MemoryRange() : m_address(0), m_size(0), m_heap(false) {} void Set(KPhysicalAddress address, size_t size, bool heap) { m_address = address; @@ -86,8 +85,8 @@ public: return m_heap; } - void Open(); - void Close(); + void Open(KernelCore& kernel); + void Close(KernelCore& kernel); }; protected: @@ -189,7 +188,6 @@ private: }; private: - KernelCore& m_kernel; Core::System& m_system; KProcessAddress m_address_space_start{}; KProcessAddress m_address_space_end{}; @@ -329,35 +327,35 @@ protected: bool IsLinearMappedPhysicalAddress(KPhysicalAddress phys_addr) { ASSERT(this->IsLockedByCurrentThread()); - return m_kernel.MemoryLayout().IsLinearMappedPhysicalAddress( + return m_system.Kernel().MemoryLayout().IsLinearMappedPhysicalAddress( m_cached_physical_linear_region, phys_addr); } bool IsLinearMappedPhysicalAddress(KPhysicalAddress phys_addr, size_t size) { ASSERT(this->IsLockedByCurrentThread()); - return m_kernel.MemoryLayout().IsLinearMappedPhysicalAddress( + return m_system.Kernel().MemoryLayout().IsLinearMappedPhysicalAddress( m_cached_physical_linear_region, phys_addr, size); } bool IsHeapPhysicalAddress(KPhysicalAddress phys_addr) { ASSERT(this->IsLockedByCurrentThread()); - return m_kernel.MemoryLayout().IsHeapPhysicalAddress(m_cached_physical_heap_region, + return m_system.Kernel().MemoryLayout().IsHeapPhysicalAddress(m_cached_physical_heap_region, phys_addr); } bool IsHeapPhysicalAddress(KPhysicalAddress phys_addr, size_t size) { ASSERT(this->IsLockedByCurrentThread()); - return m_kernel.MemoryLayout().IsHeapPhysicalAddress(m_cached_physical_heap_region, + return m_system.Kernel().MemoryLayout().IsHeapPhysicalAddress(m_cached_physical_heap_region, phys_addr, size); } bool IsHeapPhysicalAddressForFinalize(KPhysicalAddress phys_addr) { ASSERT(!this->IsLockedByCurrentThread()); - return m_kernel.MemoryLayout().IsHeapPhysicalAddress(m_cached_physical_heap_region, + return m_system.Kernel().MemoryLayout().IsHeapPhysicalAddress(m_cached_physical_heap_region, phys_addr); } @@ -372,6 +370,10 @@ private: size_t num_pages, size_t alignment, size_t offset, size_t guard_pages) const; + bool BeginTraversal(const Common::PageTable& impl, TraversalEntry* out_entry, TraversalContext* out_context, + Common::ProcessAddress address) const; + bool ContinueTraversal(const Common::PageTable& impl, TraversalEntry* out_entry, TraversalContext* context) const; + Result CheckMemoryStateContiguous(size_t* out_blocks_needed, KProcessAddress addr, size_t size, KMemoryState state_mask, KMemoryState state, KMemoryPermission perm_mask, KMemoryPermission perm, @@ -476,7 +478,14 @@ private: // Validate pre-conditions. ASSERT(this->IsLockedByCurrentThread()); - return this->GetImpl().GetPhysicalAddress(out, virt_addr); + if (virt_addr > (1ULL << m_address_space_width)) { + return false; + } + + *out = m_system.DeviceMemory().GetPhysicalAddr( + this->GetImpl().entries[GetInteger(virt_addr) >> PageBits].Pointer(true) + GetInteger(virt_addr)); + + return true; } public: @@ -744,15 +753,15 @@ public: // Member heap u8* GetHeapVirtualPointer(KPhysicalAddress addr) { - return GetHeapVirtualPointer(m_kernel, addr); + return GetHeapVirtualPointer(m_system.Kernel(), addr); } KPhysicalAddress GetHeapPhysicalAddress(KVirtualAddress addr) { - return GetHeapPhysicalAddress(m_kernel, addr); + return GetHeapPhysicalAddress(m_system.Kernel(), addr); } KVirtualAddress GetHeapVirtualAddress(KPhysicalAddress addr) { - return GetHeapVirtualAddress(m_kernel, addr); + return GetHeapVirtualAddress(m_system.Kernel(), addr); } // TODO: GetPageTableVirtualAddress diff --git a/src/core/hle/kernel/k_port.cpp b/src/core/hle/kernel/k_port.cpp index e5f5d8028c..f64d983e0c 100644 --- a/src/core/hle/kernel/k_port.cpp +++ b/src/core/hle/kernel/k_port.cpp @@ -1,8 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "core/hle/kernel/k_port.h" #include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/svc_results.h" namespace Kernel { @@ -12,15 +16,15 @@ KPort::KPort(KernelCore& kernel) KPort::~KPort() = default; -void KPort::Initialize(s32 max_sessions, bool is_light, uintptr_t name) { +void KPort::Initialize(KernelCore& kernel, s32 max_sessions, bool is_light, uintptr_t name) { // Open a new reference count to the initialized port. - this->Open(); + this->Open(kernel); // Create and initialize our server/client pair. KAutoObject::Create(std::addressof(m_server)); KAutoObject::Create(std::addressof(m_client)); - m_server.Initialize(this); - m_client.Initialize(this, max_sessions); + m_server.Initialize(kernel, this); + m_client.Initialize(kernel, this, max_sessions); // Set our member variables. m_is_light = is_light; @@ -28,42 +32,38 @@ void KPort::Initialize(s32 max_sessions, bool is_light, uintptr_t name) { m_state = State::Normal; } -void KPort::OnClientClosed() { - KScopedSchedulerLock sl{m_kernel}; +void KPort::OnClientClosed(KernelCore& kernel) { + KScopedSchedulerLock sl{kernel}; if (m_state == State::Normal) { m_state = State::ClientClosed; } } -void KPort::OnServerClosed() { - KScopedSchedulerLock sl{m_kernel}; +void KPort::OnServerClosed(KernelCore& kernel) { + KScopedSchedulerLock sl{kernel}; if (m_state == State::Normal) { m_state = State::ServerClosed; } } -bool KPort::IsServerClosed() const { - KScopedSchedulerLock sl{m_kernel}; +bool KPort::IsServerClosed(KernelCore& kernel) const { + KScopedSchedulerLock sl{kernel}; return m_state == State::ServerClosed; } -Result KPort::EnqueueSession(KServerSession* session) { - KScopedSchedulerLock sl{m_kernel}; - +Result KPort::EnqueueSession(KernelCore& kernel, KServerSession* session) { + KScopedSchedulerLock sl{kernel}; R_UNLESS(m_state == State::Normal, ResultPortClosed); - - m_server.EnqueueSession(session); + m_server.EnqueueSession(kernel, session); R_SUCCEED(); } -Result KPort::EnqueueSession(KLightServerSession* session) { - KScopedSchedulerLock sl{m_kernel}; - +Result KPort::EnqueueSession(KernelCore& kernel, KLightServerSession* session) { + KScopedSchedulerLock sl{kernel}; R_UNLESS(m_state == State::Normal, ResultPortClosed); - - m_server.EnqueueSession(session); + m_server.EnqueueSession(kernel, session); R_SUCCEED(); } diff --git a/src/core/hle/kernel/k_port.h b/src/core/hle/kernel/k_port.h index 26f5f14eff..698f794538 100644 --- a/src/core/hle/kernel/k_port.h +++ b/src/core/hle/kernel/k_port.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -23,11 +26,11 @@ public: explicit KPort(KernelCore& kernel); ~KPort() override; - static void PostDestroy(uintptr_t arg) {} + static void PostDestroy(KernelCore& kernel, uintptr_t arg) {} - void Initialize(s32 max_sessions, bool is_light, uintptr_t name); - void OnClientClosed(); - void OnServerClosed(); + void Initialize(KernelCore& kernel, s32 max_sessions, bool is_light, uintptr_t name); + void OnClientClosed(KernelCore& kernel); + void OnServerClosed(KernelCore& kernel); uintptr_t GetName() const { return m_name; @@ -36,10 +39,10 @@ public: return m_is_light; } - bool IsServerClosed() const; + bool IsServerClosed(KernelCore& kernel) const; - Result EnqueueSession(KServerSession* session); - Result EnqueueSession(KLightServerSession* session); + Result EnqueueSession(KernelCore& kernel, KServerSession* session); + Result EnqueueSession(KernelCore& kernel, KLightServerSession* session); KClientPort& GetClientPort() { return m_client; diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index 70e578f22a..27d1b1bfc4 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -28,8 +28,7 @@ namespace Kernel { namespace { -Result TerminateChildren(KernelCore& kernel, KProcess* process, - const KThread* thread_to_not_terminate) { +Result TerminateChildren(KernelCore& kernel, KProcess* process, const KThread* thread_to_not_terminate) { // Request that all children threads terminate. { KScopedLightLock proc_lk(process->GetListLock()); @@ -41,14 +40,14 @@ Result TerminateChildren(KernelCore& kernel, KProcess* process, // This is valid because the only caller which uses non-nullptr as argument uses // GetCurrentThreadPointer(), but it's still notable because it seems incorrect at // first glance. - process->UnpinCurrentThread(); + process->UnpinCurrentThread(kernel); } auto& thread_list = process->GetThreadList(); for (auto it = thread_list.begin(); it != thread_list.end(); ++it) { if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) { if (thread->GetState() != ThreadState::Terminated) { - thread->RequestTerminate(); + thread->RequestTerminate(kernel); } } } @@ -65,7 +64,7 @@ Result TerminateChildren(KernelCore& kernel, KProcess* process, for (auto it = thread_list.begin(); it != thread_list.end(); ++it) { if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) { if (thread->GetState() != ThreadState::Terminated) { - if (thread->Open()) { + if (thread->Open(kernel)) { cur_child = thread; break; } @@ -81,11 +80,10 @@ Result TerminateChildren(KernelCore& kernel, KProcess* process, // Terminate and close the thread. SCOPE_EXIT { - cur_child->Close(); + cur_child->Close(kernel); }; - if (const Result terminate_result = cur_child->Terminate(); - ResultTerminationRequested == terminate_result) { + if (const Result terminate_result = cur_child->Terminate(kernel); ResultTerminationRequested == terminate_result) { R_THROW(terminate_result); } } @@ -101,21 +99,21 @@ public: explicit ThreadQueueImplForKProcessEnterUserException(KernelCore& kernel, KThread** t) : KThreadQueue(kernel), m_exception_thread(t) {} - virtual void EndWait(KThread* waiting_thread, Result wait_result) override { + virtual void EndWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result) override { // Set the exception thread. *m_exception_thread = waiting_thread; // Invoke the base end wait handler. - KThreadQueue::EndWait(waiting_thread, wait_result); + KThreadQueue::EndWait(kernel, waiting_thread, wait_result); } - virtual void CancelWait(KThread* waiting_thread, Result wait_result, + virtual void CancelWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { // Remove the thread as a waiter on its mutex owner. - waiting_thread->GetLockOwner()->RemoveWaiter(waiting_thread); + waiting_thread->GetLockOwner(kernel)->RemoveWaiter(kernel, waiting_thread); // Invoke the base cancel wait handler. - KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + KThreadQueue::CancelWait(kernel, waiting_thread, wait_result, cancel_timer_task); } }; @@ -128,12 +126,12 @@ void GenerateRandom(std::span out_random) { } // namespace -void KProcess::Finalize() { +void KProcess::Finalize(KernelCore& kernel) { // Delete the process local region. - this->DeleteThreadLocalRegion(m_plr_address); + this->DeleteThreadLocalRegion(kernel, m_plr_address); // Get the used memory size. - const size_t used_memory_size = this->GetUsedNonSystemUserPhysicalMemorySize(); + const size_t used_memory_size = this->GetUsedNonSystemUserPhysicalMemorySize(kernel); // Finalize the page table. m_page_table.Finalize(); @@ -142,10 +140,10 @@ void KProcess::Finalize() { if (m_system_resource) { if (m_system_resource->IsSecureResource()) { // Finalize optimized memory. If memory wasn't optimized, this is a no-op. - m_kernel.MemoryManager().FinalizeOptimizedMemory(this->GetId(), m_memory_pool); + kernel.MemoryManager().FinalizeOptimizedMemory(this->GetId(), m_memory_pool); } - m_system_resource->Close(); + m_system_resource->Close(kernel); m_system_resource = nullptr; } @@ -157,12 +155,12 @@ void KProcess::Finalize() { KSharedMemory* shmem = info->GetSharedMemory(); while (!info->Close()) { - shmem->Close(); + shmem->Close(kernel); } - shmem->Close(); + shmem->Close(kernel); it = m_shared_memory_list.erase(it); - KSharedMemoryInfo::Free(m_kernel, info); + KSharedMemoryInfo::Free(kernel, info); } } @@ -173,9 +171,8 @@ void KProcess::Finalize() { // Release memory to the resource limit. if (m_resource_limit != nullptr) { ASSERT(used_memory_size >= m_memory_release_hint); - m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, used_memory_size, - used_memory_size - m_memory_release_hint); - m_resource_limit->Close(); + m_resource_limit->Release(kernel, Svc::LimitableResource::PhysicalMemoryMax, used_memory_size, used_memory_size - m_memory_release_hint); + m_resource_limit->Close(kernel); } // Clear expensive resources, as the destructor is not called for guest objects. @@ -185,15 +182,14 @@ void KProcess::Finalize() { m_exclusive_monitor.reset(); // Perform inherited finalization. - KSynchronizationObject::Finalize(); + KSynchronizationObject::Finalize(kernel); } -Result KProcess::Initialize(const Svc::CreateProcessParameter& params, KResourceLimit* res_limit, - bool is_real) { +Result KProcess::Initialize(KernelCore& kernel, const Svc::CreateProcessParameter& params, KResourceLimit* res_limit, bool is_real) { // TODO: remove this special case if (is_real) { // Create and clear the process local region. - R_TRY(this->CreateThreadLocalRegion(std::addressof(m_plr_address))); + R_TRY(this->CreateThreadLocalRegion(kernel, std::addressof(m_plr_address))); this->GetMemory().ZeroBlock(m_plr_address, Svc::ThreadLocalRegionSize); } @@ -211,6 +207,9 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, KResource m_version = params.version; m_program_id = params.program_id; m_code_address = params.code_address; + m_arg_pointer = 0; + m_arg_return_address = 0; + m_main_thread_handle_addr = 0; m_code_size = params.code_num_pages * PageSize; m_is_application = True(params.flags & Svc::CreateProcessFlag::IsApplication); @@ -256,7 +255,7 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, KResource // Open a reference to our resource limit. m_resource_limit = res_limit; - m_resource_limit->Open(); + m_resource_limit->Open(kernel); // We're initialized! m_is_initialized = true; @@ -264,7 +263,7 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, KResource R_SUCCEED(); } -Result KProcess::Initialize(const Svc::CreateProcessParameter& params, const KPageGroup& pg, +Result KProcess::Initialize(KernelCore& kernel, const Svc::CreateProcessParameter& params, const KPageGroup& pg, std::span caps, KResourceLimit* res_limit, KMemoryManager::Pool pool, bool immortal) { ASSERT(res_limit != nullptr); @@ -280,34 +279,33 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, const KPa if (const size_t system_resource_num_pages = params.system_resource_num_pages; system_resource_num_pages != 0) { // Create a secure system resource. - KSecureSystemResource* secure_resource = KSecureSystemResource::Create(m_kernel); + KSecureSystemResource* secure_resource = KSecureSystemResource::Create(kernel); R_UNLESS(secure_resource != nullptr, ResultOutOfResource); ON_RESULT_FAILURE { - secure_resource->Close(); + secure_resource->Close(kernel); }; // Initialize the secure resource. - R_TRY(secure_resource->Initialize(system_resource_num_pages * PageSize, res_limit, - m_memory_pool)); + R_TRY(secure_resource->Initialize(kernel, system_resource_num_pages * PageSize, res_limit, m_memory_pool)); // Set our system resource. m_system_resource = secure_resource; } else { // Use the system-wide system resource. const bool is_app = True(params.flags & Svc::CreateProcessFlag::IsApplication); - m_system_resource = std::addressof(is_app ? m_kernel.GetAppSystemResource() - : m_kernel.GetSystemSystemResource()); + m_system_resource = std::addressof(is_app ? kernel.GetAppSystemResource() + : kernel.GetSystemSystemResource()); m_is_default_application_system_resource = is_app; // Open reference to the system resource. - m_system_resource->Open(); + m_system_resource->Open(kernel); } // Ensure we clean up our secure resource, if we fail. ON_RESULT_FAILURE { - m_system_resource->Close(); + m_system_resource->Close(kernel); m_system_resource = nullptr; }; @@ -327,7 +325,7 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, const KPa // Ensure our memory is initialized. m_memory.SetCurrentPageTable(*this); - m_memory.SetGPUDirtyManagers(m_kernel.System().GetGPUDirtyMemoryManager()); + m_memory.SetGPUDirtyManagers(kernel.System().GetGPUDirtyMemoryManager()); // Ensure we can insert the code region. R_UNLESS(m_page_table.CanContain(params.code_address, params.code_num_pages * PageSize, @@ -347,18 +345,18 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, const KPa } // Initialize the process id. - m_process_id = m_kernel.CreateNewUserProcessID(); + m_process_id = kernel.CreateNewUserProcessID(); ASSERT(InitialProcessIdMin <= m_process_id); ASSERT(m_process_id <= InitialProcessIdMax); // Initialize the rest of the process. - R_TRY(this->Initialize(params, res_limit, true)); + R_TRY(this->Initialize(kernel, params, res_limit, true)); // We succeeded! R_SUCCEED(); } -Result KProcess::Initialize(const Svc::CreateProcessParameter& params, +Result KProcess::Initialize(KernelCore& kernel, const Svc::CreateProcessParameter& params, std::span user_caps, KResourceLimit* res_limit, KMemoryManager::Pool pool, KProcessAddress aslr_space_start) { ASSERT(res_limit != nullptr); @@ -375,22 +373,22 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, const size_t system_resource_size = system_resource_num_pages * PageSize; // Reserve memory for our code resource. - KScopedResourceReservation memory_reservation( + KScopedResourceReservation memory_reservation(kernel, res_limit, Svc::LimitableResource::PhysicalMemoryMax, code_size); R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached); // Setup our system resource. if (system_resource_num_pages != 0) { // Create a secure system resource. - KSecureSystemResource* secure_resource = KSecureSystemResource::Create(m_kernel); + KSecureSystemResource* secure_resource = KSecureSystemResource::Create(kernel); R_UNLESS(secure_resource != nullptr, ResultOutOfResource); ON_RESULT_FAILURE { - secure_resource->Close(); + secure_resource->Close(kernel); }; // Initialize the secure resource. - R_TRY(secure_resource->Initialize(system_resource_size, res_limit, m_memory_pool)); + R_TRY(secure_resource->Initialize(kernel, system_resource_size, res_limit, m_memory_pool)); // Set our system resource. m_system_resource = secure_resource; @@ -398,18 +396,18 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, } else { // Use the system-wide system resource. const bool is_app = True(params.flags & Svc::CreateProcessFlag::IsApplication); - m_system_resource = std::addressof(is_app ? m_kernel.GetAppSystemResource() - : m_kernel.GetSystemSystemResource()); + m_system_resource = std::addressof(is_app ? kernel.GetAppSystemResource() + : kernel.GetSystemSystemResource()); m_is_default_application_system_resource = is_app; // Open reference to the system resource. - m_system_resource->Open(); + m_system_resource->Open(kernel); } // Ensure we clean up our secure resource, if we fail. ON_RESULT_FAILURE { - m_system_resource->Close(); + m_system_resource->Close(kernel); m_system_resource = nullptr; }; @@ -429,7 +427,7 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, // Ensure our memory is initialized. m_memory.SetCurrentPageTable(*this); - m_memory.SetGPUDirtyManagers(m_kernel.System().GetGPUDirtyMemoryManager()); + m_memory.SetGPUDirtyManagers(kernel.System().GetGPUDirtyMemoryManager()); // Ensure we can insert the code region. R_UNLESS(m_page_table.CanContain(params.code_address, code_size, KMemoryState::Code), @@ -448,101 +446,99 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, } // Initialize the process id. - m_process_id = m_kernel.CreateNewUserProcessID(); + m_process_id = kernel.CreateNewUserProcessID(); ASSERT(ProcessIdMin <= m_process_id); ASSERT(m_process_id <= ProcessIdMax); // If we should optimize memory allocations, do so. if (m_system_resource->IsSecureResource() && True(params.flags & Svc::CreateProcessFlag::OptimizeMemoryAllocation)) { - R_TRY(m_kernel.MemoryManager().InitializeOptimizedMemory(m_process_id, pool)); + R_TRY(kernel.MemoryManager().InitializeOptimizedMemory(m_process_id, pool)); } // Initialize the rest of the process. - R_TRY(this->Initialize(params, res_limit, true)); + R_TRY(this->Initialize(kernel, params, res_limit, true)); // We succeeded, so commit our memory reservation. memory_reservation.Commit(); R_SUCCEED(); } -void KProcess::DoWorkerTaskImpl() { +void KProcess::DoWorkerTaskImpl(KernelCore& kernel) { // Terminate child threads. - TerminateChildren(m_kernel, this, nullptr); + TerminateChildren(kernel, this, nullptr); // Finalize the handle table, if we're not immortal. if (!m_is_immortal && m_is_handle_table_initialized) { - this->FinalizeHandleTable(); + this->FinalizeHandleTable(kernel); } // Finish termination. - this->FinishTermination(); + this->FinishTermination(kernel); } -Result KProcess::StartTermination() { +Result KProcess::StartTermination(KernelCore& kernel) { // Finalize the handle table when we're done, if the process isn't immortal. SCOPE_EXIT { if (!m_is_immortal) { - this->FinalizeHandleTable(); + this->FinalizeHandleTable(kernel); } }; // Terminate child threads other than the current one. - R_RETURN(TerminateChildren(m_kernel, this, GetCurrentThreadPointer(m_kernel))); + R_RETURN(TerminateChildren(kernel, this, GetCurrentThreadPointer(kernel))); } -void KProcess::FinishTermination() { +void KProcess::FinishTermination(KernelCore& kernel) { // Only allow termination to occur if the process isn't immortal. if (!m_is_immortal) { // Release resource limit hint. if (m_resource_limit != nullptr) { - m_memory_release_hint = this->GetUsedNonSystemUserPhysicalMemorySize(); - m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, 0, - m_memory_release_hint); + m_memory_release_hint = this->GetUsedNonSystemUserPhysicalMemorySize(kernel); + m_resource_limit->Release(kernel, Svc::LimitableResource::PhysicalMemoryMax, 0, m_memory_release_hint); } // Change state. { - KScopedSchedulerLock sl(m_kernel); - this->ChangeState(State::Terminated); + KScopedSchedulerLock sl(kernel); + this->ChangeState(kernel, State::Terminated); } // Close. - this->Close(); + this->Close(kernel); } } -void KProcess::Exit() { +void KProcess::Exit(KernelCore& kernel) { // Determine whether we need to start terminating bool needs_terminate = false; { KScopedLightLock lk(m_state_lock); - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); ASSERT(m_state != State::Created); ASSERT(m_state != State::CreatedAttached); ASSERT(m_state != State::Crashed); ASSERT(m_state != State::Terminated); - if (m_state == State::Running || m_state == State::RunningAttached || - m_state == State::DebugBreak) { - this->ChangeState(State::Terminating); + if (m_state == State::Running || m_state == State::RunningAttached || m_state == State::DebugBreak) { + this->ChangeState(kernel, State::Terminating); needs_terminate = true; } } // If we need to start termination, do so. if (needs_terminate) { - this->StartTermination(); + this->StartTermination(kernel); // Register the process as a work task. - m_kernel.WorkerTaskManager().AddTask(m_kernel, KWorkerTaskManager::WorkerType::Exit, this); + kernel.WorkerTaskManager().AddTask(kernel, KWorkerTaskManager::WorkerType::Exit, this); } // Exit the current thread. - GetCurrentThread(m_kernel).Exit(); + GetCurrentThread(kernel).Exit(kernel); } -Result KProcess::Terminate() { +Result KProcess::Terminate(KernelCore& kernel) { // Determine whether we need to start terminating. bool needs_terminate = false; { @@ -552,11 +548,11 @@ Result KProcess::Terminate() { R_UNLESS(m_state != State::Created, ResultInvalidState); R_UNLESS(m_state != State::CreatedAttached, ResultInvalidState); - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); if (m_state == State::Running || m_state == State::RunningAttached || m_state == State::Crashed || m_state == State::DebugBreak) { - this->ChangeState(State::Terminating); + this->ChangeState(kernel, State::Terminating); needs_terminate = true; } } @@ -564,20 +560,19 @@ Result KProcess::Terminate() { // If we need to terminate, do so. if (needs_terminate) { // Start termination. - if (R_SUCCEEDED(this->StartTermination())) { + if (R_SUCCEEDED(this->StartTermination(kernel))) { // Finish termination. - this->FinishTermination(); + this->FinishTermination(kernel); } else { // Register the process as a work task. - m_kernel.WorkerTaskManager().AddTask(m_kernel, KWorkerTaskManager::WorkerType::Exit, - this); + kernel.WorkerTaskManager().AddTask(kernel, KWorkerTaskManager::WorkerType::Exit, this); } } R_SUCCEED(); } -Result KProcess::AddSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size) { +Result KProcess::AddSharedMemory(KernelCore& kernel, KSharedMemory* shmem, KProcessAddress address, size_t size) { // Lock ourselves, to prevent concurrent access. KScopedLightLock lk(m_state_lock); @@ -593,7 +588,7 @@ Result KProcess::AddSharedMemory(KSharedMemory* shmem, KProcessAddress address, // If we didn't find an info, create one. if (info == nullptr) { // Allocate a new info. - info = KSharedMemoryInfo::Allocate(m_kernel); + info = KSharedMemoryInfo::Allocate(kernel); R_UNLESS(info != nullptr, ResultOutOfResource); // Initialize the info and add it to our list. @@ -602,13 +597,13 @@ Result KProcess::AddSharedMemory(KSharedMemory* shmem, KProcessAddress address, } // Open a reference to the shared memory and its info. - shmem->Open(); + shmem->Open(kernel); info->Open(); R_SUCCEED(); } -void KProcess::RemoveSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size) { +void KProcess::RemoveSharedMemory(KernelCore& kernel, KSharedMemory* shmem, KProcessAddress address, size_t size) { // Lock ourselves, to prevent concurrent access. KScopedLightLock lk(m_state_lock); @@ -626,19 +621,19 @@ void KProcess::RemoveSharedMemory(KSharedMemory* shmem, KProcessAddress address, // Close a reference to the info and its memory. if (info->Close()) { m_shared_memory_list.erase(it); - KSharedMemoryInfo::Free(m_kernel, info); + KSharedMemoryInfo::Free(kernel, info); } - shmem->Close(); + shmem->Close(kernel); } -Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) { +Result KProcess::CreateThreadLocalRegion(KernelCore& kernel, KProcessAddress* out) { KThreadLocalPage* tlp = nullptr; KProcessAddress tlr = 0; // See if we can get a region from a partially used TLP. { - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); if (auto it = m_partially_used_tlp_tree.begin(); it != m_partially_used_tlp_tree.end()) { tlr = it->Reserve(); @@ -656,14 +651,14 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) { } // Allocate a new page. - tlp = KThreadLocalPage::Allocate(m_kernel); + tlp = KThreadLocalPage::Allocate(kernel); R_UNLESS(tlp != nullptr, ResultOutOfMemory); ON_RESULT_FAILURE { - KThreadLocalPage::Free(m_kernel, tlp); + KThreadLocalPage::Free(kernel, tlp); }; // Initialize the new page. - R_TRY(tlp->Initialize(m_kernel, this)); + R_TRY(tlp->Initialize(kernel, this)); // Reserve a TLR. tlr = tlp->Reserve(); @@ -671,7 +666,7 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) { // Insert into our tree. { - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); if (tlp->IsAllUsed()) { m_fully_used_tlp_tree.insert(*tlp); } else { @@ -684,12 +679,12 @@ Result KProcess::CreateThreadLocalRegion(KProcessAddress* out) { R_SUCCEED(); } -Result KProcess::DeleteThreadLocalRegion(KProcessAddress addr) { +Result KProcess::DeleteThreadLocalRegion(KernelCore& kernel, KProcessAddress addr) { KThreadLocalPage* page_to_free = nullptr; // Release the region. { - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); // Try to find the page in the partially used list. auto it = m_partially_used_tlp_tree.find_key(Common::AlignDown(GetInteger(addr), PageSize)); @@ -726,57 +721,56 @@ Result KProcess::DeleteThreadLocalRegion(KProcessAddress addr) { if (page_to_free != nullptr) { page_to_free->Finalize(); - KThreadLocalPage::Free(m_kernel, page_to_free); + KThreadLocalPage::Free(kernel, page_to_free); } R_SUCCEED(); } -bool KProcess::ReserveResource(Svc::LimitableResource which, s64 value) { +bool KProcess::ReserveResource(KernelCore& kernel, Svc::LimitableResource which, s64 value) { if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) { - return rl->Reserve(which, value); + return rl->Reserve(kernel, which, value); } else { return true; } } -bool KProcess::ReserveResource(Svc::LimitableResource which, s64 value, s64 timeout) { +bool KProcess::ReserveResource(KernelCore& kernel, Svc::LimitableResource which, s64 value, s64 timeout) { if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) { - return rl->Reserve(which, value, timeout); + return rl->Reserve(kernel, which, value, timeout); } else { return true; } } -void KProcess::ReleaseResource(Svc::LimitableResource which, s64 value) { +void KProcess::ReleaseResource(KernelCore& kernel, Svc::LimitableResource which, s64 value) { if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) { - rl->Release(which, value); + rl->Release(kernel, which, value); } } -void KProcess::ReleaseResource(Svc::LimitableResource which, s64 value, s64 hint) { +void KProcess::ReleaseResource(KernelCore& kernel, Svc::LimitableResource which, s64 value, s64 hint) { if (KResourceLimit* rl = this->GetResourceLimit(); rl != nullptr) { - rl->Release(which, value, hint); + rl->Release(kernel, which, value, hint); } } -void KProcess::IncrementRunningThreadCount() { +void KProcess::IncrementRunningThreadCount(KernelCore& kernel) { ASSERT(m_num_running_threads.load() >= 0); ++m_num_running_threads; } -void KProcess::DecrementRunningThreadCount() { +void KProcess::DecrementRunningThreadCount(KernelCore& kernel) { ASSERT(m_num_running_threads.load() > 0); - if (const auto prev = m_num_running_threads--; prev == 1) { - this->Terminate(); + this->Terminate(kernel); } } -bool KProcess::EnterUserException() { +bool KProcess::EnterUserException(KernelCore& kernel) { // Get the current thread. - KThread* cur_thread = GetCurrentThreadPointer(m_kernel); + KThread* cur_thread = GetCurrentThreadPointer(kernel); ASSERT(this == cur_thread->GetOwnerProcess()); // Check that we haven't already claimed the exception thread. @@ -785,13 +779,13 @@ bool KProcess::EnterUserException() { } // Create the wait queue we'll be using. - ThreadQueueImplForKProcessEnterUserException wait_queue(m_kernel, + ThreadQueueImplForKProcessEnterUserException wait_queue(kernel, std::addressof(m_exception_thread)); // Claim the exception thread. { // Lock the scheduler. - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); // Check that we're not terminating. if (cur_thread->IsTerminationRequested()) { @@ -801,45 +795,44 @@ bool KProcess::EnterUserException() { // If we don't have an exception thread, we can just claim it directly. if (m_exception_thread == nullptr) { m_exception_thread = cur_thread; - KScheduler::SetSchedulerUpdateNeeded(m_kernel); + KScheduler::SetSchedulerUpdateNeeded(kernel); return true; } // Otherwise, we need to wait until we don't have an exception thread. // Add the current thread as a waiter on the current exception thread. - cur_thread->SetKernelAddressKey( - reinterpret_cast(std::addressof(m_exception_thread)) | 1); - m_exception_thread->AddWaiter(cur_thread); + cur_thread->SetKernelAddressKey(uintptr_t(std::addressof(m_exception_thread)) | 1); + m_exception_thread->AddWaiter(kernel, cur_thread); // Wait to claim the exception thread. - cur_thread->BeginWait(std::addressof(wait_queue)); + cur_thread->BeginWait(kernel, std::addressof(wait_queue)); } // If our wait didn't end due to thread termination, we succeeded. return ResultTerminationRequested != cur_thread->GetWaitResult(); } -bool KProcess::LeaveUserException() { - return this->ReleaseUserException(GetCurrentThreadPointer(m_kernel)); +bool KProcess::LeaveUserException(KernelCore& kernel) { + return this->ReleaseUserException(kernel, GetCurrentThreadPointer(kernel)); } -bool KProcess::ReleaseUserException(KThread* thread) { - KScopedSchedulerLock sl(m_kernel); +bool KProcess::ReleaseUserException(KernelCore& kernel, KThread* thread) { + KScopedSchedulerLock sl(kernel); if (m_exception_thread == thread) { m_exception_thread = nullptr; // Remove waiter thread. bool has_waiters; - if (KThread* next = thread->RemoveKernelWaiterByKey( + if (KThread* next = thread->RemoveKernelWaiterByKey(kernel, std::addressof(has_waiters), reinterpret_cast(std::addressof(m_exception_thread)) | 1); next != nullptr) { - next->EndWait(ResultSuccess); + next->EndWait(kernel, ResultSuccess); } - KScheduler::SetSchedulerUpdateNeeded(m_kernel); + KScheduler::SetSchedulerUpdateNeeded(kernel); return true; } else { @@ -847,19 +840,19 @@ bool KProcess::ReleaseUserException(KThread* thread) { } } -void KProcess::RegisterThread(KThread* thread) { +void KProcess::RegisterThread(KernelCore& kernel, KThread* thread) { KScopedLightLock lk(m_list_lock); m_thread_list.push_back(*thread); } -void KProcess::UnregisterThread(KThread* thread) { +void KProcess::UnregisterThread(KernelCore& kernel, KThread* thread) { KScopedLightLock lk(m_list_lock); m_thread_list.erase(m_thread_list.iterator_to(*thread)); } -size_t KProcess::GetUsedUserPhysicalMemorySize() const { +size_t KProcess::GetUsedUserPhysicalMemorySize(KernelCore& kernel) const { const size_t norm_size = m_page_table.GetNormalMemorySize(); const size_t other_size = m_code_size + m_main_thread_stack_size; const size_t sec_size = this->GetRequiredSecureMemorySizeNonDefault(); @@ -867,7 +860,7 @@ size_t KProcess::GetUsedUserPhysicalMemorySize() const { return norm_size + other_size + sec_size; } -size_t KProcess::GetTotalUserPhysicalMemorySize() const { +size_t KProcess::GetTotalUserPhysicalMemorySize(KernelCore& kernel) const { // Get the amount of free and used size. const size_t free_size = m_resource_limit->GetFreeValue(Svc::LimitableResource::PhysicalMemoryMax); @@ -886,18 +879,18 @@ size_t KProcess::GetTotalUserPhysicalMemorySize() const { if (used_size + free_size > max_size) { return max_size; } else { - return free_size + this->GetUsedUserPhysicalMemorySize(); + return free_size + this->GetUsedUserPhysicalMemorySize(kernel); } } -size_t KProcess::GetUsedNonSystemUserPhysicalMemorySize() const { +size_t KProcess::GetUsedNonSystemUserPhysicalMemorySize(KernelCore& kernel) const { const size_t norm_size = m_page_table.GetNormalMemorySize(); const size_t other_size = m_code_size + m_main_thread_stack_size; return norm_size + other_size; } -size_t KProcess::GetTotalNonSystemUserPhysicalMemorySize() const { +size_t KProcess::GetTotalNonSystemUserPhysicalMemorySize(KernelCore& kernel) const { // Get the amount of free and used size. const size_t free_size = m_resource_limit->GetFreeValue(Svc::LimitableResource::PhysicalMemoryMax); @@ -916,11 +909,11 @@ size_t KProcess::GetTotalNonSystemUserPhysicalMemorySize() const { if (used_size + free_size > max_size) { return max_size - this->GetRequiredSecureMemorySizeNonDefault(); } else { - return free_size + this->GetUsedNonSystemUserPhysicalMemorySize(); + return free_size + this->GetUsedNonSystemUserPhysicalMemorySize(kernel); } } -Result KProcess::Run(s32 priority, size_t stack_size) { +Result KProcess::Run(KernelCore& kernel, s32 priority, size_t stack_size) { // Lock ourselves, to prevent concurrent access. KScopedLightLock lk(m_state_lock); @@ -929,7 +922,7 @@ Result KProcess::Run(s32 priority, size_t stack_size) { R_UNLESS(state == State::Created || state == State::CreatedAttached, ResultInvalidState); // Place a tentative reservation of a thread for this process. - KScopedResourceReservation thread_reservation(this, Svc::LimitableResource::ThreadCountMax); + KScopedResourceReservation thread_reservation(kernel, this, Svc::LimitableResource::ThreadCountMax); R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached); // Ensure that we haven't already allocated stack. @@ -941,7 +934,7 @@ Result KProcess::Run(s32 priority, size_t stack_size) { R_UNLESS(stack_size + m_code_size >= m_code_size, ResultOutOfMemory); // Place a tentative reservation of memory for our new stack. - KScopedResourceReservation mem_reservation(this, Svc::LimitableResource::PhysicalMemoryMax, + KScopedResourceReservation mem_reservation(kernel, this, Svc::LimitableResource::PhysicalMemoryMax, stack_size); R_UNLESS(mem_reservation.Succeeded(), ResultLimitReached); @@ -971,53 +964,74 @@ Result KProcess::Run(s32 priority, size_t stack_size) { (m_main_thread_stack_size + m_code_size))); // Initialize our handle table. - R_TRY(this->InitializeHandleTable(m_capabilities.GetHandleTableSize())); + R_TRY(this->InitializeHandleTable(kernel, m_capabilities.GetHandleTableSize())); ON_RESULT_FAILURE_2 { - this->FinalizeHandleTable(); + this->FinalizeHandleTable(kernel); }; // Create a new thread for the process. - KThread* main_thread = KThread::Create(m_kernel); + KThread* main_thread = KThread::Create(kernel); R_UNLESS(main_thread != nullptr, ResultOutOfResource); SCOPE_EXIT { - main_thread->Close(); + main_thread->Close(kernel); }; // Initialize the thread. - R_TRY(KThread::InitializeUserThread(m_kernel.System(), main_thread, this->GetEntryPoint(), 0, + R_TRY(KThread::InitializeUserThread(kernel.System(), main_thread, this->GetEntryPoint(), 0, stack_top, priority, m_ideal_core_id, this)); // Register the thread, and commit our reservation. - KThread::Register(m_kernel, main_thread); + KThread::Register(kernel, main_thread); thread_reservation.Commit(); // Add the thread to our handle table. Handle thread_handle; - R_TRY(m_handle_table.Add(std::addressof(thread_handle), main_thread)); + R_TRY(m_handle_table.Add(kernel, std::addressof(thread_handle), main_thread)); - // Set the thread arguments. - main_thread->GetContext().r[0] = 0; - main_thread->GetContext().r[1] = thread_handle; + // Set the thread arguments. Two distinct entry conventions: + // * Kernel/NSO entry (no homebrew ABI): x0 = 0, x1 = thread_handle + // * Homebrew/NRO ABI (loader set arg ptr): x0 = ConfigEntry ptr, x1 = -1ULL + // libnx's switch_crt0.s tests `x0==0 || x1==0xFFFFFFFFFFFFFFFF` to take + // its normal init path; any other combination is interpreted as a user + // exception handler entry. + if (GetInteger(m_arg_pointer) != 0) { + main_thread->GetContext().r[0] = GetInteger(m_arg_pointer); + main_thread->GetContext().r[1] = UINT64_MAX; + main_thread->GetContext().lr = GetInteger(m_arg_return_address); + // Patch the MainThreadHandle entry in the ConfigEntry table now that + // the actual handle exists. libnx stores this verbatim and uses it + // for thread-control SVCs later; a pseudo-handle wouldn't survive + // svcCloseHandle on exit. + if (GetInteger(m_main_thread_handle_addr) != 0) { + this->GetMemory().Write32(m_main_thread_handle_addr, thread_handle); + } + } else { + main_thread->GetContext().r[0] = 0; + main_thread->GetContext().r[1] = thread_handle; + } // Pass the thread handle to the thread local region. this->GetMemory().Write32(GetInteger(main_thread->GetTlsAddress()) + 0x110, thread_handle); // Update our state. - this->ChangeState((state == State::Created) ? State::Running : State::RunningAttached); + this->ChangeState(kernel, (state == State::Created) ? State::Running : State::RunningAttached); ON_RESULT_FAILURE_2 { - this->ChangeState(state); + this->ChangeState(kernel, state); }; // Suspend for debug, if we should. - if (m_kernel.System().DebuggerEnabled()) { - main_thread->RequestSuspend(SuspendType::Debug); + if (kernel.System().DebuggerEnabled()) { + LOG_INFO(Debug_GDBStub, + "GDB stub enabled; suspending guest process until a debugger continues execution on port {}", + Settings::values.gdbstub_port.GetValue()); + main_thread->RequestSuspend(kernel, SuspendType::Debug); } // Run our thread. - R_TRY(main_thread->Run()); + R_TRY(main_thread->Run(kernel)); // Open a reference to represent that we're running. - this->Open(); + this->Open(kernel); // We succeeded! Commit our memory reservation. mem_reservation.Commit(); @@ -1025,10 +1039,10 @@ Result KProcess::Run(s32 priority, size_t stack_size) { R_SUCCEED(); } -Result KProcess::Reset() { +Result KProcess::Reset(KernelCore& kernel) { // Lock the process and the scheduler. KScopedLightLock lk(m_state_lock); - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); // Validate that we're in a state that we can reset. R_UNLESS(m_state != State::Terminated, ResultInvalidState); @@ -1039,11 +1053,11 @@ Result KProcess::Reset() { R_SUCCEED(); } -Result KProcess::SetActivity(Svc::ProcessActivity activity) { +Result KProcess::SetActivity(KernelCore& kernel, Svc::ProcessActivity activity) { // Lock ourselves and the scheduler. KScopedLightLock lk(m_state_lock); KScopedLightLock list_lk(m_list_lock); - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); // Validate our state. R_UNLESS(m_state != State::Terminating, ResultInvalidState); @@ -1057,7 +1071,7 @@ Result KProcess::SetActivity(Svc::ProcessActivity activity) { // Suspend all threads. auto end = this->GetThreadList().end(); for (auto it = this->GetThreadList().begin(); it != end; ++it) { - it->RequestSuspend(SuspendType::Process); + it->RequestSuspend(kernel, SuspendType::Process); } // Set ourselves as suspended. @@ -1071,7 +1085,7 @@ Result KProcess::SetActivity(Svc::ProcessActivity activity) { // Resume all threads. auto end = this->GetThreadList().end(); for (auto it = this->GetThreadList().begin(); it != end; ++it) { - it->Resume(SuspendType::Process); + it->Resume(kernel, SuspendType::Process); } // Set ourselves as resumed. @@ -1081,54 +1095,54 @@ Result KProcess::SetActivity(Svc::ProcessActivity activity) { R_SUCCEED(); } -void KProcess::PinCurrentThread() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); +void KProcess::PinCurrentThread(KernelCore& kernel) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); // Get the current thread. - const s32 core_id = GetCurrentCoreId(m_kernel); - KThread* cur_thread = GetCurrentThreadPointer(m_kernel); + const s32 core_id = GetCurrentCoreId(kernel); + KThread* cur_thread = GetCurrentThreadPointer(kernel); // If the thread isn't terminated, pin it. if (!cur_thread->IsTerminationRequested()) { // Pin it. this->PinThread(core_id, cur_thread); - cur_thread->Pin(core_id); + cur_thread->Pin(kernel, core_id); // An update is needed. - KScheduler::SetSchedulerUpdateNeeded(m_kernel); + KScheduler::SetSchedulerUpdateNeeded(kernel); } } -void KProcess::UnpinCurrentThread() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); +void KProcess::UnpinCurrentThread(KernelCore& kernel) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); // Get the current thread. - const s32 core_id = GetCurrentCoreId(m_kernel); - KThread* cur_thread = GetCurrentThreadPointer(m_kernel); + const s32 core_id = GetCurrentCoreId(kernel); + KThread* cur_thread = GetCurrentThreadPointer(kernel); // Unpin it. - cur_thread->Unpin(); + cur_thread->Unpin(kernel); this->UnpinThread(core_id, cur_thread); // An update is needed. - KScheduler::SetSchedulerUpdateNeeded(m_kernel); + KScheduler::SetSchedulerUpdateNeeded(kernel); } -void KProcess::UnpinThread(KThread* thread) { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); +void KProcess::UnpinThread(KernelCore& kernel, KThread* thread) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); // Get the thread's core id. const auto core_id = thread->GetActiveCore(); // Unpin it. this->UnpinThread(core_id, thread); - thread->Unpin(); + thread->Unpin(kernel); // An update is needed. - KScheduler::SetSchedulerUpdateNeeded(m_kernel); + KScheduler::SetSchedulerUpdateNeeded(kernel); } -Result KProcess::GetThreadList(s32* out_num_threads, KProcessAddress out_thread_ids, +Result KProcess::GetThreadList(KernelCore& kernel, s32* out_num_threads, KProcessAddress out_thread_ids, s32 max_out_count) { auto& memory = this->GetMemory(); @@ -1158,7 +1172,7 @@ Result KProcess::GetThreadList(s32* out_num_threads, KProcessAddress out_thread_ R_SUCCEED(); } -void KProcess::Switch(KProcess* cur_process, KProcess* next_process) {} +void KProcess::Switch(KernelCore& kernel, KProcess* cur_process, KProcess* next_process) {} KProcess::KProcess(KernelCore& kernel) : KAutoObjectWithSlabHeapAndContainer(kernel) @@ -1174,17 +1188,16 @@ KProcess::KProcess(KernelCore& kernel) KProcess::~KProcess() = default; -Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, - KProcessAddress aslr_space_start, size_t aslr_space_offset, bool is_hbl) { +Result KProcess::LoadFromMetadata(KernelCore& kernel, const FileSys::ProgramMetadata& metadata, std::size_t code_size, KProcessAddress aslr_space_start, size_t aslr_space_offset) { // Create a resource limit for the process. const auto pool = static_cast(metadata.GetPoolPartition()); - const auto physical_memory_size = m_kernel.MemoryManager().GetSize(pool); + const auto physical_memory_size = kernel.MemoryManager().GetSize(pool); auto* res_limit = - Kernel::CreateResourceLimitForProcess(m_kernel.System(), physical_memory_size); + Kernel::CreateResourceLimitForProcess(kernel.System(), physical_memory_size); // Ensure we maintain a clean state on exit. SCOPE_EXIT { - res_limit->Close(); + res_limit->Close(kernel); }; // Declare flags and code address. @@ -1243,23 +1256,20 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std: std::memcpy(params.name.data(), name.data(), sizeof(params.name)); // Initialize for application process. - R_TRY(this->Initialize(params, metadata.GetKernelCapabilities(), res_limit, pool, - aslr_space_start)); + R_TRY(this->Initialize(kernel, params, metadata.GetKernelCapabilities(), res_limit, pool, aslr_space_start)); // Assign remaining properties. - m_is_hbl = is_hbl; m_ideal_core_id = metadata.GetMainThreadCore(); // Set up emulation context. - this->InitializeInterfaces(); + this->InitializeInterfaces(kernel); // We succeeded. R_SUCCEED(); } -void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) { - const auto ReprotectSegment = [&](const CodeSet::Segment& segment, - Svc::MemoryPermission permission) { +void KProcess::LoadModule(KernelCore& kernel, CodeSet code_set, KProcessAddress base_addr) { + const auto ReprotectSegment = [&](const CodeSet::Segment& segment, Svc::MemoryPermission permission) { m_page_table.SetProcessMemoryPermission(segment.addr + base_addr, segment.size, permission); }; @@ -1273,7 +1283,7 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) { const auto& patch = code_set.PatchSegment(); const auto& post_patch = code_set.PostPatchSegment(); if (this->IsApplication() && Settings::IsNceEnabled() && patch.size != 0) { - auto& buffer = m_kernel.System().DeviceMemory().buffer; + auto& buffer = kernel.System().DeviceMemory().buffer; const auto& code = code_set.CodeSegment(); buffer.Protect(GetInteger(base_addr + code.addr), code.size, Common::MemoryPermission::Read | Common::MemoryPermission::Execute); @@ -1292,32 +1302,32 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) { #endif } -void KProcess::InitializeInterfaces() { +void KProcess::InitializeInterfaces(KernelCore& kernel) { m_exclusive_monitor = Core::MakeExclusiveMonitor(this->GetMemory(), Core::Hardware::NUM_CPU_CORES); #ifdef HAS_NCE if (this->IsApplication() && Settings::IsNceEnabled()) { for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) - m_arm_interfaces[i] = std::make_unique(m_kernel.System(), true, i); + m_arm_interfaces[i] = std::make_unique(kernel.System(), true, i); } else #endif if (this->Is64Bit()) { for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { m_arm_interfaces[i] = std::make_unique( - m_kernel.System(), m_kernel.IsMulticore(), this, + kernel.System(), kernel.IsMulticore(), this, static_cast(*m_exclusive_monitor), i); } } else { for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { m_arm_interfaces[i] = std::make_unique( - m_kernel.System(), m_kernel.IsMulticore(), this, + kernel.System(), kernel.IsMulticore(), this, static_cast(*m_exclusive_monitor), i); } } } -bool KProcess::InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) { +bool KProcess::InsertWatchpoint(KernelCore& kernel, KProcessAddress addr, u64 size, DebugWatchpointType type) { const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) { return wp.type == DebugWatchpointType::None; })}; @@ -1339,7 +1349,7 @@ bool KProcess::InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointT return true; } -bool KProcess::RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) { +bool KProcess::RemoveWatchpoint(KernelCore& kernel, KProcessAddress addr, u64 size, DebugWatchpointType type) { const auto watch{std::find_if(m_watchpoints.begin(), m_watchpoints.end(), [&](const auto& wp) { return wp.start_address == addr && wp.end_address == addr + size && wp.type == type; })}; diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index 975448f0dd..b5693751c3 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h @@ -78,12 +78,15 @@ private: std::array m_watchpoints{}; std::map m_debug_page_refcounts{}; #ifdef HAS_NCE - ankerl::unordered_dense::map m_post_handlers{}; + ::Common::unordered_map m_post_handlers{}; #endif std::unique_ptr m_exclusive_monitor; Core::Memory::Memory m_memory; KCapabilities m_capabilities{}; KProcessAddress m_code_address{}; + KProcessAddress m_arg_pointer{}; + KProcessAddress m_arg_return_address{}; + KProcessAddress m_main_thread_handle_addr{}; KHandleTable m_handle_table; KProcessAddress m_plr_address{}; ThreadList m_thread_list{}; @@ -133,14 +136,13 @@ private: bool m_is_initialized : 1 = false; bool m_is_application : 1 = false; bool m_is_default_application_system_resource : 1 = false; - bool m_is_hbl : 1 = false; bool m_is_suspended : 1 = false; bool m_is_immortal : 1 = false; bool m_is_handle_table_initialized : 1 = false; private: - Result StartTermination(); - void FinishTermination(); + Result StartTermination(KernelCore& kernel); + void FinishTermination(KernelCore& kernel); void PinThread(s32 core_id, KThread* thread) { ASSERT(0 <= core_id && core_id < static_cast(Core::Hardware::NUM_CPU_CORES)); @@ -160,16 +162,16 @@ public: explicit KProcess(KernelCore& kernel); ~KProcess() override; - Result Initialize(const Svc::CreateProcessParameter& params, KResourceLimit* res_limit, + Result Initialize(KernelCore& kernel, const Svc::CreateProcessParameter& params, KResourceLimit* res_limit, bool is_real); - Result Initialize(const Svc::CreateProcessParameter& params, const KPageGroup& pg, + Result Initialize(KernelCore& kernel, const Svc::CreateProcessParameter& params, const KPageGroup& pg, std::span caps, KResourceLimit* res_limit, KMemoryManager::Pool pool, bool immortal); - Result Initialize(const Svc::CreateProcessParameter& params, std::span user_caps, + Result Initialize(KernelCore& kernel, const Svc::CreateProcessParameter& params, std::span user_caps, KResourceLimit* res_limit, KMemoryManager::Pool pool, KProcessAddress aslr_space_start); - void Exit(); + void Exit(KernelCore& kernel); const char* GetName() const { return m_name.data(); @@ -220,6 +222,16 @@ public: return m_code_address; } + void SetArgPointer(KProcessAddress addr) { + m_arg_pointer = addr; + } + void SetArgReturnAddress(KProcessAddress addr) { + m_arg_return_address = addr; + } + void SetMainThreadHandleAddr(KProcessAddress addr) { + m_main_thread_handle_addr = addr; + } + size_t GetMainStackSize() const { return m_main_thread_stack_size; } @@ -255,7 +267,7 @@ public: m_pointer_buffer_size = size; } - Result Terminate(); + Result Terminate(KernelCore& kernel); bool IsTerminated() const { return m_state == State::Terminated; @@ -277,10 +289,6 @@ public: return m_capabilities.CanForceDebug(); } - bool IsHbl() const { - return m_is_hbl; - } - u32 GetAllocateOption() const { return m_page_table.GetAllocateOption(); } @@ -292,9 +300,9 @@ public: return m_thread_list; } - bool EnterUserException(); - bool LeaveUserException(); - bool ReleaseUserException(KThread* thread); + bool EnterUserException(KernelCore& kernel); + bool LeaveUserException(KernelCore& kernel); + bool ReleaseUserException(KernelCore& kernel, KThread* thread); KThread* GetPinnedThread(s32 core_id) const { ASSERT(0 <= core_id && core_id < static_cast(Core::Hardware::NUM_CPU_CORES)); @@ -309,10 +317,10 @@ public: return m_resource_limit; } - bool ReserveResource(Svc::LimitableResource which, s64 value); - bool ReserveResource(Svc::LimitableResource which, s64 value, s64 timeout); - void ReleaseResource(Svc::LimitableResource which, s64 value); - void ReleaseResource(Svc::LimitableResource which, s64 value, s64 hint); + bool ReserveResource(KernelCore& kernel, Svc::LimitableResource which, s64 value); + bool ReserveResource(KernelCore& kernel, Svc::LimitableResource which, s64 value, s64 timeout); + void ReleaseResource(KernelCore& kernel, Svc::LimitableResource which, s64 value); + void ReleaseResource(KernelCore& kernel, Svc::LimitableResource which, s64 value, s64 hint); KLightLock& GetStateLock() { return m_state_lock; @@ -335,16 +343,16 @@ public: return m_handle_table; } - size_t GetUsedUserPhysicalMemorySize() const; - size_t GetTotalUserPhysicalMemorySize() const; - size_t GetUsedNonSystemUserPhysicalMemorySize() const; - size_t GetTotalNonSystemUserPhysicalMemorySize() const; + size_t GetUsedUserPhysicalMemorySize(KernelCore& kernel) const; + size_t GetTotalUserPhysicalMemorySize(KernelCore& kernel) const; + size_t GetUsedNonSystemUserPhysicalMemorySize(KernelCore& kernel) const; + size_t GetTotalNonSystemUserPhysicalMemorySize(KernelCore& kernel) const; - Result AddSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size); - void RemoveSharedMemory(KSharedMemory* shmem, KProcessAddress address, size_t size); + Result AddSharedMemory(KernelCore& kernel, KSharedMemory* shmem, KProcessAddress address, size_t size); + void RemoveSharedMemory(KernelCore& kernel, KSharedMemory* shmem, KProcessAddress address, size_t size); - Result CreateThreadLocalRegion(KProcessAddress* out); - Result DeleteThreadLocalRegion(KProcessAddress addr); + Result CreateThreadLocalRegion(KernelCore& kernel, KProcessAddress* out); + Result DeleteThreadLocalRegion(KernelCore& kernel, KProcessAddress addr); KProcessAddress GetProcessLocalRegionAddress() const { return m_plr_address; @@ -368,8 +376,8 @@ public: ++m_schedule_count; } - void IncrementRunningThreadCount(); - void DecrementRunningThreadCount(); + void IncrementRunningThreadCount(KernelCore& kernel); + void DecrementRunningThreadCount(KernelCore& kernel); size_t GetRequiredSecureMemorySizeNonDefault() const { if (!this->IsDefaultApplicationSystemResource() && m_system_resource->IsSecureResource()) { @@ -414,11 +422,9 @@ public: } void ClearRunningThread(KThread* thread) { - for (size_t i = 0; i < m_running_threads.size(); ++i) { - if (m_running_threads[i] == thread) { + for (size_t i = 0; i < m_running_threads.size(); ++i) + if (m_running_threads[i] == thread) m_running_threads[i] = nullptr; - } - } } const KSystemResource& GetSystemResource() const { @@ -445,30 +451,27 @@ public: return m_running_thread_switch_counts[core]; } - void RegisterThread(KThread* thread); - void UnregisterThread(KThread* thread); + void RegisterThread(KernelCore& kernel, KThread* thread); + void UnregisterThread(KernelCore& kernel, KThread* thread); + Result Run(KernelCore& kernel, s32 priority, size_t stack_size); + Result Reset(KernelCore& kernel); - Result Run(s32 priority, size_t stack_size); - - Result Reset(); - - void SetDebugBreak() { + void SetDebugBreak(KernelCore& kernel) { if (m_state == State::RunningAttached) { - this->ChangeState(State::DebugBreak); + this->ChangeState(kernel, State::DebugBreak); } } - void SetAttached() { + void SetAttached(KernelCore& kernel) { if (m_state == State::DebugBreak) { - this->ChangeState(State::RunningAttached); + this->ChangeState(kernel, State::RunningAttached); } } - Result SetActivity(Svc::ProcessActivity activity); - - void PinCurrentThread(); - void UnpinCurrentThread(); - void UnpinThread(KThread* thread); + Result SetActivity(KernelCore& kernel, Svc::ProcessActivity activity); + void PinCurrentThread(KernelCore& kernel); + void UnpinCurrentThread(KernelCore& kernel); + void UnpinThread(KernelCore& kernel, KThread* thread); void SignalConditionVariable(uintptr_t cv_key, int32_t count) { return m_cond_var.Signal(cv_key, count); @@ -478,22 +481,20 @@ public: R_RETURN(m_cond_var.Wait(address, cv_key, tag, ns)); } - Result SignalAddressArbiter(uintptr_t address, Svc::SignalType signal_type, s32 value, - s32 count) { + Result SignalAddressArbiter(uintptr_t address, Svc::SignalType signal_type, s32 value, s32 count) { R_RETURN(m_address_arbiter.SignalToAddress(address, signal_type, value, count)); } - Result WaitAddressArbiter(uintptr_t address, Svc::ArbitrationType arb_type, s32 value, - s64 timeout) { + Result WaitAddressArbiter(uintptr_t address, Svc::ArbitrationType arb_type, s32 value, s64 timeout) { R_RETURN(m_address_arbiter.WaitForAddress(address, arb_type, value, timeout)); } - Result GetThreadList(s32* out_num_threads, KProcessAddress out_thread_ids, s32 max_out_count); + Result GetThreadList(KernelCore& kernel, s32* out_num_threads, KProcessAddress out_thread_ids, s32 max_out_count); - static void Switch(KProcess* cur_process, KProcess* next_process); + static void Switch(KernelCore& kernel, KProcess* cur_process, KProcess* next_process); #ifdef HAS_NCE - ankerl::unordered_dense::map& GetPostHandlers() noexcept { + ::Common::unordered_map& GetPostHandlers() noexcept { return m_post_handlers; } #endif @@ -504,22 +505,21 @@ public: public: // Attempts to insert a watchpoint into a free slot. Returns false if none are available. - bool InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type); + bool InsertWatchpoint(KernelCore& kernel, KProcessAddress addr, u64 size, DebugWatchpointType type); // Attempts to remove the watchpoint specified by the given parameters. - bool RemoveWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type); + bool RemoveWatchpoint(KernelCore& kernel, KProcessAddress addr, u64 size, DebugWatchpointType type); const std::array& GetWatchpoints() const { return m_watchpoints; } public: - Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, - KProcessAddress aslr_space_start, size_t aslr_space_offset, bool is_hbl); + Result LoadFromMetadata(KernelCore& kernel, const FileSys::ProgramMetadata& metadata, std::size_t code_size, KProcessAddress aslr_space_start, size_t aslr_space_offset); - void LoadModule(CodeSet code_set, KProcessAddress base_addr); + void LoadModule(KernelCore& kernel, CodeSet code_set, KProcessAddress base_addr); - void InitializeInterfaces(); + void InitializeInterfaces(KernelCore& kernel); Core::Memory::Memory& GetMemory() { return m_memory; @@ -535,9 +535,9 @@ public: return m_is_initialized; } - static void PostDestroy(uintptr_t arg) {} + static void PostDestroy(KernelCore& kernel, uintptr_t arg) {} - void Finalize() override; + void Finalize(KernelCore& kernel) override; u64 GetIdImpl() const { return this->GetProcessId(); @@ -546,34 +546,34 @@ public: return this->GetIdImpl(); } - virtual bool IsSignaled() const override { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + virtual bool IsSignaled(KernelCore& kernel) const override { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); return m_is_signaled; } - void DoWorkerTaskImpl(); + void DoWorkerTaskImpl(KernelCore& kernel); private: - void ChangeState(State new_state) { + void ChangeState(KernelCore& kernel, State new_state) { if (m_state != new_state) { m_state = new_state; m_is_signaled = true; - this->NotifyAvailable(); + this->NotifyAvailable(kernel); } } - Result InitializeHandleTable(s32 size) { + Result InitializeHandleTable(KernelCore& kernel, s32 size) { // Try to initialize the handle table. - R_TRY(m_handle_table.Initialize(size)); + R_TRY(m_handle_table.Initialize(kernel, size)); // We succeeded, so note that we did. m_is_handle_table_initialized = true; R_SUCCEED(); } - void FinalizeHandleTable() { + void FinalizeHandleTable(KernelCore& kernel) { // Finalize the table. - m_handle_table.Finalize(); + m_handle_table.Finalize(kernel); // Note that the table is finalized. m_is_handle_table_initialized = false; diff --git a/src/core/hle/kernel/k_readable_event.cpp b/src/core/hle/kernel/k_readable_event.cpp index c30662666b..a00941bc1e 100644 --- a/src/core/hle/kernel/k_readable_event.cpp +++ b/src/core/hle/kernel/k_readable_event.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -15,50 +18,45 @@ KReadableEvent::KReadableEvent(KernelCore& kernel) : KSynchronizationObject{kern KReadableEvent::~KReadableEvent() = default; -void KReadableEvent::Initialize(KEvent* parent) { +void KReadableEvent::Initialize(KernelCore& kernel, KEvent* parent) { m_is_signaled = false; m_parent = parent; - if (m_parent != nullptr) { - m_parent->Open(); + m_parent->Open(kernel); } } -bool KReadableEvent::IsSignaled() const { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - +bool KReadableEvent::IsSignaled(KernelCore& kernel) const { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); return m_is_signaled; } -void KReadableEvent::Destroy() { +void KReadableEvent::Destroy(KernelCore& kernel) { if (m_parent) { { - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; m_parent->OnReadableEventDestroyed(); } - m_parent->Close(); + m_parent->Close(kernel); } } -Result KReadableEvent::Signal() { - KScopedSchedulerLock lk{m_kernel}; - +Result KReadableEvent::Signal(KernelCore& kernel) { + KScopedSchedulerLock lk{kernel}; if (!m_is_signaled) { m_is_signaled = true; - this->NotifyAvailable(); + this->NotifyAvailable(kernel); } - R_SUCCEED(); } -Result KReadableEvent::Clear() { - this->Reset(); - +Result KReadableEvent::Clear(KernelCore& kernel) { + this->Reset(kernel); R_SUCCEED(); } -Result KReadableEvent::Reset() { - KScopedSchedulerLock lk{m_kernel}; +Result KReadableEvent::Reset(KernelCore& kernel) { + KScopedSchedulerLock lk{kernel}; R_UNLESS(m_is_signaled, ResultInvalidState); diff --git a/src/core/hle/kernel/k_readable_event.h b/src/core/hle/kernel/k_readable_event.h index d2ec363233..63a6e8de60 100644 --- a/src/core/hle/kernel/k_readable_event.h +++ b/src/core/hle/kernel/k_readable_event.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -20,19 +23,17 @@ public: explicit KReadableEvent(KernelCore& kernel); ~KReadableEvent() override; - void Initialize(KEvent* parent); + void Initialize(KernelCore& kernel, KEvent* parent); KEvent* GetParent() const { return m_parent; } - Result Signal(); - Result Clear(); - - bool IsSignaled() const override; - void Destroy() override; - - Result Reset(); + Result Signal(KernelCore& kernel); + Result Clear(KernelCore& kernel); + bool IsSignaled(KernelCore& kernel) const override; + void Destroy(KernelCore& kernel) override; + Result Reset(KernelCore& kernel); private: bool m_is_signaled{}; diff --git a/src/core/hle/kernel/k_resource_limit.cpp b/src/core/hle/kernel/k_resource_limit.cpp index e886f97ebd..1a2c92a665 100644 --- a/src/core/hle/kernel/k_resource_limit.cpp +++ b/src/core/hle/kernel/k_resource_limit.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -16,12 +16,15 @@ namespace Kernel { constexpr s64 DefaultTimeout = 10000000000; // 10 seconds KResourceLimit::KResourceLimit(KernelCore& kernel) - : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{m_kernel}, m_cond_var{m_kernel} {} + : KAutoObjectWithSlabHeapAndContainer{kernel} + , m_lock{kernel} + , m_cond_var{kernel} +{} KResourceLimit::~KResourceLimit() = default; void KResourceLimit::Initialize() {} -void KResourceLimit::Finalize() {} +void KResourceLimit::Finalize(KernelCore& kernel) {} s64 KResourceLimit::GetLimitValue(LimitableResource which) const { const auto index = static_cast(which); @@ -87,11 +90,11 @@ Result KResourceLimit::SetLimitValue(LimitableResource which, s64 value) { R_SUCCEED(); } -bool KResourceLimit::Reserve(LimitableResource which, s64 value) { - return Reserve(which, value, m_kernel.HardwareTimer().GetTick() + DefaultTimeout); +bool KResourceLimit::Reserve(KernelCore& kernel, LimitableResource which, s64 value) { + return Reserve(kernel, which, value, kernel.HardwareTimer().GetTick() + DefaultTimeout); } -bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { +bool KResourceLimit::Reserve(KernelCore& kernel, LimitableResource which, s64 value, s64 timeout) { ASSERT(value >= 0); const auto index = static_cast(which); KScopedLightLock lk(m_lock); @@ -119,7 +122,7 @@ bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { } if (m_current_hints[index] + value <= m_limit_values[index] && - (timeout < 0 || m_kernel.HardwareTimer().GetTick() < timeout)) { + (timeout < 0 || kernel.HardwareTimer().GetTick() < timeout)) { m_waiter_count++; m_cond_var.Wait(std::addressof(m_lock), timeout, false); m_waiter_count--; @@ -131,11 +134,11 @@ bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { return false; } -void KResourceLimit::Release(LimitableResource which, s64 value) { - Release(which, value, value); +void KResourceLimit::Release(KernelCore& kernel, LimitableResource which, s64 value) { + Release(kernel, which, value, value); } -void KResourceLimit::Release(LimitableResource which, s64 value, s64 hint) { +void KResourceLimit::Release(KernelCore& kernel, LimitableResource which, s64 value, s64 hint) { ASSERT(value >= 0); ASSERT(hint >= 0); diff --git a/src/core/hle/kernel/k_resource_limit.h b/src/core/hle/kernel/k_resource_limit.h index b733ec8f81..eb45d23bab 100644 --- a/src/core/hle/kernel/k_resource_limit.h +++ b/src/core/hle/kernel/k_resource_limit.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -32,7 +35,7 @@ public: ~KResourceLimit() override; void Initialize(); - void Finalize() override; + void Finalize(KernelCore& kernel) override; s64 GetLimitValue(LimitableResource which) const; s64 GetCurrentValue(LimitableResource which) const; @@ -41,12 +44,12 @@ public: Result SetLimitValue(LimitableResource which, s64 value); - bool Reserve(LimitableResource which, s64 value); - bool Reserve(LimitableResource which, s64 value, s64 timeout); - void Release(LimitableResource which, s64 value); - void Release(LimitableResource which, s64 value, s64 hint); + bool Reserve(KernelCore& kernel, LimitableResource which, s64 value); + bool Reserve(KernelCore& kernel, LimitableResource which, s64 value, s64 timeout); + void Release(KernelCore& kernel, LimitableResource which, s64 value); + void Release(KernelCore& kernel, LimitableResource which, s64 value, s64 hint); - static void PostDestroy(uintptr_t arg) {} + static void PostDestroy(KernelCore& kernel, uintptr_t arg) {} private: using ResourceArray = std::array(LimitableResource::Count)>; diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp index 4b76e18950..3156a17064 100644 --- a/src/core/hle/kernel/k_scheduler.cpp +++ b/src/core/hle/kernel/k_scheduler.cpp @@ -30,34 +30,32 @@ static void IncrementScheduledCount(Kernel::KThread* thread) { } } -KScheduler::KScheduler(KernelCore& kernel) : m_kernel{kernel} { - m_switch_fiber = std::make_shared([this] { +KScheduler::KScheduler(KernelCore& kernel) { + m_switch_fiber = std::make_shared([this, &kernel] { while (true) { - ScheduleImplFiber(); + ScheduleImplFiber(kernel); } }); - m_state.needs_scheduling = true; } KScheduler::~KScheduler() = default; -void KScheduler::SetInterruptTaskRunnable() { +void KScheduler::SetInterruptTaskRunnable(KernelCore& kernel) { m_state.interrupt_task_runnable = true; m_state.needs_scheduling = true; } -void KScheduler::RequestScheduleOnInterrupt() { +void KScheduler::RequestScheduleOnInterrupt(KernelCore& kernel) { m_state.needs_scheduling = true; - - if (CanSchedule(m_kernel)) { - ScheduleOnInterrupt(); + if (CanSchedule(kernel)) { + ScheduleOnInterrupt(kernel); } } void KScheduler::DisableScheduling(KernelCore& kernel) { ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0); - GetCurrentThread(kernel).DisableDispatch(); + GetCurrentThread(kernel).DisableDispatch(kernel); } void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling) { @@ -71,12 +69,12 @@ void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduli return; } - scheduler->RescheduleOtherCores(cores_needing_scheduling); + scheduler->RescheduleOtherCores(kernel, cores_needing_scheduling); if (GetCurrentThread(kernel).GetDisableDispatchCount() > 1) { - GetCurrentThread(kernel).EnableDispatch(); + GetCurrentThread(kernel).EnableDispatch(kernel); } else { - scheduler->RescheduleCurrentCore(); + scheduler->RescheduleCurrentCore(kernel); } } @@ -85,10 +83,10 @@ void KScheduler::RescheduleCurrentHLEThread(KernelCore& kernel) { ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1); // Ensure dummy threads that are waiting block. - GetCurrentThread(kernel).DummyThreadBeginWait(); + GetCurrentThread(kernel).DummyThreadBeginWait(kernel); ASSERT(GetCurrentThread(kernel).GetState() != ThreadState::Waiting); - GetCurrentThread(kernel).EnableDispatch(); + GetCurrentThread(kernel).EnableDispatch(kernel); } u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) { @@ -99,55 +97,55 @@ u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) { } } -void KScheduler::Schedule() { - ASSERT(GetCurrentThread(m_kernel).GetDisableDispatchCount() == 1); - ASSERT(m_core_id == GetCurrentCoreId(m_kernel)); +void KScheduler::Schedule(KernelCore& kernel) { + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1); + ASSERT(m_core_id == GetCurrentCoreId(kernel)); - ScheduleImpl(); + ScheduleImpl(kernel); } -void KScheduler::ScheduleOnInterrupt() { - GetCurrentThread(m_kernel).DisableDispatch(); - Schedule(); - GetCurrentThread(m_kernel).EnableDispatch(); +void KScheduler::ScheduleOnInterrupt(KernelCore& kernel) { + GetCurrentThread(kernel).DisableDispatch(kernel); + Schedule(kernel); + GetCurrentThread(kernel).EnableDispatch(kernel); } -void KScheduler::PreemptSingleCore() { - GetCurrentThread(m_kernel).DisableDispatch(); +void KScheduler::PreemptSingleCore(KernelCore& kernel) { + GetCurrentThread(kernel).DisableDispatch(kernel); - auto* thread = GetCurrentThreadPointer(m_kernel); - auto& previous_scheduler = m_kernel.Scheduler(thread->GetCurrentCore()); - previous_scheduler.Unload(thread); + auto* thread = GetCurrentThreadPointer(kernel); + auto& previous_scheduler = kernel.Scheduler(thread->GetCurrentCore()); + previous_scheduler.Unload(kernel, thread); Common::Fiber::YieldTo(thread->GetHostContext(), *m_switch_fiber); - GetCurrentThread(m_kernel).EnableDispatch(); + GetCurrentThread(kernel).EnableDispatch(kernel); } -void KScheduler::RescheduleCurrentCore() { - ASSERT(!m_kernel.IsPhantomModeForSingleCore()); - ASSERT(GetCurrentThread(m_kernel).GetDisableDispatchCount() == 1); +void KScheduler::RescheduleCurrentCore(KernelCore& kernel) { + ASSERT(!kernel.IsPhantomModeForSingleCore()); + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1); - GetCurrentThread(m_kernel).EnableDispatch(); + GetCurrentThread(kernel).EnableDispatch(kernel); if (m_state.needs_scheduling.load()) { // Disable interrupts, and then check again if rescheduling is needed. // KScopedInterruptDisable intr_disable; - m_kernel.CurrentScheduler()->RescheduleCurrentCoreImpl(); + kernel.CurrentScheduler()->RescheduleCurrentCoreImpl(kernel); } } -void KScheduler::RescheduleCurrentCoreImpl() { +void KScheduler::RescheduleCurrentCoreImpl(KernelCore& kernel) { // Check that scheduling is needed. if (m_state.needs_scheduling.load()) [[likely]] { - GetCurrentThread(m_kernel).DisableDispatch(); - Schedule(); - GetCurrentThread(m_kernel).EnableDispatch(); + GetCurrentThread(kernel).DisableDispatch(kernel); + Schedule(kernel); + GetCurrentThread(kernel).EnableDispatch(kernel); } } -void KScheduler::Initialize(KThread* main_thread, KThread* idle_thread, s32 core_id) { +void KScheduler::Initialize(KernelCore& kernel, KThread* main_thread, KThread* idle_thread, s32 core_id) { // Set core ID/idle thread/interrupt task manager. m_core_id = core_id; m_idle_thread = idle_thread; @@ -156,39 +154,39 @@ void KScheduler::Initialize(KThread* main_thread, KThread* idle_thread, s32 core // Insert the main thread into the priority queue. // { - // KScopedSchedulerLock lk{m_kernel}; - // GetPriorityQueue(m_kernel).PushBack(GetCurrentThreadPointer(m_kernel)); - // SetSchedulerUpdateNeeded(m_kernel); + // KScopedSchedulerLock lk{kernel}; + // GetPriorityQueue(kernel).PushBack(GetCurrentThreadPointer(kernel)); + // SetSchedulerUpdateNeeded(kernel); // } // Bind interrupt handler. // kernel.GetInterruptManager().BindHandler( - // GetSchedulerInterruptHandler(m_kernel), KInterruptName::Scheduler, m_core_id, + // GetSchedulerInterruptHandler(kernel), KInterruptName::Scheduler, m_core_id, // KInterruptController::PriorityLevel::Scheduler, false, false); // Set the current thread. m_current_thread = main_thread; } -void KScheduler::Activate() { - ASSERT(GetCurrentThread(m_kernel).GetDisableDispatchCount() == 1); +void KScheduler::Activate(KernelCore& kernel) { + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1); // m_state.should_count_idle = KTargetSystem::IsDebugMode(); m_is_active = true; - RescheduleCurrentCore(); + RescheduleCurrentCore(kernel); } -void KScheduler::OnThreadStart() { - GetCurrentThread(m_kernel).EnableDispatch(); +void KScheduler::OnThreadStart(KernelCore& kernel) { + GetCurrentThread(kernel).EnableDispatch(kernel); } -u64 KScheduler::UpdateHighestPriorityThread(KThread* highest_thread) { +u64 KScheduler::UpdateHighestPriorityThread(KernelCore& kernel, KThread* highest_thread) { if (KThread* prev_highest_thread = m_state.highest_priority_thread; prev_highest_thread != highest_thread) [[likely]] { if (prev_highest_thread != nullptr) [[likely]] { IncrementScheduledCount(prev_highest_thread); prev_highest_thread->SetLastScheduledTick( - m_kernel.System().CoreTiming().GetClockTicks()); + kernel.System().CoreTiming().GetClockTicks()); } if (m_state.should_count_idle) { if (highest_thread != nullptr) [[likely]] { @@ -245,8 +243,7 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { } top_threads[core_id] = top_thread; - cores_needing_scheduling |= - kernel.Scheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]); + cores_needing_scheduling |= kernel.Scheduler(core_id).UpdateHighestPriorityThread(kernel, top_threads[core_id]); } // Idle cores are bad. We're going to try to migrate threads to each idle core in turn. @@ -274,8 +271,7 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { suggested->SetActiveCore(core_id); priority_queue.ChangeCore(suggested_core, suggested); top_threads[core_id] = suggested; - cores_needing_scheduling |= - kernel.Scheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]); + cores_needing_scheduling |= kernel.Scheduler(core_id).UpdateHighestPriorityThread(kernel, top_threads[core_id]); break; } @@ -298,17 +294,13 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { // The candidate core can run some other thread! We'll migrate its current // top thread to us. top_threads[candidate_core] = next_on_candidate_core; - cores_needing_scheduling |= - kernel.Scheduler(candidate_core) - .UpdateHighestPriorityThread(top_threads[candidate_core]); + cores_needing_scheduling |= kernel.Scheduler(candidate_core).UpdateHighestPriorityThread(kernel, top_threads[candidate_core]); // Perform the migration. suggested->SetActiveCore(core_id); priority_queue.ChangeCore(candidate_core, suggested); top_threads[core_id] = suggested; - cores_needing_scheduling |= - kernel.Scheduler(core_id).UpdateHighestPriorityThread( - top_threads[core_id]); + cores_needing_scheduling |= kernel.Scheduler(core_id).UpdateHighestPriorityThread(kernel, top_threads[core_id]); break; } } @@ -319,21 +311,21 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { } // HACK: any waiting dummy threads can wake up now. - kernel.GlobalSchedulerContext().WakeupWaitingDummyThreads(); + kernel.GlobalSchedulerContext().WakeupWaitingDummyThreads(kernel); // HACK: if we are a dummy thread, and we need to go sleep, indicate // that for when the lock is released. KThread* const cur_thread = GetCurrentThreadPointer(kernel); if (cur_thread->IsDummyThread() && cur_thread->GetState() != ThreadState::Runnable) { - cur_thread->RequestDummyThreadWait(); + cur_thread->RequestDummyThreadWait(kernel); } return cores_needing_scheduling; } -void KScheduler::SwitchThread(KThread* next_thread) { - KProcess* const cur_process = GetCurrentProcessPointer(m_kernel); - KThread* const cur_thread = GetCurrentThreadPointer(m_kernel); +void KScheduler::SwitchThread(KernelCore& kernel, KThread* next_thread) { + KProcess* const cur_process = GetCurrentProcessPointer(kernel); + KThread* const cur_thread = GetCurrentThreadPointer(kernel); // We never want to schedule a null thread, so use the idle thread if we don't have a next. if (next_thread == nullptr) { @@ -355,7 +347,7 @@ void KScheduler::SwitchThread(KThread* next_thread) { // Update the CPU time tracking variables. const s64 prev_tick = m_last_context_switch_time; - const s64 cur_tick = m_kernel.System().CoreTiming().GetClockTicks(); + const s64 cur_tick = kernel.System().CoreTiming().GetClockTicks(); const s64 tick_diff = cur_tick - prev_tick; cur_thread->AddCpuTime(m_core_id, tick_diff); if (cur_process != nullptr) { @@ -379,23 +371,23 @@ void KScheduler::SwitchThread(KThread* next_thread) { // } // Set the new thread. - SetCurrentThread(m_kernel, next_thread); + SetCurrentThread(kernel, next_thread); m_current_thread = next_thread; // Set the new Thread Local region. // cpu::SwitchThreadLocalRegion(GetInteger(next_thread->GetThreadLocalRegionAddress())); // Update the thread's cpu time differential in TLS, if relevant. - next_thread->UpdateTlsThreadCpuTime(cur_tick); + next_thread->UpdateTlsThreadCpuTime(kernel, cur_tick); } -void KScheduler::ScheduleImpl() { +void KScheduler::ScheduleImpl(KernelCore& kernel) { // First, clear the needs scheduling bool. m_state.needs_scheduling.store(false, std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_seq_cst); // Load the appropriate thread pointers for scheduling. - KThread* const cur_thread{GetCurrentThreadPointer(m_kernel)}; + KThread* const cur_thread{GetCurrentThreadPointer(kernel)}; KThread* highest_priority_thread{m_state.highest_priority_thread}; // Check whether there are runnable interrupt tasks. @@ -423,7 +415,7 @@ void KScheduler::ScheduleImpl() { // Returning from ScheduleImpl occurs after this thread has been scheduled again. } -void KScheduler::ScheduleImplFiber() { +void KScheduler::ScheduleImplFiber(KernelCore& kernel) { KThread* const cur_thread{m_switch_cur_thread}; KThread* highest_priority_thread{m_switch_highest_priority_thread}; @@ -437,7 +429,7 @@ void KScheduler::ScheduleImplFiber() { m_switch_from_schedule = false; // Save the original thread context. - Unload(cur_thread); + Unload(kernel, cur_thread); // The current thread's context has been entirely taken care of. // Now we want to loop until we successfully switch the thread context. @@ -468,11 +460,15 @@ void KScheduler::ScheduleImplFiber() { // It's time to switch the thread. // Switch to the highest priority thread. - SwitchThread(highest_priority_thread); + SwitchThread(kernel, highest_priority_thread); // Check if we need scheduling. If we do, then we can't complete the switch and should // retry. if (m_state.needs_scheduling.load(std::memory_order_seq_cst)) { + // Some libc++ lazily init mutex + [[maybe_unused]] auto const can_lock = highest_priority_thread->m_context_guard.try_lock(); + DEBUG_ASSERT(!can_lock); + // Our switch failed. // We should unlock the thread context, and then retry. highest_priority_thread->m_context_guard.unlock(); @@ -493,24 +489,28 @@ void KScheduler::ScheduleImplFiber() { } // Reload the guest thread context. - Reload(highest_priority_thread); + Reload(kernel, highest_priority_thread); // Reload the host thread. Common::Fiber::YieldTo(m_switch_fiber, *highest_priority_thread->m_host_context); } -void KScheduler::Unload(KThread* thread) { - m_kernel.PhysicalCore(m_core_id).SaveContext(thread); +void KScheduler::Unload(KernelCore& kernel, KThread* thread) { + kernel.PhysicalCore(m_core_id).SaveContext(thread); // Check if the thread is terminated by checking the DPC flags. if ((thread->GetStackParameters().dpc_flags & static_cast(DpcFlag::Terminated)) == 0) { + // Some libc++ lazily init mutex + [[maybe_unused]] auto const can_lock = thread->m_context_guard.try_lock(); + DEBUG_ASSERT(!can_lock); + // The thread isn't terminated, so we want to unlock it. thread->m_context_guard.unlock(); } } -void KScheduler::Reload(KThread* thread) { - m_kernel.PhysicalCore(m_core_id).LoadContext(thread); +void KScheduler::Reload(KernelCore& kernel, KThread* thread) { + kernel.PhysicalCore(m_core_id).LoadContext(thread); } void KScheduler::ClearPreviousThread(KernelCore& kernel, KThread* thread) { @@ -877,9 +877,9 @@ void KScheduler::YieldToAnyThread(KernelCore& kernel) { } } -void KScheduler::RescheduleOtherCores(u64 cores_needing_scheduling) { +void KScheduler::RescheduleOtherCores(KernelCore& kernel, u64 cores_needing_scheduling) { if (const u64 core_mask = cores_needing_scheduling & ~(1ULL << m_core_id); core_mask != 0) { - RescheduleCores(m_kernel, core_mask); + RescheduleCores(kernel, core_mask); } } diff --git a/src/core/hle/kernel/k_scheduler.h b/src/core/hle/kernel/k_scheduler.h index d85a0c0408..5a896fd899 100644 --- a/src/core/hle/kernel/k_scheduler.h +++ b/src/core/hle/kernel/k_scheduler.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -41,15 +44,15 @@ public: explicit KScheduler(KernelCore& kernel); ~KScheduler(); - void Initialize(KThread* main_thread, KThread* idle_thread, s32 core_id); - void Activate(); - void OnThreadStart(); - void Unload(KThread* thread); - void Reload(KThread* thread); + void Initialize(KernelCore& kernel, KThread* main_thread, KThread* idle_thread, s32 core_id); + void Activate(KernelCore& kernel); + void OnThreadStart(KernelCore& kernel); + void Unload(KernelCore& kernel, KThread* thread); + void Reload(KernelCore& kernel, KThread* thread); - void SetInterruptTaskRunnable(); - void RequestScheduleOnInterrupt(); - void PreemptSingleCore(); + void SetInterruptTaskRunnable(KernelCore& kernel); + void RequestScheduleOnInterrupt(KernelCore& kernel); + void PreemptSingleCore(KernelCore& kernel); u64 GetIdleCount() { return m_state.idle_count; @@ -122,18 +125,18 @@ private: static void RescheduleCurrentHLEThread(KernelCore& kernel); // Instanced private API. - void ScheduleImpl(); - void ScheduleImplFiber(); - void SwitchThread(KThread* next_thread); + void ScheduleImpl(KernelCore& kernel); + void ScheduleImplFiber(KernelCore& kernel); + void SwitchThread(KernelCore& kernel, KThread* next_thread); - void Schedule(); - void ScheduleOnInterrupt(); + void Schedule(KernelCore& kernel); + void ScheduleOnInterrupt(KernelCore& kernel); - void RescheduleOtherCores(u64 cores_needing_scheduling); - void RescheduleCurrentCore(); - void RescheduleCurrentCoreImpl(); + void RescheduleOtherCores(KernelCore& kernel, u64 cores_needing_scheduling); + void RescheduleCurrentCore(KernelCore& kernel); + void RescheduleCurrentCoreImpl(KernelCore& kernel); - u64 UpdateHighestPriorityThread(KThread* thread); + u64 UpdateHighestPriorityThread(KernelCore& kernel, KThread* thread); private: friend class KScopedDisableDispatch; @@ -149,14 +152,12 @@ private: KInterruptTaskManager* interrupt_task_manager{nullptr}; }; - KernelCore& m_kernel; SchedulingState m_state; bool m_is_active{false}; s32 m_core_id{0}; s64 m_last_context_switch_time{0}; KThread* m_idle_thread{nullptr}; std::atomic m_current_thread{nullptr}; - std::shared_ptr m_switch_fiber{}; KThread* m_switch_cur_thread{}; KThread* m_switch_highest_priority_thread{}; diff --git a/src/core/hle/kernel/k_scoped_resource_reservation.h b/src/core/hle/kernel/k_scoped_resource_reservation.h index 2cc464612a..4fc66af725 100644 --- a/src/core/hle/kernel/k_scoped_resource_reservation.h +++ b/src/core/hle/kernel/k_scoped_resource_reservation.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -11,34 +14,42 @@ namespace Kernel { class KScopedResourceReservation { public: - explicit KScopedResourceReservation(KResourceLimit* l, LimitableResource r, s64 v, s64 timeout) - : m_limit(l), m_value(v), m_resource(r) { + explicit KScopedResourceReservation(KernelCore& kernel, KResourceLimit* l, LimitableResource r, s64 v, s64 timeout) + : m_kernel{kernel} + , m_limit(l) + , m_value(v) + , m_resource(r) + { if (m_limit && m_value) { - m_succeeded = m_limit->Reserve(m_resource, m_value, timeout); + m_succeeded = m_limit->Reserve(kernel, m_resource, m_value, timeout); } else { m_succeeded = true; } } - explicit KScopedResourceReservation(KResourceLimit* l, LimitableResource r, s64 v = 1) - : m_limit(l), m_value(v), m_resource(r) { + explicit KScopedResourceReservation(KernelCore& kernel, KResourceLimit* l, LimitableResource r, s64 v = 1) + : m_kernel{kernel} + , m_limit(l) + , m_value(v) + , m_resource(r) + { if (m_limit && m_value) { - m_succeeded = m_limit->Reserve(m_resource, m_value); + m_succeeded = m_limit->Reserve(kernel, m_resource, m_value); } else { m_succeeded = true; } } - explicit KScopedResourceReservation(const KProcess* p, LimitableResource r, s64 v, s64 t) - : KScopedResourceReservation(p->GetResourceLimit(), r, v, t) {} + explicit KScopedResourceReservation(KernelCore& kernel, const KProcess* p, LimitableResource r, s64 v, s64 t) + : KScopedResourceReservation(kernel, p->GetResourceLimit(), r, v, t) {} - explicit KScopedResourceReservation(const KProcess* p, LimitableResource r, s64 v = 1) - : KScopedResourceReservation(p->GetResourceLimit(), r, v) {} + explicit KScopedResourceReservation(KernelCore& kernel, const KProcess* p, LimitableResource r, s64 v = 1) + : KScopedResourceReservation(kernel, p->GetResourceLimit(), r, v) {} ~KScopedResourceReservation() noexcept { if (m_limit && m_value && m_succeeded) { // Resource was not committed, release the reservation. - m_limit->Release(m_resource, m_value); + m_limit->Release(m_kernel, m_resource, m_value); } } @@ -52,6 +63,7 @@ public: } private: + KernelCore& m_kernel; KResourceLimit* m_limit{}; s64 m_value{}; LimitableResource m_resource{}; diff --git a/src/core/hle/kernel/k_server_port.cpp b/src/core/hle/kernel/k_server_port.cpp index bb6632f58e..dad6ef0678 100644 --- a/src/core/hle/kernel/k_server_port.cpp +++ b/src/core/hle/kernel/k_server_port.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -15,18 +18,18 @@ namespace Kernel { KServerPort::KServerPort(KernelCore& kernel) : KSynchronizationObject{kernel} {} KServerPort::~KServerPort() = default; -void KServerPort::Initialize(KPort* parent) { +void KServerPort::Initialize(KernelCore& kernel, KPort* parent) { // Set member variables. m_parent = parent; } -bool KServerPort::IsLight() const { +bool KServerPort::IsLight(KernelCore& kernel) const { return this->GetParent()->IsLight(); } -void KServerPort::CleanupSessions() { +void KServerPort::CleanupSessions(KernelCore& kernel) { // Ensure our preconditions are met. - if (this->IsLight()) { + if (this->IsLight(kernel)) { ASSERT(m_session_list.empty()); } else { ASSERT(m_light_session_list.empty()); @@ -37,7 +40,7 @@ void KServerPort::CleanupSessions() { // Get the last session in the list. KServerSession* session = nullptr; { - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; if (!m_session_list.empty()) { session = std::addressof(m_session_list.front()); m_session_list.pop_front(); @@ -46,7 +49,7 @@ void KServerPort::CleanupSessions() { // Close the session. if (session != nullptr) { - session->Close(); + session->Close(kernel); } else { break; } @@ -57,7 +60,7 @@ void KServerPort::CleanupSessions() { // Get the last session in the list. KLightServerSession* session = nullptr; { - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; if (!m_light_session_list.empty()) { session = std::addressof(m_light_session_list.front()); m_light_session_list.pop_front(); @@ -66,60 +69,60 @@ void KServerPort::CleanupSessions() { // Close the session. if (session != nullptr) { - session->Close(); + session->Close(kernel); } else { break; } } } -void KServerPort::Destroy() { +void KServerPort::Destroy(KernelCore& kernel) { // Note with our parent that we're closed. - m_parent->OnServerClosed(); + m_parent->OnServerClosed(kernel); // Perform necessary cleanup of our session lists. - this->CleanupSessions(); + this->CleanupSessions(kernel); // Close our reference to our parent. - m_parent->Close(); + m_parent->Close(kernel); } -bool KServerPort::IsSignaled() const { - if (this->IsLight()) { +bool KServerPort::IsSignaled(KernelCore& kernel) const { + if (this->IsLight(kernel)) { return !m_light_session_list.empty(); } else { return !m_session_list.empty(); } } -void KServerPort::EnqueueSession(KServerSession* session) { - ASSERT(!this->IsLight()); +void KServerPort::EnqueueSession(KernelCore& kernel, KServerSession* session) { + ASSERT(!this->IsLight(kernel)); - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; // Add the session to our queue. m_session_list.push_back(*session); if (m_session_list.size() == 1) { - this->NotifyAvailable(); + this->NotifyAvailable(kernel); } } -void KServerPort::EnqueueSession(KLightServerSession* session) { - ASSERT(this->IsLight()); +void KServerPort::EnqueueSession(KernelCore& kernel, KLightServerSession* session) { + ASSERT(this->IsLight(kernel)); - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; // Add the session to our queue. m_light_session_list.push_back(*session); if (m_light_session_list.size() == 1) { - this->NotifyAvailable(); + this->NotifyAvailable(kernel); } } -KServerSession* KServerPort::AcceptSession() { - ASSERT(!this->IsLight()); +KServerSession* KServerPort::AcceptSession(KernelCore& kernel) { + ASSERT(!this->IsLight(kernel)); - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; // Return the first session in the list. if (m_session_list.empty()) { @@ -131,10 +134,10 @@ KServerSession* KServerPort::AcceptSession() { return session; } -KLightServerSession* KServerPort::AcceptLightSession() { - ASSERT(this->IsLight()); +KLightServerSession* KServerPort::AcceptLightSession(KernelCore& kernel) { + ASSERT(this->IsLight(kernel)); - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; // Return the first session in the list. if (m_light_session_list.empty()) { diff --git a/src/core/hle/kernel/k_server_port.h b/src/core/hle/kernel/k_server_port.h index 72fdb6734a..5996a3a3ee 100644 --- a/src/core/hle/kernel/k_server_port.h +++ b/src/core/hle/kernel/k_server_port.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -26,29 +29,29 @@ public: explicit KServerPort(KernelCore& kernel); ~KServerPort() override; - void Initialize(KPort* parent); + void Initialize(KernelCore& kernel, KPort* parent); - void EnqueueSession(KServerSession* session); - void EnqueueSession(KLightServerSession* session); + void EnqueueSession(KernelCore& kernel, KServerSession* session); + void EnqueueSession(KernelCore& kernel, KLightServerSession* session); - KServerSession* AcceptSession(); - KLightServerSession* AcceptLightSession(); + KServerSession* AcceptSession(KernelCore& kernel); + KLightServerSession* AcceptLightSession(KernelCore& kernel); const KPort* GetParent() const { return m_parent; } - bool IsLight() const; + bool IsLight(KernelCore& kernel) const; // Overridden virtual functions. - void Destroy() override; - bool IsSignaled() const override; + void Destroy(KernelCore& kernel) override; + bool IsSignaled(KernelCore& kernel) const override; private: using SessionList = Common::IntrusiveListBaseTraits::ListType; using LightSessionList = Common::IntrusiveListBaseTraits::ListType; - void CleanupSessions(); + void CleanupSessions(KernelCore& kernel); SessionList m_session_list{}; LightSessionList m_light_session_list{}; diff --git a/src/core/hle/kernel/k_server_session.cpp b/src/core/hle/kernel/k_server_session.cpp index 4c91235ac9..35b4e78534 100644 --- a/src/core/hle/kernel/k_server_session.cpp +++ b/src/core/hle/kernel/k_server_session.cpp @@ -159,7 +159,7 @@ private: }; template -Result ProcessMessageSpecialData(s32& offset, KProcess& dst_process, KProcess& src_process, +Result ProcessMessageSpecialData(KernelCore& kernel, s32& offset, KProcess& dst_process, KProcess& src_process, KThread& src_thread, const MessageBuffer& dst_msg, const MessageBuffer& src_msg, const MessageBuffer::SpecialHeader& src_special_header) { @@ -185,10 +185,9 @@ Result ProcessMessageSpecialData(s32& offset, KProcess& dst_process, KProcess& s // If we're in a success state, try to move the handle to the new table. if (R_SUCCEEDED(result) && src_handle != Svc::InvalidHandle) { KScopedAutoObject obj = - src_handle_table.GetObjectForIpc(src_handle, std::addressof(src_thread)); + src_handle_table.GetObjectForIpc(kernel, src_handle, std::addressof(src_thread)); if (obj.IsNotNull()) { - Result add_result = - dst_handle_table.Add(std::addressof(dst_handle), obj.GetPointerUnsafe()); + Result add_result = dst_handle_table.Add(kernel, std::addressof(dst_handle), obj.GetPointerUnsafe()); if (R_FAILED(add_result)) { result = add_result; dst_handle = Svc::InvalidHandle; @@ -213,12 +212,10 @@ Result ProcessMessageSpecialData(s32& offset, KProcess& dst_process, KProcess& s if (src_handle != Svc::InvalidHandle) { if (R_SUCCEEDED(result)) { KScopedAutoObject obj = - src_handle_table.GetObjectForIpcWithoutPseudoHandle(src_handle); + src_handle_table.GetObjectForIpcWithoutPseudoHandle(kernel, src_handle); if (obj.IsNotNull()) { - Result add_result = dst_handle_table.Add(std::addressof(dst_handle), - obj.GetPointerUnsafe()); - - src_handle_table.Remove(src_handle); + Result add_result = dst_handle_table.Add(kernel, std::addressof(dst_handle), obj.GetPointerUnsafe()); + src_handle_table.Remove(kernel, src_handle); if (R_FAILED(add_result)) { result = add_result; @@ -228,7 +225,7 @@ Result ProcessMessageSpecialData(s32& offset, KProcess& dst_process, KProcess& s result = ResultInvalidHandle; } } else { - src_handle_table.Remove(src_handle); + src_handle_table.Remove(kernel, src_handle); } } @@ -336,7 +333,7 @@ constexpr Result GetMapAliasTestStateAndAttributeMask(KMemoryState& out_state, R_SUCCEED(); } -void CleanupSpecialData(KProcess& dst_process, u32* dst_msg_ptr, size_t dst_buffer_size) { +void CleanupSpecialData(KernelCore& kernel, KProcess& dst_process, u32* dst_msg_ptr, size_t dst_buffer_size) { // Parse the message. const MessageBuffer dst_msg(dst_msg_ptr, dst_buffer_size); const MessageBuffer::MessageHeader dst_header(dst_msg); @@ -363,15 +360,14 @@ void CleanupSpecialData(KProcess& dst_process, u32* dst_msg_ptr, size_t dst_buff const Handle handle = dst_msg.GetHandle(offset); if (handle != Svc::InvalidHandle) { - dst_handle_table.Remove(handle); + dst_handle_table.Remove(kernel, handle); } offset = dst_msg.SetHandle(offset, Svc::InvalidHandle); } } -Result CleanupServerHandles(KernelCore& kernel, uint64_t message, size_t buffer_size, - KPhysicalAddress message_paddr) { +Result CleanupServerHandles(KernelCore& kernel, uint64_t message, size_t buffer_size, KPhysicalAddress message_paddr) { // Server is assumed to be current thread. KThread& thread = GetCurrentThread(kernel); @@ -410,7 +406,7 @@ Result CleanupServerHandles(KernelCore& kernel, uint64_t message, size_t buffer_ // Close the handles. for (auto i = 0; i < special_header.GetMoveHandleCount(); ++i) { - handle_table.Remove(msg.GetHandle(offset)); + handle_table.Remove(kernel, msg.GetHandle(offset)); offset += static_cast(sizeof(Svc::Handle) / sizeof(u32)); } } @@ -558,7 +554,7 @@ Result ReceiveMessage(KernelCore& kernel, bool& recv_list_broken, uint64_t dst_m recv_list_broken = false; // Set the server process for the request. - request->SetServerProcess(std::addressof(dst_process)); + request->SetServerProcess(kernel, std::addressof(dst_process)); // Determine the message buffers. u32 *dst_msg_ptr, *src_msg_ptr; @@ -639,7 +635,7 @@ Result ReceiveMessage(KernelCore& kernel, bool& recv_list_broken, uint64_t dst_m // Cleanup special data. if (src_header.GetHasSpecialHeader()) { - CleanupSpecialData(dst_process, dst_msg_ptr, dst_buffer_size); + CleanupSpecialData(kernel, dst_process, dst_msg_ptr, dst_buffer_size); } // Cleanup the header if the receive list isn't broken. @@ -661,7 +657,7 @@ Result ReceiveMessage(KernelCore& kernel, bool& recv_list_broken, uint64_t dst_m }; // Process special data. - R_TRY(ProcessMessageSpecialData(offset, dst_process, src_process, src_thread, + R_TRY(ProcessMessageSpecialData(kernel, offset, dst_process, src_process, src_thread, dst_msg, src_msg, src_special_header)); } @@ -922,7 +918,7 @@ Result SendMessage(KernelCore& kernel, uint64_t src_message_buffer, size_t src_b // Cleanup special data. if (processed_special_data) { if (src_header.GetHasSpecialHeader()) { - CleanupSpecialData(dst_process, dst_msg_ptr, dst_buffer_size); + CleanupSpecialData(kernel, dst_process, dst_msg_ptr, dst_buffer_size); } } else { CleanupServerHandles(kernel, src_user ? src_message_buffer : 0, src_buffer_size, @@ -987,7 +983,7 @@ Result SendMessage(KernelCore& kernel, uint64_t src_message_buffer, size_t src_b ASSERT(GetCurrentThreadPointer(kernel) == std::addressof(src_thread)); processed_special_data = true; if (src_header.GetHasSpecialHeader()) { - R_TRY(ProcessMessageSpecialData(offset, dst_process, src_process, src_thread, + R_TRY(ProcessMessageSpecialData(kernel, offset, dst_process, src_process, src_thread, dst_msg, src_msg, src_special_header)); } @@ -1080,19 +1076,17 @@ void ReplyAsyncError(KProcess* to_process, uint64_t to_msg_buf, size_t to_msg_bu } // namespace KServerSession::KServerSession(KernelCore& kernel) - : KSynchronizationObject{kernel}, m_lock{m_kernel} {} + : KSynchronizationObject{kernel}, m_lock{kernel} {} KServerSession::~KServerSession() = default; -void KServerSession::Destroy() { - m_parent->OnServerClosed(); - - this->CleanupRequests(); - - m_parent->Close(); +void KServerSession::Destroy(KernelCore& kernel) { + m_parent->OnServerClosed(kernel); + this->CleanupRequests(kernel); + m_parent->Close(kernel); } -Result KServerSession::ReceiveRequest(uintptr_t server_message, uintptr_t server_buffer_size, +Result KServerSession::ReceiveRequest(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size, KPhysicalAddress server_message_paddr, std::shared_ptr* out_context, std::weak_ptr manager) { @@ -1104,7 +1098,7 @@ Result KServerSession::ReceiveRequest(uintptr_t server_message, uintptr_t server KThread* client_thread; { - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; // Ensure that we can service the request. R_UNLESS(!m_parent->IsClientClosed(), ResultSessionClosed); @@ -1124,11 +1118,11 @@ Result KServerSession::ReceiveRequest(uintptr_t server_message, uintptr_t server R_UNLESS(client_thread != nullptr, ResultSessionClosed); // Open the client thread. - client_thread->Open(); + client_thread->Open(kernel); } SCOPE_EXIT { - client_thread->Close(); + client_thread->Close(kernel); }; // Set the request as our current. @@ -1150,13 +1144,13 @@ Result KServerSession::ReceiveRequest(uintptr_t server_message, uintptr_t server Core::Memory::Memory& memory{client_thread->GetOwnerProcess()->GetMemory()}; u32* cmd_buf{reinterpret_cast(memory.GetPointer(client_message))}; *out_context = - std::make_shared(m_kernel, memory, this, client_thread); + std::make_shared(kernel, memory, this, client_thread); (*out_context)->SetSessionRequestManager(manager); (*out_context)->PopulateFromIncomingCommandBuffer(cmd_buf); // We succeeded. R_SUCCEED(); } else { - result = ReceiveMessage(m_kernel, recv_list_broken, server_message, server_buffer_size, + result = ReceiveMessage(kernel, recv_list_broken, server_message, server_buffer_size, server_message_paddr, *client_thread, client_message, client_buffer_size, this, request); } @@ -1168,11 +1162,11 @@ Result KServerSession::ReceiveRequest(uintptr_t server_message, uintptr_t server // Clear the current request. { - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); ASSERT(m_current_request == request); m_current_request = nullptr; if (!m_request_list.empty()) { - this->NotifyAvailable(); + this->NotifyAvailable(kernel); } } @@ -1180,7 +1174,7 @@ Result KServerSession::ReceiveRequest(uintptr_t server_message, uintptr_t server { // After we reply, close our reference to the request. SCOPE_EXIT { - request->Close(); + request->Close(kernel); }; // Get the event to check whether the request is async. @@ -1199,13 +1193,13 @@ Result KServerSession::ReceiveRequest(uintptr_t server_message, uintptr_t server client_pt.UnlockForIpcUserBuffer(client_message, client_buffer_size); // Signal the event. - event->Signal(); + event->Signal(kernel); } else { // End the client thread's wait. - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); if (!client_thread->IsTerminationRequested()) { - client_thread->EndWait(result_for_client); + client_thread->EndWait(kernel, result_for_client); } } } @@ -1221,15 +1215,15 @@ Result KServerSession::ReceiveRequest(uintptr_t server_message, uintptr_t server R_RETURN(result); } -Result KServerSession::SendReply(uintptr_t server_message, uintptr_t server_buffer_size, - KPhysicalAddress server_message_paddr, bool is_hle) { +Result KServerSession::SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size, + KPhysicalAddress server_message_paddr, bool is_hle, bool session_closed) { // Lock the session. KScopedLightLock lk{m_lock}; // Get the request. KSessionRequest* request; { - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; // Get the current request. request = m_current_request; @@ -1238,13 +1232,13 @@ Result KServerSession::SendReply(uintptr_t server_message, uintptr_t server_buff // Clear the current request, since we're processing it. m_current_request = nullptr; if (!m_request_list.empty()) { - this->NotifyAvailable(); + this->NotifyAvailable(kernel); } } // Close reference to the request once we're done processing it. SCOPE_EXIT { - request->Close(); + request->Close(kernel); }; // Extract relevant information from the request. @@ -1254,7 +1248,7 @@ Result KServerSession::SendReply(uintptr_t server_message, uintptr_t server_buff KEvent* event = request->GetEvent(); // Check whether we're closed. - const bool closed = (client_thread == nullptr || m_parent->IsClientClosed()); + const bool closed = (client_thread == nullptr || m_parent->IsClientClosed() || session_closed); Result result = ResultSuccess; if (!closed) { @@ -1263,7 +1257,7 @@ Result KServerSession::SendReply(uintptr_t server_message, uintptr_t server_buff // HLE servers write directly to a pointer to the thread command buffer. Therefore // the reply has already been written in this case. } else { - result = SendMessage(m_kernel, server_message, server_buffer_size, server_message_paddr, + result = SendMessage(kernel, server_message, server_buffer_size, server_message_paddr, *client_thread, client_message, client_buffer_size, this, request); } } else if (!is_hle) { @@ -1275,7 +1269,7 @@ Result KServerSession::SendReply(uintptr_t server_message, uintptr_t server_buff (client_process != nullptr) ? std::addressof(client_process->GetPageTable()) : nullptr; // Cleanup server handles. - result = CleanupServerHandles(m_kernel, server_message, server_buffer_size, + result = CleanupServerHandles(kernel, server_message, server_buffer_size, server_message_paddr); // Cleanup mappings. @@ -1313,13 +1307,13 @@ Result KServerSession::SendReply(uintptr_t server_message, uintptr_t server_buff client_page_table->UnlockForIpcUserBuffer(client_message, client_buffer_size); // Signal the event. - event->Signal(); + event->Signal(kernel); } else { // End the client thread's wait. - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; if (!client_thread->IsTerminationRequested()) { - client_thread->EndWait(client_result); + client_thread->EndWait(kernel, client_result); } } } @@ -1327,45 +1321,45 @@ Result KServerSession::SendReply(uintptr_t server_message, uintptr_t server_buff R_RETURN(result); } -Result KServerSession::OnRequest(KSessionRequest* request) { +Result KServerSession::OnRequest(KernelCore& kernel, KSessionRequest* request) { // Create the wait queue. - ThreadQueueImplForKServerSessionRequest wait_queue{m_kernel}; + ThreadQueueImplForKServerSessionRequest wait_queue{kernel}; { // Lock the scheduler. - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; // Ensure that we can handle new requests. R_UNLESS(!m_parent->IsServerClosed(), ResultSessionClosed); // Check that we're not terminating. - R_UNLESS(!GetCurrentThread(m_kernel).IsTerminationRequested(), ResultTerminationRequested); + R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(), ResultTerminationRequested); // Get whether we're empty. const bool was_empty = m_request_list.empty(); // Add the request to the list. - request->Open(); + request->Open(kernel); m_request_list.push_back(*request); // If we were empty, signal. if (was_empty) { - this->NotifyAvailable(); + this->NotifyAvailable(kernel); } // If we have a request event, this is asynchronous, and we don't need to wait. R_SUCCEED_IF(request->GetEvent() != nullptr); // This is a synchronous request, so we should wait for our request to complete. - GetCurrentThread(m_kernel).SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC); - GetCurrentThread(m_kernel).BeginWait(std::addressof(wait_queue)); + GetCurrentThread(kernel).SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::IPC); + GetCurrentThread(kernel).BeginWait(kernel, std::addressof(wait_queue)); } - return GetCurrentThread(m_kernel).GetWaitResult(); + return GetCurrentThread(kernel).GetWaitResult(); } -bool KServerSession::IsSignaled() const { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); +bool KServerSession::IsSignaled(KernelCore& kernel) const { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); // If the client is closed, we're always signaled. if (m_parent->IsClientClosed()) { @@ -1376,7 +1370,7 @@ bool KServerSession::IsSignaled() const { return !m_request_list.empty() && m_current_request == nullptr; } -void KServerSession::CleanupRequests() { +void KServerSession::CleanupRequests(KernelCore& kernel) { KScopedLightLock lk(m_lock); // Clean up any pending requests. @@ -1384,7 +1378,7 @@ void KServerSession::CleanupRequests() { // Get the next request. KSessionRequest* request = nullptr; { - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; if (m_current_request) { // Choose the current request if we have one. @@ -1404,7 +1398,7 @@ void KServerSession::CleanupRequests() { // Close a reference to the request once it's cleaned up. SCOPE_EXIT { - request->Close(); + request->Close(kernel); }; // Extract relevant information from the request. @@ -1434,20 +1428,20 @@ void KServerSession::CleanupRequests() { client_page_table->UnlockForIpcUserBuffer(client_message, client_buffer_size); // Signal the event. - event->Signal(); + event->Signal(kernel); } else { // End the client thread's wait. - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; if (!client_thread->IsTerminationRequested()) { - client_thread->EndWait(ResultSessionClosed); + client_thread->EndWait(kernel, ResultSessionClosed); } } } } } -void KServerSession::OnClientClosed() { +void KServerSession::OnClientClosed(KernelCore& kernel) { KScopedLightLock lk{m_lock}; // Handle any pending requests. @@ -1462,12 +1456,12 @@ void KServerSession::OnClientClosed() { // Get the next request. { - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; if (m_current_request != nullptr && m_current_request != prev_request) { // Set the request, open a reference as we process it. request = m_current_request; - request->Open(); + request->Open(kernel); cur_request = true; // Get thread and event for the request. @@ -1503,14 +1497,14 @@ void KServerSession::OnClientClosed() { // Ensure that we close the request when done. SCOPE_EXIT { - request->Close(); + request->Close(kernel); }; // If we're terminating, close a reference to the thread and event. if (terminate) { - thread->Close(); + thread->Close(kernel); if (event != nullptr) { - event->Close(); + event->Close(kernel); } } @@ -1534,12 +1528,12 @@ void KServerSession::OnClientClosed() { client_pt.UnlockForIpcUserBuffer(request->GetAddress(), request->GetSize()); // Signal the event. - event->Signal(); + event->Signal(kernel); } } // Notify. - this->NotifyAvailable(ResultSessionClosed); + this->NotifyAvailable(kernel, ResultSessionClosed); } } // namespace Kernel diff --git a/src/core/hle/kernel/k_server_session.h b/src/core/hle/kernel/k_server_session.h index 2876c231b2..90aaa6dd77 100644 --- a/src/core/hle/kernel/k_server_session.h +++ b/src/core/hle/kernel/k_server_session.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -36,7 +39,7 @@ public: explicit KServerSession(KernelCore& kernel); ~KServerSession() override; - void Destroy() override; + void Destroy(KernelCore& kernel) override; void Initialize(KSession* p) { m_parent = p; @@ -46,29 +49,29 @@ public: return m_parent; } - bool IsSignaled() const override; - void OnClientClosed(); + bool IsSignaled(KernelCore& kernel) const override; + void OnClientClosed(KernelCore& kernel); - Result OnRequest(KSessionRequest* request); - Result SendReply(uintptr_t server_message, uintptr_t server_buffer_size, - KPhysicalAddress server_message_paddr, bool is_hle = false); - Result ReceiveRequest(uintptr_t server_message, uintptr_t server_buffer_size, + Result OnRequest(KernelCore& kernel, KSessionRequest* request); + Result SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size, + KPhysicalAddress server_message_paddr, bool is_hle = false, bool session_closed = false); + Result ReceiveRequest(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size, KPhysicalAddress server_message_paddr, std::shared_ptr* out_context = nullptr, std::weak_ptr manager = {}); - Result SendReplyHLE() { - R_RETURN(this->SendReply(0, 0, 0, true)); + Result SendReplyHLE(KernelCore& kernel, bool session_closed = false) { + R_RETURN(this->SendReply(kernel, 0, 0, 0, true, session_closed)); } - Result ReceiveRequestHLE(std::shared_ptr* out_context, + Result ReceiveRequestHLE(KernelCore& kernel, std::shared_ptr* out_context, std::weak_ptr manager) { - R_RETURN(this->ReceiveRequest(0, 0, 0, out_context, manager)); + R_RETURN(this->ReceiveRequest(kernel, 0, 0, 0, out_context, manager)); } private: /// Frees up waiting client sessions when this server session is about to die - void CleanupRequests(); + void CleanupRequests(KernelCore& kernel); /// KSession that owns this KServerSession KSession* m_parent{}; diff --git a/src/core/hle/kernel/k_session.cpp b/src/core/hle/kernel/k_session.cpp index 4a1f6027ee..58b0d5de67 100644 --- a/src/core/hle/kernel/k_session.cpp +++ b/src/core/hle/kernel/k_session.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -13,12 +16,12 @@ KSession::KSession(KernelCore& kernel) : KAutoObjectWithSlabHeapAndContainer{kernel}, m_server{kernel}, m_client{kernel} {} KSession::~KSession() = default; -void KSession::Initialize(KClientPort* client_port, uintptr_t name) { +void KSession::Initialize(KernelCore& kernel, KClientPort* client_port, uintptr_t name) { // Increment reference count. // Because reference count is one on creation, this will result // in a reference count of two. Thus, when both server and client are closed // this object will be destroyed. - this->Open(); + this->Open(kernel); // Create our sub sessions. KAutoObject::Create(std::addressof(m_server)); @@ -33,45 +36,45 @@ void KSession::Initialize(KClientPort* client_port, uintptr_t name) { m_name = name; // Set our owner process. - m_process = GetCurrentProcessPointer(m_kernel); - m_process->Open(); + m_process = GetCurrentProcessPointer(kernel); + m_process->Open(kernel); // Set our port. m_port = client_port; if (m_port != nullptr) { - m_port->Open(); + m_port->Open(kernel); } // Mark initialized. m_initialized = true; } -void KSession::Finalize() { +void KSession::Finalize(KernelCore& kernel) { if (m_port != nullptr) { - m_port->OnSessionFinalized(); - m_port->Close(); + m_port->OnSessionFinalized(kernel); + m_port->Close(kernel); } } -void KSession::OnServerClosed() { +void KSession::OnServerClosed(KernelCore& kernel) { if (this->GetState() == State::Normal) { this->SetState(State::ServerClosed); m_client.OnServerClosed(); } } -void KSession::OnClientClosed() { +void KSession::OnClientClosed(KernelCore& kernel) { if (this->GetState() == State::Normal) { - SetState(State::ClientClosed); - m_server.OnClientClosed(); + this->SetState(State::ClientClosed); + m_server.OnClientClosed(kernel); } } -void KSession::PostDestroy(uintptr_t arg) { +void KSession::PostDestroy(KernelCore& kernel, uintptr_t arg) { // Release the session count resource the owner process holds. KProcess* owner = reinterpret_cast(arg); - owner->GetResourceLimit()->Release(LimitableResource::SessionCountMax, 1); - owner->Close(); + owner->GetResourceLimit()->Release(kernel, LimitableResource::SessionCountMax, 1); + owner->Close(kernel); } } // namespace Kernel diff --git a/src/core/hle/kernel/k_session.h b/src/core/hle/kernel/k_session.h index 3f4dd5989f..0d77bc1fd4 100644 --- a/src/core/hle/kernel/k_session.h +++ b/src/core/hle/kernel/k_session.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -21,8 +24,8 @@ public: explicit KSession(KernelCore& kernel); ~KSession() override; - void Initialize(KClientPort* port, uintptr_t name); - void Finalize() override; + void Initialize(KernelCore& kernel, KClientPort* port, uintptr_t name); + void Finalize(KernelCore& kernel) override; bool IsInitialized() const override { return m_initialized; @@ -32,11 +35,9 @@ public: return reinterpret_cast(m_process); } - static void PostDestroy(uintptr_t arg); - - void OnServerClosed(); - - void OnClientClosed(); + static void PostDestroy(KernelCore& kernel, uintptr_t arg); + void OnServerClosed(KernelCore& kernel); + void OnClientClosed(KernelCore& kernel); bool IsServerClosed() const { return this->GetState() != State::Normal; @@ -46,8 +47,8 @@ public: return this->GetState() != State::Normal; } - Result OnRequest(KSessionRequest* request) { - R_RETURN(m_server.OnRequest(request)); + Result OnRequest(KernelCore& kernel, KSessionRequest* request) { + R_RETURN(m_server.OnRequest(kernel, request)); } KClientSession& GetClientSession() { diff --git a/src/core/hle/kernel/k_session_request.cpp b/src/core/hle/kernel/k_session_request.cpp index 9a69b4ffc1..25451a3922 100644 --- a/src/core/hle/kernel/k_session_request.cpp +++ b/src/core/hle/kernel/k_session_request.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -18,7 +21,7 @@ Result KSessionRequest::SessionMappings::PushMap(KProcessAddress client, KProces } else { // Allocate a page for the extra mappings. if (m_mappings == nullptr) { - KPageBuffer* page_buffer = KPageBuffer::Allocate(m_kernel); + KPageBuffer* page_buffer = KPageBuffer::Allocate(kernel); R_UNLESS(page_buffer != nullptr, ResultOutOfMemory); m_mappings = reinterpret_cast(page_buffer); @@ -54,7 +57,7 @@ Result KSessionRequest::SessionMappings::PushExchange(KProcessAddress client, void KSessionRequest::SessionMappings::Finalize() { if (m_mappings) { - KPageBuffer::Free(m_kernel, reinterpret_cast(m_mappings)); + KPageBuffer::Free(kernel, reinterpret_cast(m_mappings)); m_mappings = nullptr; } } diff --git a/src/core/hle/kernel/k_session_request.h b/src/core/hle/kernel/k_session_request.h index 283669e0ac..6665eca56f 100644 --- a/src/core/hle/kernel/k_session_request.h +++ b/src/core/hle/kernel/k_session_request.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -56,7 +59,7 @@ public: }; public: - explicit SessionMappings(KernelCore& kernel) : m_kernel(kernel) {} + explicit SessionMappings(KernelCore& kernel_) : kernel(kernel_) {} void Initialize() {} void Finalize(); @@ -155,7 +158,7 @@ public: } private: - KernelCore& m_kernel; + KernelCore& kernel; std::array m_static_mappings{}; Mapping* m_mappings{}; u8 m_num_send{}; @@ -174,26 +177,26 @@ public: return req; } - void Destroy() override { - this->Finalize(); - KSessionRequest::Free(m_kernel, this); + void Destroy(KernelCore& kernel) override { + this->Finalize(kernel); + KSessionRequest::Free(kernel, this); } - void Initialize(KEvent* event, uintptr_t address, size_t size) { + void Initialize(KernelCore& kernel, KEvent* event, uintptr_t address, size_t size) { m_mappings.Initialize(); - m_thread = GetCurrentThreadPointer(m_kernel); + m_thread = GetCurrentThreadPointer(kernel); m_event = event; m_address = address; m_size = size; - m_thread->Open(); + m_thread->Open(kernel); if (m_event != nullptr) { - m_event->Open(); + m_event->Open(kernel); } } - static void PostDestroy(uintptr_t arg) {} + static void PostDestroy(KernelCore& kernel, uintptr_t arg) {} KThread* GetThread() const { return m_thread; @@ -211,9 +214,9 @@ public: return m_server; } - void SetServerProcess(KProcess* process) { + void SetServerProcess(KernelCore& kernel, KProcess* process) { m_server = process; - m_server->Open(); + m_server->Open(kernel); } void ClearThread() { @@ -289,17 +292,16 @@ public: private: // NOTE: This is public and virtual in Nintendo's kernel. - void Finalize() override { + void Finalize(KernelCore& kernel) override { m_mappings.Finalize(); - if (m_thread) { - m_thread->Close(); + m_thread->Close(kernel); } if (m_event) { - m_event->Close(); + m_event->Close(kernel); } if (m_server) { - m_server->Close(); + m_server->Close(kernel); } } diff --git a/src/core/hle/kernel/k_shared_memory.cpp b/src/core/hle/kernel/k_shared_memory.cpp index f713968f68..61f2872560 100644 --- a/src/core/hle/kernel/k_shared_memory.cpp +++ b/src/core/hle/kernel/k_shared_memory.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -15,7 +18,7 @@ namespace Kernel { KSharedMemory::KSharedMemory(KernelCore& kernel) : KAutoObjectWithSlabHeapAndContainer{kernel} {} KSharedMemory::~KSharedMemory() = default; -Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory, KProcess* owner_process, +Result KSharedMemory::Initialize(KernelCore& kernel, Core::DeviceMemory& device_memory, KProcess* owner_process, Svc::MemoryPermission owner_permission, Svc::MemoryPermission user_permission, std::size_t size) { // Set members. @@ -28,11 +31,10 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory, KProcess* ow const size_t num_pages = Common::DivideUp(size, PageSize); // Get the resource limit. - KResourceLimit* reslimit = m_kernel.GetSystemResourceLimit(); + KResourceLimit* reslimit = kernel.GetSystemResourceLimit(); // Reserve memory for ourselves. - KScopedResourceReservation memory_reservation(reslimit, LimitableResource::PhysicalMemoryMax, - size); + KScopedResourceReservation memory_reservation(kernel, reslimit, LimitableResource::PhysicalMemoryMax, size); R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached); // Allocate the memory. @@ -40,12 +42,11 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory, KProcess* ow //! HACK: Open continuous mapping from sysmodule pool. auto option = KMemoryManager::EncodeOption(KMemoryManager::Pool::Secure, KMemoryManager::Direction::FromBack); - m_physical_address = m_kernel.MemoryManager().AllocateAndOpenContinuous(num_pages, 1, option); + m_physical_address = kernel.MemoryManager().AllocateAndOpenContinuous(num_pages, 1, option); R_UNLESS(m_physical_address != 0, ResultOutOfMemory); //! Insert the result into our page group. - m_page_group.emplace(m_kernel, - std::addressof(m_kernel.GetSystemSystemResource().GetBlockInfoManager())); + m_page_group.emplace(kernel, std::addressof(kernel.GetSystemSystemResource().GetBlockInfoManager())); m_page_group->AddBlock(m_physical_address, num_pages); // Commit our reservation. @@ -53,7 +54,7 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory, KProcess* ow // Set our resource limit. m_resource_limit = reslimit; - m_resource_limit->Open(); + m_resource_limit->Open(kernel); // Mark initialized. m_is_initialized = true; @@ -66,14 +67,14 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory, KProcess* ow R_SUCCEED(); } -void KSharedMemory::Finalize() { +void KSharedMemory::Finalize(KernelCore& kernel) { // Close and finalize the page group. - m_page_group->Close(); + m_page_group->Close(kernel); m_page_group->Finalize(); // Release the memory reservation. - m_resource_limit->Release(LimitableResource::PhysicalMemoryMax, m_size); - m_resource_limit->Close(); + m_resource_limit->Release(kernel, LimitableResource::PhysicalMemoryMax, m_size); + m_resource_limit->Close(kernel); } Result KSharedMemory::Map(KProcess& target_process, KProcessAddress address, std::size_t map_size, diff --git a/src/core/hle/kernel/k_shared_memory.h b/src/core/hle/kernel/k_shared_memory.h index 54b23d7ac2..3aede457c9 100644 --- a/src/core/hle/kernel/k_shared_memory.h +++ b/src/core/hle/kernel/k_shared_memory.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -26,7 +29,7 @@ public: explicit KSharedMemory(KernelCore& kernel); ~KSharedMemory() override; - Result Initialize(Core::DeviceMemory& device_memory_, KProcess* owner_process_, + Result Initialize(KernelCore& kernel, Core::DeviceMemory& device_memory_, KProcess* owner_process_, Svc::MemoryPermission owner_permission_, Svc::MemoryPermission user_permission_, std::size_t size_); @@ -66,12 +69,12 @@ public: return m_device_memory->GetPointer(m_physical_address + offset); } - void Finalize() override; + void Finalize(KernelCore& kernel) override; bool IsInitialized() const override { return m_is_initialized; } - static void PostDestroy(uintptr_t arg) {} + static void PostDestroy(KernelCore& kernel, uintptr_t arg) {} private: Core::DeviceMemory* m_device_memory{}; diff --git a/src/core/hle/kernel/k_slab_heap.h b/src/core/hle/kernel/k_slab_heap.h index c91e7969a2..a5dbab9571 100644 --- a/src/core/hle/kernel/k_slab_heap.h +++ b/src/core/hle/kernel/k_slab_heap.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -12,7 +12,6 @@ #include "common/atomic_ops.h" #include "common/common_funcs.h" #include "common/common_types.h" -#include "common/spin_lock.h" namespace Kernel { @@ -30,7 +29,7 @@ public: }; public: - constexpr KSlabHeapImpl() = default; + KSlabHeapImpl() = default; void Initialize() { ASSERT(m_head == nullptr); @@ -68,7 +67,7 @@ public: private: std::atomic m_head{}; - Common::SpinLock m_lock; + std::mutex m_lock; }; } // namespace impl diff --git a/src/core/hle/kernel/k_synchronization_object.cpp b/src/core/hle/kernel/k_synchronization_object.cpp index 3e5b735b1e..221cab655f 100644 --- a/src/core/hle/kernel/k_synchronization_object.cpp +++ b/src/core/hle/kernel/k_synchronization_object.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -18,12 +21,10 @@ namespace { class ThreadQueueImplForKSynchronizationObjectWait final : public KThreadQueueWithoutEndWait { public: - ThreadQueueImplForKSynchronizationObjectWait(KernelCore& kernel, KSynchronizationObject** o, - KSynchronizationObject::ThreadListNode* n, s32 c) + ThreadQueueImplForKSynchronizationObjectWait(KernelCore& kernel, KSynchronizationObject** o, KSynchronizationObject::ThreadListNode* n, s32 c) : KThreadQueueWithoutEndWait(kernel), m_objects(o), m_nodes(n), m_count(c) {} - void NotifyAvailable(KThread* waiting_thread, KSynchronizationObject* signaled_object, - Result wait_result) override { + void NotifyAvailable(KernelCore& kernel, KThread* waiting_thread, KSynchronizationObject* signaled_object, Result wait_result) override { // Determine the sync index, and unlink all nodes. s32 sync_index = -1; for (auto i = 0; i < m_count; ++i) { @@ -43,10 +44,10 @@ public: waiting_thread->ClearCancellable(); // Invoke the base end wait handler. - KThreadQueue::EndWait(waiting_thread, wait_result); + KThreadQueue::EndWait(kernel, waiting_thread, wait_result); } - void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { + void CancelWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { // Remove all nodes from our list. for (auto i = 0; i < m_count; ++i) { m_objects[i]->UnlinkNode(std::addressof(m_nodes[i])); @@ -56,7 +57,7 @@ public: waiting_thread->ClearCancellable(); // Invoke the base cancel wait handler. - KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + KThreadQueue::CancelWait(kernel, waiting_thread, wait_result, cancel_timer_task); } private: @@ -67,9 +68,9 @@ private: } // namespace -void KSynchronizationObject::Finalize() { +void KSynchronizationObject::Finalize(KernelCore& kernel) { this->OnFinalizeSynchronizationObject(); - KAutoObject::Finalize(); + KAutoObject::Finalize(kernel); } Result KSynchronizationObject::Wait(KernelCore& kernel, s32* out_index, @@ -98,7 +99,7 @@ Result KSynchronizationObject::Wait(KernelCore& kernel, s32* out_index, for (auto i = 0; i < num_objects; ++i) { ASSERT(objects[i] != nullptr); - if (objects[i]->IsSignaled()) { + if (objects[i]->IsSignaled(kernel)) { *out_index = i; slp.CancelSleep(); R_THROW(ResultSuccess); @@ -134,7 +135,7 @@ Result KSynchronizationObject::Wait(KernelCore& kernel, s32* out_index, // Wait for an object to be signaled. wait_queue.SetHardwareTimer(timer); - thread->BeginWait(std::addressof(wait_queue)); + thread->BeginWait(kernel, std::addressof(wait_queue)); thread->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Synchronization); } @@ -149,26 +150,26 @@ KSynchronizationObject::KSynchronizationObject(KernelCore& kernel) : KAutoObject KSynchronizationObject::~KSynchronizationObject() = default; -void KSynchronizationObject::NotifyAvailable(Result result) { - KScopedSchedulerLock sl(m_kernel); +void KSynchronizationObject::NotifyAvailable(KernelCore& kernel, Result result) { + KScopedSchedulerLock sl(kernel); // If we're not signaled, we've nothing to notify. - if (!this->IsSignaled()) { + if (!this->IsSignaled(kernel)) { return; } // Iterate over each thread. for (auto* cur_node = m_thread_list_head; cur_node != nullptr; cur_node = cur_node->next) { - cur_node->thread->NotifyAvailable(this, result); + cur_node->thread->NotifyAvailable(kernel, this, result); } } -std::vector KSynchronizationObject::GetWaitingThreadsForDebugging() const { +std::vector KSynchronizationObject::GetWaitingThreadsForDebugging(KernelCore& kernel) const { std::vector threads; // If debugging, dump the list of waiters. { - KScopedSchedulerLock lock(m_kernel); + KScopedSchedulerLock lock(kernel); for (auto* cur_node = m_thread_list_head; cur_node != nullptr; cur_node = cur_node->next) { threads.emplace_back(cur_node->thread); } diff --git a/src/core/hle/kernel/k_synchronization_object.h b/src/core/hle/kernel/k_synchronization_object.h index d55a2673df..4c54a4d2cb 100644 --- a/src/core/hle/kernel/k_synchronization_object.h +++ b/src/core/hle/kernel/k_synchronization_object.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -24,14 +27,13 @@ public: KThread* thread{}; }; - static Result Wait(KernelCore& kernel, s32* out_index, KSynchronizationObject** objects, - const s32 num_objects, s64 timeout); + static Result Wait(KernelCore& kernel, s32* out_index, KSynchronizationObject** objects, const s32 num_objects, s64 timeout); - void Finalize() override; + void Finalize(KernelCore& kernel) override; - virtual bool IsSignaled() const = 0; + virtual bool IsSignaled(KernelCore& kernel) const = 0; - std::vector GetWaitingThreadsForDebugging() const; + std::vector GetWaitingThreadsForDebugging(KernelCore& kernel) const; void LinkNode(ThreadListNode* node_) { // Link the node to the list. @@ -71,9 +73,9 @@ protected: virtual void OnFinalizeSynchronizationObject() {} - void NotifyAvailable(Result result); - void NotifyAvailable() { - return this->NotifyAvailable(ResultSuccess); + void NotifyAvailable(KernelCore& kernel, Result result); + void NotifyAvailable(KernelCore& kernel) { + return this->NotifyAvailable(kernel, ResultSuccess); } private: diff --git a/src/core/hle/kernel/k_system_resource.cpp b/src/core/hle/kernel/k_system_resource.cpp index b51941faf4..fb664eb57a 100644 --- a/src/core/hle/kernel/k_system_resource.cpp +++ b/src/core/hle/kernel/k_system_resource.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -7,8 +10,7 @@ namespace Kernel { -Result KSecureSystemResource::Initialize(size_t size, KResourceLimit* resource_limit, - KMemoryManager::Pool pool) { +Result KSecureSystemResource::Initialize(KernelCore& kernel, size_t size, KResourceLimit* resource_limit, KMemoryManager::Pool pool) { // Set members. m_resource_limit = resource_limit; m_resource_size = size; @@ -18,18 +20,17 @@ Result KSecureSystemResource::Initialize(size_t size, KResourceLimit* resource_l const size_t secure_size = this->CalculateRequiredSecureMemorySize(); // Reserve memory for our secure resource. - KScopedResourceReservation memory_reservation( - m_resource_limit, Svc::LimitableResource::PhysicalMemoryMax, secure_size); + KScopedResourceReservation memory_reservation(kernel, m_resource_limit, Svc::LimitableResource::PhysicalMemoryMax, secure_size); R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached); // Allocate secure memory. - R_TRY(KSystemControl::AllocateSecureMemory(m_kernel, std::addressof(m_resource_address), + R_TRY(KSystemControl::AllocateSecureMemory(kernel, std::addressof(m_resource_address), m_resource_size, static_cast(m_resource_pool))); ASSERT(m_resource_address != 0); // Ensure we clean up the secure memory, if we fail past this point. ON_RESULT_FAILURE { - KSystemControl::FreeSecureMemory(m_kernel, m_resource_address, m_resource_size, + KSystemControl::FreeSecureMemory(kernel, m_resource_address, m_resource_size, static_cast(m_resource_pool)); }; @@ -40,9 +41,9 @@ Result KSecureSystemResource::Initialize(size_t size, KResourceLimit* resource_l // Get resource pointer. KPhysicalAddress resource_paddr = - KPageTable::GetHeapPhysicalAddress(m_kernel, m_resource_address); + KPageTable::GetHeapPhysicalAddress(kernel, m_resource_address); auto* resource = - m_kernel.System().DeviceMemory().GetPointer(resource_paddr); + kernel.System().DeviceMemory().GetPointer(resource_paddr); // Initialize slab heaps. m_dynamic_page_manager.Initialize(m_resource_address + rc_size, m_resource_size - rc_size, @@ -66,7 +67,7 @@ Result KSecureSystemResource::Initialize(size_t size, KResourceLimit* resource_l memory_reservation.Commit(); // Open reference to our resource limit. - m_resource_limit->Open(); + m_resource_limit->Open(kernel); // Set ourselves as initialized. m_is_initialized = true; @@ -74,26 +75,25 @@ Result KSecureSystemResource::Initialize(size_t size, KResourceLimit* resource_l R_SUCCEED(); } -void KSecureSystemResource::Finalize() { +void KSecureSystemResource::Finalize(KernelCore& kernel) { // Check that we have no outstanding allocations. ASSERT(m_memory_block_slab_manager.GetUsed() == 0); ASSERT(m_block_info_manager.GetUsed() == 0); ASSERT(m_page_table_manager.GetUsed() == 0); // Free our secure memory. - KSystemControl::FreeSecureMemory(m_kernel, m_resource_address, m_resource_size, + KSystemControl::FreeSecureMemory(kernel, m_resource_address, m_resource_size, static_cast(m_resource_pool)); // Release the memory reservation. - m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax, + m_resource_limit->Release(kernel, Svc::LimitableResource::PhysicalMemoryMax, this->CalculateRequiredSecureMemorySize()); // Close reference to our resource limit. - m_resource_limit->Close(); + m_resource_limit->Close(kernel); } -size_t KSecureSystemResource::CalculateRequiredSecureMemorySize(size_t size, - KMemoryManager::Pool pool) { +size_t KSecureSystemResource::CalculateRequiredSecureMemorySize(size_t size, KMemoryManager::Pool pool) { return KSystemControl::CalculateRequiredSecureMemorySize(size, static_cast(pool)); } diff --git a/src/core/hle/kernel/k_system_resource.h b/src/core/hle/kernel/k_system_resource.h index 6ea4821858..c0362497f9 100644 --- a/src/core/hle/kernel/k_system_resource.h +++ b/src/core/hle/kernel/k_system_resource.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -29,7 +32,7 @@ protected: } public: - virtual void Destroy() override { + virtual void Destroy(KernelCore& kernel) override { UNREACHABLE_MSG("KSystemResource::Destroy() was called"); } @@ -93,13 +96,13 @@ public: this->SetSecureResource(); } - Result Initialize(size_t size, KResourceLimit* resource_limit, KMemoryManager::Pool pool); - void Finalize(); + Result Initialize(KernelCore& kernel, size_t size, KResourceLimit* resource_limit, KMemoryManager::Pool pool); + void Finalize(KernelCore& kernel); bool IsInitialized() const { return m_is_initialized; } - static void PostDestroy(uintptr_t arg) {} + static void PostDestroy(KernelCore& kernel, uintptr_t arg) {} size_t CalculateRequiredSecureMemorySize() const { return CalculateRequiredSecureMemorySize(m_resource_size, m_resource_pool); diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index 0af315d726..b499bd4073 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -95,12 +95,12 @@ namespace Kernel { explicit ThreadQueueImplForKThreadSetProperty(KernelCore& kernel, KThread::WaiterList* wl) : KThreadQueue(kernel), m_wait_list(wl) {} - void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { + void CancelWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result, bool cancel_timer_task) override { // Remove the thread from the wait list. m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread)); // Invoke the base cancel wait handler. - KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + KThreadQueue::CancelWait(kernel, waiting_thread, wait_result, cancel_timer_task); } private: @@ -113,7 +113,7 @@ namespace Kernel { : KAutoObjectWithSlabHeapAndContainer{kernel}, m_activity_pause_lock{kernel} {} KThread::~KThread() = default; - Result KThread::Initialize(KThreadFunction func, uintptr_t arg, KProcessAddress user_stack_top, + Result KThread::Initialize(KernelCore& kernel, KThreadFunction func, uintptr_t arg, KProcessAddress user_stack_top, s32 prio, s32 virt_core, KProcess* owner, ThreadType type) { // Assert parameters are valid. ASSERT((type == ThreadType::Main) || (type == ThreadType::Dummy) || @@ -225,12 +225,12 @@ namespace Kernel { if (owner != nullptr) { // Setup the TLS, if needed. if (type == ThreadType::User) { - R_TRY(owner->CreateThreadLocalRegion(std::addressof(m_tls_address))); + R_TRY(owner->CreateThreadLocalRegion(kernel, std::addressof(m_tls_address))); owner->GetMemory().ZeroBlock(m_tls_address, Svc::ThreadLocalRegionSize); } m_parent = owner; - m_parent->Open(); + m_parent->Open(kernel); } // Initialize thread context. @@ -247,28 +247,28 @@ namespace Kernel { this->SetInExceptionHandler(); // Set thread ID. - m_thread_id = m_kernel.CreateNewThreadID(); + m_thread_id = kernel.CreateNewThreadID(); // We initialized! m_initialized = true; // Register ourselves with our parent process. if (m_parent != nullptr) { - m_parent->RegisterThread(this); + m_parent->RegisterThread(kernel, this); if (m_parent->IsSuspended()) { - RequestSuspend(SuspendType::Process); + RequestSuspend(kernel, SuspendType::Process); } } R_SUCCEED(); } - Result KThread::InitializeThread(KThread* thread, KThreadFunction func, uintptr_t arg, + Result KThread::InitializeThread(KernelCore& kernel, KThread* thread, KThreadFunction func, uintptr_t arg, KProcessAddress user_stack_top, s32 prio, s32 core, KProcess* owner, ThreadType type, std::function&& init_func) { // Initialize the thread. - R_TRY(thread->Initialize(func, arg, user_stack_top, prio, core, owner, type)); + R_TRY(thread->Initialize(kernel, func, arg, user_stack_top, prio, core, owner, type)); // Initialize emulation parameters. thread->m_host_context = std::make_shared(std::move(init_func)); @@ -276,9 +276,9 @@ namespace Kernel { R_SUCCEED(); } - Result KThread::InitializeDummyThread(KThread* thread, KProcess* owner) { + Result KThread::InitializeDummyThread(Core::System& system, KThread* thread, KProcess* owner) { // Initialize the thread. - R_TRY(thread->Initialize({}, {}, {}, DummyThreadPriority, 3, owner, ThreadType::Dummy)); + R_TRY(thread->Initialize(system.Kernel(), {}, {}, {}, DummyThreadPriority, 3, owner, ThreadType::Dummy)); // Initialize emulation parameters. thread->m_stack_parameters.disable_count = 0; @@ -287,37 +287,27 @@ namespace Kernel { } Result KThread::InitializeMainThread(Core::System& system, KThread* thread, s32 virt_core) { - R_RETURN(InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, - ThreadType::Main, system.GetCpuManager().GetGuestActivateFunc())); + R_RETURN(InitializeThread(system.Kernel(), thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, ThreadType::Main, system.GetCpuManager().GetGuestActivateFunc(system.Kernel()))); } Result KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core) { - R_RETURN(InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, - ThreadType::Main, system.GetCpuManager().GetIdleThreadStartFunc())); + R_RETURN(InitializeThread(system.Kernel(), thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, ThreadType::Main, system.GetCpuManager().GetIdleThreadStartFunc(system.Kernel()))); } - Result KThread::InitializeHighPriorityThread(Core::System& system, KThread* thread, - KThreadFunction func, uintptr_t arg, s32 virt_core) { - R_RETURN(InitializeThread(thread, func, arg, {}, {}, virt_core, nullptr, - ThreadType::HighPriority, - system.GetCpuManager().GetShutdownThreadStartFunc())); + Result KThread::InitializeHighPriorityThread(Core::System& system, KThread* thread, KThreadFunction func, uintptr_t arg, s32 virt_core) { + R_RETURN(InitializeThread(system.Kernel(), thread, func, arg, {}, {}, virt_core, nullptr, ThreadType::HighPriority, system.GetCpuManager().GetShutdownThreadStartFunc(system.Kernel()))); } - Result KThread::InitializeUserThread(Core::System& system, KThread* thread, KThreadFunction func, - uintptr_t arg, KProcessAddress user_stack_top, s32 prio, - s32 virt_core, KProcess* owner) { + Result KThread::InitializeUserThread(Core::System& system, KThread* thread, KThreadFunction func, uintptr_t arg, KProcessAddress user_stack_top, s32 prio, s32 virt_core, KProcess* owner) { system.Kernel().GlobalSchedulerContext().AddThread(thread); - R_RETURN(InitializeThread(thread, func, arg, user_stack_top, prio, virt_core, owner, - ThreadType::User, system.GetCpuManager().GetGuestThreadFunc())); + R_RETURN(InitializeThread(system.Kernel(), thread, func, arg, user_stack_top, prio, virt_core, owner, ThreadType::User, system.GetCpuManager().GetGuestThreadFunc(system.Kernel()))); } - Result KThread::InitializeServiceThread(Core::System& system, KThread* thread, - std::function&& func, s32 prio, s32 virt_core, - KProcess* owner) { + Result KThread::InitializeServiceThread(Core::System& system, KThread* thread, std::function&& func, s32 prio, s32 virt_core, KProcess* owner) { system.Kernel().GlobalSchedulerContext().AddThread(thread); std::function func2{[&system, func_{std::move(func)}] { // Similar to UserModeThreadStarter. - system.Kernel().CurrentScheduler()->OnThreadStart(); + system.Kernel().CurrentScheduler()->OnThreadStart(system.Kernel()); // Run the guest function. func_(); @@ -326,35 +316,35 @@ namespace Kernel { Svc::ExitThread(system); }}; - R_RETURN(InitializeThread(thread, {}, {}, {}, prio, virt_core, owner, ThreadType::HighPriority, + R_RETURN(InitializeThread(system.Kernel(), thread, {}, {}, {}, prio, virt_core, owner, ThreadType::HighPriority, std::move(func2))); } - void KThread::PostDestroy(uintptr_t arg) { + void KThread::PostDestroy(KernelCore& kernel, uintptr_t arg) { KProcess* owner = reinterpret_cast(arg & ~1ULL); const bool resource_limit_release_hint = (arg & 1); const s64 hint_value = (resource_limit_release_hint ? 0 : 1); if (owner != nullptr) { - owner->GetResourceLimit()->Release(LimitableResource::ThreadCountMax, 1, hint_value); - owner->Close(); + owner->GetResourceLimit()->Release(kernel, LimitableResource::ThreadCountMax, 1, hint_value); + owner->Close(kernel); } } - void KThread::Finalize() { + void KThread::Finalize(KernelCore& kernel) { // If the thread has an owner process, unregister it. if (m_parent != nullptr) { - m_parent->UnregisterThread(this); + m_parent->UnregisterThread(kernel, this); } // If the thread has a local region, delete it. if (m_tls_address != 0) { - ASSERT(m_parent->DeleteThreadLocalRegion(m_tls_address).IsSuccess()); + ASSERT(m_parent->DeleteThreadLocalRegion(kernel, m_tls_address).IsSuccess()); } // Release any waiters. { ASSERT(m_waiting_lock_info == nullptr); - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; // Check that we have no kernel waiters. ASSERT(m_num_kernel_waiters == 0); @@ -378,14 +368,14 @@ namespace Kernel { } // Cancel the thread's wait. - waiter->CancelWait(ResultInvalidState, true); + waiter->CancelWait(kernel, ResultInvalidState, true); } // Remove the held lock from our list. it = m_held_lock_info_list.erase(it); // Free the lock info. - LockWithPriorityInheritanceInfo::Free(m_kernel, lock_info); + LockWithPriorityInheritanceInfo::Free(kernel, lock_info); } } @@ -393,35 +383,35 @@ namespace Kernel { m_host_context.reset(); // Perform inherited finalization. - KSynchronizationObject::Finalize(); + KSynchronizationObject::Finalize(kernel); } - bool KThread::IsSignaled() const { + bool KThread::IsSignaled(KernelCore& kernel) const { return m_signaled; } - void KThread::OnTimer() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + void KThread::OnTimer(KernelCore& kernel) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); // If we're waiting, cancel the wait. if (this->GetState() == ThreadState::Waiting) { - m_wait_queue->CancelWait(this, ResultTimedOut, false); + m_wait_queue->CancelWait(kernel, this, ResultTimedOut, false); } } - void KThread::StartTermination() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + void KThread::StartTermination(KernelCore& kernel) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); // Release user exception and unpin, if relevant. if (m_parent != nullptr) { - m_parent->ReleaseUserException(this); - if (m_parent->GetPinnedThread(GetCurrentCoreId(m_kernel)) == this) { - m_parent->UnpinCurrentThread(); + m_parent->ReleaseUserException(kernel, this); + if (m_parent->GetPinnedThread(GetCurrentCoreId(kernel)) == this) { + m_parent->UnpinCurrentThread(kernel); } } // Set state to terminated. - this->SetState(ThreadState::Terminated); + this->SetState(kernel, ThreadState::Terminated); // Clear the thread's status as running in parent. if (m_parent != nullptr) { @@ -429,41 +419,41 @@ namespace Kernel { } // Clear previous thread in KScheduler. - KScheduler::ClearPreviousThread(m_kernel, this); + KScheduler::ClearPreviousThread(kernel, this); // Register terminated dpc flag. this->RegisterDpc(DpcFlag::Terminated); } - void KThread::FinishTermination() { + void KThread::FinishTermination(KernelCore& kernel) { // Ensure that the thread is not executing on any core. if (m_parent != nullptr) { - for (std::size_t i = 0; i < static_cast(Core::Hardware::NUM_CPU_CORES); ++i) { + for (std::size_t i = 0; i < std::size_t(Core::Hardware::NUM_CPU_CORES); ++i) { KThread* core_thread{}; do { - core_thread = m_kernel.Scheduler(i).GetSchedulerCurrentThread(); + core_thread = kernel.Scheduler(i).GetSchedulerCurrentThread(); } while (core_thread == this); } } // Acquire the scheduler lock. - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; // Signal. m_signaled = true; - KSynchronizationObject::NotifyAvailable(); + KSynchronizationObject::NotifyAvailable(kernel); // Close the thread. - this->Close(); + this->Close(kernel); } - void KThread::DoWorkerTaskImpl() { + void KThread::DoWorkerTaskImpl(KernelCore& kernel) { // Finish the termination that was begun by Exit(). - this->FinishTermination(); + this->FinishTermination(kernel); } - void KThread::Pin(s32 current_core) { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + void KThread::Pin(KernelCore& kernel, s32 current_core) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); // Set ourselves as pinned. GetStackParameters().is_pinned = true; @@ -487,7 +477,7 @@ namespace Kernel { if (active_core != current_core || m_physical_affinity_mask.GetAffinityMask() != m_original_physical_affinity_mask.GetAffinityMask()) { - KScheduler::OnThreadAffinityMaskChanged(m_kernel, this, + KScheduler::OnThreadAffinityMaskChanged(kernel, this, m_original_physical_affinity_mask, active_core); } } @@ -499,15 +489,15 @@ namespace Kernel { static_cast(ThreadState::SuspendShift))); // Update our state. - this->UpdateState(); + this->UpdateState(kernel); } // TODO(bunnei): Update our SVC access permissions. ASSERT(m_parent != nullptr); } - void KThread::Unpin() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + void KThread::Unpin(KernelCore& kernel) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); // Set ourselves as unpinned. this->GetStackParameters().is_pinned = false; @@ -535,7 +525,7 @@ namespace Kernel { std::countl_zero(m_physical_affinity_mask.GetAffinityMask()))); } } - KScheduler::OnThreadAffinityMaskChanged(m_kernel, this, old_mask, active_core); + KScheduler::OnThreadAffinityMaskChanged(kernel, this, old_mask, active_core); } } @@ -546,20 +536,19 @@ namespace Kernel { static_cast(ThreadState::SuspendShift))); // Update our state. - this->UpdateState(); + this->UpdateState(kernel); } // TODO(bunnei): Update our SVC access permissions. ASSERT(m_parent != nullptr); // Resume any threads that began waiting on us while we were pinned. - for (auto it = m_pinned_waiter_list.begin(); it != m_pinned_waiter_list.end(); - it = m_pinned_waiter_list.erase(it)) { - it->EndWait(ResultSuccess); - } + for (auto it = m_pinned_waiter_list.begin(); it != m_pinned_waiter_list.end(); it = m_pinned_waiter_list.erase(it)) { + it->EndWait(kernel, ResultSuccess); + } } - u16 KThread::GetUserDisableCount() const { + u16 KThread::GetUserDisableCount(KernelCore& kernel) const { if (!this->IsUserThread()) { // We only emulate TLS for user threads return {}; @@ -569,7 +558,7 @@ namespace Kernel { return memory.Read16(m_tls_address + offsetof(ThreadLocalRegion, disable_count)); } - void KThread::SetInterruptFlag() { + void KThread::SetInterruptFlag(KernelCore& kernel) { if (!this->IsUserThread()) { // We only emulate TLS for user threads return; @@ -579,7 +568,7 @@ namespace Kernel { memory.Write16(m_tls_address + offsetof(ThreadLocalRegion, interrupt_flag), 1); } - void KThread::ClearInterruptFlag() { + void KThread::ClearInterruptFlag(KernelCore& kernel) { if (!this->IsUserThread()) { // We only emulate TLS for user threads return; @@ -589,7 +578,7 @@ namespace Kernel { memory.Write16(m_tls_address + offsetof(ThreadLocalRegion, interrupt_flag), 0); } - void KThread::UpdateTlsThreadCpuTime(s64 switch_tick) { + void KThread::UpdateTlsThreadCpuTime(KernelCore& kernel, s64 switch_tick) { if (!this->IsUserThread()) { return; } @@ -602,8 +591,8 @@ namespace Kernel { memory.Write64(m_tls_address + offsetof(ThreadLocalRegion, thread_cpu_time), static_cast(value)); } - Result KThread::GetCoreMask(s32* out_ideal_core, u64* out_affinity_mask) { - KScopedSchedulerLock sl{m_kernel}; + Result KThread::GetCoreMask(KernelCore& kernel, s32* out_ideal_core, u64* out_affinity_mask) { + KScopedSchedulerLock sl{kernel}; // Get the virtual mask. *out_ideal_core = m_virtual_ideal_core_id; @@ -612,8 +601,8 @@ namespace Kernel { R_SUCCEED(); } - Result KThread::GetPhysicalCoreMask(s32* out_ideal_core, u64* out_affinity_mask) { - KScopedSchedulerLock sl{m_kernel}; + Result KThread::GetPhysicalCoreMask(KernelCore& kernel, s32* out_ideal_core, u64* out_affinity_mask) { + KScopedSchedulerLock sl{kernel}; ASSERT(m_num_core_migration_disables >= 0); // Select between core mask and original core mask. @@ -628,7 +617,7 @@ namespace Kernel { R_SUCCEED(); } - Result KThread::SetCoreMask(s32 core_id, u64 v_affinity_mask) { + Result KThread::SetCoreMask(KernelCore& kernel, s32 core_id, u64 v_affinity_mask) { ASSERT(m_parent != nullptr); ASSERT(v_affinity_mask != 0); KScopedLightLock lk(m_activity_pause_lock); @@ -636,7 +625,7 @@ namespace Kernel { // Set the core mask. u64 p_affinity_mask = 0; { - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); ASSERT(m_num_core_migration_disables >= 0); // If we're updating, set our ideal virtual core. @@ -682,7 +671,7 @@ namespace Kernel { std::countl_zero(m_physical_affinity_mask.GetAffinityMask())); SetActiveCore(new_core); } - KScheduler::OnThreadAffinityMaskChanged(m_kernel, this, old_mask, active_core); + KScheduler::OnThreadAffinityMaskChanged(kernel, this, old_mask, active_core); } } else { // Otherwise, we edit the original affinity for restoration later. @@ -692,12 +681,12 @@ namespace Kernel { } // Update the pinned waiter list. - ThreadQueueImplForKThreadSetProperty wait_queue(m_kernel, std::addressof(m_pinned_waiter_list)); + ThreadQueueImplForKThreadSetProperty wait_queue(kernel, std::addressof(m_pinned_waiter_list)); { bool retry_update{}; do { // Lock the scheduler. - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); // Don't do any further management if our termination has been requested. R_SUCCEED_IF(this->IsTerminationRequested()); @@ -710,7 +699,7 @@ namespace Kernel { s32 thread_core; for (thread_core = 0; thread_core < static_cast(Core::Hardware::NUM_CPU_CORES); ++thread_core) { - if (m_kernel.Scheduler(thread_core).GetSchedulerCurrentThread() == this) { + if (kernel.Scheduler(thread_core).GetSchedulerCurrentThread() == this) { thread_is_current = true; break; } @@ -722,12 +711,12 @@ namespace Kernel { // If the thread is pinned, we want to wait until it's not pinned. if (this->GetStackParameters().is_pinned) { // Verify that the current thread isn't terminating. - R_UNLESS(!GetCurrentThread(m_kernel).IsTerminationRequested(), + R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(), ResultTerminationRequested); // Wait until the thread isn't pinned any more. - m_pinned_waiter_list.push_back(GetCurrentThread(m_kernel)); - GetCurrentThread(m_kernel).BeginWait(std::addressof(wait_queue)); + m_pinned_waiter_list.push_back(GetCurrentThread(kernel)); + GetCurrentThread(kernel).BeginWait(kernel, std::addressof(wait_queue)); } else { // If the thread isn't pinned, release the scheduler lock and retry until it's // not current. @@ -740,25 +729,25 @@ namespace Kernel { R_SUCCEED(); } - void KThread::SetBasePriority(s32 value) { + void KThread::SetBasePriority(KernelCore& kernel, s32 value) { ASSERT(Svc::HighestThreadPriority <= value && value <= Svc::LowestThreadPriority); - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; // Change our base priority. m_base_priority = value; // Perform a priority restoration. - RestorePriority(m_kernel, this); + RestorePriority(kernel, this); } - KThread* KThread::GetLockOwner() const { + KThread* KThread::GetLockOwner(KernelCore& kernel) const { return m_waiting_lock_info != nullptr ? m_waiting_lock_info->GetOwner() : nullptr; } - void KThread::IncreaseBasePriority(s32 priority) { + void KThread::IncreaseBasePriority(KernelCore& kernel, s32 priority) { ASSERT(Svc::HighestThreadPriority <= priority && priority <= Svc::LowestThreadPriority); - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); ASSERT(!this->GetStackParameters().is_pinned); // Set our base priority. @@ -766,47 +755,47 @@ namespace Kernel { m_base_priority = priority; // Perform a priority restoration. - RestorePriority(m_kernel, this); + RestorePriority(kernel, this); } } - void KThread::RequestSuspend(SuspendType type) { - KScopedSchedulerLock sl{m_kernel}; + void KThread::RequestSuspend(KernelCore& kernel, SuspendType type) { + KScopedSchedulerLock sl{kernel}; // Note the request in our flags. m_suspend_request_flags |= (1U << (static_cast(ThreadState::SuspendShift) + static_cast(type))); // Try to perform the suspend. - this->TrySuspend(); + this->TrySuspend(kernel); } - void KThread::Resume(SuspendType type) { - KScopedSchedulerLock sl{m_kernel}; + void KThread::Resume(KernelCore& kernel, SuspendType type) { + KScopedSchedulerLock sl{kernel}; // Clear the request in our flags. m_suspend_request_flags &= ~(1U << (static_cast(ThreadState::SuspendShift) + static_cast(type))); // Update our state. - this->UpdateState(); + this->UpdateState(kernel); } - void KThread::WaitCancel() { - KScopedSchedulerLock sl{m_kernel}; + void KThread::WaitCancel(KernelCore& kernel) { + KScopedSchedulerLock sl{kernel}; // Check if we're waiting and cancellable. if (this->GetState() == ThreadState::Waiting && m_cancellable) { m_wait_cancelled = false; - m_wait_queue->CancelWait(this, ResultCancelled, true); + m_wait_queue->CancelWait(kernel, this, ResultCancelled, true); } else { // Otherwise, note that we cancelled a wait. m_wait_cancelled = true; } } - void KThread::TrySuspend() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + void KThread::TrySuspend(KernelCore& kernel) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); ASSERT(this->IsSuspendRequested()); // Ensure that we have no waiters. @@ -816,11 +805,11 @@ namespace Kernel { ASSERT(this->GetNumKernelWaiters() == 0); // Perform the suspend. - this->UpdateState(); + this->UpdateState(kernel); } - void KThread::UpdateState() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + void KThread::UpdateState(KernelCore& kernel) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); // Set our suspend flags in state. const ThreadState old_state = m_thread_state.load(std::memory_order_relaxed); @@ -830,37 +819,37 @@ namespace Kernel { // Note the state change in scheduler. if (new_state != old_state) { - KScheduler::OnThreadStateChanged(m_kernel, this, old_state); + KScheduler::OnThreadStateChanged(kernel, this, old_state); } } - void KThread::Continue() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + void KThread::Continue(KernelCore& kernel) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); // Clear our suspend flags in state. const ThreadState old_state = m_thread_state.load(std::memory_order_relaxed); m_thread_state.store(old_state & ThreadState::Mask, std::memory_order_relaxed); // Note the state change in scheduler. - KScheduler::OnThreadStateChanged(m_kernel, this, old_state); + KScheduler::OnThreadStateChanged(kernel, this, old_state); } - void KThread::CloneFpuStatus() { + void KThread::CloneFpuStatus(KernelCore& kernel) { // We shouldn't reach here when starting kernel threads. ASSERT(this->GetOwnerProcess() != nullptr); - ASSERT(this->GetOwnerProcess() == GetCurrentProcessPointer(m_kernel)); + ASSERT(this->GetOwnerProcess() == GetCurrentProcessPointer(kernel)); - m_kernel.CurrentPhysicalCore().CloneFpuStatus(this); + kernel.CurrentPhysicalCore().CloneFpuStatus(this); } - Result KThread::SetActivity(Svc::ThreadActivity activity) { + Result KThread::SetActivity(KernelCore& kernel, Svc::ThreadActivity activity) { // Lock ourselves. KScopedLightLock lk(m_activity_pause_lock); // Set the activity. { // Lock the scheduler. - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); // Verify our state. const auto cur_state = this->GetState(); @@ -873,7 +862,7 @@ namespace Kernel { R_UNLESS(!this->IsSuspendRequested(SuspendType::Thread), ResultInvalidState); // Suspend. - this->RequestSuspend(SuspendType::Thread); + this->RequestSuspend(kernel, SuspendType::Thread); } else { ASSERT(activity == Svc::ThreadActivity::Runnable); @@ -881,19 +870,19 @@ namespace Kernel { R_UNLESS(this->IsSuspendRequested(SuspendType::Thread), ResultInvalidState); // Resume. - this->Resume(SuspendType::Thread); + this->Resume(kernel, SuspendType::Thread); } } // If the thread is now paused, update the pinned waiter list. if (activity == Svc::ThreadActivity::Paused) { - ThreadQueueImplForKThreadSetProperty wait_queue(m_kernel, + ThreadQueueImplForKThreadSetProperty wait_queue(kernel, std::addressof(m_pinned_waiter_list)); bool thread_is_current{}; do { // Lock the scheduler. - KScopedSchedulerLock sl(m_kernel); + KScopedSchedulerLock sl(kernel); // Don't do any further management if our termination has been requested. R_SUCCEED_IF(this->IsTerminationRequested()); @@ -904,17 +893,17 @@ namespace Kernel { // Check whether the thread is pinned. if (this->GetStackParameters().is_pinned) { // Verify that the current thread isn't terminating. - R_UNLESS(!GetCurrentThread(m_kernel).IsTerminationRequested(), + R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(), ResultTerminationRequested); // Wait until the thread isn't pinned any more. - m_pinned_waiter_list.push_back(GetCurrentThread(m_kernel)); - GetCurrentThread(m_kernel).BeginWait(std::addressof(wait_queue)); + m_pinned_waiter_list.push_back(GetCurrentThread(kernel)); + GetCurrentThread(kernel).BeginWait(kernel, std::addressof(wait_queue)); } else { // Check if the thread is currently running. // If it is, we'll need to retry. for (auto i = 0; i < static_cast(Core::Hardware::NUM_CPU_CORES); ++i) { - if (m_kernel.Scheduler(i).GetSchedulerCurrentThread() == this) { + if (kernel.Scheduler(i).GetSchedulerCurrentThread() == this) { thread_is_current = true; break; } @@ -926,14 +915,14 @@ namespace Kernel { R_SUCCEED(); } - Result KThread::GetThreadContext3(Svc::ThreadContext* out) { + Result KThread::GetThreadContext3(KernelCore& kernel, Svc::ThreadContext* out) { // Lock ourselves. KScopedLightLock lk{m_activity_pause_lock}; // Get the context. { // Lock the scheduler. - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; // Verify that we're suspended. R_UNLESS(this->IsSuspendRequested(SuspendType::Thread), ResultInvalidState); @@ -957,8 +946,8 @@ namespace Kernel { R_SUCCEED(); } - void KThread::AddHeldLock(LockWithPriorityInheritanceInfo* lock_info) { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + void KThread::AddHeldLock(KernelCore& kernel, LockWithPriorityInheritanceInfo* lock_info) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); // Set ourselves as the lock's owner. lock_info->SetOwner(this); @@ -967,23 +956,21 @@ namespace Kernel { m_held_lock_info_list.push_front(*lock_info); } - KThread::LockWithPriorityInheritanceInfo* KThread::FindHeldLock(KProcessAddress address_key, - bool is_kernel_address_key) { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + KThread::LockWithPriorityInheritanceInfo* KThread::FindHeldLock(KernelCore& kernel, KProcessAddress address_key, bool is_kernel_address_key) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); // Try to find an existing held lock. for (auto& held_lock : m_held_lock_info_list) { - if (held_lock.GetAddressKey() == address_key && - held_lock.GetIsKernelAddressKey() == is_kernel_address_key) { + if (held_lock.GetAddressKey() == address_key && held_lock.GetIsKernelAddressKey() == is_kernel_address_key) { return std::addressof(held_lock); - } + } } return nullptr; } - void KThread::AddWaiterImpl(KThread* thread) { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + void KThread::AddWaiterImpl(KernelCore& kernel, KThread* thread) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); ASSERT(thread->GetConditionVariableTree() == nullptr); // Get the thread's address key. @@ -993,31 +980,31 @@ namespace Kernel { // Keep track of how many kernel waiters we have. if (is_kernel_address_key) { ASSERT((m_num_kernel_waiters++) >= 0); - KScheduler::SetSchedulerUpdateNeeded(m_kernel); + KScheduler::SetSchedulerUpdateNeeded(kernel); } // Get the relevant lock info. - auto* lock_info = this->FindHeldLock(address_key, is_kernel_address_key); + auto* lock_info = this->FindHeldLock(kernel, address_key, is_kernel_address_key); if (lock_info == nullptr) { // Create a new lock for the address key. lock_info = - LockWithPriorityInheritanceInfo::Create(m_kernel, address_key, is_kernel_address_key); + LockWithPriorityInheritanceInfo::Create(kernel, address_key, is_kernel_address_key); // Add the new lock to our list. - this->AddHeldLock(lock_info); + this->AddHeldLock(kernel, lock_info); } // Add the thread as waiter to the lock info. lock_info->AddWaiter(thread); } - void KThread::RemoveWaiterImpl(KThread* thread) { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + void KThread::RemoveWaiterImpl(KernelCore& kernel, KThread* thread) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); // Keep track of how many kernel waiters we have. if (thread->GetIsKernelAddressKey()) { ASSERT((m_num_kernel_waiters--) > 0); - KScheduler::SetSchedulerUpdateNeeded(m_kernel); + KScheduler::SetSchedulerUpdateNeeded(kernel); } // Get the info for the lock the thread is waiting on. @@ -1027,7 +1014,7 @@ namespace Kernel { // Remove the waiter. if (lock_info->RemoveWaiter(thread)) { m_held_lock_info_list.erase(m_held_lock_info_list.iterator_to(*lock_info)); - LockWithPriorityInheritanceInfo::Free(m_kernel, lock_info); + LockWithPriorityInheritanceInfo::Free(kernel, lock_info); } } @@ -1048,12 +1035,12 @@ namespace Kernel { } // Get the owner of whatever lock this thread is waiting on. - KThread* const lock_owner = thread->GetLockOwner(); + KThread* const lock_owner = thread->GetLockOwner(kernel); // If the thread is waiting on some lock, remove it as a waiter to prevent violating red // black tree invariants. if (lock_owner != nullptr) { - lock_owner->RemoveWaiterImpl(thread); + lock_owner->RemoveWaiterImpl(kernel, thread); } // Ensure we don't violate condition variable red black tree invariants. @@ -1072,7 +1059,7 @@ namespace Kernel { // If we removed the thread from some lock's waiting list, add it back. if (lock_owner != nullptr) { - lock_owner->AddWaiterImpl(thread); + lock_owner->AddWaiterImpl(kernel, thread); } // Update the scheduler. @@ -1083,32 +1070,30 @@ namespace Kernel { } } - void KThread::AddWaiter(KThread* thread) { - this->AddWaiterImpl(thread); + void KThread::AddWaiter(KernelCore& kernel, KThread* thread) { + this->AddWaiterImpl(kernel, thread); // If the thread has a higher priority than us, we should inherit. if (thread->GetPriority() < this->GetPriority()) { - RestorePriority(m_kernel, this); + RestorePriority(kernel, this); } } - void KThread::RemoveWaiter(KThread* thread) { - this->RemoveWaiterImpl(thread); + void KThread::RemoveWaiter(KernelCore& kernel, KThread* thread) { + this->RemoveWaiterImpl(kernel, thread); // If our priority is the same as the thread's (and we've inherited), we may need to restore to // lower priority. - if (this->GetPriority() == thread->GetPriority() && - this->GetPriority() < this->GetBasePriority()) { - RestorePriority(m_kernel, this); - } + if (this->GetPriority() == thread->GetPriority() && this->GetPriority() < this->GetBasePriority()) { + RestorePriority(kernel, this); + } } - KThread* KThread::RemoveWaiterByKey(bool* out_has_waiters, KProcessAddress key, - bool is_kernel_address_key_) { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + KThread* KThread::RemoveWaiterByKey(KernelCore& kernel, bool* out_has_waiters, KProcessAddress key, bool is_kernel_address_key_) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); // Get the relevant lock info. - auto* lock_info = this->FindHeldLock(key, is_kernel_address_key_); + auto* lock_info = this->FindHeldLock(kernel, key, is_kernel_address_key_); if (lock_info == nullptr) { *out_has_waiters = false; return nullptr; @@ -1121,7 +1106,7 @@ namespace Kernel { if (lock_info->GetIsKernelAddressKey()) { m_num_kernel_waiters -= lock_info->GetWaiterCount(); ASSERT(m_num_kernel_waiters >= 0); - KScheduler::SetSchedulerUpdateNeeded(m_kernel); + KScheduler::SetSchedulerUpdateNeeded(kernel); } ASSERT(lock_info->GetWaiterCount() > 0); @@ -1133,13 +1118,13 @@ namespace Kernel { *out_has_waiters = false; // Free the lock info, since it has no waiters. - LockWithPriorityInheritanceInfo::Free(m_kernel, lock_info); + LockWithPriorityInheritanceInfo::Free(kernel, lock_info); } else { // There are additional waiters on the lock. *out_has_waiters = true; // Add the lock to the new owner's held list. - next_lock_owner->AddHeldLock(lock_info); + next_lock_owner->AddHeldLock(kernel, lock_info); // Keep track of any kernel waiters for the new owner. if (lock_info->GetIsKernelAddressKey()) { @@ -1155,7 +1140,7 @@ namespace Kernel { // to lower priority. if (this->GetPriority() == next_lock_owner->GetPriority() && this->GetPriority() < this->GetBasePriority()) { - RestorePriority(m_kernel, this); + RestorePriority(kernel, this); // NOTE: No need to restore priority on the next lock owner, because it was already the // highest priority waiter on the lock. } @@ -1164,91 +1149,91 @@ namespace Kernel { return next_lock_owner; } - Result KThread::Run() { + Result KThread::Run(KernelCore& kernel) { while (true) { - KScopedSchedulerLock lk{m_kernel}; + KScopedSchedulerLock lk{kernel}; // If either this thread or the current thread are requesting termination, note it. R_UNLESS(!this->IsTerminationRequested(), ResultTerminationRequested); - R_UNLESS(!GetCurrentThread(m_kernel).IsTerminationRequested(), ResultTerminationRequested); + R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(), ResultTerminationRequested); // Ensure our thread state is correct. R_UNLESS(this->GetState() == ThreadState::Initialized, ResultInvalidState); // If the current thread has been asked to suspend, suspend it and retry. - if (GetCurrentThread(m_kernel).IsSuspended()) { - GetCurrentThread(m_kernel).UpdateState(); + if (GetCurrentThread(kernel).IsSuspended()) { + GetCurrentThread(kernel).UpdateState(kernel); continue; } // If we're not a kernel thread and we've been asked to suspend, suspend ourselves. if (KProcess* owner = this->GetOwnerProcess(); owner != nullptr) { if (this->IsUserThread() && this->IsSuspended()) { - this->UpdateState(); + this->UpdateState(kernel); } - owner->IncrementRunningThreadCount(); + owner->IncrementRunningThreadCount(kernel); } // Open a reference, now that we're running. - this->Open(); + this->Open(kernel); // Set our state and finish. - this->SetState(ThreadState::Runnable); + this->SetState(kernel, ThreadState::Runnable); R_SUCCEED(); } } - void KThread::Exit() { - ASSERT(this == GetCurrentThreadPointer(m_kernel)); + void KThread::Exit(KernelCore& kernel) { + ASSERT(this == GetCurrentThreadPointer(kernel)); // Release the thread resource hint, running thread count from parent. if (m_parent != nullptr) { - m_parent->GetResourceLimit()->Release(Kernel::LimitableResource::ThreadCountMax, 0, 1); + m_parent->GetResourceLimit()->Release(kernel, Kernel::LimitableResource::ThreadCountMax, 0, 1); m_resource_limit_release_hint = true; - m_parent->DecrementRunningThreadCount(); + m_parent->DecrementRunningThreadCount(kernel); } // Perform termination. { - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; // Disallow all suspension. m_suspend_allowed_flags = 0; - this->UpdateState(); + this->UpdateState(kernel); // Disallow all suspension. m_suspend_allowed_flags = 0; // Start termination. - this->StartTermination(); + this->StartTermination(kernel); // Register the thread as a work task. - KWorkerTaskManager::AddTask(m_kernel, KWorkerTaskManager::WorkerType::Exit, this); + KWorkerTaskManager::AddTask(kernel, KWorkerTaskManager::WorkerType::Exit, this); } UNREACHABLE_MSG("KThread::Exit() would return"); } - Result KThread::Terminate() { - ASSERT(this != GetCurrentThreadPointer(m_kernel)); + Result KThread::Terminate(KernelCore& kernel) { + ASSERT(this != GetCurrentThreadPointer(kernel)); // Request the thread terminate if it hasn't already. - if (const auto new_state = this->RequestTerminate(); new_state != ThreadState::Terminated) { + if (const auto new_state = this->RequestTerminate(kernel); new_state != ThreadState::Terminated) { // If the thread isn't terminated, wait for it to terminate. s32 index; KSynchronizationObject* objects[] = {this}; - R_TRY(KSynchronizationObject::Wait(m_kernel, std::addressof(index), objects, 1, + R_TRY(KSynchronizationObject::Wait(kernel, std::addressof(index), objects, 1, Svc::WaitInfinite)); } R_SUCCEED(); } - ThreadState KThread::RequestTerminate() { - ASSERT(this != GetCurrentThreadPointer(m_kernel)); + ThreadState KThread::RequestTerminate(KernelCore& kernel) { + ASSERT(this != GetCurrentThreadPointer(kernel)); - KScopedSchedulerLock sl{m_kernel}; + KScopedSchedulerLock sl{kernel}; // Determine if this is the first termination request. const bool first_request = [&]() -> bool { @@ -1270,46 +1255,46 @@ namespace Kernel { // If the thread is pinned, unpin it. if (this->GetStackParameters().is_pinned) { - this->GetOwnerProcess()->UnpinThread(this); + this->GetOwnerProcess()->UnpinThread(kernel, this); } // If the thread is suspended, continue it. if (this->IsSuspended()) { m_suspend_allowed_flags = 0; - this->UpdateState(); + this->UpdateState(kernel); } // Change the thread's priority to be higher than any system thread's. - this->IncreaseBasePriority(TerminatingThreadPriority); + this->IncreaseBasePriority(kernel, TerminatingThreadPriority); // If the thread is runnable, send a termination interrupt to cores it may be running on. if (this->GetState() == ThreadState::Runnable) { // NOTE: We do not mask the "current core", because this code may not actually be // executing from the thread representing the "current core". if (const u64 core_mask = m_physical_affinity_mask.GetAffinityMask(); core_mask != 0) { - Kernel::KInterruptManager::SendInterProcessorInterrupt(m_kernel, core_mask); + Kernel::KInterruptManager::SendInterProcessorInterrupt(kernel, core_mask); } } // Wake up the thread. if (this->GetState() == ThreadState::Waiting) { - m_wait_queue->CancelWait(this, ResultTerminationRequested, true); + m_wait_queue->CancelWait(kernel, this, ResultTerminationRequested, true); } } return this->GetState(); } - Result KThread::Sleep(s64 timeout) { - ASSERT(!KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); - ASSERT(this == GetCurrentThreadPointer(m_kernel)); + Result KThread::Sleep(KernelCore& kernel, s64 timeout) { + ASSERT(!KScheduler::IsSchedulerLockedByCurrentThread(kernel)); + ASSERT(this == GetCurrentThreadPointer(kernel)); ASSERT(timeout > 0); - ThreadQueueImplForKThreadSleep wait_queue(m_kernel); + ThreadQueueImplForKThreadSleep wait_queue(kernel); KHardwareTimer* timer{}; { // Setup the scheduling lock and sleep. - KScopedSchedulerLockAndSleep slp(m_kernel, std::addressof(timer), this, timeout); + KScopedSchedulerLockAndSleep slp(kernel, std::addressof(timer), this, timeout); // Check if the thread should terminate. if (this->IsTerminationRequested()) { @@ -1319,15 +1304,15 @@ namespace Kernel { // Wait for the sleep to end. wait_queue.SetHardwareTimer(timer); - this->BeginWait(std::addressof(wait_queue)); + this->BeginWait(kernel, std::addressof(wait_queue)); this->SetWaitReasonForDebugging(ThreadWaitReasonForDebugging::Sleep); } R_SUCCEED(); } - void KThread::RequestDummyThreadWait() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + void KThread::RequestDummyThreadWait(KernelCore& kernel) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); ASSERT(this->IsDummyThread()); // We will block when the scheduler lock is released. @@ -1335,8 +1320,8 @@ namespace Kernel { m_dummy_thread_runnable = false; } - void KThread::DummyThreadBeginWait() { - if (!this->IsDummyThread() || m_kernel.IsPhantomModeForSingleCore()) { + void KThread::DummyThreadBeginWait(KernelCore& kernel) { + if (!this->IsDummyThread() || kernel.IsPhantomModeForSingleCore()) { // Occurs in single core mode. return; } @@ -1346,8 +1331,8 @@ namespace Kernel { m_dummy_thread_cv.wait(lock, [this] { return m_dummy_thread_runnable; }); } - void KThread::DummyThreadEndWait() { - ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(m_kernel)); + void KThread::DummyThreadEndWait(KernelCore& kernel) { + ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel)); ASSERT(this->IsDummyThread()); // Wake up the waiting thread. @@ -1358,28 +1343,26 @@ namespace Kernel { m_dummy_thread_cv.notify_one(); } - void KThread::BeginWait(KThreadQueue* queue) { + void KThread::BeginWait(KernelCore& kernel, KThreadQueue* queue) { // Set our state as waiting. - this->SetState(ThreadState::Waiting); + this->SetState(kernel, ThreadState::Waiting); // Set our wait queue. m_wait_queue = queue; } - void KThread::NotifyAvailable(KSynchronizationObject* signaled_object, Result wait_result) { + void KThread::NotifyAvailable(KernelCore& kernel, KSynchronizationObject* signaled_object, Result wait_result) { // Lock the scheduler. - KScopedSchedulerLock sl(m_kernel); - + KScopedSchedulerLock sl(kernel); // If we're waiting, notify our queue that we're available. if (this->GetState() == ThreadState::Waiting) { - m_wait_queue->NotifyAvailable(this, signaled_object, wait_result); + m_wait_queue->NotifyAvailable(kernel, this, signaled_object, wait_result); } } - void KThread::EndWait(Result wait_result) { + void KThread::EndWait(KernelCore& kernel, Result wait_result) { // Lock the scheduler. - KScopedSchedulerLock sl(m_kernel); - + KScopedSchedulerLock sl(kernel); // If we're waiting, notify our queue that we're available. if (this->GetState() == ThreadState::Waiting) { if (m_wait_queue == nullptr) { @@ -1388,32 +1371,29 @@ namespace Kernel { return; } - m_wait_queue->EndWait(this, wait_result); + m_wait_queue->EndWait(kernel, this, wait_result); } } - void KThread::CancelWait(Result wait_result, bool cancel_timer_task) { + void KThread::CancelWait(KernelCore& kernel, Result wait_result, bool cancel_timer_task) { // Lock the scheduler. - KScopedSchedulerLock sl(m_kernel); - + KScopedSchedulerLock sl(kernel); // If we're waiting, notify our queue that we're available. if (this->GetState() == ThreadState::Waiting) { - m_wait_queue->CancelWait(this, wait_result, cancel_timer_task); + m_wait_queue->CancelWait(kernel, this, wait_result, cancel_timer_task); } } - void KThread::SetState(ThreadState state) { - KScopedSchedulerLock sl{m_kernel}; - + void KThread::SetState(KernelCore& kernel, ThreadState state) { + KScopedSchedulerLock sl{kernel}; // Clear debugging state this->SetWaitReasonForDebugging({}); - const ThreadState old_state = m_thread_state.load(std::memory_order_relaxed); m_thread_state.store( static_cast((old_state & ~ThreadState::Mask) | (state & ThreadState::Mask)), std::memory_order_relaxed); if (m_thread_state.load(std::memory_order_relaxed) != old_state) { - KScheduler::OnThreadStateChanged(m_kernel, this, old_state); + KScheduler::OnThreadStateChanged(kernel, this, old_state); } } @@ -1459,12 +1439,12 @@ namespace Kernel { auto* scheduler = m_kernel.CurrentScheduler(); if (scheduler && !m_kernel.IsPhantomModeForSingleCore()) { - scheduler->RescheduleCurrentCore(); + scheduler->RescheduleCurrentCore(m_kernel); } else { KScheduler::RescheduleCurrentHLEThread(m_kernel); } } else { - GetCurrentThread(m_kernel).EnableDispatch(); + GetCurrentThread(m_kernel).EnableDispatch(m_kernel); } } } \ No newline at end of file diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index e79704ce4b..0ee5f72c8e 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -19,7 +19,6 @@ #include "common/intrusive_red_black_tree.h" #include "common/scratch_buffer.h" -#include "common/spin_lock.h" #include "core/arm/arm_interface.h" #include "core/hle/kernel/k_affinity_mask.h" #include "core/hle/kernel/k_light_lock.h" @@ -181,21 +180,17 @@ public: return m_thread_id; } - void ContinueIfHasKernelWaiters() { + void ContinueIfHasKernelWaiters(KernelCore& kernel) { if (GetNumKernelWaiters() > 0) { - Continue(); + Continue(kernel); } } - void SetBasePriority(s32 value); - - Result Run(); - - void Exit(); - - Result Terminate(); - - ThreadState RequestTerminate(); + void SetBasePriority(KernelCore& kernel, s32 value); + Result Run(KernelCore& kernel); + void Exit(KernelCore& kernel); + Result Terminate(KernelCore& kernel); + ThreadState RequestTerminate(KernelCore& kernel); u32 GetSuspendFlags() const { return m_suspend_allowed_flags & m_suspend_request_flags; @@ -215,15 +210,11 @@ public: return m_suspend_request_flags != 0; } - void RequestSuspend(SuspendType type); - - void Resume(SuspendType type); - - void TrySuspend(); - - void UpdateState(); - - void Continue(); + void RequestSuspend(KernelCore& kernel, SuspendType type); + void Resume(KernelCore& kernel, SuspendType type); + void TrySuspend(KernelCore& kernel); + void UpdateState(KernelCore& kernel); + void Continue(KernelCore& kernel); constexpr void SetSyncedIndex(s32 index) { m_synced_index = index; @@ -262,7 +253,7 @@ public: m_thread_context.tpidr = value; } - void CloneFpuStatus(); + void CloneFpuStatus(KernelCore& kernel); Svc::ThreadContext& GetContext() { return m_thread_context; @@ -282,7 +273,7 @@ public: return m_thread_state.load(std::memory_order_relaxed); } - void SetState(ThreadState state); + void SetState(KernelCore& kernel, ThreadState state); StepState GetStepState() const { return m_step_state; @@ -342,27 +333,27 @@ public: Svc::ArgumentHandleCountMax}; } - u16 GetUserDisableCount() const; - void SetInterruptFlag(); - void ClearInterruptFlag(); + u16 GetUserDisableCount(KernelCore& kernel) const; + void SetInterruptFlag(KernelCore& kernel); + void ClearInterruptFlag(KernelCore& kernel); - void UpdateTlsThreadCpuTime(s64 switch_tick); + void UpdateTlsThreadCpuTime(KernelCore& kernel, s64 switch_tick); - KThread* GetLockOwner() const; + KThread* GetLockOwner(KernelCore& kernel) const; const KAffinityMask& GetAffinityMask() const { return m_physical_affinity_mask; } - Result GetCoreMask(s32* out_ideal_core, u64* out_affinity_mask); + Result GetCoreMask(KernelCore& kernel, s32* out_ideal_core, u64* out_affinity_mask); - Result GetPhysicalCoreMask(s32* out_ideal_core, u64* out_affinity_mask); + Result GetPhysicalCoreMask(KernelCore& kernel, s32* out_ideal_core, u64* out_affinity_mask); - Result SetCoreMask(s32 cpu_core_id, u64 v_affinity_mask); + Result SetCoreMask(KernelCore& kernel, s32 cpu_core_id, u64 v_affinity_mask); - Result SetActivity(Svc::ThreadActivity activity); + Result SetActivity(KernelCore& kernel, Svc::ThreadActivity activity); - Result Sleep(s64 timeout); + Result Sleep(KernelCore& kernel, s64 timeout); s64 GetYieldScheduleCount() const { return m_schedule_count; @@ -372,7 +363,7 @@ public: m_schedule_count = count; } - void WaitCancel(); + void WaitCancel(KernelCore& kernel); bool IsWaitCancelled() const { return m_wait_cancelled; @@ -417,17 +408,17 @@ public: return reinterpret_cast(m_parent) | (m_resource_limit_release_hint ? 1 : 0); } - void Finalize() override; + void Finalize(KernelCore& kernel) override; - bool IsSignaled() const override; + bool IsSignaled(KernelCore& kernel) const override; - void OnTimer(); + void OnTimer(KernelCore& kernel); - void DoWorkerTaskImpl(); + void DoWorkerTaskImpl(KernelCore& kernel); - static void PostDestroy(uintptr_t arg); + static void PostDestroy(KernelCore& kernel, uintptr_t arg); - static Result InitializeDummyThread(KThread* thread, KProcess* owner); + static Result InitializeDummyThread(Core::System& system, KThread* thread, KProcess* owner); static Result InitializeMainThread(Core::System& system, KThread* thread, s32 virt_core); @@ -514,19 +505,18 @@ public: return this->GetStackParameters().disable_count; } - void DisableDispatch() { - ASSERT(GetCurrentThread(m_kernel).GetDisableDispatchCount() >= 0); + void DisableDispatch(KernelCore& kernel) { + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() >= 0); this->GetStackParameters().disable_count++; } - void EnableDispatch() { - ASSERT(GetCurrentThread(m_kernel).GetDisableDispatchCount() > 0); + void EnableDispatch(KernelCore& kernel) { + ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() > 0); this->GetStackParameters().disable_count--; } - void Pin(s32 current_core); - - void Unpin(); + void Pin(KernelCore& kernel, s32 current_core); + void Unpin(KernelCore& kernel); void SetInExceptionHandler() { this->GetStackParameters().is_in_exception_handler = true; @@ -592,18 +582,18 @@ public: return this->GetThreadType() == ThreadType::Dummy; } - void AddWaiter(KThread* thread); + void AddWaiter(KernelCore& kernel, KThread* thread); - void RemoveWaiter(KThread* thread); + void RemoveWaiter(KernelCore& kernel, KThread* thread); - Result GetThreadContext3(Svc::ThreadContext* out); + Result GetThreadContext3(KernelCore& kernel, Svc::ThreadContext* out); - KThread* RemoveUserWaiterByKey(bool* out_has_waiters, KProcessAddress key) { - return this->RemoveWaiterByKey(out_has_waiters, key, false); + KThread* RemoveUserWaiterByKey(KernelCore& kernel, bool* out_has_waiters, KProcessAddress key) { + return this->RemoveWaiterByKey(kernel, out_has_waiters, key, false); } - KThread* RemoveKernelWaiterByKey(bool* out_has_waiters, KProcessAddress key) { - return this->RemoveWaiterByKey(out_has_waiters, key, true); + KThread* RemoveKernelWaiterByKey(KernelCore& kernel, bool* out_has_waiters, KProcessAddress key) { + return this->RemoveWaiterByKey(kernel, out_has_waiters, key, true); } KProcessAddress GetAddressKey() const { @@ -641,10 +631,10 @@ public: m_wait_queue = nullptr; } - void BeginWait(KThreadQueue* queue); - void NotifyAvailable(KSynchronizationObject* signaled_object, Result wait_result); - void EndWait(Result wait_result); - void CancelWait(Result wait_result, bool cancel_timer_task); + void BeginWait(KernelCore& kernel, KThreadQueue* queue); + void NotifyAvailable(KernelCore& kernel, KSynchronizationObject* signaled_object, Result wait_result); + void EndWait(KernelCore& kernel, Result wait_result); + void CancelWait(KernelCore& kernel, Result wait_result, bool cancel_timer_task); s32 GetNumKernelWaiters() const { return m_num_kernel_waiters; @@ -662,9 +652,9 @@ public: // therefore will not block on guest kernel synchronization primitives. These methods handle // blocking as needed. - void RequestDummyThreadWait(); - void DummyThreadBeginWait(); - void DummyThreadEndWait(); + void RequestDummyThreadWait(KernelCore& kernel); + void DummyThreadBeginWait(KernelCore& kernel); + void DummyThreadEndWait(KernelCore& kernel); uintptr_t GetArgument() const { return m_argument; @@ -690,8 +680,7 @@ public: } private: - KThread* RemoveWaiterByKey(bool* out_has_waiters, KProcessAddress key, - bool is_kernel_address_key); + KThread* RemoveWaiterByKey(KernelCore& kernel, bool* out_has_waiters, KProcessAddress key, bool is_kernel_address_key); static constexpr size_t PriorityInheritanceCountMax = 10; union SyncObjectBuffer { @@ -735,19 +724,19 @@ private: } }; - void AddWaiterImpl(KThread* thread); - void RemoveWaiterImpl(KThread* thread); + void AddWaiterImpl(KernelCore& kernel, KThread* thread); + void RemoveWaiterImpl(KernelCore& kernel, KThread* thread); static void RestorePriority(KernelCore& kernel, KThread* thread); - void StartTermination(); - void FinishTermination(); + void StartTermination(KernelCore& kernel); + void FinishTermination(KernelCore& kernel); - void IncreaseBasePriority(s32 priority); + void IncreaseBasePriority(KernelCore& kernel, s32 priority); - Result Initialize(KThreadFunction func, uintptr_t arg, KProcessAddress user_stack_top, s32 prio, + Result Initialize(KernelCore& kernel, KThreadFunction func, uintptr_t arg, KProcessAddress user_stack_top, s32 prio, s32 virt_core, KProcess* owner, ThreadType type); - static Result InitializeThread(KThread* thread, KThreadFunction func, uintptr_t arg, + static Result InitializeThread(KernelCore& kernel, KThread* thread, KThreadFunction func, uintptr_t arg, KProcessAddress user_stack_top, s32 prio, s32 core, KProcess* owner, ThreadType type, std::function&& init_func); @@ -878,9 +867,8 @@ public: return m_waiting_lock_info; } - void AddHeldLock(LockWithPriorityInheritanceInfo* lock_info); - LockWithPriorityInheritanceInfo* FindHeldLock(KProcessAddress address_key, - bool is_kernel_address_key); + void AddHeldLock(KernelCore& kernel, LockWithPriorityInheritanceInfo* lock_info); + LockWithPriorityInheritanceInfo* FindHeldLock(KernelCore& kernel, KProcessAddress address_key, bool is_kernel_address_key); private: using LockWithPriorityInheritanceInfoList = @@ -931,7 +919,7 @@ private: bool m_resource_limit_release_hint{}; bool m_is_kernel_address_key{}; StackParameters m_stack_parameters{}; - Common::SpinLock m_context_guard{}; + std::mutex m_context_guard{}; // For emulation std::shared_ptr m_host_context{}; @@ -996,7 +984,7 @@ public: if (m_kernel.IsShuttingDown()) { return; } - GetCurrentThread(kernel).DisableDispatch(); + GetCurrentThread(kernel).DisableDispatch(kernel); } ~KScopedDisableDispatch(); @@ -1005,8 +993,8 @@ private: KernelCore& m_kernel; }; -inline void KTimerTask::OnTimer() { - static_cast(this)->OnTimer(); +inline void KTimerTask::OnTimer(KernelCore& kernel) { + static_cast(this)->OnTimer(kernel); } } // namespace Kernel diff --git a/src/core/hle/kernel/k_thread_queue.cpp b/src/core/hle/kernel/k_thread_queue.cpp index 61488f4ce6..7c4bfe5a49 100644 --- a/src/core/hle/kernel/k_thread_queue.cpp +++ b/src/core/hle/kernel/k_thread_queue.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -7,17 +10,16 @@ namespace Kernel { -void KThreadQueue::NotifyAvailable(KThread* waiting_thread, KSynchronizationObject* signaled_object, - Result wait_result) { +void KThreadQueue::NotifyAvailable(KernelCore& kernel, KThread* waiting_thread, KSynchronizationObject* signaled_object, Result wait_result) { UNREACHABLE(); } -void KThreadQueue::EndWait(KThread* waiting_thread, Result wait_result) { +void KThreadQueue::EndWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result) { // Set the thread's wait result. waiting_thread->SetWaitResult(wait_result); // Set the thread as runnable. - waiting_thread->SetState(ThreadState::Runnable); + waiting_thread->SetState(kernel, ThreadState::Runnable); // Clear the thread's wait queue. waiting_thread->ClearWaitQueue(); @@ -28,12 +30,12 @@ void KThreadQueue::EndWait(KThread* waiting_thread, Result wait_result) { } } -void KThreadQueue::CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) { +void KThreadQueue::CancelWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result, bool cancel_timer_task) { // Set the thread's wait result. waiting_thread->SetWaitResult(wait_result); // Set the thread as runnable. - waiting_thread->SetState(ThreadState::Runnable); + waiting_thread->SetState(kernel, ThreadState::Runnable); // Clear the thread's wait queue. waiting_thread->ClearWaitQueue(); @@ -44,7 +46,7 @@ void KThreadQueue::CancelWait(KThread* waiting_thread, Result wait_result, bool } } -void KThreadQueueWithoutEndWait::EndWait(KThread* waiting_thread, Result wait_result) { +void KThreadQueueWithoutEndWait::EndWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result) { UNREACHABLE(); } diff --git a/src/core/hle/kernel/k_thread_queue.h b/src/core/hle/kernel/k_thread_queue.h index 117af0919a..953bb78ac6 100644 --- a/src/core/hle/kernel/k_thread_queue.h +++ b/src/core/hle/kernel/k_thread_queue.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -12,28 +15,25 @@ class KHardwareTimer; class KThreadQueue { public: - explicit KThreadQueue(KernelCore& kernel) : m_kernel{kernel}, m_hardware_timer{} {} + explicit KThreadQueue(KernelCore& kernel) : m_hardware_timer{} {} virtual ~KThreadQueue() = default; void SetHardwareTimer(KHardwareTimer* timer) { m_hardware_timer = timer; } - virtual void NotifyAvailable(KThread* waiting_thread, KSynchronizationObject* signaled_object, - Result wait_result); - virtual void EndWait(KThread* waiting_thread, Result wait_result); - virtual void CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task); + virtual void NotifyAvailable(KernelCore& kernel, KThread* waiting_thread, KSynchronizationObject* signaled_object, Result wait_result); + virtual void EndWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result); + virtual void CancelWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result, bool cancel_timer_task); private: - KernelCore& m_kernel; KHardwareTimer* m_hardware_timer{}; }; class KThreadQueueWithoutEndWait : public KThreadQueue { public: explicit KThreadQueueWithoutEndWait(KernelCore& kernel) : KThreadQueue(kernel) {} - - void EndWait(KThread* waiting_thread, Result wait_result) override final; + void EndWait(KernelCore& kernel, KThread* waiting_thread, Result wait_result) override final; }; } // namespace Kernel diff --git a/src/core/hle/kernel/k_timer_task.h b/src/core/hle/kernel/k_timer_task.h index 66f0a5a908..7625c1fee1 100644 --- a/src/core/hle/kernel/k_timer_task.h +++ b/src/core/hle/kernel/k_timer_task.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -7,6 +10,7 @@ namespace Kernel { +class KernelCore; class KTimerTask : public Common::IntrusiveRedBlackTreeBaseNode { public: static constexpr int Compare(const KTimerTask& lhs, const KTimerTask& rhs) { @@ -30,7 +34,7 @@ public: // NOTE: This is virtual in Nintendo's kernel. Prior to 13.0.0, KWaitObject was also a // TimerTask; this is no longer the case. Since this is now KThread exclusive, we have // devirtualized (see inline declaration for this inside k_thread.h). - void OnTimer(); + void OnTimer(KernelCore& kernel); private: // Absolute time in nanoseconds diff --git a/src/core/hle/kernel/k_trace.h b/src/core/hle/kernel/k_trace.h index d61af48303..84dd4134e1 100644 --- a/src/core/hle/kernel/k_trace.h +++ b/src/core/hle/kernel/k_trace.h @@ -1,8 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include "common/literals.h" + namespace Kernel { using namespace Common::Literals; diff --git a/src/core/hle/kernel/k_transfer_memory.cpp b/src/core/hle/kernel/k_transfer_memory.cpp index 09295e8ad8..a4ef530b5b 100644 --- a/src/core/hle/kernel/k_transfer_memory.cpp +++ b/src/core/hle/kernel/k_transfer_memory.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -14,16 +17,15 @@ KTransferMemory::KTransferMemory(KernelCore& kernel) KTransferMemory::~KTransferMemory() = default; -Result KTransferMemory::Initialize(KProcessAddress addr, std::size_t size, - Svc::MemoryPermission own_perm) { +Result KTransferMemory::Initialize(KernelCore& kernel, KProcessAddress addr, std::size_t size, Svc::MemoryPermission own_perm) { // Set members. - m_owner = GetCurrentProcessPointer(m_kernel); + m_owner = GetCurrentProcessPointer(kernel); // Get the owner page table. auto& page_table = m_owner->GetPageTable(); // Construct the page group, guarding to make sure our state is valid on exit. - m_page_group.emplace(m_kernel, page_table.GetBlockInfoManager()); + m_page_group.emplace(kernel, page_table.GetBlockInfoManager()); auto pg_guard = SCOPE_GUARD { m_page_group.reset(); }; @@ -33,7 +35,7 @@ Result KTransferMemory::Initialize(KProcessAddress addr, std::size_t size, ConvertToKMemoryPermission(own_perm))); // Set remaining tracking members. - m_owner->Open(); + m_owner->Open(kernel); m_owner_perm = own_perm; m_address = addr; m_is_initialized = true; @@ -44,7 +46,7 @@ Result KTransferMemory::Initialize(KProcessAddress addr, std::size_t size, R_SUCCEED(); } -void KTransferMemory::Finalize() { +void KTransferMemory::Finalize(KernelCore& kernel) { // Unlock. if (!m_is_mapped) { const size_t size = m_page_group->GetNumPages() * PageSize; @@ -53,17 +55,17 @@ void KTransferMemory::Finalize() { } // Close the page group. - m_page_group->Close(); + m_page_group->Close(kernel); m_page_group->Finalize(); } -void KTransferMemory::PostDestroy(uintptr_t arg) { +void KTransferMemory::PostDestroy(KernelCore& kernel, uintptr_t arg) { KProcess* owner = reinterpret_cast(arg); - owner->GetResourceLimit()->Release(LimitableResource::TransferMemoryCountMax, 1); - owner->Close(); + owner->GetResourceLimit()->Release(kernel, LimitableResource::TransferMemoryCountMax, 1); + owner->Close(kernel); } -Result KTransferMemory::Map(KProcessAddress address, size_t size, Svc::MemoryPermission map_perm) { +Result KTransferMemory::Map(KernelCore& kernel, KProcessAddress address, size_t size, Svc::MemoryPermission map_perm) { // Validate the size. R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize); @@ -80,7 +82,7 @@ Result KTransferMemory::Map(KProcessAddress address, size_t size, Svc::MemoryPer const KMemoryState state = (m_owner_perm == Svc::MemoryPermission::None) ? KMemoryState::Transferred : KMemoryState::SharedTransferred; - R_TRY(GetCurrentProcess(m_kernel).GetPageTable().MapPageGroup( + R_TRY(GetCurrentProcess(kernel).GetPageTable().MapPageGroup( address, *m_page_group, state, KMemoryPermission::UserReadWrite)); // Mark ourselves as mapped. @@ -89,7 +91,7 @@ Result KTransferMemory::Map(KProcessAddress address, size_t size, Svc::MemoryPer R_SUCCEED(); } -Result KTransferMemory::Unmap(KProcessAddress address, size_t size) { +Result KTransferMemory::Unmap(KernelCore& kernel, KProcessAddress address, size_t size) { // Validate the size. R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize); @@ -100,7 +102,7 @@ Result KTransferMemory::Unmap(KProcessAddress address, size_t size) { const KMemoryState state = (m_owner_perm == Svc::MemoryPermission::None) ? KMemoryState::Transferred : KMemoryState::SharedTransferred; - R_TRY(GetCurrentProcess(m_kernel).GetPageTable().UnmapPageGroup(address, *m_page_group, state)); + R_TRY(GetCurrentProcess(kernel).GetPageTable().UnmapPageGroup(address, *m_page_group, state)); // Mark ourselves as unmapped. ASSERT(m_is_mapped); diff --git a/src/core/hle/kernel/k_transfer_memory.h b/src/core/hle/kernel/k_transfer_memory.h index 530b452182..0753b4fd6d 100644 --- a/src/core/hle/kernel/k_transfer_memory.h +++ b/src/core/hle/kernel/k_transfer_memory.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -30,9 +33,9 @@ public: explicit KTransferMemory(KernelCore& kernel); ~KTransferMemory() override; - Result Initialize(KProcessAddress address, std::size_t size, Svc::MemoryPermission owner_perm); + Result Initialize(KernelCore& kernel, KProcessAddress address, std::size_t size, Svc::MemoryPermission owner_perm); - void Finalize() override; + void Finalize(KernelCore& kernel) override; bool IsInitialized() const override { return m_is_initialized; @@ -42,7 +45,7 @@ public: return reinterpret_cast(m_owner); } - static void PostDestroy(uintptr_t arg); + static void PostDestroy(KernelCore& kernel, uintptr_t arg); KProcess* GetOwner() const override { return m_owner; @@ -54,8 +57,8 @@ public: size_t GetSize() const; - Result Map(KProcessAddress address, size_t size, Svc::MemoryPermission map_perm); - Result Unmap(KProcessAddress address, size_t size); + Result Map(KernelCore& kernel, KProcessAddress address, size_t size, Svc::MemoryPermission map_perm); + Result Unmap(KernelCore& kernel, KProcessAddress address, size_t size); private: std::optional m_page_group{}; diff --git a/src/core/hle/kernel/k_typed_address.h b/src/core/hle/kernel/k_typed_address.h index d57535ba01..d793d67a10 100644 --- a/src/core/hle/kernel/k_typed_address.h +++ b/src/core/hle/kernel/k_typed_address.h @@ -1,6 +1,11 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 "common/typed_address.h" namespace Kernel { diff --git a/src/core/hle/kernel/k_worker_task.h b/src/core/hle/kernel/k_worker_task.h index 9a230c03c4..a4820bc412 100644 --- a/src/core/hle/kernel/k_worker_task.h +++ b/src/core/hle/kernel/k_worker_task.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -10,8 +13,7 @@ namespace Kernel { class KWorkerTask : public KSynchronizationObject { public: explicit KWorkerTask(KernelCore& kernel); - - void DoWorkerTask(); + void DoWorkerTask(KernelCore& kernel); }; } // namespace Kernel diff --git a/src/core/hle/kernel/k_worker_task_manager.cpp b/src/core/hle/kernel/k_worker_task_manager.cpp index 8ead395911..4427f9859a 100644 --- a/src/core/hle/kernel/k_worker_task_manager.cpp +++ b/src/core/hle/kernel/k_worker_task_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -12,14 +15,14 @@ namespace Kernel { KWorkerTask::KWorkerTask(KernelCore& kernel) : KSynchronizationObject{kernel} {} -void KWorkerTask::DoWorkerTask() { +void KWorkerTask::DoWorkerTask(KernelCore& kernel) { if (auto* const thread = this->DynamicCast(); thread != nullptr) { - return thread->DoWorkerTaskImpl(); + return thread->DoWorkerTaskImpl(kernel); } else { auto* const process = this->DynamicCast(); ASSERT(process != nullptr); - return process->DoWorkerTaskImpl(); + return process->DoWorkerTaskImpl(kernel); } } @@ -32,9 +35,9 @@ void KWorkerTaskManager::AddTask(KernelCore& kernel, WorkerType type, KWorkerTas void KWorkerTaskManager::AddTask(KernelCore& kernel, KWorkerTask* task) { KScopedSchedulerLock sl(kernel); - m_waiting_thread.QueueWork([task]() { + m_waiting_thread.QueueWork([&kernel, task]() { // Do the task. - task->DoWorkerTask(); + task->DoWorkerTask(kernel); }); } diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 5f88fa498f..4f1d849209 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -10,7 +10,8 @@ #include #include #include -#include +#include "common/container/unordered_map.h" +#include "common/container/unordered_set.h" #include #include "common/assert.h" @@ -121,14 +122,14 @@ struct KernelCore::Impl { void TerminateAllProcesses() { std::scoped_lock lk{process_list_lock}; for (auto& process : process_list) { - process->Terminate(); - process->Close(); + process->Terminate(system.Kernel()); + process->Close(system.Kernel()); process = nullptr; } process_list.clear(); } - void Shutdown() { + void Shutdown(KernelCore& kernel) { is_shutting_down.store(true, std::memory_order_relaxed); SCOPE_EXIT { is_shutting_down.store(false, std::memory_order_relaxed); @@ -137,7 +138,7 @@ struct KernelCore::Impl { CloseServices(); if (application_process) { - application_process->Close(); + application_process->Close(system.Kernel()); application_process = nullptr; } @@ -149,9 +150,9 @@ struct KernelCore::Impl { preemption_event = nullptr; // Cleanup persistent kernel objects - auto CleanupObject = [](KAutoObject* obj) { + auto CleanupObject = [&kernel](KAutoObject* obj) { if (obj) { - obj->Close(); + obj->Close(kernel); obj = nullptr; } }; @@ -163,7 +164,7 @@ struct KernelCore::Impl { for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { if (shutdown_threads[core_id]) { - shutdown_threads[core_id]->Close(); + shutdown_threads[core_id]->Close(kernel); shutdown_threads[core_id] = nullptr; } @@ -178,7 +179,7 @@ struct KernelCore::Impl { std::scoped_lock lk{registered_in_use_objects_lock}; if (registered_in_use_objects.size()) { for (auto& object : registered_in_use_objects) { - object->Close(); + object->Close(kernel); } registered_in_use_objects.clear(); } @@ -226,7 +227,7 @@ struct KernelCore::Impl { ASSERT(Kernel::KThread::InitializeIdleThread(system, idle_thread, core).IsSuccess()); KThread::Register(system.Kernel(), idle_thread); - schedulers[i]->Initialize(main_thread, idle_thread, core); + schedulers[i]->Initialize(system.Kernel(), main_thread, idle_thread, core); } } @@ -247,19 +248,18 @@ struct KernelCore::Impl { ASSERT(system_resource_limit->SetLimitValue(LimitableResource::EventCountMax, 900).IsSuccess()); ASSERT(system_resource_limit->SetLimitValue(LimitableResource::TransferMemoryCountMax, 200).IsSuccess()); ASSERT(system_resource_limit->SetLimitValue(LimitableResource::SessionCountMax, 1133).IsSuccess()); - system_resource_limit->Reserve(LimitableResource::PhysicalMemoryMax, kernel_size); + system_resource_limit->Reserve(kernel, LimitableResource::PhysicalMemoryMax, kernel_size); // Reserve secure applet memory, introduced in firmware 5.0.0 constexpr u64 secure_applet_memory_size{4_MiB}; - ASSERT(system_resource_limit->Reserve(LimitableResource::PhysicalMemoryMax, - secure_applet_memory_size)); + ASSERT(system_resource_limit->Reserve(kernel, LimitableResource::PhysicalMemoryMax, secure_applet_memory_size)); } void InitializePreemption(KernelCore& kernel) { preemption_event = Core::Timing::CreateEvent("PreemptionCallback", [this, &kernel](s64 time, std::chrono::nanoseconds) -> std::optional { { KScopedSchedulerLock lock(kernel); - global_scheduler_context->PreemptThreads(); + global_scheduler_context->PreemptThreads(kernel); } return std::nullopt; }); @@ -350,9 +350,18 @@ struct KernelCore::Impl { object_name_global_data.emplace(kernel); } - void MakeApplicationProcess(KProcess* process) { + void SetApplicationProcess(KernelCore& kernel, KProcess* process) { + if (application_process == process) + return; + + KProcess* const previous = application_process; application_process = process; - application_process->Open(); + + if (application_process != nullptr) + application_process->Open(kernel); + + if (previous != nullptr) + previous->Close(kernel); } /// Sets the host thread ID for the caller. @@ -374,10 +383,10 @@ struct KernelCore::Impl { // Gets the dummy KThread for the caller, allocating a new one if this is the first time KThread* GetHostDummyThread(ThreadLocalData& t, KThread* existing_thread) { if (t.thread == nullptr) { - auto const initialize{[](KThread* thread) { - ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess()); + auto const initialize = [this](KThread* thread) { + ASSERT(KThread::InitializeDummyThread(system, thread, nullptr).IsSuccess()); return thread; - }}; + }; t.raw_thread.emplace(system.Kernel()); t.thread = existing_thread ? existing_thread : initialize(&*t.raw_thread); ASSERT(t.thread != nullptr); @@ -418,8 +427,7 @@ struct KernelCore::Impl { return is_shutting_down.load(std::memory_order_relaxed); } - KThread* GetCurrentEmuThread() { - auto& t = tls_data; + KThread* GetCurrentEmuThread(ThreadLocalData& t) { return t.current_thread ? t.current_thread : (t.current_thread = GetHostDummyThread(t, nullptr)); } @@ -742,19 +750,19 @@ struct KernelCore::Impl { time_shared_mem = KSharedMemory::Create(system.Kernel()); hidbus_shared_mem = KSharedMemory::Create(system.Kernel()); - font_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None, + font_shared_mem->Initialize(system.Kernel(), system.DeviceMemory(), nullptr, Svc::MemoryPermission::None, Svc::MemoryPermission::Read, font_size); KSharedMemory::Register(kernel, font_shared_mem); - irs_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None, + irs_shared_mem->Initialize(system.Kernel(), system.DeviceMemory(), nullptr, Svc::MemoryPermission::None, Svc::MemoryPermission::Read, irs_size); KSharedMemory::Register(kernel, irs_shared_mem); - time_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None, + time_shared_mem->Initialize(system.Kernel(), system.DeviceMemory(), nullptr, Svc::MemoryPermission::None, Svc::MemoryPermission::Read, time_size); KSharedMemory::Register(kernel, time_shared_mem); - hidbus_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None, + hidbus_shared_mem->Initialize(system.Kernel(), system.DeviceMemory(), nullptr, Svc::MemoryPermission::None, Svc::MemoryPermission::Read, hidbus_size); KSharedMemory::Register(kernel, hidbus_shared_mem); } @@ -785,8 +793,8 @@ struct KernelCore::Impl { std::optional object_name_global_data; - ankerl::unordered_dense::set registered_objects; - ankerl::unordered_dense::set registered_in_use_objects; + ::Common::unordered_set registered_objects; + ::Common::unordered_set registered_in_use_objects; std::mutex server_lock; std::vector> server_managers; @@ -852,7 +860,7 @@ void KernelCore::Initialize() { } void KernelCore::Shutdown() { - impl->Shutdown(); + impl->Shutdown(*this); } void KernelCore::CloseServices() { @@ -868,7 +876,7 @@ KResourceLimit* KernelCore::GetSystemResourceLimit() { } void KernelCore::AppendNewProcess(KProcess* process) { - process->Open(); + process->Open(*this); std::scoped_lock lk{impl->process_list_lock}; impl->process_list.push_back(process); @@ -877,12 +885,12 @@ void KernelCore::AppendNewProcess(KProcess* process) { void KernelCore::RemoveProcess(KProcess* process) { std::scoped_lock lk{impl->process_list_lock}; if (std::erase(impl->process_list, process)) { - process->Close(); + process->Close(*this); } } -void KernelCore::MakeApplicationProcess(KProcess* process) { - impl->MakeApplicationProcess(process); +void KernelCore::SetApplicationProcess(KProcess* process) { + impl->SetApplicationProcess(*this, process); } KProcess* KernelCore::ApplicationProcess() { @@ -893,14 +901,19 @@ const KProcess* KernelCore::ApplicationProcess() const { return impl->application_process; } +KScopedAutoObject KernelCore::GetProcessByProcessId(u64 process_id) { + std::scoped_lock lk{impl->process_list_lock}; + for (auto* const process : impl->process_list) + if (process != nullptr && process->GetProcessId() == process_id) + return {*this, process}; + return {*this, nullptr}; +} + std::list> KernelCore::GetProcessList() { std::list> processes; std::scoped_lock lk{impl->process_list_lock}; - - for (auto* const process : impl->process_list) { - processes.emplace_back(process); - } - + for (auto* const process : impl->process_list) + processes.emplace_back(*this, process); return processes; } @@ -1029,15 +1042,14 @@ void KernelCore::RegisterHostThread(KThread* existing_thread) { } } -static std::jthread RunHostThreadFunc(KernelCore& kernel, KProcess* process, - std::string&& thread_name, std::function&& func) { +static std::jthread RunHostThreadFunc(KernelCore& kernel, KProcess* process, std::string&& thread_name, std::function&& func) { // Reserve a new thread from the process resource limit. - KScopedResourceReservation thread_reservation(process, LimitableResource::ThreadCountMax); + KScopedResourceReservation thread_reservation(kernel, process, LimitableResource::ThreadCountMax); ASSERT(thread_reservation.Succeeded()); // Initialize the thread. KThread* thread = KThread::Create(kernel); - ASSERT(R_SUCCEEDED(KThread::InitializeDummyThread(thread, process))); + ASSERT(R_SUCCEEDED(KThread::InitializeDummyThread(kernel.System(), thread, process))); // Commit the thread reservation. thread_reservation.Commit(); @@ -1057,7 +1069,7 @@ static std::jthread RunHostThreadFunc(KernelCore& kernel, KProcess* process, // Close the thread. // This will free the process if it is the last reference. - thread->Close(); + thread->Close(kernel); }); } @@ -1066,11 +1078,11 @@ std::jthread KernelCore::RunOnHostCoreProcess(std::string&& process_name, // Make a new process. KProcess* process = KProcess::Create(*this); ASSERT(R_SUCCEEDED( - process->Initialize(Svc::CreateProcessParameter{}, GetSystemResourceLimit(), false))); + process->Initialize(*this, Svc::CreateProcessParameter{}, GetSystemResourceLimit(), false))); // Ensure that we don't hold onto any extra references. SCOPE_EXIT { - process->Close(); + process->Close(*this); }; // Register the new process. @@ -1096,18 +1108,18 @@ void KernelCore::RunOnGuestCoreProcess(std::string&& process_name, std::function // Make a new process. KProcess* process = KProcess::Create(*this); ASSERT(R_SUCCEEDED( - process->Initialize(Svc::CreateProcessParameter{}, GetSystemResourceLimit(), false))); + process->Initialize(*this, Svc::CreateProcessParameter{}, GetSystemResourceLimit(), false))); // Ensure that we don't hold onto any extra references. SCOPE_EXIT { - process->Close(); + process->Close(*this); }; // Register the new process. KProcess::Register(*this, process); // Reserve a new thread from the process resource limit. - KScopedResourceReservation thread_reservation(process, LimitableResource::ThreadCountMax); + KScopedResourceReservation thread_reservation(*this, process, LimitableResource::ThreadCountMax); ASSERT(thread_reservation.Succeeded()); // Initialize the thread. @@ -1122,7 +1134,7 @@ void KernelCore::RunOnGuestCoreProcess(std::string&& process_name, std::function KThread::Register(*this, thread); // Begin running the thread. - ASSERT(R_SUCCEEDED(thread->Run())); + ASSERT(R_SUCCEEDED(thread->Run(*this))); } u32 KernelCore::GetCurrentHostThreadID() const { @@ -1130,7 +1142,7 @@ u32 KernelCore::GetCurrentHostThreadID() const { } KThread* KernelCore::GetCurrentEmuThread() const { - return impl->GetCurrentEmuThread(); + return impl->GetCurrentEmuThread(Impl::tls_data); } void KernelCore::SetCurrentEmuThread(KThread* thread) { @@ -1206,9 +1218,9 @@ void KernelCore::SuspendEmulation(bool suspended) { for (auto& thread : process->GetThreadList()) { if (should_suspend) { - thread.RequestSuspend(SuspendType::System); + thread.RequestSuspend(*this, SuspendType::System); } else { - thread.Resume(SuspendType::System); + thread.Resume(*this, SuspendType::System); } } } @@ -1244,12 +1256,9 @@ void KernelCore::SuspendEmulation(bool suspended) { void KernelCore::ShutdownCores() { impl->TerminateAllProcesses(); - KScopedSchedulerLock lk{*this}; - - for (auto* thread : impl->shutdown_threads) { - void(thread->Run()); - } + for (auto* thread : impl->shutdown_threads) + void(thread->Run(*this)); } bool KernelCore::IsMulticore() const { diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 3b754e9397..2d44654f4d 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -11,7 +11,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include "common/polyfill_thread.h" @@ -124,8 +124,8 @@ public: void AppendNewProcess(KProcess* process); void RemoveProcess(KProcess* process); - /// Makes the given process the new application process. - void MakeApplicationProcess(KProcess* process); + /// Makes the given process the current application process. + void SetApplicationProcess(KProcess* process); /// Retrieves a pointer to the application process. KProcess* ApplicationProcess(); @@ -133,6 +133,9 @@ public: /// Retrieves a const pointer to the application process. const KProcess* ApplicationProcess() const; + /// Retrieves the process with the given process ID, or a null object. + KScopedAutoObject GetProcessByProcessId(u64 process_id); + /// Retrieves the list of processes. std::list> GetProcessList(); diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp index f394122764..c5236c64e3 100644 --- a/src/core/hle/kernel/physical_core.cpp +++ b/src/core/hle/kernel/physical_core.cpp @@ -17,14 +17,15 @@ namespace Kernel { PhysicalCore::PhysicalCore(KernelCore& kernel, std::size_t core_index) - : m_kernel{kernel}, m_core_index{core_index} { + : m_core_index{core_index} +{ m_is_single_core = !kernel.IsMulticore(); } PhysicalCore::~PhysicalCore() = default; -void PhysicalCore::RunThread(Kernel::KThread* thread) { +void PhysicalCore::RunThread(KernelCore& kernel, Kernel::KThread* thread) { auto* process = thread->GetOwnerProcess(); - auto& system = m_kernel.System(); + auto& system = kernel.System(); auto* interface = process->GetArmInterface(m_core_index); interface->Initialize(); @@ -72,14 +73,14 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) { while (true) { // If the thread is scheduled for termination, exit. if (thread->HasDpc() && thread->IsTerminationRequested()) { - thread->Exit(); + thread->Exit(kernel); } // Notify the debugger and go to sleep if a step was performed // and this thread has been scheduled again. if (thread->GetStepState() == StepState::StepPerformed) { system.GetDebugger().NotifyThreadStopped(thread); - thread->RequestSuspend(SuspendType::Debug); + thread->RequestSuspend(kernel, SuspendType::Debug); return; } @@ -105,7 +106,7 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) { } // Determine why we stopped. - // If a step completed successfully, skip other halt reason handlers — + // If a step completed successfully, skip other halt reason handlers // the step takes priority (e.g. step may also set InstructionBreakpoint // if the next instruction happens to be a breakpoint). const bool step_completed = True(hr & Core::HaltReason::StepThread) @@ -135,7 +136,7 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) { } else { interface->LogBacktrace(process); } - thread->RequestSuspend(SuspendType::Debug); + thread->RequestSuspend(kernel, SuspendType::Debug); return; } @@ -144,7 +145,7 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) { if (system.DebuggerEnabled()) { system.GetDebugger().NotifyThreadWatchpoint(thread, *interface->HaltedWatchpoint()); } - thread->RequestSuspend(SuspendType::Debug); + thread->RequestSuspend(kernel, SuspendType::Debug); return; } @@ -208,8 +209,8 @@ void PhysicalCore::CloneFpuStatus(KThread* dst) const { dst->GetContext().fpsr = ctx.fpsr; } -void PhysicalCore::LogBacktrace() { - auto* process = GetCurrentProcessPointer(m_kernel); +void PhysicalCore::LogBacktrace(KernelCore& kernel) { + auto* process = GetCurrentProcessPointer(kernel); if (!process) { return; } diff --git a/src/core/hle/kernel/physical_core.h b/src/core/hle/kernel/physical_core.h index bae4fe5b88..b92201204a 100644 --- a/src/core/hle/kernel/physical_core.h +++ b/src/core/hle/kernel/physical_core.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -30,7 +33,7 @@ public: YUZU_NON_MOVEABLE(PhysicalCore); // Execute guest code running on the given thread. - void RunThread(KThread* thread); + void RunThread(KernelCore& kernel, KThread* thread); // Copy context from thread to current core. void LoadContext(const KThread* thread); @@ -44,7 +47,7 @@ public: void CloneFpuStatus(KThread* dst) const; // Log backtrace of current processor state. - void LogBacktrace(); + void LogBacktrace(KernelCore& kernel); // Wait for an interrupt. void Idle(); @@ -63,9 +66,7 @@ public: } private: - KernelCore& m_kernel; const std::size_t m_core_index; - std::mutex m_guard; std::condition_variable m_on_interrupt; Core::ArmInterface* m_arm_interface{}; diff --git a/src/core/hle/kernel/slab_helpers.h b/src/core/hle/kernel/slab_helpers.h index d1bbc76709..95e6b9f6e6 100644 --- a/src/core/hle/kernel/slab_helpers.h +++ b/src/core/hle/kernel/slab_helpers.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -69,16 +72,16 @@ public: explicit KAutoObjectWithSlabHeap(KernelCore& kernel) : Base(kernel) {} virtual ~KAutoObjectWithSlabHeap() = default; - virtual void Destroy() override { + virtual void Destroy(KernelCore& kernel) override { const bool is_initialized = this->IsInitialized(); uintptr_t arg = 0; if (is_initialized) { arg = this->GetPostDestroyArgument(); - this->Finalize(); + this->Finalize(kernel); } - Free(Base::m_kernel, static_cast(this)); + Free(kernel, static_cast(this)); if (is_initialized) { - Derived::PostDestroy(arg); + Derived::PostDestroy(kernel, arg); } } @@ -89,8 +92,8 @@ public: return 0; } - size_t GetSlabIndex() const { - return SlabHeap(Base::m_kernel).GetObjectIndex(static_cast(this)); + size_t GetSlabIndex(KernelCore& kernel) const { + return SlabHeap(kernel).GetObjectIndex(static_cast(this)); } public: @@ -144,17 +147,17 @@ public: KAutoObjectWithSlabHeapAndContainer(KernelCore& kernel) : Base(kernel) {} virtual ~KAutoObjectWithSlabHeapAndContainer() {} - virtual void Destroy() override { + virtual void Destroy(KernelCore& kernel) override { const bool is_initialized = this->IsInitialized(); uintptr_t arg = 0; if (is_initialized) { - Base::m_kernel.ObjectListContainer().Unregister(this); + kernel.ObjectListContainer().Unregister(this); arg = this->GetPostDestroyArgument(); - this->Finalize(); + this->Finalize(kernel); } - Free(Base::m_kernel, static_cast(this)); + Free(kernel, static_cast(this)); if (is_initialized) { - Derived::PostDestroy(arg); + Derived::PostDestroy(kernel, arg); } } @@ -165,8 +168,8 @@ public: return 0; } - size_t GetSlabIndex() const { - return SlabHeap(Base::m_kernel).GetObjectIndex(static_cast(this)); + size_t GetSlabIndex(KernelCore& kernel) const { + return SlabHeap(kernel).GetObjectIndex(static_cast(this)); } public: @@ -177,9 +180,8 @@ public: static Derived* Create(KernelCore& kernel) { Derived* obj = Allocate(kernel); - if (obj != nullptr) { + if (obj != nullptr) KAutoObject::Create(obj); - } return obj; } diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index faf6a6432d..3542d5a3b6 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 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-late @@ -2501,14 +2501,15 @@ void Call(Core::System& system, u32 imm) { auto& process = GetCurrentProcess(kernel); std::array args; kernel.CurrentPhysicalCore().SaveSvcArguments(process, args); - LOG_TRACE(Kernel_SVC, "{} [0]={:#x} [1]={:#x} [2]={:#x} [3]={:#x} [4]={:#x} [5]={:#x} [6]={:#x}", - imm, GetArg32(args, 0), GetArg32(args, 1), GetArg32(args, 2), - GetArg32(args, 3), GetArg32(args, 4), GetArg32(args, 5), GetArg32(args, 6)); //kernel.EnterSVCProfile(); - if (process.Is64Bit()) + LOG_TRACE(Kernel_SVC, "{} [0]={:#x} [1]={:#x} [2]={:#x} [3]={:#x} [4]={:#x} [5]={:#x} [6]={:#x}", + imm, GetArg64(args, 0), GetArg64(args, 1), GetArg64(args, 2), + GetArg64(args, 3), GetArg64(args, 4), GetArg64(args, 5), GetArg64(args, 6)); + if (process.Is64Bit()) { Call64(system, imm, args); - else + } else { Call32(system, imm, args); + } //kernel.ExitSVCProfile(); kernel.CurrentPhysicalCore().LoadSvcArguments(process, args); } diff --git a/src/core/hle/kernel/svc/svc_activity.cpp b/src/core/hle/kernel/svc/svc_activity.cpp index 63bc085554..006faf9da3 100644 --- a/src/core/hle/kernel/svc/svc_activity.cpp +++ b/src/core/hle/kernel/svc/svc_activity.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -12,7 +15,7 @@ namespace Kernel::Svc { /// Sets the thread activity Result SetThreadActivity(Core::System& system, Handle thread_handle, ThreadActivity thread_activity) { - LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", thread_handle, + LOG_DEBUG(Kernel_SVC, "called, handle={:#08x}, activity={:#08x}", thread_handle, thread_activity); // Validate the activity. @@ -22,17 +25,15 @@ Result SetThreadActivity(Core::System& system, Handle thread_handle, R_UNLESS(IsValidThreadActivity(thread_activity), ResultInvalidEnumValue); // Get the thread from its handle. - KScopedAutoObject thread = - GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(thread_handle); + KScopedAutoObject thread = GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(system.Kernel(), thread_handle); R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); // Check that the activity is being set on a non-current thread for the current process. - R_UNLESS(thread->GetOwnerProcess() == GetCurrentProcessPointer(system.Kernel()), - ResultInvalidHandle); + R_UNLESS(thread->GetOwnerProcess() == GetCurrentProcessPointer(system.Kernel()), ResultInvalidHandle); R_UNLESS(thread.GetPointerUnsafe() != GetCurrentThreadPointer(system.Kernel()), ResultBusy); // Set the activity. - R_TRY(thread->SetActivity(thread_activity)); + R_TRY(thread->SetActivity(system.Kernel(), thread_activity)); return ResultSuccess; } diff --git a/src/core/hle/kernel/svc/svc_address_arbiter.cpp b/src/core/hle/kernel/svc/svc_address_arbiter.cpp index 688d6abce0..b3cd41aa8e 100644 --- a/src/core/hle/kernel/svc/svc_address_arbiter.cpp +++ b/src/core/hle/kernel/svc/svc_address_arbiter.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -43,7 +43,7 @@ constexpr bool IsValidArbitrationType(Svc::ArbitrationType type) { // Wait for an address (via Address Arbiter) Result WaitForAddress(Core::System& system, u64 address, ArbitrationType arb_type, s32 value, s64 timeout_ns) { - LOG_TRACE(Kernel_SVC, "called, address={:#X}, arb_type=0x{:X}, value=0x{:X}, timeout_ns={}", + LOG_TRACE(Kernel_SVC, "called, address={:#x}, arb_type={:#x}, value={:#x}, timeout_ns={}", address, arb_type, value, timeout_ns); // Validate input. @@ -74,7 +74,7 @@ Result WaitForAddress(Core::System& system, u64 address, ArbitrationType arb_typ // Signals to an address (via Address Arbiter) Result SignalToAddress(Core::System& system, u64 address, SignalType signal_type, s32 value, s32 count) { - LOG_TRACE(Kernel_SVC, "called, address={:#X}, signal_type=0x{:X}, value=0x{:X}, count=0x{:X}", + LOG_TRACE(Kernel_SVC, "called, address={:#x}, signal_type={:#x}, value={:#x}, count={:#x}", address, signal_type, value, count); // Validate input. diff --git a/src/core/hle/kernel/svc/svc_cache.cpp b/src/core/hle/kernel/svc/svc_cache.cpp index c2c8be10f5..24f64f28f1 100644 --- a/src/core/hle/kernel/svc/svc_cache.cpp +++ b/src/core/hle/kernel/svc/svc_cache.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -37,8 +40,7 @@ Result FlushProcessDataCache(Core::System& system, Handle process_handle, u64 ad R_UNLESS(size == static_cast(size), ResultInvalidCurrentMemory); // Get the process from its handle. - KScopedAutoObject process = - GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(process_handle); + KScopedAutoObject process = GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(system.Kernel(), process_handle); R_UNLESS(process.IsNotNull(), ResultInvalidHandle); // Verify the region is within range. diff --git a/src/core/hle/kernel/svc/svc_code_memory.cpp b/src/core/hle/kernel/svc/svc_code_memory.cpp index 4e7af9f575..9febc5f529 100644 --- a/src/core/hle/kernel/svc/svc_code_memory.cpp +++ b/src/core/hle/kernel/svc/svc_code_memory.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -33,10 +33,7 @@ constexpr bool IsValidUnmapFromOwnerCodeMemoryPermission(MemoryPermission perm) } // namespace Result CreateCodeMemory(Core::System& system, Handle* out, u64 address, uint64_t size) { - LOG_TRACE(Kernel_SVC, "called, address={:#X}, size=0x{:X}", address, size); - - // Get kernel instance. - auto& kernel = system.Kernel(); + LOG_TRACE(Kernel_SVC, "called, address={:#x}, size={:#x}", address, size); // Validate address / size. R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress); @@ -46,24 +43,23 @@ Result CreateCodeMemory(Core::System& system, Handle* out, u64 address, uint64_t // Create the code memory. - KCodeMemory* code_mem = KCodeMemory::Create(kernel); + KCodeMemory* code_mem = KCodeMemory::Create(system.Kernel()); R_UNLESS(code_mem != nullptr, ResultOutOfResource); SCOPE_EXIT { - code_mem->Close(); + code_mem->Close(system.Kernel()); }; // Verify that the region is in range. - R_UNLESS(GetCurrentProcess(system.Kernel()).GetPageTable().Contains(address, size), - ResultInvalidCurrentMemory); + R_UNLESS(GetCurrentProcess(system.Kernel()).GetPageTable().Contains(address, size), ResultInvalidCurrentMemory); // Initialize the code memory. - R_TRY(code_mem->Initialize(system.DeviceMemory(), address, size)); + R_TRY(code_mem->Initialize(system.Kernel(), system.DeviceMemory(), address, size)); // Register the code memory. - KCodeMemory::Register(kernel, code_mem); + KCodeMemory::Register(system.Kernel(), code_mem); // Add the code memory to the handle table. - R_TRY(GetCurrentProcess(system.Kernel()).GetHandleTable().Add(out, code_mem)); + R_TRY(GetCurrentProcess(system.Kernel()).GetHandleTable().Add(system.Kernel(), out, code_mem)); R_SUCCEED(); } @@ -73,8 +69,8 @@ Result ControlCodeMemory(Core::System& system, Handle code_memory_handle, MemoryPermission perm) { LOG_TRACE(Kernel_SVC, - "called, code_memory_handle={:#X}, operation=0x{:X}, address=0x{:X}, size=0x{:X}, " - "permission={:#X}", + "called, code_memory_handle={:#x}, operation={:#x}, address={:#x}, size={:#x}, " + "permission={:#x}", code_memory_handle, operation, address, size, perm); // Validate the address / size. @@ -85,8 +81,8 @@ Result ControlCodeMemory(Core::System& system, Handle code_memory_handle, // Get the code memory from its handle. KScopedAutoObject code_mem = GetCurrentProcess(system.Kernel()) - .GetHandleTable() - .GetObject(code_memory_handle); + .GetHandleTable() + .GetObject(system.Kernel(), code_memory_handle); R_UNLESS(code_mem.IsNotNull(), ResultInvalidHandle); // NOTE: Here, Atmosphere extends the SVC to allow code memory operations on one's own process. @@ -96,29 +92,23 @@ Result ControlCodeMemory(Core::System& system, Handle code_memory_handle, switch (operation) { case CodeMemoryOperation::Map: { // Check that the region is in range. - R_UNLESS(GetCurrentProcess(system.Kernel()) - .GetPageTable() - .CanContain(address, size, KMemoryState::CodeOut), - ResultInvalidMemoryRegion); + R_UNLESS(GetCurrentProcess(system.Kernel()).GetPageTable().CanContain(address, size, KMemoryState::CodeOut), ResultInvalidMemoryRegion); // Check the memory permission. R_UNLESS(IsValidMapCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); // Map the memory. - R_TRY(code_mem->Map(address, size)); + R_TRY(code_mem->Map(system.Kernel(), address, size)); } break; case CodeMemoryOperation::Unmap: { // Check that the region is in range. - R_UNLESS(GetCurrentProcess(system.Kernel()) - .GetPageTable() - .CanContain(address, size, KMemoryState::CodeOut), - ResultInvalidMemoryRegion); + R_UNLESS(GetCurrentProcess(system.Kernel()).GetPageTable().CanContain(address, size, KMemoryState::CodeOut), ResultInvalidMemoryRegion); // Check the memory permission. R_UNLESS(IsValidUnmapCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); // Unmap the memory. - R_TRY(code_mem->Unmap(address, size)); + R_TRY(code_mem->Unmap(system.Kernel(), address, size)); } break; case CodeMemoryOperation::MapToOwner: { // Check that the region is in range. @@ -130,7 +120,7 @@ Result ControlCodeMemory(Core::System& system, Handle code_memory_handle, R_UNLESS(IsValidMapToOwnerCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); // Map the memory to its owner. - R_TRY(code_mem->MapToOwner(address, size, perm)); + R_TRY(code_mem->MapToOwner(system.Kernel(), address, size, perm)); } break; case CodeMemoryOperation::UnmapFromOwner: { // Check that the region is in range. @@ -142,7 +132,7 @@ Result ControlCodeMemory(Core::System& system, Handle code_memory_handle, R_UNLESS(IsValidUnmapFromOwnerCodeMemoryPermission(perm), ResultInvalidNewMemoryPermission); // Unmap the memory from its owner. - R_TRY(code_mem->UnmapFromOwner(address, size)); + R_TRY(code_mem->UnmapFromOwner(system.Kernel(), address, size)); } break; default: R_THROW(ResultInvalidEnumValue); diff --git a/src/core/hle/kernel/svc/svc_condition_variable.cpp b/src/core/hle/kernel/svc/svc_condition_variable.cpp index 2aed6a77be..19ac025307 100644 --- a/src/core/hle/kernel/svc/svc_condition_variable.cpp +++ b/src/core/hle/kernel/svc/svc_condition_variable.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -17,7 +17,7 @@ namespace Kernel::Svc { /// Wait process wide key atomic Result WaitProcessWideKeyAtomic(Core::System& system, u64 address, u64 cv_key, u32 tag, s64 timeout_ns) { - LOG_TRACE(Kernel_SVC, "called address={:X}, cv_key={:X}, tag=0x{:08X}, timeout_ns={}", address, + LOG_TRACE(Kernel_SVC, "called address={:X}, cv_key={:X}, tag={:#08x}, timeout_ns={}", address, cv_key, tag, timeout_ns); // Validate input. @@ -48,7 +48,7 @@ Result WaitProcessWideKeyAtomic(Core::System& system, u64 address, u64 cv_key, u /// Signal process wide key void SignalProcessWideKey(Core::System& system, u64 cv_key, s32 count) { - LOG_TRACE(Kernel_SVC, "called, cv_key={:#X}, count=0x{:08X}", cv_key, count); + LOG_TRACE(Kernel_SVC, "called, cv_key={:#x}, count={:#08x}", cv_key, count); // Signal the condition variable. return GetCurrentProcess(system.Kernel()) diff --git a/src/core/hle/kernel/svc/svc_debug.cpp b/src/core/hle/kernel/svc/svc_debug.cpp index a4d1f700e8..7781339a9e 100644 --- a/src/core/hle/kernel/svc/svc_debug.cpp +++ b/src/core/hle/kernel/svc/svc_debug.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -12,8 +15,8 @@ Result DebugActiveProcess(Core::System& system, Handle* out_handle, uint64_t pro } Result BreakDebugProcess(Core::System& system, Handle debug_handle) { - UNIMPLEMENTED(); - R_THROW(ResultNotImplemented); + LOG_WARNING(Service, "(STUBBED) called"); + R_SUCCEED(); } Result TerminateDebugProcess(Core::System& system, Handle debug_handle) { diff --git a/src/core/hle/kernel/svc/svc_debug_string.cpp b/src/core/hle/kernel/svc/svc_debug_string.cpp index c190fa7a3f..6db3849a82 100644 --- a/src/core/hle/kernel/svc/svc_debug_string.cpp +++ b/src/core/hle/kernel/svc/svc_debug_string.cpp @@ -17,56 +17,12 @@ namespace Kernel::Svc { -constexpr auto MAX_MSG_TIME = std::chrono::milliseconds(250); -const auto MAX_MSG_SIZE = 0x1000; - /// Used to output a message on a debug hardware unit - does nothing on a retail unit Result OutputDebugString(Core::System& system, u64 address, u64 len) { - static struct DebugFlusher { - std::string msg_buffer; - std::mutex msg_mutex; - std::condition_variable msg_cv; - std::chrono::steady_clock::time_point last_msg_time; - std::optional thread; - } flusher_data; R_SUCCEED_IF(len == 0); - // Only start the thread the very first time this function is called - if (!flusher_data.thread) { - flusher_data.thread.emplace([](std::stop_token stop_token) { - while (!stop_token.stop_requested()) { - std::unique_lock lock(flusher_data.msg_mutex); - flusher_data.msg_cv.wait(lock, [&stop_token] { - return !flusher_data.msg_buffer.empty() || stop_token.stop_requested(); - }); - if (stop_token.stop_requested() && flusher_data.msg_buffer.empty()) - break; - auto timeout = flusher_data.last_msg_time + MAX_MSG_TIME; - bool woke_early = flusher_data.msg_cv.wait_until(lock, timeout, [&stop_token] { - return flusher_data.msg_buffer.size() >= MAX_MSG_SIZE || stop_token.stop_requested(); - }); - if (!woke_early || flusher_data.msg_buffer.size() >= MAX_MSG_SIZE || stop_token.stop_requested()) { - if (!flusher_data.msg_buffer.empty()) { - // Remove trailing newline as LOG_INFO adds that anyways - if (flusher_data.msg_buffer.back() == '\n') - flusher_data.msg_buffer.pop_back(); - - LOG_INFO(Debug_Emulated, "\n{}", flusher_data.msg_buffer); - flusher_data.msg_buffer.clear(); - } - if (stop_token.stop_requested()) break; - } - } - flusher_data.msg_cv.notify_all(); - }); - } - { - std::lock_guard lock(flusher_data.msg_mutex); - const auto old_size = flusher_data.msg_buffer.size(); - flusher_data.msg_buffer.resize(old_size + len); - GetCurrentMemory(system.Kernel()).ReadBlock(address, flusher_data.msg_buffer.data() + old_size, len); - flusher_data.last_msg_time = std::chrono::steady_clock::now(); - } - flusher_data.msg_cv.notify_one(); + std::string msg_buffer(len, 0); + GetCurrentMemory(system.Kernel()).ReadBlock(address, msg_buffer.data(), len); + LOG_INFO(Debug_Emulated, "{}", msg_buffer); R_SUCCEED(); } diff --git a/src/core/hle/kernel/svc/svc_device_address_space.cpp b/src/core/hle/kernel/svc/svc_device_address_space.cpp index ac828320f4..2deb683d4b 100644 --- a/src/core/hle/kernel/svc/svc_device_address_space.cpp +++ b/src/core/hle/kernel/svc/svc_device_address_space.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -29,17 +32,17 @@ Result CreateDeviceAddressSpace(Core::System& system, Handle* out, uint64_t das_ KDeviceAddressSpace* das = KDeviceAddressSpace::Create(system.Kernel()); R_UNLESS(das != nullptr, ResultOutOfResource); SCOPE_EXIT { - das->Close(); + das->Close(system.Kernel()); }; // Initialize the device address space. - R_TRY(das->Initialize(das_address, das_size)); + R_TRY(das->Initialize(system.Kernel(), das_address, das_size)); // Register the device address space. KDeviceAddressSpace::Register(system.Kernel(), das); // Add to the handle table. - R_TRY(GetCurrentProcess(system.Kernel()).GetHandleTable().Add(out, das)); + R_TRY(GetCurrentProcess(system.Kernel()).GetHandleTable().Add(system.Kernel(), out, das)); R_SUCCEED(); } @@ -47,8 +50,8 @@ Result CreateDeviceAddressSpace(Core::System& system, Handle* out, uint64_t das_ Result AttachDeviceAddressSpace(Core::System& system, DeviceName device_name, Handle das_handle) { // Get the device address space. KScopedAutoObject das = GetCurrentProcess(system.Kernel()) - .GetHandleTable() - .GetObject(das_handle); + .GetHandleTable() + .GetObject(system.Kernel(), das_handle); R_UNLESS(das.IsNotNull(), ResultInvalidHandle); // Attach. @@ -58,8 +61,8 @@ Result AttachDeviceAddressSpace(Core::System& system, DeviceName device_name, Ha Result DetachDeviceAddressSpace(Core::System& system, DeviceName device_name, Handle das_handle) { // Get the device address space. KScopedAutoObject das = GetCurrentProcess(system.Kernel()) - .GetHandleTable() - .GetObject(das_handle); + .GetHandleTable() + .GetObject(system.Kernel(), das_handle); R_UNLESS(das.IsNotNull(), ResultInvalidHandle); // Detach. @@ -99,13 +102,13 @@ Result MapDeviceAddressSpaceByForce(Core::System& system, Handle das_handle, Han // Get the device address space. KScopedAutoObject das = GetCurrentProcess(system.Kernel()) - .GetHandleTable() - .GetObject(das_handle); + .GetHandleTable() + .GetObject(system.Kernel(), das_handle); R_UNLESS(das.IsNotNull(), ResultInvalidHandle); // Get the process. KScopedAutoObject process = - GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(process_handle); + GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(system.Kernel(), process_handle); R_UNLESS(process.IsNotNull(), ResultInvalidHandle); // Validate that the process address is within range. @@ -140,13 +143,13 @@ Result MapDeviceAddressSpaceAligned(Core::System& system, Handle das_handle, Han // Get the device address space. KScopedAutoObject das = GetCurrentProcess(system.Kernel()) - .GetHandleTable() - .GetObject(das_handle); + .GetHandleTable() + .GetObject(system.Kernel(), das_handle); R_UNLESS(das.IsNotNull(), ResultInvalidHandle); // Get the process. KScopedAutoObject process = - GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(process_handle); + GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(system.Kernel(), process_handle); R_UNLESS(process.IsNotNull(), ResultInvalidHandle); // Validate that the process address is within range. @@ -172,13 +175,12 @@ Result UnmapDeviceAddressSpace(Core::System& system, Handle das_handle, Handle p // Get the device address space. KScopedAutoObject das = GetCurrentProcess(system.Kernel()) - .GetHandleTable() - .GetObject(das_handle); + .GetHandleTable() + .GetObject(system.Kernel(), das_handle); R_UNLESS(das.IsNotNull(), ResultInvalidHandle); // Get the process. - KScopedAutoObject process = - GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(process_handle); + KScopedAutoObject process = GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(system.Kernel(), process_handle); R_UNLESS(process.IsNotNull(), ResultInvalidHandle); // Validate that the process address is within range. diff --git a/src/core/hle/kernel/svc/svc_event.cpp b/src/core/hle/kernel/svc/svc_event.cpp index 586cb2b14e..b88d32b01c 100644 --- a/src/core/hle/kernel/svc/svc_event.cpp +++ b/src/core/hle/kernel/svc/svc_event.cpp @@ -14,7 +14,7 @@ namespace Kernel::Svc { Result SignalEvent(Core::System& system, Handle event_handle) { - LOG_DEBUG(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle); + LOG_DEBUG(Kernel_SVC, "called, event_handle={:#08x}", event_handle); // Get the current handle table. const KHandleTable& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable(); @@ -22,11 +22,11 @@ Result SignalEvent(Core::System& system, Handle event_handle) { // Fail-safe for system applets const auto program_id = GetCurrentProcess(system.Kernel()).GetProgramId(); if ((program_id & 0xFFFFFFFFFFFFFF00ull) == 0x0100000000001000ull) { - KScopedAutoObject event = handle_table.GetObject(event_handle); + KScopedAutoObject event = handle_table.GetObject(system.Kernel(), event_handle); if (event.IsNotNull()) { - event->Signal(); + event->Signal(system.Kernel()); } else { - LOG_WARNING(Kernel_SVC, "SignalEvent best-effort unknown handle=0x{:08X} (ignored)", + LOG_WARNING(Kernel_SVC, "SignalEvent best-effort unknown handle={:#08x} (ignored)", event_handle); } R_SUCCEED(); @@ -34,32 +34,32 @@ Result SignalEvent(Core::System& system, Handle event_handle) { // Get the event. - KScopedAutoObject event = handle_table.GetObject(event_handle); + KScopedAutoObject event = handle_table.GetObject(system.Kernel(), event_handle); R_UNLESS(event.IsNotNull(), ResultInvalidHandle); - R_RETURN(event->Signal()); + R_RETURN(event->Signal(system.Kernel())); } Result ClearEvent(Core::System& system, Handle event_handle) { - LOG_TRACE(Kernel_SVC, "called, event_handle=0x{:08X}", event_handle); + LOG_TRACE(Kernel_SVC, "called, event_handle={:#08x}", event_handle); // Get the current handle table. const auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable(); // Try to clear the writable event. { - KScopedAutoObject event = handle_table.GetObject(event_handle); + KScopedAutoObject event = handle_table.GetObject(system.Kernel(), event_handle); if (event.IsNotNull()) { - event->Clear(); + event->Clear(system.Kernel()); R_SUCCEED(); } } // Try to clear the readable event. { - KScopedAutoObject readable_event = handle_table.GetObject(event_handle); + KScopedAutoObject readable_event = handle_table.GetObject(system.Kernel(), event_handle); if (readable_event.IsNotNull()) { - readable_event->Clear(); + readable_event->Clear(system.Kernel()); R_SUCCEED(); } } @@ -71,43 +71,42 @@ Result CreateEvent(Core::System& system, Handle* out_write, Handle* out_read) { LOG_DEBUG(Kernel_SVC, "called"); // Get the kernel reference and handle table. - auto& kernel = system.Kernel(); - auto& handle_table = GetCurrentProcess(kernel).GetHandleTable(); + auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable(); // Reserve a new event from the process resource limit - KScopedResourceReservation event_reservation(GetCurrentProcessPointer(kernel), + KScopedResourceReservation event_reservation(system.Kernel(), GetCurrentProcessPointer(system.Kernel()), LimitableResource::EventCountMax); R_UNLESS(event_reservation.Succeeded(), ResultLimitReached); // Create a new event. - KEvent* event = KEvent::Create(kernel); + KEvent* event = KEvent::Create(system.Kernel()); R_UNLESS(event != nullptr, ResultOutOfResource); // Initialize the event. - event->Initialize(GetCurrentProcessPointer(kernel)); + event->Initialize(system.Kernel(), GetCurrentProcessPointer(system.Kernel())); // Commit the thread reservation. event_reservation.Commit(); // Ensure that we clean up the event (and its only references are handle table) on function end. SCOPE_EXIT { - event->GetReadableEvent().Close(); - event->Close(); + event->GetReadableEvent().Close(system.Kernel()); + event->Close(system.Kernel()); }; // Register the event. - KEvent::Register(kernel, event); + KEvent::Register(system.Kernel(), event); // Add the event to the handle table. - R_TRY(handle_table.Add(out_write, event)); + R_TRY(handle_table.Add(system.Kernel(), out_write, event)); // Ensure that we maintain a clean handle state on exit. ON_RESULT_FAILURE { - handle_table.Remove(*out_write); + handle_table.Remove(system.Kernel(), *out_write); }; // Add the readable event to the handle table. - R_RETURN(handle_table.Add(out_read, std::addressof(event->GetReadableEvent()))); + R_RETURN(handle_table.Add(system.Kernel(), out_read, std::addressof(event->GetReadableEvent()))); } Result SignalEvent64(Core::System& system, Handle event_handle) { diff --git a/src/core/hle/kernel/svc/svc_exception.cpp b/src/core/hle/kernel/svc/svc_exception.cpp index 47b7568288..4f214c6142 100644 --- a/src/core/hle/kernel/svc/svc_exception.cpp +++ b/src/core/hle/kernel/svc/svc_exception.cpp @@ -1,6 +1,10 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 #include "core/core.h" #include "core/debugger/debugger.h" #include "core/hle/kernel/k_process.h" @@ -38,7 +42,7 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) { std::string hexdump; for (std::size_t i = 0; i < debug_buffer.size(); i++) { hexdump += fmt::format("{:02X} ", debug_buffer[i]); - if (i != 0 && i % 16 == 0) { + if ((i + 1) % 32 == 0) { hexdump += '\n'; } } @@ -48,35 +52,35 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) { }; switch (break_reason) { case BreakReason::Panic: - LOG_CRITICAL(Debug_Emulated, "Userspace PANIC! info1=0x{:016X}, info2=0x{:016X}", info1, + LOG_CRITICAL(Debug_Emulated, "Userspace PANIC! info1={:#016x}, info2={:#016x}", info1, info2); handle_debug_buffer(info1, info2); break; case BreakReason::Assert: - LOG_CRITICAL(Debug_Emulated, "Userspace Assertion failed! info1=0x{:016X}, info2=0x{:016X}", + LOG_CRITICAL(Debug_Emulated, "Userspace Assertion failed! info1={:#016x}, info2={:#016x}", info1, info2); handle_debug_buffer(info1, info2); break; case BreakReason::User: - LOG_WARNING(Debug_Emulated, "Userspace Break! 0x{:016X} with size 0x{:016X}", info1, info2); + LOG_WARNING(Debug_Emulated, "Userspace Break! {:#016x} with size {:#016x}", info1, info2); handle_debug_buffer(info1, info2); break; case BreakReason::PreLoadDll: LOG_INFO(Debug_Emulated, - "Userspace Attempting to load an NRO at 0x{:016X} with size 0x{:016X}", info1, + "Userspace Attempting to load an NRO at {:#016x} with size {:#016x}", info1, info2); break; case BreakReason::PostLoadDll: - LOG_INFO(Debug_Emulated, "Userspace Loaded an NRO at 0x{:016X} with size 0x{:016X}", info1, + LOG_INFO(Debug_Emulated, "Userspace Loaded an NRO at {:#016x} with size {:#016x}", info1, info2); break; case BreakReason::PreUnloadDll: LOG_INFO(Debug_Emulated, - "Userspace Attempting to unload an NRO at 0x{:016X} with size 0x{:016X}", info1, + "Userspace Attempting to unload an NRO at {:#016x} with size {:#016x}", info1, info2); break; case BreakReason::PostUnloadDll: - LOG_INFO(Debug_Emulated, "Userspace Unloaded an NRO at 0x{:016X} with size 0x{:016X}", + LOG_INFO(Debug_Emulated, "Userspace Unloaded an NRO at {:#016x} with size {:#016x}", info1, info2); break; case BreakReason::CppException: @@ -85,7 +89,7 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) { default: LOG_WARNING( Debug_Emulated, - "Signalling debugger, Unknown break reason {:#X}, info1=0x{:016X}, info2=0x{:016X}", + "Signalling debugger, Unknown break reason {:#x}, info1={:#016x}, info2={:#016x}", reason, info1, info2); handle_debug_buffer(info1, info2); break; @@ -98,21 +102,19 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) { if (!notification_only) { LOG_CRITICAL( Debug_Emulated, - "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", + "Emulated program broke execution! reason={:#016x}, info1={:#016x}, info2={:#016x}", reason, info1, info2); handle_debug_buffer(info1, info2); - system.CurrentPhysicalCore().LogBacktrace(); + system.CurrentPhysicalCore().LogBacktrace(system.Kernel()); } - const bool is_hbl = GetCurrentProcess(system.Kernel()).IsHbl(); - const bool should_break = is_hbl || !notification_only; - + const bool should_break = !notification_only; if (system.DebuggerEnabled() && should_break) { auto* thread = system.Kernel().GetCurrentEmuThread(); system.GetDebugger().NotifyThreadStopped(thread); - thread->RequestSuspend(Kernel::SuspendType::Debug); + thread->RequestSuspend(system.Kernel(), Kernel::SuspendType::Debug); } } diff --git a/src/core/hle/kernel/svc/svc_info.cpp b/src/core/hle/kernel/svc/svc_info.cpp index 9e651e1171..863bb58fa8 100644 --- a/src/core/hle/kernel/svc/svc_info.cpp +++ b/src/core/hle/kernel/svc/svc_info.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -10,17 +10,15 @@ #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_resource_limit.h" #include "core/hle/kernel/svc.h" +#include "core/hle/kernel/svc_results.h" +#include "core/hle/kernel/svc_version.h" namespace Kernel::Svc { /// Gets system/memory information for the current process -Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle handle, - u64 info_sub_id) { - LOG_TRACE(Kernel_SVC, "called info_id={:#X}, info_sub_id=0x{:X}, handle=0x{:08X}", - info_id_type, info_sub_id, handle); - - u32 info_id = static_cast(info_id_type); - +Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle handle, u64 info_sub_id) { + LOG_TRACE(Kernel_SVC, "called info_id={:#x}, info_sub_id={:#x}, handle={:#08x}", info_id_type, info_sub_id, handle); + u32 info_id = u32(info_id_type); switch (info_id_type) { case InfoType::CoreMask: case InfoType::PriorityMask: @@ -46,168 +44,138 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle R_UNLESS(info_sub_id == 0, ResultInvalidEnumValue); const auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable(); - KScopedAutoObject process = handle_table.GetObject(handle); + KScopedAutoObject process = handle_table.GetObject(system.Kernel(), handle); R_UNLESS(process.IsNotNull(), ResultInvalidHandle); switch (info_id_type) { case InfoType::CoreMask: *result = process->GetCoreMask(); R_SUCCEED(); - case InfoType::PriorityMask: *result = process->GetPriorityMask(); R_SUCCEED(); - case InfoType::AliasRegionAddress: *result = GetInteger(process->GetPageTable().GetAliasRegionStart()); R_SUCCEED(); - case InfoType::AliasRegionSize: *result = process->GetPageTable().GetAliasRegionSize(); R_SUCCEED(); case InfoType::HeapRegionAddress: *result = GetInteger(process->GetPageTable().GetHeapRegionStart()); R_SUCCEED(); - case InfoType::HeapRegionSize: *result = process->GetPageTable().GetHeapRegionSize(); R_SUCCEED(); - case InfoType::AslrRegionAddress: *result = GetInteger(process->GetPageTable().GetAliasCodeRegionStart()); R_SUCCEED(); - case InfoType::AslrRegionSize: *result = process->GetPageTable().GetAliasCodeRegionSize(); R_SUCCEED(); - case InfoType::StackRegionAddress: *result = GetInteger(process->GetPageTable().GetStackRegionStart()); R_SUCCEED(); - case InfoType::StackRegionSize: *result = process->GetPageTable().GetStackRegionSize(); R_SUCCEED(); - case InfoType::TotalMemorySize: - *result = process->GetTotalUserPhysicalMemorySize(); + *result = process->GetTotalUserPhysicalMemorySize(system.Kernel()); R_SUCCEED(); - case InfoType::UsedMemorySize: - *result = process->GetUsedUserPhysicalMemorySize(); + *result = process->GetUsedUserPhysicalMemorySize(system.Kernel()); R_SUCCEED(); - case InfoType::SystemResourceSizeTotal: *result = process->GetTotalSystemResourceSize(); R_SUCCEED(); - case InfoType::SystemResourceSizeUsed: *result = process->GetUsedSystemResourceSize(); R_SUCCEED(); - case InfoType::ProgramId: *result = process->GetProgramId(); R_SUCCEED(); - case InfoType::UserExceptionContextAddress: *result = GetInteger(process->GetProcessLocalRegionAddress()); R_SUCCEED(); - case InfoType::TotalNonSystemMemorySize: - *result = process->GetTotalNonSystemUserPhysicalMemorySize(); + *result = process->GetTotalNonSystemUserPhysicalMemorySize(system.Kernel()); R_SUCCEED(); - case InfoType::UsedNonSystemMemorySize: - *result = process->GetUsedNonSystemUserPhysicalMemorySize(); + *result = process->GetUsedNonSystemUserPhysicalMemorySize(system.Kernel()); R_SUCCEED(); - case InfoType::IsApplication: *result = process->IsApplication(); R_SUCCEED(); - case InfoType::FreeThreadCount: if (KResourceLimit* resource_limit = process->GetResourceLimit(); resource_limit != nullptr) { - const auto current_value = - resource_limit->GetCurrentValue(Svc::LimitableResource::ThreadCountMax); - const auto limit_value = - resource_limit->GetLimitValue(Svc::LimitableResource::ThreadCountMax); + const auto current_value = resource_limit->GetCurrentValue(Svc::LimitableResource::ThreadCountMax); + const auto limit_value = resource_limit->GetLimitValue(Svc::LimitableResource::ThreadCountMax); *result = limit_value - current_value; } else { *result = 0; } R_SUCCEED(); - case InfoType::AliasRegionExtraSize: { - if (info_sub_id != 0) { - return ResultInvalidCombination; - } - + R_UNLESS(info_sub_id == 0, ResultInvalidCombination); KProcess* current_process = GetCurrentProcessPointer(system.Kernel()); *result = current_process->GetPageTable().GetAliasRegionExtraSize(); - R_SUCCEED(); } - default: break; } - - LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id); + LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id={:#016x}", info_id); R_THROW(ResultInvalidEnumValue); } - case InfoType::DebuggerAttached: *result = 0; R_SUCCEED(); - case InfoType::ResourceLimit: { R_UNLESS(handle == 0, ResultInvalidHandle); R_UNLESS(info_sub_id == 0, ResultInvalidCombination); - KProcess* const current_process = GetCurrentProcessPointer(system.Kernel()); KHandleTable& handle_table = current_process->GetHandleTable(); - const auto resource_limit = current_process->GetResourceLimit(); - if (!resource_limit) { + if (auto const resource_limit = current_process->GetResourceLimit(); resource_limit) { + Handle resource_handle{}; + R_TRY(handle_table.Add(system.Kernel(), std::addressof(resource_handle), resource_limit)); + *result = resource_handle; + } else { *result = Svc::InvalidHandle; - // Yes, the kernel considers this a successful operation. - R_SUCCEED(); } - - Handle resource_handle{}; - R_TRY(handle_table.Add(std::addressof(resource_handle), resource_limit)); - - *result = resource_handle; + // Yes, the kernel considers this a successful operation either way. R_SUCCEED(); } - case InfoType::RandomEntropy: R_UNLESS(handle == 0, ResultInvalidHandle); R_UNLESS(info_sub_id < 4, ResultInvalidCombination); - *result = GetCurrentProcess(system.Kernel()).GetRandomEntropy(info_sub_id); R_SUCCEED(); - - case InfoType::InitialProcessIdRange: - LOG_WARNING(Kernel_SVC, - "(STUBBED) Attempted to query privileged process id bounds, returned 0"); - *result = 0; - R_SUCCEED(); - + case InfoType::InitialProcessIdRange: { + LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query privileged process id bounds, returned 0/64"); + R_UNLESS(handle == InvalidHandle, ResultInvalidHandle); + switch (InitialProcessIdRangeInfo(info_sub_id)) { + case InitialProcessIdRangeInfo::Minimum: + *result = 0; //todo + R_SUCCEED(); + case InitialProcessIdRangeInfo::Maximum: + *result = 64; //todo + R_SUCCEED(); + default: + R_THROW(ResultInvalidCombination); + } + } case InfoType::ThreadTickCount: { constexpr u64 num_cpus = 4; if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) { - LOG_ERROR(Kernel_SVC, "Core count is out of range, expected {} but got {}", num_cpus, - info_sub_id); + LOG_ERROR(Kernel_SVC, "Core count is out of range, expected {} but got {}", num_cpus, info_sub_id); R_THROW(ResultInvalidCombination); } KScopedAutoObject thread = GetCurrentProcess(system.Kernel()) - .GetHandleTable() - .GetObject(static_cast(handle)); + .GetHandleTable() + .GetObject(system.Kernel(), Handle(handle)); if (thread.IsNull()) { - LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}", - static_cast(handle)); + LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle={:#08x}", Handle(handle)); R_THROW(ResultInvalidHandle); } @@ -220,7 +188,6 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle u64 out_ticks = 0; if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) { const u64 thread_ticks = current_thread->GetCpuTime(); - out_ticks = thread_ticks + (core_timing.GetClockTicks() - prev_ctx_ticks); } else if (same_thread && info_sub_id == system.Kernel().CurrentPhysicalCoreIndex()) { out_ticks = core_timing.GetClockTicks() - prev_ctx_ticks; @@ -230,19 +197,47 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle R_SUCCEED(); } case InfoType::IdleTickCount: { - // Verify the input handle is invalid. - R_UNLESS(handle == InvalidHandle, ResultInvalidHandle); - // Verify the requested core is valid. const bool core_valid = - (info_sub_id == 0xFFFFFFFFFFFFFFFF) || - (info_sub_id == static_cast(system.Kernel().CurrentPhysicalCoreIndex())); + (info_sub_id == 0xFFFFFFFFFFFFFFFF) + || (info_sub_id == u64(system.Kernel().CurrentPhysicalCoreIndex())); R_UNLESS(core_valid, ResultInvalidCombination); + // Verify the input handle is invalid. + R_UNLESS(handle == InvalidHandle, ResultInvalidHandle); + // Get the idle tick count. *result = system.Kernel().CurrentScheduler()->GetIdleThread()->GetCpuTime(); R_SUCCEED(); } + case InfoType::Unknown37: + case InfoType::Unknown38: { + LOG_WARNING(Kernel_SVC, "(STUBBED) called, info_id={:#x}, info_sub_id={:#x}, handle={:#08x}", + info_id, info_sub_id, handle); + *result = 0; + R_SUCCEED(); + } + case InfoType::MesosphereMeta: { + enum MesosphereMetaInfo : u64 { + KernelVersion = 0, + IsKTraceEnabled = 1, + IsSingleStepEnabled = 2, + }; + R_UNLESS(handle == InvalidHandle, ResultInvalidHandle); + switch (MesosphereMetaInfo(info_sub_id)) { + case MesosphereMetaInfo::KernelVersion: + *result = Kernel::Svc::SupportedKernelVersion; + R_SUCCEED(); + case MesosphereMetaInfo::IsKTraceEnabled: + *result = 0; + R_SUCCEED(); + case MesosphereMetaInfo::IsSingleStepEnabled: + *result = 0; + R_SUCCEED(); + default: + R_THROW(ResultInvalidCombination); + } + } case InfoType::MesosphereCurrentProcess: { // Verify the input handle is invalid. R_UNLESS(handle == InvalidHandle, ResultInvalidHandle); @@ -255,17 +250,15 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle KHandleTable& handle_table = current_process->GetHandleTable(); // Get a new handle for the current process. - Handle tmp; - R_TRY(handle_table.Add(std::addressof(tmp), current_process)); + Handle tmp{}; + R_TRY(handle_table.Add(system.Kernel(), std::addressof(tmp), current_process)); // Set the output. *result = tmp; - - // We succeeded. R_SUCCEED(); } default: - LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id=0x{:016X}", info_id); + LOG_ERROR(Kernel_SVC, "Unimplemented svcGetInfo id={:#016x}", info_id); R_THROW(ResultInvalidEnumValue); } } diff --git a/src/core/hle/kernel/svc/svc_ipc.cpp b/src/core/hle/kernel/svc/svc_ipc.cpp index db912b71fb..875590d6f7 100644 --- a/src/core/hle/kernel/svc/svc_ipc.cpp +++ b/src/core/hle/kernel/svc/svc_ipc.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -20,19 +20,17 @@ namespace Kernel::Svc { namespace { -Result SendSyncRequestImpl(KernelCore& kernel, uintptr_t message, size_t buffer_size, - Handle session_handle) { +Result SendSyncRequestImpl(KernelCore& kernel, uintptr_t message, size_t buffer_size, Handle session_handle) { // Get the client session. - KScopedAutoObject session = - GetCurrentProcess(kernel).GetHandleTable().GetObject(session_handle); + KScopedAutoObject session = GetCurrentProcess(kernel).GetHandleTable().GetObject(kernel, session_handle); R_UNLESS(session.IsNotNull(), ResultInvalidHandle); // Get the parent, and persist a reference to it until we're done. - KScopedAutoObject parent = session->GetParent(); + KScopedAutoObject parent = {kernel, session->GetParent()}; ASSERT(parent.IsNotNull()); // Send the request. - R_RETURN(session->SendSyncRequest(message, buffer_size)); + R_RETURN(session->SendSyncRequest(kernel, message, buffer_size)); } Result ReplyAndReceiveImpl(KernelCore& kernel, int32_t* out_index, uintptr_t message, @@ -41,8 +39,7 @@ Result ReplyAndReceiveImpl(KernelCore& kernel, int32_t* out_index, uintptr_t mes int64_t timeout_ns) { // Reply to the target, if one is specified. if (reply_target != InvalidHandle) { - KScopedAutoObject session = - GetCurrentProcess(kernel).GetHandleTable().GetObject(reply_target); + KScopedAutoObject session = GetCurrentProcess(kernel).GetHandleTable().GetObject(kernel, reply_target); R_UNLESS(session.IsNotNull(), ResultInvalidHandle); // If we fail to reply, we want to set the output index to -1. @@ -51,7 +48,7 @@ Result ReplyAndReceiveImpl(KernelCore& kernel, int32_t* out_index, uintptr_t mes }; // Send the reply. - R_TRY(session->SendReply(message, buffer_size, message_paddr)); + R_TRY(session->SendReply(kernel, message, buffer_size, message_paddr)); } // Receive a message. @@ -77,8 +74,7 @@ Result ReplyAndReceiveImpl(KernelCore& kernel, int32_t* out_index, uintptr_t mes while (true) { // Wait for an object. s32 index; - Result result = KSynchronizationObject::Wait(kernel, std::addressof(index), objs, - num_objects, timeout); + Result result = KSynchronizationObject::Wait(kernel, std::addressof(index), objs, num_objects, timeout); if (ResultTimedOut == result) { R_THROW(result); } @@ -87,7 +83,7 @@ Result ReplyAndReceiveImpl(KernelCore& kernel, int32_t* out_index, uintptr_t mes if (R_SUCCEEDED(result)) { KServerSession* session = objs[index]->DynamicCast(); if (session != nullptr) { - result = session->ReceiveRequest(message, buffer_size, message_paddr); + result = session->ReceiveRequest(kernel, message, buffer_size, message_paddr); if (ResultNotFound == result) { continue; } @@ -127,14 +123,14 @@ Result ReplyAndReceiveImpl(KernelCore& kernel, int32_t* out_index, uintptr_t mes // Convert the handles to objects. R_UNLESS( - handle_table.GetMultipleObjects(objs, handles, num_handles), + handle_table.GetMultipleObjects(kernel, objs, handles, num_handles), ResultInvalidHandle); } // Ensure handles are closed when we're done. SCOPE_EXIT { for (auto i = 0; i < num_handles; ++i) { - objs[i]->Close(); + objs[i]->Close(kernel); } }; @@ -188,16 +184,15 @@ Result SendAsyncRequestWithUserBuffer(Core::System& system, Handle* out_event_ha auto& handle_table = process.GetHandleTable(); // Reserve a new event from the process resource limit. - KScopedResourceReservation event_reservation(std::addressof(process), - Svc::LimitableResource::EventCountMax); + KScopedResourceReservation event_reservation(system.Kernel(), std::addressof(process), Svc::LimitableResource::EventCountMax); R_UNLESS(event_reservation.Succeeded(), ResultLimitReached); // Get the client session. - KScopedAutoObject session = process.GetHandleTable().GetObject(session_handle); + KScopedAutoObject session = process.GetHandleTable().GetObject(system.Kernel(), session_handle); R_UNLESS(session.IsNotNull(), ResultInvalidHandle); // Get the parent, and persist a reference to it until we're done. - KScopedAutoObject parent = session->GetParent(); + KScopedAutoObject parent = {system.Kernel(), session->GetParent()}; ASSERT(parent.IsNotNull()); // Create a new event. @@ -205,30 +200,30 @@ Result SendAsyncRequestWithUserBuffer(Core::System& system, Handle* out_event_ha R_UNLESS(event != nullptr, ResultOutOfResource); // Initialize the event. - event->Initialize(std::addressof(process)); + event->Initialize(system.Kernel(), std::addressof(process)); // Commit our reservation. event_reservation.Commit(); // At end of scope, kill the standing references to the sub events. SCOPE_EXIT { - event->GetReadableEvent().Close(); - event->Close(); + event->GetReadableEvent().Close(system.Kernel()); + event->Close(system.Kernel()); }; // Register the event. KEvent::Register(system.Kernel(), event); // Add the readable event to the handle table. - R_TRY(handle_table.Add(out_event_handle, std::addressof(event->GetReadableEvent()))); + R_TRY(handle_table.Add(system.Kernel(), out_event_handle, std::addressof(event->GetReadableEvent()))); // Ensure that if we fail to send the request, we close the readable handle. ON_RESULT_FAILURE { - handle_table.Remove(*out_event_handle); + handle_table.Remove(system.Kernel(), *out_event_handle); }; // Send the async request. - R_RETURN(session->SendAsyncRequest(event, message, buffer_size)); + R_RETURN(session->SendAsyncRequest(system.Kernel(), event, message, buffer_size)); } Result ReplyAndReceive(Core::System& system, s32* out_index, uint64_t handles, s32 num_handles, diff --git a/src/core/hle/kernel/svc/svc_light_ipc.cpp b/src/core/hle/kernel/svc/svc_light_ipc.cpp index 4772cbda17..ae3ff85a88 100644 --- a/src/core/hle/kernel/svc/svc_light_ipc.cpp +++ b/src/core/hle/kernel/svc/svc_light_ipc.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -14,12 +17,12 @@ namespace Kernel::Svc { Result SendSyncRequestLight(Core::System& system, Handle session_handle, u32* args) { // Get the light client session from its handle. KScopedAutoObject session = GetCurrentProcess(system.Kernel()) - .GetHandleTable() - .GetObject(session_handle); + .GetHandleTable() + .GetObject(system.Kernel(), session_handle); R_UNLESS(session.IsNotNull(), ResultInvalidHandle); // Send the request. - R_TRY(session->SendSyncRequest(args)); + R_TRY(session->SendSyncRequest(system.Kernel(), args)); R_SUCCEED(); } @@ -27,12 +30,12 @@ Result SendSyncRequestLight(Core::System& system, Handle session_handle, u32* ar Result ReplyAndReceiveLight(Core::System& system, Handle session_handle, u32* args) { // Get the light server session from its handle. KScopedAutoObject session = GetCurrentProcess(system.Kernel()) - .GetHandleTable() - .GetObject(session_handle); + .GetHandleTable() + .GetObject(system.Kernel(), session_handle); R_UNLESS(session.IsNotNull(), ResultInvalidHandle); // Handle the request. - R_TRY(session->ReplyAndReceive(args)); + R_TRY(session->ReplyAndReceive(system.Kernel(), args)); R_SUCCEED(); } diff --git a/src/core/hle/kernel/svc/svc_lock.cpp b/src/core/hle/kernel/svc/svc_lock.cpp index c44317617e..6eeedb6281 100644 --- a/src/core/hle/kernel/svc/svc_lock.cpp +++ b/src/core/hle/kernel/svc/svc_lock.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -13,7 +13,7 @@ namespace Kernel::Svc { /// Attempts to locks a mutex Result ArbitrateLock(Core::System& system, Handle thread_handle, u64 address, u32 tag) { - LOG_TRACE(Kernel_SVC, "called thread_handle=0x{:08X}, address={:#X}, tag=0x{:08X}", + LOG_TRACE(Kernel_SVC, "called thread_handle={:#08x}, address={:#x}, tag={:#08x}", thread_handle, address, tag); // Validate the input address. @@ -25,7 +25,7 @@ Result ArbitrateLock(Core::System& system, Handle thread_handle, u64 address, u3 /// Unlock a mutex Result ArbitrateUnlock(Core::System& system, u64 address) { - LOG_TRACE(Kernel_SVC, "called address={:#X}", address); + LOG_TRACE(Kernel_SVC, "called address={:#x}", address); // Validate the input address. R_UNLESS(!IsKernelAddress(address), ResultInvalidCurrentMemory); diff --git a/src/core/hle/kernel/svc/svc_memory.cpp b/src/core/hle/kernel/svc/svc_memory.cpp index 740e11ff87..970912fe50 100644 --- a/src/core/hle/kernel/svc/svc_memory.cpp +++ b/src/core/hle/kernel/svc/svc_memory.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -32,15 +32,14 @@ constexpr bool IsValidAddressRange(u64 address, u64 size) { // Helper function that performs the common sanity checks for svcMapMemory // and svcUnmapMemory. This is doable, as both functions perform their sanitizing // in the same order. -Result MapUnmapMemorySanityChecks(const KProcessPageTable& manager, u64 dst_addr, u64 src_addr, - u64 size) { - if (!Common::Is4KBAligned(dst_addr)) { - LOG_ERROR(Kernel_SVC, "Destination address is not aligned to 4KB, 0x{:016X}", dst_addr); +Result MapUnmapMemorySanityChecks(const KProcessPageTable& manager, u64 dst_addr, u64 src_addr, u64 size) { + if (!Common::IsAligned(dst_addr, Core::Memory::YUZU_PAGESIZE)) { + LOG_ERROR(Kernel_SVC, "Destination address is not aligned to 4KB, {:#016X}", dst_addr); R_THROW(ResultInvalidAddress); } - if (!Common::Is4KBAligned(src_addr)) { - LOG_ERROR(Kernel_SVC, "Source address is not aligned to 4KB, 0x{:016X}", src_addr); + if (!Common::IsAligned(src_addr, Core::Memory::YUZU_PAGESIZE)) { + LOG_ERROR(Kernel_SVC, "Source address is not aligned to 4KB, {:#016X}", src_addr); R_THROW(ResultInvalidSize); } @@ -49,27 +48,27 @@ Result MapUnmapMemorySanityChecks(const KProcessPageTable& manager, u64 dst_addr R_THROW(ResultInvalidSize); } - if (!Common::Is4KBAligned(size)) { - LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:016X}", size); + if (!Common::IsAligned(size, Core::Memory::YUZU_PAGESIZE)) { + LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, {:#016X}", size); R_THROW(ResultInvalidSize); } if (!IsValidAddressRange(dst_addr, size)) { LOG_ERROR(Kernel_SVC, - "Destination is not a valid address range, addr=0x{:016X}, size=0x{:016X}", + "Destination is not a valid address range, addr={:#016x}, size={:#016x}", dst_addr, size); R_THROW(ResultInvalidCurrentMemory); } if (!IsValidAddressRange(src_addr, size)) { - LOG_ERROR(Kernel_SVC, "Source is not a valid address range, addr=0x{:016X}, size=0x{:016X}", + LOG_ERROR(Kernel_SVC, "Source is not a valid address range, addr={:#016x}, size={:#016x}", src_addr, size); R_THROW(ResultInvalidCurrentMemory); } if (!manager.Contains(src_addr, size)) { LOG_ERROR(Kernel_SVC, - "Source is not within the address space, addr=0x{:016X}, size=0x{:016X}", + "Source is not within the address space, addr={:#016x}, size={:#016x}", src_addr, size); R_THROW(ResultInvalidCurrentMemory); } @@ -80,7 +79,7 @@ Result MapUnmapMemorySanityChecks(const KProcessPageTable& manager, u64 dst_addr } // namespace Result SetMemoryPermission(Core::System& system, u64 address, u64 size, MemoryPermission perm) { - LOG_DEBUG(Kernel_SVC, "called, address=0x{:016X}, size={:#X}, perm=0x{:08X}", address, size, + LOG_DEBUG(Kernel_SVC, "called, address={:#016x}, size={:#x}, perm={:#08x}", address, size, perm); // Validate address / size. @@ -102,7 +101,7 @@ Result SetMemoryPermission(Core::System& system, u64 address, u64 size, MemoryPe Result SetMemoryAttribute(Core::System& system, u64 address, u64 size, u32 mask, u32 attr) { LOG_DEBUG(Kernel_SVC, - "called, address=0x{:016X}, size={:#X}, mask=0x{:08X}, attribute=0x{:08X}", address, + "called, address={:#016x}, size={:#x}, mask={:#08x}, attribute={:#08x}", address, size, mask, attr); // Validate address / size. @@ -133,7 +132,7 @@ Result SetMemoryAttribute(Core::System& system, u64 address, u64 size, u32 mask, /// Maps a memory range into a different range. Result MapMemory(Core::System& system, u64 dst_addr, u64 src_addr, u64 size) { - LOG_TRACE(Kernel_SVC, "called, dst_addr={:#X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, + LOG_TRACE(Kernel_SVC, "called, dst_addr={:#x}, src_addr={:#x}, size={:#x}", dst_addr, src_addr, size); auto& page_table{GetCurrentProcess(system.Kernel()).GetPageTable()}; @@ -148,7 +147,7 @@ Result MapMemory(Core::System& system, u64 dst_addr, u64 src_addr, u64 size) { /// Unmaps a region that was previously mapped with svcMapMemory Result UnmapMemory(Core::System& system, u64 dst_addr, u64 src_addr, u64 size) { - LOG_TRACE(Kernel_SVC, "called, dst_addr={:#X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, + LOG_TRACE(Kernel_SVC, "called, dst_addr={:#x}, src_addr={:#x}, size={:#x}", dst_addr, src_addr, size); auto& page_table{GetCurrentProcess(system.Kernel()).GetPageTable()}; diff --git a/src/core/hle/kernel/svc/svc_physical_memory.cpp b/src/core/hle/kernel/svc/svc_physical_memory.cpp index facf8ee638..48915b2b1e 100644 --- a/src/core/hle/kernel/svc/svc_physical_memory.cpp +++ b/src/core/hle/kernel/svc/svc_physical_memory.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -12,7 +12,7 @@ namespace Kernel::Svc { /// Set the process heap to a given Size. It can both extend and shrink the heap. Result SetHeapSize(Core::System& system, u64* out_address, u64 size) { - LOG_TRACE(Kernel_SVC, "called, heap_size={:#X}", size); + LOG_TRACE(Kernel_SVC, "called, heap_size={:#x}", size); // Validate size. R_UNLESS(Common::IsAligned(size, HeapSizeAlignment), ResultInvalidSize); @@ -31,14 +31,14 @@ Result SetHeapSize(Core::System& system, u64* out_address, u64 size) { /// Maps memory at a desired address Result MapPhysicalMemory(Core::System& system, u64 addr, u64 size) { - LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size={:#X}", addr, size); + LOG_DEBUG(Kernel_SVC, "called, addr={:#016x}, size={:#x}", addr, size); - if (!Common::Is4KBAligned(addr)) { - LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr); + if (!Common::IsAligned(addr, Core::Memory::YUZU_PAGESIZE)) { + LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, {:#016X}", addr); R_THROW(ResultInvalidAddress); } - if (!Common::Is4KBAligned(size)) { + if (!Common::IsAligned(size, Core::Memory::YUZU_PAGESIZE)) { LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, {:#X}", size); R_THROW(ResultInvalidSize); } @@ -63,14 +63,14 @@ Result MapPhysicalMemory(Core::System& system, u64 addr, u64 size) { if (!page_table.Contains(addr, size)) { LOG_ERROR(Kernel_SVC, - "Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr, + "Address is not within the address space, addr={:#016x}, size={:#016x}", addr, size); R_THROW(ResultInvalidMemoryRegion); } if (!page_table.IsInAliasRegion(addr, size)) { LOG_ERROR(Kernel_SVC, - "Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr, + "Address is not within the alias region, addr={:#016x}, size={:#016x}", addr, size); R_THROW(ResultInvalidMemoryRegion); } @@ -80,14 +80,14 @@ Result MapPhysicalMemory(Core::System& system, u64 addr, u64 size) { /// Unmaps memory previously mapped via MapPhysicalMemory Result UnmapPhysicalMemory(Core::System& system, u64 addr, u64 size) { - LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size={:#X}", addr, size); + LOG_DEBUG(Kernel_SVC, "called, addr={:#016x}, size={:#x}", addr, size); - if (!Common::Is4KBAligned(addr)) { - LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr); + if (!Common::IsAligned(addr, Core::Memory::YUZU_PAGESIZE)) { + LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, {:#016X}", addr); R_THROW(ResultInvalidAddress); } - if (!Common::Is4KBAligned(size)) { + if (!Common::IsAligned(size, Core::Memory::YUZU_PAGESIZE)) { LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, {:#X}", size); R_THROW(ResultInvalidSize); } @@ -112,14 +112,14 @@ Result UnmapPhysicalMemory(Core::System& system, u64 addr, u64 size) { if (!page_table.Contains(addr, size)) { LOG_ERROR(Kernel_SVC, - "Address is not within the address space, addr=0x{:016X}, size=0x{:016X}", addr, + "Address is not within the address space, addr={:#016x}, size={:#016x}", addr, size); R_THROW(ResultInvalidMemoryRegion); } if (!page_table.IsInAliasRegion(addr, size)) { LOG_ERROR(Kernel_SVC, - "Address is not within the alias region, addr=0x{:016X}, size=0x{:016X}", addr, + "Address is not within the alias region, addr={:#016x}, size={:#016x}", addr, size); R_THROW(ResultInvalidMemoryRegion); } diff --git a/src/core/hle/kernel/svc/svc_port.cpp b/src/core/hle/kernel/svc/svc_port.cpp index 9a22dadaf7..bddd57e8f9 100644 --- a/src/core/hle/kernel/svc/svc_port.cpp +++ b/src/core/hle/kernel/svc/svc_port.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -33,18 +36,18 @@ Result ConnectToNamedPort(Core::System& system, Handle* out, u64 user_name) { // Reserve a handle for the port. // NOTE: Nintendo really does write directly to the output handle here. - R_TRY(handle_table.Reserve(out)); + R_TRY(handle_table.Reserve(system.Kernel(), out)); ON_RESULT_FAILURE { - handle_table.Unreserve(*out); + handle_table.Unreserve(system.Kernel(), *out); }; // Create a session. KClientSession* session; - R_TRY(port->CreateSession(std::addressof(session))); + R_TRY(port->CreateSession(system.Kernel(), std::addressof(session))); // Register the session in the table, close the extra reference. - handle_table.Register(*out, session); - session->Close(); + handle_table.Register(system.Kernel(), *out, session); + session->Close(system.Kernel()); // We succeeded. R_SUCCEED(); @@ -65,27 +68,27 @@ Result CreatePort(Core::System& system, Handle* out_server, Handle* out_client, R_UNLESS(port != nullptr, ResultOutOfResource); // Initialize the port. - port->Initialize(max_sessions, is_light, name); + port->Initialize(system.Kernel(), max_sessions, is_light, name); // Ensure that we clean up the port (and its only references are handle table) on function end. SCOPE_EXIT { - port->GetServerPort().Close(); - port->GetClientPort().Close(); + port->GetServerPort().Close(system.Kernel()); + port->GetClientPort().Close(system.Kernel()); }; // Register the port. KPort::Register(kernel, port); // Add the client to the handle table. - R_TRY(handle_table.Add(out_client, std::addressof(port->GetClientPort()))); + R_TRY(handle_table.Add(system.Kernel(), out_client, std::addressof(port->GetClientPort()))); // Ensure that we maintain a clean handle state on exit. ON_RESULT_FAILURE { - handle_table.Remove(*out_client); + handle_table.Remove(system.Kernel(), *out_client); }; // Add the server to the handle table. - R_RETURN(handle_table.Add(out_server, std::addressof(port->GetServerPort()))); + R_RETURN(handle_table.Add(system.Kernel(), out_server, std::addressof(port->GetServerPort()))); } Result ConnectToPort(Core::System& system, Handle* out, Handle port) { @@ -93,36 +96,33 @@ Result ConnectToPort(Core::System& system, Handle* out, Handle port) { auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable(); // Get the client port. - KScopedAutoObject client_port = handle_table.GetObject(port); + KScopedAutoObject client_port = handle_table.GetObject(system.Kernel(), port); R_UNLESS(client_port.IsNotNull(), ResultInvalidHandle); // Reserve a handle for the port. // NOTE: Nintendo really does write directly to the output handle here. - R_TRY(handle_table.Reserve(out)); + R_TRY(handle_table.Reserve(system.Kernel(), out)); ON_RESULT_FAILURE { - handle_table.Unreserve(*out); + handle_table.Unreserve(system.Kernel(), *out); }; // Create the session. KAutoObject* session; - if (client_port->IsLight()) { - R_TRY(client_port->CreateLightSession( - reinterpret_cast(std::addressof(session)))); + if (client_port->IsLight(system.Kernel())) { + R_TRY(client_port->CreateLightSession(system.Kernel(), reinterpret_cast(std::addressof(session)))); } else { - R_TRY(client_port->CreateSession( - reinterpret_cast(std::addressof(session)))); + R_TRY(client_port->CreateSession(system.Kernel(), reinterpret_cast(std::addressof(session)))); } // Register the session. - handle_table.Register(*out, session); - session->Close(); + handle_table.Register(system.Kernel(), *out, session); + session->Close(system.Kernel()); // We succeeded. R_SUCCEED(); } -Result ManageNamedPort(Core::System& system, Handle* out_server_handle, uint64_t user_name, - int32_t max_sessions) { +Result ManageNamedPort(Core::System& system, Handle* out_server_handle, uint64_t user_name, int32_t max_sessions) { // Copy the provided name from user memory to kernel memory. auto string_name = GetCurrentMemory(system.Kernel()).ReadCString(user_name, KObjectName::NameLengthMax); @@ -144,21 +144,21 @@ Result ManageNamedPort(Core::System& system, Handle* out_server_handle, uint64_t R_UNLESS(port != nullptr, ResultOutOfResource); // Initialize the new port. - port->Initialize(max_sessions, false, 0); + port->Initialize(system.Kernel(), max_sessions, false, 0); // Register the port. KPort::Register(system.Kernel(), port); // Ensure that our only reference to the port is in the handle table when we're done. SCOPE_EXIT { - port->GetClientPort().Close(); - port->GetServerPort().Close(); + port->GetClientPort().Close(system.Kernel()); + port->GetServerPort().Close(system.Kernel()); }; // Register the handle in the table. - R_TRY(handle_table.Add(out_server_handle, std::addressof(port->GetServerPort()))); + R_TRY(handle_table.Add(system.Kernel(), out_server_handle, std::addressof(port->GetServerPort()))); ON_RESULT_FAILURE { - handle_table.Remove(*out_server_handle); + handle_table.Remove(system.Kernel(), *out_server_handle); }; // Create a new object name. diff --git a/src/core/hle/kernel/svc/svc_process.cpp b/src/core/hle/kernel/svc/svc_process.cpp index 6d63892a94..af1bdc7807 100644 --- a/src/core/hle/kernel/svc/svc_process.cpp +++ b/src/core/hle/kernel/svc/svc_process.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -23,12 +23,12 @@ void ExitProcess(Core::System& system) { /// Gets the ID of the specified process or a specified thread's owning process. Result GetProcessId(Core::System& system, u64* out_process_id, Handle handle) { - LOG_DEBUG(Kernel_SVC, "called handle=0x{:08X}", handle); + LOG_DEBUG(Kernel_SVC, "called handle={:#08x}", handle); // Get the object from the handle table. KScopedAutoObject obj = GetCurrentProcess(system.Kernel()) - .GetHandleTable() - .GetObject(static_cast(handle)); + .GetHandleTable() + .GetObject(system.Kernel(), Handle(handle)); R_UNLESS(obj.IsNotNull(), ResultInvalidHandle); // Get the process from the object. @@ -55,7 +55,7 @@ Result GetProcessId(Core::System& system, u64* out_process_id, Handle handle) { Result GetProcessList(Core::System& system, s32* out_num_processes, u64 out_process_ids, int32_t out_process_ids_size) { - LOG_DEBUG(Kernel_SVC, "called. out_process_ids=0x{:016X}, out_process_ids_size={}", + LOG_DEBUG(Kernel_SVC, "called. out_process_ids={:#016x}, out_process_ids_size={}", out_process_ids, out_process_ids_size); // If the supplied size is negative or greater than INT32_MAX / sizeof(u64), bail. @@ -71,7 +71,7 @@ Result GetProcessList(Core::System& system, s32* out_num_processes, u64 out_proc if (out_process_ids_size > 0 && !GetCurrentProcess(kernel).GetPageTable().Contains(out_process_ids, total_copy_size)) { - LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}", + LOG_ERROR(Kernel_SVC, "Address range outside address space. begin={:#016x}, end={:#016x}", out_process_ids, out_process_ids + total_copy_size); R_THROW(ResultInvalidCurrentMemory); } @@ -95,12 +95,12 @@ Result GetProcessList(Core::System& system, s32* out_num_processes, u64 out_proc Result GetProcessInfo(Core::System& system, s64* out, Handle process_handle, ProcessInfoType info_type) { - LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type={:#X}", process_handle, info_type); + LOG_DEBUG(Kernel_SVC, "called, handle={:#08x}, type={:#x}", process_handle, info_type); const auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable(); - KScopedAutoObject process = handle_table.GetObject(process_handle); + KScopedAutoObject process = handle_table.GetObject(system.Kernel(), process_handle); if (process.IsNull()) { - LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}", + LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle={:#08x}", process_handle); R_THROW(ResultInvalidHandle); } diff --git a/src/core/hle/kernel/svc/svc_process_memory.cpp b/src/core/hle/kernel/svc/svc_process_memory.cpp index 3313118dfa..4490ba8d05 100644 --- a/src/core/hle/kernel/svc/svc_process_memory.cpp +++ b/src/core/hle/kernel/svc/svc_process_memory.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -32,7 +32,7 @@ constexpr bool IsValidProcessMemoryPermission(Svc::MemoryPermission perm) { Result SetProcessMemoryPermission(Core::System& system, Handle process_handle, u64 address, u64 size, Svc::MemoryPermission perm) { LOG_TRACE(Kernel_SVC, - "called, process_handle={:#X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}", + "called, process_handle={:#x}, addr={:#x}, size={:#x}, permissions={:#08x}", process_handle, address, size, perm); // Validate the address/size. @@ -48,7 +48,7 @@ Result SetProcessMemoryPermission(Core::System& system, Handle process_handle, u // Get the process from its handle. KScopedAutoObject process = - GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(process_handle); + GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(system.Kernel(), process_handle); R_UNLESS(process.IsNotNull(), ResultInvalidHandle); // Validate that the address is in range. @@ -62,7 +62,7 @@ Result SetProcessMemoryPermission(Core::System& system, Handle process_handle, u Result MapProcessMemory(Core::System& system, u64 dst_address, Handle process_handle, u64 src_address, u64 size) { LOG_TRACE(Kernel_SVC, - "called, dst_address={:#X}, process_handle=0x{:X}, src_address=0x{:X}, size=0x{:X}", + "called, dst_address={:#x}, process_handle={:#x}, src_address={:#x}, size={:#x}", dst_address, process_handle, src_address, size); // Validate the address/size. @@ -76,7 +76,7 @@ Result MapProcessMemory(Core::System& system, u64 dst_address, Handle process_ha // Get the processes. KProcess* dst_process = GetCurrentProcessPointer(system.Kernel()); KScopedAutoObject src_process = - dst_process->GetHandleTable().GetObjectWithoutPseudoHandle(process_handle); + dst_process->GetHandleTable().GetObjectWithoutPseudoHandle(system.Kernel(), process_handle); R_UNLESS(src_process.IsNotNull(), ResultInvalidHandle); // Get the page tables. @@ -103,7 +103,7 @@ Result MapProcessMemory(Core::System& system, u64 dst_address, Handle process_ha Result UnmapProcessMemory(Core::System& system, u64 dst_address, Handle process_handle, u64 src_address, u64 size) { LOG_TRACE(Kernel_SVC, - "called, dst_address={:#X}, process_handle=0x{:X}, src_address=0x{:X}, size=0x{:X}", + "called, dst_address={:#x}, process_handle={:#x}, src_address={:#x}, size={:#x}", dst_address, process_handle, src_address, size); // Validate the address/size. @@ -117,7 +117,7 @@ Result UnmapProcessMemory(Core::System& system, u64 dst_address, Handle process_ // Get the processes. KProcess* dst_process = GetCurrentProcessPointer(system.Kernel()); KScopedAutoObject src_process = - dst_process->GetHandleTable().GetObjectWithoutPseudoHandle(process_handle); + dst_process->GetHandleTable().GetObjectWithoutPseudoHandle(system.Kernel(), process_handle); R_UNLESS(src_process.IsNotNull(), ResultInvalidHandle); // Get the page tables. @@ -136,47 +136,47 @@ Result UnmapProcessMemory(Core::System& system, u64 dst_address, Handle process_ Result MapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address, u64 src_address, u64 size) { LOG_DEBUG(Kernel_SVC, - "called. process_handle=0x{:08X}, dst_address=0x{:016X}, " - "src_address=0x{:016X}, size=0x{:016X}", + "called. process_handle={:#08x}, dst_address={:#016x}, " + "src_address={:#016x}, size={:#016x}", process_handle, dst_address, src_address, size); - if (!Common::Is4KBAligned(src_address)) { - LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address=0x{:016X}).", + if (!Common::IsAligned(src_address, Core::Memory::YUZU_PAGESIZE)) { + LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address={:#016X}).", src_address); R_THROW(ResultInvalidAddress); } - if (!Common::Is4KBAligned(dst_address)) { - LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address=0x{:016X}).", + if (!Common::IsAligned(dst_address, Core::Memory::YUZU_PAGESIZE)) { + LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address={:#016X}).", dst_address); R_THROW(ResultInvalidAddress); } - if (size == 0 || !Common::Is4KBAligned(size)) { - LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X})", size); + if (size == 0 || !Common::IsAligned(size, Core::Memory::YUZU_PAGESIZE)) { + LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size={:#016X})", size); R_THROW(ResultInvalidSize); } if (!IsValidAddressRange(dst_address, size)) { LOG_ERROR(Kernel_SVC, - "Destination address range overflows the address space (dst_address=0x{:016X}, " - "size=0x{:016X}).", + "Destination address range overflows the address space (dst_address={:#016x}, " + "size={:#016x}).", dst_address, size); R_THROW(ResultInvalidCurrentMemory); } if (!IsValidAddressRange(src_address, size)) { LOG_ERROR(Kernel_SVC, - "Source address range overflows the address space (src_address=0x{:016X}, " - "size=0x{:016X}).", + "Source address range overflows the address space (src_address={:#016x}, " + "size={:#016x}).", src_address, size); R_THROW(ResultInvalidCurrentMemory); } const auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable(); - KScopedAutoObject process = handle_table.GetObject(process_handle); + KScopedAutoObject process = handle_table.GetObject(system.Kernel(), process_handle); if (process.IsNull()) { - LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).", + LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle={:#08x}).", process_handle); R_THROW(ResultInvalidHandle); } @@ -184,59 +184,60 @@ Result MapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst auto& page_table = process->GetPageTable(); if (!page_table.Contains(src_address, size)) { LOG_ERROR(Kernel_SVC, - "Source address range is not within the address space (src_address=0x{:016X}, " - "size=0x{:016X}).", + "Source address range is not within the address space (src_address={:#016x}, " + "size={:#016x}).", src_address, size); R_THROW(ResultInvalidCurrentMemory); } + R_UNLESS(page_table.CanContain(dst_address, size, KMemoryState::AliasCode), ResultInvalidCurrentMemory); R_RETURN(page_table.MapCodeMemory(dst_address, src_address, size)); } Result UnmapProcessCodeMemory(Core::System& system, Handle process_handle, u64 dst_address, u64 src_address, u64 size) { LOG_DEBUG(Kernel_SVC, - "called. process_handle=0x{:08X}, dst_address=0x{:016X}, src_address=0x{:016X}, " - "size=0x{:016X}", + "called. process_handle={:#08x}, dst_address={:#016x}, src_address={:#016x}, " + "size={:#016x}", process_handle, dst_address, src_address, size); - if (!Common::Is4KBAligned(dst_address)) { - LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address=0x{:016X}).", + if (!Common::IsAligned(dst_address, Core::Memory::YUZU_PAGESIZE)) { + LOG_ERROR(Kernel_SVC, "dst_address is not page-aligned (dst_address={:#016X}).", dst_address); R_THROW(ResultInvalidAddress); } - if (!Common::Is4KBAligned(src_address)) { - LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address=0x{:016X}).", + if (!Common::IsAligned(src_address, Core::Memory::YUZU_PAGESIZE)) { + LOG_ERROR(Kernel_SVC, "src_address is not page-aligned (src_address={:#016X}).", src_address); R_THROW(ResultInvalidAddress); } - if (size == 0 || !Common::Is4KBAligned(size)) { - LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size=0x{:016X}).", size); + if (size == 0 || !Common::IsAligned(size, Core::Memory::YUZU_PAGESIZE)) { + LOG_ERROR(Kernel_SVC, "Size is zero or not page-aligned (size={:#016X}).", size); R_THROW(ResultInvalidSize); } if (!IsValidAddressRange(dst_address, size)) { LOG_ERROR(Kernel_SVC, - "Destination address range overflows the address space (dst_address=0x{:016X}, " - "size=0x{:016X}).", + "Destination address range overflows the address space (dst_address={:#016x}, " + "size={:#016x}).", dst_address, size); R_THROW(ResultInvalidCurrentMemory); } if (!IsValidAddressRange(src_address, size)) { LOG_ERROR(Kernel_SVC, - "Source address range overflows the address space (src_address=0x{:016X}, " - "size=0x{:016X}).", + "Source address range overflows the address space (src_address={:#016x}, " + "size={:#016x}).", src_address, size); R_THROW(ResultInvalidCurrentMemory); } const auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable(); - KScopedAutoObject process = handle_table.GetObject(process_handle); + KScopedAutoObject process = handle_table.GetObject(system.Kernel(), process_handle); if (process.IsNull()) { - LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle=0x{:08X}).", + LOG_ERROR(Kernel_SVC, "Invalid process handle specified (handle={:#08x}).", process_handle); R_THROW(ResultInvalidHandle); } @@ -244,12 +245,13 @@ Result UnmapProcessCodeMemory(Core::System& system, Handle process_handle, u64 d auto& page_table = process->GetPageTable(); if (!page_table.Contains(src_address, size)) { LOG_ERROR(Kernel_SVC, - "Source address range is not within the address space (src_address=0x{:016X}, " - "size=0x{:016X}).", + "Source address range is not within the address space (src_address={:#016x}, " + "size={:#016x}).", src_address, size); R_THROW(ResultInvalidCurrentMemory); } + R_UNLESS(page_table.CanContain(dst_address, size, KMemoryState::AliasCode), ResultInvalidCurrentMemory); R_RETURN(page_table.UnmapCodeMemory(dst_address, src_address, size)); } diff --git a/src/core/hle/kernel/svc/svc_query_memory.cpp b/src/core/hle/kernel/svc/svc_query_memory.cpp index 816dcb8d0f..5600edd273 100644 --- a/src/core/hle/kernel/svc/svc_query_memory.cpp +++ b/src/core/hle/kernel/svc/svc_query_memory.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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,8 +13,8 @@ namespace Kernel::Svc { Result QueryMemory(Core::System& system, uint64_t out_memory_info, PageInfo* out_page_info, u64 query_address) { LOG_TRACE(Kernel_SVC, - "called, out_memory_info=0x{:016X}, " - "query_address=0x{:016X}", + "called, out_memory_info={:#016x}, " + "query_address={:#016x}", out_memory_info, query_address); // Query memory is just QueryProcessMemory on the current process. @@ -21,11 +24,11 @@ Result QueryMemory(Core::System& system, uint64_t out_memory_info, PageInfo* out Result QueryProcessMemory(Core::System& system, uint64_t out_memory_info, PageInfo* out_page_info, Handle process_handle, uint64_t address) { - LOG_TRACE(Kernel_SVC, "called process=0x{:08X} address={:X}", process_handle, address); + LOG_TRACE(Kernel_SVC, "called process={:#08x} address={:X}", process_handle, address); const auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable(); - KScopedAutoObject process = handle_table.GetObject(process_handle); + KScopedAutoObject process = handle_table.GetObject(system.Kernel(), process_handle); if (process.IsNull()) { - LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}", + LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle={:#08x}", process_handle); R_THROW(ResultInvalidHandle); } diff --git a/src/core/hle/kernel/svc/svc_resource_limit.cpp b/src/core/hle/kernel/svc/svc_resource_limit.cpp index 6f3972482f..7f78aa8b10 100644 --- a/src/core/hle/kernel/svc/svc_resource_limit.cpp +++ b/src/core/hle/kernel/svc/svc_resource_limit.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -19,7 +22,7 @@ Result CreateResourceLimit(Core::System& system, Handle* out_handle) { // Ensure we don't leak a reference to the limit. SCOPE_EXIT { - resource_limit->Close(); + resource_limit->Close(system.Kernel()); }; // Initialize the resource limit. @@ -29,7 +32,7 @@ Result CreateResourceLimit(Core::System& system, Handle* out_handle) { KResourceLimit::Register(kernel, resource_limit); // Add the limit to the handle table. - R_RETURN(GetCurrentProcess(kernel).GetHandleTable().Add(out_handle, resource_limit)); + R_RETURN(GetCurrentProcess(kernel).GetHandleTable().Add(system.Kernel(), out_handle, resource_limit)); } Result GetResourceLimitLimitValue(Core::System& system, s64* out_limit_value, @@ -42,8 +45,8 @@ Result GetResourceLimitLimitValue(Core::System& system, s64* out_limit_value, // Get the resource limit. KScopedAutoObject resource_limit = GetCurrentProcess(system.Kernel()) - .GetHandleTable() - .GetObject(resource_limit_handle); + .GetHandleTable() + .GetObject(system.Kernel(), resource_limit_handle); R_UNLESS(resource_limit.IsNotNull(), ResultInvalidHandle); // Get the limit value. @@ -62,8 +65,8 @@ Result GetResourceLimitCurrentValue(Core::System& system, s64* out_current_value // Get the resource limit. KScopedAutoObject resource_limit = GetCurrentProcess(system.Kernel()) - .GetHandleTable() - .GetObject(resource_limit_handle); + .GetHandleTable() + .GetObject(system.Kernel(), resource_limit_handle); R_UNLESS(resource_limit.IsNotNull(), ResultInvalidHandle); // Get the current value. @@ -83,7 +86,7 @@ Result SetResourceLimitLimitValue(Core::System& system, Handle resource_limit_ha // Get the resource limit. KScopedAutoObject resource_limit = GetCurrentProcess(system.Kernel()) .GetHandleTable() - .GetObject(resource_limit_handle); + .GetObject(system.Kernel(), resource_limit_handle); R_UNLESS(resource_limit.IsNotNull(), ResultInvalidHandle); // Set the limit value. diff --git a/src/core/hle/kernel/svc/svc_session.cpp b/src/core/hle/kernel/svc/svc_session.cpp index b034d21d15..4d04f0c5a3 100644 --- a/src/core/hle/kernel/svc/svc_session.cpp +++ b/src/core/hle/kernel/svc/svc_session.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -23,7 +26,7 @@ Result CreateSession(Core::System& system, Handle* out_server, Handle* out_clien // Reserve a new session from the process resource limit. // TODO: Dynamic resource limits - KScopedResourceReservation session_reservation(std::addressof(process), + KScopedResourceReservation session_reservation(system.Kernel(), std::addressof(process), LimitableResource::SessionCountMax); if (session_reservation.Succeeded()) { session = T::Create(system.Kernel()); @@ -62,7 +65,7 @@ Result CreateSession(Core::System& system, Handle* out_server, Handle* out_clien R_UNLESS(session != nullptr, ResultOutOfResource); // Initialize the session. - session->Initialize(nullptr, name); + session->Initialize(system.Kernel(), nullptr, name); // Commit the session reservation. session_reservation.Commit(); @@ -70,23 +73,23 @@ Result CreateSession(Core::System& system, Handle* out_server, Handle* out_clien // Ensure that we clean up the session (and its only references are handle table) on function // end. SCOPE_EXIT { - session->GetClientSession().Close(); - session->GetServerSession().Close(); + session->GetClientSession().Close(system.Kernel()); + session->GetServerSession().Close(system.Kernel()); }; // Register the session. T::Register(system.Kernel(), session); // Add the server session to the handle table. - R_TRY(handle_table.Add(out_server, std::addressof(session->GetServerSession()))); + R_TRY(handle_table.Add(system.Kernel(), out_server, std::addressof(session->GetServerSession()))); // Ensure that we maintain a clean handle state on exit. ON_RESULT_FAILURE { - handle_table.Remove(*out_server); + handle_table.Remove(system.Kernel(), *out_server); }; // Add the client session to the handle table. - R_RETURN(handle_table.Add(out_client, std::addressof(session->GetClientSession()))); + R_RETURN(handle_table.Add(system.Kernel(), out_client, std::addressof(session->GetClientSession()))); } } // namespace @@ -105,29 +108,29 @@ Result AcceptSession(Core::System& system, Handle* out, Handle port_handle) { auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable(); // Get the server port. - KScopedAutoObject port = handle_table.GetObject(port_handle); + KScopedAutoObject port = handle_table.GetObject(system.Kernel(), port_handle); R_UNLESS(port.IsNotNull(), ResultInvalidHandle); // Reserve an entry for the new session. - R_TRY(handle_table.Reserve(out)); + R_TRY(handle_table.Reserve(system.Kernel(), out)); ON_RESULT_FAILURE { - handle_table.Unreserve(*out); + handle_table.Unreserve(system.Kernel(), *out); }; // Accept the session. KAutoObject* session; - if (port->IsLight()) { - session = port->AcceptLightSession(); + if (port->IsLight(system.Kernel())) { + session = port->AcceptLightSession(system.Kernel()); } else { - session = port->AcceptSession(); + session = port->AcceptSession(system.Kernel()); } // Ensure we accepted successfully. R_UNLESS(session != nullptr, ResultNotFound); // Register the session. - handle_table.Register(*out, session); - session->Close(); + handle_table.Register(system.Kernel(), *out, session); + session->Close(system.Kernel()); R_SUCCEED(); } diff --git a/src/core/hle/kernel/svc/svc_shared_memory.cpp b/src/core/hle/kernel/svc/svc_shared_memory.cpp index 3ca07abe8b..23e01fdf45 100644 --- a/src/core/hle/kernel/svc/svc_shared_memory.cpp +++ b/src/core/hle/kernel/svc/svc_shared_memory.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -32,7 +32,7 @@ constexpr bool IsValidSharedMemoryPermission(MemoryPermission perm) { Result MapSharedMemory(Core::System& system, Handle shmem_handle, u64 address, u64 size, Svc::MemoryPermission map_perm) { LOG_TRACE(Kernel_SVC, - "called, shared_memory_handle={:#X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}", + "called, shared_memory_handle={:#x}, addr={:#x}, size={:#x}, permissions={:#08x}", shmem_handle, address, size, map_perm); // Validate the address/size. @@ -49,18 +49,18 @@ Result MapSharedMemory(Core::System& system, Handle shmem_handle, u64 address, u auto& page_table = process.GetPageTable(); // Get the shared memory. - KScopedAutoObject shmem = process.GetHandleTable().GetObject(shmem_handle); + KScopedAutoObject shmem = process.GetHandleTable().GetObject(system.Kernel(), shmem_handle); R_UNLESS(shmem.IsNotNull(), ResultInvalidHandle); // Verify that the mapping is in range. R_UNLESS(page_table.CanContain(address, size, KMemoryState::Shared), ResultInvalidMemoryRegion); // Add the shared memory to the process. - R_TRY(process.AddSharedMemory(shmem.GetPointerUnsafe(), address, size)); + R_TRY(process.AddSharedMemory(system.Kernel(), shmem.GetPointerUnsafe(), address, size)); // Ensure that we clean up the shared memory if we fail to map it. ON_RESULT_FAILURE { - process.RemoveSharedMemory(shmem.GetPointerUnsafe(), address, size); + process.RemoveSharedMemory(system.Kernel(), shmem.GetPointerUnsafe(), address, size); }; // Map the shared memory. @@ -79,7 +79,7 @@ Result UnmapSharedMemory(Core::System& system, Handle shmem_handle, u64 address, auto& page_table = process.GetPageTable(); // Get the shared memory. - KScopedAutoObject shmem = process.GetHandleTable().GetObject(shmem_handle); + KScopedAutoObject shmem = process.GetHandleTable().GetObject(system.Kernel(), shmem_handle); R_UNLESS(shmem.IsNotNull(), ResultInvalidHandle); // Verify that the mapping is in range. @@ -89,7 +89,7 @@ Result UnmapSharedMemory(Core::System& system, Handle shmem_handle, u64 address, R_TRY(shmem->Unmap(process, address, size)); // Remove the shared memory from the process. - process.RemoveSharedMemory(shmem.GetPointerUnsafe(), address, size); + process.RemoveSharedMemory(system.Kernel(), shmem.GetPointerUnsafe(), address, size); R_SUCCEED(); } diff --git a/src/core/hle/kernel/svc/svc_synchronization.cpp b/src/core/hle/kernel/svc/svc_synchronization.cpp index fdd4408d4d..2b019c52d1 100644 --- a/src/core/hle/kernel/svc/svc_synchronization.cpp +++ b/src/core/hle/kernel/svc/svc_synchronization.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -17,10 +17,10 @@ namespace Kernel::Svc { /// Close a handle Result CloseHandle(Core::System& system, Handle handle) { - LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle); + LOG_TRACE(Kernel_SVC, "Closing handle {:#08x}", handle); // Remove the handle. - R_UNLESS(GetCurrentProcess(system.Kernel()).GetHandleTable().Remove(handle), + R_UNLESS(GetCurrentProcess(system.Kernel()).GetHandleTable().Remove(system.Kernel(), handle), ResultInvalidHandle); R_SUCCEED(); @@ -28,24 +28,24 @@ Result CloseHandle(Core::System& system, Handle handle) { /// Clears the signaled state of an event or process. Result ResetSignal(Core::System& system, Handle handle) { - LOG_DEBUG(Kernel_SVC, "called handle 0x{:08X}", handle); + LOG_TRACE(Kernel_SVC, "called handle {:#08x}", handle); // Get the current handle table. const auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable(); // Try to reset as readable event. { - KScopedAutoObject readable_event = handle_table.GetObject(handle); + KScopedAutoObject readable_event = handle_table.GetObject(system.Kernel(), handle); if (readable_event.IsNotNull()) { - R_RETURN(readable_event->Reset()); + R_RETURN(readable_event->Reset(system.Kernel())); } } // Try to reset as process. { - KScopedAutoObject process = handle_table.GetObject(handle); + KScopedAutoObject process = handle_table.GetObject(system.Kernel(), handle); if (process.IsNotNull()) { - R_RETURN(process->Reset()); + R_RETURN(process->Reset(system.Kernel())); } } @@ -62,35 +62,30 @@ Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_ha R_UNLESS(0 <= num_handles && num_handles <= Svc::ArgumentHandleCountMax, ResultOutOfRange); // Get the synchronization context. - auto& kernel = system.Kernel(); - auto& handle_table = GetCurrentProcess(kernel).GetHandleTable(); - auto objs = GetCurrentThread(kernel).GetSynchronizationObjectBuffer(); - auto handles = GetCurrentThread(kernel).GetHandleBuffer(); + auto& handle_table = GetCurrentProcess(system.Kernel()).GetHandleTable(); + auto objs = GetCurrentThread(system.Kernel()).GetSynchronizationObjectBuffer(); + auto handles = GetCurrentThread(system.Kernel()).GetHandleBuffer(); // Copy user handles. if (num_handles > 0) { // Get the handles. - R_UNLESS(GetCurrentMemory(kernel).ReadBlock(user_handles, handles.data(), - sizeof(Handle) * num_handles), - ResultInvalidPointer); + R_UNLESS(GetCurrentMemory(system.Kernel()).ReadBlock(user_handles, handles.data(), sizeof(Handle) * num_handles), ResultInvalidPointer); // Convert the handles to objects. - R_UNLESS(handle_table.GetMultipleObjects( - objs.data(), handles.data(), num_handles), - ResultInvalidHandle); + R_UNLESS(handle_table.GetMultipleObjects(system.Kernel(), objs.data(), handles.data(), num_handles), ResultInvalidHandle); } // Ensure handles are closed when we're done. SCOPE_EXIT { for (auto i = 0; i < num_handles; ++i) { - objs[i]->Close(); + objs[i]->Close(system.Kernel()); } }; // Convert the timeout from nanoseconds to ticks. s64 timeout; if (timeout_ns > 0) { - u64 ticks = kernel.HardwareTimer().GetTick(); + u64 ticks = system.Kernel().HardwareTimer().GetTick(); ticks += timeout_ns; ticks += 2; @@ -100,7 +95,7 @@ Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_ha } // Wait on the objects. - Result res = KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout); + Result res = KSynchronizationObject::Wait(system.Kernel(), out_index, objs.data(), num_handles, timeout); R_SUCCEED_IF(res == ResultSessionClosed); R_RETURN(res); @@ -108,34 +103,31 @@ Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_ha /// Resumes a thread waiting on WaitSynchronization Result CancelSynchronization(Core::System& system, Handle handle) { - LOG_TRACE(Kernel_SVC, "called handle={:#X}", handle); + LOG_TRACE(Kernel_SVC, "called handle={:#x}", handle); // Get the thread from its handle. - KScopedAutoObject thread = - GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(handle); + KScopedAutoObject thread = GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(system.Kernel(), handle); R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); // Cancel the thread's wait. - thread->WaitCancel(); + thread->WaitCancel(system.Kernel()); R_SUCCEED(); } void SynchronizePreemptionState(Core::System& system) { - auto& kernel = system.Kernel(); - // Lock the scheduler. - KScopedSchedulerLock sl{kernel}; + KScopedSchedulerLock sl{system.Kernel()}; // If the current thread is pinned, unpin it. - KProcess* cur_process = GetCurrentProcessPointer(kernel); - const auto core_id = GetCurrentCoreId(kernel); + KProcess* cur_process = GetCurrentProcessPointer(system.Kernel()); + const auto core_id = GetCurrentCoreId(system.Kernel()); - if (cur_process->GetPinnedThread(core_id) == GetCurrentThreadPointer(kernel)) { + if (cur_process->GetPinnedThread(core_id) == GetCurrentThreadPointer(system.Kernel())) { // Clear the current thread's interrupt flag. - GetCurrentThread(kernel).ClearInterruptFlag(); + GetCurrentThread(system.Kernel()).ClearInterruptFlag(system.Kernel()); // Unpin the current thread. - cur_process->UnpinCurrentThread(); + cur_process->UnpinCurrentThread(system.Kernel()); } } diff --git a/src/core/hle/kernel/svc/svc_thread.cpp b/src/core/hle/kernel/svc/svc_thread.cpp index 1aa6f87c04..dbca13dd26 100644 --- a/src/core/hle/kernel/svc/svc_thread.cpp +++ b/src/core/hle/kernel/svc/svc_thread.cpp @@ -26,8 +26,8 @@ constexpr bool IsValidVirtualCoreId(int32_t core_id) { Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u64 arg, u64 stack_bottom, s32 priority, s32 core_id) { LOG_DEBUG(Kernel_SVC, - "called entry_point=0x{:08X}, arg=0x{:08X}, stack_bottom=0x{:08X}, " - "priority=0x{:08X}, core_id=0x{:08X}", + "called entry_point={:#08x}, arg={:#08x}, stack_bottom={:#08x}, " + "priority={:#08x}, core_id={:#08x}", entry_point, arg, stack_bottom, priority, core_id); // Adjust core id, if it's the default magic. @@ -46,7 +46,7 @@ Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority); // Reserve a new thread from the process resource limit (waiting up to 100ms). - KScopedResourceReservation thread_reservation(std::addressof(process), + KScopedResourceReservation thread_reservation(system.Kernel(), std::addressof(process), LimitableResource::ThreadCountMax, 1, kernel.HardwareTimer().GetTick() + 100000000); R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached); @@ -55,7 +55,7 @@ Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u KThread* thread = KThread::Create(kernel); R_UNLESS(thread != nullptr, ResultOutOfResource) SCOPE_EXIT { - thread->Close(); + thread->Close(system.Kernel()); }; // Initialize the thread. @@ -69,13 +69,13 @@ Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u thread_reservation.Commit(); // Clone the current fpu status to the new thread. - thread->CloneFpuStatus(); + thread->CloneFpuStatus(system.Kernel()); // Register the new thread. KThread::Register(kernel, thread); // Add the thread to the handle table. - R_TRY(process.GetHandleTable().Add(out_handle, thread)); + R_TRY(process.GetHandleTable().Add(system.Kernel(), out_handle, thread)); // Pass the thread handle to the thread local region. process.GetMemory().Write32(GetInteger(thread->GetTlsAddress()) + 0x110, *out_handle); @@ -85,15 +85,14 @@ Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u /// Starts the thread for the provided handle Result StartThread(Core::System& system, Handle thread_handle) { - LOG_DEBUG(Kernel_SVC, "called thread=0x{:08X}", thread_handle); + LOG_DEBUG(Kernel_SVC, "called thread={:#08x}", thread_handle); // Get the thread from its handle. - KScopedAutoObject thread = - GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(thread_handle); + KScopedAutoObject thread = GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(system.Kernel(), thread_handle); R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); // Try to start the thread. - R_TRY(thread->Run()); + R_TRY(thread->Run(system.Kernel())); R_SUCCEED(); } @@ -102,7 +101,7 @@ Result StartThread(Core::System& system, Handle thread_handle) { void ExitThread(Core::System& system) { auto* const current_thread = GetCurrentThreadPointer(system.Kernel()); system.GlobalSchedulerContext().RemoveThread(current_thread); - current_thread->Exit(); + current_thread->Exit(system.Kernel()); } /// Sleep the current thread @@ -130,7 +129,7 @@ void SleepThread(Core::System& system, s64 ns) { // Sleep. // NOTE: Nintendo does not check the result of this sleep. - static_cast(GetCurrentThread(kernel).Sleep(timeout)); + static_cast(GetCurrentThread(kernel).Sleep(kernel, timeout)); } else if (yield_type == Svc::YieldType::WithoutCoreMigration) { KScheduler::YieldWithoutCoreMigration(kernel); } else if (yield_type == Svc::YieldType::WithCoreMigration) { @@ -144,27 +143,23 @@ void SleepThread(Core::System& system, s64 ns) { /// Gets the thread context Result GetThreadContext3(Core::System& system, u64 out_context, Handle thread_handle) { - LOG_DEBUG(Kernel_SVC, "called, out_context=0x{:08X}, thread_handle={:#X}", out_context, - thread_handle); - - auto& kernel = system.Kernel(); + LOG_DEBUG(Kernel_SVC, "called, out_context={:#08x}, thread_handle={:#x}", out_context, thread_handle); // Get the thread from its handle. - KScopedAutoObject thread = - GetCurrentProcess(kernel).GetHandleTable().GetObject(thread_handle); + KScopedAutoObject thread = GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(system.Kernel(), thread_handle); R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); // Require the handle be to a non-current thread in the current process. - R_UNLESS(thread->GetOwnerProcess() == GetCurrentProcessPointer(kernel), ResultInvalidHandle); - R_UNLESS(thread.GetPointerUnsafe() != GetCurrentThreadPointer(kernel), ResultBusy); + R_UNLESS(thread->GetOwnerProcess() == GetCurrentProcessPointer(system.Kernel()), ResultInvalidHandle); + R_UNLESS(thread.GetPointerUnsafe() != GetCurrentThreadPointer(system.Kernel()), ResultBusy); // Get the thread context. Svc::ThreadContext context{}; - R_TRY(thread->GetThreadContext3(std::addressof(context))); + R_TRY(thread->GetThreadContext3(system.Kernel(), std::addressof(context))); // Copy the thread context to user space. R_UNLESS( - GetCurrentMemory(kernel).WriteBlock(out_context, std::addressof(context), sizeof(context)), + GetCurrentMemory(system.Kernel()).WriteBlock(out_context, std::addressof(context), sizeof(context)), ResultInvalidPointer); R_SUCCEED(); @@ -175,8 +170,7 @@ Result GetThreadPriority(Core::System& system, s32* out_priority, Handle handle) LOG_TRACE(Kernel_SVC, "called"); // Get the thread from its handle. - KScopedAutoObject thread = - GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(handle); + KScopedAutoObject thread = GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(system.Kernel(), handle); R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); // Get the thread's priority. @@ -195,11 +189,11 @@ Result SetThreadPriority(Core::System& system, Handle thread_handle, s32 priorit R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority); // Get the thread from its handle. - KScopedAutoObject thread = process.GetHandleTable().GetObject(thread_handle); + KScopedAutoObject thread = process.GetHandleTable().GetObject(system.Kernel(), thread_handle); R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); // Set the thread priority. - thread->SetBasePriority(priority); + thread->SetBasePriority(system.Kernel(), priority); R_SUCCEED(); } @@ -208,7 +202,7 @@ Result GetThreadList(Core::System& system, s32* out_num_threads, u64 out_thread_ // TODO: Handle this case when debug events are supported. UNIMPLEMENTED_IF(debug_handle != InvalidHandle); - LOG_DEBUG(Kernel_SVC, "called. out_thread_ids=0x{:016X}, out_thread_ids_size={}", + LOG_DEBUG(Kernel_SVC, "called. out_thread_ids={:#016x}, out_thread_ids_size={}", out_thread_ids, out_thread_ids_size); // If the size is negative or larger than INT32_MAX / sizeof(u64) @@ -223,7 +217,7 @@ Result GetThreadList(Core::System& system, s32* out_num_threads, u64 out_thread_ if (out_thread_ids_size > 0 && !current_process->GetPageTable().Contains(out_thread_ids, total_copy_size)) { - LOG_ERROR(Kernel_SVC, "Address range outside address space. begin=0x{:016X}, end=0x{:016X}", + LOG_ERROR(Kernel_SVC, "Address range outside address space. begin={:#016x}, end={:#016x}", out_thread_ids, out_thread_ids + total_copy_size); R_THROW(ResultInvalidCurrentMemory); } @@ -245,15 +239,14 @@ Result GetThreadList(Core::System& system, s32* out_num_threads, u64 out_thread_ Result GetThreadCoreMask(Core::System& system, s32* out_core_id, u64* out_affinity_mask, Handle thread_handle) { - LOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle); + LOG_TRACE(Kernel_SVC, "called, handle={:#08x}", thread_handle); // Get the thread from its handle. - KScopedAutoObject thread = - GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(thread_handle); + KScopedAutoObject thread = GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(system.Kernel(), thread_handle); R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); // Get the core mask. - R_RETURN(thread->GetCoreMask(out_core_id, out_affinity_mask)); + R_RETURN(thread->GetCoreMask(system.Kernel(), out_core_id, out_affinity_mask)); } Result SetThreadCoreMask(Core::System& system, Handle thread_handle, s32 core_id, @@ -279,18 +272,17 @@ Result SetThreadCoreMask(Core::System& system, Handle thread_handle, s32 core_id // Get the thread from its handle. KScopedAutoObject thread = - GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(thread_handle); + GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(system.Kernel(), thread_handle); R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); // Set the core mask. - R_RETURN(thread->SetCoreMask(core_id, affinity_mask)); + R_RETURN(thread->SetCoreMask(system.Kernel(), core_id, affinity_mask)); } /// Get the ID for the specified thread. Result GetThreadId(Core::System& system, u64* out_thread_id, Handle thread_handle) { // Get the thread from its handle. - KScopedAutoObject thread = - GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(thread_handle); + KScopedAutoObject thread = GetCurrentProcess(system.Kernel()).GetHandleTable().GetObject(system.Kernel(), thread_handle); R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); // Get the thread's id. diff --git a/src/core/hle/kernel/svc/svc_transfer_memory.cpp b/src/core/hle/kernel/svc/svc_transfer_memory.cpp index 2ea0d44215..e539eee911 100644 --- a/src/core/hle/kernel/svc/svc_transfer_memory.cpp +++ b/src/core/hle/kernel/svc/svc_transfer_memory.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -43,8 +46,7 @@ Result CreateTransferMemory(Core::System& system, Handle* out, u64 address, u64 auto& handle_table = process.GetHandleTable(); // Reserve a new transfer memory from the process resource limit. - KScopedResourceReservation trmem_reservation(std::addressof(process), - LimitableResource::TransferMemoryCountMax); + KScopedResourceReservation trmem_reservation(system.Kernel(), std::addressof(process), LimitableResource::TransferMemoryCountMax); R_UNLESS(trmem_reservation.Succeeded(), ResultLimitReached); // Create the transfer memory. @@ -53,14 +55,14 @@ Result CreateTransferMemory(Core::System& system, Handle* out, u64 address, u64 // Ensure the only reference is in the handle table when we're done. SCOPE_EXIT { - trmem->Close(); + trmem->Close(system.Kernel()); }; // Ensure that the region is in range. R_UNLESS(process.GetPageTable().Contains(address, size), ResultInvalidCurrentMemory); // Initialize the transfer memory. - R_TRY(trmem->Initialize(address, size, map_perm)); + R_TRY(trmem->Initialize(system.Kernel(), address, size, map_perm)); // Commit the reservation. trmem_reservation.Commit(); @@ -69,7 +71,7 @@ Result CreateTransferMemory(Core::System& system, Handle* out, u64 address, u64 KTransferMemory::Register(kernel, trmem); // Add the transfer memory to the handle table. - R_RETURN(handle_table.Add(out, trmem)); + R_RETURN(handle_table.Add(system.Kernel(), out, trmem)); } Result MapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address, uint64_t size, @@ -86,7 +88,7 @@ Result MapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t add // Get the transfer memory. KScopedAutoObject trmem = GetCurrentProcess(system.Kernel()) .GetHandleTable() - .GetObject(trmem_handle); + .GetObject(system.Kernel(), trmem_handle); R_UNLESS(trmem.IsNotNull(), ResultInvalidHandle); // Verify that the mapping is in range. @@ -96,7 +98,7 @@ Result MapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t add ResultInvalidMemoryRegion); // Map the transfer memory. - R_TRY(trmem->Map(address, size, map_perm)); + R_TRY(trmem->Map(system.Kernel(), address, size, map_perm)); // We succeeded. R_SUCCEED(); @@ -113,7 +115,7 @@ Result UnmapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t a // Get the transfer memory. KScopedAutoObject trmem = GetCurrentProcess(system.Kernel()) .GetHandleTable() - .GetObject(trmem_handle); + .GetObject(system.Kernel(), trmem_handle); R_UNLESS(trmem.IsNotNull(), ResultInvalidHandle); // Verify that the mapping is in range. @@ -123,7 +125,7 @@ Result UnmapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t a ResultInvalidMemoryRegion); // Unmap the transfer memory. - R_TRY(trmem->Unmap(address, size)); + R_TRY(trmem->Unmap(system.Kernel(), address, size)); R_SUCCEED(); } diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h index fce3dea476..b0f4796da4 100644 --- a/src/core/hle/kernel/svc_types.h +++ b/src/core/hle/kernel/svc_types.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -158,6 +158,11 @@ enum class InfoType : u32 { IoRegionHint = 27, AliasRegionExtraSize = 28, + TransferMemoryHint = 34, + + Unknown37 = 37, + Unknown38 = 38, + MesosphereMeta = 65000, MesosphereCurrentProcess = 65001, }; diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 06ebb22be3..da100ede90 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -6,11 +6,11 @@ #pragma once +#include #include "common/assert.h" #include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" -#include "common/expected.h" // All the constants in this file come from diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index c33b765000..d68b6bde0d 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -21,10 +21,6 @@ #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/hle/service/acc/acc.h" -#include "core/hle/service/acc/acc_aa.h" -#include "core/hle/service/acc/acc_su.h" -#include "core/hle/service/acc/acc_u0.h" -#include "core/hle/service/acc/acc_u1.h" #include "core/hle/service/acc/async_context.h" #include "core/hle/service/acc/errors.h" #include "core/hle/service/acc/profile_manager.h" @@ -729,7 +725,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(ensure_token_id); + rb.PushIpcInterface(ctx, ensure_token_id); } void LoadIdTokenCacheDeprecated(HLERequestContext& ctx) { @@ -921,7 +917,7 @@ void Module::Interface::GetProfile(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system, user_id, *profile_manager); + rb.PushIpcInterface(ctx, system, user_id, *profile_manager); } void Module::Interface::IsUserRegistrationRequestPermitted(HLERequestContext& ctx) { @@ -993,7 +989,7 @@ void Module::Interface::GetBaasAccountManagerForApplication(HLERequestContext& c LOG_DEBUG(Service_ACC, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system, profile_manager); + rb.PushIpcInterface(ctx, system, profile_manager); } void Module::Interface::IsUserAccountSwitchLocked(HLERequestContext& ctx) { @@ -1089,7 +1085,7 @@ void Module::Interface::GetProfileEditor(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system, user_id, *profile_manager); + rb.PushIpcInterface(ctx, system, user_id, *profile_manager); } void Module::Interface::GetBaasAccountAdministrator(HLERequestContext &ctx) { @@ -1100,7 +1096,7 @@ void Module::Interface::GetBaasAccountAdministrator(HLERequestContext &ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system, uuid); + rb.PushIpcInterface(ctx, system, uuid); } void Module::Interface::ListQualifiedUsers(HLERequestContext& ctx) { @@ -1143,7 +1139,7 @@ void Module::Interface::GetBaasAccountManagerForSystemService(HLERequestContext& IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system, uuid); + rb.PushIpcInterface(ctx, system, uuid); } void Module::Interface::StoreSaveDataThumbnailSystem(HLERequestContext& ctx) { @@ -1244,19 +1240,417 @@ Module::Interface::Interface(std::shared_ptr module_, Module::Interface::~Interface() = default; +class ACC_AA final : public Module::Interface { +public: + explicit ACC_AA(std::shared_ptr module_, std::shared_ptr profile_manager_, Core::System& system_) + : Interface(std::move(module_), std::move(profile_manager_), system_, "acc:aa") { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "EnsureCacheAsync"}, + {1, nullptr, "LoadCache"}, + {2, nullptr, "GetDeviceAccountId"}, + {50, nullptr, "RegisterNotificationTokenAsync"}, // 1.0.0 - 6.2.0 + {51, nullptr, "UnregisterNotificationTokenAsync"}, // 1.0.0 - 6.2.0 + }; + // clang-format on + RegisterHandlers(functions); + } + ~ACC_AA() override = default; +}; + +class ACC_SU final : public Module::Interface { +public: + explicit ACC_SU(std::shared_ptr module_, std::shared_ptr profile_manager_, Core::System& system_) + : Interface(std::move(module_), std::move(profile_manager_), system_, "acc:su") { + // clang-format off + static const FunctionInfo functions[] = { + {0, &ACC_SU::GetUserCount, "GetUserCount"}, + {1, &ACC_SU::GetUserExistence, "GetUserExistence"}, + {2, &ACC_SU::ListAllUsers, "ListAllUsers"}, + {3, &ACC_SU::ListOpenUsers, "ListOpenUsers"}, + {4, &ACC_SU::GetLastOpenedUser, "GetLastOpenedUser"}, + {5, &ACC_SU::GetProfile, "GetProfile"}, + {6, nullptr, "GetProfileDigest"}, + {50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, + {51, &ACC_SU::TrySelectUserWithoutInteractionDeprecated, "TrySelectUserWithoutInteractionDeprecated"}, + {52, &ACC_SU::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, // 19.0.0+ + {60, &ACC_SU::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, + {99, nullptr, "DebugActivateOpenContextRetention"}, + {100, nullptr, "GetUserRegistrationNotifier"}, + {101, nullptr, "GetUserStateChangeNotifier"}, + {102, &ACC_SU::GetBaasAccountManagerForSystemService, "GetBaasAccountManagerForSystemService"}, + {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"}, + {104, nullptr, "GetProfileUpdateNotifier"}, + {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, + {106, nullptr, "GetProfileSyncNotifier"}, + {110, &ACC_SU::StoreSaveDataThumbnailSystem, "StoreSaveDataThumbnail"}, + {111, nullptr, "ClearSaveDataThumbnail"}, + {112, nullptr, "LoadSaveDataThumbnail"}, + {113, nullptr, "GetSaveDataThumbnailExistence"}, + {120, nullptr, "ListOpenUsersInApplication"}, + {130, nullptr, "ActivateOpenContextRetention"}, + {140, &ACC_SU::ListQualifiedUsers, "ListQualifiedUsers"}, + {150, nullptr, "AuthenticateApplicationAsync"}, + {151, nullptr, "EnsureSignedDeviceIdentifierCacheForNintendoAccountAsync"}, + {152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"}, + {190, nullptr, "GetUserLastOpenedApplication"}, + {191, nullptr, "ActivateOpenContextHolder"}, + {200, &ACC_SU::BeginUserRegistration, "BeginUserRegistration"}, + {201, &ACC_SU::CompleteUserRegistration, "CompleteUserRegistration"}, + {202, nullptr, "CancelUserRegistration"}, + {203, &ACC_SU::DeleteUser, "DeleteUser"}, + {204, &ACC_SU::SetUserPosition, "SetUserPosition"}, + {205, &ACC_SU::GetProfileEditor, "GetProfileEditor"}, + {206, nullptr, "CompleteUserRegistrationForcibly"}, + {210, nullptr, "CreateFloatingRegistrationRequest"}, + {211, nullptr, "CreateProcedureToRegisterUserWithNintendoAccount"}, + {212, nullptr, "ResumeProcedureToRegisterUserWithNintendoAccount"}, + {230, nullptr, "AuthenticateServiceAsync"}, + {250, &ACC_SU::GetBaasAccountAdministrator, "GetBaasAccountAdministrator"}, + {290, nullptr, "ProxyProcedureForGuestLoginWithNintendoAccount"}, + {291, nullptr, "ProxyProcedureForFloatingRegistrationWithNintendoAccount"}, + {299, nullptr, "SuspendBackgroundDaemon"}, + {400, nullptr, "SetPinCode"}, // 18.0.0+ + {401, &ACC_SU::GetPinCodeLength, "GetPinCodeLength"}, // 18.0.0+ + {402, nullptr, "GetPinCode"}, // 18.0.0+ + {403, nullptr, "GetPinCodeParity"}, + {404, nullptr, "VerifyPinCode"}, + {405, nullptr, "IsPinCodeVerificationForbidden"}, + {410, nullptr, "GetPinCodeErrorCount"}, // 18.0.0+ + {411, nullptr, "ResetPinCodeErrorCount"}, // 18.0.0+ + {412, nullptr, "IncrementPinCodeErrorCount"}, // 18.0.0+ + {900, nullptr, "SetUserUnqualifiedForDebug"}, + {901, nullptr, "UnsetUserUnqualifiedForDebug"}, + {902, nullptr, "ListUsersUnqualifiedForDebug"}, + {910, nullptr, "RefreshFirmwareSettingsForDebug"}, + {997, nullptr, "DebugInvalidateTokenCacheForUser"}, + {998, nullptr, "DebugSetUserStateClose"}, + {999, nullptr, "DebugSetUserStateOpen"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + ~ACC_SU() override = default; +}; + +class ACC_U0 final : public Module::Interface { +public: + ACC_U0(std::shared_ptr module_, std::shared_ptr profile_manager_, Core::System& system_) + : Interface(std::move(module_), std::move(profile_manager_), system_, "acc:u0") { + // clang-format off + static const FunctionInfo functions[] = { + {0, &ACC_U0::GetUserCount, "GetUserCount"}, + {1, &ACC_U0::GetUserExistence, "GetUserExistence"}, + {2, &ACC_U0::ListAllUsers, "ListAllUsers"}, + {3, &ACC_U0::ListOpenUsers, "ListOpenUsers"}, + {4, &ACC_U0::GetLastOpenedUser, "GetLastOpenedUser"}, + {5, &ACC_U0::GetProfile, "GetProfile"}, + {6, nullptr, "GetProfileDigest"}, // 3.0.0+ + {50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, + {51, &ACC_U0::TrySelectUserWithoutInteractionDeprecated, "TrySelectUserWithoutInteractionDeprecated"}, + {52, &ACC_U0::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, + {60, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0 + {99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+ + {100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"}, + {101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"}, + {102, nullptr, "AuthenticateApplicationAsync"}, + {103, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+ + {110, &ACC_U0::StoreSaveDataThumbnailApplication, "StoreSaveDataThumbnail"}, + {111, nullptr, "ClearSaveDataThumbnail"}, + {120, nullptr, "CreateGuestLoginRequest"}, + {130, nullptr, "LoadOpenContext"}, // 5.0.0+ + {131, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 6.0.0+ + {140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"}, // 6.0.0+ + {141, &ACC_U0::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+ + {150, &ACC_U0::IsUserAccountSwitchLocked, "IsUserAccountSwitchLocked"}, // 6.0.0+ + {160, &ACC_U0::InitializeApplicationInfoV2, "InitializeApplicationInfoV2"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + ~ACC_U0() override = default; +}; + +class ACC_U1 final : public Module::Interface { +public: + ACC_U1(std::shared_ptr module_, std::shared_ptr profile_manager_, Core::System& system_) + : Interface(std::move(module_), std::move(profile_manager_), system_, "acc:u1") { + // clang-format off + static const FunctionInfo functions[] = { + {0, &ACC_U1::GetUserCount, "GetUserCount"}, + {1, &ACC_U1::GetUserExistence, "GetUserExistence"}, + {2, &ACC_U1::ListAllUsers, "ListAllUsers"}, + {3, &ACC_U1::ListOpenUsers, "ListOpenUsers"}, + {4, &ACC_U1::GetLastOpenedUser, "GetLastOpenedUser"}, + {5, &ACC_U1::GetProfile, "GetProfile"}, + {6, nullptr, "GetProfileDigest"}, + {50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, + {51, &ACC_U1::TrySelectUserWithoutInteractionDeprecated, "TrySelectUserWithoutInteractionDeprecated"}, + {52, &ACC_U1::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, + {60, &ACC_U1::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, + {99, nullptr, "DebugActivateOpenContextRetention"}, + {100, nullptr, "GetUserRegistrationNotifier"}, + {101, nullptr, "GetUserStateChangeNotifier"}, + {102, &ACC_U1::GetBaasAccountManagerForSystemService, "GetBaasAccountManagerForSystemService"}, + {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"}, + {104, nullptr, "GetProfileUpdateNotifier"}, + {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, + {106, nullptr, "GetProfileSyncNotifier"}, + {110, &ACC_U1::StoreSaveDataThumbnailApplication, "StoreSaveDataThumbnail"}, + {111, nullptr, "ClearSaveDataThumbnail"}, + {112, nullptr, "LoadSaveDataThumbnail"}, + {113, nullptr, "GetSaveDataThumbnailExistence"}, + {120, nullptr, "ListOpenUsersInApplication"}, + {130, nullptr, "ActivateOpenContextRetention"}, + {140, &ACC_U1::ListQualifiedUsers, "ListQualifiedUsers"}, + {150, nullptr, "AuthenticateApplicationAsync"}, + {151, nullptr, "EnsureSignedDeviceIdentifierCacheForNintendoAccountAsync"}, + {152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"}, + {190, nullptr, "GetUserLastOpenedApplication"}, + {191, nullptr, "ActivateOpenContextHolder"}, + {401, &ACC_U1::GetPinCodeLength, "GetPinCodeLength"}, // 18.0.0+ + {402, nullptr, "GetPinCode"}, // 18.0.0+ + {997, nullptr, "DebugInvalidateTokenCacheForUser"}, + {998, nullptr, "DebugSetUserStateClose"}, + {999, nullptr, "DebugSetUserStateOpen"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + ~ACC_U1() override = default; +}; + +class DAUTH_0 final : public ServiceFramework { +public: + explicit DAUTH_0(Core::System& system_) : ServiceFramework{system_, "dauth:0"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "EnsureAuthenticationTokenCacheAsync"}, + {1, nullptr, "LoadAuthenticationTokenCache"}, + {2, nullptr, "InvalidateAuthenticationTokenCache"}, + {3, nullptr, "IsDeviceAuthenticationTokenCacheAvailable"}, + {10, nullptr, "EnsureEdgeTokenCacheAsync"}, + {11, nullptr, "LoadEdgeTokenCache"}, + {12, nullptr, "InvalidateEdgeTokenCache"}, + {13, nullptr, "IsEdgeTokenCacheAvailable"}, + {20, nullptr, "EnsureApplicationAuthenticationCacheAsync"}, + {21, nullptr, "LoadApplicationAuthenticationTokenCache"}, + {22, nullptr, "LoadApplicationNetworkServiceClientConfigCache"}, + {23, nullptr, "IsApplicationAuthenticationCacheAvailable"}, + {24, nullptr, "InvalidateApplicationAuthenticationCache"}, + {30, nullptr, "EnsureGameCardAuthenticationCacheAsync"}, + {31, nullptr, "LoadGameCardAuthenticationTokenCache"}, + {32, nullptr, "IsGameCardAuthenticationCacheAvailable"}, + {33, nullptr, "InvalidateGameCardAuthenticationCache"}, + {1000, nullptr, "GetInactiveElicenseUsedEvent"}, + {9000, nullptr, "ImportVirtualClientCertificate"}, + {9010, nullptr, "DeleteVirtualClientCertificate"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class ACC_E final : public Module::Interface { +public: + explicit ACC_E(std::shared_ptr module_, std::shared_ptr profile_manager_, Core::System& system_) + : Interface(std::move(module_), std::move(profile_manager_), system_, "acc:e") { + // clang-format off + static const FunctionInfo functions[] = { + { 0, nullptr, "GetUserCount"}, + { 1, nullptr, "GetUserExistence"}, + { 2, nullptr, "ListAllUsers"}, + { 3, nullptr, "ListOpenUsers"}, + { 4, nullptr, "GetLastOpenedUser"}, + { 5, nullptr, "GetProfile"}, + { 6, nullptr, "GetProfileDigest"}, + { 50, nullptr, "IsUserRegistrationRequestPermitted"}, + { 51, nullptr, "TrySelectUserWithoutInteractionDeprecated"}, + { 52, nullptr, "TrySelectUserWithoutInteraction"}, + { 99, nullptr, "DebugActivateOpenContextRetention"}, + { 100, nullptr, "GetUserRegistrationNotifier"}, + { 101, nullptr, "GetUserStateChangeNotifier"}, + { 102, nullptr, "GetBaasAccountManagerForSystemService"}, + { 103, nullptr, "GetBaasUserAvailabilityChangeNotifier"}, + { 104, nullptr, "GetProfileUpdateNotifier"}, + { 105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, + { 106, nullptr, "GetProfileSyncNotifier"}, + { 110, nullptr, "StoreSaveDataThumbnail"}, + { 111, nullptr, "ClearSaveDataThumbnail"}, + { 112, nullptr, "LoadSaveDataThumbnail"}, + { 113, nullptr, "GetSaveDataThumbnailExistence"}, + { 120, nullptr, "ListOpenUsersInApplication"}, + { 130, nullptr, "ActivateOpenContextRetention"}, + { 140, nullptr, "ListQualifiedUsers"}, + { 151, nullptr, "EnsureSignedDeviceIdentifierCacheForNintendoAccountAsync"}, + { 152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"}, + { 170, nullptr, "GetNasOp2MembershipStateChangeNotifier"}, + { 191, nullptr, "UpdateNotificationReceiverInfo"}, + { 200, nullptr, "BeginUserRegistration"}, + { 201, nullptr, "CompleteUserRegistration"}, + { 202, nullptr, "CancelUserRegistration"}, + { 203, nullptr, "DeleteUser"}, + { 204, nullptr, "SetUserPosition"}, + { 205, nullptr, "GetProfileEditor"}, + { 206, nullptr, "CompleteUserRegistrationForcibly"}, + { 210, nullptr, "CreateFloatingRegistrationRequest"}, + { 211, nullptr, "CreateProcedureToRegisterUserWithNintendoAccount"}, + { 212, nullptr, "ResumeProcedureToRegisterUserWithNintendoAccount"}, + { 213, nullptr, "CreateProcedureToCreateUserWithNintendoAccount"}, + { 214, nullptr, "ResumeProcedureToCreateUserWithNintendoAccount"}, + { 215, nullptr, "ResumeProcedureToCreateUserWithNintendoAccountAfterApplyResponse"}, + { 230, nullptr, "AuthenticateServiceAsync"}, + { 250, nullptr, "GetBaasAccountAdministrator"}, + { 251, nullptr, "SynchronizeNetworkServiceAccountsSnapshotAsync"}, + { 290, nullptr, "ProxyProcedureForGuestLoginWithNintendoAccount"}, + { 291, nullptr, "ProxyProcedureForFloatingRegistrationWithNintendoAccount"}, + { 292, nullptr, "ProxyProcedureForDeviceMigrationAuthenticatingOperatingUser"}, + { 293, nullptr, "ProxyProcedureForDeviceMigrationDownload"}, + { 299, nullptr, "SuspendBackgroundDaemon"}, + { 350, nullptr, "CreateDeviceMigrationUserExportRequest"}, + { 351, nullptr, "UploadNasCredential"}, + { 352, nullptr, "CreateDeviceMigrationUserImportRequest"}, + { 353, nullptr, "DeleteUserMigrationSaveData"}, + { 400, nullptr, "SetPinCode"}, + { 401, nullptr, "GetPinCodeLength"}, + { 402, nullptr, "GetPinCode"}, + { 403, nullptr, "GetPinCodeParity"}, + { 404, nullptr, "VerifyPinCode"}, + { 405, nullptr, "IsPinCodeVerificationForbidden"}, + { 410, nullptr, "GetPinCodeErrorCount"}, + { 411, nullptr, "ResetPinCodeErrorCount"}, + { 412, nullptr, "IncrementPinCodeErrorCount"}, + { 413, nullptr, "SetPinCodeErrorCount"}, + { 420, nullptr, "SetStartPenaltyTime"}, + { 421, nullptr, "GetStartPenaltyTime"}, + { 900, nullptr, "SetUserUnqualifiedForDebug"}, + { 901, nullptr, "UnsetUserUnqualifiedForDebug"}, + { 902, nullptr, "ListUsersUnqualifiedForDebug"}, + { 910, nullptr, "RefreshFirmwareSettingsForDebug"}, + { 997, nullptr, "DebugInvalidateTokenCacheForUser"}, + { 998, nullptr, "DebugSetUserStateClose"}, + { 999, nullptr, "DebugSetUserStateOpen"}, + { 1000, nullptr, "CreateIAccountEntityServiceForApplication"}, + { 1100, nullptr, "CreateIUserStateManager"}, + { 10050, nullptr, "IsUserRegistrationRequestPermittedForAccountPolicy"}, + { 10105, nullptr, "CheckNetworkServiceAvailabilityAsyncForAccountPolicy"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class ACC_E_U1 final : public Module::Interface { +public: + explicit ACC_E_U1(std::shared_ptr module_, std::shared_ptr profile_manager_, Core::System& system_) + : Interface(std::move(module_), std::move(profile_manager_), system_, "acc:e:u1") { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetUserCount"}, + {1, nullptr, "GetUserExistence"}, + {2, nullptr, "ListAllUsers"}, + {3, nullptr, "ListOpenUsers"}, + {4, nullptr, "GetLastOpenedUser"}, + {5, nullptr, "GetProfile"}, + {6, nullptr, "GetProfileDigest"}, + {50, nullptr, "IsUserRegistrationRequestPermitted"}, + {51, nullptr, "TrySelectUserWithoutInteractionDeprecated"}, + {99, nullptr, "DebugActivateOpenContextRetention"}, + {100, nullptr, "GetUserRegistrationNotifier"}, + {101, nullptr, "GetUserStateChangeNotifier"}, + {102, nullptr, "GetBaasAccountManagerForSystemService"}, + {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"}, + {104, nullptr, "GetProfileUpdateNotifier"}, + {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, + {106, nullptr, "GetProfileSyncNotifier"}, + {110, nullptr, "StoreSaveDataThumbnail"}, + {111, nullptr, "ClearSaveDataThumbnail"}, + {112, nullptr, "LoadSaveDataThumbnail"}, + {113, nullptr, "GetSaveDataThumbnailExistence"}, + {120, nullptr, "ListOpenUsersInApplication"}, + {130, nullptr, "ActivateOpenContextRetention"}, + {140, nullptr, "ListQualifiedUsers"}, + {151, nullptr, "EnsureSignedDeviceIdentifierCacheForNintendoAccountAsync"}, + {152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"}, + {170, nullptr, "GetNasOp2MembershipStateChangeNotifier"}, + {191, nullptr, "UpdateNotificationReceiverInfo"}, + {997, nullptr, "DebugInvalidateTokenCacheForUser"}, + {998, nullptr, "DebugSetUserStateClose"}, + {999, nullptr, "DebugSetUserStateOpen"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class ACC_E_U2 final : public Module::Interface { +public: + explicit ACC_E_U2(std::shared_ptr module_, std::shared_ptr profile_manager_, Core::System& system_) + : Interface(std::move(module_), std::move(profile_manager_), system_, "acc:e:u2") { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetUserCount"}, + {1, nullptr, "GetUserExistence"}, + {2, nullptr, "ListAllUsers"}, + {3, nullptr, "ListOpenUsers"}, + {4, nullptr, "GetLastOpenedUser"}, + {5, nullptr, "GetProfile"}, + {6, nullptr, "GetProfileDigest"}, + {50, nullptr, "#IsUserRegistrationRequestPermitted"}, + {51, nullptr, "TrySelectUserWithoutInteractionDeprecated"}, + {52, nullptr, "TrySelectUserWithoutInteraction"}, + {99, nullptr, "DebugActivateOpenContextRetention"}, + {100, nullptr, "GetUserRegistrationNotifier"}, + {101, nullptr, "GetUserStateChangeNotifier"}, + {102, nullptr, "GetBaasAccountManagerForSystemService"}, + {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"}, + {104, nullptr, "GetProfileUpdateNotifier"}, + {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, + {106, nullptr, "GetProfileSyncNotifier"}, + {110, nullptr, "StoreSaveDataThumbnail"}, + {111, nullptr, "ClearSaveDataThumbnail"}, + {112, nullptr, "LoadSaveDataThumbnail"}, + {113, nullptr, "GetSaveDataThumbnailExistence"}, + {120, nullptr, "ListOpenUsersInApplication"}, + {130, nullptr, "ActivateOpenContextRetention"}, + {140, nullptr, "ListQualifiedUsers"}, + {151, nullptr, "EnsureSignedDeviceIdentifierCacheForNintendoAccountAsync"}, + {152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"}, + {170, nullptr, "GetNasOp2MembershipStateChangeNotifier"}, + {191, nullptr, "UpdateNotificationReceiverInfo"}, + {205, nullptr, "GetProfileEditor"}, + {401, nullptr, "GetPinCodeLength"}, + {402, nullptr, "GetPinCode"}, + {403, nullptr, "GetPinCodeParity"}, + {404, nullptr, "VerifyPinCode"}, + {405, nullptr, "IsPinCodeVerificationForbidden"}, + {997, nullptr, "DebugInvalidateTokenCacheForUser"}, + {998, nullptr, "DebugSetUserStateClose"}, + {999, nullptr, "DebugSetUserStateOpen"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + void LoopProcess(Core::System& system) { auto module = std::make_shared(); auto profile_manager = std::make_shared(); auto server_manager = std::make_unique(system); - server_manager->RegisterNamedService("acc:aa", - std::make_shared(module, profile_manager, system)); - server_manager->RegisterNamedService("acc:su", - std::make_shared(module, profile_manager, system)); - server_manager->RegisterNamedService("acc:u0", - std::make_shared(module, profile_manager, system)); - server_manager->RegisterNamedService("acc:u1", - std::make_shared(module, profile_manager, system)); + server_manager->RegisterNamedService("acc:aa", std::make_shared(module, profile_manager, system)); + server_manager->RegisterNamedService("acc:su", std::make_shared(module, profile_manager, system)); + server_manager->RegisterNamedService("acc:u0", std::make_shared(module, profile_manager, system)); + server_manager->RegisterNamedService("acc:u1", std::make_shared(module, profile_manager, system)); + + server_manager->RegisterNamedService("acc:e", std::make_shared(module, profile_manager, system)); + server_manager->RegisterNamedService("acc:e:u1", std::make_shared(module, profile_manager, system)); + server_manager->RegisterNamedService("acc:e:u2", std::make_shared(module, profile_manager, system)); + + server_manager->RegisterNamedService("dauth:0", std::make_shared(system)); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/acc/acc_aa.cpp b/src/core/hle/service/acc/acc_aa.cpp deleted file mode 100644 index 90ed0f5197..0000000000 --- a/src/core/hle/service/acc/acc_aa.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "core/hle/service/acc/acc_aa.h" - -namespace Service::Account { - -ACC_AA::ACC_AA(std::shared_ptr module_, std::shared_ptr profile_manager_, - Core::System& system_) - : Interface(std::move(module_), std::move(profile_manager_), system_, "acc:aa") { - // clang-format off - static const FunctionInfo functions[] = { - {0, nullptr, "EnsureCacheAsync"}, - {1, nullptr, "LoadCache"}, - {2, nullptr, "GetDeviceAccountId"}, - {50, nullptr, "RegisterNotificationTokenAsync"}, // 1.0.0 - 6.2.0 - {51, nullptr, "UnregisterNotificationTokenAsync"}, // 1.0.0 - 6.2.0 - }; - // clang-format on - RegisterHandlers(functions); -} - -ACC_AA::~ACC_AA() = default; - -} // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_aa.h b/src/core/hle/service/acc/acc_aa.h deleted file mode 100644 index 623daeaef0..0000000000 --- a/src/core/hle/service/acc/acc_aa.h +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "core/hle/service/acc/acc.h" - -namespace Service::Account { - -class ACC_AA final : public Module::Interface { -public: - explicit ACC_AA(std::shared_ptr module_, - std::shared_ptr profile_manager_, Core::System& system_); - ~ACC_AA() override; -}; - -} // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp deleted file mode 100644 index 195af7ff69..0000000000 --- a/src/core/hle/service/acc/acc_su.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 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 - -#include "core/hle/service/acc/acc_su.h" - -namespace Service::Account { - -ACC_SU::ACC_SU(std::shared_ptr module_, std::shared_ptr profile_manager_, - Core::System& system_) - : Interface(std::move(module_), std::move(profile_manager_), system_, "acc:su") { - // clang-format off - static const FunctionInfo functions[] = { - {0, &ACC_SU::GetUserCount, "GetUserCount"}, - {1, &ACC_SU::GetUserExistence, "GetUserExistence"}, - {2, &ACC_SU::ListAllUsers, "ListAllUsers"}, - {3, &ACC_SU::ListOpenUsers, "ListOpenUsers"}, - {4, &ACC_SU::GetLastOpenedUser, "GetLastOpenedUser"}, - {5, &ACC_SU::GetProfile, "GetProfile"}, - {6, nullptr, "GetProfileDigest"}, - {50, &ACC_SU::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, - {51, &ACC_SU::TrySelectUserWithoutInteractionDeprecated, "TrySelectUserWithoutInteractionDeprecated"}, - {52, &ACC_SU::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, // 19.0.0+ - {60, &ACC_SU::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, - {99, nullptr, "DebugActivateOpenContextRetention"}, - {100, nullptr, "GetUserRegistrationNotifier"}, - {101, nullptr, "GetUserStateChangeNotifier"}, - {102, &ACC_SU::GetBaasAccountManagerForSystemService, "GetBaasAccountManagerForSystemService"}, - {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"}, - {104, nullptr, "GetProfileUpdateNotifier"}, - {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, - {106, nullptr, "GetProfileSyncNotifier"}, - {110, &ACC_SU::StoreSaveDataThumbnailSystem, "StoreSaveDataThumbnail"}, - {111, nullptr, "ClearSaveDataThumbnail"}, - {112, nullptr, "LoadSaveDataThumbnail"}, - {113, nullptr, "GetSaveDataThumbnailExistence"}, - {120, nullptr, "ListOpenUsersInApplication"}, - {130, nullptr, "ActivateOpenContextRetention"}, - {140, &ACC_SU::ListQualifiedUsers, "ListQualifiedUsers"}, - {150, nullptr, "AuthenticateApplicationAsync"}, - {151, nullptr, "EnsureSignedDeviceIdentifierCacheForNintendoAccountAsync"}, - {152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"}, - {190, nullptr, "GetUserLastOpenedApplication"}, - {191, nullptr, "ActivateOpenContextHolder"}, - {200, &ACC_SU::BeginUserRegistration, "BeginUserRegistration"}, - {201, &ACC_SU::CompleteUserRegistration, "CompleteUserRegistration"}, - {202, nullptr, "CancelUserRegistration"}, - {203, &ACC_SU::DeleteUser, "DeleteUser"}, - {204, &ACC_SU::SetUserPosition, "SetUserPosition"}, - {205, &ACC_SU::GetProfileEditor, "GetProfileEditor"}, - {206, nullptr, "CompleteUserRegistrationForcibly"}, - {210, nullptr, "CreateFloatingRegistrationRequest"}, - {211, nullptr, "CreateProcedureToRegisterUserWithNintendoAccount"}, - {212, nullptr, "ResumeProcedureToRegisterUserWithNintendoAccount"}, - {230, nullptr, "AuthenticateServiceAsync"}, - {250, &ACC_SU::GetBaasAccountAdministrator, "GetBaasAccountAdministrator"}, - {290, nullptr, "ProxyProcedureForGuestLoginWithNintendoAccount"}, - {291, nullptr, "ProxyProcedureForFloatingRegistrationWithNintendoAccount"}, - {299, nullptr, "SuspendBackgroundDaemon"}, - {400, nullptr, "SetPinCode"}, // 18.0.0+ - {401, &ACC_SU::GetPinCodeLength, "GetPinCodeLength"}, // 18.0.0+ - {402, nullptr, "GetPinCode"}, // 18.0.0+ - {410, nullptr, "GetPinCodeErrorCount"}, // 18.0.0+ - {411, nullptr, "ResetPinCodeErrorCount"}, // 18.0.0+ - {412, nullptr, "IncrementPinCodeErrorCount"}, // 18.0.0+ - {900, nullptr, "SetUserUnqualifiedForDebug"}, - {901, nullptr, "UnsetUserUnqualifiedForDebug"}, - {902, nullptr, "ListUsersUnqualifiedForDebug"}, - {910, nullptr, "RefreshFirmwareSettingsForDebug"}, - {997, nullptr, "DebugInvalidateTokenCacheForUser"}, - {998, nullptr, "DebugSetUserStateClose"}, - {999, nullptr, "DebugSetUserStateOpen"}, - }; - // clang-format on - - RegisterHandlers(functions); -} - -ACC_SU::~ACC_SU() = default; - -} // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_su.h b/src/core/hle/service/acc/acc_su.h deleted file mode 100644 index 8daef38b8b..0000000000 --- a/src/core/hle/service/acc/acc_su.h +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "core/hle/service/acc/acc.h" - -namespace Service::Account { - -class ACC_SU final : public Module::Interface { -public: - explicit ACC_SU(std::shared_ptr module_, - std::shared_ptr profile_manager_, Core::System& system_); - ~ACC_SU() override; -}; - -} // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp deleted file mode 100644 index df4e5ad00b..0000000000 --- a/src/core/hle/service/acc/acc_u0.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// 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 - -#include "core/hle/service/acc/acc_u0.h" - -namespace Service::Account { - -ACC_U0::ACC_U0(std::shared_ptr module_, std::shared_ptr profile_manager_, - Core::System& system_) - : Interface(std::move(module_), std::move(profile_manager_), system_, "acc:u0") { - // clang-format off - static const FunctionInfo functions[] = { - {0, &ACC_U0::GetUserCount, "GetUserCount"}, - {1, &ACC_U0::GetUserExistence, "GetUserExistence"}, - {2, &ACC_U0::ListAllUsers, "ListAllUsers"}, - {3, &ACC_U0::ListOpenUsers, "ListOpenUsers"}, - {4, &ACC_U0::GetLastOpenedUser, "GetLastOpenedUser"}, - {5, &ACC_U0::GetProfile, "GetProfile"}, - {6, nullptr, "GetProfileDigest"}, // 3.0.0+ - {50, &ACC_U0::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, - {51, &ACC_U0::TrySelectUserWithoutInteractionDeprecated, "TrySelectUserWithoutInteractionDeprecated"}, - {52, &ACC_U0::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, - {60, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 5.0.0 - 5.1.0 - {99, nullptr, "DebugActivateOpenContextRetention"}, // 6.0.0+ - {100, &ACC_U0::InitializeApplicationInfo, "InitializeApplicationInfo"}, - {101, &ACC_U0::GetBaasAccountManagerForApplication, "GetBaasAccountManagerForApplication"}, - {102, nullptr, "AuthenticateApplicationAsync"}, - {103, nullptr, "CheckNetworkServiceAvailabilityAsync"}, // 4.0.0+ - {110, &ACC_U0::StoreSaveDataThumbnailApplication, "StoreSaveDataThumbnail"}, - {111, nullptr, "ClearSaveDataThumbnail"}, - {120, nullptr, "CreateGuestLoginRequest"}, - {130, nullptr, "LoadOpenContext"}, // 5.0.0+ - {131, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 6.0.0+ - {140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"}, // 6.0.0+ - {141, &ACC_U0::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+ - {150, &ACC_U0::IsUserAccountSwitchLocked, "IsUserAccountSwitchLocked"}, // 6.0.0+ - {160, &ACC_U0::InitializeApplicationInfoV2, "InitializeApplicationInfoV2"}, - }; - // clang-format on - - RegisterHandlers(functions); -} - -ACC_U0::~ACC_U0() = default; - -} // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_u0.h b/src/core/hle/service/acc/acc_u0.h deleted file mode 100644 index 35cd4b492c..0000000000 --- a/src/core/hle/service/acc/acc_u0.h +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "core/hle/service/acc/acc.h" - -namespace Service::Account { - -class ACC_U0 final : public Module::Interface { -public: - explicit ACC_U0(std::shared_ptr module_, - std::shared_ptr profile_manager_, Core::System& system_); - ~ACC_U0() override; -}; - -} // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp deleted file mode 100644 index c39e916366..0000000000 --- a/src/core/hle/service/acc/acc_u1.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 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 - -#include "core/hle/service/acc/acc_u1.h" - -namespace Service::Account { - -ACC_U1::ACC_U1(std::shared_ptr module_, std::shared_ptr profile_manager_, - Core::System& system_) - : Interface(std::move(module_), std::move(profile_manager_), system_, "acc:u1") { - // clang-format off - static const FunctionInfo functions[] = { - {0, &ACC_U1::GetUserCount, "GetUserCount"}, - {1, &ACC_U1::GetUserExistence, "GetUserExistence"}, - {2, &ACC_U1::ListAllUsers, "ListAllUsers"}, - {3, &ACC_U1::ListOpenUsers, "ListOpenUsers"}, - {4, &ACC_U1::GetLastOpenedUser, "GetLastOpenedUser"}, - {5, &ACC_U1::GetProfile, "GetProfile"}, - {6, nullptr, "GetProfileDigest"}, - {50, &ACC_U1::IsUserRegistrationRequestPermitted, "IsUserRegistrationRequestPermitted"}, - {51, &ACC_U1::TrySelectUserWithoutInteraction, "TrySelectUserWithoutInteraction"}, - {60, &ACC_U1::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, - {99, nullptr, "DebugActivateOpenContextRetention"}, - {100, nullptr, "GetUserRegistrationNotifier"}, - {101, nullptr, "GetUserStateChangeNotifier"}, - {102, &ACC_U1::GetBaasAccountManagerForSystemService, "GetBaasAccountManagerForSystemService"}, - {103, nullptr, "GetBaasUserAvailabilityChangeNotifier"}, - {104, nullptr, "GetProfileUpdateNotifier"}, - {105, nullptr, "CheckNetworkServiceAvailabilityAsync"}, - {106, nullptr, "GetProfileSyncNotifier"}, - {110, &ACC_U1::StoreSaveDataThumbnailApplication, "StoreSaveDataThumbnail"}, - {111, nullptr, "ClearSaveDataThumbnail"}, - {112, nullptr, "LoadSaveDataThumbnail"}, - {113, nullptr, "GetSaveDataThumbnailExistence"}, - {120, nullptr, "ListOpenUsersInApplication"}, - {130, nullptr, "ActivateOpenContextRetention"}, - {140, &ACC_U1::ListQualifiedUsers, "ListQualifiedUsers"}, - {150, nullptr, "AuthenticateApplicationAsync"}, - {151, nullptr, "EnsureSignedDeviceIdentifierCacheForNintendoAccountAsync"}, - {152, nullptr, "LoadSignedDeviceIdentifierCacheForNintendoAccount"}, - {190, nullptr, "GetUserLastOpenedApplication"}, - {191, nullptr, "ActivateOpenContextHolder"}, - {401, &ACC_U1::GetPinCodeLength, "GetPinCodeLength"}, // 18.0.0+ - {402, nullptr, "GetPinCode"}, // 18.0.0+ - {997, nullptr, "DebugInvalidateTokenCacheForUser"}, - {998, nullptr, "DebugSetUserStateClose"}, - {999, nullptr, "DebugSetUserStateOpen"}, - }; - // clang-format on - - RegisterHandlers(functions); -} - -ACC_U1::~ACC_U1() = default; - -} // namespace Service::Account diff --git a/src/core/hle/service/acc/acc_u1.h b/src/core/hle/service/acc/acc_u1.h deleted file mode 100644 index e711d39252..0000000000 --- a/src/core/hle/service/acc/acc_u1.h +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "core/hle/service/acc/acc.h" - -namespace Service::Account { - -class ACC_U1 final : public Module::Interface { -public: - explicit ACC_U1(std::shared_ptr module_, - std::shared_ptr profile_manager_, Core::System& system_); - ~ACC_U1() override; -}; - -} // namespace Service::Account diff --git a/src/core/hle/service/acc/async_context.cpp b/src/core/hle/service/acc/async_context.cpp index c9e0af90ce..8dec886ad7 100644 --- a/src/core/hle/service/acc/async_context.cpp +++ b/src/core/hle/service/acc/async_context.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -32,7 +35,7 @@ void IAsyncContext::GetSystemEvent(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(completion_event->GetReadableEvent()); + rb.PushCopyObjects(ctx, completion_event->GetReadableEvent()); } void IAsyncContext::Cancel(HLERequestContext& ctx) { @@ -64,7 +67,7 @@ void IAsyncContext::GetResult(HLERequestContext& ctx) { void IAsyncContext::MarkComplete() { is_complete.store(true); - completion_event->Signal(); + completion_event->Signal(system.Kernel()); } } // namespace Service::Account diff --git a/src/core/hle/service/acc/async_context.h b/src/core/hle/service/acc/async_context.h index d7bffc055e..c50537d4ae 100644 --- a/src/core/hle/service/acc/async_context.h +++ b/src/core/hle/service/acc/async_context.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/acc/errors.h b/src/core/hle/service/acc/errors.h index 433ebfe9d6..b7cf8b5f93 100644 --- a/src/core/hle/service/acc/errors.h +++ b/src/core/hle/service/acc/errors.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h index 1fd0cc700f..81688175c2 100644 --- a/src/core/hle/service/acc/profile_manager.h +++ b/src/core/hle/service/acc/profile_manager.h @@ -23,6 +23,12 @@ constexpr std::size_t profile_username_size{32}; using ProfileUsername = std::array; using UserIDArray = std::array; +// This is nn::account::Uid +struct Uid { + std::array unk0; +}; +static_assert(sizeof(Uid) == 0x10); + /// Contains extra data related to a user. /// TODO: RE this structure struct UserData { diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 2ef393439b..86d9d52e34 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 1afe253aed..0bf5880f30 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/am_results.h b/src/core/hle/service/am/am_results.h index 44846aa2e5..d1b86c9546 100644 --- a/src/core/hle/service/am/am_results.h +++ b/src/core/hle/service/am/am_results.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -10,6 +13,7 @@ namespace Service::AM { constexpr Result ResultNoDataInChannel{ErrorModule::AM, 2}; constexpr Result ResultNoMessages{ErrorModule::AM, 3}; constexpr Result ResultLibraryAppletTerminated{ErrorModule::AM, 22}; +constexpr Result ResultApplicationRecordNotFound{ErrorModule::AM, 37}; constexpr Result ResultInvalidOffset{ErrorModule::AM, 503}; constexpr Result ResultInvalidStorageType{ErrorModule::AM, 511}; constexpr Result ResultFatalSectionCountImbalance{ErrorModule::AM, 512}; diff --git a/src/core/hle/service/am/am_types.h b/src/core/hle/service/am/am_types.h index beb52b74dd..6b7cbbc12a 100644 --- a/src/core/hle/service/am/am_types.h +++ b/src/core/hle/service/am/am_types.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -154,6 +154,7 @@ enum class AppletMessage : u32 { DetectLongPressingCaptureButton = 91, AlbumScreenShotTaken = 92, AlbumRecordingSaved = 93, + StartupLogoDisappeared = 95, }; enum class LibraryAppletMode : u32 { @@ -201,6 +202,14 @@ enum class ProgramSpecifyKind : u32 { RestartProgram = 2, }; +// Maufeat: Use enums for zindex instead of using random zindex numbers +enum AppletZIndex : s32 { + Background = 0, + Foreground = 1, + ForegroundVisible = 2, + Overlay = 3, +}; + struct CommonArguments { CommonArgumentVersion arguments_version; CommonArgumentSize size; diff --git a/src/core/hle/service/am/applet.cpp b/src/core/hle/service/am/applet.cpp index 2ae3102aff..58d038348f 100644 --- a/src/core/hle/service/am/applet.cpp +++ b/src/core/hle/service/am/applet.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator @@ -52,31 +52,31 @@ void Applet::UpdateSuspensionStateLocked(bool force_message) { // Signal if the focus state was changed or the process state was changed. if (update_requested_focus_state || was_changed || force_message) { - lifecycle_manager.SignalSystemEventIfNeeded(); + lifecycle_manager.SignalSystemEventIfNeeded(context.kernel); } } -void Applet::SetInteractibleLocked(bool interactible) { - if (is_interactible == interactible) { +void Applet::SetInteractibleLocked(bool pad_interactible, bool touch_interactible) { + if (is_pad_interactible == pad_interactible && is_touch_interactible == touch_interactible) { return; } - is_interactible = interactible; + is_pad_interactible = pad_interactible; + is_touch_interactible = touch_interactible; const bool exit_requested = lifecycle_manager.GetExitRequested(); - const bool input_enabled = interactible && !exit_requested; + const bool pad_enabled = pad_interactible && !exit_requested; + const bool touch_enabled = touch_interactible && !exit_requested; - if (applet_id == AppletId::OverlayDisplay || applet_id == AppletId::Application) { - LOG_DEBUG(Service_AM, "called, applet={} interactible={} exit_requested={} input_enabled={} overlay_in_foreground={}", - static_cast(applet_id), interactible, exit_requested, input_enabled, overlay_in_foreground); - } + LOG_DEBUG(Service_AM, "applet={} pad={} touch={} exit_requested={}", + static_cast(applet_id), pad_enabled, touch_enabled, exit_requested); - hid_registration.EnableAppletToGetInput(input_enabled); + hid_registration.EnableAppletToGetInput(pad_enabled, touch_enabled); } void Applet::OnProcessTerminatedLocked() { is_completed = true; - state_changed_event.Signal(); + state_changed_event.Signal(context.kernel); } } // namespace Service::AM diff --git a/src/core/hle/service/am/applet.h b/src/core/hle/service/am/applet.h index a693a47d7a..571fa65219 100644 --- a/src/core/hle/service/am/applet.h +++ b/src/core/hle/service/am/applet.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -83,6 +83,7 @@ struct Applet { // Application functions bool game_play_recording_supported{}; + bool media_playback_state{}; GamePlayRecordingState game_play_recording_state{GamePlayRecordingState::Disabled}; bool jit_service_launched{}; bool application_crash_report_enabled{}; @@ -90,6 +91,7 @@ struct Applet { // Common state bool sleep_lock_enabled{}; bool vr_mode_enabled{}; + bool vr_mode_enabled_3d{}; bool lcd_backlight_off_enabled{}; APM::CpuBoostMode boost_mode{}; bool request_exit_to_library_applet_at_execute_next_program_enabled{}; @@ -108,6 +110,10 @@ struct Applet { std::list> child_applets{}; bool is_completed{}; + std::shared_ptr reserved_applet{}; + bool unwind_after_reserved{}; + bool is_winding{}; + // Self state bool exit_locked{}; s32 fatal_section_count{}; @@ -120,9 +126,11 @@ struct Applet { bool album_image_taken_notification_enabled{}; bool record_volume_muted{}; bool is_activity_runnable{}; - bool is_interactible{true}; + bool is_pad_interactible{true}; + bool is_touch_interactible{true}; bool window_visible{true}; - bool overlay_in_foreground{false}; + bool overlay_watching_short_home_button{false}; + bool overlay_handling_touch_input{false}; // Events Event overlay_event; @@ -143,7 +151,7 @@ struct Applet { // Process state management void UpdateSuspensionStateLocked(bool force_message); - void SetInteractibleLocked(bool interactible); + void SetInteractibleLocked(bool pad_interactible, bool touch_interactible); void OnProcessTerminatedLocked(); }; diff --git a/src/core/hle/service/am/applet_data_broker.cpp b/src/core/hle/service/am/applet_data_broker.cpp index fff78c5afb..ac0246a666 100644 --- a/src/core/hle/service/am/applet_data_broker.cpp +++ b/src/core/hle/service/am/applet_data_broker.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -11,22 +14,30 @@ namespace Service::AM { AppletStorageChannel::AppletStorageChannel(KernelHelpers::ServiceContext& context) - : m_event(context) {} + : m_event(context) +{} AppletStorageChannel::~AppletStorageChannel() = default; -void AppletStorageChannel::Push(std::shared_ptr storage) { +void AppletStorageChannel::Push(Kernel::KernelCore& kernel, std::shared_ptr storage) { std::scoped_lock lk{m_lock}; m_data.emplace_back(std::move(storage)); - m_event.Signal(); + m_event.Signal(kernel); } -Result AppletStorageChannel::Pop(std::shared_ptr* out_storage) { +void AppletStorageChannel::Unpop(Kernel::KernelCore& kernel, std::shared_ptr storage) { + std::scoped_lock lk{m_lock}; + + m_data.emplace_front(std::move(storage)); + m_event.Signal(kernel); +} + +Result AppletStorageChannel::Pop(Kernel::KernelCore& kernel, std::shared_ptr* out_storage) { std::scoped_lock lk{m_lock}; SCOPE_EXIT { if (m_data.empty()) { - m_event.Clear(); + m_event.Clear(kernel); } }; diff --git a/src/core/hle/service/am/applet_data_broker.h b/src/core/hle/service/am/applet_data_broker.h index 2718f608ad..1fbe89a385 100644 --- a/src/core/hle/service/am/applet_data_broker.h +++ b/src/core/hle/service/am/applet_data_broker.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -22,8 +25,9 @@ public: explicit AppletStorageChannel(KernelHelpers::ServiceContext& ctx); ~AppletStorageChannel(); - void Push(std::shared_ptr storage); - Result Pop(std::shared_ptr* out_storage); + void Push(Kernel::KernelCore& kernel, std::shared_ptr storage); + void Unpop(Kernel::KernelCore& kernel, std::shared_ptr storage); + Result Pop(Kernel::KernelCore& kernel, std::shared_ptr* out_storage); Kernel::KReadableEvent* GetEvent(); private: diff --git a/src/core/hle/service/am/applet_manager.cpp b/src/core/hle/service/am/applet_manager.cpp index c2920f91ae..05bcf6cc05 100644 --- a/src/core/hle/service/am/applet_manager.cpp +++ b/src/core/hle/service/am/applet_manager.cpp @@ -34,8 +34,7 @@ struct LaunchParameterAccountPreselectedUser { }; static_assert(sizeof(LaunchParameterAccountPreselectedUser) == 0x88); -AppletStorageChannel& InitializeFakeCallerApplet(Core::System& system, - std::shared_ptr& applet) { +AppletStorageChannel& InitializeFakeCallerApplet(Core::System& system, std::shared_ptr& applet) { applet->caller_applet_broker = std::make_shared(system); return applet->caller_applet_broker->GetInData(); } @@ -52,7 +51,7 @@ void PushInShowQlaunch(Core::System& system, AppletStorageChannel& channel) { std::vector argument_data(sizeof(arguments)); std::memcpy(argument_data.data(), &arguments, sizeof(arguments)); - channel.Push(std::make_shared(system, std::move(argument_data))); + channel.Push(system.Kernel(), std::make_shared(system, std::move(argument_data))); } void PushInShowAlbum(Core::System& system, AppletStorageChannel& channel) { @@ -68,8 +67,8 @@ void PushInShowAlbum(Core::System& system, AppletStorageChannel& channel) { std::vector argument_data(sizeof(arguments)); std::vector settings_data{2}; std::memcpy(argument_data.data(), &arguments, sizeof(arguments)); - channel.Push(std::make_shared(system, std::move(argument_data))); - channel.Push(std::make_shared(system, std::move(settings_data))); + channel.Push(system.Kernel(), std::make_shared(system, std::move(argument_data))); + channel.Push(system.Kernel(), std::make_shared(system, std::move(settings_data))); } void PushInShowController(Core::System& system, AppletStorageChannel& channel) { @@ -116,9 +115,9 @@ void PushInShowController(Core::System& system, AppletStorageChannel& channel) { std::memcpy(private_args_data.data(), &private_args, sizeof(private_args)); std::memcpy(user_args_data.data(), &user_args, sizeof(user_args)); - channel.Push(std::make_shared(system, std::move(common_args_data))); - channel.Push(std::make_shared(system, std::move(private_args_data))); - channel.Push(std::make_shared(system, std::move(user_args_data))); + channel.Push(system.Kernel(), std::make_shared(system, std::move(common_args_data))); + channel.Push(system.Kernel(), std::make_shared(system, std::move(private_args_data))); + channel.Push(system.Kernel(), std::make_shared(system, std::move(user_args_data))); } void PushInShowCabinetData(Core::System& system, AppletStorageChannel& channel) { @@ -146,8 +145,8 @@ void PushInShowCabinetData(Core::System& system, AppletStorageChannel& channel) std::vector settings_data(sizeof(amiibo_settings)); std::memcpy(argument_data.data(), &arguments, sizeof(arguments)); std::memcpy(settings_data.data(), &amiibo_settings, sizeof(amiibo_settings)); - channel.Push(std::make_shared(system, std::move(argument_data))); - channel.Push(std::make_shared(system, std::move(settings_data))); + channel.Push(system.Kernel(), std::make_shared(system, std::move(argument_data))); + channel.Push(system.Kernel(), std::make_shared(system, std::move(settings_data))); } void PushInShowMiiEditData(Core::System& system, AppletStorageChannel& channel) { @@ -169,7 +168,7 @@ void PushInShowMiiEditData(Core::System& system, AppletStorageChannel& channel) std::vector argument_data(sizeof(mii_arguments)); std::memcpy(argument_data.data(), &mii_arguments, sizeof(mii_arguments)); - channel.Push(std::make_shared(system, std::move(argument_data))); + channel.Push(system.Kernel(), std::make_shared(system, std::move(argument_data))); } void PushInShowSoftwareKeyboard(Core::System& system, AppletStorageChannel& channel) { @@ -222,9 +221,9 @@ void PushInShowSoftwareKeyboard(Core::System& system, AppletStorageChannel& chan std::memcpy(work_buffer.data(), initial_string.data(), swkbd_config.initial_string_length * sizeof(char16_t)); - channel.Push(std::make_shared(system, std::move(argument_data))); - channel.Push(std::make_shared(system, std::move(swkbd_data))); - channel.Push(std::make_shared(system, std::move(work_buffer))); + channel.Push(system.Kernel(), std::make_shared(system, std::move(argument_data))); + channel.Push(system.Kernel(), std::make_shared(system, std::move(swkbd_data))); + channel.Push(system.Kernel(), std::make_shared(system, std::move(work_buffer))); } } // namespace @@ -340,7 +339,7 @@ void AppletManager::SetWindowSystem(WindowSystem* window_system) { } // Applet was started by frontend, so it is foreground. - applet->lifecycle_manager.SetFocusState(FocusState::InFocus); + applet->lifecycle_manager.SetFocusState(m_system.Kernel(), FocusState::InFocus); if (applet->applet_id == AppletId::QLaunch) { applet->lifecycle_manager.SetFocusHandlingMode(false); diff --git a/src/core/hle/service/am/applet_manager.h b/src/core/hle/service/am/applet_manager.h index 2b0714adf7..cd3035de31 100644 --- a/src/core/hle/service/am/applet_manager.h +++ b/src/core/hle/service/am/applet_manager.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/am/button_poller.cpp b/src/core/hle/service/am/button_poller.cpp index 069a1934d4..825d28f5f0 100644 --- a/src/core/hle/service/am/button_poller.cpp +++ b/src/core/hle/service/am/button_poller.cpp @@ -1,9 +1,11 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 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 +#include "common/thread.h" #include "core/core.h" #include "core/hle/service/am/am_types.h" #include "core/hle/service/am/button_poller.h" @@ -34,16 +36,14 @@ ButtonPressDuration ClassifyPressDuration(std::chrono::steady_clock::time_point } // namespace -ButtonPoller::ButtonPoller(Core::System& system, WindowSystem& window_system) - : m_window_system(window_system) { +ButtonPoller::ButtonPoller(Core::System& system, WindowSystem& window_system) { // TODO: am reads this from the home button state in hid, which is controller-agnostic. Core::HID::ControllerUpdateCallback engine_callback{ - .on_change = - [this](Core::HID::ControllerTriggerType type) { - if (type == Core::HID::ControllerTriggerType::Button) { - this->OnButtonStateChanged(); - } - }, + .on_change = [this, &window_system](Core::HID::ControllerTriggerType type) { + if (type == Core::HID::ControllerTriggerType::Button) { + OnButtonStateChanged(window_system); + } + }, .is_npad_service = true, }; @@ -51,26 +51,18 @@ ButtonPoller::ButtonPoller(Core::System& system, WindowSystem& window_system) m_handheld_key = m_handheld->SetCallback(engine_callback); m_player1 = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); m_player1_key = m_player1->SetCallback(engine_callback); - - m_thread = std::thread([this] { this->ThreadLoop(); }); } ButtonPoller::~ButtonPoller() { m_handheld->DeleteCallback(m_handheld_key); m_player1->DeleteCallback(m_player1_key); - m_stop = true; - m_cv.notify_all(); - if (m_thread.joinable()) { - m_thread.join(); - } } -void ButtonPoller::OnButtonStateChanged() { - std::lock_guard lk{m_mutex}; - const bool home_button = - m_handheld->GetHomeButtons().home.Value() || m_player1->GetHomeButtons().home.Value(); - const bool capture_button = m_handheld->GetCaptureButtons().capture.Value() || - m_player1->GetCaptureButtons().capture.Value(); +void ButtonPoller::OnButtonStateChanged(WindowSystem& window_system) { + auto const home_button = m_handheld->GetHomeButtons().home.Value() + || m_player1->GetHomeButtons().home.Value(); + auto const capture_button = m_handheld->GetCaptureButtons().capture.Value() + || m_player1->GetCaptureButtons().capture.Value(); // Buttons pressed which were not previously pressed if (home_button && !m_home_button_press_start) { @@ -90,7 +82,7 @@ void ButtonPoller::OnButtonStateChanged() { if (home_button && m_home_button_press_start && !m_home_button_long_sent) { const auto duration = ClassifyPressDuration(*m_home_button_press_start); if (duration != ButtonPressDuration::ShortPressing) { - m_window_system.OnSystemButtonPress(SystemButtonType::HomeButtonLongPressing); + window_system.OnSystemButtonPress(SystemButtonType::HomeButtonLongPressing); m_home_button_long_sent = true; } } @@ -98,7 +90,7 @@ void ButtonPoller::OnButtonStateChanged() { if (capture_button && m_capture_button_press_start && !m_capture_button_long_sent) { const auto duration = ClassifyPressDuration(*m_capture_button_press_start); if (duration != ButtonPressDuration::ShortPressing) { - m_window_system.OnSystemButtonPress(SystemButtonType::CaptureButtonLongPressing); + window_system.OnSystemButtonPress(SystemButtonType::CaptureButtonLongPressing); m_capture_button_long_sent = true; } } @@ -107,9 +99,8 @@ void ButtonPoller::OnButtonStateChanged() { if (!home_button && m_home_button_press_start) { if(!m_home_button_long_sent) { const auto duration = ClassifyPressDuration(*m_home_button_press_start); - m_window_system.OnSystemButtonPress( - duration == ButtonPressDuration::ShortPressing ? SystemButtonType::HomeButtonShortPressing - : SystemButtonType::HomeButtonLongPressing); + window_system.OnSystemButtonPress(duration == ButtonPressDuration::ShortPressing + ? SystemButtonType::HomeButtonShortPressing : SystemButtonType::HomeButtonLongPressing); } m_home_button_press_start = std::nullopt; m_home_button_long_sent = false; @@ -117,9 +108,8 @@ void ButtonPoller::OnButtonStateChanged() { if (!capture_button && m_capture_button_press_start) { if (!m_capture_button_long_sent) { const auto duration = ClassifyPressDuration(*m_capture_button_press_start); - m_window_system.OnSystemButtonPress( - duration == ButtonPressDuration::ShortPressing ? SystemButtonType::CaptureButtonShortPressing - : SystemButtonType::CaptureButtonLongPressing); + window_system.OnSystemButtonPress(duration == ButtonPressDuration::ShortPressing + ? SystemButtonType::CaptureButtonShortPressing : SystemButtonType::CaptureButtonLongPressing); } m_capture_button_press_start = std::nullopt; m_capture_button_long_sent = false; @@ -130,16 +120,4 @@ void ButtonPoller::OnButtonStateChanged() { // } } -void ButtonPoller::ThreadLoop() { - using namespace std::chrono_literals; - std::unique_lock lk{m_mutex}; - while (!m_stop) { - m_cv.wait_for(lk, 50ms); - if (m_stop) break; - lk.unlock(); - OnButtonStateChanged(); - lk.lock(); - } -} - } // namespace Service::AM diff --git a/src/core/hle/service/am/button_poller.h b/src/core/hle/service/am/button_poller.h index 12e7035aa1..f5f12a13b2 100644 --- a/src/core/hle/service/am/button_poller.h +++ b/src/core/hle/service/am/button_poller.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -30,31 +30,20 @@ class ButtonPoller { public: explicit ButtonPoller(Core::System& system, WindowSystem& window_system); ~ButtonPoller(); + void OnButtonStateChanged(WindowSystem& window_system); private: - void OnButtonStateChanged(); - void ThreadLoop(); - -private: - WindowSystem& m_window_system; - - Core::HID::EmulatedController* m_handheld{}; - int m_handheld_key{}; - Core::HID::EmulatedController* m_player1{}; - int m_player1_key{}; - std::optional m_home_button_press_start{}; std::optional m_capture_button_press_start{}; std::optional m_power_button_press_start{}; - bool m_home_button_long_sent{}; - bool m_capture_button_long_sent{}; - bool m_power_button_long_sent{}; - - std::thread m_thread; - std::atomic m_stop{false}; - std::condition_variable m_cv; - std::mutex m_mutex; + Core::HID::EmulatedController* m_handheld{}; + Core::HID::EmulatedController* m_player1{}; + int32_t m_handheld_key{}; + int32_t m_player1_key{}; + bool m_home_button_long_sent : 1 = false; + bool m_capture_button_long_sent : 1 = false; + bool m_power_button_long_sent : 1 = false; }; } // namespace Service::AM diff --git a/src/core/hle/service/am/display_layer_manager.cpp b/src/core/hle/service/am/display_layer_manager.cpp index 6789629310..cd9e26e899 100644 --- a/src/core/hle/service/am/display_layer_manager.cpp +++ b/src/core/hle/service/am/display_layer_manager.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -6,6 +6,7 @@ #include "core/core.h" #include "core/hle/service/am/display_layer_manager.h" +#include "core/hle/service/nvnflinger/hwc_layer.h" #include "core/hle/service/sm/sm.h" #include "core/hle/service/vi/application_display_service.h" #include "core/hle/service/vi/container.h" @@ -33,6 +34,7 @@ void DisplayLayerManager::Initialize(Core::System& system, Kernel::KProcess* pro m_system_shared_buffer_id = 0; m_system_shared_layer_id = 0; m_applet_id = applet_id; + m_library_applet_mode = mode; m_buffer_sharing_enabled = false; m_blending_enabled = mode == LibraryAppletMode::PartialForeground || mode == LibraryAppletMode::PartialForegroundIndirectDisplay; @@ -72,17 +74,19 @@ Result DisplayLayerManager::CreateManagedDisplayLayer(u64* out_layer_id) { out_layer_id, 0, display_id, Service::AppletResourceUserId{m_process->GetProcessId()})); m_manager_display_service->SetLayerVisibility(m_visible, *out_layer_id); + (void)m_display_service->GetContainer()->SetLayerStackMask(*out_layer_id, + this->GetLayerStackMask()); if (m_applet_id != AppletId::Application) { (void)m_manager_display_service->SetLayerBlending(m_blending_enabled, *out_layer_id); if (m_applet_id == AppletId::OverlayDisplay) { - (void)m_manager_display_service->SetLayerZIndex(-1, *out_layer_id); - (void)m_display_service->GetContainer()->SetLayerIsOverlay(*out_layer_id, true); + (void)m_manager_display_service->SetLayerZIndex(Overlay, *out_layer_id); } else { - (void)m_manager_display_service->SetLayerZIndex(1, *out_layer_id); + (void)m_manager_display_service->SetLayerZIndex(Foreground, *out_layer_id); } } + m_display_service->GetContainer()->SetLayerZIndex(*out_layer_id, true); m_managed_display_layers.emplace(*out_layer_id); R_SUCCEED(); @@ -122,13 +126,16 @@ Result DisplayLayerManager::IsSystemBufferSharingEnabled() { // Ensure the overlay layer is visible m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id); - m_manager_display_service->SetLayerBlending(m_blending_enabled, m_system_shared_layer_id); - s32 initial_z = 1; + (void)m_manager_display_service->SetLayerBlending(m_blending_enabled, m_system_shared_layer_id); + s32 initial_z = Foreground; if (m_applet_id == AppletId::OverlayDisplay) { - initial_z = -1; + initial_z = Overlay; + (void)m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id); (void)m_display_service->GetContainer()->SetLayerIsOverlay(m_system_shared_layer_id, true); } m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id); + m_display_service->GetContainer()->SetLayerZIndex(m_system_shared_layer_id, true); + m_managed_display_layers.emplace(m_system_shared_layer_id); R_SUCCEED(); } @@ -142,6 +149,36 @@ Result DisplayLayerManager::GetSystemSharedLayerHandle(u64* out_system_shared_bu R_SUCCEED(); } +u32 DisplayLayerManager::GetLayerStackMask() const { + using Nvnflinger::LayerStackBit; + using Nvnflinger::LayerStackId; + + constexpr u32 Displayed = LayerStackBit(LayerStackId::Default); + constexpr u32 Screenshot = LayerStackBit(LayerStackId::Screenshot); + constexpr u32 Recording = LayerStackBit(LayerStackId::Recording); + constexpr u32 LastFrame = LayerStackBit(LayerStackId::LastFrame); + constexpr u32 Debug = LayerStackBit(LayerStackId::ApplicationForDebug); + + switch (m_applet_id) { + case AppletId::Application: + return Displayed | Screenshot | Recording | LastFrame | Debug; + case AppletId::OverlayDisplay: + return Displayed; + case AppletId::QLaunch: + return Displayed; + default: + break; + } + + switch (m_library_applet_mode) { + case LibraryAppletMode::AllForeground: + case LibraryAppletMode::AllForegroundInitiallyHidden: + return Displayed | Screenshot | LastFrame; + default: + return Displayed | Screenshot; + } +} + void DisplayLayerManager::SetWindowVisibility(bool visible) { if (m_visible == visible) { return; @@ -185,10 +222,17 @@ void DisplayLayerManager::SetOverlayZIndex(s32 z_index) { } Result DisplayLayerManager::WriteAppletCaptureBuffer(bool* out_was_written, - s32* out_fbshare_layer_index) { + s32* out_fbshare_layer_index, + VI::CaptureKind kind) { R_UNLESS(m_buffer_sharing_enabled, VI::ResultPermissionDenied); R_RETURN(m_display_service->GetContainer()->GetSharedBufferManager()->WriteAppletCaptureBuffer( - out_was_written, out_fbshare_layer_index)); + out_was_written, out_fbshare_layer_index, kind)); +} + +Result DisplayLayerManager::ClearAppletCaptureBuffer(s32 fbshare_layer_index, u32 color) { + R_UNLESS(m_buffer_sharing_enabled, VI::ResultPermissionDenied); + R_RETURN(m_display_service->GetContainer()->GetSharedBufferManager()->ClearAppletCaptureBuffer( + fbshare_layer_index, color)); } } // namespace Service::AM diff --git a/src/core/hle/service/am/display_layer_manager.h b/src/core/hle/service/am/display_layer_manager.h index 423eb432fa..e9f48476d2 100644 --- a/src/core/hle/service/am/display_layer_manager.h +++ b/src/core/hle/service/am/display_layer_manager.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -23,6 +23,7 @@ class KProcess; namespace Service::VI { class IApplicationDisplayService; class IManagerDisplayService; +enum class CaptureKind : u32; } // namespace Service::VI namespace Service::AM { @@ -48,9 +49,13 @@ public: void SetOverlayZIndex(s32 z_index); - Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_fbshare_layer_index); + Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_fbshare_layer_index, + VI::CaptureKind kind); + Result ClearAppletCaptureBuffer(s32 fbshare_layer_index, u32 color); private: + u32 GetLayerStackMask() const; + Kernel::KProcess* m_process{}; std::shared_ptr m_display_service{}; std::shared_ptr m_manager_display_service{}; @@ -59,6 +64,7 @@ private: u64 m_system_shared_buffer_id{}; u64 m_system_shared_layer_id{}; AppletId m_applet_id{}; + LibraryAppletMode m_library_applet_mode{}; bool m_buffer_sharing_enabled{}; bool m_blending_enabled{}; bool m_visible{true}; diff --git a/src/core/hle/service/am/event_observer.cpp b/src/core/hle/service/am/event_observer.cpp index 5d1d303ed3..520000b64f 100644 --- a/src/core/hle/service/am/event_observer.cpp +++ b/src/core/hle/service/am/event_observer.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -15,19 +18,33 @@ enum class UserDataTag : u32 { }; EventObserver::EventObserver(Core::System& system, WindowSystem& window_system) - : m_system(system), m_context(system, "am:EventObserver"), m_window_system(window_system), - m_wakeup_event(m_context), m_wakeup_holder(m_wakeup_event.GetHandle()) { + : m_system(system), m_context(system, "am:EventObserver") + , m_window_system(window_system) + , m_wakeup_event(m_context) + , m_wakeup_holder(m_wakeup_event.GetHandle()) +{ m_window_system.SetEventObserver(this); m_wakeup_holder.SetUserData(static_cast(UserDataTag::WakeupEvent)); m_wakeup_holder.LinkToMultiWait(std::addressof(m_multi_wait)); - m_thread = std::thread([&] { this->ThreadFunc(); }); + m_thread = std::jthread([this](std::stop_token stop_token) { + Common::SetCurrentThreadName("am:EventObserver"); + while (!stop_token.stop_requested()) { + auto* signaled_holder = this->WaitSignaled(stop_token); + if (!signaled_holder) + break; + this->Process(signaled_holder); + } + }); } EventObserver::~EventObserver() { // Signal thread and wait for processing to finish. - m_stop_source.request_stop(); - m_wakeup_event.Signal(); - m_thread.join(); + if (m_thread.joinable()) { + // Signal thread and wait for processing to finish. + m_thread.request_stop(); + m_wakeup_event.Signal(m_system.Kernel()); + m_thread.join(); + } // Free remaining owned sessions. auto it = m_process_holder_list.begin(); @@ -61,11 +78,11 @@ void EventObserver::TrackAppletProcess(Applet& applet) { } // Signal wakeup. - m_wakeup_event.Signal(); + m_wakeup_event.Signal(m_system.Kernel()); } void EventObserver::RequestUpdate() { - m_wakeup_event.Signal(); + m_wakeup_event.Signal(m_system.Kernel()); } void EventObserver::LinkDeferred() { @@ -73,12 +90,12 @@ void EventObserver::LinkDeferred() { m_multi_wait.MoveAll(std::addressof(m_deferred_wait_list)); } -MultiWaitHolder* EventObserver::WaitSignaled() { +MultiWaitHolder* EventObserver::WaitSignaled(std::stop_token stop_token) { while (true) { this->LinkDeferred(); // If we're done, return before we start waiting. - if (m_stop_source.stop_requested()) { + if (stop_token.stop_requested()) { return nullptr; } @@ -106,7 +123,7 @@ void EventObserver::Process(MultiWaitHolder* holder) { } void EventObserver::OnWakeupEvent(MultiWaitHolder* holder) { - m_wakeup_event.Clear(); + m_wakeup_event.Clear(m_system.Kernel()); // Perform recalculation. m_window_system.Update(); @@ -116,7 +133,6 @@ void EventObserver::OnProcessEvent(ProcessHolder* holder) { // Check process state. auto& applet = holder->GetApplet(); auto& process = holder->GetProcess(); - { std::scoped_lock lk{m_lock, applet.lock}; if (process.IsTerminated()) { @@ -141,22 +157,8 @@ void EventObserver::OnProcessEvent(ProcessHolder* holder) { void EventObserver::DestroyAppletProcessHolderLocked(ProcessHolder* holder) { // Remove from owned list. m_process_holder_list.erase(m_process_holder_list.iterator_to(*holder)); - // Destroy and free. delete holder; } -void EventObserver::ThreadFunc() { - Common::SetCurrentThreadName("am:EventObserver"); - - while (true) { - auto* signaled_holder = this->WaitSignaled(); - if (!signaled_holder) { - break; - } - - this->Process(signaled_holder); - } -} - } // namespace Service::AM diff --git a/src/core/hle/service/am/event_observer.h b/src/core/hle/service/am/event_observer.h index 3e52e8494d..c66eb711c8 100644 --- a/src/core/hle/service/am/event_observer.h +++ b/src/core/hle/service/am/event_observer.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -29,22 +32,14 @@ public: private: void LinkDeferred(); - MultiWaitHolder* WaitSignaled(); + MultiWaitHolder* WaitSignaled(std::stop_token stop_token); void Process(MultiWaitHolder* holder); bool WaitAndProcessImpl(); void LoopProcess(); - -private: void OnWakeupEvent(MultiWaitHolder* holder); void OnProcessEvent(ProcessHolder* holder); - -private: void DestroyAppletProcessHolderLocked(ProcessHolder* holder); -private: - void ThreadFunc(); - -private: // System reference and context. Core::System& m_system; KernelHelpers::ServiceContext m_context; @@ -67,8 +62,7 @@ private: MultiWait m_deferred_wait_list; // Processing thread. - std::thread m_thread{}; - std::stop_source m_stop_source{}; + std::jthread m_thread{}; }; } // namespace Service::AM diff --git a/src/core/hle/service/am/frontend/applet_cabinet.h b/src/core/hle/service/am/frontend/applet_cabinet.h index 3a211ed372..a74f233569 100644 --- a/src/core/hle/service/am/frontend/applet_cabinet.h +++ b/src/core/hle/service/am/frontend/applet_cabinet.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/frontend/applet_controller.h b/src/core/hle/service/am/frontend/applet_controller.h index 341ebed234..3877a7b02e 100644 --- a/src/core/hle/service/am/frontend/applet_controller.h +++ b/src/core/hle/service/am/frontend/applet_controller.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project diff --git a/src/core/hle/service/am/frontend/applet_error.h b/src/core/hle/service/am/frontend/applet_error.h index 678bf33fac..af3e2134a5 100644 --- a/src/core/hle/service/am/frontend/applet_error.h +++ b/src/core/hle/service/am/frontend/applet_error.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/frontend/applet_general.cpp b/src/core/hle/service/am/frontend/applet_general.cpp index 5345fdec33..22cad54b66 100644 --- a/src/core/hle/service/am/frontend/applet_general.cpp +++ b/src/core/hle/service/am/frontend/applet_general.cpp @@ -20,16 +20,16 @@ namespace Service::AM::Frontend { constexpr Result ERROR_INVALID_PIN{ErrorModule::PCTL, 221}; -static void LogCurrentStorage(std::shared_ptr applet, std::string_view prefix) { +static void LogCurrentStorage(Kernel::KernelCore& kernel, std::shared_ptr applet, std::string_view prefix) { std::shared_ptr storage; - while (R_SUCCEEDED(applet->caller_applet_broker->GetInData().Pop(&storage))) { + while (R_SUCCEEDED(applet->caller_applet_broker->GetInData().Pop(kernel, &storage))) { const auto data = storage->GetData(); LOG_INFO(Service_AM, "called (STUBBED), during {} received normal data with size={:08X}, data={}", prefix, data.size(), Common::HexToString(data)); } - while (R_SUCCEEDED(applet->caller_applet_broker->GetInteractiveInData().Pop(&storage))) { + while (R_SUCCEEDED(applet->caller_applet_broker->GetInteractiveInData().Pop(kernel, &storage))) { const auto data = storage->GetData(); LOG_INFO(Service_AM, "called (STUBBED), during {} received interactive data with size={:08X}, data={}", @@ -219,7 +219,7 @@ void StubApplet::Initialize() { LOG_WARNING(Service_AM, "called (STUBBED)"); FrontendApplet::Initialize(); - LogCurrentStorage(applet.lock(), "Initialize"); + LogCurrentStorage(system.Kernel(), applet.lock(), "Initialize"); } Result StubApplet::GetStatus() const { @@ -229,7 +229,7 @@ Result StubApplet::GetStatus() const { void StubApplet::ExecuteInteractive() { LOG_WARNING(Service_AM, "called (STUBBED)"); - LogCurrentStorage(applet.lock(), "ExecuteInteractive"); + LogCurrentStorage(system.Kernel(), applet.lock(), "ExecuteInteractive"); PushOutData(std::make_shared(system, std::vector(0x1000))); PushInteractiveOutData(std::make_shared(system, std::vector(0x1000))); @@ -238,7 +238,7 @@ void StubApplet::ExecuteInteractive() { void StubApplet::Execute() { LOG_WARNING(Service_AM, "called (STUBBED)"); - LogCurrentStorage(applet.lock(), "Execute"); + LogCurrentStorage(system.Kernel(), applet.lock(), "Execute"); PushOutData(std::make_shared(system, std::vector(0x1000))); PushInteractiveOutData(std::make_shared(system, std::vector(0x1000))); diff --git a/src/core/hle/service/am/frontend/applet_general.h b/src/core/hle/service/am/frontend/applet_general.h index eaa7ae25fe..ceb0537119 100644 --- a/src/core/hle/service/am/frontend/applet_general.h +++ b/src/core/hle/service/am/frontend/applet_general.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/frontend/applet_mii_edit.h b/src/core/hle/service/am/frontend/applet_mii_edit.h index 5db792f7d2..8654426431 100644 --- a/src/core/hle/service/am/frontend/applet_mii_edit.h +++ b/src/core/hle/service/am/frontend/applet_mii_edit.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/frontend/applet_mii_edit_types.h b/src/core/hle/service/am/frontend/applet_mii_edit_types.h index 23d9d7a69b..9c354bd947 100644 --- a/src/core/hle/service/am/frontend/applet_mii_edit_types.h +++ b/src/core/hle/service/am/frontend/applet_mii_edit_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/frontend/applet_net_connect.h b/src/core/hle/service/am/frontend/applet_net_connect.h index d5d503ed41..0986b63c60 100644 --- a/src/core/hle/service/am/frontend/applet_net_connect.h +++ b/src/core/hle/service/am/frontend/applet_net_connect.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/core/hle/service/am/frontend/applet_profile_select.cpp b/src/core/hle/service/am/frontend/applet_profile_select.cpp index 89b5a1eabe..0f46ee6232 100644 --- a/src/core/hle/service/am/frontend/applet_profile_select.cpp +++ b/src/core/hle/service/am/frontend/applet_profile_select.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/frontend/applet_profile_select.h b/src/core/hle/service/am/frontend/applet_profile_select.h index 674e7afe17..4feba73a37 100644 --- a/src/core/hle/service/am/frontend/applet_profile_select.h +++ b/src/core/hle/service/am/frontend/applet_profile_select.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/frontend/applet_software_keyboard_types.h b/src/core/hle/service/am/frontend/applet_software_keyboard_types.h index a25ff2a6d2..6bb8f437d3 100644 --- a/src/core/hle/service/am/frontend/applet_software_keyboard_types.h +++ b/src/core/hle/service/am/frontend/applet_software_keyboard_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/am/frontend/applet_web_browser.cpp b/src/core/hle/service/am/frontend/applet_web_browser.cpp index 92782c58e2..1ddb1ec2a3 100644 --- a/src/core/hle/service/am/frontend/applet_web_browser.cpp +++ b/src/core/hle/service/am/frontend/applet_web_browser.cpp @@ -4,6 +4,7 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "applet_web_browser_types.h" #include "common/assert.h" #include "common/fs/file.h" #include "common/fs/fs.h" @@ -370,16 +371,13 @@ void WebBrowser::ExtractOfflineRomFS() { void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) { const bool use_tlv_output = - (web_arg_header.shim_kind == ShimKind::Share && - web_applet_version >= WebAppletVersion::Version196608) || - (web_arg_header.shim_kind == ShimKind::Web && - web_applet_version >= WebAppletVersion::Version524288) || - (web_arg_header.shim_kind == ShimKind::Lhub); + (web_arg_header.shim_kind == ShimKind::Share && web_applet_version >= WebAppletVersion::Version196608) + || (web_arg_header.shim_kind == ShimKind::Web && web_applet_version >= WebAppletVersion::Version524288) + || (web_arg_header.shim_kind == ShimKind::Lhub); // https://switchbrew.org/wiki/Internet_Browser#TLVs if (use_tlv_output) { - LOG_DEBUG(Service_AM, "Using TLV output: exit_reason={}, last_url={}, last_url_size={}", - exit_reason, last_url, last_url.size()); + LOG_DEBUG(Service_AM, "Using TLV output: exit_reason={}, last_url={}, last_url_size={}", exit_reason, last_url, last_url.size()); // storage size for TLVs is 0x2000 bytes (as per switchbrew documentation) constexpr size_t TLV_STORAGE_SIZE = 0x2000; @@ -600,11 +598,14 @@ void WebBrowser::ExecuteShare() { void WebBrowser::ExecuteWeb() { LOG_INFO(Service_AM, "Opening external URL at {}", external_url); - - frontend.OpenExternalWebPage(external_url, - [this](WebExitReason exit_reason, std::string last_url) { - WebBrowserExit(exit_reason, last_url); - }); + frontend.OpenExternalWebPage(external_url, [this](WebExitReason exit_reason, std::string last_url) { + // Offline and web applets must be explicitly exited from because they respect exit state + // Unlike the other web stuffs + if (exit_reason == WebExitReason::ExitRequested || exit_reason == WebExitReason::EndButtonPressed) + exit_reason = (web_arg_header.shim_kind == ShimKind::Web || web_arg_header.shim_kind == ShimKind::Offline) + ? WebExitReason::ExitRequested : WebExitReason::EndButtonPressed; + WebBrowserExit(exit_reason, last_url); + }); } void WebBrowser::ExecuteWifi() { diff --git a/src/core/hle/service/am/frontend/applet_web_browser.h b/src/core/hle/service/am/frontend/applet_web_browser.h index ae62389f13..41461a93d0 100644 --- a/src/core/hle/service/am/frontend/applet_web_browser.h +++ b/src/core/hle/service/am/frontend/applet_web_browser.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project diff --git a/src/core/hle/service/am/frontend/applet_web_browser_types.h b/src/core/hle/service/am/frontend/applet_web_browser_types.h index 3574568f6f..80e7fdb0bd 100644 --- a/src/core/hle/service/am/frontend/applet_web_browser_types.h +++ b/src/core/hle/service/am/frontend/applet_web_browser_types.h @@ -7,7 +7,7 @@ #pragma once #include -#include +#include "common/container/unordered_map.h" #include #include "common/common_funcs.h" @@ -176,6 +176,6 @@ struct WebCommonReturnValue { }; static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size."); -using WebArgInputTLVMap = ankerl::unordered_dense::map>; +using WebArgInputTLVMap = ::Common::unordered_map>; } // namespace Service::AM::Frontend diff --git a/src/core/hle/service/am/frontend/applets.cpp b/src/core/hle/service/am/frontend/applets.cpp index 2ff2bcdbb6..c59e4b9c6b 100644 --- a/src/core/hle/service/am/frontend/applets.cpp +++ b/src/core/hle/service/am/frontend/applets.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include @@ -29,6 +29,7 @@ #include "core/hle/service/am/frontend/applet_web_browser.h" #include "core/hle/service/am/frontend/applets.h" #include "core/hle/service/am/service/storage.h" +#include "core/hle/service/am/window_system.h" #include "core/hle/service/sm/sm.h" namespace Service::AM::Frontend { @@ -52,30 +53,36 @@ void FrontendApplet::Initialize() { std::shared_ptr FrontendApplet::PopInData() { std::shared_ptr ret; - applet.lock()->caller_applet_broker->GetInData().Pop(&ret); + applet.lock()->caller_applet_broker->GetInData().Pop(system.Kernel(), &ret); return ret; } std::shared_ptr FrontendApplet::PopInteractiveInData() { std::shared_ptr ret; - applet.lock()->caller_applet_broker->GetInteractiveInData().Pop(&ret); + applet.lock()->caller_applet_broker->GetInteractiveInData().Pop(system.Kernel(), &ret); return ret; } void FrontendApplet::PushOutData(std::shared_ptr storage) { - applet.lock()->caller_applet_broker->GetOutData().Push(storage); + applet.lock()->caller_applet_broker->GetOutData().Push(system.Kernel(), storage); } void FrontendApplet::PushInteractiveOutData(std::shared_ptr storage) { - applet.lock()->caller_applet_broker->GetInteractiveOutData().Push(storage); + applet.lock()->caller_applet_broker->GetInteractiveOutData().Push(system.Kernel(), storage); } void FrontendApplet::Exit() { auto applet_ = applet.lock(); - - std::scoped_lock lk{applet_->lock}; - applet_->is_completed = true; - applet_->state_changed_event.Signal(); + { + std::scoped_lock lk{applet_->lock}; + applet_->is_completed = true; + applet_->state_changed_event.Signal(system.Kernel()); + } + if (auto caller_applet = applet_->caller_applet.lock()) { + std::scoped_lock lk{caller_applet->lock}; + std::erase(caller_applet->child_applets, applet_); + } + if (auto* window_system = system.GetAppletManager().GetWindowSystem()) window_system->RequestUpdate(); } FrontendAppletSet::FrontendAppletSet() = default; @@ -243,9 +250,10 @@ std::shared_ptr FrontendAppletHolder::GetApplet(std::shared_ptr< return std::make_shared(system, applet, mode, *frontend.photo_viewer); case AppletId::NetConnect: return std::make_shared(system, applet, mode, *frontend.net_connect); + case AppletId::None: + return nullptr; default: - LOG_ERROR(Service_AM, "No backend implementation exists for applet_id={:02X} program_id={:016X}" - "Falling back to stub applet", static_cast(id), applet->program_id); + UNIMPLEMENTED_MSG("No frontend implementation exists for applet_id={:02X} program_id={:016X}! Using stub applet.", static_cast(id), applet->program_id); return std::make_shared(system, applet, id, mode); } } diff --git a/src/core/hle/service/am/frontend/applets.h b/src/core/hle/service/am/frontend/applets.h index 8eb08ea77b..6259a5668a 100644 --- a/src/core/hle/service/am/frontend/applets.h +++ b/src/core/hle/service/am/frontend/applets.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project diff --git a/src/core/hle/service/am/hid_registration.cpp b/src/core/hle/service/am/hid_registration.cpp index ea4bd8f45b..8ce9ae748b 100644 --- a/src/core/hle/service/am/hid_registration.cpp +++ b/src/core/hle/service/am/hid_registration.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -12,7 +15,10 @@ namespace Service::AM { HidRegistration::HidRegistration(Core::System& system, Process& process) : m_process(process) { m_hid_server = system.ServiceManager().GetService("hid", true); + this->RegisterCurrentProcess(); +} +void HidRegistration::RegisterCurrentProcess() { if (m_process.IsInitialized()) { m_hid_server->GetResourceManager()->RegisterAppletResourceUserId(m_process.GetProcessId(), true); @@ -30,12 +36,17 @@ HidRegistration::~HidRegistration() { } } -void HidRegistration::EnableAppletToGetInput(bool enable) { - if (m_process.IsInitialized()) { - m_hid_server->GetResourceManager()->SetAruidValidForVibration(m_process.GetProcessId(), - enable); - m_hid_server->GetResourceManager()->EnableInput(m_process.GetProcessId(), enable); - } +void HidRegistration::EnableAppletToGetInput(bool enable_pad, bool enable_touch) { + if (!m_process.IsInitialized()) + return; + + const auto resource_manager = m_hid_server->GetResourceManager(); + const u64 aruid = m_process.GetProcessId(); + + resource_manager->EnablePadInput(aruid, enable_pad); + resource_manager->EnableTouchScreen(aruid, enable_touch); + + resource_manager->SetAruidValidForVibration(aruid, enable_pad); } } // namespace Service::AM diff --git a/src/core/hle/service/am/hid_registration.h b/src/core/hle/service/am/hid_registration.h index 54f42af189..59184b0bee 100644 --- a/src/core/hle/service/am/hid_registration.h +++ b/src/core/hle/service/am/hid_registration.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -24,7 +27,8 @@ public: explicit HidRegistration(Core::System& system, Process& process); ~HidRegistration(); - void EnableAppletToGetInput(bool enable); + void RegisterCurrentProcess(); + void EnableAppletToGetInput(bool enable_pad, bool enable_touch); private: Process& m_process; diff --git a/src/core/hle/service/am/library_applet_storage.cpp b/src/core/hle/service/am/library_applet_storage.cpp index 0412c215d5..8b14511c72 100644 --- a/src/core/hle/service/am/library_applet_storage.cpp +++ b/src/core/hle/service/am/library_applet_storage.cpp @@ -1,7 +1,11 @@ +// SPDX-FileCopyrightText: Copyright 2026 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/hle/kernel/k_transfer_memory.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/service/am/am_results.h" #include "core/hle/service/am/library_applet_storage.h" #include "core/memory.h" @@ -55,15 +59,18 @@ private: class TransferMemoryLibraryAppletStorage : public LibraryAppletStorage { public: - explicit TransferMemoryLibraryAppletStorage(Core::Memory::Memory& memory, - Kernel::KTransferMemory* trmem, bool is_writable, - s64 size) - : m_memory(memory), m_trmem(trmem), m_is_writable(is_writable), m_size(size) { - m_trmem->Open(); + explicit TransferMemoryLibraryAppletStorage(Kernel::KernelCore& kernel, Core::Memory::Memory& memory, Kernel::KTransferMemory* trmem, bool is_writable, s64 size) + : m_kernel{kernel} + , m_memory(memory) + , m_trmem(trmem) + , m_is_writable(is_writable) + , m_size(size) + { + m_trmem->Open(m_kernel); } ~TransferMemoryLibraryAppletStorage() { - m_trmem->Close(); + m_trmem->Close(m_kernel); m_trmem = nullptr; } @@ -93,6 +100,7 @@ public: } protected: + Kernel::KernelCore& m_kernel; Core::Memory::Memory& m_memory; Kernel::KTransferMemory* m_trmem; bool m_is_writable; @@ -101,9 +109,9 @@ protected: class HandleLibraryAppletStorage : public TransferMemoryLibraryAppletStorage { public: - explicit HandleLibraryAppletStorage(Core::Memory::Memory& memory, - Kernel::KTransferMemory* trmem, s64 size) - : TransferMemoryLibraryAppletStorage(memory, trmem, true, size) {} + explicit HandleLibraryAppletStorage(Kernel::KernelCore& kernel, Core::Memory::Memory& memory, Kernel::KTransferMemory* trmem, s64 size) + : TransferMemoryLibraryAppletStorage(kernel, memory, trmem, true, size) + {} ~HandleLibraryAppletStorage() = default; Kernel::KTransferMemory* GetHandle() override { @@ -125,16 +133,12 @@ std::shared_ptr CreateStorage(std::vector&& data) { return std::make_shared(std::move(data)); } -std::shared_ptr CreateTransferMemoryStorage(Core::Memory::Memory& memory, - Kernel::KTransferMemory* trmem, - bool is_writable, s64 size) { - return std::make_shared(memory, trmem, is_writable, size); +std::shared_ptr CreateTransferMemoryStorage(Kernel::KernelCore& kernel, Core::Memory::Memory& memory, Kernel::KTransferMemory* trmem, bool is_writable, s64 size) { + return std::make_shared(kernel, memory, trmem, is_writable, size); } -std::shared_ptr CreateHandleStorage(Core::Memory::Memory& memory, - Kernel::KTransferMemory* trmem, - s64 size) { - return std::make_shared(memory, trmem, size); +std::shared_ptr CreateHandleStorage(Kernel::KernelCore& kernel, Core::Memory::Memory& memory, Kernel::KTransferMemory* trmem, s64 size) { + return std::make_shared(kernel, memory, trmem, size); } } // namespace Service::AM diff --git a/src/core/hle/service/am/library_applet_storage.h b/src/core/hle/service/am/library_applet_storage.h index 7f53f3a9cd..cfdbef96d0 100644 --- a/src/core/hle/service/am/library_applet_storage.h +++ b/src/core/hle/service/am/library_applet_storage.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -10,6 +13,7 @@ class Memory; } namespace Kernel { +class KernelCore; class KTransferMemory; } @@ -27,10 +31,7 @@ public: }; std::shared_ptr CreateStorage(std::vector&& data); -std::shared_ptr CreateTransferMemoryStorage(Core::Memory::Memory& memory, - Kernel::KTransferMemory* trmem, - bool is_writable, s64 size); -std::shared_ptr CreateHandleStorage(Core::Memory::Memory& memory, - Kernel::KTransferMemory* trmem, s64 size); +std::shared_ptr CreateTransferMemoryStorage(Kernel::KernelCore& kernel, Core::Memory::Memory& memory, Kernel::KTransferMemory* trmem, bool is_writable, s64 size); +std::shared_ptr CreateHandleStorage(Kernel::KernelCore& kernel, Core::Memory::Memory& memory, Kernel::KTransferMemory* trmem, s64 size); } // namespace Service::AM diff --git a/src/core/hle/service/am/lifecycle_manager.cpp b/src/core/hle/service/am/lifecycle_manager.cpp index fbb9ad611f..4b003c4696 100644 --- a/src/core/hle/service/am/lifecycle_manager.cpp +++ b/src/core/hle/service/am/lifecycle_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -25,9 +28,9 @@ Event& LifecycleManager::GetHDCPStateChangedEvent() { return m_hdcp_state_changed_event; } -void LifecycleManager::PushUnorderedMessage(AppletMessage message) { +void LifecycleManager::PushUnorderedMessage(Kernel::KernelCore& kernel, AppletMessage message) { m_unordered_messages.push_back(message); - this->SignalSystemEventIfNeeded(); + this->SignalSystemEventIfNeeded(kernel); } AppletMessage LifecycleManager::PopMessageInOrderOfPriority() { @@ -141,36 +144,48 @@ bool LifecycleManager::ShouldSignalSystemEvent() { m_has_album_screen_shot_taken || m_has_album_recording_saved; } -void LifecycleManager::OnOperationAndPerformanceModeChanged() { +void LifecycleManager::OnOperationAndPerformanceModeChanged(Kernel::KernelCore& kernel) { if (m_operation_mode_changed_notification_enabled) { m_has_operation_mode_changed = true; } if (m_performance_mode_changed_notification_enabled) { m_has_performance_mode_changed = true; } - m_operation_mode_changed_system_event.Signal(); - this->SignalSystemEventIfNeeded(); + m_operation_mode_changed_system_event.Signal(kernel); + this->SignalSystemEventIfNeeded(kernel); } -void LifecycleManager::SignalSystemEventIfNeeded() { +void LifecycleManager::SignalSystemEventIfNeeded(Kernel::KernelCore& kernel) { // Check our cached value for the system event. const bool applet_message_available = m_applet_message_available; // If it's not current, we need to do an update, either clearing or signaling. if (applet_message_available != this->ShouldSignalSystemEvent()) { if (!applet_message_available) { - m_system_event.Signal(); + m_system_event.Signal(kernel); m_applet_message_available = true; } else { - m_system_event.Clear(); + m_system_event.Clear(kernel); m_applet_message_available = false; } } } -bool LifecycleManager::PopMessage(AppletMessage* out_message) { +void LifecycleManager::ResetForRelaunch() { + m_unordered_messages.clear(); + + m_activity_state = ActivityState::BackgroundVisible; + m_requested_focus_state = FocusState{}; + m_acknowledged_focus_state = FocusState{}; + m_has_focus_state_changed = true; + + m_suspend_mode = SuspendMode::NoOverride; + m_forced_suspend = false; +} + +bool LifecycleManager::PopMessage(Kernel::KernelCore& kernel, AppletMessage* out_message) { const auto message = this->PopMessageInOrderOfPriority(); - this->SignalSystemEventIfNeeded(); + this->SignalSystemEventIfNeeded(kernel); *out_message = message; return message != AppletMessage::None; @@ -372,6 +387,10 @@ bool LifecycleManager::UpdateRequestedFocusState() { // Mark the focus state as ready for update. m_requested_focus_state = new_state; + if (m_is_application) { + m_has_focus_state_changed = true; + } + // We changed the focus state. return true; } diff --git a/src/core/hle/service/am/lifecycle_manager.h b/src/core/hle/service/am/lifecycle_manager.h index a1ddb9e2df..15657a36d8 100644 --- a/src/core/hle/service/am/lifecycle_manager.h +++ b/src/core/hle/service/am/lifecycle_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -66,17 +69,17 @@ public: return m_acknowledged_focus_state; } - void SetFocusState(FocusState state) { + void SetFocusState(Kernel::KernelCore& kernel, FocusState state) { if (m_requested_focus_state != state) { m_has_focus_state_changed = true; } m_requested_focus_state = state; - this->SignalSystemEventIfNeeded(); + this->SignalSystemEventIfNeeded(kernel); } - void RequestExit() { + void RequestExit(Kernel::KernelCore& kernel) { m_has_requested_exit = true; - this->SignalSystemEventIfNeeded(); + this->SignalSystemEventIfNeeded(kernel); } void RequestResumeNotification() { @@ -89,22 +92,29 @@ public: } } - void OnOperationAndPerformanceModeChanged(); + void RequestFocusStateChangedNotification(Kernel::KernelCore& kernel) { + if (m_focus_state_changed_notification_enabled) { + m_has_focus_state_changed = true; + this->SignalSystemEventIfNeeded(kernel); + } + } + + void OnOperationAndPerformanceModeChanged(Kernel::KernelCore& kernel); public: - void SetFocusStateChangedNotificationEnabled(bool enabled) { + void SetFocusStateChangedNotificationEnabled(Kernel::KernelCore& kernel, bool enabled) { m_focus_state_changed_notification_enabled = enabled; - this->SignalSystemEventIfNeeded(); + this->SignalSystemEventIfNeeded(kernel); } - void SetOperationModeChangedNotificationEnabled(bool enabled) { + void SetOperationModeChangedNotificationEnabled(Kernel::KernelCore& kernel, bool enabled) { m_operation_mode_changed_notification_enabled = enabled; - this->SignalSystemEventIfNeeded(); + this->SignalSystemEventIfNeeded(kernel); } - void SetPerformanceModeChangedNotificationEnabled(bool enabled) { + void SetPerformanceModeChangedNotificationEnabled(Kernel::KernelCore& kernel, bool enabled) { m_performance_mode_changed_notification_enabled = enabled; - this->SignalSystemEventIfNeeded(); + this->SignalSystemEventIfNeeded(kernel); } void SetResumeNotificationEnabled(bool enabled) { @@ -129,11 +139,13 @@ public: void RemoveForceResumeIfPossible(); bool IsRunnable() const; bool UpdateRequestedFocusState(); - void SignalSystemEventIfNeeded(); + void SignalSystemEventIfNeeded(Kernel::KernelCore& kernel); public: - void PushUnorderedMessage(AppletMessage message); - bool PopMessage(AppletMessage* out_message); + void PushUnorderedMessage(Kernel::KernelCore& kernel, AppletMessage message); + bool PopMessage(Kernel::KernelCore& kernel, AppletMessage* out_message); + + void ResetForRelaunch(); private: FocusState GetFocusStateWhileForegroundObscured() const; diff --git a/src/core/hle/service/am/process_creation.cpp b/src/core/hle/service/am/process_creation.cpp index b5e31353a2..60154fc301 100644 --- a/src/core/hle/service/am/process_creation.cpp +++ b/src/core/hle/service/am/process_creation.cpp @@ -1,6 +1,10 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 #include "core/core.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/nca_metadata.h" @@ -16,7 +20,7 @@ namespace Service::AM { namespace { -FileSys::StorageId GetStorageIdForFrontendSlot( +[[nodiscard]] FileSys::StorageId GetStorageIdForFrontendSlot( std::optional slot) { if (!slot.has_value()) { return FileSys::StorageId::None; @@ -36,31 +40,23 @@ FileSys::StorageId GetStorageIdForFrontendSlot( } } -std::unique_ptr CreateProcessImpl(std::unique_ptr& out_loader, - Loader::ResultStatus& out_load_result, - Core::System& system, FileSys::VirtualFile file, - u64 program_id, u64 program_index) { +[[nodiscard]] inline std::unique_ptr CreateProcessImpl(std::unique_ptr& out_loader, Loader::ResultStatus& out_load_result, Core::System& system, FileSys::VirtualFile file, u64 program_id, u64 program_index) { // Get the appropriate loader to parse this NCA. out_loader = Loader::GetLoader(system, file, program_id, program_index); - // Ensure we have a loader which can parse the NCA. - if (!out_loader) { - return nullptr; + if (out_loader) { + // Try to load the process. + auto process = std::make_unique(system); + if (process->Initialize(*out_loader, out_load_result)) { + return process; + } } - - // Try to load the process. - auto process = std::make_unique(system); - if (process->Initialize(*out_loader, out_load_result)) { - return process; - } - return nullptr; } } // Anonymous namespace -std::unique_ptr CreateProcess(Core::System& system, u64 program_id, - u8 minimum_key_generation, u8 maximum_key_generation) { +std::unique_ptr CreateProcess(Core::System& system, u64 program_id, u8 minimum_key_generation, u8 maximum_key_generation) { // Attempt to load program NCA. FileSys::VirtualFile nca_raw{}; @@ -79,8 +75,7 @@ std::unique_ptr CreateProcess(Core::System& system, u64 program_id, if (nca.GetStatus() == Loader::ResultStatus::Success && (nca.GetKeyGeneration() < minimum_key_generation || nca.GetKeyGeneration() > maximum_key_generation)) { - LOG_WARNING(Service_LDR, "Skipping program {:016X} with generation {}", program_id, - nca.GetKeyGeneration()); + LOG_WARNING(Service_LDR, "Skipping program {:016X} with generation {}", program_id, nca.GetKeyGeneration()); return nullptr; } } @@ -90,42 +85,48 @@ std::unique_ptr CreateProcess(Core::System& system, u64 program_id, return CreateProcessImpl(loader, status, system, nca_raw, program_id, 0); } -std::unique_ptr CreateApplicationProcess(std::vector& out_control, - std::unique_ptr& out_loader, - Loader::ResultStatus& out_load_result, - Core::System& system, FileSys::VirtualFile file, - u64 program_id, u64 program_index) { - auto process = - CreateProcessImpl(out_loader, out_load_result, system, file, program_id, program_index); - if (!process) { - return nullptr; - } +std::unique_ptr CreateApplicationProcess(std::vector& out_control, std::unique_ptr& out_loader, Loader::ResultStatus& out_load_result, Core::System& system, FileSys::VirtualFile file, u64 program_id, u64 program_index) { + if (auto process = CreateProcessImpl(out_loader, out_load_result, system, file, program_id, program_index); process) { + FileSys::NACP nacp; + if (out_loader->ReadControlData(nacp) == Loader::ResultStatus::Success) { + out_control = nacp.GetRawBytes(); + } else { + out_control.resize(sizeof(FileSys::RawNACP)); + std::fill(out_control.begin(), out_control.end(), (u8) 0); + } - FileSys::NACP nacp; - if (out_loader->ReadControlData(nacp) == Loader::ResultStatus::Success) { - out_control = nacp.GetRawBytes(); - } else { - out_control.resize(sizeof(FileSys::RawNACP)); - std::fill(out_control.begin(), out_control.end(), (u8) 0); - } + auto& storage = system.GetContentProviderUnion(); + Service::Glue::ApplicationLaunchProperty launch{}; + launch.title_id = process->GetProgramId(); + FileSys::PatchManager pm{launch.title_id, system.GetFileSystemController(), storage}; + launch.version = pm.GetGameVersion().value_or(0); + + // TODO(DarkLordZach): When FSController/Game Card Support is added, if + // current_process_game_card use correct StorageId + launch.base_game_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(launch.title_id, FileSys::ContentRecordType::Program)); + launch.update_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program)); + + system.GetARPManager().Register(launch.title_id, launch, out_control); + return process; + } + return nullptr; +} + +bool ReinitializeProcess(Core::System& system, Process& process, u64 program_id) { auto& storage = system.GetContentProviderUnion(); - Service::Glue::ApplicationLaunchProperty launch{}; - launch.title_id = process->GetProgramId(); + const auto nca_raw = storage.GetEntryRaw(program_id, FileSys::ContentRecordType::Program); + if (!nca_raw) { + return false; + } - FileSys::PatchManager pm{launch.title_id, system.GetFileSystemController(), storage}; - launch.version = pm.GetGameVersion().value_or(0); + auto loader = Loader::GetLoader(system, nca_raw, program_id, 0); + if (!loader) { + return false; + } - // TODO(DarkLordZach): When FSController/Game Card Support is added, if - // current_process_game_card use correct StorageId - launch.base_game_storage_id = GetStorageIdForFrontendSlot( - storage.GetSlotForEntry(launch.title_id, FileSys::ContentRecordType::Program)); - launch.update_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry( - FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program)); - - system.GetARPManager().Register(launch.title_id, launch, out_control); - - return process; + Loader::ResultStatus status{}; + return process.Initialize(*loader, status); } } // namespace Service::AM diff --git a/src/core/hle/service/am/process_creation.h b/src/core/hle/service/am/process_creation.h index 8cfb9e0c9e..20f0743740 100644 --- a/src/core/hle/service/am/process_creation.h +++ b/src/core/hle/service/am/process_creation.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -24,12 +27,9 @@ class Process; namespace Service::AM { -std::unique_ptr CreateProcess(Core::System& system, u64 program_id, - u8 minimum_key_generation, u8 maximum_key_generation); -std::unique_ptr CreateApplicationProcess(std::vector& out_control, - std::unique_ptr& out_loader, - Loader::ResultStatus& out_load_result, - Core::System& system, FileSys::VirtualFile file, - u64 program_id, u64 program_index); +std::unique_ptr CreateProcess(Core::System& system, u64 program_id, u8 minimum_key_generation, u8 maximum_key_generation); +std::unique_ptr CreateApplicationProcess(std::vector& out_control, std::unique_ptr& out_loader, Loader::ResultStatus& out_load_result, Core::System& system, FileSys::VirtualFile file, u64 program_id, u64 program_index); + +bool ReinitializeProcess(Core::System& system, Process& process, u64 program_id); } // namespace Service::AM diff --git a/src/core/hle/service/am/process_holder.cpp b/src/core/hle/service/am/process_holder.cpp index 21ef5bf838..8ad6599fd6 100644 --- a/src/core/hle/service/am/process_holder.cpp +++ b/src/core/hle/service/am/process_holder.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/process_holder.h b/src/core/hle/service/am/process_holder.h index 3a9b81dfb4..dbf04668dc 100644 --- a/src/core/hle/service/am/process_holder.h +++ b/src/core/hle/service/am/process_holder.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 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 562d95f001..437032c2f4 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 @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -73,8 +73,7 @@ Result IAllSystemAppletProxiesService::OpenLibraryAppletProxy( Result IAllSystemAppletProxiesService::OpenOverlayAppletProxy( Out> out_overlay_applet_proxy, ClientProcessId pid, - InCopyHandle process_handle, - InLargeData attribute) { + InCopyHandle process_handle) { LOG_WARNING(Service_AM, "called"); if (const auto applet = this->GetAppletFromProcessId(pid); applet) { @@ -89,8 +88,7 @@ Result IAllSystemAppletProxiesService::OpenOverlayAppletProxy( Result IAllSystemAppletProxiesService::OpenSystemApplicationProxy( Out> out_system_application_proxy, ClientProcessId pid, - InCopyHandle process_handle, - InLargeData attribute) { + InCopyHandle process_handle) { LOG_DEBUG(Service_AM, "called"); if (const auto applet = this->GetAppletFromProcessId(pid); applet) { 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 eccd92a376..73cb8a09ac 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 @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -36,15 +36,13 @@ private: InCopyHandle process_handle, InLargeData attribute); Result OpenOverlayAppletProxy(Out> out_overlay_applet_proxy, - ClientProcessId pid, InCopyHandle process_handle, - InLargeData attribute); + ClientProcessId pid, InCopyHandle process_handle); Result OpenLibraryAppletProxyOld( Out> out_library_applet_proxy, ClientProcessId pid, InCopyHandle process_handle); Result OpenSystemApplicationProxy( Out> out_system_application_proxy, ClientProcessId pid, - InCopyHandle process_handle, - InLargeData attribute); + InCopyHandle process_handle); Result GetSystemProcessCommonFunctions(); Result GetAppletAlternativeFunctions(); 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 ee536221d9..5ffec23b3e 100644 --- a/src/core/hle/service/am/service/applet_common_functions.cpp +++ b/src/core/hle/service/am/service/applet_common_functions.cpp @@ -4,6 +4,8 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "core/core.h" +#include "core/file_sys/common_funcs.h" #include "core/hle/service/am/applet.h" #include "core/hle/service/am/service/applet_common_functions.h" #include "core/hle/service/cmif_serialization.h" @@ -78,7 +80,7 @@ Result IAppletCommonFunctions::SetCpuBoostRequestPriority(s32 priority) { Result IAppletCommonFunctions::GetCurrentApplicationId(Out out_application_id) { LOG_WARNING(Service_AM, "(STUBBED) called"); - *out_application_id = system.GetApplicationProcessProgramID() & ~0xFFFULL; + *out_application_id = FileSys::GetBaseTitleID(system.GetApplicationProcessProgramID()); R_SUCCEED(); } diff --git a/src/core/hle/service/am/service/application_accessor.cpp b/src/core/hle/service/am/service/application_accessor.cpp index 88a1724fc6..f357871706 100644 --- a/src/core/hle/service/am/service/application_accessor.cpp +++ b/src/core/hle/service/am/service/application_accessor.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -69,7 +69,7 @@ Result IApplicationAccessor::RequestExit() { std::scoped_lock lk{m_applet->lock}; if (m_applet->exit_locked) { - m_applet->lifecycle_manager.RequestExit(); + m_applet->lifecycle_manager.RequestExit(system.Kernel()); m_applet->UpdateSuspensionStateLocked(true); } else { m_applet->process->Terminate(); diff --git a/src/core/hle/service/am/service/application_accessor.h b/src/core/hle/service/am/service/application_accessor.h index b9797bcc0a..7908099916 100644 --- a/src/core/hle/service/am/service/application_accessor.h +++ b/src/core/hle/service/am/service/application_accessor.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/application_creator.cpp b/src/core/hle/service/am/service/application_creator.cpp index 81620a5067..4eb2f29e22 100644 --- a/src/core/hle/service/am/service/application_creator.cpp +++ b/src/core/hle/service/am/service/application_creator.cpp @@ -16,13 +16,13 @@ #include "core/hle/service/cmif_serialization.h" #include "core/loader/loader.h" #include "core/launch_timestamp_cache.h" +#include "core/hle/api_version.h" namespace Service::AM { namespace { -Result CreateGuestApplication(SharedPointer* out_application_accessor, - Core::System& system, WindowSystem& window_system, u64 program_id) { +Result CreateGuestApplication(SharedPointer* out_application_accessor, Core::System& system, WindowSystem& window_system, u64 program_id) { FileSys::VirtualFile nca_raw{}; // Get the program NCA from storage. @@ -35,8 +35,7 @@ Result CreateGuestApplication(SharedPointer* out_applicati std::vector control; std::unique_ptr loader; Loader::ResultStatus result; - auto process = - CreateApplicationProcess(control, loader, result, system, nca_raw, program_id, 0); + auto process = CreateApplicationProcess(control, loader, result, system, nca_raw, program_id, 0); R_UNLESS(process != nullptr, ResultUnknown); const auto applet = std::make_shared(system, std::move(process), true); @@ -47,8 +46,7 @@ Result CreateGuestApplication(SharedPointer* out_applicati window_system.TrackApplet(applet, true); - *out_application_accessor = - std::make_shared(system, applet, window_system); + *out_application_accessor = std::make_shared(system, applet, window_system); R_SUCCEED(); } @@ -90,9 +88,7 @@ Result IApplicationCreator::CreateSystemApplication( std::vector control; std::unique_ptr loader; - - auto process = - CreateProcess(system, application_id, 1, 22); + auto process = CreateProcess(system, application_id, 1, HLE::ApiVersion::HOS_VERSION_MAJOR); R_UNLESS(process != nullptr, ResultUnknown); const auto applet = std::make_shared(system, std::move(process), true); @@ -103,8 +99,7 @@ Result IApplicationCreator::CreateSystemApplication( m_window_system.TrackApplet(applet, true); - *out_application_accessor = - std::make_shared(system, applet, m_window_system); + *out_application_accessor = std::make_shared(system, applet, m_window_system); Core::LaunchTimestampCache::SaveLaunchTimestamp(application_id); R_SUCCEED(); } diff --git a/src/core/hle/service/am/service/application_creator.h b/src/core/hle/service/am/service/application_creator.h index 75975b9596..59ae924131 100644 --- a/src/core/hle/service/am/service/application_creator.h +++ b/src/core/hle/service/am/service/application_creator.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/am/service/application_functions.cpp b/src/core/hle/service/am/service/application_functions.cpp index 9ab343e59e..90d292c4a2 100644 --- a/src/core/hle/service/am/service/application_functions.cpp +++ b/src/core/hle/service/am/service/application_functions.cpp @@ -4,13 +4,18 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + +#include "common/hex_util.h" #include "common/settings.h" #include "common/uuid.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" #include "core/file_sys/savedata_factory.h" #include "core/hle/kernel/k_transfer_memory.h" +#include "core/hle/result.h" #include "core/hle/service/am/am_results.h" #include "core/hle/service/am/applet.h" #include "core/hle/service/am/service/application_functions.h" @@ -25,6 +30,27 @@ namespace Service::AM { +namespace { + +FileSys::PatchManager::Metadata GetApplicationMetadata(Core::System& system, u64 program_id) { + auto metadata = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, program_id); + if (metadata.first != nullptr) { + return metadata; + } + + const auto application_id = FileSys::GetBaseTitleID(program_id); + if (application_id == program_id) { + return metadata; + } + + LOG_DEBUG(Service_AM, + "no metadata for program_id={:016X}, falling back to application_id={:016X}", + program_id, application_id); + return FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, application_id); +} + +} // Anonymous namespace + IApplicationFunctions::IApplicationFunctions(Core::System& system_, std::shared_ptr applet) : ServiceFramework{system_, "IApplicationFunctions"}, m_applet{std::move(applet)} { // clang-format off @@ -56,7 +82,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_, std::shared_ {37, nullptr, "GetLimitedApplicationLicenseUpgradableEvent"}, {40, D<&IApplicationFunctions::NotifyRunning>, "NotifyRunning"}, {50, D<&IApplicationFunctions::GetPseudoDeviceId>, "GetPseudoDeviceId"}, - {60, nullptr, "SetMediaPlaybackStateForApplication"}, + {60, D<&IApplicationFunctions::SetMediaPlaybackStateForApplication>, "SetMediaPlaybackStateForApplication"}, {65, D<&IApplicationFunctions::IsGamePlayRecordingSupported>, "IsGamePlayRecordingSupported"}, {66, D<&IApplicationFunctions::InitializeGamePlayRecording>, "InitializeGamePlayRecording"}, {67, D<&IApplicationFunctions::SetGamePlayRecordingState>, "SetGamePlayRecordingState"}, @@ -153,20 +179,7 @@ Result IApplicationFunctions::GetDesiredLanguage(Out out_language_code) { // Default to 0 (all languages supported) u32 supported_languages = 0; - const auto res = [this] { - const FileSys::PatchManager pm{m_applet->program_id, system.GetFileSystemController(), - system.GetContentProvider()}; - auto metadata = pm.GetControlMetadata(); - if (metadata.first != nullptr) { - return metadata; - } - - const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(m_applet->program_id), - system.GetFileSystemController(), - system.GetContentProvider()}; - return pm_update.GetControlMetadata(); - }(); - + const auto res = GetApplicationMetadata(system, m_applet->program_id); if (res.first != nullptr) { supported_languages = res.first->GetSupportedLanguages(); } @@ -204,20 +217,7 @@ Result IApplicationFunctions::SetTerminateResult(Result terminate_result) { Result IApplicationFunctions::GetDisplayVersion(Out out_display_version) { LOG_DEBUG(Service_AM, "called"); - const auto res = [this] { - const FileSys::PatchManager pm{m_applet->program_id, system.GetFileSystemController(), - system.GetContentProvider()}; - auto metadata = pm.GetControlMetadata(); - if (metadata.first != nullptr) { - return metadata; - } - - const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(m_applet->program_id), - system.GetFileSystemController(), - system.GetContentProvider()}; - return pm_update.GetControlMetadata(); - }(); - + const auto res = GetApplicationMetadata(system, m_applet->program_id); if (res.first != nullptr) { const auto& version = res.first->GetVersionString(); std::memcpy(out_display_version->string.data(), version.data(), @@ -346,8 +346,23 @@ Result IApplicationFunctions::NotifyRunning(Out out_became_running) { } Result IApplicationFunctions::GetPseudoDeviceId(Out out_pseudo_device_id) { - LOG_WARNING(Service_AM, "(STUBBED) called"); - *out_pseudo_device_id = {}; + LOG_WARNING(Service_AM, "(stubbed)"); + R_UNLESS(out_pseudo_device_id != nullptr, ResultUnknown); + + // This should be hashed with the device specific hash + // for now this will do + const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id); + R_UNLESS(res.first != nullptr, ResultUnknown); + std::array hash; + unsigned int hash_len = 0; + auto const seed = res.first->raw.seed_for_pseudo_device_id; + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + auto const algorithm = EVP_sha1(); + EVP_DigestInit_ex(ctx, algorithm, nullptr); + EVP_DigestUpdate(ctx, &seed, sizeof(seed)); + EVP_DigestFinal_ex(ctx, hash.data(), &hash_len); + EVP_MD_CTX_free(ctx); + *out_pseudo_device_id = Common::UUID::MakeRFC4122V5(std::span{hash.begin(), hash.begin() + 16}); R_SUCCEED(); } @@ -364,6 +379,13 @@ Result IApplicationFunctions::InitializeGamePlayRecording( R_SUCCEED(); } +Result IApplicationFunctions::SetMediaPlaybackStateForApplication(bool enabled) { + LOG_WARNING(Service_AM, "(stubbed) {}", enabled); + std::scoped_lock lk{m_applet->lock}; + m_applet->media_playback_state = enabled; + R_SUCCEED(); +} + Result IApplicationFunctions::SetGamePlayRecordingState( GamePlayRecordingState game_play_recording_state) { LOG_WARNING(Service_AM, "(STUBBED) called"); @@ -433,15 +455,21 @@ Result IApplicationFunctions::ExecuteProgram(ProgramSpecifyKind kind, u64 value) // https://switchbrew.org/wiki/Applet_Manager_services#CreateApplicationAndRequestToStart Result IApplicationFunctions::CreateApplicationAndRequestToStart(u64 application_id) { - LOG_INFO(Service_AM, "called, application_id={:016X}", application_id); + LOG_INFO(Service_AM, "called, application_id={:016X} current_program_id={:016X}", application_id, m_applet->program_id); - // If application_id is 0, relaunch the current application - const u64 target_application_id = - (application_id == 0) ? m_applet->program_id : application_id; + if (application_id == 0 || + FileSys::GetBaseTitleID(application_id) == FileSys::GetBaseTitleID(m_applet->program_id)) { + const auto program_index = application_id == 0 + ? 0 + : application_id - FileSys::GetBaseTitleID(application_id); - system.GetUserChannel() = m_applet->user_channel_launch_parameter; - system.ExecuteProgram(target_application_id); - R_SUCCEED(); + system.GetUserChannel() = m_applet->user_channel_launch_parameter; + system.ExecuteProgram(program_index); + R_SUCCEED(); + } + + LOG_ERROR(Service_AM, "Launching a different application ({:016X}) is not implemented!", application_id); + R_THROW(ResultUnknown); } Result IApplicationFunctions::ClearUserChannel() { diff --git a/src/core/hle/service/am/service/application_functions.h b/src/core/hle/service/am/service/application_functions.h index abee2f9d47..b97ed7e25e 100644 --- a/src/core/hle/service/am/service/application_functions.h +++ b/src/core/hle/service/am/service/application_functions.h @@ -53,6 +53,7 @@ private: Result IsGamePlayRecordingSupported(Out out_is_game_play_recording_supported); Result InitializeGamePlayRecording( u64 transfer_memory_size, InCopyHandle transfer_memory_handle); + Result SetMediaPlaybackStateForApplication(bool enabled); Result SetGamePlayRecordingState(GamePlayRecordingState game_play_recording_state); Result EnableApplicationCrashReport(bool enabled); Result InitializeApplicationCopyrightFrameBuffer( diff --git a/src/core/hle/service/am/service/application_proxy.cpp b/src/core/hle/service/am/service/application_proxy.cpp index 6e1328fee7..df438e1e17 100644 --- a/src/core/hle/service/am/service/application_proxy.cpp +++ b/src/core/hle/service/am/service/application_proxy.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/application_proxy.h b/src/core/hle/service/am/service/application_proxy.h index 8c62459c45..b56f597605 100644 --- a/src/core/hle/service/am/service/application_proxy.h +++ b/src/core/hle/service/am/service/application_proxy.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/application_proxy_service.cpp b/src/core/hle/service/am/service/application_proxy_service.cpp index b7d7b3c2d9..7727951d8c 100644 --- a/src/core/hle/service/am/service/application_proxy_service.cpp +++ b/src/core/hle/service/am/service/application_proxy_service.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/application_proxy_service.h b/src/core/hle/service/am/service/application_proxy_service.h index e5f4ea345b..fce860a7c1 100644 --- a/src/core/hle/service/am/service/application_proxy_service.h +++ b/src/core/hle/service/am/service/application_proxy_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/audio_controller.cpp b/src/core/hle/service/am/service/audio_controller.cpp index f627b1df0e..4e9af756f2 100644 --- a/src/core/hle/service/am/service/audio_controller.cpp +++ b/src/core/hle/service/am/service/audio_controller.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/am/service/audio_controller.h b/src/core/hle/service/am/service/audio_controller.h index 4b0f3f9aed..a13f3b199b 100644 --- a/src/core/hle/service/am/service/audio_controller.h +++ b/src/core/hle/service/am/service/audio_controller.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/common_state_getter.cpp b/src/core/hle/service/am/service/common_state_getter.cpp index 5b5e21b8e0..044929e434 100644 --- a/src/core/hle/service/am/service/common_state_getter.cpp +++ b/src/core/hle/service/am/service/common_state_getter.cpp @@ -39,7 +39,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, std::shared_ptr, "PushToGeneralChannel"}, - {30, nullptr, "GetHomeButtonReaderLockAccessor"}, + {30, D<&ICommonStateGetter::GetHomeButtonReaderLockAccessor>, "GetHomeButtonReaderLockAccessor"}, {31, D<&ICommonStateGetter::GetReaderLockAccessorEx>, "GetReaderLockAccessorEx"}, //2.0.0+ {32, D<&ICommonStateGetter::GetWriterLockAccessorEx>, "GetWriterLockAccessorEx"}, //7.0.0+ {40, nullptr, "GetCradleFwVersion"}, //2.0.0+ @@ -65,7 +65,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, std::shared_ptr, "SetHandlingHomeButtonShortPressedEnabled"}, {110, nullptr, "OpenMyGpuErrorHandler"}, {120, D<&ICommonStateGetter::GetAppletLaunchedHistory>, "GetAppletLaunchedHistory"}, //13.0.0+ - {130, nullptr, "Unknown130"}, //21.0.0+ + {130, D<&ICommonStateGetter::EnableStartupLogoDisappearedMessage>, "EnableStartupLogoDisappearedMessage"}, //21.0.0+ {200, D<&ICommonStateGetter::GetOperationModeSystemInfo>, "GetOperationModeSystemInfo"}, {300, D<&ICommonStateGetter::GetSettingsPlatformRegion>, "GetSettingsPlatformRegion"}, {400, nullptr, "ActivateMigrationService"}, @@ -74,14 +74,17 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_, std::shared_ptr, "Unknown610"}, //21.0.0+ {611, D<&ICommonStateGetter::Unknown611>, "Unknown611"}, //22.0.0+ {900, D<&ICommonStateGetter::SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled>, "SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled"}, //11.0.0+ {910, nullptr, "GetLaunchRequiredTick"}, //17.0.0+ - {1000, nullptr, "BeginVrMode3d"}, //19.0.0+ - {1001, nullptr, "EndVrMode3d"}, //19.0.0+ - {1002, nullptr, "IsVrModeEnabled3d"}, //19.0.0+ + {1000, D<&ICommonStateGetter::BeginVrMode3d>, "BeginVrMode3d"}, //19.0.0+ + {1001, D<&ICommonStateGetter::EndVrMode3d>, "EndVrMode3d"}, //19.0.0+ + {1002, D<&ICommonStateGetter::IsVrModeEnabled3d>, "IsVrModeEnabled3d"}, //19.0.0+ + {1003, D<&ICommonStateGetter::GetVrLaboGoggleViewport>, "GetVrLaboGoggleViewport"}, //21.0.0+ + {1004, D<&ICommonStateGetter::GetPanelPhysicalSizeForSpecificTitle>, "GetPanelPhysicalSizeForSpecificTitle"}, //21.0.0+ + {1005, D<&ICommonStateGetter::GetPanelResolutionForSpecificTitle>, "GetPanelResolutionForSpecificTitle"}, //21.0.0+ }; // clang-format on @@ -99,7 +102,7 @@ Result ICommonStateGetter::GetEventHandle(OutCopyHandle Result ICommonStateGetter::ReceiveMessage(Out out_applet_message) { LOG_DEBUG(Service_AM, "called"); - if (!m_applet->lifecycle_manager.PopMessage(out_applet_message)) { + if (!m_applet->lifecycle_manager.PopMessage(system.Kernel(), out_applet_message)) { LOG_ERROR(Service_AM, "Tried to pop message but none was available!"); R_THROW(AM::ResultNoMessages); } @@ -123,21 +126,21 @@ Result ICommonStateGetter::RequestToAcquireSleepLock() { LOG_WARNING(Service_AM, "(STUBBED) called"); // Sleep lock is acquired immediately. - m_applet->sleep_lock_event.Signal(); + m_applet->sleep_lock_event.Signal(system.Kernel()); R_SUCCEED(); } Result ICommonStateGetter::ReleaseSleepLock() { LOG_WARNING(Service_AM, "(STUBBED) called"); - m_applet->sleep_lock_event.Clear(); + m_applet->sleep_lock_event.Clear(system.Kernel()); R_SUCCEED(); } Result ICommonStateGetter::ReleaseSleepLockTransiently() { LOG_WARNING(Service_AM, "(STUBBED) called"); - m_applet->sleep_lock_event.Clear(); + m_applet->sleep_lock_event.Clear(system.Kernel()); R_SUCCEED(); } @@ -281,25 +284,25 @@ Result ICommonStateGetter::PerformSystemButtonPressingIfInFocus(SystemButtonType switch (type) { case SystemButtonType::HomeButtonShortPressing: if (!m_applet->home_button_short_pressed_blocked) { - m_applet->lifecycle_manager.PushUnorderedMessage( + m_applet->lifecycle_manager.PushUnorderedMessage(system.Kernel(), AppletMessage::DetectShortPressingHomeButton); } break; case SystemButtonType::HomeButtonLongPressing: if (!m_applet->home_button_long_pressed_blocked) { - m_applet->lifecycle_manager.PushUnorderedMessage( + m_applet->lifecycle_manager.PushUnorderedMessage(system.Kernel(), AppletMessage::DetectLongPressingHomeButton); } break; case SystemButtonType::CaptureButtonShortPressing: if (m_applet->handling_capture_button_short_pressed_message_enabled_for_applet) { - m_applet->lifecycle_manager.PushUnorderedMessage( + m_applet->lifecycle_manager.PushUnorderedMessage(system.Kernel(), AppletMessage::DetectShortPressingCaptureButton); } break; case SystemButtonType::CaptureButtonLongPressing: if (m_applet->handling_capture_button_long_pressed_message_enabled_for_applet) { - m_applet->lifecycle_manager.PushUnorderedMessage( + m_applet->lifecycle_manager.PushUnorderedMessage(system.Kernel(), AppletMessage::DetectLongPressingCaptureButton); } break; @@ -311,6 +314,12 @@ Result ICommonStateGetter::PerformSystemButtonPressingIfInFocus(SystemButtonType R_SUCCEED(); } +Result ICommonStateGetter::EnableStartupLogoDisappearedMessage() { + LOG_WARNING(Service_AM, "(STUBBED) called"); + m_applet->lifecycle_manager.PushUnorderedMessage(system.Kernel(), AppletMessage::StartupLogoDisappeared); + R_SUCCEED(); +} + Result ICommonStateGetter::GetOperationModeSystemInfo(Out out_operation_mode_system_info) { LOG_WARNING(Service_AM, "(STUBBED) called"); *out_operation_mode_system_info = 0; @@ -355,6 +364,12 @@ Result ICommonStateGetter::PushToGeneralChannel(SharedPointer storage) R_SUCCEED(); } +Result ICommonStateGetter::GetHomeButtonReaderLockAccessor(Out> out_lock_accessor) { + LOG_DEBUG(Service_AM, "called"); + *out_lock_accessor = std::make_shared(system); + R_SUCCEED(); +} + Result ICommonStateGetter::SetHandlingHomeButtonShortPressedEnabled(bool enabled) { LOG_DEBUG(Service_AM, "called, enabled={} applet_id={}", enabled, m_applet->applet_id); @@ -363,14 +378,58 @@ Result ICommonStateGetter::SetHandlingHomeButtonShortPressedEnabled(bool enabled R_SUCCEED(); } -Result ICommonStateGetter::Unknown610() { +Result ICommonStateGetter::Unknown610(u64 unk) { LOG_WARNING(Service_AM, "(STUBBED) called"); R_SUCCEED(); } -Result ICommonStateGetter::Unknown611() { +Result ICommonStateGetter::Unknown611(u8 unk) { LOG_WARNING(Service_AM, "(STUBBED) called"); R_SUCCEED(); } +Result ICommonStateGetter::BeginVrMode3d() { + std::scoped_lock lk{m_applet->lock}; + m_applet->vr_mode_enabled_3d = true; + LOG_WARNING(Service_AM, "VR Mode is {}", m_applet->vr_mode_enabled_3d ? "on" : "off"); + R_SUCCEED(); +} + +Result ICommonStateGetter::EndVrMode3d() { + std::scoped_lock lk{m_applet->lock}; + m_applet->vr_mode_enabled_3d = false; + LOG_WARNING(Service_AM, "VR Mode is {}", m_applet->vr_mode_enabled_3d ? "on" : "off"); + R_SUCCEED(); +} + +Result ICommonStateGetter::IsVrModeEnabled3d(Out out_is_vr_mode_enabled_3d) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + std::scoped_lock lk{m_applet->lock}; + *out_is_vr_mode_enabled_3d = m_applet->vr_mode_enabled_3d; + R_SUCCEED(); +} + +Result ICommonStateGetter::GetVrLaboGoggleViewport(Out out_x, Out out_y, Out out_width, Out out_height) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + *out_x = 0; + *out_y = 0; + *out_width = 1280; + *out_height = 720; + R_SUCCEED(); +} + +Result ICommonStateGetter::GetPanelPhysicalSizeForSpecificTitle(Out out_width, Out out_height) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + *out_width = 137250.0f / 1000.0f; + *out_height = 77200.0f / 1000.0f; + R_SUCCEED(); +} + +Result ICommonStateGetter::GetPanelResolutionForSpecificTitle(Out out_width, Out out_height) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + *out_width = 1280; + *out_height = 720; + R_SUCCEED(); +} + } // namespace Service::AM diff --git a/src/core/hle/service/am/service/common_state_getter.h b/src/core/hle/service/am/service/common_state_getter.h index c1bec4f2a2..da86b5d079 100644 --- a/src/core/hle/service/am/service/common_state_getter.h +++ b/src/core/hle/service/am/service/common_state_getter.h @@ -56,15 +56,23 @@ private: Result GetDefaultDisplayResolution(Out out_width, Out out_height); Result GetBuiltInDisplayType(Out out_display_type); Result PerformSystemButtonPressingIfInFocus(SystemButtonType type); + Result EnableStartupLogoDisappearedMessage(); Result GetOperationModeSystemInfo(Out out_operation_mode_system_info); Result GetAppletLaunchedHistory(Out out_count, OutArray out_applet_ids); Result GetSettingsPlatformRegion(Out out_settings_platform_region); Result SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(); Result PushToGeneralChannel(SharedPointer storage); // cmd 20 + Result GetHomeButtonReaderLockAccessor(Out> out_lock_accessor); Result SetHandlingHomeButtonShortPressedEnabled(bool enabled); - Result Unknown610(); - Result Unknown611(); + Result Unknown610(u64 unk); + Result Unknown611(u8 unk); + Result BeginVrMode3d(); + Result EndVrMode3d(); + Result IsVrModeEnabled3d(Out out_is_vr_mode_enabled_3d); + Result GetVrLaboGoggleViewport(Out out_x, Out out_y, Out out_width, Out out_height); + Result GetPanelPhysicalSizeForSpecificTitle(Out out_width, Out out_height); + Result GetPanelResolutionForSpecificTitle(Out out_width, Out out_height); void SetCpuBoostMode(HLERequestContext& ctx); diff --git a/src/core/hle/service/am/service/cradle_firmware_updater.cpp b/src/core/hle/service/am/service/cradle_firmware_updater.cpp index 0a8af0858d..b9f19e59d2 100644 --- a/src/core/hle/service/am/service/cradle_firmware_updater.cpp +++ b/src/core/hle/service/am/service/cradle_firmware_updater.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/cradle_firmware_updater.h b/src/core/hle/service/am/service/cradle_firmware_updater.h index 3e803f0ae9..c895a34c22 100644 --- a/src/core/hle/service/am/service/cradle_firmware_updater.h +++ b/src/core/hle/service/am/service/cradle_firmware_updater.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/debug_functions.cpp b/src/core/hle/service/am/service/debug_functions.cpp index fcac4776db..4128366270 100644 --- a/src/core/hle/service/am/service/debug_functions.cpp +++ b/src/core/hle/service/am/service/debug_functions.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/debug_functions.h b/src/core/hle/service/am/service/debug_functions.h index d559687439..381499e93d 100644 --- a/src/core/hle/service/am/service/debug_functions.h +++ b/src/core/hle/service/am/service/debug_functions.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/display_controller.cpp b/src/core/hle/service/am/service/display_controller.cpp index a02f012a92..8f87327b17 100644 --- a/src/core/hle/service/am/service/display_controller.cpp +++ b/src/core/hle/service/am/service/display_controller.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -8,6 +8,7 @@ #include "core/hle/service/am/applet.h" #include "core/hle/service/am/service/display_controller.h" #include "core/hle/service/cmif_serialization.h" +#include "core/hle/service/vi/shared_buffer_manager.h" namespace Service::AM { @@ -71,16 +72,16 @@ Result IDisplayController::TakeScreenShotOfOwnLayer(bool unknown0, s32 fbshare_l } Result IDisplayController::ClearCaptureBuffer(bool unknown0, s32 fbshare_layer_index, u32 color) { - LOG_WARNING(Service_AM, "(STUBBED) called, unknown0={} fbshare_layer_index={} color={:#x}", - unknown0, fbshare_layer_index, color); - R_SUCCEED(); + LOG_DEBUG(Service_AM, "called, unknown0={} fbshare_layer_index={} color={:#x}", unknown0, + fbshare_layer_index, color); + R_RETURN(applet->display_layer_manager.ClearAppletCaptureBuffer(fbshare_layer_index, color)); } Result IDisplayController::AcquireLastForegroundCaptureSharedBuffer( Out out_was_written, Out out_fbshare_layer_index) { - LOG_WARNING(Service_AM, "(STUBBED) called"); - R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(out_was_written, - out_fbshare_layer_index)); + LOG_DEBUG(Service_AM, "called"); + R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer( + out_was_written, out_fbshare_layer_index, VI::CaptureKind::LastForeground)); } Result IDisplayController::ReleaseLastForegroundCaptureSharedBuffer() { @@ -90,9 +91,9 @@ Result IDisplayController::ReleaseLastForegroundCaptureSharedBuffer() { Result IDisplayController::AcquireCallerAppletCaptureSharedBuffer( Out out_was_written, Out out_fbshare_layer_index) { - LOG_WARNING(Service_AM, "(STUBBED) called"); - R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(out_was_written, - out_fbshare_layer_index)); + LOG_DEBUG(Service_AM, "called"); + R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer( + out_was_written, out_fbshare_layer_index, VI::CaptureKind::CallerApplet)); } Result IDisplayController::ReleaseCallerAppletCaptureSharedBuffer() { @@ -102,9 +103,9 @@ Result IDisplayController::ReleaseCallerAppletCaptureSharedBuffer() { Result IDisplayController::AcquireLastApplicationCaptureSharedBuffer( Out out_was_written, Out out_fbshare_layer_index) { - LOG_WARNING(Service_AM, "(STUBBED) called"); - R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(out_was_written, - out_fbshare_layer_index)); + LOG_DEBUG(Service_AM, "called"); + R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer( + out_was_written, out_fbshare_layer_index, VI::CaptureKind::LastApplication)); } Result IDisplayController::ReleaseLastApplicationCaptureSharedBuffer() { diff --git a/src/core/hle/service/am/service/display_controller.h b/src/core/hle/service/am/service/display_controller.h index 171feace6e..35e98b9ad3 100644 --- a/src/core/hle/service/am/service/display_controller.h +++ b/src/core/hle/service/am/service/display_controller.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/am/service/global_state_controller.cpp b/src/core/hle/service/am/service/global_state_controller.cpp index 3e3b61b0a0..e7cd60927c 100644 --- a/src/core/hle/service/am/service/global_state_controller.cpp +++ b/src/core/hle/service/am/service/global_state_controller.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/global_state_controller.h b/src/core/hle/service/am/service/global_state_controller.h index 83efb57dfc..012692ecd8 100644 --- a/src/core/hle/service/am/service/global_state_controller.h +++ b/src/core/hle/service/am/service/global_state_controller.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/library_applet_accessor.cpp b/src/core/hle/service/am/service/library_applet_accessor.cpp index ef91f69c23..849e5794cf 100644 --- a/src/core/hle/service/am/service/library_applet_accessor.cpp +++ b/src/core/hle/service/am/service/library_applet_accessor.cpp @@ -75,7 +75,7 @@ ILibraryAppletAccessor::ILibraryAppletAccessor(Core::System& system_, {105, D<&ILibraryAppletAccessor::GetPopOutDataEvent>, "GetPopOutDataEvent"}, {106, D<&ILibraryAppletAccessor::GetPopInteractiveOutDataEvent>, "GetPopInteractiveOutDataEvent"}, {110, nullptr, "NeedsToExitProcess"}, - {120, nullptr, "GetLibraryAppletInfo"}, + {120, D<&ILibraryAppletAccessor::GetLibraryAppletInfo>, "GetLibraryAppletInfo"}, {150, nullptr, "RequestForAppletToGetForeground"}, {160, D<&ILibraryAppletAccessor::GetIndirectLayerConsumerHandle>, "GetIndirectLayerConsumerHandle"}, //2.0.0+ {170, D<&ILibraryAppletAccessor::Unknown170>, "Unknown170"}, //22.0.0+ @@ -123,7 +123,7 @@ Result ILibraryAppletAccessor::RequestExit() { LOG_DEBUG(Service_AM, "called"); { std::scoped_lock lk{m_applet->lock}; - m_applet->lifecycle_manager.RequestExit(); + m_applet->lifecycle_manager.RequestExit(system.Kernel()); } FrontendRequestExit(); R_SUCCEED(); @@ -157,22 +157,22 @@ Result ILibraryAppletAccessor::PushInData(SharedPointer storage) { } } - m_broker->GetInData().Push(storage); + m_broker->GetInData().Push(system.Kernel(), storage); R_SUCCEED(); } Result ILibraryAppletAccessor::PopOutData(Out> out_storage) { LOG_DEBUG(Service_AM, "called"); + R_TRY(m_broker->GetOutData().Pop(system.Kernel(), out_storage.Get())); if (auto caller_applet = m_applet->caller_applet.lock(); caller_applet) { - caller_applet->lifecycle_manager.GetSystemEvent().Signal(); - caller_applet->lifecycle_manager.RequestResumeNotification(); - caller_applet->lifecycle_manager.GetSystemEvent().Clear(); - caller_applet->lifecycle_manager.UpdateRequestedFocusState(); + std::scoped_lock lk{caller_applet->lock}; + const bool focus_state_changed = caller_applet->lifecycle_manager.UpdateRequestedFocusState(); + const bool is_front_app = m_applet->frontend && caller_applet->lifecycle_manager.IsApplication(); + if (focus_state_changed) caller_applet->lifecycle_manager.SignalSystemEventIfNeeded(system.Kernel()); + else if (is_front_app) caller_applet->lifecycle_manager.RequestFocusStateChangedNotification(system.Kernel()); } - R_TRY(m_broker->GetOutData().Pop(out_storage.Get())); - if (m_applet->applet_id == AppletId::ProfileSelect && *out_storage) { auto impl = (*out_storage)->GetImpl(); @@ -186,14 +186,14 @@ Result ILibraryAppletAccessor::PopOutData(Out> out_stora Result ILibraryAppletAccessor::PushInteractiveInData(SharedPointer storage) { LOG_DEBUG(Service_AM, "called"); - m_broker->GetInteractiveInData().Push(storage); + m_broker->GetInteractiveInData().Push(system.Kernel(), storage); FrontendExecuteInteractive(); R_SUCCEED(); } Result ILibraryAppletAccessor::PopInteractiveOutData(Out> out_storage) { LOG_DEBUG(Service_AM, "called"); - R_RETURN(m_broker->GetInteractiveOutData().Pop(out_storage.Get())); + R_RETURN(m_broker->GetInteractiveOutData().Pop(system.Kernel(), out_storage.Get())); } Result ILibraryAppletAccessor::GetPopOutDataEvent(OutCopyHandle out_event) { @@ -218,6 +218,16 @@ Result ILibraryAppletAccessor::GetIndirectLayerConsumerHandle(Out out_handl R_SUCCEED(); } +Result ILibraryAppletAccessor::GetLibraryAppletInfo( + Out out_library_applet_info) { + LOG_INFO(Service_AM, "called"); + *out_library_applet_info = { + .applet_id = m_applet->applet_id, + .library_applet_mode = m_applet->library_applet_mode, + }; + R_SUCCEED(); +} + Result ILibraryAppletAccessor::Unknown170(OutCopyHandle out_event) { LOG_WARNING(Service_AM, "(STUBBED) called"); *out_event = m_applet->unknown_event.GetHandle(); diff --git a/src/core/hle/service/am/service/library_applet_accessor.h b/src/core/hle/service/am/service/library_applet_accessor.h index 02fcd07fd3..ad40bc76c7 100644 --- a/src/core/hle/service/am/service/library_applet_accessor.h +++ b/src/core/hle/service/am/service/library_applet_accessor.h @@ -6,6 +6,7 @@ #pragma once +#include "core/hle/service/am/service/library_applet_self_accessor.h" #include "core/hle/service/cmif_types.h" #include "core/hle/service/service.h" @@ -21,6 +22,10 @@ public: std::shared_ptr applet); ~ILibraryAppletAccessor(); + std::shared_ptr GetApplet() const { + return m_applet; + } + private: Result GetAppletStateChangedEvent(OutCopyHandle out_event); Result IsCompleted(Out out_is_completed); @@ -37,6 +42,7 @@ private: Result GetPopOutDataEvent(OutCopyHandle out_event); Result GetPopInteractiveOutDataEvent(OutCopyHandle out_event); Result GetIndirectLayerConsumerHandle(Out out_handle); + Result GetLibraryAppletInfo(Out out_library_applet_info); Result Unknown170(OutCopyHandle out_event); void FrontendExecute(); 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 3de5740237..4ee92908b5 100644 --- a/src/core/hle/service/am/service/library_applet_creator.cpp +++ b/src/core/hle/service/am/service/library_applet_creator.cpp @@ -13,6 +13,8 @@ #include "core/hle/service/am/process_creation.h" #include "core/hle/service/am/service/library_applet_accessor.h" #include "core/hle/service/am/service/library_applet_creator.h" + +#include "core/hle/api_version.h" #include "core/hle/service/am/service/storage.h" #include "core/hle/service/am/window_system.h" #include "core/hle/service/cmif_serialization.h" @@ -108,40 +110,27 @@ std::shared_ptr CreateGuestApplet(Core::System& system, return {}; } - // TODO: enable other versions of applets - enum : u8 { - Firmware1400 = 14, - Firmware1500 = 15, - Firmware1600 = 16, - Firmware1700 = 17, - Firmware1800 = 18, - Firmware1900 = 19, - Firmware2000 = 20, - Firmware2100 = 21, - Firmware2200 = 22, - }; + auto process = CreateProcess(system, program_id, 1, HLE::ApiVersion::HOS_VERSION_MAJOR); + if (process) { + const auto applet = std::make_shared(system, std::move(process), false); + applet->program_id = program_id; + applet->applet_id = applet_id; + applet->type = AppletType::LibraryApplet; + applet->library_applet_mode = mode; + applet->window_visible = mode != LibraryAppletMode::AllForegroundInitiallyHidden; - auto process = CreateProcess(system, program_id, Firmware1400, Firmware2200); - if (!process) { - // Couldn't initialize the guest process - return {}; + auto broker = std::make_shared(system); + applet->caller_applet = caller_applet; + applet->caller_applet_broker = broker; + { + std::scoped_lock lk{caller_applet->lock}; + caller_applet->child_applets.push_back(applet); + } + window_system.TrackApplet(applet, false); + return std::make_shared(system, broker, applet); } - - const auto applet = std::make_shared(system, std::move(process), false); - applet->program_id = program_id; - applet->applet_id = applet_id; - applet->type = AppletType::LibraryApplet; - applet->library_applet_mode = mode; - applet->window_visible = mode != LibraryAppletMode::AllForegroundInitiallyHidden; - - auto broker = std::make_shared(system); - applet->caller_applet = caller_applet; - applet->caller_applet_broker = broker; - caller_applet->child_applets.push_back(applet); - - window_system.TrackApplet(applet, false); - - return std::make_shared(system, broker, applet); + // Couldn't initialize the guest process + return {}; } std::shared_ptr CreateFrontendApplet(Core::System& system, @@ -162,10 +151,10 @@ std::shared_ptr CreateFrontendApplet(Core::System& syste applet->caller_applet = caller_applet; applet->caller_applet_broker = storage; applet->frontend = system.GetFrontendAppletHolder().GetApplet(applet, applet_id, mode); - caller_applet->child_applets.push_back(applet); - - window_system.TrackApplet(applet, false); - + { + std::scoped_lock lk{caller_applet->lock}; + caller_applet->child_applets.push_back(applet); + } return std::make_shared(system, storage, applet); } @@ -210,7 +199,7 @@ Result ILibraryAppletCreator::CreateLibraryApplet( } // Applet is created, can now be launched. - m_applet->library_applet_launchable_event.Signal(); + m_applet->library_applet_launchable_event.Signal(system.Kernel()); *out_library_applet_accessor = library_applet; R_SUCCEED(); } @@ -236,7 +225,7 @@ Result ILibraryAppletCreator::CreateLibraryAppletEx( } // Applet is created, can now be launched. - m_applet->library_applet_launchable_event.Signal(); + m_applet->library_applet_launchable_event.Signal(system.Kernel()); *out_library_applet_accessor = library_applet; R_SUCCEED(); } @@ -269,7 +258,7 @@ Result ILibraryAppletCreator::CreateTransferMemoryStorage( } *out_storage = std::make_shared( - system, AM::CreateTransferMemoryStorage(transfer_memory_handle->GetOwner()->GetMemory(), + system, AM::CreateTransferMemoryStorage(system.Kernel(), transfer_memory_handle->GetOwner()->GetMemory(), transfer_memory_handle.Get(), is_writable, size)); R_SUCCEED(); } @@ -290,7 +279,7 @@ Result ILibraryAppletCreator::CreateHandleStorage( } *out_storage = std::make_shared( - system, AM::CreateHandleStorage(transfer_memory_handle->GetOwner()->GetMemory(), + system, AM::CreateHandleStorage(system.Kernel(), transfer_memory_handle->GetOwner()->GetMemory(), transfer_memory_handle.Get(), size)); R_SUCCEED(); } diff --git a/src/core/hle/service/am/service/library_applet_creator.h b/src/core/hle/service/am/service/library_applet_creator.h index 34e7586e8f..1c7f930055 100644 --- a/src/core/hle/service/am/service/library_applet_creator.h +++ b/src/core/hle/service/am/service/library_applet_creator.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/am/service/library_applet_proxy.cpp b/src/core/hle/service/am/service/library_applet_proxy.cpp index f9cfb82a90..567ff75797 100644 --- a/src/core/hle/service/am/service/library_applet_proxy.cpp +++ b/src/core/hle/service/am/service/library_applet_proxy.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/library_applet_proxy.h b/src/core/hle/service/am/service/library_applet_proxy.h index 792d585827..c659dbe187 100644 --- a/src/core/hle/service/am/service/library_applet_proxy.h +++ b/src/core/hle/service/am/service/library_applet_proxy.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/library_applet_self_accessor.cpp b/src/core/hle/service/am/service/library_applet_self_accessor.cpp index 4a1d1f97d3..907be49204 100644 --- a/src/core/hle/service/am/service/library_applet_self_accessor.cpp +++ b/src/core/hle/service/am/service/library_applet_self_accessor.cpp @@ -93,23 +93,23 @@ ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default; Result ILibraryAppletSelfAccessor::PopInData(Out> out_storage) { LOG_INFO(Service_AM, "called"); - R_RETURN(m_broker->GetInData().Pop(out_storage)); + R_RETURN(m_broker->GetInData().Pop(system.Kernel(), out_storage)); } Result ILibraryAppletSelfAccessor::PushOutData(SharedPointer storage) { LOG_INFO(Service_AM, "called"); - m_broker->GetOutData().Push(storage); + m_broker->GetOutData().Push(system.Kernel(), storage); R_SUCCEED(); } Result ILibraryAppletSelfAccessor::PopInteractiveInData(Out> out_storage) { LOG_INFO(Service_AM, "called"); - R_RETURN(m_broker->GetInteractiveInData().Pop(out_storage)); + R_RETURN(m_broker->GetInteractiveInData().Pop(system.Kernel(), out_storage)); } Result ILibraryAppletSelfAccessor::PushInteractiveOutData(SharedPointer storage) { LOG_INFO(Service_AM, "called"); - m_broker->GetInteractiveOutData().Push(storage); + m_broker->GetInteractiveOutData().Push(system.Kernel(), storage); R_SUCCEED(); } @@ -233,8 +233,9 @@ Result ILibraryAppletSelfAccessor::ReportVisibleErrorWithErrorContext( R_SUCCEED(); } -Result ILibraryAppletSelfAccessor::UnpopInData() { - LOG_WARNING(Service_AM, "(STUBBED) called"); +Result ILibraryAppletSelfAccessor::UnpopInData(SharedPointer storage) { + LOG_INFO(Service_AM, "called"); + m_broker->GetInData().Unpop(system.Kernel(), storage); R_SUCCEED(); } @@ -251,19 +252,7 @@ Result ILibraryAppletSelfAccessor::GetMainAppletApplicationDesiredLanguage( // Default to 0 (all languages supported) u32 supported_languages = 0; - const auto res = [this, identity] { - const FileSys::PatchManager pm{identity.application_id, system.GetFileSystemController(), - system.GetContentProvider()}; - auto metadata = pm.GetControlMetadata(); - if (metadata.first != nullptr) { - return metadata; - } - - const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(identity.application_id), - system.GetFileSystemController(), - system.GetContentProvider()}; - return pm_update.GetControlMetadata(); - }(); + const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, identity.application_id); if (res.first != nullptr) { supported_languages = res.first->GetSupportedLanguages(); diff --git a/src/core/hle/service/am/service/library_applet_self_accessor.h b/src/core/hle/service/am/service/library_applet_self_accessor.h index 78864a94eb..d287f6b403 100644 --- a/src/core/hle/service/am/service/library_applet_self_accessor.h +++ b/src/core/hle/service/am/service/library_applet_self_accessor.h @@ -72,7 +72,7 @@ private: Result ReportVisibleError(ErrorCode error_code); Result ReportVisibleErrorWithErrorContext( ErrorCode error_code, InLargeData error_context); - Result UnpopInData(); + Result UnpopInData(SharedPointer storage); Result GetMainAppletApplicationDesiredLanguage(Out out_desired_language); Result GetCurrentApplicationId(Out out_application_id); Result GetMainAppletAvailableUsers(Out out_can_select_any_user, Out out_users_count, diff --git a/src/core/hle/service/am/service/lock_accessor.cpp b/src/core/hle/service/am/service/lock_accessor.cpp index 8e556fdd68..0341254449 100644 --- a/src/core/hle/service/am/service/lock_accessor.cpp +++ b/src/core/hle/service/am/service/lock_accessor.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -20,7 +23,7 @@ ILockAccessor::ILockAccessor(Core::System& system_) RegisterHandlers(functions); - m_event.Signal(); + m_event.Signal(system.Kernel()); } ILockAccessor::~ILockAccessor() = default; @@ -55,7 +58,7 @@ Result ILockAccessor::Unlock() { m_is_locked = false; } - m_event.Signal(); + m_event.Signal(system.Kernel()); R_SUCCEED(); } diff --git a/src/core/hle/service/am/service/lock_accessor.h b/src/core/hle/service/am/service/lock_accessor.h index 9bfb5c0502..fe3f11dcf0 100644 --- a/src/core/hle/service/am/service/lock_accessor.h +++ b/src/core/hle/service/am/service/lock_accessor.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/overlay_applet_proxy.cpp b/src/core/hle/service/am/service/overlay_applet_proxy.cpp index 5e140fe031..735ba9d307 100644 --- a/src/core/hle/service/am/service/overlay_applet_proxy.cpp +++ b/src/core/hle/service/am/service/overlay_applet_proxy.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "core/hle/service/am/service/common_state_getter.h" diff --git a/src/core/hle/service/am/service/overlay_applet_proxy.h b/src/core/hle/service/am/service/overlay_applet_proxy.h index 2c7fa57dfd..6c02166a2d 100644 --- a/src/core/hle/service/am/service/overlay_applet_proxy.h +++ b/src/core/hle/service/am/service/overlay_applet_proxy.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/core/hle/service/am/service/overlay_functions.cpp b/src/core/hle/service/am/service/overlay_functions.cpp index 41f2e5c008..8150b5dd07 100644 --- a/src/core/hle/service/am/service/overlay_functions.cpp +++ b/src/core/hle/service/am/service/overlay_functions.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "core/hle/service/am/applet.h" @@ -22,7 +22,7 @@ namespace Service::AM { {10, nullptr, "StartShutdownSequenceForOverlay"}, {11, nullptr, "StartRebootSequenceForOverlay"}, {20, D<&IOverlayFunctions::SetHandlingHomeButtonShortPressedEnabled>, "SetHandlingHomeButtonShortPressedEnabled"}, - {21, nullptr, "SetHandlingTouchScreenInputEnabled"}, + {21, D<&IOverlayFunctions::SetHandlingTouchScreenInputEnabled>, "SetHandlingTouchScreenInputEnabled"}, {30, nullptr, "SetHealthWarningShowingState"}, {31, D<&IOverlayFunctions::IsHealthWarningRequired>, "IsHealthWarningRequired"}, {40, nullptr, "GetApplicationNintendoLogo"}, @@ -43,10 +43,12 @@ namespace Service::AM { Result IOverlayFunctions::BeginToWatchShortHomeButtonMessage() { LOG_DEBUG(Service_AM, "called"); - m_applet->overlay_in_foreground = true; - m_applet->home_button_short_pressed_blocked = false; + { + std::scoped_lock lk{m_applet->lock}; + m_applet->overlay_watching_short_home_button = true; + } - if (auto *window_system = system.GetAppletManager().GetWindowSystem()) { + if (auto* window_system = system.GetAppletManager().GetWindowSystem()) { window_system->RequestUpdate(); } @@ -56,16 +58,26 @@ namespace Service::AM { Result IOverlayFunctions::EndToWatchShortHomeButtonMessage() { LOG_DEBUG(Service_AM, "called"); - m_applet->overlay_in_foreground = false; - m_applet->home_button_short_pressed_blocked = false; + { + std::scoped_lock lk{m_applet->lock}; + m_applet->overlay_watching_short_home_button = false; + } - if (auto *window_system = system.GetAppletManager().GetWindowSystem()) { + if (auto* window_system = system.GetAppletManager().GetWindowSystem()) { window_system->RequestUpdate(); } R_SUCCEED(); } + Result IOverlayFunctions::SetHandlingTouchScreenInputEnabled(bool enabled) { + LOG_DEBUG(Service_AM, "called, enabled={}", enabled); + + std::scoped_lock lk{m_applet->lock}; + m_applet->overlay_handling_touch_input = enabled; + R_SUCCEED(); + } + Result IOverlayFunctions::GetApplicationIdForLogo(Out out_application_id) { LOG_DEBUG(Service_AM, "called"); diff --git a/src/core/hle/service/am/service/overlay_functions.h b/src/core/hle/service/am/service/overlay_functions.h index 870f5eae9c..eefd8d862b 100644 --- a/src/core/hle/service/am/service/overlay_functions.h +++ b/src/core/hle/service/am/service/overlay_functions.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -20,6 +20,7 @@ namespace Service::AM { Result SetAutoSleepTimeAndDimmingTimeEnabled(bool enabled); Result IsHealthWarningRequired(Out is_required); Result SetHandlingHomeButtonShortPressedEnabled(bool enabled); + Result SetHandlingTouchScreenInputEnabled(bool enabled); Result Unknown70(); private: diff --git a/src/core/hle/service/am/service/process_winding_controller.cpp b/src/core/hle/service/am/service/process_winding_controller.cpp index 30529de550..e284aa2d89 100644 --- a/src/core/hle/service/am/service/process_winding_controller.cpp +++ b/src/core/hle/service/am/service/process_winding_controller.cpp @@ -1,9 +1,11 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 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" #include "core/hle/service/am/frontend/applets.h" #include "core/hle/service/am/service/library_applet_accessor.h" #include "core/hle/service/am/service/process_winding_controller.h" @@ -43,6 +45,18 @@ Result IProcessWindingController::OpenCallingLibraryApplet( Out> out_calling_library_applet) { LOG_INFO(Service_AM, "called"); + std::shared_ptr reserved_applet; + { + std::scoped_lock lk{m_applet->lock}; + reserved_applet = std::move(m_applet->reserved_applet); + } + + if (reserved_applet != nullptr) { + *out_calling_library_applet = std::make_shared( + system, reserved_applet->caller_applet_broker, reserved_applet); + R_SUCCEED(); + } + const auto caller_applet = m_applet->caller_applet.lock(); if (caller_applet == nullptr) { LOG_ERROR(Service_AM, "No caller applet available"); @@ -74,22 +88,82 @@ Result IProcessWindingController::PopContext(Out> out_co } Result IProcessWindingController::CancelWindingReservation() { - LOG_WARNING(Service_AM, "STUBBED"); + LOG_INFO(Service_AM, "called"); + + std::scoped_lock lk{m_applet->lock}; + m_applet->reserved_applet.reset(); + m_applet->unwind_after_reserved = false; + R_SUCCEED(); } Result IProcessWindingController::WindAndDoReserved() { - LOG_WARNING(Service_AM, "STUBBED"); + LOG_INFO(Service_AM, "called"); + + std::shared_ptr reserved_applet; + { + std::scoped_lock lk{m_applet->lock}; + + reserved_applet = m_applet->reserved_applet; + m_applet->display_layer_manager.SetWindowVisibility(false); + m_applet->exit_locked = false; + system.SetExitLocked(false); + } + + if (reserved_applet) { + { + std::scoped_lock lk{m_applet->lock}; + m_applet->is_winding = true; + } + + { + std::scoped_lock lk{reserved_applet->lock}; + reserved_applet->window_visible = true; + reserved_applet->process->Run(); + } + + if (reserved_applet->frontend) { + reserved_applet->frontend->Initialize(); + reserved_applet->frontend->Execute(); + } + } else { + LOG_WARNING(Service_AM, "called without a reserved applet to start"); + } + + m_applet->process->Terminate(); + R_SUCCEED(); } -Result IProcessWindingController::ReserveToStartAndWaitAndUnwindThis() { - LOG_WARNING(Service_AM, "STUBBED"); +Result IProcessWindingController::ReserveToStartAndWaitAndUnwindThis( + SharedPointer reserved_applet_accessor) { + LOG_INFO(Service_AM, "called"); + + if (reserved_applet_accessor == nullptr) { + LOG_ERROR(Service_AM, "No applet accessor provided"); + R_THROW(ResultUnknown); + } + + std::scoped_lock lk{m_applet->lock}; + m_applet->reserved_applet = reserved_applet_accessor->GetApplet(); + m_applet->unwind_after_reserved = true; + R_SUCCEED(); } -Result IProcessWindingController::ReserveToStartAndWait() { - LOG_WARNING(Service_AM, "STUBBED"); +Result IProcessWindingController::ReserveToStartAndWait( + SharedPointer reserved_applet_accessor) { + LOG_INFO(Service_AM, "called"); + + if (reserved_applet_accessor == nullptr) { + LOG_ERROR(Service_AM, "No applet accessor provided"); + R_THROW(ResultUnknown); + } + + std::scoped_lock lk{m_applet->lock}; + m_applet->reserved_applet = reserved_applet_accessor->GetApplet(); + m_applet->unwind_after_reserved = false; + R_SUCCEED(); } diff --git a/src/core/hle/service/am/service/process_winding_controller.h b/src/core/hle/service/am/service/process_winding_controller.h index 0d53223033..f3417bcd97 100644 --- a/src/core/hle/service/am/service/process_winding_controller.h +++ b/src/core/hle/service/am/service/process_winding_controller.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -29,8 +29,9 @@ private: Result PopContext(Out> out_context); Result CancelWindingReservation(); Result WindAndDoReserved(); - Result ReserveToStartAndWaitAndUnwindThis(); - Result ReserveToStartAndWait(); + Result ReserveToStartAndWaitAndUnwindThis( + SharedPointer reserved_applet_accessor); + Result ReserveToStartAndWait(SharedPointer reserved_applet_accessor); const std::shared_ptr m_applet; }; diff --git a/src/core/hle/service/am/service/self_controller.cpp b/src/core/hle/service/am/service/self_controller.cpp index 89e393bb97..3a654b9daa 100644 --- a/src/core/hle/service/am/service/self_controller.cpp +++ b/src/core/hle/service/am/service/self_controller.cpp @@ -57,7 +57,7 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr {64, nullptr, "SetInputDetectionSourceSet"}, {65, D<&ISelfController::ReportUserIsActive>, "ReportUserIsActive"}, {66, nullptr, "GetCurrentIlluminance"}, - {67, nullptr, "IsIlluminanceAvailable"}, + {67, D<&ISelfController::IsIlluminanceAvailable>, "IsIlluminanceAvailable"}, {68, D<&ISelfController::SetAutoSleepDisabled>, "SetAutoSleepDisabled"}, {69, D<&ISelfController::IsAutoSleepDisabled>, "IsAutoSleepDisabled"}, {70, nullptr, "ReportMultimediaError"}, @@ -71,6 +71,7 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr {120, D<&ISelfController::SaveCurrentScreenshot>, "SaveCurrentScreenshot"}, {130, D<&ISelfController::SetRecordVolumeMuted>, "SetRecordVolumeMuted"}, {230, D<&ISelfController::Unknown230>, "Unknown230"}, + {240, D<&ISelfController::Unknown240>, "Unknown240"}, {1000, nullptr, "GetDebugStorageChannel"}, }; // clang-format on @@ -151,7 +152,7 @@ Result ISelfController::GetLibraryAppletLaunchableEvent( OutCopyHandle out_event) { LOG_WARNING(Service_AM, "(STUBBED) called"); - m_applet->library_applet_launchable_event.Signal(); + m_applet->library_applet_launchable_event.Signal(system.Kernel()); *out_event = m_applet->library_applet_launchable_event.GetHandle(); R_SUCCEED(); @@ -170,7 +171,7 @@ Result ISelfController::SetOperationModeChangedNotification(bool enabled) { LOG_INFO(Service_AM, "called, enabled={}", enabled); std::scoped_lock lk{m_applet->lock}; - m_applet->lifecycle_manager.SetOperationModeChangedNotificationEnabled(enabled); + m_applet->lifecycle_manager.SetOperationModeChangedNotificationEnabled(system.Kernel(), enabled); R_SUCCEED(); } @@ -179,7 +180,7 @@ Result ISelfController::SetPerformanceModeChangedNotification(bool enabled) { LOG_INFO(Service_AM, "called, enabled={}", enabled); std::scoped_lock lk{m_applet->lock}; - m_applet->lifecycle_manager.SetPerformanceModeChangedNotificationEnabled(enabled); + m_applet->lifecycle_manager.SetPerformanceModeChangedNotificationEnabled(system.Kernel(), enabled); R_SUCCEED(); } @@ -188,7 +189,7 @@ Result ISelfController::SetFocusHandlingMode(bool notify, bool background, bool LOG_INFO(Service_AM, "called, notify={} background={} suspend={}", notify, background, suspend); std::scoped_lock lk{m_applet->lock}; - m_applet->lifecycle_manager.SetFocusStateChangedNotificationEnabled(notify); + m_applet->lifecycle_manager.SetFocusStateChangedNotificationEnabled(system.Kernel(), notify); m_applet->lifecycle_manager.SetFocusHandlingMode(suspend); m_applet->UpdateSuspensionStateLocked(true); @@ -347,6 +348,12 @@ Result ISelfController::IsAutoSleepDisabled(Out out_is_auto_sleep_disabled R_SUCCEED(); } +Result ISelfController::IsIlluminanceAvailable(Out out_is_illuminance_available) { + LOG_WARNING(Service_AM, "(stubbed)"); + *out_is_illuminance_available = false; + R_SUCCEED(); +} + Result ISelfController::SetInputDetectionPolicy(InputDetectionPolicy input_detection_policy) { LOG_WARNING(Service_AM, "(STUBBED) called"); R_SUCCEED(); @@ -416,4 +423,10 @@ Result ISelfController::Unknown230(u32 in_val, Out out_val) { R_SUCCEED(); } +Result ISelfController::Unknown240(u32 in_val) { + LOG_WARNING(Service_AM, "(STUBBED) called, in_val={}", in_val); + + R_SUCCEED(); +} + } // namespace Service::AM diff --git a/src/core/hle/service/am/service/self_controller.h b/src/core/hle/service/am/service/self_controller.h index 86cd9f1118..c9f480f575 100644 --- a/src/core/hle/service/am/service/self_controller.h +++ b/src/core/hle/service/am/service/self_controller.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -60,6 +60,7 @@ private: Result ReportUserIsActive(); Result SetAutoSleepDisabled(bool is_auto_sleep_disabled); Result IsAutoSleepDisabled(Out out_is_auto_sleep_disabled); + Result IsIlluminanceAvailable(Out out_is_illuminance_available); Result SetInputDetectionPolicy(InputDetectionPolicy input_detection_policy); Result GetAccumulatedSuspendedTickValue(Out out_accumulated_suspended_tick_value); Result GetAccumulatedSuspendedTickChangedEvent(OutCopyHandle out_event); @@ -67,6 +68,7 @@ private: Result SaveCurrentScreenshot(Capture::AlbumReportOption album_report_option); Result SetRecordVolumeMuted(bool muted); Result Unknown230(u32 in_val, Out out_val); + Result Unknown240(u32 in_val); Kernel::KProcess* const m_process; const std::shared_ptr m_applet; diff --git a/src/core/hle/service/am/service/storage.cpp b/src/core/hle/service/am/service/storage.cpp index 25ee0afbdb..5fe191bf2f 100644 --- a/src/core/hle/service/am/service/storage.cpp +++ b/src/core/hle/service/am/service/storage.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -26,17 +29,14 @@ IStorage::~IStorage() = default; Result IStorage::Open(Out> out_storage_accessor) { LOG_DEBUG(Service_AM, "called"); - R_UNLESS(m_impl->GetHandle() == nullptr, AM::ResultInvalidStorageType); - *out_storage_accessor = std::make_shared(system, m_impl); R_SUCCEED(); } -Result IStorage::OpenTransferStorage( - Out> out_transfer_storage_accessor) { +Result IStorage::OpenTransferStorage(Out> out_transfer_storage_accessor) { + LOG_DEBUG(Service_AM, "called"); R_UNLESS(m_impl->GetHandle() != nullptr, AM::ResultInvalidStorageType); - *out_transfer_storage_accessor = std::make_shared(system, m_impl); R_SUCCEED(); } diff --git a/src/core/hle/service/am/service/storage.h b/src/core/hle/service/am/service/storage.h index cde2ed0ead..462c823206 100644 --- a/src/core/hle/service/am/service/storage.h +++ b/src/core/hle/service/am/service/storage.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/storage_accessor.cpp b/src/core/hle/service/am/service/storage_accessor.cpp index 84577fee45..2fe379a74d 100644 --- a/src/core/hle/service/am/service/storage_accessor.cpp +++ b/src/core/hle/service/am/service/storage_accessor.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/storage_accessor.h b/src/core/hle/service/am/service/storage_accessor.h index 1a01730e00..76af3de363 100644 --- a/src/core/hle/service/am/service/storage_accessor.h +++ b/src/core/hle/service/am/service/storage_accessor.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/system_applet_proxy.cpp b/src/core/hle/service/am/service/system_applet_proxy.cpp index c435288a26..bb56750d09 100644 --- a/src/core/hle/service/am/service/system_applet_proxy.cpp +++ b/src/core/hle/service/am/service/system_applet_proxy.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/system_applet_proxy.h b/src/core/hle/service/am/service/system_applet_proxy.h index ab9769b3d9..76cef507a5 100644 --- a/src/core/hle/service/am/service/system_applet_proxy.h +++ b/src/core/hle/service/am/service/system_applet_proxy.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/am/service/window_controller.cpp b/src/core/hle/service/am/service/window_controller.cpp index 54396affbe..f2b9dccb27 100644 --- a/src/core/hle/service/am/service/window_controller.cpp +++ b/src/core/hle/service/am/service/window_controller.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/service/window_controller.h b/src/core/hle/service/am/service/window_controller.h index a784dd4a49..51a67f3039 100644 --- a/src/core/hle/service/am/service/window_controller.h +++ b/src/core/hle/service/am/service/window_controller.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/am/window_system.cpp b/src/core/hle/service/am/window_system.cpp index 5f20a98322..fbb1a57efd 100644 --- a/src/core/hle/service/am/window_system.cpp +++ b/src/core/hle/service/am/window_system.cpp @@ -4,11 +4,16 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + +#include "common/settings.h" #include "core/core.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/service/am/am_results.h" #include "core/hle/service/am/applet.h" #include "core/hle/service/am/applet_manager.h" #include "core/hle/service/am/event_observer.h" +#include "core/hle/service/am/process_creation.h" #include "core/hle/service/am/window_system.h" namespace Service::AM { @@ -31,46 +36,82 @@ void WindowSystem::RequestUpdate() { } void WindowSystem::Update() { - std::scoped_lock lk{m_lock}; + { + std::scoped_lock lk{m_lock}; - LOG_DEBUG(Service_AM, "called, home_menu={} application={} overlay={}", - m_home_menu != nullptr, m_application != nullptr, m_overlay_display != nullptr); + LOG_DEBUG(Service_AM, "called, home_menu={} application={} overlay={}", + m_home_menu != nullptr, m_application != nullptr, m_overlay_display != nullptr); - // Loop through all applets and remove terminated applets. - this->PruneTerminatedAppletsLocked(); + // Loop through all applets and remove terminated applets. + this->PruneTerminatedAppletsLocked(); - // If the home menu is being locked into the foreground, handle that. - if (this->LockHomeMenuIntoForegroundLocked()) { - return; + // If the home menu is being locked into the foreground, handle that. + if (!this->LockHomeMenuIntoForegroundLocked()) { + const bool overlay_takes_input = this->DoesOverlayTakeInputLocked(); + + this->UpdateAppletStateLocked(m_overlay_display, true, overlay_takes_input); + this->UpdateAppletStateLocked(m_home_menu, m_foreground_requested_applet == m_home_menu, overlay_takes_input); + this->UpdateAppletStateLocked(m_application, m_foreground_requested_applet == m_application, overlay_takes_input); + } } - bool overlay_blocks_input = false; - if (m_overlay_display) { - std::scoped_lock lk_overlay{m_overlay_display->lock}; - overlay_blocks_input = m_overlay_display->overlay_in_foreground; - } - - // Recursively update each applet root. - this->UpdateAppletStateLocked(m_home_menu, m_foreground_requested_applet == m_home_menu, overlay_blocks_input); - this->UpdateAppletStateLocked(m_application, m_foreground_requested_applet == m_application, overlay_blocks_input); - this->UpdateAppletStateLocked(m_overlay_display, true, false); // overlay is always updated, never blocked + this->NotifyApplicationChangedIfNeeded(); } void WindowSystem::TrackApplet(std::shared_ptr applet, bool is_application) { - std::scoped_lock lk{m_lock}; + { + std::scoped_lock lk{m_lock}; - if (applet->applet_id == AppletId::QLaunch) { - ASSERT(m_home_menu == nullptr); - m_home_menu = applet.get(); - } else if (applet->applet_id == AppletId::OverlayDisplay) { - m_overlay_display = applet.get(); - } else if (is_application) { - ASSERT(m_application == nullptr); - m_application = applet.get(); + if (applet->applet_id == AppletId::QLaunch) { + ASSERT(m_home_menu == nullptr); + m_home_menu = applet.get(); + } else if (applet->applet_id == AppletId::OverlayDisplay) { + m_overlay_display = applet.get(); + } else if (is_application) { + ASSERT(m_application == nullptr); + m_application = applet.get(); + } + + this->UpdateCurrentApplicationLocked(); + + m_event_observer->TrackAppletProcess(*applet); + m_applets.emplace(applet->aruid.pid, std::move(applet)); } - m_event_observer->TrackAppletProcess(*applet); - m_applets.emplace(applet->aruid.pid, std::move(applet)); + this->NotifyApplicationChangedIfNeeded(); +} + +void WindowSystem::UpdateCurrentApplicationLocked() { + const Applet* const candidate = m_application != nullptr ? m_application : m_home_menu; + if (candidate == nullptr) { + return; + } + + auto* const process = candidate->process->GetHandle(); + if (process == nullptr || process == m_system.Kernel().ApplicationProcess()) { + return; + } + + LOG_INFO(Service_AM, "Current application is now {:016X}", candidate->program_id); + + m_system.Kernel().SetApplicationProcess(process); + Settings::SetCurrentProgramID(candidate->program_id); + + m_pending_application_notification = candidate->program_id; +} + +void WindowSystem::NotifyApplicationChangedIfNeeded() { + std::optional program_id; + { + std::scoped_lock lk{m_lock}; + program_id = std::exchange(m_pending_application_notification, std::nullopt); + } + + if (!program_id.has_value()) { + return; + } + + m_system.NotifyApplicationChanged(*program_id); } std::shared_ptr WindowSystem::GetByAppletResourceUserId(u64 aruid) { @@ -154,7 +195,7 @@ void WindowSystem::OnOperationModeChanged() { for (const auto& [aruid, applet] : m_applets) { std::scoped_lock lk2{applet->lock}; - applet->lifecycle_manager.OnOperationAndPerformanceModeChanged(); + applet->lifecycle_manager.OnOperationAndPerformanceModeChanged(m_system.Kernel()); } } @@ -163,23 +204,45 @@ void WindowSystem::OnExitRequested() { for (const auto& [aruid, applet] : m_applets) { std::scoped_lock lk2{applet->lock}; - applet->lifecycle_manager.RequestExit(); + applet->lifecycle_manager.RequestExit(m_system.Kernel()); } } void WindowSystem::SendButtonAppletMessageLocked(AppletMessage message) { - if (m_home_menu) { - std::scoped_lock lk_home{m_home_menu->lock}; - m_home_menu->lifecycle_manager.PushUnorderedMessage(message); - } - if (m_overlay_display) { - std::scoped_lock lk_overlay{m_overlay_display->lock}; - m_overlay_display->lifecycle_manager.PushUnorderedMessage(message); - } - if (m_application) { - std::scoped_lock lk_application{m_application->lock}; - m_application->lifecycle_manager.PushUnorderedMessage(message); - } + const auto is_blocked = [message](const Applet& applet) { + if (message == AppletMessage::DetectShortPressingHomeButton && + applet.applet_id == AppletId::OverlayDisplay && + !applet.overlay_watching_short_home_button) { + return true; + } + + switch (message) { + case AppletMessage::DetectShortPressingHomeButton: + return applet.home_button_short_pressed_blocked; + case AppletMessage::DetectLongPressingHomeButton: + return applet.home_button_long_pressed_blocked; + default: + return false; + } + }; + + const auto send_to = [&](Applet* applet) { + if (!applet) { + return; + } + std::scoped_lock lk{applet->lock}; + if (is_blocked(*applet)) { + LOG_DEBUG(Service_AM, "Applet {} is blocking message {}", + static_cast(applet->applet_id), static_cast(message)); + return; + } + applet->lifecycle_manager.PushUnorderedMessage(m_system.Kernel(), message); + }; + + send_to(m_home_menu); + send_to(m_overlay_display); + send_to(m_application); + if (m_event_observer) { m_event_observer->RequestUpdate(); } @@ -191,19 +254,9 @@ void WindowSystem::OnSystemButtonPress(SystemButtonType type) { case SystemButtonType::HomeButtonShortPressing: SendButtonAppletMessageLocked(AppletMessage::DetectShortPressingHomeButton); break; - case SystemButtonType::HomeButtonLongPressing: { - // Toggle overlay foreground visibility on long home press - if (m_overlay_display) { - std::scoped_lock lk_overlay{m_overlay_display->lock}; - m_overlay_display->overlay_in_foreground = !m_overlay_display->overlay_in_foreground; - LOG_INFO(Service_AM, "Overlay long-press toggle: overlay_in_foreground={} window_visible={}", m_overlay_display->overlay_in_foreground, m_overlay_display->window_visible); - } + case SystemButtonType::HomeButtonLongPressing: SendButtonAppletMessageLocked(AppletMessage::DetectLongPressingHomeButton); - // Force a state update after toggling overlay - if (m_event_observer) { - m_event_observer->RequestUpdate(); - } - break; } + break; case SystemButtonType::CaptureButtonShortPressing: SendButtonAppletMessageLocked(AppletMessage::DetectShortPressingCaptureButton); break; @@ -240,6 +293,37 @@ void WindowSystem::PruneTerminatedAppletsLocked() { continue; } + // A winding applet has had its own process killed but is kept alive as a transparent slot + // while the reserved applet running in its place finishes (see WindAndDoReserved()). + if (applet->is_winding) { + if (!applet->child_applets.empty()) { + it = std::next(it); + continue; + } + + const bool unwind = applet->unwind_after_reserved; + applet->is_winding = false; + applet->unwind_after_reserved = false; + + if (unwind && this->RestartAppletProcessLocked(applet.get())) { + const u64 new_aruid = applet->aruid.pid; + const auto next = std::next(it); + + if (new_aruid != aruid) { + auto node = m_applets.extract(it); + node.key() = new_aruid; + m_applets.insert(std::move(node)); + } + + applet->process->Run(); + m_event_observer->RequestUpdate(); + it = next; + continue; + } + + applet->reserved_applet.reset(); + } + // Terminated, so ensure all child applets are terminated. if (!applet->child_applets.empty()) { this->TerminateChildAppletsLocked(applet.get()); @@ -277,8 +361,7 @@ void WindowSystem::PruneTerminatedAppletsLocked() { // If we have a home menu, send it the application exited message. if (m_home_menu) { - m_home_menu->lifecycle_manager.PushUnorderedMessage( - AppletMessage::ApplicationExited); + m_home_menu->lifecycle_manager.PushUnorderedMessage(m_system.Kernel(), AppletMessage::ApplicationExited); } } @@ -286,6 +369,8 @@ void WindowSystem::PruneTerminatedAppletsLocked() { m_overlay_display = nullptr; } + this->UpdateCurrentApplicationLocked(); + // Finalize applet. applet->OnProcessTerminatedLocked(); @@ -302,6 +387,28 @@ void WindowSystem::PruneTerminatedAppletsLocked() { } } +bool WindowSystem::RestartAppletProcessLocked(Applet* applet) { + if (!ReinitializeProcess(m_system, *applet->process, applet->program_id)) { + LOG_ERROR(Service_AM, "Failed to restart winding applet_id={}", + static_cast(applet->applet_id)); + return false; + } + + applet->aruid.pid = applet->process->GetProcessId(); + applet->is_process_running = false; + applet->is_completed = false; + + applet->hid_registration.RegisterCurrentProcess(); + + applet->lifecycle_manager.ResetForRelaunch(); + applet->is_activity_runnable = false; + + applet->launch_reason.flag = 1; + + m_event_observer->TrackAppletProcess(*applet); + return true; +} + bool WindowSystem::LockHomeMenuIntoForegroundLocked() { // If the home menu is not locked into foreground, then there's nothing to do. if (m_home_menu == nullptr || !m_home_menu_foreground_locked) { @@ -336,7 +443,20 @@ void WindowSystem::TerminateChildAppletsLocked(Applet* applet) { applet->lock.lock(); } -void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, bool overlay_blocking) { +bool WindowSystem::IsOverlayOpenLocked(const Applet& overlay) const { + return overlay.window_visible && overlay.overlay_watching_short_home_button; +} + +bool WindowSystem::DoesOverlayTakeInputLocked() const { + if (m_overlay_display == nullptr) { + return false; + } + + std::scoped_lock lk{m_overlay_display->lock}; + return this->IsOverlayOpenLocked(*m_overlay_display); +} + +void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, bool overlay_takes_input) { // With no applet, we don't have anything to do. if (!applet) { return; @@ -353,6 +473,9 @@ void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, b const bool has_obscuring_child_applets = [&] { for (const auto& child_applet : applet->child_applets) { std::scoped_lock lk2{child_applet->lock}; + if (child_applet->is_winding) { + return true; + } const auto mode = child_applet->library_applet_mode; if (child_applet->is_process_running && child_applet->window_visible && (mode == LibraryAppletMode::AllForeground || @@ -364,24 +487,18 @@ void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, b return false; }(); + const bool is_overlay = applet->applet_id == AppletId::OverlayDisplay; + // Update visibility state. - // Overlay applets should always be visible when window_visible is true, regardless of foreground state - const bool should_be_visible = (applet->applet_id == AppletId::OverlayDisplay) - ? applet->window_visible - : (is_foreground && applet->window_visible); + const bool should_be_visible = + is_overlay ? applet->window_visible : (is_foreground && applet->window_visible); applet->display_layer_manager.SetWindowVisibility(should_be_visible); + const bool needs_hid_input = + is_overlay ? this->IsOverlayOpenLocked(*applet) + : (is_foreground && applet->window_visible && !overlay_takes_input); - const bool should_be_interactible = (applet->applet_id == AppletId::OverlayDisplay) - ? applet->overlay_in_foreground - : (is_foreground && applet->window_visible && !overlay_blocking); - - if (applet->applet_id == AppletId::OverlayDisplay || applet->applet_id == AppletId::Application) { - LOG_DEBUG(Service_AM, "UpdateAppletStateLocked: applet={} overlay_in_foreground={} is_foreground={} window_visible={} overlay_blocking={} should_be_interactible={}", - static_cast(applet->applet_id), applet->overlay_in_foreground, is_foreground, applet->window_visible, overlay_blocking, should_be_interactible); - } - - applet->SetInteractibleLocked(should_be_interactible); + applet->SetInteractibleLocked(needs_hid_input, needs_hid_input); // Update focus state and suspension. const bool is_obscured = has_obscuring_child_applets || !applet->window_visible; @@ -397,23 +514,18 @@ void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, b applet->UpdateSuspensionStateLocked(true); } - // Z-index logic like in reference C# implementation (tuned for overlay extremes) - s32 z_index = 0; - const bool now_foreground = inherited_foreground; - if (applet->applet_id == AppletId::OverlayDisplay) { - z_index = applet->overlay_in_foreground ? 100000 : -1; - } else if (now_foreground && !is_obscured) { - z_index = 2; - } else if (now_foreground) { - z_index = 1; - } else { - z_index = 0; + // Layer ordering. Composition sorts back-to-front. Now with enums for calrity. + s32 z_index = Background; + if (is_overlay) { + z_index = this->IsOverlayOpenLocked(*applet) ? Overlay : Background; + } else if (inherited_foreground) { + z_index = is_obscured ? Foreground : ForegroundVisible; } applet->display_layer_manager.SetOverlayZIndex(z_index); // Recurse into child applets. for (const auto& child_applet : applet->child_applets) { - this->UpdateAppletStateLocked(child_applet.get(), is_foreground, overlay_blocking); + this->UpdateAppletStateLocked(child_applet.get(), is_foreground, overlay_takes_input); } } diff --git a/src/core/hle/service/am/window_system.h b/src/core/hle/service/am/window_system.h index c331d10c92..824ae14a1d 100644 --- a/src/core/hle/service/am/window_system.h +++ b/src/core/hle/service/am/window_system.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "common/common_types.h" #include "core/hle/service/am/am_types.h" @@ -60,10 +61,16 @@ public: void OnPowerButtonPressed(ButtonPressDuration type) {} private: + void UpdateCurrentApplicationLocked(); + void NotifyApplicationChangedIfNeeded(); void PruneTerminatedAppletsLocked(); + bool RestartAppletProcessLocked(Applet* applet); bool LockHomeMenuIntoForegroundLocked(); void TerminateChildAppletsLocked(Applet* applet); - void UpdateAppletStateLocked(Applet* applet, bool is_foreground, bool overlay_blocking = false); + bool IsOverlayOpenLocked(const Applet& overlay) const; + bool DoesOverlayTakeInputLocked() const; + + void UpdateAppletStateLocked(Applet* applet, bool is_foreground, bool overlay_takes_input); void SendButtonAppletMessageLocked(AppletMessage message); private: @@ -87,6 +94,7 @@ private: // Applet map by aruid. std::map> m_applets{}; + std::optional m_pending_application_notification{}; }; } // namespace Service::AM diff --git a/src/core/hle/service/aoc/addon_content_manager.cpp b/src/core/hle/service/aoc/addon_content_manager.cpp index 4d3e640bb3..be2aa34baa 100644 --- a/src/core/hle/service/aoc/addon_content_manager.cpp +++ b/src/core/hle/service/aoc/addon_content_manager.cpp @@ -23,6 +23,7 @@ #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/server_manager.h" +#include "core/hle/service/am/am_results.h" #include "core/loader/loader.h" namespace Service::AOC { @@ -31,6 +32,10 @@ static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) { return FileSys::GetBaseTitleID(title_id) == base; } +static u64 GetCallerBaseTitleID(Core::System& system, const ClientProcessId& process_id) { + return FileSys::GetBaseTitleID(system.ResolveCallerProgramId(*process_id)); +} + static std::vector AccumulateAOCTitleIDs(Core::System& system) { std::vector add_on_content; const auto& rcu = system.GetContentProvider(); @@ -91,7 +96,7 @@ IAddOnContentManager::~IAddOnContentManager() { Result IAddOnContentManager::CountAddOnContent(Out out_count, ClientProcessId process_id) { LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid); - const auto current = system.GetApplicationProcessProgramID(); + const auto current = GetCallerBaseTitleID(system, process_id); const auto& disabled = Settings::values.disabled_addons[current]; if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) { @@ -112,7 +117,7 @@ Result IAddOnContentManager::ListAddOnContent(Out out_count, LOG_DEBUG(Service_AOC, "called with offset={}, count={}, process_id={}", offset, count, process_id.pid); - const auto current = FileSys::GetBaseTitleID(system.GetApplicationProcessProgramID()); + const auto current = GetCallerBaseTitleID(system, process_id); std::vector out; const auto& disabled = Settings::values.disabled_addons[current]; @@ -126,8 +131,7 @@ Result IAddOnContentManager::ListAddOnContent(Out out_count, } } - // TODO(DarkLordZach): Find the correct error code. - R_UNLESS(out.size() >= offset, ResultUnknown); + R_UNLESS(out.size() >= offset, AM::ResultApplicationRecordNotFound); *out_count = static_cast(std::min(out.size() - offset, count)); std::rotate(out.begin(), out.begin() + offset, out.end()); @@ -141,7 +145,7 @@ Result IAddOnContentManager::GetAddOnContentBaseId(Out out_title_id, ClientProcessId process_id) { LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid); - const auto title_id = system.GetApplicationProcessProgramID(); + const auto title_id = system.ResolveCallerProgramId(*process_id); const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), system.GetContentProvider()}; diff --git a/src/core/hle/service/aoc/addon_content_manager.h b/src/core/hle/service/aoc/addon_content_manager.h index 91857df4c7..b505525590 100644 --- a/src/core/hle/service/aoc/addon_content_manager.h +++ b/src/core/hle/service/aoc/addon_content_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/aoc/purchase_event_manager.cpp b/src/core/hle/service/aoc/purchase_event_manager.cpp index 9e718510b4..64e1244967 100644 --- a/src/core/hle/service/aoc/purchase_event_manager.cpp +++ b/src/core/hle/service/aoc/purchase_event_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/aoc/purchase_event_manager.h b/src/core/hle/service/aoc/purchase_event_manager.h index ea3836bc93..c0bdfaf242 100644 --- a/src/core/hle/service/aoc/purchase_event_manager.h +++ b/src/core/hle/service/aoc/purchase_event_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/apm/apm.cpp b/src/core/hle/service/apm/apm.cpp index c23ff293d3..d73d08cb72 100644 --- a/src/core/hle/service/apm/apm.cpp +++ b/src/core/hle/service/apm/apm.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -15,12 +18,11 @@ void LoopProcess(Core::System& system) { auto module = std::make_shared(); auto server_manager = std::make_unique(system); - server_manager->RegisterNamedService( - "apm", std::make_shared(system, module, system.GetAPMController(), "apm")); - server_manager->RegisterNamedService( - "apm:am", std::make_shared(system, module, system.GetAPMController(), "apm:am")); - server_manager->RegisterNamedService( - "apm:sys", std::make_shared(system, system.GetAPMController())); + server_manager->RegisterNamedService("apm", std::make_shared(system, module, system.GetAPMController(), "apm")); + server_manager->RegisterNamedService("apm:am", std::make_shared(system, module, system.GetAPMController(), "apm:am")); + // Removed on [+8.0.0] but kept for compatibility + server_manager->RegisterNamedService("apm:p", std::make_shared(system, module, system.GetAPMController(), "apm:p")); + server_manager->RegisterNamedService("apm:sys", std::make_shared(system, system.GetAPMController())); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/apm/apm.h b/src/core/hle/service/apm/apm.h index e188b4e446..265e581697 100644 --- a/src/core/hle/service/apm/apm.h +++ b/src/core/hle/service/apm/apm.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/apm/apm_controller.cpp b/src/core/hle/service/apm/apm_controller.cpp index 2149af2c3c..651de2493f 100644 --- a/src/core/hle/service/apm/apm_controller.cpp +++ b/src/core/hle/service/apm/apm_controller.cpp @@ -26,8 +26,7 @@ Controller::Controller(Core::Timing::CoreTiming& core_timing_) Controller::~Controller() = default; -void Controller::SetPerformanceConfiguration(PerformanceMode mode, - PerformanceConfiguration config) { +void Controller::SetPerformanceConfiguration(PerformanceMode mode, PerformanceConfiguration config) { static constexpr std::array, 16> config_to_speed{{ {PerformanceConfiguration::Config1, 1020}, {PerformanceConfiguration::Config2, 1020}, @@ -46,17 +45,14 @@ void Controller::SetPerformanceConfiguration(PerformanceMode mode, {PerformanceConfiguration::Config15, 1020}, {PerformanceConfiguration::Config16, 1020}, }}; - - const auto iter = std::find_if(config_to_speed.cbegin(), config_to_speed.cend(), - [config](const auto& entry) { return entry.first == config; }); - - if (iter == config_to_speed.cend()) { + if (auto const it = std::find_if(config_to_speed.cbegin(), config_to_speed.cend(), [config](const auto& e) { + return e.first == config; + }); it != config_to_speed.cend()) { + SetClockSpeed(it->second); + configs.insert_or_assign(mode, config); + } else { LOG_ERROR(Service_APM, "Invalid performance configuration value provided: {}", config); - return; } - - SetClockSpeed(iter->second); - configs.insert_or_assign(mode, config); } void Controller::SetFromCpuBoostMode(CpuBoostMode mode) { @@ -65,9 +61,11 @@ void Controller::SetFromCpuBoostMode(CpuBoostMode mode) { PerformanceConfiguration::Config13, PerformanceConfiguration::Config15, }}; - - SetPerformanceConfiguration(PerformanceMode::Boost, - BOOST_MODE_TO_CONFIG_MAP.at(static_cast(mode))); + if (u32(mode) < BOOST_MODE_TO_CONFIG_MAP.size()) { + SetPerformanceConfiguration(PerformanceMode::Boost, BOOST_MODE_TO_CONFIG_MAP[u32(mode)]); + } else { + LOG_ERROR(Service_APM, "{} invalid mode", mode); + } } PerformanceMode Controller::GetCurrentPerformanceMode() const { @@ -78,7 +76,6 @@ PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(Performa if (configs.find(mode) == configs.end()) { configs.insert_or_assign(mode, DEFAULT_PERFORMANCE_CONFIGURATION); } - return configs[mode]; } diff --git a/src/core/hle/service/apm/apm_controller.h b/src/core/hle/service/apm/apm_controller.h index 3357b7762a..c2f65a2f0e 100644 --- a/src/core/hle/service/apm/apm_controller.h +++ b/src/core/hle/service/apm/apm_controller.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/apm/apm_interface.cpp b/src/core/hle/service/apm/apm_interface.cpp index 90241a7093..51c01e2f25 100644 --- a/src/core/hle/service/apm/apm_interface.cpp +++ b/src/core/hle/service/apm/apm_interface.cpp @@ -82,7 +82,7 @@ void APM::OpenSession(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system, controller); + rb.PushIpcInterface(ctx, system, controller); } void APM::GetPerformanceMode(HLERequestContext& ctx) { @@ -125,7 +125,7 @@ void APM_Sys::GetPerformanceEvent(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system, controller); + rb.PushIpcInterface(ctx, system, controller); } void APM_Sys::SetCpuBoostMode(HLERequestContext& ctx) { diff --git a/src/core/hle/service/apm/apm_interface.h b/src/core/hle/service/apm/apm_interface.h index 58718453b6..eeac3474b0 100644 --- a/src/core/hle/service/apm/apm_interface.h +++ b/src/core/hle/service/apm/apm_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp index 7637ed5bf5..fde766a275 100644 --- a/src/core/hle/service/audio/audio.cpp +++ b/src/core/hle/service/audio/audio.cpp @@ -18,17 +18,193 @@ namespace Service::Audio { +class IAudioOutManagerForApplet final : public ServiceFramework { +public: + explicit IAudioOutManagerForApplet(Core::System& system_) + : ServiceFramework{system_, "audout:a"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "RequestSuspend"}, + {1, nullptr, "RequestResume"}, + {2, nullptr, "GetProcessMasterVolume"}, + {3, nullptr, "SetProcessMasterVolume"}, + {4, nullptr, "GetProcessRecordVolume"}, + {5, nullptr, "SetProcessRecordVolume"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class IAudioSnoopManager final : public ServiceFramework { +public: + explicit IAudioSnoopManager(Core::System& system_) + : ServiceFramework{system_, "auddev"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetDspStatistics"}, + {1, nullptr, "GetAppletStateSummaries"}, + {2, nullptr, "SetDspStatisticsParameter"}, + {3, nullptr, "GetDspStatisticsParameter"}, + {6, nullptr, "GetDspUsage"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class IAudioInManagerForApplet final : public ServiceFramework { +public: + explicit IAudioInManagerForApplet(Core::System& system_) + : ServiceFramework{system_, "audin:a"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "RequestSuspend"}, + {1, nullptr, "RequestResume"}, + {2, nullptr, "GetProcessMasterVolume"}, + {3, nullptr, "SetProcessMasterVolume"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class IAudioRendererManagerForApplet final : public ServiceFramework { +public: + explicit IAudioRendererManagerForApplet(Core::System& system_) + : ServiceFramework{system_, "audren:a"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "RequestSuspend"}, + {1, nullptr, "RequestResume"}, + {2, nullptr, "GetProcessMasterVolume"}, + {3, nullptr, "SetProcessMasterVolume"}, + {4, nullptr, "RegisterAppletResourceUserId"}, + {5, nullptr, "UnregisterAppletResourceUserId"}, + {6, nullptr, "GetProcessRecordVolume"}, + {7, nullptr, "SetProcessRecordVolume"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class IAudioOutManagerForDebugger final : public ServiceFramework { +public: + explicit IAudioOutManagerForDebugger(Core::System& system_) + : ServiceFramework{system_, "audout:d"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "RequestSuspend"}, + {1, nullptr, "RequestResume"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class IAudioInManagerForDebugger final : public ServiceFramework { +public: + explicit IAudioInManagerForDebugger(Core::System& system_) + : ServiceFramework{system_, "audin:d"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "RequestSuspend"}, + {1, nullptr, "RequestResume"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class IFinalOutputRecorderManagerForDebugger final : public ServiceFramework { +public: + explicit IFinalOutputRecorderManagerForDebugger(Core::System& system_) + : ServiceFramework{system_, "audrec:d"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "RequestSuspend"}, + {1, nullptr, "RequestResume"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class IAudioRendererManagerForDebugger final : public ServiceFramework { +public: + explicit IAudioRendererManagerForDebugger(Core::System& system_) + : ServiceFramework{system_, "audren:d"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "RequestSuspend"}, + {1, nullptr, "RequestResume"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class IAudioSystemManagerForApplet final : public ServiceFramework { +public: + explicit IAudioSystemManagerForApplet(Core::System& system_) + : ServiceFramework{system_, "aud:a"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "RegisterAppletResourceUserId"}, + {1, nullptr, "UnregisterAppletResourceUserId"}, + {2, nullptr, "RequestSuspendAudio"}, + {3, nullptr, "RequestResumeAudio"}, + {4, nullptr, "GetAudioOutputProcessMasterVolume"}, + {5, nullptr, "SetAudioOutputProcessMasterVolume"}, + {6, nullptr, "GetAudioInputProcessMasterVolume"}, + {7, nullptr, "SetAudioInputProcessMasterVolume"}, + {8, nullptr, "GetAudioOutputProcessRecordVolume"}, + {9, nullptr, "SetAudioOutputProcessRecordVolume"}, + {10, nullptr, "GetAppletStateSummaries"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class IAudioSystemManagerForDebugger final : public ServiceFramework { +public: + explicit IAudioSystemManagerForDebugger(Core::System& system_) + : ServiceFramework{system_, "aud:d"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "RequestSuspendAudioForDebug"}, + {1, nullptr, "RequestResumeAudioForDebug"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); - server_manager->RegisterNamedService("audin:u", std::make_shared(system)); - server_manager->RegisterNamedService("audout:u", std::make_shared(system)); + server_manager->RegisterNamedService("aud:a", std::make_shared(system), 30); + server_manager->RegisterNamedService("aud:d", std::make_shared(system), 30); + + server_manager->RegisterNamedService("audout:d", std::make_shared(system), 30); + server_manager->RegisterNamedService("audin:d", std::make_shared(system), 30); + server_manager->RegisterNamedService("audrec:d", std::make_shared(system), 30); + server_manager->RegisterNamedService("audren:d", std::make_shared(system), 30); + + server_manager->RegisterNamedService("audin:u", std::make_shared(system), 30); + server_manager->RegisterNamedService("audin:a", std::make_shared(system), 30); + server_manager->RegisterNamedService("audout:u", std::make_shared(system), 30); + server_manager->RegisterNamedService("audout:a", std::make_shared(system), 30); + server_manager->RegisterNamedService("auddev", std::make_shared(system), 30); // Depends on audout:u and audin:u on ctor! - server_manager->RegisterNamedService("audctl", std::make_shared(system)); - server_manager->RegisterNamedService("audrec:a", std::make_shared(system)); - server_manager->RegisterNamedService("audrec:u", std::make_shared(system)); - server_manager->RegisterNamedService("audren:u", std::make_shared(system)); - server_manager->RegisterNamedService("hwopus", std::make_shared(system)); + server_manager->RegisterNamedService("audctl", std::make_shared(system), 30); + server_manager->RegisterNamedService("audrec:a", std::make_shared(system), 30); + server_manager->RegisterNamedService("audrec:u", std::make_shared(system), 30); + server_manager->RegisterNamedService("audren:u", std::make_shared(system), 30); + server_manager->RegisterNamedService("audren:a", std::make_shared(system), 30); + server_manager->RegisterNamedService("hwopus", std::make_shared(system), 25); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/audio/audio.h b/src/core/hle/service/audio/audio.h index d70f022c73..eb8f482690 100644 --- a/src/core/hle/service/audio/audio.h +++ b/src/core/hle/service/audio/audio.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/audio/audio_controller.cpp b/src/core/hle/service/audio/audio_controller.cpp index 611e48e8ba..9e2db4a84c 100644 --- a/src/core/hle/service/audio/audio_controller.cpp +++ b/src/core/hle/service/audio/audio_controller.cpp @@ -1,7 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project diff --git a/src/core/hle/service/audio/audio_controller.h b/src/core/hle/service/audio/audio_controller.h index ff51f4c244..a30b572ac7 100644 --- a/src/core/hle/service/audio/audio_controller.h +++ b/src/core/hle/service/audio/audio_controller.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project diff --git a/src/core/hle/service/audio/audio_device.cpp b/src/core/hle/service/audio/audio_device.cpp index eb64892ed8..e9f7e074a1 100644 --- a/src/core/hle/service/audio/audio_device.cpp +++ b/src/core/hle/service/audio/audio_device.cpp @@ -42,7 +42,7 @@ IAudioDevice::IAudioDevice(Core::System& system_, u64 applet_resource_user_id, u }; RegisterHandlers(functions); - event->Signal(); + event->Signal(system.Kernel()); } IAudioDevice::~IAudioDevice() { @@ -128,7 +128,7 @@ Result IAudioDevice::GetActiveAudioDeviceNameAuto( Result IAudioDevice::QueryAudioDeviceSystemEvent(OutCopyHandle out_event) { LOG_DEBUG(Service_Audio, "(STUBBED) called"); - event->Signal(); + event->Signal(system.Kernel()); *out_event = &event->GetReadableEvent(); R_SUCCEED(); } diff --git a/src/core/hle/service/audio/audio_in.cpp b/src/core/hle/service/audio/audio_in.cpp index 416803acc3..6208c2d104 100644 --- a/src/core/hle/service/audio/audio_in.cpp +++ b/src/core/hle/service/audio/audio_in.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -37,7 +40,7 @@ IAudioIn::IAudioIn(Core::System& system_, Manager& manager, size_t session_id, RegisterHandlers(functions); - process->Open(); + process->Open(system.Kernel()); if (impl->GetSystem() .Initialize(device_name, in_params, handle, applet_resource_user_id) @@ -49,7 +52,7 @@ IAudioIn::IAudioIn(Core::System& system_, Manager& manager, size_t session_id, IAudioIn::~IAudioIn() { impl->Free(); service_context.CloseEvent(event); - process->Close(); + process->Close(system.Kernel()); } Result IAudioIn::GetAudioInState(Out out_state) { diff --git a/src/core/hle/service/audio/audio_in.h b/src/core/hle/service/audio/audio_in.h index 3fe1e1e870..793d2b3ad4 100644 --- a/src/core/hle/service/audio/audio_in.h +++ b/src/core/hle/service/audio/audio_in.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/audio/audio_in_manager.cpp b/src/core/hle/service/audio/audio_in_manager.cpp index 2675a57735..fccfe38b3b 100644 --- a/src/core/hle/service/audio/audio_in_manager.cpp +++ b/src/core/hle/service/audio/audio_in_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/audio/audio_in_manager.h b/src/core/hle/service/audio/audio_in_manager.h index 2a983bc607..d8f63972c3 100644 --- a/src/core/hle/service/audio/audio_in_manager.h +++ b/src/core/hle/service/audio/audio_in_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/audio/audio_out.cpp b/src/core/hle/service/audio/audio_out.cpp index 53009d5d74..996eeede58 100644 --- a/src/core/hle/service/audio/audio_out.cpp +++ b/src/core/hle/service/audio/audio_out.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -39,13 +42,13 @@ IAudioOut::IAudioOut(Core::System& system_, Manager& manager, size_t session_id, // clang-format on RegisterHandlers(functions); - process->Open(); + process->Open(system.Kernel()); } IAudioOut::~IAudioOut() { impl->Free(); service_context.CloseEvent(event); - process->Close(); + process->Close(system.Kernel()); } Result IAudioOut::GetAudioOutState(Out out_state) { diff --git a/src/core/hle/service/audio/audio_out.h b/src/core/hle/service/audio/audio_out.h index 779b213e78..178ee57df6 100644 --- a/src/core/hle/service/audio/audio_out.h +++ b/src/core/hle/service/audio/audio_out.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/audio/audio_out_manager.cpp b/src/core/hle/service/audio/audio_out_manager.cpp index 0a8e1ec256..3b2087932c 100644 --- a/src/core/hle/service/audio/audio_out_manager.cpp +++ b/src/core/hle/service/audio/audio_out_manager.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -14,7 +14,9 @@ namespace Service::Audio { using namespace AudioCore::AudioOut; IAudioOutManager::IAudioOutManager(Core::System& system_) - : ServiceFramework{system_, "audout:u"}, impl{std::make_unique(system_)} { + : ServiceFramework{system_, "audout:u"} + , impl(system_) +{ // clang-format off static const FunctionInfo functions[] = { {0, D<&IAudioOutManager::ListAudioOuts>, "ListAudioOuts"}, diff --git a/src/core/hle/service/audio/audio_out_manager.h b/src/core/hle/service/audio/audio_out_manager.h index 791274d5e9..ec974aaba4 100644 --- a/src/core/hle/service/audio/audio_out_manager.h +++ b/src/core/hle/service/audio/audio_out_manager.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -43,7 +43,7 @@ private: AudioCore::AudioOut::AudioOutParameter parameter, InCopyHandle process_handle, ClientAppletResourceUserId aruid); - std::unique_ptr impl; + std::optional impl; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audio_renderer.cpp b/src/core/hle/service/audio/audio_renderer.cpp index 21cc742b69..dd76544094 100644 --- a/src/core/hle/service/audio/audio_renderer.cpp +++ b/src/core/hle/service/audio/audio_renderer.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -39,7 +39,7 @@ IAudioRenderer::IAudioRenderer(Core::System& system_, Manager& manager_, // clang-format on RegisterHandlers(functions); - process_handle->Open(); + process_handle->Open(system_.Kernel()); impl->Initialize(params, transfer_memory, transfer_memory_size, process_handle, applet_resource_user_id, session_id); } @@ -47,7 +47,7 @@ IAudioRenderer::IAudioRenderer(Core::System& system_, Manager& manager_, IAudioRenderer::~IAudioRenderer() { impl->Finalize(); service_context.CloseEvent(rendered_event); - process_handle->Close(); + process_handle->Close(system.Kernel()); } Result IAudioRenderer::GetSampleRate(Out out_sample_rate) { @@ -88,7 +88,7 @@ Result IAudioRenderer::RequestUpdateAuto( const auto result = impl->RequestUpdate(input, out_performance_buffer, out_buffer); if (result.IsFailure()) { - LOG_ERROR(Service_Audio, "RequestUpdate failed error 0x{:02X}!", result.GetDescription()); + LOG_ERROR(Service_Audio, "RequestUpdate failed error {:#02x}!", result.GetDescription()); } R_RETURN(result); diff --git a/src/core/hle/service/audio/audio_renderer.h b/src/core/hle/service/audio/audio_renderer.h index f25c50ce8d..7f4ea9a662 100644 --- a/src/core/hle/service/audio/audio_renderer.h +++ b/src/core/hle/service/audio/audio_renderer.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/audio/audio_renderer_manager.cpp b/src/core/hle/service/audio/audio_renderer_manager.cpp index 6a1345c074..4756802eeb 100644 --- a/src/core/hle/service/audio/audio_renderer_manager.cpp +++ b/src/core/hle/service/audio/audio_renderer_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -8,6 +11,7 @@ #include "core/hle/service/audio/audio_device.h" #include "core/hle/service/audio/audio_renderer.h" #include "core/hle/service/audio/audio_renderer_manager.h" +#include "core/hle/service/audio/errors.h" #include "core/hle/service/cmif_serialization.h" namespace Service::Audio { @@ -15,7 +19,9 @@ namespace Service::Audio { using namespace AudioCore::Renderer; IAudioRendererManager::IAudioRendererManager(Core::System& system_) - : ServiceFramework{system_, "audren:u"}, impl{std::make_unique(system_)} { + : ServiceFramework{system_, "audren:u"} + , impl(system_) +{ // clang-format off static const FunctionInfo functions[] = { {0, D<&IAudioRendererManager::OpenAudioRenderer>, "OpenAudioRenderer"}, @@ -49,6 +55,11 @@ Result IAudioRendererManager::OpenAudioRenderer( R_THROW(Audio::ResultOutOfSessions); } + if (!process_handle) { + LOG_ERROR(Service_Audio, "Invalid handles"); + R_THROW(Audio::ResultInvalidHandle); + } + LOG_DEBUG(Service_Audio, "Opened new AudioRenderer session {} sessions open {}", session_id, impl->GetSessionCount()); diff --git a/src/core/hle/service/audio/audio_renderer_manager.h b/src/core/hle/service/audio/audio_renderer_manager.h index 69eee664c3..fdce8b6ffa 100644 --- a/src/core/hle/service/audio/audio_renderer_manager.h +++ b/src/core/hle/service/audio/audio_renderer_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -30,7 +33,7 @@ private: Result GetAudioDeviceServiceWithRevisionInfo(Out> out_audio_device, u32 revision, ClientAppletResourceUserId aruid); - std::unique_ptr impl; + std::optional impl; u32 num_audio_devices{0}; }; diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h index 8062820de3..553b4ee360 100644 --- a/src/core/hle/service/audio/errors.h +++ b/src/core/hle/service/audio/errors.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -33,6 +33,7 @@ constexpr Result ResultLibOpusInternalError{ErrorModule::HwOpus, 4}; constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3}; constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2}; constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259}; +constexpr Result ResultOutOfOpusDecoders{ErrorModule::HwOpus, 385}; constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001}; constexpr Result ResultInvalidOpusChannelCount{ErrorModule::HwOpus, 1002}; diff --git a/src/core/hle/service/audio/final_output_recorder_manager.cpp b/src/core/hle/service/audio/final_output_recorder_manager.cpp index f70a0e62d7..2bc9d29e3e 100644 --- a/src/core/hle/service/audio/final_output_recorder_manager.cpp +++ b/src/core/hle/service/audio/final_output_recorder_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/audio/final_output_recorder_manager.h b/src/core/hle/service/audio/final_output_recorder_manager.h index 0663b894e7..833c621be7 100644 --- a/src/core/hle/service/audio/final_output_recorder_manager.h +++ b/src/core/hle/service/audio/final_output_recorder_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/audio/final_output_recorder_manager_for_applet.cpp b/src/core/hle/service/audio/final_output_recorder_manager_for_applet.cpp index 7e2e42bbe7..dc2ea63aa0 100644 --- a/src/core/hle/service/audio/final_output_recorder_manager_for_applet.cpp +++ b/src/core/hle/service/audio/final_output_recorder_manager_for_applet.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/audio/final_output_recorder_manager_for_applet.h b/src/core/hle/service/audio/final_output_recorder_manager_for_applet.h index 27940f7e0d..32efb75b5e 100644 --- a/src/core/hle/service/audio/final_output_recorder_manager_for_applet.h +++ b/src/core/hle/service/audio/final_output_recorder_manager_for_applet.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/audio/hardware_opus_decoder.cpp b/src/core/hle/service/audio/hardware_opus_decoder.cpp index 03d3374c14..14888afa0d 100644 --- a/src/core/hle/service/audio/hardware_opus_decoder.cpp +++ b/src/core/hle/service/audio/hardware_opus_decoder.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/audio/hardware_opus_decoder.h b/src/core/hle/service/audio/hardware_opus_decoder.h index 511bf46bdb..5297572da8 100644 --- a/src/core/hle/service/audio/hardware_opus_decoder.h +++ b/src/core/hle/service/audio/hardware_opus_decoder.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/audio/hardware_opus_decoder_manager.cpp b/src/core/hle/service/audio/hardware_opus_decoder_manager.cpp index 9de72e30fe..b89e210012 100644 --- a/src/core/hle/service/audio/hardware_opus_decoder_manager.cpp +++ b/src/core/hle/service/audio/hardware_opus_decoder_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/audio/hardware_opus_decoder_manager.h b/src/core/hle/service/audio/hardware_opus_decoder_manager.h index 4f869c5174..4dd211e232 100644 --- a/src/core/hle/service/audio/hardware_opus_decoder_manager.h +++ b/src/core/hle/service/audio/hardware_opus_decoder_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp index 491416254c..3ef70aaacc 100644 --- a/src/core/hle/service/bcat/backend/backend.cpp +++ b/src/core/hle/service/bcat/backend/backend.cpp @@ -13,9 +13,9 @@ namespace Service::BCAT { ProgressServiceBackend::ProgressServiceBackend(Core::System& system, std::string_view event_name) - : service_context{system, "ProgressServiceBackend"} { - update_event = service_context.CreateEvent("ProgressServiceBackend:UpdateEvent:" + - std::string(event_name)); + : service_context{system, "ProgressServiceBackend"} +{ + update_event = service_context.CreateEvent("ProgressServiceBackend:UpdateEvent:" + std::string(event_name)); } ProgressServiceBackend::~ProgressServiceBackend() { @@ -30,103 +30,90 @@ DeliveryCacheProgressImpl& ProgressServiceBackend::GetImpl() { return impl; } -void ProgressServiceBackend::SetTotalSize(u64 size) { +void ProgressServiceBackend::SetTotalSize(Kernel::KernelCore& kernel, u64 size) { impl.total_bytes = size; - SignalUpdate(); + SignalUpdate(kernel); } -void ProgressServiceBackend::StartConnecting() { +void ProgressServiceBackend::StartConnecting(Kernel::KernelCore& kernel) { impl.status = DeliveryCacheProgressStatus::Connecting; - SignalUpdate(); + SignalUpdate(kernel); } -void ProgressServiceBackend::StartProcessingDataList() { +void ProgressServiceBackend::StartProcessingDataList(Kernel::KernelCore& kernel) { impl.status = DeliveryCacheProgressStatus::ProcessingDataList; - SignalUpdate(); + SignalUpdate(kernel); } -void ProgressServiceBackend::StartDownloadingFile(std::string_view dir_name, - std::string_view file_name, u64 file_size) { +void ProgressServiceBackend::StartDownloadingFile(Kernel::KernelCore& kernel, std::string_view dir_name, std::string_view file_name, u64 file_size) { impl.status = DeliveryCacheProgressStatus::Downloading; impl.current_downloaded_bytes = 0; impl.current_total_bytes = file_size; - std::memcpy(impl.current_directory.data(), dir_name.data(), - std::min(dir_name.size(), 0x31ull)); - std::memcpy(impl.current_file.data(), file_name.data(), - std::min(file_name.size(), 0x31ull)); - SignalUpdate(); + std::memcpy(impl.current_directory.data(), dir_name.data(), std::min(dir_name.size(), 0x31ull)); + std::memcpy(impl.current_file.data(), file_name.data(), std::min(file_name.size(), 0x31ull)); + SignalUpdate(kernel); } -void ProgressServiceBackend::UpdateFileProgress(u64 downloaded) { +void ProgressServiceBackend::UpdateFileProgress(Kernel::KernelCore& kernel, u64 downloaded) { impl.current_downloaded_bytes = downloaded; - SignalUpdate(); + SignalUpdate(kernel); } -void ProgressServiceBackend::FinishDownloadingFile() { +void ProgressServiceBackend::FinishDownloadingFile(Kernel::KernelCore& kernel) { impl.total_downloaded_bytes += impl.current_total_bytes; - SignalUpdate(); + SignalUpdate(kernel); } -void ProgressServiceBackend::CommitDirectory(std::string_view dir_name) { +void ProgressServiceBackend::CommitDirectory(Kernel::KernelCore& kernel, std::string_view dir_name) { impl.status = DeliveryCacheProgressStatus::Committing; impl.current_file.fill(0); impl.current_downloaded_bytes = 0; impl.current_total_bytes = 0; - std::memcpy(impl.current_directory.data(), dir_name.data(), - std::min(dir_name.size(), 0x31ull)); - SignalUpdate(); + std::memcpy(impl.current_directory.data(), dir_name.data(), std::min(dir_name.size(), 0x31ull)); + SignalUpdate(kernel); } -void ProgressServiceBackend::FinishDownload(Result result) { +void ProgressServiceBackend::FinishDownload(Kernel::KernelCore& kernel, Result result) { impl.total_downloaded_bytes = impl.total_bytes; impl.status = DeliveryCacheProgressStatus::Done; impl.result = result; - SignalUpdate(); + SignalUpdate(kernel); } -void ProgressServiceBackend::SignalUpdate() { - update_event->Signal(); +void ProgressServiceBackend::SignalUpdate(Kernel::KernelCore& kernel) { + update_event->Signal(kernel); } BcatBackend::BcatBackend(DirectoryGetter getter) : dir_getter(std::move(getter)) {} - BcatBackend::~BcatBackend() = default; NullBcatBackend::NullBcatBackend(DirectoryGetter getter) : BcatBackend(std::move(getter)) {} - NullBcatBackend::~NullBcatBackend() = default; -bool NullBcatBackend::Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) { - LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id, - title.build_id); - - progress.FinishDownload(ResultSuccess); +bool NullBcatBackend::Synchronize(Kernel::KernelCore& kernel, TitleIDVersion title, ProgressServiceBackend& progress) { + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id, title.build_id); + progress.FinishDownload(kernel, ResultSuccess); return true; } -bool NullBcatBackend::SynchronizeDirectory(TitleIDVersion title, std::string name, - ProgressServiceBackend& progress) { - LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id, - title.build_id, name); - - progress.FinishDownload(ResultSuccess); +bool NullBcatBackend::SynchronizeDirectory(Kernel::KernelCore& kernel, TitleIDVersion title, std::string name, ProgressServiceBackend& progress) { + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}, name={}", title.title_id, title.build_id, name); + progress.FinishDownload(kernel, ResultSuccess); return true; } -bool NullBcatBackend::Clear(u64 title_id) { +bool NullBcatBackend::Clear(Kernel::KernelCore& kernel, u64 title_id) { LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", title_id); return true; } -void NullBcatBackend::SetPassphrase(u64 title_id, const Passphrase& passphrase) { - LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id, - Common::HexToString(passphrase)); +void NullBcatBackend::SetPassphrase(Kernel::KernelCore& kernel, u64 title_id, const Passphrase& passphrase) { + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, passphrase={}", title_id, Common::HexToString(passphrase)); } -std::optional> NullBcatBackend::GetLaunchParameter(TitleIDVersion title) { - LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id, - title.build_id); +std::optional> NullBcatBackend::GetLaunchParameter(Kernel::KernelCore& kernel, TitleIDVersion title) { + LOG_DEBUG(Service_BCAT, "called, title_id={:016X}, build_id={:016X}", title.title_id, title.build_id); return std::nullopt; } diff --git a/src/core/hle/service/bcat/backend/backend.h b/src/core/hle/service/bcat/backend/backend.h index 3680f6c9c8..645af9a386 100644 --- a/src/core/hle/service/bcat/backend/backend.h +++ b/src/core/hle/service/bcat/backend/backend.h @@ -1,14 +1,16 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 #pragma once -#include #include #include +#include #include "common/common_types.h" -#include "core/file_sys/vfs/vfs_types.h" #include "core/hle/result.h" #include "core/hle/service/bcat/bcat_types.h" #include "core/hle/service/kernel_helpers.h" @@ -34,26 +36,26 @@ public: ~ProgressServiceBackend(); // Sets the number of bytes total in the entire download. - void SetTotalSize(u64 size); + void SetTotalSize(Kernel::KernelCore& kernel, u64 size); // Notifies the application that the backend has started connecting to the server. - void StartConnecting(); + void StartConnecting(Kernel::KernelCore& kernel); // Notifies the application that the backend has begun accumulating and processing metadata. - void StartProcessingDataList(); + void StartProcessingDataList(Kernel::KernelCore& kernel); // Notifies the application that a file is starting to be downloaded. - void StartDownloadingFile(std::string_view dir_name, std::string_view file_name, u64 file_size); + void StartDownloadingFile(Kernel::KernelCore& kernel, std::string_view dir_name, std::string_view file_name, u64 file_size); // Updates the progress of the current file to the size passed. - void UpdateFileProgress(u64 downloaded); + void UpdateFileProgress(Kernel::KernelCore& kernel, u64 downloaded); // Notifies the application that the current file has completed download. - void FinishDownloadingFile(); + void FinishDownloadingFile(Kernel::KernelCore& kernel); // Notifies the application that all files in this directory have completed and are being // finalized. - void CommitDirectory(std::string_view dir_name); + void CommitDirectory(Kernel::KernelCore& kernel, std::string_view dir_name); // Notifies the application that the operation completed with result code result. - void FinishDownload(Result result); + void FinishDownload(Kernel::KernelCore& kernel, Result result); private: explicit ProgressServiceBackend(Core::System& system, std::string_view event_name); @@ -61,7 +63,7 @@ private: Kernel::KReadableEvent& GetEvent(); DeliveryCacheProgressImpl& GetImpl(); - void SignalUpdate(); + void SignalUpdate(Kernel::KernelCore& kernel); KernelHelpers::ServiceContext service_context; @@ -74,25 +76,19 @@ class BcatBackend { public: explicit BcatBackend(DirectoryGetter getter); virtual ~BcatBackend(); - // Called when the backend is needed to synchronize the data for the game with title ID and // version in title. A ProgressServiceBackend object is provided to alert the application of // status. - virtual bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) = 0; + virtual bool Synchronize(Kernel::KernelCore& kernel, TitleIDVersion title, ProgressServiceBackend& progress) = 0; // Very similar to Synchronize, but only for the directory provided. Backends should not alter // the data for any other directories. - virtual bool SynchronizeDirectory(TitleIDVersion title, std::string name, - ProgressServiceBackend& progress) = 0; - + virtual bool SynchronizeDirectory(Kernel::KernelCore& kernel, TitleIDVersion title, std::string name, ProgressServiceBackend& progress) = 0; // Removes all cached data associated with title id provided. - virtual bool Clear(u64 title_id) = 0; - + virtual bool Clear(Kernel::KernelCore& kernel, u64 title_id) = 0; // Sets the BCAT Passphrase to be used with the associated title ID. - virtual void SetPassphrase(u64 title_id, const Passphrase& passphrase) = 0; - + virtual void SetPassphrase(Kernel::KernelCore& kernel, u64 title_id, const Passphrase& passphrase) = 0; // Gets the launch parameter used by AM associated with the title ID and version provided. - virtual std::optional> GetLaunchParameter(TitleIDVersion title) = 0; - + virtual std::optional> GetLaunchParameter(Kernel::KernelCore& kernel, TitleIDVersion title) = 0; protected: DirectoryGetter dir_getter; }; @@ -103,18 +99,13 @@ public: explicit NullBcatBackend(DirectoryGetter getter); ~NullBcatBackend() override; - bool Synchronize(TitleIDVersion title, ProgressServiceBackend& progress) override; - bool SynchronizeDirectory(TitleIDVersion title, std::string name, - ProgressServiceBackend& progress) override; - - bool Clear(u64 title_id) override; - - void SetPassphrase(u64 title_id, const Passphrase& passphrase) override; - - std::optional> GetLaunchParameter(TitleIDVersion title) override; + bool Synchronize(Kernel::KernelCore& kernel, TitleIDVersion title, ProgressServiceBackend& progress) override; + bool SynchronizeDirectory(Kernel::KernelCore& kernel, TitleIDVersion title, std::string name, ProgressServiceBackend& progress) override; + bool Clear(Kernel::KernelCore& kernel, u64 title_id) override; + void SetPassphrase(Kernel::KernelCore& kernel, u64 title_id, const Passphrase& passphrase) override; + std::optional> GetLaunchParameter(Kernel::KernelCore& kernel, TitleIDVersion title) override; }; -std::unique_ptr CreateBackendFromSettings(Core::System& system, - DirectoryGetter getter); +std::unique_ptr CreateBackendFromSettings(Core::System& system, DirectoryGetter getter); } // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/bcat.cpp b/src/core/hle/service/bcat/bcat.cpp index ea8b15998d..bf9e054a49 100644 --- a/src/core/hle/service/bcat/bcat.cpp +++ b/src/core/hle/service/bcat/bcat.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -12,25 +15,16 @@ namespace Service::BCAT { void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); - server_manager->RegisterNamedService("bcat:a", - std::make_shared(system, "bcat:a")); - server_manager->RegisterNamedService("bcat:m", - std::make_shared(system, "bcat:m")); - server_manager->RegisterNamedService("bcat:u", - std::make_shared(system, "bcat:u")); - server_manager->RegisterNamedService("bcat:s", - std::make_shared(system, "bcat:s")); + server_manager->RegisterNamedService("bcat:a", std::make_shared(system, "bcat:a"), 32); + server_manager->RegisterNamedService("bcat:m", std::make_shared(system, "bcat:m"), 32); + server_manager->RegisterNamedService("bcat:u", std::make_shared(system, "bcat:u"), 32); + server_manager->RegisterNamedService("bcat:s", std::make_shared(system, "bcat:s"), 32); - server_manager->RegisterNamedService( - "news:a", std::make_shared(system, 0xffffffff, "news:a")); - server_manager->RegisterNamedService( - "news:p", std::make_shared(system, 0x1, "news:p")); - server_manager->RegisterNamedService( - "news:c", std::make_shared(system, 0x2, "news:c")); - server_manager->RegisterNamedService( - "news:v", std::make_shared(system, 0x4, "news:v")); - server_manager->RegisterNamedService( - "news:m", std::make_shared(system, 0xd, "news:m")); + server_manager->RegisterNamedService("news:a", std::make_shared(system, 0xffffffff, "news:a"), 32); + server_manager->RegisterNamedService("news:p", std::make_shared(system, 0x1, "news:p"), 32); + server_manager->RegisterNamedService("news:c", std::make_shared(system, 0x2, "news:c"), 32); + server_manager->RegisterNamedService("news:v", std::make_shared(system, 0x4, "news:v"), 32); + server_manager->RegisterNamedService("news:m", std::make_shared(system, 0xd, "news:m"), 32); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/bcat/bcat.h b/src/core/hle/service/bcat/bcat.h index 2aaffc693b..65961baffe 100644 --- a/src/core/hle/service/bcat/bcat.h +++ b/src/core/hle/service/bcat/bcat.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/bcat/bcat_result.h b/src/core/hle/service/bcat/bcat_result.h index edf8a65641..c6fa1b4e26 100644 --- a/src/core/hle/service/bcat/bcat_result.h +++ b/src/core/hle/service/bcat/bcat_result.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/bcat/bcat_service.cpp b/src/core/hle/service/bcat/bcat_service.cpp index 849bf8d0d9..bc2a3e3457 100644 --- a/src/core/hle/service/bcat/bcat_service.cpp +++ b/src/core/hle/service/bcat/bcat_service.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -24,8 +24,8 @@ static u64 GetCurrentBuildID(const Core::System::CurrentBuildProcessID& id) { return out; } -IBcatService::IBcatService(Core::System& system_, BcatBackend& backend_) - : ServiceFramework{system_, "IBcatService"}, backend{backend_}, +IBcatService::IBcatService(Core::System& system_, BcatBackend& backend_, u64 program_id_) + : ServiceFramework{system_, "IBcatService"}, backend{backend_}, program_id{program_id_}, progress{{ ProgressServiceBackend{system_, "Normal"}, ProgressServiceBackend{system_, "Directory"}, @@ -70,8 +70,7 @@ Result IBcatService::RequestSyncDeliveryCache( LOG_DEBUG(Service_BCAT, "called"); auto& progress_backend{GetProgressBackend(SyncType::Normal)}; - backend.Synchronize({system.GetApplicationProcessProgramID(), - GetCurrentBuildID(system.GetApplicationProcessBuildID())}, + backend.Synchronize(system.Kernel(), {program_id, GetCurrentBuildID(system.GetApplicationProcessBuildID())}, GetProgressBackend(SyncType::Normal)); *out_interface = std::make_shared( @@ -86,9 +85,8 @@ Result IBcatService::RequestSyncDeliveryCacheWithDirectoryName( LOG_DEBUG(Service_BCAT, "called, name={}", name); auto& progress_backend{GetProgressBackend(SyncType::Directory)}; - backend.SynchronizeDirectory({system.GetApplicationProcessProgramID(), - GetCurrentBuildID(system.GetApplicationProcessBuildID())}, - name, progress_backend); + backend.SynchronizeDirectory(system.Kernel(), {program_id, GetCurrentBuildID(system.GetApplicationProcessBuildID())}, + name, progress_backend); *out_interface = std::make_shared( system, progress_backend.GetEvent(), progress_backend.GetImpl()); @@ -107,7 +105,7 @@ Result IBcatService::SetPassphrase(u64 application_id, std::memcpy(passphrase.data(), passphrase_buffer.data(), (std::min)(passphrase.size(), passphrase_buffer.size())); - backend.SetPassphrase(application_id, passphrase); + backend.SetPassphrase(system.Kernel(), application_id, passphrase); R_SUCCEED(); } @@ -120,7 +118,7 @@ Result IBcatService::ClearDeliveryCacheStorage(u64 application_id) { LOG_DEBUG(Service_BCAT, "called, title_id={:016X}", application_id); R_UNLESS(application_id != 0, ResultInvalidArgument); - R_UNLESS(backend.Clear(application_id), FileSys::ResultPermissionDenied); + R_UNLESS(backend.Clear(system.Kernel(), application_id), FileSys::ResultPermissionDenied); R_SUCCEED(); } diff --git a/src/core/hle/service/bcat/bcat_service.h b/src/core/hle/service/bcat/bcat_service.h index dda5a2d5f3..5e11059a78 100644 --- a/src/core/hle/service/bcat/bcat_service.h +++ b/src/core/hle/service/bcat/bcat_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -19,7 +22,7 @@ class IDeliveryCacheProgressService; class IBcatService final : public ServiceFramework { public: - explicit IBcatService(Core::System& system_, BcatBackend& backend_); + explicit IBcatService(Core::System& system_, BcatBackend& backend_, u64 program_id_); ~IBcatService() override; private: @@ -39,6 +42,7 @@ private: const ProgressServiceBackend& GetProgressBackend(SyncType type) const; BcatBackend& backend; + u64 program_id; std::array(SyncType::Count)> progress; }; diff --git a/src/core/hle/service/bcat/bcat_types.h b/src/core/hle/service/bcat/bcat_types.h index b35dab7c5a..303c243542 100644 --- a/src/core/hle/service/bcat/bcat_types.h +++ b/src/core/hle/service/bcat/bcat_types.h @@ -1,9 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once #include +#include #include #include "common/common_funcs.h" diff --git a/src/core/hle/service/bcat/delivery_cache_directory_service.h b/src/core/hle/service/bcat/delivery_cache_directory_service.h index b902c64959..cea67b0d87 100644 --- a/src/core/hle/service/bcat/delivery_cache_directory_service.h +++ b/src/core/hle/service/bcat/delivery_cache_directory_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/bcat/delivery_cache_file_service.cpp b/src/core/hle/service/bcat/delivery_cache_file_service.cpp index b75fac4bfe..1d961e1655 100644 --- a/src/core/hle/service/bcat/delivery_cache_file_service.cpp +++ b/src/core/hle/service/bcat/delivery_cache_file_service.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/bcat/delivery_cache_file_service.h b/src/core/hle/service/bcat/delivery_cache_file_service.h index e1012e6873..911bda55b3 100644 --- a/src/core/hle/service/bcat/delivery_cache_file_service.h +++ b/src/core/hle/service/bcat/delivery_cache_file_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/bcat/delivery_cache_progress_service.cpp b/src/core/hle/service/bcat/delivery_cache_progress_service.cpp index 79e7e0d957..873977bf90 100644 --- a/src/core/hle/service/bcat/delivery_cache_progress_service.cpp +++ b/src/core/hle/service/bcat/delivery_cache_progress_service.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/bcat/delivery_cache_progress_service.h b/src/core/hle/service/bcat/delivery_cache_progress_service.h index f81a139806..83d4d35827 100644 --- a/src/core/hle/service/bcat/delivery_cache_progress_service.h +++ b/src/core/hle/service/bcat/delivery_cache_progress_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/bcat/delivery_cache_storage_service.cpp b/src/core/hle/service/bcat/delivery_cache_storage_service.cpp index 46ed4dae67..a363bcb2d1 100644 --- a/src/core/hle/service/bcat/delivery_cache_storage_service.cpp +++ b/src/core/hle/service/bcat/delivery_cache_storage_service.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/bcat/delivery_cache_storage_service.h b/src/core/hle/service/bcat/delivery_cache_storage_service.h index 3b8dfb1a39..4849e58047 100644 --- a/src/core/hle/service/bcat/delivery_cache_storage_service.h +++ b/src/core/hle/service/bcat/delivery_cache_storage_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/bcat/news/builtin_news.cpp b/src/core/hle/service/bcat/news/builtin_news.cpp index 2a7756a155..bf4e989fb8 100644 --- a/src/core/hle/service/bcat/news/builtin_news.cpp +++ b/src/core/hle/service/bcat/news/builtin_news.cpp @@ -40,8 +40,8 @@ std::vector default_logo_small; std::vector default_logo_large; bool default_logos_loaded = false; -ankerl::unordered_dense::map> news_images_small; -ankerl::unordered_dense::map> news_images_large; +::Common::unordered_map> news_images_small; +::Common::unordered_map> news_images_large; std::mutex images_mutex; diff --git a/src/core/hle/service/bcat/news/builtin_news.h b/src/core/hle/service/bcat/news/builtin_news.h index cb06b2d4e3..e47cc89588 100644 --- a/src/core/hle/service/bcat/news/builtin_news.h +++ b/src/core/hle/service/bcat/news/builtin_news.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/core/hle/service/bcat/news/msgpack.cpp b/src/core/hle/service/bcat/news/msgpack.cpp index b67b75ea9d..9cea2de522 100644 --- a/src/core/hle/service/bcat/news/msgpack.cpp +++ b/src/core/hle/service/bcat/news/msgpack.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "core/hle/service/bcat/news/msgpack.h" diff --git a/src/core/hle/service/bcat/news/msgpack.h b/src/core/hle/service/bcat/news/msgpack.h index a7a2aa40ba..3163689782 100644 --- a/src/core/hle/service/bcat/news/msgpack.h +++ b/src/core/hle/service/bcat/news/msgpack.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/core/hle/service/bcat/news/newly_arrived_event_holder.cpp b/src/core/hle/service/bcat/news/newly_arrived_event_holder.cpp index ed393f7a26..b60de3b1f5 100644 --- a/src/core/hle/service/bcat/news/newly_arrived_event_holder.cpp +++ b/src/core/hle/service/bcat/news/newly_arrived_event_holder.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/bcat/news/newly_arrived_event_holder.h b/src/core/hle/service/bcat/news/newly_arrived_event_holder.h index 6cc9ae0997..5c2aeec182 100644 --- a/src/core/hle/service/bcat/news/newly_arrived_event_holder.h +++ b/src/core/hle/service/bcat/news/newly_arrived_event_holder.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/bcat/news/news_data_service.h b/src/core/hle/service/bcat/news/news_data_service.h index 8f5e031701..45fe4bafcc 100644 --- a/src/core/hle/service/bcat/news/news_data_service.h +++ b/src/core/hle/service/bcat/news/news_data_service.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/bcat/news/news_database_service.cpp b/src/core/hle/service/bcat/news/news_database_service.cpp index 3580ceb0f7..27ca1ad378 100644 --- a/src/core/hle/service/bcat/news/news_database_service.cpp +++ b/src/core/hle/service/bcat/news/news_database_service.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/bcat/news/news_database_service.h b/src/core/hle/service/bcat/news/news_database_service.h index 143864e80e..7552da23de 100644 --- a/src/core/hle/service/bcat/news/news_database_service.h +++ b/src/core/hle/service/bcat/news/news_database_service.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/bcat/news/news_service.cpp b/src/core/hle/service/bcat/news/news_service.cpp index aed1536bfe..4dcaa14881 100644 --- a/src/core/hle/service/bcat/news/news_service.cpp +++ b/src/core/hle/service/bcat/news/news_service.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/bcat/news/news_service.h b/src/core/hle/service/bcat/news/news_service.h index 1a320df02f..5d6109243b 100644 --- a/src/core/hle/service/bcat/news/news_service.h +++ b/src/core/hle/service/bcat/news/news_service.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/bcat/news/news_storage.cpp b/src/core/hle/service/bcat/news/news_storage.cpp index 793c1fa94f..b65b680771 100644 --- a/src/core/hle/service/bcat/news/news_storage.cpp +++ b/src/core/hle/service/bcat/news/news_storage.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "core/hle/service/bcat/news/news_storage.h" diff --git a/src/core/hle/service/bcat/news/news_storage.h b/src/core/hle/service/bcat/news/news_storage.h index 531e2adb9a..3f5f366cc2 100644 --- a/src/core/hle/service/bcat/news/news_storage.h +++ b/src/core/hle/service/bcat/news/news_storage.h @@ -12,7 +12,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include "common/common_types.h" @@ -93,7 +93,7 @@ private: static s64 Now(); mutable std::mutex mtx; - ankerl::unordered_dense::map items; + ::Common::unordered_map items; size_t open_counter{}; }; diff --git a/src/core/hle/service/bcat/news/overwrite_event_holder.cpp b/src/core/hle/service/bcat/news/overwrite_event_holder.cpp index 1712971e4d..08449f1c4a 100644 --- a/src/core/hle/service/bcat/news/overwrite_event_holder.cpp +++ b/src/core/hle/service/bcat/news/overwrite_event_holder.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/bcat/news/overwrite_event_holder.h b/src/core/hle/service/bcat/news/overwrite_event_holder.h index cdc87d782c..507f6ab668 100644 --- a/src/core/hle/service/bcat/news/overwrite_event_holder.h +++ b/src/core/hle/service/bcat/news/overwrite_event_holder.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/bcat/news/service_creator.cpp b/src/core/hle/service/bcat/news/service_creator.cpp index a1b22c0044..f27e4e52e6 100644 --- a/src/core/hle/service/bcat/news/service_creator.cpp +++ b/src/core/hle/service/bcat/news/service_creator.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/bcat/news/service_creator.h b/src/core/hle/service/bcat/news/service_creator.h index 5a62e7c1a9..42fbccd2a0 100644 --- a/src/core/hle/service/bcat/news/service_creator.h +++ b/src/core/hle/service/bcat/news/service_creator.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/bcat/service_creator.cpp b/src/core/hle/service/bcat/service_creator.cpp index ca339e5a68..15f59d3056 100644 --- a/src/core/hle/service/bcat/service_creator.cpp +++ b/src/core/hle/service/bcat/service_creator.cpp @@ -1,6 +1,10 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 +#include "core/core.h" #include "core/hle/service/bcat/bcat_service.h" #include "core/hle/service/bcat/delivery_cache_storage_service.h" #include "core/hle/service/bcat/service_creator.h" @@ -37,7 +41,8 @@ IServiceCreator::~IServiceCreator() = default; Result IServiceCreator::CreateBcatService(ClientProcessId process_id, OutInterface out_interface) { LOG_INFO(Service_BCAT, "called, process_id={}", process_id.pid); - *out_interface = std::make_shared(system, *backend); + *out_interface = + std::make_shared(system, *backend, system.ResolveCallerProgramId(*process_id)); R_SUCCEED(); } @@ -45,7 +50,7 @@ Result IServiceCreator::CreateDeliveryCacheStorageService( ClientProcessId process_id, OutInterface out_interface) { LOG_INFO(Service_BCAT, "called, process_id={}", process_id.pid); - const auto title_id = system.GetApplicationProcessProgramID(); + const auto title_id = system.ResolveCallerProgramId(*process_id); *out_interface = std::make_shared(system, fsc.GetBCATDirectory(title_id)); R_SUCCEED(); diff --git a/src/core/hle/service/bcat/service_creator.h b/src/core/hle/service/bcat/service_creator.h index 50e6633248..2245007d6c 100644 --- a/src/core/hle/service/bcat/service_creator.h +++ b/src/core/hle/service/bcat/service_creator.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/bpc/bpc.cpp b/src/core/hle/service/bpc/bpc.cpp index 91b15e256c..6f2e00b22b 100644 --- a/src/core/hle/service/bpc/bpc.cpp +++ b/src/core/hle/service/bpc/bpc.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -54,11 +57,72 @@ public: } }; +class BPC_C final : public ServiceFramework { +public: + explicit BPC_C(Core::System& system_) : ServiceFramework{system_, "bpc:c"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "ShutdownSystem"}, + {1, nullptr, "RebootSystem"}, + {2, nullptr, "GetWakeupReason"}, + {3, nullptr, "GetShutdownReason"}, + {4, nullptr, "GetAcOk"}, + {5, nullptr, "GetPowerEvent"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class BPC_B final : public ServiceFramework { +public: + explicit BPC_B(Core::System& system_) : ServiceFramework{system_, "bpc:b"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetSleepButtonState"}, + {1, nullptr, "GetPowerButtonEvent"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class BPC_W final : public ServiceFramework { +public: + explicit BPC_W(Core::System& system_) : ServiceFramework{system_, "bpc:w"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "CreateWakeupTimer"}, + {1, nullptr, "CancelWakeupTimer"}, + {2, nullptr, "EnableWakeupTimerOnDevice"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class BPC_AMS final : public ServiceFramework { +public: + explicit BPC_AMS(Core::System& system_) : ServiceFramework{system_, "bpc:ams"} { + // clang-format off + static const FunctionInfo functions[] = { + {65000, nullptr, "RebootToFatalError"}, + {65001, nullptr, "SetRebootPayload"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); - server_manager->RegisterNamedService("bpc", std::make_shared(system)); - server_manager->RegisterNamedService("bpc:r", std::make_shared(system)); + server_manager->RegisterNamedService("bpc", std::make_shared(system), 13); + server_manager->RegisterNamedService("bpc:r", std::make_shared(system), 13); + server_manager->RegisterNamedService("bpc:c", std::make_shared(system), 13); + server_manager->RegisterNamedService("bpc:b", std::make_shared(system), 13); + server_manager->RegisterNamedService("bpc:w", std::make_shared(system), 13); + server_manager->RegisterNamedService("bpc:ams", std::make_shared(system), 4); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/bpc/bpc.h b/src/core/hle/service/bpc/bpc.h index 524391ddb5..bf13ce5bae 100644 --- a/src/core/hle/service/bpc/bpc.h +++ b/src/core/hle/service/bpc/bpc.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/btdrv/btdrv.h b/src/core/hle/service/btdrv/btdrv.h index 42713860e6..4e739a96e3 100644 --- a/src/core/hle/service/btdrv/btdrv.h +++ b/src/core/hle/service/btdrv/btdrv.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/btm/btm.cpp b/src/core/hle/service/btm/btm.cpp index 327c2d2ddb..b358387ffe 100644 --- a/src/core/hle/service/btm/btm.cpp +++ b/src/core/hle/service/btm/btm.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project diff --git a/src/core/hle/service/btm/btm.h b/src/core/hle/service/btm/btm.h index 0bf77d053e..902a61d8cc 100644 --- a/src/core/hle/service/btm/btm.h +++ b/src/core/hle/service/btm/btm.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/btm/btm_debug.cpp b/src/core/hle/service/btm/btm_debug.cpp index 0c9a3a6557..abbe7d4832 100644 --- a/src/core/hle/service/btm/btm_debug.cpp +++ b/src/core/hle/service/btm/btm_debug.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/btm/btm_debug.h b/src/core/hle/service/btm/btm_debug.h index bf4f7e14fd..fad760d674 100644 --- a/src/core/hle/service/btm/btm_debug.h +++ b/src/core/hle/service/btm/btm_debug.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/btm/btm_system.h b/src/core/hle/service/btm/btm_system.h index fe1c6dbd78..9755540204 100644 --- a/src/core/hle/service/btm/btm_system.h +++ b/src/core/hle/service/btm/btm_system.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/btm/btm_system_core.cpp b/src/core/hle/service/btm/btm_system_core.cpp index f1e623be16..318657fc32 100644 --- a/src/core/hle/service/btm/btm_system_core.cpp +++ b/src/core/hle/service/btm/btm_system_core.cpp @@ -29,7 +29,7 @@ IBtmSystemCore::IBtmSystemCore(Core::System& system_) {10, nullptr, "StartAudioDeviceDiscovery"}, {11, nullptr, "StopAudioDeviceDiscovery"}, {12, nullptr, "IsDiscoveryingAudioDevice"}, - {13, nullptr, "GetDiscoveredAudioDevice"}, + {13, C<&IBtmSystemCore::GetDiscoveredAudioDevice>, "GetDiscoveredAudioDevice"}, {14, C<&IBtmSystemCore::AcquireAudioDeviceConnectionEvent>, "AcquireAudioDeviceConnectionEvent"}, {15, nullptr, "ConnectAudioDevice"}, {16, nullptr, "IsConnectingAudioDevice"}, @@ -93,6 +93,11 @@ Result IBtmSystemCore::AcquireRadioEvent(Out out_is_valid, R_SUCCEED(); } +Result IBtmSystemCore::GetDiscoveredAudioDevice(OutArray, BufferAttr_HipcPointer> out_audio_devices, s32 count, Out out_total) { + LOG_WARNING(Service_BTM, "(STUBBED) called"); + R_SUCCEED(); +} + Result IBtmSystemCore::AcquireAudioDeviceConnectionEvent( OutCopyHandle out_event) { LOG_WARNING(Service_BTM, "(STUBBED) called"); diff --git a/src/core/hle/service/btm/btm_system_core.h b/src/core/hle/service/btm/btm_system_core.h index 06498b21ea..3fc4c0d56c 100644 --- a/src/core/hle/service/btm/btm_system_core.h +++ b/src/core/hle/service/btm/btm_system_core.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -34,9 +37,8 @@ private: Result DisableRadio(); Result IsRadioEnabled(Out out_is_enabled); - Result AcquireRadioEvent(Out out_is_valid, - OutCopyHandle out_event); - + Result AcquireRadioEvent(Out out_is_valid, OutCopyHandle out_event); + Result GetDiscoveredAudioDevice(OutArray, BufferAttr_HipcPointer> out_audio_devices, s32 count, Out out_total); Result AcquireAudioDeviceConnectionEvent(OutCopyHandle out_event); Result GetConnectedAudioDevices( diff --git a/src/core/hle/service/btm/btm_user.h b/src/core/hle/service/btm/btm_user.h index d9ee5db455..d6cf330ed6 100644 --- a/src/core/hle/service/btm/btm_user.h +++ b/src/core/hle/service/btm/btm_user.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/btm/btm_user_core.h b/src/core/hle/service/btm/btm_user_core.h index dc0a22e810..5a565a5897 100644 --- a/src/core/hle/service/btm/btm_user_core.h +++ b/src/core/hle/service/btm/btm_user_core.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp index cd1dfe9939..f07fbd2185 100644 --- a/src/core/hle/service/caps/caps.cpp +++ b/src/core/hle/service/caps/caps.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -11,27 +14,38 @@ #include "core/hle/service/caps/caps_u.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/service.h" +#include "frontend_common/firmware_manager.h" namespace Service::Capture { +class IDecoderControlService final : public ServiceFramework { +public: + explicit IDecoderControlService(Core::System& system_) : ServiceFramework{system_, "grc:d"} { + // clang-format off + static const FunctionInfo functions[] = { + {3001, nullptr, "DecodeJpeg"}, + {4001, nullptr, "ShrinkJpeg"}, + {4002, nullptr, "ShrinkJpegEx"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); auto album_manager = std::make_shared(system); - server_manager->RegisterNamedService( - "caps:a", std::make_shared(system, album_manager)); - server_manager->RegisterNamedService( - "caps:c", std::make_shared(system, album_manager)); - server_manager->RegisterNamedService( - "caps:u", std::make_shared(system, album_manager)); - - server_manager->RegisterNamedService( - "caps:ss", std::make_shared(system, album_manager)); - server_manager->RegisterNamedService("caps:sc", - std::make_shared(system)); - server_manager->RegisterNamedService( - "caps:su", std::make_shared(system, album_manager)); - + server_manager->RegisterNamedService("caps:a", std::make_shared(system, album_manager)); + server_manager->RegisterNamedService("caps:c", std::make_shared(system, album_manager)); + server_manager->RegisterNamedService("caps:u", std::make_shared(system, album_manager)); + server_manager->RegisterNamedService("caps:ss", std::make_shared(system, album_manager)); + server_manager->RegisterNamedService("caps:sc", std::make_shared(system)); + server_manager->RegisterNamedService("caps:su", std::make_shared(system, album_manager)); + // +4.0.0 + if (FirmwareManager::GetFirmwareVersion(system).first.major >= 4) { + server_manager->RegisterNamedService("caps:dc", std::make_shared(system)); + } ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/caps/caps.h b/src/core/hle/service/caps/caps.h index 58e9725b85..670ee59cda 100644 --- a/src/core/hle/service/caps/caps.h +++ b/src/core/hle/service/caps/caps.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp index 2b365f6cd7..8f561bfcdc 100644 --- a/src/core/hle/service/caps/caps_a.cpp +++ b/src/core/hle/service/caps/caps_a.cpp @@ -76,7 +76,7 @@ Result IAlbumAccessorService::GetAlbumFileList( } Result IAlbumAccessorService::DeleteAlbumFile(AlbumFileId file_id) { - LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}", + LOG_INFO(Service_Capture, "called, application_id={:#0x}, storage={}, type={}", file_id.application_id, file_id.storage, file_id.type); const Result result = manager->DeleteAlbumFile(file_id); @@ -120,7 +120,7 @@ Result IAlbumAccessorService::LoadAlbumScreenShotImageEx1( OutLargeData out_image_output, OutArray out_image, OutArray out_buffer) { - LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}", + LOG_INFO(Service_Capture, "called, application_id={:#0x}, storage={}, type={}, flags={}", file_id.application_id, file_id.storage, file_id.type, decoder_options.flags); const Result result = @@ -133,7 +133,7 @@ Result IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1( OutLargeData out_image_output, OutArray out_image, OutArray out_buffer) { - LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}", + LOG_INFO(Service_Capture, "called, application_id={:#0x}, storage={}, type={}, flags={}", file_id.application_id, file_id.storage, file_id.type, decoder_options.flags); const Result result = manager->LoadAlbumScreenShotThumbnail(*out_image_output, out_image, diff --git a/src/core/hle/service/caps/caps_a.h b/src/core/hle/service/caps/caps_a.h index 1885ba6be0..c6aa8f9210 100644 --- a/src/core/hle/service/caps/caps_a.h +++ b/src/core/hle/service/caps/caps_a.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project diff --git a/src/core/hle/service/caps/caps_c.h b/src/core/hle/service/caps/caps_c.h index 0ecdfa1147..f50de0ee8c 100644 --- a/src/core/hle/service/caps/caps_c.h +++ b/src/core/hle/service/caps/caps_c.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/caps/caps_manager.cpp b/src/core/hle/service/caps/caps_manager.cpp index af0b9b16ad..d3c4ab57d1 100644 --- a/src/core/hle/service/caps/caps_manager.cpp +++ b/src/core/hle/service/caps/caps_manager.cpp @@ -175,19 +175,10 @@ Result AlbumManager::LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& ou return ResultIsNotMounted; } - out_image_output = { - .width = 1280, - .height = 720, - .attribute = - { - .unknown_0{}, - .orientation = AlbumImageOrientation::None, - .unknown_1{}, - .unknown_2{}, - .pad163{}, - }, - .pad179{}, - }; + out_image_output = {}; + out_image_output.width = 1280; + out_image_output.height = 720; + out_image_output.attribute.orientation = AlbumImageOrientation::None; std::filesystem::path path; const auto result = GetFile(path, file_id); @@ -211,19 +202,10 @@ Result AlbumManager::LoadAlbumScreenShotThumbnail( return ResultIsNotMounted; } - out_image_output = { - .width = 320, - .height = 180, - .attribute = - { - .unknown_0{}, - .orientation = AlbumImageOrientation::None, - .unknown_1{}, - .unknown_2{}, - .pad163{}, - }, - .pad179{}, - }; + out_image_output = {}; + out_image_output.width = 320; + out_image_output.height = 180; + out_image_output.attribute.orientation = AlbumImageOrientation::None; std::filesystem::path path; const auto result = GetFile(path, file_id); @@ -248,7 +230,9 @@ Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry, AlbumReportOption report_option, const ApplicationData& app_data, std::span image_data, u64 aruid) { - const u64 title_id = system.GetApplicationProcessProgramID(); + R_UNLESS(!image_data.empty(), ResultUnknown); //TODO: ??? + + const u64 title_id = system.ResolveCallerProgramId(aruid); auto static_service = system.ServiceManager().GetService("time:u", true); @@ -264,14 +248,12 @@ Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry, } const auto date = ConvertToAlbumDateTime(posix_time); - return SaveImage(out_entry, image_data, title_id, date); } -Result AlbumManager::SaveEditedScreenShot(ApplicationAlbumEntry& out_entry, - const ScreenShotAttribute& attribute, - const AlbumFileId& file_id, - std::span image_data) { +Result AlbumManager::SaveEditedScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute, const AlbumFileId& file_id, std::span image_data) { + R_UNLESS(!image_data.empty(), ResultUnknown); //TODO: ??? + auto static_service = system.ServiceManager().GetService("time:u", true); @@ -286,7 +268,6 @@ Result AlbumManager::SaveEditedScreenShot(ApplicationAlbumEntry& out_entry, } const auto date = ConvertToAlbumDateTime(posix_time); - return SaveImage(out_entry, image_data, file_id.application_id, date); } diff --git a/src/core/hle/service/caps/caps_manager.h b/src/core/hle/service/caps/caps_manager.h index 22f5672e47..af2d22697b 100644 --- a/src/core/hle/service/caps/caps_manager.h +++ b/src/core/hle/service/caps/caps_manager.h @@ -6,7 +6,7 @@ #pragma once -#include +#include "common/container/unordered_map.h" #include "common/fs/fs.h" #include "core/hle/result.h" @@ -88,7 +88,7 @@ private: AlbumFileDateTime ConvertToAlbumDateTime(u64 posix_time) const; bool is_mounted{}; - ankerl::unordered_dense::map album_files; + ::Common::unordered_map album_files; Core::System& system; }; diff --git a/src/core/hle/service/caps/caps_result.h b/src/core/hle/service/caps/caps_result.h index 179ae48409..b7ba2a642d 100644 --- a/src/core/hle/service/caps/caps_result.h +++ b/src/core/hle/service/caps/caps_result.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/caps/caps_sc.cpp b/src/core/hle/service/caps/caps_sc.cpp index 6117cb7c6c..92d53da9f5 100644 --- a/src/core/hle/service/caps/caps_sc.cpp +++ b/src/core/hle/service/caps/caps_sc.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/caps/caps_sc.h b/src/core/hle/service/caps/caps_sc.h index d555f49792..72e947bf16 100644 --- a/src/core/hle/service/caps/caps_sc.h +++ b/src/core/hle/service/caps/caps_sc.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/caps/caps_ss.h b/src/core/hle/service/caps/caps_ss.h index da4b4cc5fc..78971bde0e 100644 --- a/src/core/hle/service/caps/caps_ss.h +++ b/src/core/hle/service/caps/caps_ss.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp index 8622ab058f..4ecfc840e4 100644 --- a/src/core/hle/service/caps/caps_su.cpp +++ b/src/core/hle/service/caps/caps_su.cpp @@ -33,8 +33,7 @@ IScreenShotApplicationService::IScreenShotApplicationService( IScreenShotApplicationService::~IScreenShotApplicationService() = default; -Result IScreenShotApplicationService::SetShimLibraryVersion(ShimLibraryVersion library_version, - ClientAppletResourceUserId aruid) { +Result IScreenShotApplicationService::SetShimLibraryVersion(ShimLibraryVersion library_version, ClientAppletResourceUserId aruid) { LOG_WARNING(Service_Capture, "(STUBBED) called. library_version={}, applet_resource_user_id={}", library_version, aruid.pid); R_SUCCEED(); @@ -74,13 +73,8 @@ void IScreenShotApplicationService::CaptureAndSaveScreenshot(AlbumReportOption r Layout::FramebufferLayout layout = Layout::DefaultFrameLayout(screenshot_width, screenshot_height); - const Capture::ScreenShotAttribute attribute{ - .unknown_0{}, - .orientation = Capture::AlbumImageOrientation::None, - .unknown_1{}, - .unknown_2{}, - .pad163{}, - }; + Capture::ScreenShotAttribute attribute{}; + attribute.orientation = Capture::AlbumImageOrientation::None; renderer.RequestScreenshot( image_data.data(), @@ -96,7 +90,8 @@ void IScreenShotApplicationService::CaptureAndSaveScreenshot(AlbumReportOption r manager->FlipVerticallyOnWrite(invert_y); manager->SaveScreenShot(entry, attribute, report_option, image_data, {}); }, - layout); + layout, + Nvnflinger::LayerStackId::Screenshot); } } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps_su.h b/src/core/hle/service/caps/caps_su.h index 4b4cbd09e6..b7e53007ce 100644 --- a/src/core/hle/service/caps/caps_su.h +++ b/src/core/hle/service/caps/caps_su.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/caps/caps_types.h b/src/core/hle/service/caps/caps_types.h index 3deaaad5b8..549ce4a307 100644 --- a/src/core/hle/service/caps/caps_types.h +++ b/src/core/hle/service/caps/caps_types.h @@ -1,8 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 #pragma once +#include #include "common/common_funcs.h" #include "common/common_types.h" diff --git a/src/core/hle/service/caps/caps_u.h b/src/core/hle/service/caps/caps_u.h index 023ee1fe79..debae97a46 100644 --- a/src/core/hle/service/caps/caps_u.h +++ b/src/core/hle/service/caps/caps_u.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/cmif_serialization.h b/src/core/hle/service/cmif_serialization.h index 75461cc6be..acc63f0ac2 100644 --- a/src/core/hle/service/cmif_serialization.h +++ b/src/core/hle/service/cmif_serialization.h @@ -444,7 +444,7 @@ void CmifReplyWrapImpl(HLERequestContext& ctx, T& t, Result (T::*f)(A...)) { const bool is_domain = mgr ? mgr->IsDomain() : false; ASSERT_MSG(!is_domain, "Non-domain reply used on domain session\n" - "Service={} (TIPC={} CmdType={} Cmd=0x{:08X}\n" + "Service={} (TIPC={} CmdType={} Cmd={:#08x}\n" "HasDomainHeader={} DomainHandlers={}\nDesc={}", t.GetServiceName(), ctx.IsTipc(), u32(ctx.GetCommandType()), u32(ctx.GetCommand()), diff --git a/src/core/hle/service/cmif_types.h b/src/core/hle/service/cmif_types.h index dad358b87c..783bce7781 100644 --- a/src/core/hle/service/cmif_types.h +++ b/src/core/hle/service/cmif_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/erpt/erpt.h b/src/core/hle/service/erpt/erpt.h index 60094f556e..456e9af910 100644 --- a/src/core/hle/service/erpt/erpt.h +++ b/src/core/hle/service/erpt/erpt.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 5adf248684..432783f96e 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -9,6 +9,7 @@ #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/service.h" +#include "frontend_common/firmware_manager.h" namespace Service::ES { @@ -319,10 +320,98 @@ private: Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); }; +class NDRM_LU final : public ServiceFramework { +public: + explicit NDRM_LU(Core::System& system_) + : ServiceFramework{system_, "ndrm:lu"} { + // clang-format off + static const FunctionInfo functions[] = { + {1, nullptr, "Cmd1"}, + {2, nullptr, "Cmd2"}, + {3, nullptr, "Cmd3"}, + {1000, nullptr, "Cmd1000"}, + {8000, nullptr, "Cmd8000"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class NDRM_LA final : public ServiceFramework { +public: + explicit NDRM_LA(Core::System& system_) + : ServiceFramework{system_, "ndrm:la"} { + // clang-format off + static const FunctionInfo functions[] = { + {1, nullptr, "Cmd1"}, + {2, nullptr, "Cmd2"}, + {3, nullptr, "Cmd3"}, + {4, nullptr, "Cmd4"}, + {5, nullptr, "Cmd5"}, + {6, nullptr, "Cmd6"}, + {7, nullptr, "Cmd7"}, + {8, nullptr, "Cmd8"}, + {9, nullptr, "Cmd9"}, + {10, nullptr, "Cmd10"}, + {11, nullptr, "Cmd11"}, + {12, nullptr, "Cmd12"}, + {13, nullptr, "Cmd13"}, + {14, nullptr, "Cmd14"}, + {15, nullptr, "Cmd15"}, + {16, nullptr, "Cmd16"}, + {17, nullptr, "Cmd17"}, + {18, nullptr, "Cmd18"}, + {19, nullptr, "Cmd19"}, + {20, nullptr, "Cmd20"}, + {21, nullptr, "Cmd21"}, + {22, nullptr, "Cmd22"}, + {23, nullptr, "Cmd23"}, + {24, nullptr, "Cmd24"}, + {25, nullptr, "Cmd25"}, + {26, nullptr, "Cmd26"}, + {27, nullptr, "Cmd27"}, + {28, nullptr, "Cmd28"}, + {29, nullptr, "Cmd29"}, + {30, nullptr, "Cmd30"}, + {31, nullptr, "Cmd31"}, + {32, nullptr, "Cmd32"}, + {33, nullptr, "Cmd33"}, + {34, nullptr, "Cmd34"}, + {35, nullptr, "Cmd35"}, + {36, nullptr, "Cmd36"}, + {37, nullptr, "Cmd37"}, + {38, nullptr, "Cmd38"}, + {39, nullptr, "Cmd39"}, + {40, nullptr, "Cmd40"}, + {42, nullptr, "Cmd42"}, + {43, nullptr, "Cmd43"}, + {44, nullptr, "Cmd44"}, + {45, nullptr, "Cmd45"}, + {46, nullptr, "Cmd46"}, + {47, nullptr, "Cmd47"}, + {48, nullptr, "Cmd48"}, + {49, nullptr, "Cmd49"}, + {50, nullptr, "Cmd50"}, + {51, nullptr, "Cmd51"}, + {8000, nullptr, "Cmd8000"}, + {8001, nullptr, "Cmd8001"}, + {8002, nullptr, "Cmd8002"}, + {8003, nullptr, "Cmd8003"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); server_manager->RegisterNamedService("es", std::make_shared(system)); + // +13.0.0 + if (FirmwareManager::GetFirmwareVersion(system).first.major >= 13) { + server_manager->RegisterNamedService("ndrm:lu", std::make_shared(system)); + server_manager->RegisterNamedService("ndrm:la", std::make_shared(system)); + } ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/es/es.h b/src/core/hle/service/es/es.h index 3176806252..215aadb98f 100644 --- a/src/core/hle/service/es/es.h +++ b/src/core/hle/service/es/es.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/eupld/eupld.cpp b/src/core/hle/service/eupld/eupld.cpp index 3cf27513ae..9b26ae9c83 100644 --- a/src/core/hle/service/eupld/eupld.cpp +++ b/src/core/hle/service/eupld/eupld.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/eupld/eupld.h b/src/core/hle/service/eupld/eupld.h index 8eb0a5b4f2..851e286733 100644 --- a/src/core/hle/service/eupld/eupld.h +++ b/src/core/hle/service/eupld/eupld.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp index 5b281a725a..db48526614 100644 --- a/src/core/hle/service/fatal/fatal.cpp +++ b/src/core/hle/service/fatal/fatal.cpp @@ -71,9 +71,9 @@ static void GenerateErrorReport(Core::System& system, Result error_code, const F std::string crash_report = fmt::format( "Eden {}-{} crash report\n" "Title ID: {:016x}\n" - "Result: {:#X} ({:04}-{:04d})\n" - "Set flags: 0x{:16X}\n" - "Program entry point: 0x{:16X}\n" + "Result: {:#x} ({:04}-{:04d})\n" + "Set flags: {:#16X}\n" + "Program entry point: {:#16X}\n" "\n", Common::g_scm_branch, Common::g_scm_desc, title_id, error_code.raw, 2000 + static_cast(error_code.GetModule()), @@ -98,7 +98,7 @@ static void GenerateErrorReport(Core::System& system, Result error_code, const F } crash_report += fmt::format("Architecture: {}\n", info.ArchAsString()); - crash_report += fmt::format("Unknown 10: 0x{:016x}\n", info.unk10); + crash_report += fmt::format("Unknown 10: {:#016x}\n", info.unk10); } LOG_ERROR(Service_Fatal, "{}", crash_report); @@ -111,7 +111,7 @@ static void GenerateErrorReport(Core::System& system, Result error_code, const F static void ThrowFatalError(Core::System& system, Result error_code, FatalType fatal_type, const FatalInfo& info) { - LOG_ERROR(Service_Fatal, "Threw fatal error type {} with error code {:#X}", fatal_type, + LOG_ERROR(Service_Fatal, "Threw fatal error type {} with error code {:#x}", fatal_type, error_code.raw); switch (fatal_type) { diff --git a/src/core/hle/service/fatal/fatal.h b/src/core/hle/service/fatal/fatal.h index f1c1104060..176ad8d0d9 100644 --- a/src/core/hle/service/fatal/fatal.h +++ b/src/core/hle/service/fatal/fatal.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/fatal/fatal_p.cpp b/src/core/hle/service/fatal/fatal_p.cpp index 4a81bb5e2f..345743d6bc 100644 --- a/src/core/hle/service/fatal/fatal_p.cpp +++ b/src/core/hle/service/fatal/fatal_p.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/fatal/fatal_p.h b/src/core/hle/service/fatal/fatal_p.h index f743368350..ea2183c125 100644 --- a/src/core/hle/service/fatal/fatal_p.h +++ b/src/core/hle/service/fatal/fatal_p.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/fatal/fatal_u.cpp b/src/core/hle/service/fatal/fatal_u.cpp index 3739711fce..b2850c3e62 100644 --- a/src/core/hle/service/fatal/fatal_u.cpp +++ b/src/core/hle/service/fatal/fatal_u.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/fatal/fatal_u.h b/src/core/hle/service/fatal/fatal_u.h index 65fbe26961..4d193857d0 100644 --- a/src/core/hle/service/fatal/fatal_u.h +++ b/src/core/hle/service/fatal/fatal_u.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/fgm/fgm.cpp b/src/core/hle/service/fgm/fgm.cpp index 6b3f77be2b..cac03ae24c 100644 --- a/src/core/hle/service/fgm/fgm.cpp +++ b/src/core/hle/service/fgm/fgm.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -45,7 +48,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } }; diff --git a/src/core/hle/service/fgm/fgm.h b/src/core/hle/service/fgm/fgm.h index 9d2465c0fb..89ef7096bb 100644 --- a/src/core/hle/service/fgm/fgm.h +++ b/src/core/hle/service/fgm/fgm.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index c192fea454..925af7b371 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -43,6 +43,16 @@ static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base, return base->GetDirectoryRelative(dir_name); } +static std::string_view GetGuestParentPath(std::string_view path) { + const auto name_index = path.find_last_of("\\/"); + return name_index == std::string_view::npos ? std::string_view{} : path.substr(0, name_index); +} + +static std::string_view GetGuestFilename(std::string_view path) { + const auto name_index = path.find_last_of("\\/"); + return name_index == std::string_view::npos ? path : path.substr(name_index + 1); +} + VfsDirectoryServiceWrapper::VfsDirectoryServiceWrapper(FileSys::VirtualDir backing_) : backing(std::move(backing_)) {} @@ -83,11 +93,12 @@ Result VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const { return ResultSuccess; } - auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); - if (dir == nullptr || dir->GetFile(Common::FS::GetFilename(path)) == nullptr) { + const auto filename = GetGuestFilename(path); + auto dir = GetDirectoryRelativeWrapped(backing, GetGuestParentPath(path)); + if (filename.empty() || dir == nullptr || dir->GetFile(filename) == nullptr) { return FileSys::ResultPathNotFound; } - if (!dir->DeleteFile(Common::FS::GetFilename(path))) { + if (!dir->DeleteFile(filename)) { // TODO(DarkLordZach): Find a better error code for this return ResultUnknown; } @@ -97,7 +108,9 @@ Result VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const { Result VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) const { std::string path(Common::FS::SanitizePath(path_)); - + if (GetDirectoryRelativeWrapped(backing, path) != nullptr) { + return FileSys::ResultPathAlreadyExists; + } // NOTE: This is inaccurate behavior. CreateDirectory is not recursive. // CreateDirectory should return PathNotFound if the parent directory does not exist. // This is here temporarily in order to have UMM "work" in the meantime. @@ -117,8 +130,19 @@ Result VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) con Result VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_) const { std::string path(Common::FS::SanitizePath(path_)); - auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); - if (!dir->DeleteSubdirectory(Common::FS::GetFilename(path))) { + const auto dirname = GetGuestFilename(path); + auto dir = GetDirectoryRelativeWrapped(backing, GetGuestParentPath(path)); + FileSys::VirtualDir target{}; + if (!dirname.empty() && dir != nullptr) { + target = dir->GetSubdirectory(dirname); + } + if (target == nullptr) { + return FileSys::ResultPathNotFound; + } + if (!target->GetFiles().empty() || !target->GetSubdirectories().empty()) { + return ResultUnknown; + } + if (!dir->DeleteSubdirectory(dirname)) { // TODO(DarkLordZach): Find a better error code for this return ResultUnknown; } @@ -127,8 +151,12 @@ Result VfsDirectoryServiceWrapper::DeleteDirectory(const std::string& path_) con Result VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::string& path_) const { std::string path(Common::FS::SanitizePath(path_)); - auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); - if (!dir->DeleteSubdirectoryRecursive(Common::FS::GetFilename(path))) { + const auto dirname = GetGuestFilename(path); + auto dir = GetDirectoryRelativeWrapped(backing, GetGuestParentPath(path)); + if (dirname.empty() || dir == nullptr || dir->GetSubdirectory(dirname) == nullptr) { + return FileSys::ResultPathNotFound; + } + if (!dir->DeleteSubdirectoryRecursive(dirname)) { // TODO(DarkLordZach): Find a better error code for this return ResultUnknown; } @@ -137,9 +165,13 @@ Result VfsDirectoryServiceWrapper::DeleteDirectoryRecursively(const std::string& Result VfsDirectoryServiceWrapper::CleanDirectoryRecursively(const std::string& path) const { const std::string sanitized_path(Common::FS::SanitizePath(path)); - auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(sanitized_path)); + const auto dirname = GetGuestFilename(sanitized_path); + auto dir = GetDirectoryRelativeWrapped(backing, GetGuestParentPath(sanitized_path)); - if (!dir->CleanSubdirectoryRecursive(Common::FS::GetFilename(sanitized_path))) { + if (dirname.empty() || dir == nullptr || dir->GetSubdirectory(dirname) == nullptr) { + return FileSys::ResultPathNotFound; + } + if (!dir->CleanSubdirectoryRecursive(dirname)) { // TODO(DarkLordZach): Find a better error code for this return ResultUnknown; } @@ -195,12 +227,13 @@ Result VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_, std::string src_path(Common::FS::SanitizePath(src_path_)); std::string dest_path(Common::FS::SanitizePath(dest_path_)); auto src = GetDirectoryRelativeWrapped(backing, src_path); + if (src == nullptr) + return FileSys::ResultPathNotFound; + if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) { - // Use more-optimized vfs implementation rename. - if (src == nullptr) - return FileSys::ResultPathNotFound; - if (!src->Rename(Common::FS::GetFilename(dest_path))) { - // TODO(DarkLordZach): Find a better error code for this + std::string full_src_path = backing->GetFullPath() + "/" + src_path; + std::string full_dest_path = backing->GetFullPath() + "/" + dest_path; + if (!Common::FS::RenameDir(full_src_path, full_dest_path)) { return ResultUnknown; } return ResultSuccess; @@ -754,6 +787,13 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove } } +void FileSystemController::InitTempStorage() { + const auto save_directory = system.GetFilesystem()->OpenDirectory(Common::FS::GetEdenPathString(Common::FS::EdenPath::SaveDir), FileSys::OpenMode::ReadWrite); + if (save_directory != nullptr) { + save_directory->DeleteSubdirectoryRecursive("temp"); + } +} + void FileSystemController::Reset() { std::scoped_lock lk{registration_lock}; registrations.clear(); @@ -764,9 +804,9 @@ void LoopProcess(Core::System& system) { const auto FileSystemProxyFactory = [&] { return std::make_shared(system); }; - server_manager->RegisterNamedService("fsp-ldr", std::make_shared(system)); - server_manager->RegisterNamedService("fsp:pr", std::make_shared(system)); - server_manager->RegisterNamedService("fsp-srv", std::move(FileSystemProxyFactory)); + server_manager->RegisterNamedService("fsp-ldr", std::make_shared(system), 61); + server_manager->RegisterNamedService("fsp-pr", std::make_shared(system), 61); + server_manager->RegisterNamedService("fsp-srv", std::move(FileSystemProxyFactory), 61); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index ef45aec627..a77358e2ef 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -127,6 +127,8 @@ public: // above is called. void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true); + void InitTempStorage(); + void Reset(); private: diff --git a/src/core/hle/service/filesystem/fsp/fs_i_directory.cpp b/src/core/hle/service/filesystem/fsp/fs_i_directory.cpp index 8483394d0c..c4e3c81f22 100644 --- a/src/core/hle/service/filesystem/fsp/fs_i_directory.cpp +++ b/src/core/hle/service/filesystem/fsp/fs_i_directory.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/filesystem/fsp/fs_i_directory.h b/src/core/hle/service/filesystem/fsp/fs_i_directory.h index b6251f7fdb..79256604f3 100644 --- a/src/core/hle/service/filesystem/fsp/fs_i_directory.h +++ b/src/core/hle/service/filesystem/fsp/fs_i_directory.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/filesystem/fsp/fs_i_file.cpp b/src/core/hle/service/filesystem/fsp/fs_i_file.cpp index ef9dd60ae1..22cbe2be2c 100644 --- a/src/core/hle/service/filesystem/fsp/fs_i_file.cpp +++ b/src/core/hle/service/filesystem/fsp/fs_i_file.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -30,7 +30,7 @@ Result IFile::Read( FileSys::ReadOption option, Out out_size, s64 offset, const OutBuffer out_buffer, s64 size) { - LOG_DEBUG(Service_FS, "called, option={}, offset={:#X}, length={}", option.value, offset, + LOG_DEBUG(Service_FS, "called, option={}, offset={:#x}, length={}", option.value, offset, size); // Read the data from the Storage backend @@ -41,7 +41,7 @@ Result IFile::Read( Result IFile::Write( const InBuffer buffer, FileSys::WriteOption option, s64 offset, s64 size) { - LOG_DEBUG(Service_FS, "called, option={}, offset={:#X}, length={}", option.value, offset, + LOG_DEBUG(Service_FS, "called, option={}, offset={:#x}, length={}", option.value, offset, size); R_RETURN(backend->Write(offset, buffer.data(), size, option)); diff --git a/src/core/hle/service/filesystem/fsp/fs_i_file.h b/src/core/hle/service/filesystem/fsp/fs_i_file.h index e8599ee2f8..14348f7fc6 100644 --- a/src/core/hle/service/filesystem/fsp/fs_i_file.h +++ b/src/core/hle/service/filesystem/fsp/fs_i_file.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp b/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp index 0638111ae9..edd381f57c 100644 --- a/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp +++ b/src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp @@ -24,7 +24,7 @@ IFileSystem::IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGe {3, D<&IFileSystem::DeleteDirectory>, "DeleteDirectory"}, {4, D<&IFileSystem::DeleteDirectoryRecursively>, "DeleteDirectoryRecursively"}, {5, D<&IFileSystem::RenameFile>, "RenameFile"}, - {6, nullptr, "RenameDirectory"}, + {6, D<&IFileSystem::RenameDirectory>, "RenameDirectory"}, {7, D<&IFileSystem::GetEntryType>, "GetEntryType"}, {8, D<&IFileSystem::OpenFile>, "OpenFile"}, {9, D<&IFileSystem::OpenDirectory>, "OpenDirectory"}, @@ -41,7 +41,7 @@ IFileSystem::IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGe Result IFileSystem::CreateFile(const InLargeData path, s32 option, s64 size) { - LOG_DEBUG(Service_FS, "called. file={}, option={:#X}, size=0x{:08X}", path->str, option, size); + LOG_DEBUG(Service_FS, "called. file={}, option={:#x}, size={:#08x}", path->str, option, size); R_RETURN(backend->CreateFile(FileSys::Path(path->str), size)); } @@ -88,6 +88,14 @@ Result IFileSystem::RenameFile( R_RETURN(backend->RenameFile(FileSys::Path(old_path->str), FileSys::Path(new_path->str))); } +Result IFileSystem::RenameDirectory( + const InLargeData old_path, + const InLargeData new_path) { + LOG_DEBUG(Service_FS, "called. directory '{}' to directory '{}'", old_path->str, new_path->str); + + R_RETURN(backend->RenameDirectory(FileSys::Path(old_path->str), FileSys::Path(new_path->str))); +} + Result IFileSystem::OpenFile(OutInterface out_interface, const InLargeData path, u32 mode) { @@ -151,7 +159,7 @@ Result IFileSystem::GetTotalSpaceSize( Result IFileSystem::GetFileTimeStampRaw( Out out_timestamp, const InLargeData path) { - LOG_WARNING(Service_FS, "(Partial Implementation) called. file={}", path->str); + LOG_DEBUG(Service_FS, "(Partial Implementation) called. file={}", path->str); FileSys::FileTimeStampRaw vfs_timestamp{}; R_TRY(backend->GetFileTimeStampRaw(&vfs_timestamp, FileSys::Path(path->str))); diff --git a/src/core/hle/service/filesystem/fsp/fs_i_filesystem.h b/src/core/hle/service/filesystem/fsp/fs_i_filesystem.h index dd069f36f3..ed03c3ef83 100644 --- a/src/core/hle/service/filesystem/fsp/fs_i_filesystem.h +++ b/src/core/hle/service/filesystem/fsp/fs_i_filesystem.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -36,6 +39,8 @@ public: const InLargeData path); Result RenameFile(const InLargeData old_path, const InLargeData new_path); + Result RenameDirectory(const InLargeData old_path, + const InLargeData new_path); Result OpenFile(OutInterface out_interface, const InLargeData path, u32 mode); Result OpenDirectory(OutInterface out_interface, diff --git a/src/core/hle/service/filesystem/fsp/fs_i_multi_commit_manager.cpp b/src/core/hle/service/filesystem/fsp/fs_i_multi_commit_manager.cpp index 6263282344..4af0a4ea2d 100644 --- a/src/core/hle/service/filesystem/fsp/fs_i_multi_commit_manager.cpp +++ b/src/core/hle/service/filesystem/fsp/fs_i_multi_commit_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/filesystem/fsp/fs_i_multi_commit_manager.h b/src/core/hle/service/filesystem/fsp/fs_i_multi_commit_manager.h index 8ebf7c7d96..3ebeb2cb0a 100644 --- a/src/core/hle/service/filesystem/fsp/fs_i_multi_commit_manager.h +++ b/src/core/hle/service/filesystem/fsp/fs_i_multi_commit_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/filesystem/fsp/fs_i_save_data_info_reader.cpp b/src/core/hle/service/filesystem/fsp/fs_i_save_data_info_reader.cpp index 495b131df4..0cac094caf 100644 --- a/src/core/hle/service/filesystem/fsp/fs_i_save_data_info_reader.cpp +++ b/src/core/hle/service/filesystem/fsp/fs_i_save_data_info_reader.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project diff --git a/src/core/hle/service/filesystem/fsp/fs_i_save_data_info_reader.h b/src/core/hle/service/filesystem/fsp/fs_i_save_data_info_reader.h index e45ad852ba..d2b8eb97cf 100644 --- a/src/core/hle/service/filesystem/fsp/fs_i_save_data_info_reader.h +++ b/src/core/hle/service/filesystem/fsp/fs_i_save_data_info_reader.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/filesystem/fsp/fs_i_storage.cpp b/src/core/hle/service/filesystem/fsp/fs_i_storage.cpp index 376a8bda29..8541c17817 100644 --- a/src/core/hle/service/filesystem/fsp/fs_i_storage.cpp +++ b/src/core/hle/service/filesystem/fsp/fs_i_storage.cpp @@ -1,9 +1,13 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 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 +#include +#include + #include "core/file_sys/errors.h" #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/filesystem/fsp/fs_i_storage.h" @@ -26,11 +30,19 @@ IStorage::IStorage(Core::System& system_, FileSys::VirtualFile backend_) Result IStorage::Read( OutBuffer out_bytes, s64 offset, s64 length) { - LOG_DEBUG(Service_FS, "called, offset={:#X}, length={}", offset, length); + LOG_DEBUG(Service_FS, "called, offset={:#x}, length={}", offset, length); R_UNLESS(length >= 0, FileSys::ResultInvalidSize); R_UNLESS(offset >= 0, FileSys::ResultInvalidOffset); + const auto now = std::chrono::steady_clock::now(); + if (now < next_read_tick) { + std::this_thread::sleep_for(next_read_tick - now); + } + next_read_tick = std::chrono::steady_clock::now() + + std::chrono::nanoseconds{std::min(length, MaxChargedReadSize) * + 1'000'000'000 / ReadBytesPerSecond}; + // Read the data from the Storage backend backend->Read(out_bytes.data(), length, offset); diff --git a/src/core/hle/service/filesystem/fsp/fs_i_storage.h b/src/core/hle/service/filesystem/fsp/fs_i_storage.h index 74d879386f..bac72d73c7 100644 --- a/src/core/hle/service/filesystem/fsp/fs_i_storage.h +++ b/src/core/hle/service/filesystem/fsp/fs_i_storage.h @@ -1,8 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 "core/file_sys/vfs/vfs.h" #include "core/hle/service/cmif_types.h" #include "core/hle/service/filesystem/filesystem.h" @@ -17,6 +22,10 @@ public: private: FileSys::VirtualFile backend; + static constexpr s64 ReadBytesPerSecond = 40'000'000; + static constexpr s64 MaxChargedReadSize = 64 * 1024; + std::chrono::steady_clock::time_point next_read_tick{}; + Result Read( OutBuffer out_bytes, s64 offset, s64 length); diff --git a/src/core/hle/service/filesystem/fsp/fsp_ldr.cpp b/src/core/hle/service/filesystem/fsp/fsp_ldr.cpp index 8ee733f474..1dcacb0231 100644 --- a/src/core/hle/service/filesystem/fsp/fsp_ldr.cpp +++ b/src/core/hle/service/filesystem/fsp/fsp_ldr.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/filesystem/fsp/fsp_ldr.h b/src/core/hle/service/filesystem/fsp/fsp_ldr.h index 358739a872..b642efe7ad 100644 --- a/src/core/hle/service/filesystem/fsp/fsp_ldr.h +++ b/src/core/hle/service/filesystem/fsp/fsp_ldr.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/filesystem/fsp/fsp_pr.cpp b/src/core/hle/service/filesystem/fsp/fsp_pr.cpp index 7c03ebaeae..e7941ef9c9 100644 --- a/src/core/hle/service/filesystem/fsp/fsp_pr.cpp +++ b/src/core/hle/service/filesystem/fsp/fsp_pr.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/filesystem/fsp/fsp_pr.h b/src/core/hle/service/filesystem/fsp/fsp_pr.h index bd4e0a730c..175a3c7a23 100644 --- a/src/core/hle/service/filesystem/fsp/fsp_pr.h +++ b/src/core/hle/service/filesystem/fsp/fsp_pr.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp index 0ee68c0332..58303a0bd6 100644 --- a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp @@ -54,7 +54,7 @@ FSP_SRV::FSP_SRV(Core::System& system_) static const FunctionInfo functions[] = { {0, nullptr, "OpenFileSystem"}, {1, D<&FSP_SRV::SetCurrentProcess>, "SetCurrentProcess"}, - {2, nullptr, "OpenDataFileSystemByCurrentProcess"}, + {2, D<&FSP_SRV::OpenDataFileSystemByCurrentProcess>, "OpenDataFileSystemByCurrentProcess"}, {7, D<&FSP_SRV::OpenFileSystemWithPatch>, "OpenFileSystemWithPatch"}, {8, nullptr, "OpenFileSystemWithId"}, {9, nullptr, "OpenDataFileSystemByApplicationId"}, @@ -118,6 +118,7 @@ FSP_SRV::FSP_SRV(Core::System& system_) {204, nullptr, "OpenDataFileSystemByProgramIndex"}, {205, D<&FSP_SRV::OpenDataStorageWithProgramIndex>, "OpenDataStorageWithProgramIndex"}, {206, nullptr, "OpenDataStorageByPath"}, + {210, D<&FSP_SRV::SetCurrentProcess>, "SetCurrentProcess"}, {400, nullptr, "OpenDeviceOperator"}, {500, nullptr, "OpenSdCardDetectionEventNotifier"}, {501, nullptr, "OpenGameCardDetectionEventNotifier"}, @@ -153,6 +154,9 @@ FSP_SRV::FSP_SRV(Core::System& system_) {720, nullptr, "AbandonAccessFailure"}, {800, nullptr, "GetAndClearFileSystemProxyErrorInfo"}, {810, nullptr, "RegisterProgramIndexMapInfo"}, + {820, nullptr, "GetContentStorageInfoIndex"}, + {830, nullptr, "EncryptStreamPlaySaveData"}, + {831, nullptr, "DecryptStreamPlaySaveData"}, {1000, nullptr, "SetBisRootForHost"}, {1001, nullptr, "SetSaveDataSize"}, {1002, nullptr, "SetSaveDataRootPath"}, @@ -190,7 +194,7 @@ FSP_SRV::~FSP_SRV() = default; Result FSP_SRV::SetCurrentProcess(ClientProcessId pid) { current_process_id = *pid; - LOG_DEBUG(Service_FS, "called. current_process_id=0x{:016X}", current_process_id); + LOG_DEBUG(Service_FS, "called. current_process_id={:#016x}", current_process_id); R_RETURN( fsc.OpenProcess(&program_id, &save_data_controller, &romfs_controller, current_process_id)); @@ -284,9 +288,17 @@ Result FSP_SRV::OpenSaveDataFileSystem(OutInterface out_interface, id = FileSys::StorageId::NandSystem; break; case FileSys::SaveDataSpaceId::Temporary: + // ok this is definitely wrong. ASSERT(false) here just kills the whole game the first + // time it opens cache storage, and plenty of games do that (TOTK for one). there is + // user-space scratch storage so it belongs on user nand. map it, do not crash. + id = FileSys::StorageId::NandUser; + break; case FileSys::SaveDataSpaceId::ProperSystem: case FileSys::SaveDataSpaceId::SafeMode: - ASSERT(false); + // same deal for these two. they are system-level spaces so they go on system nand. + // way better than nuking the title over a save-space id we just did not list out. + id = FileSys::StorageId::NandSystem; + break; } *out_interface = @@ -324,9 +336,15 @@ Result FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId(OutInterface system nand. handled, not crashed. + id = FileSys::StorageId::NandSystem; + break; } *out_interface = @@ -446,6 +464,31 @@ Result FSP_SRV::OpenSaveDataTransferProhibiter( R_SUCCEED(); } +Result FSP_SRV::OpenDataFileSystemByCurrentProcess(OutInterface out_interface) { + LOG_DEBUG(Service_FS, "called"); + + if (!romfs) { + auto current_romfs = romfs_controller->OpenRomFSCurrentProcess(); + if (!current_romfs) { + LOG_CRITICAL(Service_FS, "No file system interface available!"); + R_RETURN(ResultUnknown); + } + + romfs = current_romfs; + } + + auto extracted_romfs = FileSys::ExtractRomFS(romfs); + if (!extracted_romfs) { + LOG_CRITICAL(Service_FS, "Failed to extract RomFS for the current process!"); + R_RETURN(ResultUnknown); + } + + *out_interface = std::make_shared( + system, extracted_romfs, SizeGetter::FromStorageId(fsc, FileSys::StorageId::NandUser)); + + R_SUCCEED(); +} + Result FSP_SRV::OpenDataStorageByCurrentProcess(OutInterface out_interface) { LOG_DEBUG(Service_FS, "called"); diff --git a/src/core/hle/service/filesystem/fsp/fsp_srv.h b/src/core/hle/service/filesystem/fsp/fsp_srv.h index 0ea41903e4..156cf4e571 100644 --- a/src/core/hle/service/filesystem/fsp/fsp_srv.h +++ b/src/core/hle/service/filesystem/fsp/fsp_srv.h @@ -51,6 +51,7 @@ public: private: Result SetCurrentProcess(ClientProcessId pid); + Result OpenDataFileSystemByCurrentProcess(OutInterface out_interface); Result OpenFileSystemWithPatch(OutInterface out_interface, FileSystemProxyType type, u64 open_program_id); Result OpenSdCardFileSystem(OutInterface out_interface); diff --git a/src/core/hle/service/filesystem/fsp/fsp_types.h b/src/core/hle/service/filesystem/fsp/fsp_types.h index 294da6a2de..c704203ad1 100644 --- a/src/core/hle/service/filesystem/fsp/fsp_types.h +++ b/src/core/hle/service/filesystem/fsp/fsp_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/filesystem/fsp/save_data_transfer_prohibiter.cpp b/src/core/hle/service/filesystem/fsp/save_data_transfer_prohibiter.cpp index d8d6289fe1..f4c218db8d 100644 --- a/src/core/hle/service/filesystem/fsp/save_data_transfer_prohibiter.cpp +++ b/src/core/hle/service/filesystem/fsp/save_data_transfer_prohibiter.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/filesystem/fsp/save_data_transfer_prohibiter.h b/src/core/hle/service/filesystem/fsp/save_data_transfer_prohibiter.h index d206e1dea4..6141bae18c 100644 --- a/src/core/hle/service/filesystem/fsp/save_data_transfer_prohibiter.h +++ b/src/core/hle/service/filesystem/fsp/save_data_transfer_prohibiter.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/filesystem/romfs_controller.cpp b/src/core/hle/service/filesystem/romfs_controller.cpp index 19c9cec722..9725d02f4b 100644 --- a/src/core/hle/service/filesystem/romfs_controller.cpp +++ b/src/core/hle/service/filesystem/romfs_controller.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/filesystem/romfs_controller.h b/src/core/hle/service/filesystem/romfs_controller.h index 3c3ead344c..5471e136c3 100644 --- a/src/core/hle/service/filesystem/romfs_controller.h +++ b/src/core/hle/service/filesystem/romfs_controller.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/filesystem/save_data_controller.cpp b/src/core/hle/service/filesystem/save_data_controller.cpp index 8720681d17..d3e22b1552 100644 --- a/src/core/hle/service/filesystem/save_data_controller.cpp +++ b/src/core/hle/service/filesystem/save_data_controller.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/filesystem/save_data_controller.h b/src/core/hle/service/filesystem/save_data_controller.h index dc9d713dfb..afb95cb949 100644 --- a/src/core/hle/service/filesystem/save_data_controller.h +++ b/src/core/hle/service/filesystem/save_data_controller.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index 79578956b1..f8690dbb8b 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -183,8 +183,8 @@ private: auto& readable_event = completion_event->GetReadableEvent(); IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(readable_event.Signal()); - rb.PushCopyObjects(readable_event); + rb.Push(readable_event.Signal(system.Kernel())); + rb.PushCopyObjects(ctx, readable_event); } void Cancel(HLERequestContext& ctx) { @@ -400,7 +400,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(notification_event->GetReadableEvent()); + rb.PushCopyObjects(ctx, notification_event->GetReadableEvent()); } void Clear(HLERequestContext& ctx) { @@ -476,7 +476,7 @@ private: void Module::Interface::CreateFriendService(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); LOG_DEBUG(Service_Friend, "called"); } @@ -488,29 +488,88 @@ void Module::Interface::CreateNotificationService(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system, uuid); + rb.PushIpcInterface(ctx, system, uuid); } -Module::Interface::Interface(std::shared_ptr module_, Core::System& system_, - const char* name) - : ServiceFramework{system_, name}, module{std::move(module_)} {} +Module::Interface::Interface(std::shared_ptr module_, Core::System& system_, const char* name) + : ServiceFramework{system_, name}, module{std::move(module_)} +{} Module::Interface::~Interface() = default; +class IServiceForApplication final : public ServiceFramework { +public: + explicit IServiceForApplication(Core::System& system_) + : ServiceFramework{system_, "nd:app"} + { + static const FunctionInfo functions[] = { + {0, nullptr, "GetReceivableNeighborInfoCountMax"}, + {10, nullptr, "IsNeighborDetectionEnabled"}, + }; + RegisterHandlers(functions); + } +}; + +class IServiceForSystem final : public ServiceFramework { +public: + explicit IServiceForSystem(Core::System& system_) + : ServiceFramework{system_, "nd:sys"} + { + static const FunctionInfo functions[] = { + {0, nullptr, "GetReceivableNeighborInfoCountMax"}, + {10, nullptr, "IsNeighborDetectionEnabled"}, + {200, nullptr, "SetSystemData"}, + {201, nullptr, "ClearSystemData"}, + {203, nullptr, "GetReceivableNeighborInfoCountForSystem"}, + {204, nullptr, "ReceiveNeighborInfoForSystem"}, + {205, nullptr, "SetSender"}, + {206, nullptr, "GetSender"}, + {207, nullptr, "CreateScannerForSystem"}, + {208, nullptr, "CreateReceiveEventHolderForSystem"}, + {223, nullptr, "EnableNeighborDetection"}, + {224, nullptr, "DisableNeighborDetection"}, + {226, nullptr, "EnablePowerSave"}, + {227, nullptr, "DisablePowerSave"}, + {228, nullptr, "IsPowerSaveEnabled"}, + {232, nullptr, "ClearBlockedUsers"}, + {233, nullptr, "GetBlockedUserCount"}, + {234, nullptr, "BlockUserByLocalUserId"}, + {235, nullptr, "BlockUserByNetworkUserId"}, + {236, nullptr, "UnblockUserByLocalUserId"}, + {237, nullptr, "UnblockUserByNetworkUserId"}, + {240, nullptr, "DeleteApplication"}, + {250, nullptr, "InitializeApplicationInfo"}, + {260, nullptr, "CreateAccountSystemSaveDataAccessSuppressor"}, + {300, nullptr, "AddReceivedNeighborInfoForSystemForDebug"}, + {301, nullptr, "GetSendDataForDebug"}, + {302, nullptr, "ClearReceiveCounterForDebug"}, + {303, nullptr, "GetNextReceiveCounterForDebug"}, + {304, nullptr, "ListBlockedUsersForDebug"}, + {305, nullptr, "RefreshSendDataIdForDebug"}, + {306, nullptr, "ReloadFwdbgSettingsForDebug"}, + {307, nullptr, "EnableApplicationForDebug"}, + {308, nullptr, "GetNextReceiveCountersForDebug"}, + {309, nullptr, "ListApplicationInfoForDebug"}, + {310, nullptr, "SetApplicationDataForDebug"}, + {400, nullptr, "GetNetworkUserId"}, + {401, nullptr, "DeleteNetworkUserId"}, + }; + RegisterHandlers(functions); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); auto module = std::make_shared(); - server_manager->RegisterNamedService("friend:a", - std::make_shared(module, system, "friend:a")); - server_manager->RegisterNamedService("friend:m", - std::make_shared(module, system, "friend:m")); - server_manager->RegisterNamedService("friend:s", - std::make_shared(module, system, "friend:s")); - server_manager->RegisterNamedService("friend:u", - std::make_shared(module, system, "friend:u")); - server_manager->RegisterNamedService("friend:v", - std::make_shared(module, system, "friend:v")); + server_manager->RegisterNamedService("nd:app", std::make_shared(system)); + server_manager->RegisterNamedService("nd:sys", std::make_shared(system)); + + server_manager->RegisterNamedService("friend:a", std::make_shared(module, system, "friend:a")); + server_manager->RegisterNamedService("friend:m", std::make_shared(module, system, "friend:m")); + server_manager->RegisterNamedService("friend:s", std::make_shared(module, system, "friend:s")); + server_manager->RegisterNamedService("friend:u", std::make_shared(module, system, "friend:u")); + server_manager->RegisterNamedService("friend:v", std::make_shared(module, system, "friend:v")); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/friend/friend.h b/src/core/hle/service/friend/friend.h index 2824dc7866..61ac92fbb4 100644 --- a/src/core/hle/service/friend/friend.h +++ b/src/core/hle/service/friend/friend.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/friend/friend_interface.cpp b/src/core/hle/service/friend/friend_interface.cpp index c8b98b1f0c..b436d43303 100644 --- a/src/core/hle/service/friend/friend_interface.cpp +++ b/src/core/hle/service/friend/friend_interface.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/friend/friend_interface.h b/src/core/hle/service/friend/friend_interface.h index 3a2184c347..0315c62c39 100644 --- a/src/core/hle/service/friend/friend_interface.h +++ b/src/core/hle/service/friend/friend_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp index a7f54e980c..7eda57ddc3 100644 --- a/src/core/hle/service/glue/arp.cpp +++ b/src/core/hle/service/glue/arp.cpp @@ -36,18 +36,18 @@ std::optional GetTitleIDForProcessID(Core::System& system, u64 process_id) ARP_R::ARP_R(Core::System& system_, const ARPManager& manager_) : ServiceFramework{system_, "arp:r"}, manager{manager_} { // clang-format off - static const FunctionInfo functions[] = { - {0, &ARP_R::GetApplicationLaunchProperty, "GetApplicationLaunchProperty"}, - {1, &ARP_R::GetApplicationLaunchPropertyWithApplicationId, "GetApplicationLaunchPropertyWithApplicationId"}, - {2, &ARP_R::GetApplicationControlProperty, "GetApplicationControlProperty"}, - {3, &ARP_R::GetApplicationControlPropertyWithApplicationId, "GetApplicationControlPropertyWithApplicationId"}, - {4, nullptr, "GetApplicationInstanceUnregistrationNotifier"}, - {5, nullptr, "ListApplicationInstanceId"}, - {6, nullptr, "GetMicroApplicationInstanceId"}, - {7, nullptr, "GetApplicationCertificate"}, - {9998, nullptr, "GetPreomiaApplicationLaunchProperty"}, - {9999, nullptr, "GetPreomiaApplicationControlProperty"}, - }; + static const FunctionInfo functions[] = { + {0, &ARP_R::GetApplicationLaunchProperty, "GetApplicationLaunchProperty"}, + {1, &ARP_R::GetApplicationLaunchPropertyWithApplicationId, "GetApplicationLaunchPropertyWithApplicationId"}, + {2, &ARP_R::GetApplicationControlProperty, "GetApplicationControlProperty"}, + {3, &ARP_R::GetApplicationControlPropertyWithApplicationId, "GetApplicationControlPropertyWithApplicationId"}, + {4, nullptr, "GetApplicationInstanceUnregistrationNotifier"}, + {5, nullptr, "ListApplicationInstanceId"}, + {6, nullptr, "GetMicroApplicationInstanceId"}, + {7, nullptr, "GetApplicationCertificate"}, + {9998, nullptr, "GetPreomiaApplicationLaunchProperty"}, + {9999, nullptr, "GetPreomiaApplicationControlProperty"}, + }; // clang-format on RegisterHandlers(functions); @@ -191,8 +191,7 @@ private: } if (issued) { - LOG_ERROR(Service_ARP, - "Attempted to issue registrar, but registrar is already issued!"); + LOG_ERROR(Service_ARP, "Attempted to issue registrar, but registrar is already issued!"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(Glue::ResultAlreadyBound); return; @@ -209,9 +208,7 @@ private: LOG_DEBUG(Service_ARP, "called"); if (issued) { - LOG_ERROR( - Service_ARP, - "Attempted to set application launch property, but registrar is already issued!"); + LOG_ERROR(Service_ARP, "Attempted to set application launch property, but registrar is already issued!"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(Glue::ResultAlreadyBound); return; @@ -228,9 +225,7 @@ private: LOG_DEBUG(Service_ARP, "called"); if (issued) { - LOG_ERROR( - Service_ARP, - "Attempted to set application control property, but registrar is already issued!"); + LOG_ERROR(Service_ARP, "Attempted to set application control property, but registrar is already issued!"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(Glue::ResultAlreadyBound); return; @@ -279,7 +274,7 @@ void ARP_W::AcquireRegistrar(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(registrar); + rb.PushIpcInterface(ctx, registrar); } void ARP_W::UnregisterApplicationInstance(HLERequestContext& ctx) { diff --git a/src/core/hle/service/glue/arp.h b/src/core/hle/service/glue/arp.h index 5bce801755..ec3d809b5b 100644 --- a/src/core/hle/service/glue/arp.h +++ b/src/core/hle/service/glue/arp.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/bgtc.cpp b/src/core/hle/service/glue/bgtc.cpp index 5e51f98319..360f16f0dd 100644 --- a/src/core/hle/service/glue/bgtc.cpp +++ b/src/core/hle/service/glue/bgtc.cpp @@ -28,7 +28,7 @@ void BGTC_T::OpenTaskService(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } ITaskService::ITaskService(Core::System& system_) : ServiceFramework{system_, "ITaskService"} { diff --git a/src/core/hle/service/glue/bgtc.h b/src/core/hle/service/glue/bgtc.h index 5a5d9c9a7a..129193da1f 100644 --- a/src/core/hle/service/glue/bgtc.h +++ b/src/core/hle/service/glue/bgtc.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/ectx.cpp b/src/core/hle/service/glue/ectx.cpp index 6f71b62f3a..322ae45254 100644 --- a/src/core/hle/service/glue/ectx.cpp +++ b/src/core/hle/service/glue/ectx.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -40,6 +43,30 @@ private: } }; +ECTX_W::ECTX_W(Core::System& system_) : ServiceFramework{system_, "ectx:w"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "CreateContextRegistrar"}, + {1, nullptr, "CommitContext"}, + {2, nullptr, "RemoveContext"}, + }; + // clang-format on + RegisterHandlers(functions); +} +ECTX_W::~ECTX_W() = default; + +ECTX_R::ECTX_R(Core::System& system_) : ServiceFramework{system_, "ectx:r"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetContextInfo"}, + {1, nullptr, "PullContext"}, + {2, nullptr, "ListContextDescriptorWithResultForDebug"}, + }; + // clang-format on + RegisterHandlers(functions); +} +ECTX_R::~ECTX_R() = default; + ECTX_AW::ECTX_AW(Core::System& system_) : ServiceFramework{system_, "ectx:aw"} { // clang-format off static const FunctionInfo functions[] = { @@ -56,7 +83,7 @@ ECTX_AW::~ECTX_AW() = default; void ECTX_AW::CreateContextRegistrar(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(std::make_shared(system)); + rb.PushIpcInterface(ctx, system); } } // namespace Service::Glue diff --git a/src/core/hle/service/glue/ectx.h b/src/core/hle/service/glue/ectx.h index ffa74d8d33..ff48075eda 100644 --- a/src/core/hle/service/glue/ectx.h +++ b/src/core/hle/service/glue/ectx.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -11,6 +14,18 @@ class System; namespace Service::Glue { +class ECTX_W final : public ServiceFramework { +public: + explicit ECTX_W(Core::System& system_); + ~ECTX_W() override; +}; + +class ECTX_R final : public ServiceFramework { +public: + explicit ECTX_R(Core::System& system_); + ~ECTX_R() override; +}; + class ECTX_AW final : public ServiceFramework { public: explicit ECTX_AW(Core::System& system_); diff --git a/src/core/hle/service/glue/errors.h b/src/core/hle/service/glue/errors.h index 30feaa5c03..9839271447 100644 --- a/src/core/hle/service/glue/errors.h +++ b/src/core/hle/service/glue/errors.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/glue.cpp b/src/core/hle/service/glue/glue.cpp index ea2843462f..f267060ef4 100644 --- a/src/core/hle/service/glue/glue.cpp +++ b/src/core/hle/service/glue/glue.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -19,40 +22,27 @@ void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); // ARP - server_manager->RegisterNamedService("arp:r", - std::make_shared(system, system.GetARPManager())); - server_manager->RegisterNamedService("arp:w", - std::make_shared(system, system.GetARPManager())); + server_manager->RegisterNamedService("arp:r", std::make_shared(system, system.GetARPManager()), 16); + server_manager->RegisterNamedService("arp:w", std::make_shared(system, system.GetARPManager()), 8); // BackGround Task Controller server_manager->RegisterNamedService("bgtc:t", std::make_shared(system)); server_manager->RegisterNamedService("bgtc:sc", std::make_shared(system)); // Error Context + server_manager->RegisterNamedService("ectx:w", std::make_shared(system)); + server_manager->RegisterNamedService("ectx:r", std::make_shared(system)); server_manager->RegisterNamedService("ectx:aw", std::make_shared(system)); // Notification Services - server_manager->RegisterNamedService( - "notif:a", std::make_shared(system)); - server_manager->RegisterNamedService("notif:s", - std::make_shared(system)); + server_manager->RegisterNamedService("notif:a", std::make_shared(system)); + server_manager->RegisterNamedService("notif:s", std::make_shared(system)); // Time auto time = std::make_shared(system); - - server_manager->RegisterNamedService( - "time:u", - std::make_shared( - system, Service::PSC::Time::StaticServiceSetupInfo{0, 0, 0, 0, 0, 0}, time, "time:u")); - server_manager->RegisterNamedService( - "time:a", - std::make_shared( - system, Service::PSC::Time::StaticServiceSetupInfo{1, 1, 0, 1, 0, 0}, time, "time:a")); - server_manager->RegisterNamedService( - "time:r", - std::make_shared( - system, Service::PSC::Time::StaticServiceSetupInfo{0, 0, 0, 0, 1, 0}, time, "time:r")); - + server_manager->RegisterNamedService("time:u", std::make_shared(system, Service::PSC::Time::StaticServiceSetupInfo{0, 0, 0, 0, 0, 0}, time, "time:u")); + server_manager->RegisterNamedService("time:a", std::make_shared(system, Service::PSC::Time::StaticServiceSetupInfo{1, 1, 0, 1, 0, 0}, time, "time:a")); + server_manager->RegisterNamedService("time:r", std::make_shared(system, Service::PSC::Time::StaticServiceSetupInfo{0, 0, 0, 0, 1, 0}, time, "time:r")); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/glue/glue.h b/src/core/hle/service/glue/glue.h index 2a906f5ad6..64d45cfc14 100644 --- a/src/core/hle/service/glue/glue.h +++ b/src/core/hle/service/glue/glue.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/glue_manager.cpp b/src/core/hle/service/glue/glue_manager.cpp index 22f0017044..78fc5ebac3 100644 --- a/src/core/hle/service/glue/glue_manager.cpp +++ b/src/core/hle/service/glue/glue_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/glue_manager.h b/src/core/hle/service/glue/glue_manager.h index 216aa34c19..6330828834 100644 --- a/src/core/hle/service/glue/glue_manager.h +++ b/src/core/hle/service/glue/glue_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/notif.cpp b/src/core/hle/service/glue/notif.cpp index 49811bff4f..716e4da32d 100644 --- a/src/core/hle/service/glue/notif.cpp +++ b/src/core/hle/service/glue/notif.cpp @@ -116,12 +116,10 @@ Result NotificationServiceImpl::Initialize(u64 aruid) { R_SUCCEED(); } -std::vector::iterator NotificationServiceImpl::GetAlarmFromId( - AlarmSettingId alarm_setting_id) { - return std::find_if(alarms.begin(), alarms.end(), - [alarm_setting_id](const AlarmSetting& alarm) { - return alarm.alarm_setting_id == alarm_setting_id; - }); +std::vector::iterator NotificationServiceImpl::GetAlarmFromId(AlarmSettingId alarm_setting_id) { + return std::find_if(alarms.begin(), alarms.end(), [alarm_setting_id](const AlarmSetting& alarm) { + return alarm.alarm_setting_id == alarm_setting_id; + }); } INotificationServicesForApplication::INotificationServicesForApplication(Core::System& system_) diff --git a/src/core/hle/service/glue/notif.h b/src/core/hle/service/glue/notif.h index ef2522fdfb..7ae20ba6f8 100644 --- a/src/core/hle/service/glue/notif.h +++ b/src/core/hle/service/glue/notif.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/glue/time/alarm_worker.cpp b/src/core/hle/service/glue/time/alarm_worker.cpp index 3ff071f4a0..770dc62f5c 100644 --- a/src/core/hle/service/glue/time/alarm_worker.cpp +++ b/src/core/hle/service/glue/time/alarm_worker.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -28,7 +31,7 @@ void AlarmWorker::Initialize(std::shared_ptr "Glue:AlarmWorker::AlarmTimer", [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional { - m_timer_event->Signal(); + m_timer_event->Signal(m_system.Kernel()); return std::nullopt; }); @@ -57,7 +60,7 @@ void AlarmWorker::OnPowerStateChanged() { s64 closest_time{}; if (!GetClosestAlarmInfo(closest_alarm_info, closest_time)) { m_system.CoreTiming().UnscheduleEvent(m_timer_timing_event); - m_timer_event->Clear(); + m_timer_event->Clear(m_system.Kernel()); return; } @@ -67,7 +70,7 @@ void AlarmWorker::OnPowerStateChanged() { auto next_time{closest_alarm_info.alert_time - closest_time}; m_system.CoreTiming().UnscheduleEvent(m_timer_timing_event); - m_timer_event->Clear(); + m_timer_event->Clear(m_system.Kernel()); m_system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds(next_time), m_timer_timing_event); diff --git a/src/core/hle/service/glue/time/alarm_worker.h b/src/core/hle/service/glue/time/alarm_worker.h index 131d012a6a..c8882fed88 100644 --- a/src/core/hle/service/glue/time/alarm_worker.h +++ b/src/core/hle/service/glue/time/alarm_worker.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/time/file_timestamp_worker.cpp b/src/core/hle/service/glue/time/file_timestamp_worker.cpp index 048ff174c9..60bd688e72 100644 --- a/src/core/hle/service/glue/time/file_timestamp_worker.cpp +++ b/src/core/hle/service/glue/time/file_timestamp_worker.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/time/file_timestamp_worker.h b/src/core/hle/service/glue/time/file_timestamp_worker.h index 5f8b9b0499..58f74e7d29 100644 --- a/src/core/hle/service/glue/time/file_timestamp_worker.h +++ b/src/core/hle/service/glue/time/file_timestamp_worker.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/time/manager.cpp b/src/core/hle/service/glue/time/manager.cpp index 0da790e1c8..0b3312904e 100644 --- a/src/core/hle/service/glue/time/manager.cpp +++ b/src/core/hle/service/glue/time/manager.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project diff --git a/src/core/hle/service/glue/time/manager.h b/src/core/hle/service/glue/time/manager.h index 87e6a21fd5..5a86585754 100644 --- a/src/core/hle/service/glue/time/manager.h +++ b/src/core/hle/service/glue/time/manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/time/pm_state_change_handler.cpp b/src/core/hle/service/glue/time/pm_state_change_handler.cpp index 7470fb2259..0bc37f75da 100644 --- a/src/core/hle/service/glue/time/pm_state_change_handler.cpp +++ b/src/core/hle/service/glue/time/pm_state_change_handler.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/time/pm_state_change_handler.h b/src/core/hle/service/glue/time/pm_state_change_handler.h index 27d9f78726..bf99fa8a14 100644 --- a/src/core/hle/service/glue/time/pm_state_change_handler.h +++ b/src/core/hle/service/glue/time/pm_state_change_handler.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/time/standard_steady_clock_resource.cpp b/src/core/hle/service/glue/time/standard_steady_clock_resource.cpp index 5ebaa33e00..e191405567 100644 --- a/src/core/hle/service/glue/time/standard_steady_clock_resource.cpp +++ b/src/core/hle/service/glue/time/standard_steady_clock_resource.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/time/standard_steady_clock_resource.h b/src/core/hle/service/glue/time/standard_steady_clock_resource.h index 978d6b63b8..b5168cb837 100644 --- a/src/core/hle/service/glue/time/standard_steady_clock_resource.h +++ b/src/core/hle/service/glue/time/standard_steady_clock_resource.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/time/static.cpp b/src/core/hle/service/glue/time/static.cpp index d8672b7248..beccf6ce43 100644 --- a/src/core/hle/service/glue/time/static.cpp +++ b/src/core/hle/service/glue/time/static.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/time/static.h b/src/core/hle/service/glue/time/static.h index 7bb0e0b36e..a52b84bf31 100644 --- a/src/core/hle/service/glue/time/static.h +++ b/src/core/hle/service/glue/time/static.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/time/time_zone.cpp b/src/core/hle/service/glue/time/time_zone.cpp index c358cfbf28..52bd651f2b 100644 --- a/src/core/hle/service/glue/time/time_zone.cpp +++ b/src/core/hle/service/glue/time/time_zone.cpp @@ -23,11 +23,13 @@ TimeZoneService::TimeZoneService( Core::System& system_, FileTimestampWorker& file_timestamp_worker, bool can_write_timezone_device_location, TimeZoneBinary& time_zone_binary, std::shared_ptr time_zone_service) - : ServiceFramework{system_, "ITimeZoneService"}, m_system{system}, - m_can_write_timezone_device_location{can_write_timezone_device_location}, - m_file_timestamp_worker{file_timestamp_worker}, m_wrapped_service{std::move( - time_zone_service)}, - m_operation_event{m_system}, m_time_zone_binary{time_zone_binary} { + : ServiceFramework{system_, "ITimeZoneService"} + , m_can_write_timezone_device_location{can_write_timezone_device_location} + , m_file_timestamp_worker{file_timestamp_worker} + , m_wrapped_service{std::move(time_zone_service)} + , m_operation_event{system} + , m_time_zone_binary{time_zone_binary} +{ // clang-format off static const FunctionInfo functions[] = { {0, D<&TimeZoneService::GetDeviceLocationName>, "GetDeviceLocationName"}, @@ -48,8 +50,7 @@ TimeZoneService::TimeZoneService( // clang-format on RegisterHandlers(functions); - m_set_sys = - m_system.ServiceManager().GetService("set:sys", true); + m_set_sys = system.ServiceManager().GetService("set:sys", true); } TimeZoneService::~TimeZoneService() = default; @@ -89,7 +90,7 @@ Result TimeZoneService::SetDeviceLocationName( std::scoped_lock m{m_list_mutex}; for (auto& operation_event : m_list_nodes) { - operation_event.m_event->Signal(); + operation_event.m_event->Signal(system.Kernel()); } R_SUCCEED(); } diff --git a/src/core/hle/service/glue/time/time_zone.h b/src/core/hle/service/glue/time/time_zone.h index 2b130b275d..635070263b 100644 --- a/src/core/hle/service/glue/time/time_zone.h +++ b/src/core/hle/service/glue/time/time_zone.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -77,9 +80,7 @@ public: const Service::PSC::Time::CalendarTime& calendar_time); private: - Core::System& m_system; std::shared_ptr m_set_sys; - bool m_can_write_timezone_device_location; FileTimestampWorker& m_file_timestamp_worker; std::shared_ptr m_wrapped_service; diff --git a/src/core/hle/service/glue/time/time_zone_binary.cpp b/src/core/hle/service/glue/time/time_zone_binary.cpp index 3cc1a62afd..eadad551e5 100644 --- a/src/core/hle/service/glue/time/time_zone_binary.cpp +++ b/src/core/hle/service/glue/time/time_zone_binary.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/time/time_zone_binary.h b/src/core/hle/service/glue/time/time_zone_binary.h index 135da631f2..012b933573 100644 --- a/src/core/hle/service/glue/time/time_zone_binary.h +++ b/src/core/hle/service/glue/time/time_zone_binary.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/glue/time/worker.cpp b/src/core/hle/service/glue/time/worker.cpp index 1dab3e9dcb..5025efc717 100644 --- a/src/core/hle/service/glue/time/worker.cpp +++ b/src/core/hle/service/glue/time/worker.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -30,7 +33,7 @@ TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady "Time::SteadyClockEvent", [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional { - m_timer_steady_clock->Signal(); + m_timer_steady_clock->Signal(m_system.Kernel()); return std::nullopt; }); @@ -38,19 +41,19 @@ TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady "Time::SteadyClockEvent", [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional { - m_timer_file_system->Signal(); + m_timer_file_system->Signal(m_system.Kernel()); return std::nullopt; }); } TimeWorker::~TimeWorker() { - m_local_clock_event->Signal(); - m_network_clock_event->Signal(); - m_ephemeral_clock_event->Signal(); + m_local_clock_event->Signal(m_system.Kernel()); + m_network_clock_event->Signal(m_system.Kernel()); + m_ephemeral_clock_event->Signal(m_system.Kernel()); std::this_thread::sleep_for(std::chrono::milliseconds(16)); m_thread.request_stop(); - m_event->Signal(); + m_event->Signal(m_system.Kernel()); m_thread.join(); m_ctx.CloseEvent(m_event); @@ -167,19 +170,19 @@ void TimeWorker::ThreadFunc(std::stop_token stop_token) { return; case EventType::PowerStateChange: - m_alarm_worker.GetEvent().Clear(); + m_alarm_worker.GetEvent().Clear(m_system.Kernel()); if (m_pm_state_change_handler.m_priority <= 1) { m_alarm_worker.OnPowerStateChanged(); } break; case EventType::SignalAlarms: - m_alarm_worker.GetTimerEvent().Clear(); + m_alarm_worker.GetTimerEvent().Clear(m_system.Kernel()); m_time_m->CheckAndSignalAlarms(); break; case EventType::UpdateLocalSystemClock: { - m_local_clock_event->Clear(); + m_local_clock_event->Clear(m_system.Kernel()); Service::PSC::Time::SystemClockContext context{}; R_ASSERT(m_local_clock->GetSystemClockContext(&context)); @@ -190,7 +193,7 @@ void TimeWorker::ThreadFunc(std::stop_token stop_token) { } case EventType::UpdateNetworkSystemClock: { - m_network_clock_event->Clear(); + m_network_clock_event->Clear(m_system.Kernel()); Service::PSC::Time::SystemClockContext context{}; R_ASSERT(m_network_clock->GetSystemClockContext(&context)); @@ -218,7 +221,7 @@ void TimeWorker::ThreadFunc(std::stop_token stop_token) { } case EventType::UpdateEphemeralSystemClock: { - m_ephemeral_clock_event->Clear(); + m_ephemeral_clock_event->Clear(m_system.Kernel()); Service::PSC::Time::SystemClockContext context{}; auto res = m_ephemeral_clock->GetSystemClockContext(&context); @@ -247,20 +250,20 @@ void TimeWorker::ThreadFunc(std::stop_token stop_token) { } case EventType::UpdateSteadyClock: - m_timer_steady_clock->Clear(); + m_timer_steady_clock->Clear(m_system.Kernel()); m_steady_clock_resource.UpdateTime(); m_time_m->SetStandardSteadyClockBaseTime(m_steady_clock_resource.GetTime()); break; case EventType::UpdateFileTimestamp: - m_timer_file_system->Clear(); + m_timer_file_system->Clear(m_system.Kernel()); m_file_timestamp_worker.SetFilesystemPosixTime(); break; case EventType::AutoCorrect: { - m_standard_user_auto_correct_clock_event->Clear(); + m_standard_user_auto_correct_clock_event->Clear(m_system.Kernel()); bool automatic_correction{}; R_ASSERT(m_time_sm->IsStandardUserSystemClockAutomaticCorrectionEnabled( diff --git a/src/core/hle/service/glue/time/worker.h b/src/core/hle/service/glue/time/worker.h index 69904e6748..f4be2ff367 100644 --- a/src/core/hle/service/glue/time/worker.h +++ b/src/core/hle/service/glue/time/worker.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/gpio/gpio.cpp b/src/core/hle/service/gpio/gpio.cpp new file mode 100644 index 0000000000..8b4aa0f5ab --- /dev/null +++ b/src/core/hle/service/gpio/gpio.cpp @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 + +#include "core/core.h" +#include "core/hle/service/gpio/gpio.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/service.h" + +namespace Service::GPIO { + +class GPIO final : public ServiceFramework { +public: + explicit GPIO(Core::System& system_) + : ServiceFramework{system_, "gpio"} + { + static const FunctionInfo functions[] = { + {0, nullptr, "Cmd0"}, + }; + RegisterHandlers(functions); + } + ~GPIO() override = default; +}; + +void LoopProcess(Core::System& system) { + auto server_manager = std::make_unique(system); + server_manager->RegisterNamedService("gpio", std::make_shared(system)); + ServerManager::RunServer(std::move(server_manager)); +} + +} // namespace Service::GPIO diff --git a/src/core/hle/service/gpio/gpio.h b/src/core/hle/service/gpio/gpio.h new file mode 100644 index 0000000000..57f9a64e56 --- /dev/null +++ b/src/core/hle/service/gpio/gpio.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 + +#pragma once + +namespace Core { +class System; +} + +namespace Service::GPIO { +void LoopProcess(Core::System& system); +} // namespace Service::GPIO diff --git a/src/core/hle/service/grc/grc.cpp b/src/core/hle/service/grc/grc.cpp index 64275da36d..e8e901662d 100644 --- a/src/core/hle/service/grc/grc.cpp +++ b/src/core/hle/service/grc/grc.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -26,10 +29,25 @@ public: } }; +class GRC_D final : public ServiceFramework { +public: + explicit GRC_D(Core::System& system_) : ServiceFramework{system_, "grc:d"} { + // clang-format off + static const FunctionInfo functions[] = { + {1, nullptr, "Initialize"}, + {2, nullptr, "Transfer"}, + {3, nullptr, "Cmd3"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); - server_manager->RegisterNamedService("grc:c", std::make_shared(system)); + server_manager->RegisterNamedService("grc:c", std::make_shared(system), 4); + server_manager->RegisterNamedService("grc:d", std::make_shared(system), 4); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/grc/grc.h b/src/core/hle/service/grc/grc.h index a3f8a5b90e..4c2b45057d 100644 --- a/src/core/hle/service/grc/grc.h +++ b/src/core/hle/service/grc/grc.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/hid/active_vibration_device_list.h b/src/core/hle/service/hid/active_vibration_device_list.h index beaa44d97a..f155eaf839 100644 --- a/src/core/hle/service/hid/active_vibration_device_list.h +++ b/src/core/hle/service/hid/active_vibration_device_list.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/hid/applet_resource.cpp b/src/core/hle/service/hid/applet_resource.cpp index 04d4d1b654..44ae767841 100644 --- a/src/core/hle/service/hid/applet_resource.cpp +++ b/src/core/hle/service/hid/applet_resource.cpp @@ -30,7 +30,7 @@ Result IAppletResource::GetSharedMemoryHandle( OutCopyHandle out_shared_memory_handle) { const auto result = resource_manager->GetSharedMemoryHandle(out_shared_memory_handle, aruid); - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, result={:#X}", aruid, result.raw); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, result={:#x}", aruid, result.raw); R_RETURN(result); } diff --git a/src/core/hle/service/hid/applet_resource.h b/src/core/hle/service/hid/applet_resource.h index d1e7db9b1e..40c3852baa 100644 --- a/src/core/hle/service/hid/applet_resource.h +++ b/src/core/hle/service/hid/applet_resource.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 47eedd73b2..25096ff36f 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -16,27 +19,77 @@ namespace Service::HID { +class IHidTemporaryServer final : public ServiceFramework { +public: + explicit IHidTemporaryServer(Core::System& system_) + : ServiceFramework{system_, "hid:tmp"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetConsoleSixAxisSensorCalibrationValues"}, + }; + // clang-format on + RegisterHandlers(functions); + } + ~IHidTemporaryServer() override = default; +}; + +class AHID_CD final : public ServiceFramework { +public: + explicit AHID_CD(Core::System& system_) + : ServiceFramework{system_, "ahid:cd"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "AcquireDevice"}, + {1, nullptr, "ReleaseDevice"}, + {2, nullptr, "GetCtrlSession"}, + {3, nullptr, "GetReadSession"}, + {4, nullptr, "GetWriteSession"}, + }; + // clang-format on + RegisterHandlers(functions); + } + ~AHID_CD() override = default; +}; + +class AHID_HDR final : public ServiceFramework { +public: + explicit AHID_HDR(Core::System& system_) + : ServiceFramework{system_, "ahid:hdr"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetDeviceEntries"}, + {1, nullptr, "GetDeviceList"}, + {2, nullptr, "GetDeviceParameters"}, + {3, nullptr, "AttachDevice"}, + {4, nullptr, "DetachDevice"}, + {5, nullptr, "SetDeviceFilter"}, + }; + // clang-format on + RegisterHandlers(functions); + } + ~AHID_HDR() override = default; +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); - std::shared_ptr firmware_settings = - std::make_shared(system); - std::shared_ptr resource_manager = - std::make_shared(system, firmware_settings); + auto firmware_settings = std::make_shared(system); + auto resource_manager = std::make_shared(system, firmware_settings); resource_manager->Initialize(); - server_manager->RegisterNamedService( - "hid", std::make_shared(system, resource_manager, firmware_settings)); - server_manager->RegisterNamedService( - "hid:dbg", std::make_shared(system, resource_manager, firmware_settings)); - server_manager->RegisterNamedService( - "hid:sys", std::make_shared(system, resource_manager, firmware_settings)); + server_manager->RegisterNamedService("hid", std::make_shared(system, resource_manager, firmware_settings)); + server_manager->RegisterNamedService("hid:dbg", std::make_shared(system, resource_manager, firmware_settings)); + server_manager->RegisterNamedService("hid:sys", std::make_shared(system, resource_manager, firmware_settings)); + server_manager->RegisterNamedService("hid:tmp", std::make_shared(system)); server_manager->RegisterNamedService("hidbus", std::make_shared(system)); server_manager->RegisterNamedService("irs", std::make_shared(system)); server_manager->RegisterNamedService("irs:sys", std::make_shared(system)); + server_manager->RegisterNamedService("ahid:cd", std::make_shared(system)); + server_manager->RegisterNamedService("ahid:hdr", std::make_shared(system)); + server_manager->RegisterNamedService("xcd:sys", std::make_shared(system)); system.RunServer(std::move(server_manager)); diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index ec5463f4e2..bf8cd44481 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/hid/hid_debug_server.cpp b/src/core/hle/service/hid/hid_debug_server.cpp index 43fe0f3a19..42428a9ff9 100644 --- a/src/core/hle/service/hid/hid_debug_server.cpp +++ b/src/core/hle/service/hid/hid_debug_server.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project diff --git a/src/core/hle/service/hid/hid_debug_server.h b/src/core/hle/service/hid/hid_debug_server.h index 3a483f07e5..f48f0205ed 100644 --- a/src/core/hle/service/hid/hid_debug_server.h +++ b/src/core/hle/service/hid/hid_debug_server.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp index f29cf4e3d7..9689bce8dd 100644 --- a/src/core/hle/service/hid/hid_server.cpp +++ b/src/core/hle/service/hid/hid_server.cpp @@ -237,7 +237,7 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr r {3013, nullptr, "SetDebugPadGenericPadMap"}, //21.0.0+ {3014, nullptr, "GetDebugPadKeyboardMap"}, //21.0.0+ {3015, nullptr, "SetDebugPadKeyboardMap"}, //21.0.0+ - {3150, nullptr, "SetMouseLibraryVersion"}, //21.0.0+ + {3150, C<&IHidServer::SetMouseLibraryVersion>, "SetMouseLibraryVersion"}, //21.0.0+ // What? -- {12010, nullptr, "SetButtonConfigLeft"}, }; // clang-format on @@ -251,7 +251,7 @@ Result IHidServer::CreateAppletResource(OutInterface out_applet ClientAppletResourceUserId aruid) { const auto result = GetResourceManager()->CreateAppletResource(aruid.pid); - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, result={:#X}", aruid.pid, + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, result={:#x}", aruid.pid, result.raw); *out_applet_resource = std::make_shared(system, resource_manager, aruid.pid); @@ -650,8 +650,7 @@ Result IHidServer::SetSupportedNpadStyleSet(Core::HID::NpadStyleSet supported_st LOG_DEBUG(Service_HID, "called, supported_style_set={}, applet_resource_user_id={}", supported_style_set, aruid.pid); - R_TRY( - GetResourceManager()->GetNpad()->SetSupportedNpadStyleSet(aruid.pid, supported_style_set)); + R_TRY(GetResourceManager()->GetNpad()->SetSupportedNpadStyleSet(system.Kernel(), aruid.pid, supported_style_set)); Core::HID::NpadStyleTag style_tag{supported_style_set}; const auto revision = GetResourceManager()->GetNpad()->GetRevision(aruid.pid); @@ -667,7 +666,7 @@ Result IHidServer::GetSupportedNpadStyleSet(Out out_sup ClientAppletResourceUserId aruid) { LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", aruid.pid); - R_RETURN(GetResourceManager()->GetNpad()->GetSupportedNpadStyleSet(aruid.pid, + R_RETURN(GetResourceManager()->GetNpad()->GetSupportedNpadStyleSet(system.Kernel(), aruid.pid, *out_supported_style_set)); } @@ -677,7 +676,7 @@ Result IHidServer::SetSupportedNpadIdType( LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", aruid.pid); R_RETURN( - GetResourceManager()->GetNpad()->SetSupportedNpadIdType(aruid.pid, supported_npad_list)); + GetResourceManager()->GetNpad()->SetSupportedNpadIdType(system.Kernel(), aruid.pid, supported_npad_list)); } Result IHidServer::ActivateNpad(ClientAppletResourceUserId aruid) { @@ -702,14 +701,13 @@ Result IHidServer::AcquireNpadStyleSetUpdateEventHandle( LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}", npad_id, aruid.pid, unknown); - R_RETURN(GetResourceManager()->GetNpad()->AcquireNpadStyleSetUpdateEventHandle( - aruid.pid, out_event, npad_id)); + R_RETURN(GetResourceManager()->GetNpad()->AcquireNpadStyleSetUpdateEventHandle(system.Kernel(), aruid.pid, out_event, npad_id)); } Result IHidServer::DisconnectNpad(Core::HID::NpadIdType npad_id, ClientAppletResourceUserId aruid) { LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id, aruid.pid); - R_RETURN(GetResourceManager()->GetNpad()->DisconnectNpad(aruid.pid, npad_id)); + R_RETURN(GetResourceManager()->GetNpad()->DisconnectNpad(system.Kernel(), aruid.pid, npad_id)); } Result IHidServer::GetPlayerLedPattern(Out out_led_pattern, @@ -779,7 +777,7 @@ Result IHidServer::SetNpadJoyAssignmentModeSingleByDefault(Core::HID::NpadIdType LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id, aruid.pid); Core::HID::NpadIdType new_npad_id{}; - GetResourceManager()->GetNpad()->SetNpadMode( + GetResourceManager()->GetNpad()->SetNpadMode(system.Kernel(), aruid.pid, new_npad_id, npad_id, NpadJoyDeviceType::Left, NpadJoyAssignmentMode::Single); R_SUCCEED(); } @@ -791,7 +789,7 @@ Result IHidServer::SetNpadJoyAssignmentModeSingle(Core::HID::NpadIdType npad_id, npad_id, aruid.pid, npad_joy_device_type); Core::HID::NpadIdType new_npad_id{}; - GetResourceManager()->GetNpad()->SetNpadMode( + GetResourceManager()->GetNpad()->SetNpadMode(system.Kernel(), aruid.pid, new_npad_id, npad_id, npad_joy_device_type, NpadJoyAssignmentMode::Single); R_SUCCEED(); } @@ -801,7 +799,7 @@ Result IHidServer::SetNpadJoyAssignmentModeDual(Core::HID::NpadIdType npad_id, LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id, aruid.pid); Core::HID::NpadIdType new_npad_id{}; - GetResourceManager()->GetNpad()->SetNpadMode(aruid.pid, new_npad_id, npad_id, {}, + GetResourceManager()->GetNpad()->SetNpadMode(system.Kernel(), aruid.pid, new_npad_id, npad_id, {}, NpadJoyAssignmentMode::Dual); R_SUCCEED(); } @@ -813,7 +811,7 @@ Result IHidServer::MergeSingleJoyAsDualJoy(Core::HID::NpadIdType npad_id_1, npad_id_1, npad_id_2, aruid.pid); R_RETURN( - GetResourceManager()->GetNpad()->MergeSingleJoyAsDualJoy(aruid.pid, npad_id_1, npad_id_2)); + GetResourceManager()->GetNpad()->MergeSingleJoyAsDualJoy(system.Kernel(), aruid.pid, npad_id_1, npad_id_2)); } Result IHidServer::StartLrAssignmentMode(ClientAppletResourceUserId aruid) { @@ -840,67 +838,40 @@ Result IHidServer::SetNpadHandheldActivationMode(ClientAppletResourceUserId arui ASSERT_MSG(false, "Activation mode should be always None, Single or Dual"); R_SUCCEED(); } - - R_RETURN( - GetResourceManager()->GetNpad()->SetNpadHandheldActivationMode(aruid.pid, activation_mode)); + R_RETURN(GetResourceManager()->GetNpad()->SetNpadHandheldActivationMode(system.Kernel(), aruid.pid, activation_mode)); } Result IHidServer::GetNpadHandheldActivationMode( Out out_activation_mode, ClientAppletResourceUserId aruid) { LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", aruid.pid); - - R_RETURN(GetResourceManager()->GetNpad()->GetNpadHandheldActivationMode(aruid.pid, - *out_activation_mode)); + R_RETURN(GetResourceManager()->GetNpad()->GetNpadHandheldActivationMode(system.Kernel(), aruid.pid, *out_activation_mode)); } -Result IHidServer::SwapNpadAssignment(Core::HID::NpadIdType npad_id_1, - Core::HID::NpadIdType npad_id_2, - ClientAppletResourceUserId aruid) { - LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}", - npad_id_1, npad_id_2, aruid.pid); - - R_RETURN(GetResourceManager()->GetNpad()->SwapNpadAssignment(aruid.pid, npad_id_1, npad_id_2)) +Result IHidServer::SwapNpadAssignment(Core::HID::NpadIdType npad_id_1, Core::HID::NpadIdType npad_id_2, ClientAppletResourceUserId aruid) { + LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}", npad_id_1, npad_id_2, aruid.pid); + R_RETURN(GetResourceManager()->GetNpad()->SwapNpadAssignment(system.Kernel(), aruid.pid, npad_id_1, npad_id_2)) } -Result IHidServer::IsUnintendedHomeButtonInputProtectionEnabled(Out out_is_enabled, - Core::HID::NpadIdType npad_id, - ClientAppletResourceUserId aruid) { +Result IHidServer::IsUnintendedHomeButtonInputProtectionEnabled(Out out_is_enabled, Core::HID::NpadIdType npad_id, ClientAppletResourceUserId aruid) { LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id, aruid.pid); - R_UNLESS(IsNpadIdValid(npad_id), ResultInvalidNpadId); - R_RETURN(GetResourceManager()->GetNpad()->IsUnintendedHomeButtonInputProtectionEnabled( - *out_is_enabled, aruid.pid, npad_id)); + R_RETURN(GetResourceManager()->GetNpad()->IsUnintendedHomeButtonInputProtectionEnabled(*out_is_enabled, aruid.pid, npad_id)); } -Result IHidServer::EnableUnintendedHomeButtonInputProtection(bool is_enabled, - Core::HID::NpadIdType npad_id, - ClientAppletResourceUserId aruid) { - LOG_DEBUG(Service_HID, "called, is_enabled={}, npad_id={}, applet_resource_user_id={}", - is_enabled, npad_id, aruid.pid); - +Result IHidServer::EnableUnintendedHomeButtonInputProtection(bool is_enabled, Core::HID::NpadIdType npad_id, ClientAppletResourceUserId aruid) { + LOG_DEBUG(Service_HID, "called, is_enabled={}, npad_id={}, applet_resource_user_id={}",is_enabled, npad_id, aruid.pid); R_UNLESS(IsNpadIdValid(npad_id), ResultInvalidNpadId); - R_RETURN(GetResourceManager()->GetNpad()->EnableUnintendedHomeButtonInputProtection( - aruid.pid, npad_id, is_enabled)); + R_RETURN(GetResourceManager()->GetNpad()->EnableUnintendedHomeButtonInputProtection(aruid.pid, npad_id, is_enabled)); } -Result IHidServer::SetNpadJoyAssignmentModeSingleWithDestination( - Out out_is_reassigned, Out out_new_npad_id, - Core::HID::NpadIdType npad_id, ClientAppletResourceUserId aruid, - NpadJoyDeviceType npad_joy_device_type) { - LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}", - npad_id, aruid.pid, npad_joy_device_type); - - *out_is_reassigned = GetResourceManager()->GetNpad()->SetNpadMode( - aruid.pid, *out_new_npad_id, npad_id, npad_joy_device_type, NpadJoyAssignmentMode::Single); - +Result IHidServer::SetNpadJoyAssignmentModeSingleWithDestination(Out out_is_reassigned, Out out_new_npad_id, Core::HID::NpadIdType npad_id, ClientAppletResourceUserId aruid, NpadJoyDeviceType npad_joy_device_type) { + LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}", npad_id, aruid.pid, npad_joy_device_type); + *out_is_reassigned = GetResourceManager()->GetNpad()->SetNpadMode(system.Kernel(), aruid.pid, *out_new_npad_id, npad_id, npad_joy_device_type, NpadJoyAssignmentMode::Single); R_SUCCEED(); } -Result IHidServer::SetNpadAnalogStickUseCenterClamp(bool use_center_clamp, - ClientAppletResourceUserId aruid) { - LOG_INFO(Service_HID, "called, use_center_clamp={}, applet_resource_user_id={}", - use_center_clamp, aruid.pid); - +Result IHidServer::SetNpadAnalogStickUseCenterClamp(bool use_center_clamp, ClientAppletResourceUserId aruid) { + LOG_INFO(Service_HID, "called, use_center_clamp={}, applet_resource_user_id={}", use_center_clamp, aruid.pid); GetResourceManager()->GetNpad()->SetNpadAnalogStickUseCenterClamp(aruid.pid, use_center_clamp); R_SUCCEED(); } @@ -1179,7 +1150,7 @@ Result IHidServer::InitializeSevenSixAxisSensor(ClientAppletResourceUserId aruid InCopyHandle t_mem_1, InCopyHandle t_mem_2) { LOG_WARNING(Service_HID, - "called, t_mem_1_size=0x{:08X}, t_mem_2_size=0x{:08X}, " + "called, t_mem_1_size={:#08x}, t_mem_2_size={:#08x}, " "applet_resource_user_id={}", t_mem_1_size, t_mem_2_size, aruid.pid); @@ -1193,7 +1164,7 @@ Result IHidServer::InitializeSevenSixAxisSensor(ClientAppletResourceUserId aruid GetResourceManager()->GetConsoleSixAxis()->Activate(); GetResourceManager()->GetSevenSixAxis()->Activate(); - GetResourceManager()->GetSevenSixAxis()->SetTransferMemoryAddress(t_mem_1->GetSourceAddress()); + GetResourceManager()->GetSevenSixAxis()->SetTransferMemoryAddress(t_mem_1->GetSourceAddress(), t_mem_1->GetOwner()); R_SUCCEED(); } @@ -1500,6 +1471,12 @@ Result IHidServer::SetTouchScreenResolution(u32 width, u32 height, R_SUCCEED(); } +Result IHidServer::SetMouseLibraryVersion(ClientAppletResourceUserId aruid) { + LOG_INFO(Service_HID, "(STUBBED) called, applet_resource_user_id={}", aruid.pid); + + R_SUCCEED(); +} + std::shared_ptr IHidServer::GetResourceManager() { resource_manager->Initialize(); return resource_manager; diff --git a/src/core/hle/service/hid/hid_server.h b/src/core/hle/service/hid/hid_server.h index 2a2d722937..57a1a04b9c 100644 --- a/src/core/hle/service/hid/hid_server.h +++ b/src/core/hle/service/hid/hid_server.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -263,6 +263,7 @@ private: Result IsFirmwareUpdateNeededForNotification(Out out_is_firmware_update_needed, s32 unknown, ClientAppletResourceUserId aruid); Result SetTouchScreenResolution(u32 width, u32 height, ClientAppletResourceUserId aruid); + Result SetMouseLibraryVersion(ClientAppletResourceUserId aruid); std::shared_ptr resource_manager; std::shared_ptr firmware_settings; diff --git a/src/core/hle/service/hid/hid_system_server.cpp b/src/core/hle/service/hid/hid_system_server.cpp index 1de5dd6f28..e544adf03c 100644 --- a/src/core/hle/service/hid/hid_system_server.cpp +++ b/src/core/hle/service/hid/hid_system_server.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -38,10 +38,30 @@ IHidSystemServer::IHidSystemServer(Core::System& system_, std::shared_ptrGetNpad()->ApplyNpadSystemCommonPolicy(applet_resource_user_id); + GetResourceManager()->GetNpad()->ApplyNpadSystemCommonPolicy(system.Kernel(), applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -328,7 +348,7 @@ void IHidSystemServer::ApplyNpadSystemCommonPolicyFull(HLERequestContext& ctx) { LOG_INFO(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - GetResourceManager()->GetNpad()->ApplyNpadSystemCommonPolicyFull(applet_resource_user_id); + GetResourceManager()->GetNpad()->ApplyNpadSystemCommonPolicyFull(system.Kernel(), applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -358,9 +378,8 @@ void IHidSystemServer::GetMaskedSupportedNpadStyleSet(HLERequestContext& ctx) { LOG_INFO(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); Core::HID::NpadStyleSet supported_styleset{}; - const auto& npad = GetResourceManager()->GetNpad(); - const Result result = - npad->GetMaskedSupportedNpadStyleSet(applet_resource_user_id, supported_styleset); + const auto npad = GetResourceManager()->GetNpad(); + const Result result = npad->GetMaskedSupportedNpadStyleSet(system.Kernel(), applet_resource_user_id, supported_styleset); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(result); @@ -373,9 +392,8 @@ void IHidSystemServer::SetSupportedNpadStyleSetAll(HLERequestContext& ctx) { LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - const auto& npad = GetResourceManager()->GetNpad(); - const auto result = - npad->SetSupportedNpadStyleSet(applet_resource_user_id, Core::HID::NpadStyleSet::All); + const auto npad = GetResourceManager()->GetNpad(); + const auto result = npad->SetSupportedNpadStyleSet(system.Kernel(), applet_resource_user_id, Core::HID::NpadStyleSet::All); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); @@ -728,7 +746,7 @@ void IHidSystemServer::AcquireConnectionTriggerTimeoutEvent(HLERequestContext& c IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(acquire_device_registered_event->GetReadableEvent()); + rb.PushCopyObjects(ctx, acquire_device_registered_event->GetReadableEvent()); } void IHidSystemServer::AcquireDeviceRegisteredEventForControllerSupport(HLERequestContext& ctx) { @@ -736,11 +754,11 @@ void IHidSystemServer::AcquireDeviceRegisteredEventForControllerSupport(HLEReque IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(acquire_device_registered_event->GetReadableEvent()); + rb.PushCopyObjects(ctx, acquire_device_registered_event->GetReadableEvent()); } void IHidSystemServer::GetRegisteredDevices(HLERequestContext& ctx) { - LOG_WARNING(Service_HID, "(STUBBED) called"); + LOG_WARNING(Service_HID, "(STUBBED) called, command={}", ctx.GetCommand()); //548 or 551 struct RegisterData { std::array data; @@ -761,7 +779,7 @@ void IHidSystemServer::AcquireUniquePadConnectionEventHandle(HLERequestContext& LOG_WARNING(Service_HID, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.PushCopyObjects(unique_pad_connection_event->GetReadableEvent()); + rb.PushCopyObjects(ctx, unique_pad_connection_event->GetReadableEvent()); rb.Push(ResultSuccess); } @@ -778,7 +796,7 @@ void IHidSystemServer::AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestConte IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(joy_detach_event->GetReadableEvent()); + rb.PushCopyObjects(ctx, joy_detach_event->GetReadableEvent()); } void IHidSystemServer::IsUsbFullKeyControllerEnabled(HLERequestContext& ctx) { diff --git a/src/core/hle/service/hid/hid_system_server.h b/src/core/hle/service/hid/hid_system_server.h index 1a4f244d72..150b697305 100644 --- a/src/core/hle/service/hid/hid_system_server.h +++ b/src/core/hle/service/hid/hid_system_server.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/hid/hidbus.cpp b/src/core/hle/service/hid/hidbus.cpp index ebc66932ab..70bb5437e3 100644 --- a/src/core/hle/service/hid/hidbus.cpp +++ b/src/core/hle/service/hid/hidbus.cpp @@ -71,78 +71,58 @@ Hidbus::~Hidbus() { void Hidbus::UpdateHidbus(std::chrono::nanoseconds ns_late) { if (is_hidbus_enabled) { for (std::size_t i = 0; i < devices.size(); ++i) { - if (!devices[i].is_device_initialized) { - continue; + if (devices[i].is_device_initialized) { + auto& device = devices[i].device; + device->OnUpdate(); + auto& cur_entry = hidbus_status.entries[devices[i].handle.internal_index]; + cur_entry.is_polling_mode = device->IsPollingMode(); + cur_entry.polling_mode = device->GetPollingMode(); + cur_entry.is_enabled = device->IsEnabled(); + u8* shared_memory = system.Kernel().GetHidBusSharedMem().GetPointer(); + std::memcpy(shared_memory + (i * sizeof(HidbusStatusManagerEntry)), &hidbus_status, sizeof(HidbusStatusManagerEntry)); } - auto& device = devices[i].device; - device->OnUpdate(); - auto& cur_entry = hidbus_status.entries[devices[i].handle.internal_index]; - cur_entry.is_polling_mode = device->IsPollingMode(); - cur_entry.polling_mode = device->GetPollingMode(); - cur_entry.is_enabled = device->IsEnabled(); - - u8* shared_memory = system.Kernel().GetHidBusSharedMem().GetPointer(); - std::memcpy(shared_memory + (i * sizeof(HidbusStatusManagerEntry)), &hidbus_status, - sizeof(HidbusStatusManagerEntry)); } } } std::optional Hidbus::GetDeviceIndexFromHandle(BusHandle handle) const { - for (std::size_t i = 0; i < devices.size(); ++i) { - const auto& device_handle = devices[i].handle; - if (handle.abstracted_pad_id == device_handle.abstracted_pad_id && - handle.internal_index == device_handle.internal_index && - handle.player_number == device_handle.player_number && - handle.bus_type_id == device_handle.bus_type_id && - handle.is_valid == device_handle.is_valid) { - return i; - } - } - return std::nullopt; + auto const it = std::ranges::find_if(devices, [&handle](auto const& e) { + return handle.abstracted_pad_id == e.handle.abstracted_pad_id + && handle.internal_index == e.handle.internal_index + && handle.player_number == e.handle.player_number + && handle.bus_type_id == e.handle.bus_type_id + && handle.is_valid == e.handle.is_valid; + }); + return it != devices.end() + ? std::optional{std::distance(devices.begin(), it)} + : std::nullopt; } -Result Hidbus::GetBusHandle(Out out_is_valid, Out out_bus_handle, - Core::HID::NpadIdType npad_id, BusType bus_type, - AppletResourceUserId aruid) { - LOG_INFO(Service_HID, "called, npad_id={}, bus_type={}, applet_resource_user_id={}", npad_id, - bus_type, aruid.pid); - - bool is_handle_found = 0; - std::size_t handle_index = 0; - - for (std::size_t i = 0; i < devices.size(); i++) { - const auto& handle = devices[i].handle; - if (!handle.is_valid) { - continue; - } - if (handle.player_number.As() == npad_id && - handle.bus_type_id == static_cast(bus_type)) { - is_handle_found = true; - handle_index = i; - break; - } - } - +Result Hidbus::GetBusHandle(Out out_is_valid, Out out_bus_handle, Core::HID::NpadIdType npad_id, BusType bus_type, AppletResourceUserId aruid) { + LOG_INFO(Service_HID, "called, npad_id={}, bus_type={}, applet_resource_user_id={}", npad_id, bus_type, aruid.pid); + *out_is_valid = false; + *out_bus_handle = devices[0].handle; //TODO: does it give 0th? + if (auto const it = std::ranges::find_if(devices, [npad_id, bus_type](auto const& e) { + return e.handle.is_valid && e.handle.player_number == u64(npad_id) && e.handle.bus_type_id == u8(bus_type); + }); it != devices.end()) { + *out_bus_handle = devices[std::distance(devices.begin(), it)].handle; + *out_is_valid = true; // Handle not found. Create a new one - if (!is_handle_found) { - for (std::size_t i = 0; i < devices.size(); i++) { - if (devices[i].handle.is_valid) { - continue; - } - devices[i].handle.raw = 0; - devices[i].handle.abstracted_pad_id.Assign(i); - devices[i].handle.internal_index.Assign(i); - devices[i].handle.player_number.Assign(static_cast(npad_id)); - devices[i].handle.bus_type_id.Assign(static_cast(bus_type)); - devices[i].handle.is_valid.Assign(true); - handle_index = i; - break; - } + } else if (auto const free_it = std::ranges::find_if(devices, [](auto const& e) { + return !e.handle.is_valid; + }); free_it != devices.end()) { + auto const i = std::distance(devices.begin(), free_it); + devices[i].handle.raw = 0; + devices[i].handle.abstracted_pad_id.Assign(i); + devices[i].handle.internal_index.Assign(i); + devices[i].handle.player_number.Assign(u8(npad_id)); + devices[i].handle.bus_type_id.Assign(u8(bus_type)); + devices[i].handle.is_valid.Assign(true); + *out_bus_handle = devices[i].handle; + *out_is_valid = true; + } else { + LOG_ERROR(Service_HID, "no free or matching handle found"); } - - *out_is_valid = true; - *out_bus_handle = devices[handle_index].handle; R_SUCCEED(); } @@ -332,7 +312,7 @@ Result Hidbus::EnableJoyPollingReceiveMode(u32 t_mem_size, JoyPollingMode pollin auto& device = devices[device_index.value()].device; device->SetPollingMode(polling_mode); - device->SetTransferMemoryAddress(t_mem->GetSourceAddress()); + device->SetTransferMemoryAddress(t_mem->GetSourceAddress(), t_mem->GetOwner()); R_SUCCEED(); } diff --git a/src/core/hle/service/hid/hidbus.h b/src/core/hle/service/hid/hidbus.h index af4d956673..19dd894ed3 100644 --- a/src/core/hle/service/hid/hidbus.h +++ b/src/core/hle/service/hid/hidbus.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp index 7d7368ff9c..3d87e6788d 100644 --- a/src/core/hle/service/hid/irs.cpp +++ b/src/core/hle/service/hid/irs.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -147,7 +150,7 @@ Result IRS::RunImageTransferProcessor( MakeProcessorWithCoreContext(camera_handle, device); auto& image_transfer_processor = GetProcessor(camera_handle); image_transfer_processor.SetConfig(processor_config); - image_transfer_processor.SetTransferMemoryAddress(t_mem->GetSourceAddress()); + image_transfer_processor.SetTransferMemoryAddress(t_mem->GetSourceAddress(), t_mem->GetOwner()); npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, Common::Input::PollingMode::IR); @@ -295,7 +298,8 @@ Result IRS::RunImageTransferExProcessor( MakeProcessorWithCoreContext(camera_handle, device); auto& image_transfer_processor = GetProcessor(camera_handle); image_transfer_processor.SetConfig(processor_config); - image_transfer_processor.SetTransferMemoryAddress(t_mem->GetSourceAddress()); + image_transfer_processor.SetTransferMemoryAddress(t_mem->GetSourceAddress(), + t_mem->GetOwner()); npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, Common::Input::PollingMode::IR); diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h index 58dfee6c36..2ca34b9106 100644 --- a/src/core/hle/service/hid/irs.h +++ b/src/core/hle/service/hid/irs.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/hid/xcd.cpp b/src/core/hle/service/hid/xcd.cpp index 75cc266ea9..2e41cbd3da 100644 --- a/src/core/hle/service/hid/xcd.cpp +++ b/src/core/hle/service/hid/xcd.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/hid/xcd.h b/src/core/hle/service/hid/xcd.h index fe0b91b919..9000110a9a 100644 --- a/src/core/hle/service/hid/xcd.h +++ b/src/core/hle/service/hid/xcd.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/hle_ipc.cpp b/src/core/hle/service/hle_ipc.cpp index 8569d2bad8..9bb705171f 100644 --- a/src/core/hle/service/hle_ipc.cpp +++ b/src/core/hle/service/hle_ipc.cpp @@ -113,7 +113,7 @@ Result SessionRequestManager::HandleDomainSyncRequest(Kernel::KServerSession* se } case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: { - LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id); + LOG_DEBUG(IPC, "CloseVirtualHandle, object_id={:#08x}", object_id); this->CloseDomainHandler(object_id - 1); @@ -128,10 +128,12 @@ Result SessionRequestManager::HandleDomainSyncRequest(Kernel::KServerSession* se return ResultSuccess; } -HLERequestContext::HLERequestContext(Kernel::KernelCore& kernel_, Core::Memory::Memory& memory_, - Kernel::KServerSession* server_session_, - Kernel::KThread* thread_) - : server_session(server_session_), thread(thread_), kernel{kernel_}, memory{memory_} { +HLERequestContext::HLERequestContext(Kernel::KernelCore& kernel_, Core::Memory::Memory& memory_, Kernel::KServerSession* server_session_, Kernel::KThread* thread_) + : server_session(server_session_) + , thread(thread_) + , kernel{kernel_} + , memory{memory_} +{ cmd_buf[0] = 0; } @@ -155,9 +157,6 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) { } if (incoming) { // Populate the object lists with the data in the IPC request. - incoming_copy_handles.reserve(handle_descriptor_header->num_handles_to_copy); - incoming_move_handles.reserve(handle_descriptor_header->num_handles_to_move); - for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) { incoming_copy_handles.push_back(rp.Pop()); } @@ -172,11 +171,6 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) { } } - buffer_x_descriptors.reserve(command_header->num_buf_x_descriptors); - buffer_a_descriptors.reserve(command_header->num_buf_a_descriptors); - buffer_b_descriptors.reserve(command_header->num_buf_b_descriptors); - buffer_w_descriptors.reserve(command_header->num_buf_w_descriptors); - for (u32 i = 0; i < command_header->num_buf_x_descriptors; ++i) { buffer_x_descriptors.push_back(rp.PopRaw()); } @@ -281,17 +275,17 @@ Result HLERequestContext::WriteToOutgoingCommandBuffer() { for (auto& object : outgoing_copy_objects) { Handle handle{}; if (object) { - R_TRY(handle_table.Add(&handle, object)); + R_TRY(handle_table.Add(kernel, &handle, object)); } cmd_buf[current_offset++] = handle; } for (auto& object : outgoing_move_objects) { Handle handle{}; if (object) { - R_TRY(handle_table.Add(&handle, object)); + R_TRY(handle_table.Add(kernel, &handle, object)); // Close our reference to the object, as it is being moved to the caller. - object->Close(); + object->Close(kernel); } cmd_buf[current_offset++] = handle; } @@ -509,11 +503,10 @@ bool HLERequestContext::CanWriteBuffer(std::size_t buffer_index) const { } void HLERequestContext::AddMoveInterface(SessionRequestHandlerPtr s) { - ASSERT(Kernel::GetCurrentProcess(kernel).GetResourceLimit()->Reserve( - Kernel::LimitableResource::SessionCountMax, 1)); + ASSERT(Kernel::GetCurrentProcess(kernel).GetResourceLimit()->Reserve(kernel, Kernel::LimitableResource::SessionCountMax, 1)); auto* session = Kernel::KSession::Create(kernel); - session->Initialize(nullptr, 0); + session->Initialize(kernel, nullptr, 0); Kernel::KSession::Register(kernel, session); auto& server = manager.lock()->GetServerManager(); diff --git a/src/core/hle/service/hle_ipc.h b/src/core/hle/service/hle_ipc.h index c2e0e5e8c4..4dee40224a 100644 --- a/src/core/hle/service/hle_ipc.h +++ b/src/core/hle/service/hle_ipc.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -11,6 +14,7 @@ #include #include #include +#include #include "common/assert.h" #include "common/common_types.h" @@ -27,7 +31,7 @@ class Memory; } namespace IPC { -class ResponseBuilder; +struct ResponseBuilder; } namespace Service { @@ -181,8 +185,7 @@ private: */ class HLERequestContext { public: - explicit HLERequestContext(Kernel::KernelCore& kernel, Core::Memory::Memory& memory, - Kernel::KServerSession* session, Kernel::KThread* thread); + explicit HLERequestContext(Kernel::KernelCore& kernel, Core::Memory::Memory& memory, Kernel::KServerSession* session, Kernel::KThread* thread); ~HLERequestContext(); /// Returns a pointer to the IPC command buffer for this request. @@ -233,19 +236,19 @@ public: return data_payload_offset; } - [[nodiscard]] const std::vector& BufferDescriptorX() const { + [[nodiscard]] const boost::container::static_vector& BufferDescriptorX() const { return buffer_x_descriptors; } - [[nodiscard]] const std::vector& BufferDescriptorA() const { + [[nodiscard]] const boost::container::static_vector& BufferDescriptorA() const { return buffer_a_descriptors; } - [[nodiscard]] const std::vector& BufferDescriptorB() const { + [[nodiscard]] const boost::container::static_vector& BufferDescriptorB() const { return buffer_b_descriptors; } - [[nodiscard]] const std::vector& BufferDescriptorC() const { + [[nodiscard]] const boost::container::static_vector& BufferDescriptorC() const { return buffer_c_descriptors; } @@ -327,12 +330,12 @@ public: /// Helper function to test whether the output buffer at buffer_index can be written [[nodiscard]] bool CanWriteBuffer(std::size_t buffer_index = 0) const; - [[nodiscard]] Handle GetCopyHandle(std::size_t index) const { - return incoming_copy_handles.at(index); + [[nodiscard]] Handle GetCopyHandle(std::size_t index) const noexcept { + return index >= incoming_copy_handles.size() ? 0 : incoming_copy_handles[index]; } - [[nodiscard]] Handle GetMoveHandle(std::size_t index) const { - return incoming_move_handles.at(index); + [[nodiscard]] Handle GetMoveHandle(std::size_t index) const noexcept { + return index >= incoming_move_handles.size() ? 0 : incoming_move_handles[index]; } void AddMoveObject(Kernel::KAutoObject* object) { @@ -370,11 +373,10 @@ public: template Kernel::KScopedAutoObject GetObjectFromHandle(u32 handle) { - auto obj = client_handle_table->GetObjectForIpc(handle, thread); - if (obj.IsNotNull()) { - return obj->DynamicCast(); - } - return nullptr; + auto obj = client_handle_table->GetObjectForIpc(kernel, handle, thread); + if (obj.IsNotNull()) + return {kernel, obj->DynamicCast()}; + return {kernel, nullptr}; } [[nodiscard]] std::shared_ptr GetManager() const { @@ -390,7 +392,7 @@ public: } private: - friend class IPC::ResponseBuilder; + friend struct IPC::ResponseBuilder; void ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming); @@ -399,38 +401,35 @@ private: Kernel::KHandleTable* client_handle_table{}; Kernel::KThread* thread{}; - std::vector incoming_move_handles; - std::vector incoming_copy_handles; + boost::container::static_vector buffer_x_descriptors; + boost::container::static_vector buffer_a_descriptors; + boost::container::static_vector buffer_b_descriptors; + boost::container::static_vector buffer_w_descriptors; + boost::container::static_vector buffer_c_descriptors; + boost::container::static_vector incoming_move_handles; + boost::container::static_vector incoming_copy_handles; + boost::container::static_vector outgoing_move_objects; + boost::container::static_vector outgoing_copy_objects; + boost::container::static_vector outgoing_domain_objects; - std::vector outgoing_move_objects; - std::vector outgoing_copy_objects; - std::vector outgoing_domain_objects; + mutable std::array, 3> read_buffer_data_a{}; + mutable std::array, 3> read_buffer_data_x{}; std::optional command_header; std::optional handle_descriptor_header; std::optional data_payload_header; std::optional domain_message_header; - std::vector buffer_x_descriptors; - std::vector buffer_a_descriptors; - std::vector buffer_b_descriptors; - std::vector buffer_w_descriptors; - std::vector buffer_c_descriptors; + std::weak_ptr manager{}; + Kernel::KernelCore& kernel; + Core::Memory::Memory& memory; - u32_le command{}; u64 pid{}; + u32_le command{}; u32 write_size{}; u32 data_payload_offset{}; u32 handles_offset{}; u32 domain_offset{}; - - std::weak_ptr manager{}; - bool is_deferred{false}; - - Kernel::KernelCore& kernel; - Core::Memory::Memory& memory; - - mutable std::array, 3> read_buffer_data_a{}; - mutable std::array, 3> read_buffer_data_x{}; + bool is_deferred = false; }; } // namespace Service diff --git a/src/core/hle/service/i2c/i2c.cpp b/src/core/hle/service/i2c/i2c.cpp new file mode 100644 index 0000000000..0b242768c2 --- /dev/null +++ b/src/core/hle/service/i2c/i2c.cpp @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 + +#include "core/core.h" +#include "core/hle/result.h" +#include "core/hle/service/i2c/i2c.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/cmif_serialization.h" +#include "core/hle/service/cmif_types.h" +#include "core/hle/service/service.h" +#include "core/hle/service/server_manager.h" + +namespace Service::I2C { + +class I2CSession final : public ServiceFramework { +public: + explicit I2CSession(Core::System& system_) + : ServiceFramework{system_, "I2CSession"} + { + static const FunctionInfo functions[] = { + {0, nullptr, "SendOld"}, + {1, nullptr, "ReceiveOld"}, + {2, nullptr, "ExecuteCommandListOld"}, + {10, C<&I2CSession::Send>, "Send"}, + {11, nullptr, "Receive"}, + {12, nullptr, "ExecuteCommandList"}, + {13, nullptr, "SetRetryPolicy"}, + }; + RegisterHandlers(functions); + } + ~I2CSession() override = default; + Result Send(InBuffer in_data, u32 transaction_option) { + LOG_WARNING(Service, "(stubbed) topt={}", transaction_option); + R_THROW(ResultUnknown); + } +}; + +enum class I2CDevice : u32 { + ClassicController, + Ftm3bd56, +}; + +class I2C final : public ServiceFramework { +public: + explicit I2C(Core::System& system_) + : ServiceFramework{system_, "i2c"} + { + static const FunctionInfo functions[] = { + {0, C<&I2C::OpenSessionForDev>, "OpenSessionForDev"}, + {1, C<&I2C::OpenSession>, "OpenSession"}, + {2, C<&I2C::HasDevice>, "HasDevice"}, + {3, C<&I2C::HasDeviceForDev>, "HasDeviceForDev"}, + {4, C<&I2C::OpenSession2>, "OpenSession2"}, + }; + RegisterHandlers(functions); + } + ~I2C() override = default; + Result OpenSessionForDev(OutInterface out_session, s32 bus_idx, u32 slave_address, u32 addressing_mode, u32 speed_mode) { + LOG_DEBUG(Service, "(stubbed)"); + *out_session = std::make_shared(system); + R_SUCCEED(); + } + Result OpenSession(OutInterface out_session, I2CDevice device) { + LOG_DEBUG(Service, "(stubbed)"); + *out_session = std::make_shared(system); + R_SUCCEED(); + } + Result HasDevice(Out out_has_device, I2CDevice device) { + LOG_DEBUG(Service, "(stubbed)"); + *out_has_device = false; + R_SUCCEED(); + } + Result HasDeviceForDev(Out out_has_device, I2CDevice device) { + LOG_DEBUG(Service, "(stubbed)"); + *out_has_device = false; + R_SUCCEED(); + } + Result OpenSession2(OutInterface out_session, u32 device_code) { + LOG_DEBUG(Service, "(stubbed) device_code={}", device_code); + *out_session = std::make_shared(system); + R_SUCCEED(); + } +}; + +void LoopProcess(Core::System& system) { + auto server_manager = std::make_unique(system); + server_manager->RegisterNamedService("i2c", std::make_shared(system)); + ServerManager::RunServer(std::move(server_manager)); +} + +} // namespace Service::I2C diff --git a/src/core/hle/service/i2c/i2c.h b/src/core/hle/service/i2c/i2c.h new file mode 100644 index 0000000000..c26df8e86f --- /dev/null +++ b/src/core/hle/service/i2c/i2c.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 + +#pragma once + +namespace Core { +class System; +} + +namespace Service::I2C { +void LoopProcess(Core::System& system); +} // namespace Service::I2C diff --git a/src/core/hle/service/ipc_helpers.h b/src/core/hle/service/ipc_helpers.h index 8aee17db8d..a266b1a05c 100644 --- a/src/core/hle/service/ipc_helpers.h +++ b/src/core/hle/service/ipc_helpers.h @@ -22,47 +22,10 @@ namespace IPC { +constexpr Result ResultNotSupported{ErrorModule::HIPC, 1}; constexpr Result ResultSessionClosed{ErrorModule::HIPC, 301}; -class RequestHelperBase { -protected: - Service::HLERequestContext* context = nullptr; - u32* cmdbuf; - u32 index = 0; - -public: - explicit RequestHelperBase(u32* command_buffer) : cmdbuf(command_buffer) {} - - explicit RequestHelperBase(Service::HLERequestContext& ctx) - : context(&ctx), cmdbuf(ctx.CommandBuffer()) {} - - void Skip(u32 size_in_words, bool set_to_null) { - if (set_to_null) { - memset(cmdbuf + index, 0, size_in_words * sizeof(u32)); - } - index += size_in_words; - } - - /** - * Aligns the current position forward to a 16-byte boundary, padding with zeros. - */ - void AlignWithPadding() { - if (index & 3) { - Skip(static_cast(4 - (index & 3)), true); - } - } - - u32 GetCurrentOffset() const { - return index; - } - - void SetCurrentOffset(u32 offset) { - index = offset; - } -}; - -class ResponseBuilder : public RequestHelperBase { -public: +struct ResponseBuilder { /// Flags used for customizing the behavior of ResponseBuilder enum class Flags : u32 { None = 0, @@ -71,14 +34,13 @@ public: AlwaysMoveHandles = 1, }; - explicit ResponseBuilder(Service::HLERequestContext& ctx, u32 normal_params_size_, - u32 num_handles_to_copy_ = 0, u32 num_objects_to_move_ = 0, - Flags flags = Flags::None) - : RequestHelperBase(ctx), normal_params_size(normal_params_size_), - num_handles_to_copy(num_handles_to_copy_), - num_objects_to_move(num_objects_to_move_), kernel{ctx.kernel} { - - memset(cmdbuf, 0, sizeof(u32) * IPC::COMMAND_BUFFER_LENGTH); + inline explicit ResponseBuilder(Service::HLERequestContext& ctx, u32 normal_params_size_, u32 num_handles_to_copy_ = 0, u32 num_objects_to_move_ = 0, Flags flags = Flags::None) + : cmdbuf(ctx.CommandBuffer()) + , normal_params_size(normal_params_size_) + , num_handles_to_copy(num_handles_to_copy_) + , num_objects_to_move(num_objects_to_move_) + { + std::memset(cmdbuf, 0, sizeof(u32) * IPC::COMMAND_BUFFER_LENGTH); IPC::CommandHeader header{}; auto const mgr = ctx.GetManager().get(); @@ -117,9 +79,7 @@ public: handle_descriptor_header.num_handles_to_copy.Assign(num_handles_to_copy_); handle_descriptor_header.num_handles_to_move.Assign(num_handles_to_move); PushRaw(handle_descriptor_header); - ctx.handles_offset = index; - Skip(num_handles_to_copy + num_handles_to_move, true); } @@ -131,7 +91,6 @@ public: domain_header.num_objects = num_domain_objects; PushRaw(domain_header); } - IPC::DataPayloadHeader data_payload_header{}; data_payload_header.magic = Common::MakeMagic('S', 'F', 'C', 'O'); PushRaw(data_payload_header); @@ -141,35 +100,39 @@ public: ctx.data_payload_offset = index; ctx.write_size += index; - ctx.domain_offset = static_cast(index + raw_data_size / sizeof(u32)); + ctx.domain_offset = u32(index + raw_data_size / sizeof(u32)); } - template - void PushIpcInterface(std::shared_ptr iface) { - auto manager{context->GetManager()}; + inline void Skip(u32 size_in_words, bool set_to_null) { + if (set_to_null) std::memset(cmdbuf + index, 0, size_in_words * sizeof(u32)); + index += size_in_words; + } + /// @brief Aligns the current position forward to a 16-byte boundary, padding with zeros. + inline void AlignWithPadding() { if (index & 3) Skip(u32(4 - (index & 3)), true); } + inline u32 GetCurrentOffset() const { return index; } + inline void SetCurrentOffset(u32 offset) { index = offset; } + template inline void PushIpcInterface(Service::HLERequestContext& ctx, std::shared_ptr iface) { + auto manager = ctx.GetManager(); if (manager->IsDomain()) { - context->AddDomainObject(std::move(iface)); + ctx.AddDomainObject(std::move(iface)); } else { - ASSERT(Kernel::GetCurrentProcess(kernel).GetResourceLimit()->Reserve( - Kernel::LimitableResource::SessionCountMax, 1)); + ASSERT(Kernel::GetCurrentProcess(ctx.kernel).GetResourceLimit()->Reserve(ctx.kernel, Kernel::LimitableResource::SessionCountMax, 1)); - auto* session = Kernel::KSession::Create(kernel); - session->Initialize(nullptr, 0); - Kernel::KSession::Register(kernel, session); + auto* session = Kernel::KSession::Create(ctx.kernel); + session->Initialize(ctx.kernel, nullptr, 0); + Kernel::KSession::Register(ctx.kernel, session); - auto next_manager = std::make_shared( - kernel, manager->GetServerManager()); + auto next_manager = std::make_shared(ctx.kernel, manager->GetServerManager()); next_manager->SetSessionHandler(iface); manager->GetServerManager().RegisterSession(&session->GetServerSession(), next_manager); - context->AddMoveObject(&session->GetClientSession()); + ctx.AddMoveObject(&session->GetClientSession()); } } - template - void PushIpcInterface(Args&&... args) { - PushIpcInterface(std::make_shared(std::forward(args)...)); + template inline void PushIpcInterface(Service::HLERequestContext& ctx, Args&&... args) { + PushIpcInterface(ctx, std::make_shared(std::forward(args)...)); } void PushImpl(s8 value); @@ -185,59 +148,38 @@ public: void PushImpl(bool value); void PushImpl(Result value); - template - void Push(T value) { + template inline void Push(T value) { return PushImpl(value); } template void Push(const First& first_value, const Other&... other_values); - /** - * Helper function for pushing strongly-typed enumeration values. - * - * @tparam Enum The enumeration type to be pushed - * - * @param value The value to push. - * - * @note The underlying size of the enumeration type is the size of the - * data that gets pushed. e.g. "enum class SomeEnum : u16" will - * push a u16-sized amount of data. - */ - template - void PushEnum(Enum value) { + /// @brief Helper function for pushing strongly-typed enumeration values. + /// @tparam Enum The enumeration type to be pushed + /// @param value The value to push. + /// @note The underlying size of the enumeration type is the size of the data that gets pushed. + /// e.g. "enum class SomeEnum : u16" will push a u16-sized amount of data. + template inline void PushEnum(Enum value) { static_assert(std::is_enum_v, "T must be an enum type within a PushEnum call."); - static_assert(!std::is_convertible_v, - "enum type in PushEnum must be a strongly typed enum."); + static_assert(!std::is_convertible_v, "enum type in PushEnum must be a strongly typed enum."); Push(static_cast>(value)); } - /** - * @brief Copies the content of the given trivially copyable class to the buffer as a normal - * param - * @note: The input class must be correctly packed/padded to fit hardware layout. - */ - template - void PushRaw(const T& value); + /// @brief Copies the content of the given trivially copyable class to the buffer as a normal param + /// @note: The input class must be correctly packed/padded to fit hardware layout. + template void PushRaw(const T& value); + template void PushMoveObjects(Service::HLERequestContext& ctx, O*... pointers); + template void PushMoveObjects(Service::HLERequestContext& ctx, O&... pointers); + template void PushCopyObjects(Service::HLERequestContext& ctx, O*... pointers); + template void PushCopyObjects(Service::HLERequestContext& ctx, O&... pointers); - template - void PushMoveObjects(O*... pointers); - - template - void PushMoveObjects(O&... pointers); - - template - void PushCopyObjects(O*... pointers); - - template - void PushCopyObjects(O&... pointers); - -private: + u32* cmdbuf; + u32 index = 0; u32 normal_params_size{}; u32 num_handles_to_copy{}; u32 num_objects_to_move{}; ///< Domain objects or move handles, context dependent u32 data_payload_index{}; - Kernel::KernelCore& kernel; }; /// Push /// @@ -252,8 +194,7 @@ inline void ResponseBuilder::PushImpl(u32 value) { template void ResponseBuilder::PushRaw(const T& value) { - static_assert(std::is_trivially_copyable_v, - "It's undefined behavior to use memcpy with non-trivially copyable objects"); + static_assert(std::is_trivially_copyable_v, "It's undefined behavior to use memcpy with non-trivially copyable objects"); std::memcpy(cmdbuf + index, &value, sizeof(T)); index += (sizeof(T) + 3) / 4; // round up to word length } @@ -273,8 +214,8 @@ inline void ResponseBuilder::PushImpl(s16 value) { } inline void ResponseBuilder::PushImpl(s64 value) { - PushImpl(static_cast(value)); - PushImpl(static_cast(value >> 32)); + PushImpl(u32(value)); + PushImpl(u32(value >> 32)); } inline void ResponseBuilder::PushImpl(u8 value) { @@ -286,8 +227,8 @@ inline void ResponseBuilder::PushImpl(u16 value) { } inline void ResponseBuilder::PushImpl(u64 value) { - PushImpl(static_cast(value)); - PushImpl(static_cast(value >> 32)); + PushImpl(u32(value)); + PushImpl(u32(value >> 32)); } inline void ResponseBuilder::PushImpl(float value) { @@ -313,90 +254,88 @@ void ResponseBuilder::Push(const First& first_value, const Other&... other_value } template -inline void ResponseBuilder::PushCopyObjects(O*... pointers) { +inline void ResponseBuilder::PushCopyObjects(Service::HLERequestContext& ctx, O*... pointers) { auto objects = {pointers...}; for (auto& object : objects) { - context->AddCopyObject(object); + ctx.AddCopyObject(object); } } template -inline void ResponseBuilder::PushCopyObjects(O&... pointers) { +inline void ResponseBuilder::PushCopyObjects(Service::HLERequestContext& ctx, O&... pointers) { auto objects = {&pointers...}; for (auto& object : objects) { - context->AddCopyObject(object); + ctx.AddCopyObject(object); } } template -inline void ResponseBuilder::PushMoveObjects(O*... pointers) { +inline void ResponseBuilder::PushMoveObjects(Service::HLERequestContext& ctx, O*... pointers) { auto objects = {pointers...}; for (auto& object : objects) { - context->AddMoveObject(object); + ctx.AddMoveObject(object); } } template -inline void ResponseBuilder::PushMoveObjects(O&... pointers) { +inline void ResponseBuilder::PushMoveObjects(Service::HLERequestContext& ctx, O&... pointers) { auto objects = {&pointers...}; for (auto& object : objects) { - context->AddMoveObject(object); + ctx.AddMoveObject(object); } } -class RequestParser : public RequestHelperBase { -public: - explicit RequestParser(u32* command_buffer) : RequestHelperBase(command_buffer) {} - - explicit RequestParser(Service::HLERequestContext& ctx) : RequestHelperBase(ctx) { +struct RequestParser { + inline explicit RequestParser(u32* command_buffer) : cmdbuf(command_buffer) {} + inline explicit RequestParser(Service::HLERequestContext& ctx) + : cmdbuf(ctx.CommandBuffer()) + { // TIPC does not have data payload offset if (!ctx.IsTipc()) { ASSERT_MSG(ctx.GetDataPayloadOffset(), "context is incomplete"); Skip(ctx.GetDataPayloadOffset(), false); } - // Skip the u64 command id, it's already stored in the context static constexpr u32 CommandIdSize = 2; Skip(CommandIdSize, false); } - template - T Pop(); + inline void Skip(u32 size_in_words, bool set_to_null) { + if (set_to_null) std::memset(cmdbuf + index, 0, size_in_words * sizeof(u32)); + index += size_in_words; + } + /// @brief Aligns the current position forward to a 16-byte boundary, padding with zeros. + inline void AlignWithPadding() { if (index & 3) Skip(u32(4 - (index & 3)), true); } + inline u32 GetCurrentOffset() const { return index; } + inline void SetCurrentOffset(u32 offset) { index = offset; } - template - void Pop(T& value); - - template - void Pop(First& first_value, Other&... other_values); + template T Pop(); + template void Pop(T& value); + template void Pop(First& first_value, Other&... other_values); template T PopEnum() { static_assert(std::is_enum_v, "T must be an enum type within a PopEnum call."); - static_assert(!std::is_convertible_v, - "enum type in PopEnum must be a strongly typed enum."); - return static_cast(Pop>()); + static_assert(!std::is_convertible_v, "enum type in PopEnum must be a strongly typed enum."); + return T(Pop>()); } - /** - * @brief Reads the next normal parameters as a struct, by copying it - * @note: The output class must be correctly packed/padded to fit hardware layout. - */ - template - void PopRaw(T& value); + /// @brief Reads the next normal parameters as a struct, by copying it + /// @note: The output class must be correctly packed/padded to fit hardware layout. + template void PopRaw(T& value); - /** - * @brief Reads the next normal parameters as a struct, by copying it into a new value - * @note: The output class must be correctly packed/padded to fit hardware layout. - */ - template - T PopRaw(); + /// @brief Reads the next normal parameters as a struct, by copying it into a new value + /// @note: The output class must be correctly packed/padded to fit hardware layout. + template T PopRaw(); - template - std::weak_ptr PopIpcInterface() { - ASSERT(context->GetManager()->IsDomain()); - ASSERT(context->GetDomainMessageHeader().input_object_count > 0); - return context->GetDomainHandler(Pop() - 1); + template [[nodiscard]] std::weak_ptr PopIpcInterface(Service::HLERequestContext& ctx) { + ASSERT(ctx.GetManager()->IsDomain()); + ASSERT(ctx.GetDomainMessageHeader().input_object_count > 0); + return ctx.GetDomainHandler(Pop() - 1); } + + u32* cmdbuf; + u32 index = 0; }; /// Pop /// @@ -408,7 +347,7 @@ inline u32 RequestParser::Pop() { template <> inline s32 RequestParser::Pop() { - return static_cast(Pop()); + return s32(Pop()); } // Ignore the -Wclass-memaccess warning on memcpy for non-trivially default constructible objects. @@ -418,8 +357,7 @@ inline s32 RequestParser::Pop() { #endif template void RequestParser::PopRaw(T& value) { - static_assert(std::is_trivially_copyable_v, - "It's undefined behavior to use memcpy with non-trivially copyable objects"); + static_assert(std::is_trivially_copyable_v, "It's undefined behavior to use memcpy with non-trivially copyable objects"); std::memcpy(&value, cmdbuf + index, sizeof(T)); index += (sizeof(T) + 3) / 4; // round up to word length } diff --git a/src/core/hle/service/jit/jit.cpp b/src/core/hle/service/jit/jit.cpp index 4941a71a01..ec3a3a8dcb 100644 --- a/src/core/hle/service/jit/jit.cpp +++ b/src/core/hle/service/jit/jit.cpp @@ -1,6 +1,10 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 +#include "common/logging.h" #include "core/arm/debug.h" #include "core/arm/symbols.h" #include "core/core.h" @@ -26,12 +30,13 @@ static_assert(sizeof(Struct32) == 32, "Struct32 has wrong size"); class IJitEnvironment final : public ServiceFramework { public: - explicit IJitEnvironment(Core::System& system_, - Kernel::KScopedAutoObject process_, - CodeMemory&& user_rx_, CodeMemory&& user_ro_) - : ServiceFramework{system_, "IJitEnvironment"}, process{std::move(process_)}, - user_rx{std::move(user_rx_)}, user_ro{std::move(user_ro_)}, - context{system_.ApplicationMemory()} { + explicit IJitEnvironment(Core::System& system_, Kernel::KProcess* process_, CodeMemory&& user_rx_, CodeMemory&& user_ro_) + : ServiceFramework{system_, "IJitEnvironment"} + , process{kernel, process_} + , user_rx{std::move(user_rx_)} + , user_ro{std::move(user_ro_)} + , context{process_->GetMemory()} + { // clang-format off static const FunctionInfo functions[] = { @@ -54,6 +59,11 @@ public: configuration.sys_ro_memory = configuration.user_ro_memory; } + ~IJitEnvironment() { + user_rx.Finalize(system.Kernel()); + user_ro.Finalize(system.Kernel()); + } + Result GenerateCode(Out out_return_value, Out out_range0, Out out_range1, OutBuffer out_buffer, u32 data_size, u64 command, CodeRange range0, CodeRange range1, @@ -111,10 +121,7 @@ public: const VAddr input_ptr{context.AddHeap(in_data.data(), in_data.size())}; const VAddr output_ptr{context.AddHeap(out_data.data(), out_data.size())}; - const u64 wrapper_value{context.CallFunction(callbacks.Control, ret_ptr, configuration_ptr, - command, input_ptr, in_data.size(), output_ptr, - out_data.size())}; - + const u64 wrapper_value = context.CallFunction(callbacks.Control, ret_ptr, configuration_ptr, command, input_ptr, in_data.size(), output_ptr, out_data.size()); *out_return_value = context.GetHeap(ret_ptr); context.GetHeap(output_ptr, out_data.data(), out_data.size()); @@ -126,9 +133,7 @@ public: R_THROW(ResultUnknown); } - Result LoadPlugin(u64 tmem_size, InCopyHandle tmem, - InBuffer nrr, - InBuffer nro) { + Result LoadPlugin(u64 tmem_size, InCopyHandle tmem, InBuffer nrr, InBuffer nro) { if (!tmem) { LOG_ERROR(Service_JIT, "Invalid transfer memory handle!"); R_THROW(ResultUnknown); @@ -139,9 +144,10 @@ public: configuration.transfer_memory.size = tmem_size; // Gather up all the callbacks from the loaded plugin - auto symbols{Core::Symbols::GetSymbols(nro, true)}; - const auto GetSymbol{[&](const std::string& name) { return symbols[name].first; }}; - + auto symbols = Core::Symbols::GetSymbols(nro, true); + const auto GetSymbol = [&](const std::string& name) { + return symbols[name].first; + }; callbacks.rtld_fini = GetSymbol("_fini"); callbacks.rtld_init = GetSymbol("_init"); callbacks.Control = GetSymbol("nnjitpluginControl"); @@ -153,8 +159,7 @@ public: callbacks.OnPrepared = GetSymbol("nnjitpluginOnPrepared"); callbacks.Keeper = GetSymbol("nnjitpluginKeeper"); - if (callbacks.GetVersion == 0 || callbacks.Configure == 0 || callbacks.GenerateCode == 0 || - callbacks.OnPrepared == 0) { + if (callbacks.GetVersion == 0 || callbacks.Configure == 0 || callbacks.GenerateCode == 0 || callbacks.OnPrepared == 0 || callbacks.Control == 0) { LOG_ERROR(Service_JIT, "plugin does not implement all necessary functionality"); R_THROW(ResultUnknown); } @@ -164,12 +169,9 @@ public: R_THROW(ResultUnknown); } - context.MapProcessMemory(configuration.sys_ro_memory.offset, - configuration.sys_ro_memory.size); - context.MapProcessMemory(configuration.sys_rx_memory.offset, - configuration.sys_rx_memory.size); - context.MapProcessMemory(configuration.transfer_memory.offset, - configuration.transfer_memory.size); + context.MapProcessMemory(configuration.sys_ro_memory.offset, configuration.sys_ro_memory.size); + context.MapProcessMemory(configuration.sys_rx_memory.offset, configuration.sys_rx_memory.size); + context.MapProcessMemory(configuration.transfer_memory.offset, configuration.transfer_memory.size); // Run ELF constructors, if needed if (callbacks.rtld_init != 0) { @@ -179,7 +181,7 @@ public: // Function prototype: // u64 GetVersion(); const auto version{context.CallFunction(callbacks.GetVersion)}; - if (version != 1) { + if (version > 1) { LOG_ERROR(Service_JIT, "unknown plugin version {}", version); R_THROW(ResultUnknown); } @@ -285,14 +287,9 @@ private: } CodeMemory rx, ro; - - R_TRY(rx.Initialize(*process, *rx_mem, rx_size, Kernel::Svc::MemoryPermission::ReadExecute, - generate_random)); - R_TRY(ro.Initialize(*process, *ro_mem, ro_size, Kernel::Svc::MemoryPermission::Read, - generate_random)); - - *out_jit_environment = - std::make_shared(system, process.Get(), std::move(rx), std::move(ro)); + R_TRY(rx.Initialize(system.Kernel(), *process, *rx_mem, rx_size, Kernel::Svc::MemoryPermission::ReadExecute, generate_random)); + R_TRY(ro.Initialize(system.Kernel(), *process, *ro_mem, ro_size, Kernel::Svc::MemoryPermission::Read, generate_random)); + *out_jit_environment = std::make_shared(system, process.Get(), std::move(rx), std::move(ro)); R_SUCCEED(); } diff --git a/src/core/hle/service/jit/jit.h b/src/core/hle/service/jit/jit.h index 19014c75a4..a9ebf146ec 100644 --- a/src/core/hle/service/jit/jit.h +++ b/src/core/hle/service/jit/jit.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/jit/jit_code_memory.cpp b/src/core/hle/service/jit/jit_code_memory.cpp index 2b480488a8..3218512af4 100644 --- a/src/core/hle/service/jit/jit_code_memory.cpp +++ b/src/core/hle/service/jit/jit_code_memory.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -5,12 +8,9 @@ namespace Service::JIT { -Result CodeMemory::Initialize(Kernel::KProcess& process, Kernel::KCodeMemory& code_memory, - size_t size, Kernel::Svc::MemoryPermission perm, - std::mt19937_64& generate_random) { +Result CodeMemory::Initialize(Kernel::KernelCore& kernel, Kernel::KProcess& process, Kernel::KCodeMemory& code_memory, size_t size, Kernel::Svc::MemoryPermission perm, std::mt19937_64& generate_random) { auto& page_table = process.GetPageTable(); - const u64 alias_code_start = - GetInteger(page_table.GetAliasCodeRegionStart()) / Kernel::PageSize; + const u64 alias_code_start = GetInteger(page_table.GetAliasCodeRegionStart()) / Kernel::PageSize; const u64 alias_code_size = page_table.GetAliasCodeRegionSize() / Kernel::PageSize; // NOTE: This will retry indefinitely until mapping the code memory succeeds. @@ -20,7 +20,7 @@ Result CodeMemory::Initialize(Kernel::KProcess& process, Kernel::KCodeMemory& co (alias_code_start + (generate_random() % alias_code_size)) * Kernel::PageSize; // Try to map the address - R_TRY_CATCH(code_memory.MapToOwner(mapped_address, size, perm)) { + R_TRY_CATCH(code_memory.MapToOwner(kernel, mapped_address, size, perm)) { R_CATCH(Kernel::ResultInvalidMemoryRegion) { // If we could not map here, retry. continue; @@ -35,17 +35,17 @@ Result CodeMemory::Initialize(Kernel::KProcess& process, Kernel::KCodeMemory& co m_perm = perm; // Open a new reference to the code memory. - m_code_memory->Open(); + m_code_memory->Open(kernel); // We succeeded. R_SUCCEED(); } } -void CodeMemory::Finalize() { +void CodeMemory::Finalize(Kernel::KernelCore& kernel) { if (m_code_memory) { - R_ASSERT(m_code_memory->UnmapFromOwner(m_address, m_size)); - m_code_memory->Close(); + R_ASSERT(m_code_memory->UnmapFromOwner(kernel, m_address, m_size)); + m_code_memory->Close(kernel); } m_code_memory = nullptr; diff --git a/src/core/hle/service/jit/jit_code_memory.h b/src/core/hle/service/jit/jit_code_memory.h index 6376d4c4eb..d741520aeb 100644 --- a/src/core/hle/service/jit/jit_code_memory.h +++ b/src/core/hle/service/jit/jit_code_memory.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -7,29 +10,16 @@ #include "core/hle/kernel/k_code_memory.h" +namespace Kernel { +class KernelCore; +} + namespace Service::JIT { class CodeMemory { public: - YUZU_NON_COPYABLE(CodeMemory); - - explicit CodeMemory() = default; - - CodeMemory(CodeMemory&& rhs) { - std::swap(m_code_memory, rhs.m_code_memory); - std::swap(m_size, rhs.m_size); - std::swap(m_address, rhs.m_address); - std::swap(m_perm, rhs.m_perm); - } - - ~CodeMemory() { - this->Finalize(); - } - -public: - Result Initialize(Kernel::KProcess& process, Kernel::KCodeMemory& code_memory, size_t size, - Kernel::Svc::MemoryPermission perm, std::mt19937_64& generate_random); - void Finalize(); + Result Initialize(Kernel::KernelCore& kernel, Kernel::KProcess& process, Kernel::KCodeMemory& code_memory, size_t size, Kernel::Svc::MemoryPermission perm, std::mt19937_64& generate_random); + void Finalize(Kernel::KernelCore& kernel); size_t GetSize() const { return m_size; @@ -39,7 +29,6 @@ public: return m_address; } -private: Kernel::KCodeMemory* m_code_memory{}; size_t m_size{}; u64 m_address{}; diff --git a/src/core/hle/service/jit/jit_context.cpp b/src/core/hle/service/jit/jit_context.cpp index 522d849e6f..e1e8eeadbe 100644 --- a/src/core/hle/service/jit/jit_context.cpp +++ b/src/core/hle/service/jit/jit_context.cpp @@ -24,15 +24,25 @@ using namespace Common::ELF; namespace Service::JIT { +enum HelperFn { + None, + Stop, + Resolve, + Panic, + Memcpy, + Memmove, + Memset, + // sm64 + PanicForPlugin, + AbortImpl, + UnexpectedImpl, + Count +}; + constexpr std::array SVC0_ARM64 = { 0x01, 0x00, 0x00, 0xd4, // svc #0 0xc0, 0x03, 0x5f, 0xd6, // ret }; - -constexpr std::array HELPER_FUNCTIONS{ - "_stop", "_resolve", "_panic", "memcpy", "memmove", "memset", -}; - constexpr size_t STACK_ALIGN = 16; class JITContextImpl; @@ -42,10 +52,12 @@ using IntervalType = boost::icl::interval_set::interval_type; class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks { public: - explicit DynarmicCallbacks64(Core::Memory::Memory& memory_, std::vector& local_memory_, - IntervalSet& mapped_ranges_, JITContextImpl& parent_) - : memory{memory_}, local_memory{local_memory_}, - mapped_ranges{mapped_ranges_}, parent{parent_} {} + explicit DynarmicCallbacks64(Core::Memory::Memory& memory_, std::vector& local_memory_, IntervalSet& mapped_ranges_, JITContextImpl& parent_) + : memory{memory_} + , local_memory{local_memory_} + , mapped_ranges{mapped_ranges_} + , parent{parent_} + {} std::optional MemoryReadCode(VAddr vaddr) override { static_assert(Core::Memory::YUZU_PAGESIZE == Dynarmic::CODE_PAGE_SIZE); @@ -57,45 +69,36 @@ public: return cached_code_page.inst[(vaddr & Core::Memory::YUZU_PAGEMASK) / sizeof(u32)]; } void InstructionSynchronizationBarrierRaised() override { - last_code_addr = 0; //reset back, force refetch + last_code_addr = u64(-1); //reset back, force refetch } - u8 MemoryRead8(u64 vaddr) override { - return ReadMemory(vaddr); - } - u16 MemoryRead16(u64 vaddr) override { - return ReadMemory(vaddr); - } - u32 MemoryRead32(u64 vaddr) override { - return ReadMemory(vaddr); - } - u64 MemoryRead64(u64 vaddr) override { - return ReadMemory(vaddr); + u64 MemoryRead(u64 vaddr, size_t size) override { + switch (size) { + case sizeof(u64): return ReadMemory(vaddr); + case sizeof(u32): return ReadMemory(vaddr); + case sizeof(u16): return ReadMemory(vaddr); + case sizeof(u8): return ReadMemory(vaddr); + default: UNREACHABLE(); + } } u128 MemoryRead128(u64 vaddr) override { return ReadMemory(vaddr); } std::string MemoryReadCString(u64 vaddr) { - std::string result; + std::string result{}; u8 next; - - while ((next = MemoryRead8(vaddr++)) != 0) { - result += next; - } - + while ((next = u8(MemoryRead(vaddr++, sizeof(u8)))) != 0) + result += char(next); return result; } - void MemoryWrite8(u64 vaddr, u8 value) override { - WriteMemory(vaddr, value); - } - void MemoryWrite16(u64 vaddr, u16 value) override { - WriteMemory(vaddr, value); - } - void MemoryWrite32(u64 vaddr, u32 value) override { - WriteMemory(vaddr, value); - } - void MemoryWrite64(u64 vaddr, u64 value) override { - WriteMemory(vaddr, value); + void MemoryWrite(u64 vaddr, u64 value, size_t size) override { + switch (size) { + case sizeof(u64): WriteMemory(vaddr, u64(value)); break; + case sizeof(u32): WriteMemory(vaddr, u32(value)); break; + case sizeof(u16): WriteMemory(vaddr, u16(value)); break; + case sizeof(u8): WriteMemory(vaddr, u8(value)); break; + default: UNREACHABLE(); + } } void MemoryWrite128(u64 vaddr, u128 value) override { WriteMemory(vaddr, value); @@ -133,7 +136,7 @@ public: if (boost::icl::contains(mapped_ranges, vaddr)) { memory.ReadBlock(vaddr, &ret, sizeof(T)); } else if (vaddr + sizeof(T) > local_memory.size()) { - LOG_CRITICAL(Service_JIT, "plugin: unmapped read @ 0x{:016x}", vaddr); + LOG_CRITICAL(Service_JIT, "plugin: unmapped read @ {:#016x}", vaddr); } else { std::memcpy(&ret, local_memory.data() + vaddr, sizeof(T)); } @@ -145,7 +148,7 @@ public: if (boost::icl::contains(mapped_ranges, vaddr)) { memory.WriteBlock(vaddr, &value, sizeof(T)); } else if (vaddr + sizeof(T) > local_memory.size()) { - LOG_CRITICAL(Service_JIT, "plugin: unmapped write @ 0x{:016x}", vaddr); + LOG_CRITICAL(Service_JIT, "plugin: unmapped write @ {:#016x}", vaddr); } else { std::memcpy(local_memory.data() + vaddr, &value, sizeof(T)); } @@ -157,32 +160,26 @@ private: std::vector& local_memory; IntervalSet& mapped_ranges; JITContextImpl& parent; - Dynarmic::CodePage cached_code_page; - u64 last_code_addr = 0; + u64 last_code_addr = u64(-1); }; class JITContextImpl { public: explicit JITContextImpl(Core::Memory::Memory& memory_) : memory{memory_} { - callbacks = - std::make_unique(memory, local_memory, mapped_ranges, *this); - user_config.callbacks = callbacks.get(); - jit = std::make_unique(user_config); + callbacks.emplace(memory, local_memory, mapped_ranges, *this); + user_config.callbacks = std::addressof(callbacks.value()); + jit.emplace(user_config); } bool LoadNRO(std::span data) { - local_memory.clear(); - relocbase = local_memory.size(); local_memory.insert(local_memory.end(), data.begin(), data.end()); - if (FixupRelocations()) { InsertHelperFunctions(); InsertStack(); return true; } - return false; } @@ -190,55 +187,46 @@ public: // The loaded NRO file has ELF relocations that must be processed before it can run. // Normally this would be processed by RTLD, but in HLE context, we don't have // the linker available, so we have to do it ourselves. - - const VAddr mod_offset{callbacks->MemoryRead32(4)}; - if (callbacks->MemoryRead32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) { + const VAddr mod_offset{callbacks->MemoryRead(4, sizeof(u32))}; + if (callbacks->MemoryRead(mod_offset, sizeof(u32)) != Common::MakeMagic('M', 'O', 'D', '0')) return false; - } // For more info about dynamic entries, see the ELF ABI specification: // https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html // https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html - VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)}; + VAddr dynamic_offset{mod_offset + callbacks->MemoryRead(mod_offset + 4, sizeof(u32))}; VAddr rela_dyn = 0, relr_dyn = 0; size_t num_rela = 0, num_relr = 0; while (true) { const auto dyn{callbacks->ReadMemory(dynamic_offset)}; dynamic_offset += sizeof(Elf64_Dyn); - if (!dyn.d_tag) { break; - } - if (dyn.d_tag == ElfDtRela) { + } else if (dyn.d_tag == ElfDtRela) { rela_dyn = dyn.d_un.d_ptr; - } - if (dyn.d_tag == ElfDtRelasz) { + } else if (dyn.d_tag == ElfDtRelasz) { num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela); - } - if (dyn.d_tag == ElfDtRelr) { + } else if (dyn.d_tag == ElfDtRelr) { relr_dyn = dyn.d_un.d_ptr; - } - if (dyn.d_tag == ElfDtRelrsz) { + } else if (dyn.d_tag == ElfDtRelrsz) { num_relr = dyn.d_un.d_val / sizeof(Elf64_Relr); } } for (size_t i = 0; i < num_rela; i++) { const auto rela{callbacks->ReadMemory(rela_dyn + i * sizeof(Elf64_Rela))}; - if (Elf64RelType(rela.r_info) != ElfAArch64Relative) { - continue; + if (Elf64RelType(rela.r_info) == ElfAArch64Relative) { + const VAddr contents{callbacks->MemoryRead(rela.r_offset, sizeof(u64))}; + callbacks->MemoryWrite(rela.r_offset, contents + rela.r_addend, sizeof(u64)); } - const VAddr contents{callbacks->MemoryRead64(rela.r_offset)}; - callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend); } VAddr relr_where = 0; for (size_t i = 0; i < num_relr; i++) { - const auto relr{callbacks->ReadMemory(relr_dyn + i * sizeof(Elf64_Relr))}; - const auto incr{[&](VAddr where) { - callbacks->MemoryWrite64(where, callbacks->MemoryRead64(where) + relocbase); - }}; - + const auto relr = callbacks->ReadMemory(relr_dyn + i * sizeof(Elf64_Relr)); + const auto incr = [&](VAddr where) { + callbacks->MemoryWrite(where, callbacks->MemoryRead(where, sizeof(u64)) + relocbase, sizeof(u64)); + }; if ((relr & 1) == 0) { // where pointer relr_where = relocbase + relr; @@ -254,13 +242,12 @@ public: relr_where += 63 * sizeof(Elf64_Addr); } } - return true; } void InsertHelperFunctions() { - for (const auto& name : HELPER_FUNCTIONS) { - helpers[name] = local_memory.size(); + for (size_t i = 0; i < size_t(HelperFn::Count); ++i) { + helpers[i] = local_memory.size(); local_memory.insert(local_memory.end(), SVC0_ARM64.begin(), SVC0_ARM64.end()); } } @@ -268,9 +255,8 @@ public: void InsertStack() { // Allocate enough space to avoid any reasonable risk of // overflowing the stack during plugin execution - const u64 pad_amount{Common::AlignUp(local_memory.size(), STACK_ALIGN) - - local_memory.size()}; - local_memory.insert(local_memory.end(), 0x10000 + pad_amount, 0); + const u64 pad_amount = Common::AlignUp(local_memory.size(), STACK_ALIGN) - local_memory.size(); + local_memory.insert(local_memory.end(), (4096 * 32) + pad_amount, 0); top_of_stack = local_memory.size(); heap_pointer = top_of_stack; } @@ -297,27 +283,21 @@ public: // // For more info, see the AArch64 ABI PCS: // https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst - - for (size_t i = 0; i < 8 && i < argument_stack.size(); i++) { + for (size_t i = 0; i < 8 && i < argument_stack.size(); i++) jit->SetRegister(i, argument_stack[i]); - } - if (argument_stack.size() > 8) { - const VAddr new_sp = Common::AlignDown( - top_of_stack - (argument_stack.size() - 8) * sizeof(u64), STACK_ALIGN); - for (size_t i = 8; i < argument_stack.size(); i++) { - callbacks->MemoryWrite64(new_sp + (i - 8) * sizeof(u64), argument_stack[i]); - } + const VAddr new_sp = Common::AlignDown(top_of_stack - (argument_stack.size() - 8) * sizeof(u64), STACK_ALIGN); + for (size_t i = 8; i < argument_stack.size(); i++) + callbacks->MemoryWrite(new_sp + (i - 8) * sizeof(u64), argument_stack[i], sizeof(u64)); jit->SetSP(new_sp); } - // Reset the call state for the next invocation argument_stack.clear(); heap_pointer = top_of_stack; } u64 CallFunction(VAddr func) { - jit->SetRegister(30, helpers["_stop"]); + jit->SetRegister(30, helpers[size_t(HelperFn::Stop)]); jit->SetSP(top_of_stack); SetupArguments(); @@ -326,21 +306,14 @@ public: return jit->GetRegister(0); } - VAddr GetHelper(const std::string& name) { - return helpers[name]; - } - VAddr AddHeap(const void* data, size_t size) { // Require all heap data types to have the same alignment as the // stack pointer, for compatibility - const size_t num_bytes{Common::AlignUp(size, STACK_ALIGN)}; - + const size_t num_bytes = Common::AlignUp(size, STACK_ALIGN); // Make additional memory space if required if (heap_pointer + num_bytes > local_memory.size()) { - local_memory.insert(local_memory.end(), - (heap_pointer + num_bytes) - local_memory.size(), 0); + local_memory.insert(local_memory.end(), (heap_pointer + num_bytes) - local_memory.size(), 0); } - const VAddr location{heap_pointer}; std::memcpy(local_memory.data() + location, data, size); heap_pointer += num_bytes; @@ -351,13 +324,29 @@ public: std::memcpy(data, local_memory.data() + location, size); } - std::unique_ptr callbacks; + VAddr GetHelper(const std::string& name) { + if (name == "_resolve") return helpers[HelperFn::Resolve]; + else if (name == "_panic") return helpers[HelperFn::Panic]; + else if (name == "_stop") return helpers[HelperFn::Stop]; + else if (name == "memset") return helpers[HelperFn::Memset]; + else if (name == "memcpy") return helpers[HelperFn::Memcpy]; + else if (name == "memmove") return helpers[HelperFn::Memmove]; + else if (name == "PanicForPlugin") return helpers[HelperFn::PanicForPlugin]; + else if (name == "_ZN2nn4diag6detail9AbortImplEPKcS3_S3_i") return helpers[HelperFn::AbortImpl]; + else if (name == "_ZN2nn6detail21UnexpectedDefaultImplEPKcS2_i") return helpers[HelperFn::UnexpectedImpl]; + else { + LOG_CRITICAL(Service_JIT, "unresolved {}", name); + return helpers[HelperFn::Panic]; + } + } + + std::optional callbacks; + std::optional jit; std::vector local_memory; - std::vector argument_stack; + std::vector argument_stack; IntervalSet mapped_ranges; Dynarmic::A64::UserConfig user_config; - std::unique_ptr jit; - std::map> helpers; + std::array helpers; Core::Memory::Memory& memory; VAddr top_of_stack; VAddr heap_pointer; @@ -383,54 +372,52 @@ void DynarmicCallbacks64::CallSVC(u32 swi) { } u64 pc{parent.jit->GetPC() - 4}; - auto& helpers{parent.helpers}; - - if (pc == helpers["memcpy"] || pc == helpers["memmove"]) { + if (pc == parent.helpers[size_t(HelperFn::Memcpy)] || pc == parent.helpers[size_t(HelperFn::Memmove)]) { const VAddr dest{parent.jit->GetRegister(0)}; const VAddr src{parent.jit->GetRegister(1)}; const size_t n{parent.jit->GetRegister(2)}; if (dest < src) { - for (size_t i = 0; i < n; i++) { - MemoryWrite8(dest + i, MemoryRead8(src + i)); - } + for (size_t i = 0; i < n; i++) + MemoryWrite(dest + i, u8(MemoryRead(src + i, sizeof(u8))), sizeof(u8)); } else { - for (size_t i = n; i > 0; i--) { - MemoryWrite8(dest + i - 1, MemoryRead8(src + i - 1)); - } + for (size_t i = n; i > 0; i--) + MemoryWrite(dest + i - 1, u8(MemoryRead(src + i - 1, sizeof(u8))), sizeof(u8)); } - } else if (pc == helpers["memset"]) { + } else if (pc == parent.helpers[size_t(HelperFn::Memset)]) { const VAddr dest{parent.jit->GetRegister(0)}; const u64 c{parent.jit->GetRegister(1)}; const size_t n{parent.jit->GetRegister(2)}; - - for (size_t i = 0; i < n; i++) { - MemoryWrite8(dest + i, static_cast(c)); - } - } else if (pc == helpers["_resolve"]) { + for (size_t i = 0; i < n; i++) + MemoryWrite(dest + i, u8(c), sizeof(u8)); + } else if (pc == parent.helpers[size_t(HelperFn::Resolve)]) { // X0 contains a char* for a symbol to resolve const auto name{MemoryReadCString(parent.jit->GetRegister(0))}; - const auto helper{helpers[name]}; - - if (helper != 0) { - parent.jit->SetRegister(0, helper); - } else { - LOG_WARNING(Service_JIT, "plugin requested unknown function {}", name); - parent.jit->SetRegister(0, helpers["_panic"]); - } - } else if (pc == helpers["_stop"]) { + parent.jit->SetRegister(0, u64(parent.GetHelper(name))); + } else if (pc == parent.helpers[size_t(HelperFn::Stop)]) { parent.jit->HaltExecution(); - } else if (pc == helpers["_panic"]) { + } else if (pc == parent.helpers[size_t(HelperFn::Panic)]) { + LOG_CRITICAL(Service_JIT, "plugin panicked!"); + parent.jit->HaltExecution(); + // SM64 + } else if (pc == parent.helpers[size_t(HelperFn::PanicForPlugin)]) { + LOG_CRITICAL(Service_JIT, "plugin panicked!"); + parent.jit->HaltExecution(); + } else if (pc == parent.helpers[size_t(HelperFn::AbortImpl)]) { + LOG_CRITICAL(Service_JIT, "plugin panicked!"); + parent.jit->HaltExecution(); + } else if (pc == parent.helpers[size_t(HelperFn::UnexpectedImpl)]) { LOG_CRITICAL(Service_JIT, "plugin panicked!"); parent.jit->HaltExecution(); } else { - LOG_CRITICAL(Service_JIT, "plugin issued syscall at unknown address 0x{:x}", pc); + LOG_CRITICAL(Service_JIT, "plugin issued syscall at unknown address {:#x}", pc); parent.jit->HaltExecution(); } } void DynarmicCallbacks64::ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) { - LOG_CRITICAL(Service_JIT, "Illegal operation PC @ {:08x}", pc); + auto const inst = MemoryRead(pc, sizeof(u32)); + LOG_CRITICAL(Service_JIT, "{} PC @ {:08x}, data = {:08x}", exception, pc, inst); parent.jit->HaltExecution(); } diff --git a/src/core/hle/service/jit/jit_context.h b/src/core/hle/service/jit/jit_context.h index f17fc5e244..aa9a2d3589 100644 --- a/src/core/hle/service/jit/jit_context.h +++ b/src/core/hle/service/jit/jit_context.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/kernel_helpers.cpp b/src/core/hle/service/kernel_helpers.cpp index f080f7ffa2..f7a5cb7049 100644 --- a/src/core/hle/service/kernel_helpers.cpp +++ b/src/core/hle/service/kernel_helpers.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -14,15 +17,15 @@ namespace Service::KernelHelpers { ServiceContext::ServiceContext(Core::System& system_, std::string name_) - : kernel(system_.Kernel()) { + : kernel(system_.Kernel()) +{ if (process = Kernel::GetCurrentProcessPointer(kernel); process != nullptr) { return; } // Create the process. process = Kernel::KProcess::Create(kernel); - ASSERT(R_SUCCEEDED(process->Initialize(Kernel::Svc::CreateProcessParameter{}, - kernel.GetSystemResourceLimit(), false))); + ASSERT(R_SUCCEEDED(process->Initialize(kernel, Kernel::Svc::CreateProcessParameter{}, kernel.GetSystemResourceLimit(), false))); // Register the process. Kernel::KProcess::Register(kernel, process); @@ -31,14 +34,14 @@ ServiceContext::ServiceContext(Core::System& system_, std::string name_) ServiceContext::~ServiceContext() { if (process_created) { - process->Close(); + process->Close(kernel); process = nullptr; } } Kernel::KEvent* ServiceContext::CreateEvent(std::string&& name) { // Reserve a new event from the process resource limit - Kernel::KScopedResourceReservation event_reservation(process, + Kernel::KScopedResourceReservation event_reservation(kernel, process, Kernel::LimitableResource::EventCountMax); if (!event_reservation.Succeeded()) { LOG_CRITICAL(Service, "Resource limit reached!"); @@ -53,7 +56,7 @@ Kernel::KEvent* ServiceContext::CreateEvent(std::string&& name) { } // Initialize the event. - event->Initialize(process); + event->Initialize(kernel, process); // Commit the thread reservation. event_reservation.Commit(); @@ -65,11 +68,10 @@ Kernel::KEvent* ServiceContext::CreateEvent(std::string&& name) { } void ServiceContext::CloseEvent(Kernel::KEvent* event) { - if (!event) { - return; + if (event) { + event->GetReadableEvent().Close(kernel); + event->Close(kernel); } - event->GetReadableEvent().Close(); - event->Close(); } } // namespace Service::KernelHelpers diff --git a/src/core/hle/service/kernel_helpers.h b/src/core/hle/service/kernel_helpers.h index eca9aefb52..4e21c8857f 100644 --- a/src/core/hle/service/kernel_helpers.h +++ b/src/core/hle/service/kernel_helpers.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -26,7 +29,6 @@ public: void CloseEvent(Kernel::KEvent* event); -private: Kernel::KernelCore& kernel; Kernel::KProcess* process{}; bool process_created{false}; diff --git a/src/core/hle/service/lbl/lbl.h b/src/core/hle/service/lbl/lbl.h index e47759c01c..e2cd1b3980 100644 --- a/src/core/hle/service/lbl/lbl.h +++ b/src/core/hle/service/lbl/lbl.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ldn/client_process_monitor.cpp b/src/core/hle/service/ldn/client_process_monitor.cpp deleted file mode 100644 index 0c1963a4ca..0000000000 --- a/src/core/hle/service/ldn/client_process_monitor.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "core/hle/service/cmif_serialization.h" -#include "core/hle/service/ldn/ldn_results.h" -#include "core/hle/service/ldn/client_process_monitor.h" -#include "core/hle/service/ipc_helpers.h" - -namespace Service::LDN { - -IClientProcessMonitor::IClientProcessMonitor(Core::System& system_) - : ServiceFramework{system_, "IClientProcessMonitor"} { - static const FunctionInfo functions[] = { - {0, D<&IClientProcessMonitor::RegisterClient>, "RegisterClient"}, - }; - RegisterHandlers(functions); -} - -IClientProcessMonitor::~IClientProcessMonitor() = default; - -Result IClientProcessMonitor::RegisterClient() { - LOG_WARNING(Service_LDN, "(STUBBED) called"); - - R_SUCCEED(); -} - -} // namespace Service::LDN diff --git a/src/core/hle/service/ldn/client_process_monitor.h b/src/core/hle/service/ldn/client_process_monitor.h deleted file mode 100644 index 9caa337795..0000000000 --- a/src/core/hle/service/ldn/client_process_monitor.h +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include "core/hle/service/cmif_types.h" -#include "core/hle/service/service.h" - -namespace Core { -class System; -} - -namespace Service::LDN { - -class IClientProcessMonitor final - : public ServiceFramework { -public: - explicit IClientProcessMonitor(Core::System& system_); - ~IClientProcessMonitor() override; - -private: - Result RegisterClient(); -}; - -} // namespace Service::LDN diff --git a/src/core/hle/service/ldn/lan_discovery.cpp b/src/core/hle/service/ldn/lan_discovery.cpp index f37dbe492f..90adcdcee4 100644 --- a/src/core/hle/service/ldn/lan_discovery.cpp +++ b/src/core/hle/service/ldn/lan_discovery.cpp @@ -1,5 +1,6 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ldn/lan_discovery.h b/src/core/hle/service/ldn/lan_discovery.h index fb24608ae1..f1f3c4bece 100644 --- a/src/core/hle/service/ldn/lan_discovery.h +++ b/src/core/hle/service/ldn/lan_discovery.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -15,7 +18,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include "common/logging.h" #include "common/socket_types.h" @@ -119,7 +122,7 @@ protected: std::array stations; std::array node_changes{}; std::array node_last_states{}; - ankerl::unordered_dense::map scan_results{}; + ::Common::unordered_map scan_results{}; NodeInfo node_info{}; NetworkInfo network_info{}; State state{State::None}; diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp index c77ae2911e..1a0165fd2e 100644 --- a/src/core/hle/service/ldn/ldn.cpp +++ b/src/core/hle/service/ldn/ldn.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -7,7 +7,6 @@ #include "core/core.h" #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/ldn/ldn.h" -#include "core/hle/service/ldn/client_process_monitor.h" #include "core/hle/service/ldn/monitor_service.h" #include "core/hle/service/ldn/sf_monitor_service.h" #include "core/hle/service/ldn/sf_service.h" @@ -17,6 +16,26 @@ namespace Service::LDN { +class IClientProcessMonitor final + : public ServiceFramework { +public: + explicit IClientProcessMonitor(Core::System& system_) + : ServiceFramework{system_, "IClientProcessMonitor"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, D<&IClientProcessMonitor::RegisterClient>, "RegisterClient"}, + }; + // clang-format on + RegisterHandlers(functions); + } + ~IClientProcessMonitor() override = default; +private: + Result RegisterClient(ClientProcessId pid) { + LOG_WARNING(Service_LDN, "(STUBBED) called"); + R_SUCCEED(); + } +}; + class IMonitorServiceCreator final : public ServiceFramework { public: explicit IMonitorServiceCreator(Core::System& system_) : ServiceFramework{system_, "ldn:m"} { @@ -25,7 +44,6 @@ public: {0, C<&IMonitorServiceCreator::CreateMonitorService>, "CreateMonitorService"} }; // clang-format on - RegisterHandlers(functions); } diff --git a/src/core/hle/service/ldn/ldn.h b/src/core/hle/service/ldn/ldn.h index dae037fa8f..76c7f4f439 100644 --- a/src/core/hle/service/ldn/ldn.h +++ b/src/core/hle/service/ldn/ldn.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ldn/ldn_results.h b/src/core/hle/service/ldn/ldn_results.h index f340bda422..cf6c22b54b 100644 --- a/src/core/hle/service/ldn/ldn_results.h +++ b/src/core/hle/service/ldn/ldn_results.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/ldn/ldn_types.h b/src/core/hle/service/ldn/ldn_types.h index 975af5b6c8..4107d579ab 100644 --- a/src/core/hle/service/ldn/ldn_types.h +++ b/src/core/hle/service/ldn/ldn_types.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project @@ -6,6 +6,7 @@ #pragma once +#include #include #include "common/common_funcs.h" diff --git a/src/core/hle/service/ldn/monitor_service.cpp b/src/core/hle/service/ldn/monitor_service.cpp index ea6ac4d5dd..6cb7180b15 100644 --- a/src/core/hle/service/ldn/monitor_service.cpp +++ b/src/core/hle/service/ldn/monitor_service.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/ldn/monitor_service.h b/src/core/hle/service/ldn/monitor_service.h index e663145b4f..2d4a69c311 100644 --- a/src/core/hle/service/ldn/monitor_service.h +++ b/src/core/hle/service/ldn/monitor_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/ldn/sf_monitor_service.cpp b/src/core/hle/service/ldn/sf_monitor_service.cpp index 9e6736ff2c..5ac663b4c2 100644 --- a/src/core/hle/service/ldn/sf_monitor_service.cpp +++ b/src/core/hle/service/ldn/sf_monitor_service.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/ldn/sf_monitor_service.h b/src/core/hle/service/ldn/sf_monitor_service.h index d021152010..a596093f1b 100644 --- a/src/core/hle/service/ldn/sf_monitor_service.h +++ b/src/core/hle/service/ldn/sf_monitor_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/ldn/sf_service.cpp b/src/core/hle/service/ldn/sf_service.cpp index 61cabe219a..b77672a1b6 100644 --- a/src/core/hle/service/ldn/sf_service.cpp +++ b/src/core/hle/service/ldn/sf_service.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/ldn/sf_service.h b/src/core/hle/service/ldn/sf_service.h index 05534b5677..ca7a81cf29 100644 --- a/src/core/hle/service/ldn/sf_service.h +++ b/src/core/hle/service/ldn/sf_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/ldn/sf_service_monitor.cpp b/src/core/hle/service/ldn/sf_service_monitor.cpp index 9a1cde6881..95a9bcaf71 100644 --- a/src/core/hle/service/ldn/sf_service_monitor.cpp +++ b/src/core/hle/service/ldn/sf_service_monitor.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/ldn/sf_service_monitor.h b/src/core/hle/service/ldn/sf_service_monitor.h index 924cc17594..87854b868d 100644 --- a/src/core/hle/service/ldn/sf_service_monitor.h +++ b/src/core/hle/service/ldn/sf_service_monitor.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/ldn/system_local_communication_service.cpp b/src/core/hle/service/ldn/system_local_communication_service.cpp index 7b52223cdc..57a1c6a6c2 100644 --- a/src/core/hle/service/ldn/system_local_communication_service.cpp +++ b/src/core/hle/service/ldn/system_local_communication_service.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/ldn/system_local_communication_service.h b/src/core/hle/service/ldn/system_local_communication_service.h index a02b097ea7..fa0e45bde4 100644 --- a/src/core/hle/service/ldn/system_local_communication_service.h +++ b/src/core/hle/service/ldn/system_local_communication_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/ldn/user_local_communication_service.cpp b/src/core/hle/service/ldn/user_local_communication_service.cpp index 1e984a9782..7011bd52f2 100644 --- a/src/core/hle/service/ldn/user_local_communication_service.cpp +++ b/src/core/hle/service/ldn/user_local_communication_service.cpp @@ -1,5 +1,6 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -322,7 +323,7 @@ void IUserLocalCommunicationService::OnLDNPacketReceived(const Network::LDNPacke } void IUserLocalCommunicationService::OnEventFired() { - state_change_event->Signal(); + state_change_event->Signal(system.Kernel()); } } // namespace Service::LDN diff --git a/src/core/hle/service/ldn/user_local_communication_service.h b/src/core/hle/service/ldn/user_local_communication_service.h index 1e725ed531..720d5e9bf4 100644 --- a/src/core/hle/service/ldn/user_local_communication_service.h +++ b/src/core/hle/service/ldn/user_local_communication_service.h @@ -1,7 +1,7 @@ -// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index ba58b3a093..db4d51b49d 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -56,9 +59,9 @@ public: void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); - server_manager->RegisterNamedService("ldr:dmnt", std::make_shared(system)); - server_manager->RegisterNamedService("ldr:pm", std::make_shared(system)); - server_manager->RegisterNamedService("ldr:shel", std::make_shared(system)); + server_manager->RegisterNamedService("ldr:dmnt", std::make_shared(system), 3); + server_manager->RegisterNamedService("ldr:pm", std::make_shared(system), 1); + server_manager->RegisterNamedService("ldr:shel", std::make_shared(system), 3); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/ldr/ldr.h b/src/core/hle/service/ldr/ldr.h index c9281dbfb2..12a6b1f507 100644 --- a/src/core/hle/service/ldr/ldr.h +++ b/src/core/hle/service/ldr/ldr.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp index 26f7e0fc32..d631a6dd80 100644 --- a/src/core/hle/service/lm/lm.cpp +++ b/src/core/hle/service/lm/lm.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include "common/container/unordered_map.h" #include #include "common/logging.h" #include "core/core.h" @@ -331,7 +331,7 @@ private: }; static_assert(sizeof(LogPacketHeader) == 0x18, "LogPacketHeader is an invalid size"); - ankerl::unordered_dense::map> entries{}; + ::Common::unordered_map> entries{}; LogDestination destination{LogDestination::All}; }; @@ -353,7 +353,22 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); + } +}; + +class LM_GET final : public ServiceFramework { +public: + explicit LM_GET(Core::System& system_) + : ServiceFramework{system_, "lm:get"} + { + static const FunctionInfo functions[] = { + {0, nullptr, "StartLogging"}, + {1, nullptr, "StopLogging"}, + {2, nullptr, "GetLog"}, + {100, nullptr, "CreateDevNotificationReceiver"}, + }; + RegisterHandlers(functions); } }; @@ -361,6 +376,7 @@ void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); server_manager->RegisterNamedService("lm", std::make_shared(system)); + server_manager->RegisterNamedService("lm:get", std::make_shared(system)); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/lm/lm.h b/src/core/hle/service/lm/lm.h index 0d7c39cbc3..890344f44a 100644 --- a/src/core/hle/service/lm/lm.h +++ b/src/core/hle/service/lm/lm.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/mig/mig.cpp b/src/core/hle/service/mig/mig.cpp index edc96506c0..7412674417 100644 --- a/src/core/hle/service/mig/mig.cpp +++ b/src/core/hle/service/mig/mig.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project diff --git a/src/core/hle/service/mig/mig.h b/src/core/hle/service/mig/mig.h index c8ed732a5b..39e92a59eb 100644 --- a/src/core/hle/service/mig/mig.h +++ b/src/core/hle/service/mig/mig.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp index 51df5bc628..513bb671f8 100644 --- a/src/core/hle/service/mii/mii.cpp +++ b/src/core/hle/service/mii/mii.cpp @@ -367,10 +367,8 @@ void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); std::shared_ptr manager = std::make_shared(); - server_manager->RegisterNamedService( - "mii:e", std::make_shared(system, "mii:e", manager, true)); - server_manager->RegisterNamedService( - "mii:u", std::make_shared(system, "mii:u", manager, false)); + server_manager->RegisterNamedService("mii:e", std::make_shared(system, "mii:e", manager, true)); + server_manager->RegisterNamedService("mii:u", std::make_shared(system, "mii:u", manager, false)); server_manager->RegisterNamedService("miiimg", std::make_shared(system)); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/mii/mii.h b/src/core/hle/service/mii/mii.h index 8683ac1a52..9ec6f248e5 100644 --- a/src/core/hle/service/mii/mii.h +++ b/src/core/hle/service/mii/mii.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/mii/mii_database.cpp b/src/core/hle/service/mii/mii_database.cpp index d1f4e1f198..6dd9773b2b 100644 --- a/src/core/hle/service/mii/mii_database.cpp +++ b/src/core/hle/service/mii/mii_database.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project diff --git a/src/core/hle/service/mii/mii_database.h b/src/core/hle/service/mii/mii_database.h index 3bd240f93b..6d28696206 100644 --- a/src/core/hle/service/mii/mii_database.h +++ b/src/core/hle/service/mii/mii_database.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/mii/mii_database_manager.cpp b/src/core/hle/service/mii/mii_database_manager.cpp index 606df7953d..8e07eb8442 100644 --- a/src/core/hle/service/mii/mii_database_manager.cpp +++ b/src/core/hle/service/mii/mii_database_manager.cpp @@ -77,7 +77,7 @@ Result DatabaseManager::Initialize(DatabaseSessionMetadata& metadata, bool& is_d const auto result = database.CheckIntegrity(); if (result.IsError()) { - LOG_ERROR(Service_Mii, "Mii database is corrupted 0x{:0x}", result.raw); + LOG_ERROR(Service_Mii, "Mii database is corrupted {:#0x}", result.raw); database.CleanDatabase(); return ResultSuccess; } diff --git a/src/core/hle/service/mii/mii_database_manager.h b/src/core/hle/service/mii/mii_database_manager.h index 52c32be82c..52ec3a91cc 100644 --- a/src/core/hle/service/mii/mii_database_manager.h +++ b/src/core/hle/service/mii/mii_database_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h index 48d8e8bb7d..9c546d2cbf 100644 --- a/src/core/hle/service/mii/mii_manager.h +++ b/src/core/hle/service/mii/mii_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/mii/mii_result.h b/src/core/hle/service/mii/mii_result.h index e2c36e556d..f4964133e7 100644 --- a/src/core/hle/service/mii/mii_result.h +++ b/src/core/hle/service/mii/mii_result.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/mii/mii_types.h b/src/core/hle/service/mii/mii_types.h index 08c6029dff..6afa07ef43 100644 --- a/src/core/hle/service/mii/mii_types.h +++ b/src/core/hle/service/mii/mii_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/mii/types/char_info.cpp b/src/core/hle/service/mii/types/char_info.cpp index e90124af4c..b5f4492994 100644 --- a/src/core/hle/service/mii/types/char_info.cpp +++ b/src/core/hle/service/mii/types/char_info.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/mii/types/char_info.h b/src/core/hle/service/mii/types/char_info.h index d0c457fd56..e9c41435e1 100644 --- a/src/core/hle/service/mii/types/char_info.h +++ b/src/core/hle/service/mii/types/char_info.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/mii/types/core_data.cpp b/src/core/hle/service/mii/types/core_data.cpp index ba1da76bae..7f2acea3ef 100644 --- a/src/core/hle/service/mii/types/core_data.cpp +++ b/src/core/hle/service/mii/types/core_data.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/mii/types/core_data.h b/src/core/hle/service/mii/types/core_data.h index 8897e4f3b3..48a1ba5c73 100644 --- a/src/core/hle/service/mii/types/core_data.h +++ b/src/core/hle/service/mii/types/core_data.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/mii/types/raw_data.h b/src/core/hle/service/mii/types/raw_data.h index 9a4cfa738e..f8182847ec 100644 --- a/src/core/hle/service/mii/types/raw_data.h +++ b/src/core/hle/service/mii/types/raw_data.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/mii/types/store_data.cpp b/src/core/hle/service/mii/types/store_data.cpp index 127221fdb6..fcef3850f2 100644 --- a/src/core/hle/service/mii/types/store_data.cpp +++ b/src/core/hle/service/mii/types/store_data.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/mii/types/store_data.h b/src/core/hle/service/mii/types/store_data.h index ed5dfb9494..5d5269a15b 100644 --- a/src/core/hle/service/mii/types/store_data.h +++ b/src/core/hle/service/mii/types/store_data.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/mii/types/ver3_store_data.cpp b/src/core/hle/service/mii/types/ver3_store_data.cpp index c27646fcfd..840e0f35b3 100644 --- a/src/core/hle/service/mii/types/ver3_store_data.cpp +++ b/src/core/hle/service/mii/types/ver3_store_data.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/mii/types/ver3_store_data.h b/src/core/hle/service/mii/types/ver3_store_data.h index 47907bf7d7..f2cb395136 100644 --- a/src/core/hle/service/mii/types/ver3_store_data.h +++ b/src/core/hle/service/mii/types/ver3_store_data.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/mm/mm_u.h b/src/core/hle/service/mm/mm_u.h index 43117c9b1c..ab0e76f40a 100644 --- a/src/core/hle/service/mm/mm_u.h +++ b/src/core/hle/service/mm/mm_u.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/mnpp/mnpp.cpp b/src/core/hle/service/mnpp/mnpp.cpp new file mode 100644 index 0000000000..11b6f3a386 --- /dev/null +++ b/src/core/hle/service/mnpp/mnpp.cpp @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 + +#include "common/logging.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/mnpp/mnpp.h" +#include "core/hle/service/server_manager.h" +#include "core/hle/service/service.h" + +namespace Service::MNPP { + +class MNPP_APP final : public ServiceFramework { +public: + explicit MNPP_APP(Core::System& system_) : ServiceFramework{system_, "mnpp:app"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &MNPP_APP::Cmd0, "Cmd0"}, + {1, &MNPP_APP::Cmd1, "Cmd1"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void Cmd0(HLERequestContext& ctx) { + LOG_WARNING(Service_MNPP, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void Cmd1(HLERequestContext& ctx) { + LOG_WARNING(Service_MNPP, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } +}; + +class MNPP_SYS final : public ServiceFramework { +public: + explicit MNPP_SYS(Core::System& system_) : ServiceFramework{system_, "mnpp:sys"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "Cmd0"}, + {10, nullptr, "Cmd10"}, + {100, nullptr, "Cmd100"}, + {200, nullptr, "Cmd200"}, + {300, nullptr, "Cmd300"}, + {400, nullptr, "Cmd400"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class MNPP_WEB final : public ServiceFramework { +public: + explicit MNPP_WEB(Core::System& system_) : ServiceFramework{system_, "mnpp:web"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "Cmd0"}, + {1, nullptr, "Cmd1"}, + {10, nullptr, "Cmd10"}, + {20, nullptr, "Cmd20"}, + {100, nullptr, "Cmd100"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +void LoopProcess(Core::System& system) { + auto server_manager = std::make_unique(system); + + server_manager->RegisterNamedService("mnpp:app", std::make_shared(system)); + server_manager->RegisterNamedService("mnpp:sys", std::make_shared(system)); + server_manager->RegisterNamedService("mnpp:web", std::make_shared(system)); + ServerManager::RunServer(std::move(server_manager)); +} + +} // namespace Service::MNPP diff --git a/src/core/hle/service/mnpp/mnpp_app.h b/src/core/hle/service/mnpp/mnpp.h similarity index 69% rename from src/core/hle/service/mnpp/mnpp_app.h rename to src/core/hle/service/mnpp/mnpp.h index 40d0395bdf..3cdcf14cac 100644 --- a/src/core/hle/service/mnpp/mnpp_app.h +++ b/src/core/hle/service/mnpp/mnpp.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/mnpp/mnpp_app.cpp b/src/core/hle/service/mnpp/mnpp_app.cpp deleted file mode 100644 index 19bbfd795b..0000000000 --- a/src/core/hle/service/mnpp/mnpp_app.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 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 - -#include "common/logging.h" -#include "core/hle/service/ipc_helpers.h" -#include "core/hle/service/mnpp/mnpp_app.h" -#include "core/hle/service/server_manager.h" -#include "core/hle/service/service.h" - -namespace Service::MNPP { - -class MNPP_APP final : public ServiceFramework { -public: - explicit MNPP_APP(Core::System& system_) : ServiceFramework{system_, "mnpp:app"} { - // clang-format off - static const FunctionInfo functions[] = { - {0, &MNPP_APP::Unknown0, "unknown0"}, - {1, &MNPP_APP::Unknown1, "unknown1"}, - }; - // clang-format on - - RegisterHandlers(functions); - } - -private: - void Unknown0(HLERequestContext& ctx) { - LOG_WARNING(Service_MNPP, "(STUBBED) called"); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); - } - - void Unknown1(HLERequestContext& ctx) { - LOG_WARNING(Service_MNPP, "(STUBBED) called"); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); - } -}; - -void LoopProcess(Core::System& system) { - auto server_manager = std::make_unique(system); - - server_manager->RegisterNamedService("mnpp:app", std::make_shared(system)); - ServerManager::RunServer(std::move(server_manager)); -} - -} // namespace Service::MNPP diff --git a/src/core/hle/service/ncm/ncm.cpp b/src/core/hle/service/ncm/ncm.cpp index 650666d6b7..6c754d5567 100644 --- a/src/core/hle/service/ncm/ncm.cpp +++ b/src/core/hle/service/ncm/ncm.cpp @@ -1,9 +1,19 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 +#include +#include #include +#include +#include "core/core.h" +#include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs_factory.h" +#include "core/hle/api_version.h" +#include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ncm/ncm.h" #include "core/hle/service/server_manager.h" @@ -88,6 +98,268 @@ public: } }; +class IContentStorage final : public ServiceFramework { +public: + explicit IContentStorage(Core::System& system_, FileSys::StorageId id) + : ServiceFramework{system_, "IContentStorage"}, storage{id} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IContentStorage::GeneratePlaceHolderId, "GeneratePlaceHolderId"}, + {1, &IContentStorage::CreatePlaceHolder, "CreatePlaceHolder"}, + {2, &IContentStorage::DeletePlaceHolder, "DeletePlaceHolder"}, + {4, &IContentStorage::WritePlaceHolder, "WritePlaceHolder"}, + {5, &IContentStorage::Register, "Register"}, + {6, &IContentStorage::Delete, "Delete"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void GeneratePlaceHolderId(HLERequestContext& ctx) { + LOG_DEBUG(Service_NCM, "called"); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.PushRaw(FileSys::PlaceholderCache::Generate()); + } + + void CreatePlaceHolder(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + [[maybe_unused]] FileSys::NcaID content_id{}; + FileSys::NcaID placeholder_id{}; + if constexpr (HLE::ApiVersion::HOS_VERSION_MAJOR >= 16) { + placeholder_id = rp.PopRaw(); + content_id = rp.PopRaw(); + } else { + content_id = rp.PopRaw(); + placeholder_id = rp.PopRaw(); + } + const auto size = rp.Pop(); + + auto* const placeholder_cache = + system.GetFileSystemController().GetPlaceholderCacheForStorage(storage); + const bool succeeded = + placeholder_cache != nullptr && size >= 0 && + (placeholder_cache->Exists(placeholder_id) || + placeholder_cache->Create(placeholder_id, static_cast(size))); + + if (succeeded) { + LOG_DEBUG(Service_NCM, "called, storage_id={}, size={}", static_cast(storage), + size); + } else { + LOG_WARNING(Service_NCM, "failed, storage_id={}, size={}", static_cast(storage), + size); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(succeeded ? ResultSuccess : ResultUnknown); + } + + void DeletePlaceHolder(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto placeholder_id = rp.PopRaw(); + + auto* const placeholder_cache = + system.GetFileSystemController().GetPlaceholderCacheForStorage(storage); + const bool succeeded = + placeholder_cache != nullptr && + (!placeholder_cache->Exists(placeholder_id) || placeholder_cache->Delete(placeholder_id)); + + if (succeeded) { + LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast(storage)); + } else { + LOG_WARNING(Service_NCM, "failed, storage_id={}", static_cast(storage)); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(succeeded ? ResultSuccess : ResultUnknown); + } + + void WritePlaceHolder(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto placeholder_id = rp.PopRaw(); + const auto offset = rp.Pop(); + const auto data = ctx.ReadBuffer(); + + auto* const placeholder_cache = + system.GetFileSystemController().GetPlaceholderCacheForStorage(storage); + const std::vector write_data{data.begin(), data.end()}; + const bool succeeded = + placeholder_cache != nullptr && + placeholder_cache->Write(placeholder_id, offset, write_data); + + if (succeeded) { + LOG_DEBUG(Service_NCM, "called, storage_id={}, offset={}, size={}", + static_cast(storage), offset, data.size()); + } else { + LOG_WARNING(Service_NCM, "failed, storage_id={}, offset={}, size={}", + static_cast(storage), offset, data.size()); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(succeeded ? ResultSuccess : ResultUnknown); + } + + void Register(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + FileSys::NcaID content_id{}; + FileSys::NcaID placeholder_id{}; + if constexpr (HLE::ApiVersion::HOS_VERSION_MAJOR >= 16) { + placeholder_id = rp.PopRaw(); + content_id = rp.PopRaw(); + } else { + content_id = rp.PopRaw(); + placeholder_id = rp.PopRaw(); + } + + auto& fsc = system.GetFileSystemController(); + auto* const placeholder_cache = fsc.GetPlaceholderCacheForStorage(storage); + auto* const registered_cache = fsc.GetRegisteredCacheForStorage(storage); + const bool succeeded = + placeholder_cache != nullptr && registered_cache != nullptr && + placeholder_cache->Register(registered_cache, placeholder_id, content_id); + + if (succeeded) { + LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast(storage)); + } else { + LOG_WARNING(Service_NCM, "failed, storage_id={}", static_cast(storage)); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(succeeded ? ResultSuccess : ResultUnknown); + } + + void Delete(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto content_id = rp.PopRaw(); + + auto* const registered_cache = + system.GetFileSystemController().GetRegisteredCacheForStorage(storage); + const bool succeeded = registered_cache != nullptr && registered_cache->Delete(content_id); + if (succeeded) { + registered_cache->Refresh(); + } + + if (succeeded) { + LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast(storage)); + } else { + LOG_WARNING(Service_NCM, "failed, storage_id={}", static_cast(storage)); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(succeeded ? ResultSuccess : ResultUnknown); + } + + FileSys::StorageId storage; +}; + +class IContentMetaDatabase final : public ServiceFramework { +public: + explicit IContentMetaDatabase(Core::System& system_, FileSys::StorageId id) + : ServiceFramework{system_, "IContentMetaDatabase"}, storage{id} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IContentMetaDatabase::Set, "Set"}, + {2, &IContentMetaDatabase::Remove, "Remove"}, + {8, &IContentMetaDatabase::Has, "Has"}, + {15, &IContentMetaDatabase::Commit, "Commit"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + struct ContentMetaKey { + u64 id; + u32 version; + FileSys::TitleType type; + u8 install_type; + std::array padding; + }; + static_assert(sizeof(ContentMetaKey) == 0x10); + + void Set(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto key = rp.PopRaw(); + + const auto entry_matches = [&key](const ContentMetaKey& entry) { + return entry.id == key.id && entry.version == key.version && entry.type == key.type && + entry.install_type == key.install_type; + }; + if (std::find_if(entries.begin(), entries.end(), entry_matches) == entries.end()) { + entries.push_back(key); + } + + LOG_DEBUG(Service_NCM, + "called, storage_id={}, title_id={:016X}, version={}, type={}, size={}", + static_cast(storage), key.id, key.version, static_cast(key.type), + ctx.GetReadBufferSize()); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void Remove(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto key = rp.PopRaw(); + + std::erase_if(entries, [&key](const ContentMetaKey& entry) { + return entry.id == key.id && entry.version == key.version && entry.type == key.type && + entry.install_type == key.install_type; + }); + + LOG_DEBUG(Service_NCM, "called, storage_id={}, title_id={:016X}, version={}, type={}", + static_cast(storage), key.id, key.version, static_cast(key.type)); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void Has(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto key = rp.PopRaw(); + + const bool has_pending = + std::find_if(entries.begin(), entries.end(), [&key](const ContentMetaKey& entry) { + return entry.id == key.id && entry.version == key.version && + entry.type == key.type && entry.install_type == key.install_type; + }) != entries.end(); + + auto* const registered_cache = + system.GetFileSystemController().GetRegisteredCacheForStorage(storage); + const bool has_registered = + registered_cache != nullptr && + registered_cache->HasEntry(key.id, FileSys::ContentRecordType::Meta); + + LOG_DEBUG(Service_NCM, "called, storage_id={}, title_id={:016X}, version={}, type={}, has={}", + static_cast(storage), key.id, key.version, static_cast(key.type), + has_pending || has_registered); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(has_pending || has_registered); + } + + void Commit(HLERequestContext& ctx) { + auto* const registered_cache = + system.GetFileSystemController().GetRegisteredCacheForStorage(storage); + if (registered_cache != nullptr) { + registered_cache->Refresh(); + } + + LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast(storage)); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + FileSys::StorageId storage; + std::vector entries; +}; + class LR final : public ServiceFramework { public: explicit LR(Core::System& system_) : ServiceFramework{system_, "lr"} { @@ -113,8 +385,8 @@ public: {1, nullptr, "CreateContentMetaDatabase"}, {2, nullptr, "VerifyContentStorage"}, {3, nullptr, "VerifyContentMetaDatabase"}, - {4, nullptr, "OpenContentStorage"}, - {5, nullptr, "OpenContentMetaDatabase"}, + {4, &NCM::OpenContentStorage, "OpenContentStorage"}, + {5, &NCM::OpenContentMetaDatabase, "OpenContentMetaDatabase"}, {6, nullptr, "CloseContentStorageForcibly"}, {7, nullptr, "CloseContentMetaDatabaseForcibly"}, {8, nullptr, "CleanupContentMetaDatabase"}, @@ -130,6 +402,41 @@ public: RegisterHandlers(functions); } + +private: + void OpenContentStorage(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto storage_id = rp.PopEnum(); + + LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast(storage_id)); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(ctx, system, storage_id); + } + + void OpenContentMetaDatabase(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto storage_id = rp.PopEnum(); + + LOG_DEBUG(Service_NCM, "called, storage_id={}", static_cast(storage_id)); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(ctx, system, storage_id); + } +}; + +class NCM_V final : public ServiceFramework { +public: + explicit NCM_V(Core::System& system_) + : ServiceFramework{system_, "ncm:v"} + { + static const FunctionInfo functions[] = { + {0, nullptr, "GetSystemVersion"}, + }; + RegisterHandlers(functions); + } }; void LoopProcess(Core::System& system) { @@ -137,6 +444,9 @@ void LoopProcess(Core::System& system) { server_manager->RegisterNamedService("lr", std::make_shared(system)); server_manager->RegisterNamedService("ncm", std::make_shared(system)); + if (1 /* not retail */) { + server_manager->RegisterNamedService("ncm:v", std::make_shared(system)); + } ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/ncm/ncm.h b/src/core/hle/service/ncm/ncm.h index b78efdcd79..1d8bc7a9ea 100644 --- a/src/core/hle/service/ncm/ncm.h +++ b/src/core/hle/service/ncm/ncm.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp index faa2b1f584..2876c6f739 100644 --- a/src/core/hle/service/nfc/common/device.cpp +++ b/src/core/hle/service/nfc/common/device.cpp @@ -71,13 +71,13 @@ NfcDevice::~NfcDevice() { void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) { if (type == Core::HID::ControllerTriggerType::Connected) { Initialize(); - availability_change_event->Signal(); + availability_change_event->Signal(system.Kernel()); return; } if (type == Core::HID::ControllerTriggerType::Disconnected) { Finalize(); - availability_change_event->Signal(); + availability_change_event->Signal(system.Kernel()); return; } @@ -138,8 +138,8 @@ bool NfcDevice::LoadNfcTag(u8 protocol, u8 tag_type, u8 uuid_length, UniqueSeria }; device_state = DeviceState::TagFound; - deactivate_event->GetReadableEvent().Clear(); - activate_event->Signal(); + deactivate_event->GetReadableEvent().Clear(system.Kernel()); + activate_event->Signal(system.Kernel()); return true; } @@ -192,8 +192,8 @@ void NfcDevice::CloseNfcTag() { device_state = DeviceState::TagRemoved; encrypted_tag_data = {}; tag_data = {}; - activate_event->GetReadableEvent().Clear(); - deactivate_event->Signal(); + activate_event->GetReadableEvent().Clear(system.Kernel()); + deactivate_event->Signal(system.Kernel()); } Kernel::KReadableEvent& NfcDevice::GetActivateEvent() const { diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h index d59202d18f..98063bbc29 100644 --- a/src/core/hle/service/nfc/common/device.h +++ b/src/core/hle/service/nfc/common/device.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/nfc/common/device_manager.h b/src/core/hle/service/nfc/common/device_manager.h index 6c0e6b255e..116ae8cb7e 100644 --- a/src/core/hle/service/nfc/common/device_manager.h +++ b/src/core/hle/service/nfc/common/device_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nfc/mifare_result.h b/src/core/hle/service/nfc/mifare_result.h index 16a9171e60..399f01313e 100644 --- a/src/core/hle/service/nfc/mifare_result.h +++ b/src/core/hle/service/nfc/mifare_result.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nfc/mifare_types.h b/src/core/hle/service/nfc/mifare_types.h index 4679373994..237f60f0de 100644 --- a/src/core/hle/service/nfc/mifare_types.h +++ b/src/core/hle/service/nfc/mifare_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp index 6583cd2445..f64445835e 100644 --- a/src/core/hle/service/nfc/nfc.cpp +++ b/src/core/hle/service/nfc/nfc.cpp @@ -151,7 +151,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } }; @@ -173,7 +173,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } }; @@ -195,7 +195,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } }; @@ -217,7 +217,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } }; diff --git a/src/core/hle/service/nfc/nfc.h b/src/core/hle/service/nfc/nfc.h index d15955b755..bfda30d6f1 100644 --- a/src/core/hle/service/nfc/nfc.h +++ b/src/core/hle/service/nfc/nfc.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/nfc/nfc_interface.cpp b/src/core/hle/service/nfc/nfc_interface.cpp index 35849bd6c4..084227fa06 100644 --- a/src/core/hle/service/nfc/nfc_interface.cpp +++ b/src/core/hle/service/nfc/nfc_interface.cpp @@ -143,7 +143,7 @@ void NfcInterface::AttachAvailabilityChangeEvent(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(GetManager()->AttachAvailabilityChangeEvent()); + rb.PushCopyObjects(ctx, GetManager()->AttachAvailabilityChangeEvent()); } void NfcInterface::StartDetection(HLERequestContext& ctx) { @@ -203,7 +203,7 @@ void NfcInterface::AttachActivateEvent(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(result); - rb.PushCopyObjects(out_event); + rb.PushCopyObjects(ctx, out_event); } void NfcInterface::AttachDeactivateEvent(HLERequestContext& ctx) { @@ -217,7 +217,7 @@ void NfcInterface::AttachDeactivateEvent(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(result); - rb.PushCopyObjects(out_event); + rb.PushCopyObjects(ctx, out_event); } void NfcInterface::SetNfcEnabled(HLERequestContext& ctx) { diff --git a/src/core/hle/service/nfc/nfc_interface.h b/src/core/hle/service/nfc/nfc_interface.h index 5cc0d8ec04..a1e0d8d9ca 100644 --- a/src/core/hle/service/nfc/nfc_interface.h +++ b/src/core/hle/service/nfc/nfc_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/nfc/nfc_result.h b/src/core/hle/service/nfc/nfc_result.h index 464b5fd69a..aab83c326c 100644 --- a/src/core/hle/service/nfc/nfc_result.h +++ b/src/core/hle/service/nfc/nfc_result.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nfc/nfc_types.h b/src/core/hle/service/nfc/nfc_types.h index 6d44014ea1..6774a9b4d6 100644 --- a/src/core/hle/service/nfc/nfc_types.h +++ b/src/core/hle/service/nfc/nfc_types.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index 604daabbe2..e0c24679ab 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -42,6 +42,7 @@ public: {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"}, {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, {24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"}, + {25, &IUser::StartDetection, "StartDetectionWithFilter"}, }; // clang-format on @@ -74,6 +75,7 @@ public: {20, &ISystem::GetDeviceState, "GetDeviceState"}, {21, &ISystem::GetNpadId, "GetNpadId"}, {23, &ISystem::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, + {25, &ISystem::StartDetection, "StartDetectionWithFilter"}, {100, &ISystem::Format, "Format"}, {101, &ISystem::GetAdminInfo, "GetAdminInfo"}, {102, &ISystem::GetRegisterInfoPrivate, "GetRegisterInfoPrivate"}, @@ -118,6 +120,7 @@ public: {22, &IDebug::GetApplicationAreaSize, "GetApplicationAreaSize"}, {23, &IDebug::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, {24, &IDebug::RecreateApplicationArea, "RecreateApplicationArea"}, + {25, &IDebug::StartDetection, "StartDetectionWithFilter"}, {100, &IDebug::Format, "Format"}, {101, &IDebug::GetAdminInfo, "GetAdminInfo"}, {102, &IDebug::GetRegisterInfoPrivate, "GetRegisterInfoPrivate"}, @@ -157,7 +160,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } }; @@ -179,7 +182,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } }; @@ -201,7 +204,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } }; diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h index a5aac710b5..e7af7ae39d 100644 --- a/src/core/hle/service/nfp/nfp.h +++ b/src/core/hle/service/nfp/nfp.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/nfp/nfp_interface.h b/src/core/hle/service/nfp/nfp_interface.h index fa985b068e..531db84652 100644 --- a/src/core/hle/service/nfp/nfp_interface.h +++ b/src/core/hle/service/nfp/nfp_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/nfp/nfp_result.h b/src/core/hle/service/nfp/nfp_result.h index 6185338438..528b5417fa 100644 --- a/src/core/hle/service/nfp/nfp_result.h +++ b/src/core/hle/service/nfp/nfp_result.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h index 8330134d18..108548ea46 100644 --- a/src/core/hle/service/nfp/nfp_types.h +++ b/src/core/hle/service/nfp/nfp_types.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project diff --git a/src/core/hle/service/ngc/ngc.cpp b/src/core/hle/service/ngc/ngc.cpp index 0275ec52e5..e79108a1eb 100644 --- a/src/core/hle/service/ngc/ngc.cpp +++ b/src/core/hle/service/ngc/ngc.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -6,20 +6,26 @@ #include "common/string_util.h" #include "core/core.h" +#include "core/hle/kernel/k_client_session.h" +#include "core/hle/result.h" +#include "core/hle/service/acc/profile_manager.h" +#include "core/hle/service/cmif_types.h" +#include "core/hle/service/cmif_serialization.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ngc/ngc.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/service.h" +#include "frontend_common/firmware_manager.h" namespace Service::NGC { -class NgctServiceImpl final : public ServiceFramework { +class IService final : public ServiceFramework { public: - explicit NgctServiceImpl(Core::System& system_) : ServiceFramework{system_, "ngct:u"} { + explicit IService(Core::System& system_) : ServiceFramework{system_, "ngct:u"} { // clang-format off static const FunctionInfo functions[] = { - {0, &NgctServiceImpl::Match, "Match"}, - {1, &NgctServiceImpl::Filter, "Filter"}, + {0, &IService::Match, "Match"}, + {1, &IService::Filter, "Filter"}, }; // clang-format on @@ -29,8 +35,9 @@ public: private: void Match(HLERequestContext& ctx) { const auto buffer = ctx.ReadBuffer(); - const auto text = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast(buffer.data()), buffer.size()); + const auto text = !buffer.empty() + ? Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast(buffer.data()), buffer.size()) + : std::string{}; LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text); @@ -42,8 +49,9 @@ private: void Filter(HLERequestContext& ctx) { const auto buffer = ctx.ReadBuffer(); - const auto text = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast(buffer.data()), buffer.size()); + const auto text = !buffer.empty() + ? Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast(buffer.data()), buffer.size()) + : std::string{}; LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text); @@ -144,11 +152,143 @@ private: } }; +class IServiceWithManagementApi final : public ServiceFramework { +public: + explicit IServiceWithManagementApi(Core::System& system_) : ServiceFramework(system_, "ngct:s") { + // clang-format off + static const FunctionInfo functions[] = { + {0 , nullptr, "Match"}, + {1 , nullptr, "Filter"}, + {100, nullptr, "ConfigureAutoUpdateSetting"}, + {101, nullptr, "RequestResourceUpdateCheck"}, + {110, nullptr, "Reload"}, + {111, nullptr, "IsReloadRequired"}, + {112, nullptr, "TryAcquireReloadRequestNotifier"}, + {120, nullptr, "CalculateContentFingerprint"}, + {130, nullptr, "TryEnableTemporalPassThrough"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +struct SaveDataHandle { + u64 unk0; +}; +static_assert(sizeof(SaveDataHandle) == 0x08); + +class IUserShimScopedObject final : public ServiceFramework { +public: + explicit IUserShimScopedObject(Core::System& system_) : ServiceFramework(system_, "IUserShimScopedObject") { + // clang-format off + static const FunctionInfo functions[] = { + {450, nullptr, "InitializeForSaveData"}, + {451, nullptr, "FinalizeForSaveData"}, + {452, D<&IUserShimScopedObject::OpenSaveData>, "OpenSaveData"}, + {453, nullptr, "CloseSaveData"}, + {454, D<&IUserShimScopedObject::ReadSaveSlot>, "ReadSaveSlot"}, + {455, D<&IUserShimScopedObject::WriteSaveSlot>, "WriteSaveSlot"}, + {456, nullptr, "FlushSaveSlot"}, + {457, nullptr, "CommitSaveData"}, + }; + // clang-format on + RegisterHandlers(functions); + } + + Result OpenSaveData(Account::Uid unk0, Out unk1) { + LOG_WARNING(Service_NGC, "stubbed"); + R_THROW(IPC::ResultNotSupported); + } + + Result ReadSaveSlot(s32 offset, SaveDataHandle handle, OutBuffer out_data, Out out_size) { + LOG_WARNING(Service_NGC, "stubbed"); + R_THROW(IPC::ResultNotSupported); + } + + Result WriteSaveSlot(s32 offset, SaveDataHandle handle, InBuffer out_data) { + LOG_WARNING(Service_NGC, "stubbed"); + // to implement + R_SUCCEED(); + } +}; + +class IUserService final : public ServiceFramework { +public: + explicit IUserService(Core::System& system_) : ServiceFramework(system_, "stpl:u") { + // clang-format off + static const FunctionInfo functions[] = { + {0 , D<&IUserService::Cmd0>, "Cmd0"}, + }; + // clang-format on + RegisterHandlers(functions); + } + Result Cmd0(u32 unk0, OutInterface out_interface) { + LOG_WARNING(Service_NGC, "stubbed"); + *out_interface = std::make_shared(system); + R_SUCCEED(); + } +}; + +class ISystemShimScopedObject final : public ServiceFramework { +public: + explicit ISystemShimScopedObject(Core::System& system_) : ServiceFramework(system_, "ISystemShimScopedObject") { + // clang-format off + static const FunctionInfo functions[] = { + {106, nullptr, "Cmd106"}, + {107, nullptr, "Cmd107"}, + {108, D<&ISystemShimScopedObject::Cmd108>, "Cmd108"}, + {207, nullptr, "Cmd207"}, + {208, D<&ISystemShimScopedObject::Cmd208>, "Cmd208"}, + {209, nullptr, "Cmd209"}, + {210, nullptr, "Cmd210"}, + {211, nullptr, "Cmd211"}, + {212, nullptr, "Cmd212"}, + }; + // clang-format on + RegisterHandlers(functions); + } + + Result Cmd108() { + LOG_WARNING(Service_NGC, "stubbed"); + R_THROW(IPC::ResultNotSupported); + } + + Result Cmd208(Out> unk0) { + LOG_WARNING(Service_NGC, "stubbed"); + R_THROW(IPC::ResultNotSupported); + } +}; + +class ISystemService final : public ServiceFramework { +public: + explicit ISystemService(Core::System& system_) : ServiceFramework(system_, "stpl:sys") { + // clang-format off + static const FunctionInfo functions[] = { + {0 , D<&ISystemService::Cmd0>, "Cmd0"}, + }; + // clang-format on + RegisterHandlers(functions); + } + Result Cmd0(OutInterface out_interface) { + LOG_WARNING(Service_NGC, "stubbed"); + *out_interface = std::make_shared(system); + R_SUCCEED(); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); - server_manager->RegisterNamedService("ngct:u", std::make_shared(system)); - server_manager->RegisterNamedService("ngc:u", std::make_shared(system)); + server_manager->RegisterNamedService("ngct:u", std::make_shared(system), 4); + server_manager->RegisterNamedService("ngct:s", std::make_shared(system), 4); + server_manager->RegisterNamedService("ngc:u", std::make_shared(system), 4); + + // +23.0.0 + if (FirmwareManager::GetFirmwareVersion(system).first.major >= 23) { + server_manager->RegisterNamedService("stpl:u", std::make_shared(system), 4); + server_manager->RegisterNamedService("stpl:sys", std::make_shared(system), 4); + } + ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/ngc/ngc.h b/src/core/hle/service/ngc/ngc.h index 823b1aa819..7dc4a46577 100644 --- a/src/core/hle/service/ngc/ngc.h +++ b/src/core/hle/service/ngc/ngc.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index a0302a5841..efada9b807 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -23,7 +23,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #ifdef _WIN32 @@ -275,7 +275,7 @@ private: } state.store(State::Processing); - evt_processing->Signal(); + evt_processing->Signal(system.Kernel()); worker = std::thread(&IScanRequest::WorkerThread, this); IPC::ResponseBuilder{ctx, 2}.Push(ResultSuccess); @@ -297,7 +297,7 @@ private: void GetSystemEventReadableHandle(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 2}; rb.Push(ResultSuccess); - rb.PushCopyObjects(evt_scan_complete->GetReadableEvent(), + rb.PushCopyObjects(ctx, evt_scan_complete->GetReadableEvent(), evt_processing->GetReadableEvent()); } @@ -326,7 +326,7 @@ private: void Finish(Result rc) { worker_result.store(rc); state.store(State::Finished); - evt_scan_complete->Signal(); + evt_scan_complete->Signal(system.Kernel()); } KernelHelpers::ServiceContext svc_ctx; @@ -457,7 +457,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 2}; rb.Push(ResultSuccess); - rb.PushCopyObjects(event1->GetReadableEvent(), event2->GetReadableEvent()); + rb.PushCopyObjects(ctx, event1->GetReadableEvent(), event2->GetReadableEvent()); } void Cancel(HLERequestContext& ctx) { @@ -491,7 +491,7 @@ private: void UpdateState(RequestState new_state) { LOG_DEBUG(Service_NIFM, "(STUBBED) called"); state = new_state; - event1->Signal(); + event1->Signal(system.Kernel()); } KernelHelpers::ServiceContext service_context; @@ -533,7 +533,7 @@ void IGeneralService::CreateScanRequest(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } void IGeneralService::CreateRequest(HLERequestContext& ctx) { @@ -542,7 +542,7 @@ void IGeneralService::CreateRequest(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } void IGeneralService::GetCurrentNetworkProfile(HLERequestContext& ctx) { @@ -721,7 +721,7 @@ void IGeneralService::GetNetworkProfile(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } void IGeneralService::SetNetworkProfile(HLERequestContext& ctx) { @@ -874,7 +874,7 @@ void IGeneralService::CreateTemporaryNetworkProfile(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 6, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); rb.PushRaw(uuid); } @@ -1129,7 +1129,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } void CreateGeneralService(HLERequestContext& ctx) { @@ -1137,19 +1137,16 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } }; void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); - server_manager->RegisterNamedService("nifm:a", - std::make_shared("nifm:a", system)); - server_manager->RegisterNamedService("nifm:s", - std::make_shared("nifm:s", system)); - server_manager->RegisterNamedService("nifm:u", - std::make_shared("nifm:u", system)); + server_manager->RegisterNamedService("nifm:a", std::make_shared("nifm:a", system), 2); + server_manager->RegisterNamedService("nifm:s", std::make_shared("nifm:s", system), 16); + server_manager->RegisterNamedService("nifm:u", std::make_shared("nifm:u", system), 5); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h index 3b5a438c75..44859e0c51 100644 --- a/src/core/hle/service/nifm/nifm.h +++ b/src/core/hle/service/nifm/nifm.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp index 4f465c5da7..e5f2d85dde 100644 --- a/src/core/hle/service/nim/nim.cpp +++ b/src/core/hle/service/nim/nim.cpp @@ -1,37 +1,140 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 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 #include -#include +#include +#include +#include + #include "core/core.h" #include "core/hle/kernel/k_event.h" +#include "core/hle/service/cmif_serialization.h" +#include "core/hle/service/cmif_types.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/kernel_helpers.h" #include "core/hle/service/nim/nim.h" +#include "core/hle/service/os/event.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/service.h" namespace Service::NIM { - class IShopServiceAsync final : public ServiceFramework { public: explicit IShopServiceAsync(Core::System& system_) - : ServiceFramework{system_, "IShopServiceAsync"} { + : ServiceFramework{system_, "IShopServiceAsync"}, + service_context{system_, "IShopServiceAsync"} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "Cancel"}, - {1, nullptr, "GetSize"}, - {2, nullptr, "Read"}, - {3, nullptr, "GetErrorCode"}, - {4, nullptr, "Request"}, - {5, nullptr, "Prepare"}, + {0, D<&IShopServiceAsync::Cancel>, "Cancel"}, + {1, D<&IShopServiceAsync::GetSize>, "GetSize"}, + {2, D<&IShopServiceAsync::Read>, "Read"}, + {3, D<&IShopServiceAsync::GetErrorCode>, "GetErrorCode"}, + {4, D<&IShopServiceAsync::Request>, "Request"}, + {5, D<&IShopServiceAsync::Prepare>, "Prepare"}, }; // clang-format on RegisterHandlers(functions); + + completion_event = service_context.CreateEvent("IShopServiceAsync:Completion"); + } + + ~IShopServiceAsync() override { + CancelImpl(); + service_context.CloseEvent(completion_event); + } + + Kernel::KReadableEvent* GetEvent() const { + return &completion_event->GetReadableEvent(); + } + +private: + KernelHelpers::ServiceContext service_context; + Kernel::KEvent* completion_event; + + std::jthread worker; + std::atomic error_code{0}; + + std::mutex data_mutex; + std::vector download_data; + + void CancelImpl() { + worker.request_stop(); + if (worker.joinable()) { + worker.join(); + } + } + + Result Cancel() { + LOG_DEBUG(Service_NIM, "called"); + CancelImpl(); + R_SUCCEED(); + } + + Result GetSize(Out out_size) { + LOG_DEBUG(Service_NIM, "called"); + std::scoped_lock lock{data_mutex}; + *out_size = download_data.size(); + R_SUCCEED(); + } + + Result Read(Out out_size, u64 offset, OutBuffer out_buffer) { + std::scoped_lock lock{data_mutex}; + + u64 actual_read = 0; + if (offset < download_data.size()) { + actual_read = std::min(out_buffer.size(), download_data.size() - offset); + std::memcpy(out_buffer.data(), download_data.data() + offset, actual_read); + } + + *out_size = actual_read; + R_SUCCEED(); + } + + Result GetErrorCode(Out out_error_code) { + LOG_DEBUG(Service_NIM, "called"); + *out_error_code = error_code.load(); + R_SUCCEED(); + } + + Result Request() { + LOG_DEBUG(Service_NIM, "(STUBBED) called"); + CancelImpl(); + + error_code.store(0); + completion_event->Clear(system.Kernel()); + + { + std::scoped_lock lock{data_mutex}; + download_data.clear(); + } + + worker = std::jthread([this](const std::stop_token& stop_token) { + if (stop_token.stop_requested()) { + error_code.store(1); + } else { + std::scoped_lock lock{data_mutex}; + // Dummy JSON response, else it fails... + const std::string dummy_response = "{}"; + download_data.assign(dummy_response.begin(), dummy_response.end()); + error_code.store(0); + } + completion_event->Signal(system.Kernel()); + }); + + R_SUCCEED(); + } + + Result Prepare(InArray in_path, InArray in_post) { + LOG_DEBUG(Service_NIM, "called"); + if (!in_path.empty()) { + std::string url(in_path.data(), in_path.size()); + LOG_INFO(Service_NIM, "Preparing request for URL: {}", url); + } + R_SUCCEED(); } }; @@ -49,11 +152,13 @@ public: } private: - void CreateAsyncInterface(HLERequestContext& ctx) { - LOG_WARNING(Service_NIM, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + void CreateAsyncInterface(HLERequestContext& ctx) {LOG_DEBUG(Service_NIM, "called"); + auto async_interface = std::make_shared(system); + + IPC::ResponseBuilder rb{ctx, 2, 1, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushCopyObjects(ctx, async_interface->GetEvent()); + rb.PushIpcInterface(ctx, std::move(async_interface)); } }; @@ -75,7 +180,7 @@ private: LOG_WARNING(Service_NIM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } }; @@ -336,7 +441,7 @@ private: LOG_DEBUG(Service_NIM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } void IsLargeResourceAvailable(HLERequestContext& ctx) { @@ -356,7 +461,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } }; @@ -429,7 +534,7 @@ private: void StartTask(HLERequestContext& ctx) { // No need to connect to the internet, just finish the task straight away. LOG_DEBUG(Service_NIM, "called"); - finished_event->Signal(); + finished_event->Signal(system.Kernel()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } @@ -439,7 +544,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(finished_event->GetReadableEvent()); + rb.PushCopyObjects(ctx, finished_event->GetReadableEvent()); } void GetResult(HLERequestContext& ctx) { @@ -451,7 +556,7 @@ private: void Cancel(HLERequestContext& ctx) { LOG_DEBUG(Service_NIM, "called"); - finished_event->Clear(); + finished_event->Clear(system.Kernel()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } @@ -500,7 +605,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } // TODO(ogniK): Do we need these? @@ -519,11 +624,25 @@ private: } }; +class NIM_ECAS final : public ServiceFramework { +public: + explicit NIM_ECAS(Core::System& system_) : ServiceFramework{system_, "nim:ecas"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "RegisterSpecialClient"}, + {1, nullptr, "UnregisterSpecialClient"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); server_manager->RegisterNamedService("nim", std::make_shared(system)); server_manager->RegisterNamedService("nim:eca", std::make_shared(system)); + server_manager->RegisterNamedService("nim:ecas", std::make_shared(system)); server_manager->RegisterNamedService("nim:shp", std::make_shared(system)); server_manager->RegisterNamedService("ntc", std::make_shared(system)); ServerManager::RunServer(std::move(server_manager)); diff --git a/src/core/hle/service/nim/nim.h b/src/core/hle/service/nim/nim.h index e7d599908d..0fad2a71af 100644 --- a/src/core/hle/service/nim/nim.h +++ b/src/core/hle/service/nim/nim.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/npns/npns.h b/src/core/hle/service/npns/npns.h index 0019fca763..e54a91a213 100644 --- a/src/core/hle/service/npns/npns.h +++ b/src/core/hle/service/npns/npns.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/account_proxy_interface.cpp b/src/core/hle/service/ns/account_proxy_interface.cpp index e5041af66b..2e3dcf928d 100644 --- a/src/core/hle/service/ns/account_proxy_interface.cpp +++ b/src/core/hle/service/ns/account_proxy_interface.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/account_proxy_interface.h b/src/core/hle/service/ns/account_proxy_interface.h index e944d2a75f..14cec4ddbc 100644 --- a/src/core/hle/service/ns/account_proxy_interface.h +++ b/src/core/hle/service/ns/account_proxy_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/application_manager_interface.cpp b/src/core/hle/service/ns/application_manager_interface.cpp index 5490c09d03..348649917e 100644 --- a/src/core/hle/service/ns/application_manager_interface.cpp +++ b/src/core/hle/service/ns/application_manager_interface.cpp @@ -9,6 +9,7 @@ #include "core/file_sys/registered_cache.h" #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ns/application_manager_interface.h" #include "core/file_sys/content_archive.h" @@ -19,6 +20,7 @@ #include "core/launch_timestamp_cache.h" #include +#include #include namespace Service::NS { @@ -36,14 +38,14 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ {1, nullptr, "GenerateApplicationRecordCount"}, {2, D<&IApplicationManagerInterface::GetApplicationRecordUpdateSystemEvent>, "GetApplicationRecordUpdateSystemEvent"}, {3, nullptr, "GetApplicationViewDeprecated"}, - {4, nullptr, "DeleteApplicationEntity"}, - {5, nullptr, "DeleteApplicationCompletely"}, + {4, D<&IApplicationManagerInterface::DeleteApplicationEntity>, "DeleteApplicationEntity"}, + {5, D<&IApplicationManagerInterface::DeleteApplicationCompletely>, "DeleteApplicationCompletely"}, {6, nullptr, "IsAnyApplicationEntityRedundant"}, {7, nullptr, "DeleteRedundantApplicationEntity"}, {8, nullptr, "IsApplicationEntityMovable"}, {9, nullptr, "MoveApplicationEntity"}, {11, nullptr, "CalculateApplicationOccupiedSize"}, - {16, nullptr, "PushApplicationRecord"}, + {16, &IApplicationManagerInterface::PushApplicationRecord, "PushApplicationRecord"}, {17, nullptr, "ListApplicationRecordContentMeta"}, {19, nullptr, "LaunchApplicationOld"}, {21, nullptr, "GetApplicationContentPath"}, @@ -137,7 +139,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ {405, nullptr, "ListApplicationControlCacheEntryInfo"}, {406, nullptr, "GetApplicationControlProperty"}, {407, &IApplicationManagerInterface::ListApplicationTitle, "ListApplicationTitle"}, - {408, nullptr, "ListApplicationIcon"}, + {408, &IApplicationManagerInterface::ListApplicationIcon, "ListApplicationIcon"}, {411, nullptr, "Unknown411"}, //19.0.0+ {412, nullptr, "Unknown412"}, //19.0.0+ {413, nullptr, "Unknown413"}, //19.0.0+ @@ -226,9 +228,9 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ {930, nullptr, "Unknown930"}, //20.0.0+ {931, nullptr, "Unknown931"}, //20.0.0+ {933, nullptr, "Unknown933"}, //20.0.0+ - {934, nullptr, "Unknown934"}, //20.0.0+ - {935, nullptr, "Unknown935"}, //20.0.0+ - {936, nullptr, "Unknown936"}, //20.0.0+ + {934, nullptr, "Unknown934"}, //21.0.0+ + {935, nullptr, "Unknown935"}, //21.0.0+ + {936, D<&IApplicationManagerInterface::Unknown936>, "Unknown936"}, //21.0.0+ {1000, nullptr, "RequestVerifyApplicationDeprecated"}, {1001, nullptr, "CorruptApplicationForDebug"}, {1002, nullptr, "RequestVerifyAddOnContentsRights"}, @@ -420,7 +422,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ {4039, nullptr, "Unknown4039"}, //20.0.0+ {4040, nullptr, "Unknown4040"}, //20.0.0+ {4041, nullptr, "Unknown4041"}, //20.0.0+ - {4042, nullptr, "Unknown4042"}, //20.0.0+ + {4042, D<&IApplicationManagerInterface::Unknown4042>, "Unknown4042"}, //20.0.0+ {4043, nullptr, "Unknown4043"}, //20.0.0+ {4044, nullptr, "Unknown4044"}, //20.0.0+ {4045, nullptr, "Unknown4045"}, //20.0.0+ @@ -474,6 +476,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ {4096, nullptr, "Unknown4096"}, //20.0.0+ {4097, nullptr, "Unknown4097"}, //20.0.0+ {4099, nullptr, "Unknown4099"}, //21.0.0+ + {4105, D<&IApplicationManagerInterface::Unknown4105>, "Unknown4105"}, //23.0.0+ {5000, nullptr, "Unknown5000"}, //18.0.0+ {5001, nullptr, "Unknown5001"}, //18.0.0+ {9999, nullptr, "GetApplicationCertificate"}, //10.0.0-10.2.0 @@ -610,7 +613,7 @@ Result IApplicationManagerInterface::GetApplicationRecordUpdateSystemEvent( OutCopyHandle out_event) { LOG_WARNING(Service_NS, "(STUBBED) called"); - record_update_system_event.Signal(); + record_update_system_event.Signal(system.Kernel()); *out_event = record_update_system_event.GetHandle(); R_SUCCEED(); @@ -636,6 +639,12 @@ Result IApplicationManagerInterface::IsGameCardApplicationRunning(Out out_ R_SUCCEED(); } +Result IApplicationManagerInterface::Unknown936(Out out_result) { + LOG_WARNING(Service_NS, "(STUBBED) called."); + *out_result = 0; + R_SUCCEED(); +} + Result IApplicationManagerInterface::IsAnyApplicationEntityInstalled( Out out_is_any_application_entity_installed) { LOG_WARNING(Service_NS, "(STUBBED) called"); @@ -643,6 +652,27 @@ Result IApplicationManagerInterface::IsAnyApplicationEntityInstalled( R_SUCCEED(); } +Result IApplicationManagerInterface::DeleteApplicationEntity(u64 application_id) { + LOG_DEBUG(Service_NS, "called, application_id={:016X}", application_id); + + auto& fsc = system.GetFileSystemController(); + if (auto* const user_cache = fsc.GetUserNANDContents(); user_cache != nullptr) { + user_cache->RemoveExistingEntry(application_id); + user_cache->Refresh(); + } + if (auto* const sdmc_cache = fsc.GetSDMCContents(); sdmc_cache != nullptr) { + sdmc_cache->RemoveExistingEntry(application_id); + sdmc_cache->Refresh(); + } + + record_update_system_event.Signal(system.Kernel()); + R_SUCCEED(); +} + +Result IApplicationManagerInterface::DeleteApplicationCompletely(u64 application_id) { + R_RETURN(DeleteApplicationEntity(application_id)); +} + Result IApplicationManagerInterface::GetApplicationViewDeprecated( OutArray out_application_views, InArray application_ids) { @@ -820,14 +850,14 @@ Result IApplicationManagerInterface::RequestDownloadApplicationControlDataInBack LOG_INFO(Service_NS, "called, control_source={} app={:016X}", control_source, application_id); - unknown_event.Signal(); + unknown_event.Signal(system.Kernel()); R_SUCCEED(); } Result IApplicationManagerInterface::Unknown4022( OutCopyHandle out_event) { LOG_WARNING(Service_NS, "(STUBBED) called"); - unknown_event.Signal(); + unknown_event.Signal(system.Kernel()); *out_event = unknown_event.GetHandle(); R_SUCCEED(); } @@ -838,14 +868,56 @@ Result IApplicationManagerInterface::Unknown4023(Out out_result) { R_SUCCEED(); } +Result IApplicationManagerInterface::Unknown4042(OutInterface out_interface, + OutCopyHandle out_event, + u64 arg1, u64 arg2) { + LOG_WARNING(Service_NS, "(STUBBED) called, arg1={:016X}, arg2={:016X}", arg1, arg2); + *out_event = unknown_event.GetHandle(); + *out_interface = std::make_shared(system, &unknown_event); + R_SUCCEED(); +} + Result IApplicationManagerInterface::Unknown4053() { LOG_WARNING(Service_NS, "(STUBBED) called."); R_SUCCEED(); } +Result IApplicationManagerInterface::Unknown4105() { + LOG_WARNING(Service_NS, "(STUBBED) called."); + R_SUCCEED(); +} + +void IApplicationManagerInterface::PushApplicationRecord(HLERequestContext& ctx) { + const auto record = ctx.ReadBuffer(); + u64 application_id{}; + if (record.size() >= sizeof(application_id)) { + std::memcpy(&application_id, record.data(), sizeof(application_id)); + } + + LOG_DEBUG(Service_NS, "called, application_id={:016X}, size={}", application_id, record.size()); + + auto& fsc = system.GetFileSystemController(); + if (auto* const user_cache = fsc.GetUserNANDContents(); user_cache != nullptr) { + user_cache->Refresh(); + } + if (auto* const sdmc_cache = fsc.GetSDMCContents(); sdmc_cache != nullptr) { + sdmc_cache->Refresh(); + } + + record_update_system_event.Signal(system.Kernel()); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + void IApplicationManagerInterface::ListApplicationTitle(HLERequestContext& ctx) { LOG_DEBUG(Service_NS, "called"); IReadOnlyApplicationControlDataInterface(system).ListApplicationTitle(ctx); } +void IApplicationManagerInterface::ListApplicationIcon(HLERequestContext& ctx) { + LOG_DEBUG(Service_NS, "called"); + IReadOnlyApplicationControlDataInterface(system).ListApplicationIcon(ctx); +} + } // 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 245d59a068..c4ca66f319 100644 --- a/src/core/hle/service/ns/application_manager_interface.h +++ b/src/core/hle/service/ns/application_manager_interface.h @@ -7,6 +7,7 @@ #pragma once #include "core/hle/service/cmif_types.h" +#include "core/hle/service/ns/async_result.h" #include "core/hle/service/ns/language.h" #include "core/hle/service/ns/ns_types.h" #include "core/hle/service/os/event.h" @@ -34,6 +35,7 @@ public: Result GetGameCardMountFailureEvent(OutCopyHandle out_event); Result GetGameCardWakenReadyEvent(OutCopyHandle out_event); Result IsGameCardApplicationRunning(Out out_is_running); + Result Unknown936(Out out_result); Result IsAnyApplicationEntityInstalled(Out out_is_any_application_entity_installed); Result GetApplicationViewDeprecated( OutArray out_application_views, @@ -56,6 +58,8 @@ public: Result ResumeAll(); Result IsQualificationTransitionSupportedByProcessId(Out out_is_supported, u64 process_id); + Result DeleteApplicationEntity(u64 application_id); + Result DeleteApplicationCompletely(u64 application_id); Result GetStorageSize(Out out_total_space_size, Out out_free_space_size, FileSys::StorageId storage_id); Result TouchApplication(u64 application_id); @@ -69,12 +73,18 @@ public: InBuffer logo_path_buffer); Result Unknown4022(OutCopyHandle out_event); Result Unknown4023(Out out_result); + Result Unknown4042(OutInterface out_interface, + OutCopyHandle out_event, + u64 arg1, u64 arg2); Result Unknown4053(); + Result Unknown4105(); Result RequestDownloadApplicationControlDataInBackground(u64 control_source, u64 application_id); + void PushApplicationRecord(HLERequestContext& ctx); void ListApplicationTitle(HLERequestContext& ctx); + void ListApplicationIcon(HLERequestContext& ctx); private: KernelHelpers::ServiceContext service_context; diff --git a/src/core/hle/service/ns/application_version_interface.cpp b/src/core/hle/service/ns/application_version_interface.cpp index b89e127dbe..0ed9514c2c 100644 --- a/src/core/hle/service/ns/application_version_interface.cpp +++ b/src/core/hle/service/ns/application_version_interface.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/application_version_interface.h b/src/core/hle/service/ns/application_version_interface.h index b288cff1be..fcbb966a4d 100644 --- a/src/core/hle/service/ns/application_version_interface.h +++ b/src/core/hle/service/ns/application_version_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/async_result.cpp b/src/core/hle/service/ns/async_result.cpp new file mode 100644 index 0000000000..8617280bef --- /dev/null +++ b/src/core/hle/service/ns/async_result.cpp @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/cmif_serialization.h" +#include "core/hle/service/ns/async_result.h" + +#include + +namespace Service::NS { + +IAsyncResult::IAsyncResult(Core::System& system_, Service::Event* event_) + : ServiceFramework{system_, "nn::ns::detail::IAsyncResult"}, event{event_} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "Get"}, + {1, D<&IAsyncResult::Cancel>, "Cancel"}, + {2, nullptr, "GetErrorContext"}, // 4.0.0+ + }; + // clang-format on + + RegisterHandlers(functions); +} + +IAsyncResult::~IAsyncResult() = default; + +Result IAsyncResult::Cancel() { + LOG_DEBUG(Service_NS, "called"); + if (event != nullptr) { + event->Signal(system.Kernel()); + } + + R_SUCCEED(); +} + +} // namespace Service::NS diff --git a/src/core/hle/service/ns/async_result.h b/src/core/hle/service/ns/async_result.h new file mode 100644 index 0000000000..914ea5a42c --- /dev/null +++ b/src/core/hle/service/ns/async_result.h @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/hle/service/service.h" + +namespace Service::NS { + +class IAsyncResult final : public ServiceFramework { +public: + explicit IAsyncResult(Core::System& system_, Service::Event* event_); + ~IAsyncResult() override; + +private: + Result Cancel(); + + Service::Event* event{}; +}; + +} // namespace Service::NS \ No newline at end of file diff --git a/src/core/hle/service/ns/develop_interface.cpp b/src/core/hle/service/ns/develop_interface.cpp index 880bdbebbf..5a44c61f1b 100644 --- a/src/core/hle/service/ns/develop_interface.cpp +++ b/src/core/hle/service/ns/develop_interface.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/develop_interface.h b/src/core/hle/service/ns/develop_interface.h index a9f81ccd60..d7cb9e7a56 100644 --- a/src/core/hle/service/ns/develop_interface.h +++ b/src/core/hle/service/ns/develop_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/document_interface.cpp b/src/core/hle/service/ns/document_interface.cpp index 51a1e46c0d..00593d1edc 100644 --- a/src/core/hle/service/ns/document_interface.cpp +++ b/src/core/hle/service/ns/document_interface.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/document_interface.h b/src/core/hle/service/ns/document_interface.h index cd461652cf..4c8b15b54a 100644 --- a/src/core/hle/service/ns/document_interface.h +++ b/src/core/hle/service/ns/document_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/download_task_interface.cpp b/src/core/hle/service/ns/download_task_interface.cpp index 62dc7f1874..18db60e7b0 100644 --- a/src/core/hle/service/ns/download_task_interface.cpp +++ b/src/core/hle/service/ns/download_task_interface.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/download_task_interface.h b/src/core/hle/service/ns/download_task_interface.h index b1cb69cb86..1c46339daa 100644 --- a/src/core/hle/service/ns/download_task_interface.h +++ b/src/core/hle/service/ns/download_task_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/ecommerce_interface.cpp b/src/core/hle/service/ns/ecommerce_interface.cpp index 76fc425f0c..b8794bb5fb 100644 --- a/src/core/hle/service/ns/ecommerce_interface.cpp +++ b/src/core/hle/service/ns/ecommerce_interface.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/ecommerce_interface.h b/src/core/hle/service/ns/ecommerce_interface.h index 4352101f4a..6ff9512123 100644 --- a/src/core/hle/service/ns/ecommerce_interface.h +++ b/src/core/hle/service/ns/ecommerce_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/factory_reset_interface.cpp b/src/core/hle/service/ns/factory_reset_interface.cpp index fd5cf7e1fd..11cdefb4be 100644 --- a/src/core/hle/service/ns/factory_reset_interface.cpp +++ b/src/core/hle/service/ns/factory_reset_interface.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/factory_reset_interface.h b/src/core/hle/service/ns/factory_reset_interface.h index 50d1251239..8181d6b379 100644 --- a/src/core/hle/service/ns/factory_reset_interface.h +++ b/src/core/hle/service/ns/factory_reset_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp index 8402e83cb1..fe057422ce 100644 --- a/src/core/hle/service/ns/ns.cpp +++ b/src/core/hle/service/ns/ns.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -7,37 +10,92 @@ #include "core/hle/service/ns/query_service.h" #include "core/hle/service/ns/service_getter_interface.h" #include "core/hle/service/ns/system_update_interface.h" -#include "core/hle/service/ns/vulnerability_manager_interface.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/cmif_serialization.h" +#include "core/hle/service/cmif_types.h" #include "core/hle/service/server_manager.h" namespace Service::NS { +class INotifyService final : public ServiceFramework { +public: + explicit INotifyService(Core::System& system_) : ServiceFramework{system_, "pdm:ntfy"} { + // clang-format off + static const FunctionInfo functions[] = { + { 0, nullptr, "NotifyAppletEvent" }, + { 2, nullptr, "NotifyOperationModeChangeEvent" }, + { 3, nullptr, "NotifyPowerStateChangeEvent" }, + { 4, nullptr, "NotifyClearAllEvent" }, + { 5, nullptr, "NotifyEventForDebug" }, + { 6, nullptr, "SuspendUserAccountEventService" }, + { 7, nullptr, "ResumeUserAccountEventService" }, + { 8, nullptr, "NotifyLibraryAppletEvent" }, + { 9, nullptr, "Cmd9" }, + { 20, nullptr, "Cmd20" }, + { 30, nullptr, "Cmd30" }, + { 100, nullptr, "Cmd100" }, + { 101, nullptr, "Cmd101" }, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class IVulnerabilityManagerInterface final + : public ServiceFramework { +public: + explicit IVulnerabilityManagerInterface(Core::System& system_) + : ServiceFramework{system_, "ns:vm"} { + // clang-format off + static const FunctionInfo functions[] = { + {1200, D<&IVulnerabilityManagerInterface::NeedsUpdateVulnerability>, "NeedsUpdateVulnerability"}, + {1201, nullptr, "UpdateSafeSystemVersionForDebug"}, + {1202, nullptr, "GetSafeSystemVersion"}, + {3100, D<&IVulnerabilityManagerInterface::GetSafeSystemVersionCheckInfo>, "GetSafeSystemVersionCheckInfo"}, + {3101, nullptr, "RequestUpdateSafeSystemVersionCheckInfo"}, + {3102, D<&IVulnerabilityManagerInterface::ResetSafeSystemVersionCheckInfo>, "ResetSafeSystemVersionCheckInfo"}, + }; + // clang-format on + RegisterHandlers(functions); + } + ~IVulnerabilityManagerInterface() override = default; + + Result NeedsUpdateVulnerability(Out out_needs_update_vulnerability) { + LOG_WARNING(Service_NS, "(STUBBED)"); + *out_needs_update_vulnerability = false; + R_SUCCEED(); + } + + Result GetSafeSystemVersionCheckInfo(Out> out_system_version_check_info) { + LOG_WARNING(Service_NS, "(STUBBED)"); + *out_system_version_check_info = {}; + R_SUCCEED(); + } + + Result ResetSafeSystemVersionCheckInfo() { + LOG_WARNING(Service_NS, "(STUBBED)"); + R_SUCCEED(); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); - server_manager->RegisterNamedService( - "ns:am2", std::make_shared(system, "ns:am2")); - server_manager->RegisterNamedService( - "ns:ec", std::make_shared(system, "ns:ec")); - server_manager->RegisterNamedService( - "ns:rid", std::make_shared(system, "ns:rid")); - server_manager->RegisterNamedService( - "ns:rt", std::make_shared(system, "ns:rt")); - server_manager->RegisterNamedService( - "ns:web", std::make_shared(system, "ns:web")); - server_manager->RegisterNamedService( - "ns:ro", std::make_shared(system, "ns:ro")); + server_manager->RegisterNamedService("ns:am2", std::make_shared(system, "ns:am2"), 5); + server_manager->RegisterNamedService("ns:ec", std::make_shared(system, "ns:ec"), 5); + server_manager->RegisterNamedService("ns:rid", std::make_shared(system, "ns:rid"), 5); + server_manager->RegisterNamedService("ns:rt", std::make_shared(system, "ns:rt"), 5); + server_manager->RegisterNamedService("ns:web", std::make_shared(system, "ns:web"), 5); + server_manager->RegisterNamedService("ns:ro", std::make_shared(system, "ns:ro"), 5); - server_manager->RegisterNamedService("ns:dev", std::make_shared(system)); - server_manager->RegisterNamedService("ns:su", std::make_shared(system)); - server_manager->RegisterNamedService("ns:vm", - std::make_shared(system)); + server_manager->RegisterNamedService("ns:dev", std::make_shared(system), 5); + server_manager->RegisterNamedService("ns:su", std::make_shared(system), 5); + server_manager->RegisterNamedService("ns:vm", std::make_shared(system), 5); + server_manager->RegisterNamedService("pdm:ntfy", std::make_shared(system)); server_manager->RegisterNamedService("pdm:qry", std::make_shared(system)); - server_manager->RegisterNamedService("pl:s", - std::make_shared(system, "pl:s")); - server_manager->RegisterNamedService("pl:u", - std::make_shared(system, "pl:u")); + server_manager->RegisterNamedService("pl:s", std::make_shared(system, "pl:s")); + server_manager->RegisterNamedService("pl:u", std::make_shared(system, "pl:u")); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h index f79b4ae3d6..2b14d115ca 100644 --- a/src/core/hle/service/ns/ns.h +++ b/src/core/hle/service/ns/ns.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/ns_results.h b/src/core/hle/service/ns/ns_results.h index 16d2ea6f7b..52f4c0ea59 100644 --- a/src/core/hle/service/ns/ns_results.h +++ b/src/core/hle/service/ns/ns_results.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/platform_service_manager.h b/src/core/hle/service/ns/platform_service_manager.h index b82c385a6c..73521abbc2 100644 --- a/src/core/hle/service/ns/platform_service_manager.h +++ b/src/core/hle/service/ns/platform_service_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/query_service.cpp b/src/core/hle/service/ns/query_service.cpp index ea605cb831..d22de1d169 100644 --- a/src/core/hle/service/ns/query_service.cpp +++ b/src/core/hle/service/ns/query_service.cpp @@ -38,6 +38,12 @@ IQueryService::IQueryService(Core::System& system_) : ServiceFramework{system_, {17, D<&IQueryService::QueryLastPlayTime>, "QueryLastPlayTime"}, {18, D<&IQueryService::QueryApplicationPlayStatisticsForSystem>, "QueryApplicationPlayStatisticsForSystem"}, {19, D<&IQueryService::QueryApplicationPlayStatisticsByUserAccountIdForSystem>, "QueryApplicationPlayStatisticsByUserAccountIdForSystem"}, + { 30, nullptr, "Cmd30" }, + { 31, nullptr, "Cmd31" }, + { 100, nullptr, "Cmd100" }, + { 110, nullptr, "Cmd110" }, + { 118, nullptr, "Cmd118" }, + { 119, nullptr, "Cmd119" }, }; // clang-format on diff --git a/src/core/hle/service/ns/read_only_application_control_data_interface.cpp b/src/core/hle/service/ns/read_only_application_control_data_interface.cpp index 03f0a17865..1359cdbb95 100644 --- a/src/core/hle/service/ns/read_only_application_control_data_interface.cpp +++ b/src/core/hle/service/ns/read_only_application_control_data_interface.cpp @@ -7,17 +7,20 @@ #include #include #include -#include -#include -#include #include +#include "common/stb.h" +#include "common/logging.h" #include "common/settings.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/vfs/vfs.h" +#include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_transfer_memory.h" +#include "core/hle/result.h" #include "core/hle/service/cmif_serialization.h" +#include "core/hle/service/cmif_types.h" +#include "core/hle/service/hle_ipc.h" #include "core/hle/service/ns/language.h" #include "core/hle/service/ns/ns_types.h" #include "core/hle/service/ns/ns_results.h" @@ -73,24 +76,26 @@ void SanitizeJPEGImageSize(std::vector& image) { // IAsyncValue implementation for ListApplicationTitle // https://switchbrew.org/wiki/NS_services#ListApplicationTitle -class IAsyncValueForListApplicationTitle final : public ServiceFramework { +class IAsyncValue final : public ServiceFramework { public: - explicit IAsyncValueForListApplicationTitle(Core::System& system_, s32 offset, s32 size) - : ServiceFramework{system_, "IAsyncValue"}, service_context{system_, "IAsyncValue"}, - data_offset{offset}, data_size{size} { + explicit IAsyncValue(Core::System& system_, s32 offset, s32 size) + : ServiceFramework{system_, "IAsyncValue"} + , service_context{system_, "IAsyncValue"} + , data_offset{offset} + , data_size{size} + { static const FunctionInfo functions[] = { - {0, &IAsyncValueForListApplicationTitle::GetSize, "GetSize"}, - {1, &IAsyncValueForListApplicationTitle::Get, "Get"}, - {2, &IAsyncValueForListApplicationTitle::Cancel, "Cancel"}, - {3, &IAsyncValueForListApplicationTitle::GetErrorContext, "GetErrorContext"}, + {0, D<&IAsyncValue::GetSize>, "GetSize"}, + {1, D<&IAsyncValue::Get>, "Get"}, + {2, D<&IAsyncValue::Cancel>, "Cancel"}, + {3, D<&IAsyncValue::GetErrorContext>, "GetErrorContext"}, }; RegisterHandlers(functions); - completion_event = service_context.CreateEvent("IAsyncValue:Completion"); - completion_event->GetReadableEvent().Signal(); + completion_event->GetReadableEvent().Signal(system.Kernel()); } - ~IAsyncValueForListApplicationTitle() override { + ~IAsyncValue() override { service_context.CloseEvent(completion_event); } @@ -99,35 +104,24 @@ public: } private: - void GetSize(HLERequestContext& ctx) { + Result GetSize(Out out_data_size) { LOG_DEBUG(Service_NS, "called"); - IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(ResultSuccess); - rb.Push(data_size); + *out_data_size = data_size; + R_SUCCEED(); } - - void Get(HLERequestContext& ctx) { + Result Get(OutBuffer out_data_offset) { LOG_DEBUG(Service_NS, "called"); - std::vector buffer(sizeof(s32)); - std::memcpy(buffer.data(), &data_offset, sizeof(s32)); - ctx.WriteBuffer(buffer); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + std::memcpy(out_data_offset.data(), &data_offset, sizeof(s32)); + R_SUCCEED(); } - - void Cancel(HLERequestContext& ctx) { + Result Cancel() { LOG_DEBUG(Service_NS, "called"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + R_SUCCEED(); } - - void GetErrorContext(HLERequestContext& ctx) { + Result GetErrorContext() { LOG_DEBUG(Service_NS, "called"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + R_SUCCEED(); } - KernelHelpers::ServiceContext service_context; Kernel::KEvent* completion_event{}; s32 data_offset; @@ -145,8 +139,10 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa {3, nullptr, "ConvertLanguageCodeToApplicationLanguage"}, {4, nullptr, "SelectApplicationDesiredLanguage"}, {5, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData2>, "GetApplicationControlData"}, + {10, &IReadOnlyApplicationControlDataInterface::ListApplicationIcon, "ListApplicationIcon"}, {13, &IReadOnlyApplicationControlDataInterface::ListApplicationTitle, "ListApplicationTitle"}, {19, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlData"}, + {23, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlData"}, //23.0.0+ }; // clang-format on @@ -161,8 +157,7 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData( LOG_INFO(Service_NS, "called with control_source={}, application_id={:016X}", application_control_source, application_id); - const FileSys::PatchManager pm{application_id, system.GetFileSystemController(), - system.GetContentProvider()}; + const FileSys::PatchManager pm{application_id, system.GetFileSystemController(), system.GetContentProvider()}; const auto control = pm.GetControlMetadata(); const auto size = out_buffer.size(); @@ -170,8 +165,7 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData( const auto total_size = sizeof(FileSys::RawNACP) + icon_size; if (size < total_size) { - LOG_ERROR(Service_NS, "output buffer is too small! (actual={:016X}, expected_min=0x4000)", - size); + LOG_ERROR(Service_NS, "output buffer is too small! (actual={:016X}, expected_min=0x4000)", size); R_THROW(ResultUnknown); } @@ -179,8 +173,7 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData( const auto bytes = control.first->GetRawBytes(); std::memcpy(out_buffer.data(), bytes.data(), bytes.size()); } else { - LOG_WARNING(Service_NS, "missing NACP data for application_id={:016X}, defaulting to zero", - application_id); + LOG_WARNING(Service_NS, "missing NACP data for application_id={:016X}, defaulting to zero", application_id); std::memset(out_buffer.data(), 0, sizeof(FileSys::RawNACP)); } @@ -205,15 +198,12 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationDesiredLanguage( // Convert to application language, get priority list const auto application_language = ConvertToApplicationLanguage(language_code); if (application_language == std::nullopt) { - LOG_ERROR(Service_NS, "Could not convert application language! language_code={}", - language_code); + LOG_ERROR(Service_NS, "Could not convert application language! language_code={}", language_code); R_THROW(Service::NS::ResultApplicationLanguageNotFound); } const auto priority_list = GetApplicationLanguagePriorityList(*application_language); if (!priority_list) { - LOG_ERROR(Service_NS, - "Could not find application language priorities! application_language={}", - *application_language); + LOG_ERROR(Service_NS, "Could not find application language priorities! application_language={}", *application_language); R_THROW(Service::NS::ResultApplicationLanguageNotFound); } @@ -257,8 +247,7 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData2( const auto nacp_size = sizeof(FileSys::RawNACP); if (size < nacp_size) { - LOG_ERROR(Service_NS, "output buffer is too small! (actual={:016X}, expected_min={:08X})", - size, nacp_size); + LOG_ERROR(Service_NS, "output buffer is too small! (actual={:016X}, expected_min={:08X})", size, nacp_size); R_THROW(ResultUnknown); } @@ -309,15 +298,54 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData2( R_SUCCEED(); } +void IReadOnlyApplicationControlDataInterface::ListApplicationIcon(HLERequestContext& ctx) { + LOG_WARNING(Service_NS, "(stubbed)"); + const auto app_ids_buffer = ctx.ReadBuffer(); + const u64 app_count = app_ids_buffer.size() / sizeof(u64); + auto t_mem_obj = ctx.GetObjectFromHandle(ctx.GetCopyHandle(0)); + auto* t_mem = t_mem_obj.GetPointerUnsafe(); + size_t out_length = 0; + if (t_mem != nullptr && t_mem->GetOwner() != nullptr && app_count > 0) { + auto& memory = t_mem->GetOwner()->GetMemory(); + const auto t_mem_address = t_mem->GetSourceAddress(); + // u64 - app count + memory.WriteBlock(t_mem_address + out_length, &app_count, sizeof(u64)); + out_length += sizeof(u64); + ASSERT(out_length <= t_mem->GetSize()); + // [list of u64] - size of icons + for (size_t i = 0; i < app_count; ++i) { + const u64 app_id = app_ids_buffer[i]; + const FileSys::PatchManager pm{app_id, system.GetFileSystemController(), system.GetContentProvider()}; + if (const auto control = pm.GetControlMetadata(); control.second) { + u64 full_size = control.second->GetSize(); + memory.WriteBlock(t_mem_address + out_length, &full_size, sizeof(u64)); + } + out_length += sizeof(u64); + ASSERT(out_length <= t_mem->GetSize()); + } + // [list of raw icon data] + for (size_t i = 0; i < app_count; ++i) { + const u64 app_id = app_ids_buffer[i]; + const FileSys::PatchManager pm{app_id, system.GetFileSystemController(), system.GetContentProvider()}; + if (const auto control = pm.GetControlMetadata(); control.second) { + if (auto const full_size = control.second->GetSize(); full_size > 0) { + std::vector full_icon_data(full_size); + control.second->Read(full_icon_data.data(), full_size, 0); + memory.WriteBlock(t_mem_address + out_length, full_icon_data.data(), full_size); + out_length += full_size; + ASSERT(out_length <= t_mem->GetSize()); + } + } + } + } + auto async_value = std::make_shared(system, 0, s32(out_length)); + IPC::ResponseBuilder rb{ctx, 2, 1, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(ctx, async_value->ReadableEvent()); + rb.PushIpcInterface(ctx, std::move(async_value)); +} void IReadOnlyApplicationControlDataInterface::ListApplicationTitle(HLERequestContext& ctx) { - /* - IPC::RequestParser rp{ctx}; - auto control_source = rp.PopRaw(); - rp.Skip(7, false); - auto transfer_memory_size = rp.Pop(); - */ - const auto app_ids_buffer = ctx.ReadBuffer(); const size_t app_count = app_ids_buffer.size() / sizeof(u64); @@ -328,44 +356,33 @@ void IReadOnlyApplicationControlDataInterface::ListApplicationTitle(HLERequestCo auto t_mem_obj = ctx.GetObjectFromHandle(ctx.GetCopyHandle(0)); auto* t_mem = t_mem_obj.GetPointerUnsafe(); - constexpr size_t title_entry_size = sizeof(FileSys::LanguageEntry); const size_t total_data_size = app_count * title_entry_size; - constexpr s32 data_offset = 0; - if (t_mem != nullptr && app_count > 0) { auto& memory = system.ApplicationMemory(); const auto t_mem_address = t_mem->GetSourceAddress(); - for (size_t i = 0; i < app_count; ++i) { const u64 app_id = application_ids[i]; const FileSys::PatchManager pm{app_id, system.GetFileSystemController(), system.GetContentProvider()}; const auto control = pm.GetControlMetadata(); - FileSys::LanguageEntry entry{}; if (control.first != nullptr) { entry = control.first->GetLanguageEntry(); } - const size_t offset = i * title_entry_size; memory.WriteBlock(t_mem_address + offset, &entry, title_entry_size); } } - - auto async_value = std::make_shared( - system, data_offset, static_cast(total_data_size)); - + auto async_value = std::make_shared(system, data_offset, s32(total_data_size)); IPC::ResponseBuilder rb{ctx, 2, 1, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(async_value->ReadableEvent()); - rb.PushIpcInterface(std::move(async_value)); + rb.PushCopyObjects(ctx, async_value->ReadableEvent()); + rb.PushIpcInterface(ctx, std::move(async_value)); } -Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData3( - OutBuffer out_buffer, Out out_flags_a, Out out_flags_b, - Out out_actual_size, ApplicationControlSource application_control_source, u8 flag1, u8 flag2, u64 application_id) { +Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData3(OutBuffer out_buffer, Out out_flags_a, Out out_flags_b, Out out_actual_size, ApplicationControlSource application_control_source, u8 flag1, u8 flag2, u64 application_id) { LOG_INFO(Service_NS, "called with control_source={}, flags=({:02X},{:02X}), application_id={:016X}", application_control_source, flag1, flag2, application_id); diff --git a/src/core/hle/service/ns/read_only_application_control_data_interface.h b/src/core/hle/service/ns/read_only_application_control_data_interface.h index 441408b651..a4a285151a 100644 --- a/src/core/hle/service/ns/read_only_application_control_data_interface.h +++ b/src/core/hle/service/ns/read_only_application_control_data_interface.h @@ -34,6 +34,7 @@ public: u8 flag1, u8 flag2, u64 application_id); + void ListApplicationIcon(HLERequestContext& ctx); void ListApplicationTitle(HLERequestContext& ctx); Result GetApplicationControlData3( OutBuffer out_buffer, diff --git a/src/core/hle/service/ns/read_only_application_record_interface.cpp b/src/core/hle/service/ns/read_only_application_record_interface.cpp index 18f9e41d8c..8ce7851ae2 100644 --- a/src/core/hle/service/ns/read_only_application_record_interface.cpp +++ b/src/core/hle/service/ns/read_only_application_record_interface.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -16,10 +16,8 @@ IReadOnlyApplicationRecordInterface::IReadOnlyApplicationRecordInterface(Core::S static const FunctionInfo functions[] = { {0, D<&IReadOnlyApplicationRecordInterface::HasApplicationRecord>, "HasApplicationRecord"}, {1, nullptr, "NotifyApplicationFailure"}, - {2, D<&IReadOnlyApplicationRecordInterface::IsDataCorruptedResult>, - "IsDataCorruptedResult"}, - {3, D<&IReadOnlyApplicationRecordInterface::ListApplicationRecord>, - "ListApplicationRecord"}, + {2, D<&IReadOnlyApplicationRecordInterface::IsDataCorruptedResult>, "IsDataCorruptedResult"}, + {3, D<&IReadOnlyApplicationRecordInterface::ListApplicationRecord>, "ListApplicationRecord"}, }; // clang-format on diff --git a/src/core/hle/service/ns/read_only_application_record_interface.h b/src/core/hle/service/ns/read_only_application_record_interface.h index fc43c768ba..dd842070e8 100644 --- a/src/core/hle/service/ns/read_only_application_record_interface.h +++ b/src/core/hle/service/ns/read_only_application_record_interface.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/ns/service_getter_interface.cpp b/src/core/hle/service/ns/service_getter_interface.cpp index 1a3dd7166c..7b7f791650 100644 --- a/src/core/hle/service/ns/service_getter_interface.cpp +++ b/src/core/hle/service/ns/service_getter_interface.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/service_getter_interface.h b/src/core/hle/service/ns/service_getter_interface.h index bbc18d4445..fc36b04325 100644 --- a/src/core/hle/service/ns/service_getter_interface.h +++ b/src/core/hle/service/ns/service_getter_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/system_update_control.cpp b/src/core/hle/service/ns/system_update_control.cpp index f5f5cfd909..2711db508c 100644 --- a/src/core/hle/service/ns/system_update_control.cpp +++ b/src/core/hle/service/ns/system_update_control.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/system_update_control.h b/src/core/hle/service/ns/system_update_control.h index a30a090000..70794f4abd 100644 --- a/src/core/hle/service/ns/system_update_control.h +++ b/src/core/hle/service/ns/system_update_control.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/system_update_interface.cpp b/src/core/hle/service/ns/system_update_interface.cpp index 7e22ca3db0..3f4d42fdb6 100644 --- a/src/core/hle/service/ns/system_update_interface.cpp +++ b/src/core/hle/service/ns/system_update_interface.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/system_update_interface.h b/src/core/hle/service/ns/system_update_interface.h index 36a2880ec9..76f8959ed6 100644 --- a/src/core/hle/service/ns/system_update_interface.h +++ b/src/core/hle/service/ns/system_update_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ns/vulnerability_manager_interface.cpp b/src/core/hle/service/ns/vulnerability_manager_interface.cpp deleted file mode 100644 index 69c21fb894..0000000000 --- a/src/core/hle/service/ns/vulnerability_manager_interface.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "core/hle/service/cmif_serialization.h" -#include "core/hle/service/ns/vulnerability_manager_interface.h" - -namespace Service::NS { - -IVulnerabilityManagerInterface::IVulnerabilityManagerInterface(Core::System& system_) - : ServiceFramework{system_, "ns:vm"} { - // clang-format off - static const FunctionInfo functions[] = { - {1200, D<&IVulnerabilityManagerInterface::NeedsUpdateVulnerability>, "NeedsUpdateVulnerability"}, - {1201, nullptr, "UpdateSafeSystemVersionForDebug"}, - {1202, nullptr, "GetSafeSystemVersion"}, - }; - // clang-format on - - RegisterHandlers(functions); -} - -IVulnerabilityManagerInterface::~IVulnerabilityManagerInterface() = default; - -Result IVulnerabilityManagerInterface::NeedsUpdateVulnerability( - Out out_needs_update_vulnerability) { - LOG_WARNING(Service_NS, "(STUBBED) called"); - *out_needs_update_vulnerability = false; - R_SUCCEED(); -} - -} // namespace Service::NS diff --git a/src/core/hle/service/ns/vulnerability_manager_interface.h b/src/core/hle/service/ns/vulnerability_manager_interface.h deleted file mode 100644 index c689cf7ece..0000000000 --- a/src/core/hle/service/ns/vulnerability_manager_interface.h +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "core/hle/service/cmif_types.h" -#include "core/hle/service/service.h" - -namespace Service::NS { - -class IVulnerabilityManagerInterface final - : public ServiceFramework { -public: - explicit IVulnerabilityManagerInterface(Core::System& system_); - ~IVulnerabilityManagerInterface() override; - -private: - Result NeedsUpdateVulnerability(Out out_needs_update_vulnerability); -}; - -} // namespace Service::NS diff --git a/src/core/hle/service/nvdrv/core/container.h b/src/core/hle/service/nvdrv/core/container.h index 62404e14a7..bc60636ac5 100644 --- a/src/core/hle/service/nvdrv/core/container.h +++ b/src/core/hle/service/nvdrv/core/container.h @@ -9,7 +9,7 @@ #include #include -#include +#include "common/container/unordered_map.h" #include "core/device_memory_manager.h" #include "core/hle/service/nvdrv/nvdata.h" diff --git a/src/core/hle/service/nvdrv/core/nvmap.h b/src/core/hle/service/nvdrv/core/nvmap.h index 17b9ccd1c7..98b1d5a6cc 100644 --- a/src/core/hle/service/nvdrv/core/nvmap.h +++ b/src/core/hle/service/nvdrv/core/nvmap.h @@ -12,7 +12,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include "common/bit_field.h" @@ -161,7 +161,7 @@ private: std::list> unmap_queue{}; std::mutex unmap_queue_lock{}; //!< Protects access to `unmap_queue` - ankerl::unordered_dense::map> + ::Common::unordered_map> handles{}; //!< Main owning map of handles std::mutex handles_lock; //!< Protects access to `handles` diff --git a/src/core/hle/service/nvdrv/devices/ioctl_serialization.h b/src/core/hle/service/nvdrv/devices/ioctl_serialization.h index c3baa3a75b..37d04e3cf1 100644 --- a/src/core/hle/service/nvdrv/devices/ioctl_serialization.h +++ b/src/core/hle/service/nvdrv/devices/ioctl_serialization.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h index 8adaddc60b..d23ec63bae 100644 --- a/src/core/hle/service/nvdrv/devices/nvdevice.h +++ b/src/core/hle/service/nvdrv/devices/nvdevice.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp index f26f5347eb..b8a8979315 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp @@ -6,6 +6,7 @@ #include +#include "common/adpf.h" #include "common/assert.h" #include "common/logging.h" #include "core/core.h" @@ -76,19 +77,27 @@ void nvdisp_disp0::Composite(std::span sorted_layers .transform_flags = layer.transform, .crop_rect = layer.crop_rect, .blending = ConvertBlending(layer.blending), + .layer_stack_mask = layer.layer_stack_mask, }); for (size_t i = 0; i < layer.acquire_fence.num_fences; i++) { - output_fences.push_back(layer.acquire_fence.fences[i]); + if (layer.acquire_fence.fences[i].id >= 0) { + output_fences.push_back(layer.acquire_fence.fences[i]); + } } } system.GPU().RequestComposite(std::move(output_layers), std::move(output_fences)); + Common::ADPF::ReportFrameInterval(); system.SpeedLimiter().DoSpeedLimiting(system.CoreTiming().GetGlobalTimeUs()); system.GetPerfStats().EndSystemFrame(); system.GetPerfStats().BeginSystemFrame(); } +void nvdisp_disp0::WaitForComposite() { + system.GPU().WaitForComposite(); +} + Kernel::KEvent* nvdisp_disp0::QueryEvent(u32 event_id) { LOG_CRITICAL(Service_NVDRV, "Unknown DISP Event {}", event_id); return nullptr; diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h index 1082b85c2b..ff0bfb2ec9 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -37,6 +40,8 @@ public: /// Performs a screen flip, compositing each buffer. void Composite(std::span sorted_layers); + void WaitForComposite(); + Kernel::KEvent* QueryEvent(u32 event_id) override; private: diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index 3bfef0c29c..3c9e42f602 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -94,7 +94,7 @@ void nvhost_as_gpu::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {} void nvhost_as_gpu::OnClose(DeviceFD fd) {} NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) { - LOG_DEBUG(Service_NVDRV, "called, big_page_size={:#X}", params.big_page_size); + LOG_DEBUG(Service_NVDRV, "called, big_page_size={:#x}", params.big_page_size); std::scoped_lock lock(mutex); @@ -105,12 +105,12 @@ NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) { if (params.big_page_size) { if (!std::has_single_bit(params.big_page_size)) { - LOG_ERROR(Service_NVDRV, "Non power-of-2 big page size: {:#X}!", params.big_page_size); + LOG_ERROR(Service_NVDRV, "Non power-of-2 big page size: {:#x}!", params.big_page_size); return NvResult::BadValue; } if ((params.big_page_size & VM::SUPPORTED_BIG_PAGE_SIZES) == 0) { - LOG_ERROR(Service_NVDRV, "Unsupported big page size: {:#X}!", params.big_page_size); + LOG_ERROR(Service_NVDRV, "Unsupported big page size: {:#x}!", params.big_page_size); return NvResult::BadValue; } @@ -127,7 +127,7 @@ NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) { vm.va_range_end = params.va_range_end; } - const u64 max_big_page_bits = Common::Log2Ceil64(vm.va_range_end); + const u64 max_big_page_bits = Common::Log2Ceil(vm.va_range_end); const auto start_pages{static_cast(vm.va_range_start >> VM::PAGE_SIZE_BITS)}; const auto end_pages{static_cast(vm.va_range_split >> VM::PAGE_SIZE_BITS)}; @@ -138,8 +138,7 @@ NvResult nvhost_as_gpu::AllocAsEx(IoctlAllocAsEx& params) { static_cast((vm.va_range_end - vm.va_range_split) >> vm.big_page_size_bits)}; vm.big_page_allocator.emplace(start_big_pages, end_big_pages); - gmmu = std::make_shared(system, max_big_page_bits, vm.va_range_split, - vm.big_page_size_bits, VM::PAGE_SIZE_BITS); + gmmu = std::make_unique(system, max_big_page_bits, vm.va_range_split, vm.big_page_size_bits, VM::PAGE_SIZE_BITS); system.GPU().InitAddressSpace(*gmmu); vm.initialised = true; @@ -253,7 +252,7 @@ NvResult nvhost_as_gpu::FreeSpace(IoctlFreeSpace& params) { } NvResult nvhost_as_gpu::Remap(std::span entries) { - LOG_DEBUG(Service_NVDRV, "called, num_entries={:#X}", entries.size()); + LOG_DEBUG(Service_NVDRV, "called, num_entries={:#x}", entries.size()); if (!vm.initialised) { return NvResult::BadValue; @@ -301,7 +300,7 @@ NvResult nvhost_as_gpu::Remap(std::span entries) { NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) { LOG_DEBUG(Service_NVDRV, "called, flags={:X}, nvmap_handle={:X}, buffer_offset={}, mapping_size={}" - ", offset={:#X}", + ", offset={:#x}", params.flags, params.handle, params.buffer_offset, params.mapping_size, params.offset); @@ -316,7 +315,7 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) { if (auto const it = mapping_map.find(params.offset); it != mapping_map.end()) { auto const mapping = it->second; if (mapping.size < params.mapping_size) { - LOG_WARNING(Service_NVDRV, "Cannot remap a partially mapped GPU address space region: {:#X}", params.offset); + LOG_WARNING(Service_NVDRV, "Cannot remap a partially mapped GPU address space region: {:#x}", params.offset); return NvResult::BadValue; } u64 gpu_address = u64(params.offset + params.buffer_offset); @@ -324,7 +323,7 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) { gmmu->Map(gpu_address, device_address, params.mapping_size, Tegra::PTEKind(params.kind), mapping.big_page); return NvResult::Success; } else { - LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: {:#X}", params.offset); + LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: {:#x}", params.offset); return NvResult::BadValue; } } @@ -384,7 +383,7 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) { NvResult nvhost_as_gpu::UnmapBuffer(IoctlUnmapBuffer& params) { std::scoped_lock lock(mutex); if (auto const offset_it = map_buffer_offsets.find(params.offset); offset_it != map_buffer_offsets.end()) { - LOG_DEBUG(Service_NVDRV, "called, offset={:#X}", params.offset); + LOG_DEBUG(Service_NVDRV, "called, offset={:#x}", params.offset); if (!vm.initialised) { return NvResult::BadValue; } @@ -416,7 +415,7 @@ NvResult nvhost_as_gpu::BindChannel(IoctlBindChannel& params) { LOG_DEBUG(Service_NVDRV, "called, fd={:X}", params.fd); auto gpu_channel_device = module.GetDevice(params.fd); - gpu_channel_device->channel_state->memory_manager = gmmu; + gpu_channel_device->channel_state->memory_manager = gmmu.get(); return NvResult::Success; } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h index 44892ee368..b13b1c9efa 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h @@ -13,7 +13,7 @@ #include #include #include -#include +#include "common/container/unordered_set.h" #include #include "common/address_space.h" @@ -113,7 +113,7 @@ private: }; static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size"); - ankerl::unordered_dense::set map_buffer_offsets{}; + ::Common::unordered_set map_buffer_offsets{}; struct IoctlMapBufferEx { MappingFlags flags{}; // bit0: fixed_offset, bit2: cacheable @@ -219,7 +219,7 @@ private: bool initialised{}; } vm; - std::shared_ptr gmmu; + std::unique_ptr gmmu; }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp index b2580f2bb9..6ef32b890f 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp @@ -203,7 +203,7 @@ NvResult nvhost_ctrl::IocCtrlEventWait(IocCtrlEventWaitParams& params, bool is_a auto& event_ = events[slot]; if (event_.status.exchange(EventState::Signalling, std::memory_order_acq_rel) == EventState::Waiting) { - event_.kevent->Signal(); + event_.kevent->Signal(system.Kernel()); } event_.status.store(EventState::Signalled, std::memory_order_release); }); @@ -292,7 +292,7 @@ NvResult nvhost_ctrl::IocCtrlClearEventWait(IocCtrlEventClearParams& params) { } event.fails++; event.status.store(EventState::Cancelled, std::memory_order_release); - event.kevent->Clear(); + event.kevent->Clear(system.Kernel()); return NvResult::Success; } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp index ace1f7e8f7..14cdd8fda4 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -47,6 +47,8 @@ NvResult nvhost_ctrl_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span return WrapFixed(this, &nvhost_ctrl_gpu::FlushL2, input, output); case 0x14: return WrapFixed(this, &nvhost_ctrl_gpu::GetActiveSlotMask, input, output); + case 0x15: + return WrapFixed(this, &nvhost_ctrl_gpu::PmuGetGpuLoad, input, output); case 0x1c: return WrapFixed(this, &nvhost_ctrl_gpu::GetGpuTime, input, output); default: @@ -208,7 +210,7 @@ NvResult nvhost_ctrl_gpu::GetCharacteristics3( } NvResult nvhost_ctrl_gpu::GetTPCMasks1(IoctlGpuGetTpcMasksArgs& params) { - LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size={:#X}", params.mask_buffer_size); + LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size={:#x}", params.mask_buffer_size); if (params.mask_buffer_size != 0) { params.tcp_mask = 3; } @@ -216,7 +218,7 @@ NvResult nvhost_ctrl_gpu::GetTPCMasks1(IoctlGpuGetTpcMasksArgs& params) { } NvResult nvhost_ctrl_gpu::GetTPCMasks3(IoctlGpuGetTpcMasksArgs& params, std::span tpc_mask) { - LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size={:#X}", params.mask_buffer_size); + LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size={:#x}", params.mask_buffer_size); if (params.mask_buffer_size != 0) { params.tcp_mask = 3; } @@ -234,6 +236,12 @@ NvResult nvhost_ctrl_gpu::GetActiveSlotMask(IoctlActiveSlotMask& params) { return NvResult::Success; } +NvResult nvhost_ctrl_gpu::PmuGetGpuLoad(IoctlPmuGetLoad& params) { + LOG_WARNING(Service_NVDRV, "(stubbed) called"); + params.pmu_gpu_load = 100; + return NvResult::Success; +} + NvResult nvhost_ctrl_gpu::ZCullGetCtxSize(IoctlZcullGetCtxSize& params) { LOG_DEBUG(Service_NVDRV, "called"); params.size = 0x1; @@ -257,7 +265,7 @@ NvResult nvhost_ctrl_gpu::ZCullGetInfo(IoctlNvgpuGpuZcullGetInfoArgs& params) { NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) { if (params.type > supported_types) { - LOG_ERROR(Service_NVDRV, "ZBCSetTable: invalid type {:#X}", params.type); + LOG_ERROR(Service_NVDRV, "ZBCSetTable: invalid type {:#x}", params.type); return NvResult::BadParameter; } @@ -280,11 +288,11 @@ NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) { if (color_it != zbc_colors.end()) { ++color_it->ref_cnt; - LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused color entry fmt={:#X}, ref_cnt={:#X}", + LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused color entry fmt={:#x}, ref_cnt={:#x}", params.format, color_it->ref_cnt); } else { zbc_colors.push_back(color_entry); - LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added color entry fmt={:#X}, index={:#X}", + LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added color entry fmt={:#x}, index={:#x}", params.format, zbc_colors.size() - 1); } break; @@ -300,11 +308,11 @@ NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) { if (depth_it != zbc_depths.end()) { ++depth_it->ref_cnt; - LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused depth entry fmt={:#X}, ref_cnt={:#X}", + LOG_DEBUG(Service_NVDRV, "ZBCSetTable: reused depth entry fmt={:#x}, ref_cnt={:#x}", depth_entry.format, depth_it->ref_cnt); } else { zbc_depths.push_back(depth_entry); - LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added depth entry fmt={:#X}, index={:#X}", + LOG_DEBUG(Service_NVDRV, "ZBCSetTable: added depth entry fmt={:#x}, index={:#x}", depth_entry.format, zbc_depths.size() - 1); } } @@ -315,7 +323,7 @@ NvResult nvhost_ctrl_gpu::ZBCSetTable(IoctlZbcSetTable& params) { NvResult nvhost_ctrl_gpu::ZBCQueryTable(IoctlZbcQueryTable& params) { if (params.type > supported_types) { - LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid type {:#X}", params.type); + LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid type {:#x}", params.type); return NvResult::BadParameter; } @@ -324,7 +332,7 @@ NvResult nvhost_ctrl_gpu::ZBCQueryTable(IoctlZbcQueryTable& params) { switch (static_cast(params.type)) { case ZBCTypes::color: { if (params.index_size >= zbc_colors.size()) { - LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid color index {:#X}", params.index_size); + LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid color index {:#x}", params.index_size); return NvResult::BadParameter; } @@ -339,7 +347,7 @@ NvResult nvhost_ctrl_gpu::ZBCQueryTable(IoctlZbcQueryTable& params) { } case ZBCTypes::depth: { if (params.index_size >= zbc_depths.size()) { - LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid depth index {:#X}", params.index_size); + LOG_ERROR(Service_NVDRV, "ZBCQueryTable: invalid depth index {:#x}", params.index_size); return NvResult::BadParameter; } @@ -357,7 +365,7 @@ NvResult nvhost_ctrl_gpu::ZBCQueryTable(IoctlZbcQueryTable& params) { } NvResult nvhost_ctrl_gpu::FlushL2(IoctlFlushL2& params) { - LOG_DEBUG(Service_NVDRV, "called {:#X}", params.flush); + LOG_DEBUG(Service_NVDRV, "called {:#x}", params.flush); // if ((params.flush & 0x01) != 0) //l2 flush // /* we dont emulate l2 */; // if ((params.flush & 0x04) != 0) //fb flush diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h index e0603f9a71..80b7abd6a1 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -184,6 +184,11 @@ private: }; static_assert(sizeof(IoctlGetCpuTimeCorrelationInfo) == 264); + struct IoctlPmuGetLoad { + u32 pmu_gpu_load; + }; + static_assert(sizeof(IoctlPmuGetLoad) == 4); + NvResult GetCharacteristics1(IoctlCharacteristics& params); NvResult GetCharacteristics3(IoctlCharacteristics& params, std::span gpu_characteristics); @@ -192,6 +197,7 @@ private: NvResult GetTPCMasks3(IoctlGpuGetTpcMasksArgs& params, std::span tpc_mask); NvResult GetActiveSlotMask(IoctlActiveSlotMask& params); + NvResult PmuGetGpuLoad(IoctlPmuGetLoad& params); NvResult ZCullGetCtxSize(IoctlZcullGetCtxSize& params); NvResult ZCullGetInfo(IoctlNvgpuGpuZcullGetInfoArgs& params); NvResult ZBCSetTable(IoctlZbcSetTable& params); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index 5869bec903..1e571be4c9 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -69,7 +69,7 @@ NvResult nvhost_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span inpu case 0x3: return WrapFixed(this, &nvhost_gpu::ChannelSetTimeout, input, output); case 0x8: - return WrapFixedVariable(this, &nvhost_gpu::SubmitGPFIFOBase1, input, output, false); + return WrapFixedVariable(this, &nvhost_gpu::SubmitGPFIFOBase1, input, output, fd, false); case 0x9: return WrapFixed(this, &nvhost_gpu::AllocateObjectContext, input, output); case 0xb: @@ -83,7 +83,7 @@ NvResult nvhost_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span inpu case 0x1a: return WrapFixed(this, &nvhost_gpu::AllocGPFIFOEx2, input, output, fd); case 0x1b: - return WrapFixedVariable(this, &nvhost_gpu::SubmitGPFIFOBase1, input, output, true); + return WrapFixedVariable(this, &nvhost_gpu::SubmitGPFIFOBase1, input, output, fd, true); case 0x1d: return WrapFixed(this, &nvhost_gpu::ChannelSetTimeslice, input, output); default: @@ -259,7 +259,7 @@ s32_le nvhost_gpu::GetObjectContextClassNumberIndex(CtxClasses class_number) { } NvResult nvhost_gpu::AllocateObjectContext(IoctlAllocObjCtx& params) { - LOG_DEBUG(Service_NVDRV, "called, class_num={:#X}, flags={:#X}, obj_id={:#X}", params.class_num, + LOG_DEBUG(Service_NVDRV, "called, class_num={:#x}, flags={:#x}, obj_id={:#x}", params.class_num, params.flags, params.obj_id); // Do not require channel initialization here: some clients allocate contexts before binding. @@ -271,23 +271,23 @@ NvResult nvhost_gpu::AllocateObjectContext(IoctlAllocObjCtx& params) { std::scoped_lock lk(channel_mutex); if (params.flags) { - LOG_WARNING(Service_NVDRV, "non-zero flags={:#X} for class={:#X}", 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 = + 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}", + 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_WARNING(Service_NVDRV, "Object context for class {:#X} already allocated on this channel", + LOG_WARNING(Service_NVDRV, "Object context for class {:#x} already allocated on this channel", params.class_num); return NvResult::AlreadyAllocated; } @@ -387,8 +387,19 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL return NvResult::Success; } +Core::Memory::Memory& nvhost_gpu::GetSessionMemory(DeviceFD fd) { + if (const auto it = sessions.find(fd); it != sessions.end()) + if (auto* const session = core.GetSession(it->second); + session != nullptr && session->process != nullptr) + return session->process->GetMemory(); + + LOG_ERROR(Service_NVDRV, "No session for fd={}, falling back to application memory", fd); + return system.ApplicationMemory(); +} + NvResult nvhost_gpu::SubmitGPFIFOBase1(IoctlSubmitGpfifo& params, - std::span commands, bool kickoff) { + std::span commands, DeviceFD fd, + bool kickoff) { if (params.num_entries > commands.size()) { UNIMPLEMENTED(); return NvResult::InvalidSize; @@ -396,7 +407,7 @@ NvResult nvhost_gpu::SubmitGPFIFOBase1(IoctlSubmitGpfifo& params, Tegra::CommandList entries(params.num_entries); if (kickoff) { - system.ApplicationMemory().ReadBlock(params.address, entries.command_lists.data(), + this->GetSessionMemory(fd).ReadBlock(params.address, entries.command_lists.data(), params.num_entries * sizeof(Tegra::CommandListHeader)); } else { std::memcpy(entries.command_lists.data(), commands.data(), @@ -420,20 +431,20 @@ NvResult nvhost_gpu::SubmitGPFIFOBase2(IoctlSubmitGpfifo& params, } NvResult nvhost_gpu::GetWaitbase(IoctlGetWaitbase& params) { - LOG_INFO(Service_NVDRV, "called, unknown={:#X}", params.unknown); + LOG_INFO(Service_NVDRV, "called, unknown={:#x}", params.unknown); params.value = 0; // Seems to be hard coded at 0 return NvResult::Success; } NvResult nvhost_gpu::ChannelSetTimeout(IoctlChannelSetTimeout& params) { - LOG_INFO(Service_NVDRV, "called, timeout={:#X}", params.timeout); + LOG_INFO(Service_NVDRV, "called, timeout={:#x}", params.timeout); return NvResult::Success; } NvResult nvhost_gpu::ChannelSetTimeslice(IoctlSetTimeslice& params) { - LOG_INFO(Service_NVDRV, "called, timeslice={:#X}", params.timeslice); + LOG_INFO(Service_NVDRV, "called, timeslice={:#x}", params.timeslice); if (params.timeslice < 1000 || params.timeslice > 5000) { return NvResult::BadParameter; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h index c29231dc0c..70a6b1bd68 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h @@ -16,6 +16,10 @@ #include "core/hle/service/nvdrv/nvdata.h" #include "video_core/dma_pusher.h" +namespace Core::Memory { +class Memory; +} + namespace Tegra { namespace Control { struct ChannelState; @@ -196,8 +200,11 @@ private: NvResult SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandList&& entries); + Core::Memory::Memory& GetSessionMemory(DeviceFD fd); + NvResult SubmitGPFIFOBase1(IoctlSubmitGpfifo& params, - std::span commands, bool kickoff = false); + std::span commands, DeviceFD fd, + bool kickoff = false); NvResult SubmitGPFIFOBase2(IoctlSubmitGpfifo& params, std::span commands); @@ -210,7 +217,7 @@ private: NvCore::SyncpointManager& syncpoint_manager; NvCore::NvMap& nvmap; std::shared_ptr channel_state; - ankerl::unordered_dense::map sessions; + ::Common::unordered_map sessions; u32 channel_syncpoint; std::mutex channel_mutex; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp index d64658bddc..7ac3dfaa46 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp @@ -25,18 +25,20 @@ NvResult nvhost_nvdec::Ioctl1(DeviceFD fd, Ioctl command, std::span in switch (command.group) { case 0x0: switch (command.cmd) { - case 0x1: + case 0x01: return WrapFixedVariable(this, &nvhost_nvdec::Submit, input, output, fd); - case 0x2: + case 0x02: return WrapFixed(this, &nvhost_nvdec::GetSyncpoint, input, output); - case 0x3: + case 0x03: return WrapFixed(this, &nvhost_nvdec::GetWaitbase, input, output); - case 0x7: + case 0x07: return WrapFixed(this, &nvhost_nvdec::SetSubmitTimeout, input, output); - case 0x9: + case 0x09: return WrapFixedVariable(this, &nvhost_nvdec::MapBuffer, input, output, fd); - case 0xa: + case 0x0a: return WrapFixedVariable(this, &nvhost_nvdec::UnmapBuffer, input, output); + case 0x23: + return WrapFixed(this, &nvhost_nvdec::GetClkRate, input, output); default: break; } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h index 627686757b..1747881d48 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp index c726d6f93e..698712e152 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp @@ -168,6 +168,13 @@ NvResult nvhost_nvdec_common::SetSubmitTimeout(u32 timeout) { return NvResult::Success; } +NvResult nvhost_nvdec_common::GetClkRate(IoctlGetClkRate& params) { + LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + params.clk_rate = 614400000; + params.module_id = 0; + return NvResult::Success; +} + Kernel::KEvent* nvhost_nvdec_common::QueryEvent(u32 event_id) { LOG_CRITICAL(Service_NVDRV, "Unknown HOSTX1 Event {}", event_id); return nullptr; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h index 6b66a0d28d..7c8d5e0eb1 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h @@ -7,7 +7,7 @@ #pragma once #include -#include +#include "common/container/unordered_map.h" #include #include "common/common_types.h" @@ -111,6 +111,12 @@ protected: }; static_assert(sizeof(IoctlMapBuffer) == 0x0C, "IoctlMapBuffer is incorrect size"); + struct IoctlGetClkRate { + u32_le clk_rate{}; + u32_le module_id{}; + }; + static_assert(sizeof(IoctlGetClkRate) == 8); + /// Ioctl command implementations NvResult SetNVMAPfd(IoctlSetNvmapFD&); NvResult Submit(IoctlSubmit& params, std::span input, DeviceFD fd); @@ -119,6 +125,7 @@ protected: NvResult MapBuffer(IoctlMapBuffer& params, std::span entries, DeviceFD fd); NvResult UnmapBuffer(IoctlMapBuffer& params, std::span entries); NvResult SetSubmitTimeout(u32 timeout); + NvResult GetClkRate(IoctlGetClkRate& params); Kernel::KEvent* QueryEvent(u32 event_id) override; @@ -131,7 +138,7 @@ protected: NvCore::NvMap& nvmap; NvCore::ChannelType channel_type; std::array device_syncpoints{}; - ankerl::unordered_dense::map sessions; + ::Common::unordered_map sessions; }; }; // namespace Devices } // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h index def9c254d1..e0735b7f01 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.h b/src/core/hle/service/nvdrv/devices/nvhost_vic.h index 0cc04354a0..b1cbcb9ad7 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_vic.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp index be3fab68b5..a42b5682aa 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.cpp +++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp @@ -81,7 +81,7 @@ void nvmap::OnClose(DeviceFD fd) { } NvResult nvmap::IocCreate(IocCreateParams& params) { - LOG_DEBUG(Service_NVDRV, "called, size=0x{:08X}", params.size); + LOG_DEBUG(Service_NVDRV, "called, size={:#08x}", params.size); std::shared_ptr handle_description{}; auto result = @@ -92,7 +92,7 @@ NvResult nvmap::IocCreate(IocCreateParams& params) { } handle_description->orig_size = params.size; // Orig size is the unaligned size params.handle = handle_description->id; - LOG_DEBUG(Service_NVDRV, "handle: {}, size: {:#X}", handle_description->id, params.size); + LOG_DEBUG(Service_NVDRV, "handle: {}, size: {:#x}", handle_description->id, params.size); return NvResult::Success; } diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h index 19c5b479df..16ad8b3414 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.h +++ b/src/core/hle/service/nvdrv/devices/nvmap.h @@ -7,7 +7,7 @@ #pragma once #include -#include +#include "common/container/unordered_map.h" #include #include "common/common_funcs.h" #include "common/common_types.h" @@ -118,7 +118,7 @@ private: NvCore::Container& container; NvCore::NvMap& file; - ankerl::unordered_dense::map sessions; + ::Common::unordered_map sessions; }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp index 8c618ca258..b252a2efa5 100644 --- a/src/core/hle/service/nvdrv/nvdrv.cpp +++ b/src/core/hle/service/nvdrv/nvdrv.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2021 yuzu Emulator Project // SPDX-FileCopyrightText: 2021 Skyline Team and Contributors // SPDX-License-Identifier: GPL-3.0-or-later @@ -25,6 +28,7 @@ #include "core/hle/service/nvdrv/nvmemp.h" #include "core/hle/service/nvnflinger/nvnflinger.h" #include "core/hle/service/server_manager.h" +#include "frontend_common/firmware_manager.h" #include "video_core/gpu.h" namespace Service::Nvidia { @@ -42,26 +46,80 @@ void EventInterface::FreeEvent(Kernel::KEvent* event) { module.service_context.CloseEvent(event); } +class NVGEM_C final : public ServiceFramework { +public: + explicit NVGEM_C(Core::System& system_) + : ServiceFramework{system_, "nvgem:c"} + { + static const FunctionInfo functions[] = { + {0, nullptr, "Initialize"}, + {1, nullptr, "GetEventHandle"}, + {2, nullptr, "ControlNotification"}, + {3, nullptr, "SetNotificationPerm"}, + {4, nullptr, "SetCoreDumpPerm"}, + {5, nullptr, "GetAruid"}, + {6, nullptr, "Reset"}, + {7, nullptr, "GetAruid2"}, + }; + RegisterHandlers(functions); + } +}; + +class NVGEM_CD final : public ServiceFramework { +public: + explicit NVGEM_CD(Core::System& system_) + : ServiceFramework{system_, "nvgem:cd"} + { + static const FunctionInfo functions[] = { + {0, nullptr, "Initialize"}, + {1, nullptr, "GetAruid"}, + {2, nullptr, "ReadNextBlock"}, + {3, nullptr, "GetNextBlockSize"}, + {4, nullptr, "ReadNextBlock2"}, + }; + RegisterHandlers(functions); + } +}; + +class NVDBG_D final : public ServiceFramework { +public: + explicit NVDBG_D(Core::System& system_) + : ServiceFramework{system_, "nvdbg:d"} + { + static const FunctionInfo functions[] = { + {0, nullptr, "Open"}, + {1, nullptr, "Ioctl"}, + {2, nullptr, "Close"}, + {4, nullptr, "QueryEvent"}, + {9, nullptr, "DumpStatus"}, + {10, nullptr, "InitializeDevtools"}, + {11, nullptr, "Ioctl2"}, + {12, nullptr, "Ioctl3"}, + {13, nullptr, "SetConfiguration"}, + }; + RegisterHandlers(functions); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); auto module = std::make_shared(system); - const auto NvdrvInterfaceFactoryForApplication = [&, module] { - return std::make_shared(system, module, "nvdrv"); - }; + server_manager->RegisterNamedService("nvdrv", std::make_shared(system, module, "nvdrv")); + const auto NvdrvInterfaceFactoryForApplets = [&, module] { return std::make_shared(system, module, "nvdrv:a"); }; - const auto NvdrvInterfaceFactoryForSysmodules = [&, module] { - return std::make_shared(system, module, "nvdrv:s"); - }; - const auto NvdrvInterfaceFactoryForTesting = [&, module] { - return std::make_shared(system, module, "nvdrv:t"); - }; - server_manager->RegisterNamedService("nvdrv", NvdrvInterfaceFactoryForApplication); + server_manager->RegisterNamedService("nvdrv:a", NvdrvInterfaceFactoryForApplets); - server_manager->RegisterNamedService("nvdrv:s", NvdrvInterfaceFactoryForSysmodules); - server_manager->RegisterNamedService("nvdrv:t", NvdrvInterfaceFactoryForTesting); + server_manager->RegisterNamedService("nvdrv:s", std::make_shared(system, module, "nvdrv:s")); + server_manager->RegisterNamedService("nvdrv:t", std::make_shared(system, module, "nvdrv:t")); server_manager->RegisterNamedService("nvmemp", std::make_shared(system)); + server_manager->RegisterNamedService("nvgem:c", std::make_shared(system)); + server_manager->RegisterNamedService("nvgem:cd", std::make_shared(system)); + // +10.0.0 + if (FirmwareManager::GetFirmwareVersion(system).first.major >= 10) { + server_manager->RegisterNamedService("nvdbg:d", std::make_shared(system)); + } ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h index 329ac91c41..f503f108c3 100644 --- a/src/core/hle/service/nvdrv/nvdrv.h +++ b/src/core/hle/service/nvdrv/nvdrv.h @@ -12,7 +12,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include "common/common_types.h" #include "core/hle/service/kernel_helpers.h" @@ -103,7 +103,7 @@ private: /// Id to use for the next open file descriptor. DeviceFD next_fd = 1; - using FilesContainerType = ankerl::unordered_dense::map>; + using FilesContainerType = ::Common::unordered_map>; /// Mapping of file descriptors to the devices they reference. FilesContainerType open_files; @@ -111,7 +111,7 @@ private: EventInterface events_interface; - ankerl::unordered_dense::map> builders; + ::Common::unordered_map> builders; }; void LoopProcess(Core::System& system); diff --git a/src/core/hle/service/nvdrv/nvdrv_interface.cpp b/src/core/hle/service/nvdrv/nvdrv_interface.cpp index 15983ba881..5fae68adbb 100644 --- a/src/core/hle/service/nvdrv/nvdrv_interface.cpp +++ b/src/core/hle/service/nvdrv/nvdrv_interface.cpp @@ -59,7 +59,7 @@ void NVDRV::Ioctl1(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto fd = rp.Pop(); const auto command = rp.PopRaw(); - LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw); + LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl={:#08x}", fd, command.raw); if (!is_initialized) { ServiceError(ctx, NvResult::NotInitialized); @@ -85,7 +85,7 @@ void NVDRV::Ioctl2(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto fd = rp.Pop(); const auto command = rp.PopRaw(); - LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw); + LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl={:#08x}", fd, command.raw); if (!is_initialized) { ServiceError(ctx, NvResult::NotInitialized); @@ -112,7 +112,7 @@ void NVDRV::Ioctl3(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto fd = rp.Pop(); const auto command = rp.PopRaw(); - LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw); + LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl={:#08x}", fd, command.raw); if (!is_initialized) { ServiceError(ctx, NvResult::NotInitialized); @@ -199,7 +199,7 @@ void NVDRV::QueryEvent(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3, 1}; rb.Push(ResultSuccess); auto& readable_event = event->GetReadableEvent(); - rb.PushCopyObjects(readable_event); + rb.PushCopyObjects(ctx, readable_event); rb.PushEnum(NvResult::Success); } else { LOG_ERROR(Service_NVDRV, "Invalid event request!"); @@ -212,7 +212,7 @@ void NVDRV::QueryEvent(HLERequestContext& ctx) { void NVDRV::SetAruid(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; pid = rp.Pop(); - LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid={:#X}", pid); + LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid={:#x}", pid); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); diff --git a/src/core/hle/service/nvdrv/nvdrv_interface.h b/src/core/hle/service/nvdrv/nvdrv_interface.h index c72f925979..f628fca7df 100644 --- a/src/core/hle/service/nvdrv/nvdrv_interface.h +++ b/src/core/hle/service/nvdrv/nvdrv_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/nvdrv/nvmemp.h b/src/core/hle/service/nvdrv/nvmemp.h index 85e3053a8d..6ff59834fe 100644 --- a/src/core/hle/service/nvdrv/nvmemp.h +++ b/src/core/hle/service/nvdrv/nvmemp.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/nvnflinger/binder.h b/src/core/hle/service/nvnflinger/binder.h index 124accb946..6c7ce0ed0f 100644 --- a/src/core/hle/service/nvnflinger/binder.h +++ b/src/core/hle/service/nvnflinger/binder.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/buffer_item.h b/src/core/hle/service/nvnflinger/buffer_item.h index 7fd808f546..718188d849 100644 --- a/src/core/hle/service/nvnflinger/buffer_item.h +++ b/src/core/hle/service/nvnflinger/buffer_item.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/buffer_item_consumer.h b/src/core/hle/service/nvnflinger/buffer_item_consumer.h index 9f95c92800..7857569c0f 100644 --- a/src/core/hle/service/nvnflinger/buffer_item_consumer.h +++ b/src/core/hle/service/nvnflinger/buffer_item_consumer.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2012 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/buffer_queue_consumer.h b/src/core/hle/service/nvnflinger/buffer_queue_consumer.h index a9226f1c35..84f36285c0 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_consumer.h +++ b/src/core/hle/service/nvnflinger/buffer_queue_consumer.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/buffer_queue_defs.h b/src/core/hle/service/nvnflinger/buffer_queue_defs.h index 6fd3156f4a..77ebd0a6a0 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_defs.h +++ b/src/core/hle/service/nvnflinger/buffer_queue_defs.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp index 436b0f4b05..c3eb2796e3 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp @@ -10,6 +10,7 @@ #include "common/assert.h" #include "common/logging.h" #include "common/settings.h" +#include "common/cpu_features.h" #include "core/hle/kernel/k_event.h" #include "core/hle/kernel/k_readable_event.h" #include "core/hle/kernel/kernel.h" @@ -26,8 +27,10 @@ namespace Service::android { BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_, std::shared_ptr buffer_queue_core_, Service::Nvidia::NvCore::NvMap& nvmap_) - : service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots), - clock{Common::CreateOptimalClock()}, nvmap(nvmap_) { + : service_context{service_context_}, core{std::move(buffer_queue_core_)} + , slots(core->slots) + , nvmap(nvmap_) +{ buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent"); } @@ -108,7 +111,7 @@ Status BufferQueueProducer::SetBufferCount(s32 buffer_count) { core->override_max_buffer_count = buffer_count; core->SignalDequeueCondition(); - buffer_wait_event->Signal(); + buffer_wait_event->Signal(service_context.kernel); listener = core->consumer_listener; } @@ -485,7 +488,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input, slots[slot].buffer_state = BufferState::Queued; slots[slot].frame_number = core->frame_counter; slots[slot].queue_time = timestamp; - slots[slot].presentation_time = clock->GetTimeNS().count(); + slots[slot].presentation_time = Common::g_wall_clock.GetTimeNS().count(); slots[slot].fence = fence; item.slot = slot; @@ -573,7 +576,7 @@ void BufferQueueProducer::CancelBuffer(s32 slot, const Fence& fence) { slots[slot].fence = fence; core->SignalDequeueCondition(); - buffer_wait_event->Signal(); + buffer_wait_event->Signal(service_context.kernel); } Status BufferQueueProducer::Query(NativeWindow what, s32* out_value) { @@ -612,6 +615,9 @@ Status BufferQueueProducer::Query(NativeWindow what, s32* out_value) { case NativeWindow::ConsumerUsageBits: value = core->consumer_usage_bit; break; + case NativeWindow::DefaultDataSpace: + value = core->GetMaxBufferCountLocked(false); + break; default: ASSERT(false); return Status::BadValue; @@ -708,7 +714,7 @@ Status BufferQueueProducer::Disconnect(NativeWindowApi api) { core->connected_producer_listener = nullptr; core->connected_api = NativeWindowApi::NoConnectedApi; core->SignalDequeueCondition(); - buffer_wait_event->Signal(); + buffer_wait_event->Signal(service_context.kernel); listener = core->consumer_listener; } else { LOG_ERROR(Service_Nvnflinger, @@ -758,7 +764,7 @@ Status BufferQueueProducer::SetPreallocatedBuffer(s32 slot, } core->SignalDequeueCondition(); - buffer_wait_event->Signal(); + buffer_wait_event->Signal(service_context.kernel); return Status::NoError; } diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.h b/src/core/hle/service/nvnflinger/buffer_queue_producer.h index 06cb49cbad..697de0ac9b 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_producer.h +++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.h @@ -14,7 +14,7 @@ #include #include "common/common_funcs.h" -#include "common/wall_clock.h" +#include "common/cpu_features.h" #include "core/hle/service/nvdrv/nvdata.h" #include "core/hle/service/nvnflinger/binder.h" #include "core/hle/service/nvnflinger/buffer_queue_defs.h" @@ -89,8 +89,6 @@ private: s32 next_callback_ticket{}; s32 current_callback_ticket{}; std::condition_variable_any callback_condition; - std::unique_ptr clock; - Service::Nvidia::NvCore::NvMap& nvmap; }; diff --git a/src/core/hle/service/nvnflinger/buffer_transform_flags.h b/src/core/hle/service/nvnflinger/buffer_transform_flags.h index ffe579718f..6fb40d0a72 100644 --- a/src/core/hle/service/nvnflinger/buffer_transform_flags.h +++ b/src/core/hle/service/nvnflinger/buffer_transform_flags.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/consumer_base.h b/src/core/hle/service/nvnflinger/consumer_base.h index b29c16f86b..e187b520ca 100644 --- a/src/core/hle/service/nvnflinger/consumer_base.h +++ b/src/core/hle/service/nvnflinger/consumer_base.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/consumer_listener.h b/src/core/hle/service/nvnflinger/consumer_listener.h index 74a1939883..4b4d939551 100644 --- a/src/core/hle/service/nvnflinger/consumer_listener.h +++ b/src/core/hle/service/nvnflinger/consumer_listener.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/display.h b/src/core/hle/service/nvnflinger/display.h index ada302a79e..0beb52b966 100644 --- a/src/core/hle/service/nvnflinger/display.h +++ b/src/core/hle/service/nvnflinger/display.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -15,7 +15,8 @@ struct Layer { explicit Layer(std::shared_ptr buffer_item_consumer_, s32 consumer_id_) : buffer_item_consumer(std::move(buffer_item_consumer_)), consumer_id(consumer_id_), - blending(LayerBlending::None), visible(true), z_index(0), is_overlay(false) {} + blending(LayerBlending::None), visible(true), z_index(0), is_overlay(false), + layer_stack_mask(DefaultLayerStackMask) {} ~Layer() { buffer_item_consumer->Abandon(); } @@ -26,6 +27,7 @@ struct Layer { bool visible; s32 z_index; bool is_overlay; + u32 layer_stack_mask; }; struct LayerStack { diff --git a/src/core/hle/service/nvnflinger/graphic_buffer_producer.cpp b/src/core/hle/service/nvnflinger/graphic_buffer_producer.cpp index d72b49a8e7..5de604233d 100644 --- a/src/core/hle/service/nvnflinger/graphic_buffer_producer.cpp +++ b/src/core/hle/service/nvnflinger/graphic_buffer_producer.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/graphic_buffer_producer.h b/src/core/hle/service/nvnflinger/graphic_buffer_producer.h index 5d7cff7d38..05980eb482 100644 --- a/src/core/hle/service/nvnflinger/graphic_buffer_producer.h +++ b/src/core/hle/service/nvnflinger/graphic_buffer_producer.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/hardware_composer.cpp b/src/core/hle/service/nvnflinger/hardware_composer.cpp index cfd999497b..9ac56e17bb 100644 --- a/src/core/hle/service/nvnflinger/hardware_composer.cpp +++ b/src/core/hle/service/nvnflinger/hardware_composer.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -57,7 +57,8 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display, // Set default speed limit to 100%. *out_speed_scale = 1.0f; - // If no layers are available, skip the logic. + // If no layers are visible, skip the logic. Acquiring and holding buffers here would + // consume the producer's queue without ever presenting. bool any_visible = false; for (auto& layer : display.stack.layers) { if (layer->visible) { @@ -66,10 +67,12 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display, } } if (!any_visible) { - *out_speed_scale = 1.0f; return 1; } + // Wait for the previous composite, bounding the number of in-flight composites. + nvdisp.WaitForComposite(); + // Determine the number of vsync periods to wait before composing again. std::optional swap_interval{}; bool has_acquired_buffer{}; @@ -125,6 +128,7 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display, .transform = static_cast(item.transform), .crop_rect = item.crop, .acquire_fence = item.fence, + .layer_stack_mask = layer->layer_stack_mask, }); } @@ -158,57 +162,39 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display, nvdisp.Composite(composition_stack); } - // Batch framebuffer releases, instead of one-into-one. - std::vector> to_release; - for (auto& [layer_id, framebuffer] : m_framebuffers) { - if (!framebuffer.is_acquired) - continue; - - auto layer = display.stack.FindLayer(layer_id); - if (!layer) - continue; - - // Overlay layers always release after every compose - // Non-overlay layers release based on their swap interval - if (layer->is_overlay || framebuffer.release_frame_number <= m_frame_number) { - to_release.emplace_back(layer.get(), &framebuffer); - } - } - for (auto& [layer, framebuffer] : to_release) { - layer->buffer_item_consumer->ReleaseBuffer(framebuffer->item, android::Fence::NoFence()); - framebuffer->is_acquired = false; - } - // Advance by 1 frame (60 FPS compositing) m_frame_number += 1; - // Release any necessary framebuffers (non-overlay layers only, as overlays are already released above). - for (auto& [layer_id, framebuffer] : m_framebuffers) { - if (!framebuffer.is_acquired) { - // Already released. - continue; - } - - if (framebuffer.release_frame_number > m_frame_number) { - continue; - } - - if (const auto layer = display.stack.FindLayer(layer_id); layer != nullptr) { - // Skip overlay layers as they were already released above - if (layer->is_overlay) { - continue; - } - - // TODO: support release fence - // This is needed to prevent screen tearing - layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence()); - framebuffer.is_acquired = false; - } - } + // Release framebuffers once the frame has been handed to the compositor. This happens + // within the same vsync period as the acquire (release_frame_number is checked against + // the advanced counter), so the producer gets its buffers back immediately instead of + // losing a queue slot for a whole frame. + // TODO: support release fences; releasing before the composite has finished can tear. + this->ReleaseFramebuffersLocked(display); return 1; } +void HardwareComposer::ReleaseFramebuffersLocked(Display& display) { + for (auto& [layer_id, framebuffer] : m_framebuffers) { + if (!framebuffer.is_acquired) { + continue; + } + + const auto layer = display.stack.FindLayer(layer_id); + if (!layer) { + continue; + } + + if (!layer->is_overlay && framebuffer.release_frame_number > m_frame_number) { + continue; + } + + layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence()); + framebuffer.is_acquired = false; + } +} + void HardwareComposer::RemoveLayerLocked(Display& display, ConsumerId consumer_id) { // Check if we are tracking a slot with this consumer_id. const auto it = m_framebuffers.find(consumer_id); diff --git a/src/core/hle/service/nvnflinger/hardware_composer.h b/src/core/hle/service/nvnflinger/hardware_composer.h index e9b7194612..9702e0584b 100644 --- a/src/core/hle/service/nvnflinger/hardware_composer.h +++ b/src/core/hle/service/nvnflinger/hardware_composer.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -52,6 +52,7 @@ private: private: bool TryAcquireFramebufferLocked(Layer& layer, Framebuffer& framebuffer); CacheStatus CacheFramebufferLocked(Layer& layer, ConsumerId consumer_id); + void ReleaseFramebuffersLocked(Display& display); }; } // namespace Service::Nvnflinger diff --git a/src/core/hle/service/nvnflinger/hos_binder_driver.cpp b/src/core/hle/service/nvnflinger/hos_binder_driver.cpp index d30aa3717d..fa1becc34e 100644 --- a/src/core/hle/service/nvnflinger/hos_binder_driver.cpp +++ b/src/core/hle/service/nvnflinger/hos_binder_driver.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/nvnflinger/hos_binder_driver.h b/src/core/hle/service/nvnflinger/hos_binder_driver.h index b7fb07bd2e..6b551c91d2 100644 --- a/src/core/hle/service/nvnflinger/hos_binder_driver.h +++ b/src/core/hle/service/nvnflinger/hos_binder_driver.h @@ -1,6 +1,11 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 +#pragma once + #include "core/hle/service/cmif_types.h" #include "core/hle/service/service.h" diff --git a/src/core/hle/service/nvnflinger/hos_binder_driver_server.h b/src/core/hle/service/nvnflinger/hos_binder_driver_server.h index b66f1776b0..d6ea945004 100644 --- a/src/core/hle/service/nvnflinger/hos_binder_driver_server.h +++ b/src/core/hle/service/nvnflinger/hos_binder_driver_server.h @@ -8,7 +8,7 @@ #include #include -#include +#include "common/container/unordered_map.h" #include "common/common_types.h" #include "core/hle/service/nvnflinger/binder.h" @@ -39,8 +39,8 @@ private: mutable std::mutex lock; s32 last_id = 0; - ankerl::unordered_dense::map> binders; - ankerl::unordered_dense::map refcounts; + ::Common::unordered_map> binders; + ::Common::unordered_map refcounts; }; } // namespace Service::Nvnflinger diff --git a/src/core/hle/service/nvnflinger/hwc_layer.h b/src/core/hle/service/nvnflinger/hwc_layer.h index f71a5d8227..fd9819b526 100644 --- a/src/core/hle/service/nvnflinger/hwc_layer.h +++ b/src/core/hle/service/nvnflinger/hwc_layer.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -23,6 +26,25 @@ enum class LayerBlending : u32 { Coverage = 0x405, }; +enum class LayerStackId : u32 { + Default = 0, + Lcd = 1, + Screenshot = 2, + Recording = 3, + LastFrame = 4, + Arbitrary = 5, + ApplicationForDebug = 6, + Null = 10, +}; + +constexpr u32 LayerStackBit(LayerStackId id) { + return 1U << static_cast(id); +} + +constexpr u32 DefaultLayerStackMask = + LayerStackBit(LayerStackId::Default) | LayerStackBit(LayerStackId::Screenshot) | + LayerStackBit(LayerStackId::Recording) | LayerStackBit(LayerStackId::LastFrame); + struct HwcLayer { u32 buffer_handle; u32 offset; @@ -35,6 +57,7 @@ struct HwcLayer { android::BufferTransformFlags transform; Common::Rectangle crop_rect; android::Fence acquire_fence; + u32 layer_stack_mask; }; } // namespace Service::Nvnflinger diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp index 9e3b68b8a0..48862a747d 100644 --- a/src/core/hle/service/nvnflinger/nvnflinger.cpp +++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h index 5c41f3013c..2f5465e7ef 100644 --- a/src/core/hle/service/nvnflinger/nvnflinger.h +++ b/src/core/hle/service/nvnflinger/nvnflinger.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/parcel.h b/src/core/hle/service/nvnflinger/parcel.h index 4567942016..ed85512792 100644 --- a/src/core/hle/service/nvnflinger/parcel.h +++ b/src/core/hle/service/nvnflinger/parcel.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project diff --git a/src/core/hle/service/nvnflinger/pixel_format.h b/src/core/hle/service/nvnflinger/pixel_format.h index f77d0acfb3..b6e8b5f8a0 100644 --- a/src/core/hle/service/nvnflinger/pixel_format.h +++ b/src/core/hle/service/nvnflinger/pixel_format.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/producer_listener.h b/src/core/hle/service/nvnflinger/producer_listener.h index 6bf8aaf1ee..892f6d531d 100644 --- a/src/core/hle/service/nvnflinger/producer_listener.h +++ b/src/core/hle/service/nvnflinger/producer_listener.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/status.h b/src/core/hle/service/nvnflinger/status.h index 3fa0fe15b9..7b188b42f5 100644 --- a/src/core/hle/service/nvnflinger/status.h +++ b/src/core/hle/service/nvnflinger/status.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/surface_flinger.cpp b/src/core/hle/service/nvnflinger/surface_flinger.cpp index fbf70592bd..a6136c111d 100644 --- a/src/core/hle/service/nvnflinger/surface_flinger.cpp +++ b/src/core/hle/service/nvnflinger/surface_flinger.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -104,6 +104,14 @@ void SurfaceFlinger::SetLayerBlending(s32 consumer_binder_id, LayerBlending blen } } +void SurfaceFlinger::SetLayerStackMask(s32 consumer_binder_id, u32 layer_stack_mask) { + if (const auto layer = this->FindLayer(consumer_binder_id); layer != nullptr) { + layer->layer_stack_mask = layer_stack_mask; + LOG_DEBUG(Service_VI, "Layer {} stack mask set to {:#x}", consumer_binder_id, + layer_stack_mask); + } +} + void SurfaceFlinger::SetLayerIsOverlay(s32 consumer_binder_id, bool is_overlay) { if (const auto layer = this->FindLayer(consumer_binder_id); layer != nullptr) { layer->is_overlay = is_overlay; diff --git a/src/core/hle/service/nvnflinger/surface_flinger.h b/src/core/hle/service/nvnflinger/surface_flinger.h index 05b4206c34..5eb0b22ca9 100644 --- a/src/core/hle/service/nvnflinger/surface_flinger.h +++ b/src/core/hle/service/nvnflinger/surface_flinger.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -48,6 +48,7 @@ public: void SetLayerVisibility(s32 consumer_binder_id, bool visible); void SetLayerBlending(s32 consumer_binder_id, LayerBlending blending); void SetLayerIsOverlay(s32 consumer_binder_id, bool is_overlay); + void SetLayerStackMask(s32 consumer_binder_id, u32 layer_stack_mask); std::shared_ptr FindLayer(s32 consumer_binder_id); diff --git a/src/core/hle/service/nvnflinger/ui/fence.h b/src/core/hle/service/nvnflinger/ui/fence.h index 177aed7580..261eb51669 100644 --- a/src/core/hle/service/nvnflinger/ui/fence.h +++ b/src/core/hle/service/nvnflinger/ui/fence.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2012 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -15,10 +18,8 @@ namespace Service::android { class Fence { public: - constexpr Fence() = default; - static constexpr Fence NoFence() { - Fence fence; + Fence fence{}; fence.fences[0].id = -1; fence.fences[1].id = -1; fence.fences[2].id = -1; @@ -26,7 +27,6 @@ public: return fence; } -public: u32 num_fences{}; std::array fences{}; }; diff --git a/src/core/hle/service/nvnflinger/ui/graphic_buffer.cpp b/src/core/hle/service/nvnflinger/ui/graphic_buffer.cpp index ede2a11933..5ba7a31cc8 100644 --- a/src/core/hle/service/nvnflinger/ui/graphic_buffer.cpp +++ b/src/core/hle/service/nvnflinger/ui/graphic_buffer.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/ui/graphic_buffer.h b/src/core/hle/service/nvnflinger/ui/graphic_buffer.h index da430aa752..099429b3f0 100644 --- a/src/core/hle/service/nvnflinger/ui/graphic_buffer.h +++ b/src/core/hle/service/nvnflinger/ui/graphic_buffer.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2007 The Android Open Source Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/nvnflinger/window.h b/src/core/hle/service/nvnflinger/window.h index 36d6cde3db..78a99f57ae 100644 --- a/src/core/hle/service/nvnflinger/window.h +++ b/src/core/hle/service/nvnflinger/window.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/olsc/daemon_controller.h b/src/core/hle/service/olsc/daemon_controller.h index fc7dd7bbcb..70f3d9e235 100644 --- a/src/core/hle/service/olsc/daemon_controller.h +++ b/src/core/hle/service/olsc/daemon_controller.h @@ -4,7 +4,9 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#pragma once + +#include "common/container/unordered_map.h" #include "common/uuid.h" #include "core/hle/service/cmif_types.h" @@ -54,10 +56,10 @@ private: } }; - ankerl::unordered_dense::map app_auto_transfer_{}; - ankerl::unordered_dense::map global_auto_upload_{}; - ankerl::unordered_dense::map global_auto_download_{}; - ankerl::unordered_dense::map autonomy_task_status_{}; + ::Common::unordered_map app_auto_transfer_{}; + ::Common::unordered_map global_auto_upload_{}; + ::Common::unordered_map global_auto_download_{}; + ::Common::unordered_map autonomy_task_status_{}; }; } // namespace Service::OLSC diff --git a/src/core/hle/service/olsc/native_handle_holder.cpp b/src/core/hle/service/olsc/native_handle_holder.cpp index d381714bc3..1e50195539 100644 --- a/src/core/hle/service/olsc/native_handle_holder.cpp +++ b/src/core/hle/service/olsc/native_handle_holder.cpp @@ -32,7 +32,7 @@ INativeHandleHolder::~INativeHandleHolder() { Result INativeHandleHolder::GetNativeHandle(OutCopyHandle out_event) { LOG_WARNING(Service_OLSC, "(STUBBED) called"); if (event) { - event->Signal(); + event->Signal(system.Kernel()); *out_event = std::addressof(event->GetReadableEvent()); } else { *out_event = nullptr; diff --git a/src/core/hle/service/olsc/native_handle_holder.h b/src/core/hle/service/olsc/native_handle_holder.h index 985c60d1cb..e0eac50c28 100644 --- a/src/core/hle/service/olsc/native_handle_holder.h +++ b/src/core/hle/service/olsc/native_handle_holder.h @@ -4,6 +4,8 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + #include "core/hle/service/cmif_types.h" #include "core/hle/service/service.h" diff --git a/src/core/hle/service/olsc/olsc.cpp b/src/core/hle/service/olsc/olsc.cpp index 18e5ad43f8..a86c525f9a 100644 --- a/src/core/hle/service/olsc/olsc.cpp +++ b/src/core/hle/service/olsc/olsc.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -9,20 +12,23 @@ namespace Service::OLSC { +class ISProfileBgAgentForSystemProcess final : public ServiceFramework { +public: + explicit ISProfileBgAgentForSystemProcess(Core::System& system_) + : ServiceFramework{system_, "spbg:sp"} + { + static const FunctionInfo functions[] = { + { 100, nullptr, "OpenBgAgentController" }, + }; + RegisterHandlers(functions); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); - - const auto OlscFactoryForApplication = [&] { - return std::make_shared(system); - }; - - const auto OlscFactoryForSystemService = [&] { - return std::make_shared(system); - }; - - server_manager->RegisterNamedService("olsc:u", OlscFactoryForApplication); - server_manager->RegisterNamedService("olsc:s", OlscFactoryForSystemService); - + server_manager->RegisterNamedService("olsc:u", std::make_shared(system)); + server_manager->RegisterNamedService("olsc:s", std::make_shared(system)); + server_manager->RegisterNamedService("spbg:sp", std::make_shared(system)); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/olsc/olsc.h b/src/core/hle/service/olsc/olsc.h index 620b634fae..62000529c5 100644 --- a/src/core/hle/service/olsc/olsc.h +++ b/src/core/hle/service/olsc/olsc.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/olsc/olsc_service_for_application.cpp b/src/core/hle/service/olsc/olsc_service_for_application.cpp index 01360f5ef2..73d7d4ed64 100644 --- a/src/core/hle/service/olsc/olsc_service_for_application.cpp +++ b/src/core/hle/service/olsc/olsc_service_for_application.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/olsc/olsc_service_for_application.h b/src/core/hle/service/olsc/olsc_service_for_application.h index 3f9ac7536a..8941659478 100644 --- a/src/core/hle/service/olsc/olsc_service_for_application.h +++ b/src/core/hle/service/olsc/olsc_service_for_application.h @@ -1,6 +1,11 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 +#pragma once + #include "core/hle/service/cmif_types.h" #include "core/hle/service/ns/ns_types.h" #include "core/hle/service/service.h" diff --git a/src/core/hle/service/olsc/olsc_service_for_system_service.h b/src/core/hle/service/olsc/olsc_service_for_system_service.h index 447e37005e..d16c8b979b 100644 --- a/src/core/hle/service/olsc/olsc_service_for_system_service.h +++ b/src/core/hle/service/olsc/olsc_service_for_system_service.h @@ -4,6 +4,8 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + #include #include "common/uuid.h" diff --git a/src/core/hle/service/olsc/remote_storage_controller.h b/src/core/hle/service/olsc/remote_storage_controller.h index bae1e8efb5..21b439013a 100644 --- a/src/core/hle/service/olsc/remote_storage_controller.h +++ b/src/core/hle/service/olsc/remote_storage_controller.h @@ -4,6 +4,8 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + #include "core/hle/service/cmif_types.h" #include "core/hle/service/service.h" diff --git a/src/core/hle/service/olsc/transfer_task_list_controller.cpp b/src/core/hle/service/olsc/transfer_task_list_controller.cpp index 0f9042bf01..95e41cdb51 100644 --- a/src/core/hle/service/olsc/transfer_task_list_controller.cpp +++ b/src/core/hle/service/olsc/transfer_task_list_controller.cpp @@ -36,7 +36,7 @@ ITransferTaskListController::ITransferTaskListController(Core::System& system_) {18, nullptr, "ListTransferTaskInfo"}, {19, nullptr, "DeleteTransferTask"}, {20, nullptr, "RaiseTransferTaskPriority"}, - {21, nullptr, "GetTransferTaskProgress"}, + {21, D<&ITransferTaskListController::GetTransferTaskProgress>, "GetTransferTaskProgress"}, //10.1.0+ {22, nullptr, "GetTransferTaskLastResult"}, {23, nullptr, "SuspendTransferTask"}, {24, D<&ITransferTaskListController::GetCurrentTransferTaskInfo>, "GetCurrentTransferTaskInfo"}, @@ -82,6 +82,11 @@ Result ITransferTaskListController::GetCurrentTransferTaskInfo(Out> out_info, InBuffer in) { LOG_WARNING(Service_OLSC, "(STUBBED) called, in_size={}", in.size()); diff --git a/src/core/hle/service/olsc/transfer_task_list_controller.h b/src/core/hle/service/olsc/transfer_task_list_controller.h index 72dbf610d8..5f3429879a 100644 --- a/src/core/hle/service/olsc/transfer_task_list_controller.h +++ b/src/core/hle/service/olsc/transfer_task_list_controller.h @@ -4,6 +4,8 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + #include "core/hle/service/cmif_types.h" #include "core/hle/service/service.h" @@ -22,6 +24,7 @@ private: Result GetTransferTaskStartEventNativeHandleHolder(Out> out_holder); Result StopNextTransferTaskExecution(Out> out_stopper); Result GetTransferTaskCount(Out out_count, u8 unknown); + Result GetTransferTaskProgress(); Result GetCurrentTransferTaskInfo(Out> out_info, u8 unknown); Result FindTransferTaskInfo(Out> out_info, InBuffer in); }; diff --git a/src/core/hle/service/omm/omm.cpp b/src/core/hle/service/omm/omm.cpp index b95319e26b..cc0b686ab1 100644 --- a/src/core/hle/service/omm/omm.cpp +++ b/src/core/hle/service/omm/omm.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/omm/omm.h b/src/core/hle/service/omm/omm.h index 7bf04688ae..6b100a6d61 100644 --- a/src/core/hle/service/omm/omm.h +++ b/src/core/hle/service/omm/omm.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/omm/operation_mode_manager.cpp b/src/core/hle/service/omm/operation_mode_manager.cpp index fe7ed84a7f..1af7714d0d 100644 --- a/src/core/hle/service/omm/operation_mode_manager.cpp +++ b/src/core/hle/service/omm/operation_mode_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/omm/operation_mode_manager.h b/src/core/hle/service/omm/operation_mode_manager.h index 32bc7b2f93..dd6753757c 100644 --- a/src/core/hle/service/omm/operation_mode_manager.h +++ b/src/core/hle/service/omm/operation_mode_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/omm/policy_manager_system.cpp b/src/core/hle/service/omm/policy_manager_system.cpp index 1cd6fd807b..b9e8277f8c 100644 --- a/src/core/hle/service/omm/policy_manager_system.cpp +++ b/src/core/hle/service/omm/policy_manager_system.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/omm/policy_manager_system.h b/src/core/hle/service/omm/policy_manager_system.h index 151ca0d2ee..2fb6eb9293 100644 --- a/src/core/hle/service/omm/policy_manager_system.h +++ b/src/core/hle/service/omm/policy_manager_system.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/omm/power_state_interface.cpp b/src/core/hle/service/omm/power_state_interface.cpp index 22cac82595..04ea75b496 100644 --- a/src/core/hle/service/omm/power_state_interface.cpp +++ b/src/core/hle/service/omm/power_state_interface.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/omm/power_state_interface.h b/src/core/hle/service/omm/power_state_interface.h index 825a6512d9..dc1d376a13 100644 --- a/src/core/hle/service/omm/power_state_interface.h +++ b/src/core/hle/service/omm/power_state_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/os/event.cpp b/src/core/hle/service/os/event.cpp index ec52c17fd8..8808c5b4f0 100644 --- a/src/core/hle/service/os/event.cpp +++ b/src/core/hle/service/os/event.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -7,25 +10,27 @@ namespace Service { -Event::Event(KernelHelpers::ServiceContext& ctx) { +Event::Event(KernelHelpers::ServiceContext& ctx_) + : ctx{ctx_} +{ m_event = ctx.CreateEvent("Event"); } Event::~Event() { - m_event->GetReadableEvent().Close(); - m_event->Close(); + m_event->GetReadableEvent().Close(ctx.kernel); + m_event->Close(ctx.kernel); } -void Event::Signal() { - m_event->Signal(); +void Event::Signal(Kernel::KernelCore& kernel) noexcept { + m_event->Signal(kernel); } -void Event::Clear() { - m_event->Clear(); +void Event::Clear(Kernel::KernelCore& kernel) noexcept { + m_event->Clear(kernel); } -Kernel::KReadableEvent* Event::GetHandle() { - return &m_event->GetReadableEvent(); +Kernel::KReadableEvent* Event::GetHandle() noexcept { + return std::addressof(m_event->GetReadableEvent()); } } // namespace Service diff --git a/src/core/hle/service/os/event.h b/src/core/hle/service/os/event.h index cdbc4635a6..bc23598b58 100644 --- a/src/core/hle/service/os/event.h +++ b/src/core/hle/service/os/event.h @@ -1,9 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 #pragma once namespace Kernel { +class KernelCore; class KEvent; class KReadableEvent; } // namespace Kernel @@ -19,12 +23,12 @@ public: explicit Event(KernelHelpers::ServiceContext& ctx); ~Event(); - void Signal(); - void Clear(); - - Kernel::KReadableEvent* GetHandle(); + void Signal(Kernel::KernelCore& kernel) noexcept; + void Clear(Kernel::KernelCore& kernel) noexcept; + Kernel::KReadableEvent* GetHandle() noexcept; private: + KernelHelpers::ServiceContext& ctx; Kernel::KEvent* m_event; }; diff --git a/src/core/hle/service/os/multi_wait.cpp b/src/core/hle/service/os/multi_wait.cpp index 7b80d28bed..c9bb256ab0 100644 --- a/src/core/hle/service/os/multi_wait.cpp +++ b/src/core/hle/service/os/multi_wait.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/os/multi_wait.h b/src/core/hle/service/os/multi_wait.h index 340c611b5b..c9076c21d7 100644 --- a/src/core/hle/service/os/multi_wait.h +++ b/src/core/hle/service/os/multi_wait.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/os/multi_wait_holder.cpp b/src/core/hle/service/os/multi_wait_holder.cpp index 01efa045b5..f3864218c9 100644 --- a/src/core/hle/service/os/multi_wait_holder.cpp +++ b/src/core/hle/service/os/multi_wait_holder.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/os/multi_wait_holder.h b/src/core/hle/service/os/multi_wait_holder.h index 646395a3f3..3b28ec6b5a 100644 --- a/src/core/hle/service/os/multi_wait_holder.h +++ b/src/core/hle/service/os/multi_wait_holder.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/os/multi_wait_utils.h b/src/core/hle/service/os/multi_wait_utils.h index 96d3a10f37..d8f3ade4c1 100644 --- a/src/core/hle/service/os/multi_wait_utils.h +++ b/src/core/hle/service/os/multi_wait_utils.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/os/mutex.cpp b/src/core/hle/service/os/mutex.cpp index 6009f48668..12b872b57f 100644 --- a/src/core/hle/service/os/mutex.cpp +++ b/src/core/hle/service/os/mutex.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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,22 +13,22 @@ namespace Service { Mutex::Mutex(Core::System& system) : m_system(system) { m_event = Kernel::KEvent::Create(system.Kernel()); - m_event->Initialize(nullptr); + m_event->Initialize(system.Kernel(), nullptr); // Register the event. Kernel::KEvent::Register(system.Kernel(), m_event); - ASSERT(R_SUCCEEDED(m_event->Signal())); + ASSERT(R_SUCCEEDED(m_event->Signal(system.Kernel()))); } Mutex::~Mutex() { - m_event->GetReadableEvent().Close(); - m_event->Close(); + m_event->GetReadableEvent().Close(m_system.Kernel()); + m_event->Close(m_system.Kernel()); } void Mutex::lock() { // Infinitely retry until we successfully clear the event. - while (R_FAILED(m_event->GetReadableEvent().Reset())) { + while (R_FAILED(m_event->GetReadableEvent().Reset(m_system.Kernel()))) { s32 index; Kernel::KSynchronizationObject* obj = &m_event->GetReadableEvent(); @@ -40,7 +43,7 @@ void Mutex::lock() { void Mutex::unlock() { // Unlock. - ASSERT(R_SUCCEEDED(m_event->Signal())); + ASSERT(R_SUCCEEDED(m_event->Signal(m_system.Kernel()))); } } // namespace Service diff --git a/src/core/hle/service/os/mutex.h b/src/core/hle/service/os/mutex.h index 95ac9b117a..8e29bbb633 100644 --- a/src/core/hle/service/os/mutex.h +++ b/src/core/hle/service/os/mutex.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/os/process.cpp b/src/core/hle/service/os/process.cpp index 0dbadc315e..5e46de245d 100644 --- a/src/core/hle/service/os/process.cpp +++ b/src/core/hle/service/os/process.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -10,14 +13,6 @@ namespace Service { -Process::Process(Core::System& system) - : m_system(system), m_process(), m_main_thread_priority(), m_main_thread_stack_size(), - m_process_started() {} - -Process::~Process() { - this->Finalize(); -} - bool Process::Initialize(Loader::AppLoader& loader, Loader::ResultStatus& out_load_result) { // First, ensure we are not holding another process. this->Finalize(); @@ -28,7 +23,7 @@ bool Process::Initialize(Loader::AppLoader& loader, Loader::ResultStatus& out_lo // On exit, ensure we free the additional reference to the process. SCOPE_EXIT { - process->Close(); + process->Close(m_system.Kernel()); }; // Insert process modules into memory. @@ -52,7 +47,7 @@ bool Process::Initialize(Loader::AppLoader& loader, Loader::ResultStatus& out_lo // Take ownership of the process object. m_process = process; - m_process->Open(); + m_process->Open(m_system.Kernel()); // We succeeded. return true; @@ -64,8 +59,7 @@ void Process::Finalize() { // Close the process. if (m_process) { - m_process->Close(); - + m_process->Close(m_system.Kernel()); // TODO: remove this, kernel already tracks this m_system.Kernel().RemoveProcess(m_process); } @@ -85,7 +79,7 @@ bool Process::Run() { // Start. if (m_process) { - m_process->Run(m_main_thread_priority, m_main_thread_stack_size); + m_process->Run(m_system.Kernel(), m_main_thread_priority, m_main_thread_stack_size); } // Mark as started. @@ -97,13 +91,13 @@ bool Process::Run() { void Process::Terminate() { if (m_process) { - m_process->Terminate(); + m_process->Terminate(m_system.Kernel()); } } void Process::ResetSignal() { if (m_process) { - m_process->Reset(); + m_process->Reset(m_system.Kernel()); } } @@ -144,8 +138,7 @@ u64 Process::GetProgramId() const { void Process::Suspend(bool suspended) { if (m_process) { - m_process->SetActivity(suspended ? Kernel::Svc::ProcessActivity::Paused - : Kernel::Svc::ProcessActivity::Runnable); + m_process->SetActivity(m_system.Kernel(), suspended ? Kernel::Svc::ProcessActivity::Paused : Kernel::Svc::ProcessActivity::Runnable); } } diff --git a/src/core/hle/service/os/process.h b/src/core/hle/service/os/process.h index 9109b7d0a5..103ba7aa6c 100644 --- a/src/core/hle/service/os/process.h +++ b/src/core/hle/service/os/process.h @@ -1,8 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 #pragma once +#include + #include "common/common_types.h" namespace Core { @@ -22,8 +27,17 @@ namespace Service { class Process { public: - explicit Process(Core::System& system); - ~Process(); + inline explicit Process(Core::System& system) noexcept : m_system(system) {} + inline ~Process() { this->Finalize(); } + + Process(const Process&) = delete; + Process& operator=(const Process&) = delete; + Process& operator=(Process&&) = delete; + inline Process(Process&& other) noexcept + : m_system(other.m_system), m_process(std::exchange(other.m_process, nullptr)), + m_main_thread_stack_size(std::exchange(other.m_main_thread_stack_size, 0)), + m_main_thread_priority(std::exchange(other.m_main_thread_priority, 0)), + m_process_started(std::exchange(other.m_process_started, false)) {} bool Initialize(Loader::AppLoader& loader, Loader::ResultStatus& out_load_result); void Finalize(); @@ -50,8 +64,8 @@ public: private: Core::System& m_system; Kernel::KProcess* m_process{}; - s32 m_main_thread_priority{}; u64 m_main_thread_stack_size{}; + s32 m_main_thread_priority{}; bool m_process_started{}; }; diff --git a/src/core/hle/service/pcie/pcie.cpp b/src/core/hle/service/pcie/pcie.cpp index afb8067cd5..d162881ecf 100644 --- a/src/core/hle/service/pcie/pcie.cpp +++ b/src/core/hle/service/pcie/pcie.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -9,6 +9,7 @@ #include "core/hle/service/pcie/pcie.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/service.h" +#include "frontend_common/firmware_manager.h" namespace Service::PCIe { @@ -48,9 +49,9 @@ public: } }; -class PCIe final : public ServiceFramework { +class PCIE final : public ServiceFramework { public: - explicit PCIe(Core::System& system_) : ServiceFramework{system_, "pcie"} { + explicit PCIE(Core::System& system_) : ServiceFramework{system_, "pcie"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RegisterClassDriver"}, @@ -62,10 +63,27 @@ public: } }; +class PCIE_LOG final : public ServiceFramework { +public: + explicit PCIE_LOG(Core::System& system_) : ServiceFramework{system_, "pcie:log"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetLoggedState"}, + {1, nullptr, "GetLoggedStateEvent"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); - server_manager->RegisterNamedService("pcie", std::make_shared(system)); + server_manager->RegisterNamedService("pcie", std::make_shared(system)); + // +6.0.0 + if (FirmwareManager::GetFirmwareVersion(system).first.major >= 6) { + server_manager->RegisterNamedService("pcie:log", std::make_shared(system)); + } ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/pcie/pcie.h b/src/core/hle/service/pcie/pcie.h index 5c2d4b805b..cf2048ce65 100644 --- a/src/core/hle/service/pcie/pcie.h +++ b/src/core/hle/service/pcie/pcie.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/pctl/parental_control_service.cpp b/src/core/hle/service/pctl/parental_control_service.cpp index c2ddbcbc51..51953c2657 100644 --- a/src/core/hle/service/pctl/parental_control_service.cpp +++ b/src/core/hle/service/pctl/parental_control_service.cpp @@ -1,9 +1,11 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 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 + #include "core/core.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" @@ -13,8 +15,10 @@ namespace Service::PCTL { -IParentalControlService::IParentalControlService(Core::System& system_, Capability capability_) +IParentalControlService::IParentalControlService(Core::System& system_, Capability capability_, + u64 program_id_) : ServiceFramework{system_, "IParentalControlService"}, capability{capability_}, + program_id{program_id_}, service_context{system_, "IParentalControlService"}, synchronization_event{service_context}, unlinked_event{service_context}, request_suspension_event{service_context} { // clang-format off @@ -92,6 +96,7 @@ IParentalControlService::IParentalControlService(Core::System& system_, Capabili {1457, D<&IParentalControlService::GetPlayTimerEventToRequestSuspension>, "GetPlayTimerEventToRequestSuspension"}, {1458, D<&IParentalControlService::IsPlayTimerAlarmDisabled>, "IsPlayTimerAlarmDisabled"}, {1459, D<&IParentalControlService::GetPlayTimerRemainingTimeDisplayInfo>, "GetPlayTimerRemainingTimeDisplayInfo"}, + {1460, D<&IParentalControlService::Unknown1460>, "Unknown1460"}, {1471, nullptr, "NotifyWrongPinCodeInputManyTimes"}, {1472, nullptr, "CancelNetworkRequest"}, {1473, D<&IParentalControlService::GetUnlinkedEvent>, "GetUnlinkedEvent"}, @@ -202,7 +207,6 @@ Result IParentalControlService::Initialize() { // TODO(ogniK): Recovery flag initialization for pctl:r - const auto program_id = system.GetApplicationProcessProgramID(); if (program_id != 0) { const FileSys::PatchManager pm{program_id, system.GetFileSystemController(), system.GetContentProvider()}; @@ -439,8 +443,15 @@ Result IParentalControlService::IsPlayTimerAlarmDisabled(Out out_play_time R_SUCCEED(); } -Result IParentalControlService::GetPlayTimerRemainingTimeDisplayInfo(/* Out 0x18 */) { - LOG_INFO(Service_PCTL, "called"); +Result IParentalControlService::GetPlayTimerRemainingTimeDisplayInfo(Out out_display_info) { + LOG_DEBUG(Service_PCTL, "called"); + *out_display_info = {.state = PlayTimerDisplayState::NotConfigured}; + R_SUCCEED(); +} + +Result IParentalControlService::Unknown1460(u8 in_unk, Out out_display_info) { + LOG_DEBUG(Service_PCTL, "called, in_unk={}", in_unk); + *out_display_info = {.state = PlayTimerDisplayState::NotConfigured}; R_SUCCEED(); } diff --git a/src/core/hle/service/pctl/parental_control_service.h b/src/core/hle/service/pctl/parental_control_service.h index 1c96360561..9dabe3d059 100644 --- a/src/core/hle/service/pctl/parental_control_service.h +++ b/src/core/hle/service/pctl/parental_control_service.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator @@ -16,7 +16,8 @@ namespace Service::PCTL { class IParentalControlService final : public ServiceFramework { public: - explicit IParentalControlService(Core::System& system_, Capability capability_); + explicit IParentalControlService(Core::System& system_, Capability capability_, + u64 program_id_); ~IParentalControlService() override; private: @@ -54,7 +55,8 @@ private: Result GetPlayTimerSettingsOld(Out out_play_timer_settings); Result GetPlayTimerEventToRequestSuspension(OutCopyHandle out_event); Result IsPlayTimerAlarmDisabled(Out out_play_timer_alarm_disabled); - Result GetPlayTimerRemainingTimeDisplayInfo(); + Result GetPlayTimerRemainingTimeDisplayInfo(Out out_display_info); + Result Unknown1460(u8 in_unk, Out out_display_info); Result GetUnlinkedEvent(OutCopyHandle out_event); Result GetStereoVisionRestriction(Out out_stereo_vision_restriction); Result SetStereoVisionRestriction(bool stereo_vision_restriction); @@ -84,7 +86,8 @@ private: RestrictionSettings restriction_settings{}; std::array pin_code{}; Capability capability{}; - // TODO: this is RAW as fuck + u64 program_id{}; + // TODO: this is raw PlayTimerSettings raw_play_timer_settings{}; KernelHelpers::ServiceContext service_context; diff --git a/src/core/hle/service/pctl/parental_control_service_factory.cpp b/src/core/hle/service/pctl/parental_control_service_factory.cpp index 7d8f361e95..ae278a9bbc 100644 --- a/src/core/hle/service/pctl/parental_control_service_factory.cpp +++ b/src/core/hle/service/pctl/parental_control_service_factory.cpp @@ -1,6 +1,10 @@ +// SPDX-FileCopyrightText: Copyright 2026 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/cmif_serialization.h" #include "core/hle/service/pctl/parental_control_service.h" #include "core/hle/service/pctl/parental_control_service_factory.h" @@ -23,17 +27,17 @@ IParentalControlServiceFactory::~IParentalControlServiceFactory() = default; Result IParentalControlServiceFactory::CreateService( Out> out_service, ClientProcessId process_id) { - LOG_DEBUG(Service_PCTL, "called"); - // TODO(ogniK): Get application id from process - *out_service = std::make_shared(system, capability); + LOG_DEBUG(Service_PCTL, "called, process_id={}", process_id.pid); + *out_service = std::make_shared( + system, capability, system.ResolveCallerProgramId(*process_id)); R_SUCCEED(); } Result IParentalControlServiceFactory::CreateServiceWithoutInitialize( Out> out_service, ClientProcessId process_id) { - LOG_DEBUG(Service_PCTL, "called"); - // TODO(ogniK): Get application id from process - *out_service = std::make_shared(system, capability); + LOG_DEBUG(Service_PCTL, "called, process_id={}", process_id.pid); + *out_service = std::make_shared( + system, capability, system.ResolveCallerProgramId(*process_id)); R_SUCCEED(); } diff --git a/src/core/hle/service/pctl/parental_control_service_factory.h b/src/core/hle/service/pctl/parental_control_service_factory.h index 362988add1..0b5afede82 100644 --- a/src/core/hle/service/pctl/parental_control_service_factory.h +++ b/src/core/hle/service/pctl/parental_control_service_factory.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/pctl/pctl.cpp b/src/core/hle/service/pctl/pctl.cpp index d92dbe2169..37f18a5d58 100644 --- a/src/core/hle/service/pctl/pctl.cpp +++ b/src/core/hle/service/pctl/pctl.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/pctl/pctl.h b/src/core/hle/service/pctl/pctl.h index 5f9d03d4df..020866aa3d 100644 --- a/src/core/hle/service/pctl/pctl.h +++ b/src/core/hle/service/pctl/pctl.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/pctl/pctl_results.h b/src/core/hle/service/pctl/pctl_results.h index 1fc54727b7..0aa4714a9c 100644 --- a/src/core/hle/service/pctl/pctl_results.h +++ b/src/core/hle/service/pctl/pctl_results.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/pctl/pctl_types.h b/src/core/hle/service/pctl/pctl_types.h index a559c5d35f..3a5a19099d 100644 --- a/src/core/hle/service/pctl/pctl_types.h +++ b/src/core/hle/service/pctl/pctl_types.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -49,4 +49,25 @@ struct PlayTimerSettings { }; static_assert(sizeof(PlayTimerSettings) == 0x44, "PlayTimerSettings has incorrect size."); +enum class PlayTimerDisplayState : u8 { + TimesUp = 0, + BedtimeAlarm = 1, + RemainingTime = 2, + Unknown3 = 3, + Unknown4 = 4, + Unknown5 = 5, + TimerDisabled = 6, + NotConfigured = 7, +}; + +// This is nn::pctl::PlayTimerRemainingTimeDisplayInfo +struct PlayTimerRemainingTimeDisplayInfo { + PlayTimerDisplayState state; + INSERT_PADDING_BYTES(0x7); + u64 unknown_08; + u64 remaining_time; +}; +static_assert(sizeof(PlayTimerRemainingTimeDisplayInfo) == 0x18, + "PlayTimerRemainingTimeDisplayInfo has incorrect size."); + } // namespace Service::PCTL diff --git a/src/core/hle/service/pcv/pcv.cpp b/src/core/hle/service/pcv/pcv.cpp index 3d0f2aeb7d..1b314f3518 100644 --- a/src/core/hle/service/pcv/pcv.cpp +++ b/src/core/hle/service/pcv/pcv.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -52,6 +55,30 @@ public: } }; +class PCV_ARB final : public ServiceFramework { +public: + explicit PCV_ARB(Core::System& system_) : ServiceFramework{system_, "pcv:arb"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "ReleaseControl"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class PCV_IMM final : public ServiceFramework { +public: + explicit PCV_IMM(Core::System& system_) : ServiceFramework{system_, "pcv:imm"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "SetClockRate"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + class IClkrstSession final : public ServiceFramework { public: explicit IClkrstSession(Core::System& system_, DeviceCode device_code_) @@ -124,7 +151,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system, device_code); + rb.PushIpcInterface(ctx, system, device_code); } }; @@ -145,6 +172,8 @@ void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); server_manager->RegisterNamedService("pcv", std::make_shared(system)); + server_manager->RegisterNamedService("pcv:arb", std::make_shared(system)); + server_manager->RegisterNamedService("pcv:imm", std::make_shared(system)); server_manager->RegisterNamedService("clkrst", std::make_shared(system, "clkrst")); server_manager->RegisterNamedService("clkrst:i", std::make_shared(system, "clkrst:i")); server_manager->RegisterNamedService("clkrst:a", std::make_shared(system)); diff --git a/src/core/hle/service/pcv/pcv.h b/src/core/hle/service/pcv/pcv.h index bf541e6feb..27eb179ff4 100644 --- a/src/core/hle/service/pcv/pcv.h +++ b/src/core/hle/service/pcv/pcv.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp index b52468e419..ff7053b690 100644 --- a/src/core/hle/service/pm/pm.cpp +++ b/src/core/hle/service/pm/pm.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -25,19 +28,15 @@ constexpr u64 NO_PROCESS_FOUND_PID{0}; using ProcessList = std::list>; template -Kernel::KScopedAutoObject SearchProcessList(ProcessList& process_list, - F&& predicate) { - const auto iter = std::find_if(process_list.begin(), process_list.end(), predicate); - - if (iter == process_list.end()) { - return nullptr; - } - - return iter->GetPointerUnsafe(); +Kernel::KScopedAutoObject SearchProcessList(Kernel::KernelCore& kernel, ProcessList& process_list, F&& predicate) { + auto const it = std::find_if(process_list.begin(), process_list.end(), predicate); + if (it == process_list.end()) + return {kernel, nullptr}; + return {kernel, it->GetPointerUnsafe()}; } -void GetApplicationPidGeneric(HLERequestContext& ctx, ProcessList& process_list) { - auto process = SearchProcessList(process_list, [](auto& p) { return p->IsApplication(); }); +void GetApplicationPidGeneric(Kernel::KernelCore& kernel, HLERequestContext& ctx, ProcessList& process_list) { + auto process = SearchProcessList(kernel, process_list, [](auto& p) { return p->IsApplication(); }); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); @@ -105,7 +104,7 @@ private: LOG_DEBUG(Service_PM, "called, program_id={:016X}", program_id); auto list = kernel.GetProcessList(); - auto process = SearchProcessList( + auto process = SearchProcessList(system.Kernel(), list, [program_id](auto& p) { return p->GetProgramId() == program_id; }); if (process.IsNull()) { @@ -122,7 +121,7 @@ private: void GetApplicationProcessId(HLERequestContext& ctx) { LOG_DEBUG(Service_PM, "called"); auto list = kernel.GetProcessList(); - GetApplicationPidGeneric(ctx, list); + GetApplicationPidGeneric(system.Kernel(), ctx, list); } void AtmosphereGetProcessInfo(HLERequestContext& ctx) { @@ -133,8 +132,7 @@ private: LOG_WARNING(Service_PM, "(Partial Implementation) called, pid={:016X}", pid); - auto list = kernel.GetProcessList(); - auto process = SearchProcessList(list, [pid](auto& p) { return p->GetProcessId() == pid; }); + auto process = kernel.GetProcessByProcessId(pid); if (process.IsNull()) { IPC::ResponseBuilder rb{ctx, 2}; @@ -162,7 +160,7 @@ private: IPC::ResponseBuilder rb{ctx, 10, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(*process); + rb.PushCopyObjects(ctx, *process); rb.PushRaw(program_location); rb.PushRaw(override_status); } @@ -187,9 +185,7 @@ private: LOG_DEBUG(Service_PM, "called, process_id={:016X}", process_id); - auto list = kernel.GetProcessList(); - auto process = SearchProcessList( - list, [process_id](auto& p) { return p->GetProcessId() == process_id; }); + auto process = kernel.GetProcessByProcessId(process_id); if (process.IsNull()) { IPC::ResponseBuilder rb{ctx, 2}; @@ -209,7 +205,7 @@ private: LOG_DEBUG(Service_PM, "called, program_id={:016X}", program_id); auto list = system.Kernel().GetProcessList(); - auto process = SearchProcessList( + auto process = SearchProcessList(system.Kernel(), list, [program_id](auto& p) { return p->GetProgramId() == program_id; }); if (process.IsNull()) { @@ -249,17 +245,17 @@ private: void GetApplicationProcessIdForShell(HLERequestContext& ctx) { LOG_DEBUG(Service_PM, "called"); auto list = kernel.GetProcessList(); - GetApplicationPidGeneric(ctx, list); + GetApplicationPidGeneric(system.Kernel(), ctx, list); } }; void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); - server_manager->RegisterNamedService("pm:bm", std::make_shared(system)); - server_manager->RegisterNamedService("pm:dmnt", std::make_shared(system)); - server_manager->RegisterNamedService("pm:info", std::make_shared(system)); - server_manager->RegisterNamedService("pm:shell", std::make_shared(system)); + server_manager->RegisterNamedService("pm:bm", std::make_shared(system), 4); // Nx = 4, Ams = 8 + server_manager->RegisterNamedService("pm:dmnt", std::make_shared(system), 16); + server_manager->RegisterNamedService("pm:shell", std::make_shared(system), 3); //Nx = 3, AMS = 8 + server_manager->RegisterNamedService("pm:info", std::make_shared(system), 25); //48-(4+16+3) ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/pm/pm.h b/src/core/hle/service/pm/pm.h index 5d4a1a171c..da70cfc6b8 100644 --- a/src/core/hle/service/pm/pm.h +++ b/src/core/hle/service/pm/pm.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp index 02fa82047d..e76c3ea93c 100644 --- a/src/core/hle/service/prepo/prepo.cpp +++ b/src/core/hle/service/prepo/prepo.cpp @@ -76,7 +76,7 @@ private: Type, process_id, data1.size(), data2.size()); const auto& reporter{system.GetReporter()}; - reporter.SavePlayReport(Type, system.GetApplicationProcessProgramID(), {data1, data2}, + reporter.SavePlayReport(Type, system.ResolveCallerProgramId(process_id), {data1, data2}, process_id); IPC::ResponseBuilder rb{ctx, 2}; @@ -98,7 +98,7 @@ private: Type, user_id[1], user_id[0], process_id, data1.size(), data2.size()); const auto& reporter{system.GetReporter()}; - reporter.SavePlayReport(Type, system.GetApplicationProcessProgramID(), {data1, data2}, + reporter.SavePlayReport(Type, system.ResolveCallerProgramId(process_id), {data1, data2}, process_id, user_id); IPC::ResponseBuilder rb{ctx, 2}; diff --git a/src/core/hle/service/prepo/prepo.h b/src/core/hle/service/prepo/prepo.h index 2c2462f936..4caf752888 100644 --- a/src/core/hle/service/prepo/prepo.h +++ b/src/core/hle/service/prepo/prepo.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/ovln/ovln_types.h b/src/core/hle/service/psc/ovln/ovln_types.h index f96b52fb49..d827f039fe 100644 --- a/src/core/hle/service/psc/ovln/ovln_types.h +++ b/src/core/hle/service/psc/ovln/ovln_types.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/psc/ovln/receiver.cpp b/src/core/hle/service/psc/ovln/receiver.cpp index f7d3882a05..f43d705126 100644 --- a/src/core/hle/service/psc/ovln/receiver.cpp +++ b/src/core/hle/service/psc/ovln/receiver.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -100,7 +100,7 @@ Result IReceiver::ReceiveWithTick(Out out_notification, } } if (!has_messages) { - receive_event->Clear(); + receive_event->Clear(system.Kernel()); } R_SUCCEED(); diff --git a/src/core/hle/service/psc/ovln/receiver.h b/src/core/hle/service/psc/ovln/receiver.h index 2b96216a1a..5f73d1a66c 100644 --- a/src/core/hle/service/psc/ovln/receiver.h +++ b/src/core/hle/service/psc/ovln/receiver.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/psc/ovln/receiver_service.cpp b/src/core/hle/service/psc/ovln/receiver_service.cpp index d869325181..3b0b180725 100644 --- a/src/core/hle/service/psc/ovln/receiver_service.cpp +++ b/src/core/hle/service/psc/ovln/receiver_service.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -24,7 +24,7 @@ IReceiverService::~IReceiverService() = default; Result IReceiverService::OpenReceiver(Out> out_receiver) { LOG_DEBUG(Service_PSC, "called"); - *out_receiver = std::shared_ptr(new IReceiver(system)); + *out_receiver = std::make_shared(system); R_SUCCEED(); } diff --git a/src/core/hle/service/psc/ovln/receiver_service.h b/src/core/hle/service/psc/ovln/receiver_service.h index b3b31ba4aa..a7a9b012e8 100644 --- a/src/core/hle/service/psc/ovln/receiver_service.h +++ b/src/core/hle/service/psc/ovln/receiver_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/ovln/sender.cpp b/src/core/hle/service/psc/ovln/sender.cpp index 3227a56f25..dffcd4bc36 100644 --- a/src/core/hle/service/psc/ovln/sender.cpp +++ b/src/core/hle/service/psc/ovln/sender.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/ovln/sender.h b/src/core/hle/service/psc/ovln/sender.h index c1575428e3..9b0fe786bd 100644 --- a/src/core/hle/service/psc/ovln/sender.h +++ b/src/core/hle/service/psc/ovln/sender.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/ovln/sender_service.cpp b/src/core/hle/service/psc/ovln/sender_service.cpp index 18d2c83a3d..8f80a5c461 100644 --- a/src/core/hle/service/psc/ovln/sender_service.cpp +++ b/src/core/hle/service/psc/ovln/sender_service.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/ovln/sender_service.h b/src/core/hle/service/psc/ovln/sender_service.h index 10027701f4..8176e85e23 100644 --- a/src/core/hle/service/psc/ovln/sender_service.h +++ b/src/core/hle/service/psc/ovln/sender_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/pm_control.cpp b/src/core/hle/service/psc/pm_control.cpp index 7dedb76629..fc5852f863 100644 --- a/src/core/hle/service/psc/pm_control.cpp +++ b/src/core/hle/service/psc/pm_control.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/pm_control.h b/src/core/hle/service/psc/pm_control.h index e0ae2f39ca..7967f7ab18 100644 --- a/src/core/hle/service/psc/pm_control.h +++ b/src/core/hle/service/psc/pm_control.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/pm_module.cpp b/src/core/hle/service/psc/pm_module.cpp index 74dc7ed4e8..38ca36ab8f 100644 --- a/src/core/hle/service/psc/pm_module.cpp +++ b/src/core/hle/service/psc/pm_module.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/pm_module.h b/src/core/hle/service/psc/pm_module.h index b3a2d25842..dac8fcff2a 100644 --- a/src/core/hle/service/psc/pm_module.h +++ b/src/core/hle/service/psc/pm_module.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/pm_service.cpp b/src/core/hle/service/psc/pm_service.cpp index c4e0ad2286..d1803bdf48 100644 --- a/src/core/hle/service/psc/pm_service.cpp +++ b/src/core/hle/service/psc/pm_service.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/pm_service.h b/src/core/hle/service/psc/pm_service.h index 08e14c6f8f..721a0602c9 100644 --- a/src/core/hle/service/psc/pm_service.h +++ b/src/core/hle/service/psc/pm_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/psc.cpp b/src/core/hle/service/psc/psc.cpp index e1762d6943..31fb9f0117 100644 --- a/src/core/hle/service/psc/psc.cpp +++ b/src/core/hle/service/psc/psc.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -14,23 +17,109 @@ namespace Service::PSC { +class PSC_L final : public ServiceFramework { +public: + explicit PSC_L(Core::System& system_) : ServiceFramework{system_, "psc:l"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "Initialize_3"}, + {1, nullptr, "Lock"}, + {2, nullptr, "Unlock"}, + {3, nullptr, "IsLocked"}, + {4, nullptr, "GetRelatedState"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class INS_R final : public ServiceFramework { +public: + explicit INS_R(Core::System& system_) : ServiceFramework{system_, "ins:r"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetInputSourceState"}, + {1, nullptr, "GetTriggerTargetEvent"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class INS_S final : public ServiceFramework { +public: + explicit INS_S(Core::System& system_) : ServiceFramework{system_, "ins:s"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetNotifyEvent"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class HSHL_SYS final : public ServiceFramework { +public: + explicit HSHL_SYS(Core::System& system_) : ServiceFramework{system_, "hshl:sys"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetBatteryPercentage"}, + {1, nullptr, "GetChargerType"}, + {2, nullptr, "OpenChargeSession"}, + {3, nullptr, "GetRawBatteryPercentage"}, + {4, nullptr, "GetBatteryVoltageLevel"}, + {5, nullptr, "OpenThermalSession"}, + {6, nullptr, "GetAbnormalTemperatureSet"}, + {7, nullptr, "OpenClockSession"}, + {8, nullptr, "GetClockRate"}, + {9, nullptr, "OpenBridgeSession"}, + {10, nullptr, "GetBridgePowerSupply"}, + {11, nullptr, "OpenVsysVoltageSession"}, + {12, nullptr, "GetIsBatteryEnoughForFullAwake"}, + {13, nullptr, "GetIsCharging"}, + {14, nullptr, "Cmd14"}, + {15, nullptr, "Cmd15"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class HSHL_SET final : public ServiceFramework { +public: + explicit HSHL_SET(Core::System& system_) : ServiceFramework{system_, "hshl:set"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "OpenChargeSession_2"}, + {1, nullptr, "OpenThermalSession_2"}, + {2, nullptr, "SetClockRate"}, + {3, nullptr, "SetBridgePowerSupply"}, + {4, nullptr, "Cmd4"}, + {5, nullptr, "Cmd5"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); server_manager->RegisterNamedService("psc:c", std::make_shared(system)); server_manager->RegisterNamedService("psc:m", std::make_shared(system)); + server_manager->RegisterNamedService("psc:l", std::make_shared(system)); + server_manager->RegisterNamedService("ins:r", std::make_shared(system)); + server_manager->RegisterNamedService("ins:s", std::make_shared(system)); + server_manager->RegisterNamedService("hshl:sys", std::make_shared(system)); + server_manager->RegisterNamedService("hshl:set", std::make_shared(system)); server_manager->RegisterNamedService("ovln:rcv", std::make_shared(system)); server_manager->RegisterNamedService("ovln:snd", std::make_shared(system)); auto time = std::make_shared(system); - server_manager->RegisterNamedService( - "time:m", std::make_shared(system, time, server_manager.get())); - server_manager->RegisterNamedService( - "time:su", std::make_shared( - system, Time::StaticServiceSetupInfo{0, 0, 0, 0, 0, 1}, time, "time:su")); - server_manager->RegisterNamedService("time:al", - std::make_shared(system, time)); + server_manager->RegisterNamedService("time:m", std::make_shared(system, time, server_manager.get())); + server_manager->RegisterNamedService("time:su", std::make_shared(system, Time::StaticServiceSetupInfo{0, 0, 0, 0, 0, 1}, time, "time:su")); + server_manager->RegisterNamedService("time:al", std::make_shared(system, time)); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/psc/psc.h b/src/core/hle/service/psc/psc.h index c83d07ca80..d89e6854cc 100644 --- a/src/core/hle/service/psc/psc.h +++ b/src/core/hle/service/psc/psc.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/alarms.cpp b/src/core/hle/service/psc/time/alarms.cpp index 5e52c19f82..17a795c4c5 100644 --- a/src/core/hle/service/psc/time/alarms.cpp +++ b/src/core/hle/service/psc/time/alarms.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -7,8 +10,10 @@ namespace Service::PSC::Time { Alarm::Alarm(Core::System& system, KernelHelpers::ServiceContext& ctx, AlarmType type) - : m_ctx{ctx}, m_event{ctx.CreateEvent("Psc:Alarm:Event")} { - m_event->Clear(); + : m_ctx{ctx} + , m_event{ctx.CreateEvent("Psc:Alarm:Event")} +{ + m_event->Clear(system.Kernel()); switch (type) { case WakeupAlarm: @@ -27,17 +32,19 @@ Alarm::~Alarm() { m_ctx.CloseEvent(m_event); } -Alarms::Alarms(Core::System& system, StandardSteadyClockCore& steady_clock, - PowerStateRequestManager& power_state_request_manager) - : m_system{system}, m_ctx{system, "Psc:Alarms"}, m_steady_clock{steady_clock}, - m_power_state_request_manager{power_state_request_manager}, m_event{m_ctx.CreateEvent( - "Psc:Alarms:Event")} {} +Alarms::Alarms(Core::System& system, StandardSteadyClockCore& steady_clock, PowerStateRequestManager& power_state_request_manager) + : m_system{system} + , m_ctx{system, "Psc:Alarms"} + , m_steady_clock{steady_clock} + , m_power_state_request_manager{power_state_request_manager} + , m_event{m_ctx.CreateEvent("Psc:Alarms:Event")} +{} Alarms::~Alarms() { m_ctx.CloseEvent(m_event); } -Result Alarms::Enable(Alarm& alarm, s64 time) { +Result Alarms::Enable(Kernel::KernelCore& kernel, Alarm& alarm, s64 time) { R_UNLESS(m_steady_clock.IsInitialized(), ResultClockUninitialized); std::scoped_lock l{m_mutex}; @@ -49,21 +56,21 @@ Result Alarms::Enable(Alarm& alarm, s64 time) { time_ns = Common::AlignUp(time_ns, one_second_ns); alarm.SetAlertTime(time_ns); - Insert(alarm); - R_RETURN(UpdateClosestAndSignal()); + Insert(kernel, alarm); + R_RETURN(UpdateClosestAndSignal(kernel)); } -void Alarms::Disable(Alarm& alarm) { +void Alarms::Disable(Kernel::KernelCore& kernel, Alarm& alarm) { std::scoped_lock l{m_mutex}; if (!alarm.IsLinked()) { return; } - Erase(alarm); - UpdateClosestAndSignal(); + Erase(kernel, alarm); + UpdateClosestAndSignal(kernel); } -void Alarms::CheckAndSignal() { +void Alarms::CheckAndSignal(Kernel::KernelCore& kernel) { std::scoped_lock l{m_mutex}; if (m_alarms.empty()) { return; @@ -72,9 +79,9 @@ void Alarms::CheckAndSignal() { bool alarm_signalled{false}; for (auto& alarm : m_alarms) { if (m_steady_clock.GetRawTime() >= alarm.GetAlertTime()) { - alarm.Signal(); + alarm.Signal(kernel); alarm.Lock(); - Erase(alarm); + Erase(kernel, alarm); m_power_state_request_manager.UpdatePendingPowerStateRequestPriority( alarm.GetPriority()); @@ -87,7 +94,7 @@ void Alarms::CheckAndSignal() { } m_power_state_request_manager.SignalPowerStateRequestAvailability(); - UpdateClosestAndSignal(); + UpdateClosestAndSignal(kernel); } bool Alarms::GetClosestAlarm(Alarm** out_alarm) { @@ -97,7 +104,7 @@ bool Alarms::GetClosestAlarm(Alarm** out_alarm) { return alarm != nullptr; } -void Alarms::Insert(Alarm& alarm) { +void Alarms::Insert(Kernel::KernelCore& kernel, Alarm& alarm) { // Alarms are sorted by alert time, then priority auto it{m_alarms.begin()}; while (it != m_alarms.end()) { @@ -113,21 +120,23 @@ void Alarms::Insert(Alarm& alarm) { m_alarms.push_back(alarm); } -void Alarms::Erase(Alarm& alarm) { +void Alarms::Erase(Kernel::KernelCore& kernel, Alarm& alarm) { m_alarms.erase(m_alarms.iterator_to(alarm)); } -Result Alarms::UpdateClosestAndSignal() { +Result Alarms::UpdateClosestAndSignal(Kernel::KernelCore& kernel) { m_closest_alarm = m_alarms.empty() ? nullptr : std::addressof(m_alarms.front()); R_SUCCEED_IF(m_closest_alarm == nullptr); - m_event->Signal(); + m_event->Signal(kernel); R_SUCCEED(); } IAlarmService::IAlarmService(Core::System& system_, std::shared_ptr manager) - : ServiceFramework{system_, "time:al"}, m_system{system}, m_alarms{manager->m_alarms} { + : ServiceFramework{system_, "time:al"} + , m_alarms{manager->m_alarms} +{ // clang-format off static const FunctionInfo functions[] = { {0, &IAlarmService::CreateWakeupAlarm, "CreateWakeupAlarm"}, @@ -142,7 +151,7 @@ void IAlarmService::CreateWakeupAlarm(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system, m_alarms, AlarmType::WakeupAlarm); + rb.PushIpcInterface(ctx, system, m_alarms, AlarmType::WakeupAlarm); } void IAlarmService::CreateBackgroundTaskAlarm(HLERequestContext& ctx) { @@ -150,7 +159,7 @@ void IAlarmService::CreateBackgroundTaskAlarm(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system, m_alarms, AlarmType::BackgroundTaskAlarm); + rb.PushIpcInterface(ctx, system, m_alarms, AlarmType::BackgroundTaskAlarm); } ISteadyClockAlarm::ISteadyClockAlarm(Core::System& system_, Alarms& alarms, AlarmType type) @@ -174,7 +183,7 @@ void ISteadyClockAlarm::GetAlarmEvent(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(m_alarm.GetEventHandle()); + rb.PushCopyObjects(ctx, m_alarm.GetEventHandle()); } void ISteadyClockAlarm::Enable(HLERequestContext& ctx) { @@ -183,7 +192,7 @@ void ISteadyClockAlarm::Enable(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; auto time{rp.Pop()}; - auto res = m_alarms.Enable(m_alarm, time); + auto res = m_alarms.Enable(system.Kernel(), m_alarm, time); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(res); @@ -192,7 +201,7 @@ void ISteadyClockAlarm::Enable(HLERequestContext& ctx) { void ISteadyClockAlarm::Disable(HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called."); - m_alarms.Disable(m_alarm); + m_alarms.Disable(system.Kernel(), m_alarm); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); diff --git a/src/core/hle/service/psc/time/alarms.h b/src/core/hle/service/psc/time/alarms.h index 597770028b..0a2fa7fac5 100644 --- a/src/core/hle/service/psc/time/alarms.h +++ b/src/core/hle/service/psc/time/alarms.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -48,8 +51,8 @@ struct Alarm : public Common::IntrusiveListBaseNode { return m_priority; } - void Signal() { - m_event->Signal(); + void Signal(Kernel::KernelCore& kernel) { + m_event->Signal(kernel); } Result Lock() { @@ -71,8 +74,7 @@ struct Alarm : public Common::IntrusiveListBaseNode { class Alarms { public: - explicit Alarms(Core::System& system, StandardSteadyClockCore& steady_clock, - PowerStateRequestManager& power_state_request_manager); + explicit Alarms(Core::System& system, StandardSteadyClockCore& steady_clock, PowerStateRequestManager& power_state_request_manager); ~Alarms(); Kernel::KEvent& GetEvent() { @@ -83,15 +85,15 @@ public: return m_steady_clock.GetRawTime(); } - Result Enable(Alarm& alarm, s64 time); - void Disable(Alarm& alarm); - void CheckAndSignal(); + Result Enable(Kernel::KernelCore& kernel, Alarm& alarm, s64 time); + void Disable(Kernel::KernelCore& kernel, Alarm& alarm); + void CheckAndSignal(Kernel::KernelCore& kernel); bool GetClosestAlarm(Alarm** out_alarm); private: - void Insert(Alarm& alarm); - void Erase(Alarm& alarm); - Result UpdateClosestAndSignal(); + void Insert(Kernel::KernelCore& kernel, Alarm& alarm); + void Erase(Kernel::KernelCore& kernel, Alarm& alarm); + Result UpdateClosestAndSignal(Kernel::KernelCore& kernel); Core::System& m_system; KernelHelpers::ServiceContext m_ctx; @@ -107,21 +109,17 @@ private: class IAlarmService final : public ServiceFramework { public: explicit IAlarmService(Core::System& system, std::shared_ptr manager); - ~IAlarmService() override = default; private: void CreateWakeupAlarm(HLERequestContext& ctx); void CreateBackgroundTaskAlarm(HLERequestContext& ctx); - - Core::System& m_system; Alarms& m_alarms; }; class ISteadyClockAlarm final : public ServiceFramework { public: explicit ISteadyClockAlarm(Core::System& system, Alarms& alarms, AlarmType type); - ~ISteadyClockAlarm() override = default; private: diff --git a/src/core/hle/service/psc/time/clocks/context_writers.cpp b/src/core/hle/service/psc/time/clocks/context_writers.cpp index a44486b438..4e54a07a37 100644 --- a/src/core/hle/service/psc/time/clocks/context_writers.cpp +++ b/src/core/hle/service/psc/time/clocks/context_writers.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -6,10 +9,10 @@ namespace Service::PSC::Time { -void ContextWriter::SignalAllNodes() { +void ContextWriter::SignalAllNodes(Kernel::KernelCore& kernel) { std::scoped_lock l{m_mutex}; for (auto& operation : m_operation_events) { - operation.m_event->Signal(); + operation.m_event->Signal(kernel); } } @@ -33,7 +36,7 @@ Result LocalSystemClockContextWriter::Write(const SystemClockContext& context) { m_shared_memory.SetLocalSystemContext(context); - SignalAllNodes(); + SignalAllNodes(m_system.Kernel()); R_SUCCEED(); } @@ -57,7 +60,7 @@ Result NetworkSystemClockContextWriter::Write(const SystemClockContext& context) m_shared_memory.SetNetworkSystemContext(context); - SignalAllNodes(); + SignalAllNodes(m_system.Kernel()); R_SUCCEED(); } @@ -75,7 +78,7 @@ Result EphemeralNetworkSystemClockContextWriter::Write(const SystemClockContext& m_in_use = true; } - SignalAllNodes(); + SignalAllNodes(m_system.Kernel()); R_SUCCEED(); } diff --git a/src/core/hle/service/psc/time/clocks/context_writers.h b/src/core/hle/service/psc/time/clocks/context_writers.h index 6643fc9f2a..2adc7d2546 100644 --- a/src/core/hle/service/psc/time/clocks/context_writers.h +++ b/src/core/hle/service/psc/time/clocks/context_writers.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -11,6 +14,9 @@ #include "core/hle/service/psc/time/common.h" #include "core/hle/service/psc/time/shared_memory.h" +namespace Kernel { +class KernelCore; +} namespace Core { class System; } @@ -25,7 +31,7 @@ public: virtual ~ContextWriter() = default; virtual Result Write(const SystemClockContext& context) = 0; - void SignalAllNodes(); + void SignalAllNodes(Kernel::KernelCore& kernel); void Link(OperationEvent& operation_event); private: diff --git a/src/core/hle/service/psc/time/clocks/ephemeral_network_system_clock_core.h b/src/core/hle/service/psc/time/clocks/ephemeral_network_system_clock_core.h index 0a68867d94..0abe200206 100644 --- a/src/core/hle/service/psc/time/clocks/ephemeral_network_system_clock_core.h +++ b/src/core/hle/service/psc/time/clocks/ephemeral_network_system_clock_core.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/clocks/standard_local_system_clock_core.cpp b/src/core/hle/service/psc/time/clocks/standard_local_system_clock_core.cpp index 6a74d45946..d0e596c504 100644 --- a/src/core/hle/service/psc/time/clocks/standard_local_system_clock_core.cpp +++ b/src/core/hle/service/psc/time/clocks/standard_local_system_clock_core.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/clocks/standard_local_system_clock_core.h b/src/core/hle/service/psc/time/clocks/standard_local_system_clock_core.h index 5722d8e965..832c6907a0 100644 --- a/src/core/hle/service/psc/time/clocks/standard_local_system_clock_core.h +++ b/src/core/hle/service/psc/time/clocks/standard_local_system_clock_core.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/clocks/standard_network_system_clock_core.cpp b/src/core/hle/service/psc/time/clocks/standard_network_system_clock_core.cpp index 6938d369fe..b3143ead71 100644 --- a/src/core/hle/service/psc/time/clocks/standard_network_system_clock_core.cpp +++ b/src/core/hle/service/psc/time/clocks/standard_network_system_clock_core.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/clocks/standard_network_system_clock_core.h b/src/core/hle/service/psc/time/clocks/standard_network_system_clock_core.h index bfafc7d71f..47a411e811 100644 --- a/src/core/hle/service/psc/time/clocks/standard_network_system_clock_core.h +++ b/src/core/hle/service/psc/time/clocks/standard_network_system_clock_core.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/clocks/standard_steady_clock_core.cpp b/src/core/hle/service/psc/time/clocks/standard_steady_clock_core.cpp index 7a72d7aa25..8fc44fd691 100644 --- a/src/core/hle/service/psc/time/clocks/standard_steady_clock_core.cpp +++ b/src/core/hle/service/psc/time/clocks/standard_steady_clock_core.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/clocks/standard_steady_clock_core.h b/src/core/hle/service/psc/time/clocks/standard_steady_clock_core.h index bbf98fcd5c..342d6385fd 100644 --- a/src/core/hle/service/psc/time/clocks/standard_steady_clock_core.h +++ b/src/core/hle/service/psc/time/clocks/standard_steady_clock_core.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/clocks/standard_user_system_clock_core.cpp b/src/core/hle/service/psc/time/clocks/standard_user_system_clock_core.cpp index 31ed273966..7a51e51f3c 100644 --- a/src/core/hle/service/psc/time/clocks/standard_user_system_clock_core.cpp +++ b/src/core/hle/service/psc/time/clocks/standard_user_system_clock_core.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -57,7 +60,7 @@ Result StandardUserSystemClockCore::GetTimePoint(SteadyClockTimePoint& out_time_ void StandardUserSystemClockCore::SetTimePointAndSignal(SteadyClockTimePoint& time_point) { m_time_point = time_point; - m_event->Signal(); + m_event->Signal(m_system.Kernel()); } } // namespace Service::PSC::Time diff --git a/src/core/hle/service/psc/time/clocks/standard_user_system_clock_core.h b/src/core/hle/service/psc/time/clocks/standard_user_system_clock_core.h index 32b8bc3bcd..14c8e613f4 100644 --- a/src/core/hle/service/psc/time/clocks/standard_user_system_clock_core.h +++ b/src/core/hle/service/psc/time/clocks/standard_user_system_clock_core.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/clocks/steady_clock_core.h b/src/core/hle/service/psc/time/clocks/steady_clock_core.h index f933cc15f8..31be5e25ad 100644 --- a/src/core/hle/service/psc/time/clocks/steady_clock_core.h +++ b/src/core/hle/service/psc/time/clocks/steady_clock_core.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/clocks/system_clock_core.cpp b/src/core/hle/service/psc/time/clocks/system_clock_core.cpp index 2b7466831d..7ca5e8515e 100644 --- a/src/core/hle/service/psc/time/clocks/system_clock_core.cpp +++ b/src/core/hle/service/psc/time/clocks/system_clock_core.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/clocks/system_clock_core.h b/src/core/hle/service/psc/time/clocks/system_clock_core.h index 0b928432fb..d95bab5191 100644 --- a/src/core/hle/service/psc/time/clocks/system_clock_core.h +++ b/src/core/hle/service/psc/time/clocks/system_clock_core.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/clocks/tick_based_steady_clock_core.cpp b/src/core/hle/service/psc/time/clocks/tick_based_steady_clock_core.cpp index 22da1fbccc..8666df57f1 100644 --- a/src/core/hle/service/psc/time/clocks/tick_based_steady_clock_core.cpp +++ b/src/core/hle/service/psc/time/clocks/tick_based_steady_clock_core.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/clocks/tick_based_steady_clock_core.h b/src/core/hle/service/psc/time/clocks/tick_based_steady_clock_core.h index a7bea86a25..62890cd5ca 100644 --- a/src/core/hle/service/psc/time/clocks/tick_based_steady_clock_core.h +++ b/src/core/hle/service/psc/time/clocks/tick_based_steady_clock_core.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/common.cpp b/src/core/hle/service/psc/time/common.cpp index a6d9f02ca3..dce2249ea1 100644 --- a/src/core/hle/service/psc/time/common.cpp +++ b/src/core/hle/service/psc/time/common.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/common.h b/src/core/hle/service/psc/time/common.h index 0ad2ed51f8..2183fa72b8 100644 --- a/src/core/hle/service/psc/time/common.h +++ b/src/core/hle/service/psc/time/common.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -8,12 +8,13 @@ #include #include +#include #include #include "common/common_types.h" #include "common/intrusive_list.h" #include "common/uuid.h" -#include "common/wall_clock.h" +#include "common/cpu_features.h" #include "core/hle/kernel/k_event.h" #include "core/hle/service/kernel_helpers.h" #include "core/hle/service/psc/time/errors.h" diff --git a/src/core/hle/service/psc/time/manager.h b/src/core/hle/service/psc/time/manager.h index 62ded12475..7c771d25f5 100644 --- a/src/core/hle/service/psc/time/manager.h +++ b/src/core/hle/service/psc/time/manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/power_state_request_manager.cpp b/src/core/hle/service/psc/time/power_state_request_manager.cpp index b28b513649..7a494f47cb 100644 --- a/src/core/hle/service/psc/time/power_state_request_manager.cpp +++ b/src/core/hle/service/psc/time/power_state_request_manager.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -35,7 +35,7 @@ void PowerStateRequestManager::SignalPowerStateRequestAvailability() { } m_has_pending_request = false; m_available_request_priority = m_pending_request_priority; - m_event->Signal(); + m_event->Signal(m_system.Kernel()); } } @@ -45,7 +45,7 @@ bool PowerStateRequestManager::GetAndClearPowerStateRequest(u32& out_priority) { if (m_has_available_request) { out_priority = m_available_request_priority; m_has_available_request = false; - m_event->Clear(); + m_event->Clear(m_system.Kernel()); } return had_request; } diff --git a/src/core/hle/service/psc/time/power_state_request_manager.h b/src/core/hle/service/psc/time/power_state_request_manager.h index 30a0c947d6..325f73913b 100644 --- a/src/core/hle/service/psc/time/power_state_request_manager.h +++ b/src/core/hle/service/psc/time/power_state_request_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/power_state_service.cpp b/src/core/hle/service/psc/time/power_state_service.cpp index ab1d32c703..39af16b6f1 100644 --- a/src/core/hle/service/psc/time/power_state_service.cpp +++ b/src/core/hle/service/psc/time/power_state_service.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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,8 +11,9 @@ namespace Service::PSC::Time { IPowerStateRequestHandler::IPowerStateRequestHandler( Core::System& system_, PowerStateRequestManager& power_state_request_manager) - : ServiceFramework{system_, "time:p"}, m_system{system}, m_power_state_request_manager{ - power_state_request_manager} { + : ServiceFramework{system_, "time:p"} + , m_power_state_request_manager{power_state_request_manager} +{ // clang-format off static const FunctionInfo functions[] = { {0, D<&IPowerStateRequestHandler::GetPowerStateRequestEventReadableHandle>, "GetPowerStateRequestEventReadableHandle"}, diff --git a/src/core/hle/service/psc/time/power_state_service.h b/src/core/hle/service/psc/time/power_state_service.h index 56e2c4b87e..81b3adf234 100644 --- a/src/core/hle/service/psc/time/power_state_service.h +++ b/src/core/hle/service/psc/time/power_state_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -17,16 +20,13 @@ namespace Service::PSC::Time { class IPowerStateRequestHandler final : public ServiceFramework { public: - explicit IPowerStateRequestHandler(Core::System& system, - PowerStateRequestManager& power_state_request_manager); - + explicit IPowerStateRequestHandler(Core::System& system, PowerStateRequestManager& power_state_request_manager); ~IPowerStateRequestHandler() override = default; Result GetPowerStateRequestEventReadableHandle(OutCopyHandle out_event); Result GetAndClearPowerStateRequest(Out out_cleared, Out out_priority); private: - Core::System& m_system; PowerStateRequestManager& m_power_state_request_manager; }; diff --git a/src/core/hle/service/psc/time/service_manager.cpp b/src/core/hle/service/psc/time/service_manager.cpp index ed9fb32cdf..e9f1407e4d 100644 --- a/src/core/hle/service/psc/time/service_manager.cpp +++ b/src/core/hle/service/psc/time/service_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -12,7 +15,7 @@ namespace Service::PSC::Time { ServiceManager::ServiceManager(Core::System& system_, std::shared_ptr time, ServerManager* server_manager) - : ServiceFramework{system_, "time:m"}, m_system{system}, m_time{std::move(time)}, + : ServiceFramework{system_, "time:m"}, m_time{std::move(time)}, m_server_manager{*server_manager}, m_local_system_clock{m_time->m_standard_local_system_clock}, m_user_system_clock{m_time->m_standard_user_system_clock}, @@ -23,7 +26,7 @@ ServiceManager::ServiceManager(Core::System& system_, std::shared_ptrm_local_system_clock_context_writer}, m_network_system_context_writer{m_time->m_network_system_clock_context_writer}, m_ephemeral_system_context_writer{m_time->m_ephemeral_network_clock_context_writer}, - m_local_operation{m_system}, m_network_operation{m_system}, m_ephemeral_operation{m_system} { + m_local_operation{system_}, m_network_operation{system_}, m_ephemeral_operation{system_} { // clang-format off static const FunctionInfo functions[] = { {0, D<&ServiceManager::GetStaticServiceAsUser>, "GetStaticServiceAsUser"}, @@ -90,7 +93,7 @@ Result ServiceManager::SetupStandardSteadyClockCore(bool is_rtc_reset_detected, m_steady_clock.Initialize(clock_source_id, rtc_offset, internal_offset, test_offset, is_rtc_reset_detected); auto time = m_steady_clock.GetRawTime(); - auto ticks = m_system.CoreTiming().GetClockTicks(); + auto ticks = system.CoreTiming().GetClockTicks(); auto boot_time = time - ConvertToTimeSpan(ticks).count(); m_shared_memory.SetSteadyClockTimePoint(clock_source_id, boot_time); m_steady_clock.SetContinuousAdjustment(clock_source_id, boot_time); @@ -156,6 +159,8 @@ Result ServiceManager::SetupTimeZoneServiceCore(const LocationName& name, name, rule_version, location_count, time_point, time_point.clock_source_id.RawString()); + R_UNLESS(!rule_buffer.empty(), ResultUnknown); //TODO: ??? + if (m_time_zone.ParseBinary(name, rule_buffer) != ResultSuccess) { LOG_ERROR(Service_Time, "Failed to parse time zone binary!"); } @@ -216,7 +221,7 @@ Result ServiceManager::SetStandardSteadyClockBaseTime(s64 base_time) { m_steady_clock.SetRtcOffset(base_time); auto time = m_steady_clock.GetRawTime(); - auto ticks = m_system.CoreTiming().GetClockTicks(); + auto ticks = system.CoreTiming().GetClockTicks(); auto diff = time - ConvertToTimeSpan(ticks).count(); m_shared_memory.UpdateBaseTime(diff); m_steady_clock.UpdateContinuousAdjustmentTime(diff); @@ -238,7 +243,7 @@ Result ServiceManager::GetClosestAlarmUpdatedEvent( Result ServiceManager::CheckAndSignalAlarms() { LOG_DEBUG(Service_Time, "called."); - m_alarms.CheckAndSignal(); + m_alarms.CheckAndSignal(system.Kernel()); R_SUCCEED(); } @@ -274,16 +279,16 @@ void ServiceManager::SetupSAndP() { m_is_s_and_p_setup = true; m_server_manager.RegisterNamedService( "time:s", std::make_shared( - m_system, StaticServiceSetupInfo{0, 0, 1, 0, 0, 0}, m_time, "time:s")); + system, StaticServiceSetupInfo{0, 0, 1, 0, 0, 0}, m_time, "time:s")); m_server_manager.RegisterNamedService("time:p", std::make_shared( - m_system, m_time->m_power_state_request_manager)); + system, m_time->m_power_state_request_manager)); } } Result ServiceManager::GetStaticService(OutInterface out_service, StaticServiceSetupInfo setup_info, const char* name) { - *out_service = std::make_shared(m_system, setup_info, m_time, name); + *out_service = std::make_shared(system, setup_info, m_time, name); R_SUCCEED(); } diff --git a/src/core/hle/service/psc/time/service_manager.h b/src/core/hle/service/psc/time/service_manager.h index 22720e1617..09cff25038 100644 --- a/src/core/hle/service/psc/time/service_manager.h +++ b/src/core/hle/service/psc/time/service_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -60,10 +63,8 @@ public: private: void CheckAndSetupServicesSAndP(); void SetupSAndP(); - Result GetStaticService(OutInterface out_service, - StaticServiceSetupInfo setup_info, const char* name); + Result GetStaticService(OutInterface out_service, StaticServiceSetupInfo setup_info, const char* name); - Core::System& m_system; std::shared_ptr m_time; ServerManager& m_server_manager; bool m_is_s_and_p_setup{}; diff --git a/src/core/hle/service/psc/time/shared_memory.cpp b/src/core/hle/service/psc/time/shared_memory.cpp index adef6bcd8b..b6841a87cb 100644 --- a/src/core/hle/service/psc/time/shared_memory.cpp +++ b/src/core/hle/service/psc/time/shared_memory.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/shared_memory.h b/src/core/hle/service/psc/time/shared_memory.h index b7bd00fc1d..58363bd698 100644 --- a/src/core/hle/service/psc/time/shared_memory.h +++ b/src/core/hle/service/psc/time/shared_memory.h @@ -1,10 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 "common/common_types.h" #include "core/hle/service/psc/time/common.h" diff --git a/src/core/hle/service/psc/time/static.cpp b/src/core/hle/service/psc/time/static.cpp index 9a0adb2955..28cff3b764 100644 --- a/src/core/hle/service/psc/time/static.cpp +++ b/src/core/hle/service/psc/time/static.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -32,7 +35,7 @@ constexpr Result GetTimeFromTimePointAndContext(s64* out_time, SteadyClockTimePo StaticService::StaticService(Core::System& system_, StaticServiceSetupInfo setup_info, std::shared_ptr time, const char* name) - : ServiceFramework{system_, name}, m_system{system}, m_setup_info{setup_info}, m_time{time}, + : ServiceFramework{system_, name}, m_setup_info{setup_info}, m_time{time}, m_local_system_clock{m_time->m_standard_local_system_clock}, m_user_system_clock{m_time->m_standard_user_system_clock}, m_network_system_clock{m_time->m_standard_network_system_clock}, @@ -69,7 +72,7 @@ StaticService::StaticService(Core::System& system_, StaticServiceSetupInfo setup Result StaticService::GetStandardUserSystemClock(OutInterface out_service) { LOG_DEBUG(Service_Time, "called."); - *out_service = std::make_shared(m_system, m_user_system_clock, + *out_service = std::make_shared(system, m_user_system_clock, m_setup_info.can_write_user_clock, m_setup_info.can_write_uninitialized_clock); R_SUCCEED(); @@ -78,7 +81,7 @@ Result StaticService::GetStandardUserSystemClock(OutInterface out_s Result StaticService::GetStandardNetworkSystemClock(OutInterface out_service) { LOG_DEBUG(Service_Time, "called."); - *out_service = std::make_shared(m_system, m_network_system_clock, + *out_service = std::make_shared(system, m_network_system_clock, m_setup_info.can_write_network_clock, m_setup_info.can_write_uninitialized_clock); R_SUCCEED(); @@ -88,7 +91,7 @@ Result StaticService::GetStandardSteadyClock(OutInterface out_servi LOG_DEBUG(Service_Time, "called."); *out_service = - std::make_shared(m_system, m_time, m_setup_info.can_write_steady_clock, + std::make_shared(system, m_time, m_setup_info.can_write_steady_clock, m_setup_info.can_write_uninitialized_clock); R_SUCCEED(); } @@ -97,7 +100,7 @@ Result StaticService::GetTimeZoneService(OutInterface out_servi LOG_DEBUG(Service_Time, "called."); *out_service = - std::make_shared(m_system, m_time->m_standard_steady_clock, m_time_zone, + std::make_shared(system, m_time->m_standard_steady_clock, m_time_zone, m_setup_info.can_write_timezone_device_location); R_SUCCEED(); } @@ -105,7 +108,7 @@ Result StaticService::GetTimeZoneService(OutInterface out_servi Result StaticService::GetStandardLocalSystemClock(OutInterface out_service) { LOG_DEBUG(Service_Time, "called."); - *out_service = std::make_shared(m_system, m_local_system_clock, + *out_service = std::make_shared(system, m_local_system_clock, m_setup_info.can_write_local_clock, m_setup_info.can_write_uninitialized_clock); R_SUCCEED(); @@ -114,7 +117,7 @@ Result StaticService::GetStandardLocalSystemClock(OutInterface out_ Result StaticService::GetEphemeralNetworkSystemClock(OutInterface out_service) { LOG_DEBUG(Service_Time, "called."); - *out_service = std::make_shared(m_system, m_ephemeral_network_clock, + *out_service = std::make_shared(system, m_ephemeral_network_clock, m_setup_info.can_write_network_clock, m_setup_info.can_write_uninitialized_clock); R_SUCCEED(); @@ -171,7 +174,7 @@ Result StaticService::SetStandardUserSystemClockAutomaticCorrectionEnabled( R_TRY(m_time->m_standard_steady_clock.GetCurrentTimePoint(time_point)); m_user_system_clock.SetTimePointAndSignal(time_point); - m_user_system_clock.GetEvent().Signal(); + m_user_system_clock.GetEvent().Signal(system.Kernel()); R_SUCCEED(); } @@ -219,7 +222,7 @@ Result StaticService::CalculateMonotonicSystemClockBaseTimePoint( auto one_second_ns{ std::chrono::duration_cast(std::chrono::seconds(1)).count()}; - auto ticks{m_system.CoreTiming().GetClockTicks()}; + auto ticks{system.CoreTiming().GetClockTicks()}; auto current_time_ns{ConvertToTimeSpan(ticks).count()}; *out_time = ((context.offset + time_point.time_point) - (current_time_ns / one_second_ns)); diff --git a/src/core/hle/service/psc/time/static.h b/src/core/hle/service/psc/time/static.h index e11db8093b..52659166f1 100644 --- a/src/core/hle/service/psc/time/static.h +++ b/src/core/hle/service/psc/time/static.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -68,8 +71,6 @@ private: Result GetClockSnapshotImpl(OutClockSnapshot out_snapshot, const SystemClockContext& user_context, const SystemClockContext& network_context, TimeType type); - - Core::System& m_system; StaticServiceSetupInfo m_setup_info; std::shared_ptr m_time; StandardLocalSystemClockCore& m_local_system_clock; diff --git a/src/core/hle/service/psc/time/steady_clock.cpp b/src/core/hle/service/psc/time/steady_clock.cpp index cfeaf0a653..1d1551acf9 100644 --- a/src/core/hle/service/psc/time/steady_clock.cpp +++ b/src/core/hle/service/psc/time/steady_clock.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -13,10 +13,11 @@ namespace Service::PSC::Time { SteadyClock::SteadyClock(Core::System& system_, std::shared_ptr manager, bool can_write_steady_clock, bool can_write_uninitialized_clock) - : ServiceFramework{system_, "ISteadyClock"}, m_system{system}, - m_clock_core{manager->m_standard_steady_clock}, - m_can_write_steady_clock{can_write_steady_clock}, m_can_write_uninitialized_clock{ - can_write_uninitialized_clock} { + : ServiceFramework{system_, "ISteadyClock"} + , m_clock_core{manager->m_standard_steady_clock} + , m_can_write_steady_clock{can_write_steady_clock} + , m_can_write_uninitialized_clock{can_write_uninitialized_clock} +{ // clang-format off static const FunctionInfo functions[] = { {0, D<&SteadyClock::GetCurrentTimePoint>, "GetCurrentTimePoint"}, @@ -90,7 +91,7 @@ Result SteadyClock::IsRtcResetDetected(Out out_is_detected) { Result SteadyClock::GetSetupResultValue(Out out_result) { SCOPE_EXIT { - LOG_DEBUG(Service_Time, "called. out_result={:#X}", out_result->raw); + LOG_DEBUG(Service_Time, "called. out_result={:#x}", out_result->raw); }; R_UNLESS(m_can_write_uninitialized_clock || m_clock_core.IsInitialized(), diff --git a/src/core/hle/service/psc/time/steady_clock.h b/src/core/hle/service/psc/time/steady_clock.h index 025d758a6a..8d3d9a6280 100644 --- a/src/core/hle/service/psc/time/steady_clock.h +++ b/src/core/hle/service/psc/time/steady_clock.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -32,8 +35,6 @@ public: Result GetInternalOffset(Out out_internal_offset); private: - Core::System& m_system; - StandardSteadyClockCore& m_clock_core; bool m_can_write_steady_clock; bool m_can_write_uninitialized_clock; diff --git a/src/core/hle/service/psc/time/system_clock.cpp b/src/core/hle/service/psc/time/system_clock.cpp index 9f841d8e04..e978ffb5e5 100644 --- a/src/core/hle/service/psc/time/system_clock.cpp +++ b/src/core/hle/service/psc/time/system_clock.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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,11 +11,12 @@ namespace Service::PSC::Time { -SystemClock::SystemClock(Core::System& system_, SystemClockCore& clock_core, bool can_write_clock, - bool can_write_uninitialized_clock) - : ServiceFramework{system_, "ISystemClock"}, m_system{system}, m_clock_core{clock_core}, - m_can_write_clock{can_write_clock}, m_can_write_uninitialized_clock{ - can_write_uninitialized_clock} { +SystemClock::SystemClock(Core::System& system_, SystemClockCore& clock_core, bool can_write_clock, bool can_write_uninitialized_clock) + : ServiceFramework{system_, "ISystemClock"} + , m_clock_core{clock_core} + , m_can_write_clock{can_write_clock} + , m_can_write_uninitialized_clock{can_write_uninitialized_clock} +{ // clang-format off static const FunctionInfo functions[] = { {0, D<&SystemClock::GetCurrentTime>, "GetCurrentTime"}, @@ -72,7 +76,7 @@ Result SystemClock::GetOperationEventReadableHandle( LOG_DEBUG(Service_Time, "called."); if (!m_operation_event) { - m_operation_event = std::make_unique(m_system); + m_operation_event = std::make_unique(system); R_UNLESS(m_operation_event != nullptr, ResultFailed); m_clock_core.LinkOperationEvent(*m_operation_event); diff --git a/src/core/hle/service/psc/time/system_clock.h b/src/core/hle/service/psc/time/system_clock.h index 3c11fb2f81..0d91aaa735 100644 --- a/src/core/hle/service/psc/time/system_clock.h +++ b/src/core/hle/service/psc/time/system_clock.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -18,9 +21,7 @@ namespace Service::PSC::Time { class SystemClock final : public ServiceFramework { public: - explicit SystemClock(Core::System& system, SystemClockCore& system_clock_core, - bool can_write_clock, bool can_write_uninitialized_clock); - + explicit SystemClock(Core::System& system, SystemClockCore& system_clock_core, bool can_write_clock, bool can_write_uninitialized_clock); ~SystemClock() override = default; Result GetCurrentTime(Out out_time); @@ -30,8 +31,6 @@ public: Result GetOperationEventReadableHandle(OutCopyHandle out_event); private: - Core::System& m_system; - SystemClockCore& m_clock_core; bool m_can_write_clock; bool m_can_write_uninitialized_clock; diff --git a/src/core/hle/service/psc/time/time_zone.cpp b/src/core/hle/service/psc/time/time_zone.cpp index 81bfb90929..e537a821db 100644 --- a/src/core/hle/service/psc/time/time_zone.cpp +++ b/src/core/hle/service/psc/time/time_zone.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/time_zone.h b/src/core/hle/service/psc/time/time_zone.h index 0e4ed6ed00..68a235e440 100644 --- a/src/core/hle/service/psc/time/time_zone.h +++ b/src/core/hle/service/psc/time/time_zone.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/psc/time/time_zone_service.cpp b/src/core/hle/service/psc/time/time_zone_service.cpp index d49c0056ec..bd6bb3ba93 100644 --- a/src/core/hle/service/psc/time/time_zone_service.cpp +++ b/src/core/hle/service/psc/time/time_zone_service.cpp @@ -15,9 +15,11 @@ namespace Service::PSC::Time { TimeZoneService::TimeZoneService(Core::System& system_, StandardSteadyClockCore& clock_core, TimeZone& time_zone, bool can_write_timezone_device_location) - : ServiceFramework{system_, "ITimeZoneService"}, m_system{system}, m_clock_core{clock_core}, - m_time_zone{time_zone}, m_can_write_timezone_device_location{ - can_write_timezone_device_location} { + : ServiceFramework{system_, "ITimeZoneService"} + , m_clock_core{clock_core} + , m_time_zone{time_zone} + , m_can_write_timezone_device_location{can_write_timezone_device_location} +{ // clang-format off static const FunctionInfo functions[] = { {0, D<&TimeZoneService::GetDeviceLocationName>, "GetDeviceLocationName"}, diff --git a/src/core/hle/service/psc/time/time_zone_service.h b/src/core/hle/service/psc/time/time_zone_service.h index 79b6073e50..0067b08258 100644 --- a/src/core/hle/service/psc/time/time_zone_service.h +++ b/src/core/hle/service/psc/time/time_zone_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -56,8 +59,6 @@ public: const CalendarTime& calendar_time); private: - Core::System& m_system; - StandardSteadyClockCore& m_clock_core; TimeZone& m_time_zone; bool m_can_write_timezone_device_location; diff --git a/src/core/hle/service/ptm/psm.cpp b/src/core/hle/service/ptm/psm.cpp index f144b8e497..41d172b71b 100644 --- a/src/core/hle/service/ptm/psm.cpp +++ b/src/core/hle/service/ptm/psm.cpp @@ -6,10 +6,12 @@ #include +#include "common/common_funcs.h" #include "common/device_power_state.h" #include "common/logging.h" #include "core/core.h" #include "core/hle/kernel/k_event.h" +#include "core/hle/result.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/kernel_helpers.h" #include "core/hle/service/ptm/psm.h" @@ -41,19 +43,19 @@ public: void SignalChargerTypeChanged() { if (should_signal && should_signal_charger_type) { - state_change_event->Signal(); + state_change_event->Signal(system.Kernel()); } } void SignalPowerSupplyChanged() { if (should_signal && should_signal_power_supply) { - state_change_event->Signal(); + state_change_event->Signal(system.Kernel()); } } void SignalBatteryVoltageStateChanged() { if (should_signal && should_signal_battery_voltage) { - state_change_event->Signal(); + state_change_event->Signal(system.Kernel()); } } @@ -65,7 +67,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(state_change_event->GetReadableEvent()); + rb.PushCopyObjects(ctx, state_change_event->GetReadableEvent()); } void UnbindStateChangeEvent(HLERequestContext& ctx) { @@ -134,12 +136,12 @@ PSM::PSM(Core::System& system_) : ServiceFramework{system_, "psm"} { {9, nullptr, "DisableEnoughPowerChargeEmulation"}, {10, nullptr, "EnableFastBatteryCharging"}, {11, nullptr, "DisableFastBatteryCharging"}, - {12, nullptr, "GetBatteryVoltageState"}, + {12, &PSM::GetBatteryVoltageState, "GetBatteryVoltageState"}, {13, nullptr, "GetRawBatteryChargePercentage"}, {14, nullptr, "IsEnoughPowerSupplied"}, - {15, nullptr, "GetBatteryAgePercentage"}, + {15, &PSM::GetBatteryAgePercentage, "GetBatteryAgePercentage"}, {16, nullptr, "GetBatteryChargeInfoEvent"}, - {17, nullptr, "GetBatteryChargeInfoFields"}, + {17, &PSM::GetBatteryChargeInfoFields, "GetBatteryChargeInfoFields"}, {18, nullptr, "GetBatteryChargeCalibratedEvent"}, }; // clang-format on @@ -184,7 +186,67 @@ void PSM::OpenSession(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); +} + +void PSM::GetBatteryVoltageState(HLERequestContext& ctx) { + LOG_DEBUG(Service_PTM, "(stubbed)"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushRaw(0); // +} + +void PSM::GetBatteryAgePercentage(HLERequestContext& ctx) { + LOG_DEBUG(Service_PTM, "(stubbed)"); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushRaw(1.f); +} + +struct BatteryChargeInfoFields { + u32 input_current_limit; //mA + u32 boost_mode_current_limit; + u32 fast_charge_current_limit; + u32 charge_voltage_limit; + u32 charger_type; + u8 hi2_mode; + u8 battery_charging; + INSERT_PADDING_BYTES_NOINIT(2); + u32 vdd50_state; + u32 temperature_celcius; + f32 battery_charge_percentage; + u32 battery_charge_milli_voltage; + f32 battery_age_percentage; + u32 usb_power_role; + u32 usb_charger_type; + u32 charger_input_voltage_limit; + u32 charger_input_current_limit; + u8 fast_battery_charging; + u8 controller_power_supply; + u8 otg_request; + INSERT_PADDING_BYTES_NOINIT(1); + INSERT_PADDING_BYTES_NOINIT(0x14); //[+17.0.0] +}; +static_assert(sizeof(struct BatteryChargeInfoFields) == 0x54); +void PSM::GetBatteryChargeInfoFields(HLERequestContext& ctx) { + LOG_DEBUG(Service_PTM, "called"); + Common::PowerStatus power_status = Common::GetPowerStatus(); + + BatteryChargeInfoFields r{}; + r.battery_charge_percentage = f32(power_status.percentage); //100% + r.battery_age_percentage = f32(power_status.percentage); //100% + r.battery_charging = power_status.charging ? 1 : 0; + r.charger_type = u32(power_status.has_battery && power_status.charging + ? ChargerType::RegularCharger : ChargerType::Unplugged); + r.charger_input_voltage_limit = 100; + r.charger_input_voltage_limit = 100; + r.input_current_limit = 100; + r.boost_mode_current_limit = 100; + r.fast_charge_current_limit = 100; + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushRaw(r); } } // namespace Service::PTM diff --git a/src/core/hle/service/ptm/psm.h b/src/core/hle/service/ptm/psm.h index 2853a45753..77c38d1f9b 100644 --- a/src/core/hle/service/ptm/psm.h +++ b/src/core/hle/service/ptm/psm.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -22,10 +22,12 @@ private: LowPowerCharger = 2, Unknown = 3, }; - void GetBatteryChargePercentage(HLERequestContext& ctx); void GetChargerType(HLERequestContext& ctx); void OpenSession(HLERequestContext& ctx); + void GetBatteryVoltageState(HLERequestContext& ctx); + void GetBatteryAgePercentage(HLERequestContext& ctx); + void GetBatteryChargeInfoFields(HLERequestContext& ctx); }; } // namespace Service::PTM diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp index 6f0cfe04b8..de1d34a688 100644 --- a/src/core/hle/service/ptm/ptm.cpp +++ b/src/core/hle/service/ptm/ptm.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -11,10 +14,42 @@ namespace Service::PTM { +class PSM_MANU final : public ServiceFramework { +public: + explicit PSM_MANU(Core::System& system_) + : ServiceFramework{system_, "psm:manu"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "EnableVdd50StateControl"}, + {1, nullptr, "DisableVdd50StateControl"}, + {2, nullptr, "SetVdd50State"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class POWCTL final : public ServiceFramework { +public: + explicit POWCTL(Core::System& system_) + : ServiceFramework{system_, "powctl"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "OpenSession"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); server_manager->RegisterNamedService("psm", std::make_shared(system)); + if (1 /* not retail */) { + server_manager->RegisterNamedService("psm:manu", std::make_shared(system)); + server_manager->RegisterNamedService("powctl", std::make_shared(system)); + } server_manager->RegisterNamedService("ts", std::make_shared(system)); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/ptm/ptm.h b/src/core/hle/service/ptm/ptm.h index a0ae03d282..a62877e441 100644 --- a/src/core/hle/service/ptm/ptm.h +++ b/src/core/hle/service/ptm/ptm.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/ptm/ts.cpp b/src/core/hle/service/ptm/ts.cpp index 652f38b97e..b1ac0c8f11 100644 --- a/src/core/hle/service/ptm/ts.cpp +++ b/src/core/hle/service/ptm/ts.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -82,7 +85,7 @@ void TS::OpenSession(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } } // namespace Service::PTM diff --git a/src/core/hle/service/ptm/ts.h b/src/core/hle/service/ptm/ts.h index a10a91a64b..d8a0d49bf8 100644 --- a/src/core/hle/service/ptm/ts.h +++ b/src/core/hle/service/ptm/ts.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/ro/ro.cpp b/src/core/hle/service/ro/ro.cpp index cd50e69ce1..335efa3ad3 100644 --- a/src/core/hle/service/ro/ro.cpp +++ b/src/core/hle/service/ro/ro.cpp @@ -7,6 +7,7 @@ #include #include +#include "common/scope_exit.h" #include "core/hle/kernel/k_process.h" #include "core/hle/service/cmif_serialization.h" @@ -16,15 +17,22 @@ #include "core/hle/service/ro/ro_types.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/service.h" +#ifdef HAS_NCE +#include "core/arm/nce/patcher.h" +#include "core/hle/kernel/k_shared_memory.h" +#endif namespace Service::RO { namespace { -// Convenience definitions. -constexpr size_t MaxSessions = 0x3; -constexpr size_t MaxNrrInfos = 0x40; -constexpr size_t MaxNroInfos = 0x40; +// Atmosphere defines as follows: +// Sessions = 0x03, NrrInfos = 0x40, NroInfos = 0x40 +// This may not be enough for some mods (plugin.nro dependant games) like SSBU +// Suppose someone loads like 64 plugins of these, now what? +constexpr size_t MaxSessions = 0x03; // No change +constexpr size_t MaxNrrInfos = 0x100; // Up to 256 NRRs +constexpr size_t MaxNroInfos = 0x100; // Up to 256 NROs constexpr u64 InvalidProcessId = 0xffffffffffffffffULL; constexpr u64 InvalidContextId = 0xffffffffffffffffULL; @@ -54,7 +62,7 @@ struct NrrInfo { struct ProcessContext { constexpr ProcessContext() = default; - void Initialize(Kernel::KProcess* process, u64 process_id) { + void Initialize(Kernel::KernelCore& kernel, Kernel::KProcess* process, u64 process_id) { ASSERT(!m_in_use); m_nro_in_use = {}; @@ -67,15 +75,15 @@ struct ProcessContext { m_in_use = true; if (m_process) { - m_process->Open(); + m_process->Open(kernel); } } - void Finalize() { + void Finalize(Kernel::KernelCore& kernel) { ASSERT(m_in_use); if (m_process) { - m_process->Close(); + m_process->Close(kernel); } m_nro_in_use = {}; @@ -304,7 +312,7 @@ class RoContext { public: explicit RoContext() = default; - Result RegisterProcess(size_t* out_context_id, Kernel::KProcess* process, u64 process_id) { + Result RegisterProcess(Kernel::KernelCore& kernel, size_t* out_context_id, Kernel::KProcess* process, u64 process_id) { // Validate process id. R_UNLESS(process->GetProcessId() == process_id, RO::ResultInvalidProcess); @@ -312,7 +320,7 @@ public: R_UNLESS(this->GetContextByProcessId(process_id) == nullptr, RO::ResultInvalidSession); // Allocate a context to manage the process handle. - *out_context_id = this->AllocateContext(process, process_id); + *out_context_id = this->AllocateContext(kernel, process, process_id); R_SUCCEED(); } @@ -324,8 +332,8 @@ public: R_SUCCEED(); } - void UnregisterProcess(size_t context_id) { - this->FreeContext(context_id); + void UnregisterProcess(Kernel::KernelCore& kernel, size_t context_id) { + this->FreeContext(kernel, context_id); } Result RegisterModuleInfo(size_t context_id, u64 nrr_address, u64 nrr_size, NrrKind nrr_kind, @@ -383,7 +391,7 @@ public: R_SUCCEED(); } - Result MapManualLoadModuleMemory(u64* out_address, size_t context_id, u64 nro_address, + Result MapManualLoadModuleMemory(Kernel::KernelCore& kernel, u64* out_address, size_t context_id, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size) { // Get context. ProcessContext* context = this->GetContextById(context_id); @@ -418,10 +426,51 @@ public: R_TRY(context->ValidateNro(std::addressof(nro_info->module_id), std::addressof(rx_size), std::addressof(ro_size), std::addressof(rw_size), nro_info->base_address, nro_size, bss_size)); - - // Set NRO perms. - R_TRY(SetNroPerms(context->GetProcess(), nro_info->base_address, rx_size, ro_size, - rw_size + bss_size)); + R_TRY(SetNroPerms(context->GetProcess(), nro_info->base_address, rx_size, ro_size, rw_size + bss_size)); +#ifdef HAS_NCE + if (Settings::IsNceEnabled()) { + auto* process = context->GetProcess(); + auto& memory = process->GetMemory(); + std::vector text(rx_size); + memory.ReadBlock(nro_info->base_address, text.data(), text.size()); + Core::NCE::Patcher patch{text}; + const size_t patch_size = patch.GetSectionSize(); + if (patch_size != 0) { + constexpr auto permission = Kernel::Svc::MemoryPermission::ReadExecute; + auto* patch_memory = Kernel::KSharedMemory::Create(kernel); + SCOPE_EXIT { patch_memory->Close(kernel); }; + R_TRY(patch_memory->Initialize(kernel, kernel.System().DeviceMemory(), process, permission, permission, patch_size)); + Kernel::KSharedMemory::Register(kernel, patch_memory); + const std::array offsets{static_cast(total_size + Kernel::PageSize),-static_cast(patch_size + Kernel::PageSize)}; + std::vector tls_patches; + std::span instructions; + u64 patch_address = 0; + for (const s64 offset : offsets) { + instructions = patch.RelocateRuntimeTls(offset, tls_patches); + if (instructions.empty()) {continue;} + const u64 address = static_cast(static_cast(nro_info->base_address) + offset); + if (R_FAILED(patch_memory->Map(*process, address, patch_size, permission))) {continue;} + patch_address = address; + break; + } + R_UNLESS(patch_address != 0, RO::ResultOutOfAddressSpace); + const Result add_shared_memory_result = process->AddSharedMemory(kernel, patch_memory, patch_address, patch_size); + if (R_FAILED(add_shared_memory_result)) { + R_ASSERT(patch_memory->Unmap(*process, patch_address, patch_size)); + } + R_TRY(add_shared_memory_result); + std::memcpy(patch_memory->GetPointer(), instructions.data(), instructions.size_bytes()); + auto* ptr = reinterpret_cast(patch_address); + __builtin___clear_cache(ptr, ptr + instructions.size_bytes()); + for (const auto& instruction : tls_patches) { + const u64 address = nro_info->base_address + instruction.offset; + memory.Write32(address, instruction.replacement); + ptr = reinterpret_cast(address); + __builtin___clear_cache(ptr, ptr + sizeof(u32)); + } + } + } +#endif context->SetNroInfoInUse(nro_info, true); nro_info->code_size = rx_size + ro_size; @@ -478,13 +527,13 @@ private: return nullptr; } - size_t AllocateContext(Kernel::KProcess* process, u64 process_id) { + size_t AllocateContext(Kernel::KernelCore& kernel, Kernel::KProcess* process, u64 process_id) { // Find a free process context. for (size_t i = 0; i < MaxSessions; i++) { ProcessContext* context = std::addressof(process_contexts[i]); if (context->IsFree()) { - context->Initialize(process, process_id); + context->Initialize(kernel, process, process_id); return i; } } @@ -493,9 +542,9 @@ private: UNREACHABLE(); } - void FreeContext(size_t context_id) { + void FreeContext(Kernel::KernelCore& kernel, size_t context_id) { if (ProcessContext* context = GetContextById(context_id); context != nullptr) { - context->Finalize(); + context->Finalize(kernel); } } }; @@ -522,13 +571,13 @@ public: } ~RoInterface() { - m_ro->UnregisterProcess(m_context_id); + m_ro->UnregisterProcess(system.Kernel(), m_context_id); } Result MapManualLoadModuleMemory(Out out_load_address, ClientProcessId client_pid, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size) { R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid)); - R_RETURN(m_ro->MapManualLoadModuleMemory(out_load_address.Get(), m_context_id, nro_address, + R_RETURN(m_ro->MapManualLoadModuleMemory(system.Kernel(), out_load_address.Get(), m_context_id, nro_address, nro_size, bss_address, bss_size)); } @@ -548,20 +597,16 @@ public: R_RETURN(m_ro->UnregisterModuleInfo(m_context_id, nrr_address)); } - Result RegisterProcessHandle(ClientProcessId client_pid, - InCopyHandle process) { + Result RegisterProcessHandle(ClientProcessId client_pid, InCopyHandle process) { // Register the process. - R_RETURN(m_ro->RegisterProcess(std::addressof(m_context_id), process.Get(), *client_pid)); + R_RETURN(m_ro->RegisterProcess(system.Kernel(), std::addressof(m_context_id), process.Get(), *client_pid)); } - Result RegisterProcessModuleInfo(ClientProcessId client_pid, u64 nrr_address, u64 nrr_size, - InCopyHandle process) { + Result RegisterProcessModuleInfo(ClientProcessId client_pid, u64 nrr_address, u64 nrr_size, InCopyHandle process) { // Validate the process. R_TRY(m_ro->ValidateProcess(m_context_id, *client_pid)); - // Register the module. - R_RETURN(m_ro->RegisterModuleInfo(m_context_id, nrr_address, nrr_size, m_nrr_kind, - m_nrr_kind == NrrKind::JitPlugin)); + R_RETURN(m_ro->RegisterModuleInfo(m_context_id, nrr_address, nrr_size, m_nrr_kind, m_nrr_kind == NrrKind::JitPlugin)); } private: @@ -572,6 +617,19 @@ private: } // namespace + +class IDebugMonitorInterface final : public ServiceFramework { +public: + explicit IDebugMonitorInterface(Core::System& system_) + : ServiceFramework{system_, "ro:dmnt"} + { + static const FunctionInfo functions[] = { + { 0, nullptr, "GetProcessModuleInfo" }, + }; + RegisterHandlers(functions); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); @@ -581,13 +639,9 @@ void LoopProcess(Core::System& system) { return std::make_shared(system, "ldr:ro", ro, NrrKind::User); }; - const auto RoInterfaceFactoryForJitPlugin = [&, ro] { - return std::make_shared(system, "ro:1", ro, NrrKind::JitPlugin); - }; - - server_manager->RegisterNamedService("ldr:ro", std::move(RoInterfaceFactoryForUser)); - server_manager->RegisterNamedService("ro:1", std::move(RoInterfaceFactoryForJitPlugin)); - + server_manager->RegisterNamedService("ldr:ro", std::move(RoInterfaceFactoryForUser), 2); + server_manager->RegisterNamedService("ro:1", std::make_shared(system, "ro:1", ro, NrrKind::JitPlugin), 2); + server_manager->RegisterNamedService("ro:dmnt", std::make_shared(system), 2); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/ro/ro.h b/src/core/hle/service/ro/ro.h index 74dc085368..c93aac3833 100644 --- a/src/core/hle/service/ro/ro.h +++ b/src/core/hle/service/ro/ro.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ro/ro_nro_utils.cpp b/src/core/hle/service/ro/ro_nro_utils.cpp index 268c7f93e7..1c2a97619d 100644 --- a/src/core/hle/service/ro/ro_nro_utils.cpp +++ b/src/core/hle/service/ro/ro_nro_utils.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ro/ro_nro_utils.h b/src/core/hle/service/ro/ro_nro_utils.h index f7083a1ba3..32b97abed2 100644 --- a/src/core/hle/service/ro/ro_nro_utils.h +++ b/src/core/hle/service/ro/ro_nro_utils.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ro/ro_results.h b/src/core/hle/service/ro/ro_results.h index 00f05c5a59..906378bc7f 100644 --- a/src/core/hle/service/ro/ro_results.h +++ b/src/core/hle/service/ro/ro_results.h @@ -1,6 +1,11 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 "core/hle/result.h" namespace Service::RO { diff --git a/src/core/hle/service/ro/ro_types.h b/src/core/hle/service/ro/ro_types.h index 50b424395a..8e88f43246 100644 --- a/src/core/hle/service/ro/ro_types.h +++ b/src/core/hle/service/ro/ro_types.h @@ -4,6 +4,8 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + #include "common/assert.h" #include "common/common_funcs.h" #include "common/common_types.h" diff --git a/src/core/hle/service/server_manager.cpp b/src/core/hle/service/server_manager.cpp index 3d898725e8..4b3db04c76 100644 --- a/src/core/hle/service/server_manager.cpp +++ b/src/core/hle/service/server_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -28,13 +31,15 @@ enum class UserDataTag { class Port : public MultiWaitHolder, public Common::IntrusiveListBaseNode { public: - explicit Port(Kernel::KServerPort* server_port, SessionRequestHandlerFactory&& handler_factory) - : MultiWaitHolder(server_port), m_handler_factory(std::move(handler_factory)) { + explicit Port(Kernel::KernelCore& kernel, Kernel::KServerPort* server_port, SessionRequestHandlerFactory&& handler_factory) + : MultiWaitHolder(server_port), m_handler_factory(std::move(handler_factory)) + , m_kernel{kernel} + { this->SetUserData(static_cast(UserDataTag::Port)); } ~Port() { - this->GetNativeHandle()->Close(); + this->GetNativeHandle()->Close(m_kernel); } SessionRequestHandlerPtr CreateHandler() { @@ -43,18 +48,20 @@ public: private: const SessionRequestHandlerFactory m_handler_factory; + Kernel::KernelCore& m_kernel; }; class Session : public MultiWaitHolder, public Common::IntrusiveListBaseNode { public: - explicit Session(Kernel::KServerSession* server_session, - std::shared_ptr&& manager) - : MultiWaitHolder(server_session), m_manager(std::move(manager)) { + explicit Session(Kernel::KernelCore& kernel, Kernel::KServerSession* server_session, std::shared_ptr&& manager) + : MultiWaitHolder(server_session), m_manager(std::move(manager)) + , m_kernel{kernel} + { this->SetUserData(static_cast(UserDataTag::Session)); } ~Session() { - this->GetNativeHandle()->Close(); + this->GetNativeHandle()->Close(m_kernel); } std::shared_ptr& GetManager() { @@ -68,12 +75,16 @@ public: private: std::shared_ptr m_manager; std::shared_ptr m_context; + Kernel::KernelCore& m_kernel; }; -ServerManager::ServerManager(Core::System& system) : m_system{system}, m_selection_mutex{system} { +ServerManager::ServerManager(Core::System& system) + : m_system{system} + , m_selection_mutex{system} +{ // Initialize event. m_wakeup_event = Kernel::KEvent::Create(system.Kernel()); - m_wakeup_event->Initialize(nullptr); + m_wakeup_event->Initialize(m_system.Kernel(), nullptr); // Register event. Kernel::KEvent::Register(system.Kernel(), m_wakeup_event); @@ -86,7 +97,7 @@ ServerManager::ServerManager(Core::System& system) : m_system{system}, m_selecti ServerManager::~ServerManager() { // Signal stop. m_stop_source.request_stop(); - m_wakeup_event->Signal(); + m_wakeup_event->Signal(m_system.Kernel()); // Wait for processing to stop. m_stopped.Wait(); @@ -109,11 +120,11 @@ ServerManager::~ServerManager() { } // Close wakeup event. - m_wakeup_event->GetReadableEvent().Close(); - m_wakeup_event->Close(); + m_wakeup_event->GetReadableEvent().Close(m_system.Kernel()); + m_wakeup_event->Close(m_system.Kernel()); if (m_deferral_event) { - m_deferral_event->GetReadableEvent().Close(); + m_deferral_event->GetReadableEvent().Close(m_system.Kernel()); // Write event is owned by ServiceManager } } @@ -125,7 +136,7 @@ void ServerManager::RunServer(std::unique_ptr&& server_manager) { Result ServerManager::RegisterSession(Kernel::KServerSession* server_session, std::shared_ptr manager) { // We are taking ownership of the server session, so don't open it. - auto* session = new Session(server_session, std::move(manager)); + auto* session = new Session(m_system.Kernel(), server_session, std::move(manager)); // Begin tracking the server session. { @@ -148,7 +159,7 @@ Result ServerManager::RegisterNamedService(const std::string& service_name, max_sessions, handler_factory)); // We are taking ownership of the server port, so don't open it. - auto* server = new Port(server_port, std::move(handler_factory)); + auto* server = new Port(m_system.Kernel(), server_port, std::move(handler_factory)); // Begin tracking the server port. { @@ -177,15 +188,15 @@ Result ServerManager::ManageNamedPort(const std::string& service_name, u32 max_sessions) { // Create a new port. auto* port = Kernel::KPort::Create(m_system.Kernel()); - port->Initialize(max_sessions, false, 0); + port->Initialize(m_system.Kernel(), max_sessions, false, 0); // Register the port. Kernel::KPort::Register(m_system.Kernel(), port); // Ensure that our reference to the port is closed if we fail to register it. SCOPE_EXIT { - port->GetClientPort().Close(); - port->GetServerPort().Close(); + port->GetClientPort().Close(m_system.Kernel()); + port->GetServerPort().Close(m_system.Kernel()); }; // Register the object name with the kernel. @@ -193,10 +204,10 @@ Result ServerManager::ManageNamedPort(const std::string& service_name, service_name.c_str())); // Open a new reference to the server port. - port->GetServerPort().Open(); + port->GetServerPort().Open(m_system.Kernel()); // Transfer ownership into a new port object. - auto* server = new Port(std::addressof(port->GetServerPort()), std::move(handler_factory)); + auto* server = new Port(m_system.Kernel(), std::addressof(port->GetServerPort()), std::move(handler_factory)); // Begin tracking the port. { @@ -217,7 +228,7 @@ Result ServerManager::ManageDeferral(Kernel::KEvent** out_event) { ASSERT(m_deferral_event != nullptr); // Initialize the event. - m_deferral_event->Initialize(nullptr); + m_deferral_event->Initialize(m_system.Kernel(), nullptr); // Register the event. Kernel::KEvent::Register(m_system.Kernel(), m_deferral_event); @@ -258,7 +269,7 @@ void ServerManager::LinkToDeferredList(MultiWaitHolder* holder) { } // Signal the wakeup event. - m_wakeup_event->Signal(); + m_wakeup_event->Signal(m_system.Kernel()); } void ServerManager::LinkDeferred() { @@ -281,7 +292,7 @@ MultiWaitHolder* ServerManager::WaitSignaled() { auto* selected = m_multi_wait.WaitAny(m_system.Kernel()); if (selected == std::addressof(*m_wakeup_holder)) { // Clear and restart if we were woken up. - m_wakeup_event->Clear(); + m_wakeup_event->Clear(m_system.Kernel()); } else { // Unlink and handle the event. selected->UnlinkFromMultiWait(); @@ -323,7 +334,7 @@ Result ServerManager::LoopProcessImpl() { Result ServerManager::OnPortEvent(Port* server) { // Accept a new server session. auto* server_port = static_cast(server->GetNativeHandle()); - Kernel::KServerSession* server_session = server_port->AcceptSession(); + Kernel::KServerSession* server_session = server_port->AcceptSession(m_system.Kernel()); ASSERT(server_session != nullptr); // Create the session manager and install the handler. @@ -345,7 +356,7 @@ Result ServerManager::OnSessionEvent(Session* session) { // Try to receive a message. auto* server_session = static_cast(session->GetNativeHandle()); - res = server_session->ReceiveRequestHLE(&session->GetContext(), session->GetManager()); + res = server_session->ReceiveRequestHLE(m_system.Kernel(), &session->GetContext(), session->GetManager()); // If the session has been closed, we're done. if (res == Kernel::ResultSessionClosed) { @@ -382,7 +393,7 @@ Result ServerManager::CompleteSyncRequest(Session* session) { } // Send the reply. - res = server_session->SendReplyHLE(); + res = server_session->SendReplyHLE(m_system.Kernel(), service_res == IPC::ResultSessionClosed); // If the session has been closed, we're done. if (res == Kernel::ResultSessionClosed || service_res == IPC::ResultSessionClosed) { @@ -401,7 +412,7 @@ Result ServerManager::CompleteSyncRequest(Session* session) { Result ServerManager::OnDeferralEvent() { // Clear event before grabbing the list. - m_deferral_event->Clear(); + m_deferral_event->Clear(m_system.Kernel()); // Get and clear list. const auto deferrals = [&] { diff --git a/src/core/hle/service/server_manager.h b/src/core/hle/service/server_manager.h index 5173ce46e1..d86d20b7c5 100644 --- a/src/core/hle/service/server_manager.h +++ b/src/core/hle/service/server_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index e054ad441b..b4fe2b3ed5 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -25,7 +25,7 @@ namespace Service { int num_params = (cmd_buf[0] & 0x3F) + ((cmd_buf[0] >> 6) & 0x3F); std::string function_string = fmt::format("fn '{}': port={}", name, port_name); for (int i = 1; i <= num_params; ++i) - function_string += fmt::format(", cmd_buf[{}]={:#X}", i, cmd_buf[i]); + function_string += fmt::format(", cmd_buf[{}]={:#x}", i, cmd_buf[i]); return function_string; } @@ -62,9 +62,9 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(HLERequestContext& ctx, std::string function_name = info == nullptr ? "" : info->name; fmt::memory_buffer buf; - fmt::format_to(std::back_inserter(buf), "function '{}({})': port='{}' cmd_buf={{[0]={:#X}", ctx.GetCommand(), function_name, service_name, cmd_buf[0]); + fmt::format_to(std::back_inserter(buf), "function '{}({})': port='{}' cmd_buf={{[0]={:#x}", ctx.GetCommand(), function_name, service_name, cmd_buf[0]); for (int i = 1; i <= 8; ++i) - fmt::format_to(std::back_inserter(buf), ", [{}]={:#X}", i, cmd_buf[i]); + fmt::format_to(std::back_inserter(buf), ", [{}]={:#x}", i, cmd_buf[i]); buf.push_back('}'); system.GetReporter().SaveUnimplementedFunctionReport(ctx, ctx.GetCommand(), function_name, service_name); diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 41429958ad..68b0d636d8 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -8,7 +8,7 @@ #include #include -#include +#include "common/container/unordered_map.h" #include "common/common_types.h" #include "core/hle/service/hle_ipc.h" @@ -99,8 +99,8 @@ private: void ReportUnimplementedFunction(HLERequestContext& ctx, const FunctionInfoBase* info); protected: - ankerl::unordered_dense::map handlers; - ankerl::unordered_dense::map handlers_tipc; + ::Common::unordered_map handlers; + ::Common::unordered_map handlers_tipc; /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread. std::mutex lock_service; /// System context that the service operates under. @@ -141,7 +141,7 @@ protected: /// @param expected_header_ request header in the command buffer which will trigger dispatch to this handler /// @param handler_callback_ member function in this service which will be called to handle the request /// @param name_ human-friendly name for the request. Used mostly for logging purposes. - constexpr FunctionInfoTyped(u32 expected_header_, HandlerFnP handler_callback_, const char* name_) + FunctionInfoTyped(u32 expected_header_, HandlerFnP handler_callback_, const char* name_) : FunctionInfoBase{expected_header_, HandlerFnP(handler_callback_), name_} {} }; using FunctionInfo = FunctionInfoTyped; diff --git a/src/core/hle/service/services.cpp b/src/core/hle/service/services.cpp index 636f54ad49..b90af960d6 100644 --- a/src/core/hle/service/services.cpp +++ b/src/core/hle/service/services.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -25,8 +25,10 @@ #include "core/hle/service/friend/friend.h" #include "core/hle/service/glue/glue.h" #include "core/hle/service/grc/grc.h" +#include "core/hle/service/gpio/gpio.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/i2c/i2c.h" #include "core/hle/service/jit/jit.h" #include "core/hle/service/lbl/lbl.h" #include "core/hle/service/ldn/ldn.h" @@ -35,7 +37,7 @@ #include "core/hle/service/mig/mig.h" #include "core/hle/service/mii/mii.h" #include "core/hle/service/mm/mm_u.h" -#include "core/hle/service/mnpp/mnpp_app.h" +#include "core/hle/service/mnpp/mnpp.h" #include "core/hle/service/ncm/ncm.h" #include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfp/nfp.h" @@ -62,6 +64,8 @@ #include "core/hle/service/sockets/sockets.h" #include "core/hle/service/spl/spl_module.h" #include "core/hle/service/ssl/ssl.h" +#include "core/hle/service/wlan/wlan.h" +#include "core/hle/service/tma/tma.h" #include "core/hle/service/usb/usb.h" #include "core/hle/service/vi/vi.h" @@ -87,9 +91,7 @@ Services::Services(std::shared_ptr& sm, Core::System& system for (auto const& e : std::vector>{ {"audio", &Audio::LoopProcess}, {"FS", &FileSystem::LoopProcess}, - {"jit", &JIT::LoopProcess}, {"ldn", &LDN::LoopProcess}, - {"Loader", &LDR::LoopProcess}, {"nvservices", &Nvidia::LoopProcess}, {"bsdsocket", &Sockets::LoopProcess}, }) @@ -118,19 +120,21 @@ Services::Services(std::shared_ptr& sm, Core::System& system {"glue", &Glue::LoopProcess}, {"grc", &GRC::LoopProcess}, {"hid", &HID::LoopProcess}, + {"jit", &JIT::LoopProcess}, {"lbl", &LBL::LoopProcess}, + {"Loader", &LDR::LoopProcess}, {"LogManager.Prod", &LM::LoopProcess}, {"mig", &Migration::LoopProcess}, {"mii", &Mii::LoopProcess}, {"mm", &MM::LoopProcess}, {"mnpp", &MNPP::LoopProcess}, - {"nvnflinger", &Nvnflinger::LoopProcess}, {"NCM", &NCM::LoopProcess}, {"nfc", &NFC::LoopProcess}, {"nfp", &NFP::LoopProcess}, {"ngc", &NGC::LoopProcess}, {"nifm", &NIFM::LoopProcess}, {"nim", &NIM::LoopProcess}, + {"nvnflinger", &Nvnflinger::LoopProcess}, {"npns", &NPNS::LoopProcess}, {"ns", &NS::LoopProcess}, {"olsc", &OLSC::LoopProcess}, @@ -144,7 +148,11 @@ Services::Services(std::shared_ptr& sm, Core::System& system {"ro", &RO::LoopProcess}, {"spl", &SPL::LoopProcess}, {"ssl", &SSL::LoopProcess}, - {"usb", &USB::LoopProcess} + {"wlan", &WLAN::LoopProcess}, + {"tma", &TMA::LoopProcess}, + {"usb", &USB::LoopProcess}, + {"i2c", &I2C::LoopProcess}, + {"gpio", &GPIO::LoopProcess}, }) kernel.RunOnGuestCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); }); } diff --git a/src/core/hle/service/services.h b/src/core/hle/service/services.h index da5281ff29..4072afa3d1 100644 --- a/src/core/hle/service/services.h +++ b/src/core/hle/service/services.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/set/factory_settings_server.cpp b/src/core/hle/service/set/factory_settings_server.cpp index a8e307ae26..d663283c98 100644 --- a/src/core/hle/service/set/factory_settings_server.cpp +++ b/src/core/hle/service/set/factory_settings_server.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/set/factory_settings_server.h b/src/core/hle/service/set/factory_settings_server.h index e64cd13809..ddffcee643 100644 --- a/src/core/hle/service/set/factory_settings_server.h +++ b/src/core/hle/service/set/factory_settings_server.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/set/firmware_debug_settings_server.cpp b/src/core/hle/service/set/firmware_debug_settings_server.cpp index b7c66047e6..c491675567 100644 --- a/src/core/hle/service/set/firmware_debug_settings_server.cpp +++ b/src/core/hle/service/set/firmware_debug_settings_server.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project diff --git a/src/core/hle/service/set/firmware_debug_settings_server.h b/src/core/hle/service/set/firmware_debug_settings_server.h index 5dae2263e9..05019faa9f 100644 --- a/src/core/hle/service/set/firmware_debug_settings_server.h +++ b/src/core/hle/service/set/firmware_debug_settings_server.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/set/key_code_map.h b/src/core/hle/service/set/key_code_map.h index c76dc57298..8e010e718b 100644 --- a/src/core/hle/service/set/key_code_map.h +++ b/src/core/hle/service/set/key_code_map.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later diff --git a/src/core/hle/service/set/setting_formats/appln_settings.cpp b/src/core/hle/service/set/setting_formats/appln_settings.cpp index f7c7d5b912..5726a41cdf 100644 --- a/src/core/hle/service/set/setting_formats/appln_settings.cpp +++ b/src/core/hle/service/set/setting_formats/appln_settings.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/set/setting_formats/appln_settings.h b/src/core/hle/service/set/setting_formats/appln_settings.h index ba9af998ab..2128a631e0 100644 --- a/src/core/hle/service/set/setting_formats/appln_settings.h +++ b/src/core/hle/service/set/setting_formats/appln_settings.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/set/setting_formats/device_settings.cpp b/src/core/hle/service/set/setting_formats/device_settings.cpp index 5f295404d9..6e9c321d8e 100644 --- a/src/core/hle/service/set/setting_formats/device_settings.cpp +++ b/src/core/hle/service/set/setting_formats/device_settings.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/set/setting_formats/device_settings.h b/src/core/hle/service/set/setting_formats/device_settings.h index 2827756f6a..8dcfcd7cef 100644 --- a/src/core/hle/service/set/setting_formats/device_settings.h +++ b/src/core/hle/service/set/setting_formats/device_settings.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -30,15 +33,15 @@ struct DeviceSettings { INSERT_PADDING_BYTES(0x20); // Reserved // nn::settings::system::ConsoleSixAxisSensorAccelerationBias - Common::Vec3 console_six_axis_sensor_acceleration_bias; + Common::Vec console_six_axis_sensor_acceleration_bias; // nn::settings::system::ConsoleSixAxisSensorAngularVelocityBias - Common::Vec3 console_six_axis_sensor_angular_velocity_bias; + Common::Vec console_six_axis_sensor_angular_velocity_bias; // nn::settings::system::ConsoleSixAxisSensorAccelerationGain std::array console_six_axis_sensor_acceleration_gain; // nn::settings::system::ConsoleSixAxisSensorAngularVelocityGain std::array console_six_axis_sensor_angular_velocity_gain; // nn::settings::system::ConsoleSixAxisSensorAngularVelocityTimeBias - Common::Vec3 console_six_axis_sensor_angular_velocity_time_bias; + Common::Vec console_six_axis_sensor_angular_velocity_time_bias; // nn::settings::system::ConsoleSixAxisSensorAngularAcceleration std::array console_six_axis_sensor_angular_acceleration; }; diff --git a/src/core/hle/service/set/setting_formats/private_settings.cpp b/src/core/hle/service/set/setting_formats/private_settings.cpp index 81c3624826..c3a67b60eb 100644 --- a/src/core/hle/service/set/setting_formats/private_settings.cpp +++ b/src/core/hle/service/set/setting_formats/private_settings.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/set/setting_formats/private_settings.h b/src/core/hle/service/set/setting_formats/private_settings.h index 6579e95e04..75e3e03886 100644 --- a/src/core/hle/service/set/setting_formats/private_settings.h +++ b/src/core/hle/service/set/setting_formats/private_settings.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/set/setting_formats/system_settings.cpp b/src/core/hle/service/set/setting_formats/system_settings.cpp index 258e213e13..fd70e2ace3 100644 --- a/src/core/hle/service/set/setting_formats/system_settings.cpp +++ b/src/core/hle/service/set/setting_formats/system_settings.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project diff --git a/src/core/hle/service/set/setting_formats/system_settings.h b/src/core/hle/service/set/setting_formats/system_settings.h index 75deb1a93f..206c81a7dd 100644 --- a/src/core/hle/service/set/setting_formats/system_settings.h +++ b/src/core/hle/service/set/setting_formats/system_settings.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -153,15 +153,15 @@ struct SystemSettings { INSERT_PADDING_BYTES(0x7FF8); // Reserved // nn::settings::system::ConsoleSixAxisSensorAccelerationBias - Common::Vec3 console_six_axis_sensor_acceleration_bias; + Common::Vec console_six_axis_sensor_acceleration_bias; // nn::settings::system::ConsoleSixAxisSensorAngularVelocityBias - Common::Vec3 console_six_axis_sensor_angular_velocity_bias; + Common::Vec console_six_axis_sensor_angular_velocity_bias; // nn::settings::system::ConsoleSixAxisSensorAccelerationGain std::array console_six_axis_sensor_acceleration_gain; // nn::settings::system::ConsoleSixAxisSensorAngularVelocityGain std::array console_six_axis_sensor_angular_velocity_gain; // nn::settings::system::ConsoleSixAxisSensorAngularVelocityTimeBias - Common::Vec3 console_six_axis_sensor_angular_velocity_time_bias; + Common::Vec console_six_axis_sensor_angular_velocity_time_bias; // nn::settings::system::ConsoleSixAxisSensorAngularAcceleration std::array console_six_axis_sensor_angular_velocity_acceleration; INSERT_PADDING_BYTES(0x70); // Reserved diff --git a/src/core/hle/service/set/settings.cpp b/src/core/hle/service/set/settings.cpp index 73d021ff4d..407332b9b7 100644 --- a/src/core/hle/service/set/settings.cpp +++ b/src/core/hle/service/set/settings.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -13,13 +16,10 @@ namespace Service::Set { void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); - server_manager->RegisterNamedService("set", std::make_shared(system)); - server_manager->RegisterNamedService("set:cal", - std::make_shared(system)); - server_manager->RegisterNamedService("set:fd", - std::make_shared(system)); - server_manager->RegisterNamedService("set:sys", - std::make_shared(system)); + server_manager->RegisterNamedService("set", std::make_shared(system), 60); + server_manager->RegisterNamedService("set:cal", std::make_shared(system), 60); + server_manager->RegisterNamedService("set:fd", std::make_shared(system), 60); + server_manager->RegisterNamedService("set:sys", std::make_shared(system), 60); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/set/settings.h b/src/core/hle/service/set/settings.h index 03cd4bb661..e6288af2e5 100644 --- a/src/core/hle/service/set/settings.h +++ b/src/core/hle/service/set/settings.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/set/settings_server.h b/src/core/hle/service/set/settings_server.h index a9cee5f626..c37a65cbc9 100644 --- a/src/core/hle/service/set/settings_server.h +++ b/src/core/hle/service/set/settings_server.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -46,7 +49,7 @@ private: Result GetDeviceNickName( OutLargeData, BufferAttr_HipcMapAlias> out_device_name); - + Result GetKeyCodeMapByPort(OutLargeData out_key_code_map, u32 port); }; diff --git a/src/core/hle/service/set/settings_types.h b/src/core/hle/service/set/settings_types.h index 696938c4c7..acb54856e0 100644 --- a/src/core/hle/service/set/settings_types.h +++ b/src/core/hle/service/set/settings_types.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include "common/bit_field.h" #include "common/common_funcs.h" diff --git a/src/core/hle/service/set/system_settings_server.cpp b/src/core/hle/service/set/system_settings_server.cpp index 0ed6d28dd4..52642e37f7 100644 --- a/src/core/hle/service/set/system_settings_server.cpp +++ b/src/core/hle/service/set/system_settings_server.cpp @@ -142,10 +142,10 @@ ISystemSettingsServer::ISystemSettingsServer(Core::System& system_) {22, C<&ISystemSettingsServer::SetEulaVersions>, "SetEulaVersions"}, {23, C<&ISystemSettingsServer::GetColorSetId>, "GetColorSetId"}, {24, C<&ISystemSettingsServer::SetColorSetId>, "SetColorSetId"}, - {25, nullptr, "GetConsoleInformationUploadFlag"}, - {26, nullptr, "SetConsoleInformationUploadFlag"}, - {27, nullptr, "GetAutomaticApplicationDownloadFlag"}, - {28, nullptr, "SetAutomaticApplicationDownloadFlag"}, + {25, C<&ISystemSettingsServer::GetConsoleInformationUploadFlag>, "GetConsoleInformationUploadFlag"}, + {26, C<&ISystemSettingsServer::SetConsoleInformationUploadFlag>, "SetConsoleInformationUploadFlag"}, + {27, C<&ISystemSettingsServer::GetAutomaticApplicationDownloadFlag>, "GetAutomaticApplicationDownloadFlag"}, + {28, C<&ISystemSettingsServer::SetAutomaticApplicationDownloadFlag>, "SetAutomaticApplicationDownloadFlag"}, {29, C<&ISystemSettingsServer::GetNotificationSettings>, "GetNotificationSettings"}, {30, C<&ISystemSettingsServer::SetNotificationSettings>, "SetNotificationSettings"}, {31, C<&ISystemSettingsServer::GetAccountNotificationSettings>, "GetAccountNotificationSettings"}, @@ -160,8 +160,8 @@ ISystemSettingsServer::ISystemSettingsServer(Core::System& system_) {42, nullptr, "SetEdid"}, {43, C<&ISystemSettingsServer::GetAudioOutputMode>, "GetAudioOutputMode"}, {44, C<&ISystemSettingsServer::SetAudioOutputMode>, "SetAudioOutputMode"}, - {45, C<&ISystemSettingsServer::GetSpeakerAutoMuteFlag> , "GetSpeakerAutoMuteFlag"}, - {46, C<&ISystemSettingsServer::SetSpeakerAutoMuteFlag> , "SetSpeakerAutoMuteFlag"}, + {45, C<&ISystemSettingsServer::GetSpeakerAutoMuteFlag>, "GetSpeakerAutoMuteFlag"}, + {46, C<&ISystemSettingsServer::SetSpeakerAutoMuteFlag>, "SetSpeakerAutoMuteFlag"}, {47, C<&ISystemSettingsServer::GetQuestFlag>, "GetQuestFlag"}, {48, C<&ISystemSettingsServer::SetQuestFlag>, "SetQuestFlag"}, {49, nullptr, "GetDataDeletionSettings"}, @@ -180,8 +180,8 @@ ISystemSettingsServer::ISystemSettingsServer(Core::System& system_) {62, C<&ISystemSettingsServer::GetDebugModeFlag>, "GetDebugModeFlag"}, {63, C<&ISystemSettingsServer::GetPrimaryAlbumStorage>, "GetPrimaryAlbumStorage"}, {64, C<&ISystemSettingsServer::SetPrimaryAlbumStorage>, "SetPrimaryAlbumStorage"}, - {65, nullptr, "GetUsb30EnableFlag"}, - {66, nullptr, "SetUsb30EnableFlag"}, + {65, C<&ISystemSettingsServer::GetUsb30EnableFlag>, "GetUsb30EnableFlag"}, + {66, C<&ISystemSettingsServer::SetUsb30EnableFlag>, "SetUsb30EnableFlag"}, {67, C<&ISystemSettingsServer::GetBatteryLot>, "GetBatteryLot"}, {68, C<&ISystemSettingsServer::GetSerialNumber>, "GetSerialNumber"}, {69, C<&ISystemSettingsServer::GetNfcEnableFlag>, "GetNfcEnableFlag"}, @@ -1020,7 +1020,7 @@ Result ISystemSettingsServer::GetBatteryLot(Out out_battery_lot) { c.lot_number[1] = 'H'; c.lot_number[2] = 'A'; c.lot_number[3] = 'C'; - // TODO: I have no fucking idea what the letters mean + // TODO: what do the letters mean? c.lot_number[4] = 'H'; c.lot_number[5] = 'Z'; c.lot_number[6] = 'Z'; @@ -1074,6 +1074,45 @@ Result ISystemSettingsServer::SetNfcEnableFlag(bool nfc_enable_flag) { R_SUCCEED(); } +Result ISystemSettingsServer::GetConsoleInformationUploadFlag(Out out_flag) { + LOG_INFO(Service_SET, "called {}", m_system_settings.console_information_upload_flag); + *out_flag = m_system_settings.console_information_upload_flag; + R_SUCCEED(); +} + +Result ISystemSettingsServer::SetConsoleInformationUploadFlag(bool flag) { + LOG_INFO(Service_SET, "called {}", flag); + m_system_settings.usb_30_enable_flag = flag; + SetSaveNeeded(); + R_SUCCEED(); +} + +Result ISystemSettingsServer::GetAutomaticApplicationDownloadFlag(Out out_flag) { + LOG_INFO(Service_SET, "called {}", m_system_settings.usb_30_enable_flag); + *out_flag = m_system_settings.automatic_application_download_flag; + R_SUCCEED(); +} + +Result ISystemSettingsServer::SetAutomaticApplicationDownloadFlag(bool flag) { + LOG_INFO(Service_SET, "called {}", flag); + m_system_settings.automatic_application_download_flag = flag; + SetSaveNeeded(); + R_SUCCEED(); +} + +Result ISystemSettingsServer::GetUsb30EnableFlag(Out out_usb30_enable_flag) { + LOG_INFO(Service_SET, "called, usb30_enable_flag={}", m_system_settings.usb_30_enable_flag); + *out_usb30_enable_flag = m_system_settings.usb_30_enable_flag; + R_SUCCEED(); +} + +Result ISystemSettingsServer::SetUsb30EnableFlag(bool usb30_enable_flag) { + LOG_INFO(Service_SET, "called, usb30_enable_flag={}", usb30_enable_flag); + m_system_settings.usb_30_enable_flag = usb30_enable_flag; + SetSaveNeeded(); + R_SUCCEED(); +} + Result ISystemSettingsServer::GetSleepSettings(Out out_sleep_settings) { LOG_INFO(Service_SET, "called, flags={}, handheld_sleep_plan={}, console_sleep_plan={}", m_system_settings.sleep_settings.flags.raw, diff --git a/src/core/hle/service/set/system_settings_server.h b/src/core/hle/service/set/system_settings_server.h index 2a160dfc6b..1d56e61f88 100644 --- a/src/core/hle/service/set/system_settings_server.h +++ b/src/core/hle/service/set/system_settings_server.h @@ -109,6 +109,12 @@ public: Result SetPrimaryAlbumStorage(PrimaryAlbumStorage primary_album_storage); Result GetBatteryLot(Out out_battery_lot); Result GetSerialNumber(Out out_console_serial); + Result GetConsoleInformationUploadFlag(Out out_flag); + Result SetConsoleInformationUploadFlag(bool flag); + Result GetAutomaticApplicationDownloadFlag(Out out_flag); + Result SetAutomaticApplicationDownloadFlag(bool flag); + Result GetUsb30EnableFlag(Out out_usb30_enable_flag); + Result SetUsb30EnableFlag(bool usb30_enable_flag); Result GetNfcEnableFlag(Out out_nfc_enable_flag); Result SetNfcEnableFlag(bool nfc_enable_flag); Result GetSleepSettings(Out out_sleep_settings); diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index 1095dcf6c3..c75c5437df 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -11,6 +14,7 @@ #include "core/hle/kernel/k_scoped_resource_reservation.h" #include "core/hle/kernel/k_server_port.h" #include "core/hle/result.h" +#include "core/hle/service/cmif_types.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/sm/sm.h" @@ -29,11 +33,11 @@ ServiceManager::ServiceManager(Kernel::KernelCore& kernel_) : kernel{kernel_} { ServiceManager::~ServiceManager() { for (auto& [name, port] : service_ports) { - port->Close(); + port->Close(kernel); } if (deferral_event) { - deferral_event->Close(); + deferral_event->Close(kernel); } } @@ -49,8 +53,7 @@ static Result ValidateServiceName(const std::string& name) { return ResultSuccess; } -Result ServiceManager::RegisterService(Kernel::KServerPort** out_server_port, std::string name, - u32 max_sessions, SessionRequestHandlerFactory handler) { +Result ServiceManager::RegisterService(Kernel::KServerPort** out_server_port, std::string name, u32 max_sessions, SessionRequestHandlerFactory handler) { R_TRY(ValidateServiceName(name)); std::scoped_lock lk{lock}; @@ -60,7 +63,7 @@ Result ServiceManager::RegisterService(Kernel::KServerPort** out_server_port, st } auto* port = Kernel::KPort::Create(kernel); - port->Initialize(ServerSessionCountMax, false, 0); + port->Initialize(kernel, max_sessions, false, 0); // Register the port. Kernel::KPort::Register(kernel, port); @@ -68,7 +71,7 @@ Result ServiceManager::RegisterService(Kernel::KServerPort** out_server_port, st service_ports.emplace(name, std::addressof(port->GetClientPort())); registered_services.emplace(name, handler); if (deferral_event) { - deferral_event->Signal(); + deferral_event->Signal(kernel); } // Set our output. @@ -136,7 +139,7 @@ void SM::GetServiceCmif(HLERequestContext& ctx) { if (result == ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; rb.Push(result); - rb.PushMoveObjects(client_session); + rb.PushMoveObjects(ctx, client_session); } else { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); @@ -153,7 +156,7 @@ void SM::GetServiceTipc(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; rb.Push(result); - rb.PushMoveObjects(result == ResultSuccess ? client_session : nullptr); + rb.PushMoveObjects(ctx, result == ResultSuccess ? client_session : nullptr); } static std::string PopServiceName(IPC::RequestParser& rp) { @@ -191,8 +194,8 @@ Result SM::GetServiceImpl(Kernel::KClientSession** out_client_session, HLEReques // Create a new session. Kernel::KClientSession* session{}; - if (const auto result = client_port->CreateSession(&session); result.IsError()) { - LOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, result.raw); + if (const auto result = client_port->CreateSession(kernel, &session); result.IsError()) { + LOG_ERROR(Service_SM, "called service={} -> error {:#08x}", name, result.raw); return result; } @@ -226,8 +229,7 @@ void SM::RegisterServiceImpl(HLERequestContext& ctx, std::string name, u32 max_s max_session_count, is_light); Kernel::KServerPort* server_port{}; - if (const auto result = service_manager.RegisterService(std::addressof(server_port), name, - max_session_count, nullptr); + if (const auto result = service_manager.RegisterService(std::addressof(server_port), name, max_session_count, nullptr); result.IsError()) { LOG_ERROR(Service_SM, "failed to register service with error_code={:08X}", result.raw); IPC::ResponseBuilder rb{ctx, 2}; @@ -237,7 +239,7 @@ void SM::RegisterServiceImpl(HLERequestContext& ctx, std::string name, u32 max_s IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; rb.Push(ResultSuccess); - rb.PushMoveObjects(server_port); + rb.PushMoveObjects(ctx, server_port); } void SM::UnregisterService(HLERequestContext& ctx) { @@ -250,15 +252,37 @@ void SM::UnregisterService(HLERequestContext& ctx) { rb.Push(service_manager.UnregisterService(name)); } +void SM::AtmosphereHasService(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + std::string name(PopServiceName(rp)); + LOG_WARNING(Service_SM, "(stubbed) called with name={}", name); + IPC::ResponseBuilder rb{ctx, 3}; + Kernel::KClientPort* out_client_port = nullptr; + rb.Push(ResultSuccess); + rb.Push(service_manager.GetServicePort(&out_client_port, name) == ResultSuccess); +} + SM::SM(ServiceManager& service_manager_, Core::System& system_) - : ServiceFramework{system_, "sm:", 4}, - service_manager{service_manager_}, kernel{system_.Kernel()} { + : ServiceFramework{system_, "sm:", 64} + , service_manager{service_manager_} + , kernel{system_.Kernel()} +{ RegisterHandlers({ {0, &SM::Initialize, "Initialize"}, {1, &SM::GetServiceCmif, "GetService"}, {2, &SM::RegisterServiceCmif, "RegisterService"}, {3, &SM::UnregisterService, "UnregisterService"}, {4, nullptr, "DetachClient"}, + // TODO: are these non-TIPC as well? + {65000, nullptr, "AtmosphereInstallMitm"}, + {65001, nullptr, "AtmosphereUninstallMitm"}, + {65002, nullptr, "Deprecated_AtmosphereAssociatePidTidForMitm"}, + {65003, nullptr, "AtmosphereAcknowledgeMitmSession"}, + {65004, nullptr, "AtmosphereHasMitm"}, + {65005, nullptr, "AtmosphereWaitMitm"}, + {65006, nullptr, "AtmosphereDeclareFutureMitm"}, + {65100, &SM::AtmosphereHasService, "AtmosphereHasService"}, + {65101, nullptr, "AtmosphereWaitService"}, }); RegisterHandlersTipc({ {0, &SM::Initialize, "Initialize"}, @@ -266,6 +290,15 @@ SM::SM(ServiceManager& service_manager_, Core::System& system_) {2, &SM::RegisterServiceTipc, "RegisterService"}, {3, &SM::UnregisterService, "UnregisterService"}, {4, nullptr, "DetachClient"}, + {65000, nullptr, "AtmosphereInstallMitm"}, + {65001, nullptr, "AtmosphereUninstallMitm"}, + {65002, nullptr, "Deprecated_AtmosphereAssociatePidTidForMitm"}, + {65003, nullptr, "AtmosphereAcknowledgeMitmSession"}, + {65004, nullptr, "AtmosphereHasMitm"}, + {65005, nullptr, "AtmosphereWaitMitm"}, + {65006, nullptr, "AtmosphereDeclareFutureMitm"}, + {65100, &SM::AtmosphereHasService, "AtmosphereHasService"}, + {65101, nullptr, "AtmosphereWaitService"}, }); } diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index 65e6876926..c520a46fb2 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include "core/hle/kernel/k_port.h" @@ -47,6 +47,7 @@ private: void RegisterServiceCmif(HLERequestContext& ctx); void RegisterServiceTipc(HLERequestContext& ctx); void UnregisterService(HLERequestContext& ctx); + void AtmosphereHasService(HLERequestContext& ctx); Result GetServiceImpl(Kernel::KClientSession** out_client_session, HLERequestContext& ctx); void RegisterServiceImpl(HLERequestContext& ctx, std::string name, u32 max_session_count, @@ -99,8 +100,8 @@ private: /// Map of registered services, retrieved using GetServicePort. mutable std::mutex lock; - ankerl::unordered_dense::map registered_services; - ankerl::unordered_dense::map service_ports; + ::Common::unordered_map registered_services; + ::Common::unordered_map service_ports; /// Kernel context Kernel::KernelCore& kernel; diff --git a/src/core/hle/service/sm/sm_controller.cpp b/src/core/hle/service/sm/sm_controller.cpp index 00c88f6de1..125eb4a247 100644 --- a/src/core/hle/service/sm/sm_controller.cpp +++ b/src/core/hle/service/sm/sm_controller.cpp @@ -37,8 +37,7 @@ void Controller::CloneCurrentObject(HLERequestContext& ctx) { // once this is a proper process // Reserve a new session from the process resource limit. - Kernel::KScopedResourceReservation session_reservation( - Kernel::GetCurrentProcessPointer(kernel), Kernel::LimitableResource::SessionCountMax); + Kernel::KScopedResourceReservation session_reservation(system.Kernel(), Kernel::GetCurrentProcessPointer(kernel), Kernel::LimitableResource::SessionCountMax); ASSERT(session_reservation.Succeeded()); // Create the session. @@ -46,7 +45,7 @@ void Controller::CloneCurrentObject(HLERequestContext& ctx) { ASSERT(session != nullptr); // Initialize the session. - session->Initialize(nullptr, 0); + session->Initialize(kernel, nullptr, 0); // Commit the session reservation. session_reservation.Commit(); @@ -61,7 +60,7 @@ void Controller::CloneCurrentObject(HLERequestContext& ctx) { // We succeeded. IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; rb.Push(ResultSuccess); - rb.PushMoveObjects(session->GetClientSession()); + rb.PushMoveObjects(ctx, session->GetClientSession()); } void Controller::CloneCurrentObjectEx(HLERequestContext& ctx) { diff --git a/src/core/hle/service/sm/sm_controller.h b/src/core/hle/service/sm/sm_controller.h index f7e529a269..f1fa327ff7 100644 --- a/src/core/hle/service/sm/sm_controller.h +++ b/src/core/hle/service/sm/sm_controller.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index fd758d4915..0137a3be66 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -11,6 +11,7 @@ #include +#include "common/logging.h" #include "common/socket_types.h" #include "core/core.h" #include "core/hle/kernel/k_thread.h" @@ -23,9 +24,6 @@ #include "network/network.h" #include -using Common::Expected; -using Common::Unexpected; - namespace Service::Sockets { namespace { @@ -56,11 +54,11 @@ void PutValue(std::span buffer, const T& t) { } // Anonymous namespace -void BSD::PollWork::Execute(BSD* bsd) { +void BSD_USA::PollWork::Execute(BSD_USA* bsd) { std::tie(ret, bsd_errno) = bsd->PollImpl(write_buffer, read_buffer, nfds, timeout); } -void BSD::PollWork::Response(HLERequestContext& ctx) { +void BSD_USA::PollWork::Response(HLERequestContext& ctx) { if (write_buffer.size() > 0) { ctx.WriteBuffer(write_buffer); } @@ -71,11 +69,11 @@ void BSD::PollWork::Response(HLERequestContext& ctx) { rb.PushEnum(bsd_errno); } -void BSD::AcceptWork::Execute(BSD* bsd) { +void BSD_USA::AcceptWork::Execute(BSD_USA* bsd) { std::tie(ret, bsd_errno) = bsd->AcceptImpl(fd, write_buffer); } -void BSD::AcceptWork::Response(HLERequestContext& ctx) { +void BSD_USA::AcceptWork::Response(HLERequestContext& ctx) { if (write_buffer.size() > 0) { ctx.WriteBuffer(write_buffer); } @@ -87,22 +85,22 @@ void BSD::AcceptWork::Response(HLERequestContext& ctx) { rb.Push(static_cast(write_buffer.size())); } -void BSD::ConnectWork::Execute(BSD* bsd) { +void BSD_USA::ConnectWork::Execute(BSD_USA* bsd) { bsd_errno = bsd->ConnectImpl(fd, addr); } -void BSD::ConnectWork::Response(HLERequestContext& ctx) { +void BSD_USA::ConnectWork::Response(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); rb.Push(bsd_errno == Errno::SUCCESS ? 0 : -1); rb.PushEnum(bsd_errno); } -void BSD::RecvWork::Execute(BSD* bsd) { +void BSD_USA::RecvWork::Execute(BSD_USA* bsd) { std::tie(ret, bsd_errno) = bsd->RecvImpl(fd, flags, message); } -void BSD::RecvWork::Response(HLERequestContext& ctx) { +void BSD_USA::RecvWork::Response(HLERequestContext& ctx) { ctx.WriteBuffer(message); IPC::ResponseBuilder rb{ctx, 4}; @@ -111,11 +109,11 @@ void BSD::RecvWork::Response(HLERequestContext& ctx) { rb.PushEnum(bsd_errno); } -void BSD::RecvFromWork::Execute(BSD* bsd) { +void BSD_USA::RecvFromWork::Execute(BSD_USA* bsd) { std::tie(ret, bsd_errno) = bsd->RecvFromImpl(fd, flags, message, addr); } -void BSD::RecvFromWork::Response(HLERequestContext& ctx) { +void BSD_USA::RecvFromWork::Response(HLERequestContext& ctx) { ctx.WriteBuffer(message, 0); if (!addr.empty()) { ctx.WriteBuffer(addr, 1); @@ -128,29 +126,29 @@ void BSD::RecvFromWork::Response(HLERequestContext& ctx) { rb.Push(static_cast(addr.size())); } -void BSD::SendWork::Execute(BSD* bsd) { +void BSD_USA::SendWork::Execute(BSD_USA* bsd) { std::tie(ret, bsd_errno) = bsd->SendImpl(fd, flags, message); } -void BSD::SendWork::Response(HLERequestContext& ctx) { +void BSD_USA::SendWork::Response(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); rb.Push(ret); rb.PushEnum(bsd_errno); } -void BSD::SendToWork::Execute(BSD* bsd) { +void BSD_USA::SendToWork::Execute(BSD_USA* bsd) { std::tie(ret, bsd_errno) = bsd->SendToImpl(fd, flags, message, addr); } -void BSD::SendToWork::Response(HLERequestContext& ctx) { +void BSD_USA::SendToWork::Response(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); rb.Push(ret); rb.PushEnum(bsd_errno); } -void BSD::RegisterClient(HLERequestContext& ctx) { +void BSD_USA::RegisterClient(HLERequestContext& ctx) { LOG_WARNING(Service, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; @@ -159,7 +157,7 @@ void BSD::RegisterClient(HLERequestContext& ctx) { rb.Push(0); // bsd errno } -void BSD::StartMonitoring(HLERequestContext& ctx) { +void BSD_USA::StartMonitoring(HLERequestContext& ctx) { LOG_WARNING(Service, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2}; @@ -167,7 +165,7 @@ void BSD::StartMonitoring(HLERequestContext& ctx) { rb.Push(ResultSuccess); } -void BSD::Socket(HLERequestContext& ctx) { +void BSD_USA::Socket(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u32 domain = rp.Pop(); const u32 type = rp.Pop(); @@ -175,8 +173,7 @@ void BSD::Socket(HLERequestContext& ctx) { LOG_DEBUG(Service, "called. domain={} type={} protocol={}", domain, type, protocol); - const auto [fd, bsd_errno] = SocketImpl(static_cast(domain), static_cast(type), - static_cast(protocol)); + const auto [fd, bsd_errno] = SocketImpl(Domain(domain), Type(type), Protocol(protocol)); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); @@ -184,7 +181,26 @@ void BSD::Socket(HLERequestContext& ctx) { rb.PushEnum(bsd_errno); } -void BSD::Select(HLERequestContext& ctx) { +void BSD_USA::SocketExempt(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u32 domain = rp.Pop(); + const u32 type = rp.Pop(); + const u32 protocol = rp.Pop(); + + LOG_DEBUG(Service, "called. domain={} type={} protocol={}", domain, type, protocol); + + auto [fd, bsd_errno] = SocketImpl(Domain(domain), Type(type), Protocol(protocol)); + if (bsd_errno == Errno::SUCCESS) { + bsd_errno = ShutdownImpl(fd, 0); + } + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); + rb.Push(fd); + rb.PushEnum(bsd_errno); +} + +void BSD_USA::Select(HLERequestContext& ctx) { LOG_DEBUG(Service, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 4}; @@ -194,7 +210,7 @@ void BSD::Select(HLERequestContext& ctx) { rb.Push(0); // bsd errno } -void BSD::Poll(HLERequestContext& ctx) { +void BSD_USA::Poll(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 nfds = rp.Pop(); const s32 timeout = rp.Pop(); @@ -209,7 +225,7 @@ void BSD::Poll(HLERequestContext& ctx) { }); } -void BSD::Accept(HLERequestContext& ctx) { +void BSD_USA::Accept(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); @@ -221,7 +237,7 @@ void BSD::Accept(HLERequestContext& ctx) { }); } -void BSD::Bind(HLERequestContext& ctx) { +void BSD_USA::Bind(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); @@ -229,7 +245,7 @@ void BSD::Bind(HLERequestContext& ctx) { BuildErrnoResponse(ctx, BindImpl(fd, ctx.ReadBuffer())); } -void BSD::Connect(HLERequestContext& ctx) { +void BSD_USA::Connect(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); @@ -241,7 +257,7 @@ void BSD::Connect(HLERequestContext& ctx) { }); } -void BSD::GetPeerName(HLERequestContext& ctx) { +void BSD_USA::GetPeerName(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); @@ -259,7 +275,7 @@ void BSD::GetPeerName(HLERequestContext& ctx) { rb.Push(static_cast(write_buffer.size())); } -void BSD::GetSockName(HLERequestContext& ctx) { +void BSD_USA::GetSockName(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); @@ -277,7 +293,7 @@ void BSD::GetSockName(HLERequestContext& ctx) { rb.Push(static_cast(write_buffer.size())); } -void BSD::GetSockOpt(HLERequestContext& ctx) { +void BSD_USA::GetSockOpt(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); const u32 level = rp.Pop(); @@ -285,7 +301,7 @@ void BSD::GetSockOpt(HLERequestContext& ctx) { std::vector optval(ctx.GetWriteBufferSize()); - LOG_DEBUG(Service, "called. fd={} level={} optname=0x{:x} len=0x{:x}", fd, level, optname, + LOG_DEBUG(Service, "called. fd={} level={} optname={:#x} len={:#x}", fd, level, optname, optval.size()); const Errno err = GetSockOptImpl(fd, level, optname, optval); @@ -299,7 +315,7 @@ void BSD::GetSockOpt(HLERequestContext& ctx) { rb.Push(static_cast(optval.size())); } -void BSD::Listen(HLERequestContext& ctx) { +void BSD_USA::Listen(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); const s32 backlog = rp.Pop(); @@ -309,7 +325,7 @@ void BSD::Listen(HLERequestContext& ctx) { BuildErrnoResponse(ctx, ListenImpl(fd, backlog)); } -void BSD::Fcntl(HLERequestContext& ctx) { +void BSD_USA::Fcntl(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); const s32 cmd = rp.Pop(); @@ -325,7 +341,7 @@ void BSD::Fcntl(HLERequestContext& ctx) { rb.PushEnum(bsd_errno); } -void BSD::SetSockOpt(HLERequestContext& ctx) { +void BSD_USA::SetSockOpt(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); @@ -333,13 +349,13 @@ void BSD::SetSockOpt(HLERequestContext& ctx) { const OptName optname = static_cast(rp.Pop()); const auto optval = ctx.ReadBuffer(); - LOG_DEBUG(Service, "called. fd={} level={} optname=0x{:x} optlen={}", fd, level, + LOG_DEBUG(Service, "called. fd={} level={} optname={:#x} optlen={}", fd, level, static_cast(optname), optval.size()); BuildErrnoResponse(ctx, SetSockOptImpl(fd, level, optname, optval)); } -void BSD::Shutdown(HLERequestContext& ctx) { +void BSD_USA::Shutdown(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); @@ -350,13 +366,13 @@ void BSD::Shutdown(HLERequestContext& ctx) { BuildErrnoResponse(ctx, ShutdownImpl(fd, how)); } -void BSD::Recv(HLERequestContext& ctx) { +void BSD_USA::Recv(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); const u32 flags = rp.Pop(); - LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetWriteBufferSize()); + LOG_DEBUG(Service, "called. fd={} flags={:#x} len={}", fd, flags, ctx.GetWriteBufferSize()); ExecuteWork(ctx, RecvWork{ .fd = fd, @@ -365,13 +381,13 @@ void BSD::Recv(HLERequestContext& ctx) { }); } -void BSD::RecvFrom(HLERequestContext& ctx) { +void BSD_USA::RecvFrom(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); const u32 flags = rp.Pop(); - LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={} addrlen={}", fd, flags, + LOG_DEBUG(Service, "called. fd={} flags={:#x} len={} addrlen={}", fd, flags, ctx.GetWriteBufferSize(0), ctx.GetWriteBufferSize(1)); ExecuteWork(ctx, RecvFromWork{ @@ -382,13 +398,13 @@ void BSD::RecvFrom(HLERequestContext& ctx) { }); } -void BSD::Send(HLERequestContext& ctx) { +void BSD_USA::Send(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); const u32 flags = rp.Pop(); - LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetReadBufferSize()); + LOG_DEBUG(Service, "called. fd={} flags={:#x} len={}", fd, flags, ctx.GetReadBufferSize()); ExecuteWork(ctx, SendWork{ .fd = fd, @@ -397,7 +413,7 @@ void BSD::Send(HLERequestContext& ctx) { }); } -void BSD::SendTo(HLERequestContext& ctx) { +void BSD_USA::SendTo(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); const u32 flags = rp.Pop(); @@ -413,7 +429,7 @@ void BSD::SendTo(HLERequestContext& ctx) { }); } -void BSD::Write(HLERequestContext& ctx) { +void BSD_USA::Write(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); @@ -426,7 +442,7 @@ void BSD::Write(HLERequestContext& ctx) { }); } -void BSD::Read(HLERequestContext& ctx) { +void BSD_USA::Read(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); @@ -438,7 +454,7 @@ void BSD::Read(HLERequestContext& ctx) { rb.Push(0); // bsd errno } -void BSD::Close(HLERequestContext& ctx) { +void BSD_USA::Close(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const s32 fd = rp.Pop(); @@ -447,7 +463,8 @@ void BSD::Close(HLERequestContext& ctx) { BuildErrnoResponse(ctx, CloseImpl(fd)); } -void BSD::DuplicateSocket(HLERequestContext& ctx) { +/// @brief Only bsd:s is able to dup() +void BSD_USA::DuplicateSocket(HLERequestContext& ctx) { struct InputParameters { s32 fd; u64 reserved; @@ -463,16 +480,32 @@ void BSD::DuplicateSocket(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; auto input = rp.PopRaw(); - Expected res = DuplicateSocketImpl(input.fd); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); - rb.PushRaw(OutputParameters{ - .ret = res.value_or(0), - .bsd_errno = res ? Errno::SUCCESS : res.error(), - }); + if (is_user) { + rb.PushRaw(OutputParameters{ + .ret = 0, + .bsd_errno = Errno::INVAL, + }); + return; + } + + auto const res_v = DuplicateSocketImpl(input.fd); + if (auto* res = std::get_if(&res_v)) { + rb.PushRaw(OutputParameters{ + .ret = *res, + .bsd_errno = Errno::SUCCESS, + }); + } else { + auto* err = std::get_if(&res_v); + rb.PushRaw(OutputParameters{ + .ret = 0, + .bsd_errno = *err, + }); + } } -void BSD::EventFd(HLERequestContext& ctx) { +void BSD_USA::EventFd(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u64 initval = rp.Pop(); const u32 flags = rp.Pop(); @@ -483,17 +516,19 @@ void BSD::EventFd(HLERequestContext& ctx) { } template -void BSD::ExecuteWork(HLERequestContext& ctx, Work work) { +void BSD_USA::ExecuteWork(HLERequestContext& ctx, Work work) { work.Execute(this); work.Response(ctx); } -std::pair BSD::SocketImpl(Domain domain, Type type, Protocol protocol) { - - if (type == Type::SEQPACKET) { - UNIMPLEMENTED_MSG("SOCK_SEQPACKET errno management"); - } else if (type == Type::RAW && (domain != Domain::INET || protocol != Protocol::ICMP)) { - UNIMPLEMENTED_MSG("SOCK_RAW errno management"); +std::pair BSD_USA::SocketImpl(Domain domain, Type type, Protocol protocol) { + // user bsd:u has restrictions on SOCK_SEQPACKET and SOCK_RAW + if (is_user && (type == Type::SEQPACKET || type == Type::RAW)) { + if (type == Type::RAW && domain == Domain::INET && protocol == Protocol::ICMP) { + // fine, can use on bsd:s and bsd:u + } else { + return {-1, Errno::INVAL}; + } } [[maybe_unused]] const bool unk_flag = (static_cast(type) & 0x20000000) != 0; @@ -530,7 +565,7 @@ std::pair BSD::SocketImpl(Domain domain, Type type, Protocol protoco return {fd, Errno::SUCCESS}; } -std::pair BSD::PollImpl(std::vector& write_buffer, std::span read_buffer, +std::pair BSD_USA::PollImpl(std::vector& write_buffer, std::span read_buffer, s32 nfds, s32 timeout) { if (nfds <= 0) { // When no entries are provided, -1 is returned with errno zero @@ -597,7 +632,7 @@ std::pair BSD::PollImpl(std::vector& write_buffer, std::span BSD::AcceptImpl(s32 fd, std::vector& write_buffer) { +std::pair BSD_USA::AcceptImpl(s32 fd, std::vector& write_buffer) { if (!IsFileDescriptorValid(fd)) { return {-1, Errno::BADF}; } @@ -625,22 +660,32 @@ std::pair BSD::AcceptImpl(s32 fd, std::vector& write_buffer) { return {new_fd, Errno::SUCCESS}; } -Errno BSD::BindImpl(s32 fd, std::span addr) { +Errno BSD_USA::BindImpl(s32 fd, std::span addr) { if (!IsFileDescriptorValid(fd)) { return Errno::BADF; } - ASSERT(addr.size() == sizeof(SockAddrIn)); + ASSERT(addr.size() >= 16); + if (!file_descriptors[fd]->socket) { + LOG_WARNING(Service, "Uninitialized socket"); + return Errno::BADF; + } + auto addr_in = GetValue(addr); return Translate(file_descriptors[fd]->socket->Bind(Translate(addr_in))); } -Errno BSD::ConnectImpl(s32 fd, std::span addr) { +Errno BSD_USA::ConnectImpl(s32 fd, std::span addr) { if (!IsFileDescriptorValid(fd)) { return Errno::BADF; } - UNIMPLEMENTED_IF(addr.size() != sizeof(SockAddrIn)); + ASSERT(addr.size() >= 16); + if (!file_descriptors[fd]->socket) { + LOG_WARNING(Service, "Uninitialized socket"); + return Errno::BADF; + } + auto addr_in = GetValue(addr); const Errno result = Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in))); @@ -653,11 +698,16 @@ Errno BSD::ConnectImpl(s32 fd, std::span addr) { return result; } -Errno BSD::GetPeerNameImpl(s32 fd, std::vector& write_buffer) { +Errno BSD_USA::GetPeerNameImpl(s32 fd, std::vector& write_buffer) { if (!IsFileDescriptorValid(fd)) { return Errno::BADF; } + if (!file_descriptors[fd]->socket) { + LOG_WARNING(Service, "Uninitialized socket"); + return Errno::BADF; + } + const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetPeerName(); if (bsd_errno != Network::Errno::SUCCESS) { return Translate(bsd_errno); @@ -670,11 +720,16 @@ Errno BSD::GetPeerNameImpl(s32 fd, std::vector& write_buffer) { return Translate(bsd_errno); } -Errno BSD::GetSockNameImpl(s32 fd, std::vector& write_buffer) { +Errno BSD_USA::GetSockNameImpl(s32 fd, std::vector& write_buffer) { if (!IsFileDescriptorValid(fd)) { return Errno::BADF; } + if (!file_descriptors[fd]->socket) { + LOG_WARNING(Service, "Uninitialized socket"); + return Errno::BADF; + } + const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetSockName(); if (bsd_errno != Network::Errno::SUCCESS) { return Translate(bsd_errno); @@ -687,17 +742,25 @@ Errno BSD::GetSockNameImpl(s32 fd, std::vector& write_buffer) { return Translate(bsd_errno); } -Errno BSD::ListenImpl(s32 fd, s32 backlog) { +Errno BSD_USA::ListenImpl(s32 fd, s32 backlog) { if (!IsFileDescriptorValid(fd)) { return Errno::BADF; } + if (!file_descriptors[fd]->socket) { + LOG_WARNING(Service, "Uninitialized socket"); + return Errno::BADF; + } return Translate(file_descriptors[fd]->socket->Listen(backlog)); } -std::pair BSD::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) { +std::pair BSD_USA::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) { if (!IsFileDescriptorValid(fd)) { return {-1, Errno::BADF}; } + if (!file_descriptors[fd]->socket) { + LOG_WARNING(Service, "Uninitialized socket"); + return {-1, Errno::BADF}; + } FileDescriptor& descriptor = *file_descriptors[fd]; @@ -720,10 +783,14 @@ std::pair BSD::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) { } } -Errno BSD::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector& optval) { +Errno BSD_USA::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector& optval) { if (!IsFileDescriptorValid(fd)) { return Errno::BADF; } + if (!file_descriptors[fd]->socket) { + LOG_WARNING(Service, "Uninitialized socket"); + return Errno::BADF; + } if (level != static_cast(SocketLevel::SOCKET)) { UNIMPLEMENTED_MSG("Unknown getsockopt level"); @@ -751,10 +818,14 @@ Errno BSD::GetSockOptImpl(s32 fd, u32 level, OptName optname, std::vector& o } } -Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span optval) { +Errno BSD_USA::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span optval) { if (!IsFileDescriptorValid(fd)) { return Errno::BADF; } + if (!file_descriptors[fd]->socket) { + LOG_WARNING(Service, "Uninitialized socket"); + return Errno::BADF; + } if (level != static_cast(SocketLevel::SOCKET)) { LOG_WARNING(Service, "(STUBBED) setsockopt with level={}, optname={}", level, optname); @@ -801,15 +872,19 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::spansocket) { + LOG_WARNING(Service, "Uninitialized socket"); + return Errno::BADF; + } const Network::ShutdownHow host_how = Translate(static_cast(how)); return Translate(file_descriptors[fd]->socket->Shutdown(host_how)); } -std::pair BSD::RecvImpl(s32 fd, u32 flags, std::vector& message) { +std::pair BSD_USA::RecvImpl(s32 fd, u32 flags, std::vector& message) { if (!IsFileDescriptorValid(fd)) { return {-1, Errno::BADF}; } @@ -836,7 +911,7 @@ std::pair BSD::RecvImpl(s32 fd, u32 flags, std::vector& message) return {ret, bsd_errno}; } -std::pair BSD::RecvFromImpl(s32 fd, u32 flags, std::vector& message, +std::pair BSD_USA::RecvFromImpl(s32 fd, u32 flags, std::vector& message, std::vector& addr) { if (!IsFileDescriptorValid(fd)) { return {-1, Errno::BADF}; @@ -874,7 +949,7 @@ std::pair BSD::RecvFromImpl(s32 fd, u32 flags, std::vector& mess if (ret < 0) { addr.clear(); } else { - ASSERT(addr.size() == sizeof(SockAddrIn)); + ASSERT(addr.size() >= 16); const SockAddrIn result = Translate(addr_in); PutValue(addr, result); } @@ -883,23 +958,31 @@ std::pair BSD::RecvFromImpl(s32 fd, u32 flags, std::vector& mess return {ret, bsd_errno}; } -std::pair BSD::SendImpl(s32 fd, u32 flags, std::span message) { +std::pair BSD_USA::SendImpl(s32 fd, u32 flags, std::span message) { if (!IsFileDescriptorValid(fd)) { return {-1, Errno::BADF}; } + if (!file_descriptors[fd]->socket) { + LOG_WARNING(Service, "Uninitialized socket"); + return {-1, Errno::BADF}; + } return Translate(file_descriptors[fd]->socket->Send(message, flags)); } -std::pair BSD::SendToImpl(s32 fd, u32 flags, std::span message, +std::pair BSD_USA::SendToImpl(s32 fd, u32 flags, std::span message, std::span addr) { if (!IsFileDescriptorValid(fd)) { return {-1, Errno::BADF}; } + if (!file_descriptors[fd]->socket) { + LOG_WARNING(Service, "Uninitialized socket"); + return {-1, Errno::BADF}; + } Network::SockAddrIn addr_in; Network::SockAddrIn* p_addr_in = nullptr; if (!addr.empty()) { - ASSERT(addr.size() == sizeof(SockAddrIn)); + ASSERT(addr.size() >= 16); auto guest_addr_in = GetValue(addr); addr_in = Translate(guest_addr_in); p_addr_in = &addr_in; @@ -908,10 +991,14 @@ std::pair BSD::SendToImpl(s32 fd, u32 flags, std::span mes return Translate(file_descriptors[fd]->socket->SendTo(flags, message, p_addr_in)); } -Errno BSD::CloseImpl(s32 fd) { +Errno BSD_USA::CloseImpl(s32 fd) { if (!IsFileDescriptorValid(fd)) { return Errno::BADF; } + if (!file_descriptors[fd]->socket) { + LOG_WARNING(Service, "Uninitialized socket"); + return Errno::BADF; + } const Errno bsd_errno = Translate(file_descriptors[fd]->socket->Close()); if (bsd_errno != Errno::SUCCESS) { @@ -924,15 +1011,15 @@ Errno BSD::CloseImpl(s32 fd) { return bsd_errno; } -Expected BSD::DuplicateSocketImpl(s32 fd) { +std::variant BSD_USA::DuplicateSocketImpl(s32 fd) { if (!IsFileDescriptorValid(fd)) { - return Unexpected(Errno::BADF); + return Errno::BADF; } const s32 new_fd = FindFreeFileDescriptorHandle(); if (new_fd < 0) { LOG_ERROR(Service, "No more file descriptors available"); - return Unexpected(Errno::MFILE); + return Errno::MFILE; } file_descriptors[new_fd] = FileDescriptor{ @@ -943,14 +1030,18 @@ Expected BSD::DuplicateSocketImpl(s32 fd) { return new_fd; } -std::optional> BSD::GetSocket(s32 fd) { +std::optional> BSD_USA::GetSocket(s32 fd) { if (!IsFileDescriptorValid(fd)) { return std::nullopt; } + if (!file_descriptors[fd]->socket) { + LOG_WARNING(Service, "Uninitialized socket"); + return std::nullopt; + } return file_descriptors[fd]->socket; } -s32 BSD::FindFreeFileDescriptorHandle() noexcept { +s32 BSD_USA::FindFreeFileDescriptorHandle() noexcept { for (s32 fd = 0; fd < static_cast(file_descriptors.size()); ++fd) { if (!file_descriptors[fd]) { return fd; @@ -959,7 +1050,7 @@ s32 BSD::FindFreeFileDescriptorHandle() noexcept { return -1; } -bool BSD::IsFileDescriptorValid(s32 fd) const noexcept { +bool BSD_USA::IsFileDescriptorValid(s32 fd) const noexcept { if (fd > static_cast(MAX_FD) || fd < 0) { LOG_ERROR(Service, "Invalid file descriptor handle={}", fd); return false; @@ -971,7 +1062,7 @@ bool BSD::IsFileDescriptorValid(s32 fd) const noexcept { return true; } -void BSD::BuildErrnoResponse(HLERequestContext& ctx, Errno bsd_errno) const noexcept { +void BSD_USA::BuildErrnoResponse(HLERequestContext& ctx, Errno bsd_errno) const noexcept { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); @@ -979,7 +1070,7 @@ void BSD::BuildErrnoResponse(HLERequestContext& ctx, Errno bsd_errno) const noex rb.PushEnum(bsd_errno); } -void BSD::OnProxyPacketReceived(const Network::ProxyPacket& packet) { +void BSD_USA::OnProxyPacketReceived(const Network::ProxyPacket& packet) { for (auto& optional_descriptor : file_descriptors) { if (!optional_descriptor.has_value()) { continue; @@ -989,42 +1080,43 @@ void BSD::OnProxyPacketReceived(const Network::ProxyPacket& packet) { } } -BSD::BSD(Core::System& system_, const char* name) - : ServiceFramework{system_, name} { +BSD_USA::BSD_USA(Core::System& system_, const char* name, bool is_user_) + : ServiceFramework{system_, name} + , is_user{is_user_} { // clang-format off static const FunctionInfo functions[] = { - {0, &BSD::RegisterClient, "RegisterClient"}, - {1, &BSD::StartMonitoring, "StartMonitoring"}, - {2, &BSD::Socket, "Socket"}, - {3, nullptr, "SocketExempt"}, + {0, &BSD_USA::RegisterClient, "RegisterClient"}, + {1, &BSD_USA::StartMonitoring, "StartMonitoring"}, + {2, &BSD_USA::Socket, "Socket"}, + {3, &BSD_USA::SocketExempt, "SocketExempt"}, {4, nullptr, "Open"}, - {5, &BSD::Select, "Select"}, - {6, &BSD::Poll, "Poll"}, + {5, &BSD_USA::Select, "Select"}, + {6, &BSD_USA::Poll, "Poll"}, {7, nullptr, "Sysctl"}, - {8, &BSD::Recv, "Recv"}, - {9, &BSD::RecvFrom, "RecvFrom"}, - {10, &BSD::Send, "Send"}, - {11, &BSD::SendTo, "SendTo"}, - {12, &BSD::Accept, "Accept"}, - {13, &BSD::Bind, "Bind"}, - {14, &BSD::Connect, "Connect"}, - {15, &BSD::GetPeerName, "GetPeerName"}, - {16, &BSD::GetSockName, "GetSockName"}, - {17, &BSD::GetSockOpt, "GetSockOpt"}, - {18, &BSD::Listen, "Listen"}, + {8, &BSD_USA::Recv, "Recv"}, + {9, &BSD_USA::RecvFrom, "RecvFrom"}, + {10, &BSD_USA::Send, "Send"}, + {11, &BSD_USA::SendTo, "SendTo"}, + {12, &BSD_USA::Accept, "Accept"}, + {13, &BSD_USA::Bind, "Bind"}, + {14, &BSD_USA::Connect, "Connect"}, + {15, &BSD_USA::GetPeerName, "GetPeerName"}, + {16, &BSD_USA::GetSockName, "GetSockName"}, + {17, &BSD_USA::GetSockOpt, "GetSockOpt"}, + {18, &BSD_USA::Listen, "Listen"}, {19, nullptr, "Ioctl"}, - {20, &BSD::Fcntl, "Fcntl"}, - {21, &BSD::SetSockOpt, "SetSockOpt"}, - {22, &BSD::Shutdown, "Shutdown"}, + {20, &BSD_USA::Fcntl, "Fcntl"}, + {21, &BSD_USA::SetSockOpt, "SetSockOpt"}, + {22, &BSD_USA::Shutdown, "Shutdown"}, {23, nullptr, "ShutdownAllSockets"}, - {24, &BSD::Write, "Write"}, - {25, &BSD::Read, "Read"}, - {26, &BSD::Close, "Close"}, - {27, &BSD::DuplicateSocket, "DuplicateSocket"}, + {24, &BSD_USA::Write, "Write"}, + {25, &BSD_USA::Read, "Read"}, + {26, &BSD_USA::Close, "Close"}, + {27, &BSD_USA::DuplicateSocket, "DuplicateSocket"}, {28, nullptr, "GetResourceStatistics"}, {29, nullptr, "RecvMMsg"}, //3.0.0+ {30, nullptr, "SendMMsg"}, //3.0.0+ - {31, &BSD::EventFd, "EventFd"}, //7.0.0+ + {31, &BSD_USA::EventFd, "EventFd"}, //7.0.0+ {32, nullptr, "RegisterResourceStatisticsName"}, //7.0.0+ {33, nullptr, "RegisterClientShared"}, //10.0.0+ {34, nullptr, "GetSocketStatistics"}, //15.0.0+ @@ -1052,17 +1144,18 @@ BSD::BSD(Core::System& system_, const char* name) } } -BSD::~BSD() { +BSD_USA::~BSD_USA() { if (auto room_member = Network::GetRoomMember().lock()) { room_member->Unbind(proxy_packet_received); } } -std::unique_lock BSD::LockService() noexcept { +std::unique_lock BSD_USA::LockService() noexcept { return {}; } -BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} { +BSDCFG::BSDCFG(Core::System& system_, const char *name) + : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SetIfUp"}, @@ -1089,4 +1182,16 @@ BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} { BSDCFG::~BSDCFG() = default; +BSD_NU::BSD_NU(Core::System& system_) + : ServiceFramework{system_, "bsd:nu"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "CreateUserService"}, + }; + // clang-format on + RegisterHandlers(functions); +} + +BSD_NU::~BSD_NU() = default; + } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index 32b5bdc3cf..5cc3eb5b81 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h @@ -1,5 +1,6 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -8,9 +9,9 @@ #include #include #include +#include #include "common/common_types.h" -#include "common/expected.h" #include "common/socket_types.h" #include "core/hle/service/service.h" #include "core/hle/service/sockets/sockets.h" @@ -27,15 +28,15 @@ class Socket; namespace Service::Sockets { -class BSD final : public ServiceFramework { +class BSD_USA final : public ServiceFramework { public: - explicit BSD(Core::System& system_, const char* name); - ~BSD() override; + explicit BSD_USA(Core::System& system_, const char* name, bool is_user); + ~BSD_USA() override; // These methods are called from SSL; the first two are also called from // this class for the corresponding IPC methods. // On the real device, the SSL service makes IPC calls to this service. - Common::Expected DuplicateSocketImpl(s32 fd); + std::variant DuplicateSocketImpl(s32 fd); Errno CloseImpl(s32 fd); std::optional> GetSocket(s32 fd); @@ -50,7 +51,7 @@ private: }; struct PollWork { - void Execute(BSD* bsd); + void Execute(BSD_USA* bsd); void Response(HLERequestContext& ctx); s32 nfds; @@ -62,7 +63,7 @@ private: }; struct AcceptWork { - void Execute(BSD* bsd); + void Execute(BSD_USA* bsd); void Response(HLERequestContext& ctx); s32 fd; @@ -72,7 +73,7 @@ private: }; struct ConnectWork { - void Execute(BSD* bsd); + void Execute(BSD_USA* bsd); void Response(HLERequestContext& ctx); s32 fd; @@ -81,7 +82,7 @@ private: }; struct RecvWork { - void Execute(BSD* bsd); + void Execute(BSD_USA* bsd); void Response(HLERequestContext& ctx); s32 fd; @@ -92,7 +93,7 @@ private: }; struct RecvFromWork { - void Execute(BSD* bsd); + void Execute(BSD_USA* bsd); void Response(HLERequestContext& ctx); s32 fd; @@ -104,7 +105,7 @@ private: }; struct SendWork { - void Execute(BSD* bsd); + void Execute(BSD_USA* bsd); void Response(HLERequestContext& ctx); s32 fd; @@ -115,7 +116,7 @@ private: }; struct SendToWork { - void Execute(BSD* bsd); + void Execute(BSD_USA* bsd); void Response(HLERequestContext& ctx); s32 fd; @@ -129,6 +130,7 @@ private: void RegisterClient(HLERequestContext& ctx); void StartMonitoring(HLERequestContext& ctx); void Socket(HLERequestContext& ctx); + void SocketExempt(HLERequestContext& ctx); void Select(HLERequestContext& ctx); void Poll(HLERequestContext& ctx); void Accept(HLERequestContext& ctx); @@ -155,8 +157,7 @@ private: void ExecuteWork(HLERequestContext& ctx, Work work); std::pair SocketImpl(Domain domain, Type type, Protocol protocol); - std::pair PollImpl(std::vector& write_buffer, std::span read_buffer, - s32 nfds, s32 timeout); + std::pair PollImpl(std::vector& write_buffer, std::span read_buffer, s32 nfds, s32 timeout); std::pair AcceptImpl(s32 fd, std::vector& write_buffer); Errno BindImpl(s32 fd, std::span addr); Errno ConnectImpl(s32 fd, std::span addr); @@ -189,12 +190,19 @@ private: protected: std::unique_lock LockService() noexcept override; + bool is_user = false; }; class BSDCFG final : public ServiceFramework { public: - explicit BSDCFG(Core::System& system_); + explicit BSDCFG(Core::System& system_, const char *name); ~BSDCFG() override; }; +class BSD_NU final : public ServiceFramework { +public: + explicit BSD_NU(Core::System& system_); + ~BSD_NU() override; +}; + } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index f9f0ee56eb..93c01bf786 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -55,12 +55,45 @@ enum class NetDbError : s32 { static const constexpr std::array blockedDomains = { "srv.nintendo.net", //obvious + "nintendo.es", + "nintendowifi.net", + "nintendo-europe.com", + "nintendo.com.hk", + "nintendo.com.au", + "nintendo.co.kr", + "nintendo.co.uk", + "nintendo.co.jp", + "nintendo.co.nz", + "nintendo.co.za", + "nintendo.com", + "nintendo.jp", + "nintendo.tw", + "nintendo.at", + "nintendo.be", + "nintendo.dk", + "nintendo.de", + "nintendo.fi", + "nintendo.fr", + "nintendo.gr", + "nintendo.hu", + "nintendo.it", + "nintendo.nl", + "nintendo.no", + "nintendo.pt", + "nintendo.ru", + "nintendo.ch", + "nintendo.se", + "nintendoswitch.com.cn", + "nintendoswitch.com", + "sun.hac.lp1.d4c.nintendo.net", "phoenix-api.wbagora.com", //hogwarts legacy "battle.net", - "microsoft.com", //minecraft dungeons + other games + "microsoft.com", // Minecraft dungeons + other games "mojang.com", "xboxlive.com", - "minecraftservices.com" + "api.epicgames.dev", // marvel cosmic invasion +? + "minecraftservices.com", + "508223012e5a5ff19f30a391b2bdadc0.my.2k.com", // Civilization 5 }; static bool IsBlockedHost(const std::string& host) { @@ -175,16 +208,15 @@ static std::pair GetHostByNameRequestImpl(HLERequestConte return {0, GetAddrInfoError::AGAIN}; } - auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt); - if (!res.has_value()) { - return {0, Translate(res.error())}; + auto res_v = Network::GetAddressInfo(host, /*service*/ std::nullopt); + if (auto* res = std::get_if>(&res_v)) { + const std::vector data = SerializeAddrInfoAsHostEnt(*res, host); + const u32 data_size = u32(data.size()); + ctx.WriteBuffer(data, 0); + return {data_size, GetAddrInfoError::SUCCESS}; } - - const std::vector data = SerializeAddrInfoAsHostEnt(res.value(), host); - const u32 data_size = static_cast(data.size()); - ctx.WriteBuffer(data, 0); - - return {data_size, GetAddrInfoError::SUCCESS}; + auto* err = std::get_if(&res_v); + return {0, Translate(*err)}; } void SFDNSRES::GetHostByNameRequest(HLERequestContext& ctx) { @@ -238,7 +270,7 @@ static std::vector SerializeAddrInfo(const std::vector& v Append(data, static_cast(Translate(addrinfo.family))); // ai_family Append(data, static_cast(Translate(addrinfo.socket_type))); // ai_socktype Append(data, static_cast(Translate(addrinfo.protocol))); // ai_protocol - Append(data, sizeof(SockAddrIn)); // ai_addrlen + Append(data, 16); // ai_addrlen // ^ *not* sizeof(SerializedSockAddrIn), not that it matters since they're the same size // ai_addr: @@ -300,16 +332,15 @@ static std::pair GetAddrInfoRequestImpl(HLERequestContext // Serialized hints are also passed in a buffer, but are ignored for now. - auto res = Network::GetAddressInfo(host, service); - if (!res.has_value()) { - return {0, Translate(res.error())}; + auto res_v = Network::GetAddressInfo(host, service); + if (auto* res = std::get_if>(&res_v)) { + const std::vector data = SerializeAddrInfo(*res, host); + const u32 data_size = u32(data.size()); + ctx.WriteBuffer(data, 0); + return {data_size, GetAddrInfoError::SUCCESS}; } - - const std::vector data = SerializeAddrInfo(res.value(), host); - const u32 data_size = static_cast(data.size()); - ctx.WriteBuffer(data, 0); - - return {data_size, GetAddrInfoError::SUCCESS}; + auto* err = std::get_if(&res_v); + return {0, Translate(*err)}; } void SFDNSRES::GetAddrInfoRequest(HLERequestContext& ctx) { @@ -375,4 +406,21 @@ void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) { rb.Push(ResultSuccess); rb.Push(0); // bsd errno } + + +DNS_PRIV::DNS_PRIV(Core::System& system_) + : ServiceFramework{system_, "dns:priv"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "Cmd0"}, + {1, nullptr, "Cmd1"}, + {2, nullptr, "Cmd2"}, + }; + // clang-format on + RegisterHandlers(functions); +} + +DNS_PRIV::~DNS_PRIV() = default; + + } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sfdnsres.h b/src/core/hle/service/sockets/sfdnsres.h index 282ef9071f..4302edc43e 100644 --- a/src/core/hle/service/sockets/sfdnsres.h +++ b/src/core/hle/service/sockets/sfdnsres.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -25,4 +28,10 @@ private: void ResolverSetOptionRequest(HLERequestContext& ctx); }; +class DNS_PRIV final : public ServiceFramework { +public: + explicit DNS_PRIV(Core::System& system_); + ~DNS_PRIV() override; +}; + } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sockets.cpp b/src/core/hle/service/sockets/sockets.cpp index 676d24e036..e96d7c5638 100644 --- a/src/core/hle/service/sockets/sockets.cpp +++ b/src/core/hle/service/sockets/sockets.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -9,15 +12,72 @@ namespace Service::Sockets { +class ETHC_C final : public ServiceFramework { +public: + explicit ETHC_C(Core::System& system_) + : ServiceFramework{system_, "ethc:c"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "Initialize"}, + {1, nullptr, "Cancel"}, + {2, nullptr, "GetResult"}, + {3, nullptr, "GetMediaList"}, + {4, nullptr, "SetMediaType"}, + {5, nullptr, "GetMediaType"}, + {6, nullptr, "GetMacAddress"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class ETHC_I final : public ServiceFramework { +public: + explicit ETHC_I(Core::System& system_) + : ServiceFramework{system_, "ethc:i"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetReadableHandle"}, + {1, nullptr, "Cancel"}, + {2, nullptr, "GetResult"}, + {3, nullptr, "GetInterfaceList"}, + {4, nullptr, "GetInterfaceCount"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class ISfDriverServiceCreator final : public ServiceFramework { +public: + explicit ISfDriverServiceCreator(Core::System& system_) + : ServiceFramework{system_, "eth:nd"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "CreateDriverService"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); - server_manager->RegisterNamedService("bsd:s", std::make_shared(system, "bsd:s")); - server_manager->RegisterNamedService("bsd:u", std::make_shared(system, "bsd:u")); - server_manager->RegisterNamedService("bsdcfg", std::make_shared(system)); + server_manager->RegisterNamedService("ethc:c", std::make_shared(system), 5); + server_manager->RegisterNamedService("ethc:i", std::make_shared(system), 5); + server_manager->RegisterNamedService("bsd:s", std::make_shared(system, "bsd:s", false), 0x7E); + server_manager->RegisterNamedService("bsd:u", std::make_shared(system, "bsd:u", true), 0x0f); + server_manager->RegisterNamedService("bsd:a", std::make_shared(system, "bsd:a", true), 0x17); + server_manager->RegisterNamedService("bsd:nu", std::make_shared(system), 4); + server_manager->RegisterNamedService("bsdcfg", std::make_shared(system, "bsdcfg")); + server_manager->RegisterNamedService("ifcfg", std::make_shared(system, "ifcfg")); server_manager->RegisterNamedService("nsd:a", std::make_shared(system, "nsd:a")); server_manager->RegisterNamedService("nsd:u", std::make_shared(system, "nsd:u")); - server_manager->RegisterNamedService("sfdnsres", std::make_shared(system)); + server_manager->RegisterNamedService("sfdnsres", std::make_shared(system), 30); + server_manager->RegisterNamedService("dns:priv", std::make_shared(system)); + server_manager->RegisterNamedService("eth:nd", std::make_shared(system)); + server_manager->StartAdditionalHostThreads("bsdsocket", 2); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index b72bf53c16..2837ce71ea 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h @@ -65,10 +65,121 @@ enum class Type : u32 { }; enum class Protocol : u32 { - Unspecified = 0, + IP = 0, ICMP = 1, TCP = 6, UDP = 17, + // + IPV6 = 41, + RAW = 255, + // + HOPOPTS = 0, + IGMP = 2, + GGP = 3, + IPV4 = 4, + ST = 7, + EGP = 8, + PIGP = 9, + RCCMON = 10, + NVPII = 11, + PUP = 12, + ARGUS = 13, + EMCON = 14, + XNET = 15, + CHAOS = 16, + MUX = 18, + MEAS = 19, + HMP = 20, + PRM = 21, + IDP = 22, + TRUNK1 = 23, + TRUNK2 = 24, + LEAF1 = 25, + LEAF2 = 26, + RDP = 27, + IRTP = 28, + TP = 29, + BLT = 30, + NSP = 31, + INP = 32, + DCCP = 33, + //3PC = 34, + IDPR = 35, + XTP = 36, + DDP = 37, + CMTP = 38, + TPXX = 39, + IL = 40, + SDRP = 42, + ROUTING = 43, + FRAGMENT = 44, + IDRP = 45, + RSVP = 46, + GRE = 47, + MHRP = 48, + BHA = 49, + ESP = 50, + AH = 51, + INLSP = 52, + SWIPE = 53, + NHRP = 54, + MOBILE = 55, + TLSP = 56, + SKIP = 57, + ICMPV6 = 58, + NONE = 59, + DSTOPTS = 60, + AHIP = 61, + CFTP = 62, + HELLO = 63, + SATEXPAK = 64, + KRYPTOLAN = 65, + RVD = 66, + IPPC = 67, + ADFS = 68, + SATMON = 69, + VISA = 70, + IPCV = 71, + CPNX = 72, + CPHB = 73, + WSN = 74, + PVP = 75, + BRSATMON = 76, + ND = 77, + WBMON = 78, + WBEXPAK = 79, + EON = 80, + VMTP = 81, + SVMTP = 82, + VINES = 83, + TTP = 84, + IGP = 85, + DGP = 86, + TCF = 87, + IGRP = 88, + OSPFIGP = 89, + SRPC = 90, + LARP = 91, + MTP = 92, + AX25 = 93, + IPEIP = 94, + MICP = 95, + SCCSP = 96, + ETHERIP = 97, + ENCAP = 98, + APES = 99, + GMTP = 100, + IPCOMP = 108, + SCTP = 132, + MH = 135, + UDPLITE = 136, + HIP = 139, + SHIM6 = 140, + PIM = 103, + CARP = 112, + PGM = 113, + MPLS = 137, + PFSYNC = 240, }; enum class SocketLevel : u32 { @@ -110,8 +221,9 @@ struct SockAddrIn { u8 family; u16 portno; std::array ip; - std::array zeroes; + std::array zeroes; }; +static_assert(sizeof(SockAddrIn) == 0x100); enum class PollEvents : u16 { // Using Pascal case because IN is a macro on Windows. diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp index ea12977f62..46ef0a89cb 100644 --- a/src/core/hle/service/sockets/sockets_translate.cpp +++ b/src/core/hle/service/sockets/sockets_translate.cpp @@ -175,49 +175,151 @@ Network::Type Translate(Type type) { Type Translate(Network::Type type) { switch (type) { - case Network::Type::Unspecified: - return Type::Unspecified; - case Network::Type::STREAM: - return Type::STREAM; - case Network::Type::DGRAM: - return Type::DGRAM; - case Network::Type::RAW: - return Type::RAW; - case Network::Type::SEQPACKET: - return Type::SEQPACKET; + case Network::Type::Unspecified: return Type::Unspecified; + case Network::Type::STREAM: return Type::STREAM; + case Network::Type::DGRAM: return Type::DGRAM; + case Network::Type::RAW: return Type::RAW; + case Network::Type::SEQPACKET: return Type::SEQPACKET; default: UNIMPLEMENTED_MSG("Unimplemented type={}", type); return Type{}; } } -Network::Protocol Translate(Protocol protocol) { +#define NETWORK_PROTOCOL_TRANSLATE_LIST \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ICMP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TCP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(UDP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IPV6) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(RAW) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IGMP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(GGP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IPV4) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ST) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(EGP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PIGP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(RCCMON) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(NVPII) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PUP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ARGUS) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(EMCON) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(XNET) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(CHAOS) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(MUX) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(MEAS) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(HMP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PRM) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IDP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TRUNK1) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TRUNK2) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(LEAF1) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(LEAF2) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(RDP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IRTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(BLT) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(NSP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(INP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(DCCP) \ + /*NETWORK_PROTOCOL_TRANSLATE_ELEM(3PC)*/ \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IDPR) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(XTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(DDP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(CMTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TPXX) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IL) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SDRP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ROUTING) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(FRAGMENT) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IDRP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(RSVP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(GRE) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(MHRP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(BHA) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ESP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(AH) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(INLSP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SWIPE) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(NHRP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(MOBILE) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TLSP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SKIP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ICMPV6) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(NONE) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(DSTOPTS) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(AHIP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(CFTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(HELLO) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SATEXPAK) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(KRYPTOLAN) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(RVD) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IPPC) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ADFS) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SATMON) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(VISA) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IPCV) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(CPNX) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(CPHB) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(WSN) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PVP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(BRSATMON) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ND) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(WBMON) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(WBEXPAK) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(EON) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(VMTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SVMTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(VINES) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IGP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(DGP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TCF) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IGRP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(OSPFIGP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SRPC) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(LARP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(MTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(AX25) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IPEIP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(MICP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SCCSP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ETHERIP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ENCAP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(APES) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(GMTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IPCOMP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SCTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(MH) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(UDPLITE) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(HIP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SHIM6) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PIM) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(CARP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PGM) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(MPLS) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PFSYNC) +[[nodiscard]] Network::Protocol Translate(Protocol protocol) { switch (protocol) { - case Protocol::Unspecified: - return Network::Protocol::Unspecified; - case Protocol::TCP: - return Network::Protocol::TCP; - case Protocol::UDP: - return Network::Protocol::UDP; +#define NETWORK_PROTOCOL_TRANSLATE_ELEM(name) case Protocol::name: return Network::Protocol::name; + NETWORK_PROTOCOL_TRANSLATE_LIST +#undef NETWORK_PROTOCOL_TRANSLATE_ELEM default: UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol); - return Network::Protocol::Unspecified; + return {}; } } - -Protocol Translate(Network::Protocol protocol) { +[[nodiscard]] Protocol Translate(Network::Protocol protocol) { switch (protocol) { - case Network::Protocol::Unspecified: - return Protocol::Unspecified; - case Network::Protocol::TCP: - return Protocol::TCP; - case Network::Protocol::UDP: - return Protocol::UDP; +#define NETWORK_PROTOCOL_TRANSLATE_ELEM(name) case Network::Protocol::name: return Protocol::name; + NETWORK_PROTOCOL_TRANSLATE_LIST +#undef NETWORK_PROTOCOL_TRANSLATE_ELEM default: UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol); - return Protocol::Unspecified; + return {}; } } +#undef NETWORK_PROTOCOL_TRANSLATE_LIST Network::PollEvents Translate(PollEvents flags) { Network::PollEvents result{}; @@ -265,13 +367,9 @@ PollEvents Translate(Network::PollEvents flags) { } Network::SockAddrIn Translate(SockAddrIn value) { - if (value.len != 0 && value.len != sizeof(value) && value.len != 6) { - LOG_WARNING(Service, "Unexpected SockAddrIn len={}, expected 0, {}, or 6", - value.len, sizeof(value)); - } - + // All lengths are valid, from [0 upto 256] return { - .family = Translate(static_cast(value.family)), + .family = Translate(Domain(value.family)), .ip = value.ip, .portno = static_cast(value.portno >> 8 | value.portno << 8), }; @@ -279,7 +377,7 @@ Network::SockAddrIn Translate(SockAddrIn value) { SockAddrIn Translate(Network::SockAddrIn value) { return { - .len = sizeof(SockAddrIn), + .len = 16, .family = static_cast(Translate(value.family)), .portno = static_cast(value.portno >> 8 | value.portno << 8), .ip = value.ip, diff --git a/src/core/hle/service/sockets/sockets_translate.h b/src/core/hle/service/sockets/sockets_translate.h index bd6721fd3e..b1dfc71961 100644 --- a/src/core/hle/service/sockets/sockets_translate.h +++ b/src/core/hle/service/sockets/sockets_translate.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/spl/csrng.cpp b/src/core/hle/service/spl/csrng.cpp deleted file mode 100644 index ca121fb054..0000000000 --- a/src/core/hle/service/spl/csrng.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "core/hle/service/spl/csrng.h" - -namespace Service::SPL { - -CSRNG::CSRNG(Core::System& system_, std::shared_ptr module_) - : Interface(system_, std::move(module_), "csrng") { - static const FunctionInfo functions[] = { - {0, &CSRNG::GenerateRandomBytes, "GenerateRandomBytes"}, - }; - RegisterHandlers(functions); -} - -CSRNG::~CSRNG() = default; - -} // namespace Service::SPL diff --git a/src/core/hle/service/spl/csrng.h b/src/core/hle/service/spl/csrng.h deleted file mode 100644 index b337a82816..0000000000 --- a/src/core/hle/service/spl/csrng.h +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "core/hle/service/spl/spl_module.h" - -namespace Core { -class System; -} - -namespace Service::SPL { - -class CSRNG final : public Module::Interface { -public: - explicit CSRNG(Core::System& system_, std::shared_ptr module_); - ~CSRNG() override; -}; - -} // namespace Service::SPL diff --git a/src/core/hle/service/spl/spl.cpp b/src/core/hle/service/spl/spl.cpp index fde212186f..81be99c360 100644 --- a/src/core/hle/service/spl/spl.cpp +++ b/src/core/hle/service/spl/spl.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -22,15 +25,17 @@ SPL::SPL(Core::System& system_, std::shared_ptr module_) RegisterHandlers(functions); } +SPL::~SPL() = default; + SPL_MIG::SPL_MIG(Core::System& system_, std::shared_ptr module_) : Interface(system_, std::move(module_), "spl:mig") { // clang-format off static const FunctionInfo functions[] = { {0, &SPL::GetConfig, "GetConfig"}, {1, &SPL::ModularExponentiate, "ModularExponentiate"}, - {2, nullptr, "GenerateAesKek"}, + {2, &SPL::GenerateAesKek, "GenerateAesKek"}, {3, nullptr, "LoadAesKey"}, - {4, nullptr, "GenerateAesKey"}, + {4, &SPL::GenerateAesKey, "GenerateAesKey"}, {5, &SPL::SetConfig, "SetConfig"}, {7, &SPL::GenerateRandomBytes, "GenerateRandomBytes"}, {11, &SPL::IsDevelopment, "IsDevelopment"}, @@ -48,6 +53,8 @@ SPL_MIG::SPL_MIG(Core::System& system_, std::shared_ptr module_) RegisterHandlers(functions); } +SPL_MIG::~SPL_MIG() = default; + SPL_FS::SPL_FS(Core::System& system_, std::shared_ptr module_) : Interface(system_, std::move(module_), "spl:fs") { // clang-format off @@ -79,6 +86,8 @@ SPL_FS::SPL_FS(Core::System& system_, std::shared_ptr module_) RegisterHandlers(functions); } +SPL_FS::~SPL_FS() = default; + SPL_SSL::SPL_SSL(Core::System& system_, std::shared_ptr module_) : Interface(system_, std::move(module_), "spl:ssl") { // clang-format off @@ -108,6 +117,8 @@ SPL_SSL::SPL_SSL(Core::System& system_, std::shared_ptr module_) RegisterHandlers(functions); } +SPL_SSL::~SPL_SSL() = default; + SPL_ES::SPL_ES(Core::System& system_, std::shared_ptr module_) : Interface(system_, std::move(module_), "spl:es") { // clang-format off @@ -142,6 +153,8 @@ SPL_ES::SPL_ES(Core::System& system_, std::shared_ptr module_) RegisterHandlers(functions); } +SPL_ES::~SPL_ES() = default; + SPL_MANU::SPL_MANU(Core::System& system_, std::shared_ptr module_) : Interface(system_, std::move(module_), "spl:manu") { // clang-format off @@ -170,16 +183,6 @@ SPL_MANU::SPL_MANU(Core::System& system_, std::shared_ptr module_) RegisterHandlers(functions); } -SPL::~SPL() = default; - -SPL_MIG::~SPL_MIG() = default; - -SPL_FS::~SPL_FS() = default; - -SPL_SSL::~SPL_SSL() = default; - -SPL_ES::~SPL_ES() = default; - SPL_MANU::~SPL_MANU() = default; } // namespace Service::SPL diff --git a/src/core/hle/service/spl/spl.h b/src/core/hle/service/spl/spl.h index 7b63d8b1a9..83a3cda539 100644 --- a/src/core/hle/service/spl/spl.h +++ b/src/core/hle/service/spl/spl.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/spl/spl_module.cpp b/src/core/hle/service/spl/spl_module.cpp index 17c9f8a887..520cb1860b 100644 --- a/src/core/hle/service/spl/spl_module.cpp +++ b/src/core/hle/service/spl/spl_module.cpp @@ -13,7 +13,6 @@ #include "core/hle/api_version.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/server_manager.h" -#include "core/hle/service/spl/csrng.h" #include "core/hle/service/spl/spl.h" #include "core/hle/service/spl/spl_module.h" @@ -59,6 +58,36 @@ void Module::Interface::ModularExponentiate(HLERequestContext& ctx) { rb.Push(ResultSecureMonitorNotImplemented); } +void Module::Interface::GenerateAesKek(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + [[maybe_unused]] const auto key_source = rp.PopRaw(); + const auto generation = rp.Pop(); + const auto option = rp.Pop(); + + LOG_WARNING(Service_SPL, "(STUBBED) called, generation={:#x}, option={:#x}", generation, + option); + + AccessKey access_key{}; + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.PushRaw(access_key); +} + +void Module::Interface::GenerateAesKey(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + [[maybe_unused]] const auto access_key = rp.PopRaw(); + [[maybe_unused]] const auto key_source = rp.PopRaw(); + + LOG_WARNING(Service_SPL, "(STUBBED) called"); + + AesKey aes_key{}; + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.PushRaw(aes_key); +} + void Module::Interface::SetConfig(HLERequestContext& ctx) { UNIMPLEMENTED_MSG("SetConfig is not implemented!"); @@ -173,6 +202,18 @@ Result Module::Interface::GetConfigImpl(u64* out_config, ConfigItem config_item) } } +class CSRNG final : public Module::Interface { +public: + explicit CSRNG(Core::System& system_, std::shared_ptr module_) + : Interface(system_, std::move(module_), "csrng") { + static const FunctionInfo functions[] = { + {0, &CSRNG::GenerateRandomBytes, "GenerateRandomBytes"}, + }; + RegisterHandlers(functions); + } + ~CSRNG() override = default; +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); auto module = std::make_shared(); diff --git a/src/core/hle/service/spl/spl_module.h b/src/core/hle/service/spl/spl_module.h index 06dcffa6c4..6cf2b49f39 100644 --- a/src/core/hle/service/spl/spl_module.h +++ b/src/core/hle/service/spl/spl_module.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -25,6 +28,8 @@ public: // General void GetConfig(HLERequestContext& ctx); void ModularExponentiate(HLERequestContext& ctx); + void GenerateAesKek(HLERequestContext& ctx); + void GenerateAesKey(HLERequestContext& ctx); void SetConfig(HLERequestContext& ctx); void GenerateRandomBytes(HLERequestContext& ctx); void IsDevelopment(HLERequestContext& ctx); diff --git a/src/core/hle/service/spl/spl_results.h b/src/core/hle/service/spl/spl_results.h index dd7ba11f34..8c09be49ab 100644 --- a/src/core/hle/service/spl/spl_results.h +++ b/src/core/hle/service/spl/spl_results.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/spl/spl_types.h b/src/core/hle/service/spl/spl_types.h index 91f9cc0321..d7f0c4afbb 100644 --- a/src/core/hle/service/spl/spl_types.h +++ b/src/core/hle/service/spl/spl_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later diff --git a/src/core/hle/service/ssl/cert_store.cpp b/src/core/hle/service/ssl/cert_store.cpp index b321e5d32b..ee5bf8306b 100644 --- a/src/core/hle/service/ssl/cert_store.cpp +++ b/src/core/hle/service/ssl/cert_store.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ssl/cert_store.h b/src/core/hle/service/ssl/cert_store.h index 613d7b02af..4bbb3c9c1a 100644 --- a/src/core/hle/service/ssl/cert_store.h +++ b/src/core/hle/service/ssl/cert_store.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index b66c686b2a..999efd4295 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -48,6 +48,12 @@ enum class OptionType : u32 { EnableAlpn = 3, }; +// This is nn::ssl::sf::RenegotiationMode +enum RenegotiationMode : u32 { + None = 0, ///< None + Secure = 1, ///< Secure +}; + // This is nn::ssl::sf::SslVersion struct SslVersion { union { @@ -75,34 +81,34 @@ public: shared_data{shared_data_in}, backend{std::move(backend_in)} { // clang-format off static const FunctionInfo functions[] = { - {0, &ISslConnection::SetSocketDescriptor, "SetSocketDescriptor"}, - {1, &ISslConnection::SetHostName, "SetHostName"}, - {2, &ISslConnection::SetVerifyOption, "SetVerifyOption"}, - {3, &ISslConnection::SetIoMode, "SetIoMode"}, - {4, nullptr, "GetSocketDescriptor"}, - {5, nullptr, "GetHostName"}, + {0, D<&ISslConnection::SetSocketDescriptor>, "SetSocketDescriptor"}, + {1, D<&ISslConnection::SetHostName>, "SetHostName"}, + {2, D<&ISslConnection::SetVerifyOption>, "SetVerifyOption"}, + {3, D<&ISslConnection::SetIoMode>, "SetIoMode"}, + {4, D<&ISslConnection::GetSocketDescriptor>, "GetSocketDescriptor"}, + {5, D<&ISslConnection::GetHostName>, "GetHostName"}, {6, nullptr, "GetVerifyOption"}, - {7, nullptr, "GetIoMode"}, - {8, &ISslConnection::DoHandshake, "DoHandshake"}, + {7, D<&ISslConnection::GetIoMode>, "GetIoMode"}, + {8, D<&ISslConnection::DoHandshake>, "DoHandshake"}, {9, &ISslConnection::DoHandshakeGetServerCert, "DoHandshakeGetServerCert"}, - {10, &ISslConnection::Read, "Read"}, - {11, &ISslConnection::Write, "Write"}, - {12, &ISslConnection::Pending, "Pending"}, - {13, nullptr, "Peek"}, - {14, nullptr, "Poll"}, - {15, nullptr, "GetVerifyCertError"}, - {16, nullptr, "GetNeededServerCertBufferSize"}, - {17, &ISslConnection::SetSessionCacheMode, "SetSessionCacheMode"}, - {18, nullptr, "GetSessionCacheMode"}, - {19, nullptr, "FlushSessionCache"}, - {20, nullptr, "SetRenegotiationMode"}, - {21, nullptr, "GetRenegotiationMode"}, - {22, &ISslConnection::SetOption, "SetOption"}, - {23, &ISslConnection::GetOption, "GetOption"}, + {10, D<&ISslConnection::Read>, "Read"}, + {11, D<&ISslConnection::Write>, "Write"}, + {12, D<&ISslConnection::Pending>, "Pending"}, + {13, D<&ISslConnection::Peek>, "Peek"}, + {14, D<&ISslConnection::Poll>, "Poll"}, + {15, D<&ISslConnection::GetVerifyCertError>, "GetVerifyCertError"}, + {16, D<&ISslConnection::GetNeededServerCertBufferSize>, "GetNeededServerCertBufferSize"}, + {17, D<&ISslConnection::SetSessionCacheMode>, "SetSessionCacheMode"}, + {18, D<&ISslConnection::GetSessionCacheMode>, "GetSessionCacheMode"}, + {19, D<&ISslConnection::FlushSessionCache>, "FlushSessionCache"}, + {20, D<&ISslConnection::SetRenegotiationMode>, "SetRenegotiationMode"}, + {21, D<&ISslConnection::GetRenegotiationMode>, "GetRenegotiationMode"}, + {22, D<&ISslConnection::SetOption>, "SetOption"}, + {23, D<&ISslConnection::GetOption>, "GetOption"}, {24, nullptr, "GetVerifyCertErrors"}, {25, nullptr, "GetCipherInfo"}, - {26, &ISslConnection::SetNextAlpnProto, "SetNextAlpnProto"}, - {27, &ISslConnection::GetNextAlpnProto, "GetNextAlpnProto"}, + {26, D<&ISslConnection::SetNextAlpnProto>, "SetNextAlpnProto"}, + {27, D<&ISslConnection::GetNextAlpnProto>, "GetNextAlpnProto"}, {28, nullptr, "SetDtlsSocketDescriptor"}, {29, nullptr, "GetDtlsHandshakeTimeout"}, {30, nullptr, "SetPrivateOption"}, @@ -129,7 +135,7 @@ public: LOG_ERROR(Service_SSL, "do_not_close_socket was changed after setting socket; is this right?"); } else { - auto bsd = system.ServiceManager().GetService("bsd:u"); + auto bsd = system.ServiceManager().GetService("bsd:u"); if (bsd) { auto err = bsd->CloseImpl(fd); if (err != Service::Sockets::Errno::SUCCESS) { @@ -141,83 +147,6 @@ public: } private: - SslVersion ssl_version; - std::shared_ptr shared_data; - std::unique_ptr backend; - std::optional fd_to_close; - bool do_not_close_socket = false; - bool get_server_cert_chain = false; - bool skip_default_verify = false; - bool enable_alpn = false; - std::shared_ptr socket; - std::vector next_alpn_proto; - bool did_handshake = false; - u32 verify_option = 0; - - Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) { - LOG_DEBUG(Service_SSL, "called, fd={}", fd); - ASSERT(!did_handshake); - auto bsd = system.ServiceManager().GetService("bsd:u"); - ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; }); - - auto res = bsd->DuplicateSocketImpl(fd); - if (!res.has_value()) { - LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", fd); - return ResultInvalidSocket; - } - - const s32 duplicated_fd = *res; - - if (do_not_close_socket) { - *out_fd = duplicated_fd; - } else { - *out_fd = -1; - fd_to_close = duplicated_fd; - } - - std::optional> sock = bsd->GetSocket(duplicated_fd); - if (!sock.has_value()) { - LOG_ERROR(Service_SSL, "invalid socket fd {} after duplication", duplicated_fd); - return ResultInvalidSocket; - } - socket = std::move(*sock); - backend->SetSocket(socket); - return ResultSuccess; - } - - Result SetHostNameImpl(const std::string& hostname) { - LOG_DEBUG(Service_SSL, "called. hostname={}", hostname); - ASSERT(!did_handshake); - return backend->SetHostName(hostname); - } - - Result SetVerifyOptionImpl(u32 option) { - ASSERT(!did_handshake); - LOG_DEBUG(Service_SSL, "called. option={} (forcing 0)", option); - verify_option = 0; - backend->SetVerifyOption(0); - return ResultSuccess; - } - - Result SetIoModeImpl(u32 input_mode) { - auto mode = static_cast(input_mode); - ASSERT(mode == IoMode::Blocking || mode == IoMode::NonBlocking); - ASSERT_OR_EXECUTE(socket, { return ResultNoSocket; }); - - const bool non_block = mode == IoMode::NonBlocking; - const Network::Errno error = socket->SetNonBlock(non_block); - if (error != Network::Errno::SUCCESS) { - LOG_ERROR(Service_SSL, "Failed to set native socket non-block flag to {}", non_block); - } - return ResultSuccess; - } - - Result SetSessionCacheModeImpl(u32 mode) { - ASSERT(!did_handshake); - LOG_WARNING(Service_SSL, "(STUBBED) called. value={}", mode); - return ResultSuccess; - } - Result DoHandshakeImpl() { ASSERT_OR_EXECUTE(!did_handshake && socket, { return ResultNoSocket; }); Result res = backend->DoHandshake(); @@ -237,19 +166,17 @@ private: }; if (!get_server_cert_chain) { // Just return the first one, unencoded. - ASSERT_OR_EXECUTE_MSG( - !certs.empty(), { return {}; }, "Should be at least one server cert"); + ASSERT_OR_EXECUTE_MSG(!certs.empty(), { return {}; }, "Should be at least one server cert"); return certs[0]; } std::vector ret; - Header header{0x4E4D684374726543, static_cast(certs.size()), 0}; + Header header{0x4E4D684374726543, u32(certs.size()), 0}; ret.insert(ret.end(), reinterpret_cast(&header), reinterpret_cast(&header + 1)); size_t data_offset = sizeof(Header) + certs.size() * sizeof(EntryHeader); for (auto& cert : certs) { - EntryHeader entry_header{static_cast(cert.size()), static_cast(data_offset)}; + EntryHeader entry_header{u32(cert.size()), u32(data_offset)}; data_offset += cert.size(); - ret.insert(ret.end(), reinterpret_cast(&entry_header), - reinterpret_cast(&entry_header + 1)); + ret.insert(ret.end(), reinterpret_cast(&entry_header), reinterpret_cast(&entry_header + 1)); } for (auto& cert : certs) { ret.insert(ret.end(), cert.begin(), cert.end()); @@ -257,65 +184,77 @@ private: return ret; } - Result ReadImpl(std::vector* out_data) { - ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; }); - size_t actual_size{}; - Result res = backend->Read(&actual_size, *out_data); - if (res != ResultSuccess) { - return res; + Result SetSocketDescriptor(s32 in_fd, Out out_fd) { + LOG_DEBUG(Service_SSL, "called, fd={}", in_fd); + ASSERT(!did_handshake); + auto bsd = system.ServiceManager().GetService("bsd:u"); + ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; }); + + auto const res_v = bsd->DuplicateSocketImpl(in_fd); + if (auto *res = std::get_if(&res_v)) { + const s32 dup_fd = *res; + *out_fd = do_not_close_socket ? dup_fd : -1; + if (!do_not_close_socket) + fd_to_close = dup_fd; + auto const sock = bsd->GetSocket(dup_fd); + if (!sock.has_value()) { + LOG_ERROR(Service_SSL, "invalid socket fd {} after duplication", dup_fd); + return ResultInvalidSocket; + } + socket = std::move(*sock); + backend->SetSocket(std::move(socket)); + return ResultSuccess; } - out_data->resize(actual_size); - return res; + LOG_ERROR(Service_SSL, "Failed to duplicate socket with fd {}", in_fd); + return ResultInvalidSocket; } - Result WriteImpl(size_t* out_size, std::span data) { - ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; }); - return backend->Write(out_size, data); + Result SetHostName(InBuffer buf) { + auto const hostname = Common::StringFromBuffer(buf); + LOG_DEBUG(Service_SSL, "called. hostname={}", hostname); + ASSERT(!did_handshake); + return backend->SetHostName(hostname.c_str()); } - Result PendingImpl(s32* out_pending) { - LOG_WARNING(Service_SSL, "(STUBBED) called."); - *out_pending = 0; - return ResultSuccess; + Result SetVerifyOption(u32 option) { + LOG_DEBUG(Service_SSL, "called. option={} (forcing 0)", option); + ASSERT(!did_handshake); + verify_option = 0; + backend->SetVerifyOption(0); + R_SUCCEED(); } - void SetSocketDescriptor(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const s32 in_fd = rp.Pop(); - s32 out_fd{-1}; - const Result res = SetSocketDescriptorImpl(&out_fd, in_fd); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(res); - rb.Push(out_fd); + Result SetIoMode(u32 input_mode) { + auto mode = IoMode(input_mode); + ASSERT(mode == IoMode::Blocking || mode == IoMode::NonBlocking); + R_UNLESS(socket, ResultNoSocket); + const bool non_block = mode == IoMode::NonBlocking; + const Network::Errno error = socket->SetNonBlock(non_block); + if (error != Network::Errno::SUCCESS) { + LOG_ERROR(Service_SSL, "Failed to set native socket non-block flag to {}", non_block); + } + R_SUCCEED(); } - void SetHostName(HLERequestContext& ctx) { - const std::string hostname = Common::StringFromBuffer(ctx.ReadBuffer()); - const Result res = SetHostNameImpl(hostname); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res); + Result GetSocketDescriptor(Out out_fd) { + LOG_WARNING(Service_SSL, "(STUBBED)"); + *out_fd = uint32_t(socket->GetFD()); + R_SUCCEED(); } - void SetVerifyOption(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const u32 option = rp.Pop(); - const Result res = SetVerifyOptionImpl(option); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res); + Result GetHostName(OutBuffer data, Out out_size) { + LOG_WARNING(Service_SSL, "(STUBBED)"); + ASSERT(!did_handshake); + return backend->GetHostName(std::span{data.begin(), data.end()}, out_size); } - void SetIoMode(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const u32 mode = rp.Pop(); - const Result res = SetIoModeImpl(mode); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res); + Result GetIoMode(Out out_mode) { + LOG_WARNING(Service_SSL, "(STUBBED)"); + R_SUCCEED(); } - void DoHandshake(HLERequestContext& ctx) { - const Result res = DoHandshakeImpl(); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res); + Result DoHandshake() { + return DoHandshakeImpl(); } void DoHandshakeGetServerCert(HLERequestContext& ctx) { @@ -354,131 +293,158 @@ private: rb.PushRaw(out); } - void Read(HLERequestContext& ctx) { - std::vector output_bytes(ctx.GetWriteBufferSize()); - const Result res = ReadImpl(&output_bytes); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(res); - if (res == ResultSuccess) { - rb.Push(static_cast(output_bytes.size())); - ctx.WriteBuffer(output_bytes); - } else { - rb.Push(static_cast(0)); - } + Result Read(OutBuffer data, Out out_size) { + R_UNLESS(did_handshake, ResultInternalError); + size_t tmp{}; + auto const res = backend->Read(&tmp, data); + *out_size = u32(tmp); + return res; } - void Write(HLERequestContext& ctx) { - size_t write_size{0}; - const Result res = WriteImpl(&write_size, ctx.ReadBuffer()); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(res); - rb.Push(static_cast(write_size)); + Result Write(InBuffer data, Out out_size) { + R_UNLESS(did_handshake, ResultInternalError); + size_t tmp{}; + auto const res = backend->Write(&tmp, data); + *out_size = u32(tmp); + return res; } - void Pending(HLERequestContext& ctx) { - s32 pending_size{0}; - const Result res = PendingImpl(&pending_size); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(res); - rb.Push(pending_size); + Result Pending(Out out_pending_size) { + LOG_WARNING(Service_SSL, "(STUBBED)"); + *out_pending_size = s32(backend->Pending()); + R_SUCCEED(); } - void SetSessionCacheMode(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const u32 mode = rp.Pop(); - const Result res = SetSessionCacheModeImpl(mode); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res); + Result Peek(OutBuffer data, Out out_size) { + LOG_WARNING(Service_SSL, "(STUBBED)"); + size_t tmp{}; + auto const res = backend->Peek(&tmp, data); + *out_size = u32(tmp); + return res; } - void SetOption(HLERequestContext& ctx) { - struct Parameters { - OptionType option; - s32 value; - }; - static_assert(sizeof(Parameters) == 0x8, "Parameters is an invalid size"); - - IPC::RequestParser rp{ctx}; - const auto parameters = rp.PopRaw(); - - switch (parameters.option) { - case OptionType::DoNotCloseSocket: - do_not_close_socket = static_cast(parameters.value); - break; - case OptionType::GetServerCertChain: - get_server_cert_chain = static_cast(parameters.value); - break; - case OptionType::SkipDefaultVerify: - skip_default_verify = static_cast(parameters.value); - break; - case OptionType::EnableAlpn: - enable_alpn = static_cast(parameters.value); - break; - default: - LOG_WARNING(Service_SSL, "Unknown option={}, value={}", parameters.option, - parameters.value); - } - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + Result Poll(u32 in_pollevent, u32 timer, Out out_pollevent) { + LOG_WARNING(Service_SSL, "(STUBBED)"); + R_SUCCEED(); } - void GetOption(HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto option = rp.PopRaw(); + Result GetVerifyCertError() { + LOG_WARNING(Service_SSL, "(STUBBED)"); + R_SUCCEED(); + } - u8 value = 0; + Result GetNeededServerCertBufferSize(Out out_needed_buffer_size) { + LOG_WARNING(Service_SSL, "(STUBBED)"); + R_SUCCEED(); + } + Result SetSessionCacheMode(u32 mode) { + LOG_WARNING(Service_SSL, "(STUBBED) called. value={}", mode); + R_UNLESS(!did_handshake, ResultInternalError); + R_SUCCEED(); + } + + Result GetSessionCacheMode(Out mode) { + LOG_WARNING(Service_SSL, "(STUBBED)"); + R_UNLESS(!did_handshake, ResultInternalError); + R_SUCCEED(); + } + + Result FlushSessionCache() { + LOG_WARNING(Service_SSL, "(STUBBED)"); + R_UNLESS(!did_handshake, ResultInternalError); + R_SUCCEED(); + } + + Result SetRenegotiationMode(RenegotiationMode mode) { + LOG_WARNING(Service_SSL, "(STUBBED)"); + backend->SetRenegotiationMode(u32(mode)); + R_SUCCEED(); + } + + Result GetRenegotiationMode(Out mode) { + LOG_WARNING(Service_SSL, "(STUBBED)"); + u32 tmp{}; + auto const res = backend->GetRenegotiationMode(&tmp); + *mode = RenegotiationMode(tmp); + return res; + } + + Result SetOption(OptionType option, s32 value) { switch (option) { case OptionType::DoNotCloseSocket: - value = static_cast(do_not_close_socket); + do_not_close_socket = bool(value); break; case OptionType::GetServerCertChain: - value = static_cast(get_server_cert_chain); + get_server_cert_chain = bool(value); break; case OptionType::SkipDefaultVerify: - value = static_cast(skip_default_verify); + skip_default_verify = bool(value); break; case OptionType::EnableAlpn: - value = static_cast(enable_alpn); + enable_alpn = bool(value); break; default: - LOG_WARNING(Service_SSL, "Unknown option={}", option); - value = 0; + LOG_WARNING(Service_SSL, "Unknown option={}, value={}", option, value); + } + R_SUCCEED(); + } + + Result GetOption(OptionType option, Out value) { + switch (option) { + case OptionType::DoNotCloseSocket: + *value = u8(do_not_close_socket); + break; + case OptionType::GetServerCertChain: + *value = u8(get_server_cert_chain); + break; + case OptionType::SkipDefaultVerify: + *value = u8(skip_default_verify); + break; + case OptionType::EnableAlpn: + *value = u8(enable_alpn); + break; + default: + LOG_WARNING(Service_SSL, "Unknown option={}", u32(option)); + *value = 0; break; } - - LOG_DEBUG(Service_SSL, "GetOption called, option={}, ret value={}", option, value); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(value); + LOG_DEBUG(Service_SSL, "GetOption called, option={}, ret value={}", u32(option), *value); + R_SUCCEED(); } - void SetNextAlpnProto(HLERequestContext& ctx) { - const auto data = ctx.ReadBuffer(0); - next_alpn_proto.assign(data.begin(), data.end()); - + Result SetNextAlpnProto(InBuffer data) { + auto const to_write = u32((std::min)(next_alpn_proto.size(), data.size())); + next_alpn_proto.assign(data.begin(), data.begin() + to_write); LOG_DEBUG(Service_SSL, "SetNextAlpnProto called, size={}", next_alpn_proto.size()); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + R_SUCCEED(); } - void GetNextAlpnProto(HLERequestContext& ctx) { - const size_t writable = ctx.GetWriteBufferSize(); - const size_t to_write = (std::min)(next_alpn_proto.size(), writable); - - if (to_write != 0) { - ctx.WriteBuffer(std::span(next_alpn_proto.data(), to_write)); - } - - LOG_DEBUG(Service_SSL, "GetNextAlpnProto called, size={}", to_write); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(static_cast(to_write)); + Result GetNextAlpnProto(OutBuffer data, Out to_write) { + *to_write = u32((std::min)(next_alpn_proto.size(), data.size())); + next_alpn_proto.assign(data.begin(), data.begin() + *to_write); + LOG_DEBUG(Service_SSL, "GetNextAlpnProto called, size={}", *to_write); + R_SUCCEED(); } + + Result GetVerifyCertErrors(OutBuffer unk0, Out unk1, Out unk2) { + LOG_WARNING(Service_SSL, "(STUBBED)"); + R_SUCCEED(); + } + + SslVersion ssl_version; + std::shared_ptr shared_data; + std::unique_ptr backend; + std::optional fd_to_close; + std::shared_ptr socket; + std::vector next_alpn_proto; + u32 verify_option = 0; + + bool do_not_close_socket = false; + bool get_server_cert_chain = false; + bool skip_default_verify = false; + bool enable_alpn = false; + bool did_handshake = false; }; class ISslContext final : public ServiceFramework { @@ -495,7 +461,7 @@ public: {5, &ISslContext::ImportClientPki, "ImportClientPki"}, {6, nullptr, "RemoveServerPki"}, {7, nullptr, "RemoveClientPki"}, - {8, nullptr, "RegisterInternalPki"}, + {8, D<&ISslContext::RegisterInternalPki>, "RegisterInternalPki"}, {9, nullptr, "AddPolicyOid"}, {10, nullptr, "ImportCrl"}, {11, nullptr, "RemoveCrl"}, @@ -546,8 +512,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(res); if (res == ResultSuccess) { - rb.PushIpcInterface(system, ssl_version, shared_data, - std::move(backend)); + rb.PushIpcInterface(ctx, system, ssl_version, shared_data, std::move(backend)); } } @@ -591,6 +556,11 @@ private: rb.Push(ResultSuccess); rb.Push(client_id); } + + Result RegisterInternalPki() { + LOG_WARNING(Service_SSL, "(STUBBED) called"); + R_SUCCEED(); + } }; class ISslService final : public ServiceFramework { @@ -627,12 +597,11 @@ private: IPC::RequestParser rp{ctx}; const auto parameters = rp.PopRaw(); - LOG_WARNING(Service_SSL, "(STUBBED) called, api_version={}, pid_placeholder={}", - parameters.ssl_version.api_version, parameters.pid_placeholder); + LOG_WARNING(Service_SSL, "(STUBBED) called, api_version={}, pid_placeholder={}", parameters.ssl_version.api_version, parameters.pid_placeholder); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system, parameters.ssl_version); + rb.PushIpcInterface(ctx, system, parameters.ssl_version); } void SetInterfaceVersion(HLERequestContext& ctx) { diff --git a/src/core/hle/service/ssl/ssl.h b/src/core/hle/service/ssl/ssl.h index f6e21bbb3b..dc9f2b746b 100644 --- a/src/core/hle/service/ssl/ssl.h +++ b/src/core/hle/service/ssl/ssl.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/ssl/ssl_backend.h b/src/core/hle/service/ssl/ssl_backend.h index a3127342a2..ef6bf8f9de 100644 --- a/src/core/hle/service/ssl/ssl_backend.h +++ b/src/core/hle/service/ssl/ssl_backend.h @@ -36,12 +36,17 @@ class SSLConnectionBackend { public: virtual ~SSLConnectionBackend() {} virtual void SetSocket(std::shared_ptr socket) = 0; - virtual Result SetHostName(const std::string& hostname) = 0; virtual void SetVerifyOption(u32 option) = 0; virtual Result DoHandshake() = 0; virtual Result Read(size_t* out_size, std::span data) = 0; + virtual Result Peek(size_t* out_size, std::span data) = 0; virtual Result Write(size_t* out_size, std::span data) = 0; virtual Result GetServerCerts(std::vector>* out_certs) = 0; + virtual Result SetHostName(const char* hostname) = 0; + virtual Result GetHostName(std::span hostname, u32* out_size) = 0; + virtual int Pending() = 0; + virtual Result SetRenegotiationMode(u32 mode) = 0; + virtual Result GetRenegotiationMode(u32* mode) = 0; }; Result CreateSSLConnectionBackend(std::unique_ptr* out_backend); diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp index f0e05266ba..435ada845d 100644 --- a/src/core/hle/service/ssl/ssl_backend_openssl.cpp +++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp @@ -157,18 +157,30 @@ public: socket = std::move(socket_in); } - Result SetHostName(const std::string& hostname) override { + Result SetHostName(const char* hostname) override { if (!skip_cert_verification) { - if (!SSL_set1_host(ssl, hostname.c_str())) { + if (!SSL_set1_host(ssl, hostname)) { LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname); return CheckOpenSSLErrors(); } } - if (!SSL_set_tlsext_host_name(ssl, hostname.c_str())) { // hostname for SNI + if (!SSL_set_tlsext_host_name(ssl, hostname)) { // hostname for SNI LOG_ERROR(Service_SSL, "SSL_set_tlsext_host_name({}) failed", hostname); return CheckOpenSSLErrors(); } - return ResultSuccess; + R_SUCCEED(); + } + + Result GetHostName(std::span data, u32* out_size) override { + auto const peer_name = SSL_get0_peername(ssl); + if (peer_name == nullptr) { + LOG_ERROR(Service_SSL, "SSL_get0_peername()"); + return CheckOpenSSLErrors(); + } + auto const s = std::string{peer_name}; + *out_size = u32(s.size()); + std::memcpy(data.data(), s.data(), (std::min)(s.size(), data.size())); + R_SUCCEED(); } void SetVerifyOption(u32 option) override { @@ -213,6 +225,13 @@ public: return HandleReturn("SSL_read_ex", out_size, ret); } + Result Peek(size_t* out_size, std::span data) override { + auto const n = (std::min)(data.size(), *out_size); + const int ret = SSL_peek(ssl, data.data(), int(n)); + *out_size = n; + return HandleReturn("SSL_write_ex", out_size, ret); + } + Result Write(size_t* out_size, std::span data) override { const int ret = SSL_write_ex(ssl, data.data(), data.size(), out_size); return HandleReturn("SSL_write_ex", out_size, ret); @@ -263,7 +282,25 @@ public: out_certs->emplace_back(buf, buf + len); OPENSSL_free(buf); } - return ResultSuccess; + R_SUCCEED(); + } + + int Pending() override { + return SSL_pending(ssl); + } + + Result SetRenegotiationMode(u32 mode) override { + if (mode == 0) { + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_RENEGOTIATION); + } else { + SSL_CTX_set_options(ssl_ctx, SSL_OP_ALLOW_CLIENT_RENEGOTIATION); + } + R_SUCCEED(); + } + + Result GetRenegotiationMode(u32* mode) override { + *mode = SSL_get_secure_renegotiation_support(ssl) ? 1 : 0; + R_SUCCEED(); } ~SSLConnectionBackendOpenSSL() { diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp deleted file mode 100644 index 2b68f29eaf..0000000000 --- a/src/core/hle/service/ssl/ssl_backend_schannel.cpp +++ /dev/null @@ -1,563 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 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 - -#include "common/error.h" -#include "common/fs/file.h" -#include "common/hex_util.h" -#include "common/string_util.h" - -#include "core/hle/service/ssl/ssl_backend.h" -#include "core/internal_network/network.h" -#include "core/internal_network/sockets.h" - -namespace { - -// These includes are inside the namespace to avoid a conflict on MinGW where -// the headers define an enum containing Network and Service as enumerators -// (which clash with the correspondingly named namespaces). -#define SECURITY_WIN32 -#include -#include -#include - -std::once_flag one_time_init_flag; -bool one_time_init_success = false; - -SCHANNEL_CRED schannel_cred{}; -CredHandle cred_handle; - -static void OneTimeInit() { - schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; - schannel_cred.dwFlags = - SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols - SCH_CRED_NO_SERVERNAME_CHECK | // don't validate server names - SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate - // ^ I'm assuming that nobody would want to connect Yuzu to a - // service that requires some OS-provided corporate client - // certificate, and presenting one to some arbitrary server - // might be a privacy concern? Who knows, though. - - const SECURITY_STATUS ret = - AcquireCredentialsHandle(nullptr, const_cast(UNISP_NAME), SECPKG_CRED_OUTBOUND, - nullptr, &schannel_cred, nullptr, nullptr, &cred_handle, nullptr); - if (ret != SEC_E_OK) { - // SECURITY_STATUS codes are a type of HRESULT and can be used with NativeErrorToString. - LOG_ERROR(Service_SSL, "AcquireCredentialsHandle failed: {}", - Common::NativeErrorToString(ret)); - return; - } - - if (getenv("SSLKEYLOGFILE")) { - LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but Schannel does not support exporting " - "keys; not logging keys!"); - // Not fatal. - } - - one_time_init_success = true; -} - -} // namespace - -namespace Service::SSL { - -class SSLConnectionBackendSchannel final : public SSLConnectionBackend { -public: - Result Init() { - std::call_once(one_time_init_flag, OneTimeInit); - - if (!one_time_init_success) { - LOG_ERROR( - Service_SSL, - "Can't create SSL connection because Schannel one-time initialization failed"); - return ResultInternalError; - } - - return ResultSuccess; - } - - void SetSocket(std::shared_ptr socket_in) override { - socket = std::move(socket_in); - } - - Result SetHostName(const std::string& hostname_in) override { - hostname = hostname_in; - return ResultSuccess; - } - - void SetVerifyOption(u32 option) override { - skip_cert_verification = (option == 0); - LOG_WARNING(Service_SSL, "option={} skip_verification={}", option, - skip_cert_verification); - } - - Result DoHandshake() override { - while (1) { - Result r; - switch (handshake_state) { - case HandshakeState::Initial: - if ((r = FlushCiphertextWriteBuf()) != ResultSuccess || - (r = CallInitializeSecurityContext()) != ResultSuccess) { - return r; - } - // CallInitializeSecurityContext updated `handshake_state`. - continue; - case HandshakeState::ContinueNeeded: - case HandshakeState::IncompleteMessage: - if ((r = FlushCiphertextWriteBuf()) != ResultSuccess || - (r = FillCiphertextReadBuf()) != ResultSuccess) { - return r; - } - if (ciphertext_read_buf.empty()) { - LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up"); - return ResultInternalError; - } - if ((r = CallInitializeSecurityContext()) != ResultSuccess) { - return r; - } - // CallInitializeSecurityContext updated `handshake_state`. - continue; - case HandshakeState::DoneAfterFlush: - if ((r = FlushCiphertextWriteBuf()) != ResultSuccess) { - return r; - } - handshake_state = HandshakeState::Connected; - return ResultSuccess; - case HandshakeState::Connected: - LOG_ERROR(Service_SSL, "Called DoHandshake but we already handshook"); - return ResultInternalError; - case HandshakeState::Error: - return ResultInternalError; - } - } - } - - Result FillCiphertextReadBuf() { - const size_t fill_size = read_buf_fill_size ? read_buf_fill_size : 4096; - read_buf_fill_size = 0; - // This unnecessarily zeroes the buffer; oh well. - const size_t offset = ciphertext_read_buf.size(); - ASSERT_OR_EXECUTE(offset + fill_size >= offset, { return ResultInternalError; }); - ciphertext_read_buf.resize(offset + fill_size, 0); - const auto read_span = std::span(ciphertext_read_buf).subspan(offset, fill_size); - const auto [actual, err] = socket->Recv(0, read_span); - switch (err) { - case Network::Errno::SUCCESS: - ASSERT(static_cast(actual) <= fill_size); - ciphertext_read_buf.resize(offset + actual); - return ResultSuccess; - case Network::Errno::AGAIN: - ciphertext_read_buf.resize(offset); - return ResultWouldBlock; - default: - ciphertext_read_buf.resize(offset); - LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err); - return ResultInternalError; - } - } - - // Returns success if the write buffer has been completely emptied. - Result FlushCiphertextWriteBuf() { - while (!ciphertext_write_buf.empty()) { - const auto [actual, err] = socket->Send(ciphertext_write_buf, 0); - switch (err) { - case Network::Errno::SUCCESS: - ASSERT(static_cast(actual) <= ciphertext_write_buf.size()); - ciphertext_write_buf.erase(ciphertext_write_buf.begin(), - ciphertext_write_buf.begin() + actual); - break; - case Network::Errno::AGAIN: - return ResultWouldBlock; - default: - LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err); - return ResultInternalError; - } - } - return ResultSuccess; - } - - Result CallInitializeSecurityContext() { - unsigned long req = ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY | - ISC_REQ_INTEGRITY | ISC_REQ_REPLAY_DETECT | - ISC_REQ_SEQUENCE_DETECT | ISC_REQ_STREAM | - ISC_REQ_USE_SUPPLIED_CREDS; - - if (skip_cert_verification) { - req |= ISC_REQ_MANUAL_CRED_VALIDATION; - } - - unsigned long attr; - // https://learn.microsoft.com/en-us/windows/win32/secauthn/initializesecuritycontext--schannel - std::array input_buffers{{ - // only used if `initial_call_done` - { - // [0] - .cbBuffer = static_cast(ciphertext_read_buf.size()), - .BufferType = SECBUFFER_TOKEN, - .pvBuffer = ciphertext_read_buf.data(), - }, - { - // [1] (will be replaced by SECBUFFER_MISSING when SEC_E_INCOMPLETE_MESSAGE is - // returned, or SECBUFFER_EXTRA when SEC_E_CONTINUE_NEEDED is returned if the - // whole buffer wasn't used) - .cbBuffer = 0, - .BufferType = SECBUFFER_EMPTY, - .pvBuffer = nullptr, - }, - }}; - std::array output_buffers{{ - { - .cbBuffer = 0, - .BufferType = SECBUFFER_TOKEN, - .pvBuffer = nullptr, - }, // [0] - { - .cbBuffer = 0, - .BufferType = SECBUFFER_ALERT, - .pvBuffer = nullptr, - }, // [1] - }}; - SecBufferDesc input_desc{ - .ulVersion = SECBUFFER_VERSION, - .cBuffers = static_cast(input_buffers.size()), - .pBuffers = input_buffers.data(), - }; - SecBufferDesc output_desc{ - .ulVersion = SECBUFFER_VERSION, - .cBuffers = static_cast(output_buffers.size()), - .pBuffers = output_buffers.data(), - }; - ASSERT_OR_EXECUTE_MSG( - input_buffers[0].cbBuffer == ciphertext_read_buf.size(), - { return ResultInternalError; }, "read buffer too large"); - - bool initial_call_done = handshake_state != HandshakeState::Initial; - if (initial_call_done) { - LOG_DEBUG(Service_SSL, "Passing {} bytes into InitializeSecurityContext", - ciphertext_read_buf.size()); - } - - char* hostname_ptr = hostname ? const_cast(hostname->c_str()) : nullptr; - const SECURITY_STATUS ret = InitializeSecurityContextA( - &cred_handle, initial_call_done ? &ctxt : nullptr, hostname_ptr, req, - 0, // Reserved1 - 0, // TargetDataRep not used with Schannel - initial_call_done ? &input_desc : nullptr, - 0, // Reserved2 - initial_call_done ? nullptr : &ctxt, &output_desc, &attr, - nullptr); // ptsExpiry - - if (output_buffers[0].pvBuffer) { - const std::span span(static_cast(output_buffers[0].pvBuffer), - output_buffers[0].cbBuffer); - ciphertext_write_buf.insert(ciphertext_write_buf.end(), span.begin(), span.end()); - FreeContextBuffer(output_buffers[0].pvBuffer); - } - - if (output_buffers[1].pvBuffer) { - const std::span span(static_cast(output_buffers[1].pvBuffer), - output_buffers[1].cbBuffer); - // The documentation doesn't explain what format this data is in. - LOG_DEBUG(Service_SSL, "Got a {}-byte alert buffer: {}", span.size(), - Common::HexToString(span)); - } - - switch (ret) { - case SEC_I_CONTINUE_NEEDED: - LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_I_CONTINUE_NEEDED"); - if (input_buffers[1].BufferType == SECBUFFER_EXTRA) { - LOG_DEBUG(Service_SSL, "EXTRA of size {}", input_buffers[1].cbBuffer); - ASSERT(input_buffers[1].cbBuffer <= ciphertext_read_buf.size()); - ciphertext_read_buf.erase(ciphertext_read_buf.begin(), - ciphertext_read_buf.end() - input_buffers[1].cbBuffer); - } else { - ASSERT(input_buffers[1].BufferType == SECBUFFER_EMPTY); - ciphertext_read_buf.clear(); - } - handshake_state = HandshakeState::ContinueNeeded; - return ResultSuccess; - case SEC_E_INCOMPLETE_MESSAGE: - LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_INCOMPLETE_MESSAGE"); - ASSERT(input_buffers[1].BufferType == SECBUFFER_MISSING); - read_buf_fill_size = input_buffers[1].cbBuffer; - handshake_state = HandshakeState::IncompleteMessage; - return ResultSuccess; - case SEC_E_OK: - LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_OK"); - ciphertext_read_buf.clear(); - handshake_state = HandshakeState::DoneAfterFlush; - return GrabStreamSizes(); - default: - LOG_ERROR(Service_SSL, - "InitializeSecurityContext failed (probably certificate/protocol issue): {}", - Common::NativeErrorToString(ret)); - handshake_state = HandshakeState::Error; - return ResultInternalError; - } - } - - Result GrabStreamSizes() { - const SECURITY_STATUS ret = - QueryContextAttributes(&ctxt, SECPKG_ATTR_STREAM_SIZES, &stream_sizes); - if (ret != SEC_E_OK) { - LOG_ERROR(Service_SSL, "QueryContextAttributes(SECPKG_ATTR_STREAM_SIZES) failed: {}", - Common::NativeErrorToString(ret)); - handshake_state = HandshakeState::Error; - return ResultInternalError; - } - return ResultSuccess; - } - - Result Read(size_t* out_size, std::span data) override { - *out_size = 0; - if (handshake_state != HandshakeState::Connected) { - LOG_ERROR(Service_SSL, "Called Read but we did not successfully handshake"); - return ResultInternalError; - } - if (data.size() == 0 || got_read_eof) { - return ResultSuccess; - } - while (1) { - if (!cleartext_read_buf.empty()) { - *out_size = (std::min)(cleartext_read_buf.size(), data.size()); - std::memcpy(data.data(), cleartext_read_buf.data(), *out_size); - cleartext_read_buf.erase(cleartext_read_buf.begin(), - cleartext_read_buf.begin() + *out_size); - return ResultSuccess; - } - if (!ciphertext_read_buf.empty()) { - SecBuffer empty{ - .cbBuffer = 0, - .BufferType = SECBUFFER_EMPTY, - .pvBuffer = nullptr, - }; - std::array buffers{{ - { - .cbBuffer = static_cast(ciphertext_read_buf.size()), - .BufferType = SECBUFFER_DATA, - .pvBuffer = ciphertext_read_buf.data(), - }, - empty, - empty, - empty, - }}; - ASSERT_OR_EXECUTE_MSG( - buffers[0].cbBuffer == ciphertext_read_buf.size(), - { return ResultInternalError; }, "read buffer too large"); - SecBufferDesc desc{ - .ulVersion = SECBUFFER_VERSION, - .cBuffers = static_cast(buffers.size()), - .pBuffers = buffers.data(), - }; - SECURITY_STATUS ret = - DecryptMessage(&ctxt, &desc, /*MessageSeqNo*/ 0, /*pfQOP*/ nullptr); - switch (ret) { - case SEC_E_OK: - ASSERT_OR_EXECUTE(buffers[0].BufferType == SECBUFFER_STREAM_HEADER, - { return ResultInternalError; }); - ASSERT_OR_EXECUTE(buffers[1].BufferType == SECBUFFER_DATA, - { return ResultInternalError; }); - ASSERT_OR_EXECUTE(buffers[2].BufferType == SECBUFFER_STREAM_TRAILER, - { return ResultInternalError; }); - cleartext_read_buf.assign(static_cast(buffers[1].pvBuffer), - static_cast(buffers[1].pvBuffer) + - buffers[1].cbBuffer); - if (buffers[3].BufferType == SECBUFFER_EXTRA) { - ASSERT(buffers[3].cbBuffer <= ciphertext_read_buf.size()); - ciphertext_read_buf.erase(ciphertext_read_buf.begin(), - ciphertext_read_buf.end() - buffers[3].cbBuffer); - } else { - ASSERT(buffers[3].BufferType == SECBUFFER_EMPTY); - ciphertext_read_buf.clear(); - } - continue; - case SEC_E_INCOMPLETE_MESSAGE: - break; - case SEC_I_CONTEXT_EXPIRED: - // Server hung up by sending close_notify. - got_read_eof = true; - *out_size = 0; - return ResultSuccess; - default: - LOG_ERROR(Service_SSL, "DecryptMessage failed: {}", - Common::NativeErrorToString(ret)); - return ResultInternalError; - } - } - const Result r = FillCiphertextReadBuf(); - if (r != ResultSuccess) { - return r; - } - if (ciphertext_read_buf.empty()) { - got_read_eof = true; - *out_size = 0; - return ResultSuccess; - } - } - } - - Result Write(size_t* out_size, std::span data) override { - *out_size = 0; - - if (handshake_state != HandshakeState::Connected) { - LOG_ERROR(Service_SSL, "Called Write but we did not successfully handshake"); - return ResultInternalError; - } - if (data.size() == 0) { - return ResultSuccess; - } - data = data.subspan(0, std::min(data.size(), stream_sizes.cbMaximumMessage)); - if (!cleartext_write_buf.empty()) { - // Already in the middle of a write. It wouldn't make sense to not - // finish sending the entire buffer since TLS has - // header/MAC/padding/etc. - if (data.size() != cleartext_write_buf.size() || - std::memcmp(data.data(), cleartext_write_buf.data(), data.size())) { - LOG_ERROR(Service_SSL, "Called Write but buffer does not match previous buffer"); - return ResultInternalError; - } - return WriteAlreadyEncryptedData(out_size); - } else { - cleartext_write_buf.assign(data.begin(), data.end()); - } - - std::vector header_buf(stream_sizes.cbHeader, 0); - std::vector tmp_data_buf = cleartext_write_buf; - std::vector trailer_buf(stream_sizes.cbTrailer, 0); - - std::array buffers{{ - { - .cbBuffer = stream_sizes.cbHeader, - .BufferType = SECBUFFER_STREAM_HEADER, - .pvBuffer = header_buf.data(), - }, - { - .cbBuffer = static_cast(tmp_data_buf.size()), - .BufferType = SECBUFFER_DATA, - .pvBuffer = tmp_data_buf.data(), - }, - { - .cbBuffer = stream_sizes.cbTrailer, - .BufferType = SECBUFFER_STREAM_TRAILER, - .pvBuffer = trailer_buf.data(), - }, - }}; - ASSERT_OR_EXECUTE_MSG( - buffers[1].cbBuffer == tmp_data_buf.size(), { return ResultInternalError; }, - "temp buffer too large"); - SecBufferDesc desc{ - .ulVersion = SECBUFFER_VERSION, - .cBuffers = static_cast(buffers.size()), - .pBuffers = buffers.data(), - }; - - const SECURITY_STATUS ret = EncryptMessage(&ctxt, /*fQOP*/ 0, &desc, /*MessageSeqNo*/ 0); - if (ret != SEC_E_OK) { - LOG_ERROR(Service_SSL, "EncryptMessage failed: {}", Common::NativeErrorToString(ret)); - return ResultInternalError; - } - ciphertext_write_buf.insert(ciphertext_write_buf.end(), header_buf.begin(), - header_buf.end()); - ciphertext_write_buf.insert(ciphertext_write_buf.end(), tmp_data_buf.begin(), - tmp_data_buf.end()); - ciphertext_write_buf.insert(ciphertext_write_buf.end(), trailer_buf.begin(), - trailer_buf.end()); - return WriteAlreadyEncryptedData(out_size); - } - - Result WriteAlreadyEncryptedData(size_t* out_size) { - const Result r = FlushCiphertextWriteBuf(); - if (r != ResultSuccess) { - return r; - } - // write buf is empty - *out_size = cleartext_write_buf.size(); - cleartext_write_buf.clear(); - return ResultSuccess; - } - - Result GetServerCerts(std::vector>* out_certs) override { - PCCERT_CONTEXT returned_cert = nullptr; - const SECURITY_STATUS ret = - QueryContextAttributes(&ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &returned_cert); - if (ret != SEC_E_OK) { - LOG_ERROR(Service_SSL, - "QueryContextAttributes(SECPKG_ATTR_REMOTE_CERT_CONTEXT) failed: {}", - Common::NativeErrorToString(ret)); - return ResultInternalError; - } - PCCERT_CONTEXT some_cert = nullptr; - while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert)) != - nullptr) { - out_certs->emplace_back(static_cast(some_cert->pbCertEncoded), - static_cast(some_cert->pbCertEncoded) + - some_cert->cbCertEncoded); - } - std::reverse(out_certs->begin(), - out_certs->end()); // Windows returns certs in reverse order from what we want - CertFreeCertificateContext(returned_cert); - return ResultSuccess; - } - - ~SSLConnectionBackendSchannel() { - if (handshake_state != HandshakeState::Initial) { - DeleteSecurityContext(&ctxt); - } - } - - enum class HandshakeState { - // Haven't called anything yet. - Initial, - // `SEC_I_CONTINUE_NEEDED` was returned by - // `InitializeSecurityContext`; must finish sending data (if any) in - // the write buffer, then read at least one byte before calling - // `InitializeSecurityContext` again. - ContinueNeeded, - // `SEC_E_INCOMPLETE_MESSAGE` was returned by - // `InitializeSecurityContext`; hopefully the write buffer is empty; - // must read at least one byte before calling - // `InitializeSecurityContext` again. - IncompleteMessage, - // `SEC_E_OK` was returned by `InitializeSecurityContext`; must - // finish sending data in the write buffer before having `DoHandshake` - // report success. - DoneAfterFlush, - // We finished the above and are now connected. At this point, writing - // and reading are separate 'state machines' represented by the - // nonemptiness of the ciphertext and cleartext read and write buffers. - Connected, - // Another error was returned and we shouldn't allow initialization - // to continue. - Error, - } handshake_state = HandshakeState::Initial; - - CtxtHandle ctxt; - SecPkgContext_StreamSizes stream_sizes; - - std::shared_ptr socket; - std::optional hostname; - - std::vector ciphertext_read_buf; - std::vector ciphertext_write_buf; - std::vector cleartext_read_buf; - std::vector cleartext_write_buf; - - bool got_read_eof = false; - bool skip_cert_verification = false; - size_t read_buf_fill_size = 0; -}; - -Result CreateSSLConnectionBackend(std::unique_ptr* out_backend) { - auto conn = std::make_unique(); - - R_TRY(conn->Init()); - - *out_backend = std::move(conn); - return ResultSuccess; -} - -} // namespace Service::SSL diff --git a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp deleted file mode 100644 index 65f81be631..0000000000 --- a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp +++ /dev/null @@ -1,236 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 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 - -// SecureTransport has been deprecated in its entirety in favor of -// Network.framework, but that does not allow layering TLS on top of an -// arbitrary socket. -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#include -#pragma GCC diagnostic pop -#endif - -#include "core/hle/service/ssl/ssl_backend.h" -#include "core/internal_network/network.h" -#include "core/internal_network/sockets.h" - -namespace { - -template -struct CFReleaser { - T ptr; - - YUZU_NON_COPYABLE(CFReleaser); - constexpr CFReleaser() : ptr(nullptr) {} - constexpr CFReleaser(T ptr) : ptr(ptr) {} - constexpr operator T() { - return ptr; - } - ~CFReleaser() { - if (ptr) { - CFRelease(ptr); - } - } -}; - -std::string CFStringToString(CFStringRef cfstr) { - CFReleaser cfdata( - CFStringCreateExternalRepresentation(nullptr, cfstr, kCFStringEncodingUTF8, 0)); - ASSERT_OR_EXECUTE(cfdata, { return "???"; }); - return std::string(reinterpret_cast(CFDataGetBytePtr(cfdata)), - CFDataGetLength(cfdata)); -} - -std::string OSStatusToString(OSStatus status) { - CFReleaser cfstr(SecCopyErrorMessageString(status, nullptr)); - if (!cfstr) { - return "[unknown error]"; - } - return CFStringToString(cfstr); -} - -} // namespace - -namespace Service::SSL { - -class SSLConnectionBackendSecureTransport final : public SSLConnectionBackend { -public: - Result Init() { - static std::once_flag once_flag; - std::call_once(once_flag, []() { - if (getenv("SSLKEYLOGFILE")) { - LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but SecureTransport does not " - "support exporting keys; not logging keys!"); - // Not fatal. - } - }); - - context.ptr = SSLCreateContext(nullptr, kSSLClientSide, kSSLStreamType); - if (!context) { - LOG_ERROR(Service_SSL, "SSLCreateContext failed"); - return ResultInternalError; - } - - OSStatus status; - if ((status = SSLSetIOFuncs(context, ReadCallback, WriteCallback)) || - (status = SSLSetConnection(context, this))) { - LOG_ERROR(Service_SSL, "SSLContext initialization failed: {}", - OSStatusToString(status)); - return ResultInternalError; - } - - return ResultSuccess; - } - - void SetSocket(std::shared_ptr in_socket) override { - socket = std::move(in_socket); - } - - Result SetHostName(const std::string& hostname) override { - OSStatus status = SSLSetPeerDomainName(context, hostname.c_str(), hostname.size()); - if (status) { - LOG_ERROR(Service_SSL, "SSLSetPeerDomainName failed: {}", OSStatusToString(status)); - return ResultInternalError; - } - return ResultSuccess; - } - - void SetVerifyOption(u32 option) override { - skip_cert_verification = (option == 0); - LOG_WARNING(Service_SSL, "option={} skip_verification={}", option, - skip_cert_verification); - if (skip_cert_verification) { - SSLSetSessionOption(context, kSSLSessionOptionBreakOnServerAuth, true); - } - } - - Result DoHandshake() override { - OSStatus status = SSLHandshake(context); - - if (skip_cert_verification && status == errSSLServerAuthCompleted) { - LOG_DEBUG(Service_SSL, "Skipping certificate verification as requested"); - status = SSLHandshake(context); - } - - return HandleReturn("SSLHandshake", 0, status); - } - - Result Read(size_t* out_size, std::span data) override { - OSStatus status = SSLRead(context, data.data(), data.size(), out_size); - return HandleReturn("SSLRead", out_size, status); - } - - Result Write(size_t* out_size, std::span data) override { - OSStatus status = SSLWrite(context, data.data(), data.size(), out_size); - return HandleReturn("SSLWrite", out_size, status); - } - - Result HandleReturn(const char* what, size_t* actual, OSStatus status) { - switch (status) { - case 0: - return ResultSuccess; - case errSSLWouldBlock: - return ResultWouldBlock; - default: { - std::string reason; - if (got_read_eof) { - reason = "server hung up"; - } else { - reason = OSStatusToString(status); - } - LOG_ERROR(Service_SSL, "{} failed: {}", what, reason); - return ResultInternalError; - } - } - } - - Result GetServerCerts(std::vector>* out_certs) override { - CFReleaser trust; - OSStatus status = SSLCopyPeerTrust(context, &trust.ptr); - if (status) { - LOG_ERROR(Service_SSL, "SSLCopyPeerTrust failed: {}", OSStatusToString(status)); - return ResultInternalError; - } - for (CFIndex i = 0, count = SecTrustGetCertificateCount(trust); i < count; i++) { - SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i); - CFReleaser data(SecCertificateCopyData(cert)); - ASSERT_OR_EXECUTE(data, { return ResultInternalError; }); - const u8* ptr = CFDataGetBytePtr(data); - out_certs->emplace_back(ptr, ptr + CFDataGetLength(data)); - } - return ResultSuccess; - } - - static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) { - return ReadOrWriteCallback(connection, data, dataLength, true); - } - - static OSStatus WriteCallback(SSLConnectionRef connection, const void* data, - size_t* dataLength) { - return ReadOrWriteCallback(connection, const_cast(data), dataLength, false); - } - - static OSStatus ReadOrWriteCallback(SSLConnectionRef connection, void* data, size_t* dataLength, - bool is_read) { - auto self = - static_cast(const_cast(connection)); - ASSERT_OR_EXECUTE_MSG( - self->socket, { return 0; }, "SecureTransport asked to {} but we have no socket", - is_read ? "read" : "write"); - - // SecureTransport callbacks (unlike OpenSSL BIO callbacks) are - // expected to read/write the full requested dataLength or return an - // error, so we have to add a loop ourselves. - size_t requested_len = *dataLength; - size_t offset = 0; - while (offset < requested_len) { - std::span cur(reinterpret_cast(data) + offset, requested_len - offset); - auto [actual, err] = is_read ? self->socket->Recv(0, cur) : self->socket->Send(cur, 0); - LOG_CRITICAL(Service_SSL, "op={}, offset={} actual={}/{} err={}", is_read, offset, - actual, cur.size(), static_cast(err)); - switch (err) { - case Network::Errno::SUCCESS: - offset += actual; - if (actual == 0) { - ASSERT(is_read); - self->got_read_eof = true; - return errSecEndOfData; - } - break; - case Network::Errno::AGAIN: - *dataLength = offset; - return errSSLWouldBlock; - default: - LOG_ERROR(Service_SSL, "Socket {} returned Network::Errno {}", - is_read ? "recv" : "send", err); - return errSecIO; - } - } - ASSERT(offset == requested_len); - return 0; - } - -private: - CFReleaser context = nullptr; - bool got_read_eof = false; - bool skip_cert_verification = false; - - std::shared_ptr socket; -}; - -Result CreateSSLConnectionBackend(std::unique_ptr* out_backend) { - auto conn = std::make_unique(); - - R_TRY(conn->Init()); - - *out_backend = std::move(conn); - return ResultSuccess; -} - -} // namespace Service::SSL diff --git a/src/core/hle/service/ssl/ssl_types.h b/src/core/hle/service/ssl/ssl_types.h index 16623c0fc5..cbd84ad1f9 100644 --- a/src/core/hle/service/ssl/ssl_types.h +++ b/src/core/hle/service/ssl/ssl_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/tma/tma.cpp b/src/core/hle/service/tma/tma.cpp new file mode 100644 index 0000000000..c2fb33cd16 --- /dev/null +++ b/src/core/hle/service/tma/tma.cpp @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 + +#include "core/core.h" +#include "core/hle/result.h" +#include "core/hle/service/tma/tma.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/cmif_serialization.h" +#include "core/hle/service/cmif_types.h" +#include "core/hle/service/service.h" +#include "core/hle/service/server_manager.h" + +namespace Service::TMA { + +class HTC_TENV final : public ServiceFramework { +public: + explicit HTC_TENV(Core::System& system_) + : ServiceFramework{system_, "htc:tenv"} + { + static const FunctionInfo functions[] = { + {0, nullptr, "GetServiceInterface"}, + }; + RegisterHandlers(functions); + } +}; + +void LoopProcess(Core::System& system) { + auto server_manager = std::make_unique(system); + server_manager->RegisterNamedService("htc:tenv", std::make_shared(system)); + ServerManager::RunServer(std::move(server_manager)); +} + +} // namespace Service::TMA diff --git a/src/core/hle/service/tma/tma.h b/src/core/hle/service/tma/tma.h new file mode 100644 index 0000000000..05324eaa1e --- /dev/null +++ b/src/core/hle/service/tma/tma.h @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 + +#pragma once + +namespace Core { +class System; +} + +namespace Service::TMA { +void LoopProcess(Core::System& system); +} // namespace Service::TMA diff --git a/src/core/hle/service/usb/usb.cpp b/src/core/hle/service/usb/usb.cpp index 7f07cfef19..ff2a377172 100644 --- a/src/core/hle/service/usb/usb.cpp +++ b/src/core/hle/service/usb/usb.cpp @@ -11,6 +11,7 @@ #include "core/hle/service/server_manager.h" #include "core/hle/service/service.h" #include "core/hle/service/usb/usb.h" +#include "frontend_common/firmware_manager.h" namespace Service::USB { @@ -155,7 +156,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } }; @@ -199,7 +200,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system); + rb.PushIpcInterface(ctx, system); } }; @@ -221,14 +222,61 @@ public: } }; +class IPdManufactureManager final : public ServiceFramework { +public: + explicit IPdManufactureManager(Core::System& system_) : ServiceFramework{system_, "usb:pd:m"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "OpenManufactureSession"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class IQdbManager final : public ServiceFramework { +public: + explicit IQdbManager(Core::System& system_) : ServiceFramework{system_, "usb:qdb"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "ImportQuirkDevices"}, + {1, nullptr, "HasQuirk"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + +class IPmObserverService final : public ServiceFramework { +public: + explicit IPmObserverService(Core::System& system_) : ServiceFramework{system_, "usb:obsv"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetTopologyChangeEvent"}, + {1, nullptr, "GetFlattenedTopology"}, + }; + // clang-format on + RegisterHandlers(functions); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique(system); server_manager->RegisterNamedService("usb:ds", std::make_shared(system)); server_manager->RegisterNamedService("usb:hs", std::make_shared(system)); - server_manager->RegisterNamedService("usb:pd", std::make_shared(system)); - server_manager->RegisterNamedService("usb:pd:c", std::make_shared(system)); - server_manager->RegisterNamedService("usb:pm", std::make_shared(system)); + server_manager->RegisterNamedService("usb:pd", std::make_shared(system), 6); + server_manager->RegisterNamedService("usb:pd:c", std::make_shared(system), 4); + server_manager->RegisterNamedService("usb:pd:m", std::make_shared(system)); + server_manager->RegisterNamedService("usb:pm", std::make_shared(system), 5); + // +7.0.0 + if (FirmwareManager::GetFirmwareVersion(system).first.major >= 7) { + server_manager->RegisterNamedService("usb:qdb", std::make_shared(system)); + } + // +8.0.0 + if (FirmwareManager::GetFirmwareVersion(system).first.major >= 8) { + server_manager->RegisterNamedService("usb:obsv", std::make_shared(system), 2); + } ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/usb/usb.h b/src/core/hle/service/usb/usb.h index 98376ebc0c..cc25a3b29e 100644 --- a/src/core/hle/service/usb/usb.h +++ b/src/core/hle/service/usb/usb.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/vi/application_display_service.cpp b/src/core/hle/service/vi/application_display_service.cpp index 969c86f53b..f179ba676a 100644 --- a/src/core/hle/service/vi/application_display_service.cpp +++ b/src/core/hle/service/vi/application_display_service.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -13,6 +13,7 @@ #include "core/hle/service/vi/manager_display_service.h" #include "core/hle/service/vi/system_display_service.h" #include "core/hle/service/vi/vi_results.h" +#include "service_creator.h" namespace Service::VI { @@ -38,6 +39,7 @@ IApplicationDisplayService::IApplicationDisplayService(Core::System& system_, {2031, C<&IApplicationDisplayService::DestroyStrayLayer>, "DestroyStrayLayer"}, {2101, C<&IApplicationDisplayService::SetLayerScalingMode>, "SetLayerScalingMode"}, {2102, C<&IApplicationDisplayService::ConvertScalingMode>, "ConvertScalingMode"}, + {2103, C<&IApplicationDisplayService::Cmd2103>, "Cmd2103"}, {2450, C<&IApplicationDisplayService::GetIndirectLayerImageMap>, "GetIndirectLayerImageMap"}, {2451, nullptr, "GetIndirectLayerImageCropMap"}, {2460, C<&IApplicationDisplayService::GetIndirectLayerImageRequiredMemoryInfo>, "GetIndirectLayerImageRequiredMemoryInfo"}, @@ -141,7 +143,7 @@ Result IApplicationDisplayService::GetDisplayResolution(Out out_width, Out< } Result IApplicationDisplayService::SetLayerScalingMode(NintendoScaleMode scale_mode, u64 layer_id) { - LOG_DEBUG(Service_VI, "called. scale_mode={}, unknown=0x{:016X}", scale_mode, layer_id); + LOG_DEBUG(Service_VI, "called. scale_mode={}, unknown={:#016x}", scale_mode, layer_id); if (scale_mode > NintendoScaleMode::PreserveAspectRatio) { LOG_ERROR(Service_VI, "Invalid scaling mode provided."); @@ -289,6 +291,11 @@ Result IApplicationDisplayService::ConvertScalingMode(Out ou } } +Result IApplicationDisplayService::Cmd2103(Out> out_unk18) { + LOG_WARNING(Service_VI, "(stubbed)"); + R_SUCCEED(); +} + Result IApplicationDisplayService::GetIndirectLayerImageMap( Out out_size, Out out_stride, OutBuffer out_buffer, diff --git a/src/core/hle/service/vi/application_display_service.h b/src/core/hle/service/vi/application_display_service.h index 1bdeb8f845..d2db391efd 100644 --- a/src/core/hle/service/vi/application_display_service.h +++ b/src/core/hle/service/vi/application_display_service.h @@ -1,6 +1,11 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 +#pragma once + #include #include @@ -59,6 +64,7 @@ public: Result GetDisplayVsyncEvent(OutCopyHandle out_vsync_event, u64 display_id); Result ConvertScalingMode(Out out_scaling_mode, NintendoScaleMode mode); + Result Cmd2103(Out> out_unk18); Result GetIndirectLayerImageMap( Out out_size, Out out_stride, OutBuffer out_buffer, diff --git a/src/core/hle/service/vi/application_root_service.cpp b/src/core/hle/service/vi/application_root_service.cpp index 7f35a048da..2204f77ed4 100644 --- a/src/core/hle/service/vi/application_root_service.cpp +++ b/src/core/hle/service/vi/application_root_service.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/vi/application_root_service.h b/src/core/hle/service/vi/application_root_service.h index 15aa4483d6..b66293b73f 100644 --- a/src/core/hle/service/vi/application_root_service.h +++ b/src/core/hle/service/vi/application_root_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/vi/conductor.cpp b/src/core/hle/service/vi/conductor.cpp index 801a135dd7..fd7233cf93 100644 --- a/src/core/hle/service/vi/conductor.cpp +++ b/src/core/hle/service/vi/conductor.cpp @@ -4,7 +4,11 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + +#include "common/adpf.h" #include "common/settings.h" +#include "common/thread.h" #include "core/core.h" #include "core/core_timing.h" #include "core/hle/service/vi/conductor.h" @@ -14,6 +18,8 @@ constexpr auto FrameNs = std::chrono::nanoseconds{1000000000 / 60}; +constexpr s64 UNLOCKED_TARGET_DIVISOR = 4; + namespace Service::VI { Conductor::Conductor(Core::System& system, Container& container, DisplayList& displays) @@ -68,14 +74,19 @@ void Conductor::UnlinkVsyncEvent(u64 display_id, Event* event) { } void Conductor::ProcessVsync() { + Common::PollThreadPolicies(); + Common::ADPF::SetTargetWorkDuration(std::chrono::nanoseconds{this->GetFramePeriodNs()}); + for (auto& [display_id, manager] : m_vsync_managers) { m_container.ComposeOnDisplay(&m_swap_interval, &m_compose_speed_scale, display_id); - manager.SignalVsync(); + manager.SignalVsync(m_system.Kernel()); } } void Conductor::VsyncThread(std::stop_token token) { Common::SetCurrentThreadName("VSyncThread"); + Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh); + Common::SetCurrentThreadToPerformanceCores(); while (!token.stop_requested()) { m_signal.Wait(); @@ -114,4 +125,25 @@ s64 Conductor::GetNextTicks() const { return static_cast(speed_scale * (1000000000.f / effective_fps)); } +s64 Conductor::GetFramePeriodNs() const { + const auto& settings = Settings::values; + f32 speed_scale = 1.f; + bool unlocked = false; + if (settings.use_multi_core.GetValue()) { + if (settings.use_speed_limit.GetValue()) { + speed_scale = 100.f / Settings::SpeedLimit(); + } else { + unlocked = true; + } + } + speed_scale /= m_compose_speed_scale; + + const f32 effective_fps = 60.f / static_cast(m_swap_interval); + s64 period = static_cast(speed_scale * (1000000000.f / effective_fps)); + if (unlocked) { + period /= UNLOCKED_TARGET_DIVISOR; + } + return std::clamp(period, 1'000'000, 100'000'000); +} + } // namespace Service::VI diff --git a/src/core/hle/service/vi/conductor.h b/src/core/hle/service/vi/conductor.h index 59eb2fd022..fdccd48670 100644 --- a/src/core/hle/service/vi/conductor.h +++ b/src/core/hle/service/vi/conductor.h @@ -7,7 +7,7 @@ #pragma once #include -#include +#include "common/container/unordered_map.h" #include "common/common_types.h" #include "common/polyfill_thread.h" @@ -44,11 +44,12 @@ private: void ProcessVsync(); void VsyncThread(std::stop_token token); s64 GetNextTicks() const; + s64 GetFramePeriodNs() const; private: Core::System& m_system; Container& m_container; - ankerl::unordered_dense::map m_vsync_managers; + ::Common::unordered_map m_vsync_managers; std::shared_ptr m_event; Common::Event m_signal; std::jthread m_thread; diff --git a/src/core/hle/service/vi/container.cpp b/src/core/hle/service/vi/container.cpp index e7255c2b0a..9c45f6435b 100644 --- a/src/core/hle/service/vi/container.cpp +++ b/src/core/hle/service/vi/container.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -166,6 +166,16 @@ Result Container::GetLayerZIndex(u64 layer_id, s32* out_z_index) { R_RETURN(VI::ResultNotFound); } +Result Container::SetLayerStackMask(u64 layer_id, u32 layer_stack_mask) { + std::scoped_lock lk{m_lock}; + + auto* const layer = m_layers.GetLayerById(layer_id); + R_UNLESS(layer != nullptr, VI::ResultNotFound); + + m_surface_flinger->SetLayerStackMask(layer->GetConsumerBinderId(), layer_stack_mask); + R_SUCCEED(); +} + Result Container::SetLayerIsOverlay(u64 layer_id, bool is_overlay) { std::scoped_lock lk{m_lock}; diff --git a/src/core/hle/service/vi/container.h b/src/core/hle/service/vi/container.h index 5d111e71ab..e4453a6f72 100644 --- a/src/core/hle/service/vi/container.h +++ b/src/core/hle/service/vi/container.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -68,6 +68,7 @@ public: Result SetLayerZIndex(u64 layer_id, s32 z_index); Result GetLayerZIndex(u64 layer_id, s32* out_z_index); Result SetLayerIsOverlay(u64 layer_id, bool is_overlay); + Result SetLayerStackMask(u64 layer_id, u32 layer_stack_mask); void LinkVsyncEvent(u64 display_id, Event* event); void UnlinkVsyncEvent(u64 display_id, Event* event); diff --git a/src/core/hle/service/vi/display.h b/src/core/hle/service/vi/display.h index fceda75e39..543c6ee68d 100644 --- a/src/core/hle/service/vi/display.h +++ b/src/core/hle/service/vi/display.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/vi/display_list.h b/src/core/hle/service/vi/display_list.h index f710ac472c..bf9cf78a27 100644 --- a/src/core/hle/service/vi/display_list.h +++ b/src/core/hle/service/vi/display_list.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/vi/layer.h b/src/core/hle/service/vi/layer.h index e4c9c98646..9bd6d033ac 100644 --- a/src/core/hle/service/vi/layer.h +++ b/src/core/hle/service/vi/layer.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/vi/layer_list.h b/src/core/hle/service/vi/layer_list.h index 4afca6f403..8fc7cd3ade 100644 --- a/src/core/hle/service/vi/layer_list.h +++ b/src/core/hle/service/vi/layer_list.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/vi/manager_display_service.cpp b/src/core/hle/service/vi/manager_display_service.cpp index e4d22ca11c..34a0175b49 100644 --- a/src/core/hle/service/vi/manager_display_service.cpp +++ b/src/core/hle/service/vi/manager_display_service.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/vi/manager_display_service.h b/src/core/hle/service/vi/manager_display_service.h index 14a107cb43..2e05e730c7 100644 --- a/src/core/hle/service/vi/manager_display_service.h +++ b/src/core/hle/service/vi/manager_display_service.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/vi/manager_root_service.cpp b/src/core/hle/service/vi/manager_root_service.cpp index 0f16a15b4a..094261e060 100644 --- a/src/core/hle/service/vi/manager_root_service.cpp +++ b/src/core/hle/service/vi/manager_root_service.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/vi/manager_root_service.h b/src/core/hle/service/vi/manager_root_service.h index 77cd328699..114dc36c07 100644 --- a/src/core/hle/service/vi/manager_root_service.h +++ b/src/core/hle/service/vi/manager_root_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/vi/service_creator.cpp b/src/core/hle/service/vi/service_creator.cpp index 2b8e5f9577..34dbf7baae 100644 --- a/src/core/hle/service/vi/service_creator.cpp +++ b/src/core/hle/service/vi/service_creator.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/vi/service_creator.h b/src/core/hle/service/vi/service_creator.h index c6ba1797dc..8b50e94af5 100644 --- a/src/core/hle/service/vi/service_creator.h +++ b/src/core/hle/service/vi/service_creator.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/vi/shared_buffer_manager.cpp b/src/core/hle/service/vi/shared_buffer_manager.cpp index 21e42e2094..2380e15d93 100644 --- a/src/core/hle/service/vi/shared_buffer_manager.cpp +++ b/src/core/hle/service/vi/shared_buffer_manager.cpp @@ -4,9 +4,15 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include #include +#include "common/assert.h" +#include "common/logging.h" +#include "common/scratch_buffer.h" #include "core/core.h" +#include "core/hle/kernel/k_page_group.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_system_resource.h" #include "core/hle/service/nvdrv/devices/nvmap.h" @@ -47,7 +53,7 @@ Result AllocateSharedBufferMemory(std::unique_ptr* out_page_ u32* end = system.DeviceMemory().GetPointer(block.GetAddress() + block.GetSize()); for (; start < end; start++) { - *start = 0xFF0000FF; + *start = 0x00000000; } } @@ -168,7 +174,14 @@ constexpr u32 SharedBufferBlockLinearWidth = 1280; constexpr u32 SharedBufferBlockLinearHeight = 768; constexpr u32 SharedBufferBlockLinearStride = SharedBufferBlockLinearWidth * SharedBufferBlockLinearBpp; -constexpr u32 SharedBufferNumSlots = 7; + +constexpr u32 SharedBufferNumCaptureSlots = 3; +constexpr u32 SharedBufferSlotsPerSession = 2; +constexpr u32 SharedBufferMaxSessions = 2; + +constexpr u32 SharedBufferNumSlots = + SharedBufferNumCaptureSlots + SharedBufferSlotsPerSession * SharedBufferMaxSessions; +static_assert(SharedBufferNumSlots <= 16, "Shared buffer pool exceeds the maximum texture count"); constexpr u32 SharedBufferWidth = 1280; constexpr u32 SharedBufferHeight = 720; @@ -192,7 +205,52 @@ constexpr SharedMemoryPoolLayout SharedBufferPoolLayout = [] { return layout; }(); -void MakeGraphicBuffer(android::BufferQueueProducer& producer, u32 slot, u32 handle) { +constexpr u32 GetCaptureSlot(CaptureKind kind) { + return static_cast(kind); +} +static_assert(static_cast(CaptureKind::CallerApplet) + 1 == SharedBufferNumCaptureSlots, + "Capture slot count does not match CaptureKind"); + +constexpr u32 GetPresentationSlot(u32 slot_base, u32 index) { + return SharedBufferNumCaptureSlots + slot_base + index; +} + +constexpr u32 ColorOpaqueBlackRgba32 = 0xFF000000; + +template +void ForEachPoolChunk(Core::System& system, Kernel::KPageGroup& page_group, u64 offset, u64 size, + F&& writer) { + Common::ScratchBuffer scratch; + const u64 range_end = offset + size; + u64 pool_pos = 0; + + for (auto& block : page_group) { + const u64 block_begin = pool_pos; + const u64 block_end = block_begin + block.GetSize(); + pool_pos = block_end; + + if (block_end <= offset) { + continue; + } + if (block_begin >= range_end) { + break; + } + + const u64 chunk_begin = (std::max)(block_begin, offset); + const u64 chunk_end = (std::min)(block_end, range_end); + const u64 chunk_size = chunk_end - chunk_begin; + + u8* const dst = + system.DeviceMemory().GetPointer(block.GetAddress()) + (chunk_begin - block_begin); + + writer(dst, chunk_begin - offset, chunk_size); + + system.GPU().Host1x().MemoryManager().ApplyOpOnPointer( + dst, scratch, [&](DAddr addr) { system.GPU().InvalidateRegion(addr, chunk_size); }); + } +} + +void MakeGraphicBuffer(android::BufferQueueProducer& producer, u32 producer_slot, u32 pool_slot, u32 handle) { auto buffer = std::make_shared(); buffer->width = SharedBufferWidth; buffer->height = SharedBufferHeight; @@ -200,8 +258,8 @@ void MakeGraphicBuffer(android::BufferQueueProducer& producer, u32 slot, u32 han buffer->format = SharedBufferBlockLinearFormat; buffer->external_format = SharedBufferBlockLinearFormat; buffer->buffer_id = handle; - buffer->offset = slot * SharedBufferSlotSize; - ASSERT(producer.SetPreallocatedBuffer(slot, buffer) == android::Status::NoError); + buffer->offset = pool_slot * SharedBufferSlotSize; + ASSERT(producer.SetPreallocatedBuffer(producer_slot, buffer) == android::Status::NoError); } } // namespace @@ -215,59 +273,68 @@ SharedBufferManager::~SharedBufferManager() = default; Result SharedBufferManager::CreateSession(Kernel::KProcess* owner_process, u64* out_buffer_id, u64* out_layer_handle, u64 display_id, bool enable_blending) { - std::scoped_lock lk{m_guard}; + { + std::scoped_lock lk{m_guard}; - // Ensure we haven't already created. - const u64 aruid = owner_process->GetProcessId(); - R_UNLESS(!m_sessions.contains(aruid), VI::ResultPermissionDenied); + // Ensure we haven't already created. + const u64 aruid = owner_process->GetProcessId(); + R_UNLESS(!m_sessions.contains(aruid), VI::ResultPermissionDenied); - // Allocate memory for the shared buffer if needed. - if (!m_buffer_page_group) { - R_TRY(AllocateSharedBufferMemory(std::addressof(m_buffer_page_group), m_system, - SharedBufferSize)); + // Allocate memory for the shared buffer if needed. + if (!m_buffer_page_group) { + R_TRY(AllocateSharedBufferMemory(std::addressof(m_buffer_page_group), m_system, + SharedBufferSize)); - // Record buffer id. - m_buffer_id = m_next_buffer_id++; + // Record buffer id. + m_buffer_id = m_next_buffer_id++; - // Record display id. - m_display_id = display_id; + // Record display id. + m_display_id = display_id; + + for (u32 slot = 0; slot < SharedBufferNumCaptureSlots; slot++) { + ForEachPoolChunk(m_system, *m_buffer_page_group, u64{slot} * SharedBufferSlotSize, + SharedBufferSlotSize, [](u8* dst, u64, u64 length) { + std::fill_n(reinterpret_cast(dst), length / sizeof(u32), + ColorOpaqueBlackRgba32); + }); + } + } + + // Map into process. + Common::ProcessAddress map_address{}; + R_TRY(MapSharedBufferIntoProcessAddressSpace(std::addressof(map_address), m_buffer_page_group, + owner_process, m_system)); + + // Create new session. + auto [it, was_emplaced] = m_sessions.emplace(aruid, SharedBufferSession{}); + auto& session = it->second; + + auto& container = m_nvdrv->GetContainer(); + session.session_id = container.OpenSession(owner_process); + session.nvmap_fd = m_nvdrv->Open("/dev/nvmap", session.session_id); + + // Create an nvmap handle for the buffer and assign the memory to it. + R_TRY(AllocateHandleForBuffer(std::addressof(session.buffer_nvmap_handle), *m_nvdrv, + session.nvmap_fd, map_address, SharedBufferSize)); + + // Create and open a layer for the display. + s32 producer_binder_id; + R_TRY(m_container.CreateStrayLayer(std::addressof(producer_binder_id), + std::addressof(session.layer_id), display_id)); + + // Configure blending and z-index + R_ASSERT(m_container.SetLayerBlending(session.layer_id, enable_blending)); + + // Get the producer and set preallocated buffers. + std::shared_ptr producer; + R_TRY(m_container.GetLayerProducerHandle(std::addressof(producer), session.layer_id)); + for (u32 i = 0; i < SharedBufferSlotsPerSession; i++) + MakeGraphicBuffer(*producer, i, GetPresentationSlot(session.presentation_slot_base, i), session.buffer_nvmap_handle); + + // Assign outputs. + *out_buffer_id = m_buffer_id; + *out_layer_handle = session.layer_id; } - - // Map into process. - Common::ProcessAddress map_address{}; - R_TRY(MapSharedBufferIntoProcessAddressSpace(std::addressof(map_address), m_buffer_page_group, - owner_process, m_system)); - - // Create new session. - auto [it, was_emplaced] = m_sessions.emplace(aruid, SharedBufferSession{}); - auto& session = it->second; - - auto& container = m_nvdrv->GetContainer(); - session.session_id = container.OpenSession(owner_process); - session.nvmap_fd = m_nvdrv->Open("/dev/nvmap", session.session_id); - - // Create an nvmap handle for the buffer and assign the memory to it. - R_TRY(AllocateHandleForBuffer(std::addressof(session.buffer_nvmap_handle), *m_nvdrv, - session.nvmap_fd, map_address, SharedBufferSize)); - - // Create and open a layer for the display. - s32 producer_binder_id; - R_TRY(m_container.CreateStrayLayer(std::addressof(producer_binder_id), - std::addressof(session.layer_id), display_id)); - - // Configure blending and z-index - R_ASSERT(m_container.SetLayerBlending(session.layer_id, enable_blending)); - - // Get the producer and set preallocated buffers. - std::shared_ptr producer; - R_TRY(m_container.GetLayerProducerHandle(std::addressof(producer), session.layer_id)); - MakeGraphicBuffer(*producer, 0, session.buffer_nvmap_handle); - MakeGraphicBuffer(*producer, 1, session.buffer_nvmap_handle); - - // Assign outputs. - *out_buffer_id = m_buffer_id; - *out_layer_handle = session.layer_id; - // We succeeded. R_SUCCEED(); } @@ -336,14 +403,51 @@ Result SharedBufferManager::AcquireSharedFrameBuffer(android::Fence* out_fence, SharedBufferBlockLinearFormat, 0) == android::Status::NoError, VI::ResultOperationFailed); - // Assign remaining outputs. - *out_target_slot = slot; - out_slot_indexes = {0, 1, -1, -1}; + out_slot_indexes.fill(-1); + + { + std::scoped_lock lk{m_guard}; + const auto* const session = this->FindSessionByLayerIdLocked(layer_id); + if (session == nullptr) { + producer->CancelBuffer(slot, *out_fence); + // LOG_DEBUG(Service_VI, "No Session found"); + R_THROW(VI::ResultNotFound); + } + + for (u32 i = 0; i < SharedBufferSlotsPerSession; i++) + out_slot_indexes[i] = + static_cast(GetPresentationSlot(session->presentation_slot_base, i)); + + *out_target_slot = static_cast( + GetPresentationSlot(session->presentation_slot_base, static_cast(slot))); + } // We succeeded. R_SUCCEED(); } +Result SharedBufferManager::GetProducerSlotLocked(s32* out_producer_slot, u64 layer_id, + s64 pool_slot) const { + const auto* const session = this->FindSessionByLayerIdLocked(layer_id); + R_UNLESS(session != nullptr, VI::ResultNotFound); + + const s64 base = GetPresentationSlot(session->presentation_slot_base, 0); + const s64 producer_slot = pool_slot - base; + R_UNLESS(producer_slot >= 0 && producer_slot < SharedBufferSlotsPerSession, + VI::ResultOperationFailed); + + *out_producer_slot = static_cast(producer_slot); + R_SUCCEED(); +} + +const SharedBufferSession* SharedBufferManager::FindSessionByLayerIdLocked(u64 layer_id) const { + for (const auto& [aruid, session] : m_sessions) + if (session.layer_id == layer_id) + return std::addressof(session); + + return nullptr; +} + Result SharedBufferManager::PresentSharedFrameBuffer(android::Fence fence, Common::Rectangle crop_region, u32 transform, s32 swap_interval, u64 layer_id, @@ -352,14 +456,20 @@ Result SharedBufferManager::PresentSharedFrameBuffer(android::Fence fence, std::shared_ptr producer; R_TRY(m_container.GetLayerProducerHandle(std::addressof(producer), layer_id)); + s32 producer_slot; + { + std::scoped_lock lk{m_guard}; + R_TRY(this->GetProducerSlotLocked(std::addressof(producer_slot), layer_id, slot)); + } + // Request to queue the buffer. std::shared_ptr buffer; - R_UNLESS(producer->RequestBuffer(static_cast(slot), std::addressof(buffer)) == + R_UNLESS(producer->RequestBuffer(producer_slot, std::addressof(buffer)) == android::Status::NoError, VI::ResultOperationFailed); ON_RESULT_FAILURE { - producer->CancelBuffer(static_cast(slot), fence); + producer->CancelBuffer(producer_slot, fence); }; // Queue the buffer to the producer. @@ -369,12 +479,10 @@ Result SharedBufferManager::PresentSharedFrameBuffer(android::Fence fence, input.fence = fence; input.transform = static_cast(transform); input.swap_interval = swap_interval; - R_UNLESS(producer->QueueBuffer(static_cast(slot), input, std::addressof(output)) == + R_UNLESS(producer->QueueBuffer(producer_slot, input, std::addressof(output)) == android::Status::NoError, VI::ResultOperationFailed); - (void)m_container.SetLayerZIndex(layer_id, 100000); - // We succeeded. R_SUCCEED(); } @@ -384,8 +492,14 @@ Result SharedBufferManager::CancelSharedFrameBuffer(u64 layer_id, s64 slot) { std::shared_ptr producer; R_TRY(m_container.GetLayerProducerHandle(std::addressof(producer), layer_id)); + s32 producer_slot; + { + std::scoped_lock lk{m_guard}; + R_TRY(this->GetProducerSlotLocked(std::addressof(producer_slot), layer_id, slot)); + } + // Cancel. - producer->CancelBuffer(static_cast(slot), android::Fence::NoFence()); + producer->CancelBuffer(producer_slot, android::Fence::NoFence()); // We succeeded. R_SUCCEED(); @@ -404,31 +518,47 @@ Result SharedBufferManager::GetSharedFrameBufferAcquirableEvent(Kernel::KReadabl R_SUCCEED(); } -Result SharedBufferManager::WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index) { - std::vector capture_buffer(m_system.GPU().GetAppletCaptureBuffer()); - Common::ScratchBuffer scratch; +Result SharedBufferManager::WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index, CaptureKind kind) { + std::scoped_lock lk{m_guard}; + R_UNLESS(m_buffer_page_group != nullptr, VI::ResultNotFound); - // TODO: this could be optimized - s64 e = -1280 * 768 * 4; - for (auto& block : *m_buffer_page_group) { - u8* start = m_system.DeviceMemory().GetPointer(block.GetAddress()); - u8* end = m_system.DeviceMemory().GetPointer(block.GetAddress() + block.GetSize()); + const std::vector capture = m_system.GPU().GetAppletCaptureBuffer(); + const u32 slot = GetCaptureSlot(kind); - for (; start < end; start++) { - *start = 0; - if (e >= 0 && e < static_cast(capture_buffer.size())) { - *start = capture_buffer[e]; - } - e++; - } - - m_system.GPU().Host1x().MemoryManager().ApplyOpOnPointer(start, scratch, [&](DAddr addr) { - m_system.GPU().InvalidateRegion(addr, end - start); - }); + if (capture.size() < SharedBufferSlotSize) { + //LOG_WARNING(Service_VI, "Capture buffer is {} bytes, expected at least {}; not writing", + // capture.size(), SharedBufferSlotSize); + *out_was_written = false; + *out_layer_index = static_cast(slot); + R_SUCCEED(); } + ForEachPoolChunk(m_system, *m_buffer_page_group, u64{slot} * SharedBufferSlotSize, + SharedBufferSlotSize, + [&](u8* dst, u64 src_offset, u64 length) { + std::memcpy(dst, capture.data() + src_offset, length); + }); + *out_was_written = true; - *out_layer_index = 1; + *out_layer_index = static_cast(slot); + R_SUCCEED(); +} + +Result SharedBufferManager::ClearAppletCaptureBuffer(s32 layer_index, u32 color) { + std::scoped_lock lk{m_guard}; + R_UNLESS(m_buffer_page_group != nullptr, VI::ResultNotFound); + + if (layer_index < 0 || layer_index >= static_cast(SharedBufferNumCaptureSlots)) { + LOG_WARNING(Service_VI, "Couldnt clear non-capture slot {}", layer_index); + R_SUCCEED(); + } + + ForEachPoolChunk(m_system, *m_buffer_page_group, u64{static_cast(layer_index)} * SharedBufferSlotSize, + SharedBufferSlotSize, [&](u8* dst, u64 src_offset, u64 length) { + ASSERT(src_offset % sizeof(u32) == 0 && length % sizeof(u32) == 0); + std::fill_n(reinterpret_cast(dst), length / sizeof(u32), color); + }); + R_SUCCEED(); } diff --git a/src/core/hle/service/vi/shared_buffer_manager.h b/src/core/hle/service/vi/shared_buffer_manager.h index 9ef6234867..807db10f31 100644 --- a/src/core/hle/service/vi/shared_buffer_manager.h +++ b/src/core/hle/service/vi/shared_buffer_manager.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -48,6 +48,12 @@ static_assert(sizeof(SharedMemoryPoolLayout) == 0x188, "SharedMemoryPoolLayout h struct SharedBufferSession; +enum class CaptureKind : u32 { + LastApplication, + LastForeground, + CallerApplet, +}; + class SharedBufferManager final { public: explicit SharedBufferManager(Core::System& system, Container& container, @@ -68,9 +74,15 @@ public: Result CancelSharedFrameBuffer(u64 layer_id, s64 slot); Result GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event, u64 layer_id); - Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index); + Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index, CaptureKind kind); + Result ClearAppletCaptureBuffer(s32 layer_index, u32 color); private: + const SharedBufferSession* FindSessionByLayerIdLocked(u64 layer_id) const; + + /// Converts a pool slot index, which is what the guest works in, back to the buffer queues slot index + Result GetProducerSlotLocked(s32* out_producer_slot, u64 layer_id, s64 pool_slot) const; + u64 m_next_buffer_id = 1; u64 m_display_id = 0; u64 m_buffer_id = 0; @@ -89,6 +101,7 @@ struct SharedBufferSession { Nvidia::NvCore::SessionId session_id = {}; u64 layer_id = {}; u32 buffer_nvmap_handle = 0; + u32 presentation_slot_base = 0; }; } // namespace Service::VI diff --git a/src/core/hle/service/vi/system_display_service.cpp b/src/core/hle/service/vi/system_display_service.cpp index 0956053dcf..aa225f69df 100644 --- a/src/core/hle/service/vi/system_display_service.cpp +++ b/src/core/hle/service/vi/system_display_service.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project diff --git a/src/core/hle/service/vi/system_display_service.h b/src/core/hle/service/vi/system_display_service.h index d6cc8c75f1..a3b88c519d 100644 --- a/src/core/hle/service/vi/system_display_service.h +++ b/src/core/hle/service/vi/system_display_service.h @@ -1,9 +1,11 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 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 +#pragma once + #include "common/math_util.h" #include "core/hle/service/cmif_types.h" #include "core/hle/service/nvnflinger/ui/fence.h" diff --git a/src/core/hle/service/vi/system_root_service.cpp b/src/core/hle/service/vi/system_root_service.cpp index 3489727d81..c761f136e2 100644 --- a/src/core/hle/service/vi/system_root_service.cpp +++ b/src/core/hle/service/vi/system_root_service.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/vi/system_root_service.h b/src/core/hle/service/vi/system_root_service.h index 9d5aa53d38..1587e0e30f 100644 --- a/src/core/hle/service/vi/system_root_service.h +++ b/src/core/hle/service/vi/system_root_service.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index b388efaf6f..2ea64f5d32 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/vi/vi.h b/src/core/hle/service/vi/vi.h index 7c1f350d84..bcc55228aa 100644 --- a/src/core/hle/service/vi/vi.h +++ b/src/core/hle/service/vi/vi.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/vi/vi_results.h b/src/core/hle/service/vi/vi_results.h index 22bac799f0..89b7317792 100644 --- a/src/core/hle/service/vi/vi_results.h +++ b/src/core/hle/service/vi/vi_results.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/vi/vi_types.h b/src/core/hle/service/vi/vi_types.h index 95ff663582..2d4287169a 100644 --- a/src/core/hle/service/vi/vi_types.h +++ b/src/core/hle/service/vi/vi_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/core/hle/service/vi/vsync_manager.cpp b/src/core/hle/service/vi/vsync_manager.cpp index bdc4dfa966..59d0b82e0a 100644 --- a/src/core/hle/service/vi/vsync_manager.cpp +++ b/src/core/hle/service/vi/vsync_manager.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -9,18 +12,10 @@ namespace Service::VI { VsyncManager::VsyncManager() = default; VsyncManager::~VsyncManager() = default; -void VsyncManager::SignalVsync() { +void VsyncManager::SignalVsync(Kernel::KernelCore& kernel) { for (auto* event : m_vsync_events) { - event->Signal(); + event->Signal(kernel); } } -void VsyncManager::LinkVsyncEvent(Event* event) { - m_vsync_events.insert(event); -} - -void VsyncManager::UnlinkVsyncEvent(Event* event) { - m_vsync_events.erase(event); -} - } // namespace Service::VI diff --git a/src/core/hle/service/vi/vsync_manager.h b/src/core/hle/service/vi/vsync_manager.h index 5d45bb5eec..9c04516749 100644 --- a/src/core/hle/service/vi/vsync_manager.h +++ b/src/core/hle/service/vi/vsync_manager.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -5,6 +8,10 @@ #include +namespace Kernel { +class KernelCore; +} + namespace Service { class Event; } @@ -18,9 +25,13 @@ public: explicit VsyncManager(); ~VsyncManager(); - void SignalVsync(); - void LinkVsyncEvent(Event* event); - void UnlinkVsyncEvent(Event* event); + void SignalVsync(Kernel::KernelCore& kernel); + void LinkVsyncEvent(Event* event) { + m_vsync_events.insert(event); + } + void UnlinkVsyncEvent(Event* event) { + m_vsync_events.erase(event); + } private: std::set m_vsync_events; diff --git a/src/core/hle/service/wlan/wlan.cpp b/src/core/hle/service/wlan/wlan.cpp new file mode 100644 index 0000000000..bcd357d57a --- /dev/null +++ b/src/core/hle/service/wlan/wlan.cpp @@ -0,0 +1,260 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/core.h" +#include "core/hle/result.h" +#include "core/hle/service/wlan/wlan.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/cmif_serialization.h" +#include "core/hle/service/cmif_types.h" +#include "core/hle/service/service.h" +#include "core/hle/service/server_manager.h" + +namespace Service::WLAN { + +class ILocalManager final : public ServiceFramework { +public: + explicit ILocalManager(Core::System& system_) + : ServiceFramework{system_, "wlan:lcl"} + { + static const FunctionInfo functions[] = { + { 0, nullptr, "OpenMasterMode" }, + { 0, nullptr, "OpenMode_2" }, + { 1, nullptr, "CloseMasterMode" }, + { 1, nullptr, "CloseMode_2" }, + { 2, nullptr, "OpenClientMode" }, + { 2, nullptr, "GetMacAddress_2" }, + { 3, nullptr, "CloseClientMode" }, + { 3, nullptr, "CreateBss" }, + { 4, nullptr, "OpenSpectatorMode" }, + { 4, nullptr, "DestroyBss" }, + { 5, nullptr, "CloseSpectatorMode" }, + { 5, nullptr, "StartScan_2" }, + { 6, nullptr, "GetMacAddress_2" }, + { 6, nullptr, "StopScan_2" }, + { 7, nullptr, "CreateBss" }, + { 7, nullptr, "Connect_2" }, + { 8, nullptr, "DestroyBss" }, + { 8, nullptr, "CancelConnect_2" }, + { 9, nullptr, "StartScan_2" }, + { 9, nullptr, "Join" }, + { 10, nullptr, "StopScan_2" }, + { 10, nullptr, "CancelJoin" }, + { 11, nullptr, "Connect_2" }, + { 11, nullptr, "Disconnect_2" }, + { 12, nullptr, "CancelConnect_2" }, + { 12, nullptr, "SetBeaconLostCount" }, + { 13, nullptr, "Join" }, + { 13, nullptr, "GetSystemEvent_2" }, + { 14, nullptr, "CancelJoin" }, + { 14, nullptr, "GetConnectionStatus_2" }, + { 15, nullptr, "Disconnect_2" }, + { 15, nullptr, "GetClientStatus" }, + { 16, nullptr, "SetBeaconLostCount" }, + { 16, nullptr, "GetBssIndicationEvent" }, + { 17, nullptr, "GetSystemEvent_2" }, + { 17, nullptr, "GetBssIndicationInfo" }, + { 18, nullptr, "GetConnectionStatus_2" }, + { 18, nullptr, "GetState_2" }, + { 19, nullptr, "GetClientStatus" }, + { 19, nullptr, "GetAllowedChannels" }, + { 20, nullptr, "GetBssIndicationEvent" }, + { 20, nullptr, "AddIe" }, + { 21, nullptr, "GetBssIndicationInfo" }, + { 21, nullptr, "DeleteIe" }, + { 22, nullptr, "GetState_2" }, + { 22, nullptr, "PutFrameRaw" }, + { 23, nullptr, "GetAllowedChannels" }, + { 23, nullptr, "CancelGetFrame" }, + { 24, nullptr, "AddIe" }, + { 24, nullptr, "CreateRxEntry" }, + { 25, nullptr, "DeleteIe" }, + { 25, nullptr, "DeleteRxEntry" }, + { 26, nullptr, "PutFrameRaw" }, + { 26, nullptr, "AddEthertypeToRxEntry" }, + { 27, nullptr, "CancelGetFrame" }, + { 27, nullptr, "DeleteEthertypeFromRxEntry" }, + { 28, nullptr, "CreateRxEntry" }, + { 28, nullptr, "AddMatchingDataToRxEntry" }, + { 29, nullptr, "DeleteRxEntry" }, + { 29, nullptr, "RemoveMatchingDataFromRxEntry" }, + { 30, nullptr, "AddEthertypeToRxEntry" }, + { 30, nullptr, "GetScanResult_2" }, + { 31, nullptr, "DeleteEthertypeFromRxEntry" }, + { 31, nullptr, "PutActionFrameOneShot" }, + { 32, nullptr, "AddMatchingDataToRxEntry" }, + { 32, nullptr, "SetActionFrameWithBeacon" }, + { 33, nullptr, "RemoveMatchingDataFromRxEntry" }, + { 33, nullptr, "CancelActionFrameWithBeacon" }, + { 34, nullptr, "GetScanResult_2" }, + { 34, nullptr, "CreateRxEntryForActionFrame" }, + { 35, nullptr, "PutActionFrameOneShot" }, + { 35, nullptr, "DeleteRxEntryForActionFrame" }, + { 36, nullptr, "SetActionFrameWithBeacon" }, + { 36, nullptr, "AddSubtypeToRxEntryForActionFrame" }, + { 37, nullptr, "CancelActionFrameWithBeacon" }, + { 37, nullptr, "DeleteSubtypeFromRxEntryForActionFrame" }, + { 38, nullptr, "CreateRxEntryForActionFrame" }, + { 38, nullptr, "CancelGetActionFrame" }, + { 39, nullptr, "DeleteRxEntryForActionFrame" }, + { 39, nullptr, "GetRssi_2" }, + { 40, nullptr, "AddSubtypeToRxEntryForActionFrame" }, + { 40, nullptr, "SetMaxAssociationNumber" }, + { 41, nullptr, "DeleteSubtypeFromRxEntryForActionFrame" }, + { 41, nullptr, "Cmd41" }, + { 42, nullptr, "CancelGetActionFrame" }, + { 42, nullptr, "Cmd42" }, + { 43, nullptr, "GetRssi_2" }, + { 43, nullptr, "Cmd43" }, + { 44, nullptr, "SetMaxAssociationNumber" }, + { 45, nullptr, "OpenLcsMasterMode" }, + { 46, nullptr, "CloseLcsMasterMode" }, + { 47, nullptr, "OpenLcsClientMode" }, + { 48, nullptr, "CloseLcsClientMode" }, + { 49, nullptr, "GetChannelStats" }, + { 50, nullptr, "Cmd50" }, + { 51, nullptr, "Cmd51" }, + { 52, nullptr, "Cmd52" }, + }; + RegisterHandlers(functions); + } +}; + +class ILocalGetFrame final : public ServiceFramework { +public: + explicit ILocalGetFrame(Core::System& system_) + : ServiceFramework{system_, "wlan:lg"} + { + static const FunctionInfo functions[] = { + { 0, nullptr, "GetFrameRaw" }, + }; + RegisterHandlers(functions); + } +}; + +class ILocalGetActionFrame final : public ServiceFramework { +public: + explicit ILocalGetActionFrame(Core::System& system_) + : ServiceFramework{system_, "wlan:lga"} + { + static const FunctionInfo functions[] = { + { 0, nullptr, "GetActionFrame" }, + }; + RegisterHandlers(functions); + } +}; + +class ISocketGetFrame final : public ServiceFramework { +public: + explicit ISocketGetFrame(Core::System& system_) + : ServiceFramework{system_, "wlan:sg"} + { + static const FunctionInfo functions[] = { + { 0, nullptr, "GetFrameRaw" }, + }; + RegisterHandlers(functions); + } +}; + +class ISocketManager final : public ServiceFramework { +public: + explicit ISocketManager(Core::System& system_) + : ServiceFramework{system_, "wlan:soc"} + { + static const FunctionInfo functions[] = { + { 0, nullptr, "PutFrameRaw_2" }, + { 1, nullptr, "CancelGetFrame_2" }, + { 2, nullptr, "CreateRxEntry_2" }, + { 3, nullptr, "DeleteRxEntry_2" }, + { 4, nullptr, "AddEthertypeToRxEntry_2" }, + { 5, nullptr, "DeleteEthertypeFromRxEntry_2" }, + { 6, nullptr, "GetMacAddress_3" }, + { 7, nullptr, "SwitchTsfTimerFunction" }, + { 8, nullptr, "GetDeltaTimeBetweenSystemAndTsf" }, + { 9, nullptr, "RegisterSharedMemory" }, + { 10, nullptr, "UnregisterSharedMemory" }, + { 11, nullptr, "EnableSharedMemory" }, + { 12, nullptr, "SetMulticastFilter" }, + }; + RegisterHandlers(functions); + } +}; + +class IDetectManager final : public ServiceFramework { +public: + explicit IDetectManager(Core::System& system_) + : ServiceFramework{system_, "wlan:dtc"} + { + static const FunctionInfo functions[] = { + { 0, nullptr, "Cmd0" }, + { 1, nullptr, "Cmd1" }, + { 2, nullptr, "Cmd2" }, + { 3, nullptr, "Cmd3" }, + { 4, nullptr, "Cmd4" }, + { 5, nullptr, "Cmd5" }, + { 6, nullptr, "Cmd6" }, + { 7, nullptr, "Cmd7" }, + { 8, nullptr, "Cmd8" }, + { 9, nullptr, "Cmd9" }, + { 10, nullptr, "Cmd10" }, + { 11, nullptr, "Cmd11" }, + { 12, nullptr, "Cmd12" }, + { 13, nullptr, "Cmd13" }, + { 14, nullptr, "Cmd14" }, + { 15, nullptr, "Cmd15" }, + { 16, nullptr, "Cmd16" }, + { 17, nullptr, "Cmd17" }, + { 18, nullptr, "Cmd18" }, + { 19, nullptr, "Cmd19" }, + { 20, nullptr, "Cmd20" }, + { 21, nullptr, "Cmd21" }, + { 22, nullptr, "Cmd22" }, + { 23, nullptr, "Cmd23" }, + { 24, nullptr, "Cmd24" }, + { 25, nullptr, "Cmd25" }, + { 26, nullptr, "Cmd26" }, + { 27, nullptr, "Cmd27" }, + }; + RegisterHandlers(functions); + } +}; + +class IPrivateServiceCreator final : public ServiceFramework { +public: + explicit IPrivateServiceCreator(Core::System& system_) + : ServiceFramework{system_, "wlan:p"} + { + static const FunctionInfo functions[] = { + { 0, nullptr, "CreateWirelessCommunicationService" }, + { 1, nullptr, "CreatePrivateWirelessCommunicationService" }, + }; + RegisterHandlers(functions); + } +}; + +class ISfDriverServiceCreator final : public ServiceFramework { +public: + explicit ISfDriverServiceCreator(Core::System& system_) + : ServiceFramework{system_, "wlan:nd"} + { + static const FunctionInfo functions[] = { + { 0, nullptr, "CreateDriverService" }, + }; + RegisterHandlers(functions); + } +}; + +void LoopProcess(Core::System& system) { + auto server_manager = std::make_unique(system); + server_manager->RegisterNamedService("wlan:lcl", std::make_shared(system), 10); + server_manager->RegisterNamedService("wlan:lg", std::make_shared(system), 10); + server_manager->RegisterNamedService("wlan:lga", std::make_shared(system), 10); + server_manager->RegisterNamedService("wlan:sg", std::make_shared(system), 10); + server_manager->RegisterNamedService("wlan:soc", std::make_shared(system), 10); + server_manager->RegisterNamedService("wlan:dtc", std::make_shared(system), 4); + server_manager->RegisterNamedService("wlan:p", std::make_shared(system), 30); + server_manager->RegisterNamedService("wlan:nd", std::make_shared(system), 5); + ServerManager::RunServer(std::move(server_manager)); +} + +} // namespace Service::WLAN diff --git a/src/core/hle/service/wlan/wlan.h b/src/core/hle/service/wlan/wlan.h new file mode 100644 index 0000000000..45589a5ec0 --- /dev/null +++ b/src/core/hle/service/wlan/wlan.h @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +namespace Core { +class System; +} + +namespace Service::WLAN { +void LoopProcess(Core::System& system); +} // namespace Service::WLAN diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp index 1b27934f69..01d37fb35b 100644 --- a/src/core/internal_network/network.cpp +++ b/src/core/internal_network/network.cpp @@ -28,7 +28,6 @@ #include "common/assert.h" #include "common/common_types.h" -#include "common/expected.h" #include "common/logging.h" #include "common/settings.h" #include "core/internal_network/network.h" @@ -326,52 +325,37 @@ Errno GetAndLogLastError(CallType call_type = CallType::Other) { GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int gai_err) { switch (gai_err) { - case 0: - return GetAddrInfoError::SUCCESS; + case 0: return GetAddrInfoError::SUCCESS; + case EAI_AGAIN: return GetAddrInfoError::AGAIN; + case EAI_BADFLAGS: return GetAddrInfoError::BADFLAGS; + case EAI_FAIL: return GetAddrInfoError::FAIL; + case EAI_FAMILY: return GetAddrInfoError::FAMILY; + case EAI_MEMORY: return GetAddrInfoError::MEMORY; + case EAI_NONAME: return GetAddrInfoError::NONAME; + case EAI_SERVICE: return GetAddrInfoError::SERVICE; + case EAI_SOCKTYPE: return GetAddrInfoError::SOCKTYPE; + // These codes may not be defined on all systems: #ifdef EAI_ADDRFAMILY - case EAI_ADDRFAMILY: - return GetAddrInfoError::ADDRFAMILY; + case EAI_ADDRFAMILY: return GetAddrInfoError::ADDRFAMILY; #endif - case EAI_AGAIN: - return GetAddrInfoError::AGAIN; - case EAI_BADFLAGS: - return GetAddrInfoError::BADFLAGS; - case EAI_FAIL: - return GetAddrInfoError::FAIL; - case EAI_FAMILY: - return GetAddrInfoError::FAMILY; - case EAI_MEMORY: - return GetAddrInfoError::MEMORY; - case EAI_NONAME: - return GetAddrInfoError::NONAME; - case EAI_SERVICE: - return GetAddrInfoError::SERVICE; - case EAI_SOCKTYPE: - return GetAddrInfoError::SOCKTYPE; - // These codes may not be defined on all systems: #ifdef EAI_SYSTEM - case EAI_SYSTEM: - return GetAddrInfoError::SYSTEM; + case EAI_SYSTEM: return GetAddrInfoError::SYSTEM; #endif #ifdef EAI_BADHINTS - case EAI_BADHINTS: - return GetAddrInfoError::BADHINTS; + case EAI_BADHINTS: return GetAddrInfoError::BADHINTS; #endif #ifdef EAI_PROTOCOL - case EAI_PROTOCOL: - return GetAddrInfoError::PROTOCOL; + case EAI_PROTOCOL: return GetAddrInfoError::PROTOCOL; #endif #ifdef EAI_OVERFLOW - case EAI_OVERFLOW: - return GetAddrInfoError::OVERFLOW_; + case EAI_OVERFLOW: return GetAddrInfoError::OVERFLOW_; #endif default: #ifdef EAI_NODATA // This can't be a case statement because it would create a duplicate // case on Windows where EAI_NODATA is an alias for EAI_NONAME. - if (gai_err == EAI_NODATA) { + if (gai_err == EAI_NODATA) return GetAddrInfoError::NODATA; - } #endif return GetAddrInfoError::OTHER; } @@ -405,14 +389,10 @@ Type TranslateTypeFromNative(int type) { switch (type) { case 0: return Type::Unspecified; - case SOCK_STREAM: - return Type::STREAM; - case SOCK_DGRAM: - return Type::DGRAM; - case SOCK_RAW: - return Type::RAW; - case SOCK_SEQPACKET: - return Type::SEQPACKET; + case SOCK_STREAM: return Type::STREAM; + case SOCK_DGRAM: return Type::DGRAM; + case SOCK_RAW: return Type::RAW; + case SOCK_SEQPACKET: return Type::SEQPACKET; default: UNIMPLEMENTED_MSG("Unimplemented type={}", type); return Type::STREAM; @@ -423,55 +403,228 @@ int TranslateTypeToNative(Type type) { switch (type) { case Type::Unspecified: return 0; - case Type::STREAM: - return SOCK_STREAM; - case Type::DGRAM: - return SOCK_DGRAM; - case Type::RAW: - return SOCK_RAW; + case Type::STREAM: return SOCK_STREAM; + case Type::DGRAM: return SOCK_DGRAM; + case Type::RAW: return SOCK_RAW; + case Type::SEQPACKET: return SOCK_SEQPACKET; default: UNIMPLEMENTED_MSG("Unimplemented type={}", type); return 0; } } -Protocol TranslateProtocolFromNative(int protocol) { +// Some of those protocols may not be supported on some platforms +// It doesn't really matter, except that some homebrew may not work correctly +// Official software uses TCP & UDP mainly, SCTP is used by some homebrew as well +#ifdef __FreeBSD__ +#define NETWORK_PROTOCOL_TRANSLATE_LIST \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ICMP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TCP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(UDP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IPV6) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(RAW) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IGMP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(GGP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IPV4) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ST) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(EGP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PIGP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(RCCMON) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(NVPII) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PUP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ARGUS) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(EMCON) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(XNET) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(CHAOS) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(MUX) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(MEAS) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(HMP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PRM) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IDP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TRUNK1) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TRUNK2) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(LEAF1) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(LEAF2) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(RDP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IRTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(BLT) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(NSP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(INP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(DCCP) \ + /*NETWORK_PROTOCOL_TRANSLATE_ELEM(3PC)*/ \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IDPR) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(XTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(DDP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(CMTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TPXX) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IL) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SDRP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ROUTING) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(FRAGMENT) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IDRP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(RSVP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(GRE) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(MHRP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(BHA) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ESP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(AH) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(INLSP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SWIPE) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(NHRP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(MOBILE) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TLSP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SKIP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ICMPV6) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(NONE) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(DSTOPTS) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(AHIP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(CFTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(HELLO) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SATEXPAK) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(KRYPTOLAN) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(RVD) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IPPC) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ADFS) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SATMON) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(VISA) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IPCV) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(CPNX) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(CPHB) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(WSN) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PVP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(BRSATMON) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ND) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(WBMON) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(WBEXPAK) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(EON) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(VMTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SVMTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(VINES) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IGP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(DGP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TCF) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IGRP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(OSPFIGP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SRPC) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(LARP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(MTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(AX25) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IPEIP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(MICP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SCCSP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ETHERIP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ENCAP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(APES) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(GMTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IPCOMP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SCTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(MH) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(UDPLITE) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(HIP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SHIM6) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PIM) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(CARP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PGM) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(MPLS) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PFSYNC) +#elif defined(__linux__) +// Other platforms may not support some niche protocols. +// This is usually not an issue +#define NETWORK_PROTOCOL_TRANSLATE_LIST \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IP) \ + /*NETWORK_PROTOCOL_TRANSLATE_ELEM(HOPOPTS)*/ \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ICMP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IGMP) \ + /*NETWORK_PROTOCOL_TRANSLATE_ELEM(IPIP)*/ \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TCP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(EGP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PUP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(UDP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IDP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(DCCP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IPV6) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ROUTING) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(FRAGMENT) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(RSVP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(GRE) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ESP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(AH) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ICMPV6) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(NONE) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(DSTOPTS) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(MTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ENCAP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PIM) \ + /*NETWORK_PROTOCOL_TRANSLATE_ELEM(COMP)*/ \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SCTP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(UDPLITE) +#elif defined(_WIN32) +#define NETWORK_PROTOCOL_TRANSLATE_LIST \ + /*NETWORK_PROTOCOL_TRANSLATE_ELEM(HOPOPTS)*/ \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ICMP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IGMP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(GGP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IPV4) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ST) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TCP) \ + /*NETWORK_PROTOCOL_TRANSLATE_ELEM(CBT)*/ \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(EGP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IGP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PUP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(UDP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IDP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(RDP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(IPV6) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ROUTING) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(FRAGMENT) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ESP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(AH) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ICMPV6) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(NONE) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(DSTOPTS) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(ND) \ + /*NETWORK_PROTOCOL_TRANSLATE_ELEM(ICLFXBM)*/ \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PIM) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(PGM) \ + /*NETWORK_PROTOCOL_TRANSLATE_ELEM(L2TP)*/ \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SCTP) +#else +#define NETWORK_PROTOCOL_TRANSLATE_LIST \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(TCP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(UDP) \ + NETWORK_PROTOCOL_TRANSLATE_ELEM(SCTP) +#endif +[[nodiscard]] Protocol TranslateProtocolFromNative(u32 protocol) { switch (protocol) { - case 0: - return Protocol::Unspecified; - case IPPROTO_TCP: - return Protocol::TCP; - case IPPROTO_UDP: - return Protocol::UDP; +#define NETWORK_PROTOCOL_TRANSLATE_ELEM(x) case IPPROTO_##x: return Protocol::x; + NETWORK_PROTOCOL_TRANSLATE_LIST +#undef NETWORK_PROTOCOL_TRANSLATE_ELEM default: UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol); - return Protocol::Unspecified; + return Protocol::IP; } } - -int TranslateProtocolToNative(Protocol protocol) { +[[nodiscard]] u32 TranslateProtocolToNative(Protocol protocol) { switch (protocol) { - case Protocol::Unspecified: - return 0; - case Protocol::TCP: - return IPPROTO_TCP; - case Protocol::UDP: - return IPPROTO_UDP; +#define NETWORK_PROTOCOL_TRANSLATE_ELEM(x) case Protocol::x: return IPPROTO_##x; + NETWORK_PROTOCOL_TRANSLATE_LIST +#undef NETWORK_PROTOCOL_TRANSLATE_ELEM default: UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol); return 0; } } +#undef NETWORK_PROTOCOL_TRANSLATE_LIST SockAddrIn TranslateToSockAddrIn(sockaddr_in input, size_t input_len) { - SockAddrIn result; - + SockAddrIn result{}; result.family = TranslateDomainFromNative(input.sin_family); - result.portno = ntohs(input.sin_port); - result.ip = TranslateIPv4(input.sin_addr); - return result; } @@ -500,13 +653,13 @@ short TranslatePollEvents(PollEvents events) { // Unlike poll on other OSes, WSAPoll will complain if any other flags are set on input. if (result & ~allowed_events) { LOG_DEBUG(Network, - "Removing WSAPoll input events 0x{:x} because Windows doesn't support them", + "Removing WSAPoll input events {:#x} because Windows doesn't support them", result & ~allowed_events); } result &= allowed_events; #endif - UNIMPLEMENTED_IF_MSG((u16)events != 0, "Unhandled guest events=0x{:x}", (u16)events); + UNIMPLEMENTED_IF_MSG((u16)events != 0, "Unhandled guest events={:#x}", (u16)events); return result; } @@ -530,7 +683,7 @@ PollEvents TranslatePollRevents(short revents) { translate(POLLRDBAND, PollEvents::RdBand); translate(POLLWRBAND, PollEvents::WrBand); - UNIMPLEMENTED_IF_MSG(revents != 0, "Unhandled host revents=0x{:x}", revents); + UNIMPLEMENTED_IF_MSG(revents != 0, "Unhandled host revents={:#x}", revents); return result; } @@ -580,15 +733,14 @@ u32 IPv4AddressToInteger(IPv4Address ip_addr) { static_cast(ip_addr[2]) << 8 | static_cast(ip_addr[3]); } -Common::Expected, GetAddrInfoError> GetAddressInfo( +std::variant, GetAddrInfoError> GetAddressInfo( const std::string& host, const std::optional& service) { addrinfo hints{}; hints.ai_family = AF_INET; // Switch only supports IPv4. addrinfo* addrinfo; - s32 gai_err = getaddrinfo(host.c_str(), service.has_value() ? service->c_str() : nullptr, - &hints, &addrinfo); + s32 gai_err = getaddrinfo(host.c_str(), service.has_value() ? service->c_str() : nullptr, &hints, &addrinfo); if (gai_err != 0) { - return Common::Unexpected(TranslateGetAddrInfoErrorFromNative(gai_err)); + return TranslateGetAddrInfoErrorFromNative(gai_err); } std::vector ret; for (auto* current = addrinfo; current; current = current->ai_next) { @@ -685,8 +837,7 @@ Errno Socket::SetSockOpt(SOCKET fd_so, int option, T value) { } Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) { - fd = socket(TranslateDomainToNative(domain), TranslateTypeToNative(type), - TranslateProtocolToNative(protocol)); + fd = socket(TranslateDomainToNative(domain), TranslateTypeToNative(type), TranslateProtocolToNative(protocol)); if (fd != INVALID_SOCKET) { return Errno::SUCCESS; } diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h index ce259c0e66..2c9ae523c5 100644 --- a/src/core/internal_network/network.h +++ b/src/core/internal_network/network.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "common/common_funcs.h" #include "common/common_types.h" @@ -124,7 +125,6 @@ std::string IPv4AddressToString(IPv4Address ip_addr); u32 IPv4AddressToInteger(IPv4Address ip_addr); // named to avoid name collision with Windows macro -Common::Expected, GetAddrInfoError> GetAddressInfo( - const std::string& host, const std::optional& service); +std::variant, GetAddrInfoError> GetAddressInfo(const std::string& host, const std::optional& service); } // namespace Network diff --git a/src/core/internal_network/wifi_scanner.cpp b/src/core/internal_network/wifi_scanner.cpp index 8c91f8b524..67c0318c41 100644 --- a/src/core/internal_network/wifi_scanner.cpp +++ b/src/core/internal_network/wifi_scanner.cpp @@ -114,13 +114,13 @@ std::vector ScanWifiNetworks(std::chrono::milliseconds deadli char ifname[IFNAMSIZ] = {0}; char *args[1] = {ifname}; - iw_enum_devices(sock, [](int skfd, char* ifname, char* args[], int count) -> int { + iw_enum_devices(sock, [](int f_skfd, char* f_ifname, char* f_args[], int) -> int { iwrange range; - int res = iw_get_range_info(skfd, ifname, &range); - LOG_INFO(Network, "ifname {} returned {} on iw_get_range_info", ifname, res); + int res = iw_get_range_info(f_skfd, f_ifname, &range); + LOG_INFO(Network, "ifname {} returned {} on iw_get_range_info", f_ifname, res); if (res >= 0) { - strncpy(args[0], ifname, IFNAMSIZ - 1); - args[0][IFNAMSIZ - 1] = 0; + strncpy(f_args[0], f_ifname, IFNAMSIZ - 1); + f_args[0][IFNAMSIZ - 1] = 0; return 1; } return 0; diff --git a/src/core/launch_params.cpp b/src/core/launch_params.cpp new file mode 100644 index 0000000000..a0483226ec --- /dev/null +++ b/src/core/launch_params.cpp @@ -0,0 +1,205 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include "common/assert.h" +#include "common/logging.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "common/scm_rev.h" +#include "core/core.h" +#include "core/hle/service/acc/profile_manager.h" +#include "core/launch_params.h" + +#undef _UNICODE +#include +#ifndef _MSC_VER +#include +#endif + +namespace Core { +LaunchParams ParseLaunchParams(Core::System& system, int argc, char *argv[], wchar_t *argv_w[]) noexcept { + LaunchParams p{}; + + int option_index = 0; + static struct option long_options[] = { + // clang-format off + {"debug", no_argument, 0, 'd'}, + {"config", required_argument, 0, 'c'}, + {"fullscreen", no_argument, 0, 'f'}, + {"help", no_argument, 0, 'h'}, + {"game", required_argument, 0, 'g'}, + {"multiplayer", required_argument, 0, 'm'}, + {"program", optional_argument, 0, 'p'}, + {"user", required_argument, 0, 'u'}, + {"version", no_argument, 0, 'v'}, + {"input-profile", no_argument, 0, 'i'}, + {"null-render", no_argument, 0, 'n'}, + {"singlecore", no_argument, 0, 's'}, + {"filter", no_argument, 0, 'x'}, + {0, 0, 0, 0}, + // clang-format on + }; + + while (optind < argc) { + if (int arg = getopt_long(argc, argv, "dc:fhg:m:p:u:vinsx", long_options, &option_index); arg != -1) { + switch (char(arg)) { + case 'd': + p.override_gdb_port = uint16_t(atoi(optarg)); + break; + case 'c': + p.config_path = optarg; + break; + case 'f': + p.fullscreen = true; + LOG_INFO(Frontend, "Starting in fullscreen mode..."); + break; + case 'h': + p.print_help = true; + break; + case 'g': + p.filepath = std::string(optarg); + break; + case 'i': { + p.input_profile = std::string(optarg); + break; + } + case 'm': { + p.use_multiplayer = true; + const std::string str_arg(optarg); + // regex to check if the format is nickname:password@ip:port + // with optional :password + const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$"); + if (std::regex_match(str_arg, re)) { + std::smatch match; + std::regex_search(str_arg, match, re); + ASSERT(match.size() == 5); + p.nickname = match[1]; + p.password = match[2]; + p.address = match[3]; + if (!match[4].str().empty()) { + p.port = u16(std::strtoul(match[4].str().c_str(), nullptr, 0)); + } + std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$"); + ASSERT(std::regex_match(p.nickname, nickname_re) && "Nickname is not valid. Must be 4 to 20 alphanumeric characters"); + ASSERT(!p.address.empty() && "Address is empty"); + } + break; + } + case 'p': + p.program_args = argv[optind]; + ++optind; + break; + case 'u': { + // Launch game with a specific user + bool argument_ok = isdigit(optarg[0]); + p.selected_user = atoi(optarg); + if (!argument_ok) { + // try to look it up by username, only finds the first username that matches. + auto const user_idx = system.GetProfileManager().GetUserIndex(optarg); + if (user_idx != std::nullopt) { + p.selected_user = user_idx.value(); + } else { + LOG_ERROR(Frontend, "Invalid user argument '{}'", optarg); + break; + } + } + if (system.GetProfileManager().UserExistsIndex(*p.selected_user)) { + Settings::values.current_user = s32(*p.selected_user); + } else { + LOG_ERROR(Frontend, "Selected user {} doesn't exist", *p.selected_user); + } + break; + } + case 'v': + p.print_version = true; + break; + case 'n': + p.force_null_render = true; + break; + case 's': + p.force_single_core = true; + break; + case 'x': + p.log_filter = argv[optind]; + ++optind; + break; + } + } else { + // only kept due to shortcuts made by Qt frontend which use "-qlaunch" + if (strcmp(argv[optind], "-hlaunch") == 0) { + p.launch_hlaunch = true; + } else if (strcmp(argv[optind], "-qlaunch") == 0) { + p.launch_qlaunch = true; + } else if (strcmp(argv[optind], "-setup") == 0) { + p.launch_setup = true; + } else { + // qt feeds utf8 data, sdl frontend feeds raw utf16 data +#ifdef _WIN32 + p.filepath = argv_w != nullptr + ? Common::UTF16ToUTF8(argv_w[optind]) + : argv[optind]; +#else + p.filepath = argv[optind]; +#endif + } + optind++; + } + } + p.argv0 = argv[0]; + return p; +} + +void ApplyLaunchParams(LaunchParams const& lp) noexcept { + // apply the log_filter setting + // the logger was initialized before and doesn't pick up the filter on its own + Common::Log::Filter filter{}; + filter.ParseFilterString(lp.log_filter.value_or(Settings::values.log_filter.GetValue())); + Common::Log::SetGlobalFilter(filter); + + if (!lp.program_args.empty()) { + Settings::values.program_args = lp.program_args; + } + if (!lp.input_profile.empty()) { + auto& players = Settings::values.players.GetValue(); + players[0].profile_name = lp.input_profile; + } + if (lp.selected_user.has_value()) { + Settings::values.current_user = std::clamp(*lp.selected_user, 0, 7); + } + if (lp.override_gdb_port.has_value()) { + Settings::values.use_gdbstub = true; + Settings::values.gdbstub_port = *lp.override_gdb_port; + } + if (lp.force_single_core) { + Settings::values.use_multi_core = false; + } + if (lp.force_null_render) { + Settings::values.renderer_backend = Settings::RendererBackend::Null; + } + + if (lp.print_version) { + LOG_INFO(Frontend, "Eden {} {}", Common::g_scm_branch, Common::g_scm_desc); + } + if (lp.print_help) { + LOG_INFO(Frontend, + "Usage: {}" + " [options] \n" + "-c, --config Load the specified configuration file\n" + "-f, --fullscreen Start in fullscreen mode\n" + "-g, --game File path of the game to load\n" + "-h, --help Display this help and exit\n" + "-m, --multiplayer=nick:password@address:port" + " Nickname, password, address and port for multiplayer\n" + "-p, --program Pass following string as arguments to executable\n" + "-u, --user Select a specific user profile from 0 to 7\n" + "-d, --debug Run the GDB stub on a port from 1 to 65535\n" + "-v, --version Output version information and exit\n", + lp.argv0 + ); + } +} + +} diff --git a/src/core/launch_params.h b/src/core/launch_params.h new file mode 100644 index 0000000000..4da30fc1c2 --- /dev/null +++ b/src/core/launch_params.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include "common/common_types.h" +#include "network/room.h" + +namespace Core { + +class System; +struct LaunchParams { + std::string argv0{}; + std::string filepath{}; + std::string nickname{}; + std::string password{}; + std::string address{}; + std::string input_profile{}; + std::string program_args{}; + std::optional config_path{}; + std::optional selected_user{}; + std::optional override_gdb_port{}; + std::optional log_filter{}; + u16 port = Network::DefaultRoomPort; + bool use_multiplayer = false; + bool fullscreen = false; + bool force_null_render = false; + bool force_single_core = false; + // print + bool print_version = false; + bool print_help = false; + // qt + bool launch_hlaunch = false; + bool launch_qlaunch = false; + bool launch_setup = false; +}; + +LaunchParams ParseLaunchParams(Core::System& system, int argc, char *argv[], wchar_t *argv_w[]) noexcept; +void ApplyLaunchParams(LaunchParams const& lp) noexcept; + +} diff --git a/src/core/launch_timestamp_cache.cpp b/src/core/launch_timestamp_cache.cpp index c58ffa1d5e..2b030f20b3 100644 --- a/src/core/launch_timestamp_cache.cpp +++ b/src/core/launch_timestamp_cache.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include @@ -21,8 +21,8 @@ namespace Core::LaunchTimestampCache { namespace { -using CacheMap = ankerl::unordered_dense::map; -using CountMap = ankerl::unordered_dense::map; +using CacheMap = ::Common::unordered_map; +using CountMap = ::Common::unordered_map; std::mutex g_mutex; CacheMap g_cache; diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index afa5cdbc36..5e2ec7e0b9 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -71,9 +71,10 @@ struct PatchCollection { std::array module_patcher_indices{}; }; -AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_, - bool override_update_) - : AppLoader(std::move(file_)), override_update(override_update_), is_hbl(false) { +AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_, bool override_update_) + : AppLoader(std::move(file_)) + , override_update(override_update_) +{ const auto file_dir = file->GetContainingDirectory(); // Title ID @@ -124,9 +125,11 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys } AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( - FileSys::VirtualDir directory, bool override_update_, bool is_hbl_) - : AppLoader(directory->GetFile("main")), dir(std::move(directory)), - override_update(override_update_), is_hbl(is_hbl_) {} + FileSys::VirtualDir directory, bool override_update_) + : AppLoader(directory->GetFile("main")) + , dir(std::move(directory)) + , override_update(override_update_) +{} FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) { if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) { @@ -232,7 +235,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect ? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000; // Setup the process code layout - if (process.LoadFromMetadata(metadata, code_size, fastmem_base, aslr_offset, is_hbl).IsError()) { + if (process.LoadFromMetadata(system.Kernel(), metadata, code_size, fastmem_base, aslr_offset).IsError()) { return {ResultStatus::ErrorUnableToParseKernelMetadata, {}}; } @@ -260,7 +263,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect next_load_addr = *tentative_next_load_addr; modules.insert_or_assign(load_addr, module); - LOG_DEBUG(Loader, "loaded module {} @ {:#X}", module, load_addr); + LOG_DEBUG(Loader, "loaded module {} @ {:#x}", module, load_addr); } is_loaded = true; diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h index 1e9f765c9d..7283db0662 100644 --- a/src/core/loader/deconstructed_rom_directory.h +++ b/src/core/loader/deconstructed_rom_directory.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -22,13 +25,9 @@ namespace Loader { */ class AppLoader_DeconstructedRomDirectory final : public AppLoader { public: - explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile main_file, - bool override_update_ = false); - + explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile main_file, bool override_update_ = false); // Overload to accept exefs directory. Must contain 'main' and 'main.npdm' - explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory, - bool override_update_ = false, - bool is_hbl_ = false); + explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory, bool override_update_ = false); /** * Identifies whether or not the given file is a deconstructed ROM directory. @@ -63,7 +62,6 @@ private: std::string name; u64 title_id{}; bool override_update; - bool is_hbl; Modules modules; }; diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp index 978ffed2b9..c6098b9efd 100644 --- a/src/core/loader/kip.cpp +++ b/src/core/loader/kip.cpp @@ -93,13 +93,13 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process, ? ::Settings::values.rng_seed.GetValue() : Common::Random::Random64(0)) << 12) & 0xfff000; // Setup the process code layout - if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), codeset.memory.size(), 0, aslr_offset, false).IsError()) { + if (process.LoadFromMetadata(system.Kernel(), FileSys::ProgramMetadata::GetDefault(), codeset.memory.size(), 0, aslr_offset).IsError()) { return {ResultStatus::ErrorNotInitialized, {}}; } const VAddr base_address = GetInteger(process.GetEntryPoint()); - process.LoadModule(std::move(codeset), base_address); + process.LoadModule(system.Kernel(), std::move(codeset), base_address); - LOG_DEBUG(Loader, "loaded module {} @ {:#X}", kip->GetName(), base_address); + LOG_DEBUG(Loader, "loaded module {} @ {:#x}", kip->GetName(), base_address); is_loaded = true; return {ResultStatus::Success, diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 69bfa2e879..92e2e2f602 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -17,6 +17,8 @@ #include "core/core.h" #include "core/file_sys/card_image.h" #include "core/file_sys/common_funcs.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/registered_cache.h" #include "core/file_sys/submission_package.h" #include "core/hle/kernel/k_process.h" #include "core/loader/deconstructed_rom_directory.h" @@ -41,6 +43,38 @@ std::optional IdentifyFileLoader(FileSys::VirtualFile file) { return std::nullopt; } +struct IndexedProgram { + FileSys::VirtualFile file; + u64 program_id; + bool update_only; +}; + +std::optional ResolveIndexedProgram(Core::System& system, u64 program_id, + std::size_t program_index) { + if (program_index == 0 || program_id == 0) { + return std::nullopt; + } + + const auto target_id = FileSys::GetBaseTitleIDWithProgramIndex(program_id, program_index); + const auto& provider = system.GetContentProvider(); + + if (auto base = provider.GetEntryRaw(target_id, FileSys::ContentRecordType::Program)) { + LOG_INFO(Loader, "Program index {} resolved to {:016X}", program_index, target_id); + return IndexedProgram{std::move(base), target_id, false}; + } + + const auto update_id = FileSys::GetUpdateTitleID(target_id); + if (auto update = provider.GetEntryRaw(update_id, FileSys::ContentRecordType::Program)) { + LOG_INFO(Loader, "Program index {} has no base program, loading it from update {:016X}", + program_index, update_id); + return IndexedProgram{std::move(update), target_id, true}; + } + + LOG_WARNING(Loader, "No program NCA for {:016X} (index {}), falling back to the container", + target_id, program_index); + return std::nullopt; +} + std::shared_ptr OpenContainerAsNsp(FileSys::VirtualFile file, FileType type, u64 program_id = 0, std::size_t program_index = 0) { @@ -48,23 +82,27 @@ std::shared_ptr OpenContainerAsNsp(FileSys::VirtualFile file, File return nullptr; } - if (type == FileType::NSP) { + if (type == FileType::NSP || type == FileType::NSZ) { auto nsp = std::make_shared(file, program_id, program_index); return nsp->GetStatus() == ResultStatus::Success ? nsp : nullptr; } - if (type == FileType::XCI) { + if (type == FileType::XCI || type == FileType::XCZ) { FileSys::XCI xci{file, program_id, program_index}; - if (xci.GetStatus() != ResultStatus::Success) { - return nullptr; + if (xci.GetStatus() == ResultStatus::Success) { + auto secure_nsp = xci.GetSecurePartitionNSP(); + if (secure_nsp != nullptr && secure_nsp->GetStatus() == ResultStatus::Success) { + return secure_nsp; + } } - auto secure_nsp = xci.GetSecurePartitionNSP(); - if (secure_nsp == nullptr || secure_nsp->GetStatus() != ResultStatus::Success) { - return nullptr; + // PFS0-shaped .xcz (NSZ-style container) does not parse as XCI; fall back to NSP. + if (type == FileType::XCZ) { + auto nsp = std::make_shared(file, program_id, program_index); + return nsp->GetStatus() == ResultStatus::Success ? nsp : nullptr; } - return secure_nsp; + return nullptr; } return nullptr; @@ -110,7 +148,8 @@ FileType IdentifyFile(FileSys::VirtualFile file) { } bool IsContainerType(FileType type) { - return type == FileType::NSP || type == FileType::XCI; + return type == FileType::NSP || type == FileType::NSZ || type == FileType::XCI || + type == FileType::XCZ; } bool IsBootableGameContainer(FileSys::VirtualFile file, FileType type, u64 program_id, @@ -146,8 +185,12 @@ FileType GuessFromFilename(const std::string& name) { return FileType::NCA; else if (extension == "xci") return FileType::XCI; + else if (extension == "xcz") + return FileType::XCZ; else if (extension == "nsp") return FileType::NSP; + else if (extension == "nsz") + return FileType::NSZ; else if (extension == "kip") return FileType::KIP; return FileType::Unknown; @@ -163,10 +206,14 @@ std::string GetFileTypeString(FileType type) { return "NCA"; case FileType::XCI: return "XCI"; + case FileType::XCZ: + return "XCZ"; case FileType::NAX: return "NAX"; case FileType::NSP: return "NSP"; + case FileType::NSZ: + return "NSZ"; case FileType::KIP: return "KIP"; case FileType::DeconstructedRomDirectory: @@ -292,12 +339,29 @@ static std::unique_ptr GetFileLoader(Core::System& system, FileSys::V system.GetContentProvider(), program_id, program_index); + // NCZ-compressed XCI container. XCI-shaped .xcz goes to the XCI loader, while + // PFS0-shaped (NSZ-style) .xcz containers fall back to the NSP loader. + case FileType::XCZ: { + FileSys::XCI probe{file, program_id, program_index}; + if (probe.GetStatus() == ResultStatus::Success && + probe.GetSecurePartitionNSP() != nullptr) { + return std::make_unique(std::move(file), + system.GetFileSystemController(), + system.GetContentProvider(), program_id, + program_index); + } + return std::make_unique(std::move(file), system.GetFileSystemController(), + system.GetContentProvider(), program_id, + program_index); + } + // NX NAX (NintendoAesXts) file format. case FileType::NAX: return std::make_unique(std::move(file)); // NX NSP (Nintendo Submission Package) file format case FileType::NSP: + case FileType::NSZ: return std::make_unique(std::move(file), system.GetFileSystemController(), system.GetContentProvider(), program_id, program_index); @@ -321,6 +385,11 @@ std::unique_ptr GetLoader(Core::System& system, FileSys::VirtualFile return nullptr; } + if (const auto indexed = ResolveIndexedProgram(system, program_id, program_index)) { + return std::make_unique( + indexed->file, indexed->update_only ? indexed->program_id : 0); + } + FileType type = IdentifyFile(file); const FileType filename_type = GuessFromFilename(file->GetName()); diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 95ce638da0..d3c0230cb0 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -42,7 +42,9 @@ enum class FileType { NRO, NCA, NSP, + NSZ, XCI, + XCZ, NAX, KIP, DeconstructedRomDirectory, diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index b656b9fe96..2f9cd68e9c 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp @@ -12,7 +12,9 @@ #include "common/scope_exit.h" #include "core/core.h" #include "core/file_sys/content_archive.h" +#include "core/file_sys/control_metadata.h" #include "core/file_sys/nca_metadata.h" +#include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs_factory.h" #include "core/hle/kernel/k_process.h" @@ -33,8 +35,14 @@ static u32 CalculatePointerBufferSize(size_t heap_size) { } } -AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file_) - : AppLoader(std::move(file_)), nca(std::make_unique(file)) {} +AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file_, u64 update_only_program_id_) + : AppLoader(std::move(file_)), + nca(std::make_unique(file, nullptr, update_only_program_id_ != 0)), + update_only_program_id(update_only_program_id_) {} + +u64 AppLoader_NCA::GetProgramId() const { + return update_only_program_id != 0 ? update_only_program_id : nca->GetTitleId(); +} AppLoader_NCA::~AppLoader_NCA() = default; @@ -110,11 +118,21 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S } LOG_INFO(Loader, "Set pointer buffer size to {:#x} bytes for ProgramID {:#018x} (Heap size: {:#x})", - process.GetPointerBufferSize(), nca->GetTitleId(), heap_size); + process.GetPointerBufferSize(), GetProgramId(), heap_size); + + auto metadata = + FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, this->GetProgramId()); + if (metadata.first != nullptr) { + nacp = std::move(metadata.first); + LOG_INFO(Loader, "Control data for {:016X}: name=\"{}\" supported_languages={:08X}", + this->GetProgramId(), nacp->GetApplicationName(), nacp->GetSupportedLanguages()); + } else { + LOG_WARNING(Loader, "No control data found for program {:016X}", this->GetProgramId()); + } // Register the process in the file system controller system.GetFileSystemController().RegisterProcess( - process.GetProcessId(), nca->GetTitleId(), + process.GetProcessId(), GetProgramId(), std::make_shared(*this, system.GetContentProvider(), system.GetFileSystemController())); @@ -222,7 +240,25 @@ ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { return ResultStatus::ErrorNotInitialized; } - out_program_id = nca->GetTitleId(); + out_program_id = GetProgramId(); + return ResultStatus::Success; +} + +ResultStatus AppLoader_NCA::ReadControlData(FileSys::NACP& out_control) { + if (nacp == nullptr) { + return ResultStatus::ErrorNoControl; + } + + out_control = *nacp; + return ResultStatus::Success; +} + +ResultStatus AppLoader_NCA::ReadTitle(std::string& out_title) { + if (nacp == nullptr) { + return ResultStatus::ErrorNoControl; + } + + out_title = nacp->GetApplicationName(); return ResultStatus::Success; } diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index 96779e27fc..cf63822120 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -11,6 +14,7 @@ class System; } namespace FileSys { +class NACP; class NCA; } @@ -21,7 +25,7 @@ class AppLoader_DeconstructedRomDirectory; /// Loads an NCA file class AppLoader_NCA final : public AppLoader { public: - explicit AppLoader_NCA(FileSys::VirtualFile file_); + explicit AppLoader_NCA(FileSys::VirtualFile file_, u64 update_only_program_id = 0); ~AppLoader_NCA() override; /** @@ -43,14 +47,20 @@ public: ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; ResultStatus ReadProgramId(u64& out_program_id) override; + ResultStatus ReadControlData(FileSys::NACP& out_control) override; + ResultStatus ReadTitle(std::string& out_title) override; ResultStatus ReadBanner(std::vector& buffer) override; ResultStatus ReadLogo(std::vector& buffer) override; ResultStatus ReadNSOModules(Modules& modules) override; + u64 GetProgramId() const; + private: std::unique_ptr nca; + u64 update_only_program_id{}; + std::unique_ptr nacp; std::unique_ptr directory_loader; }; diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 738d805149..4ba4bde0bb 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -4,9 +4,15 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include #include #include +#include "common/alignment.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/logging.h" @@ -23,7 +29,6 @@ #include "core/hle/kernel/k_thread.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/nro.h" -#include "core/loader/nso.h" #include "core/memory.h" #ifdef HAS_NCE @@ -174,19 +179,6 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, codeset.segments[i].size = PageAlignSize(nro_header.segments[i].size); } - if (!Settings::values.program_args.GetValue().empty()) { - const auto arg_data = Settings::values.program_args.GetValue(); - codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE; - NSOArgumentHeader args_header{ - NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast(arg_data.size()), {}}; - const auto end_offset = program_image.size(); - program_image.resize(static_cast(program_image.size()) + - NSO_ARGUMENT_DATA_ALLOCATION_SIZE); - std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader)); - std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(), - arg_data.size()); - } - // Default .bss to NRO header bss size if MOD0 section doesn't exist u32 bss_size{PageAlignSize(nro_header.bss_size)}; @@ -203,6 +195,47 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, codeset.DataSegment().size += bss_size; program_image.resize(static_cast(program_image.size()) + bss_size); + struct ConfigEntry { + u32_le key; + u32_le flags; + u64_le value[2]; + }; + static_assert(sizeof(ConfigEntry) == 0x18); + // AArch64 encoding for svc #0x7 (ExitProcess). + constexpr u32 kSvcExitProcessInstruction = 0xD40000E1; + constexpr size_t kNumEntries = 4; // MainThreadHandle, AppletType, Argv, EndOfList + constexpr size_t kConfigTableSize = kNumEntries * sizeof(ConfigEntry); + std::string argv_string; + size_t args_offset_in_image = 0; + std::optional exit_process_offset_in_image; + const auto& program_args = Settings::values.program_args.GetValue(); + if (!program_args.empty()) { + argv_string = "homebrew "; + argv_string += program_args; + argv_string.push_back('\0'); + + const auto& code = codeset.CodeSegment(); + const size_t code_end = (std::min)(program_image.size(), code.offset + code.size); + for (size_t offset = code.offset; offset + sizeof(u32) <= code_end; offset += sizeof(u32)) { + u32 instruction{}; + std::memcpy(&instruction, program_image.data() + offset, sizeof(instruction)); + if (instruction == kSvcExitProcessInstruction) { + exit_process_offset_in_image = offset; + break; + } + } + if (!exit_process_offset_in_image) { + LOG_WARNING(Loader, + "Unable to find svcExitProcess in NRO; returning from main may fault"); + } + + const size_t entries_and_argv = + Common::AlignUp(kConfigTableSize + argv_string.size(), Core::Memory::YUZU_PAGESIZE); + + args_offset_in_image = program_image.size(); + codeset.DataSegment().size += static_cast(entries_and_argv); + program_image.resize(args_offset_in_image + entries_and_argv); + } size_t image_size = program_image.size(); #ifdef HAS_NCE @@ -247,7 +280,7 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, // Setup the process code layout if (process - .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), image_size, fastmem_base, aslr_offset, false) + .LoadFromMetadata(system.Kernel(), FileSys::ProgramMetadata::GetDefault(), image_size, fastmem_base, aslr_offset) .IsError()) { return false; } @@ -263,7 +296,33 @@ static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process, // Load codeset for current process codeset.memory = std::move(program_image); - process.LoadModule(std::move(codeset), process.GetEntryPoint()); + process.LoadModule(system.Kernel(), std::move(codeset), process.GetEntryPoint()); + if (!argv_string.empty()) { + constexpr u32 kEntryEndOfList = 0; + constexpr u32 kEntryMainThreadHandle = 1; + constexpr u32 kEntryArgv = 5; + constexpr u32 kEntryAppletType = 7; + constexpr u32 kAppletTypeApplication = 0; + + const u64 base = GetInteger(process.GetEntryPoint()); + const u64 config_addr = base + args_offset_in_image; + const u64 argv_addr = config_addr + kConfigTableSize; + + const ConfigEntry entries[kNumEntries] = { + {kEntryMainThreadHandle, 0, {0, 0}}, // Value[0] patched in Run() + {kEntryAppletType, 0, {kAppletTypeApplication, 0}}, + {kEntryArgv, 0, {0, argv_addr}}, + {kEntryEndOfList, 0, {0, 0}}, + }; + process.GetMemory().WriteBlock(Common::ProcessAddress{config_addr}, entries, sizeof(entries)); + process.GetMemory().WriteBlock(Common::ProcessAddress{argv_addr}, argv_string.data(), argv_string.size()); + constexpr size_t kMainThreadHandleValueOffset = offsetof(ConfigEntry, value); + process.SetArgPointer(Kernel::KProcessAddress{config_addr}); + if (exit_process_offset_in_image) { + process.SetArgReturnAddress(Kernel::KProcessAddress{base + *exit_process_offset_in_image}); + } + process.SetMainThreadHandleAddr(Kernel::KProcessAddress{config_addr + kMainThreadHandleValueOffset}); + } return true; } diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 482c853542..ea1d1c9ecd 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -7,12 +7,14 @@ #include #include #include +#include #include #include "common/common_funcs.h" #include "common/hex_util.h" #include "common/logging.h" #include "common/lz4_compression.h" +#include "common/zbic_compression.h" #include "common/settings.h" #include "common/swap.h" #include "core/core.h" @@ -104,11 +106,36 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: for (std::size_t i = 0; i < nso_header.segments.size(); ++i) { nso_file.Read(compressed_data.data(), nso_header.segments_compressed_size[i], nso_header.segments[i].offset); if (nso_header.IsSegmentCompressed(i)) { - int r = Common::Compression::DecompressDataLZ4(decompressed_size.data(), nso_header.segments[i].size, compressed_data.data(), nso_header.segments_compressed_size[i]); - ASSERT(r == int(nso_header.segments[i].size)); - std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, decompressed_size.data(), nso_header.segments[i].size); + if (nso_header.IsZBICCompressed()) { + // ZBIC compression + const int r = Common::Compression::DecompressDataZBIC( + std::span{decompressed_size}.first(nso_header.segments[i].size), + std::span{compressed_data}.first(nso_header.segments_compressed_size[i]) + ); + ASSERT(r > 0); + } else { + // LZ4 compression + int r = Common::Compression::DecompressDataLZ4( + decompressed_size.data(), + nso_header.segments[i].size, + compressed_data.data(), + nso_header.segments_compressed_size[i] + ); + ASSERT(r == int(nso_header.segments[i].size)); + } + + std::memcpy( + codeset.memory.data() + module_start + nso_header.segments[i].location, + decompressed_size.data(), + nso_header.segments[i].size + ); } else { - std::memcpy(codeset.memory.data() + module_start + nso_header.segments[i].location, compressed_data.data(), nso_header.segments[i].size); + // Not compressed + std::memcpy( + codeset.memory.data() + module_start + nso_header.segments[i].location, + compressed_data.data(), + nso_header.segments[i].size + ); } codeset.segments[i].addr = module_start + nso_header.segments[i].location; codeset.segments[i].offset = module_start + nso_header.segments[i].location; @@ -209,7 +236,10 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: // Apply cheats if they exist and the program has a valid title ID if (pm) { - system.SetApplicationProcessBuildID(nso_header.build_id); + // TODO(Maufeat): Check if there is a better way to check + if (name == "main") + system.SetApplicationProcessBuildID(nso_header.build_id); + const auto cheats = pm->CreateCheatList(nso_header.build_id); if (!cheats.empty()) { system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size); @@ -217,7 +247,7 @@ std::optional AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: } // Load codeset for current process - process.LoadModule(std::move(codeset), load_base); + process.LoadModule(system.Kernel(), std::move(codeset), load_base); return load_base + image_size; } @@ -235,7 +265,7 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::KProcess& process, Core::S } modules.insert_or_assign(base_address, file->GetName()); - LOG_DEBUG(Loader, "loaded module {} @ {:#X}", file->GetName(), base_address); + LOG_DEBUG(Loader, "loaded module {} @ {:#x}", file->GetName(), base_address); is_loaded = true; return {ResultStatus::Success, LoadParameters{Kernel::KThread::DefaultThreadPriority, diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h index 6356697e33..4a373edd07 100644 --- a/src/core/loader/nso.h +++ b/src/core/loader/nso.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -58,6 +61,9 @@ struct NSOHeader { std::array segment_hashes; bool IsSegmentCompressed(size_t segment_num) const; + bool IsZBICCompressed() const { + return ((flags >> 7) & 1) != 0; + } }; static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size."); static_assert(std::is_trivially_copyable_v, "NSOHeader must be trivially copyable."); diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 4333acb70c..8b2c79394d 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -8,6 +8,7 @@ #include "common/common_types.h" #include "core/core.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/nca_metadata.h" @@ -34,17 +35,16 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file_, } if (nsp->IsExtractedType()) { - secondary_loader = std::make_unique( - nsp->GetExeFS(), false, file->GetName() == "hbl.nsp"); + secondary_loader = std::make_unique(nsp->GetExeFS(), false); } else { const auto control_nca = - nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); + nsp->GetNCA(FileSys::GetBaseTitleID(nsp->GetProgramTitleID()), FileSys::ContentRecordType::Control); if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) { return; } std::tie(nacp_file, icon_file) = [this, &content_provider, &control_nca, &fsc] { - const FileSys::PatchManager pm{nsp->GetProgramTitleID(), fsc, content_provider}; + const FileSys::PatchManager pm{FileSys::GetBaseTitleID(nsp->GetProgramTitleID()), fsc, content_provider}; return pm.ParseControlNCA(*control_nca); }(); @@ -56,6 +56,17 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file_, AppLoader_NSP::~AppLoader_NSP() = default; FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& nsp_file) { + // .nsz is NSP-compatible; return NSZ so the game list shows the right type. + if (nsp_file->GetName().ends_with(".nsz")) { + return FileType::NSZ; + } + + // PFS0-shaped .xcz containers are handled by this loader too; report XCZ so the game + // list does not show them as NSP. XCI-shaped .xcz is identified by AppLoader_XCI. + if (nsp_file->GetName().ends_with(".xcz")) { + return FileType::XCZ; + } + const FileSys::NSP nsp(nsp_file); if (nsp.GetStatus() != ResultStatus::Success) { diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 3a9ea308a8..eabc6dfccb 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -58,9 +58,9 @@ struct Memory::Impl { #ifdef __ANDROID__ heap_tracker.emplace(system.DeviceMemory().buffer); - buffer = std::addressof(*heap_tracker); + host_buffer = std::addressof(*heap_tracker); #else - buffer = std::addressof(system.DeviceMemory().buffer); + host_buffer = std::addressof(system.DeviceMemory().buffer); #endif } @@ -75,8 +75,7 @@ struct Memory::Impl { Common::PageType::Memory); if (current_page_table->fastmem_arena) { - buffer->Map(GetInteger(base), GetInteger(target) - DramMemoryMap::Base, size, perms, - separate_heap); + host_buffer->Map(GetInteger(base), GetInteger(target) - DramMemoryMap::Base, size, perms, separate_heap); } } @@ -88,7 +87,7 @@ struct Memory::Impl { Common::PageType::Unmapped); if (current_page_table->fastmem_arena) { - buffer->Unmap(GetInteger(base), size, separate_heap); + host_buffer->Unmap(GetInteger(base), size, separate_heap); } } @@ -102,12 +101,14 @@ struct Memory::Impl { } u64 protect_bytes = 0, protect_begin = 0; + + current_page_table->entries.CommitRegion(vaddr >> YUZU_PAGEBITS, (vaddr + size) >> YUZU_PAGEBITS); for (u64 addr = vaddr; addr < vaddr + size; addr += YUZU_PAGESIZE) { - const Common::PageType page_type = current_page_table->entries[addr >> YUZU_PAGEBITS].ptr.Type(); + const Common::PageType page_type = current_page_table->entries.GetUnchecked(addr >> YUZU_PAGEBITS).Type(); switch (page_type) { case Common::PageType::RasterizerCachedMemory: if (protect_bytes > 0) { - buffer->Protect(protect_begin, protect_bytes, perms); + host_buffer->Protect(protect_begin, protect_bytes, perms); protect_bytes = 0; } break; @@ -119,21 +120,19 @@ struct Memory::Impl { } if (protect_bytes > 0) { - buffer->Protect(protect_begin, protect_bytes, perms); + host_buffer->Protect(protect_begin, protect_bytes, perms); } } [[nodiscard]] u8* GetPointerFromRasterizerCachedMemory(u64 vaddr) const { - Common::PhysicalAddress const paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].addr; - if (paddr) - return system.DeviceMemory().GetPointer(paddr + vaddr); + if (u64 paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].Pointer(true); paddr) + return reinterpret_cast(paddr) + vaddr; return {}; } [[nodiscard]] u8* GetPointerFromDebugMemory(u64 vaddr) const { - const Common::PhysicalAddress paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].addr; - if (paddr != 0) - return system.DeviceMemory().GetPointer(paddr + vaddr); + if (u64 paddr = current_page_table->entries[vaddr >> YUZU_PAGEBITS].Pointer(true); paddr) + return reinterpret_cast(paddr) + vaddr; return {}; } @@ -233,225 +232,140 @@ struct Memory::Impl { return string; } - bool WalkBlock(const Common::ProcessAddress addr, const std::size_t size, auto on_unmapped, - auto on_memory, auto on_rasterizer, auto increment) { - const auto& page_table = *current_page_table; + template + inline bool WalkBlock(const Common::ProcessAddress addr, const std::size_t size, F1&& on_unmapped, F2&& on_memory, F3&& on_rasterizer) { + std::size_t offset = 0; + if (!AddressSpaceContains(*current_page_table, addr, size)) { + on_unmapped(offset, size, addr); + return false; + } std::size_t remaining_size = size; std::size_t page_index = addr >> YUZU_PAGEBITS; std::size_t page_offset = addr & YUZU_PAGEMASK; bool user_accessible = true; - if (!AddressSpaceContains(page_table, addr, size)) [[unlikely]] { - on_unmapped(size, addr); - return false; - } - - while (remaining_size) { - const std::size_t copy_amount = - (std::min)(static_cast(YUZU_PAGESIZE) - page_offset, remaining_size); - const auto current_vaddr = - static_cast((page_index << YUZU_PAGEBITS) + page_offset); - - const auto [pointer, type] = page_table.entries[page_index].ptr.PointerType(); + current_page_table->entries.CommitRegion(page_index, page_index + (size >> YUZU_PAGEBITS) + 1); + while (remaining_size != 0) { + const std::size_t copy_amount = (std::min)(std::size_t(YUZU_PAGESIZE) - page_offset, remaining_size); + const auto current_vaddr = u64((page_index << YUZU_PAGEBITS) + page_offset); + const auto [pointer, type, _] = current_page_table->entries.GetUnchecked(page_index).PointerTypeBlock(); switch (type) { case Common::PageType::Unmapped: { user_accessible = false; - on_unmapped(copy_amount, current_vaddr); + on_unmapped(offset, copy_amount, current_vaddr); break; } case Common::PageType::Memory: { - u8* mem_ptr = - reinterpret_cast(pointer + page_offset + (page_index << YUZU_PAGEBITS)); - on_memory(copy_amount, mem_ptr); + u8* mem_ptr = reinterpret_cast(pointer + page_offset + (page_index << YUZU_PAGEBITS)); + on_memory(offset, copy_amount, mem_ptr); break; } case Common::PageType::DebugMemory: { - u8* const mem_ptr{GetPointerFromDebugMemory(current_vaddr)}; - on_memory(copy_amount, mem_ptr); + u8* const mem_ptr = GetPointerFromDebugMemory(current_vaddr); + on_memory(offset, copy_amount, mem_ptr); break; } case Common::PageType::RasterizerCachedMemory: { - u8* const host_ptr{GetPointerFromRasterizerCachedMemory(current_vaddr)}; - on_rasterizer(current_vaddr, copy_amount, host_ptr); + u8* const host_ptr = GetPointerFromRasterizerCachedMemory(current_vaddr); + on_rasterizer(current_vaddr, offset, copy_amount, host_ptr); break; } default: UNREACHABLE(); } - page_index++; page_offset = 0; - increment(copy_amount); + offset += copy_amount; remaining_size -= copy_amount; } - return user_accessible; } - template - bool ReadBlockImpl(const Common::ProcessAddress src_addr, void* dest_buffer, - const std::size_t size) { - return WalkBlock( - src_addr, size, - [src_addr, size, &dest_buffer](const std::size_t copy_amount, - const Common::ProcessAddress current_vaddr) { - LOG_ERROR(HW_Memory, - "Unmapped ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", - GetInteger(current_vaddr), GetInteger(src_addr), size); - std::memset(dest_buffer, 0, copy_amount); - }, - [&](const std::size_t copy_amount, const u8* const src_ptr) { - std::memcpy(dest_buffer, src_ptr, copy_amount); - }, - [&](const Common::ProcessAddress current_vaddr, const std::size_t copy_amount, - const u8* const host_ptr) { - if constexpr (!UNSAFE) { - HandleRasterizerDownload(GetInteger(current_vaddr), copy_amount); - } - std::memcpy(dest_buffer, host_ptr, copy_amount); - }, - [&](const std::size_t copy_amount) { - dest_buffer = static_cast(dest_buffer) + copy_amount; - }); - } - - bool ReadBlock(const Common::ProcessAddress src_addr, void* dest_buffer, - const std::size_t size) { - // TODO: If you want a proper multithreaded implementation (w/o cache coherency fights) - // use TBB or something that splits the job properly - return ReadBlockImpl(src_addr, dest_buffer, size); - } - - bool ReadBlockUnsafe(const Common::ProcessAddress src_addr, void* dest_buffer, - const std::size_t size) { - return ReadBlockImpl(src_addr, dest_buffer, size); - } - - const u8* GetSpan(const VAddr src_addr, const std::size_t size) const { - if (current_page_table->entries[src_addr >> YUZU_PAGEBITS].block == - current_page_table->entries[(src_addr + size) >> YUZU_PAGEBITS].block) { - return GetPointerSilent(src_addr); - } - return nullptr; - } - - u8* GetSpan(const VAddr src_addr, const std::size_t size) { - if (current_page_table->entries[src_addr >> YUZU_PAGEBITS].block == - current_page_table->entries[(src_addr + size) >> YUZU_PAGEBITS].block) { - return GetPointerSilent(src_addr); - } - return nullptr; - } - - template - bool WriteBlockImpl(const Common::ProcessAddress dest_addr, const void* src_buffer, - const std::size_t size) { - return WalkBlock( - dest_addr, size, - [dest_addr, size](const std::size_t copy_amount, - const Common::ProcessAddress current_vaddr) { - LOG_ERROR(HW_Memory, - "Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", - GetInteger(current_vaddr), GetInteger(dest_addr), size); - }, - [&](const std::size_t copy_amount, u8* const dest_ptr) { - std::memcpy(dest_ptr, src_buffer, copy_amount); - }, - [&](const Common::ProcessAddress current_vaddr, const std::size_t copy_amount, - u8* const host_ptr) { - if constexpr (!UNSAFE) { - HandleRasterizerWrite(GetInteger(current_vaddr), copy_amount); - } - std::memcpy(host_ptr, src_buffer, copy_amount); - }, - [&](const std::size_t copy_amount) { - src_buffer = static_cast(src_buffer) + copy_amount; - }); - } - - bool WriteBlock(const Common::ProcessAddress dest_addr, const void* src_buffer, - const std::size_t size) { - // TODO: If you want a proper multithreaded implementation (w/o cache coherency fights) - // use TBB or something that splits the job properly - return WriteBlockImpl(dest_addr, src_buffer, size); - } - - bool WriteBlockUnsafe(const Common::ProcessAddress dest_addr, const void* src_buffer, - const std::size_t size) { - return WriteBlockImpl(dest_addr, src_buffer, size); - } - - bool ZeroBlock(const Common::ProcessAddress dest_addr, const std::size_t size) { - return WalkBlock( - dest_addr, size, - [dest_addr, size](const std::size_t copy_amount, - const Common::ProcessAddress current_vaddr) { - LOG_ERROR(HW_Memory, - "Unmapped ZeroBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", - GetInteger(current_vaddr), GetInteger(dest_addr), size); - }, - [](const std::size_t copy_amount, u8* const dest_ptr) { - std::memset(dest_ptr, 0, copy_amount); - }, - [&](const Common::ProcessAddress current_vaddr, const std::size_t copy_amount, - u8* const host_ptr) { - HandleRasterizerWrite(GetInteger(current_vaddr), copy_amount); - std::memset(host_ptr, 0, copy_amount); - }, - [](const std::size_t copy_amount) {}); - } - - bool CopyBlock(Common::ProcessAddress dest_addr, Common::ProcessAddress src_addr, - const std::size_t size) { - return WalkBlock( - dest_addr, size, - [&](const std::size_t copy_amount, const Common::ProcessAddress current_vaddr) { - LOG_ERROR(HW_Memory, - "Unmapped CopyBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})", - GetInteger(current_vaddr), GetInteger(src_addr), size); - ZeroBlock(dest_addr, copy_amount); - }, - [&](const std::size_t copy_amount, const u8* const src_ptr) { - WriteBlockImpl(dest_addr, src_ptr, copy_amount); - }, - [&](const Common::ProcessAddress current_vaddr, const std::size_t copy_amount, - u8* const host_ptr) { + bool ReadBlockImpl(const Common::ProcessAddress addr, void* buffer, const std::size_t size, bool unsafe) { + return WalkBlock(addr, size, + [addr, size, &buffer](const std::size_t offset, const std::size_t copy_amount, const Common::ProcessAddress current_vaddr) { + LOG_ERROR(HW_Memory, "Unmapped @ {:#016x} (start address = {:#016x}, size = {})", GetInteger(current_vaddr), GetInteger(addr), size); + std::memset(reinterpret_cast(buffer) + offset, 0, copy_amount); + }, + [&](const std::size_t offset, const std::size_t copy_amount, const u8* const ptr) { + std::memcpy(reinterpret_cast(buffer) + offset, ptr, copy_amount); + }, + [&](const Common::ProcessAddress current_vaddr, const std::size_t offset, const std::size_t copy_amount, const u8* const ptr) { + if (!unsafe) { HandleRasterizerDownload(GetInteger(current_vaddr), copy_amount); - WriteBlockImpl(dest_addr, host_ptr, copy_amount); - }, - [&](const std::size_t copy_amount) { - dest_addr += copy_amount; - src_addr += copy_amount; - }); + } + std::memcpy(reinterpret_cast(buffer) + offset, ptr, copy_amount); + }); + } + + [[nodiscard]] inline const u8* GetSpan(const VAddr addr, const std::size_t size) const noexcept { + return (current_page_table->entries[addr >> YUZU_PAGEBITS].Block() == current_page_table->entries[(addr + size) >> YUZU_PAGEBITS].Block()) ? GetPointerSilent(addr) : nullptr; + } + [[nodiscard]] inline u8* GetSpan(const VAddr addr, const std::size_t size) noexcept { + return (current_page_table->entries[addr >> YUZU_PAGEBITS].Block() == current_page_table->entries[(addr + size) >> YUZU_PAGEBITS].Block()) ? GetPointerSilent(addr) : nullptr; + } + + bool WriteBlockImpl(const Common::ProcessAddress addr, const void* buffer, const std::size_t size, bool unsafe) { + return WalkBlock(addr, size, + [addr, size](const std::size_t offset, const std::size_t copy_amount, const Common::ProcessAddress current_vaddr) { + LOG_ERROR(HW_Memory, "Unmapped @ {:#016x} (start address = {:#016x}, size = {})", GetInteger(current_vaddr), GetInteger(addr), size); + }, + [&](const std::size_t offset, const std::size_t copy_amount, u8* const ptr) { + std::memcpy(ptr, reinterpret_cast(buffer) + offset, copy_amount); + }, + [&](const Common::ProcessAddress current_vaddr, const std::size_t offset, const std::size_t copy_amount, u8* const ptr) { + if (!unsafe) { + HandleRasterizerWrite(GetInteger(current_vaddr), copy_amount); + } + std::memcpy(ptr, reinterpret_cast(buffer) + offset, copy_amount); + }); + } + + bool ZeroBlock(const Common::ProcessAddress addr, const std::size_t size) { + return WalkBlock(addr, size, + [addr, size](const std::size_t offset, const std::size_t copy_amount, const Common::ProcessAddress current_vaddr) { + LOG_ERROR(HW_Memory, "Unmapped @ {:#016x} (start address = {:#016x}, size = {})", GetInteger(current_vaddr), GetInteger(addr), size); + }, + [=](const std::size_t offset, const std::size_t copy_amount, u8* const ptr) { + std::memset(ptr, 0, copy_amount); + }, + [&](const Common::ProcessAddress current_vaddr, const std::size_t offset, const std::size_t copy_amount, u8* const ptr) { + HandleRasterizerWrite(GetInteger(current_vaddr), copy_amount); + std::memset(ptr, 0, copy_amount); + }); + } + + bool CopyBlock(Common::ProcessAddress dest_addr, Common::ProcessAddress src_addr, const std::size_t size) { + return WalkBlock(dest_addr, size, + [&](const std::size_t offset, const std::size_t copy_amount, const Common::ProcessAddress current_vaddr) { + LOG_ERROR(HW_Memory, "Unmapped @ {:#016x} (start address = {:#016x}, size = {})", GetInteger(current_vaddr), GetInteger(src_addr), size); + ZeroBlock(dest_addr + offset, copy_amount); + }, + [&](const std::size_t offset, const std::size_t copy_amount, const u8* const ptr) { + WriteBlockImpl(dest_addr + offset, ptr, copy_amount, false); + }, + [&](const Common::ProcessAddress current_vaddr, const std::size_t offset, const std::size_t copy_amount, u8* const ptr) { + HandleRasterizerDownload(GetInteger(current_vaddr), copy_amount); + WriteBlockImpl(dest_addr + offset, ptr, copy_amount, false); + }); } template - Result PerformCacheOperation(Common::ProcessAddress dest_addr, std::size_t size, - Callback&& cb) { - class InvalidMemoryException : public std::exception {}; - - try { - WalkBlock( - dest_addr, size, - [&](const std::size_t block_size, const Common::ProcessAddress current_vaddr) { - LOG_ERROR(HW_Memory, "Unmapped cache maintenance @ {:#018X}", - GetInteger(current_vaddr)); - throw InvalidMemoryException(); - }, - [&](const std::size_t block_size, u8* const host_ptr) {}, - [&](const Common::ProcessAddress current_vaddr, const std::size_t block_size, - u8* const host_ptr) { cb(current_vaddr, block_size); }, - [](const std::size_t block_size) {}); - } catch (InvalidMemoryException&) { - return Kernel::ResultInvalidCurrentMemory; - } - - return ResultSuccess; + Result PerformCacheOperation(Common::ProcessAddress dest_addr, std::size_t size, Callback&& cb) { + bool had_unmapped = false; + WalkBlock(dest_addr, size, + [&](const std::size_t offset, const std::size_t copy_amount, const Common::ProcessAddress current_vaddr) { + LOG_ERROR(HW_Memory, "Unmapped @ {:#018X}", GetInteger(current_vaddr)); + had_unmapped = true; + }, + [](const std::size_t offset, const std::size_t copy_amount, u8* const host_ptr) {}, + [&](const Common::ProcessAddress current_vaddr, const std::size_t offset, const std::size_t copy_amount, u8* const host_ptr) { cb(current_vaddr, offset, copy_amount); } + ); + return had_unmapped ? Kernel::ResultInvalidCurrentMemory : ResultSuccess; } Result InvalidateDataCache(Common::ProcessAddress dest_addr, std::size_t size) { - auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, - const std::size_t block_size) { + auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, const std::size_t offset, const std::size_t block_size) { // dc ivac: Invalidate to point of coherency // GPU flush -> CPU invalidate HandleRasterizerDownload(GetInteger(current_vaddr), block_size); @@ -460,8 +374,7 @@ struct Memory::Impl { } Result StoreDataCache(Common::ProcessAddress dest_addr, std::size_t size) { - auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, - const std::size_t block_size) { + auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, const std::size_t offset, const std::size_t block_size) { // dc cvac: Store to point of coherency // CPU flush -> GPU invalidate HandleRasterizerWrite(GetInteger(current_vaddr), block_size); @@ -470,8 +383,7 @@ struct Memory::Impl { } Result FlushDataCache(Common::ProcessAddress dest_addr, std::size_t size) { - auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, - const std::size_t block_size) { + auto on_rasterizer = [&](const Common::ProcessAddress current_vaddr, const std::size_t offset, const std::size_t block_size) { // dc civac: Store to point of coherency, and invalidate from cache // CPU flush -> GPU invalidate HandleRasterizerWrite(GetInteger(current_vaddr), block_size); @@ -487,18 +399,21 @@ struct Memory::Impl { if (current_page_table->fastmem_arena) { const auto perm{debug ? Common::MemoryPermission{} : Common::MemoryPermission::ReadWrite}; - buffer->Protect(vaddr, size, perm); + host_buffer->Protect(vaddr, size, perm); } // Iterate over a contiguous CPU address space, marking/unmarking the region. // The region is at a granularity of CPU pages. const u64 num_pages = ((vaddr + size - 1) >> YUZU_PAGEBITS) - (vaddr >> YUZU_PAGEBITS) + 1; + + current_page_table->entries.CommitRegion(vaddr >> YUZU_PAGEBITS, (vaddr >> YUZU_PAGEBITS) + num_pages); for (u64 i = 0; i < num_pages; ++i, vaddr += YUZU_PAGESIZE) { - const Common::PageType page_type = current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Type(); + auto& entry = current_page_table->entries.GetUnchecked(vaddr >> YUZU_PAGEBITS); + const auto [pointer, type, block] = entry.PointerTypeBlock(true); if (debug) { // Switch page type to debug if now debug - switch (page_type) { + switch (type) { case Common::PageType::Unmapped: ASSERT(false && "Attempted to mark unmapped pages as debug"); break; @@ -507,14 +422,14 @@ struct Memory::Impl { // Page is already marked. break; case Common::PageType::Memory: - current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Store(0, Common::PageType::DebugMemory); + entry.MarkDebug(pointer, block); break; default: UNREACHABLE(); } } else { // Switch page type to non-debug if now non-debug - switch (page_type) { + switch (type) { case Common::PageType::Unmapped: ASSERT(false && "Attempted to mark unmapped pages as non-debug"); break; @@ -523,8 +438,7 @@ struct Memory::Impl { // Don't mess with already non-debug or rasterizer memory. break; case Common::PageType::DebugMemory: { - u8* const pointer = GetPointerFromDebugMemory(vaddr & ~YUZU_PAGEMASK); - current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Store(uintptr_t(pointer) - (vaddr & ~YUZU_PAGEMASK), Common::PageType::Memory); + entry.Store(false, Common::PageType::Memory, block, pointer); break; } default: @@ -547,7 +461,7 @@ struct Memory::Impl { if (!cached) { perm |= Common::MemoryPermission::Write; } - buffer->Protect(vaddr, size, perm); + host_buffer->Protect(vaddr, size, perm); } // Iterate over a contiguous CPU address space, which corresponds to the specified GPU @@ -556,8 +470,10 @@ struct Memory::Impl { // is different). This assumes the specified GPU address region is contiguous as well. const u64 num_pages = ((vaddr + size - 1) >> YUZU_PAGEBITS) - (vaddr >> YUZU_PAGEBITS) + 1; + current_page_table->entries.CommitRegion(vaddr >> YUZU_PAGEBITS, (vaddr >> YUZU_PAGEBITS) + num_pages); for (u64 i = 0; i < num_pages; ++i, vaddr += YUZU_PAGESIZE) { - const Common::PageType page_type= current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Type(); + auto& entry = current_page_table->entries.GetUnchecked(vaddr >> YUZU_PAGEBITS); + const Common::PageType page_type = entry.Type(); if (cached) { // Switch page type to cached if now cached switch (page_type) { @@ -567,7 +483,7 @@ struct Memory::Impl { break; case Common::PageType::DebugMemory: case Common::PageType::Memory: - current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Store(0, Common::PageType::RasterizerCachedMemory); + entry.MarkRasterizerCached(); break; case Common::PageType::RasterizerCachedMemory: // There can be more than one GPU region mapped per CPU region, so it's common @@ -589,13 +505,13 @@ struct Memory::Impl { // that this area is already unmarked as cached. break; case Common::PageType::RasterizerCachedMemory: { - if (u8* const pointer = GetPointerFromRasterizerCachedMemory(vaddr & ~YUZU_PAGEMASK); pointer == nullptr) { + if (auto [ptr, _, block] = entry.PointerTypeBlock(true); ptr == 0) { // It's possible that this function has been called while updating the // pagetable after unmapping a VMA. In that case the underlying VMA will no // longer exist, and we should just leave the pagetable entry blank. - current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Store(0, Common::PageType::Unmapped); + entry.Store(false, Common::PageType::Unmapped, block, 0); } else { - current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Store(uintptr_t(pointer) - (vaddr & ~YUZU_PAGEMASK), Common::PageType::Memory); + entry.Store(false, Common::PageType::Memory, block, ptr); } break; } @@ -629,22 +545,18 @@ struct Memory::Impl { ASSERT_MSG(type != Common::PageType::Memory, "Mapping memory page without a pointer @ {:016x}", base * YUZU_PAGESIZE); - while (base != end) { - page_table.entries[base].ptr.Store(0, type); - page_table.entries[base].addr = 0; - page_table.entries[base].block = 0; - base += 1; - } + page_table.entries.ZeroRegion(base, end); } else { - auto orig_base = base; - while (base != end) { - auto host_ptr = uintptr_t(system.DeviceMemory().GetPointer(target)) - (base << YUZU_PAGEBITS); - auto backing = GetInteger(target) - (base << YUZU_PAGEBITS); - page_table.entries[base].ptr.Store(host_ptr, type); - page_table.entries[base].addr = backing; - page_table.entries[base].block = orig_base << YUZU_PAGEBITS; + auto current_block = block_count.fetch_add(1, std::memory_order_relaxed); + ASSERT(current_block != 65535); - ASSERT_MSG(page_table.entries[base].ptr.Pointer(), + page_table.entries.CommitRegion(base, end); + while (base != end) { + auto host_ptr = reinterpret_cast(system.DeviceMemory().GetPointer(target)) - (base << YUZU_PAGEBITS);; + auto& entry = page_table.entries.GetUnchecked(base); + + entry.Store(false, type, current_block, host_ptr); + ASSERT_MSG(page_table.entries[base].Pointer(), "memory mapping base yield a nullptr within the table"); base += 1; @@ -659,13 +571,13 @@ struct Memory::Impl { vaddr &= 0xffffffffffffULL; if (AddressSpaceContains(*current_page_table, vaddr, 1)) [[likely]] { // Avoid adding any extra logic to this fast-path block - const uintptr_t raw_pointer = current_page_table->entries[vaddr >> YUZU_PAGEBITS].ptr.Raw(); - if (const uintptr_t pointer = Common::PageTable::PageInfo::ExtractPointer(raw_pointer)) [[likely]] { + const auto raw = current_page_table->entries[vaddr >> YUZU_PAGEBITS].Raw(); + if (auto pointer = Common::PageTable::PageEntryData::ExtractPointer(raw); pointer) [[likely]] { return reinterpret_cast(pointer + vaddr); } else { - switch (Common::PageTable::PageInfo::ExtractType(raw_pointer)) { + switch (static_cast(raw.type)) { case Common::PageType::Memory: - ASSERT_MSG(false, "Mapped memory page without a pointer @ 0x{:016X}", vaddr); + ASSERT_MSG(false, "Mapped memory page without a pointer @ {:#016x}", vaddr); return nullptr; case Common::PageType::DebugMemory: return GetPointerFromDebugMemory(vaddr); @@ -693,7 +605,7 @@ struct Memory::Impl { return GetPointerImpl( GetInteger(vaddr), [vaddr]() { - LOG_ERROR(HW_Memory, "Unmapped GetPointer @ 0x{:016X}", GetInteger(vaddr)); + LOG_ERROR(HW_Memory, "Unmapped GetPointer @ {:#016x}", GetInteger(vaddr)); }, []() {}); } @@ -711,7 +623,7 @@ struct Memory::Impl { inline T Read(Common::ProcessAddress vaddr) noexcept requires(std::is_trivially_copyable_v) { const u64 addr = GetInteger(vaddr); if (auto const ptr = GetPointerImpl(addr, [addr]() { - LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8, addr); + LOG_ERROR(HW_Memory, "Unmapped Read{} @ {:#016x}", sizeof(T) * 8, addr); }, [&]() { HandleRasterizerDownload(addr, sizeof(T)); }); ptr) [[likely]] { @@ -732,7 +644,7 @@ struct Memory::Impl { inline void Write(Common::ProcessAddress vaddr, const T data) noexcept requires(std::is_trivially_copyable_v) { const u64 addr = GetInteger(vaddr); if (auto const ptr = GetPointerImpl(addr, [addr, data]() { - LOG_ERROR(HW_Memory, "Unmapped Write{} @ 0x{:016X} = 0x{:016X}", sizeof(T) * 8, addr, u64(data)); + LOG_ERROR(HW_Memory, "Unmapped Write{} @ {:#016x} = {:#016x}", sizeof(T) * 8, addr, u64(data)); }, [&]() { HandleRasterizerWrite(addr, sizeof(T)); }); ptr) [[likely]] std::memcpy(ptr, &data, sizeof(T)); } @@ -742,7 +654,7 @@ struct Memory::Impl { u8* const ptr = GetPointerImpl( GetInteger(vaddr), [vaddr, data]() { - LOG_ERROR(HW_Memory, "Unmapped WriteExclusive{} @ 0x{:016X} = 0x{:016X}", + LOG_ERROR(HW_Memory, "Unmapped WriteExclusive{} @ {:#016x} = {:#016x}", sizeof(T) * 8, GetInteger(vaddr), static_cast(data)); }, [&]() { HandleRasterizerWrite(GetInteger(vaddr), sizeof(T)); }); @@ -756,7 +668,7 @@ struct Memory::Impl { u8* const ptr = GetPointerImpl( GetInteger(vaddr), [vaddr, data]() { - LOG_ERROR(HW_Memory, "Unmapped WriteExclusive128 @ 0x{:016X} = 0x{:016X}{:016X}", + LOG_ERROR(HW_Memory, "Unmapped WriteExclusive128 @ {:#016x} = {:#016x}{:016X}", GetInteger(vaddr), static_cast(data[1]), static_cast(data[0])); }, [&]() { HandleRasterizerWrite(GetInteger(vaddr), sizeof(u128)); }); @@ -845,7 +757,7 @@ struct Memory::Impl { }; auto& gpu = system.GPU(); gpu_device_memory->ApplyOpOnPointer( - p, scratch_buffers[core], [&](DAddr address) { gpu.InvalidateRegion(address, size); }); + p, scratch_buffers[core], [&](DAddr address) { gpu.InvalidateRegion(address, size, true); }); } Core::System& system; @@ -859,10 +771,11 @@ struct Memory::Impl { std::mutex sys_core_guard; #ifdef __ANDROID__ std::optional heap_tracker; - Common::HeapTracker* buffer{}; + Common::HeapTracker* host_buffer{}; #else - Common::HostMemory* buffer{}; + Common::HostMemory* host_buffer{}; #endif + std::atomic block_count = 0; }; Memory::Memory(Core::System& system_) : system{system_} { @@ -901,7 +814,7 @@ bool Memory::IsValidVirtualAddress(const Common::ProcessAddress vaddr) const { if (page >= page_table.entries.size()) { return false; } - const auto [pointer, type] = page_table.entries[page].ptr.PointerType(); + const auto [pointer, type, _] = page_table.entries[page].PointerTypeBlock(); return pointer != 0 || type == Common::PageType::RasterizerCachedMemory || type == Common::PageType::DebugMemory; } @@ -989,12 +902,12 @@ std::string Memory::ReadCString(Common::ProcessAddress vaddr, std::size_t max_le bool Memory::ReadBlock(const Common::ProcessAddress src_addr, void* dest_buffer, const std::size_t size) { - return impl->ReadBlock(src_addr, dest_buffer, size); + return impl->ReadBlockImpl(src_addr, dest_buffer, size, false); } bool Memory::ReadBlockUnsafe(const Common::ProcessAddress src_addr, void* dest_buffer, const std::size_t size) { - return impl->ReadBlockUnsafe(src_addr, dest_buffer, size); + return impl->ReadBlockImpl(src_addr, dest_buffer, size, true); } const u8* Memory::GetSpan(const VAddr src_addr, const std::size_t size) const { @@ -1007,12 +920,12 @@ u8* Memory::GetSpan(const VAddr src_addr, const std::size_t size) { bool Memory::WriteBlock(const Common::ProcessAddress dest_addr, const void* src_buffer, const std::size_t size) { - return impl->WriteBlock(dest_addr, src_buffer, size); + return impl->WriteBlockImpl(dest_addr, src_buffer, size, false); } bool Memory::WriteBlockUnsafe(const Common::ProcessAddress dest_addr, const void* src_buffer, const std::size_t size) { - return impl->WriteBlockUnsafe(dest_addr, src_buffer, size); + return impl->WriteBlockImpl(dest_addr, src_buffer, size, true); } bool Memory::CopyBlock(Common::ProcessAddress dest_addr, Common::ProcessAddress src_addr, @@ -1066,7 +979,7 @@ bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) { #ifdef __ANDROID__ if (!rasterizer && mapped) { - impl->buffer->DeferredMapSeparateHeap(GetInteger(vaddr)); + impl->host_buffer->DeferredMapSeparateHeap(GetInteger(vaddr)); } #endif @@ -1075,7 +988,7 @@ bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) { bool Memory::InvalidateSeparateHeap(void* fault_address) { #ifdef __ANDROID__ - return impl->buffer->DeferredMapSeparateHeap(static_cast(fault_address)); + return impl->host_buffer->DeferredMapSeparateHeap(static_cast(fault_address)); #else return false; #endif diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp index dcfd23644f..e14147f5c9 100644 --- a/src/core/memory/cheat_engine.cpp +++ b/src/core/memory/cheat_engine.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -51,24 +51,41 @@ StandardVmCallbacks::StandardVmCallbacks(System& system_, const CheatProcessMeta StandardVmCallbacks::~StandardVmCallbacks() = default; +Kernel::KProcess* StandardVmCallbacks::GetProcess() const { + if (cached_process != nullptr && cached_process_id == metadata.process_id) { + return cached_process; + } + + auto process = system.Kernel().GetProcessByProcessId(metadata.process_id); + cached_process = process.IsNull() ? nullptr : process.GetPointerUnsafe(); + cached_process_id = metadata.process_id; + return cached_process; +} + void StandardVmCallbacks::MemoryReadUnsafe(VAddr address, void* data, u64 size) { + auto* const process = this->GetProcess(); + // Return zero on invalid address - if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) { + if (process == nullptr || !IsAddressInRange(address) || + !process->GetMemory().IsValidVirtualAddress(address)) { std::memset(data, 0, size); return; } - system.ApplicationMemory().ReadBlock(address, data, size); + process->GetMemory().ReadBlock(address, data, size); } void StandardVmCallbacks::MemoryWriteUnsafe(VAddr address, const void* data, u64 size) { + auto* const process = this->GetProcess(); + // Skip invalid memory write address - if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) { + if (process == nullptr || !IsAddressInRange(address) || + !process->GetMemory().IsValidVirtualAddress(address)) { return; } - if (system.ApplicationMemory().WriteBlock(address, data, size)) { - Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), address, size); + if (process->GetMemory().WriteBlock(address, data, size)) { + Core::InvalidateInstructionCacheRange(process, address, size); } } @@ -91,17 +108,17 @@ u64 StandardVmCallbacks::HidKeysDown() { } void StandardVmCallbacks::PauseProcess() { - if (system.ApplicationProcess()->IsSuspended()) { - return; + auto* const process = this->GetProcess(); + if (process != nullptr && !process->IsSuspended()) { + process->SetActivity(system.Kernel(), Kernel::Svc::ProcessActivity::Paused); } - system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Paused); } void StandardVmCallbacks::ResumeProcess() { - if (!system.ApplicationProcess()->IsSuspended()) { - return; + auto* const process = this->GetProcess(); + if (process != nullptr && process->IsSuspended()) { + process->SetActivity(system.Kernel(), Kernel::Svc::ProcessActivity::Runnable); } - system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Runnable); } void StandardVmCallbacks::DebugLog(u8 id, u64 value) { diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h index f52f2be7c3..abd3abc93f 100644 --- a/src/core/memory/cheat_engine.h +++ b/src/core/memory/cheat_engine.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -15,6 +18,10 @@ namespace Core { class System; } +namespace Kernel { +class KProcess; +} + namespace Core::Timing { class CoreTiming; struct EventType; @@ -38,8 +45,12 @@ public: private: bool IsAddressInRange(VAddr address) const; + Kernel::KProcess* GetProcess() const; + const CheatProcessMetadata& metadata; Core::System& system; + mutable Kernel::KProcess* cached_process{}; + mutable u64 cached_process_id{}; }; // Intermediary class that parses a text file or other disk format for storing cheats into a diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp index 1723636811..d7edb3ffcb 100644 --- a/src/core/reporter.cpp +++ b/src/core/reporter.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -20,6 +20,7 @@ #include "common/settings.h" #include "core/arm/arm_interface.h" #include "core/core.h" +#include "core/hle/ipc.h" #include "core/hle/kernel/k_page_table.h" #include "core/hle/kernel/k_process.h" #include "core/hle/result.h" @@ -124,8 +125,7 @@ json GetFullDataAuto(const std::string& timestamp, u64 title_id, Core::System& s } template -json GetHLEBufferDescriptorData(const std::vector& buffer, - Core::Memory::Memory& memory) { +json GetHLEBufferDescriptorData(const boost::container::static_vector& buffer, Core::Memory::Memory& memory) { auto buffer_out = json::array(); for (const auto& desc : buffer) { auto entry = json{ @@ -239,7 +239,7 @@ void Reporter::SaveUnimplementedFunctionReport(Service::HLERequestContext& ctx, const auto title_id = system.GetApplicationProcessProgramID(); auto out = GetFullDataAuto(timestamp, title_id, system); - auto function_out = GetHLERequestContextData(ctx, system.ApplicationMemory()); + auto function_out = GetHLERequestContextData(ctx, ctx.GetMemory()); function_out["command_id"] = command_id; function_out["function_name"] = name; function_out["service_name"] = service_name; diff --git a/src/core/tools/freezer.cpp b/src/core/tools/freezer.cpp index 73a86b3c8c..606ac2da42 100644 --- a/src/core/tools/freezer.cpp +++ b/src/core/tools/freezer.cpp @@ -33,18 +33,10 @@ u64 MemoryReadWidth(Core::Memory::Memory& memory, u32 width, VAddr addr) { void MemoryWriteWidth(Core::Memory::Memory& memory, u32 width, VAddr addr, u64 value) { switch (width) { - case 1: - memory.Write8(addr, static_cast(value)); - break; - case 2: - memory.Write16(addr, static_cast(value)); - break; - case 4: - memory.Write32(addr, static_cast(value)); - break; - case 8: - memory.Write64(addr, value); - break; + case sizeof(u64): return memory.Write64(addr, value); + case sizeof(u32): return memory.Write32(addr, u32(value)); + case sizeof(u16): return memory.Write16(addr, u16(value)); + case sizeof(u8): return memory.Write8(addr, u8(value)); default: UNREACHABLE(); } diff --git a/src/core/tools/renderdoc.cpp b/src/core/tools/renderdoc.cpp index 4d91956902..6a6df23432 100644 --- a/src/core/tools/renderdoc.cpp +++ b/src/core/tools/renderdoc.cpp @@ -31,7 +31,7 @@ RenderdocAPI::RenderdocAPI() { #elif defined(__HAIKU__) // no rtld on haiku #else -#ifdef ANDROID +#ifdef __ANDROID__ static constexpr const char RENDERDOC_LIB[] = "libVkLayer_GLES_RenderDoc.so"; #else static constexpr const char RENDERDOC_LIB[] = "librenderdoc.so"; diff --git a/src/dedicated_room/CMakeLists.txt b/src/dedicated_room/CMakeLists.txt index 36c8af5e34..8ca138473f 100644 --- a/src/dedicated_room/CMakeLists.txt +++ b/src/dedicated_room/CMakeLists.txt @@ -7,8 +7,7 @@ add_library(yuzu-room STATIC EXCLUDE_FROM_ALL yuzu_room.cpp yuzu_room.h - yuzu_room.rc -) + yuzu_room.rc) target_link_libraries(yuzu-room PRIVATE common network) if (ENABLE_WEB_SERVICE) diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp index d5b8c36141..824fd0f26c 100644 --- a/src/dedicated_room/yuzu_room.cpp +++ b/src/dedicated_room/yuzu_room.cpp @@ -24,7 +24,6 @@ #include #include "common/common_types.h" -#include "common/detached_tasks.h" #include "common/fs/file.h" #include "common/fs/fs.h" #include "common/fs/path_util.h" @@ -172,9 +171,7 @@ static void SaveBanList(const Network::Room::BanList& ban_list, const std::strin } /// Application entry point -void LaunchRoom(int argc, char** argv, bool called_by_option) -{ - Common::DetachedTasks detached_tasks; +void LaunchRoom(int argc, char** argv, bool called_by_option) { int option_index = 0; char* endarg; @@ -393,6 +390,5 @@ void LaunchRoom(int argc, char** argv, bool called_by_option) room->Destroy(); } Network::Shutdown(); - detached_tasks.WaitForAllTasks(); std::exit(0); } diff --git a/src/dynarmic/CMakeLists.txt b/src/dynarmic/CMakeLists.txt index 4c4bf86d3e..bacbd7ab57 100644 --- a/src/dynarmic/CMakeLists.txt +++ b/src/dynarmic/CMakeLists.txt @@ -25,7 +25,7 @@ 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) -if (PLATFORM_OPENBSD OR PLATFORM_DRAGONFLY OR PLATFORM_NETBSD) +if (OPENBSD OR DRAGONFLY OR NETBSD) set(REQUIRE_WX ON) else() set(REQUIRE_WX OFF) @@ -120,7 +120,6 @@ if (NOT Boost_FOUND) endif() find_package(fmt 8 CONFIG) -find_package(unordered_dense REQUIRED) if ("arm64" IN_LIST ARCHITECTURE OR DYNARMIC_TESTS) find_package(oaknut 2.0.1 CONFIG) @@ -130,6 +129,10 @@ if ("riscv64" IN_LIST ARCHITECTURE) find_package(biscuit 0.9.1 REQUIRED) endif() +if ("loongarch64" IN_LIST ARCHITECTURE) + find_package(lagoon REQUIRED) +endif() + if ("x86_64" IN_LIST ARCHITECTURE) find_package(xbyak 7 CONFIG) endif() diff --git a/src/dynarmic/CMakeModules/dynarmicConfig.cmake.in b/src/dynarmic/CMakeModules/dynarmicConfig.cmake.in index ce14607c89..baa1db7c45 100644 --- a/src/dynarmic/CMakeModules/dynarmicConfig.cmake.in +++ b/src/dynarmic/CMakeModules/dynarmicConfig.cmake.in @@ -8,7 +8,6 @@ if (NOT @BUILD_SHARED_LIBS@) find_dependency(Boost 1.57) find_dependency(fmt 9) find_dependency(mcl 0.1.12 EXACT) - find_dependency(unordered_dense) if ("arm64" IN_LIST ARCHITECTURE) find_dependency(oaknut 2.0.1) diff --git a/src/dynarmic/src/dynarmic/CMakeLists.txt b/src/dynarmic/src/dynarmic/CMakeLists.txt index c51a660c03..cc1cbcbe39 100644 --- a/src/dynarmic/src/dynarmic/CMakeLists.txt +++ b/src/dynarmic/src/dynarmic/CMakeLists.txt @@ -295,6 +295,34 @@ if ("riscv64" IN_LIST ARCHITECTURE) message(WARNING "TODO: Incomplete frontend for this host architecture") endif() +if ("loongarch64" IN_LIST ARCHITECTURE) + target_link_libraries(dynarmic PRIVATE lagoon::lagoon) + + target_sources(dynarmic PRIVATE + backend/loongarch64/abi.h + backend/loongarch64/a32_jitstate.cpp + backend/loongarch64/a32_jitstate.h + backend/loongarch64/code_block.h + backend/loongarch64/emit_context.h + backend/loongarch64/emit_loongarch64.cpp + backend/loongarch64/emit_loongarch64.h + backend/loongarch64/emit_loongarch64_a32.cpp + backend/loongarch64/emit_loongarch64_data_processing.cpp + backend/loongarch64/a32_address_space.cpp + backend/loongarch64/a32_address_space.h + backend/loongarch64/a32_interface.cpp + backend/loongarch64/a64_interface.cpp + backend/loongarch64/exclusive_monitor.cpp + backend/loongarch64/reg_alloc.cpp + backend/loongarch64/reg_alloc.h + backend/loongarch64/stack_layout.h + backend/loongarch64/lagoon_cpp.h + + common/spin_lock_loongarch64.cpp + ) + message(WARNING "TODO: Incomplete frontend for this host architecture") +endif() + if (WIN32) target_sources(dynarmic PRIVATE backend/exception_handler_windows.cpp) elseif (APPLE) @@ -335,7 +363,7 @@ elseif (APPLE) backend/exception_handler_macos_mig.c ) endif() -elseif (UNIX AND NOT PLATFORM_HAIKU) +elseif (UNIX AND NOT HAIKUOS) # Haiku lacks if (CMAKE_SYSTEM_NAME STREQUAL "Linux") target_link_libraries(dynarmic PRIVATE rt) @@ -359,7 +387,6 @@ set_target_properties(dynarmic PROPERTIES target_compile_options(dynarmic PRIVATE ${DYNARMIC_CXX_FLAGS}) -target_link_libraries(dynarmic PRIVATE unordered_dense::unordered_dense) target_link_libraries(dynarmic PUBLIC fmt::fmt common) if (BOOST_NO_HEADERS) diff --git a/src/dynarmic/src/dynarmic/backend/arm64/a32_address_space.cpp b/src/dynarmic/src/dynarmic/backend/arm64/a32_address_space.cpp index da51220e9c..0cf9bb9fae 100644 --- a/src/dynarmic/src/dynarmic/backend/arm64/a32_address_space.cpp +++ b/src/dynarmic/src/dynarmic/backend/arm64/a32_address_space.cpp @@ -24,29 +24,65 @@ namespace Dynarmic::Backend::Arm64 { template -static void* EmitCallTrampoline(oaknut::CodeGenerator& code, T* this_) { +static void* EmitCallReadTrampoline(oaknut::CodeGenerator& code, T* this_, size_t bitsize) { using namespace oaknut::util; - const auto info = Devirtualize(this_); - oaknut::Label l_addr, l_this; - void* target = code.xptr(); + // params = { this, vaddr, bitsize } code.LDR(X0, l_this); + // X1 = vaddr + code.MOV(X2, bitsize); code.LDR(Xscratch0, l_addr); code.BR(Xscratch0); - code.align(8); code.l(l_this); code.dx(info.this_ptr); code.l(l_addr); code.dx(info.fn_ptr); - return target; } template -static void* EmitWrappedReadCallTrampoline(oaknut::CodeGenerator& code, T* this_) { +static void* EmitCallWriteTrampoline(oaknut::CodeGenerator& code, T* this_, size_t bitsize) { + using namespace oaknut::util; + const auto info = Devirtualize(this_); + oaknut::Label l_addr, l_this; + void* target = code.xptr(); + // params = { this, vaddr, value, bitsize } + code.LDR(X0, l_this); + // X1 = vaddr + // X2 = value + code.MOV(X3, bitsize); + code.LDR(Xscratch0, l_addr); + code.BR(Xscratch0); + code.align(8); + code.l(l_this); + code.dx(info.this_ptr); + code.l(l_addr); + code.dx(info.fn_ptr); + return target; +} + +template +static void* EmitCallTrampoline(oaknut::CodeGenerator& code, T* this_) { + using namespace oaknut::util; + const auto info = Devirtualize(this_); + oaknut::Label l_addr, l_this; + void* target = code.xptr(); + code.LDR(X0, l_this); + code.LDR(Xscratch0, l_addr); + code.BR(Xscratch0); + code.align(8); + code.l(l_this); + code.dx(info.this_ptr); + code.l(l_addr); + code.dx(info.fn_ptr); + return target; +} + +template +static void* EmitWrappedReadCallTrampoline(oaknut::CodeGenerator& code, T* this_, size_t bitsize) { using namespace oaknut::util; const auto info = Devirtualize(this_); @@ -59,6 +95,7 @@ static void* EmitWrappedReadCallTrampoline(oaknut::CodeGenerator& code, T* this_ ABI_PushRegisters(code, save_regs, 0); code.LDR(X0, l_this); code.MOV(X1, Xscratch0); + code.MOV(X2, bitsize); code.LDR(Xscratch0, l_addr); code.BLR(Xscratch0); code.MOV(Xscratch0, X0); @@ -82,7 +119,7 @@ static void* EmitExclusiveReadCallTrampoline(oaknut::CodeGenerator& code, const auto fn = [](const A32::UserConfig& conf, A32::VAddr vaddr) -> T { return conf.global_monitor->ReadAndMark(conf.processor_id, vaddr, [&]() -> T { - return (conf.callbacks->*callback)(vaddr); + return (conf.callbacks->*callback)(vaddr, sizeof(T)); }); }; @@ -101,7 +138,7 @@ static void* EmitExclusiveReadCallTrampoline(oaknut::CodeGenerator& code, const } template -static void* EmitWrappedWriteCallTrampoline(oaknut::CodeGenerator& code, T* this_) { +static void* EmitWrappedWriteCallTrampoline(oaknut::CodeGenerator& code, T* this_, size_t bitsize) { using namespace oaknut::util; const auto info = Devirtualize(this_); @@ -115,6 +152,7 @@ static void* EmitWrappedWriteCallTrampoline(oaknut::CodeGenerator& code, T* this code.LDR(X0, l_this); code.MOV(X1, Xscratch0); code.MOV(X2, Xscratch1); + code.MOV(X3, bitsize); code.LDR(Xscratch0, l_addr); code.BLR(Xscratch0); ABI_PopRegisters(code, save_regs, 0); @@ -136,12 +174,9 @@ static void* EmitExclusiveWriteCallTrampoline(oaknut::CodeGenerator& code, const oaknut::Label l_addr, l_this; auto fn = [](const A32::UserConfig& conf, A32::VAddr vaddr, T value) -> u32 { - return conf.global_monitor->DoExclusiveOperation(conf.processor_id, vaddr, - [&](T expected) -> bool { - return (conf.callbacks->*callback)(vaddr, value, expected); - }) - ? 0 - : 1; + return conf.global_monitor->DoExclusiveOperation(conf.processor_id, vaddr, [&](T expected) -> bool { + return (conf.callbacks->*callback)(vaddr, value, expected); + }) ? 0 : 1; }; void* target = code.xptr(); @@ -179,26 +214,27 @@ void A32AddressSpace::EmitPrelude() { UnprotectCodeMemory(); - prelude_info.read_memory_8 = EmitCallTrampoline<&A32::UserCallbacks::MemoryRead8>(code, conf.callbacks); - prelude_info.read_memory_16 = EmitCallTrampoline<&A32::UserCallbacks::MemoryRead16>(code, conf.callbacks); - prelude_info.read_memory_32 = EmitCallTrampoline<&A32::UserCallbacks::MemoryRead32>(code, conf.callbacks); - prelude_info.read_memory_64 = EmitCallTrampoline<&A32::UserCallbacks::MemoryRead64>(code, conf.callbacks); - prelude_info.wrapped_read_memory_8 = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead8>(code, conf.callbacks); - prelude_info.wrapped_read_memory_16 = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead16>(code, conf.callbacks); - prelude_info.wrapped_read_memory_32 = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead32>(code, conf.callbacks); - prelude_info.wrapped_read_memory_64 = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead64>(code, conf.callbacks); - prelude_info.exclusive_read_memory_8 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead8, u8>(code, conf); - prelude_info.exclusive_read_memory_16 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead16, u16>(code, conf); - prelude_info.exclusive_read_memory_32 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead32, u32>(code, conf); - prelude_info.exclusive_read_memory_64 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead64, u64>(code, conf); - prelude_info.write_memory_8 = EmitCallTrampoline<&A32::UserCallbacks::MemoryWrite8>(code, conf.callbacks); - prelude_info.write_memory_16 = EmitCallTrampoline<&A32::UserCallbacks::MemoryWrite16>(code, conf.callbacks); - prelude_info.write_memory_32 = EmitCallTrampoline<&A32::UserCallbacks::MemoryWrite32>(code, conf.callbacks); - prelude_info.write_memory_64 = EmitCallTrampoline<&A32::UserCallbacks::MemoryWrite64>(code, conf.callbacks); - prelude_info.wrapped_write_memory_8 = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite8>(code, conf.callbacks); - prelude_info.wrapped_write_memory_16 = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite16>(code, conf.callbacks); - prelude_info.wrapped_write_memory_32 = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite32>(code, conf.callbacks); - prelude_info.wrapped_write_memory_64 = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite64>(code, conf.callbacks); + + prelude_info.read_memory_8 = EmitCallReadTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u8)); + prelude_info.read_memory_16 = EmitCallReadTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u16)); + prelude_info.read_memory_32 = EmitCallReadTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u32)); + prelude_info.read_memory_64 = EmitCallReadTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u64)); + prelude_info.wrapped_read_memory_8 = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u8)); + prelude_info.wrapped_read_memory_16 = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u16)); + prelude_info.wrapped_read_memory_32 = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u32)); + prelude_info.wrapped_read_memory_64 = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u64)); + prelude_info.exclusive_read_memory_8 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead, u8>(code, conf); + prelude_info.exclusive_read_memory_16 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead, u16>(code, conf); + prelude_info.exclusive_read_memory_32 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead, u32>(code, conf); + prelude_info.exclusive_read_memory_64 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead, u64>(code, conf); + prelude_info.write_memory_8 = EmitCallWriteTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u8)); + prelude_info.write_memory_16 = EmitCallWriteTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u16)); + prelude_info.write_memory_32 = EmitCallWriteTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u32)); + prelude_info.write_memory_64 = EmitCallWriteTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u64)); + prelude_info.wrapped_write_memory_8 = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u8)); + prelude_info.wrapped_write_memory_16 = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u16)); + prelude_info.wrapped_write_memory_32 = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u32)); + prelude_info.wrapped_write_memory_64 = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u64)); prelude_info.exclusive_write_memory_8 = EmitExclusiveWriteCallTrampoline<&A32::UserCallbacks::MemoryWriteExclusive8, u8>(code, conf); prelude_info.exclusive_write_memory_16 = EmitExclusiveWriteCallTrampoline<&A32::UserCallbacks::MemoryWriteExclusive16, u16>(code, conf); prelude_info.exclusive_write_memory_32 = EmitExclusiveWriteCallTrampoline<&A32::UserCallbacks::MemoryWriteExclusive32, u32>(code, conf); @@ -371,8 +407,10 @@ EmitConfig A32AddressSpace::GetEmitConfig() { .page_table_pointer = std::bit_cast(conf.page_table), .page_table_address_space_bits = 32, - .page_table_pointer_mask_bits = conf.page_table_pointer_mask_bits, + .page_table_pointer_mask = conf.page_table_pointer_mask, .page_table_log2_stride = conf.page_table_log2_stride, + .page_table_marked_bit = conf.page_table_marked_bit, + .page_table_sign_extension = conf.page_table_sign_extension, .silently_mirror_page_table = true, .absolute_offset_page_table = conf.absolute_offset_page_table, .detect_misaligned_access_via_page_table = conf.detect_misaligned_access_via_page_table, diff --git a/src/dynarmic/src/dynarmic/backend/arm64/a64_address_space.cpp b/src/dynarmic/src/dynarmic/backend/arm64/a64_address_space.cpp index 2c71ffe282..492242fa73 100644 --- a/src/dynarmic/src/dynarmic/backend/arm64/a64_address_space.cpp +++ b/src/dynarmic/src/dynarmic/backend/arm64/a64_address_space.cpp @@ -23,29 +23,65 @@ namespace Dynarmic::Backend::Arm64 { template -static void* EmitCallTrampoline(oaknut::CodeGenerator& code, T* this_) { +static void* EmitCallReadTrampoline(oaknut::CodeGenerator& code, T* this_, size_t bitsize) { using namespace oaknut::util; - const auto info = Devirtualize(this_); - oaknut::Label l_addr, l_this; - void* target = code.xptr(); + // params = { this, vaddr, bitsize } code.LDR(X0, l_this); + // X1 = vaddr + code.MOV(X2, bitsize); code.LDR(Xscratch0, l_addr); code.BR(Xscratch0); - code.align(8); code.l(l_this); code.dx(info.this_ptr); code.l(l_addr); code.dx(info.fn_ptr); - return target; } template -static void* EmitWrappedReadCallTrampoline(oaknut::CodeGenerator& code, T* this_) { +static void* EmitCallWriteTrampoline(oaknut::CodeGenerator& code, T* this_, size_t bitsize) { + using namespace oaknut::util; + const auto info = Devirtualize(this_); + oaknut::Label l_addr, l_this; + void* target = code.xptr(); + // params = { this, vaddr, value, bitsize } + code.LDR(X0, l_this); + // X1 = vaddr + // X2 = value + code.MOV(X3, bitsize); + code.LDR(Xscratch0, l_addr); + code.BR(Xscratch0); + code.align(8); + code.l(l_this); + code.dx(info.this_ptr); + code.l(l_addr); + code.dx(info.fn_ptr); + return target; +} + +template +static void* EmitCallTrampoline(oaknut::CodeGenerator& code, T* this_) { + using namespace oaknut::util; + const auto info = Devirtualize(this_); + oaknut::Label l_addr, l_this; + void* target = code.xptr(); + code.LDR(X0, l_this); + code.LDR(Xscratch0, l_addr); + code.BR(Xscratch0); + code.align(8); + code.l(l_this); + code.dx(info.this_ptr); + code.l(l_addr); + code.dx(info.fn_ptr); + return target; +} + +template +static void* EmitWrappedReadCallTrampoline(oaknut::CodeGenerator& code, T* this_, size_t bitsize) { using namespace oaknut::util; const auto info = Devirtualize(this_); @@ -58,6 +94,7 @@ static void* EmitWrappedReadCallTrampoline(oaknut::CodeGenerator& code, T* this_ ABI_PushRegisters(code, save_regs, 0); code.LDR(X0, l_this); code.MOV(X1, Xscratch0); + code.MOV(X2, bitsize); code.LDR(Xscratch0, l_addr); code.BLR(Xscratch0); code.MOV(Xscratch0, X0); @@ -81,7 +118,7 @@ static void* EmitExclusiveReadCallTrampoline(oaknut::CodeGenerator& code, const auto fn = [](const A64::UserConfig& conf, A64::VAddr vaddr) -> T { return conf.global_monitor->ReadAndMark(conf.processor_id, vaddr, [&]() -> T { - return (conf.callbacks->*callback)(vaddr); + return (conf.callbacks->*callback)(vaddr, sizeof(T)); }); }; @@ -100,7 +137,7 @@ static void* EmitExclusiveReadCallTrampoline(oaknut::CodeGenerator& code, const } template -static void* EmitWrappedWriteCallTrampoline(oaknut::CodeGenerator& code, T* this_) { +static void* EmitWrappedWriteCallTrampoline(oaknut::CodeGenerator& code, T* this_, size_t bitsize) { using namespace oaknut::util; const auto info = Devirtualize(this_); @@ -114,6 +151,7 @@ static void* EmitWrappedWriteCallTrampoline(oaknut::CodeGenerator& code, T* this code.LDR(X0, l_this); code.MOV(X1, Xscratch0); code.MOV(X2, Xscratch1); + code.MOV(X3, bitsize); code.LDR(Xscratch0, l_addr); code.BLR(Xscratch0); ABI_PopRegisters(code, save_regs, 0); @@ -133,14 +171,10 @@ static void* EmitExclusiveWriteCallTrampoline(oaknut::CodeGenerator& code, const using namespace oaknut::util; oaknut::Label l_addr, l_this; - auto fn = [](const A64::UserConfig& conf, A64::VAddr vaddr, T value) -> u32 { - return conf.global_monitor->DoExclusiveOperation(conf.processor_id, vaddr, - [&](T expected) -> bool { - return (conf.callbacks->*callback)(vaddr, value, expected); - }) - ? 0 - : 1; + return conf.global_monitor->DoExclusiveOperation(conf.processor_id, vaddr, [&](T expected) -> bool { + return (conf.callbacks->*callback)(vaddr, value, expected); + }) ? 0 : 1; }; void* target = code.xptr(); @@ -346,30 +380,30 @@ void A64AddressSpace::EmitPrelude() { UnprotectCodeMemory(); - prelude_info.read_memory_8 = EmitCallTrampoline<&A64::UserCallbacks::MemoryRead8>(code, conf.callbacks); - prelude_info.read_memory_16 = EmitCallTrampoline<&A64::UserCallbacks::MemoryRead16>(code, conf.callbacks); - prelude_info.read_memory_32 = EmitCallTrampoline<&A64::UserCallbacks::MemoryRead32>(code, conf.callbacks); - prelude_info.read_memory_64 = EmitCallTrampoline<&A64::UserCallbacks::MemoryRead64>(code, conf.callbacks); + prelude_info.read_memory_8 = EmitCallReadTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u8)); + prelude_info.read_memory_16 = EmitCallReadTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u16)); + prelude_info.read_memory_32 = EmitCallReadTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u32)); + prelude_info.read_memory_64 = EmitCallReadTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u64)); prelude_info.read_memory_128 = EmitRead128CallTrampoline(code, conf.callbacks); - prelude_info.wrapped_read_memory_8 = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead8>(code, conf.callbacks); - prelude_info.wrapped_read_memory_16 = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead16>(code, conf.callbacks); - prelude_info.wrapped_read_memory_32 = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead32>(code, conf.callbacks); - prelude_info.wrapped_read_memory_64 = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead64>(code, conf.callbacks); + prelude_info.wrapped_read_memory_8 = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u8)); + prelude_info.wrapped_read_memory_16 = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u16)); + prelude_info.wrapped_read_memory_32 = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u32)); + prelude_info.wrapped_read_memory_64 = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u64)); prelude_info.wrapped_read_memory_128 = EmitWrappedRead128CallTrampoline(code, conf.callbacks); - prelude_info.exclusive_read_memory_8 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead8, u8>(code, conf); - prelude_info.exclusive_read_memory_16 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead16, u16>(code, conf); - prelude_info.exclusive_read_memory_32 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead32, u32>(code, conf); - prelude_info.exclusive_read_memory_64 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead64, u64>(code, conf); + prelude_info.exclusive_read_memory_8 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead, u8>(code, conf); + prelude_info.exclusive_read_memory_16 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead, u16>(code, conf); + prelude_info.exclusive_read_memory_32 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead, u32>(code, conf); + prelude_info.exclusive_read_memory_64 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead, u64>(code, conf); prelude_info.exclusive_read_memory_128 = EmitExclusiveRead128CallTrampoline(code, conf); - prelude_info.write_memory_8 = EmitCallTrampoline<&A64::UserCallbacks::MemoryWrite8>(code, conf.callbacks); - prelude_info.write_memory_16 = EmitCallTrampoline<&A64::UserCallbacks::MemoryWrite16>(code, conf.callbacks); - prelude_info.write_memory_32 = EmitCallTrampoline<&A64::UserCallbacks::MemoryWrite32>(code, conf.callbacks); - prelude_info.write_memory_64 = EmitCallTrampoline<&A64::UserCallbacks::MemoryWrite64>(code, conf.callbacks); + prelude_info.write_memory_8 = EmitCallWriteTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u8)); + prelude_info.write_memory_16 = EmitCallWriteTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u16)); + prelude_info.write_memory_32 = EmitCallWriteTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u32)); + prelude_info.write_memory_64 = EmitCallWriteTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u64)); prelude_info.write_memory_128 = EmitWrite128CallTrampoline(code, conf.callbacks); - prelude_info.wrapped_write_memory_8 = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite8>(code, conf.callbacks); - prelude_info.wrapped_write_memory_16 = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite16>(code, conf.callbacks); - prelude_info.wrapped_write_memory_32 = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite32>(code, conf.callbacks); - prelude_info.wrapped_write_memory_64 = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite64>(code, conf.callbacks); + prelude_info.wrapped_write_memory_8 = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u8)); + prelude_info.wrapped_write_memory_16 = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u16)); + prelude_info.wrapped_write_memory_32 = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u32)); + prelude_info.wrapped_write_memory_64 = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u64)); prelude_info.wrapped_write_memory_128 = EmitWrappedWrite128CallTrampoline(code, conf.callbacks); prelude_info.exclusive_write_memory_8 = EmitExclusiveWriteCallTrampoline<&A64::UserCallbacks::MemoryWriteExclusive8, u8>(code, conf); prelude_info.exclusive_write_memory_16 = EmitExclusiveWriteCallTrampoline<&A64::UserCallbacks::MemoryWriteExclusive16, u16>(code, conf); @@ -545,8 +579,10 @@ EmitConfig A64AddressSpace::GetEmitConfig() { .page_table_pointer = std::bit_cast(conf.page_table), .page_table_address_space_bits = conf.page_table_address_space_bits, - .page_table_pointer_mask_bits = conf.page_table_pointer_mask_bits, + .page_table_pointer_mask = conf.page_table_pointer_mask, .page_table_log2_stride = conf.page_table_log2_stride, + .page_table_marked_bit = conf.page_table_marked_bit, + .page_table_sign_extension = conf.page_table_sign_extension, .silently_mirror_page_table = conf.silently_mirror_page_table, .absolute_offset_page_table = conf.absolute_offset_page_table, .detect_misaligned_access_via_page_table = conf.detect_misaligned_access_via_page_table, diff --git a/src/dynarmic/src/dynarmic/backend/arm64/address_space.cpp b/src/dynarmic/src/dynarmic/backend/arm64/address_space.cpp index aa36119830..4ef3296c4a 100644 --- a/src/dynarmic/src/dynarmic/backend/arm64/address_space.cpp +++ b/src/dynarmic/src/dynarmic/backend/arm64/address_space.cpp @@ -75,7 +75,7 @@ CodePtr AddressSpace::GetOrEmit(IR::LocationDescriptor descriptor) { return block_info.entry_point; } -void AddressSpace::InvalidateBasicBlocks(const ankerl::unordered_dense::set& descriptors) { +void AddressSpace::InvalidateBasicBlocks(const ::Common::unordered_set& descriptors) { UnprotectCodeMemory(); for (const auto& descriptor : descriptors) { @@ -145,6 +145,7 @@ void AddressSpace::Link(EmittedBlockInfo& block_info) { case LinkTarget::ReturnFromRunCode: c.B(prelude_info.return_from_run_code); break; + // { this, vaddr, size } case LinkTarget::ReadMemory8: c.BL(prelude_info.read_memory_8); break; @@ -160,6 +161,7 @@ void AddressSpace::Link(EmittedBlockInfo& block_info) { case LinkTarget::ReadMemory128: c.BL(prelude_info.read_memory_128); break; + // { this, vaddr, size } case LinkTarget::WrappedReadMemory8: c.BL(prelude_info.wrapped_read_memory_8); break; @@ -175,6 +177,7 @@ void AddressSpace::Link(EmittedBlockInfo& block_info) { case LinkTarget::WrappedReadMemory128: c.BL(prelude_info.wrapped_read_memory_128); break; + // case LinkTarget::ExclusiveReadMemory8: c.BL(prelude_info.exclusive_read_memory_8); break; @@ -190,6 +193,7 @@ void AddressSpace::Link(EmittedBlockInfo& block_info) { case LinkTarget::ExclusiveReadMemory128: c.BL(prelude_info.exclusive_read_memory_128); break; + // { this, vaddr, value, size } case LinkTarget::WriteMemory8: c.BL(prelude_info.write_memory_8); break; @@ -205,6 +209,7 @@ void AddressSpace::Link(EmittedBlockInfo& block_info) { case LinkTarget::WriteMemory128: c.BL(prelude_info.write_memory_128); break; + // case LinkTarget::WrappedWriteMemory8: c.BL(prelude_info.wrapped_write_memory_8); break; @@ -220,6 +225,7 @@ void AddressSpace::Link(EmittedBlockInfo& block_info) { case LinkTarget::WrappedWriteMemory128: c.BL(prelude_info.wrapped_write_memory_128); break; + // case LinkTarget::ExclusiveWriteMemory8: c.BL(prelude_info.exclusive_write_memory_8); break; diff --git a/src/dynarmic/src/dynarmic/backend/arm64/address_space.h b/src/dynarmic/src/dynarmic/backend/arm64/address_space.h index fba199aefb..755b05bd02 100644 --- a/src/dynarmic/src/dynarmic/backend/arm64/address_space.h +++ b/src/dynarmic/src/dynarmic/backend/arm64/address_space.h @@ -14,7 +14,8 @@ #include "common/common_types.h" #include #include -#include +#include "common/container/unordered_map.h" +#include "common/container/unordered_set.h" #include "dynarmic/backend/arm64/emit_arm64.h" #include "dynarmic/backend/arm64/fastmem.h" @@ -41,7 +42,7 @@ public: CodePtr GetOrEmit(IR::LocationDescriptor descriptor); - void InvalidateBasicBlocks(const ankerl::unordered_dense::set& descriptors); + void InvalidateBasicBlocks(const ::Common::unordered_set& descriptors); void ClearCache(); protected: @@ -76,9 +77,9 @@ protected: // A IR::LocationDescriptor will have one current CodePtr. // However, there can be multiple other CodePtrs which are older, previously invalidated blocks. std::map reverse_block_entries; - ankerl::unordered_dense::map block_entries; - ankerl::unordered_dense::map block_infos; - ankerl::unordered_dense::map> block_references; + ::Common::unordered_map block_entries; + ::Common::unordered_map block_infos; + ::Common::unordered_map> block_references; ExceptionHandler exception_handler; FastmemManager fastmem_manager; diff --git a/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64.h b/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64.h index fefbcd8f5a..693f71ea39 100644 --- a/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64.h +++ b/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64.h @@ -14,7 +14,7 @@ #include #include "common/common_types.h" -#include +#include "common/container/unordered_map.h" #include "dynarmic/backend/arm64/fastmem.h" #include "dynarmic/interface/A32/coprocessor.h" @@ -105,8 +105,8 @@ struct EmittedBlockInfo { CodePtr entry_point; std::size_t size; std::vector relocations; - ankerl::unordered_dense::map> block_relocations; - ankerl::unordered_dense::map fastmem_patch_info; + ::Common::unordered_map> block_relocations; + ::Common::unordered_map fastmem_patch_info; }; struct EmitConfig { @@ -128,8 +128,10 @@ struct EmitConfig { // Page table u64 page_table_pointer; std::size_t page_table_address_space_bits; - int page_table_pointer_mask_bits; + u64 page_table_pointer_mask; std::size_t page_table_log2_stride; + std::optional page_table_marked_bit; + std::optional page_table_sign_extension; bool silently_mirror_page_table; bool absolute_offset_page_table; u8 detect_misaligned_access_via_page_table; diff --git a/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_a32.cpp b/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_a32.cpp index 213403b4ba..b48ff057a7 100644 --- a/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_a32.cpp +++ b/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_a32.cpp @@ -34,7 +34,8 @@ oaknut::Label EmitA32Cond(oaknut::CodeGenerator& code, EmitContext&, IR::Cond co return pass; } -void EmitA32Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::Terminal terminal, IR::LocationDescriptor initial_location, bool is_single_step); +void EmitA32LeafTerminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::LeafTerminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step); +void EmitA32Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::Terminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step); void EmitA32Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::ReturnToDispatch, IR::LocationDescriptor, bool) { EmitRelocation(code, ctx, LinkTarget::ReturnToDispatcher); @@ -125,31 +126,53 @@ void EmitA32Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::Fa void EmitA32Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::If terminal, IR::LocationDescriptor initial_location, bool is_single_step) { oaknut::Label pass = EmitA32Cond(code, ctx, terminal.if_); - EmitA32Terminal(code, ctx, terminal.else_, initial_location, is_single_step); + EmitA32LeafTerminal(code, ctx, terminal.else_, initial_location, is_single_step); code.l(pass); - EmitA32Terminal(code, ctx, terminal.then_, initial_location, is_single_step); + EmitA32LeafTerminal(code, ctx, terminal.then_, initial_location, is_single_step); } void EmitA32Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::CheckBit terminal, IR::LocationDescriptor initial_location, bool is_single_step) { oaknut::Label fail; code.LDRB(Wscratch0, SP, offsetof(StackLayout, check_bit)); code.CBZ(Wscratch0, fail); - EmitA32Terminal(code, ctx, terminal.then_, initial_location, is_single_step); + EmitA32LeafTerminal(code, ctx, terminal.then_, initial_location, is_single_step); code.l(fail); - EmitA32Terminal(code, ctx, terminal.else_, initial_location, is_single_step); + EmitA32LeafTerminal(code, ctx, terminal.else_, initial_location, is_single_step); } void EmitA32Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::CheckHalt terminal, IR::LocationDescriptor initial_location, bool is_single_step) { oaknut::Label fail; code.LDAR(Wscratch0, Xhalt); code.CBNZ(Wscratch0, fail); - EmitA32Terminal(code, ctx, terminal.else_, initial_location, is_single_step); + EmitA32LeafTerminal(code, ctx, terminal.else_, initial_location, is_single_step); code.l(fail); EmitRelocation(code, ctx, LinkTarget::ReturnToDispatcher); } -void EmitA32Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::Terminal terminal, IR::LocationDescriptor initial_location, bool is_single_step) { - boost::apply_visitor([&](const auto& t) { EmitA32Terminal(code, ctx, t, initial_location, is_single_step); }, terminal); +void EmitA32LeafTerminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::LeafTerminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step) { + if (auto const x = std::get_if(&terminal)) + return EmitA32Terminal(code, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA32Terminal(code, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA32Terminal(code, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA32Terminal(code, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA32Terminal(code, ctx, *x, initial_location, is_single_step); + UNREACHABLE(); +} + +void EmitA32Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::Terminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step) { + if (auto const x = std::get_if(&terminal)) + return EmitA32LeafTerminal(code, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA32Terminal(code, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA32Terminal(code, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA32Terminal(code, ctx, *x, initial_location, is_single_step); + UNREACHABLE(); } void EmitA32Terminal(oaknut::CodeGenerator& code, EmitContext& ctx) { diff --git a/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_a64.cpp b/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_a64.cpp index da4364c1b5..69533b9ca4 100644 --- a/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_a64.cpp +++ b/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_a64.cpp @@ -33,7 +33,8 @@ oaknut::Label EmitA64Cond(oaknut::CodeGenerator& code, EmitContext&, IR::Cond co return pass; } -void EmitA64Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::Terminal terminal, IR::LocationDescriptor initial_location, bool is_single_step); +void EmitA64LeafTerminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::LeafTerminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step); +void EmitA64Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::Terminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step); void EmitA64Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::ReturnToDispatch, IR::LocationDescriptor, bool) { EmitRelocation(code, ctx, LinkTarget::ReturnToDispatcher); @@ -108,31 +109,53 @@ void EmitA64Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::Fa void EmitA64Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::If terminal, IR::LocationDescriptor initial_location, bool is_single_step) { oaknut::Label pass = EmitA64Cond(code, ctx, terminal.if_); - EmitA64Terminal(code, ctx, terminal.else_, initial_location, is_single_step); + EmitA64LeafTerminal(code, ctx, terminal.else_, initial_location, is_single_step); code.l(pass); - EmitA64Terminal(code, ctx, terminal.then_, initial_location, is_single_step); + EmitA64LeafTerminal(code, ctx, terminal.then_, initial_location, is_single_step); } void EmitA64Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::CheckBit terminal, IR::LocationDescriptor initial_location, bool is_single_step) { oaknut::Label fail; code.LDRB(Wscratch0, SP, offsetof(StackLayout, check_bit)); code.CBZ(Wscratch0, fail); - EmitA64Terminal(code, ctx, terminal.then_, initial_location, is_single_step); + EmitA64LeafTerminal(code, ctx, terminal.then_, initial_location, is_single_step); code.l(fail); - EmitA64Terminal(code, ctx, terminal.else_, initial_location, is_single_step); + EmitA64LeafTerminal(code, ctx, terminal.else_, initial_location, is_single_step); } void EmitA64Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::CheckHalt terminal, IR::LocationDescriptor initial_location, bool is_single_step) { oaknut::Label fail; code.LDAR(Wscratch0, Xhalt); code.CBNZ(Wscratch0, fail); - EmitA64Terminal(code, ctx, terminal.else_, initial_location, is_single_step); + EmitA64LeafTerminal(code, ctx, terminal.else_, initial_location, is_single_step); code.l(fail); EmitRelocation(code, ctx, LinkTarget::ReturnToDispatcher); } -void EmitA64Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::Terminal terminal, IR::LocationDescriptor initial_location, bool is_single_step) { - boost::apply_visitor([&](const auto& t) { EmitA64Terminal(code, ctx, t, initial_location, is_single_step); }, terminal); +void EmitA64LeafTerminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::LeafTerminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step) { + if (auto const x = std::get_if(&terminal)) + return EmitA64Terminal(code, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA64Terminal(code, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA64Terminal(code, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA64Terminal(code, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA64Terminal(code, ctx, *x, initial_location, is_single_step); + UNREACHABLE(); +} + +void EmitA64Terminal(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Term::Terminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step) { + if (auto const x = std::get_if(&terminal)) + return EmitA64LeafTerminal(code, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA64Terminal(code, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA64Terminal(code, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA64Terminal(code, ctx, *x, initial_location, is_single_step); + UNREACHABLE(); } void EmitA64Terminal(oaknut::CodeGenerator& code, EmitContext& ctx) { diff --git a/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_memory.cpp b/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_memory.cpp index 143244d60b..170fcf9613 100644 --- a/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_memory.cpp +++ b/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_memory.cpp @@ -273,9 +273,18 @@ std::pair InlinePageTableEmitVAddrLookup(oaknut::Cod // load x0 = *<(u8*)pagetable + index> code.LDR(Xscratch0, Xpagetable, Xscratch0); - if (ctx.conf.page_table_pointer_mask_bits != 0) { - const u64 mask = u64(~u64(0)) << ctx.conf.page_table_pointer_mask_bits; - code.AND(Xscratch0, Xscratch0, mask); + if (ctx.conf.page_table_marked_bit) { + code.TST(Xscratch0, 1ULL << *ctx.conf.page_table_marked_bit); + code.B(NE, *fallback); + } + + if (ctx.conf.page_table_pointer_mask != 0) { + code.AND(Xscratch0, Xscratch0, ctx.conf.page_table_pointer_mask); + } + + // TODO: combine this with page_table_pointer_mask + if (ctx.conf.page_table_sign_extension) { + code.SBFM(Xscratch0, Xscratch0, 0, *ctx.conf.page_table_sign_extension); } code.CBZ(Xscratch0, *fallback); diff --git a/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_memory.h b/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_memory.h index 25608dbfe3..3084f3df4d 100644 --- a/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_memory.h +++ b/src/dynarmic/src/dynarmic/backend/arm64/emit_arm64_memory.h @@ -6,6 +6,7 @@ * SPDX-License-Identifier: 0BSD */ +#pragma once #include namespace oaknut { diff --git a/src/dynarmic/src/dynarmic/backend/arm64/fastmem.h b/src/dynarmic/src/dynarmic/backend/arm64/fastmem.h index 2eae740f20..6947392325 100644 --- a/src/dynarmic/src/dynarmic/backend/arm64/fastmem.h +++ b/src/dynarmic/src/dynarmic/backend/arm64/fastmem.h @@ -10,7 +10,7 @@ #include #include -#include +#include "common/container/unordered_set.h" #include "dynarmic/mcl/bit.hpp" #include "common/common_types.h" @@ -24,7 +24,7 @@ using DoNotFastmemMarker = std::tuple; constexpr std::size_t xmrx(std::size_t x) noexcept { x ^= x >> 32; x *= 0xff51afd7ed558ccd; - x ^= mcl::bit::rotate_right(x, 47) ^ mcl::bit::rotate_right(x, 23); + x ^= std::rotr(x, 47) ^ std::rotr(x, 23); return x; } @@ -59,7 +59,7 @@ public: private: ExceptionHandler& exception_handler; - ankerl::unordered_dense::set do_not_fastmem; + ::Common::unordered_set do_not_fastmem; }; } // namespace Dynarmic::Backend::Arm64 diff --git a/src/dynarmic/src/dynarmic/backend/arm64/reg_alloc.h b/src/dynarmic/src/dynarmic/backend/arm64/reg_alloc.h index af21cfbc25..93afcee0b2 100644 --- a/src/dynarmic/src/dynarmic/backend/arm64/reg_alloc.h +++ b/src/dynarmic/src/dynarmic/backend/arm64/reg_alloc.h @@ -18,7 +18,8 @@ #include "common/common_types.h" #include "dynarmic/mcl/is_instance_of_template.hpp" #include -#include +#include "common/container/unordered_map.h" +#include "common/container/unordered_set.h" #include "dynarmic/backend/arm64/stack_layout.h" #include "dynarmic/ir/cond.h" @@ -336,7 +337,7 @@ private: std::array spills; mutable std::size_t alloc_candidate_index = 0; - ankerl::unordered_dense::set defined_insts; + ::Common::unordered_set defined_insts; }; template diff --git a/src/dynarmic/src/dynarmic/backend/block_range_information.cpp b/src/dynarmic/src/dynarmic/backend/block_range_information.cpp index bd7310194f..1a56b7adb6 100644 --- a/src/dynarmic/src/dynarmic/backend/block_range_information.cpp +++ b/src/dynarmic/src/dynarmic/backend/block_range_information.cpp @@ -11,13 +11,13 @@ #include #include #include "common/common_types.h" -#include +#include "common/container/unordered_set.h" namespace Dynarmic::Backend { template void BlockRangeInformation

::AddRange(boost::icl::discrete_interval

range, IR::LocationDescriptor location) { - block_ranges.add(std::make_pair(range, ankerl::unordered_dense::set{location})); + block_ranges.add(std::make_pair(range, ::Common::unordered_set{location})); } template @@ -26,8 +26,8 @@ void BlockRangeInformation

::ClearCache() { } template -ankerl::unordered_dense::set BlockRangeInformation

::InvalidateRanges(const boost::icl::interval_set

& ranges) { - ankerl::unordered_dense::set erase_locations; +::Common::unordered_set BlockRangeInformation

::InvalidateRanges(const boost::icl::interval_set

& ranges) { + ::Common::unordered_set erase_locations; for (auto invalidate_interval : ranges) { auto pair = block_ranges.equal_range(invalidate_interval); for (auto it = pair.first; it != pair.second; ++it) diff --git a/src/dynarmic/src/dynarmic/backend/block_range_information.h b/src/dynarmic/src/dynarmic/backend/block_range_information.h index 2af1a1120b..8f7daa9d9c 100644 --- a/src/dynarmic/src/dynarmic/backend/block_range_information.h +++ b/src/dynarmic/src/dynarmic/backend/block_range_information.h @@ -12,7 +12,8 @@ #include #include -#include +#include "common/container/unordered_map.h" +#include "common/container/unordered_set.h" #include "dynarmic/ir/location_descriptor.h" @@ -23,8 +24,8 @@ class BlockRangeInformation { public: void AddRange(boost::icl::discrete_interval

range, IR::LocationDescriptor location); void ClearCache(); - ankerl::unordered_dense::set InvalidateRanges(const boost::icl::interval_set

& ranges); - boost::icl::interval_map> block_ranges; + ::Common::unordered_set InvalidateRanges(const boost::icl::interval_set

& ranges); + boost::icl::interval_map> block_ranges; }; } // namespace Dynarmic::Backend diff --git a/src/dynarmic/src/dynarmic/backend/exception_handler.h b/src/dynarmic/src/dynarmic/backend/exception_handler.h index b62bd7ab71..ce6abef5ce 100644 --- a/src/dynarmic/src/dynarmic/backend/exception_handler.h +++ b/src/dynarmic/src/dynarmic/backend/exception_handler.h @@ -26,6 +26,10 @@ class CodeBlock; namespace Dynarmic::Backend::RV64 { class CodeBlock; } // namespace Dynarmic::Backend::RV64 +#elif defined(ARCHITECTURE_loongarch64) +namespace Dynarmic::Backend::LoongArch64 { +class CodeBlock; +} // namespace Dynarmic::Backend::LoongArch64 #else # error "Invalid architecture" #endif @@ -45,6 +49,10 @@ struct FakeCall { struct FakeCall { u64 call_sepc; }; +#elif defined(ARCHITECTURE_loongarch64) +struct FakeCall { + u64 call_pc; +}; #else # error "Invalid architecture" #endif @@ -60,6 +68,8 @@ public: void Register(oaknut::CodeBlock& mem, std::size_t mem_size); #elif defined(ARCHITECTURE_riscv64) void Register(RV64::CodeBlock& mem, std::size_t mem_size); +#elif defined(ARCHITECTURE_loongarch64) + void Register(LoongArch64::CodeBlock& mem, std::size_t mem_size); #else # error "Invalid architecture" #endif diff --git a/src/dynarmic/src/dynarmic/backend/exception_handler_generic.cpp b/src/dynarmic/src/dynarmic/backend/exception_handler_generic.cpp index 23dc294511..f19fe98a6d 100644 --- a/src/dynarmic/src/dynarmic/backend/exception_handler_generic.cpp +++ b/src/dynarmic/src/dynarmic/backend/exception_handler_generic.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later /* This file is part of the dynarmic project. @@ -28,6 +28,10 @@ void ExceptionHandler::Register(oaknut::CodeBlock&, std::size_t) { void ExceptionHandler::Register(RV64::CodeBlock&, std::size_t) { // Do nothing } +#elif defined(ARCHITECTURE_loongarch64) +void ExceptionHandler::Register(LoongArch64::CodeBlock&, std::size_t) { + // Do nothing +} #else # error "Invalid architecture" #endif diff --git a/src/dynarmic/src/dynarmic/backend/exception_handler_posix.cpp b/src/dynarmic/src/dynarmic/backend/exception_handler_posix.cpp index 50d552aa0e..24a059aee2 100644 --- a/src/dynarmic/src/dynarmic/backend/exception_handler_posix.cpp +++ b/src/dynarmic/src/dynarmic/backend/exception_handler_posix.cpp @@ -6,20 +6,26 @@ * SPDX-License-Identifier: 0BSD */ +#include + +#include +#include #include #include -#include #include #include -#include #include -#include +#include + +#include "common/container/unordered_map.h" #include -#include -#include "dynarmic/backend/exception_handler.h" +#include + #include "common/assert.h" -#include "dynarmic/common/context.h" +#include "common/logging.h" #include "common/common_types.h" +#include "dynarmic/backend/exception_handler.h" +#include "dynarmic/common/context.h" #if defined(ARCHITECTURE_x86_64) # include "dynarmic/backend/x64/block_of_code.h" #elif defined(ARCHITECTURE_arm64) @@ -27,6 +33,8 @@ # include "dynarmic/backend/arm64/abi.h" #elif defined(ARCHITECTURE_riscv64) # include "dynarmic/backend/riscv64/code_block.h" +#elif defined(ARCHITECTURE_loongarch64) +# include "dynarmic/backend/loongarch64/code_block.h" #else # error "Invalid architecture" #endif @@ -46,23 +54,21 @@ class SigHandler { return e.first <= offset && e.first + e.second.size > offset; }); } - static void SigAction(int sig, siginfo_t* info, void* raw_context); - bool supports_fast_mem = true; - void* signal_stack_memory = nullptr; - ankerl::unordered_dense::map code_block_infos; + ::Common::unordered_map code_block_infos; std::shared_mutex code_block_infos_mutex; struct sigaction old_sa_segv; struct sigaction old_sa_bus; - std::size_t signal_stack_size; + std::unique_ptr signal_stack_memory; + bool supports_fast_mem = true; public: SigHandler() noexcept { - signal_stack_size = std::max(SIGSTKSZ, 2 * 1024 * 1024); - signal_stack_memory = mmap(nullptr, signal_stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + auto const stack_size = std::max(SIGSTKSZ, 2 * 1024 * 1024); + signal_stack_memory = std::make_unique(stack_size); stack_t signal_stack{}; - signal_stack.ss_sp = signal_stack_memory; - signal_stack.ss_size = signal_stack_size; + signal_stack.ss_sp = signal_stack_memory.get(); + signal_stack.ss_size = stack_size; signal_stack.ss_flags = 0; if (sigaltstack(&signal_stack, nullptr) != 0) { fmt::print(stderr, "dynarmic: POSIX SigHandler: init failure at sigaltstack\n"); @@ -80,7 +86,7 @@ public: supports_fast_mem = false; return; } -#ifdef __APPLE__ +#if defined(__APPLE__) if (sigaction(SIGBUS, &sa, &old_sa_bus) != 0) { fmt::print(stderr, "dynarmic: POSIX SigHandler: could not set SIGBUS handler\n"); supports_fast_mem = false; @@ -89,10 +95,6 @@ public: #endif } - ~SigHandler() noexcept { - munmap(signal_stack_memory, signal_stack_size); - } - void AddCodeBlock(u64 offset, CodeBlockInfo cbi) noexcept { std::unique_lock guard(code_block_infos_mutex); code_block_infos.insert_or_assign(offset, cbi); @@ -102,14 +104,17 @@ public: code_block_infos.erase(offset); } - bool SupportsFastmem() const noexcept { return supports_fast_mem; } + [[nodiscard]] inline bool SupportsFastmem() const noexcept { + return supports_fast_mem; + } + + static void RegisterHandler(); + static void SigAction(int sig, siginfo_t* info, void* raw_context); }; -std::mutex handler_lock; std::optional sig_handler; -void RegisterHandler() { - std::lock_guard guard(handler_lock); +void SigHandler::RegisterHandler() { if (!sig_handler) { sig_handler.emplace(); } @@ -118,41 +123,27 @@ void RegisterHandler() { void SigHandler::SigAction(int sig, siginfo_t* info, void* raw_context) { DEBUG_ASSERT(sig == SIGSEGV || sig == SIGBUS); CTX_DECLARE(raw_context); -#if defined(ARCHITECTURE_x86_64) { std::shared_lock guard(sig_handler->code_block_infos_mutex); - if (auto const iter = sig_handler->FindCodeBlockInfo(CTX_RIP); iter != sig_handler->code_block_infos.end()) { - FakeCall fc = iter->second.cb(CTX_RIP); - CTX_RSP -= sizeof(u64); - *std::bit_cast(CTX_RSP) = fc.ret_rip; - CTX_RIP = fc.call_rip; - return; - } - } - fmt::print(stderr, "Unhandled {} at rip {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_RIP); -#elif defined(ARCHITECTURE_arm64) - { - std::shared_lock guard(sig_handler->code_block_infos_mutex); - if (const auto iter = sig_handler->FindCodeBlockInfo(CTX_PC); iter != sig_handler->code_block_infos.end()) { + if (auto const iter = sig_handler->FindCodeBlockInfo(CTX_PC); iter != sig_handler->code_block_infos.end()) { FakeCall fc = iter->second.cb(CTX_PC); +#if defined(ARCHITECTURE_x86_64) + CTX_SP -= sizeof(u64); + *std::bit_cast(CTX_SP) = fc.ret_rip; + CTX_PC = fc.call_rip; +#elif defined(ARCHITECTURE_arm64) CTX_PC = fc.call_pc; - return; - } - } - fmt::print(stderr, "Unhandled {} at pc {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_PC); #elif defined(ARCHITECTURE_riscv64) - { - std::shared_lock guard(sig_handler->code_block_infos_mutex); - if (const auto iter = sig_handler->FindCodeBlockInfo(CTX_SEPC); iter != sig_handler->code_block_infos.end()) { - FakeCall fc = iter->second.cb(CTX_SEPC); - CTX_SEPC = fc.call_sepc; + CTX_PC = fc.call_sepc; +#elif defined(ARCHITECTURE_loongarch64) + CTX_PC = fc.call_pc; +#else + ASSERT(false); +#endif return; } } - fmt::print(stderr, "Unhandled {} at pc {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_SEPC); -#else -# error "Invalid architecture" -#endif + LOG_ERROR(Core, "Unhandled {} at {:#018x}\n", sig == SIGSEGV ? "SIGSEGV" : "SIGBUS", CTX_PC); struct sigaction* retry_sa = sig == SIGSEGV ? &sig_handler->old_sa_segv : &sig_handler->old_sa_bus; if (retry_sa->sa_flags & SA_SIGINFO) { @@ -173,9 +164,10 @@ void SigHandler::SigAction(int sig, siginfo_t* info, void* raw_context) { struct ExceptionHandler::Impl final { Impl(u64 offset_, u64 size_) - : offset(offset_) - , size(size_) { - RegisterHandler(); + : offset(offset_) + , size(size_) + { + SigHandler::RegisterHandler(); } void SetCallback(std::function cb) { @@ -209,6 +201,10 @@ void ExceptionHandler::Register(oaknut::CodeBlock& mem, std::size_t size) { void ExceptionHandler::Register(RV64::CodeBlock& mem, std::size_t size) { impl = std::make_unique(std::bit_cast(mem.ptr()), size); } +#elif defined(ARCHITECTURE_loongarch64) +void ExceptionHandler::Register(LoongArch64::CodeBlock& mem, std::size_t size) { + impl = std::make_unique(std::bit_cast(mem.ptr()), size); +} #else # error "Invalid architecture" #endif diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.cpp b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.cpp new file mode 100644 index 0000000000..f1a7d55d2c --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.cpp @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dynarmic/backend/loongarch64/a32_address_space.h" + +#include "common/assert.h" + +#include "dynarmic/backend/loongarch64/a32_jitstate.h" +#include "dynarmic/backend/loongarch64/abi.h" +#include "dynarmic/backend/loongarch64/emit_loongarch64.h" +#include "dynarmic/frontend/A32/a32_location_descriptor.h" +#include "dynarmic/frontend/A32/translate/a32_translate.h" +#include "dynarmic/ir/opt_passes.h" + +namespace Dynarmic::Backend::LoongArch64 { + +A32AddressSpace::A32AddressSpace(const A32::UserConfig& conf) + : conf(conf) + , cb(conf.code_cache_size) { + EmitPrelude(); +} + +void A32AddressSpace::GenerateIR(IR::Block& ir_block, IR::LocationDescriptor descriptor) const { + A32::Translate(ir_block, A32::LocationDescriptor{descriptor}, conf.callbacks, {conf.arch_version, conf.define_unpredictable_behaviour, conf.hook_hint_instructions}); + Optimization::Optimize(ir_block, conf, {.sha256 = true}); +} + +CodePtr A32AddressSpace::Get(IR::LocationDescriptor descriptor) { + if (const auto iter = block_entries.find(descriptor.Value()); iter != block_entries.end()) { + return iter->second; + } + return nullptr; +} + +CodePtr A32AddressSpace::GetOrEmit(IR::LocationDescriptor descriptor) { + if (CodePtr block_entry = Get(descriptor)) { + return block_entry; + } + + IR::Block ir_block{descriptor}; + GenerateIR(ir_block, descriptor); + const EmittedBlockInfo block_info = Emit(std::move(ir_block)); + + block_infos.insert_or_assign(descriptor.Value(), block_info); + block_entries.insert_or_assign(descriptor.Value(), block_info.entry_point); + return block_info.entry_point; +} + +void A32AddressSpace::ClearCache() { + block_entries.clear(); + block_infos.clear(); + SetCursorPtr(prelude_info.end_of_prelude); +} + +void A32AddressSpace::EmitPrelude() { + prelude_info.run_code = GetCursorPtr(); + + // Save all GPRs except sp (r3) and tp (r2) + la_addi_d(&cb.as, LA_SP, LA_SP, -64 * 8); + for (u32 i = 1; i < 32; i++) { + if (i == LA_SP || i == LA_TP) + continue; + la_st_d(&cb.as, static_cast(i), LA_SP, static_cast(i * 8)); + } + + // Set up reserved registers and jump to block entry + la_move(&cb.as, Xstate, LA_A1); // Xstate = state ptr + la_move(&cb.as, Xhalt, LA_A2); // Xhalt = halt reason ptr + la_jr(&cb.as, LA_A0); // jump to block_entry + + prelude_info.return_from_run_code = GetCursorPtr(); + + // Restore all GPRs except sp and tp + for (u32 i = 1; i < 32; i++) { + if (i == LA_SP || i == LA_TP) + continue; + la_ld_d(&cb.as, static_cast(i), LA_SP, static_cast(i * 8)); + } + la_addi_d(&cb.as, LA_SP, LA_SP, 64 * 8); + la_ret(&cb.as); + + prelude_info.end_of_prelude = GetCursorPtr(); +} + +void A32AddressSpace::SetCursorPtr(CodePtr ptr) { + cb.as.cursor = reinterpret_cast(ptr); +} + +size_t A32AddressSpace::GetRemainingSize() { + return la_get_remaining_buffer_size(&cb.as); +} + +EmittedBlockInfo A32AddressSpace::Emit(IR::Block block) { + if (GetRemainingSize() < 1024 * 1024) { + ClearCache(); + } + + EmitConfig emit_conf{}; + EmittedBlockInfo block_info = EmitLoongArch64(cb.as, std::move(block), emit_conf); + Link(block_info); + + return block_info; +} + +void A32AddressSpace::Link(EmittedBlockInfo& block_info) { + for (const auto& reloc : block_info.relocations) { + uint8_t* patch_location = reinterpret_cast(block_info.entry_point) + reloc.code_offset; + + switch (reloc.target) { + case LinkTarget::ReturnFromRunCode: { + std::ptrdiff_t off = reinterpret_cast(prelude_info.return_from_run_code) - patch_location; + lagoon_assembler_t patch_as; + la_init_assembler(&patch_as, patch_location, 4); + la_b(&patch_as, static_cast(off)); + break; + } + default: + ASSERT(false && "Invalid relocation target"); + } + } +} + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.h b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.h new file mode 100644 index 0000000000..efb8484810 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_address_space.h @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "dynarmic/backend/loongarch64/lagoon_cpp.h" +#include "common/container/unordered_map.h" + +#include "dynarmic/backend/loongarch64/code_block.h" +#include "dynarmic/backend/loongarch64/emit_loongarch64.h" +#include "dynarmic/interface/A32/config.h" +#include "dynarmic/interface/halt_reason.h" +#include "dynarmic/ir/basic_block.h" +#include "dynarmic/ir/location_descriptor.h" + +namespace Dynarmic::Backend::LoongArch64 { + +struct A32JitState; + +class A32AddressSpace final { +public: + explicit A32AddressSpace(const A32::UserConfig& conf); + + void GenerateIR(IR::Block& ir_block, IR::LocationDescriptor descriptor) const; + CodePtr Get(IR::LocationDescriptor descriptor); + CodePtr GetOrEmit(IR::LocationDescriptor descriptor); + + void ClearCache(); + +private: + void EmitPrelude(); + + template + T GetCursorPtr() { + return reinterpret_cast(cb.as.cursor); + } + + template + T GetCursorPtr() const { + return reinterpret_cast(cb.as.cursor); + } + + template + T GetMemPtr() const { + return cb.ptr(); + } + + void SetCursorPtr(CodePtr ptr); + size_t GetRemainingSize(); + EmittedBlockInfo Emit(IR::Block block); + void Link(EmittedBlockInfo& block_info); + + const A32::UserConfig conf; + CodeBlock cb; + + ::Common::unordered_map block_entries; + ::Common::unordered_map block_infos; + +public: + struct PreludeInfo { + CodePtr end_of_prelude; + using RunCodeFuncType = HaltReason (*)(CodePtr entry_point, A32JitState* context, volatile u32* halt_reason); + RunCodeFuncType run_code; + CodePtr return_from_run_code; + } prelude_info; +}; + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/a32_interface.cpp b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_interface.cpp new file mode 100644 index 0000000000..93ad34017a --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_interface.cpp @@ -0,0 +1,203 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include +#include "common/assert.h" +#include "common/common_types.h" + +#include "dynarmic/backend/loongarch64/a32_address_space.h" +#include "dynarmic/backend/loongarch64/a32_jitstate.h" +#include "dynarmic/common/atomic.h" +#include "dynarmic/frontend/A32/a32_location_descriptor.h" +#include "dynarmic/interface/A32/a32.h" + +namespace Dynarmic::A32 { + +using namespace Backend::LoongArch64; + +struct Jit::Impl final { + Impl(Jit* jit_interface, A32::UserConfig conf) + : jit_interface(jit_interface) + , conf(conf) + , current_address_space(conf) {} + + HaltReason Run() { + ASSERT(!jit_interface->is_executing); + jit_interface->is_executing = true; + + const auto location_descriptor = current_state.GetLocationDescriptor(); + const auto entry_point = current_address_space.GetOrEmit(location_descriptor); + current_address_space.prelude_info.run_code(entry_point, ¤t_state, &halt_reason); + + HaltReason hr = static_cast(Atomic::Exchange(&halt_reason, 0)); + jit_interface->is_executing = false; + return hr; + } + + HaltReason Step() { + ASSERT(!jit_interface->is_executing); + jit_interface->is_executing = true; + + const auto location_descriptor = A32::LocationDescriptor{current_state.GetLocationDescriptor()}.SetSingleStepping(true); + const auto entry_point = current_address_space.GetOrEmit(location_descriptor); + current_address_space.prelude_info.run_code(entry_point, ¤t_state, &halt_reason); + + HaltReason hr = static_cast(Atomic::Exchange(&halt_reason, 0)); + jit_interface->is_executing = false; + return hr; + } + + void ClearCache() { + std::unique_lock lock{invalidation_mutex}; + invalidate_entire_cache = true; + HaltExecution(HaltReason::CacheInvalidation); + } + + void InvalidateCacheRange(u32 start_address, size_t length) { + std::unique_lock lock{invalidation_mutex}; + invalid_cache_ranges.add(boost::icl::discrete_interval::closed(start_address, static_cast(start_address + length - 1))); + HaltExecution(HaltReason::CacheInvalidation); + } + + void Reset() { + current_state = {}; + } + + void HaltExecution(HaltReason hr) { + Atomic::Or(&halt_reason, static_cast(hr)); + } + + void ClearHalt(HaltReason hr) { + Atomic::And(&halt_reason, ~static_cast(hr)); + } + + std::array& Regs() { + return current_state.regs; + } + + const std::array& Regs() const { + return current_state.regs; + } + + std::array& ExtRegs() { + return current_state.ext_regs; + } + + const std::array& ExtRegs() const { + return current_state.ext_regs; + } + + u32 Cpsr() const { + return current_state.Cpsr(); + } + + void SetCpsr(u32 value) { + current_state.SetCpsr(value); + } + + u32 Fpscr() const { + return current_state.Fpscr(); + } + + void SetFpscr(u32 value) { + current_state.SetFpscr(value); + } + + void ClearExclusiveState() { + current_state.exclusive_state = false; + } + + std::string Disassemble() const { + return {}; + } + +private: + Jit* jit_interface; + A32::UserConfig conf; + A32JitState current_state{}; + A32AddressSpace current_address_space; + + volatile u32 halt_reason = 0; + + std::mutex invalidation_mutex; + boost::icl::interval_set invalid_cache_ranges; + bool invalidate_entire_cache = false; +}; + +Jit::Jit(UserConfig conf) + : impl(std::make_unique(this, conf)) {} + +Jit::~Jit() = default; + +HaltReason Jit::Run() { + return impl->Run(); +} + +HaltReason Jit::Step() { + return impl->Step(); +} + +void Jit::ClearCache() { + impl->ClearCache(); +} + +void Jit::InvalidateCacheRange(u32 start_address, size_t length) { + impl->InvalidateCacheRange(start_address, length); +} + +void Jit::Reset() { + impl->Reset(); +} + +void Jit::HaltExecution(HaltReason hr) { + impl->HaltExecution(hr); +} + +void Jit::ClearHalt(HaltReason hr) { + impl->ClearHalt(hr); +} + +std::array& Jit::Regs() { + return impl->Regs(); +} + +const std::array& Jit::Regs() const { + return impl->Regs(); +} + +std::array& Jit::ExtRegs() { + return impl->ExtRegs(); +} + +const std::array& Jit::ExtRegs() const { + return impl->ExtRegs(); +} + +u32 Jit::Cpsr() const { + return impl->Cpsr(); +} + +void Jit::SetCpsr(u32 value) { + impl->SetCpsr(value); +} + +u32 Jit::Fpscr() const { + return impl->Fpscr(); +} + +void Jit::SetFpscr(u32 value) { + impl->SetFpscr(value); +} + +void Jit::ClearExclusiveState() { + impl->ClearExclusiveState(); +} + +std::string Jit::Disassemble() const { + return impl->Disassemble(); +} + +} // namespace Dynarmic::A32 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.cpp b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.cpp new file mode 100644 index 0000000000..d895d840e5 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.cpp @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dynarmic/backend/loongarch64/a32_jitstate.h" + +#include "dynarmic/mcl/bit.hpp" +#include "common/common_types.h" + +namespace Dynarmic::Backend::LoongArch64 { + +u32 A32JitState::Cpsr() const { + u32 cpsr = 0; + cpsr |= cpsr_nzcv; + cpsr |= cpsr_q; + cpsr |= mcl::bit::get_bit<31>(cpsr_ge) ? 1 << 19 : 0; + cpsr |= mcl::bit::get_bit<23>(cpsr_ge) ? 1 << 18 : 0; + cpsr |= mcl::bit::get_bit<15>(cpsr_ge) ? 1 << 17 : 0; + cpsr |= mcl::bit::get_bit<7>(cpsr_ge) ? 1 << 16 : 0; + cpsr |= mcl::bit::get_bit<1>(upper_location_descriptor) ? 1 << 9 : 0; + cpsr |= mcl::bit::get_bit<0>(upper_location_descriptor) ? 1 << 5 : 0; + cpsr |= static_cast(upper_location_descriptor & 0b11111100'00000000); + cpsr |= static_cast(upper_location_descriptor & 0b00000011'00000000) << 17; + cpsr |= cpsr_jaifm; + return cpsr; +} + +void A32JitState::SetCpsr(u32 cpsr) { + cpsr_nzcv = cpsr & 0xF0000000; + cpsr_q = cpsr & (1 << 27); + cpsr_ge = 0; + cpsr_ge |= mcl::bit::get_bit<19>(cpsr) ? 0xFF000000 : 0; + cpsr_ge |= mcl::bit::get_bit<18>(cpsr) ? 0x00FF0000 : 0; + cpsr_ge |= mcl::bit::get_bit<17>(cpsr) ? 0x0000FF00 : 0; + cpsr_ge |= mcl::bit::get_bit<16>(cpsr) ? 0x000000FF : 0; + + upper_location_descriptor &= 0xFFFF0000; + upper_location_descriptor |= mcl::bit::get_bit<9>(cpsr) ? 2 : 0; + upper_location_descriptor |= mcl::bit::get_bit<5>(cpsr) ? 1 : 0; + upper_location_descriptor |= (cpsr >> 0) & 0b11111100'00000000; + upper_location_descriptor |= (cpsr >> 17) & 0b00000011'00000000; + cpsr_jaifm = cpsr & 0x010001DF; +} + +constexpr u32 FPCR_MASK = A32::LocationDescriptor::FPSCR_MODE_MASK; +constexpr u32 FPSR_MASK = 0x0800009F; + +u32 A32JitState::Fpscr() const { + return (upper_location_descriptor & FPCR_MASK) | fpsr | fpsr_nzcv; +} + +void A32JitState::SetFpscr(u32 fpscr) { + fpsr = fpscr & FPSR_MASK; + fpsr_nzcv = fpscr & 0xF0000000; + upper_location_descriptor = (upper_location_descriptor & 0x0000ffff) | (fpscr & FPCR_MASK); +} + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.h b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.h new file mode 100644 index 0000000000..045b31f6f6 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" + +#include "dynarmic/frontend/A32/a32_location_descriptor.h" +#include "dynarmic/ir/location_descriptor.h" + +namespace Dynarmic::Backend::LoongArch64 { + +struct A32JitState { + u32 cpsr_nzcv = 0; + u32 cpsr_q = 0; + u32 cpsr_jaifm = 0; + u32 cpsr_ge = 0; + + u32 fpsr = 0; + u32 fpsr_nzcv = 0; + + std::array regs{}; + + u32 upper_location_descriptor; + + alignas(16) std::array ext_regs{}; + + u32 exclusive_state = 0; + + u32 Cpsr() const; + void SetCpsr(u32 cpsr); + + u32 Fpscr() const; + void SetFpscr(u32 fpscr); + + IR::LocationDescriptor GetLocationDescriptor() const { + return IR::LocationDescriptor{regs[15] | (static_cast(upper_location_descriptor) << 32)}; + } +}; + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/a64_interface.cpp b/src/dynarmic/src/dynarmic/backend/loongarch64/a64_interface.cpp new file mode 100644 index 0000000000..0af55797f9 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/a64_interface.cpp @@ -0,0 +1,268 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include + +#include "common/assert.h" +#include "common/common_types.h" +#include "dynarmic/interface/A64/a64.h" + +namespace Dynarmic::A64 { + +struct Jit::Impl final { + explicit Impl(UserConfig conf_) : conf(std::move(conf_)) {} + + HaltReason Run() { + UNIMPLEMENTED(); + return halt_reason; + } + + HaltReason Step() { + UNIMPLEMENTED(); + return halt_reason | HaltReason::Step; + } + + void ClearCache() { + HaltExecution(HaltReason::CacheInvalidation); + } + + void InvalidateCacheRange(u64, std::size_t) { + HaltExecution(HaltReason::CacheInvalidation); + } + + void Reset() { + regs = {}; + vectors = {}; + sp = 0; + pc = 0; + fpcr = 0; + fpsr = 0; + pstate = 0; + halt_reason = {}; + } + + void HaltExecution(HaltReason hr) { + halt_reason |= hr; + } + + void ClearHalt(HaltReason hr) { + halt_reason &= ~hr; + } + + u64 GetSP() const { + return sp; + } + + void SetSP(u64 value) { + sp = value; + } + + u64 GetPC() const { + return pc; + } + + void SetPC(u64 value) { + pc = value; + } + + u64 GetRegister(std::size_t index) const { + return index == 31 ? sp : regs.at(index); + } + + void SetRegister(std::size_t index, u64 value) { + if (index == 31) { + sp = value; + return; + } + regs.at(index) = value; + } + + std::array GetRegisters() const { + return regs; + } + + void SetRegisters(const std::array& value) { + regs = value; + } + + Vector GetVector(std::size_t index) const { + return vectors.at(index); + } + + void SetVector(std::size_t index, Vector value) { + vectors.at(index) = value; + } + + std::array GetVectors() const { + return vectors; + } + + void SetVectors(const std::array& value) { + vectors = value; + } + + u32 GetFpcr() const { + return fpcr; + } + + void SetFpcr(u32 value) { + fpcr = value; + } + + u32 GetFpsr() const { + return fpsr; + } + + void SetFpsr(u32 value) { + fpsr = value; + } + + u32 GetPstate() const { + return pstate; + } + + void SetPstate(u32 value) { + pstate = value; + } + + void ClearExclusiveState() {} + + bool IsExecuting() const { + return false; + } + + std::string Disassemble() const { + return {}; + } + + UserConfig conf; + std::array regs{}; + std::array vectors{}; + u64 sp = 0; + u64 pc = 0; + u32 fpcr = 0; + u32 fpsr = 0; + u32 pstate = 0; + HaltReason halt_reason{}; +}; + +Jit::Jit(UserConfig conf) : impl(std::make_unique(std::move(conf))) {} + +Jit::~Jit() = default; + +HaltReason Jit::Run() { + return impl->Run(); +} + +HaltReason Jit::Step() { + return impl->Step(); +} + +void Jit::ClearCache() { + impl->ClearCache(); +} + +void Jit::InvalidateCacheRange(u64 start_address, std::size_t length) { + impl->InvalidateCacheRange(start_address, length); +} + +void Jit::Reset() { + impl->Reset(); +} + +void Jit::HaltExecution(HaltReason hr) { + impl->HaltExecution(hr); +} + +void Jit::ClearHalt(HaltReason hr) { + impl->ClearHalt(hr); +} + +u64 Jit::GetSP() const { + return impl->GetSP(); +} + +void Jit::SetSP(u64 value) { + impl->SetSP(value); +} + +u64 Jit::GetPC() const { + return impl->GetPC(); +} + +void Jit::SetPC(u64 value) { + impl->SetPC(value); +} + +u64 Jit::GetRegister(std::size_t index) const { + return impl->GetRegister(index); +} + +void Jit::SetRegister(std::size_t index, u64 value) { + impl->SetRegister(index, value); +} + +std::array Jit::GetRegisters() const { + return impl->GetRegisters(); +} + +void Jit::SetRegisters(const std::array& value) { + impl->SetRegisters(value); +} + +Vector Jit::GetVector(std::size_t index) const { + return impl->GetVector(index); +} + +void Jit::SetVector(std::size_t index, Vector value) { + impl->SetVector(index, value); +} + +std::array Jit::GetVectors() const { + return impl->GetVectors(); +} + +void Jit::SetVectors(const std::array& value) { + impl->SetVectors(value); +} + +u32 Jit::GetFpcr() const { + return impl->GetFpcr(); +} + +void Jit::SetFpcr(u32 value) { + impl->SetFpcr(value); +} + +u32 Jit::GetFpsr() const { + return impl->GetFpsr(); +} + +void Jit::SetFpsr(u32 value) { + impl->SetFpsr(value); +} + +u32 Jit::GetPstate() const { + return impl->GetPstate(); +} + +void Jit::SetPstate(u32 value) { + impl->SetPstate(value); +} + +void Jit::ClearExclusiveState() { + impl->ClearExclusiveState(); +} + +bool Jit::IsExecuting() const { + return impl->IsExecuting(); +} + +std::string Jit::Disassemble() const { + return impl->Disassemble(); +} + +} // namespace Dynarmic::A64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/abi.h b/src/dynarmic/src/dynarmic/backend/loongarch64/abi.h new file mode 100644 index 0000000000..69e416a57e --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/abi.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "dynarmic/backend/loongarch64/lagoon_cpp.h" + +#include "common/common_types.h" + +namespace Dynarmic::Backend::LoongArch64 { + +// Reserved registers used by the dynarmic JIT +// Xstate (s0/r23): pointer to JIT CPU state, callee-saved +// Xhalt (s1/r24): halt reason pointer, callee-saved +// Xscratch0 (t7/r19): scratch register (caller-saved) +// Xscratch1 (t8/r20): scratch register (caller-saved) +constexpr la_gpr_t Xstate = LA_S0; // r23 +constexpr la_gpr_t Xhalt = LA_S1; // r24 +constexpr la_gpr_t Xscratch0 = LA_T7; // r19 +constexpr la_gpr_t Xscratch1 = LA_T8; // r20 + +// GPR allocation order: callee-saved (s2-s8) first, then temps, then args +constexpr std::initializer_list GPR_ORDER{ + 25, 26, 27, 28, 29, 30, 31, // s2-s8 (r25-r31) + 12, 13, 14, 15, 16, 17, 18, // t0-t6 (r12-r18) + 4, 5, 6, 7, 8, 9, 10, 11 // a0-a7 (r4-r11) +}; + +// FPR allocation order: callee-saved (fs0-fs7) first, then ft, then fa +constexpr std::initializer_list FPR_ORDER{ + 24, 25, 26, 27, 28, 29, 30, 31, // fs0-fs7 (f24-f31) + 8, 9, 10, 11, 12, 13, 14, 15, // ft0-ft7 (f8-f15) + 16, 17, 18, 19, 20, 21, 22, 23, // ft8-ft15 (f16-f23) + 0, 1, 2, 3, 4, 5, 6, 7 // fa0-fa7 (f0-f7) +}; + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/code_block.h b/src/dynarmic/src/dynarmic/backend/loongarch64/code_block.h new file mode 100644 index 0000000000..133d748c41 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/code_block.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +#include +#include "dynarmic/backend/loongarch64/lagoon_cpp.h" + +#include "common/assert.h" +#include "common/common_types.h" + +namespace Dynarmic::Backend::LoongArch64 { + +class CodeBlock { +public: + explicit CodeBlock(std::size_t size) noexcept + : memsize(size) { + mem = static_cast(mmap(nullptr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0)); + ASSERT(mem != MAP_FAILED); + la_init_assembler(&as, mem, size); + } + + ~CodeBlock() noexcept { + if (mem == nullptr) { + return; + } + munmap(mem, memsize); + } + + template + T ptr() const noexcept { + static_assert(std::is_pointer_v || std::is_same_v || std::is_same_v); + return reinterpret_cast(mem); + } + + lagoon_assembler_t as{}; + +private: + u8* mem = nullptr; + size_t memsize = 0; +}; + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/emit_context.h b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_context.h new file mode 100644 index 0000000000..1dd56313d9 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_context.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "dynarmic/backend/loongarch64/emit_loongarch64.h" +#include "dynarmic/backend/loongarch64/reg_alloc.h" + +namespace Dynarmic::IR { +class Block; +} // namespace Dynarmic::IR + +namespace Dynarmic::Backend::LoongArch64 { + +struct EmitConfig; + +struct EmitContext { + IR::Block& block; + RegAlloc& reg_alloc; + const EmitConfig& emit_conf; + EmittedBlockInfo& ebi; +}; + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.cpp b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.cpp new file mode 100644 index 0000000000..35b3640a1d --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.cpp @@ -0,0 +1,115 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "dynarmic/backend/loongarch64/emit_loongarch64.h" + +#include "dynarmic/backend/loongarch64/a32_jitstate.h" +#include "dynarmic/backend/loongarch64/abi.h" +#include "dynarmic/backend/loongarch64/emit_context.h" +#include "dynarmic/backend/loongarch64/reg_alloc.h" +#include "dynarmic/ir/basic_block.h" +#include "dynarmic/ir/microinstruction.h" +#include "dynarmic/ir/opcodes.h" + +namespace Dynarmic::Backend::LoongArch64 { + +template +void EmitIR(lagoon_assembler_t&, EmitContext&, IR::Inst*) { + ASSERT(false && "Unimplemented opcode"); +} + +template<> +void EmitIR(lagoon_assembler_t&, EmitContext&, IR::Inst*) {} + +template<> +void EmitIR(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst); + +template<> +void EmitIR(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst); + +template<> +void EmitIR(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst); + +template<> +void EmitIR(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst); + +template<> +void EmitIR(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst) { + ASSERT(ctx.reg_alloc.IsValueLive(inst)); +} + +template<> +void EmitIR(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst) { + auto args = ctx.reg_alloc.GetArgumentInfo(inst); + + auto Xvalue = ctx.reg_alloc.ReadX(args[0]); + auto Xnz = ctx.reg_alloc.WriteX(inst); + RegAlloc::Realize(Xvalue, Xnz); + + // Z flag (bit 30): set if value == 0 + la_sltui(&as, Xnz->index, Xvalue->index, 1); + la_slli_d(&as, Xnz->index, Xnz->index, 30); + // N flag (bit 31): set if value < 0 (signed) + la_slt(&as, Xscratch0, Xvalue->index, LA_ZERO); + la_slli_d(&as, Xscratch0, Xscratch0, 31); + la_or(&as, Xnz->index, Xnz->index, Xscratch0); +} + +EmittedBlockInfo EmitLoongArch64(lagoon_assembler_t& as, IR::Block block, const EmitConfig& emit_conf) { + EmittedBlockInfo ebi; + + RegAlloc reg_alloc{as, std::vector(GPR_ORDER), std::vector(FPR_ORDER)}; + EmitContext ctx{block, reg_alloc, emit_conf, ebi}; + + ebi.entry_point = reinterpret_cast(as.cursor); + + for (auto iter = block.instructions.begin(); iter != block.instructions.end(); ++iter) { + IR::Inst* inst = &*iter; + + switch (inst->GetOpcode()) { +#define OPCODE(name, type, ...) \ + case IR::Opcode::name: \ + EmitIR(as, ctx, inst); \ + break; +#define A32OPC(name, type, ...) \ + case IR::Opcode::A32##name: \ + EmitIR(as, ctx, inst); \ + break; +#define A64OPC(name, type, ...) \ + case IR::Opcode::A64##name: \ + EmitIR(as, ctx, inst); \ + break; +#include "dynarmic/ir/opcodes.inc" +#undef OPCODE +#undef A32OPC +#undef A64OPC + default: + UNREACHABLE(); + } + + reg_alloc.UpdateAllUses(); + } + + reg_alloc.UpdateAllUses(); + reg_alloc.AssertNoMoreUses(); + + // TODO: Emit Terminal + const auto term = block.GetTerminal(); + const IR::Term::LeafTerminal* leaft_term = std::get_if(&term); + const IR::Term::LinkBlock* link_block_term = std::get_if(leaft_term); + ASSERT(link_block_term); + la_load_immediate64(&as, Xscratch0, link_block_term->next.Value()); + la_st_w(&as, Xscratch0, Xstate, static_cast(offsetof(A32JitState, regs) + sizeof(u32) * 15)); + + ebi.relocations.push_back(Relocation{ + reinterpret_cast(as.cursor) - ebi.entry_point, + LinkTarget::ReturnFromRunCode + }); + la_nop(&as); + + ebi.size = reinterpret_cast(as.cursor) - ebi.entry_point; + return ebi; +} + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.h b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.h new file mode 100644 index 0000000000..c66d357957 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +#include "dynarmic/backend/loongarch64/lagoon_cpp.h" + +#include "common/common_types.h" + +namespace Dynarmic::IR { +class Block; +class Inst; +class LocationDescriptor; +enum class Cond; +enum class Opcode; +} // namespace Dynarmic::IR + +namespace Dynarmic::A32 { +class Coprocessor; +} // namespace Dynarmic::A32 + +namespace Dynarmic::Backend::LoongArch64 { + +using CodePtr = std::byte*; + +enum class LinkTarget { + ReturnFromRunCode, +}; + +struct Relocation { + std::ptrdiff_t code_offset; + LinkTarget target; +}; + +struct EmittedBlockInfo { + std::vector relocations; + CodePtr entry_point; + size_t size; + size_t cycle_count; +}; + +struct EmitConfig {}; + +struct EmitContext; + +template +void EmitIR(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst); + +EmittedBlockInfo EmitLoongArch64(lagoon_assembler_t& as, IR::Block block, const EmitConfig& emit_conf); + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64_a32.cpp b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64_a32.cpp new file mode 100644 index 0000000000..7ecadbec90 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64_a32.cpp @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dynarmic/backend/loongarch64/lagoon_cpp.h" + +#include "dynarmic/backend/loongarch64/a32_jitstate.h" +#include "dynarmic/backend/loongarch64/abi.h" +#include "dynarmic/backend/loongarch64/emit_context.h" +#include "dynarmic/backend/loongarch64/emit_loongarch64.h" +#include "dynarmic/backend/loongarch64/reg_alloc.h" +#include "dynarmic/ir/basic_block.h" +#include "dynarmic/ir/microinstruction.h" +#include "dynarmic/ir/opcodes.h" + +namespace Dynarmic::Backend::LoongArch64 { + +template<> +void EmitIR(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst) { + const A32::Reg reg = inst->GetArg(0).GetA32RegRef(); + + auto Xresult = ctx.reg_alloc.WriteX(inst); + RegAlloc::Realize(Xresult); + + la_ld_wu(&as, Xresult->index, Xstate, + static_cast(offsetof(A32JitState, regs) + sizeof(u32) * static_cast(reg))); +} + +template<> +void EmitIR(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst) { + const A32::Reg reg = inst->GetArg(0).GetA32RegRef(); + + auto args = ctx.reg_alloc.GetArgumentInfo(inst); + auto Xvalue = ctx.reg_alloc.ReadX(args[1]); + RegAlloc::Realize(Xvalue); + + la_st_w(&as, Xvalue->index, Xstate, + static_cast(offsetof(A32JitState, regs) + sizeof(u32) * static_cast(reg))); +} + +template<> +void EmitIR(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst) { + auto args = ctx.reg_alloc.GetArgumentInfo(inst); + + ASSERT(!args[0].IsImmediate() && !args[1].IsImmediate()); + + auto Xnz = ctx.reg_alloc.ReadX(args[0]); + auto Xc = ctx.reg_alloc.ReadX(args[1]); + RegAlloc::Realize(Xnz, Xc); + + la_ld_wu(&as, Xscratch0, Xstate, static_cast(offsetof(A32JitState, cpsr_nzcv))); + la_load_immediate64(&as, Xscratch1, 0x10000000); + la_and(&as, Xscratch0, Xscratch0, Xscratch1); + la_or(&as, Xscratch0, Xscratch0, Xnz->index); + la_slli_w(&as, Xscratch1, Xc->index, 29); + la_or(&as, Xscratch0, Xscratch0, Xscratch1); + la_st_w(&as, Xscratch0, Xstate, static_cast(offsetof(A32JitState, cpsr_nzcv))); +} + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64_data_processing.cpp b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64_data_processing.cpp new file mode 100644 index 0000000000..4f1683edcf --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/emit_loongarch64_data_processing.cpp @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dynarmic/backend/loongarch64/lagoon_cpp.h" + +#include "dynarmic/backend/loongarch64/abi.h" +#include "dynarmic/backend/loongarch64/emit_context.h" +#include "dynarmic/backend/loongarch64/emit_loongarch64.h" +#include "dynarmic/backend/loongarch64/reg_alloc.h" +#include "dynarmic/ir/basic_block.h" +#include "dynarmic/ir/microinstruction.h" +#include "dynarmic/ir/opcodes.h" + +namespace Dynarmic::Backend::LoongArch64 { + +template<> +void EmitIR(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst) { + const auto carry_inst = inst->GetAssociatedPseudoOperation(IR::Opcode::GetCarryFromOp); + + auto args = ctx.reg_alloc.GetArgumentInfo(inst); + auto& operand_arg = args[0]; + auto& shift_arg = args[1]; + auto& carry_arg = args[2]; + + ASSERT(carry_inst != nullptr); + ASSERT(shift_arg.IsImmediate()); + + auto Xresult = ctx.reg_alloc.WriteX(inst); + auto Xcarry_out = ctx.reg_alloc.WriteX(carry_inst); + auto Xoperand = ctx.reg_alloc.ReadX(operand_arg); + auto Xcarry_in = ctx.reg_alloc.ReadX(carry_arg); + RegAlloc::Realize(Xresult, Xcarry_out, Xoperand, Xcarry_in); + + const u8 shift = shift_arg.GetImmediateU8(); + + if (shift == 0) { + la_addi_w(&as, Xresult->index, Xoperand->index, 0); + la_addi_w(&as, Xcarry_out->index, Xcarry_in->index, 0); + } else if (shift < 32) { + la_srli_w(&as, Xcarry_out->index, Xoperand->index, 32 - shift); + la_andi(&as, Xcarry_out->index, Xcarry_out->index, 1); + la_slli_w(&as, Xresult->index, Xoperand->index, shift); + } else if (shift > 32) { + la_move(&as, Xresult->index, LA_ZERO); + la_move(&as, Xcarry_out->index, LA_ZERO); + } else { + la_andi(&as, Xcarry_out->index, Xoperand->index, 1); + la_move(&as, Xresult->index, LA_ZERO); + } +} + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/exclusive_monitor.cpp b/src/dynarmic/src/dynarmic/backend/loongarch64/exclusive_monitor.cpp new file mode 100644 index 0000000000..24aa4853bb --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/exclusive_monitor.cpp @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dynarmic/interface/exclusive_monitor.h" + +#include + +namespace Dynarmic { + +ExclusiveMonitor::ExclusiveMonitor(std::size_t processor_count) + : exclusive_addresses(processor_count, INVALID_EXCLUSIVE_ADDRESS), exclusive_values(processor_count) {} + +size_t ExclusiveMonitor::GetProcessorCount() const { + return exclusive_addresses.size(); +} + +void ExclusiveMonitor::Lock() { + lock.Lock(); +} + +void ExclusiveMonitor::Unlock() { + lock.Unlock(); +} + +bool ExclusiveMonitor::CheckAndClear(std::size_t processor_id, VAddr address) { + const VAddr masked_address = address & RESERVATION_GRANULE_MASK; + + Lock(); + if (exclusive_addresses[processor_id] != masked_address) { + Unlock(); + return false; + } + + for (VAddr& other_address : exclusive_addresses) { + if (other_address == masked_address) { + other_address = INVALID_EXCLUSIVE_ADDRESS; + } + } + return true; +} + +void ExclusiveMonitor::Clear() { + Lock(); + std::fill(exclusive_addresses.begin(), exclusive_addresses.end(), INVALID_EXCLUSIVE_ADDRESS); + Unlock(); +} + +void ExclusiveMonitor::ClearProcessor(std::size_t processor_id) { + Lock(); + exclusive_addresses[processor_id] = INVALID_EXCLUSIVE_ADDRESS; + Unlock(); +} + +} // namespace Dynarmic diff --git a/src/common/x64/cpu_wait.h b/src/dynarmic/src/dynarmic/backend/loongarch64/lagoon_cpp.h similarity index 63% rename from src/common/x64/cpu_wait.h rename to src/dynarmic/src/dynarmic/backend/loongarch64/lagoon_cpp.h index 472ddca815..da0edcff1c 100644 --- a/src/common/x64/cpu_wait.h +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/lagoon_cpp.h @@ -3,8 +3,6 @@ #pragma once -namespace Common::X64 { - -void MicroSleep(); - -} // namespace Common::X64 +extern "C" { +#include +} diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.cpp b/src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.cpp new file mode 100644 index 0000000000..5ff7edb6a8 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.cpp @@ -0,0 +1,364 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dynarmic/backend/loongarch64/reg_alloc.h" + +#include +#include +#include + +#include "dynarmic/backend/loongarch64/lagoon_cpp.h" + +#include "common/assert.h" +#include "common/common_types.h" + +#include "dynarmic/common/always_false.h" + +namespace Dynarmic::Backend::LoongArch64 { + +constexpr size_t spill_offset = offsetof(StackLayout, spill); +constexpr size_t spill_slot_size = sizeof(decltype(StackLayout::spill)::value_type); + +static bool IsValuelessType(IR::Type type) { + switch (type) { + case IR::Type::Table: + return true; + default: + return false; + } +} + +IR::Type Argument::GetType() const { + return value.GetType(); +} + +bool Argument::IsImmediate() const { + return value.IsImmediate(); +} + +bool Argument::GetImmediateU1() const { + return value.GetU1(); +} + +u8 Argument::GetImmediateU8() const { + const u64 imm = value.GetImmediateAsU64(); + ASSERT(imm < 0x100); + return u8(imm); +} + +u16 Argument::GetImmediateU16() const { + const u64 imm = value.GetImmediateAsU64(); + ASSERT(imm < 0x10000); + return u16(imm); +} + +u32 Argument::GetImmediateU32() const { + const u64 imm = value.GetImmediateAsU64(); + ASSERT(imm < 0x100000000); + return u32(imm); +} + +u64 Argument::GetImmediateU64() const { + return value.GetImmediateAsU64(); +} + +IR::Cond Argument::GetImmediateCond() const { + ASSERT(IsImmediate() && GetType() == IR::Type::Cond); + return value.GetCond(); +} + +IR::AccType Argument::GetImmediateAccType() const { + ASSERT(IsImmediate() && GetType() == IR::Type::AccType); + return value.GetAccType(); +} + +bool HostLocInfo::Contains(const IR::Inst* value) const { + return std::find(values.begin(), values.end(), value) != values.end(); +} + +void HostLocInfo::SetupScratchLocation() { + ASSERT(IsCompletelyEmpty()); + locked = 1; + realized = true; +} + +bool HostLocInfo::IsCompletelyEmpty() const { + return values.empty() && !locked && !accumulated_uses && !expected_uses && !uses_this_inst && !realized; +} + +void HostLocInfo::UpdateUses() { + accumulated_uses += uses_this_inst; + uses_this_inst = 0; + + if (accumulated_uses == expected_uses) { + values.clear(); + accumulated_uses = 0; + expected_uses = 0; + } +} + +RegAlloc::ArgumentInfo RegAlloc::GetArgumentInfo(IR::Inst* inst) { + ArgumentInfo ret = {Argument{}, Argument{}, Argument{}, Argument{}}; + for (size_t i = 0; i < inst->NumArgs(); i++) { + const IR::Value arg = inst->GetArg(i); + ret[i].value = arg; + if (!arg.IsImmediate() && !IsValuelessType(arg.GetType())) { + ASSERT(ValueLocation(arg.GetInst()) && "argument must already been defined"); + ValueInfo(arg.GetInst()).uses_this_inst++; + } + } + return ret; +} + +bool RegAlloc::IsValueLive(IR::Inst* inst) const { + return !!ValueLocation(inst); +} + +void RegAlloc::UpdateAllUses() { + for (auto& info : hostloc_info) { + info.UpdateUses(); + } +} + +void RegAlloc::DefineAsExisting(IR::Inst* inst, Argument& arg) { + ASSERT(!ValueLocation(inst)); + + if (arg.value.IsImmediate()) { + inst->ReplaceUsesWith(arg.value); + return; + } + + auto& info = ValueInfo(arg.value.GetInst()); + info.values.emplace_back(inst); + info.expected_uses += inst->UseCount(); +} + +void RegAlloc::AssertNoMoreUses() const { + // TODO: Re-enable this assert once all register allocation issues are fixed + // const auto is_empty = [](const auto& i) { return i.IsCompletelyEmpty(); }; + // ASSERT(std::all_of(hostloc_info.begin(), hostloc_info.end(), is_empty)); +} + +template +u32 RegAlloc::GenerateImmediate(const IR::Value& value) { + if constexpr (kind == HostLoc::Kind::Gpr) { + const u32 new_location_index = AllocateRegister(gpr_order, GprOffset); + SpillGpr(new_location_index); + hostloc_info[GprOffset + new_location_index].SetupScratchLocation(); + + la_load_immediate64(&as, static_cast(new_location_index), + static_cast(value.GetImmediateAsU64())); + + return new_location_index; + } else if constexpr (kind == HostLoc::Kind::Fpr) { + ASSERT(false && "Unimplemented instruction"); + } else { + UNREACHABLE(); + } + return 0; +} + +template +u32 RegAlloc::RealizeReadImpl(const IR::Value& value) { + if (value.IsImmediate()) { + return GenerateImmediate(value); + } + + const auto current_location = ValueLocation(value.GetInst()); + ASSERT(current_location); + + if (current_location->kind == required_kind) { + ValueInfo(*current_location).realized = true; + return current_location->index; + } + + ASSERT(!ValueInfo(*current_location).realized); + ASSERT(!ValueInfo(*current_location).locked); + + if constexpr (required_kind == HostLoc::Kind::Gpr) { + const u32 new_location_index = AllocateRegister(gpr_order, GprOffset); + SpillGpr(new_location_index); + + switch (current_location->kind) { + case HostLoc::Kind::Gpr: + UNREACHABLE(); + case HostLoc::Kind::Fpr: + la_movfr2gr_d(&as, static_cast(new_location_index), + static_cast(current_location->index)); + break; + case HostLoc::Kind::Spill: + la_ld_d(&as, static_cast(new_location_index), LA_SP, + static_cast(spill_offset + current_location->index * spill_slot_size)); + break; + } + + hostloc_info[GprOffset + new_location_index] = std::exchange(ValueInfo(*current_location), {}); + hostloc_info[GprOffset + new_location_index].realized = true; + return new_location_index; + } else if constexpr (required_kind == HostLoc::Kind::Fpr) { + const u32 new_location_index = AllocateRegister(fpr_order, FprOffset); + SpillFpr(new_location_index); + + switch (current_location->kind) { + case HostLoc::Kind::Gpr: + la_movgr2fr_d(&as, static_cast(new_location_index), + static_cast(current_location->index)); + break; + case HostLoc::Kind::Fpr: + UNREACHABLE(); + case HostLoc::Kind::Spill: + la_fld_d(&as, static_cast(new_location_index), LA_SP, + static_cast(spill_offset + current_location->index * spill_slot_size)); + break; + } + + hostloc_info[FprOffset + new_location_index] = std::exchange(ValueInfo(*current_location), {}); + hostloc_info[FprOffset + new_location_index].realized = true; + return new_location_index; + } else { + UNREACHABLE(); + } +} + +u32 RegAlloc::RealizeWriteImpl(const IR::Inst* value, HostLoc::Kind required_kind) { + if (value == nullptr) { + // Scratch register allocation + if (required_kind == HostLoc::Kind::Gpr) { + const u32 idx = AllocateRegister(gpr_order, GprOffset); + SpillGpr(idx); + hostloc_info[GprOffset + idx].SetupScratchLocation(); + return idx; + } else if (required_kind == HostLoc::Kind::Fpr) { + const u32 idx = AllocateRegister(fpr_order, FprOffset); + SpillFpr(idx); + hostloc_info[FprOffset + idx].SetupScratchLocation(); + return idx; + } + } + + ASSERT(!ValueLocation(value)); + + const auto setup_location = [&](HostLocInfo& info) { + info = {}; + info.values.emplace_back(value); + info.locked = true; + info.realized = true; + info.expected_uses = value->UseCount(); + }; + + if (required_kind == HostLoc::Kind::Gpr) { + const u32 new_location_index = AllocateRegister(gpr_order, GprOffset); + SpillGpr(new_location_index); + setup_location(hostloc_info[GprOffset + new_location_index]); + return new_location_index; + } else if (required_kind == HostLoc::Kind::Fpr) { + const u32 new_location_index = AllocateRegister(fpr_order, FprOffset); + SpillFpr(new_location_index); + setup_location(hostloc_info[FprOffset + new_location_index]); + return new_location_index; + } else { + UNREACHABLE(); + } +} + +template u32 RegAlloc::RealizeReadImpl(const IR::Value& value); +template u32 RegAlloc::RealizeReadImpl(const IR::Value& value); + +u32 RegAlloc::AllocateRegister(const std::vector& order, size_t base_offset) { + const auto empty = std::find_if(order.begin(), order.end(), [&](u32 i) { + auto& info = hostloc_info[base_offset + i]; + return info.values.empty() && !info.locked; + }); + if (empty != order.end()) { + return *empty; + } + + std::vector candidates; + std::copy_if(order.begin(), order.end(), std::back_inserter(candidates), [&](u32 i) { + return !hostloc_info[base_offset + i].locked; + }); + ASSERT(!candidates.empty()); + + u32 best = candidates[0]; + size_t min_lru = hostloc_info[base_offset + best].lru_counter; + for (size_t i = 1; i < candidates.size(); ++i) { + auto& info = hostloc_info[base_offset + candidates[i]]; + if (info.lru_counter < min_lru) { + min_lru = info.lru_counter; + best = candidates[i]; + } + } + hostloc_info[base_offset + best].lru_counter++; + return best; +} + +void RegAlloc::SpillGpr(u32 index) { + auto& gpr_info = hostloc_info[GprOffset + index]; + ASSERT(!gpr_info.locked && !gpr_info.realized); + if (gpr_info.values.empty()) { + return; + } + const u32 new_location_index = FindFreeSpill(); + la_st_d(&as, static_cast(index), LA_SP, + static_cast(spill_offset + new_location_index * spill_slot_size)); + hostloc_info[SpillOffset + new_location_index] = std::exchange(gpr_info, {}); +} + +void RegAlloc::SpillFpr(u32 index) { + auto& fpr_info = hostloc_info[FprOffset + index]; + ASSERT(!fpr_info.locked && !fpr_info.realized); + if (fpr_info.values.empty()) { + return; + } + const u32 new_location_index = FindFreeSpill(); + la_fst_d(&as, static_cast(index), LA_SP, + static_cast(spill_offset + new_location_index * spill_slot_size)); + hostloc_info[SpillOffset + new_location_index] = std::exchange(fpr_info, {}); +} + +u32 RegAlloc::FindFreeSpill() const { + for (size_t i = 0; i < SpillCount; ++i) { + if (hostloc_info[SpillOffset + i].values.empty()) { + return static_cast(i); + } + } + UNREACHABLE(); +} + +std::optional RegAlloc::ValueLocation(const IR::Inst* value) const { + for (size_t i = 0; i < hostloc_info.size(); ++i) { + if (hostloc_info[i].Contains(value)) { + if (i < GprCount) { + return HostLoc{HostLoc::Kind::Gpr, static_cast(i)}; + } else if (i < GprCount + FprCount) { + return HostLoc{HostLoc::Kind::Fpr, static_cast(i - GprCount)}; + } else { + return HostLoc{HostLoc::Kind::Spill, static_cast(i - GprCount - FprCount)}; + } + } + } + return std::nullopt; +} + +HostLocInfo& RegAlloc::ValueInfo(HostLoc host_loc) { + switch (host_loc.kind) { + case HostLoc::Kind::Gpr: + return hostloc_info[GprOffset + host_loc.index]; + case HostLoc::Kind::Fpr: + return hostloc_info[FprOffset + host_loc.index]; + case HostLoc::Kind::Spill: + return hostloc_info[SpillOffset + host_loc.index]; + } + UNREACHABLE(); +} + +HostLocInfo& RegAlloc::ValueInfo(const IR::Inst* value) { + for (auto& info : hostloc_info) { + if (info.Contains(value)) { + return info; + } + } + UNREACHABLE(); +} + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.h b/src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.h new file mode 100644 index 0000000000..56a0597484 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.h @@ -0,0 +1,232 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "dynarmic/backend/loongarch64/lagoon_cpp.h" +#include "common/container/unordered_map.h" + +#include "common/assert.h" +#include "common/common_types.h" +#include "dynarmic/mcl/is_instance_of_template.hpp" + +#include "dynarmic/backend/loongarch64/stack_layout.h" +#include "dynarmic/ir/cond.h" +#include "dynarmic/ir/microinstruction.h" +#include "dynarmic/ir/value.h" + +namespace Dynarmic::Backend::LoongArch64 { + +class RegAlloc; + +// Wrapper types for LoongArch GPR/FPR (replacing biscuit's register types) +struct GPR { + la_gpr_t index = LA_ZERO; + GPR() = default; + explicit GPR(u32 i) : index{static_cast(i)} {} + uint32_t Index() const { return static_cast(index); } +}; + +struct FPR { + la_fpr_t index = LA_F0; + FPR() = default; + explicit FPR(u32 i) : index{static_cast(i)} {} + uint32_t Index() const { return static_cast(index); } +}; + +struct VPR { + la_vpr_t index; + VPR() = default; + explicit VPR(u32 i) : index{static_cast(i)} {} + uint32_t Index() const { return static_cast(index); } +}; + +struct HostLoc { + enum class Kind { + Gpr, + Fpr, + Spill, + } kind; + u32 index; +}; + +struct Argument { +public: + using copyable_reference = std::reference_wrapper; + + IR::Type GetType() const; + bool IsImmediate() const; + + bool GetImmediateU1() const; + u8 GetImmediateU8() const; + u16 GetImmediateU16() const; + u32 GetImmediateU32() const; + u64 GetImmediateU64() const; + IR::Cond GetImmediateCond() const; + IR::AccType GetImmediateAccType() const; + +private: + friend class RegAlloc; + explicit Argument() {} + + IR::Value value; + bool allocated = false; +}; + +template +struct RAReg { +public: + static constexpr HostLoc::Kind kind = std::is_same_v || std::is_same_v + ? HostLoc::Kind::Fpr + : HostLoc::Kind::Gpr; + + operator T() const { return *reg; } + + T operator*() const { return *reg; } + + const T* operator->() const { return &*reg; } + + ~RAReg(); + +private: + friend class RegAlloc; + explicit RAReg(RegAlloc& reg_alloc, bool write, const IR::Value& value); + + void Realize(); + + RegAlloc& reg_alloc; + bool write; + const IR::Value value; + std::optional reg; +}; + +struct HostLocInfo final { + std::vector values; + size_t locked = 0; + size_t uses_this_inst = 0; + size_t accumulated_uses = 0; + size_t expected_uses = 0; + bool realized = false; + size_t lru_counter = 0; + + bool Contains(const IR::Inst*) const; + void SetupScratchLocation(); + bool IsCompletelyEmpty() const; + void UpdateUses(); +}; + +class RegAlloc { +public: + using ArgumentInfo = std::array; + + explicit RegAlloc(lagoon_assembler_t& as, std::vector gpr_order, std::vector fpr_order) + : as{as}, gpr_order{std::move(gpr_order)}, fpr_order{std::move(fpr_order)} {} + + ArgumentInfo GetArgumentInfo(IR::Inst* inst); + bool IsValueLive(IR::Inst* inst) const; + + auto ReadX(Argument& arg) { return RAReg{*this, false, arg.value}; } + auto ReadD(Argument& arg) { return RAReg{*this, false, arg.value}; } + auto ReadV(Argument& arg) { return RAReg{*this, false, arg.value}; } + + auto WriteX(IR::Inst* inst) { return RAReg{*this, true, inst ? IR::Value{inst} : IR::Value{}}; } + auto WriteD(IR::Inst* inst) { return RAReg{*this, true, inst ? IR::Value{inst} : IR::Value{}}; } + auto WriteV(IR::Inst* inst) { return RAReg{*this, true, inst ? IR::Value{inst} : IR::Value{}}; } + + auto ScratchGpr() { return RAReg{*this, true, IR::Value{}}; } + auto ScratchFpr() { return RAReg{*this, true, IR::Value{}}; } + auto ScratchVec() { return RAReg{*this, true, IR::Value{}}; } + + void DefineAsExisting(IR::Inst* inst, Argument& arg); + + template + static void Realize(Ts&... rs) { + static_assert((mcl::is_instance_of_template() && ...)); + (rs.Realize(), ...); + } + + void UpdateAllUses(); + void AssertNoMoreUses() const; + +private: + template + friend struct RAReg; + + template + u32 GenerateImmediate(const IR::Value& value); + template + u32 RealizeReadImpl(const IR::Value& value); + u32 RealizeWriteImpl(const IR::Inst* value, HostLoc::Kind required_kind); + + u32 AllocateRegister(const std::vector& order, size_t base_offset); + void SpillGpr(u32 index); + void SpillFpr(u32 index); + u32 FindFreeSpill() const; + + std::optional ValueLocation(const IR::Inst* value) const; + HostLocInfo& ValueInfo(HostLoc host_loc); + HostLocInfo& ValueInfo(const IR::Inst* value); + + lagoon_assembler_t& as; + std::vector gpr_order; + std::vector fpr_order; + + static constexpr size_t GprCount = 32; + static constexpr size_t FprCount = 32; + static constexpr size_t GprOffset = 0; + static constexpr size_t FprOffset = GprCount; + static constexpr size_t SpillOffset = GprCount + FprCount; + + std::array hostloc_info; +}; + +template +RAReg::RAReg(RegAlloc& reg_alloc, bool write, const IR::Value& value) + : reg_alloc{reg_alloc}, write{write}, value{value} { + if (!write && !value.IsImmediate()) { + reg_alloc.ValueInfo(value.GetInst()).locked++; + } +} + +template +RAReg::~RAReg() { + if (value.IsEmpty()) { + if (reg) { + HostLocInfo& info = reg_alloc.ValueInfo(HostLoc{kind, reg->Index()}); + info.locked--; + info.realized = false; + } + return; + } + + if (value.IsImmediate()) { + if (reg) { + // Immediate was materialized into a scratch register + HostLocInfo& info = reg_alloc.ValueInfo(HostLoc{kind, reg->Index()}); + info.locked--; + info.realized = false; + } + } else if (!value.IsEmpty()) { + HostLocInfo& info = reg_alloc.ValueInfo(value.GetInst()); + info.locked--; + if (reg) { + info.realized = false; + } + } +} + +template +void RAReg::Realize() { + if (write && value.IsEmpty()) { + reg = T{reg_alloc.RealizeWriteImpl(nullptr, kind)}; + } else { + reg = T{write ? reg_alloc.RealizeWriteImpl(value.GetInst(), kind) : reg_alloc.RealizeReadImpl(value)}; + } +} + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/loongarch64/stack_layout.h b/src/dynarmic/src/dynarmic/backend/loongarch64/stack_layout.h new file mode 100644 index 0000000000..1583447f41 --- /dev/null +++ b/src/dynarmic/src/dynarmic/backend/loongarch64/stack_layout.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" + +namespace Dynarmic::Backend::LoongArch64 { + +constexpr size_t SpillCount = 64; + +struct alignas(16) StackLayout { + s64 cycles_remaining; + s64 cycles_to_run; + + std::array spill; + + u32 save_host_fpcr; + u32 save_host_fpsr; + + bool check_bit; +}; + +static_assert(sizeof(StackLayout) % 16 == 0); + +} // namespace Dynarmic::Backend::LoongArch64 diff --git a/src/dynarmic/src/dynarmic/backend/riscv64/a32_address_space.h b/src/dynarmic/src/dynarmic/backend/riscv64/a32_address_space.h index e882a9e2e6..ca5a1d165d 100644 --- a/src/dynarmic/src/dynarmic/backend/riscv64/a32_address_space.h +++ b/src/dynarmic/src/dynarmic/backend/riscv64/a32_address_space.h @@ -9,7 +9,7 @@ #pragma once #include -#include +#include "common/container/unordered_map.h" #include "dynarmic/backend/riscv64/code_block.h" #include "dynarmic/backend/riscv64/emit_riscv64.h" @@ -74,8 +74,8 @@ private: CodeBlock cb; biscuit::Assembler as; - ankerl::unordered_dense::map block_entries; - ankerl::unordered_dense::map block_infos; + ::Common::unordered_map block_entries; + ::Common::unordered_map block_infos; struct PreludeInfo { CodePtr end_of_prelude; diff --git a/src/dynarmic/src/dynarmic/backend/riscv64/emit_riscv64_a32.cpp b/src/dynarmic/src/dynarmic/backend/riscv64/emit_riscv64_a32.cpp index 8218ca3489..36709c7dbc 100644 --- a/src/dynarmic/src/dynarmic/backend/riscv64/emit_riscv64_a32.cpp +++ b/src/dynarmic/src/dynarmic/backend/riscv64/emit_riscv64_a32.cpp @@ -112,6 +112,7 @@ void EmitA32Cond(biscuit::Assembler& as, EmitContext&, IR::Cond cond, biscuit::L } } +void EmitA32LeafTerminal(biscuit::Assembler& as, EmitContext& ctx, IR::Term::LeafTerminal terminal, IR::LocationDescriptor initial_location, bool is_single_step); void EmitA32Terminal(biscuit::Assembler& as, EmitContext& ctx, IR::Term::Terminal terminal, IR::LocationDescriptor initial_location, bool is_single_step); void EmitA32Terminal(biscuit::Assembler& as, EmitContext& ctx, IR::Term::ReturnToDispatch, IR::LocationDescriptor, bool) { @@ -170,18 +171,18 @@ void EmitA32Terminal(biscuit::Assembler& as, EmitContext& ctx, IR::Term::FastDis void EmitA32Terminal(biscuit::Assembler& as, EmitContext& ctx, IR::Term::If terminal, IR::LocationDescriptor initial_location, bool is_single_step) { biscuit::Label pass; EmitA32Cond(as, ctx, terminal.if_, &pass); - EmitA32Terminal(as, ctx, terminal.else_, initial_location, is_single_step); + EmitA32LeafTerminal(as, ctx, terminal.else_, initial_location, is_single_step); as.Bind(&pass); - EmitA32Terminal(as, ctx, terminal.then_, initial_location, is_single_step); + EmitA32LeafTerminal(as, ctx, terminal.then_, initial_location, is_single_step); } void EmitA32Terminal(biscuit::Assembler& as, EmitContext& ctx, IR::Term::CheckBit terminal, IR::LocationDescriptor initial_location, bool is_single_step) { biscuit::Label fail; as.LBU(Xscratch0, offsetof(StackLayout, check_bit), Xstate); as.BEQZ(Xscratch0, &fail); - EmitA32Terminal(as, ctx, terminal.then_, initial_location, is_single_step); + EmitA32LeafTerminal(as, ctx, terminal.then_, initial_location, is_single_step); as.Bind(&fail); - EmitA32Terminal(as, ctx, terminal.else_, initial_location, is_single_step); + EmitA32LeafTerminal(as, ctx, terminal.else_, initial_location, is_single_step); } void EmitA32Terminal(biscuit::Assembler& as, EmitContext& ctx, IR::Term::CheckHalt terminal, IR::LocationDescriptor initial_location, bool is_single_step) { @@ -189,13 +190,35 @@ void EmitA32Terminal(biscuit::Assembler& as, EmitContext& ctx, IR::Term::CheckHa as.LWU(Xscratch0, 0, Xhalt); as.FENCE(biscuit::FenceOrder::RW, biscuit::FenceOrder::RW); as.BNEZ(Xscratch0, &fail); - EmitA32Terminal(as, ctx, terminal.else_, initial_location, is_single_step); + EmitA32LeafTerminal(as, ctx, terminal.else_, initial_location, is_single_step); as.Bind(&fail); EmitRelocation(as, ctx, LinkTarget::ReturnFromRunCode); } +void EmitA32LeafTerminal(biscuit::Assembler& as, EmitContext& ctx, IR::Term::LeafTerminal terminal, IR::LocationDescriptor initial_location, bool is_single_step) { + if (auto const x = std::get_if(&terminal)) + return EmitA32LeafTerminal(as, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA32LeafTerminal(as, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA32LeafTerminal(as, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA32LeafTerminal(as, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA32LeafTerminal(as, ctx, *x, initial_location, is_single_step); + UNREACHABLE(); +} + void EmitA32Terminal(biscuit::Assembler& as, EmitContext& ctx, IR::Term::Terminal terminal, IR::LocationDescriptor initial_location, bool is_single_step) { - boost::apply_visitor([&](const auto& t) { EmitA32Terminal(as, ctx, t, initial_location, is_single_step); }, terminal); + if (auto const x = std::get_if(&terminal)) + return EmitA32LeafTerminal(as, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA32Terminal(as, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA32Terminal(as, ctx, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitA32Terminal(as, ctx, *x, initial_location, is_single_step); + UNREACHABLE(); } void EmitA32Terminal(biscuit::Assembler& as, EmitContext& ctx) { diff --git a/src/dynarmic/src/dynarmic/backend/riscv64/reg_alloc.h b/src/dynarmic/src/dynarmic/backend/riscv64/reg_alloc.h index 5a0bd660de..8a70f284cb 100644 --- a/src/dynarmic/src/dynarmic/backend/riscv64/reg_alloc.h +++ b/src/dynarmic/src/dynarmic/backend/riscv64/reg_alloc.h @@ -19,7 +19,7 @@ #include "common/assert.h" #include "common/common_types.h" #include "dynarmic/mcl/is_instance_of_template.hpp" -#include +#include "common/container/unordered_map.h" #include "dynarmic/backend/riscv64/stack_layout.h" #include "dynarmic/ir/cond.h" diff --git a/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64.cpp b/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64.cpp index 1c21886c60..5c6a90e15a 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64.cpp @@ -175,7 +175,7 @@ finish_this_inst: if (conf.enable_cycle_counting) EmitAddCycles(block.CycleCount()); code.mov(rbp, code.qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, abi_base_pointer)]); - EmitTerminal(block.GetTerminal(), ctx.Location().SetSingleStepping(false), ctx.IsSingleStep()); + EmitTerminal(block.terminal, ctx.Location().SetSingleStepping(false), ctx.IsSingleStep()); code.int3(); for (auto& deferred_emit : ctx.deferred_emits) @@ -219,7 +219,7 @@ void A32EmitX64::EmitCondPrelude(const A32EmitContext& ctx) { if (conf.enable_cycle_counting) { EmitAddCycles(ctx.block.ConditionFailedCycleCount()); } - EmitTerminal(IR::Term::LinkBlock{ctx.block.ConditionFailedLocation()}, ctx.Location().SetSingleStepping(false), ctx.IsSingleStep()); + EmitLeafTerminal(IR::Term::LinkBlock{ctx.block.ConditionFailedLocation()}, ctx.Location().SetSingleStepping(false), ctx.IsSingleStep()); code.L(pass); } @@ -247,7 +247,7 @@ void A32EmitX64::GenTerminalHandlers() { calculate_location_descriptor(); code.mov(eax, dword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_ptr)]); code.sub(eax, 1); - code.and_(eax, u32(A32JitState::RSBPtrMask)); + code.and_(eax, u32(A32JitState::RSB_PTR_MASK)); code.mov(dword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_ptr)], eax); code.cmp(rbx, qword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_location_descriptors) + rax * sizeof(u64)]); if (conf.HasOptimization(OptimizationFlag::FastDispatch)) { @@ -1155,11 +1155,12 @@ void A32EmitX64::EmitSetUpperLocationDescriptor(IR::LocationDescriptor new_locat } namespace { -void EmitTerminalImpl(A32EmitX64& e, IR::Term::ReturnToDispatch, IR::LocationDescriptor, bool) { +bool EmitTerminalImpl(A32EmitX64& e, IR::Term::ReturnToDispatch, IR::LocationDescriptor, bool) { e.code.ReturnFromRunCode(); + return true; } -void EmitTerminalImpl(A32EmitX64& e, IR::Term::LinkBlock terminal, IR::LocationDescriptor initial_location, bool is_single_step) { +bool EmitTerminalImpl(A32EmitX64& e, IR::Term::LinkBlock terminal, IR::LocationDescriptor initial_location, bool is_single_step) { e.EmitSetUpperLocationDescriptor(terminal.next, initial_location); if (!e.conf.HasOptimization(OptimizationFlag::BlockLinking) || is_single_step) { e.code.mov(MJitStateReg(A32::Reg::PC), A32::LocationDescriptor{terminal.next}.PC()); @@ -1186,9 +1187,10 @@ void EmitTerminalImpl(A32EmitX64& e, IR::Term::LinkBlock terminal, IR::LocationD e.PushRSBHelper(rax, rbx, terminal.next); e.code.ForceReturnFromRunCode(); } + return true; } -void EmitTerminalImpl(A32EmitX64& e, IR::Term::LinkBlockFast terminal, IR::LocationDescriptor initial_location, bool is_single_step) { +bool EmitTerminalImpl(A32EmitX64& e, IR::Term::LinkBlockFast terminal, IR::LocationDescriptor initial_location, bool is_single_step) { e.EmitSetUpperLocationDescriptor(terminal.next, initial_location); if (!e.conf.HasOptimization(OptimizationFlag::BlockLinking) || is_single_step) { e.code.mov(MJitStateReg(A32::Reg::PC), A32::LocationDescriptor{terminal.next}.PC()); @@ -1201,55 +1203,78 @@ void EmitTerminalImpl(A32EmitX64& e, IR::Term::LinkBlockFast terminal, IR::Locat e.EmitPatchJmp(terminal.next); } } + return true; } -void EmitTerminalImpl(A32EmitX64& e, IR::Term::PopRSBHint, IR::LocationDescriptor, bool is_single_step) { +bool EmitTerminalImpl(A32EmitX64& e, IR::Term::PopRSBHint, IR::LocationDescriptor, bool is_single_step) { if (!e.conf.HasOptimization(OptimizationFlag::ReturnStackBuffer) || is_single_step) { e.code.ReturnFromRunCode(); } else { e.code.jmp(e.terminal_handler_pop_rsb_hint); } + return true; } -void EmitTerminalImpl(A32EmitX64& e, IR::Term::FastDispatchHint, IR::LocationDescriptor, bool is_single_step) { +bool EmitTerminalImpl(A32EmitX64& e, IR::Term::FastDispatchHint, IR::LocationDescriptor, bool is_single_step) { if (!e.conf.HasOptimization(OptimizationFlag::FastDispatch) || is_single_step) { e.code.ReturnFromRunCode(); } else { e.code.jmp(e.terminal_handler_fast_dispatch_hint); } + return true; } -void EmitTerminalImpl(A32EmitX64& e, IR::Term::If terminal, IR::LocationDescriptor initial_location, bool is_single_step) { +bool EmitTerminalImpl(A32EmitX64& e, IR::Term::If terminal, IR::LocationDescriptor initial_location, bool is_single_step) { Xbyak::Label pass = e.EmitCond(terminal.if_); - e.EmitTerminal(terminal.else_, initial_location, is_single_step); + e.EmitLeafTerminal(terminal.else_, initial_location, is_single_step); e.code.L(pass); - e.EmitTerminal(terminal.then_, initial_location, is_single_step); + e.EmitLeafTerminal(terminal.then_, initial_location, is_single_step); + return true; } -void EmitTerminalImpl(A32EmitX64& e, IR::Term::CheckBit terminal, IR::LocationDescriptor initial_location, bool is_single_step) { +bool EmitTerminalImpl(A32EmitX64& e, IR::Term::CheckBit terminal, IR::LocationDescriptor initial_location, bool is_single_step) { Xbyak::Label fail; e.code.cmp(e.code.byte[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, check_bit)], u8(0)); e.code.jz(fail); - e.EmitTerminal(terminal.then_, initial_location, is_single_step); + e.EmitLeafTerminal(terminal.then_, initial_location, is_single_step); e.code.L(fail); - e.EmitTerminal(terminal.else_, initial_location, is_single_step); + e.EmitLeafTerminal(terminal.else_, initial_location, is_single_step); + return true; } -void EmitTerminalImpl(A32EmitX64& e, IR::Term::CheckHalt terminal, IR::LocationDescriptor initial_location, bool is_single_step) { +bool EmitTerminalImpl(A32EmitX64& e, IR::Term::CheckHalt terminal, IR::LocationDescriptor initial_location, bool is_single_step) { e.code.cmp(dword[e.code.ABI_JIT_PTR + offsetof(A32JitState, halt_reason)], 0); e.code.jne(e.code.GetForceReturnFromRunCodeAddress()); - e.EmitTerminal(terminal.else_, initial_location, is_single_step); + e.EmitLeafTerminal(terminal.else_, initial_location, is_single_step); + return true; } -void EmitTerminalImpl(A32EmitX64&, IR::Term::Invalid, IR::LocationDescriptor, bool) { +} + +bool A32EmitX64::EmitLeafTerminal(IR::Term::LeafTerminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step) noexcept { + if (auto const x = std::get_if(&terminal)) + return EmitTerminalImpl(*this, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitTerminalImpl(*this, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitTerminalImpl(*this, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitTerminalImpl(*this, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitTerminalImpl(*this, *x, initial_location, is_single_step); UNREACHABLE(); } -} -void A32EmitX64::EmitTerminal(IR::Terminal terminal, IR::LocationDescriptor initial_location, bool is_single_step) noexcept { - boost::apply_visitor([this, initial_location, is_single_step](auto x) { - EmitTerminalImpl(*this, x, initial_location, is_single_step); - }, terminal); +bool A32EmitX64::EmitTerminal(IR::Term::Terminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step) noexcept { + if (auto const e = std::get_if(&terminal)) + return EmitLeafTerminal(*e, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitTerminalImpl(*this, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitTerminalImpl(*this, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitTerminalImpl(*this, *x, initial_location, is_single_step); + UNREACHABLE(); } void A32EmitX64::EmitPatchJg(const IR::LocationDescriptor& target_desc, CodePtr target_code_ptr) { diff --git a/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64.h b/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64.h index 8e97dc7737..a106bb6482 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64.h +++ b/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64.h @@ -13,7 +13,8 @@ #include #include -#include +#include "common/container/unordered_map.h" +#include "common/container/unordered_set.h" #include "dynarmic/backend/block_range_information.h" #include "dynarmic/backend/x64/a32_jitstate.h" @@ -112,7 +113,8 @@ public: // Terminal instruction emitters void EmitSetUpperLocationDescriptor(IR::LocationDescriptor new_location, IR::LocationDescriptor old_location); - void EmitTerminal(IR::Terminal terminal, IR::LocationDescriptor initial_location, bool is_single_step) noexcept override; + bool EmitLeafTerminal(IR::Term::LeafTerminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step) noexcept override; + bool EmitTerminal(IR::Term::Terminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step) noexcept override; // Patching void Unpatch(const IR::LocationDescriptor& target_desc) override; @@ -125,11 +127,11 @@ public: RegAlloc reg_alloc; //reusable reg alloc BlockRangeInformation block_ranges; std::array fast_dispatch_table; - ankerl::unordered_dense::map fastmem_patch_info; - ankerl::unordered_dense::map, void (*)()> read_fallbacks; - ankerl::unordered_dense::map, void (*)()> write_fallbacks; - ankerl::unordered_dense::map, void (*)()> exclusive_write_fallbacks; - ankerl::unordered_dense::set do_not_fastmem; + ::Common::unordered_map fastmem_patch_info; + ::Common::unordered_map, void (*)()> read_fallbacks; + ::Common::unordered_map, void (*)()> write_fallbacks; + ::Common::unordered_map, void (*)()> exclusive_write_fallbacks; + ::Common::unordered_set do_not_fastmem; boost::container::stable_vector shared_labels; void (*memory_read_128)() = nullptr; // Dummy void (*memory_write_128)() = nullptr; // Dummy diff --git a/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64_memory.cpp b/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64_memory.cpp index f138b5f137..e866b2acd6 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64_memory.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/a32_emit_x64_memory.cpp @@ -31,16 +31,16 @@ using namespace Xbyak::util; void A32EmitX64::GenFastmemFallbacks() { const std::initializer_list idxes{0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}; const std::array, 4> read_callbacks{{ - {8, Devirtualize<&A32::UserCallbacks::MemoryRead8>(conf.callbacks)}, - {16, Devirtualize<&A32::UserCallbacks::MemoryRead16>(conf.callbacks)}, - {32, Devirtualize<&A32::UserCallbacks::MemoryRead32>(conf.callbacks)}, - {64, Devirtualize<&A32::UserCallbacks::MemoryRead64>(conf.callbacks)}, + {8, Devirtualize<&A32::UserCallbacks::MemoryRead>(conf.callbacks)}, + {16, Devirtualize<&A32::UserCallbacks::MemoryRead>(conf.callbacks)}, + {32, Devirtualize<&A32::UserCallbacks::MemoryRead>(conf.callbacks)}, + {64, Devirtualize<&A32::UserCallbacks::MemoryRead>(conf.callbacks)}, }}; const std::array, 4> write_callbacks{{ - {8, Devirtualize<&A32::UserCallbacks::MemoryWrite8>(conf.callbacks)}, - {16, Devirtualize<&A32::UserCallbacks::MemoryWrite16>(conf.callbacks)}, - {32, Devirtualize<&A32::UserCallbacks::MemoryWrite32>(conf.callbacks)}, - {64, Devirtualize<&A32::UserCallbacks::MemoryWrite64>(conf.callbacks)}, + {8, Devirtualize<&A32::UserCallbacks::MemoryWrite>(conf.callbacks)}, + {16, Devirtualize<&A32::UserCallbacks::MemoryWrite>(conf.callbacks)}, + {32, Devirtualize<&A32::UserCallbacks::MemoryWrite>(conf.callbacks)}, + {64, Devirtualize<&A32::UserCallbacks::MemoryWrite>(conf.callbacks)}, }}; const std::array, 4> exclusive_write_callbacks{{ {8, Devirtualize<&A32::UserCallbacks::MemoryWriteExclusive8>(conf.callbacks)}, @@ -56,12 +56,12 @@ void A32EmitX64::GenFastmemFallbacks() { code.align(); read_fallbacks[std::make_tuple(ordered, bitsize, vaddr_idx, value_idx)] = code.getCurr(); ABI_PushCallerSaveRegistersAndAdjustStackExcept(code, HostLocRegIdx(value_idx)); + // params = { this, vaddr, size } if (vaddr_idx != code.ABI_PARAM2.getIdx()) { code.mov(code.ABI_PARAM2, Xbyak::Reg64{vaddr_idx}); } - if (ordered) { - code.mfence(); - } + code.mov(code.ABI_PARAM3, bitsize / CHAR_BIT); + if (ordered) code.mfence(); callback.EmitCall(code); if (value_idx != code.ABI_RETURN.getIdx()) { code.mov(Xbyak::Reg64{value_idx}, code.ABI_RETURN); @@ -76,6 +76,7 @@ void A32EmitX64::GenFastmemFallbacks() { code.align(); write_fallbacks[std::make_tuple(ordered, bitsize, vaddr_idx, value_idx)] = code.getCurr(); ABI_PushCallerSaveRegistersAndAdjustStack(code); + // params = { this, vaddr, value, size } if (vaddr_idx == code.ABI_PARAM3.getIdx() && value_idx == code.ABI_PARAM2.getIdx()) { code.xchg(code.ABI_PARAM2, code.ABI_PARAM3); } else if (vaddr_idx == code.ABI_PARAM3.getIdx()) { @@ -92,10 +93,9 @@ void A32EmitX64::GenFastmemFallbacks() { } } code.ZeroExtendFrom(bitsize, code.ABI_PARAM3); + code.mov(code.ABI_PARAM4, bitsize / CHAR_BIT); callback.EmitCall(code); - if (ordered) { - code.mfence(); - } + if (ordered) code.mfence(); ABI_PopCallerSaveRegistersAndAdjustStack(code); code.ret(); PerfMapRegister(write_fallbacks[std::make_tuple(ordered, bitsize, vaddr_idx, value_idx)], code.getCurr(), fmt::format("a32_write_fallback_{}", bitsize)); @@ -138,35 +138,35 @@ void A32EmitX64::GenFastmemFallbacks() { #undef Axx void A32EmitX64::EmitA32ReadMemory8(A32EmitContext& ctx, IR::Inst* inst) { - EmitMemoryRead<8, &A32::UserCallbacks::MemoryRead8>(ctx, inst); + EmitMemoryRead<8, &A32::UserCallbacks::MemoryRead>(ctx, inst); } void A32EmitX64::EmitA32ReadMemory16(A32EmitContext& ctx, IR::Inst* inst) { - EmitMemoryRead<16, &A32::UserCallbacks::MemoryRead16>(ctx, inst); + EmitMemoryRead<16, &A32::UserCallbacks::MemoryRead>(ctx, inst); } void A32EmitX64::EmitA32ReadMemory32(A32EmitContext& ctx, IR::Inst* inst) { - EmitMemoryRead<32, &A32::UserCallbacks::MemoryRead32>(ctx, inst); + EmitMemoryRead<32, &A32::UserCallbacks::MemoryRead>(ctx, inst); } void A32EmitX64::EmitA32ReadMemory64(A32EmitContext& ctx, IR::Inst* inst) { - EmitMemoryRead<64, &A32::UserCallbacks::MemoryRead64>(ctx, inst); + EmitMemoryRead<64, &A32::UserCallbacks::MemoryRead>(ctx, inst); } void A32EmitX64::EmitA32WriteMemory8(A32EmitContext& ctx, IR::Inst* inst) { - EmitMemoryWrite<8, &A32::UserCallbacks::MemoryWrite8>(ctx, inst); + EmitMemoryWrite<8, &A32::UserCallbacks::MemoryWrite>(ctx, inst); } void A32EmitX64::EmitA32WriteMemory16(A32EmitContext& ctx, IR::Inst* inst) { - EmitMemoryWrite<16, &A32::UserCallbacks::MemoryWrite16>(ctx, inst); + EmitMemoryWrite<16, &A32::UserCallbacks::MemoryWrite>(ctx, inst); } void A32EmitX64::EmitA32WriteMemory32(A32EmitContext& ctx, IR::Inst* inst) { - EmitMemoryWrite<32, &A32::UserCallbacks::MemoryWrite32>(ctx, inst); + EmitMemoryWrite<32, &A32::UserCallbacks::MemoryWrite>(ctx, inst); } void A32EmitX64::EmitA32WriteMemory64(A32EmitContext& ctx, IR::Inst* inst) { - EmitMemoryWrite<64, &A32::UserCallbacks::MemoryWrite64>(ctx, inst); + EmitMemoryWrite<64, &A32::UserCallbacks::MemoryWrite>(ctx, inst); } void A32EmitX64::EmitA32ClearExclusive(A32EmitContext&, IR::Inst*) { @@ -175,33 +175,33 @@ void A32EmitX64::EmitA32ClearExclusive(A32EmitContext&, IR::Inst*) { void A32EmitX64::EmitA32ExclusiveReadMemory8(A32EmitContext& ctx, IR::Inst* inst) { if (conf.fastmem_exclusive_access) { - EmitExclusiveReadMemoryInline<8, &A32::UserCallbacks::MemoryRead8>(ctx, inst); + EmitExclusiveReadMemoryInline<8, &A32::UserCallbacks::MemoryRead>(ctx, inst); } else { - EmitExclusiveReadMemory<8, &A32::UserCallbacks::MemoryRead8>(ctx, inst); + EmitExclusiveReadMemory<8, &A32::UserCallbacks::MemoryRead>(ctx, inst); } } void A32EmitX64::EmitA32ExclusiveReadMemory16(A32EmitContext& ctx, IR::Inst* inst) { if (conf.fastmem_exclusive_access) { - EmitExclusiveReadMemoryInline<16, &A32::UserCallbacks::MemoryRead16>(ctx, inst); + EmitExclusiveReadMemoryInline<16, &A32::UserCallbacks::MemoryRead>(ctx, inst); } else { - EmitExclusiveReadMemory<16, &A32::UserCallbacks::MemoryRead16>(ctx, inst); + EmitExclusiveReadMemory<16, &A32::UserCallbacks::MemoryRead>(ctx, inst); } } void A32EmitX64::EmitA32ExclusiveReadMemory32(A32EmitContext& ctx, IR::Inst* inst) { if (conf.fastmem_exclusive_access) { - EmitExclusiveReadMemoryInline<32, &A32::UserCallbacks::MemoryRead32>(ctx, inst); + EmitExclusiveReadMemoryInline<32, &A32::UserCallbacks::MemoryRead>(ctx, inst); } else { - EmitExclusiveReadMemory<32, &A32::UserCallbacks::MemoryRead32>(ctx, inst); + EmitExclusiveReadMemory<32, &A32::UserCallbacks::MemoryRead>(ctx, inst); } } void A32EmitX64::EmitA32ExclusiveReadMemory64(A32EmitContext& ctx, IR::Inst* inst) { if (conf.fastmem_exclusive_access) { - EmitExclusiveReadMemoryInline<64, &A32::UserCallbacks::MemoryRead64>(ctx, inst); + EmitExclusiveReadMemoryInline<64, &A32::UserCallbacks::MemoryRead>(ctx, inst); } else { - EmitExclusiveReadMemory<64, &A32::UserCallbacks::MemoryRead64>(ctx, inst); + EmitExclusiveReadMemory<64, &A32::UserCallbacks::MemoryRead>(ctx, inst); } } diff --git a/src/dynarmic/src/dynarmic/backend/x64/a32_interface.cpp b/src/dynarmic/src/dynarmic/backend/x64/a32_interface.cpp index 097e0f426f..72627b9e1a 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/a32_interface.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/a32_interface.cpp @@ -37,9 +37,9 @@ using namespace Backend::X64; static RunCodeCallbacks GenRunCodeCallbacks(A32::UserCallbacks* cb, CodePtr (*LookupBlock)(void* lookup_block_arg), void* arg, const A32::UserConfig& conf) { return RunCodeCallbacks{ - std::make_unique(LookupBlock, reinterpret_cast(arg)), - std::make_unique(Devirtualize<&A32::UserCallbacks::AddTicks>(cb)), - std::make_unique(Devirtualize<&A32::UserCallbacks::GetTicksRemaining>(cb)), + ArgCallback(LookupBlock, reinterpret_cast(arg)), + ArgCallback(Devirtualize<&A32::UserCallbacks::AddTicks>(cb)), + ArgCallback(Devirtualize<&A32::UserCallbacks::GetTicksRemaining>(cb)), conf.enable_cycle_counting, }; } @@ -79,7 +79,7 @@ struct Jit::Impl { jit_interface->is_executing = true; const CodePtr current_codeptr = [this] { // RSB optimization - const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A32JitState::RSBPtrMask; + const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A32JitState::RSB_PTR_MASK; if (jit_state.GetUniqueHash() == jit_state.rsb_location_descriptors[new_rsb_ptr]) { jit_state.rsb_ptr = new_rsb_ptr; return reinterpret_cast(jit_state.rsb_codeptrs[new_rsb_ptr]); @@ -234,7 +234,7 @@ private: BlockOfCode block_of_code; A32EmitX64 emitter; Optimization::PolyfillOptions polyfill_options; - // Keep it here, you don't wanna mess with the fuckery that's initializer lists + // Keep it here in order to not mess with initializer lists const A32::UserConfig conf; Jit* jit_interface; diff --git a/src/dynarmic/src/dynarmic/backend/x64/a32_jitstate.h b/src/dynarmic/src/dynarmic/backend/x64/a32_jitstate.h index c276ab0b8b..ad151e6092 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/a32_jitstate.h +++ b/src/dynarmic/src/dynarmic/backend/x64/a32_jitstate.h @@ -27,6 +27,9 @@ struct A32JitState { A32JitState() { ResetRSB(); } + static constexpr std::size_t RSB_SIZE = 8; // MUST be a power of 2. + static constexpr std::size_t RSB_PTR_MASK = RSB_SIZE - 1; + std::array Reg{}; // Current register file. // TODO: Mode-specific register sets unimplemented. @@ -36,8 +39,9 @@ struct A32JitState { u32 cpsr_q = 0; u32 cpsr_nzcv = 0; u32 cpsr_jaifm = 0; - u32 Cpsr() const; - void SetCpsr(u32 cpsr); + u32 fpsr_exc = 0; + u32 fpsr_qc = 0; + u32 fpsr_nzcv = 0; alignas(16) std::array ExtReg{}; // Extension registers. @@ -49,21 +53,19 @@ struct A32JitState { // Exclusive state u32 exclusive_state = 0; - static constexpr std::size_t RSBSize = 8; // MUST be a power of 2. - static constexpr std::size_t RSBPtrMask = RSBSize - 1; u32 rsb_ptr = 0; - std::array rsb_location_descriptors; - std::array rsb_codeptrs; - void ResetRSB(); + std::array rsb_location_descriptors; + std::array rsb_codeptrs; - u32 fpsr_exc = 0; - u32 fpsr_qc = 0; - u32 fpsr_nzcv = 0; + u32 Cpsr() const; + void SetCpsr(u32 cpsr); + + void ResetRSB(); u32 Fpscr() const; void SetFpscr(u32 FPSCR); u64 GetUniqueHash() const noexcept { - return (static_cast(upper_location_descriptor) << 32) | (static_cast(Reg[15])); + return (u64(upper_location_descriptor) << 32) | (u64(Reg[15])); } void TransferJitState(const A32JitState& src, bool reset_rsb) { diff --git a/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.cpp b/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.cpp index cf9dab4034..71e5e62cd9 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.cpp @@ -12,6 +12,7 @@ #include #include "common/assert.h" #include "common/common_types.h" +#include "dynarmic/ir/terminal.h" #include "dynarmic/mcl/integer_of_size.hpp" #include @@ -147,7 +148,7 @@ finish_this_inst: if (conf.enable_cycle_counting) EmitAddCycles(block.CycleCount()); code.mov(rbp, code.qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, abi_base_pointer)]); - EmitTerminal(block.GetTerminal(), ctx.Location().SetSingleStepping(false), ctx.IsSingleStep()); + EmitTerminal(block.terminal, ctx.Location().SetSingleStepping(false), ctx.IsSingleStep()); code.int3(); for (auto& deferred_emit : ctx.deferred_emits) deferred_emit(); @@ -207,7 +208,7 @@ void A64EmitX64::GenTerminalHandlers() { calculate_location_descriptor(); code.mov(eax, dword[code.ABI_JIT_PTR + offsetof(A64JitState, rsb_ptr)]); code.sub(eax, 1); - code.and_(eax, u32(A64JitState::RSBPtrMask)); + code.and_(eax, u32(A64JitState::RSB_PTR_MASK)); code.mov(dword[code.ABI_JIT_PTR + offsetof(A64JitState, rsb_ptr)], eax); code.cmp(rbx, qword[code.ABI_JIT_PTR + offsetof(A64JitState, rsb_location_descriptors) + rax * sizeof(u64)]); if (conf.HasOptimization(OptimizationFlag::FastDispatch)) { @@ -224,7 +225,7 @@ void A64EmitX64::GenTerminalHandlers() { terminal_handler_fast_dispatch_hint = code.getCurr(); calculate_location_descriptor(); code.L(rsb_cache_miss); - code.mov(r8, reinterpret_cast(fast_dispatch_table.data())); + code.mov(r8, u64(fast_dispatch_table.data())); //code.mov(r12, qword[code.ABI_JIT_PTR + offsetof(A64JitState, pc)]); code.mov(r12, rbx); if (code.HasHostFeature(HostFeature::SSE42)) { @@ -244,7 +245,7 @@ void A64EmitX64::GenTerminalHandlers() { code.align(); fast_dispatch_table_lookup = code.getCurr(); - code.mov(code.ABI_PARAM2, reinterpret_cast(fast_dispatch_table.data())); + code.mov(code.ABI_PARAM2, u64(fast_dispatch_table.data())); if (code.HasHostFeature(HostFeature::SSE42)) { code.crc32(code.ABI_PARAM1, code.ABI_PARAM2); } @@ -617,11 +618,12 @@ std::string A64EmitX64::LocationDescriptorToFriendlyName(const IR::LocationDescr } namespace { -void EmitTerminalImpl(A64EmitX64& e, IR::Term::ReturnToDispatch, IR::LocationDescriptor, bool) { +bool EmitTerminalImpl(A64EmitX64& e, IR::Term::ReturnToDispatch, IR::LocationDescriptor, bool) { e.code.ReturnFromRunCode(); + return true; } -void EmitTerminalImpl(A64EmitX64& e, IR::Term::LinkBlock terminal, IR::LocationDescriptor, bool is_single_step) { +bool EmitTerminalImpl(A64EmitX64& e, IR::Term::LinkBlock terminal, IR::LocationDescriptor, bool is_single_step) { // Used for patches and linking if (e.conf.HasOptimization(OptimizationFlag::BlockLinking) && !is_single_step) { if (e.conf.enable_cycle_counting) { @@ -649,9 +651,10 @@ void EmitTerminalImpl(A64EmitX64& e, IR::Term::LinkBlock terminal, IR::LocationD e.code.mov(qword[e.code.ABI_JIT_PTR + offsetof(A64JitState, pc)], rax); e.code.ReturnFromRunCode(); } + return true; } -void EmitTerminalImpl(A64EmitX64& e, IR::Term::LinkBlockFast terminal, IR::LocationDescriptor, bool is_single_step) { +bool EmitTerminalImpl(A64EmitX64& e, IR::Term::LinkBlockFast terminal, IR::LocationDescriptor, bool is_single_step) { if (e.conf.HasOptimization(OptimizationFlag::BlockLinking) && !is_single_step) { e.patch_information[terminal.next].jmp.push_back(e.code.getCurr()); if (auto next_bb = e.GetBasicBlock(terminal.next)) { @@ -664,63 +667,86 @@ void EmitTerminalImpl(A64EmitX64& e, IR::Term::LinkBlockFast terminal, IR::Locat e.code.mov(qword[e.code.ABI_JIT_PTR + offsetof(A64JitState, pc)], rax); e.code.ReturnFromRunCode(); } + return true; } -void EmitTerminalImpl(A64EmitX64& e, IR::Term::PopRSBHint, IR::LocationDescriptor, bool is_single_step) { +bool EmitTerminalImpl(A64EmitX64& e, IR::Term::PopRSBHint, IR::LocationDescriptor, bool is_single_step) { if (e.conf.HasOptimization(OptimizationFlag::ReturnStackBuffer) && !is_single_step) { e.code.jmp(e.terminal_handler_pop_rsb_hint); } else { e.code.ReturnFromRunCode(); } + return true; } -void EmitTerminalImpl(A64EmitX64& e, IR::Term::FastDispatchHint, IR::LocationDescriptor, bool is_single_step) { +bool EmitTerminalImpl(A64EmitX64& e, IR::Term::FastDispatchHint, IR::LocationDescriptor, bool is_single_step) { if (!e.conf.HasOptimization(OptimizationFlag::FastDispatch) || is_single_step) { e.code.ReturnFromRunCode(); } else { e.code.jmp(e.terminal_handler_fast_dispatch_hint); } + return true; } -void EmitTerminalImpl(A64EmitX64& e, IR::Term::If terminal, IR::LocationDescriptor initial_location, bool is_single_step) { +bool EmitTerminalImpl(A64EmitX64& e, IR::Term::If terminal, IR::LocationDescriptor initial_location, bool is_single_step) { switch (terminal.if_) { case IR::Cond::AL: case IR::Cond::NV: - e.EmitTerminal(terminal.then_, initial_location, is_single_step); + e.EmitLeafTerminal(terminal.then_, initial_location, is_single_step); break; default: Xbyak::Label pass = e.EmitCond(terminal.if_); - e.EmitTerminal(terminal.else_, initial_location, is_single_step); + e.EmitLeafTerminal(terminal.else_, initial_location, is_single_step); e.code.L(pass); - e.EmitTerminal(terminal.then_, initial_location, is_single_step); + e.EmitLeafTerminal(terminal.then_, initial_location, is_single_step); break; } + return true; } -void EmitTerminalImpl(A64EmitX64& e, IR::Term::CheckBit terminal, IR::LocationDescriptor initial_location, bool is_single_step) { +bool EmitTerminalImpl(A64EmitX64& e, IR::Term::CheckBit terminal, IR::LocationDescriptor initial_location, bool is_single_step) { Xbyak::Label fail; e.code.cmp(e.code.byte[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, check_bit)], u8(0)); e.code.jz(fail); - e.EmitTerminal(terminal.then_, initial_location, is_single_step); + e.EmitLeafTerminal(terminal.then_, initial_location, is_single_step); e.code.L(fail); - e.EmitTerminal(terminal.else_, initial_location, is_single_step); + e.EmitLeafTerminal(terminal.else_, initial_location, is_single_step); + return true; } -void EmitTerminalImpl(A64EmitX64& e, IR::Term::CheckHalt terminal, IR::LocationDescriptor initial_location, bool is_single_step) { +bool EmitTerminalImpl(A64EmitX64& e, IR::Term::CheckHalt terminal, IR::LocationDescriptor initial_location, bool is_single_step) { e.code.cmp(dword[e.code.ABI_JIT_PTR + offsetof(A64JitState, halt_reason)], 0); e.code.jne(e.code.GetForceReturnFromRunCodeAddress()); - e.EmitTerminal(terminal.else_, initial_location, is_single_step); + e.EmitLeafTerminal(terminal.else_, initial_location, is_single_step); + return true; } -void EmitTerminalImpl(A64EmitX64&, IR::Term::Invalid, IR::LocationDescriptor, bool) { +} + +bool A64EmitX64::EmitLeafTerminal(IR::Term::LeafTerminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step) noexcept { + if (auto const x = std::get_if(&terminal)) + return EmitTerminalImpl(*this, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitTerminalImpl(*this, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitTerminalImpl(*this, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitTerminalImpl(*this, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitTerminalImpl(*this, *x, initial_location, is_single_step); UNREACHABLE(); } -} -void A64EmitX64::EmitTerminal(IR::Terminal terminal, IR::LocationDescriptor initial_location, bool is_single_step) noexcept { - boost::apply_visitor([this, initial_location, is_single_step](auto x) { - EmitTerminalImpl(*this, x, initial_location, is_single_step); - }, terminal); +bool A64EmitX64::EmitTerminal(IR::Term::Terminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step) noexcept { + if (auto const x = std::get_if(&terminal)) + return EmitLeafTerminal(*x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitTerminalImpl(*this, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitTerminalImpl(*this, *x, initial_location, is_single_step); + if (auto const x = std::get_if(&terminal)) + return EmitTerminalImpl(*this, *x, initial_location, is_single_step); + UNREACHABLE(); } void A64EmitX64::EmitPatchJg(const IR::LocationDescriptor& target_desc, CodePtr target_code_ptr) { diff --git a/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.h b/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.h index d57b1d81b9..1310b4ad34 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.h +++ b/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64.h @@ -12,7 +12,8 @@ #include #include #include -#include +#include "common/container/unordered_map.h" +#include "common/container/unordered_set.h" #include #include "dynarmic/backend/block_range_information.h" @@ -107,7 +108,8 @@ public: void EmitExclusiveWriteMemoryInline(A64EmitContext& ctx, IR::Inst* inst); // Terminal instruction emitters - void EmitTerminal(IR::Terminal terminal, IR::LocationDescriptor initial_location, bool is_single_step) noexcept override; + bool EmitLeafTerminal(IR::Term::LeafTerminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step) noexcept override; + bool EmitTerminal(IR::Term::Terminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step) noexcept override; // Patching void Unpatch(const IR::LocationDescriptor& target_desc) override; @@ -121,11 +123,11 @@ public: RegAlloc reg_alloc; //reusable reg alloc BlockRangeInformation block_ranges; std::array fast_dispatch_table; - ankerl::unordered_dense::map fastmem_patch_info; - ankerl::unordered_dense::map, void (*)()> read_fallbacks; - ankerl::unordered_dense::map, void (*)()> write_fallbacks; - ankerl::unordered_dense::map, void (*)()> exclusive_write_fallbacks; - ankerl::unordered_dense::set do_not_fastmem; + ::Common::unordered_map fastmem_patch_info; + ::Common::unordered_map, void (*)()> read_fallbacks; + ::Common::unordered_map, void (*)()> write_fallbacks; + ::Common::unordered_map, void (*)()> exclusive_write_fallbacks; + ::Common::unordered_set do_not_fastmem; boost::container::stable_vector shared_labels; const void* terminal_handler_pop_rsb_hint = nullptr; const void* terminal_handler_fast_dispatch_hint = nullptr; diff --git a/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64_memory.cpp b/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64_memory.cpp index 4b7054b4b2..8bb2fcfa59 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64_memory.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/a64_emit_x64_memory.cpp @@ -115,16 +115,16 @@ void A64EmitX64::GenMemory128Accessors() { void A64EmitX64::GenFastmemFallbacks() { const std::initializer_list idxes{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; const std::array, 4> read_callbacks{{ - {8, Devirtualize<&A64::UserCallbacks::MemoryRead8>(conf.callbacks)}, - {16, Devirtualize<&A64::UserCallbacks::MemoryRead16>(conf.callbacks)}, - {32, Devirtualize<&A64::UserCallbacks::MemoryRead32>(conf.callbacks)}, - {64, Devirtualize<&A64::UserCallbacks::MemoryRead64>(conf.callbacks)}, + {8, Devirtualize<&A64::UserCallbacks::MemoryRead>(conf.callbacks)}, + {16, Devirtualize<&A64::UserCallbacks::MemoryRead>(conf.callbacks)}, + {32, Devirtualize<&A64::UserCallbacks::MemoryRead>(conf.callbacks)}, + {64, Devirtualize<&A64::UserCallbacks::MemoryRead>(conf.callbacks)}, }}; const std::array, 4> write_callbacks{{ - {8, Devirtualize<&A64::UserCallbacks::MemoryWrite8>(conf.callbacks)}, - {16, Devirtualize<&A64::UserCallbacks::MemoryWrite16>(conf.callbacks)}, - {32, Devirtualize<&A64::UserCallbacks::MemoryWrite32>(conf.callbacks)}, - {64, Devirtualize<&A64::UserCallbacks::MemoryWrite64>(conf.callbacks)}, + {8, Devirtualize<&A64::UserCallbacks::MemoryWrite>(conf.callbacks)}, + {16, Devirtualize<&A64::UserCallbacks::MemoryWrite>(conf.callbacks)}, + {32, Devirtualize<&A64::UserCallbacks::MemoryWrite>(conf.callbacks)}, + {64, Devirtualize<&A64::UserCallbacks::MemoryWrite>(conf.callbacks)}, }}; const std::array, 4> exclusive_write_callbacks{{ {8, Devirtualize<&A64::UserCallbacks::MemoryWriteExclusive8>(conf.callbacks)}, @@ -204,12 +204,12 @@ void A64EmitX64::GenFastmemFallbacks() { code.align(); read_fallbacks[std::make_tuple(ordered, bitsize, vaddr_idx, value_idx)] = code.getCurr(); ABI_PushCallerSaveRegistersAndAdjustStackExcept(code, HostLocRegIdx(value_idx)); + // params = { this, vaddr, size } if (vaddr_idx != code.ABI_PARAM2.getIdx()) { code.mov(code.ABI_PARAM2, Xbyak::Reg64{vaddr_idx}); } - if (ordered) { - code.mfence(); - } + code.mov(code.ABI_PARAM3, bitsize / CHAR_BIT); + if (ordered) code.mfence(); callback.EmitCall(code); if (value_idx != code.ABI_RETURN.getIdx()) { code.mov(Xbyak::Reg64{value_idx}, code.ABI_RETURN); @@ -224,6 +224,7 @@ void A64EmitX64::GenFastmemFallbacks() { code.align(); write_fallbacks[std::make_tuple(ordered, bitsize, vaddr_idx, value_idx)] = code.getCurr(); ABI_PushCallerSaveRegistersAndAdjustStack(code); + // params = { this, vaddr, value, size } if (vaddr_idx == code.ABI_PARAM3.getIdx() && value_idx == code.ABI_PARAM2.getIdx()) { code.xchg(code.ABI_PARAM2, code.ABI_PARAM3); } else if (vaddr_idx == code.ABI_PARAM3.getIdx()) { @@ -240,10 +241,9 @@ void A64EmitX64::GenFastmemFallbacks() { } } code.ZeroExtendFrom(bitsize, code.ABI_PARAM3); + code.mov(code.ABI_PARAM4, bitsize / CHAR_BIT); callback.EmitCall(code); - if (ordered) { - code.mfence(); - } + if (ordered) code.mfence(); ABI_PopCallerSaveRegistersAndAdjustStack(code); code.ret(); PerfMapRegister(write_fallbacks[std::make_tuple(ordered, bitsize, vaddr_idx, value_idx)], code.getCurr(), fmt::format("a64_write_fallback_{}", bitsize)); @@ -286,19 +286,19 @@ void A64EmitX64::GenFastmemFallbacks() { #undef Axx void A64EmitX64::EmitA64ReadMemory8(A64EmitContext& ctx, IR::Inst* inst) { - EmitMemoryRead<8, &A64::UserCallbacks::MemoryRead8>(ctx, inst); + EmitMemoryRead<8, &A64::UserCallbacks::MemoryRead>(ctx, inst); } void A64EmitX64::EmitA64ReadMemory16(A64EmitContext& ctx, IR::Inst* inst) { - EmitMemoryRead<16, &A64::UserCallbacks::MemoryRead16>(ctx, inst); + EmitMemoryRead<16, &A64::UserCallbacks::MemoryRead>(ctx, inst); } void A64EmitX64::EmitA64ReadMemory32(A64EmitContext& ctx, IR::Inst* inst) { - EmitMemoryRead<32, &A64::UserCallbacks::MemoryRead32>(ctx, inst); + EmitMemoryRead<32, &A64::UserCallbacks::MemoryRead>(ctx, inst); } void A64EmitX64::EmitA64ReadMemory64(A64EmitContext& ctx, IR::Inst* inst) { - EmitMemoryRead<64, &A64::UserCallbacks::MemoryRead64>(ctx, inst); + EmitMemoryRead<64, &A64::UserCallbacks::MemoryRead>(ctx, inst); } void A64EmitX64::EmitA64ReadMemory128(A64EmitContext& ctx, IR::Inst* inst) { @@ -306,23 +306,23 @@ void A64EmitX64::EmitA64ReadMemory128(A64EmitContext& ctx, IR::Inst* inst) { } void A64EmitX64::EmitA64WriteMemory8(A64EmitContext& ctx, IR::Inst* inst) { - EmitMemoryWrite<8, &A64::UserCallbacks::MemoryWrite8>(ctx, inst); + EmitMemoryWrite<8, &A64::UserCallbacks::MemoryWrite>(ctx, inst); } void A64EmitX64::EmitA64WriteMemory16(A64EmitContext& ctx, IR::Inst* inst) { - EmitMemoryWrite<16, &A64::UserCallbacks::MemoryWrite16>(ctx, inst); + EmitMemoryWrite<16, &A64::UserCallbacks::MemoryWrite>(ctx, inst); } void A64EmitX64::EmitA64WriteMemory32(A64EmitContext& ctx, IR::Inst* inst) { - EmitMemoryWrite<32, &A64::UserCallbacks::MemoryWrite32>(ctx, inst); + EmitMemoryWrite<32, &A64::UserCallbacks::MemoryWrite>(ctx, inst); } void A64EmitX64::EmitA64WriteMemory64(A64EmitContext& ctx, IR::Inst* inst) { - EmitMemoryWrite<64, &A64::UserCallbacks::MemoryWrite64>(ctx, inst); + EmitMemoryWrite<64, &A64::UserCallbacks::MemoryWrite>(ctx, inst); } void A64EmitX64::EmitA64WriteMemory128(A64EmitContext& ctx, IR::Inst* inst) { - EmitMemoryWrite<128, &A64::UserCallbacks::MemoryWrite64>(ctx, inst); + EmitMemoryWrite<128, &A64::UserCallbacks::MemoryWrite>(ctx, inst); } void A64EmitX64::EmitA64ClearExclusive(A64EmitContext&, IR::Inst*) { @@ -331,33 +331,33 @@ void A64EmitX64::EmitA64ClearExclusive(A64EmitContext&, IR::Inst*) { void A64EmitX64::EmitA64ExclusiveReadMemory8(A64EmitContext& ctx, IR::Inst* inst) { if (conf.fastmem_exclusive_access) { - EmitExclusiveReadMemoryInline<8, &A64::UserCallbacks::MemoryRead8>(ctx, inst); + EmitExclusiveReadMemoryInline<8, &A64::UserCallbacks::MemoryRead>(ctx, inst); } else { - EmitExclusiveReadMemory<8, &A64::UserCallbacks::MemoryRead8>(ctx, inst); + EmitExclusiveReadMemory<8, &A64::UserCallbacks::MemoryRead>(ctx, inst); } } void A64EmitX64::EmitA64ExclusiveReadMemory16(A64EmitContext& ctx, IR::Inst* inst) { if (conf.fastmem_exclusive_access) { - EmitExclusiveReadMemoryInline<16, &A64::UserCallbacks::MemoryRead16>(ctx, inst); + EmitExclusiveReadMemoryInline<16, &A64::UserCallbacks::MemoryRead>(ctx, inst); } else { - EmitExclusiveReadMemory<16, &A64::UserCallbacks::MemoryRead16>(ctx, inst); + EmitExclusiveReadMemory<16, &A64::UserCallbacks::MemoryRead>(ctx, inst); } } void A64EmitX64::EmitA64ExclusiveReadMemory32(A64EmitContext& ctx, IR::Inst* inst) { if (conf.fastmem_exclusive_access) { - EmitExclusiveReadMemoryInline<32, &A64::UserCallbacks::MemoryRead32>(ctx, inst); + EmitExclusiveReadMemoryInline<32, &A64::UserCallbacks::MemoryRead>(ctx, inst); } else { - EmitExclusiveReadMemory<32, &A64::UserCallbacks::MemoryRead32>(ctx, inst); + EmitExclusiveReadMemory<32, &A64::UserCallbacks::MemoryRead>(ctx, inst); } } void A64EmitX64::EmitA64ExclusiveReadMemory64(A64EmitContext& ctx, IR::Inst* inst) { if (conf.fastmem_exclusive_access) { - EmitExclusiveReadMemoryInline<64, &A64::UserCallbacks::MemoryRead64>(ctx, inst); + EmitExclusiveReadMemoryInline<64, &A64::UserCallbacks::MemoryRead>(ctx, inst); } else { - EmitExclusiveReadMemory<64, &A64::UserCallbacks::MemoryRead64>(ctx, inst); + EmitExclusiveReadMemory<64, &A64::UserCallbacks::MemoryRead>(ctx, inst); } } diff --git a/src/dynarmic/src/dynarmic/backend/x64/a64_interface.cpp b/src/dynarmic/src/dynarmic/backend/x64/a64_interface.cpp index 9acaf3f9f6..bb072129e7 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/a64_interface.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/a64_interface.cpp @@ -33,9 +33,9 @@ using namespace Backend::X64; static RunCodeCallbacks GenRunCodeCallbacks(A64::UserCallbacks* cb, CodePtr (*LookupBlock)(void* lookup_block_arg), void* arg, const A64::UserConfig& conf) { return RunCodeCallbacks{ - std::make_unique(LookupBlock, reinterpret_cast(arg)), - std::make_unique(Devirtualize<&A64::UserCallbacks::AddTicks>(cb)), - std::make_unique(Devirtualize<&A64::UserCallbacks::GetTicksRemaining>(cb)), + ArgCallback(LookupBlock, reinterpret_cast(arg)), + ArgCallback(Devirtualize<&A64::UserCallbacks::AddTicks>(cb)), + ArgCallback(Devirtualize<&A64::UserCallbacks::GetTicksRemaining>(cb)), conf.enable_cycle_counting, }; } @@ -78,7 +78,7 @@ public: // TODO: Check code alignment const CodePtr current_code_ptr = [this] { // RSB optimization - const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A64JitState::RSBPtrMask; + const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A64JitState::RSB_PTR_MASK; if (jit_state.GetUniqueHash() == jit_state.rsb_location_descriptors[new_rsb_ptr]) { jit_state.rsb_ptr = new_rsb_ptr; return CodePtr(jit_state.rsb_codeptrs[new_rsb_ptr]); diff --git a/src/dynarmic/src/dynarmic/backend/x64/a64_jitstate.h b/src/dynarmic/src/dynarmic/backend/x64/a64_jitstate.h index 85ee1aa4b9..c3d8b04d1e 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/a64_jitstate.h +++ b/src/dynarmic/src/dynarmic/backend/x64/a64_jitstate.h @@ -29,18 +29,19 @@ struct A64JitState { A64JitState() { ResetRSB(); } + // Exclusive state stuff + static constexpr u64 RESERVATION_GRANULE_MASK = 0xFFFF'FFFF'FFFF'FFF0ull; + // Return stack buffer + static constexpr size_t RSB_SIZE = 8; // MUST be a power of 2. + static constexpr size_t RSB_PTR_MASK = RSB_SIZE - 1; + std::array reg{}; u64 sp = 0; u64 pc = 0; - u32 cpsr_nzcv = 0; - - u32 GetPstate() const { - return NZCV::FromX64(cpsr_nzcv); - } - void SetPstate(u32 new_pstate) { - cpsr_nzcv = NZCV::ToX64(new_pstate); - } + u32 fpsr_exc = 0; + u32 fpsr_qc = 0; + u32 fpcr = 0; alignas(16) std::array vec{}; // Extension registers. @@ -50,29 +51,31 @@ struct A64JitState { volatile u32 halt_reason = 0; // Exclusive state - static constexpr u64 RESERVATION_GRANULE_MASK = 0xFFFF'FFFF'FFFF'FFF0ull; u8 exclusive_state = 0; - - static constexpr size_t RSBSize = 8; // MUST be a power of 2. - static constexpr size_t RSBPtrMask = RSBSize - 1; u32 rsb_ptr = 0; - std::array rsb_location_descriptors; - std::array rsb_codeptrs; + std::array rsb_location_descriptors; + std::array rsb_codeptrs; + + u32 GetPstate() const { + return NZCV::FromX64(cpsr_nzcv); + } + + void SetPstate(u32 new_pstate) { + cpsr_nzcv = NZCV::ToX64(new_pstate); + } + void ResetRSB() { rsb_location_descriptors.fill(0xFFFFFFFFFFFFFFFFull); rsb_codeptrs.fill(0); } - u32 fpsr_exc = 0; - u32 fpsr_qc = 0; - u32 fpcr = 0; u32 GetFpcr() const; u32 GetFpsr() const; void SetFpcr(u32 value); void SetFpsr(u32 value); u64 GetUniqueHash() const noexcept { - const u64 fpcr_u64 = static_cast(fpcr & A64::LocationDescriptor::fpcr_mask) << A64::LocationDescriptor::fpcr_shift; + const u64 fpcr_u64 = u64(fpcr & A64::LocationDescriptor::fpcr_mask) << A64::LocationDescriptor::fpcr_shift; const u64 pc_u64 = pc & A64::LocationDescriptor::pc_mask; return pc_u64 | fpcr_u64; } diff --git a/src/dynarmic/src/dynarmic/backend/x64/abi.cpp b/src/dynarmic/src/dynarmic/backend/x64/abi.cpp index 5d2ee735f3..59ecc974f7 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/abi.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/abi.cpp @@ -26,7 +26,7 @@ struct FrameInfo { }; static_assert(ABI_SHADOW_SPACE <= 32); -static FrameInfo CalculateFrameInfo(const size_t num_gprs, const size_t num_xmms, size_t frame_size) { +static FrameInfo CalculateFrameInfo(const size_t num_gprs, const size_t num_xmms, size_t frame_size) noexcept { // We are initially 8 byte aligned because the return value is pushed onto an aligned stack after a call. const size_t rsp_alignment = (num_gprs % 2 == 0) ? 8 : 0; const size_t total_xmm_size = num_xmms * XMM_SIZE; @@ -40,7 +40,7 @@ static FrameInfo CalculateFrameInfo(const size_t num_gprs, const size_t num_xmms }; } -void ABI_PushRegistersAndAdjustStack(BlockOfCode& code, const size_t frame_size, std::bitset<32> const& regs) { +static void ABI_PushRegistersAndAdjustStack(BlockOfCode& code, const size_t frame_size, std::bitset<32> regs) noexcept { using namespace Xbyak::util; const size_t num_gprs = (ABI_ALL_GPRS & regs).count(); @@ -65,7 +65,7 @@ void ABI_PushRegistersAndAdjustStack(BlockOfCode& code, const size_t frame_size, } } -void ABI_PopRegistersAndAdjustStack(BlockOfCode& code, const size_t frame_size, std::bitset<32> const& regs) { +static void ABI_PopRegistersAndAdjustStack(BlockOfCode& code, const size_t frame_size, std::bitset<32> regs) noexcept { using namespace Xbyak::util; const size_t num_gprs = (ABI_ALL_GPRS & regs).count(); @@ -107,13 +107,13 @@ void ABI_PopCallerSaveRegistersAndAdjustStack(BlockOfCode& code, const std::size // Windows ABI registers are not in the same allocation algorithm as unix's void ABI_PushCallerSaveRegistersAndAdjustStackExcept(BlockOfCode& code, const HostLoc exception) { - std::bitset<32> regs = ABI_ALL_CALLER_SAVE; + auto regs = ABI_ALL_CALLER_SAVE; regs.reset(size_t(exception)); ABI_PushRegistersAndAdjustStack(code, 0, regs); } void ABI_PopCallerSaveRegistersAndAdjustStackExcept(BlockOfCode& code, const HostLoc exception) { - std::bitset<32> regs = ABI_ALL_CALLER_SAVE; + auto regs = ABI_ALL_CALLER_SAVE; regs.reset(size_t(exception)); ABI_PopRegistersAndAdjustStack(code, 0, regs); } 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 f41bea6c83..384d6203a4 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/block_of_code.cpp @@ -61,73 +61,6 @@ namespace { constexpr size_t CONSTANT_POOL_SIZE = 2 * 1024 * 1024; constexpr size_t PRELUDE_COMMIT_SIZE = 16 * 1024 * 1024; -class CustomXbyakAllocator : public Xbyak::Allocator { -public: -#ifdef _WIN32 - uint8_t* alloc(size_t size) override { - void* p = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE); - if (p == nullptr) { - using Xbyak::Error; - XBYAK_THROW(Xbyak::ERR_CANT_ALLOC); - } - return static_cast(p); - } - - void free(uint8_t* p) override { - VirtualFree(static_cast(p), 0, MEM_RELEASE); - } - - bool useProtect() const override { return false; } -#else - static constexpr size_t DYNARMIC_PAGE_SIZE = 4096; - - // Can't subclass Xbyak::MmapAllocator because it is not a pure interface - // and doesn't expose its construtor - uint8_t* alloc(size_t size) override { - // Waste a page to store the size - size += DYNARMIC_PAGE_SIZE; - - int mode = MAP_PRIVATE; -#if defined(MAP_ANONYMOUS) - mode |= MAP_ANONYMOUS; -#elif defined(MAP_ANON) - mode |= MAP_ANON; -#else -# error "not supported" -#endif -#ifdef MAP_JIT - mode |= MAP_JIT; -#endif - int prot = PROT_READ | PROT_WRITE; -#ifdef PROT_MPROTECT - // https://man.netbsd.org/mprotect.2 specifies that an mprotect() that is LESS - // restrictive than the original mapping MUST fail - prot |= PROT_MPROTECT(PROT_READ) | PROT_MPROTECT(PROT_WRITE) | PROT_MPROTECT(PROT_EXEC); -#endif - void* p = mmap(nullptr, size, prot, mode, -1, 0); - if (p == MAP_FAILED) { - using Xbyak::Error; - XBYAK_THROW(Xbyak::ERR_CANT_ALLOC); - } - std::memcpy(p, &size, sizeof(size_t)); - return static_cast(p) + DYNARMIC_PAGE_SIZE; - } - - void free(uint8_t* p) override { - size_t size; - std::memcpy(&size, p - DYNARMIC_PAGE_SIZE, sizeof(size_t)); - munmap(p - DYNARMIC_PAGE_SIZE, size); - } - -# ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT - bool useProtect() const override { return false; } -# endif -#endif -}; - -// This is threadsafe as Xbyak::Allocator does not contain any state; it is a pure interface. -CustomXbyakAllocator s_allocator; - #ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT void ProtectMemory(const void* base, size_t size, bool is_executable) { # ifdef _WIN32 @@ -143,60 +76,34 @@ void ProtectMemory(const void* base, size_t size, bool is_executable) { } #endif -HostFeature GetHostFeatures() { - HostFeature features = {}; - +static const HostFeature features = []() { + HostFeature f{}; #ifdef DYNARMIC_ENABLE_CPU_FEATURE_DETECTION using Cpu = Xbyak::util::Cpu; - Xbyak::util::Cpu cpu_info; - - if (cpu_info.has(Cpu::tSSSE3)) - features |= HostFeature::SSSE3; - if (cpu_info.has(Cpu::tSSE41)) - features |= HostFeature::SSE41; - if (cpu_info.has(Cpu::tSSE42)) - features |= HostFeature::SSE42; - if (cpu_info.has(Cpu::tAVX)) - features |= HostFeature::AVX; - if (cpu_info.has(Cpu::tAVX2)) - features |= HostFeature::AVX2; - if (cpu_info.has(Cpu::tAVX512F)) - features |= HostFeature::AVX512F; - if (cpu_info.has(Cpu::tAVX512CD)) - features |= HostFeature::AVX512CD; - if (cpu_info.has(Cpu::tAVX512VL)) - features |= HostFeature::AVX512VL; - if (cpu_info.has(Cpu::tAVX512BW)) - features |= HostFeature::AVX512BW; - if (cpu_info.has(Cpu::tAVX512DQ)) - features |= HostFeature::AVX512DQ; - if (cpu_info.has(Cpu::tAVX512_BITALG)) - features |= HostFeature::AVX512BITALG; - if (cpu_info.has(Cpu::tAVX512VBMI)) - features |= HostFeature::AVX512VBMI; - if (cpu_info.has(Cpu::tPCLMULQDQ)) - features |= HostFeature::PCLMULQDQ; - if (cpu_info.has(Cpu::tF16C)) - features |= HostFeature::F16C; - if (cpu_info.has(Cpu::tFMA)) - features |= HostFeature::FMA; - if (cpu_info.has(Cpu::tAESNI)) - features |= HostFeature::AES; - if (cpu_info.has(Cpu::tSHA)) - features |= HostFeature::SHA; - if (cpu_info.has(Cpu::tPOPCNT)) - features |= HostFeature::POPCNT; - if (cpu_info.has(Cpu::tBMI1)) - features |= HostFeature::BMI1; - if (cpu_info.has(Cpu::tBMI2)) - features |= HostFeature::BMI2; - if (cpu_info.has(Cpu::tLZCNT)) - features |= HostFeature::LZCNT; - if (cpu_info.has(Cpu::tGFNI)) - features |= HostFeature::GFNI; - if (cpu_info.has(Cpu::tWAITPKG)) - features |= HostFeature::WAITPKG; - + Xbyak::util::Cpu cpu_info{}; + if (cpu_info.has(Cpu::tSSSE3)) f |= HostFeature::SSSE3; + if (cpu_info.has(Cpu::tSSE41)) f |= HostFeature::SSE41; + if (cpu_info.has(Cpu::tSSE42)) f |= HostFeature::SSE42; + if (cpu_info.has(Cpu::tAVX)) f |= HostFeature::AVX; + if (cpu_info.has(Cpu::tAVX2)) f |= HostFeature::AVX2; + if (cpu_info.has(Cpu::tAVX512F)) f |= HostFeature::AVX512F; + if (cpu_info.has(Cpu::tAVX512CD)) f |= HostFeature::AVX512CD; + if (cpu_info.has(Cpu::tAVX512VL)) f |= HostFeature::AVX512VL; + if (cpu_info.has(Cpu::tAVX512BW)) f |= HostFeature::AVX512BW; + if (cpu_info.has(Cpu::tAVX512DQ)) f |= HostFeature::AVX512DQ; + if (cpu_info.has(Cpu::tAVX512_BITALG)) f |= HostFeature::AVX512BITALG; + if (cpu_info.has(Cpu::tAVX512VBMI)) f |= HostFeature::AVX512VBMI; + if (cpu_info.has(Cpu::tPCLMULQDQ)) f |= HostFeature::PCLMULQDQ; + if (cpu_info.has(Cpu::tF16C)) f |= HostFeature::F16C; + if (cpu_info.has(Cpu::tFMA)) f |= HostFeature::FMA; + if (cpu_info.has(Cpu::tAESNI)) f |= HostFeature::AES; + if (cpu_info.has(Cpu::tSHA)) f |= HostFeature::SHA; + if (cpu_info.has(Cpu::tPOPCNT)) f |= HostFeature::POPCNT; + if (cpu_info.has(Cpu::tBMI1)) f |= HostFeature::BMI1; + if (cpu_info.has(Cpu::tBMI2)) f |= HostFeature::BMI2; + if (cpu_info.has(Cpu::tLZCNT)) f |= HostFeature::LZCNT; + if (cpu_info.has(Cpu::tGFNI)) f |= HostFeature::GFNI; + if (cpu_info.has(Cpu::tWAITPKG)) f |= HostFeature::WAITPKG; if (cpu_info.has(Cpu::tBMI2)) { // BMI2 instructions such as pdep and pext have been very slow up until Zen 3. // Check for Zen 3 or newer by its family (0x19). @@ -208,13 +115,15 @@ HostFeature GetHostFeatures() { const u32 family_extended = mcl::bit::get_bits<20, 27>(data[0]); const u32 family = family_base + family_extended; if (family >= 0x19) - features |= HostFeature::FastBMI2; + f |= HostFeature::FastBMI2; } else { - features |= HostFeature::FastBMI2; + f |= HostFeature::FastBMI2; } } + return f; #endif - +}(); +HostFeature GetHostFeatures() { return features; } @@ -233,23 +142,27 @@ 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, default_cg_mode, &s_allocator) - , cb(std::move(cb)) - , jsi(jsi) - , constant_pool(*this, CONSTANT_POOL_SIZE) - , host_features(GetHostFeatures()) { + : Xbyak::CodeGenerator(total_code_size +#ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT + , Xbyak::DontSetProtectRWE +#else + , nullptr //Allow RWE +#endif + , nullptr) + , constant_pool(*this, CONSTANT_POOL_SIZE) + , jsi(jsi) + , cb(std::move(cb)) +{ EnableWriting(); EnsureMemoryCommitted(PRELUDE_COMMIT_SIZE); GenRunCode(rcp); } +bool BlockOfCode::HasHostFeature(HostFeature feature) const noexcept { + return (GetHostFeatures() & feature) == feature; +} + void BlockOfCode::PreludeComplete() { prelude_complete = true; code_begin = getCurr(); @@ -341,7 +254,7 @@ void BlockOfCode::GenRunCode(std::function rcp) { mov(rbx, ABI_PARAM2); // save temporarily in non-volatile register if (cb.enable_cycle_counting) { - cb.GetTicksRemaining->EmitCall(*this); + cb.GetTicksRemaining.EmitCall(*this); mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)], ABI_RETURN); mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], ABI_RETURN); } @@ -388,7 +301,7 @@ void BlockOfCode::GenRunCode(std::function rcp) { cmp(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], 0); jng(return_to_caller); } - cb.LookupBlock->EmitCall(*this); + cb.LookupBlock.EmitCall(*this); jmp(ABI_RETURN); align(); @@ -401,7 +314,7 @@ void BlockOfCode::GenRunCode(std::function rcp) { jng(return_to_caller_mxcsr_already_exited); } SwitchMxcsrOnEntry(); - cb.LookupBlock->EmitCall(*this); + cb.LookupBlock.EmitCall(*this); jmp(ABI_RETURN); align(); @@ -415,7 +328,7 @@ void BlockOfCode::GenRunCode(std::function rcp) { L(return_to_caller_mxcsr_already_exited); if (cb.enable_cycle_counting) { - cb.AddTicks->EmitCall(*this, [this](RegList param) { + cb.AddTicks.EmitCall(*this, [this](RegList param) { mov(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)]); sub(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)]); }); @@ -455,18 +368,18 @@ void BlockOfCode::UpdateTicks() { return; } - cb.AddTicks->EmitCall(*this, [this](RegList param) { + cb.AddTicks.EmitCall(*this, [this](RegList param) { mov(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)]); sub(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)]); }); - cb.GetTicksRemaining->EmitCall(*this); + cb.GetTicksRemaining.EmitCall(*this); mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)], ABI_RETURN); mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], ABI_RETURN); } void BlockOfCode::LookupBlock() { - cb.LookupBlock->EmitCall(*this); + cb.LookupBlock.EmitCall(*this); } void BlockOfCode::LoadRequiredFlagsForCondFromRax(IR::Cond cond) { @@ -520,7 +433,7 @@ void BlockOfCode::LoadRequiredFlagsForCondFromRax(IR::Cond cond) { } Xbyak::Address BlockOfCode::Const(const Xbyak::AddressFrame& frame, u64 lower, u64 upper) { - return constant_pool.GetConstant(frame, lower, upper); + return constant_pool.GetConstant(*this, frame, lower, upper); } CodePtr BlockOfCode::GetCodeBegin() const { diff --git a/src/dynarmic/src/dynarmic/backend/x64/block_of_code.h b/src/dynarmic/src/dynarmic/backend/x64/block_of_code.h index 857b1a4484..20012a070f 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/block_of_code.h +++ b/src/dynarmic/src/dynarmic/backend/x64/block_of_code.h @@ -13,8 +13,9 @@ #include #include -#include "dynarmic/mcl/bit.hpp" #include "common/common_types.h" +#include "common/x64/xbyak.h" +#include "dynarmic/mcl/bit.hpp" #include "dynarmic/backend/x64/xbyak.h" #include "dynarmic/backend/x64/abi.h" #include "dynarmic/backend/x64/callback.h" @@ -30,9 +31,9 @@ namespace Dynarmic::Backend::X64 { using CodePtr = const void*; struct RunCodeCallbacks { - std::unique_ptr LookupBlock; - std::unique_ptr AddTicks; - std::unique_ptr GetTicksRemaining; + ArgCallback LookupBlock; + ArgCallback AddTicks; + ArgCallback GetTicksRemaining; bool enable_cycle_counting; }; @@ -82,28 +83,17 @@ public: /// Code emitter: Load required flags for conditional cond from rax into host rflags void LoadRequiredFlagsForCondFromRax(IR::Cond cond); - /// Code emitter: Calls the function - template - void CallFunction(FunctionPointer fn) { - static_assert(std::is_pointer_v && std::is_function_v>, - "Supplied type must be a pointer to a function"); - - const u64 address = reinterpret_cast(fn); - const u64 distance = address - (getCurr() + 5); - - if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) { - // Far call - mov(rax, address); - call(rax); - } else { - call(fn); - } + /// @brief Code emitter: Calls the function + template + requires std::is_pointer_v && std::is_function_v> + void CallFunction(F fn) { + ::Common::X64::CallFarFunction(*this, fn); } - /// Code emitter: Calls the lambda. Lambda must not have any captures. + /// @brief Code emitter: Calls the lambda. Lambda must not have any captures. template void CallLambda(Lambda l) { - CallFunction(Common::FptrCast(l)); + ::Common::X64::CallFarFunction(*this, Common::FptrCast(l)); } void ZeroExtendFrom(size_t bitsize, Xbyak::Reg64 reg) { @@ -176,27 +166,24 @@ public: JitStateInfo GetJitStateInfo() const { return jsi; } - bool HasHostFeature(HostFeature feature) const { - return (host_features & feature) == feature; - } + bool HasHostFeature(HostFeature feature) const noexcept; private: using RunCodeFuncType = HaltReason (*)(void*, CodePtr); static constexpr size_t MXCSR_ALREADY_EXITED = 1 << 0; static constexpr size_t FORCE_RETURN = 1 << 1; - RunCodeCallbacks cb; + ConstantPool constant_pool; JitStateInfo jsi; + std::array return_from_run_code; + RunCodeFuncType run_code = nullptr; + RunCodeFuncType step_code = nullptr; + RunCodeCallbacks cb; CodePtr code_begin = nullptr; #ifdef _WIN32 size_t committed_size = 0; #endif - ConstantPool constant_pool; - RunCodeFuncType run_code = nullptr; - RunCodeFuncType step_code = nullptr; - std::array return_from_run_code; bool prelude_complete = false; - const HostFeature host_features; void GenRunCode(std::function rcp); }; diff --git a/src/dynarmic/src/dynarmic/backend/x64/constant_pool.cpp b/src/dynarmic/src/dynarmic/backend/x64/constant_pool.cpp index 127149a29e..8782ab0c73 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/constant_pool.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/constant_pool.cpp @@ -6,36 +6,35 @@ * SPDX-License-Identifier: 0BSD */ -#include "dynarmic/backend/x64/constant_pool.h" - #include #include "common/assert.h" #include "dynarmic/backend/x64/block_of_code.h" +#include "dynarmic/backend/x64/constant_pool.h" namespace Dynarmic::Backend::X64 { ConstantPool::ConstantPool(BlockOfCode& code, size_t size) - : code(code), insertion_point(0) { + : insertion_point(0) +{ code.EnsureMemoryCommitted(align_size + size); code.int3(); code.align(align_size); - pool = std::span( - reinterpret_cast(code.AllocateFromCodeSpace(size)), size / align_size); + pool = std::span(reinterpret_cast(code.AllocateFromCodeSpace(size)), size / align_size); } -Xbyak::Address ConstantPool::GetConstant(const Xbyak::AddressFrame& frame, u64 lower, u64 upper) { +Xbyak::Address ConstantPool::GetConstant(BlockOfCode& code, const Xbyak::AddressFrame& frame, u64 lower, u64 upper) { const auto constant = ConstantT(lower, upper); - auto iter = constant_info.find(constant); - if (iter == constant_info.end()) { + auto it = constant_info.find(constant); + if (it == constant_info.end()) { ASSERT(insertion_point < pool.size()); ConstantT& target_constant = pool[insertion_point]; target_constant = constant; - iter = constant_info.insert({constant, &target_constant}).first; + it = constant_info.insert({constant, &target_constant}).first; ++insertion_point; } - return frame[code.rip + iter->second]; + return frame[code.rip + it->second]; } } // namespace Dynarmic::Backend::X64 diff --git a/src/dynarmic/src/dynarmic/backend/x64/constant_pool.h b/src/dynarmic/src/dynarmic/backend/x64/constant_pool.h index 79e57fc78c..d3bad95d70 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/constant_pool.h +++ b/src/dynarmic/src/dynarmic/backend/x64/constant_pool.h @@ -14,7 +14,7 @@ #include #include "common/common_types.h" -#include +#include "common/container/unordered_map.h" #include "dynarmic/backend/x64/xbyak.h" namespace Dynarmic::Backend::X64 { @@ -29,7 +29,7 @@ class ConstantPool final { public: ConstantPool(BlockOfCode& code, size_t size); - Xbyak::Address GetConstant(const Xbyak::AddressFrame& frame, u64 lower, u64 upper = 0); + Xbyak::Address GetConstant(BlockOfCode& code, const Xbyak::AddressFrame& frame, u64 lower, u64 upper = 0); private: static constexpr size_t align_size = 16; // bytes @@ -43,9 +43,8 @@ private: } }; - ankerl::unordered_dense::map constant_info; + ::Common::unordered_map constant_info; std::span pool; - BlockOfCode& code; std::size_t insertion_point; }; diff --git a/src/dynarmic/src/dynarmic/backend/x64/devirtualize.h b/src/dynarmic/src/dynarmic/backend/x64/devirtualize.h index e6efe010ff..9b618fe71b 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/devirtualize.h +++ b/src/dynarmic/src/dynarmic/backend/x64/devirtualize.h @@ -75,7 +75,7 @@ ArgCallback DevirtualizeItanium(mcl::class_type* this_) { template ArgCallback Devirtualize(mcl::class_type* this_) { -#if defined(__APPLE__) || defined(linux) || defined(__linux) || defined(__linux__) +#if defined(__APPLE__) || defined(__linux__) return DevirtualizeItanium(this_); #elif defined(__MINGW64__) return DevirtualizeItanium(this_); diff --git a/src/dynarmic/src/dynarmic/backend/x64/emit_x64.cpp b/src/dynarmic/src/dynarmic/backend/x64/emit_x64.cpp index 3d28365b1c..b9df3987ca 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/emit_x64.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/emit_x64.cpp @@ -14,7 +14,8 @@ #include #include "dynarmic/mcl/bit.hpp" #include "common/common_types.h" -#include +#include "common/container/unordered_map.h" +#include "common/container/unordered_set.h" #include "dynarmic/backend/x64/block_of_code.h" #include "dynarmic/backend/x64/nzcv_util.h" @@ -396,7 +397,7 @@ void EmitX64::ClearCache() { PerfMapClear(); } -void EmitX64::InvalidateBasicBlocks(const ankerl::unordered_dense::set& locations) { +void EmitX64::InvalidateBasicBlocks(const ::Common::unordered_set& locations) { code.EnableWriting(); for (const auto& descriptor : locations) { if (auto const it = block_descriptors.find(descriptor); it != block_descriptors.end()) { diff --git a/src/dynarmic/src/dynarmic/backend/x64/emit_x64.h b/src/dynarmic/src/dynarmic/backend/x64/emit_x64.h index 619945e19a..5e540e5f70 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/emit_x64.h +++ b/src/dynarmic/src/dynarmic/backend/x64/emit_x64.h @@ -16,7 +16,8 @@ #include #include -#include +#include "common/container/unordered_map.h" +#include "common/container/unordered_set.h" #include #include @@ -88,7 +89,7 @@ public: virtual void ClearCache(); /// Invalidates a selection of basic blocks. - void InvalidateBasicBlocks(const ankerl::unordered_dense::set& locations); + void InvalidateBasicBlocks(const ::Common::unordered_set& locations); //protected: // Microinstruction emitters @@ -111,7 +112,8 @@ public: #ifndef NDEBUG void EmitVerboseDebuggingOutput(RegAlloc& reg_alloc); #endif - virtual void EmitTerminal(IR::Terminal terminal, IR::LocationDescriptor initial_location, bool is_single_step) noexcept = 0; + virtual bool EmitLeafTerminal(IR::Term::LeafTerminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step) noexcept = 0; + virtual bool EmitTerminal(IR::Term::Terminal const& terminal, IR::LocationDescriptor initial_location, bool is_single_step) noexcept = 0; // Patching struct PatchInformation { @@ -130,8 +132,8 @@ public: // State BlockOfCode& code; ExceptionHandler exception_handler; - ankerl::unordered_dense::map block_descriptors; - ankerl::unordered_dense::map patch_information; + ::Common::unordered_map block_descriptors; + ::Common::unordered_map patch_information; // We need materialized protected members friend class A64EmitX64; diff --git a/src/dynarmic/src/dynarmic/backend/x64/emit_x64_floating_point.cpp b/src/dynarmic/src/dynarmic/backend/x64/emit_x64_floating_point.cpp index 827600c7c2..756a6f0a36 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/emit_x64_floating_point.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/emit_x64_floating_point.cpp @@ -1532,7 +1532,7 @@ void EmitX64::EmitFPSingleToHalf(EmitContext& ctx, IR::Inst* inst) { if (ctx.FPCR().DN()) { ForceToDefaultNaN<32>(code, result); } - code.vcvtps2ph(result, result, static_cast(*round_imm)); + code.vcvtps2ph(result, result, u8(*round_imm)); ctx.reg_alloc.DefineValue(code, inst, result); return; @@ -1540,7 +1540,7 @@ void EmitX64::EmitFPSingleToHalf(EmitContext& ctx, IR::Inst* inst) { ctx.reg_alloc.HostCall(code, inst, args[0]); code.mov(code.ABI_PARAM2.cvt32(), ctx.FPCR().Value()); - code.mov(code.ABI_PARAM3.cvt32(), static_cast(rounding_mode)); + code.mov(code.ABI_PARAM3.cvt32(), u32(rounding_mode)); code.lea(code.ABI_PARAM4, code.ptr[code.ABI_JIT_PTR + code.GetJitStateInfo().offsetof_fpsr_exc]); code.CallFunction(&FP::FPConvert); } diff --git a/src/dynarmic/src/dynarmic/backend/x64/emit_x64_memory.cpp.inc b/src/dynarmic/src/dynarmic/backend/x64/emit_x64_memory.cpp.inc index 4fa14d504b..8bb8be3d5b 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/emit_x64_memory.cpp.inc +++ b/src/dynarmic/src/dynarmic/backend/x64/emit_x64_memory.cpp.inc @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + /* This file is part of the dynarmic project. * Copyright (c) 2022 MerryMage * SPDX-License-Identifier: 0BSD @@ -56,16 +59,13 @@ void AxxEmitX64::EmitMemoryRead(AxxEmitContext& ctx, IR::Inst* inst) { // Neither fastmem nor page table: Use callbacks if constexpr (bitsize == 128) { ctx.reg_alloc.HostCall(code, nullptr, {}, args[1]); - if (ordered) { - code.mfence(); - } + if (ordered) code.mfence(); code.CallFunction(memory_read_128); ctx.reg_alloc.DefineValue(code, inst, xmm1); } else { ctx.reg_alloc.HostCall(code, inst, {}, args[1]); - if (ordered) { - code.mfence(); - } + code.mov(code.ABI_PARAM3.cvt32(), bitsize / CHAR_BIT); + if (ordered) code.mfence(); Devirtualize(conf.callbacks).EmitCall(code); code.ZeroExtendFrom(bitsize, code.ABI_RETURN); } @@ -148,12 +148,12 @@ void AxxEmitX64::EmitMemoryWrite(AxxEmitContext& ctx, IR::Inst* inst) { ctx.reg_alloc.HostCall(code, nullptr); code.CallFunction(memory_write_128); } else { + // { this, vaddr, value, size } ctx.reg_alloc.HostCall(code, nullptr, {}, args[1], args[2]); + code.mov(code.ABI_PARAM4.cvt32(), bitsize / CHAR_BIT); Devirtualize(conf.callbacks).EmitCall(code); } - if (ordered) { - code.mfence(); - } + if (ordered) code.mfence(); EmitCheckMemoryAbort(ctx, inst); return; } @@ -230,12 +230,11 @@ void AxxEmitX64::EmitExclusiveReadMemory(AxxEmitContext& ctx, IR::Inst* inst) { if (ordered) { code.mfence(); } - code.CallLambda( - [](AxxUserConfig& conf, Axx::VAddr vaddr) -> T { - return conf.global_monitor->ReadAndMark(conf.processor_id, vaddr, [&]() -> T { - return (conf.callbacks->*callback)(vaddr); - }); + code.CallLambda([](AxxUserConfig& conf, Axx::VAddr vaddr) -> T { + return conf.global_monitor->ReadAndMark(conf.processor_id, vaddr, [&]() -> T { + return (conf.callbacks->*callback)(vaddr, bitsize / CHAR_BIT); }); + }); code.ZeroExtendFrom(bitsize, code.ABI_RETURN); } else { const Xbyak::Xmm result = ctx.reg_alloc.ScratchXmm(code); @@ -250,12 +249,11 @@ void AxxEmitX64::EmitExclusiveReadMemory(AxxEmitContext& ctx, IR::Inst* inst) { if (ordered) { code.mfence(); } - code.CallLambda( - [](AxxUserConfig& conf, Axx::VAddr vaddr, Vector& ret) { - ret = conf.global_monitor->ReadAndMark(conf.processor_id, vaddr, [&]() -> Vector { - return (conf.callbacks->*callback)(vaddr); - }); + code.CallLambda([](AxxUserConfig& conf, Axx::VAddr vaddr, Vector& ret) { + ret = conf.global_monitor->ReadAndMark(conf.processor_id, vaddr, [&]() -> Vector { + return (conf.callbacks->*callback)(vaddr); }); + }); code.movups(result, xword[rsp + ABI_SHADOW_SPACE]); ctx.reg_alloc.ReleaseStackSpace(code, 16 + ABI_SHADOW_SPACE); diff --git a/src/dynarmic/src/dynarmic/backend/x64/emit_x64_memory.h b/src/dynarmic/src/dynarmic/backend/x64/emit_x64_memory.h index 3ac078f1d7..d072b9963d 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/emit_x64_memory.h +++ b/src/dynarmic/src/dynarmic/backend/x64/emit_x64_memory.h @@ -9,6 +9,7 @@ #pragma once #include +#include #include "dynarmic/backend/x64/xbyak.h" #include "dynarmic/backend/x64/a32_emit_x64.h" @@ -78,27 +79,46 @@ Xbyak::RegExp EmitVAddrLookup(BlockOfCode& code, EmitContext& ctx, size_t bitsiz template<> [[maybe_unused]] Xbyak::RegExp EmitVAddrLookup(BlockOfCode& code, A32EmitContext& ctx, size_t bitsize, Xbyak::Label& abort, Xbyak::Reg64 vaddr) { const Xbyak::Reg64 page = ctx.reg_alloc.ScratchGpr(code); - const Xbyak::Reg32 tmp = ctx.conf.absolute_offset_page_table ? page.cvt32() : ctx.reg_alloc.ScratchGpr(code).cvt32(); + const Xbyak::Reg64 tmp = ctx.conf.absolute_offset_page_table && ctx.conf.page_table_pointer_mask == 0 ? page : ctx.reg_alloc.ScratchGpr(code); - EmitDetectMisalignedVAddr(code, ctx, bitsize, abort, vaddr, tmp.cvt64()); + EmitDetectMisalignedVAddr(code, ctx, bitsize, abort, vaddr, tmp); - // TODO: This code assumes vaddr has been zext from 32-bits to 64-bits. - - code.mov(tmp, vaddr.cvt32()); + code.mov(tmp, vaddr); code.shr(tmp, int(page_table_const_bits)); - code.shl(tmp, int(ctx.conf.page_table_log2_stride)); - code.mov(page, qword[r14 + tmp.cvt64()]); - if (ctx.conf.page_table_pointer_mask_bits == 0) { - code.test(page, page); + + if (ctx.conf.page_table_log2_stride > 3) { + code.shl(tmp, int(ctx.conf.page_table_log2_stride)); + code.mov(page, qword[r14 + tmp.cvt64()]); } else { - code.and_(page, ~u32(0) << ctx.conf.page_table_pointer_mask_bits); + code.mov(page, qword[r14 + tmp.cvt64() * int(1 << ctx.conf.page_table_log2_stride)]); } + + // check for marked bit, use as unmapped if marked + if (ctx.conf.page_table_marked_bit) { + code.bt(page, *ctx.conf.page_table_marked_bit); + code.jc(abort, code.T_NEAR); + } + // mask away attributes + if (ctx.conf.page_table_pointer_mask == 0) { + code.test(page, page); + } else if (std::in_range(ctx.conf.page_table_pointer_mask)) { + code.and_(page, ctx.conf.page_table_pointer_mask); + } else { + code.mov(tmp, ctx.conf.page_table_pointer_mask); + code.and_(page, tmp); + } + // check for sign bit, apply sign extension as needed + if (ctx.conf.page_table_sign_extension) { + code.shl(page, 63 - int(*ctx.conf.page_table_sign_extension)); + code.sar(page, 63 - int(*ctx.conf.page_table_sign_extension)); + } + code.jz(abort, code.T_NEAR); if (ctx.conf.absolute_offset_page_table) { return page + vaddr; } - code.mov(tmp, vaddr.cvt32()); - code.and_(tmp, static_cast(page_table_const_mask)); + code.mov(tmp, vaddr); + code.and_(tmp, u32(page_table_const_mask)); return page + tmp.cvt64(); } @@ -108,7 +128,7 @@ template<> const size_t unused_top_bits = 64 - ctx.conf.page_table_address_space_bits; const Xbyak::Reg64 page = ctx.reg_alloc.ScratchGpr(code); - const Xbyak::Reg64 tmp = ctx.conf.absolute_offset_page_table ? page : ctx.reg_alloc.ScratchGpr(code); + const Xbyak::Reg64 tmp = ctx.conf.absolute_offset_page_table && ctx.conf.page_table_pointer_mask == 0 ? page : ctx.reg_alloc.ScratchGpr(code); EmitDetectMisalignedVAddr(code, ctx, bitsize, abort, vaddr, tmp); @@ -141,13 +161,33 @@ template<> code.jnz(abort, code.T_NEAR); } - code.shl(tmp, int(ctx.conf.page_table_log2_stride)); - code.mov(page, qword[r14 + tmp]); - if (ctx.conf.page_table_pointer_mask_bits == 0) { - code.test(page, page); + if (ctx.conf.page_table_log2_stride > 3) { + code.shl(tmp, int(ctx.conf.page_table_log2_stride)); + code.mov(page, qword[r14 + tmp.cvt64()]); } else { - code.and_(page, ~u32(0) << ctx.conf.page_table_pointer_mask_bits); + code.mov(page, qword[r14 + tmp.cvt64() * int(1 << ctx.conf.page_table_log2_stride)]); } + + // check for marked bit, use as unmapped if marked + if (ctx.conf.page_table_marked_bit) { + code.bt(page, *ctx.conf.page_table_marked_bit); + code.jc(abort, code.T_NEAR); + } + // mask away attributes + if (ctx.conf.page_table_pointer_mask == 0) { + code.test(page, page); + } else if (std::in_range(ctx.conf.page_table_pointer_mask)) { + code.and_(page, ctx.conf.page_table_pointer_mask); + } else { + code.mov(tmp, ctx.conf.page_table_pointer_mask); + code.and_(page, tmp); + } + // check for sign bit, apply sign extension as needed + if (ctx.conf.page_table_sign_extension) { + code.shl(page, *ctx.conf.page_table_sign_extension); + code.sar(page, *ctx.conf.page_table_sign_extension); + } + code.jz(abort, code.T_NEAR); if (ctx.conf.absolute_offset_page_table) { return page + vaddr; diff --git a/src/dynarmic/src/dynarmic/backend/x64/emit_x64_saturation.cpp b/src/dynarmic/src/dynarmic/backend/x64/emit_x64_saturation.cpp index 74e8c76275..e116382b93 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/emit_x64_saturation.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/emit_x64_saturation.cpp @@ -38,7 +38,7 @@ void EmitSignedSaturatedOp(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst) Xbyak::Reg addend = ctx.reg_alloc.UseGpr(code, args[1]).changeBit(size); Xbyak::Reg overflow = ctx.reg_alloc.ScratchGpr(code).changeBit(size); - constexpr u64 int_max = static_cast((std::numeric_limits>::max)()); + constexpr u64 int_max = u64((std::numeric_limits>>::max)()); if constexpr (size < 64) { code.xor_(overflow.cvt32(), overflow.cvt32()); code.bt(result.cvt32(), size - 1); diff --git a/src/dynarmic/src/dynarmic/backend/x64/emit_x64_vector.cpp b/src/dynarmic/src/dynarmic/backend/x64/emit_x64_vector.cpp index b5ec6ec7cf..1165abb0e4 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/emit_x64_vector.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/emit_x64_vector.cpp @@ -2103,36 +2103,57 @@ void EmitX64::EmitVectorMaxU64(EmitContext& ctx, IR::Inst* inst) { } else if (code.HasHostFeature(HostFeature::AVX)) { auto const x = ctx.reg_alloc.UseScratchXmm(code, args[0]); auto const y = ctx.reg_alloc.UseXmm(code, args[1]); - auto const tmp = ctx.reg_alloc.ScratchXmm(code); - code.vmovdqa(xmm0, code.Const(xword, 0x8000000000000000, 0x8000000000000000)); - code.vpsubq(tmp, y, xmm0); - code.vpsubq(xmm0, x, xmm0); - code.vpcmpgtq(xmm0, tmp, xmm0); - code.pblendvb(x, y); + auto const tmp0 = ctx.reg_alloc.ScratchXmm(code); + auto const tmp1 = ctx.reg_alloc.ScratchXmm(code, HostLoc::XMM0); + code.vmovdqa(tmp1, code.Const(xword, 0x8000000000000000, 0x8000000000000000)); + code.vpsubq(tmp0, y, tmp1); + code.vpsubq(tmp1, x, tmp1); + code.vpcmpgtq(tmp1, tmp0, tmp1); + code.pblendvb(x, y); // XMM0 is implicit ctx.reg_alloc.DefineValue(code, inst, x); + } else if (code.HasHostFeature(HostFeature::SSE41)) { + auto const tmp0 = ctx.reg_alloc.UseScratchXmm(code, args[0]); + auto const tmp1 = ctx.reg_alloc.UseXmm(code, args[1]); + auto const tmp2 = ctx.reg_alloc.ScratchXmm(code); + auto const tmp3 = ctx.reg_alloc.ScratchXmm(code); + auto const tmp4 = ctx.reg_alloc.ScratchXmm(code); + auto const tax = ctx.reg_alloc.ScratchGpr(code); + auto const tdx = ctx.reg_alloc.ScratchGpr(code); + code.movq(tdx, tmp1); + code.movq(tax, tmp0); + code.movhlps(tmp3, tmp0); + code.cmp(tdx, tax); + code.movhlps(tmp2, tmp1); + code.cmovnb(tax, tdx); + code.movq(tdx, tmp2); + code.pinsrq(tmp4, tax, 0); + code.movq(tax, tmp3); + code.cmp(tdx, tax); + code.cmovnb(tax, tdx); + code.pinsrq(tmp4, tax, 1); + ctx.reg_alloc.DefineValue(code, inst, tmp4); } else { auto const tmp0 = ctx.reg_alloc.UseScratchXmm(code, args[0]); auto const tmp1 = ctx.reg_alloc.UseXmm(code, args[1]); auto const tmp2 = ctx.reg_alloc.ScratchXmm(code); auto const tmp3 = ctx.reg_alloc.ScratchXmm(code); auto const tmp4 = ctx.reg_alloc.ScratchXmm(code); - auto const tmp5 = ctx.reg_alloc.ScratchXmm(code); - code.movdqa(tmp2, code.Const(xword, 0x8000000080000000, 0x8000000080000000)); - code.movdqa(tmp3, tmp1); - code.pxor(tmp3, tmp2); - code.pxor(tmp2, tmp0); - code.movdqa(tmp4, tmp2); - code.pcmpeqd(tmp2, tmp3); - code.pcmpgtd(tmp4, tmp3); - code.pshufd(tmp2, tmp2, 245); - code.pshufd(tmp5, tmp4, 160); - code.pshufd(tmp3, tmp4, 245); - code.pand(tmp2, tmp5); - code.por(tmp3, tmp2); - code.pand(tmp0, tmp3); - code.pandn(tmp3, tmp1); - code.por(tmp0, tmp3); - ctx.reg_alloc.DefineValue(code, inst, tmp0); + auto const tax = ctx.reg_alloc.ScratchGpr(code); + auto const tdx = ctx.reg_alloc.ScratchGpr(code); + code.movq(tdx, tmp1); + code.movq(tax, tmp0); + code.movhlps(tmp3, tmp0); + code.cmp(tdx, tax); + code.movhlps(tmp2, tmp1); + code.cmovnb(tax, tdx); + code.movq(tdx, tmp2); + code.movq(tmp4, tax); + code.movq(tax, tmp3); + code.cmp(tdx, tax); + code.cmovnb(tax, tdx); + code.movq(tmp3, tax); + code.punpcklqdq(tmp4, tmp3); + ctx.reg_alloc.DefineValue(code, inst, tmp4); } } @@ -2247,37 +2268,57 @@ void EmitX64::EmitVectorMinU64(EmitContext& ctx, IR::Inst* inst) { } else if (code.HasHostFeature(HostFeature::AVX)) { auto const x = ctx.reg_alloc.UseXmm(code, args[0]); auto const y = ctx.reg_alloc.UseScratchXmm(code, args[1]); - auto const tmp = ctx.reg_alloc.ScratchXmm(code); - code.vmovdqa(xmm0, code.Const(xword, 0x8000000000000000, 0x8000000000000000)); - code.vpsubq(tmp, y, xmm0); - code.vpsubq(xmm0, x, xmm0); - code.vpcmpgtq(xmm0, tmp, xmm0); + auto const tmp0 = ctx.reg_alloc.ScratchXmm(code); + auto const tmp1 = ctx.reg_alloc.ScratchXmm(code, HostLoc::XMM0); + code.vmovdqa(tmp1, code.Const(xword, 0x8000000000000000, 0x8000000000000000)); + code.vpsubq(tmp0, y, tmp1); + code.vpsubq(tmp1, x, tmp1); + code.vpcmpgtq(tmp1, tmp0, tmp1); code.pblendvb(y, x); ctx.reg_alloc.DefineValue(code, inst, y); + } else if (code.HasHostFeature(HostFeature::SSE41)) { + auto const tmp0 = ctx.reg_alloc.UseScratchXmm(code, args[0]); + auto const tmp1 = ctx.reg_alloc.UseXmm(code, args[1]); + auto const tmp2 = ctx.reg_alloc.ScratchXmm(code); + auto const tmp3 = ctx.reg_alloc.ScratchXmm(code); + auto const tmp4 = ctx.reg_alloc.ScratchXmm(code); + auto const tax = ctx.reg_alloc.ScratchGpr(code); + auto const tdx = ctx.reg_alloc.ScratchGpr(code); + code.movq(tdx, tmp1); + code.movq(tax, tmp0); + code.movhlps(tmp3, tmp0); + code.cmp(tdx, tax); + code.movhlps(tmp2, tmp1); + code.cmovbe(tax, tdx); + code.movq(tdx, tmp2); + code.pinsrq(tmp4, tax, 0); + code.movq(tax, tmp3); + code.cmp(tdx, tax); + code.cmovbe(tax, tdx); + code.pinsrq(tmp4, tax, 1); + ctx.reg_alloc.DefineValue(code, inst, tmp4); } else { auto const tmp0 = ctx.reg_alloc.UseScratchXmm(code, args[0]); auto const tmp1 = ctx.reg_alloc.UseXmm(code, args[1]); auto const tmp2 = ctx.reg_alloc.ScratchXmm(code); auto const tmp3 = ctx.reg_alloc.ScratchXmm(code); auto const tmp4 = ctx.reg_alloc.ScratchXmm(code); - auto const tmp5 = ctx.reg_alloc.ScratchXmm(code); - code.movdqa(tmp2, code.Const(xword, 0x8000000080000000, 0x8000000080000000)); - code.movdqa(tmp3, tmp1); - code.pxor(tmp3, tmp2); - code.pxor(tmp2, tmp0); - code.movdqa(tmp4, tmp2); - code.pcmpeqd(tmp2, tmp3); - code.pcmpgtd(tmp4, tmp3); - code.pshufd(tmp3, tmp2, 245); - code.pshufd(tmp5, tmp4, 160); - code.pshufd(tmp2, tmp4, 245); - code.pand(tmp3, tmp5); - code.por(tmp2, tmp3); - code.pand(tmp1, tmp2); - code.pandn(tmp2, tmp0); - code.por(tmp2, tmp1); - //code.movdqa(tmp0, tmp2); - ctx.reg_alloc.DefineValue(code, inst, tmp2); + auto const tax = ctx.reg_alloc.ScratchGpr(code); + auto const tdx = ctx.reg_alloc.ScratchGpr(code); + code.movq(tdx, tmp1); + code.movq(tax, tmp0); + code.movhlps(tmp3, tmp0); + code.cmp(tdx, tax); + code.movhlps(tmp2, tmp1); + code.cmovbe(tax, tdx); + code.movq(tdx, tmp2); + code.movq(tmp4, tax); + code.movq(tax, tmp3); + code.cmp(tdx, tax); + code.cmovbe(tax, tdx); + code.movq(tmp3, tax); + code.punpcklqdq(tmp4, tmp3); + ctx.reg_alloc.DefineValue(code, inst, tmp4); } } @@ -2471,13 +2512,11 @@ void EmitX64::EmitVectorNarrow64(EmitContext& ctx, IR::Inst* inst) { ctx.reg_alloc.DefineValue(code, inst, result); } else { - auto const a = ctx.reg_alloc.UseScratchXmm(code, args[0]); - auto const zeros = ctx.reg_alloc.ScratchXmm(code); - - code.pxor(zeros, zeros); - code.shufps(a, zeros, 0b00001000); - - ctx.reg_alloc.DefineValue(code, inst, a); + auto const a = ctx.reg_alloc.UseScratchXmm(code, args[0]); + auto const zeros = ctx.reg_alloc.ScratchXmm(code); + code.pxor(zeros, zeros); + code.shufps(a, zeros, 0b00001000); + ctx.reg_alloc.DefineValue(code, inst, a); } } @@ -2490,11 +2529,11 @@ void EmitX64::EmitVectorNot(EmitContext& ctx, IR::Inst* inst) { code.vpternlogq(result, operand, operand, u8(~Tern::c)); ctx.reg_alloc.DefineValue(code, inst, result); } else { - auto const xmm_a = ctx.reg_alloc.UseScratchXmm(code, args[0]); - auto const xmm_b = ctx.reg_alloc.ScratchXmm(code); - code.pcmpeqw(xmm_b, xmm_b); - code.pxor(xmm_a, xmm_b); - ctx.reg_alloc.DefineValue(code, inst, xmm_a); + auto const xmm_a = ctx.reg_alloc.UseScratchXmm(code, args[0]); + auto const xmm_b = ctx.reg_alloc.ScratchXmm(code); + code.pcmpeqw(xmm_b, xmm_b); + code.pxor(xmm_a, xmm_b); + ctx.reg_alloc.DefineValue(code, inst, xmm_a); } } @@ -2504,11 +2543,9 @@ void EmitX64::EmitVectorOr(EmitContext& ctx, IR::Inst* inst) { void EmitX64::EmitVectorPairedAddLower8(EmitContext& ctx, IR::Inst* inst) { auto args = ctx.reg_alloc.GetArgumentInfo(inst); - auto const xmm_a = ctx.reg_alloc.UseScratchXmm(code, args[0]); auto const xmm_b = ctx.reg_alloc.UseXmm(code, args[1]); auto const tmp = ctx.reg_alloc.ScratchXmm(code); - code.punpcklqdq(xmm_a, xmm_b); code.movdqa(tmp, xmm_a); code.psllw(xmm_a, 8); @@ -2516,7 +2553,6 @@ void EmitX64::EmitVectorPairedAddLower8(EmitContext& ctx, IR::Inst* inst) { code.pxor(tmp, tmp); code.psrlw(xmm_a, 8); code.packuswb(xmm_a, tmp); - ctx.reg_alloc.DefineValue(code, inst, xmm_a); } @@ -3553,7 +3589,7 @@ void EmitX64::EmitVectorPopulationCount(EmitContext& ctx, IR::Inst* inst) { EmitOneArgumentFallback(code, ctx, inst, [](VectorArray& result, const VectorArray& a) { std::transform(a.begin(), a.end(), result.begin(), [](u8 val) { - return static_cast(mcl::bit::count_ones(val)); + return static_cast(std::popcount(val)); }); }); } @@ -5040,9 +5076,8 @@ void EmitX64::EmitVectorSignedSaturatedNeg64(EmitContext& ctx, IR::Inst* inst) { # pragma clang diagnostic ignored "-Wunused-lambda-capture" #endif template> + requires std::is_signed_v static bool VectorSignedSaturatedShiftLeft(VectorArray& dst, const VectorArray& data, const VectorArray& shift_values) { - static_assert(std::is_signed_v, "T must be signed."); - bool qc_flag = false; constexpr size_t bit_size_minus_one = mcl::bitsizeof - 1; @@ -5098,9 +5133,9 @@ void EmitX64::EmitVectorSignedSaturatedShiftLeft64(EmitContext& ctx, IR::Inst* i } template + requires std::is_signed_v static bool VectorSignedSaturatedShiftLeftUnsigned(VectorArray& dst, const VectorArray& data, u8 shift_amount) { using U = std::make_unsigned_t; - static_assert(std::is_signed_v, "T must be signed."); bool qc_flag = false; for (size_t i = 0; i < dst.size(); i++) { auto const element = data[i]; @@ -5994,17 +6029,15 @@ void EmitX64::EmitVectorUnsignedRecipSqrtEstimate(EmitContext& ctx, IR::Inst* in // Simple generic case for 8, 16, and 32-bit values. 64-bit values // will need to be special-cased as we can't simply use a larger integral size. template> + requires std::is_signed_v static bool EmitVectorUnsignedSaturatedAccumulateSigned(VectorArray& result, const VectorArray& lhs, const VectorArray& rhs) { - static_assert(std::is_signed_v, "T must be signed."); static_assert(mcl::bitsizeof < 64, "T must be less than 64 bits in size."); - bool qc_flag = false; - for (size_t i = 0; i < result.size(); i++) { // We treat rhs' members as unsigned, so cast to unsigned before signed to inhibit sign-extension. // We use the unsigned equivalent of T, as we want zero-extension to occur, rather than a plain move. const s64 x = s64{lhs[i]}; - const s64 y = static_cast(static_cast>(rhs[i])); + const s64 y = s64(std::make_unsigned_t(rhs[i])); const s64 sum = x + y; if (sum > (std::numeric_limits::max)()) { @@ -6014,10 +6047,9 @@ static bool EmitVectorUnsignedSaturatedAccumulateSigned(VectorArray& result, result[i] = (std::numeric_limits::min)(); qc_flag = true; } else { - result[i] = static_cast(sum); + result[i] = U(sum); } } - return qc_flag; } @@ -6098,29 +6130,23 @@ void EmitX64::EmitVectorUnsignedSaturatedNarrow64(EmitContext& ctx, IR::Inst* in } template> + requires std::is_unsigned_v static bool VectorUnsignedSaturatedShiftLeft(VectorArray& dst, const VectorArray& data, const VectorArray& shift_values) { - static_assert(std::is_unsigned_v, "T must be an unsigned type."); - bool qc_flag = false; - constexpr size_t bit_size = mcl::bitsizeof; - constexpr S negative_bit_size = -static_cast(bit_size); - + constexpr S negative_bit_size = -S(bit_size); for (size_t i = 0; i < dst.size(); i++) { const T element = data[i]; - const S shift = std::clamp(static_cast(mcl::bit::sign_extend<8>(static_cast(shift_values[i] & 0xFF))), - negative_bit_size, (std::numeric_limits::max)()); - + const S shift = std::clamp(S(mcl::bit::sign_extend<8>(T(shift_values[i] & 0xFF))), negative_bit_size, (std::numeric_limits::max)()); if (element == 0 || shift <= negative_bit_size) { dst[i] = 0; } else if (shift < 0) { - dst[i] = static_cast(element >> -shift); - } else if (shift >= static_cast(bit_size)) { + dst[i] = T(element >> -shift); + } else if (shift >= S(bit_size)) { dst[i] = (std::numeric_limits::max)(); qc_flag = true; } else { const T shifted = element << shift; - if ((shifted >> shift) != element) { dst[i] = (std::numeric_limits::max)(); qc_flag = true; diff --git a/src/dynarmic/src/dynarmic/backend/x64/emit_x64_vector_floating_point.cpp b/src/dynarmic/src/dynarmic/backend/x64/emit_x64_vector_floating_point.cpp index 926653a920..0ad3b022d0 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/emit_x64_vector_floating_point.cpp +++ b/src/dynarmic/src/dynarmic/backend/x64/emit_x64_vector_floating_point.cpp @@ -1657,20 +1657,10 @@ void EmitFPVectorRoundInt(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst) { if constexpr (fsize != 16) { if (code.HasHostFeature(HostFeature::SSE41) && rounding != FP::RoundingMode::ToNearest_TieAwayFromZero && !exact) { - const u8 round_imm = [&]() -> u8 { - switch (rounding) { - case FP::RoundingMode::ToNearest_TieEven: return 0b00; - case FP::RoundingMode::TowardsPlusInfinity: return 0b10; - case FP::RoundingMode::TowardsMinusInfinity: return 0b01; - case FP::RoundingMode::TowardsZero: return 0b11; - default: UNREACHABLE(); - } - }(); - + const auto round_imm = ConvertRoundingModeToX64Immediate(rounding); EmitTwoOpVectorOperation(code, ctx, inst, [&](const Xbyak::Xmm& result, const Xbyak::Xmm& xmm_a) { - FCODE(roundp)(result, xmm_a, round_imm); + FCODE(roundp)(result, xmm_a, *round_imm); }); - return; } } @@ -2002,19 +1992,7 @@ void EmitFPVectorToFixed(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst) { auto args = ctx.reg_alloc.GetArgumentInfo(inst); const Xbyak::Xmm src = ctx.reg_alloc.UseScratchXmm(code, args[0]); MaybeStandardFPSCRValue(code, ctx, fpcr_controlled, [&] { - const int round_imm = [&] { - switch (rounding) { - case FP::RoundingMode::ToNearest_TieEven: - default: - return 0b00; - case FP::RoundingMode::TowardsPlusInfinity: - return 0b10; - case FP::RoundingMode::TowardsMinusInfinity: - return 0b01; - case FP::RoundingMode::TowardsZero: - return 0b11; - } - }(); + const auto round_imm = ConvertRoundingModeToX64Immediate(rounding); const auto perform_conversion = [&code, &ctx](const Xbyak::Xmm& src) { // MSVC doesn't allow us to use a [&] capture, so we have to do this instead. (void)ctx; @@ -2046,7 +2024,7 @@ void EmitFPVectorToFixed(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst) { FCODE(mulp)(src, GetVectorOf(code, scale_factor)); } - FCODE(roundp)(src, src, u8(round_imm)); + FCODE(roundp)(src, src, u8(*round_imm)); const Xbyak::Xmm nan_mask = xmm0; if (code.HasHostFeature(HostFeature::AVX512_OrthoFloat)) { static constexpr u32 nan_to_zero = FixupLUT(FpFixup::PosZero, FpFixup::PosZero); @@ -2182,8 +2160,6 @@ void EmitFPVectorToFixed(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst) { ROUNDING_MODE_CASE(CASE, 0x3e) \ ROUNDING_MODE_CASE(CASE, 0x3f) - // FUCK YOU MSVC, FUCKING DEPTH CANT EVEN HANDLE 8+16+32+64 DEPTH OF A ELSE STATMENT YOU FUCKING STUPID - // BURN MSVC BURN IT STUPID COMPILER CAN'T EVEN COMPILE THE MOST BASIC C++ ROUNDING_MODE_SWITCH(ToNearest_TieEven) ROUNDING_MODE_SWITCH(TowardsPlusInfinity) ROUNDING_MODE_SWITCH(TowardsMinusInfinity) diff --git a/src/dynarmic/src/dynarmic/backend/x64/host_feature.h b/src/dynarmic/src/dynarmic/backend/x64/host_feature.h index 019a36635b..3cd08362a0 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/host_feature.h +++ b/src/dynarmic/src/dynarmic/backend/x64/host_feature.h @@ -12,7 +12,7 @@ namespace Dynarmic::Backend::X64 { -enum class HostFeature : u64 { +enum class HostFeature : u32 { SSSE3 = 1ULL << 0, SSE41 = 1ULL << 1, SSE42 = 1ULL << 2, diff --git a/src/dynarmic/src/dynarmic/backend/x64/hostloc.h b/src/dynarmic/src/dynarmic/backend/x64/hostloc.h index 7191a0ceba..d62090d612 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/hostloc.h +++ b/src/dynarmic/src/dynarmic/backend/x64/hostloc.h @@ -8,8 +8,6 @@ #pragma once #include -#include - #include "common/assert.h" #include "common/common_types.h" #include "dynarmic/backend/x64/xbyak.h" diff --git a/src/dynarmic/src/dynarmic/backend/x64/jitstate_info.h b/src/dynarmic/src/dynarmic/backend/x64/jitstate_info.h index 654b3f0400..83c1d2e126 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/jitstate_info.h +++ b/src/dynarmic/src/dynarmic/backend/x64/jitstate_info.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + /* This file is part of the dynarmic project. * Copyright (c) 2016 MerryMage * SPDX-License-Identifier: 0BSD @@ -15,7 +18,7 @@ struct JitStateInfo { : offsetof_guest_MXCSR(offsetof(JitStateType, guest_MXCSR)) , offsetof_asimd_MXCSR(offsetof(JitStateType, asimd_MXCSR)) , offsetof_rsb_ptr(offsetof(JitStateType, rsb_ptr)) - , rsb_ptr_mask(JitStateType::RSBPtrMask) + , rsb_ptr_mask(JitStateType::RSB_PTR_MASK) , offsetof_rsb_location_descriptors(offsetof(JitStateType, rsb_location_descriptors)) , offsetof_rsb_codeptrs(offsetof(JitStateType, rsb_codeptrs)) , offsetof_cpsr_nzcv(offsetof(JitStateType, cpsr_nzcv)) diff --git a/src/dynarmic/src/dynarmic/backend/x64/xbyak.h b/src/dynarmic/src/dynarmic/backend/x64/xbyak.h index ee74794fb4..9dc67f3778 100644 --- a/src/dynarmic/src/dynarmic/backend/x64/xbyak.h +++ b/src/dynarmic/src/dynarmic/backend/x64/xbyak.h @@ -3,13 +3,12 @@ #pragma once -#include -#include - -// TODO: Defining this crashes e v e r y t h i n g -// #define XBYAK_STD_UNORDERED_SET ankerl::unordered_dense::set -// #define XBYAK_STD_UNORDERED_MAP ankerl::unordered_dense::map -// #define XBYAK_STD_UNORDERED_MULTIMAP boost::unordered_multimap - +// You must ensure this matches with src/common/x64/xbyak.h on root dir +#include "common/container/unordered_map.h" +#include "common/container/unordered_set.h" +#include +#define XBYAK_STD_UNORDERED_SET ::Common::unordered_set +#define XBYAK_STD_UNORDERED_MAP ::Common::unordered_map +#define XBYAK_STD_UNORDERED_MULTIMAP boost::unordered_multimap #include #include diff --git a/src/dynarmic/src/dynarmic/common/atomic.h b/src/dynarmic/src/dynarmic/common/atomic.h index 5eb4288517..8af6626be9 100644 --- a/src/dynarmic/src/dynarmic/common/atomic.h +++ b/src/dynarmic/src/dynarmic/common/atomic.h @@ -36,6 +36,14 @@ inline void And(volatile u32* ptr, u32 value) { #endif } +inline u32 Exchange(volatile u32* ptr, u32 value) { +#ifdef _MSC_VER + return static_cast(_InterlockedExchange(reinterpret_cast(ptr), value)); +#else + return __atomic_exchange_n(ptr, value, __ATOMIC_SEQ_CST); +#endif +} + inline void Barrier() { #ifdef _MSC_VER _ReadWriteBarrier(); diff --git a/src/dynarmic/src/dynarmic/common/context.h b/src/dynarmic/src/dynarmic/common/context.h index e849c9861b..0c18807ce5 100644 --- a/src/dynarmic/src/dynarmic/common/context.h +++ b/src/dynarmic/src/dynarmic/common/context.h @@ -51,7 +51,25 @@ # endif #endif -#ifdef ARCHITECTURE_arm64 +// Some OSes define generic helper macros for all architectures +#if defined(__NetBSD__) +// NetBSD always has useful macros for everything don't they? +// Basically this special path means that for every arch that NetBSD supports +// it atleast has the macro for _UC_MACHINE defined, thus it will be avoided on the +// other macro branches! +// https://github.com/NetBSD/src/blob/trunk/sys/arch/powerpc/include/mcontext.h +// https://github.com/NetBSD/src/blob/trunk/sys/arch/mips/include/mcontext.h +# define CTX_PC (_UC_MACHINE_PC(ucontext)) +// NetBSD defines a redzone for SP but we don't require nor want that +// ((uc)->uc_mcontext.__gregs[_REG_RSP] - 128) -> this is not desirable due to calcs +#if defined(ARCHITECTURE_x86_64) +# define CTX_SP (mctx.__gregs[_REG_RSP]) +#else +# define CTX_SP (_UC_MACHINE_SP(ucontext)) +#endif +#endif + +#if defined(ARCHITECTURE_arm64) # if defined(__APPLE__) # define CTX_PC (mctx->__ss.__pc) # define CTX_SP (mctx->__ss.__sp) @@ -76,9 +94,7 @@ # define CTX_LR (mctx.mc_gpregs.gp_lr) # define CTX_X(i) (mctx.mc_gpregs.gp_x[i]) # define CTX_Q(i) (mctx.mc_fpregs.fp_q[i]) -# elif defined(__NetBSD__) -# define CTX_PC (mctx.mc_gpregs.gp_elr) -# define CTX_SP (mctx.mc_gpregs.gp_sp) +# elif defined(__NetBSD__) // SP + PC defined above # define CTX_LR (mctx.mc_gpregs.gp_lr) # define CTX_X(i) (mctx.mc_gpregs.gp_x[i]) # define CTX_Q(i) (mctx.mc_fpregs.fp_q[i]) @@ -92,48 +108,42 @@ # else # error "unknown platform" # endif -#elif defined(__NetBSD__) -// NetBSD always has useful macros for everything don't they? -// Basically this special path means that for every arch that NetBSD supports -// it atleast has the macro for _UC_MACHINE defined, thus it will be avoided on the -// other macro branches! -// https://github.com/NetBSD/src/blob/trunk/sys/arch/powerpc/include/mcontext.h -// https://github.com/NetBSD/src/blob/trunk/sys/arch/mips/include/mcontext.h -# define CTX_PC (_UC_MACHINE_PC(ucontext)) -# define CTX_SP (_UC_MACHINE_SP(ucontext)) #elif defined(ARCHITECTURE_x86_64) # if defined(__APPLE__) -# define CTX_RIP (mctx->__ss.__rip) -# define CTX_RSP (mctx->__ss.__rsp) +# define CTX_PC (mctx->__ss.__rip) +# define CTX_SP (mctx->__ss.__rsp) # elif defined(__linux__) -# define CTX_RIP (mctx.gregs[REG_RIP]) -# define CTX_RSP (mctx.gregs[REG_RSP]) +# define CTX_PC (mctx.gregs[REG_RIP]) +# define CTX_SP (mctx.gregs[REG_RSP]) # elif defined(__FreeBSD__) -# define CTX_RIP (mctx.mc_rip) -# define CTX_RSP (mctx.mc_rsp) +# define CTX_PC (mctx.mc_rip) +# define CTX_SP (mctx.mc_rsp) # elif defined(__OpenBSD__) // https://github.com/openbsd/src/blob/master/sys/arch/x86_64/include/signal.h -# define CTX_RIP (ucontext->sc_rip) -# define CTX_RSP (ucontext->sc_rsp) +# define CTX_PC (ucontext->sc_rip) +# define CTX_SP (ucontext->sc_rsp) # elif defined(__sun__) -# define CTX_RIP (mctx.gregs[REG_RIP]) -# define CTX_RSP (mctx.gregs[REG_RSP]) +# define CTX_PC (mctx.gregs[REG_RIP]) +# define CTX_SP (mctx.gregs[REG_RSP]) # elif defined(__DragonFly__) -# define CTX_RIP (mctx.mc_rip) -# define CTX_RSP (mctx.mc_rsp) +# define CTX_PC (mctx.mc_rip) +# define CTX_SP (mctx.mc_rsp) +# elif defined(__managarm__) +# define CTX_PC (mctx.__pollution(gregs)[REG_RIP]) +# define CTX_SP (mctx.__pollution(gregs)[REG_RSP]) # else # error "unknown platform" # endif #elif defined(ARCHITECTURE_riscv64) # if defined(__FreeBSD__) -# define CTX_SEPC (mctx.mc_gpregs.gp_sepc) +# define CTX_PC (mctx.mc_gpregs.gp_sepc) # define CTX_SP (mctx.mc_gpregs.gp_sp) # elif defined(__linux__) -# define CTX_SEPC (mctx.__gregs[REG_PC]) +# define CTX_PC (mctx.__gregs[REG_PC]) # define CTX_SP (mctx.__gregs[REG_SP]) # elif defined(__OpenBSD__) // https://github.com/openbsd/src/blob/master/sys/arch/riscv64/include/signal.h -# define CTX_SEPC (ucontext->sc_sepc) +# define CTX_PC (ucontext->sc_sepc) # define CTX_SP (ucontext->sc_sp) # else # error "unknown platform" diff --git a/src/dynarmic/src/dynarmic/common/fp/op/FPToFixed.cpp b/src/dynarmic/src/dynarmic/common/fp/op/FPToFixed.cpp index d920f2d900..7c322050c3 100644 --- a/src/dynarmic/src/dynarmic/common/fp/op/FPToFixed.cpp +++ b/src/dynarmic/src/dynarmic/common/fp/op/FPToFixed.cpp @@ -78,7 +78,7 @@ u64 FPToFixed(size_t ibits, FPT op, size_t fbits, bool unsigned_, FPCR fpcr, Rou } // Detect Overflow - const int min_exponent_for_overflow = static_cast(ibits) - static_cast(mcl::bit::highest_set_bit(value.mantissa + (round_up ? Safe::LogicalShiftRight(1, exponent) : 0))) - (unsigned_ ? 0 : 1); + const int min_exponent_for_overflow = int(ibits) - int(mcl::bit::highest_set_bit(value.mantissa + (round_up ? Safe::LogicalShiftRight(1, exponent) : 0))) - (unsigned_ ? 0 : 1); if (exponent >= min_exponent_for_overflow) { // Positive overflow if (unsigned_ || !sign) { @@ -87,10 +87,10 @@ u64 FPToFixed(size_t ibits, FPT op, size_t fbits, bool unsigned_, FPCR fpcr, Rou } // Negative overflow - const u64 min_value = Safe::Negate(static_cast(1) << (ibits - 1)); + const u64 min_value = Safe::Negate(u64(1) << (ibits - 1)); if (!(exponent == min_exponent_for_overflow && int_result == min_value)) { FPProcessException(FPExc::InvalidOp, fpcr, fpsr); - return static_cast(1) << (ibits - 1); + return u64(1) << (ibits - 1); } } diff --git a/src/dynarmic/src/dynarmic/common/llvm_disassemble.cpp b/src/dynarmic/src/dynarmic/common/llvm_disassemble.cpp index 6aff6d6cc7..6896b43d62 100644 --- a/src/dynarmic/src/dynarmic/common/llvm_disassemble.cpp +++ b/src/dynarmic/src/dynarmic/common/llvm_disassemble.cpp @@ -7,7 +7,6 @@ */ #include - #include #ifdef DYNARMIC_USE_LLVM @@ -16,7 +15,6 @@ #endif #include "common/assert.h" -#include #include "common/common_types.h" #include "dynarmic/common/llvm_disassemble.h" diff --git a/src/dynarmic/src/dynarmic/common/safe_ops.h b/src/dynarmic/src/dynarmic/common/safe_ops.h index f5559336e0..261272bb58 100644 --- a/src/dynarmic/src/dynarmic/common/safe_ops.h +++ b/src/dynarmic/src/dynarmic/common/safe_ops.h @@ -28,18 +28,11 @@ T ArithmeticShiftRight(T value, int shift_amount); template T LogicalShiftLeft(T value, int shift_amount) { - static_assert(std::is_integral_v); - - if (shift_amount >= static_cast(mcl::bitsizeof)) { + if (shift_amount >= int(mcl::bitsizeof)) return 0; - } - - if (shift_amount < 0) { - return LogicalShiftRight(value, -shift_amount); - } - - auto unsigned_value = static_cast>(value); - return static_cast(unsigned_value << shift_amount); + return shift_amount < 0 + ? LogicalShiftRight(value, -shift_amount) + : T(std::make_unsigned_t(value) << shift_amount); } template<> @@ -49,18 +42,11 @@ inline u128 LogicalShiftLeft(u128 value, int shift_amount) { template T LogicalShiftRight(T value, int shift_amount) { - static_assert(std::is_integral_v); - - if (shift_amount >= static_cast(mcl::bitsizeof)) { + if (shift_amount >= int(mcl::bitsizeof)) return 0; - } - - if (shift_amount < 0) { - return LogicalShiftLeft(value, -shift_amount); - } - - auto unsigned_value = static_cast>(value); - return static_cast(unsigned_value >> shift_amount); + return shift_amount < 0 + ? LogicalShiftLeft(value, -shift_amount) + : T(std::make_unsigned_t(value) >> shift_amount); } template<> @@ -75,34 +61,20 @@ T LogicalShiftRightDouble(T top, T bottom, int shift_amount) { template T ArithmeticShiftLeft(T value, int shift_amount) { - static_assert(std::is_integral_v); - - if (shift_amount >= static_cast(mcl::bitsizeof)) { + if (shift_amount >= int(mcl::bitsizeof)) return 0; - } - - if (shift_amount < 0) { - return ArithmeticShiftRight(value, -shift_amount); - } - - auto unsigned_value = static_cast>(value); - return static_cast(unsigned_value << shift_amount); + return shift_amount < 0 + ? ArithmeticShiftRight(value, -shift_amount) + : T(std::make_unsigned_t(value) << shift_amount); } template T ArithmeticShiftRight(T value, int shift_amount) { - static_assert(std::is_integral_v); - - if (shift_amount >= static_cast(mcl::bitsizeof)) { - return mcl::bit::most_significant_bit(value) ? ~static_cast(0) : 0; - } - - if (shift_amount < 0) { - return ArithmeticShiftLeft(value, -shift_amount); - } - - auto signed_value = static_cast>(value); - return static_cast(signed_value >> shift_amount); + if (shift_amount >= int(mcl::bitsizeof)) + return mcl::bit::most_significant_bit(value) ? ~T(0) : 0; + return shift_amount < 0 + ? ArithmeticShiftLeft(value, -shift_amount) + : T(std::make_signed_t(value) >> shift_amount); } template @@ -112,7 +84,7 @@ T ArithmeticShiftRightDouble(T top, T bottom, int shift_amount) { template T Negate(T value) { - return static_cast(~static_cast(value) + 1); + return T(~std::uintmax_t(value) + 1); } } // namespace Dynarmic::Safe diff --git a/src/dynarmic/src/dynarmic/common/spin_lock_loongarch64.cpp b/src/dynarmic/src/dynarmic/common/spin_lock_loongarch64.cpp new file mode 100644 index 0000000000..6efd05bc26 --- /dev/null +++ b/src/dynarmic/src/dynarmic/common/spin_lock_loongarch64.cpp @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dynarmic/common/spin_lock.h" + +namespace Dynarmic { + +void SpinLock::Lock() noexcept { +} + +void SpinLock::Unlock() noexcept { +} + +} // namespace Dynarmic diff --git a/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp b/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp index e3f1721132..f1898a92ef 100644 --- a/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp +++ b/src/dynarmic/src/dynarmic/common/spin_lock_x64.cpp @@ -26,6 +26,7 @@ void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 Xbyak::Label start, loop; code.jmp(start, code.T_NEAR); code.L(loop); + if (waitpkg) { // TODO: this is because we lack regalloc - so better to be safe :( code.push(Xbyak::util::rax); @@ -33,7 +34,14 @@ void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 code.push(Xbyak::util::rdx); // TODO: This clobbers EAX and EDX did we tell the regalloc? // ARM ptr for address-monitoring - code.umonitor(ptr); + + // XBYAK BUG: code.umonitor(ptr); see issue #255 + // replace once xbyak has been fixed + code.db(0xF3); + if (ptr.getIdx() >= 8) code.db(0x41); + code.db(0x0F); code.db(0xAE); + code.db(uint8_t((3 << 6) | ((6 & 7) << 3) | (ptr.getIdx() & 7))); + // tmp.bit[0] = 0: C0.1 | Slow Wakup | Better Savings // tmp.bit[0] = 1: C0.2 | Fast Wakup | Lesser Savings // edx:eax is implicitly used as a 64-bit deadline timestamp @@ -82,6 +90,8 @@ std::once_flag flag; std::optional impl; void SpinLockImpl::Initialize() noexcept { + // Needed for W^X systems (i.e SElinux, OpenBSD) + code.setProtectMode(Xbyak::CodeArray::ProtectMode::PROTECT_RW); Xbyak::Reg64 const ABI_PARAM1 = Backend::X64::HostLocToReg64(Backend::X64::ABI_PARAM1); code.align(); lock = code.getCurr(); @@ -91,6 +101,7 @@ void SpinLockImpl::Initialize() noexcept { unlock = code.getCurr(); EmitSpinLockUnlock(code, ABI_PARAM1, code.eax); code.ret(); + code.setProtectMode(Xbyak::CodeArray::ProtectMode::PROTECT_RE); } void SpinLockImpl::GlobalInitialize() noexcept { diff --git a/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/a32_translate_impl.h b/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/a32_translate_impl.h index 82e919720e..dd88b79237 100644 --- a/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/a32_translate_impl.h +++ b/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/a32_translate_impl.h @@ -52,7 +52,7 @@ struct TranslatorVisitor final { u32 imm32 = imm8.ZeroExtend(); auto carry_out = carry_in; if (rotate) { - imm32 = mcl::bit::rotate_right(imm8.ZeroExtend(), rotate * 2); + imm32 = std::rotr(imm8.ZeroExtend(), rotate * 2); carry_out = ir.Imm1(mcl::bit::get_bit<31>(imm32)); } return {imm32, carry_out}; @@ -81,7 +81,7 @@ struct TranslatorVisitor final { }(); return {imm32, carry_in}; } - const u32 imm32 = mcl::bit::rotate_right((1 << 7) | imm12.Bits<0, 6>(), imm12.Bits<7, 11>()); + const u32 imm32 = std::rotr((1 << 7) | imm12.Bits<0, 6>(), imm12.Bits<7, 11>()); return {imm32, ir.Imm1(mcl::bit::get_bit<31>(imm32))}; } diff --git a/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/asimd_misc.cpp b/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/asimd_misc.cpp index 6ccfe1e3bc..d255ad8b2c 100644 --- a/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/asimd_misc.cpp +++ b/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/asimd_misc.cpp @@ -79,7 +79,7 @@ bool TranslatorVisitor::asimd_VDUP_scalar(bool D, Imm<4> imm4, size_t Vd, bool Q return UndefinedInstruction(); } - const size_t imm4_lsb = mcl::bit::lowest_set_bit(imm4.ZeroExtend()); + const size_t imm4_lsb = std::countr_zero(imm4.ZeroExtend()); const size_t esize = 8u << imm4_lsb; const size_t index = imm4.ZeroExtend() >> (imm4_lsb + 1); const auto d = ToVector(Q, Vd, D); diff --git a/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/load_store.cpp b/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/load_store.cpp index d7c667aecf..208e7d1122 100644 --- a/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/load_store.cpp +++ b/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/load_store.cpp @@ -799,7 +799,7 @@ static bool LDMHelper(A32::IREmitter& ir, bool W, Reg n, RegList list, IR::U32 s // LDM {!}, bool TranslatorVisitor::arm_LDM(Cond cond, bool W, Reg n, RegList list) { - if (n == Reg::PC || mcl::bit::count_ones(list) < 1) { + if (n == Reg::PC || std::popcount(list) < 1) { return UnpredictableInstruction(); } if (W && mcl::bit::get_bit(static_cast(n), list)) { @@ -811,13 +811,13 @@ bool TranslatorVisitor::arm_LDM(Cond cond, bool W, Reg n, RegList list) { } const auto start_address = ir.GetRegister(n); - const auto writeback_address = ir.Add(start_address, ir.Imm32(u32(mcl::bit::count_ones(list) * 4))); + const auto writeback_address = ir.Add(start_address, ir.Imm32(u32(std::popcount(list) * 4))); return LDMHelper(ir, W, n, list, start_address, writeback_address); } // LDMDA {!}, bool TranslatorVisitor::arm_LDMDA(Cond cond, bool W, Reg n, RegList list) { - if (n == Reg::PC || mcl::bit::count_ones(list) < 1) { + if (n == Reg::PC || std::popcount(list) < 1) { return UnpredictableInstruction(); } if (W && mcl::bit::get_bit(static_cast(n), list)) { @@ -828,14 +828,14 @@ bool TranslatorVisitor::arm_LDMDA(Cond cond, bool W, Reg n, RegList list) { return true; } - const auto start_address = ir.Sub(ir.GetRegister(n), ir.Imm32(u32(4 * mcl::bit::count_ones(list) - 4))); + const auto start_address = ir.Sub(ir.GetRegister(n), ir.Imm32(u32(4 * std::popcount(list) - 4))); const auto writeback_address = ir.Sub(start_address, ir.Imm32(4)); return LDMHelper(ir, W, n, list, start_address, writeback_address); } // LDMDB {!}, bool TranslatorVisitor::arm_LDMDB(Cond cond, bool W, Reg n, RegList list) { - if (n == Reg::PC || mcl::bit::count_ones(list) < 1) { + if (n == Reg::PC || std::popcount(list) < 1) { return UnpredictableInstruction(); } if (W && mcl::bit::get_bit(static_cast(n), list)) { @@ -846,14 +846,14 @@ bool TranslatorVisitor::arm_LDMDB(Cond cond, bool W, Reg n, RegList list) { return true; } - const auto start_address = ir.Sub(ir.GetRegister(n), ir.Imm32(u32(4 * mcl::bit::count_ones(list)))); + const auto start_address = ir.Sub(ir.GetRegister(n), ir.Imm32(u32(4 * std::popcount(list)))); const auto writeback_address = start_address; return LDMHelper(ir, W, n, list, start_address, writeback_address); } // LDMIB {!}, bool TranslatorVisitor::arm_LDMIB(Cond cond, bool W, Reg n, RegList list) { - if (n == Reg::PC || mcl::bit::count_ones(list) < 1) { + if (n == Reg::PC || std::popcount(list) < 1) { return UnpredictableInstruction(); } if (W && mcl::bit::get_bit(static_cast(n), list)) { @@ -865,7 +865,7 @@ bool TranslatorVisitor::arm_LDMIB(Cond cond, bool W, Reg n, RegList list) { } const auto start_address = ir.Add(ir.GetRegister(n), ir.Imm32(4)); - const auto writeback_address = ir.Add(ir.GetRegister(n), ir.Imm32(u32(4 * mcl::bit::count_ones(list)))); + const auto writeback_address = ir.Add(ir.GetRegister(n), ir.Imm32(u32(4 * std::popcount(list)))); return LDMHelper(ir, W, n, list, start_address, writeback_address); } @@ -896,7 +896,7 @@ static bool STMHelper(A32::IREmitter& ir, bool W, Reg n, RegList list, IR::U32 s // STM {!}, bool TranslatorVisitor::arm_STM(Cond cond, bool W, Reg n, RegList list) { - if (n == Reg::PC || mcl::bit::count_ones(list) < 1) { + if (n == Reg::PC || std::popcount(list) < 1) { return UnpredictableInstruction(); } @@ -905,13 +905,13 @@ bool TranslatorVisitor::arm_STM(Cond cond, bool W, Reg n, RegList list) { } const auto start_address = ir.GetRegister(n); - const auto writeback_address = ir.Add(start_address, ir.Imm32(u32(mcl::bit::count_ones(list) * 4))); + const auto writeback_address = ir.Add(start_address, ir.Imm32(u32(std::popcount(list) * 4))); return STMHelper(ir, W, n, list, start_address, writeback_address); } // STMDA {!}, bool TranslatorVisitor::arm_STMDA(Cond cond, bool W, Reg n, RegList list) { - if (n == Reg::PC || mcl::bit::count_ones(list) < 1) { + if (n == Reg::PC || std::popcount(list) < 1) { return UnpredictableInstruction(); } @@ -919,14 +919,14 @@ bool TranslatorVisitor::arm_STMDA(Cond cond, bool W, Reg n, RegList list) { return true; } - const auto start_address = ir.Sub(ir.GetRegister(n), ir.Imm32(u32(4 * mcl::bit::count_ones(list) - 4))); + const auto start_address = ir.Sub(ir.GetRegister(n), ir.Imm32(u32(4 * std::popcount(list) - 4))); const auto writeback_address = ir.Sub(start_address, ir.Imm32(4)); return STMHelper(ir, W, n, list, start_address, writeback_address); } // STMDB {!}, bool TranslatorVisitor::arm_STMDB(Cond cond, bool W, Reg n, RegList list) { - if (n == Reg::PC || mcl::bit::count_ones(list) < 1) { + if (n == Reg::PC || std::popcount(list) < 1) { return UnpredictableInstruction(); } @@ -934,14 +934,14 @@ bool TranslatorVisitor::arm_STMDB(Cond cond, bool W, Reg n, RegList list) { return true; } - const auto start_address = ir.Sub(ir.GetRegister(n), ir.Imm32(u32(4 * mcl::bit::count_ones(list)))); + const auto start_address = ir.Sub(ir.GetRegister(n), ir.Imm32(u32(4 * std::popcount(list)))); const auto writeback_address = start_address; return STMHelper(ir, W, n, list, start_address, writeback_address); } // STMIB {!}, bool TranslatorVisitor::arm_STMIB(Cond cond, bool W, Reg n, RegList list) { - if (n == Reg::PC || mcl::bit::count_ones(list) < 1) { + if (n == Reg::PC || std::popcount(list) < 1) { return UnpredictableInstruction(); } @@ -950,7 +950,7 @@ bool TranslatorVisitor::arm_STMIB(Cond cond, bool W, Reg n, RegList list) { } const auto start_address = ir.Add(ir.GetRegister(n), ir.Imm32(4)); - const auto writeback_address = ir.Add(ir.GetRegister(n), ir.Imm32(u32(4 * mcl::bit::count_ones(list)))); + const auto writeback_address = ir.Add(ir.GetRegister(n), ir.Imm32(u32(4 * std::popcount(list)))); return STMHelper(ir, W, n, list, start_address, writeback_address); } diff --git a/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/thumb16.cpp b/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/thumb16.cpp index a8c75e22b9..082cd30db2 100644 --- a/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/thumb16.cpp +++ b/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/thumb16.cpp @@ -688,7 +688,7 @@ bool TranslatorVisitor::thumb16_NOP() { // IT{{{}}} bool TranslatorVisitor::thumb16_IT(Imm<8> imm8) { ASSERT((imm8.Bits<0, 3>() != 0b0000) && "Decode Error"); - if (imm8.Bits<4, 7>() == 0b1111 || (imm8.Bits<4, 7>() == 0b1110 && mcl::bit::count_ones(imm8.Bits<0, 3>()) != 1)) { + if (imm8.Bits<4, 7>() == 0b1111 || (imm8.Bits<4, 7>() == 0b1110 && std::popcount(imm8.Bits<0, 3>()) != 1)) { return UnpredictableInstruction(); } if (ir.current_location.IT().IsInITBlock()) { @@ -738,11 +738,11 @@ bool TranslatorVisitor::thumb16_PUSH(bool M, RegList reg_list) { if (M) { reg_list |= 1 << 14; } - if (mcl::bit::count_ones(reg_list) < 1) { + if (std::popcount(reg_list) < 1) { return UnpredictableInstruction(); } - const u32 num_bytes_to_push = static_cast(4 * mcl::bit::count_ones(reg_list)); + const u32 num_bytes_to_push = static_cast(4 * std::popcount(reg_list)); const auto final_address = ir.Sub(ir.GetRegister(Reg::SP), ir.Imm32(num_bytes_to_push)); auto address = final_address; for (size_t i = 0; i < 16; i++) { @@ -764,7 +764,7 @@ bool TranslatorVisitor::thumb16_POP(bool P, RegList reg_list) { if (P) { reg_list |= 1 << 15; } - if (mcl::bit::count_ones(reg_list) < 1) { + if (std::popcount(reg_list) < 1) { return UnpredictableInstruction(); } @@ -851,10 +851,10 @@ bool TranslatorVisitor::thumb16_BKPT(Imm<8> /*imm8*/) { // STM !, bool TranslatorVisitor::thumb16_STMIA(Reg n, RegList reg_list) { - if (mcl::bit::count_ones(reg_list) == 0) { + if (std::popcount(reg_list) == 0) { return UnpredictableInstruction(); } - if (mcl::bit::get_bit(static_cast(n), reg_list) && n != static_cast(mcl::bit::lowest_set_bit(reg_list))) { + if (mcl::bit::get_bit(static_cast(n), reg_list) && n != static_cast(std::countr_zero(reg_list))) { return UnpredictableInstruction(); } @@ -873,7 +873,7 @@ bool TranslatorVisitor::thumb16_STMIA(Reg n, RegList reg_list) { // LDM !, bool TranslatorVisitor::thumb16_LDMIA(Reg n, RegList reg_list) { - if (mcl::bit::count_ones(reg_list) == 0) { + if (std::popcount(reg_list) == 0) { return UnpredictableInstruction(); } diff --git a/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/thumb32_branch.cpp b/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/thumb32_branch.cpp index 83734f5c70..1c2c4792c1 100644 --- a/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/thumb32_branch.cpp +++ b/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/thumb32_branch.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + /* This file is part of the dynarmic project. * Copyright (c) 2016 MerryMage * SPDX-License-Identifier: 0BSD diff --git a/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/thumb32_load_store_multiple.cpp b/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/thumb32_load_store_multiple.cpp index b68a2cb7c5..e829c6fb42 100644 --- a/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/thumb32_load_store_multiple.cpp +++ b/src/dynarmic/src/dynarmic/frontend/A32/translate/impl/thumb32_load_store_multiple.cpp @@ -53,7 +53,7 @@ static bool STMHelper(A32::IREmitter& ir, bool W, Reg n, u32 list, const IR::U32 bool TranslatorVisitor::thumb32_LDMDB(bool W, Reg n, Imm<16> reg_list) { const auto regs_imm = reg_list.ZeroExtend(); - const auto num_regs = static_cast(mcl::bit::count_ones(regs_imm)); + const auto num_regs = static_cast(std::popcount(regs_imm)); if (n == Reg::PC || num_regs < 2) { return UnpredictableInstruction(); @@ -78,7 +78,7 @@ bool TranslatorVisitor::thumb32_LDMDB(bool W, Reg n, Imm<16> reg_list) { bool TranslatorVisitor::thumb32_LDMIA(bool W, Reg n, Imm<16> reg_list) { const auto regs_imm = reg_list.ZeroExtend(); - const auto num_regs = static_cast(mcl::bit::count_ones(regs_imm)); + const auto num_regs = static_cast(std::popcount(regs_imm)); if (n == Reg::PC || num_regs < 2) { return UnpredictableInstruction(); @@ -111,7 +111,7 @@ bool TranslatorVisitor::thumb32_PUSH(Imm<15> reg_list) { bool TranslatorVisitor::thumb32_STMIA(bool W, Reg n, Imm<15> reg_list) { const auto regs_imm = reg_list.ZeroExtend(); - const auto num_regs = static_cast(mcl::bit::count_ones(regs_imm)); + const auto num_regs = static_cast(std::popcount(regs_imm)); if (n == Reg::PC || num_regs < 2) { return UnpredictableInstruction(); @@ -130,7 +130,7 @@ bool TranslatorVisitor::thumb32_STMIA(bool W, Reg n, Imm<15> reg_list) { bool TranslatorVisitor::thumb32_STMDB(bool W, Reg n, Imm<15> reg_list) { const auto regs_imm = reg_list.ZeroExtend(); - const auto num_regs = static_cast(mcl::bit::count_ones(regs_imm)); + const auto num_regs = static_cast(std::popcount(regs_imm)); if (n == Reg::PC || num_regs < 2) { return UnpredictableInstruction(); diff --git a/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/impl.cpp b/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/impl.cpp index f3ecd7c604..998fa4c13b 100644 --- a/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/impl.cpp +++ b/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/impl.cpp @@ -55,7 +55,7 @@ std::optional TranslatorVisitor::DecodeBitMasks(boo const size_t esize = size_t{1} << len; const u64 welem = mcl::bit::ones(S + 1); const u64 telem = mcl::bit::ones(d + 1); - const u64 wmask = mcl::bit::rotate_right(mcl::bit::replicate_element(esize, welem), R); + const u64 wmask = std::rotr(mcl::bit::replicate_element(esize, welem), R); const u64 tmask = mcl::bit::replicate_element(esize, telem); return BitMasks{wmask, tmask}; diff --git a/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/simd_copy.cpp b/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/simd_copy.cpp index 9354b54fa5..0d6ec29417 100644 --- a/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/simd_copy.cpp +++ b/src/dynarmic/src/dynarmic/frontend/A64/translate/impl/simd_copy.cpp @@ -13,7 +13,7 @@ namespace Dynarmic::A64 { bool TranslatorVisitor::DUP_elt_1(Imm<5> imm5, Vec Vn, Vec Vd) { - const size_t size = mcl::bit::lowest_set_bit(imm5.ZeroExtend()); + const size_t size = std::countr_zero(imm5.ZeroExtend()); if (size > 3) { return ReservedValue(); } @@ -30,7 +30,7 @@ bool TranslatorVisitor::DUP_elt_1(Imm<5> imm5, Vec Vn, Vec Vd) { } bool TranslatorVisitor::DUP_elt_2(bool Q, Imm<5> imm5, Vec Vn, Vec Vd) { - const size_t size = mcl::bit::lowest_set_bit(imm5.ZeroExtend()); + const size_t size = std::countr_zero(imm5.ZeroExtend()); if (size > 3) { return ReservedValue(); } @@ -51,7 +51,7 @@ bool TranslatorVisitor::DUP_elt_2(bool Q, Imm<5> imm5, Vec Vn, Vec Vd) { } bool TranslatorVisitor::DUP_gen(bool Q, Imm<5> imm5, Reg Rn, Vec Vd) { - const size_t size = mcl::bit::lowest_set_bit(imm5.ZeroExtend()); + const size_t size = std::countr_zero(imm5.ZeroExtend()); if (size > 3) { return ReservedValue(); } @@ -73,7 +73,7 @@ bool TranslatorVisitor::DUP_gen(bool Q, Imm<5> imm5, Reg Rn, Vec Vd) { } bool TranslatorVisitor::SMOV(bool Q, Imm<5> imm5, Vec Vn, Reg Rd) { - const size_t size = mcl::bit::lowest_set_bit(imm5.ZeroExtend()); + const size_t size = std::countr_zero(imm5.ZeroExtend()); if (size == 2 && !Q) { return UnallocatedEncoding(); } @@ -96,7 +96,7 @@ bool TranslatorVisitor::SMOV(bool Q, Imm<5> imm5, Vec Vn, Reg Rd) { } bool TranslatorVisitor::UMOV(bool Q, Imm<5> imm5, Vec Vn, Reg Rd) { - const size_t size = mcl::bit::lowest_set_bit(imm5.ZeroExtend()); + const size_t size = std::countr_zero(imm5.ZeroExtend()); if (size < 3 && Q) { return UnallocatedEncoding(); } @@ -123,7 +123,7 @@ bool TranslatorVisitor::UMOV(bool Q, Imm<5> imm5, Vec Vn, Reg Rd) { } bool TranslatorVisitor::INS_gen(Imm<5> imm5, Reg Rn, Vec Vd) { - const size_t size = mcl::bit::lowest_set_bit(imm5.ZeroExtend()); + const size_t size = std::countr_zero(imm5.ZeroExtend()); if (size > 3) { return ReservedValue(); } @@ -140,7 +140,7 @@ bool TranslatorVisitor::INS_gen(Imm<5> imm5, Reg Rn, Vec Vd) { } bool TranslatorVisitor::INS_elt(Imm<5> imm5, Imm<4> imm4, Vec Vn, Vec Vd) { - const size_t size = mcl::bit::lowest_set_bit(imm5.ZeroExtend()); + const size_t size = std::countr_zero(imm5.ZeroExtend()); if (size > 3) { return ReservedValue(); } diff --git a/src/dynarmic/src/dynarmic/frontend/decoder/matcher.h b/src/dynarmic/src/dynarmic/frontend/decoder/matcher.h index 49095f67e3..77fb7d941f 100644 --- a/src/dynarmic/src/dynarmic/frontend/decoder/matcher.h +++ b/src/dynarmic/src/dynarmic/frontend/decoder/matcher.h @@ -9,6 +9,7 @@ #pragma once #include +#include #include "common/assert.h" @@ -31,6 +32,10 @@ public: , expected{expected} {} + constexpr Matcher(std::tuple t) noexcept + : Matcher(std::get<0>(t), std::get<1>(t)) + {} + /// @brief Gets the mask for this instruction. constexpr inline T GetMask() const noexcept { return mask; diff --git a/src/dynarmic/src/dynarmic/interface/A32/config.h b/src/dynarmic/src/dynarmic/interface/A32/config.h index 5a97fb69f3..49063ed619 100644 --- a/src/dynarmic/src/dynarmic/interface/A32/config.h +++ b/src/dynarmic/src/dynarmic/interface/A32/config.h @@ -66,7 +66,9 @@ struct UserCallbacks : public TranslateCallbacks { // All reads through this callback are 4-byte aligned. // Memory must be interpreted as little endian. - std::optional MemoryReadCode(VAddr vaddr) override { return MemoryRead32(vaddr); } + std::optional MemoryReadCode(VAddr vaddr) override { + return std::uint32_t(MemoryRead(vaddr, sizeof(std::uint32_t))); + } // This function is called before the instruction at pc is read. // IR code can be emitted by the callee prior to instruction handling. @@ -80,16 +82,10 @@ struct UserCallbacks : public TranslateCallbacks { // Reads through these callbacks may not be aligned. // Memory must be interpreted as if ENDIANSTATE == 0, endianness will be corrected by the JIT. - virtual std::uint8_t MemoryRead8(VAddr vaddr) = 0; - virtual std::uint16_t MemoryRead16(VAddr vaddr) = 0; - virtual std::uint32_t MemoryRead32(VAddr vaddr) = 0; - virtual std::uint64_t MemoryRead64(VAddr vaddr) = 0; + virtual std::uint64_t MemoryRead(VAddr vaddr, std::size_t size) = 0; // Writes through these callbacks may not be aligned. - virtual void MemoryWrite8(VAddr vaddr, std::uint8_t value) = 0; - virtual void MemoryWrite16(VAddr vaddr, std::uint16_t value) = 0; - virtual void MemoryWrite32(VAddr vaddr, std::uint32_t value) = 0; - virtual void MemoryWrite64(VAddr vaddr, std::uint64_t value) = 0; + virtual void MemoryWrite(VAddr vaddr, std::uint64_t value, std::size_t size) = 0; // Writes through these callbacks may not be aligned. virtual bool MemoryWriteExclusive8(VAddr /*vaddr*/, std::uint8_t /*value*/, std::uint8_t /*expected*/) { return false; } @@ -159,14 +155,23 @@ struct UserConfig { /// Maximum size is limited by the maximum length of a x86_64 / arm64 jump. std::uint32_t code_cache_size = 128 * 1024 * 1024; // bytes - /// Masks out the first N bits in host pointers from the page table. + /// Applies a bit mask to the bits in host pointers from the page table. /// The intention behind this is to allow users of Dynarmic to pack attributes in the /// same integer and update the pointer attribute pair atomically. - /// If the configured value is 3, all pointers will be forcefully aligned to 8 bytes. - std::int32_t page_table_pointer_mask_bits = 0; + /// If the configured value is ~(0b111ULL), all pointers will be forcefully aligned to 8 bytes. + std::uint64_t page_table_pointer_mask = 0; - // Log2 of the size per page entry, value should be either 3 or 4 - std::size_t page_table_log2_stride = 3; + /// Log2 of the size per page entry, value should be either 3 or 4 + std::uint32_t page_table_log2_stride = 3; + + /// Setting this value has Dynarmic check the specified bit of the page pointer provided by page table. + /// If the bit is set to 1, Dynarmic will treat it as unmapped. + /// This bit should be included as part of `page_table_pointer_mask_bits`. + std::optional page_table_marked_bit = std::nullopt; + + /// If this value is set, Dynarmic will sign extend the page table pointer by this bit. + /// Useful for compacting bits into the page table and should be used as part of `page_table_pointer_mask`. + std::optional page_table_sign_extension = std::nullopt; /// Select the architecture version to use. /// There are minor behavioural differences between versions. diff --git a/src/dynarmic/src/dynarmic/interface/A64/config.h b/src/dynarmic/src/dynarmic/interface/A64/config.h index 83c1593fd8..859aeba709 100644 --- a/src/dynarmic/src/dynarmic/interface/A64/config.h +++ b/src/dynarmic/src/dynarmic/interface/A64/config.h @@ -89,20 +89,16 @@ struct UserCallbacks { // All reads through this callback are 4-byte aligned. // Memory must be interpreted as little endian. - virtual std::optional MemoryReadCode(VAddr vaddr) { return MemoryRead32(vaddr); } + virtual std::optional MemoryReadCode(VAddr vaddr) { + return std::uint32_t(MemoryRead(vaddr, sizeof(std::uint32_t))); + } // Reads through these callbacks may not be aligned. - virtual std::uint8_t MemoryRead8(VAddr vaddr) = 0; - virtual std::uint16_t MemoryRead16(VAddr vaddr) = 0; - virtual std::uint32_t MemoryRead32(VAddr vaddr) = 0; - virtual std::uint64_t MemoryRead64(VAddr vaddr) = 0; + virtual std::uint64_t MemoryRead(VAddr vaddr, std::size_t size) = 0; virtual Vector MemoryRead128(VAddr vaddr) = 0; // Writes through these callbacks may not be aligned. - virtual void MemoryWrite8(VAddr vaddr, std::uint8_t value) = 0; - virtual void MemoryWrite16(VAddr vaddr, std::uint16_t value) = 0; - virtual void MemoryWrite32(VAddr vaddr, std::uint32_t value) = 0; - virtual void MemoryWrite64(VAddr vaddr, std::uint64_t value) = 0; + virtual void MemoryWrite(VAddr vaddr, std::uint64_t value, std::size_t size) = 0; virtual void MemoryWrite128(VAddr vaddr, Vector value) = 0; // Writes through these callbacks may not be aligned. @@ -173,14 +169,23 @@ struct UserConfig { /// This is only used if page_table is not nullptr. std::uint32_t page_table_address_space_bits = 36; - /// Masks out the first N bits in host pointers from the page table. + /// Applies a bit mask to the bits in host pointers from the page table. /// The intention behind this is to allow users of Dynarmic to pack attributes in the /// same integer and update the pointer attribute pair atomically. - /// If the configured value is 3, all pointers will be forcefully aligned to 8 bytes. - std::int32_t page_table_pointer_mask_bits = 0; + /// If the configured value is ~(0b111ULL), all pointers will be forcefully aligned to 8 bytes. + std::uint64_t page_table_pointer_mask = 0; - // Log2 of the size per page entry, value should be either 3 or 4 - std::size_t page_table_log2_stride = 3; + /// Log2 of the size per page entry, value should be either 3 or 4 + std::uint32_t page_table_log2_stride = 3; + + /// Setting this value has Dynarmic check the specified bit of the page pointer provided by page table. + /// If the bit is set to 1, Dynarmic will treat it as unmapped. + /// This bit should be included as part of `page_table_pointer_mask`. + std::optional page_table_marked_bit = std::nullopt; + + /// If this value is set, Dynarmic will sign extend the page table pointer by this bit. + /// Useful for compacting bits into the page table and should be used as part of `page_table_pointer_mask`. + std::optional page_table_sign_extension = std::nullopt; /// Counter-timer frequency register. The value of the register is not interpreted by /// dynarmic. diff --git a/src/dynarmic/src/dynarmic/interface/exclusive_monitor.h b/src/dynarmic/src/dynarmic/interface/exclusive_monitor.h index 566743c767..05b067bdd8 100644 --- a/src/dynarmic/src/dynarmic/interface/exclusive_monitor.h +++ b/src/dynarmic/src/dynarmic/interface/exclusive_monitor.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -47,8 +50,8 @@ public: /// the exclusive state for processors if their exclusive region(s) /// contain [address, address+size). template + requires std::is_trivially_copyable_v bool DoExclusiveOperation(size_t processor_id, VAddr address, Function op) { - static_assert(std::is_trivially_copyable_v); if (!CheckAndClear(processor_id, address)) { return false; } diff --git a/src/dynarmic/src/dynarmic/ir/basic_block.cpp b/src/dynarmic/src/dynarmic/ir/basic_block.cpp index 284f115328..4a7b2bf261 100644 --- a/src/dynarmic/src/dynarmic/ir/basic_block.cpp +++ b/src/dynarmic/src/dynarmic/ir/basic_block.cpp @@ -66,43 +66,44 @@ void Block::Reset(LocationDescriptor location_) noexcept { location = location_; end_location = location_; cond = Cond::AL; - terminal = Term::Invalid{}; + terminal = std::monostate{}; cond_failed_cycle_count = 0; cycle_count = 0; ASSERT(instructions.size() == 0); } -static std::string TerminalToString(const Terminal& terminal_variant) noexcept { - struct : boost::static_visitor { - std::string operator()(const Term::Invalid&) const { - return ""; - } - std::string operator()(const Term::ReturnToDispatch&) const { - return "ReturnToDispatch{}"; - } - std::string operator()(const Term::LinkBlock& terminal) const { - return fmt::format("LinkBlock{{{}}}", terminal.next); - } - std::string operator()(const Term::LinkBlockFast& terminal) const { - return fmt::format("LinkBlockFast{{{}}}", terminal.next); - } - std::string operator()(const Term::PopRSBHint&) const { - return "PopRSBHint{}"; - } - std::string operator()(const Term::FastDispatchHint&) const { - return "FastDispatchHint{}"; - } - std::string operator()(const Term::If& terminal) const { - return fmt::format("If{{{}, {}, {}}}", A64::CondToString(terminal.if_), TerminalToString(terminal.then_), TerminalToString(terminal.else_)); - } - std::string operator()(const Term::CheckBit& terminal) const { - return fmt::format("CheckBit{{{}, {}}}", TerminalToString(terminal.then_), TerminalToString(terminal.else_)); - } - std::string operator()(const Term::CheckHalt& terminal) const { - return fmt::format("CheckHalt{{{}}}", TerminalToString(terminal.else_)); - } - } visitor; - return boost::apply_visitor(visitor, terminal_variant); +static std::string TerminalToString(const Term::Terminal& terminal_variant) noexcept { + // struct : boost::static_visitor { + // std::string operator()(const std::monostate&) const { + // return ""; + // } + // std::string operator()(const Term::ReturnToDispatch&) const { + // return "ReturnToDispatch{}"; + // } + // std::string operator()(const Term::LinkBlock& terminal) const { + // return fmt::format("LinkBlock{{{}}}", terminal.next); + // } + // std::string operator()(const Term::LinkBlockFast& terminal) const { + // return fmt::format("LinkBlockFast{{{}}}", terminal.next); + // } + // std::string operator()(const Term::PopRSBHint&) const { + // return "PopRSBHint{}"; + // } + // std::string operator()(const Term::FastDispatchHint&) const { + // return "FastDispatchHint{}"; + // } + // std::string operator()(const Term::If& terminal) const { + // return fmt::format("If{{{}, {}, {}}}", A64::CondToString(terminal.if_), TerminalToString(terminal.then_), TerminalToString(terminal.else_)); + // } + // std::string operator()(const Term::CheckBit& terminal) const { + // return fmt::format("CheckBit{{{}, {}}}", TerminalToString(terminal.then_), TerminalToString(terminal.else_)); + // } + // std::string operator()(const Term::CheckHalt& terminal) const { + // return fmt::format("CheckHalt{{{}}}", TerminalToString(terminal.else_)); + // } + // } visitor; + // return boost::apply_visitor(visitor, terminal_variant); + return ""; } std::string DumpBlock(const IR::Block& block) noexcept { diff --git a/src/dynarmic/src/dynarmic/ir/basic_block.h b/src/dynarmic/src/dynarmic/ir/basic_block.h index 1518903d2a..37db8745a4 100644 --- a/src/dynarmic/src/dynarmic/ir/basic_block.h +++ b/src/dynarmic/src/dynarmic/ir/basic_block.h @@ -114,22 +114,22 @@ public: } /// Gets the terminal instruction for this basic block. - inline Terminal GetTerminal() const noexcept { + inline Term::Terminal GetTerminal() const noexcept { return terminal; } /// Sets the terminal instruction for this basic block. - inline void SetTerminal(Terminal term) noexcept { + inline void SetTerminal(Term::Terminal term) noexcept { ASSERT(!HasTerminal() && "Terminal has already been set."); terminal = std::move(term); } /// Replaces the terminal instruction for this basic block. - inline void ReplaceTerminal(Terminal term) noexcept { + inline void ReplaceTerminal(Term::Terminal term) noexcept { ASSERT(HasTerminal() && "Terminal has not been set."); terminal = std::move(term); } /// Determines whether or not this basic block has a terminal instruction. inline bool HasTerminal() const noexcept { - return terminal.which() != 0; + return !std::holds_alternative(terminal); } /// Gets a mutable reference to the cycle count for this basic block. @@ -156,7 +156,7 @@ public: /// Conditional to pass in order to execute this block Cond cond = Cond::AL; /// Terminal instruction of this block. - Terminal terminal = Term::Invalid{}; + Term::Terminal terminal = std::monostate{}; /// Number of cycles this block takes to execute if the conditional fails. size_t cond_failed_cycle_count = 0; /// Number of cycles this block takes to execute. diff --git a/src/dynarmic/src/dynarmic/ir/ir_emitter.h b/src/dynarmic/src/dynarmic/ir/ir_emitter.h index c035af2708..23ec707ee9 100644 --- a/src/dynarmic/src/dynarmic/ir/ir_emitter.h +++ b/src/dynarmic/src/dynarmic/ir/ir_emitter.h @@ -2943,7 +2943,7 @@ public: Inst(Opcode::CallHostFunction, Imm64(std::bit_cast(fn)), arg1, arg2, arg3); } - void SetTerm(const Terminal& terminal) { + void SetTerm(const Term::Terminal& terminal) { block.SetTerminal(terminal); } diff --git a/src/dynarmic/src/dynarmic/ir/opt_passes.cpp b/src/dynarmic/src/dynarmic/ir/opt_passes.cpp index 999d4c49bc..b32bbd517a 100644 --- a/src/dynarmic/src/dynarmic/ir/opt_passes.cpp +++ b/src/dynarmic/src/dynarmic/ir/opt_passes.cpp @@ -9,8 +9,9 @@ #include #include #include +#include -#include +#include "common/container/unordered_map.h" #include "boost/container/small_vector.hpp" #include "dynarmic/frontend/A32/a32_ir_emitter.h" #include "dynarmic/frontend/A32/a32_location_descriptor.h" @@ -28,7 +29,6 @@ #include "dynarmic/ir/opt_passes.h" #include "dynarmic/ir/type.h" #include "dynarmic/mcl/bit.hpp" -#include "dynarmic/mcl/bit.hpp" namespace Dynarmic::Optimization { @@ -40,7 +40,7 @@ static void ConstantMemoryReads(IR::Block& block, A32::UserCallbacks* cb) { if (inst.AreAllArgsImmediates()) { const u32 vaddr = inst.GetArg(1).GetU32(); if (cb->IsReadOnlyMemory(vaddr)) { - const u8 value_from_memory = cb->MemoryRead8(vaddr); + const u8 value_from_memory = u8(cb->MemoryRead(vaddr, sizeof(u8))); inst.ReplaceUsesWith(IR::Value{value_from_memory}); } } @@ -51,7 +51,7 @@ static void ConstantMemoryReads(IR::Block& block, A32::UserCallbacks* cb) { if (inst.AreAllArgsImmediates()) { const u32 vaddr = inst.GetArg(1).GetU32(); if (cb->IsReadOnlyMemory(vaddr)) { - const u16 value_from_memory = cb->MemoryRead16(vaddr); + const u16 value_from_memory = u16(cb->MemoryRead(vaddr, sizeof(u16))); inst.ReplaceUsesWith(IR::Value{value_from_memory}); } } @@ -62,7 +62,7 @@ static void ConstantMemoryReads(IR::Block& block, A32::UserCallbacks* cb) { if (inst.AreAllArgsImmediates()) { const u32 vaddr = inst.GetArg(1).GetU32(); if (cb->IsReadOnlyMemory(vaddr)) { - const u32 value_from_memory = cb->MemoryRead32(vaddr); + const u32 value_from_memory = u32(cb->MemoryRead(vaddr, sizeof(u32))); inst.ReplaceUsesWith(IR::Value{value_from_memory}); } } @@ -73,7 +73,7 @@ static void ConstantMemoryReads(IR::Block& block, A32::UserCallbacks* cb) { if (inst.AreAllArgsImmediates()) { const u32 vaddr = inst.GetArg(1).GetU32(); if (cb->IsReadOnlyMemory(vaddr)) { - const u64 value_from_memory = cb->MemoryRead64(vaddr); + const u64 value_from_memory = u64(cb->MemoryRead(vaddr, sizeof(u64))); inst.ReplaceUsesWith(IR::Value{value_from_memory}); } } @@ -787,7 +787,7 @@ static void FoldCountLeadingZeros(IR::Inst& inst, bool is_32_bit) { /// Folds division operations based on the following: /// /// 1. x / 0 -> 0 (NOTE: This is an ARM-specific behavior defined in the architecture reference manual) -/// 2a. 0x8000_0000 / 0xFFFF_FFFF -> 0x8000_0000 (NOTE: More ARM bullshit) +/// 2a. 0x8000_0000 / 0xFFFF_FFFF -> 0x8000_0000 (NOTE: More ARM errata) /// 2b. 0x8000_0000_0000_0000 / 0xFFFF_FFFF_FFFF_FFFF -> 0x8000_0000_0000_0000 /// 3. imm_x / imm_y -> result /// 4. x / 1 -> x @@ -1074,12 +1074,12 @@ static void ConstantPropagation(IR::Block& block) { break; case Op::BitRotateRight32: if (FoldShifts(inst)) { - ReplaceUsesWith(inst, true, mcl::bit::rotate_right(inst.GetArg(0).GetU32(), inst.GetArg(1).GetU8())); + ReplaceUsesWith(inst, true, std::rotr(inst.GetArg(0).GetU32(), inst.GetArg(1).GetU8())); } break; case Op::BitRotateRight64: if (FoldShifts(inst)) { - ReplaceUsesWith(inst, false, mcl::bit::rotate_right(inst.GetArg(0).GetU64(), inst.GetArg(1).GetU8())); + ReplaceUsesWith(inst, false, std::rotr(inst.GetArg(0).GetU64(), inst.GetArg(1).GetU8())); } break; case Op::LogicalShiftLeftMasked32: @@ -1114,12 +1114,12 @@ static void ConstantPropagation(IR::Block& block) { break; case Op::RotateRightMasked32: if (inst.AreAllArgsImmediates()) { - ReplaceUsesWith(inst, true, mcl::bit::rotate_right(inst.GetArg(0).GetU32(), inst.GetArg(1).GetU32())); + ReplaceUsesWith(inst, true, std::rotr(inst.GetArg(0).GetU32(), inst.GetArg(1).GetU32())); } break; case Op::RotateRightMasked64: if (inst.AreAllArgsImmediates()) { - ReplaceUsesWith(inst, false, mcl::bit::rotate_right(inst.GetArg(0).GetU64(), inst.GetArg(1).GetU64())); + ReplaceUsesWith(inst, false, std::rotr(inst.GetArg(0).GetU64(), inst.GetArg(1).GetU64())); } break; case Op::Add32: @@ -1433,7 +1433,7 @@ static void VerificationPass(const IR::Block& block) { ASSERT(IR::AreTypesCompatible(t1, t2)); } } - ankerl::unordered_dense::map actual_uses; + ::Common::unordered_map actual_uses; for (auto const& inst : block.instructions) { for (size_t i = 0; i < inst.NumArgs(); i++) if (IR::Value const arg = inst.GetArg(i); !arg.IsImmediate()) diff --git a/src/dynarmic/src/dynarmic/ir/terminal.h b/src/dynarmic/src/dynarmic/ir/terminal.h index 9b91b27382..b8de480939 100644 --- a/src/dynarmic/src/dynarmic/ir/terminal.h +++ b/src/dynarmic/src/dynarmic/ir/terminal.h @@ -8,7 +8,7 @@ #pragma once -#include +#include #include "common/common_types.h" #include "dynarmic/ir/cond.h" @@ -17,106 +17,89 @@ namespace Dynarmic::IR { namespace Term { -struct Invalid {}; - -/** - * This terminal instruction returns control to the dispatcher. - * The dispatcher will use the current cpu state to determine what comes next. - */ +/// This terminal instruction returns control to the dispatcher. +/// The dispatcher will use the current cpu state to determine what comes next. struct ReturnToDispatch {}; -/** - * This terminal instruction jumps to the basic block described by `next` if we have enough - * cycles remaining. If we do not have enough cycles remaining, we return to the - * dispatcher, which will return control to the host. - */ +/// This terminal instruction jumps to the basic block described by `next` if we have enough +/// cycles remaining. If we do not have enough cycles remaining, we return to the +/// dispatcher, which will return control to the host. struct LinkBlock { - explicit LinkBlock(const LocationDescriptor& next_) - : next(next_) {} + explicit LinkBlock(const LocationDescriptor& next_) : next(next_) {} LocationDescriptor next; ///< Location descriptor for next block. }; -/** - * This terminal instruction jumps to the basic block described by `next` unconditionally. - * This is an optimization and MUST only be emitted when this is guaranteed not to result - * in hanging, even in the face of other optimizations. (In practice, this means that only - * forward jumps to short-ish blocks would use this instruction.) - * A backend that doesn't support this optimization may choose to implement this exactly - * as LinkBlock. - */ +/// This terminal instruction jumps to the basic block described by `next` unconditionally. +/// This is an optimization and MUST only be emitted when this is guaranteed not to result +/// in hanging, even in the face of other optimizations. (In practice, this means that only +/// forward jumps to short-ish blocks would use this instruction.) +/// A backend that doesn't support this optimization may choose to implement this exactly +/// as LinkBlock. struct LinkBlockFast { - explicit LinkBlockFast(const LocationDescriptor& next_) - : next(next_) {} + explicit LinkBlockFast(const LocationDescriptor& next_) : next(next_) {} LocationDescriptor next; ///< Location descriptor for next block. }; -/** - * This terminal instruction checks the top of the Return Stack Buffer against the current - * location descriptor. If RSB lookup fails, control is returned to the dispatcher. - * This is an optimization for faster function calls. A backend that doesn't support - * this optimization or doesn't have a RSB may choose to implement this exactly as - * ReturnToDispatch. - */ +/// This terminal instruction checks the top of the Return Stack Buffer against the current +/// location descriptor. If RSB lookup fails, control is returned to the dispatcher. +/// This is an optimization for faster function calls. A backend that doesn't support +/// this optimization or doesn't have a RSB may choose to implement this exactly as +/// ReturnToDispatch. struct PopRSBHint {}; -/** - * This terminal instruction performs a lookup of the current location descriptor in the - * fast dispatch lookup table. A backend that doesn't support this optimization may choose - * to implement this exactly as ReturnToDispatch. - */ +/// This terminal instruction performs a lookup of the current location descriptor in the +/// fast dispatch lookup table. A backend that doesn't support this optimization may choose +/// to implement this exactly as ReturnToDispatch. struct FastDispatchHint {}; struct If; struct CheckBit; struct CheckHalt; -/// A Terminal is the terminal instruction in a MicroBlock. -using Terminal = boost::variant< - Invalid, + +/// Non recursive kind of terminal +using LeafTerminal = std::variant< + std::monostate, ReturnToDispatch, LinkBlock, LinkBlockFast, PopRSBHint, - FastDispatchHint, - boost::recursive_wrapper, - boost::recursive_wrapper, - boost::recursive_wrapper>; + FastDispatchHint +>; -/** - * This terminal instruction conditionally executes one terminal or another depending - * on the run-time state of the ARM flags. - */ +/// A Terminal is the terminal instruction in a MicroBlock. +using Terminal = std::variant< + std::monostate, + LeafTerminal, + If, + CheckBit, + CheckHalt +>; + +/// This terminal instruction conditionally executes one terminal or another depending +/// on the run-time state of the ARM flags. struct If { - If(Cond if_, Terminal then_, Terminal else_) - : if_(if_), then_(std::move(then_)), else_(std::move(else_)) {} + explicit If(Cond if_, LeafTerminal then_, LeafTerminal else_) : if_(if_), then_(std::move(then_)), else_(std::move(else_)) {} Cond if_; - Terminal then_; - Terminal else_; + LeafTerminal then_; + LeafTerminal else_; }; -/** - * This terminal instruction conditionally executes one terminal or another depending - * on the run-time state of the check bit. - * then_ is executed if the check bit is non-zero, otherwise else_ is executed. - */ +/// This terminal instruction conditionally executes one terminal or another depending +/// on the run-time state of the check bit. +/// then_ is executed if the check bit is non-zero, otherwise else_ is executed. struct CheckBit { - CheckBit(Terminal then_, Terminal else_) - : then_(std::move(then_)), else_(std::move(else_)) {} - Terminal then_; - Terminal else_; + explicit CheckBit(LeafTerminal then_, LeafTerminal else_) : then_(std::move(then_)), else_(std::move(else_)) {} + LeafTerminal then_; + LeafTerminal else_; }; -/** - * This terminal instruction checks if a halt was requested. If it wasn't, else_ is - * executed. - */ +/// This terminal instruction checks if a halt was requested. If it wasn't, else_ is +/// executed. struct CheckHalt { - explicit CheckHalt(Terminal else_) - : else_(std::move(else_)) {} - Terminal else_; + explicit CheckHalt(LeafTerminal else_) : else_(std::move(else_)) {} + LeafTerminal else_; }; } // namespace Term -using Term::Terminal; - } // namespace Dynarmic::IR diff --git a/src/dynarmic/src/dynarmic/mcl/bit.hpp b/src/dynarmic/src/dynarmic/mcl/bit.hpp index bbedffd8ae..ea4d6c057f 100644 --- a/src/dynarmic/src/dynarmic/mcl/bit.hpp +++ b/src/dynarmic/src/dynarmic/mcl/bit.hpp @@ -8,26 +8,15 @@ #include #include #include +#include +#include #include "common/common_types.h" #include "common/assert.h" namespace mcl { -namespace detail { -template -concept SameHelper = std::is_same_v; -} // namespace detail -template -concept SameAs = detail::SameHelper && detail::SameHelper; -template -concept IsAnyOf = (SameAs || ...); -/// Integral upon which bit operations can be safely performed. -template -concept BitIntegral = IsAnyOf; - template constexpr std::size_t bitsizeof = CHAR_BIT * sizeof(T); - } // namespace mcl namespace mcl::bit { @@ -71,58 +60,37 @@ constexpr u64 swap_words_64(u64 value) { | ((value & 0x00000000ffffffffull) << 32); } -template -constexpr T rotate_right(T x, size_t amount) { - amount %= bitsizeof; - if (amount == 0) { - return x; - } - return static_cast((x >> amount) | (x << (bitsizeof - amount))); -} - -template -constexpr T rotate_left(T x, size_t amount) { - amount %= bitsizeof; - if (amount == 0) { - return x; - } - return static_cast((x << amount) | (x >> (bitsizeof - amount))); -} - /// Create a mask with `count` number of one bits. -template +template constexpr T ones() { static_assert(count <= bitsizeof, "count larger than bitsize of T"); - if constexpr (count == 0) { return 0; } else { - return static_cast(~static_cast(0)) >> (bitsizeof - count); + return T(~T(0)) >> (bitsizeof - count); } } /// Create a mask with `count` number of one bits. -template +template constexpr T ones(size_t count) { ASSERT(count <= bitsizeof && "count larger than bitsize of T"); - if (count == 0) { + if (count == 0) return 0; - } - return static_cast(~static_cast(0)) >> (bitsizeof - count); + return T(~T(0)) >> (bitsizeof - count); } /// Create a mask of type T for bits [begin_bit, end_bit] inclusive. -template +template constexpr T mask() { static_assert(begin_bit <= end_bit, "invalid bit range (position of beginning bit cannot be greater than that of end bit)"); static_assert(begin_bit < bitsizeof, "begin_bit must be smaller than size of T"); static_assert(end_bit < bitsizeof, "end_bit must be smaller than size of T"); - return ones() << begin_bit; } /// Create a mask of type T for bits [begin_bit, end_bit] inclusive. -template +template constexpr T mask(size_t begin_bit, size_t end_bit) { ASSERT(begin_bit <= end_bit && "invalid bit range (position of beginning bit cannot be greater than that of end bit)"); ASSERT(begin_bit < bitsizeof && "begin_bit must be smaller than size of T"); @@ -131,101 +99,100 @@ constexpr T mask(size_t begin_bit, size_t end_bit) { } /// Extract bits [begin_bit, end_bit] inclusive from value of type T. -template +template constexpr T get_bits(T value) { constexpr T m = mask(); return (value & m) >> begin_bit; } /// Extract bits [begin_bit, end_bit] inclusive from value of type T. -template +template constexpr T get_bits(size_t begin_bit, size_t end_bit, T value) { const T m = mask(begin_bit, end_bit); return (value & m) >> begin_bit; } /// Clears bits [begin_bit, end_bit] inclusive of value of type T. -template +template constexpr T clear_bits(T value) { constexpr T m = mask(); return value & ~m; } /// Clears bits [begin_bit, end_bit] inclusive of value of type T. -template +template constexpr T clear_bits(size_t begin_bit, size_t end_bit, T value) { const T m = mask(begin_bit, end_bit); return value & ~m; } /// Modifies bits [begin_bit, end_bit] inclusive of value of type T. -template +template constexpr T set_bits(T value, T new_bits) { constexpr T m = mask(); return (value & ~m) | ((new_bits << begin_bit) & m); } /// Modifies bits [begin_bit, end_bit] inclusive of value of type T. -template +template constexpr T set_bits(size_t begin_bit, size_t end_bit, T value, T new_bits) { const T m = mask(begin_bit, end_bit); return (value & ~m) | ((new_bits << begin_bit) & m); } /// Extract bit at bit_position from value of type T. -template +template constexpr bool get_bit(T value) { constexpr T m = mask(); return (value & m) != 0; } /// Extract bit at bit_position from value of type T. -template +template constexpr bool get_bit(size_t bit_position, T value) { const T m = mask(bit_position, bit_position); return (value & m) != 0; } /// Clears bit at bit_position of value of type T. -template +template constexpr T clear_bit(T value) { constexpr T m = mask(); return value & ~m; } /// Clears bit at bit_position of value of type T. -template +template constexpr T clear_bit(size_t bit_position, T value) { const T m = mask(bit_position, bit_position); return value & ~m; } /// Modifies bit at bit_position of value of type T. -template +template constexpr T set_bit(T value, bool new_bit) { constexpr T m = mask(); - return (value & ~m) | (new_bit ? m : static_cast(0)); + return (value & ~m) | (new_bit ? m : T(0)); } /// Modifies bit at bit_position of value of type T. -template +template constexpr T set_bit(size_t bit_position, T value, bool new_bit) { const T m = mask(bit_position, bit_position); - return (value & ~m) | (new_bit ? m : static_cast(0)); + return (value & ~m) | (new_bit ? m : T(0)); } /// Sign-extends a value that has bit_count bits to the full bitwidth of type T. -template +template constexpr T sign_extend(T value) { static_assert(bit_count != 0, "cannot sign-extend zero-sized value"); - using S = std::make_signed_t; constexpr size_t shift_amount = bitsizeof - bit_count; - return static_cast(static_cast(value << shift_amount) >> shift_amount); + return T(S(value << shift_amount) >> shift_amount); } /// Sign-extends a value that has bit_count bits to the full bitwidth of type T. -template +template constexpr T sign_extend(size_t bit_count, T value) { ASSERT(bit_count != 0 && "cannot sign-extend zero-sized value"); using S = std::make_signed_t; @@ -234,78 +201,42 @@ constexpr T sign_extend(size_t bit_count, T value) { } /// Replicate an element across a value of type T. -template +template constexpr T replicate_element(T value) { static_assert(element_size <= bitsizeof, "element_size is too large"); static_assert(bitsizeof % element_size == 0, "bitsize of T not divisible by element_size"); - if constexpr (element_size == bitsizeof) { return value; } else { - return replicate_element(static_cast(value | (value << element_size))); + return replicate_element(T(value | (value << element_size))); } } /// Replicate an element of type U across a value of type T. -template +template constexpr T replicate_element(T value) { static_assert(bitsizeof <= bitsizeof, "element_size is too large"); - return replicate_element, T>(value); } /// Replicate an element across a value of type T. -template +template constexpr T replicate_element(size_t element_size, T value) { ASSERT(element_size <= bitsizeof && "element_size is too large"); ASSERT(bitsizeof % element_size == 0 && "bitsize of T not divisible by element_size"); if (element_size == bitsizeof) return value; - return replicate_element(element_size * 2, static_cast(value | (value << element_size))); + return replicate_element(element_size * 2, T(value | (value << element_size))); } -template +template constexpr bool most_significant_bit(T value) { return get_bit - 1, T>(value); } -template -inline size_t count_ones(T x) { - return std::bitset>(x).count(); -} - -template -constexpr size_t count_leading_zeros(T x) { - size_t result = bitsizeof; - while (x != 0) { - x >>= 1; - result--; - } - return result; -} - -template +template constexpr int highest_set_bit(T x) { - int result = -1; - while (x != 0) { - x >>= 1; - result++; - } - return result; -} - -template -constexpr size_t lowest_set_bit(T x) { - if (x == 0) { - return bitsizeof; - } - - size_t result = 0; - while ((x & 1) == 0) { - x >>= 1; - result++; - } - return result; + return std::bit_width(x) - 1; } } // namespace mcl::bit diff --git a/src/dynarmic/src/dynarmic/mcl/integer_of_size.hpp b/src/dynarmic/src/dynarmic/mcl/integer_of_size.hpp index 1d54e9e3ef..6f05ea8eac 100644 --- a/src/dynarmic/src/dynarmic/mcl/integer_of_size.hpp +++ b/src/dynarmic/src/dynarmic/mcl/integer_of_size.hpp @@ -17,25 +17,25 @@ struct integer_of_size_impl {}; template<> struct integer_of_size_impl<8> { using unsigned_type = u8; - using signed_type = s8; + using signed_type = std::make_signed_t; }; template<> struct integer_of_size_impl<16> { using unsigned_type = u16; - using signed_type = s16; + using signed_type = std::make_signed_t; }; template<> struct integer_of_size_impl<32> { using unsigned_type = u32; - using signed_type = s32; + using signed_type = std::make_signed_t; }; template<> struct integer_of_size_impl<64> { using unsigned_type = u64; - using signed_type = s64; + using signed_type = std::make_signed_t; }; } // namespace detail diff --git a/src/dynarmic/src/dynarmic/mcl/is_instance_of_template.hpp b/src/dynarmic/src/dynarmic/mcl/is_instance_of_template.hpp index 0cb143430d..272b15add3 100644 --- a/src/dynarmic/src/dynarmic/mcl/is_instance_of_template.hpp +++ b/src/dynarmic/src/dynarmic/mcl/is_instance_of_template.hpp @@ -10,23 +10,12 @@ namespace mcl { -/// A metavalue (of type VT and value v). -template using value = std::integral_constant; -/// A metavalue of type size_t (and value v). -template using size_value = value; -/// A metavalue of type bool (and value v). (Aliases to std::bool_constant.) -template using bool_value = value; -/// true metavalue (Aliases to std::true_type). -using true_type = bool_value; -/// false metavalue (Aliases to std::false_type). -using false_type = bool_value; - /// Is type T an instance of template class C? template class, class> -struct is_instance_of_template : false_type {}; +struct is_instance_of_template : std::false_type {}; template class C, class... As> -struct is_instance_of_template> : true_type {}; +struct is_instance_of_template> : std::true_type {}; /// Is type T an instance of template class C? template class C, class T> diff --git a/src/dynarmic/tests/A32/fuzz_arm.cpp b/src/dynarmic/tests/A32/fuzz_arm.cpp index e2546de635..abb9830372 100644 --- a/src/dynarmic/tests/A32/fuzz_arm.cpp +++ b/src/dynarmic/tests/A32/fuzz_arm.cpp @@ -43,32 +43,20 @@ namespace { using namespace Dynarmic; template -bool AnyLocationDescriptorForTerminalHas(IR::Terminal terminal, Fn fn) { - return boost::apply_visitor([&](auto t) -> bool { - using T = std::decay_t; - if constexpr (std::is_same_v) { - return false; - } else if constexpr (std::is_same_v) { - return false; - } else if constexpr (std::is_same_v) { - return fn(t.next); - } else if constexpr (std::is_same_v) { - return fn(t.next); - } else if constexpr (std::is_same_v) { - return false; - } else if constexpr (std::is_same_v) { - return false; - } else if constexpr (std::is_same_v) { - return AnyLocationDescriptorForTerminalHas(t.then_, fn) || AnyLocationDescriptorForTerminalHas(t.else_, fn); - } else if constexpr (std::is_same_v) { - return AnyLocationDescriptorForTerminalHas(t.then_, fn) || AnyLocationDescriptorForTerminalHas(t.else_, fn); - } else if constexpr (std::is_same_v) { - return AnyLocationDescriptorForTerminalHas(t.else_, fn); - } else { - ASSERT(false && "Invalid terminal type"); - return false; - } - }, terminal); +bool AnyLocationDescriptorForTerminalHas(IR::Term::Terminal terminal, Fn fn) { + if (auto const e = std::get_if(&terminal)) { + if (auto const x = std::get_if(e)) + return fn(x->next); + if (auto const x = std::get_if(e)) + return fn(x->next); + } + if (auto const x = std::get_if(&terminal)) + return AnyLocationDescriptorForTerminalHas(x->then_, fn) || AnyLocationDescriptorForTerminalHas(x->else_, fn); + if (auto const x = std::get_if(&terminal)) + return AnyLocationDescriptorForTerminalHas(x->then_, fn) || AnyLocationDescriptorForTerminalHas(x->else_, fn); + if (auto const x = std::get_if(&terminal)) + return AnyLocationDescriptorForTerminalHas(x->else_, fn); + return false; } bool ShouldTestInst(u32 instruction, u32 pc, bool is_thumb, bool is_last_inst, A32::ITState it_state = {}) { @@ -333,8 +321,6 @@ static void RunTestInstance(Dynarmic::A32::Jit& jit, uni_env.PadCodeMem(); jit_env.modified_memory.clear(); uni_env.modified_memory.clear(); - jit_env.interrupts.clear(); - uni_env.interrupts.clear(); jit.Regs() = regs; jit.ExtRegs() = vecs; @@ -665,7 +651,7 @@ TEST_CASE("A32: Test thumb IT instruction", "[thumb]") { A32::ITState it_state = [&] { while (true) { const u16 imm8 = RandInt(0, 0xFF); - if (mcl::bit::get_bits<0, 3>(imm8) == 0b0000 || mcl::bit::get_bits<4, 7>(imm8) == 0b1111 || (mcl::bit::get_bits<4, 7>(imm8) == 0b1110 && mcl::bit::count_ones(mcl::bit::get_bits<0, 3>(imm8)) != 1)) { + if (mcl::bit::get_bits<0, 3>(imm8) == 0b0000 || mcl::bit::get_bits<4, 7>(imm8) == 0b1111 || (mcl::bit::get_bits<4, 7>(imm8) == 0b1110 && std::popcount(mcl::bit::get_bits<0, 3>(imm8)) != 1)) { continue; } instructions.push_back(0b1011111100000000 | imm8); diff --git a/src/dynarmic/tests/A32/fuzz_thumb.cpp b/src/dynarmic/tests/A32/fuzz_thumb.cpp index 67a01daf9c..a29b877f3e 100644 --- a/src/dynarmic/tests/A32/fuzz_thumb.cpp +++ b/src/dynarmic/tests/A32/fuzz_thumb.cpp @@ -503,7 +503,7 @@ TEST_CASE("Fuzz Thumb32 instructions set", "[JitX64][Thumb][Thumb32]") { } } -TEST_CASE("Verify fix for off by one error in MemoryRead32 worked", "[Thumb][Thumb16]") { +TEST_CASE("Verify fix for off by one error in MemoryRead<32> worked", "[Thumb][Thumb16]") { ThumbTestEnv test_env; // Prepare test subjects diff --git a/src/dynarmic/tests/A32/test_arm_instructions.cpp b/src/dynarmic/tests/A32/test_arm_instructions.cpp index 5a27cd499c..ee24131386 100644 --- a/src/dynarmic/tests/A32/test_arm_instructions.cpp +++ b/src/dynarmic/tests/A32/test_arm_instructions.cpp @@ -553,9 +553,9 @@ TEST_CASE("arm: Memory access (fastmem)", "[arm][A32]") { memset(backing_memory, 0, memory_size); memcpy(backing_memory + 0x100, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", 57); - env.MemoryWrite32(0, 0xE5904000); // LDR R4, [R0] - env.MemoryWrite32(4, 0xE5814000); // STR R4, [R1] - env.MemoryWrite32(8, 0xEAFFFFFE); // B . + env.MemoryWrite(0, 0xE5904000, sizeof(u32)); // LDR R4, [R0] + env.MemoryWrite(4, 0xE5814000, sizeof(u32)); // STR R4, [R1] + env.MemoryWrite(8, 0xEAFFFFFE, sizeof(u32)); // B . jit.Regs()[0] = 0x100; jit.Regs()[1] = 0x1F0; jit.Regs()[15] = 0; // PC = 0 diff --git a/src/dynarmic/tests/A32/testenv.h b/src/dynarmic/tests/A32/testenv.h index bebc2566da..a22cc7131e 100644 --- a/src/dynarmic/tests/A32/testenv.h +++ b/src/dynarmic/tests/A32/testenv.h @@ -16,6 +16,7 @@ #include "common/assert.h" #include "common/common_types.h" +#include "dynarmic/frontend/A32/translate/translate_callbacks.h" #include "dynarmic/interface/A32/a32.h" template @@ -59,42 +60,51 @@ public: return infinite_loop_u32; // B . } - std::uint8_t MemoryRead8(u32 vaddr) override { - if (IsInCodeMem(vaddr)) { - return reinterpret_cast(code_mem.data())[vaddr]; + u64 MemoryRead(u32 vaddr, size_t size) override { + switch (size) { + case sizeof(u64): + return MemoryRead(vaddr, sizeof(u32)) + | MemoryRead(vaddr + sizeof(u32), sizeof(u32)) << 32; + case sizeof(u32): + return MemoryRead(vaddr, sizeof(u16)) + | MemoryRead(vaddr + sizeof(u16), sizeof(u16)) << 16; + case sizeof(u16): + return MemoryRead(vaddr, sizeof(u8)) + | MemoryRead(vaddr + sizeof(u8), sizeof(u8)) << 8; + case sizeof(u8): { + if (IsInCodeMem(vaddr)) + return reinterpret_cast(code_mem.data())[vaddr]; + if (auto iter = modified_memory.find(vaddr); iter != modified_memory.end()) + return iter->second; + return u8(vaddr); } - if (auto iter = modified_memory.find(vaddr); iter != modified_memory.end()) { - return iter->second; + default: + std::abort(); } - return static_cast(vaddr); - } - std::uint16_t MemoryRead16(u32 vaddr) override { - return u16(MemoryRead8(vaddr)) | u16(MemoryRead8(vaddr + 1)) << 8; - } - std::uint32_t MemoryRead32(u32 vaddr) override { - return u32(MemoryRead16(vaddr)) | u32(MemoryRead16(vaddr + 2)) << 16; - } - std::uint64_t MemoryRead64(u32 vaddr) override { - return u64(MemoryRead32(vaddr)) | u64(MemoryRead32(vaddr + 4)) << 32; } - void MemoryWrite8(u32 vaddr, std::uint8_t value) override { - if (vaddr < code_mem.size() * sizeof(u32)) { - code_mem_modified_by_guest = true; + void MemoryWrite(Dynarmic::A32::VAddr vaddr, u64 value, size_t size) override { + switch (size) { + case sizeof(u64): + MemoryWrite(vaddr, u32(value), sizeof(u32)); + MemoryWrite(vaddr + 4, u32(value >> 32), sizeof(u32)); + break; + case sizeof(u32): + MemoryWrite(vaddr, u16(value), sizeof(u16)); + MemoryWrite(vaddr + 2, u16(value >> 16), sizeof(u16)); + break; + case sizeof(u16): + MemoryWrite(vaddr, u8(value), sizeof(u8)); + MemoryWrite(vaddr + 1, u8(value >> 8), sizeof(u8)); + break; + case sizeof(u8): + if (vaddr < code_mem.size() * sizeof(u32)) + code_mem_modified_by_guest = true; + modified_memory[vaddr] = value; + break; + default: + std::abort(); } - modified_memory[vaddr] = value; - } - void MemoryWrite16(u32 vaddr, std::uint16_t value) override { - MemoryWrite8(vaddr, static_cast(value)); - MemoryWrite8(vaddr + 1, static_cast(value >> 8)); - } - void MemoryWrite32(u32 vaddr, std::uint32_t value) override { - MemoryWrite16(vaddr, static_cast(value)); - MemoryWrite16(vaddr + 2, static_cast(value >> 16)); - } - void MemoryWrite64(u32 vaddr, std::uint64_t value) override { - MemoryWrite32(vaddr, static_cast(value)); - MemoryWrite32(vaddr + 4, static_cast(value >> 32)); } void CallSVC(std::uint32_t swi) override { @@ -143,46 +153,41 @@ public: return read(vaddr); } - std::uint8_t MemoryRead8(std::uint32_t vaddr) override { - return read(vaddr); - } - std::uint16_t MemoryRead16(std::uint32_t vaddr) override { - return read(vaddr); - } - std::uint32_t MemoryRead32(std::uint32_t vaddr) override { - return read(vaddr); - } - std::uint64_t MemoryRead64(std::uint32_t vaddr) override { - return read(vaddr); + u64 MemoryRead(u32 vaddr, size_t size) override { + switch (size) { + case sizeof(u64): return read(vaddr); + case sizeof(u32): return read(vaddr); + case sizeof(u16): return read(vaddr); + case sizeof(u8): return read(vaddr); + default: + std::abort(); + } } - void MemoryWrite8(std::uint32_t vaddr, std::uint8_t value) override { - write(vaddr, value); - } - void MemoryWrite16(std::uint32_t vaddr, std::uint16_t value) override { - write(vaddr, value); - } - void MemoryWrite32(std::uint32_t vaddr, std::uint32_t value) override { - write(vaddr, value); - } - void MemoryWrite64(std::uint32_t vaddr, std::uint64_t value) override { - write(vaddr, value); + void MemoryWrite(Dynarmic::A32::VAddr vaddr, std::uint64_t value, size_t size) override { + switch (size) { + case sizeof(u64): return write(vaddr, u64(value)); + case sizeof(u32): return write(vaddr, u32(value)); + case sizeof(u16): return write(vaddr, u16(value)); + case sizeof(u8): return write(vaddr, u8(value)); + default: std::abort(); + } } - bool MemoryWriteExclusive8(std::uint32_t vaddr, std::uint8_t value, [[maybe_unused]] std::uint8_t expected) override { - MemoryWrite8(vaddr, value); + bool MemoryWriteExclusive8(Dynarmic::A32::VAddr vaddr, std::uint8_t value, [[maybe_unused]] std::uint8_t expected) override { + MemoryWrite(vaddr, value, sizeof(u8)); return true; } - bool MemoryWriteExclusive16(std::uint32_t vaddr, std::uint16_t value, [[maybe_unused]] std::uint16_t expected) override { - MemoryWrite16(vaddr, value); + bool MemoryWriteExclusive16(Dynarmic::A32::VAddr vaddr, std::uint16_t value, [[maybe_unused]] std::uint16_t expected) override { + MemoryWrite(vaddr, value, sizeof(u16)); return true; } - bool MemoryWriteExclusive32(std::uint32_t vaddr, std::uint32_t value, [[maybe_unused]] std::uint32_t expected) override { - MemoryWrite32(vaddr, value); + bool MemoryWriteExclusive32(Dynarmic::A32::VAddr vaddr, std::uint32_t value, [[maybe_unused]] std::uint32_t expected) override { + MemoryWrite(vaddr, value, sizeof(u32)); return true; } - bool MemoryWriteExclusive64(std::uint32_t vaddr, std::uint64_t value, [[maybe_unused]] std::uint64_t expected) override { - MemoryWrite64(vaddr, value); + bool MemoryWriteExclusive64(Dynarmic::A32::VAddr vaddr, std::uint64_t value, [[maybe_unused]] std::uint64_t expected) override { + MemoryWrite(vaddr, value, sizeof(u64)); return true; } diff --git a/src/dynarmic/tests/A64/a64.cpp b/src/dynarmic/tests/A64/a64.cpp index 9bd9c36a86..24ddfd4084 100644 --- a/src/dynarmic/tests/A64/a64.cpp +++ b/src/dynarmic/tests/A64/a64.cpp @@ -1006,8 +1006,8 @@ TEST_CASE("A64: 128-bit exclusive read/write", "[a64]") { REQUIRE(jit.GetRegister(1) == 0x7f7e7d7c7b7a7978); REQUIRE(jit.GetRegister(2) == 0x8786858483828180); REQUIRE(jit.GetRegister(4) == 0); - REQUIRE(env.MemoryRead64(0x1234567812345678) == 0xaf00d1e5badcafe0); - REQUIRE(env.MemoryRead64(0x1234567812345680) == 0xd0d0cacad0d0caca); + REQUIRE(env.MemoryRead(0x1234567812345678, sizeof(u64)) == 0xaf00d1e5badcafe0); + REQUIRE(env.MemoryRead(0x1234567812345680, sizeof(u64)) == 0xd0d0cacad0d0caca); } TEST_CASE("A64: CNTPCT_EL0", "[a64]") { @@ -1517,11 +1517,11 @@ TEST_CASE("A64: Memory access (fastmem)", "[a64]") { memset(backing_memory, 0, memory_size); memcpy(backing_memory + 0x100, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", 57); - env.MemoryWrite32(0, 0xA9401404); // LDP X4, X5, [X0] - env.MemoryWrite32(4, 0xF9400046); // LDR X6, [X2] - env.MemoryWrite32(8, 0xA9001424); // STP X4, X5, [X1] - env.MemoryWrite32(12, 0xF9000066); // STR X6, [X3] - env.MemoryWrite32(16, 0x14000000); // B . + env.MemoryWrite(0, 0xA9401404, sizeof(u32)); // LDP X4, X5, [X0] + env.MemoryWrite(4, 0xF9400046, sizeof(u32)); // LDR X6, [X2] + env.MemoryWrite(8, 0xA9001424, sizeof(u32)); // STP X4, X5, [X1] + env.MemoryWrite(12, 0xF9000066, sizeof(u32)); // STR X6, [X3] + env.MemoryWrite(16, 0x14000000, sizeof(u32)); // B . jit.SetRegister(0, 0x100); jit.SetRegister(1, 0x1F0); jit.SetRegister(2, 0x10F); @@ -1788,8 +1788,8 @@ TEST_CASE("A64: rand3", "[a64]") { memset(backing_memory, 0, memory_size); // cat rand2.txt | awk '{print "env.code_mem.emplace_back(0x"$2"); // "$0}' > rand2-out.tx - env.MemoryWrite32(100, 0x58028edd); // 0000000000000084 58028edd ldr x29, #20952 - env.MemoryWrite32(104, 0x14000000); // 0000000000000ea4 14000000 b #0 + env.MemoryWrite(100, 0x58028edd, sizeof(u32)); // 0000000000000084 58028edd ldr x29, #20952 + env.MemoryWrite(104, 0x14000000, sizeof(u32)); // 0000000000000ea4 14000000 b #0 jit.SetPC(100); jit.SetPstate(0xb0000000); @@ -1824,7 +1824,7 @@ TEST_CASE("A64: rand2", "[a64][.]") { // cat rand2.txt | awk '{print "env.code_mem.emplace_back(0x"$2"); // "$0}' > rand2-out.tx const std::array code32 = {0xea80f352, 0x6e65e59d, 0x1e20c343, 0x2e3a7192, 0x2e267249, 0xd500405f, 0x6f01f461, 0x6eb684fc, 0x58028edd, 0x0ea5f5b6, 0x0ea069fb, 0x2e769517, 0x5e066063, 0x1e65c3f5, 0x4f00ff52, 0x93401cf6, 0x1e274248, 0x6f67aaf5, 0x5e0c0782, 0x5ef43f3c, 0x2e6595b7, 0x4e20590f, 0xb35aa451, 0x6ee2c5ed, 0x4e32bf46, 0x2ea1ba8f, 0x2f68a85e, 0x9237d90a, 0x5e23dd10, 0x0e762e32, 0x4e31a8cf, 0xce1f3360, 0x781a4ac0, 0x13834066, 0x5fa8101c, 0x6f7c5594, 0x0e71bb68, 0xbc0b3e8f, 0x785dbbda, 0x6f51e794, 0xce50af75, 0x1ad728ec, 0x6ee0da4c, 0xb84efa14, 0x2eb3f613, 0x4e287ade, 0x4eb8c734, 0x2e83f4e8, 0x0e397c80, 0xd08f93f8, 0xce718e48, 0x0f672a0d, 0x2e9edd40, 0x0e14128b, 0x6f5942e6, 0x8b3a0f03, 0x3c5d16b9, 0x7f7e3743, 0x4f4c54e4, 0x0ea0a9e9, 0x9e59dbe6, 0x6e7ddcd3, 0xcec08377, 0x9ba759f8, 0x2ea5046e, 0x0e24c569, 0xb8979780, 0x4e31b98c, 0x4efe4f46, 0x4ea7c762, 0x7e61c9c6, 0x6e30c880, 0x1ada0c25, 0x4e603a2f, 0xda9d7218, 0x0d40c5d9, 0x5e214b05, 0x9ba9efc5, 0x5e61b81e, 0x6e7bc31c, 0x0e61a163, 0x9e5832d2, 0x4e772248, 0x4e3d17c8, 0x92624f60, 0x7a1a02dc, 0x79891f65, 0x6eb45036, 0x0e321ee8, 0x4e2566f0, 0x4ea02b9b, 0x0f9dcb3d, 0x2e21b9f9, 0x0e21a8c3, 0xda1700bd, 0x6ea0fb38, 0x7e607a0b, 0x72845817, 0x7f61068e, 0x0d60e529, 0x4ea0ca5c, 0x1a94b20f, 0x8b87419d, 0x7ea9ed71, 0x2ea1a86e, 0x4d40c4da, 0x5ea0eada, 0x784ba96e, 0x7eb6ee02, 0x3db1c710, 0x0e217836, 0x7ee0bb96, 0x4e786c08, 0x4e976a08, 0x489ffe86, 0x4e79fc9b, 0x0e21cbce, 0x5ef7fc65, 0x4ea1286d, 0xd29c771e, 0x6f5c2839, 0x0ea00a9d, 0x6ee44c06, 0x5ee1d858, 0x5ef2fda6, 0x7eb0c9fe, 0x7f762791, 0x2e212ae6, 0x4e61c9db, 0x13003c57, 0x5ee1b8f8, 0x0f2396d2, 0x6ea0db1e, 0x0e71ba82, 0xab29c807, 0x6ef8f8b3, 0x1f18d4a1, 0x0e261d15, 0x1e290081, 0x1b0c7d12, 0x4e7771c3, 0xf845f1e4, 0x4d40c9e8, 0xce778452, 0x6eb9879d, 0x6e21c93d, 0xcec0829f, 0x52a0969f, 0x1e772b4f, 0x7ee1da88, 0x5f52fe0a, 0x7f3387b1, 0x5e214850, 0x1e65c025, 0x0e2ca294, 0x2e614829, 0x1e640077, 0x9e240048, 0x4ebe9537, 0x9bb7925e, 0x38b669c5, 0x2840d089, 0x6f43e648, 0x2e662d28, 0x4eabaff3, 0x6e734cc7, 0x0e31baee, 0x7ee0d93c, 0x5e282bde, 0x7e21bba4, 0x4e6c75fa, 0x5ac01217, 0x7f4304af, 0x1e7878ed, 0x1ada2196, 0x7ee1aba3, 0x93407f3c, 0x4f6c34eb, 0x6e3447a9, 0x7e7ae545, 0x5e0802bb, 0x6eeae63a, 0x7ee1da62, 0x5e280bb3, 0xf81d4009, 0x1e603b21, 0x5e281a14, 0x6eb0a99b, 0x1e266a25, 0x0d60cafe, 0x0e0b6194, 0x7a4ed2c5, 0x92b762ec, 0x4e6b5749, 0x3c16a6e5, 0x4ea0a92b, 0x0fa58b6a, 0x5f76148c, 0x6e30c95f, 0x1e6540fd, 0x5e28e40f, 0x0d403fd4, 0x7e30da36, 0x7fda9b51, 0x2ea04bde, 0x1e25c3d2, 0x1ee0434c, 0x5e21d8e7, 0x5ee1ba51, 0x5e61aba9, 0x4e2849fb, 0x5ee098ea, 0x4e60f63d, 0x0f280443, 0x5ee0da27, 0x2e78a6ce, 0x78054afc, 0x4e14286b, 0x4e218bd8, 0x2a3d2551, 0x3a04017a, 0x5f4317cd, 0x0e604a37, 0x9a834614, 0x0e2edf4d, 0x7a51a0a0, 0x5f8e9043, 0x6ea06bb2, 0xaa2857dd, 0x7a1903fc, 0x301ba9ba, 0x9ac929cd, 0x4e061ff0, 0x2e38fcfc, 0x0e2f614a, 0x7ee0d8e4, 0x6e73afda, 0x7f4156f7, 0x0e6078bf, 0x4ee1d9ed, 0x93403fbe, 0xce6f8640, 0x4e3855e3, 0x6f76fe23, 0x112466e8, 0x1e358a90, 0x7f45272c, 0x6ea19a9d, 0x8a696350, 0x1e3900f6, 0x5e61c866, 0x0e3fbfd0, 0x5ee09ad0, 0x0e651d27, 0x4dffc35e, 0x2e20c6ce, 0x0fbe118d, 0x1e656a15, 0xd1357365, 0x0e20a847, 0xce4a835c, 0x4e203905, 0x2e60090d, 0x7f4a27bb, 0x1e64c316, 0xce7d86a4, 0x7ebded2d, 0x6e70a97e, 0x4eb9a42b, 0x0e209bef, 0x6f151730, 0x0e7e30f7, 0x4e724509, 0xd503375f, 0xce58b6ae, 0x5e21a9b8, 0xcb2ca538, 0x5ac01131, 0x6ea19a24, 0xeb40c8b3, 0xc8df7d65, 0x78108341, 0x3218ab9b, 0x0f3da7dd, 0x2e003089, 0x4e21cab5, 0x8aa5c924, 0x1a94950c, 0x123e506f, 0x13117e37, 0x1ee6005b, 0x5ac00647, 0x5eec8cd5, 0x7ef0fb3d, 0x9223272a, 0x5ee0cb02, 0x6e66071d, 0x6ea1dbbf, 0x5e61c903, 0x5ac015ea, 0x93db6206, 0x7e62b5e3, 0x6ea0c87b, 0xdac0090e, 0x48df7d90, 0x6e206ba5, 0x9e2503c2, 0x6e25fc89, 0x4d60e2db, 0x1e3e22a0, 0x2eb81c19, 0x7856ea00, 0x5fbfb22d, 0x1e630244, 0x4e202a83, 0x1f50a722, 0x7f7b55d2, 0x0fae89b9, 0x4e781d73, 0xce738c3a, 0x4f15a591, 0x6e21c7e1, 0x586ff77e, 0x8a5d3592, 0x93401c67, 0x5e61cb86, 0xce6bc2c1, 0x6e393f10, 0x9bb70ec3, 0xdac0098c, 0x4da84b95, 0x7f494476, 0x9ace5c11, 0x7e61ca14, 0x4f7a60ef, 0x1ad32b39, 0x0ea3777f, 0x5e61da7f, 0x4f1404e2, 0x4e3244e2, 0x6e1b1ceb, 0x0dee5aac, 0x4e2f9dc4, 0x5ea1b8c3, 0x1e59f863, 0xd500403f, 0x4e3ae7d0, 0x4ef5c6ea, 0x08dffe3b, 0x6e36f4f6, 0x2e764f29, 0x0e726f23, 0x5f42375b, 0x7f71fc40, 0x6e618aad, 0x93403e5b, 0x0e205976, 0x0e7250c4, 0x6eb0abc9, 0x2e2049f0, 0x5f14754d, 0x7f6ce468, 0x6f950bbe, 0x6e31aa47, 0x4eb83396, 0x0dccc952, 0x2ea1ca90, 0xce69c701, 0xb0bed69e, 0x7c5dec39, 0x4e2868a2, 0x0e591b08, 0x5f34e6dd, 0x3a449184, 0x5e3ce6de, 0x4ea149b7, 0x4e7ad29b, 0xba198503, 0x1f683e8f, 0xfa52f2a7, 0x6e30dffc, 0x4e6c3d17, 0x2eae3248, 0xd503349f, 0x1e60002c, 0x0f180680, 0x9e240049, 0x6f75774e, 0xa90d8678, 0x9ad924c4, 0x7eb0f85b, 0x0e205aaf, 0x7ee08899, 0x5f4bffd8, 0x1b0ff5f3, 0x4ee11dcd, 0x2e218948, 0x0dcb2733, 0x4eac107c, 0x4ea04a53, 0x4e287b44, 0x0e60b82a, 0x5ee0ebbc, 0xce454ff1, 0x5e1761e7, 0x5e09202f, 0x0e0c0754, 0x1e72e6b9, 0x7e21da70, 0x0fbdb20c, 0x5efb8c84, 0xd500401f, 0x3a47526e, 0x1e680acf, 0x7f7375fc, 0xf80522da, 0x4ee60c02, 0x4d40c2e7, 0x6f89096b, 0x7ee1bb6e, 0x5e280b4a, 0x1e3120c8, 0x7eb2ef96, 0x4fd012dd, 0x0f3027ef, 0x4e2078a8, 0xd503201f, 0x2e2312d9, 0x6ebf1c6e, 0x5ee1f8df, 0x4e607a46, 0x6e30c877, 0x6c09d2d1, 0x4e61abd8, 0x0e35267e, 0x6ac17728, 0x0e861aa0, 0x6f63fe26, 0x6f157628, 0x6f30a5f9, 0x4d60cc0c, 0x4e21cb59, 0x2e68a3fb, 0x7efae601, 0x6ea0f82c, 0x9b25ec12, 0x1a1a0305, 0x0e043fe1, 0x6e73c0ed, 0x6ea1b8c0, 0x7e20380b, 0x0f0534e8, 0x1f56bc7d, 0xba0c0128, 0x1e672160, 0x6e7b259b, 0x7ee07b5d, 0x9a820443, 0x4e040581, 0x2f1d87e8, 0x1acd2f5b, 0x6e20794f, 0x2e6a3c93, 0xc8dffe13, 0xce5ab1c6, 0x6eea55f6, 0x4ea039b3, 0x0d602fec, 0x2e246e2f, 0x7857be39, 0xb80608fb, 0x1e67c017, 0x9bcf7f63, 0x0f92d857, 0x5e0812f7, 0x1e210172, 0x7e6128e9, 0x7ea94d41, 0x981179e1, 0x1effb018, 0x2e600828, 0x0eb9c6b2, 0x6ee1baae, 0x4ea0db28, 0x2ea1487b, 0x4ea6c7f0, 0x2e2374c7, 0x7e30d8dd, 0xb9991fa7, 0x4e791e3e, 0x889f7c4b, 0x0e6c753c, 0x1e740ad1, 0x1e244324, 0x1ef33010, 0x5ac01102, 0x9bd97fba, 0x6e290143, 0x1e2220d8, 0x4d8d5aee, 0x6f28570b, 0xfa4ab0c1, 0xdac00b14, 0x7ea1a90e, 0x2e3027d8, 0x6f25a733, 0x4e61a96e, 0x4e1a2fcb, 0x0e22fe0a, 0xc8df7cd0, 0x5e280a55, 0x4e012b20, 0x7e70dbf4, 0x520c5a4e, 0x6ea6c57f, 0x0e861af8, 0xd503233f, 0x889ffe3c, 0x5e274ea9, 0x4e21a89a, 0x0e170c02, 0x6efd4c0b, 0xd5033ebf, 0x6e61a92c, 0x2e205b72, 0x789fb828, 0x0e626e94, 0x2ea6724c, 0x9a10028b, 0x2c6c51fc, 0x5a9de6b9, 0x6e6881f3, 0x5ee0ea6b, 0x0faec36e, 0x0e955bca, 0x1acf206d, 0x7f6f571b, 0x4e286930, 0x12b41ceb, 0x1e770b7a, 0x0ea18ac2, 0x5e282aaf, 0xf2b7fa1e, 0x1ac34311, 0x13167d11, 0x4ea63412, 0x6e758038, 0x2f1d85d6, 0x0f275480, 0x0ead6c71, 0x6e204b69, 0x1e6303f4, 0x5e0031ef, 0x13001e40, 0x7a16006f, 0x6e6ae4c0, 0x0f0f242f, 0x6e674f50, 0x4e606b7a, 0x7e6ee684, 0x1e6b5957, 0x7ea1bbab, 0x7ea0b6cb, 0xce4da241, 0x0ea1b953, 0x0eb2af4b, 0x9ac309d0, 0x6e61d8bd, 0x5ea0d890, 0x5f47d1e7, 0xfa5981ca, 0x1e7f7959, 0x6ef24dd8, 0x0e0a41d1, 0x5ee0e898, 0x4e6038e2, 0x13097d65, 0x6f839088, 0x9e290265, 0x0e208824, 0x2e65af79, 0x6f36a561, 0x9ad3204b, 0x0e21482e, 0x1e24431d, 0xd50330bf, 0x0df641aa, 0x6e602a83, 0xce30505f, 0x5e025238, 0xd503201f, 0x4e608880, 0x4de9c38d, 0x5e0f5348, 0x6eb48ca9, 0x50fda31b, 0x2e251eec, 0x7842ba50, 0xd8a1cd86, 0x2ea09862, 0x0ea09983, 0x2ea333b0, 0x0ea6032c, 0x4f94801b, 0x7e3ee57d, 0x38135e4f, 0xd8fdd9dd, 0x5ee0fcde, 0x9e64033d, 0x6e37f547, 0x6e3dd7ef, 0x13003f3d, 0x0e602f9f, 0x4e7ad014, 0x9b3b6857, 0x5ea0cb67, 0x0eb31c9f, 0x4e7c5372, 0x5e61b8c0, 0x0ea19b23, 0x0ee6e1df, 0x6e63a626, 0x2f139405, 0x7eb0f96d, 0x9e588c63, 0x2e714c3a, 0x6e8c941e, 0x0f61b331, 0x6f01f625, 0x4e78d4ea, 0x6f403709, 0x1a0300da, 0xda0102c8, 0x7e61d9fd, 0xb89469bb, 0x0c838780, 0x2e60a590, 0x4dfd29e1, 0x4e150f2e, 0xce2810bc, 0x5f541591, 0x9ee60259, 0x2eb40e56, 0x5e014027, 0x2ef71faf, 0x4e2d452f, 0x5ee0a813, 0x4eb03301, 0x38443acf, 0x6eabd502, 0x0e2ee71e, 0x5a960364, 0xce7ec596, 0x7efbed09, 0x4ef42ea2, 0x0eb30ea5, 0x5ee0d9f8, 0x6f513552, 0xf89eb3fa, 0x7ea2eca6, 0x9b00cc19, 0xf897409e, 0x1e73485f, 0x381afa77, 0x0f169f3b, 0x5ee1aa70, 0x5e1803ee, 0x0dbf5a4c, 0xce78c7a6, 0x9b0b260c, 0x2ef8fa19, 0x6e70aa4b, 0xce45b805, 0x2ea08e86, 0x4ee0bafd, 0x2ea09a1f, 0x4e218900, 0x6e744f13, 0xce518653, 0xf81b7a68, 0xce45ac5e, 0x7e62e416, 0x1a1b02b6, 0x7e21db48, 0x381daaaf, 0x6b2c0987, 0x0e2ec651, 0x4eae8502, 0x9bde7ca0, 0x6f47201f, 0x7e61a8a3, 0x6e60d5db, 0x4e2879de, 0xf81d194e, 0x4f1b8d05, 0x4d0048b2, 0x6e203be9, 0x4e3e7eb1, 0x0e260ef8, 0x2e688518, 0x7e3fec46, 0xdac00843, 0xf85c8917, 0x2e212a0f, 0x0e8196da, 0xd503359f, 0xce4c81f2, 0x6ee19992, 0x6e21ca79, 0x4d40c1d2, 0x4f5816ef, 0x4e34c3ea, 0x4df7c283, 0x7ef7eeb6, 0x18e276ce, 0xab0d21c0, 0xd5032f7f, 0x4ea00dbf, 0x5ac01251, 0xd0121955, 0x7f1495e4, 0x7ef0fa11, 0x5e24dd9c, 0x9add25b5, 0x0eb2bdef, 0x9e1977c7, 0x6f4b26bd, 0x0e200a9c, 0x9b4f7c00, 0x0ea0392e, 0x7e212a2c, 0x0b248b90, 0x1acc27a1, 0x2e701c90, 0x5ee1b870, 0x5e280aba, 0x5ea0780e, 0x1e264246, 0x4e052d04, 0x0e731dc4, 0xce461997, 0x9a9e9413, 0x3d462048, 0x5ea1fac5, 0x2ea0c8c4, 0x9a030280, 0x2ebda4b8, 0x5eef8614, 0x6eadc4e0, 0xbd035a8f, 0x4e606b84, 0x4eb1aba1, 0x4e286928, 0x4e2858cc, 0x9add0ce9, 0x4e070d65, 0x5fd399d5, 0x0f03fde7, 0x6ee90c74, 0x4ef8e31e, 0x381d986a, 0x5ea0ebf4, 0x5ea0d87e, 0x2e76ac9e, 0x6eb36cd4, 0x2e6e1c4c, 0x2e2feebc, 0x1ace4b03, 0x5ee0db12, 0x5ea0e9b1, 0x2e1c32d5, 0x5fa49a09, 0x0e258737, 0x7e21ca8e, 0xce4f9988, 0x5f7f56a6, 0x0e739766, 0x4e28586c, 0x6e619908, 0xd500401f, 0xf88b9252, 0x6e251c8e, 0x9e20015b, 0x7f1486b9, 0x717c339b, 0x1f31ff70, 0x4ea0eb62, 0x9acb0926, 0x489f7d85, 0x4e209b54, 0x2e84cf03, 0x2e65946c, 0x0e7d80cd, 0xc8dffecc, 0xce668bd8, 0x6e2188af, 0xeb4ada34, 0x2b25ec33, 0x0d40e6e7, 0x4eb2c757, 0x4ec82ad0, 0x7e21cb0a, 0x0e21a847, 0x4e0b1ec0, 0x381e6ac0, 0x6e61c8f5, 0x0f10071c, 0x2ee21daa, 0x5e61ab31, 0x6e218892, 0x2e7e7cb5, 0x6f2826aa, 0x7f6b54df, 0x4eaa2620, 0xdac00034, 0x4f6477be, 0x7e6148ea, 0x4eef1f57, 0x78459aeb, 0x2ebc3f10, 0x2e35f4eb, 0x4fbf19ce, 0xd8d0e58e, 0x2e21bbc7, 0x6ee0cab6, 0x9bc57e3f, 0x2f854037, 0x4e92181c, 0x6e6d1f89, 0x0f305545, 0x4ee19a57, 0x0e887bdf, 0x5e1a4185, 0x7ef0c821, 0x2eb6607c, 0x2ea0d9b8, 0x9e0380f4, 0x2ebf1c83, 0x1e62597d, 0x7f6e2548, 0x5ac00205, 0x4e616adb, 0xce638b8c, 0x5e1653cf, 0x2e6069be, 0x0e2ac641, 0x1e33c76f, 0xce44956d, 0x9bb90d31, 0x1e24c20a, 0x7ee038c1, 0x93407e5e, 0x4e280127, 0xc8df7f7d, 0xba42f263, 0x1e6f199c, 0x6e212889, 0x6e92f60e, 0x6ebdc499, 0x8b9acbf8, 0x4d40c581, 0x3a020250, 0x6e6a6716, 0x9248403b, 0x9081ffea, 0x4e603856, 0x9ad1242b, 0x6f270579, 0x1a070349, 0xcec08133, 0xd503305f, 0x5a1a00ca, 0x2e60b8a2, 0x0e5f28fd, 0x0e31a3da, 0x7e61cbc1, 0xd503399f, 0x5f5e54aa, 0x0eb8bdea, 0x4eba8f10, 0x4e2a2e60, 0x2f3da7d6, 0x1e58e297, 0x6e71aa3e, 0x6b86701a, 0xce4fa5e6, 0x4ee7c463, 0x8a79307f, 0x0ebea541, 0x2e218af4, 0x4e774f8a, 0xb9b95dc5, 0x6e61abd5, 0x4dd1e814, 0x4da72098, 0x98307582, 0x3a512101, 0x7ef95497, 0x1ace5535, 0x5a0c0349, 0x4e28581b, 0x6ebf1c02, 0x5ea1da23, 0x1e274314, 0x5e25dd29, 0x6e75f594, 0x6eaf6ed5, 0x4e214abe, 0x4e064172, 0x2e21c8f4, 0xf84c5b08, 0x1e244312, 0x14000000}; for (size_t i = 0; i < code32.size(); ++i) - env.MemoryWrite32(100 + i, code32[i]); + env.MemoryWrite(100 + i, code32[i], sizeof(u32)); env.ignore_invalid_insn = true; jit.SetRegister(0, 0x866524401a1d4e47); @@ -2587,11 +2587,11 @@ TEST_CASE("A64: Manual Vector Min/Max U64 (Optimizer Test)", "[a64]") { // MaxU64 pattern: (a > b) ? a : b code.CMHI(V2.D2(), V0.D2(), V1.D2()); // V2 = Mask (A > B) - code.BSL(V2.B16(), V0.B16(), V1.B16()); // V2 = Resul + code.BSL(V2.B16(), V0.B16(), V1.B16()); // V2 = Result // MinU64 pattern: (a > b) ? b : a code.CMHI(V3.D2(), V0.D2(), V1.D2()); // V3 = Mask (A > B) - code.BSL(V3.B16(), V1.B16(), V0.B16()); // V3 = Resul + code.BSL(V3.B16(), V1.B16(), V0.B16()); // V3 = Result jit.SetPC(0); jit.SetVector(0, {100, 20}); @@ -2603,3 +2603,32 @@ TEST_CASE("A64: Manual Vector Min/Max U64 (Optimizer Test)", "[a64]") { CHECK(jit.GetVector(2) == Vector{100, 200}); CHECK(jit.GetVector(3) == Vector{50, 20}); } + +TEST_CASE("A64: Rounding", "[a64]") { + A64TestEnv env; + A64::UserConfig jit_user_config{}; + jit_user_config.callbacks = &env; + A64::Jit jit{jit_user_config}; + + oaknut::VectorCodeGenerator code{env.code_mem, nullptr}; + + code.FRINTN(V1.S4(), V0.S4()); // ToNearest_TieEven + code.FRINTM(V2.S4(), V0.S4()); // TowardsMinusInfinity + code.FRINTP(V3.S4(), V0.S4()); // TowardsPlusInfinity + code.FRINTZ(V4.S4(), V0.S4()); // TowardsZero + code.FRINTA(V5.S4(), V0.S4()); // ToNearest_TieAwayFromZero + code.FRINTX(V6.S4(), V0.S4()); // ToNearest_TieAwayFromZero + + jit.SetPC(0); + jit.SetVector(0, {0x4001e17c4001e17c, 0x4001e17c4001e17c}); + env.ticks_left = env.code_mem.size(); + CheckedRun([&]() { jit.Run(); }); + + CHECK(jit.GetVector(0) == Vector{0x4001e17c4001e17c, 0x4001e17c4001e17c}); + CHECK(jit.GetVector(1) == Vector{0x4000000040000000, 0x4000000040000000}); + CHECK(jit.GetVector(2) == Vector{0x4000000040000000, 0x4000000040000000}); + CHECK(jit.GetVector(3) == Vector{0x4040000040400000, 0x4040000040400000}); + CHECK(jit.GetVector(4) == Vector{0x4000000040000000, 0x4000000040000000}); + CHECK(jit.GetVector(5) == Vector{0x4000000040000000, 0x4000000040000000}); + CHECK(jit.GetVector(6) == Vector{0x4000000040000000, 0x4000000040000000}); +} diff --git a/src/dynarmic/tests/A64/fibonacci.cpp b/src/dynarmic/tests/A64/fibonacci.cpp index 12c4170f66..8816a89b1c 100644 --- a/src/dynarmic/tests/A64/fibonacci.cpp +++ b/src/dynarmic/tests/A64/fibonacci.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include "common/container/unordered_map.h" #include #include "common/common_types.h" @@ -23,50 +23,57 @@ namespace { class MyEnvironment final : public A64::UserCallbacks { public: u64 ticks_left = 0; - ankerl::unordered_dense::map memory{}; + ::Common::unordered_map memory{}; - u8 MemoryRead8(u64 vaddr) override { - return memory[vaddr]; - } - - u16 MemoryRead16(u64 vaddr) override { - return u16(MemoryRead8(vaddr)) | u16(MemoryRead8(vaddr + 1)) << 8; - } - - u32 MemoryRead32(u64 vaddr) override { - return u32(MemoryRead16(vaddr)) | u32(MemoryRead16(vaddr + 2)) << 16; - } - - u64 MemoryRead64(u64 vaddr) override { - return u64(MemoryRead32(vaddr)) | u64(MemoryRead32(vaddr + 4)) << 32; + u64 MemoryRead(u64 vaddr, size_t size) override { + switch (size) { + case sizeof(u64): + return MemoryRead(vaddr, sizeof(u32)) + | MemoryRead(vaddr + sizeof(u32), sizeof(u32)) << 32; + case sizeof(u32): + return MemoryRead(vaddr, sizeof(u16)) + | MemoryRead(vaddr + sizeof(u16), sizeof(u16)) << 16; + case sizeof(u16): + return MemoryRead(vaddr, sizeof(u8)) + | MemoryRead(vaddr + sizeof(u8), sizeof(u8)) << 8; + case sizeof(u8): + return memory[vaddr]; + default: + std::abort(); + } } std::array MemoryRead128(u64 vaddr) override { - return {MemoryRead64(vaddr), MemoryRead64(vaddr + 8)}; + return { + MemoryRead(vaddr, sizeof(u64)), + MemoryRead(vaddr + sizeof(u64), sizeof(u64)) + }; } - void MemoryWrite8(u64 vaddr, u8 value) override { - memory[vaddr] = value; + void MemoryWrite(Dynarmic::A64::VAddr vaddr, u64 value, size_t size) override { + switch (size) { + case sizeof(u64): + MemoryWrite(vaddr, u32(value), sizeof(u32)); + MemoryWrite(vaddr + 4, u32(value >> 32), sizeof(u32)); + break; + case sizeof(u32): + MemoryWrite(vaddr, u16(value), sizeof(u16)); + MemoryWrite(vaddr + 2, u16(value >> 16), sizeof(u16)); + break; + case sizeof(u16): + MemoryWrite(vaddr, u8(value), sizeof(u8)); + MemoryWrite(vaddr + 1, u8(value >> 8), sizeof(u8)); + break; + case sizeof(u8): + memory[vaddr] = value; + break; + default: + std::abort(); + } } - - void MemoryWrite16(u64 vaddr, u16 value) override { - MemoryWrite8(vaddr, u8(value)); - MemoryWrite8(vaddr + 1, u8(value >> 8)); - } - - void MemoryWrite32(u64 vaddr, u32 value) override { - MemoryWrite16(vaddr, u16(value)); - MemoryWrite16(vaddr + 2, u16(value >> 16)); - } - - void MemoryWrite64(u64 vaddr, u64 value) override { - MemoryWrite32(vaddr, u32(value)); - MemoryWrite32(vaddr + 4, u32(value >> 32)); - } - void MemoryWrite128(u64 vaddr, std::array value) override { - MemoryWrite64(vaddr, value[0]); - MemoryWrite64(vaddr + 8, value[1]); + MemoryWrite(vaddr, value[0], sizeof(u64)); + MemoryWrite(vaddr + 8, value[1], sizeof(u64)); } void CallSVC(u32) override { @@ -135,9 +142,9 @@ TEST_CASE("A64: fibonacci", "[a64]") { code.RET(); for (size_t i = 0; i < 1024; i++) { - env.MemoryWrite32(i * 4, instructions[i]); + env.MemoryWrite(i * 4, instructions[i], sizeof(u32)); } - env.MemoryWrite32(8888, 0xd4200000); + env.MemoryWrite(8888, 0xd4200000, sizeof(u32)); cpu.SetRegister(30, 8888); cpu.SetRegister(0, 10); diff --git a/src/dynarmic/tests/A64/fuzz_with_unicorn.cpp b/src/dynarmic/tests/A64/fuzz_with_unicorn.cpp index 90df625143..ea5b5302f8 100644 --- a/src/dynarmic/tests/A64/fuzz_with_unicorn.cpp +++ b/src/dynarmic/tests/A64/fuzz_with_unicorn.cpp @@ -176,8 +176,6 @@ static void RunTestInstance(Dynarmic::A64::Jit& jit, A64Unicorn& uni, A64TestEnv uni_env.code_mem_start_address = instructions_start; jit_env.modified_memory.clear(); uni_env.modified_memory.clear(); - jit_env.interrupts.clear(); - uni_env.interrupts.clear(); const u64 initial_sp = RandInt(0x30'0000'0000, 0x40'0000'0000) * 4; diff --git a/src/dynarmic/tests/A64/testenv.h b/src/dynarmic/tests/A64/testenv.h index c25790e1c9..11aa6389f8 100644 --- a/src/dynarmic/tests/A64/testenv.h +++ b/src/dynarmic/tests/A64/testenv.h @@ -8,96 +8,106 @@ #pragma once -#include +#include "common/container/unordered_map.h" #include "common/assert.h" #include "common/common_types.h" #include "dynarmic/interface/A64/a64.h" +#include "dynarmic/interface/A64/config.h" using Vector = Dynarmic::A64::Vector; class A64TestEnv : public Dynarmic::A64::UserCallbacks { public: - u64 ticks_left = 0; - - bool code_mem_modified_by_guest = false; - u64 code_mem_start_address = 0; + ::Common::unordered_map modified_memory; std::vector code_mem; - - ankerl::unordered_dense::map modified_memory; - std::vector interrupts; + u64 ticks_left = 0; + u64 code_mem_start_address = 0; + bool code_mem_modified_by_guest = false; bool IsInCodeMem(u64 vaddr) const { return vaddr >= code_mem_start_address && vaddr < code_mem_start_address + code_mem.size() * 4; } std::optional MemoryReadCode(u64 vaddr) override { - if (!IsInCodeMem(vaddr)) { + if (!IsInCodeMem(vaddr)) return 0x14000000; // B . - } - const size_t index = (vaddr - code_mem_start_address) / 4; return code_mem[index]; } - std::uint8_t MemoryRead8(u64 vaddr) override { - if (IsInCodeMem(vaddr)) { - return reinterpret_cast(code_mem.data())[vaddr - code_mem_start_address]; + u64 MemoryRead(u64 vaddr, size_t size) override { + switch (size) { + case sizeof(u64): + return MemoryRead(vaddr, sizeof(u32)) + | MemoryRead(vaddr + sizeof(u32), sizeof(u32)) << 32; + case sizeof(u32): + return MemoryRead(vaddr, sizeof(u16)) + | MemoryRead(vaddr + sizeof(u16), sizeof(u16)) << 16; + case sizeof(u16): + return MemoryRead(vaddr, sizeof(u8)) + | MemoryRead(vaddr + sizeof(u8), sizeof(u8)) << 8; + case sizeof(u8): { + if (IsInCodeMem(vaddr)) + return reinterpret_cast(code_mem.data())[vaddr - code_mem_start_address]; + if (auto const it = modified_memory.find(vaddr); it != modified_memory.end()) + return it->second; + return u8(vaddr); } - if (auto iter = modified_memory.find(vaddr); iter != modified_memory.end()) { - return iter->second; + default: + std::abort(); } - return static_cast(vaddr); - } - std::uint16_t MemoryRead16(u64 vaddr) override { - return u16(MemoryRead8(vaddr)) | u16(MemoryRead8(vaddr + 1)) << 8; - } - std::uint32_t MemoryRead32(u64 vaddr) override { - return u32(MemoryRead16(vaddr)) | u32(MemoryRead16(vaddr + 2)) << 16; - } - std::uint64_t MemoryRead64(u64 vaddr) override { - return u64(MemoryRead32(vaddr)) | u64(MemoryRead32(vaddr + 4)) << 32; - } - Vector MemoryRead128(u64 vaddr) override { - return {MemoryRead64(vaddr), MemoryRead64(vaddr + 8)}; } - void MemoryWrite8(u64 vaddr, std::uint8_t value) override { - if (IsInCodeMem(vaddr)) { - code_mem_modified_by_guest = true; + Vector MemoryRead128(u64 vaddr) override { + return { + MemoryRead(vaddr, sizeof(u64)), + MemoryRead(vaddr + 8, sizeof(u64)) + }; + } + + void MemoryWrite(Dynarmic::A64::VAddr vaddr, u64 value, size_t size) override { + switch (size) { + case sizeof(u64): + MemoryWrite(vaddr, u32(value), sizeof(u32)); + MemoryWrite(vaddr + 4, u32(value >> 32), sizeof(u32)); + break; + case sizeof(u32): + MemoryWrite(vaddr, u16(value), sizeof(u16)); + MemoryWrite(vaddr + 2, u16(value >> 16), sizeof(u16)); + break; + case sizeof(u16): + MemoryWrite(vaddr, u8(value), sizeof(u8)); + MemoryWrite(vaddr + 1, u8(value >> 8), sizeof(u8)); + break; + case sizeof(u8): + if (IsInCodeMem(vaddr)) { + code_mem_modified_by_guest = true; + } + modified_memory[vaddr] = value; + break; + default: + std::abort(); } - modified_memory[vaddr] = value; - } - void MemoryWrite16(u64 vaddr, std::uint16_t value) override { - MemoryWrite8(vaddr, static_cast(value)); - MemoryWrite8(vaddr + 1, static_cast(value >> 8)); - } - void MemoryWrite32(u64 vaddr, std::uint32_t value) override { - MemoryWrite16(vaddr, static_cast(value)); - MemoryWrite16(vaddr + 2, static_cast(value >> 16)); - } - void MemoryWrite64(u64 vaddr, std::uint64_t value) override { - MemoryWrite32(vaddr, static_cast(value)); - MemoryWrite32(vaddr + 4, static_cast(value >> 32)); } void MemoryWrite128(u64 vaddr, Vector value) override { - MemoryWrite64(vaddr, value[0]); - MemoryWrite64(vaddr + 8, value[1]); + MemoryWrite(vaddr, value[0], sizeof(u64)); + MemoryWrite(vaddr + 8, value[1], sizeof(u64)); } bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, [[maybe_unused]] std::uint8_t expected) override { - MemoryWrite8(vaddr, value); + MemoryWrite(vaddr, value, sizeof(u8)); return true; } bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, [[maybe_unused]] std::uint16_t expected) override { - MemoryWrite16(vaddr, value); + MemoryWrite(vaddr, value, sizeof(u16)); return true; } bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, [[maybe_unused]] std::uint32_t expected) override { - MemoryWrite32(vaddr, value); + MemoryWrite(vaddr, value, sizeof(u32)); return true; } bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, [[maybe_unused]] std::uint64_t expected) override { - MemoryWrite64(vaddr, value); + MemoryWrite(vaddr, value, sizeof(u64)); return true; } bool MemoryWriteExclusive128(u64 vaddr, Vector value, [[maybe_unused]] Vector expected) override { @@ -139,64 +149,58 @@ public: template T read(u64 vaddr) { T value; - memcpy(&value, backing_memory + vaddr, sizeof(T)); + std::memcpy(&value, backing_memory + vaddr, sizeof(T)); return value; } template void write(u64 vaddr, const T& value) { - memcpy(backing_memory + vaddr, &value, sizeof(T)); + std::memcpy(backing_memory + vaddr, &value, sizeof(T)); } std::optional MemoryReadCode(u64 vaddr) override { return read(vaddr); } - std::uint8_t MemoryRead8(u64 vaddr) override { - return read(vaddr); - } - std::uint16_t MemoryRead16(u64 vaddr) override { - return read(vaddr); - } - std::uint32_t MemoryRead32(u64 vaddr) override { - return read(vaddr); - } - std::uint64_t MemoryRead64(u64 vaddr) override { - return read(vaddr); + u64 MemoryRead(u64 vaddr, size_t size) override { + switch (size) { + case sizeof(u64): return read(vaddr); + case sizeof(u32): return read(vaddr); + case sizeof(u16): return read(vaddr); + case sizeof(u8): return read(vaddr); + default: std::abort(); + } } Vector MemoryRead128(u64 vaddr) override { return read(vaddr); } - void MemoryWrite8(u64 vaddr, std::uint8_t value) override { - write(vaddr, value); - } - void MemoryWrite16(u64 vaddr, std::uint16_t value) override { - write(vaddr, value); - } - void MemoryWrite32(u64 vaddr, std::uint32_t value) override { - write(vaddr, value); - } - void MemoryWrite64(u64 vaddr, std::uint64_t value) override { - write(vaddr, value); + void MemoryWrite(u64 vaddr, std::uint64_t value, size_t size) override { + switch (size) { + case sizeof(u64): return write(vaddr, u64(value)); + case sizeof(u32): return write(vaddr, u32(value)); + case sizeof(u16): return write(vaddr, u16(value)); + case sizeof(u8): return write(vaddr, u8(value)); + default: std::abort(); + } } void MemoryWrite128(u64 vaddr, Vector value) override { write(vaddr, value); } bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, [[maybe_unused]] std::uint8_t expected) override { - MemoryWrite8(vaddr, value); + MemoryWrite(vaddr, value, sizeof(u8)); return true; } bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, [[maybe_unused]] std::uint16_t expected) override { - MemoryWrite16(vaddr, value); + MemoryWrite(vaddr, value, sizeof(u16)); return true; } bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, [[maybe_unused]] std::uint32_t expected) override { - MemoryWrite32(vaddr, value); + MemoryWrite(vaddr, value, sizeof(u32)); return true; } bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, [[maybe_unused]] std::uint64_t expected) override { - MemoryWrite64(vaddr, value); + MemoryWrite(vaddr, value, sizeof(u64)); return true; } bool MemoryWriteExclusive128(u64 vaddr, Vector value, [[maybe_unused]] Vector expected) override { diff --git a/src/dynarmic/tests/CMakeLists.txt b/src/dynarmic/tests/CMakeLists.txt index ca44dce593..15a77e4b5c 100644 --- a/src/dynarmic/tests/CMakeLists.txt +++ b/src/dynarmic/tests/CMakeLists.txt @@ -24,7 +24,7 @@ add_executable(dynarmic_tests A64/real_world.cpp A64/testenv.h ) -target_link_libraries(dynarmic_tests PRIVATE merry::oaknut unordered_dense::unordered_dense) +target_link_libraries(dynarmic_tests PRIVATE merry::oaknut) if (DYNARMIC_TESTS_USE_UNICORN) target_link_libraries(dynarmic_tests PRIVATE Unicorn::Unicorn) @@ -72,7 +72,7 @@ add_executable(dynarmic_print_info print_info.cpp ) create_target_directory_groups(dynarmic_print_info) -target_link_libraries(dynarmic_print_info PRIVATE dynarmic fmt::fmt unordered_dense::unordered_dense) +target_link_libraries(dynarmic_print_info PRIVATE dynarmic fmt::fmt) if (BOOST_NO_HEADERS) target_link_libraries(dynarmic_print_info PRIVATE Boost::variant Boost::icl Boost::pool) else() @@ -93,7 +93,7 @@ add_executable(dynarmic_test_generator create_target_directory_groups(dynarmic_test_generator) -target_link_libraries(dynarmic_test_generator PRIVATE dynarmic fmt::fmt unordered_dense::unordered_dense) +target_link_libraries(dynarmic_test_generator PRIVATE dynarmic fmt::fmt) if (BOOST_NO_HEADERS) target_link_libraries(dynarmic_test_generator PRIVATE Boost::variant Boost::icl Boost::pool) else() @@ -110,7 +110,7 @@ add_executable(dynarmic_test_reader test_reader.cpp ) create_target_directory_groups(dynarmic_test_reader) -target_link_libraries(dynarmic_test_reader PRIVATE dynarmic fmt::fmt unordered_dense::unordered_dense) +target_link_libraries(dynarmic_test_reader PRIVATE dynarmic fmt::fmt) if (BOOST_NO_HEADERS) target_link_libraries(dynarmic_test_reader PRIVATE Boost::variant Boost::icl Boost::pool) else() @@ -123,7 +123,7 @@ target_compile_definitions(dynarmic_test_reader PRIVATE FMT_USE_USER_DEFINED_LIT # create_target_directory_groups(dynarmic_tests) -target_link_libraries(dynarmic_tests PRIVATE dynarmic Catch2::Catch2WithMain fmt::fmt unordered_dense::unordered_dense) +target_link_libraries(dynarmic_tests PRIVATE dynarmic Catch2::Catch2WithMain fmt::fmt) if (BOOST_NO_HEADERS) target_link_libraries(dynarmic_tests PRIVATE Boost::variant Boost::icl Boost::pool) else() diff --git a/src/dynarmic/tests/print_info.cpp b/src/dynarmic/tests/print_info.cpp index 988a8d294e..87f4438ad4 100644 --- a/src/dynarmic/tests/print_info.cpp +++ b/src/dynarmic/tests/print_info.cpp @@ -18,6 +18,7 @@ #include #include #include +#include "dynarmic/frontend/A32/translate/translate_callbacks.h" #include "dynarmic/mcl/bit.hpp" #include "common/common_types.h" @@ -118,36 +119,47 @@ public: u64 ticks_left = 0; std::map memory; - std::uint8_t MemoryRead8(u32 vaddr) override { - if (auto iter = memory.find(vaddr); iter != memory.end()) { - return iter->second; + u64 MemoryRead(Dynarmic::A32::VAddr vaddr, size_t size) override { + switch (size) { + case sizeof(u64): + return MemoryRead(vaddr, sizeof(u32)) + | MemoryRead(vaddr + sizeof(u32), sizeof(u32)) << 32; + case sizeof(u32): + return MemoryRead(vaddr, sizeof(u16)) + | MemoryRead(vaddr + sizeof(u16), sizeof(u16)) << 16; + case sizeof(u16): + return MemoryRead(vaddr, sizeof(u8)) + | MemoryRead(vaddr + sizeof(u8), sizeof(u8)) << 8; + case sizeof(u8): { + if (auto const it = memory.find(vaddr); it != memory.end()) + return it->second; + return 0; + } + default: + std::abort(); } - return 0; - } - std::uint16_t MemoryRead16(u32 vaddr) override { - return u16(MemoryRead8(vaddr)) | u16(MemoryRead8(vaddr + 1)) << 8; - } - std::uint32_t MemoryRead32(u32 vaddr) override { - return u32(MemoryRead16(vaddr)) | u32(MemoryRead16(vaddr + 2)) << 16; - } - std::uint64_t MemoryRead64(u32 vaddr) override { - return u64(MemoryRead32(vaddr)) | u64(MemoryRead32(vaddr + 4)) << 32; } - void MemoryWrite8(u32 vaddr, std::uint8_t value) override { - memory[vaddr] = value; - } - void MemoryWrite16(u32 vaddr, std::uint16_t value) override { - MemoryWrite8(vaddr, static_cast(value)); - MemoryWrite8(vaddr + 1, static_cast(value >> 8)); - } - void MemoryWrite32(u32 vaddr, std::uint32_t value) override { - MemoryWrite16(vaddr, static_cast(value)); - MemoryWrite16(vaddr + 2, static_cast(value >> 16)); - } - void MemoryWrite64(u32 vaddr, std::uint64_t value) override { - MemoryWrite32(vaddr, static_cast(value)); - MemoryWrite32(vaddr + 4, static_cast(value >> 32)); + void MemoryWrite(Dynarmic::A32::VAddr vaddr, u64 value, size_t size) override { + switch (size) { + case sizeof(u64): + MemoryWrite(vaddr, u32(value), sizeof(u32)); + MemoryWrite(vaddr + 4, u32(value >> 32), sizeof(u32)); + break; + case sizeof(u32): + MemoryWrite(vaddr, u16(value), sizeof(u16)); + MemoryWrite(vaddr + 2, u16(value >> 16), sizeof(u16)); + break; + case sizeof(u16): + MemoryWrite(vaddr, u8(value), sizeof(u8)); + MemoryWrite(vaddr + 1, u8(value >> 8), sizeof(u8)); + break; + case sizeof(u8): + memory[vaddr] = value; + break; + default: + std::abort(); + } } void CallSVC(std::uint32_t swi) override { @@ -224,15 +236,15 @@ void ExecuteA32Instruction(u32 instruction) { fmt::print("value: "); if (const auto value = get_value()) { *(iter->second) = *value; - fmt::print("> {} = 0x{:08x}\n", reg_name, *value); + fmt::print("> {} = {:#08x}\n", reg_name, *value); } } else if (reg_name.starts_with("m")) { fmt::print("address: "); if (const auto address = get_value()) { fmt::print("value: "); if (const auto value = get_value()) { - env.MemoryWrite32(*address, *value); - fmt::print("> mem[0x{:08x}] = 0x{:08x}\n", *address, *value); + env.MemoryWrite(*address, *value, sizeof(u32)); + fmt::print("> mem[{:#08x}] = {:#08x}\n", *address, *value); } } } else if (reg_name == "exit" || reg_name == "end" || reg_name.starts_with("q")) { @@ -247,8 +259,8 @@ void ExecuteA32Instruction(u32 instruction) { cpu.SetFpscr(fpscr); const u32 initial_pc = regs[15]; - env.MemoryWrite32(initial_pc + 0, instruction); - env.MemoryWrite32(initial_pc + 4, 0xEAFFFFFE); // B +0 + env.MemoryWrite(initial_pc + 0, instruction, sizeof(u32)); + env.MemoryWrite(initial_pc + 4, 0xEAFFFFFE, sizeof(u32)); // B +0 cpu.Run(); fmt::print("{}", fmt::join(cpu.Disassemble(), "\n")); @@ -282,20 +294,17 @@ int main(int argc, char** argv) { return 1; } - const char* const hex_instruction = [argv] { - if (strlen(argv[2]) > 2 && argv[2][0] == '0' && argv[2][1] == 'x') { - return argv[2] + 2; - } - return argv[2]; + const char* const hex_instruction = [s = argv[2]] { + if (strlen(s) > 2 && s[0] == '0' && s[1] == 'x') + return s + 2; + return s; }(); - if (strlen(hex_instruction) > 8) { fmt::print("hex string too long\n"); return 1; } const u32 instruction = strtol(hex_instruction, nullptr, 16); - if (strcmp(argv[1], "a32") == 0) { PrintA32Instruction(instruction); } else if (strcmp(argv[1], "a64") == 0) { @@ -306,13 +315,11 @@ int main(int argc, char** argv) { fmt::print("Invalid mode: {}\nValid values: a32, a64, thumb\n", argv[1]); return 1; } - if (argc == 4) { if (strcmp(argv[3], "-exec") != 0) { fmt::print("Invalid option {}\n", argv[3]); return 1; } - if (strcmp(argv[1], "a32") == 0) { ExecuteA32Instruction(instruction); } else { @@ -320,6 +327,5 @@ int main(int argc, char** argv) { return 1; } } - return 0; } diff --git a/src/dynarmic/tests/rand_int.h b/src/dynarmic/tests/rand_int.h index 3997a43130..1c62d94705 100644 --- a/src/dynarmic/tests/rand_int.h +++ b/src/dynarmic/tests/rand_int.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + /* This file is part of the dynarmic project. * Copyright (c) 2020 MerryMage * SPDX-License-Identifier: 0BSD @@ -17,11 +20,9 @@ inline std::mt19937 g_rand_int_generator = [] { } // namespace detail template + requires std::is_integral_v + && (!std::is_same_v && !std::is_same_v) T RandInt(T min, T max) { - static_assert(std::is_integral_v, "T must be an integral type."); - static_assert(!std::is_same_v && !std::is_same_v, - "Using char with uniform_int_distribution is undefined behavior."); - std::uniform_int_distribution rand(min, max); return rand(detail::g_rand_int_generator); } diff --git a/src/dynarmic/tests/test_generator.cpp b/src/dynarmic/tests/test_generator.cpp index dacb8a6004..35163fffcf 100644 --- a/src/dynarmic/tests/test_generator.cpp +++ b/src/dynarmic/tests/test_generator.cpp @@ -354,6 +354,8 @@ u32 GenRandomA64Inst(u64 pc, bool is_last_inst) { "MSR_reg", "MSR_imm", "MRS", + // generates F16 + "FCADD_vec", }; for (const auto& [fn, bitstring] : list) { @@ -440,7 +442,6 @@ void RunTestInstance(Dynarmic::A32::Jit& jit, std::copy(instructions.begin(), instructions.end(), jit_env.code_mem.begin() + num_words); jit_env.PadCodeMem(); jit_env.modified_memory.clear(); - jit_env.interrupts.clear(); jit.Regs() = regs; jit.ExtRegs() = vecs; @@ -510,7 +511,6 @@ void RunTestInstance(Dynarmic::A64::Jit& jit, jit_env.code_mem.emplace_back(0x14000000); // B . jit_env.code_mem_start_address = start_address; jit_env.modified_memory.clear(); - jit_env.interrupts.clear(); jit.SetRegisters(regs); jit.SetVectors(vecs); @@ -536,36 +536,52 @@ void RunTestInstance(Dynarmic::A64::Jit& jit, fmt::print("initial_regs:"); for (u64 i : regs) fmt::print(" {:016x}", i); - fmt::print("\n"); - fmt::print("initial_vecs:"); + fmt::print( + "\n" + "initial_vecs:" + ); for (auto i : vecs) fmt::print(" {:016x}:{:016x}", i[0], i[1]); - fmt::print("\n"); - fmt::print("initial_sp: {:016x}\n", initial_sp); - fmt::print("initial_pstate: {:08x}\n", pstate); - fmt::print("initial_fpcr: {:08x}\n", fpcr); - fmt::print("final_regs:"); + fmt::print( + "\n" + "initial_sp: {:016x}\n" + "initial_pstate: {:08x}\n" + "initial_fpcr: {:08x}\n" + "final_regs:" + , initial_sp + , pstate + , fpcr + ); for (u64 i : jit.GetRegisters()) fmt::print(" {:016x}", i); - fmt::print("\n"); - fmt::print("final_vecs:"); + fmt::print( + "\n" + "final_vecs:" + ); for (auto i : jit.GetVectors()) fmt::print(" {:016x}:{:016x}", i[0], i[1]); - fmt::print("\n"); - fmt::print("final_sp: {:016x}\n", jit.GetSP()); - fmt::print("final_pc: {:016x}\n", jit.GetPC()); - fmt::print("final_pstate: {:08x}\n", jit.GetPstate()); - fmt::print("final_fpcr: {:08x}\n", jit.GetFpcr()); - fmt::print("final_qc : {}\n", FP::FPSR{jit.GetFpsr()}.QC()); - fmt::print("mod_mem:"); + fmt::print( + "\n" + "final_sp: {:016x}\n" + "final_pc: {:016x}\n" + "final_pstate: {:08x}\n" + "final_fpcr: {:08x}\n" + "final_qc : {}\n" + "mod_mem:" + , jit.GetSP() + , jit.GetPC() + , jit.GetPstate() + , jit.GetFpcr() + , FP::FPSR{jit.GetFpsr()}.QC() + ); for (auto [addr, value] : jit_env.modified_memory) fmt::print(" {:08x}:{:02x}", addr, value); - fmt::print("\n"); - fmt::print("interrupts:\n"); - for (const auto& i : jit_env.interrupts) - std::puts(i.c_str()); - fmt::print("===\n"); - fmt::print("{}", jit.Disassemble()); + fmt::print( + "\n" + "===\n" + "{}" + , jit.Disassemble() + ); } } diff --git a/src/dynarmic/tests/test_reader.cpp b/src/dynarmic/tests/test_reader.cpp index 1c2cba1c44..d44e698c27 100644 --- a/src/dynarmic/tests/test_reader.cpp +++ b/src/dynarmic/tests/test_reader.cpp @@ -99,7 +99,6 @@ void RunTestInstance(Dynarmic::A32::Jit& jit, std::copy(instructions.begin(), instructions.end(), jit_env.code_mem.begin() + num_words); jit_env.PadCodeMem(); jit_env.modified_memory.clear(); - jit_env.interrupts.clear(); jit.Regs() = regs; jit.ExtRegs() = vecs; @@ -192,7 +191,6 @@ void RunTestInstance(A64::Jit& jit, jit_env.code_mem.emplace_back(0x14000000); // B . jit_env.code_mem_start_address = start_address; jit_env.modified_memory.clear(); - jit_env.interrupts.clear(); jit.SetRegisters(regs); jit.SetVectors(vecs); diff --git a/src/dynarmic/tests/unicorn_emu/a32_unicorn.cpp b/src/dynarmic/tests/unicorn_emu/a32_unicorn.cpp index c0e1e13d9b..1fe09cdef0 100644 --- a/src/dynarmic/tests/unicorn_emu/a32_unicorn.cpp +++ b/src/dynarmic/tests/unicorn_emu/a32_unicorn.cpp @@ -290,7 +290,7 @@ bool A32Unicorn::UnmappedMemoryHook(uc_engine* uc, uc_mem_type auto page = std::make_unique(); page->address = base_address; for (size_t i = 0; i < page->data.size(); ++i) - page->data[i] = this_->testenv.MemoryRead8(static_cast(base_address + i)); + page->data[i] = u8(this_->testenv.MemoryRead(u32(base_address + i), sizeof(u8))); uc_err err = uc_mem_map_ptr(uc, base_address, page->data.size(), permissions, page->data.data()); if (err == UC_ERR_MAP) @@ -321,24 +321,7 @@ bool A32Unicorn::UnmappedMemoryHook(uc_engine* uc, uc_mem_type template bool A32Unicorn::MemoryWriteHook(uc_engine* /*uc*/, uc_mem_type /*type*/, u32 start_address, int size, u64 value, void* user_data) { auto* this_ = static_cast(user_data); - - switch (size) { - case 1: - this_->testenv.MemoryWrite8(start_address, static_cast(value)); - break; - case 2: - this_->testenv.MemoryWrite16(start_address, static_cast(value)); - break; - case 4: - this_->testenv.MemoryWrite32(start_address, static_cast(value)); - break; - case 8: - this_->testenv.MemoryWrite64(start_address, value); - break; - default: - UNREACHABLE(); - } - + this_->testenv.MemoryWrite(start_address, value, size); return true; } diff --git a/src/dynarmic/tests/unicorn_emu/a64_unicorn.cpp b/src/dynarmic/tests/unicorn_emu/a64_unicorn.cpp index c9a194d50a..4afb6a6859 100644 --- a/src/dynarmic/tests/unicorn_emu/a64_unicorn.cpp +++ b/src/dynarmic/tests/unicorn_emu/a64_unicorn.cpp @@ -197,7 +197,7 @@ bool A64Unicorn::UnmappedMemoryHook(uc_engine* uc, uc_mem_type /*type*/, u64 sta auto page = std::make_unique(); page->address = base_address; for (size_t i = 0; i < page->data.size(); ++i) - page->data[i] = this_->testenv.MemoryRead8(base_address + i); + page->data[i] = u8(this_->testenv.MemoryRead(base_address + i, sizeof(u8))); uc_err err = uc_mem_map_ptr(uc, base_address, page->data.size(), permissions, page->data.data()); if (err == UC_ERR_MAP) @@ -227,23 +227,6 @@ bool A64Unicorn::UnmappedMemoryHook(uc_engine* uc, uc_mem_type /*type*/, u64 sta bool A64Unicorn::MemoryWriteHook(uc_engine* /*uc*/, uc_mem_type /*type*/, u64 start_address, int size, u64 value, void* user_data) { auto* this_ = static_cast(user_data); - - switch (size) { - case 1: - this_->testenv.MemoryWrite8(start_address, static_cast(value)); - break; - case 2: - this_->testenv.MemoryWrite16(start_address, static_cast(value)); - break; - case 4: - this_->testenv.MemoryWrite32(start_address, static_cast(value)); - break; - case 8: - this_->testenv.MemoryWrite64(start_address, value); - break; - default: - UNREACHABLE(); - } - + this_->testenv.MemoryWrite(start_address, value, size); return true; } diff --git a/src/dynarmic/tests/x64_cpu_info.cpp b/src/dynarmic/tests/x64_cpu_info.cpp index 8e67f0fc39..8837e64ae0 100644 --- a/src/dynarmic/tests/x64_cpu_info.cpp +++ b/src/dynarmic/tests/x64_cpu_info.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + /* This file is part of the dynarmic project. * Copyright (c) 2020 MerryMage * SPDX-License-Identifier: 0BSD @@ -11,7 +14,7 @@ #include #include -#include +#include "dynarmic/backend/x64/xbyak.h" TEST_CASE("Host CPU supports", "[a64]") { using Cpu = Xbyak::util::Cpu; @@ -36,8 +39,6 @@ TEST_CASE("Host CPU supports", "[a64]") { X(tAMX_TILE), X(tAVX), X(tAVX2), - X(tAVX512_4FMAPS), - X(tAVX512_4VNNIW), X(tAVX512_BF16), X(tAVX512_BITALG), X(tAVX512_FP16), @@ -50,10 +51,8 @@ TEST_CASE("Host CPU supports", "[a64]") { X(tAVX512BW), X(tAVX512CD), X(tAVX512DQ), - X(tAVX512ER), X(tAVX512F), X(tAVX512IFMA), - X(tAVX512PF), X(tAVX512VBMI), X(tAVX512VL), X(tAVX_VNNI), @@ -81,7 +80,6 @@ TEST_CASE("Host CPU supports", "[a64]") { X(tPCLMULQDQ), X(tPOPCNT), X(tPREFETCHW), - X(tPREFETCHWT1), X(tRDRAND), X(tRDSEED), X(tRDTSCP), diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp index f275727c17..f61192dfae 100644 --- a/src/frontend_common/config.cpp +++ b/src/frontend_common/config.cpp @@ -238,33 +238,27 @@ void Config::ReadControlValues() { void Config::ReadMotionTouchValues() { Settings::values.touch_from_button_maps.clear(); int num_touch_from_button_maps = BeginArray(std::string("touch_from_button_maps")); - if (num_touch_from_button_maps > 0) { for (int i = 0; i < num_touch_from_button_maps; ++i) { SetArrayIndex(i); - Settings::TouchFromButtonMap map; map.name = ReadStringSetting(std::string("name"), std::string("default")); - - const int num_touch_maps = BeginArray(std::string("entries")); - map.buttons.reserve(num_touch_maps); + int const num_touch_maps = BeginArray(std::string("entries")); + map.buttons.resize(num_touch_maps); for (int j = 0; j < num_touch_maps; j++) { SetArrayIndex(j); - std::string touch_mapping = ReadStringSetting(std::string("bind")); - map.buttons.emplace_back(std::move(touch_mapping)); + map.buttons[j] = ReadStringSetting(std::string("bind")); } EndArray(); // entries Settings::values.touch_from_button_maps.emplace_back(std::move(map)); } } else { - Settings::values.touch_from_button_maps.emplace_back( - Settings::TouchFromButtonMap{"default", {}}); + Settings::values.touch_from_button_maps.emplace_back(Settings::TouchFromButtonMap{"default", {}}); num_touch_from_button_maps = 1; } EndArray(); // touch_from_button_maps - Settings::values.touch_from_button_map_index = std::clamp( - Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1); + Settings::values.touch_from_button_map_index = (std::min)(Settings::values.touch_from_button_map_index.GetValue(), u32(num_touch_from_button_maps - 1)); } void Config::ReadCoreValues() { @@ -305,12 +299,17 @@ void Config::ReadDataStorageValues() { void Config::ReadDebuggingValues() { BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging)); - // Intentionally not using the QT default setting as this is intended to be changed in the ini - Settings::values.record_frame_times = - ReadBooleanSetting(std::string("record_frame_times"), std::make_optional(false)); + if (global) { + // Intentionally not using the QT default setting as this is intended to be changed in the ini + Settings::values.record_frame_times = + ReadBooleanSetting(std::string("record_frame_times"), std::make_optional(false)); - ReadCategory(Settings::Category::Debugging); - ReadCategory(Settings::Category::DebuggingGraphics); + ReadCategory(Settings::Category::Debugging); + ReadCategory(Settings::Category::DebuggingGraphics); + } else { + ReadSettingGeneric(&Settings::values.program_args); + ReadSettingGeneric(&Settings::values.debug_knobs); + } EndGroup(); } @@ -421,12 +420,12 @@ void Config::ReadLibraryAppletValues() { void Config::ReadValues() { if (global) { ReadDataStorageValues(); - ReadDebuggingValues(); ReadDisabledAddOnValues(); ReadServiceValues(); ReadWebServiceValues(); ReadMiscellaneousValues(); } + ReadDebuggingValues(); ReadLibraryAppletValues(); ReadNetworkValues(); ReadControlValues(); @@ -501,15 +500,12 @@ void Config::SaveMotionTouchValues() { BeginArray(std::string("touch_from_button_maps")); for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) { SetArrayIndex(int(p)); - WriteStringSetting(std::string("name"), Settings::values.touch_from_button_maps[p].name, - std::make_optional(std::string("default"))); - + WriteStringSetting(std::string("name"), Settings::values.touch_from_button_maps[p].name, std::make_optional(std::string("default"))); BeginArray(std::string("entries")); for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size(); ++q) { SetArrayIndex(int(q)); - WriteStringSetting(std::string("bind"), - Settings::values.touch_from_button_maps[p].buttons[q]); + WriteStringSetting(std::string("bind"), Settings::values.touch_from_button_maps[p].buttons[q]); } EndArray(); // entries } @@ -520,13 +516,13 @@ void Config::SaveValues() { if (global) { LOG_DEBUG(Config, "Saving global generic configuration values"); SaveDataStorageValues(); - SaveDebuggingValues(); SaveDisabledAddOnValues(); SaveWebServiceValues(); SaveMiscellaneousValues(); } else { LOG_DEBUG(Config, "Saving only generic configuration values"); } + SaveDebuggingValues(); SaveLibraryAppletValues(); SaveNetworkValues(); SaveControlValues(); @@ -609,11 +605,16 @@ void Config::SaveDataStorageValues() { void Config::SaveDebuggingValues() { BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging)); - // Intentionally not using the QT default setting as this is intended to be changed in the ini - WriteBooleanSetting(std::string("record_frame_times"), Settings::values.record_frame_times); + if (global) { + // Intentionally not using the QT default setting as this is intended to be changed in the ini + WriteBooleanSetting(std::string("record_frame_times"), Settings::values.record_frame_times); - WriteCategory(Settings::Category::Debugging); - WriteCategory(Settings::Category::DebuggingGraphics); + WriteCategory(Settings::Category::Debugging); + WriteCategory(Settings::Category::DebuggingGraphics); + } else { + WriteSettingGeneric(&Settings::values.program_args); + WriteSettingGeneric(&Settings::values.debug_knobs); + } EndGroup(); } @@ -638,8 +639,7 @@ void Config::SaveDisabledAddOnValues() { BeginArray(std::string("disabled")); for (std::size_t j = 0; j < elem.second.size(); ++j) { SetArrayIndex(int(j)); - WriteStringSetting(std::string("d"), elem.second[j], - std::make_optional(std::string(""))); + WriteStringSetting(std::string("d"), elem.second[j], std::make_optional(std::string(""))); } EndArray(); // disabled ++i; @@ -733,21 +733,18 @@ s64 Config::ReadIntegerSetting(const std::string& key, const std::optional std::string full_key = GetFullKey(key, false); if (!default_value.has_value()) { try { - return std::stoll( - std::string(config->GetValue(GetSection().c_str(), full_key.c_str(), "0"))); + return std::stoll(std::string(config->GetValue(GetSection().c_str(), full_key.c_str(), "0"))); } catch (...) { return 0; } } s64 result = 0; - if (config->GetBoolValue(GetSection().c_str(), - std::string(full_key).append("\\default").c_str(), true)) { + if (config->GetBoolValue(GetSection().c_str(), std::string(full_key).append("\\default").c_str(), true)) { result = default_value.value(); } else { try { - result = std::stoll(std::string(config->GetValue( - GetSection().c_str(), full_key.c_str(), ToString(default_value.value()).c_str()))); + result = std::stoll(std::string(config->GetValue(GetSection().c_str(), full_key.c_str(), ToString(default_value.value()).c_str()))); } catch (...) { result = default_value.value(); } @@ -919,14 +916,12 @@ void Config::ReadSettingGeneric(Settings::BasicSetting* const setting) { bool use_global = true; if (setting->Switchable() && !global) { - use_global = - ReadBooleanSetting(std::string(key).append("\\use_global"), std::make_optional(true)); + use_global = ReadBooleanSetting(std::string(key).append("\\use_global"), std::make_optional(true)); setting->SetGlobal(use_global); } if (global || !use_global) { - const bool is_default = - ReadBooleanSetting(std::string(key).append("\\default"), std::make_optional(true)); + const bool is_default = ReadBooleanSetting(std::string(key).append("\\default"), std::make_optional(true)); if (!is_default) { setting->LoadString(ReadStringSetting(key, default_value)); } else { @@ -1009,7 +1004,7 @@ std::string Config::AdjustOutputString(const std::string& string) { // Windows requires that two forward slashes are used at the start of a path for unmapped // network drives so we have to watch for that here -#ifndef ANDROID +#ifndef __ANDROID__ if (string.substr(0, 2) == "//") { boost::replace_all(adjusted_string, "//", "/"); adjusted_string.insert(0, "/"); @@ -1050,10 +1045,9 @@ std::string Config::GetFullKey(const std::string& key, bool skipArrayIndex) { int Config::BeginArray(const std::string& array) { array_stack.push_back(ConfigArray{AdjustKey(array), 0, 0}); - const int size = config->GetLongValue(GetSection().c_str(), - GetFullKey(std::string("size"), true).c_str(), 0); - array_stack.back().size = size; - return size; + const int size = config->GetLongValue(GetSection().c_str(), GetFullKey(std::string("size"), true).c_str(), 0); + array_stack.back().size = (std::max)(0, size); + return array_stack.back().size; } void Config::EndArray() { @@ -1071,7 +1065,7 @@ void Config::EndArray() { // Edge-case where the first array created doesn't have a name config->SetValue(GetSection().c_str(), std::string("size").c_str(), ToString(size).c_str()); } else { - const auto key = GetFullKey(std::string("size"), true); + auto const key = GetFullKey(std::string("size"), true); config->SetValue(GetSection().c_str(), key.c_str(), ToString(size).c_str()); } diff --git a/src/frontend_common/content_manager.h b/src/frontend_common/content_manager.h index 4f3112e3fb..6d79098efa 100644 --- a/src/frontend_common/content_manager.h +++ b/src/frontend_common/content_manager.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2024 yuzu Emulator Project @@ -163,7 +163,9 @@ inline InstallResult InstallNSP(Core::System& system, FileSys::VfsFilesystem& vf std::shared_ptr nsp; FileSys::VirtualFile file = vfs.OpenFile(filename, FileSys::OpenMode::Read); - if (boost::to_lower_copy(file->GetName()).ends_with("nsp")) { + const auto lower_name = boost::to_lower_copy(file->GetName()); + + if (lower_name.ends_with("nsp") || lower_name.ends_with("nsz")) { nsp = std::make_shared(file); if (nsp->IsExtractedType()) { return InstallResult::Failure; diff --git a/src/frontend_common/data_manager.cpp b/src/frontend_common/data_manager.cpp index 280cbba4ab..e4e9a32ce1 100644 --- a/src/frontend_common/data_manager.cpp +++ b/src/frontend_common/data_manager.cpp @@ -46,10 +46,10 @@ const std::string GetDataDirString(DataDir dir, const std::string &user_id) u64 ClearDir(DataDir dir, const std::string &user_id) { fs::path data_dir = GetDataDir(dir, user_id); - u64 result = fs::remove_all(data_dir); - + std::error_code ec; + u64 result = fs::remove_all(data_dir, ec); // mkpath at the end just so it actually exists - fs::create_directories(data_dir); + fs::create_directories(data_dir, ec); return result; } diff --git a/src/frontend_common/data_manager.h b/src/frontend_common/data_manager.h index afb7cb0d2b..b43067d984 100644 --- a/src/frontend_common/data_manager.h +++ b/src/frontend_common/data_manager.h @@ -1,8 +1,7 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef DATA_MANAGER_H -#define DATA_MANAGER_H +#pragma once #include "common/common_types.h" #include @@ -21,5 +20,3 @@ std::string ReadableBytesSize(u64 size) noexcept; u64 DataDirSize(DataDir dir); }; // namespace FrontendCommon::DataManager - -#endif // DATA_MANAGER_H diff --git a/src/frontend_common/firmware_manager.cpp b/src/frontend_common/firmware_manager.cpp index 79af202ed8..c7f1177ee2 100644 --- a/src/frontend_common/firmware_manager.cpp +++ b/src/frontend_common/firmware_manager.cpp @@ -13,7 +13,7 @@ #include "core/crypto/key_manager.h" #include "frontend_common/content_manager.h" -#ifdef ANDROID +#ifdef __ANDROID__ #include #include #include @@ -25,7 +25,7 @@ FirmwareManager::InstallKeys(std::string location, std::string extension) { const auto keys_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::KeysDir); -#ifdef ANDROID +#ifdef __ANDROID__ JNIEnv *env = Common::Android::GetEnvForThread(); jstring jsrc = Common::Android::ToJString(env, location); diff --git a/src/frontend_common/firmware_manager.h b/src/frontend_common/firmware_manager.h index 94469e9295..4c1297431d 100644 --- a/src/frontend_common/firmware_manager.h +++ b/src/frontend_common/firmware_manager.h @@ -1,8 +1,7 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef FIRMWARE_MANAGER_H -#define FIRMWARE_MANAGER_H +#pragma once #include "common/common_types.h" #include "core/core.h" @@ -107,5 +106,3 @@ inline std::pair GetFirmwareVersion // TODO(crueter): GET AS STRING } - -#endif diff --git a/src/frontend_common/mod_manager.cpp b/src/frontend_common/mod_manager.cpp index 35d7068f2f..b1adaebf91 100644 --- a/src/frontend_common/mod_manager.cpp +++ b/src/frontend_common/mod_manager.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include "common/fs/fs.h" #include "common/fs/fs_types.h" diff --git a/src/frontend_common/mod_manager.h b/src/frontend_common/mod_manager.h index 57fdbc3533..79c9e8ade7 100644 --- a/src/frontend_common/mod_manager.h +++ b/src/frontend_common/mod_manager.h @@ -5,6 +5,7 @@ #include #include +#include #include "common/common_types.h" namespace FrontendCommon { diff --git a/src/hid_core/CMakeLists.txt b/src/hid_core/CMakeLists.txt index c740cdbe7f..e617160834 100644 --- a/src/hid_core/CMakeLists.txt +++ b/src/hid_core/CMakeLists.txt @@ -151,7 +151,7 @@ if (MSVC) ) else() target_compile_options(hid_core PRIVATE - $<$:-Werror=conversion> + $<$:-Wconversion> $<$:-Wno-sign-conversion> $<$:-Wno-cast-function-type> $<$:-fsized-deallocation>) diff --git a/src/hid_core/frontend/emulated_console.cpp b/src/hid_core/frontend/emulated_console.cpp index 114c22fb74..fd34233122 100644 --- a/src/hid_core/frontend/emulated_console.cpp +++ b/src/hid_core/frontend/emulated_console.cpp @@ -1,7 +1,11 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/settings.h" +#include "common/assert.h" #include "hid_core/frontend/emulated_console.h" #include "hid_core/frontend/input_converter.h" @@ -96,6 +100,7 @@ void EmulatedConsole::ReloadInput() { motion.gyro = emulated_motion.GetGyroscope(); motion.rotation = emulated_motion.GetRotations(); motion.orientation = emulated_motion.GetOrientation(); + motion.quaternion = emulated_motion.GetQuaternion(); motion.is_at_rest = !emulated_motion.IsMoving(motion_sensitivity); // Unique index for identifying touch device source @@ -165,12 +170,12 @@ void EmulatedConsole::SetMotion(const Common::Input::CallbackStatus& callback) { auto& emulated = console.motion_values.emulated; raw_status = TransformToMotion(callback); - emulated.SetAcceleration(Common::Vec3f{ + emulated.SetAcceleration(Common::Vec{ raw_status.accel.x.value, raw_status.accel.y.value, raw_status.accel.z.value, }); - emulated.SetGyroscope(Common::Vec3f{ + emulated.SetGyroscope(Common::Vec{ raw_status.gyro.x.value, raw_status.gyro.y.value, raw_status.gyro.z.value, @@ -308,17 +313,15 @@ void EmulatedConsole::TriggerOnChange(ConsoleTriggerType type) { int EmulatedConsole::SetCallback(ConsoleUpdateCallback update_callback) { std::scoped_lock lock{callback_mutex}; + ++last_callback_key; callback_list.insert_or_assign(last_callback_key, std::move(update_callback)); - return last_callback_key++; + return last_callback_key; } void EmulatedConsole::DeleteCallback(int key) { std::scoped_lock lock{callback_mutex}; - const auto& iterator = callback_list.find(key); - if (iterator == callback_list.end()) { - LOG_ERROR(Input, "Tried to delete non-existent callback {}", key); - return; - } - callback_list.erase(iterator); + auto const it = callback_list.find(key); + ASSERT_MSG(it != callback_list.end(), "Tried to delete non-existent callback {}", key); + callback_list.erase(it); } } // namespace Core::HID diff --git a/src/hid_core/frontend/emulated_console.h b/src/hid_core/frontend/emulated_console.h index b8ce45f05c..9e2f7e2c1b 100644 --- a/src/hid_core/frontend/emulated_console.h +++ b/src/hid_core/frontend/emulated_console.h @@ -11,14 +11,13 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/input.h" #include "common/param_package.h" #include "common/point.h" -#include "common/quaternion.h" #include "common/vector_math.h" #include "hid_core/frontend/motion_input.h" #include "hid_core/hid_types.h" @@ -43,12 +42,12 @@ using TouchValues = std::array; // Contains all motion related data that is used on the services struct ConsoleMotion { - Common::Vec3f accel{}; - Common::Vec3f gyro{}; - Common::Vec3f rotation{}; - std::array orientation{}; - Common::Quaternion quaternion{}; - Common::Vec3f gyro_bias{}; + Common::Vec accel{}; + Common::Vec gyro{}; + Common::Vec rotation{}; + std::array, 3> orientation{}; + Common::Vec quaternion{}; + Common::Vec gyro_bias{}; f32 verticalization_error{}; bool is_at_rest{}; }; @@ -185,7 +184,7 @@ private: mutable std::mutex mutex; mutable std::mutex callback_mutex; - ankerl::unordered_dense::map callback_list; + ::Common::unordered_map callback_list; int last_callback_key = 0; // Stores the current status of all console input diff --git a/src/hid_core/frontend/emulated_controller.cpp b/src/hid_core/frontend/emulated_controller.cpp index 604414c470..7b4af13232 100644 --- a/src/hid_core/frontend/emulated_controller.cpp +++ b/src/hid_core/frontend/emulated_controller.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -10,6 +10,7 @@ #include #include "common/thread.h" +#include "common/assert.h" #include "hid_core/frontend/emulated_controller.h" #include "hid_core/frontend/input_converter.h" #include "hid_core/hid_util.h" @@ -108,10 +109,10 @@ void EmulatedController::ReloadFromSettings() { // Other or debug controller should always be a pro controller if (npad_id_type != NpadIdType::Other) { SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type)); - original_npad_type = npad_type; + original_npad_type = npad_type.load(); } else { SetNpadStyleIndex(NpadStyleIndex::Fullkey); - original_npad_type = npad_type; + original_npad_type = npad_type.load(); } // Disable special features before disconnecting @@ -179,7 +180,7 @@ void EmulatedController::LoadDevices() { if (npad_id_type == NpadIdType::Player1 || npad_id_type == NpadIdType::Handheld) { camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"}; nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"}; -#ifndef ANDROID +#ifndef __ANDROID__ ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"}; #else android_params = Common::ParamPackage{"engine:android,port:100"}; @@ -577,10 +578,9 @@ void EmulatedController::UnloadInput() { } void EmulatedController::EnableConfiguration() { - std::unique_lock lock1{connect_mutex}, lock2{npad_mutex}; - is_configuring = true; - tmp_is_connected = is_connected; - tmp_npad_type = npad_type; + is_configuring.store(true); + tmp_is_connected.store(is_connected); + tmp_npad_type.store(npad_type); } void EmulatedController::DisableConfiguration() { @@ -600,7 +600,7 @@ void EmulatedController::DisableConfiguration() { Disconnect(); } SetNpadStyleIndex(tmp_npad_type); - original_npad_type = tmp_npad_type; + original_npad_type.store(tmp_npad_type); } // Apply temporary connected status to the real controller @@ -614,19 +614,16 @@ void EmulatedController::DisableConfiguration() { } void EmulatedController::EnableSystemButtons() { - std::unique_lock lock{mutex}; system_buttons_enabled = true; } void EmulatedController::DisableSystemButtons() { - std::unique_lock lock{mutex}; system_buttons_enabled = false; controller.home_button_state.raw = 0; controller.capture_button_state.raw = 0; } void EmulatedController::ResetSystemButtons() { - std::unique_lock lock{mutex}; controller.home_button_state.home.Assign(false); controller.capture_button_state.capture.Assign(false); } @@ -763,16 +760,11 @@ void EmulatedController::StartMotionCalibration() { } } -void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index, - Common::UUID uuid) { - const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type); - const auto& player = Settings::values.players.GetValue()[player_index]; - +void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index, Common::UUID uuid) { if (index >= controller.button_values.size()) { return; } - std::unique_lock lock{mutex}; bool value_changed = false; const auto new_status = TransformToButton(callback); auto& current_status = controller.button_values[index]; @@ -818,7 +810,6 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback controller.debug_pad_button_state.raw = 0; controller.home_button_state.raw = 0; controller.capture_button_state.raw = 0; - lock.unlock(); TriggerOnChange(ControllerTriggerType::Button, false); return; } @@ -922,23 +913,10 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback break; } - lock.unlock(); - - if (!is_connected) { - if (npad_type == NpadStyleIndex::Handheld) { - if (npad_id_type == NpadIdType::Handheld) { - Connect(); - controller_connected[player_index] = true; - } - } else if (npad_type != NpadStyleIndex::Handheld) { - if (npad_id_type == NpadIdType::Player1) { - Connect(); - controller_connected[player_index] = true; - } else if (player.connected && !controller_connected[player_index]) { - Connect(); - controller_connected[player_index] = true; - } - } + const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type); + const auto& player = Settings::values.players.GetValue()[player_index]; + if (player.connected) { + Connect(); } TriggerOnChange(ControllerTriggerType::Button, true); @@ -952,7 +930,6 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback, auto trigger_guard = SCOPE_GUARD { TriggerOnChange(ControllerTriggerType::Stick, !is_configuring); }; - std::unique_lock lock{mutex}; const auto stick_value = TransformToStick(callback); // Only read stick values that have the same uuid or are over the threshold to avoid flapping @@ -1009,7 +986,6 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac auto trigger_guard = SCOPE_GUARD { TriggerOnChange(ControllerTriggerType::Trigger, !is_configuring); }; - std::unique_lock lock{mutex}; const auto trigger_value = TransformToTrigger(callback); // Only read trigger values that have the same uuid or are pressed once @@ -1057,17 +1033,16 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback SCOPE_EXIT { TriggerOnChange(ControllerTriggerType::Motion, !is_configuring); }; - std::unique_lock lock{mutex}; auto& raw_status = controller.motion_values[index].raw_status; auto& emulated = controller.motion_values[index].emulated; raw_status = TransformToMotion(callback); - emulated.SetAcceleration(Common::Vec3f{ + emulated.SetAcceleration(Common::Vec{ raw_status.accel.x.value, raw_status.accel.y.value, raw_status.accel.z.value, }); - emulated.SetGyroscope(Common::Vec3f{ + emulated.SetGyroscope(Common::Vec{ raw_status.gyro.x.value, raw_status.gyro.y.value, raw_status.gyro.z.value, @@ -1093,7 +1068,6 @@ void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback auto trigger_guard = SCOPE_GUARD { TriggerOnChange(ControllerTriggerType::Color, !is_configuring); }; - std::unique_lock lock{mutex}; controller.color_values[index] = TransformToColor(callback); if (is_configuring) { @@ -1136,15 +1110,13 @@ void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback } } -void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback, - std::size_t index) { +void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index) { if (index >= controller.battery_values.size()) { return; } SCOPE_EXIT { TriggerOnChange(ControllerTriggerType::Battery, !is_configuring); }; - std::unique_lock lock{mutex}; controller.battery_values[index] = TransformToBattery(callback); if (is_configuring) { @@ -1209,47 +1181,33 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback SCOPE_EXIT { TriggerOnChange(ControllerTriggerType::IrSensor, !is_configuring); }; - std::unique_lock lock{mutex}; controller.camera_values = TransformToCamera(callback); - - if (is_configuring) { - return; + if (!is_configuring) { + controller.camera_state.sample++; + controller.camera_state.format = Core::IrSensor::ImageTransferProcessorFormat(controller.camera_values.format); + controller.camera_state.data = controller.camera_values.data; } - - controller.camera_state.sample++; - controller.camera_state.format = - static_cast(controller.camera_values.format); - controller.camera_state.data = controller.camera_values.data; } void EmulatedController::SetRingAnalog(const Common::Input::CallbackStatus& callback) { SCOPE_EXIT { TriggerOnChange(ControllerTriggerType::RingController, !is_configuring); }; - std::unique_lock lock{mutex}; const auto force_value = TransformToStick(callback); - controller.ring_analog_value = force_value.x; - - if (is_configuring) { - return; + if (!is_configuring) { + controller.ring_analog_state.force = force_value.x.value; } - - controller.ring_analog_state.force = force_value.x.value; } void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) { SCOPE_EXIT { TriggerOnChange(ControllerTriggerType::Nfc, !is_configuring); }; - std::unique_lock lock{mutex}; controller.nfc_values = TransformToNfc(callback); - - if (is_configuring) { - return; + if (!is_configuring) { + controller.nfc_state = controller.nfc_values; } - - controller.nfc_state = controller.nfc_values; } bool EmulatedController::SetVibration(bool should_vibrate) { @@ -1258,7 +1216,6 @@ bool EmulatedController::SetVibration(bool should_vibrate) { vibration_value.high_amplitude = 1.0f; vibration_value.low_amplitude = 1.0f; } - return SetVibration(DeviceIndex::Left, vibration_value); } @@ -1268,7 +1225,6 @@ bool EmulatedController::SetVibration(u32 slot, Core::HID::VibrationGcErmCommand vibration_value.high_amplitude = 1.0f; vibration_value.low_amplitude = 1.0f; } - return SetVibration(DeviceIndex::Left, vibration_value); } @@ -1632,7 +1588,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles) // Attempt to reconnect with the original type if (npad_type != original_npad_type) { Disconnect(); - const auto current_npad_type = npad_type; + const auto current_npad_type = npad_type.load(); SetNpadStyleIndex(original_npad_type); if (IsControllerSupported()) { Connect(); @@ -1650,7 +1606,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles) // Fallback Fullkey controllers to Pro controllers if (IsControllerFullkey() && supported_style_tag.fullkey) { - LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type); + LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type.load()); SetNpadStyleIndex(NpadStyleIndex::Fullkey); Connect(); return; @@ -1658,7 +1614,7 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles) // Fallback Dual joycon controllers to Pro controllers if (npad_type == NpadStyleIndex::JoyconDual && supported_style_tag.fullkey) { - LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type); + LOG_WARNING(Service_HID, "Reconnecting controller type {} as Pro controller", npad_type.load()); SetNpadStyleIndex(NpadStyleIndex::Fullkey); Connect(); return; @@ -1666,19 +1622,16 @@ void EmulatedController::SetSupportedNpadStyleTag(NpadStyleTag supported_styles) // Fallback Pro controllers to Dual joycon if (npad_type == NpadStyleIndex::Fullkey && supported_style_tag.joycon_dual) { - LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type); + LOG_WARNING(Service_HID, "Reconnecting controller type {} as Dual Joycons", npad_type.load()); SetNpadStyleIndex(NpadStyleIndex::JoyconDual); Connect(); return; } - - LOG_ERROR(Service_HID, "Controller type {} is not supported. Disconnecting controller", - npad_type); + LOG_ERROR(Service_HID, "Controller type {} is not supported. Disconnecting controller", npad_type.load()); } bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const { - std::unique_lock lock{mutex}; - const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type; + const auto type = is_configuring.load() && use_temporary_value ? tmp_npad_type.load() : npad_type.load(); switch (type) { case NpadStyleIndex::Fullkey: case NpadStyleIndex::GameCube: @@ -1693,39 +1646,26 @@ bool EmulatedController::IsControllerFullkey(bool use_temporary_value) const { } bool EmulatedController::IsControllerSupported(bool use_temporary_value) const { - std::unique_lock lock{mutex}; - const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type; + const auto type = is_configuring.load() && use_temporary_value ? tmp_npad_type.load() : npad_type.load(); switch (type) { - case NpadStyleIndex::Fullkey: - return supported_style_tag.fullkey.As(); - case NpadStyleIndex::Handheld: - return supported_style_tag.handheld.As(); - case NpadStyleIndex::JoyconDual: - return supported_style_tag.joycon_dual.As(); - case NpadStyleIndex::JoyconLeft: - return supported_style_tag.joycon_left.As(); - case NpadStyleIndex::JoyconRight: - return supported_style_tag.joycon_right.As(); - case NpadStyleIndex::GameCube: - return supported_style_tag.gamecube.As(); - case NpadStyleIndex::Pokeball: - return supported_style_tag.palma.As(); - case NpadStyleIndex::NES: - return supported_style_tag.lark.As(); - case NpadStyleIndex::SNES: - return supported_style_tag.lucia.As(); - case NpadStyleIndex::N64: - return supported_style_tag.lagoon.As(); - case NpadStyleIndex::SegaGenesis: - return supported_style_tag.lager.As(); - default: - return false; + case NpadStyleIndex::Fullkey: return bool(supported_style_tag.fullkey); + case NpadStyleIndex::Handheld: return bool(supported_style_tag.handheld); + case NpadStyleIndex::JoyconDual: return bool(supported_style_tag.joycon_dual); + case NpadStyleIndex::JoyconLeft: return bool(supported_style_tag.joycon_left); + case NpadStyleIndex::JoyconRight: return bool(supported_style_tag.joycon_right); + case NpadStyleIndex::GameCube: return bool(supported_style_tag.gamecube); + case NpadStyleIndex::Pokeball: return bool(supported_style_tag.palma); + case NpadStyleIndex::NES: return bool(supported_style_tag.lark); + case NpadStyleIndex::SNES: return bool(supported_style_tag.lucia); + case NpadStyleIndex::N64: return bool(supported_style_tag.lagoon); + case NpadStyleIndex::SegaGenesis: return bool(supported_style_tag.lager); + default: return false; } } void EmulatedController::Connect(bool use_temporary_value) { if (!IsControllerSupported(use_temporary_value)) { - const auto type = is_configuring && use_temporary_value ? tmp_npad_type : npad_type; + const auto type = is_configuring.load() && use_temporary_value ? tmp_npad_type.load() : npad_type.load(); LOG_ERROR(Service_HID, "Controller type {} is not supported", type); return; } @@ -1733,12 +1673,10 @@ void EmulatedController::Connect(bool use_temporary_value) { auto trigger_guard = SCOPE_GUARD { TriggerOnChange(ControllerTriggerType::Connected, !is_configuring); }; - std::unique_lock lock1{connect_mutex}, lock2{mutex}; if (is_configuring) { tmp_is_connected = true; return; } - if (is_connected) { trigger_guard.Cancel(); return; @@ -1750,12 +1688,10 @@ void EmulatedController::Disconnect() { auto trigger_guard = SCOPE_GUARD { TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring); }; - std::unique_lock lock1{connect_mutex}, lock2{mutex}; if (is_configuring) { tmp_is_connected = false; return; } - if (!is_connected) { trigger_guard.Cancel(); return; @@ -1764,19 +1700,16 @@ void EmulatedController::Disconnect() { } bool EmulatedController::IsConnected(bool get_temporary_value) const { - std::shared_lock lock{connect_mutex}; if (get_temporary_value && is_configuring) return tmp_is_connected; return is_connected; } NpadIdType EmulatedController::GetNpadIdType() const { - std::shared_lock lock{mutex}; return npad_id_type; } NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) const { - std::shared_lock lock{npad_mutex}; if (get_temporary_value && is_configuring) return tmp_npad_type; return npad_type; @@ -1786,8 +1719,6 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) { auto trigger_guard = SCOPE_GUARD { TriggerOnChange(ControllerTriggerType::Type, !is_configuring); }; - std::unique_lock lock1{mutex}, lock2{npad_mutex}; - if (is_configuring) { if (tmp_npad_type == npad_type_) { trigger_guard.Cancel(); @@ -1796,14 +1727,12 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) { tmp_npad_type = npad_type_; return; } - if (npad_type == npad_type_) { trigger_guard.Cancel(); return; } if (is_connected) { - LOG_WARNING(Service_HID, "Controller {} type changed while it's connected", - Service::HID::NpadIdTypeToIndex(npad_id_type)); + LOG_WARNING(Service_HID, "Controller {} type changed while it's connected", Service::HID::NpadIdTypeToIndex(npad_id_type)); } npad_type = npad_type_; } @@ -1832,37 +1761,30 @@ LedPattern EmulatedController::GetLedPattern() const { } ButtonValues EmulatedController::GetButtonsValues() const { - std::unique_lock lock{mutex}; return controller.button_values; } SticksValues EmulatedController::GetSticksValues() const { - std::unique_lock lock{mutex}; return controller.stick_values; } TriggerValues EmulatedController::GetTriggersValues() const { - std::unique_lock lock{mutex}; return controller.trigger_values; } ControllerMotionValues EmulatedController::GetMotionValues() const { - std::unique_lock lock{mutex}; return controller.motion_values; } ColorValues EmulatedController::GetColorsValues() const { - std::unique_lock lock{mutex}; return controller.color_values; } BatteryValues EmulatedController::GetBatteryValues() const { - std::unique_lock lock{mutex}; return controller.battery_values; } CameraValues EmulatedController::GetCameraValues() const { - std::unique_lock lock{mutex}; return controller.camera_values; } @@ -1871,72 +1793,54 @@ RingAnalogValue EmulatedController::GetRingSensorValues() const { } HomeButtonState EmulatedController::GetHomeButtons() const { - std::unique_lock lock{mutex}; - if (is_configuring) { + if (is_configuring) return {}; - } return controller.home_button_state; } CaptureButtonState EmulatedController::GetCaptureButtons() const { - std::unique_lock lock{mutex}; - if (is_configuring) { + if (is_configuring) return {}; - } return controller.capture_button_state; } NpadButtonState EmulatedController::GetNpadButtons() const { - std::unique_lock lock{mutex}; - if (is_configuring) { + if (is_configuring) return {}; - } return {controller.npad_button_state.raw & GetTurboButtonMask()}; } DebugPadButton EmulatedController::GetDebugPadButtons() const { - std::unique_lock lock{mutex}; - if (is_configuring) { + if (is_configuring) return {}; - } return controller.debug_pad_button_state; } AnalogSticks EmulatedController::GetSticks() const { - std::unique_lock lock{mutex}; - - if (is_configuring) { + if (is_configuring) return {}; - } - return controller.analog_stick_state; } NpadGcTriggerState EmulatedController::GetTriggers() const { - std::unique_lock lock{mutex}; - if (is_configuring) { + if (is_configuring) return {}; - } return controller.gc_trigger_state; } MotionState EmulatedController::GetMotions() const { - std::unique_lock lock{mutex}; return controller.motion_state; } ControllerColors EmulatedController::GetColors() const { - std::unique_lock lock{mutex}; return controller.colors_state; } BatteryLevelState EmulatedController::GetBattery() const { - std::unique_lock lock{mutex}; return controller.battery_state; } const CameraState& EmulatedController::GetCamera() const { - std::unique_lock lock{mutex}; return controller.camera_state; } @@ -1945,27 +1849,23 @@ RingSensorForce EmulatedController::GetRingSensorForce() const { } const NfcState& EmulatedController::GetNfc() const { - std::unique_lock lock{mutex}; return controller.nfc_state; } NpadColor EmulatedController::GetNpadColor(u32 color) { return { - .r = static_cast((color >> 16) & 0xFF), - .g = static_cast((color >> 8) & 0xFF), - .b = static_cast(color & 0xFF), + .r = u8((color >> 16) & 0xFF), + .g = u8((color >> 8) & 0xFF), + .b = u8(color & 0xFF), .a = 0xff, }; } void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_npad_service_update) { std::unique_lock lock{callback_mutex}; - for (const auto& poller_pair : callback_list) { - const ControllerUpdateCallback& poller = poller_pair.second; - if (!is_npad_service_update && poller.is_npad_service) { - continue; - } - if (poller.on_change) { + for (auto const& p : callback_list) { + auto const& poller = p.second; + if (is_npad_service_update || !poller.is_npad_service) { poller.on_change(type); } } @@ -1973,18 +1873,16 @@ void EmulatedController::TriggerOnChange(ControllerTriggerType type, bool is_npa int EmulatedController::SetCallback(ControllerUpdateCallback update_callback) { std::unique_lock lock{callback_mutex}; + ++last_callback_key; callback_list.insert_or_assign(last_callback_key, std::move(update_callback)); - return last_callback_key++; + return last_callback_key; } void EmulatedController::DeleteCallback(int key) { std::unique_lock lock{callback_mutex}; - const auto& iterator = callback_list.find(key); - if (iterator == callback_list.end()) { - LOG_ERROR(Input, "Tried to delete non-existent callback {}", key); - return; - } - callback_list.erase(iterator); + auto const it = callback_list.find(key); + ASSERT_MSG(it != callback_list.end(), "Tried to delete non-existent callback {}", key); + callback_list.erase(it); } void EmulatedController::StatusUpdate() { diff --git a/src/hid_core/frontend/emulated_controller.h b/src/hid_core/frontend/emulated_controller.h index e4256faa3e..80ea633c63 100644 --- a/src/hid_core/frontend/emulated_controller.h +++ b/src/hid_core/frontend/emulated_controller.h @@ -11,8 +11,10 @@ #include #include #include -#include #include +#include + +#include "common/container/unordered_map.h" #include "common/common_types.h" #include "common/input.h" @@ -20,7 +22,6 @@ #include "common/settings.h" #include "common/vector_math.h" #include "hid_core/frontend/motion_input.h" -#include "hid_core/hid_core.h" #include "hid_core/hid_types.h" #include "hid_core/irsensor/irs_types.h" @@ -105,11 +106,11 @@ struct RingSensorForce { using NfcState = Common::Input::NfcStatus; struct ControllerMotion { - Common::Vec3f accel{}; - Common::Vec3f gyro{}; - Common::Vec3f rotation{}; - Common::Vec3f euler{}; - std::array orientation{}; + Common::Vec accel{}; + Common::Vec gyro{}; + Common::Vec rotation{}; + Common::Vec euler{}; + std::array, 3> orientation{}; bool is_at_rest{}; }; @@ -576,24 +577,24 @@ private: NpadButton GetTurboButtonMask() const; const NpadIdType npad_id_type; - NpadStyleIndex npad_type{NpadStyleIndex::None}; - NpadStyleIndex original_npad_type{NpadStyleIndex::None}; NpadStyleTag supported_style_tag{NpadStyleSet::All}; - bool is_connected{false}; - bool is_configuring{false}; - bool is_initialized{false}; - bool system_buttons_enabled{true}; f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard}; u32 turbo_button_state{0}; std::size_t nfc_handles{0}; std::array last_vibration_value{DEFAULT_VIBRATION_VALUE, DEFAULT_VIBRATION_VALUE}; std::array last_vibration_timepoint{}; - std::array controller_connected{}; + // Atomically synched values + std::atomic npad_type{HID::NpadStyleIndex::None}; + std::atomic original_npad_type{HID::NpadStyleIndex::None}; // Temporary values to avoid doing changes while the controller is in configuring mode - NpadStyleIndex tmp_npad_type{NpadStyleIndex::None}; - bool tmp_is_connected{false}; + std::atomic tmp_npad_type{HID::NpadStyleIndex::None}; + std::atomic tmp_is_connected{false}; + std::atomic is_connected{false}; + std::atomic is_configuring{false}; + std::atomic is_initialized{false}; + std::atomic system_buttons_enabled{true}; ButtonParams button_params; StickParams stick_params; @@ -632,11 +633,8 @@ private: StickDevices virtual_stick_devices; ControllerMotionDevices virtual_motion_devices; - mutable std::shared_mutex mutex; - mutable std::shared_mutex callback_mutex; - mutable std::shared_mutex npad_mutex; - mutable std::shared_mutex connect_mutex; - ankerl::unordered_dense::map callback_list; + mutable std::mutex callback_mutex; + ::Common::unordered_map callback_list; int last_callback_key = 0; // Stores the current status of all controller input diff --git a/src/hid_core/frontend/emulated_devices.cpp b/src/hid_core/frontend/emulated_devices.cpp index f0b543c54c..ae3ee31f3d 100644 --- a/src/hid_core/frontend/emulated_devices.cpp +++ b/src/hid_core/frontend/emulated_devices.cpp @@ -1,9 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include +#include "common/assert.h" #include "hid_core/frontend/emulated_devices.h" #include "hid_core/frontend/input_converter.h" @@ -467,17 +471,15 @@ void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) { int EmulatedDevices::SetCallback(InterfaceUpdateCallback update_callback) { std::scoped_lock lock{callback_mutex}; + ++last_callback_key; callback_list.insert_or_assign(last_callback_key, std::move(update_callback)); - return last_callback_key++; + return last_callback_key; } void EmulatedDevices::DeleteCallback(int key) { std::scoped_lock lock{callback_mutex}; - const auto& iterator = callback_list.find(key); - if (iterator == callback_list.end()) { - LOG_ERROR(Input, "Tried to delete non-existent callback {}", key); - return; - } - callback_list.erase(iterator); + auto const it = callback_list.find(key); + ASSERT_MSG(it != callback_list.end(), "Tried to delete non-existent callback {}", key); + callback_list.erase(it); } } // namespace Core::HID diff --git a/src/hid_core/frontend/emulated_devices.h b/src/hid_core/frontend/emulated_devices.h index fb6c982a70..3d4ff4c148 100644 --- a/src/hid_core/frontend/emulated_devices.h +++ b/src/hid_core/frontend/emulated_devices.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include "common/common_types.h" @@ -205,7 +205,7 @@ private: mutable std::mutex mutex; mutable std::mutex callback_mutex; - ankerl::unordered_dense::map callback_list; + ::Common::unordered_map callback_list; int last_callback_key = 0; // Stores the current status of all external device input diff --git a/src/hid_core/frontend/motion_input.cpp b/src/hid_core/frontend/motion_input.cpp index 417cd03f97..1dcb910a50 100644 --- a/src/hid_core/frontend/motion_input.cpp +++ b/src/hid_core/frontend/motion_input.cpp @@ -1,7 +1,11 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include "common/math_util.h" #include "hid_core/frontend/motion_input.h" @@ -22,20 +26,19 @@ void MotionInput::SetPID(f32 new_kp, f32 new_ki, f32 new_kd) { kd = new_kd; } -void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) { +void MotionInput::SetAcceleration(const Common::Vec& acceleration) { accel = acceleration; - - accel.x = std::clamp(accel.x, -AccelMaxValue, AccelMaxValue); - accel.y = std::clamp(accel.y, -AccelMaxValue, AccelMaxValue); - accel.z = std::clamp(accel.z, -AccelMaxValue, AccelMaxValue); + accel[0] = std::clamp(accel[0], -AccelMaxValue, AccelMaxValue); + accel[1] = std::clamp(accel[1], -AccelMaxValue, AccelMaxValue); + accel[2] = std::clamp(accel[2], -AccelMaxValue, AccelMaxValue); } -void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) { +void MotionInput::SetGyroscope(const Common::Vec& gyroscope) { gyro = gyroscope - gyro_bias; - gyro.x = std::clamp(gyro.x, -GyroMaxValue, GyroMaxValue); - gyro.y = std::clamp(gyro.y, -GyroMaxValue, GyroMaxValue); - gyro.z = std::clamp(gyro.z, -GyroMaxValue, GyroMaxValue); + gyro[0] = std::clamp(gyro[0], -GyroMaxValue, GyroMaxValue); + gyro[1] = std::clamp(gyro[1], -GyroMaxValue, GyroMaxValue); + gyro[2] = std::clamp(gyro[2], -GyroMaxValue, GyroMaxValue); // Auto adjust gyro_bias to minimize drift if (!IsMoving(IsAtRestRelaxed)) { @@ -55,25 +58,25 @@ void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) { } } -void MotionInput::SetQuaternion(const Common::Quaternion& quaternion) { +void MotionInput::SetQuaternion(const Common::Vec& quaternion) { quat = quaternion; } -void MotionInput::SetEulerAngles(const Common::Vec3f& euler_angles) { - const float cr = std::cos(euler_angles.x * 0.5f); - const float sr = std::sin(euler_angles.x * 0.5f); - const float cp = std::cos(euler_angles.y * 0.5f); - const float sp = std::sin(euler_angles.y * 0.5f); - const float cy = std::cos(euler_angles.z * 0.5f); - const float sy = std::sin(euler_angles.z * 0.5f); +void MotionInput::SetEulerAngles(const Common::Vec& euler_angles) { + const float cr = std::cos(euler_angles[0] * 0.5f); + const float sr = std::sin(euler_angles[0] * 0.5f); + const float cp = std::cos(euler_angles[1] * 0.5f); + const float sp = std::sin(euler_angles[1] * 0.5f); + const float cy = std::cos(euler_angles[2] * 0.5f); + const float sy = std::sin(euler_angles[2] * 0.5f); - quat.w = cr * cp * cy + sr * sp * sy; - quat.xyz.x = sr * cp * cy - cr * sp * sy; - quat.xyz.y = cr * sp * cy + sr * cp * sy; - quat.xyz.z = cr * cp * sy - sr * sp * cy; + quat[3] = cr * cp * cy + sr * sp * sy; + quat[0] = sr * cp * cy - cr * sp * sy; + quat[1] = cr * sp * cy + sr * cp * sy; + quat[2] = cr * cp * sy - sr * sp * cy; } -void MotionInput::SetGyroBias(const Common::Vec3f& bias) { +void MotionInput::SetGyroBias(const Common::Vec& bias) { gyro_bias = bias; } @@ -94,7 +97,7 @@ void MotionInput::ResetRotations() { } void MotionInput::ResetQuaternion() { - quat = {{0.0f, 0.0f, -1.0f}, 0.0f}; + quat = Common::Vec{0.0f, 0.0f, -1.0f, 0.0f}; } bool MotionInput::IsMoving(f32 sensitivity) const { @@ -133,10 +136,10 @@ void MotionInput::UpdateOrientation(u64 elapsed_time) { ResetOrientation(); } // Short name local variable for readability - f32 q1 = quat.w; - f32 q2 = quat.xyz[0]; - f32 q3 = quat.xyz[1]; - f32 q4 = quat.xyz[2]; + f32 q1 = quat[3]; + f32 q2 = quat[0]; + f32 q3 = quat[1]; + f32 q4 = quat[2]; const auto sample_period = static_cast(elapsed_time) / 1000000.0f; // Ignore invalid elapsed time @@ -145,24 +148,24 @@ void MotionInput::UpdateOrientation(u64 elapsed_time) { } const auto normal_accel = accel.Normalized(); - auto rad_gyro = gyro * Common::PI * 2; - const f32 swap = rad_gyro.x; - rad_gyro.x = rad_gyro.y; - rad_gyro.y = -swap; - rad_gyro.z = -rad_gyro.z; + auto rad_gyro = gyro * std::numbers::pi_v * 2.f; + const f32 swap = rad_gyro[0]; + rad_gyro[0] = rad_gyro[1]; + rad_gyro[1] = -swap; + rad_gyro[2] = -rad_gyro[2]; // Clear gyro values if there is no gyro present if (only_accelerometer) { - rad_gyro.x = 0; - rad_gyro.y = 0; - rad_gyro.z = 0; + rad_gyro[0] = 0; + rad_gyro[1] = 0; + rad_gyro[2] = 0; } // Ignore drift correction if acceleration is not reliable if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) { - const f32 ax = -normal_accel.x; - const f32 ay = normal_accel.y; - const f32 az = -normal_accel.z; + const f32 ax = -normal_accel[0]; + const f32 ay = normal_accel[1]; + const f32 az = -normal_accel[2]; // Estimated direction of gravity const f32 vx = 2.0f * (q2 * q4 - q1 * q3); @@ -170,7 +173,7 @@ void MotionInput::UpdateOrientation(u64 elapsed_time) { const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4; // Error is cross product between estimated direction and measured direction of gravity - const Common::Vec3f new_real_error = { + const Common::Vec new_real_error{ az * vx - ax * vz, ay * vz - az * vy, ax * vy - ay * vx, @@ -198,16 +201,16 @@ void MotionInput::UpdateOrientation(u64 elapsed_time) { rad_gyro += 10.0f * kd * derivative_error; // Emulate gyro values for games that need them - gyro.x = -rad_gyro.y; - gyro.y = rad_gyro.x; - gyro.z = -rad_gyro.z; + gyro[0] = -rad_gyro[1]; + gyro[1] = rad_gyro[0]; + gyro[2] = -rad_gyro[2]; UpdateRotation(elapsed_time); } } - const f32 gx = rad_gyro.y; - const f32 gy = rad_gyro.x; - const f32 gz = rad_gyro.z; + const f32 gx = rad_gyro[1]; + const f32 gy = rad_gyro[0]; + const f32 gz = rad_gyro[2]; // Integrate rate of change of quaternion const f32 pa = q2; @@ -218,61 +221,62 @@ void MotionInput::UpdateOrientation(u64 elapsed_time) { q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period); q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period); - quat.w = q1; - quat.xyz[0] = q2; - quat.xyz[1] = q3; - quat.xyz[2] = q4; + quat[3] = q1; + quat[0] = q2; + quat[1] = q3; + quat[2] = q4; quat = quat.Normalized(); } -std::array MotionInput::GetOrientation() const { - const Common::Quaternion quad{ - .xyz = {-quat.xyz[1], -quat.xyz[0], -quat.w}, - .w = -quat.xyz[2], +std::array, 3> MotionInput::GetOrientation() const { + const Common::Vec quad{ + -quat[1], + -quat[0], + -quat[3], + -quat[2], }; - const std::array matrix4x4 = quad.ToMatrix(); - - return {Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]), - Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]), - Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])}; + const std::array matrix4x4 = quad.ToMatrix(); + return {Common::Vec(matrix4x4[0], matrix4x4[1], -matrix4x4[2]), + Common::Vec(matrix4x4[4], matrix4x4[5], -matrix4x4[6]), + Common::Vec(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])}; } -Common::Vec3f MotionInput::GetAcceleration() const { +Common::Vec MotionInput::GetAcceleration() const { return accel; } -Common::Vec3f MotionInput::GetGyroscope() const { +Common::Vec MotionInput::GetGyroscope() const { return gyro; } -Common::Vec3f MotionInput::GetGyroBias() const { +Common::Vec MotionInput::GetGyroBias() const { return gyro_bias; } -Common::Quaternion MotionInput::GetQuaternion() const { +Common::Vec MotionInput::GetQuaternion() const { return quat; } -Common::Vec3f MotionInput::GetRotations() const { +Common::Vec MotionInput::GetRotations() const { return rotations; } -Common::Vec3f MotionInput::GetEulerAngles() const { +Common::Vec MotionInput::GetEulerAngles() const { // roll (x-axis rotation) - const float sinr_cosp = 2 * (quat.w * quat.xyz.x + quat.xyz.y * quat.xyz.z); - const float cosr_cosp = 1 - 2 * (quat.xyz.x * quat.xyz.x + quat.xyz.y * quat.xyz.y); + const float sinr_cosp = 2 * (quat[3] * quat[0] + quat[1] * quat[2]); + const float cosr_cosp = 1 - 2 * (quat[0] * quat[0] + quat[1] * quat[1]); // pitch (y-axis rotation) - const float sinp = std::sqrt(1 + 2 * (quat.w * quat.xyz.y - quat.xyz.x * quat.xyz.z)); - const float cosp = std::sqrt(1 - 2 * (quat.w * quat.xyz.y - quat.xyz.x * quat.xyz.z)); + const float sinp = std::sqrt(1 + 2 * (quat[3] * quat[1] - quat[0] * quat[2])); + const float cosp = std::sqrt(1 - 2 * (quat[3] * quat[1] - quat[0] * quat[2])); // yaw (z-axis rotation) - const float siny_cosp = 2 * (quat.w * quat.xyz.z + quat.xyz.x * quat.xyz.y); - const float cosy_cosp = 1 - 2 * (quat.xyz.y * quat.xyz.y + quat.xyz.z * quat.xyz.z); + const float siny_cosp = 2 * (quat[3] * quat[2] + quat[0] * quat[1]); + const float cosy_cosp = 1 - 2 * (quat[1] * quat[1] + quat[2] * quat[2]); return { std::atan2(sinr_cosp, cosr_cosp), - 2 * std::atan2(sinp, cosp) - Common::PI / 2, + 2 * std::atan2(sinp, cosp) - float(std::numbers::pi_v) / 2, std::atan2(siny_cosp, cosy_cosp), }; } @@ -281,13 +285,13 @@ void MotionInput::ResetOrientation() { if (!reset_enabled || only_accelerometer) { return; } - if (!IsMoving(IsAtRestRelaxed) && accel.z <= -0.9f) { + if (!IsMoving(IsAtRestRelaxed) && accel[2] <= -0.9f) { ++reset_counter; if (reset_counter > 900) { - quat.w = 0; - quat.xyz[0] = 0; - quat.xyz[1] = 0; - quat.xyz[2] = -1; + quat[3] = 0; + quat[0] = 0; + quat[1] = 0; + quat[2] = -1; SetOrientationFromAccelerometer(); integral_error = {}; reset_counter = 0; @@ -305,15 +309,15 @@ void MotionInput::SetOrientationFromAccelerometer() { while (!IsCalibrated(0.01f) && ++iterations < 100) { // Short name local variable for readability - f32 q1 = quat.w; - f32 q2 = quat.xyz[0]; - f32 q3 = quat.xyz[1]; - f32 q4 = quat.xyz[2]; + f32 q1 = quat[3]; + f32 q2 = quat[0]; + f32 q3 = quat[1]; + f32 q4 = quat[2]; - Common::Vec3f rad_gyro; - const f32 ax = -normal_accel.x; - const f32 ay = normal_accel.y; - const f32 az = -normal_accel.z; + Common::Vec rad_gyro; + const f32 ax = -normal_accel[0]; + const f32 ay = normal_accel[1]; + const f32 az = -normal_accel[2]; // Estimated direction of gravity const f32 vx = 2.0f * (q2 * q4 - q1 * q3); @@ -321,7 +325,7 @@ void MotionInput::SetOrientationFromAccelerometer() { const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4; // Error is cross product between estimated direction and measured direction of gravity - const Common::Vec3f new_real_error = { + const Common::Vec new_real_error = { az * vx - ax * vz, ay * vz - az * vy, ax * vy - ay * vx, @@ -334,9 +338,9 @@ void MotionInput::SetOrientationFromAccelerometer() { rad_gyro += 5.0f * ki * integral_error; rad_gyro += 10.0f * kd * derivative_error; - const f32 gx = rad_gyro.y; - const f32 gy = rad_gyro.x; - const f32 gz = rad_gyro.z; + const f32 gx = rad_gyro[1]; + const f32 gy = rad_gyro[0]; + const f32 gz = rad_gyro[2]; // Integrate rate of change of quaternion const f32 pa = q2; @@ -347,10 +351,10 @@ void MotionInput::SetOrientationFromAccelerometer() { q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period); q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period); - quat.w = q1; - quat.xyz[0] = q2; - quat.xyz[1] = q3; - quat.xyz[2] = q4; + quat[3] = q1; + quat[0] = q2; + quat[1] = q3; + quat[2] = q4; quat = quat.Normalized(); } } diff --git a/src/hid_core/frontend/motion_input.h b/src/hid_core/frontend/motion_input.h index c902a3a6e5..09dbb706d7 100644 --- a/src/hid_core/frontend/motion_input.h +++ b/src/hid_core/frontend/motion_input.h @@ -1,10 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "common/common_types.h" -#include "common/quaternion.h" #include "common/vector_math.h" namespace Core::HID { @@ -34,11 +36,11 @@ public: MotionInput& operator=(MotionInput&&) = default; void SetPID(f32 new_kp, f32 new_ki, f32 new_kd); - void SetAcceleration(const Common::Vec3f& acceleration); - void SetGyroscope(const Common::Vec3f& gyroscope); - void SetQuaternion(const Common::Quaternion& quaternion); - void SetEulerAngles(const Common::Vec3f& euler_angles); - void SetGyroBias(const Common::Vec3f& bias); + void SetAcceleration(const Common::Vec& acceleration); + void SetGyroscope(const Common::Vec& gyroscope); + void SetQuaternion(const Common::Vec& quaternion); + void SetEulerAngles(const Common::Vec& euler_angles); + void SetGyroBias(const Common::Vec& bias); void SetGyroThreshold(f32 threshold); /// Applies a modifier on top of the normal gyro threshold @@ -53,13 +55,13 @@ public: void Calibrate(); - [[nodiscard]] std::array GetOrientation() const; - [[nodiscard]] Common::Vec3f GetAcceleration() const; - [[nodiscard]] Common::Vec3f GetGyroscope() const; - [[nodiscard]] Common::Vec3f GetGyroBias() const; - [[nodiscard]] Common::Vec3f GetRotations() const; - [[nodiscard]] Common::Quaternion GetQuaternion() const; - [[nodiscard]] Common::Vec3f GetEulerAngles() const; + [[nodiscard]] std::array, 3> GetOrientation() const; + [[nodiscard]] Common::Vec GetAcceleration() const; + [[nodiscard]] Common::Vec GetGyroscope() const; + [[nodiscard]] Common::Vec GetGyroBias() const; + [[nodiscard]] Common::Vec GetRotations() const; + [[nodiscard]] Common::Vec GetQuaternion() const; + [[nodiscard]] Common::Vec GetEulerAngles() const; [[nodiscard]] bool IsMoving(f32 sensitivity) const; [[nodiscard]] bool IsCalibrated(f32 sensitivity) const; @@ -75,24 +77,24 @@ private: f32 kd; // PID errors - Common::Vec3f real_error; - Common::Vec3f integral_error; - Common::Vec3f derivative_error; + Common::Vec real_error; + Common::Vec integral_error; + Common::Vec derivative_error; // Quaternion containing the device orientation - Common::Quaternion quat; + Common::Vec quat; // Number of full rotations in each axis - Common::Vec3f rotations; + Common::Vec rotations; // Acceleration vector measurement in G force - Common::Vec3f accel; + Common::Vec accel; // Gyroscope vector measurement in radians/s. - Common::Vec3f gyro; + Common::Vec gyro; // Vector to be subtracted from gyro measurements - Common::Vec3f gyro_bias; + Common::Vec gyro_bias; // Minimum gyro amplitude to detect if the device is moving f32 gyro_threshold = 0.0f; diff --git a/src/hid_core/hid_core.cpp b/src/hid_core/hid_core.cpp index 410c84afbc..cbc193b741 100644 --- a/src/hid_core/hid_core.cpp +++ b/src/hid_core/hid_core.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -10,18 +13,21 @@ namespace Core::HID { -HIDCore::HIDCore() - : player_1{std::make_unique(NpadIdType::Player1)}, - player_2{std::make_unique(NpadIdType::Player2)}, - player_3{std::make_unique(NpadIdType::Player3)}, - player_4{std::make_unique(NpadIdType::Player4)}, - player_5{std::make_unique(NpadIdType::Player5)}, - player_6{std::make_unique(NpadIdType::Player6)}, - player_7{std::make_unique(NpadIdType::Player7)}, - player_8{std::make_unique(NpadIdType::Player8)}, - other{std::make_unique(NpadIdType::Other)}, - handheld{std::make_unique(NpadIdType::Handheld)}, - console{std::make_unique()}, devices{std::make_unique()} {} +HIDCore::HIDCore(Kernel::KernelCore& kernel_) + : player_1{std::make_unique(NpadIdType::Player1)} + , player_2{std::make_unique(NpadIdType::Player2)} + , player_3{std::make_unique(NpadIdType::Player3)} + , player_4{std::make_unique(NpadIdType::Player4)} + , player_5{std::make_unique(NpadIdType::Player5)} + , player_6{std::make_unique(NpadIdType::Player6)} + , player_7{std::make_unique(NpadIdType::Player7)} + , player_8{std::make_unique(NpadIdType::Player8)} + , other{std::make_unique(NpadIdType::Other)} + , handheld{std::make_unique(NpadIdType::Handheld)} + , console{std::make_unique()} + , devices{std::make_unique()} + , kernel{kernel_} +{} HIDCore::~HIDCore() = default; diff --git a/src/hid_core/hid_core.h b/src/hid_core/hid_core.h index dae29c5062..7da11897ca 100644 --- a/src/hid_core/hid_core.h +++ b/src/hid_core/hid_core.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -8,6 +11,10 @@ #include "common/common_funcs.h" #include "hid_core/hid_types.h" +namespace Kernel { +class KernelCore; +} + namespace Core::HID { class EmulatedConsole; class EmulatedController; @@ -18,7 +25,7 @@ namespace Core::HID { class HIDCore { public: - explicit HIDCore(); + explicit HIDCore(Kernel::KernelCore& kernel); ~HIDCore(); YUZU_NON_COPYABLE(HIDCore); @@ -69,7 +76,6 @@ public: /// Number of emulated controllers static constexpr std::size_t available_controllers{10}; -private: std::unique_ptr player_1; std::unique_ptr player_2; std::unique_ptr player_3; @@ -82,6 +88,7 @@ private: std::unique_ptr handheld; std::unique_ptr console; std::unique_ptr devices; + Kernel::KernelCore& kernel; NpadStyleTag supported_style_tag{NpadStyleSet::All}; NpadIdType last_active_controller{NpadIdType::Handheld}; }; diff --git a/src/hid_core/hid_types.h b/src/hid_core/hid_types.h index 40a90c2bc3..ae12059a92 100644 --- a/src/hid_core/hid_types.h +++ b/src/hid_core/hid_types.h @@ -1,8 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include +#include +#include + #include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" @@ -602,10 +609,10 @@ static_assert(sizeof(SixAxisSensorAttribute) == 4, "SixAxisSensorAttribute is an struct SixAxisSensorState { s64 delta_time{}; s64 sampling_number{}; - Common::Vec3f accel{}; - Common::Vec3f gyro{}; - Common::Vec3f rotation{}; - std::array orientation{}; + Common::Vec accel{}; + Common::Vec gyro{}; + Common::Vec rotation{}; + std::array, 3> orientation{}; SixAxisSensorAttribute attribute{}; INSERT_PADDING_BYTES(4); // Reserved }; diff --git a/src/hid_core/hidbus/hidbus_base.cpp b/src/hid_core/hidbus/hidbus_base.cpp index 632bb173b6..e57121fa31 100644 --- a/src/hid_core/hidbus/hidbus_base.cpp +++ b/src/hid_core/hidbus/hidbus_base.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -62,8 +65,9 @@ void HidbusBase::DisablePollingMode() { polling_mode_enabled = false; } -void HidbusBase::SetTransferMemoryAddress(Common::ProcessAddress t_mem) { +void HidbusBase::SetTransferMemoryAddress(Common::ProcessAddress t_mem, Kernel::KProcess* owner) { transfer_memory = t_mem; + transfer_memory_owner = owner; } Kernel::KReadableEvent& HidbusBase::GetSendCommandAsycEvent() const { diff --git a/src/hid_core/hidbus/hidbus_base.h b/src/hid_core/hidbus/hidbus_base.h index c948606e40..5c29ada2e2 100644 --- a/src/hid_core/hidbus/hidbus_base.h +++ b/src/hid_core/hidbus/hidbus_base.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -14,6 +17,7 @@ class System; namespace Kernel { class KEvent; +class KProcess; class KReadableEvent; } // namespace Kernel @@ -138,7 +142,7 @@ public: void DisablePollingMode(); // Called on EnableJoyPollingReceiveMode - void SetTransferMemoryAddress(Common::ProcessAddress t_mem); + void SetTransferMemoryAddress(Common::ProcessAddress t_mem, Kernel::KProcess* owner); Kernel::KReadableEvent& GetSendCommandAsycEvent() const; @@ -175,6 +179,7 @@ protected: ButtonOnlyPollingDataAccessor button_only_data{}; Common::ProcessAddress transfer_memory{}; + Kernel::KProcess* transfer_memory_owner{}; Core::System& system; Kernel::KEvent* send_command_async_event; diff --git a/src/hid_core/hidbus/ringcon.cpp b/src/hid_core/hidbus/ringcon.cpp index 1927a6f856..a6798d22aa 100644 --- a/src/hid_core/hidbus/ringcon.cpp +++ b/src/hid_core/hidbus/ringcon.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -6,6 +6,7 @@ #include "core/core.h" #include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_readable_event.h" #include "core/memory.h" #include "hid_core/frontend/emulated_controller.h" @@ -67,8 +68,10 @@ void RingController::OnUpdate() { curr_entry.polling_data.out_size = sizeof(ringcon_value); std::memcpy(curr_entry.polling_data.data.data(), &ringcon_value, sizeof(ringcon_value)); - system.ApplicationMemory().WriteBlock(transfer_memory, &enable_sixaxis_data, - sizeof(enable_sixaxis_data)); + if (transfer_memory_owner != nullptr) { + transfer_memory_owner->GetMemory().WriteBlock(transfer_memory, &enable_sixaxis_data, + sizeof(enable_sixaxis_data)); + } break; } default: @@ -141,12 +144,12 @@ bool RingController::SetCommand(std::span data) { case RingConCommands::ReadRepCount: case RingConCommands::ReadTotalPushCount: ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes"); - send_command_async_event->Signal(); + send_command_async_event->Signal(system.Kernel()); return true; case RingConCommands::ResetRepCount: ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes"); total_rep_count = 0; - send_command_async_event->Signal(); + send_command_async_event->Signal(system.Kernel()); return true; case RingConCommands::SaveCalData: { ASSERT_MSG(data.size() == 0x14, "data.size is not 0x14 bytes"); @@ -154,14 +157,14 @@ bool RingController::SetCommand(std::span data) { SaveCalData save_info{}; std::memcpy(&save_info, data.data(), sizeof(SaveCalData)); user_calibration = save_info.calibration; - send_command_async_event->Signal(); + send_command_async_event->Signal(system.Kernel()); return true; } default: LOG_ERROR(Service_HID, "Command not implemented {}", command); command = RingConCommands::Error; // Signal a reply to avoid softlocking the game - send_command_async_event->Signal(); + send_command_async_event->Signal(system.Kernel()); return false; } } @@ -284,9 +287,9 @@ u8 RingController::GetCrcValue(const std::vector& data) const { } template + requires std::is_trivially_copyable_v u64 RingController::GetData(const T& reply, std::span out_data) const { - static_assert(std::is_trivially_copyable_v); - const auto data_size = static_cast((std::min)(sizeof(reply), out_data.size())); + const auto data_size = u64((std::min)(sizeof(reply), out_data.size())); std::memcpy(out_data.data(), &reply, data_size); return data_size; } diff --git a/src/hid_core/hidbus/ringcon.h b/src/hid_core/hidbus/ringcon.h index a48eeed459..b38a67bc3e 100644 --- a/src/hid_core/hidbus/ringcon.h +++ b/src/hid_core/hidbus/ringcon.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -221,6 +224,7 @@ private: // Converts structs to an u8 vector equivalent template + requires std::is_trivially_copyable_v u64 GetData(const T& reply, std::span out_data) const; RingConCommands command{RingConCommands::Error}; diff --git a/src/hid_core/irsensor/image_transfer_processor.cpp b/src/hid_core/irsensor/image_transfer_processor.cpp index fb3e424d91..229433df79 100644 --- a/src/hid_core/irsensor/image_transfer_processor.cpp +++ b/src/hid_core/irsensor/image_transfer_processor.cpp @@ -1,10 +1,11 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "core/core.h" +#include "core/hle/kernel/k_process.h" #include "core/memory.h" #include "hid_core/frontend/emulated_controller.h" #include "hid_core/hid_core.h" @@ -48,10 +49,12 @@ void ImageTransferProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType if (type != Core::HID::ControllerTriggerType::IrSensor) { return; } - if (transfer_memory == 0) { + if (transfer_memory == 0 || transfer_memory_owner == nullptr) { return; } + auto& memory = transfer_memory_owner->GetMemory(); + const auto& camera_data = npad_device->GetCamera(); // This indicates how much ambient light is present @@ -61,16 +64,14 @@ void ImageTransferProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType if (camera_data.format != current_config.origin_format) { LOG_WARNING(Service_IRS, "Wrong Input format {} expected {}", camera_data.format, current_config.origin_format); - system.ApplicationMemory().ZeroBlock(transfer_memory, - GetDataSize(current_config.trimming_format)); + memory.ZeroBlock(transfer_memory, GetDataSize(current_config.trimming_format)); return; } if (current_config.origin_format > current_config.trimming_format) { LOG_WARNING(Service_IRS, "Origin format {} is smaller than trimming format {}", current_config.origin_format, current_config.trimming_format); - system.ApplicationMemory().ZeroBlock(transfer_memory, - GetDataSize(current_config.trimming_format)); + memory.ZeroBlock(transfer_memory, GetDataSize(current_config.trimming_format)); return; } @@ -87,8 +88,7 @@ void ImageTransferProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType "Trimming area ({}, {}, {}, {}) is outside of origin area ({}, {})", current_config.trimming_start_x, current_config.trimming_start_y, trimming_width, trimming_height, origin_width, origin_height); - system.ApplicationMemory().ZeroBlock(transfer_memory, - GetDataSize(current_config.trimming_format)); + memory.ZeroBlock(transfer_memory, GetDataSize(current_config.trimming_format)); return; } @@ -102,8 +102,8 @@ void ImageTransferProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType } } - system.ApplicationMemory().WriteBlock(transfer_memory, window_data.data(), - GetDataSize(current_config.trimming_format)); + memory.WriteBlock(transfer_memory, window_data.data(), + GetDataSize(current_config.trimming_format)); if (!IsProcessorActive()) { StartProcessor(); @@ -143,14 +143,19 @@ void ImageTransferProcessor::SetConfig( npad_device->SetCameraFormat(current_config.origin_format); } -void ImageTransferProcessor::SetTransferMemoryAddress(Common::ProcessAddress t_mem) { +void ImageTransferProcessor::SetTransferMemoryAddress(Common::ProcessAddress t_mem, + Kernel::KProcess* owner) { transfer_memory = t_mem; + transfer_memory_owner = owner; } Core::IrSensor::ImageTransferProcessorState ImageTransferProcessor::GetState( std::span data) const { + if (transfer_memory_owner == nullptr) + return processor_state; + const auto size = (std::min)(GetDataSize(current_config.trimming_format), data.size()); - system.ApplicationMemory().ReadBlock(transfer_memory, data.data(), size); + transfer_memory_owner->GetMemory().ReadBlock(transfer_memory, data.data(), size); return processor_state; } diff --git a/src/hid_core/irsensor/image_transfer_processor.h b/src/hid_core/irsensor/image_transfer_processor.h index df1c9d9208..f2f3dcba83 100644 --- a/src/hid_core/irsensor/image_transfer_processor.h +++ b/src/hid_core/irsensor/image_transfer_processor.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -9,6 +12,10 @@ #include "hid_core/irsensor/irs_types.h" #include "hid_core/irsensor/processor_base.h" +namespace Kernel { +class KProcess; +} + namespace Core { class System; } @@ -39,7 +46,7 @@ public: void SetConfig(Core::IrSensor::PackedImageTransferProcessorExConfig config); // Transfer memory where the image data will be stored - void SetTransferMemoryAddress(Common::ProcessAddress t_mem); + void SetTransferMemoryAddress(Common::ProcessAddress t_mem, Kernel::KProcess* owner); Core::IrSensor::ImageTransferProcessorState GetState(std::span data) const; @@ -75,5 +82,6 @@ private: Core::System& system; Common::ProcessAddress transfer_memory{}; + Kernel::KProcess* transfer_memory_owner{}; }; } // namespace Service::IRS diff --git a/src/hid_core/resource_manager.cpp b/src/hid_core/resource_manager.cpp index 0c10d1bec9..d4d62ea4ba 100644 --- a/src/hid_core/resource_manager.cpp +++ b/src/hid_core/resource_manager.cpp @@ -91,7 +91,7 @@ ResourceManager::~ResourceManager() { system.CoreTiming().UnscheduleEvent(mouse_keyboard_update_event); system.CoreTiming().UnscheduleEvent(motion_update_event); system.CoreTiming().UnscheduleEvent(touch_update_event); - input_event->Finalize(); + input_event->Finalize(system.Kernel()); }; void ResourceManager::Initialize() { @@ -486,7 +486,7 @@ void ResourceManager::UpdateControllers(std::chrono::nanoseconds ns_late) { void ResourceManager::UpdateNpad(std::chrono::nanoseconds ns_late) { auto& core_timing = system.CoreTiming(); - npad->OnUpdate(core_timing); + npad->OnUpdate(system.Kernel(), core_timing); } void ResourceManager::UpdateMouseKeyboard(std::chrono::nanoseconds ns_late) { diff --git a/src/hid_core/resources/abstracted_pad/abstract_battery_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_battery_handler.cpp index d5f37247e1..e84f38f2e9 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_battery_handler.cpp +++ b/src/hid_core/resources/abstracted_pad/abstract_battery_handler.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -16,7 +16,9 @@ namespace Service::HID { -NpadAbstractBatteryHandler::NpadAbstractBatteryHandler() {} +NpadAbstractBatteryHandler::NpadAbstractBatteryHandler(Kernel::KernelCore& kernel_) + : kernel{kernel_} +{} NpadAbstractBatteryHandler::~NpadAbstractBatteryHandler() = default; @@ -51,12 +53,11 @@ Result NpadAbstractBatteryHandler::DecrementRefCounter() { Result NpadAbstractBatteryHandler::UpdateBatteryState(u64 aruid) { const auto npad_index = NpadIdTypeToIndex(properties_handler->GetNpadId()); AruidData* aruid_data = applet_resource_holder->applet_resource->GetAruidData(aruid); - if (aruid_data == nullptr) { + if (aruid_data == nullptr || aruid_data->shared_memory_format == nullptr) { return ResultSuccess; } - auto& npad_internal_state = - aruid_data->shared_memory_format->npad.npad_entry[npad_index].internal_state; + auto& npad_internal_state = aruid_data->shared_memory_format->npad.npad_entry[npad_index].internal_state; auto& system_properties = npad_internal_state.system_properties; system_properties.is_charging_joy_dual.Assign(dual_battery.is_charging); diff --git a/src/hid_core/resources/abstracted_pad/abstract_battery_handler.h b/src/hid_core/resources/abstracted_pad/abstract_battery_handler.h index 85ac5eb72f..9c270aa92d 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_battery_handler.h +++ b/src/hid_core/resources/abstracted_pad/abstract_battery_handler.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -7,6 +10,10 @@ #include "core/hle/result.h" #include "hid_core/hid_types.h" +namespace Kernel { +class KernelCore; +} + namespace Service::HID { struct AppletResourceHolder; class NpadAbstractedPadHolder; @@ -15,7 +22,7 @@ class NpadAbstractPropertiesHandler; /// Handles Npad request from HID interfaces class NpadAbstractBatteryHandler final { public: - explicit NpadAbstractBatteryHandler(); + explicit NpadAbstractBatteryHandler(Kernel::KernelCore& kernel_); ~NpadAbstractBatteryHandler(); void SetAbstractPadHolder(NpadAbstractedPadHolder* holder); @@ -44,6 +51,7 @@ private: Core::HID::NpadPowerInfo left_battery{}; Core::HID::NpadPowerInfo right_battery{}; bool has_new_battery_data{}; + Kernel::KernelCore& kernel; }; } // namespace Service::HID diff --git a/src/hid_core/resources/abstracted_pad/abstract_button_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_button_handler.cpp index 36deb25c94..239fc7656e 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_button_handler.cpp +++ b/src/hid_core/resources/abstracted_pad/abstract_button_handler.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -16,7 +16,9 @@ namespace Service::HID { -NpadAbstractButtonHandler::NpadAbstractButtonHandler() {} +NpadAbstractButtonHandler::NpadAbstractButtonHandler(Kernel::KernelCore& kernel_) + : kernel{kernel_} +{} NpadAbstractButtonHandler::~NpadAbstractButtonHandler() = default; diff --git a/src/hid_core/resources/abstracted_pad/abstract_button_handler.h b/src/hid_core/resources/abstracted_pad/abstract_button_handler.h index 01eafe96df..6e49471b5e 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_button_handler.h +++ b/src/hid_core/resources/abstracted_pad/abstract_button_handler.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -7,6 +10,10 @@ #include "core/hle/result.h" #include "hid_core/hid_types.h" +namespace Kernel { +class KernelCore; +} + namespace Service::HID { struct NpadSharedMemoryEntry; @@ -17,7 +24,7 @@ class NpadAbstractPropertiesHandler; /// Handles Npad request from HID interfaces class NpadAbstractButtonHandler final { public: - explicit NpadAbstractButtonHandler(); + explicit NpadAbstractButtonHandler(Kernel::KernelCore& kernel_); ~NpadAbstractButtonHandler(); void SetAbstractPadHolder(NpadAbstractedPadHolder* holder); @@ -70,6 +77,7 @@ private: u64 gc_sampling_number{}; GcTrigger gc_trigger_state{}; + Kernel::KernelCore& kernel; }; } // namespace Service::HID diff --git a/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.cpp index 16ec2257fb..a2aaa1d8ec 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.cpp +++ b/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -14,7 +14,9 @@ namespace Service::HID { -NpadAbstractIrSensorHandler::NpadAbstractIrSensorHandler() {} +NpadAbstractIrSensorHandler::NpadAbstractIrSensorHandler(Kernel::KernelCore& kernel_) + : kernel{kernel_} +{} NpadAbstractIrSensorHandler::~NpadAbstractIrSensorHandler() = default; @@ -52,7 +54,7 @@ void NpadAbstractIrSensorHandler::UpdateIrSensorState() { if (sensor_state == previous_state) { return; } - ir_sensor_event->Signal(); + ir_sensor_event->Signal(kernel); return; } @@ -77,7 +79,7 @@ void NpadAbstractIrSensorHandler::UpdateIrSensorState() { if (sensor_state == previous_state) { return; } - ir_sensor_event->Signal(); + ir_sensor_event->Signal(kernel); return; } @@ -86,7 +88,7 @@ void NpadAbstractIrSensorHandler::UpdateIrSensorState() { return; } - ir_sensor_event->Signal(); + ir_sensor_event->Signal(kernel); return; } @@ -105,7 +107,7 @@ Result NpadAbstractIrSensorHandler::ActivateIrSensor(bool is_enabled) { } sensor_state = NpadIrSensorState::Available; } - ir_sensor_event->Signal(); + ir_sensor_event->Signal(kernel); return ResultSuccess; } diff --git a/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h b/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h index 9978115116..799d83e82f 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h +++ b/src/hid_core/resources/abstracted_pad/abstract_ir_sensor_handler.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -12,6 +15,7 @@ class EmulatedController; } namespace Kernel { +class KernelCore; class KEvent; class KReadableEvent; } // namespace Kernel @@ -30,7 +34,7 @@ class NpadAbstractPropertiesHandler; /// Handles Npad request from HID interfaces class NpadAbstractIrSensorHandler final { public: - explicit NpadAbstractIrSensorHandler(); + explicit NpadAbstractIrSensorHandler(Kernel::KernelCore& kernel_); ~NpadAbstractIrSensorHandler(); void SetAbstractPadHolder(NpadAbstractedPadHolder* holder); @@ -56,5 +60,6 @@ private: Kernel::KEvent* ir_sensor_event{nullptr}; Core::HID::EmulatedController* xcd_handle{}; NpadIrSensorState sensor_state{}; + Kernel::KernelCore& kernel; }; } // namespace Service::HID diff --git a/src/hid_core/resources/abstracted_pad/abstract_led_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_led_handler.cpp index 813d3d3188..94d895509b 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_led_handler.cpp +++ b/src/hid_core/resources/abstracted_pad/abstract_led_handler.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -15,7 +15,9 @@ namespace Service::HID { -NpadAbstractLedHandler::NpadAbstractLedHandler() {} +NpadAbstractLedHandler::NpadAbstractLedHandler(Kernel::KernelCore& kernel_) + : kernel{kernel_} +{} NpadAbstractLedHandler::~NpadAbstractLedHandler() = default; diff --git a/src/hid_core/resources/abstracted_pad/abstract_led_handler.h b/src/hid_core/resources/abstracted_pad/abstract_led_handler.h index 09528129b6..7ff12a9e75 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_led_handler.h +++ b/src/hid_core/resources/abstracted_pad/abstract_led_handler.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -7,6 +10,10 @@ #include "core/hle/result.h" #include "hid_core/hid_types.h" +namespace Kernel { +class KernelCore; +} + namespace Service::HID { struct AppletResourceHolder; class NpadAbstractedPadHolder; @@ -15,7 +22,7 @@ class NpadAbstractPropertiesHandler; /// Handles Npad request from HID interfaces class NpadAbstractLedHandler final { public: - explicit NpadAbstractLedHandler(); + explicit NpadAbstractLedHandler(Kernel::KernelCore& kernel_); ~NpadAbstractLedHandler(); void SetAbstractPadHolder(NpadAbstractedPadHolder* holder); @@ -39,5 +46,6 @@ private: Core::HID::LedPattern left_pattern{0, 0, 0, 0}; Core::HID::LedPattern right_pattern{0, 0, 0, 0}; u64 led_interval{}; + Kernel::KernelCore& kernel; }; } // namespace Service::HID diff --git a/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.cpp index 2b763d8e0f..d87446030a 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.cpp +++ b/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -12,7 +12,9 @@ namespace Service::HID { -NpadAbstractMcuHandler::NpadAbstractMcuHandler() {} +NpadAbstractMcuHandler::NpadAbstractMcuHandler(Kernel::KernelCore& kernel_) + : kernel{kernel_} +{} NpadAbstractMcuHandler::~NpadAbstractMcuHandler() = default; diff --git a/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.h b/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.h index 9902dd03a7..44be82cd43 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.h +++ b/src/hid_core/resources/abstracted_pad/abstract_mcu_handler.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -7,6 +10,10 @@ #include "core/hle/result.h" #include "hid_core/hid_types.h" +namespace Kernel { +class KernelCore; +} + namespace Service::HID { struct IAbstractedPad; class NpadAbstractedPadHolder; @@ -28,7 +35,7 @@ static_assert(sizeof(NpadMcuHolder) == 0x10, "NpadMcuHolder is an invalid size") /// Handles Npad request from HID interfaces class NpadAbstractMcuHandler final { public: - explicit NpadAbstractMcuHandler(); + explicit NpadAbstractMcuHandler(Kernel::KernelCore& kernel_); ~NpadAbstractMcuHandler(); void SetAbstractPadHolder(NpadAbstractedPadHolder* holder); @@ -48,5 +55,6 @@ private: s32 ref_counter{}; std::array mcu_holder{}; + Kernel::KernelCore& kernel; }; } // namespace Service::HID diff --git a/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.cpp index 80f5f3ba62..9990add4cc 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.cpp +++ b/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -14,7 +14,9 @@ namespace Service::HID { -NpadAbstractNfcHandler::NpadAbstractNfcHandler() {} +NpadAbstractNfcHandler::NpadAbstractNfcHandler(Kernel::KernelCore& kernel_) + : kernel{kernel_} +{} NpadAbstractNfcHandler::~NpadAbstractNfcHandler() = default; @@ -48,13 +50,13 @@ void NpadAbstractNfcHandler::UpdateNfcState() { if (count == 0) { if (sensor_state == NpadNfcState::Active) { - nfc_activate_event->Signal(); + nfc_activate_event->Signal(kernel); } if (sensor_state == NpadNfcState::Unavailable) { return; } sensor_state = NpadNfcState::Unavailable; - input_event->Signal(); + input_event->Signal(kernel); return; } @@ -79,19 +81,18 @@ void NpadAbstractNfcHandler::UpdateNfcState() { return; } sensor_state = NpadNfcState::Available; - input_event->Signal(); + input_event->Signal(kernel); return; } if (sensor_state == NpadNfcState::Active) { - nfc_activate_event->Signal(); + nfc_activate_event->Signal(kernel); } if (sensor_state == NpadNfcState::Unavailable) { return; } sensor_state = NpadNfcState::Unavailable; - input_event->Signal(); - return; + input_event->Signal(kernel); } bool NpadAbstractNfcHandler::HasNfcSensor() { @@ -123,7 +124,7 @@ Result NpadAbstractNfcHandler::ActivateNfc(bool is_enabled) { } if (sensor_state != new_state) { sensor_state = new_state; - nfc_activate_event->Signal(); + nfc_activate_event->Signal(kernel); } return ResultSuccess; } diff --git a/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.h b/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.h index 0702722a6b..223a0afd4e 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.h +++ b/src/hid_core/resources/abstracted_pad/abstract_nfc_handler.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -8,7 +11,9 @@ #include "hid_core/hid_types.h" namespace Kernel { +class KernelCore; class KReadableEvent; +class KEvent; } enum class NpadNfcState : u32 { @@ -24,7 +29,7 @@ class NpadAbstractPropertiesHandler; /// Handles Npad request from HID interfaces class NpadAbstractNfcHandler final { public: - explicit NpadAbstractNfcHandler(); + explicit NpadAbstractNfcHandler(Kernel::KernelCore& kernel_); ~NpadAbstractNfcHandler(); void SetAbstractPadHolder(NpadAbstractedPadHolder* holder); @@ -53,5 +58,6 @@ private: Kernel::KEvent* input_event{nullptr}; u64 xcd_handle{}; NpadNfcState sensor_state{NpadNfcState::Unavailable}; + Kernel::KernelCore& kernel; }; } // namespace Service::HID diff --git a/src/hid_core/resources/abstracted_pad/abstract_pad.cpp b/src/hid_core/resources/abstracted_pad/abstract_pad.cpp index d7cf2bba9b..b828c9b11e 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_pad.cpp +++ b/src/hid_core/resources/abstracted_pad/abstract_pad.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -12,7 +12,20 @@ namespace Service::HID { -AbstractPad::AbstractPad() {} +AbstractPad::AbstractPad(Kernel::KernelCore& kernel_) + //: abstract_pad_holder{kernel_} + : properties_handler{kernel_} + , led_handler{kernel_} + , ir_sensor_handler{kernel_} + , nfc_handler{kernel_} + , mcu_handler{kernel_} + , vibration_handler{kernel_} + , sixaxis_handler{kernel_} + , button_handler{kernel_} + , battery_handler{kernel_} + , palma_handler{kernel_} + , kernel{kernel_} +{} AbstractPad::~AbstractPad() = default; diff --git a/src/hid_core/resources/abstracted_pad/abstract_pad.h b/src/hid_core/resources/abstracted_pad/abstract_pad.h index 3297924574..99d886d40c 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_pad.h +++ b/src/hid_core/resources/abstracted_pad/abstract_pad.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -27,6 +30,10 @@ #include "hid_core/resources/vibration/n64_vibration_device.h" #include "hid_core/resources/vibration/vibration_device.h" +namespace Kernel { +class KernelCore; +} + namespace Service::HID { class AppletResource; class SixAxisResource; @@ -49,7 +56,7 @@ struct HandheldConfig; /// Handles Npad request from HID interfaces class AbstractPad final { public: - explicit AbstractPad(); + explicit AbstractPad(Kernel::KernelCore& kernel_); ~AbstractPad(); void SetExternals(AppletResourceHolder* applet_resource, @@ -88,17 +95,19 @@ public: private: AppletResourceHolder* applet_resource_holder{nullptr}; + + // TODO: fix NpadAbstractedPadHolder abstract_pad_holder{}; - NpadAbstractPropertiesHandler properties_handler{}; - NpadAbstractLedHandler led_handler{}; - NpadAbstractIrSensorHandler ir_sensor_handler{}; - NpadAbstractNfcHandler nfc_handler{}; - NpadAbstractMcuHandler mcu_handler{}; - NpadAbstractVibrationHandler vibration_handler{}; - NpadAbstractSixAxisHandler sixaxis_handler{}; - NpadAbstractButtonHandler button_handler{}; - NpadAbstractBatteryHandler battery_handler{}; - NpadAbstractPalmaHandler palma_handler{}; + NpadAbstractPropertiesHandler properties_handler; + NpadAbstractLedHandler led_handler; + NpadAbstractIrSensorHandler ir_sensor_handler; + NpadAbstractNfcHandler nfc_handler; + NpadAbstractMcuHandler mcu_handler; + NpadAbstractVibrationHandler vibration_handler; + NpadAbstractSixAxisHandler sixaxis_handler; + NpadAbstractButtonHandler button_handler; + NpadAbstractBatteryHandler battery_handler; + NpadAbstractPalmaHandler palma_handler; NpadN64VibrationDevice vibration_n64{}; NpadVibrationDevice vibration_left{}; @@ -114,6 +123,7 @@ private: s32 ref_counter{}; Core::HID::NpadInterfaceType interface_type{Core::HID::NpadInterfaceType::None}; + Kernel::KernelCore& kernel; }; using FullAbstractPad = std::array; diff --git a/src/hid_core/resources/abstracted_pad/abstract_palma_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_palma_handler.cpp index 7766bedfd0..981f92ea00 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_palma_handler.cpp +++ b/src/hid_core/resources/abstracted_pad/abstract_palma_handler.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -10,7 +10,9 @@ namespace Service::HID { -NpadAbstractPalmaHandler::NpadAbstractPalmaHandler() {} +NpadAbstractPalmaHandler::NpadAbstractPalmaHandler(Kernel::KernelCore& kernel_) + : kernel{kernel_} +{} NpadAbstractPalmaHandler::~NpadAbstractPalmaHandler() = default; diff --git a/src/hid_core/resources/abstracted_pad/abstract_palma_handler.h b/src/hid_core/resources/abstracted_pad/abstract_palma_handler.h index fbd2e67e53..c09572a7b6 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_palma_handler.h +++ b/src/hid_core/resources/abstracted_pad/abstract_palma_handler.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -7,6 +10,10 @@ #include "core/hle/result.h" #include "hid_core/hid_types.h" +namespace Kernel { +class KernelCore; +} + namespace Service::HID { class NpadAbstractedPadHolder; class NpadAbstractPropertiesHandler; @@ -14,7 +21,7 @@ class PalmaResource; class NpadAbstractPalmaHandler final { public: - explicit NpadAbstractPalmaHandler(); + explicit NpadAbstractPalmaHandler(Kernel::KernelCore& kernel_); ~NpadAbstractPalmaHandler(); void SetAbstractPadHolder(NpadAbstractedPadHolder* holder); @@ -32,6 +39,7 @@ private: PalmaResource* palma_resource{nullptr}; s32 ref_counter{}; + Kernel::KernelCore& kernel; }; } // namespace Service::HID diff --git a/src/hid_core/resources/abstracted_pad/abstract_properties_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_properties_handler.cpp index c225670043..db011bc6dd 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_properties_handler.cpp +++ b/src/hid_core/resources/abstracted_pad/abstract_properties_handler.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -14,7 +14,9 @@ namespace Service::HID { -NpadAbstractPropertiesHandler::NpadAbstractPropertiesHandler() {} +NpadAbstractPropertiesHandler::NpadAbstractPropertiesHandler(Kernel::KernelCore& kernel_) + : kernel{kernel_} +{} NpadAbstractPropertiesHandler::~NpadAbstractPropertiesHandler() = default; diff --git a/src/hid_core/resources/abstracted_pad/abstract_properties_handler.h b/src/hid_core/resources/abstracted_pad/abstract_properties_handler.h index fa68278998..1b8cf4be82 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_properties_handler.h +++ b/src/hid_core/resources/abstracted_pad/abstract_properties_handler.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -10,6 +13,10 @@ #include "hid_core/hid_types.h" #include "hid_core/resources/npad/npad_types.h" +namespace Kernel { +class KernelCore; +} + namespace Service::HID { struct NpadSharedMemoryEntry; @@ -25,7 +32,7 @@ struct ColorProperties { /// Handles Npad request from HID interfaces class NpadAbstractPropertiesHandler final { public: - explicit NpadAbstractPropertiesHandler(); + explicit NpadAbstractPropertiesHandler(Kernel::KernelCore& kernel_); ~NpadAbstractPropertiesHandler(); void SetAbstractPadHolder(NpadAbstractedPadHolder* holder); @@ -82,5 +89,6 @@ private: ColorProperties fullkey_color{}; ColorProperties left_color{}; ColorProperties right_color{}; + Kernel::KernelCore& kernel; }; } // namespace Service::HID diff --git a/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.cpp index a8d2e5b2a0..a33525b72e 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.cpp +++ b/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -15,7 +15,9 @@ namespace Service::HID { -NpadAbstractSixAxisHandler::NpadAbstractSixAxisHandler() {} +NpadAbstractSixAxisHandler::NpadAbstractSixAxisHandler(Kernel::KernelCore& kernel_) + : kernel{kernel_} +{} NpadAbstractSixAxisHandler::~NpadAbstractSixAxisHandler() = default; @@ -64,8 +66,7 @@ Result NpadAbstractSixAxisHandler::UpdateSixAxisState() { continue; } auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)]; - UpdateSixaxisInternalState(npad_entry, data->aruid, - data->flag.enable_six_axis_sensor.As()); + UpdateSixaxisInternalState(npad_entry, data->aruid, bool(data->flag.enable_six_axis_sensor)); } return ResultSuccess; } @@ -77,8 +78,7 @@ Result NpadAbstractSixAxisHandler::UpdateSixAxisState(u64 aruid) { return ResultSuccess; } auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)]; - UpdateSixaxisInternalState(npad_entry, data->aruid, - data->flag.enable_six_axis_sensor.As()); + UpdateSixaxisInternalState(npad_entry, data->aruid, bool(data->flag.enable_six_axis_sensor)); return ResultSuccess; } @@ -89,8 +89,7 @@ Result NpadAbstractSixAxisHandler::UpdateSixAxisState2(u64 aruid) { return ResultSuccess; } auto& npad_internal_state = aruid_data->shared_memory_format->npad.npad_entry[npad_index]; - UpdateSixaxisInternalState(npad_internal_state, aruid, - aruid_data->flag.enable_six_axis_sensor.As()); + UpdateSixaxisInternalState(npad_internal_state, aruid, bool(aruid_data->flag.enable_six_axis_sensor)); return ResultSuccess; } diff --git a/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.h b/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.h index 9c20459e91..c126c25a23 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.h +++ b/src/hid_core/resources/abstracted_pad/abstract_sixaxis_handler.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -6,6 +9,11 @@ #include "common/common_types.h" #include "core/hle/result.h" #include "hid_core/hid_types.h" +#include "hid_core/resources/shared_memory_format.h" + +namespace Kernel { +class KernelCore; +} namespace Service::HID { class SixAxisResource; @@ -17,7 +25,7 @@ struct NpadSixAxisSensorLifo; /// Handles Npad request from HID interfaces class NpadAbstractSixAxisHandler final { public: - explicit NpadAbstractSixAxisHandler(); + explicit NpadAbstractSixAxisHandler(Kernel::KernelCore& kernel_); ~NpadAbstractSixAxisHandler(); void SetAbstractPadHolder(NpadAbstractedPadHolder* holder); @@ -56,6 +64,7 @@ private: SixAxisResource* six_axis_resource{nullptr}; s32 ref_counter{}; + Kernel::KernelCore& kernel; }; } // namespace Service::HID diff --git a/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.cpp b/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.cpp index 3266422133..a0a37806fc 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.cpp +++ b/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -19,7 +19,9 @@ namespace Service::HID { -NpadAbstractVibrationHandler::NpadAbstractVibrationHandler() {} +NpadAbstractVibrationHandler::NpadAbstractVibrationHandler(Kernel::KernelCore& kernel_) + : kernel{kernel_} +{} NpadAbstractVibrationHandler::~NpadAbstractVibrationHandler() = default; diff --git a/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.h b/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.h index 8bc8129c24..d57bf27814 100644 --- a/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.h +++ b/src/hid_core/resources/abstracted_pad/abstract_vibration_handler.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -9,6 +12,10 @@ #include "core/hle/result.h" #include "hid_core/hid_types.h" +namespace Kernel { +class KernelCore; +} + namespace Core::HID { class HIDCore; } @@ -25,7 +32,7 @@ class NpadVibration; /// Keeps track of battery levels and updates npad battery shared memory values class NpadAbstractVibrationHandler final { public: - explicit NpadAbstractVibrationHandler(); + explicit NpadAbstractVibrationHandler(Kernel::KernelCore& kernel_); ~NpadAbstractVibrationHandler(); void SetAbstractPadHolder(NpadAbstractedPadHolder* holder); @@ -55,5 +62,6 @@ private: NpadGcVibrationDevice* gc_vibration_device{nullptr}; NpadVibration* vibration_handler{nullptr}; s32 ref_counter{}; + Kernel::KernelCore& kernel; }; } // namespace Service::HID diff --git a/src/hid_core/resources/applet_resource.cpp b/src/hid_core/resources/applet_resource.cpp index 31480a0e90..a7b9658cac 100644 --- a/src/hid_core/resources/applet_resource.cpp +++ b/src/hid_core/resources/applet_resource.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -14,7 +14,10 @@ namespace Service::HID { AppletResource::AppletResource(Core::System& system_) : system{system_} {} -AppletResource::~AppletResource() = default; +AppletResource::~AppletResource() { + for (size_t i = 0; i < shared_memory_holder.size(); ++i) + shared_memory_holder[i].Finalize(system); +} Result AppletResource::CreateAppletResource(u64 aruid) { const u64 index = GetIndexFromAruid(aruid); @@ -34,7 +37,7 @@ Result AppletResource::CreateAppletResource(u64 aruid) { return result; } if (shared_memory.GetAddress() == nullptr) { - shared_memory.Finalize(); + shared_memory.Finalize(system); return ResultSharedMemoryNotInitialized; } } @@ -139,7 +142,7 @@ void AppletResource::FreeAppletResourceId(u64 aruid) { if (aruid_data.flag.is_assigned) { aruid_data.shared_memory_format = nullptr; aruid_data.flag.is_assigned.Assign(false); - shared_memory_holder[index].Finalize(); + shared_memory_holder[index].Finalize(system); } } diff --git a/src/hid_core/resources/npad/npad.cpp b/src/hid_core/resources/npad/npad.cpp index 3a191f4539..2b04e2c670 100644 --- a/src/hid_core/resources/npad/npad.cpp +++ b/src/hid_core/resources/npad/npad.cpp @@ -29,30 +29,46 @@ namespace Service::HID { NPad::NPad(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service_context_) - : hid_core{hid_core_}, service_context{service_context_}, npad_resource{service_context} { + : hid_core{hid_core_} + , service_context{service_context_} + , npad_resource{hid_core_.kernel, service_context} + , abstracted_pads{{ + AbstractPad{hid_core_.kernel}, + AbstractPad{hid_core_.kernel}, + AbstractPad{hid_core_.kernel}, + AbstractPad{hid_core_.kernel}, + AbstractPad{hid_core_.kernel}, + AbstractPad{hid_core_.kernel}, + AbstractPad{hid_core_.kernel}, + AbstractPad{hid_core_.kernel}, + AbstractPad{hid_core_.kernel}, + AbstractPad{hid_core_.kernel}, + }} +{ for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; ++aruid_index) { for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) { auto& controller = controller_data[aruid_index][i]; controller.device = hid_core.GetEmulatedControllerByIndex(i); Core::HID::ControllerUpdateCallback engine_callback{ - .on_change = - [this, i](Core::HID::ControllerTriggerType type) { ControllerUpdate(type, i); }, + .on_change = [this, i, kernel = &hid_core.kernel](Core::HID::ControllerTriggerType type) { + ControllerUpdate(*kernel, type, i); + }, .is_npad_service = true, }; controller.callback_key = controller.device->SetCallback(engine_callback); } } for (std::size_t i = 0; i < abstracted_pads.size(); ++i) { - abstracted_pads[i] = AbstractPad{}; abstracted_pads[i].SetNpadId(IndexToNpadIdType(i)); } } NPad::~NPad() { - for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; ++aruid_index) { - for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) { - auto& controller = controller_data[aruid_index][i]; - controller.device->DeleteCallback(controller.callback_key); + for (std::size_t aruid = 0; aruid < AruidIndexMax; ++aruid) { + for (std::size_t i = 0; i < controller_data[aruid].size(); ++i) { + if (auto& controller = controller_data[aruid][i]; controller.device && controller.callback_key) { + controller.device->DeleteCallback(controller.callback_key); + } } } } @@ -77,7 +93,11 @@ Result NPad::Activate(u64 aruid) { std::scoped_lock shared_lock{*applet_resource_holder.shared_mutex}; auto* data = applet_resource_holder.applet_resource->GetAruidData(aruid); - const auto aruid_index = applet_resource_holder.applet_resource->GetIndexFromAruid(aruid); + auto aruid_index = applet_resource_holder.applet_resource->GetIndexFromAruid(aruid); + if (aruid_index >= AruidIndexMax) { + LOG_ERROR(Service_HID, "Invalid aruid:{:016X}", aruid); + aruid_index = 0; + } if (data == nullptr || !data->flag.is_assigned) { return ResultSuccess; @@ -123,10 +143,10 @@ void NPad::FreeAppletResourceId(u64 aruid) { return npad_resource.FreeAppletResourceId(aruid); } -void NPad::ControllerUpdate(Core::HID::ControllerTriggerType type, std::size_t controller_idx) { +void NPad::ControllerUpdate(Kernel::KernelCore& kernel, Core::HID::ControllerTriggerType type, std::size_t controller_idx) { if (type == Core::HID::ControllerTriggerType::All) { - ControllerUpdate(Core::HID::ControllerTriggerType::Connected, controller_idx); - ControllerUpdate(Core::HID::ControllerTriggerType::Battery, controller_idx); + ControllerUpdate(kernel, Core::HID::ControllerTriggerType::Connected, controller_idx); + ControllerUpdate(kernel, Core::HID::ControllerTriggerType::Battery, controller_idx); return; } @@ -151,7 +171,7 @@ void NPad::ControllerUpdate(Core::HID::ControllerTriggerType type, std::size_t c if (is_connected == controller.is_connected) { return; } - UpdateControllerAt(data->aruid, npad_type, npad_id, is_connected); + UpdateControllerAt(kernel, data->aruid, npad_type, npad_id, is_connected); break; case Core::HID::ControllerTriggerType::Battery: { if (!controller.device->IsConnected()) { @@ -173,7 +193,7 @@ void NPad::ControllerUpdate(Core::HID::ControllerTriggerType type, std::size_t c } } -void NPad::InitNewlyAddedController(u64 aruid, Core::HID::NpadIdType npad_id) { +void NPad::InitNewlyAddedController(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadIdType npad_id) { auto& controller = GetControllerFromNpadIdType(aruid, npad_id); if (!npad_resource.IsControllerSupported(aruid, controller.device->GetNpadStyleIndex())) { return; @@ -188,7 +208,7 @@ void NPad::InitNewlyAddedController(u64 aruid, Core::HID::NpadIdType npad_id) { return; } if (controller_type == Core::HID::NpadStyleIndex::None) { - npad_resource.SignalStyleSetUpdateEvent(aruid, npad_id); + npad_resource.SignalStyleSetUpdateEvent(kernel, aruid, npad_id); return; } @@ -372,7 +392,7 @@ void NPad::InitNewlyAddedController(u64 aruid, Core::HID::NpadIdType npad_id) { Common::Input::PollingMode::Active); } - npad_resource.SignalStyleSetUpdateEvent(aruid, npad_id); + npad_resource.SignalStyleSetUpdateEvent(kernel, aruid, npad_id); WriteEmptyEntry(controller.shared_memory); hid_core.SetLastActiveController(npad_id); abstracted_pads[NpadIdTypeToIndex(npad_id)].Update(); @@ -381,38 +401,38 @@ void NPad::InitNewlyAddedController(u64 aruid, Core::HID::NpadIdType npad_id) { void NPad::WriteEmptyEntry(NpadInternalState* npad) { NPadGenericState dummy_pad_state{}; NpadGcTriggerState dummy_gc_state{}; - dummy_pad_state.sampling_number = npad->fullkey_lifo.ReadCurrentEntry().sampling_number + 1; + dummy_pad_state.sampling_number = npad->fullkey_lifo.ReadCurrentEntry().state.sampling_number + 1; npad->fullkey_lifo.WriteNextEntry(dummy_pad_state); - dummy_pad_state.sampling_number = npad->handheld_lifo.ReadCurrentEntry().sampling_number + 1; + dummy_pad_state.sampling_number = npad->handheld_lifo.ReadCurrentEntry().state.sampling_number + 1; npad->handheld_lifo.WriteNextEntry(dummy_pad_state); - dummy_pad_state.sampling_number = npad->joy_dual_lifo.ReadCurrentEntry().sampling_number + 1; + dummy_pad_state.sampling_number = npad->joy_dual_lifo.ReadCurrentEntry().state.sampling_number + 1; npad->joy_dual_lifo.WriteNextEntry(dummy_pad_state); - dummy_pad_state.sampling_number = npad->joy_left_lifo.ReadCurrentEntry().sampling_number + 1; + dummy_pad_state.sampling_number = npad->joy_left_lifo.ReadCurrentEntry().state.sampling_number + 1; npad->joy_left_lifo.WriteNextEntry(dummy_pad_state); - dummy_pad_state.sampling_number = npad->joy_right_lifo.ReadCurrentEntry().sampling_number + 1; + dummy_pad_state.sampling_number = npad->joy_right_lifo.ReadCurrentEntry().state.sampling_number + 1; npad->joy_right_lifo.WriteNextEntry(dummy_pad_state); - dummy_pad_state.sampling_number = npad->palma_lifo.ReadCurrentEntry().sampling_number + 1; + dummy_pad_state.sampling_number = npad->palma_lifo.ReadCurrentEntry().state.sampling_number + 1; npad->palma_lifo.WriteNextEntry(dummy_pad_state); - dummy_pad_state.sampling_number = npad->system_ext_lifo.ReadCurrentEntry().sampling_number + 1; + dummy_pad_state.sampling_number = npad->system_ext_lifo.ReadCurrentEntry().state.sampling_number + 1; npad->system_ext_lifo.WriteNextEntry(dummy_pad_state); - dummy_gc_state.sampling_number = npad->gc_trigger_lifo.ReadCurrentEntry().sampling_number + 1; + dummy_gc_state.sampling_number = npad->gc_trigger_lifo.ReadCurrentEntry().state.sampling_number + 1; npad->gc_trigger_lifo.WriteNextEntry(dummy_gc_state); } -void NPad::RequestPadStateUpdate(u64 aruid, Core::HID::NpadIdType npad_id) { +void NPad::RequestPadStateUpdate(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadIdType npad_id) { std::scoped_lock lock{*applet_resource_holder.shared_mutex}; auto& controller = GetControllerFromNpadIdType(aruid, npad_id); const auto controller_type = controller.device->GetNpadStyleIndex(); if (!controller.device->IsConnected() && controller.is_connected) { - DisconnectNpad(aruid, npad_id); + DisconnectNpad(kernel, aruid, npad_id); return; } if (!controller.device->IsConnected()) { return; } if (controller.device->IsConnected() && !controller.is_connected) { - InitNewlyAddedController(aruid, npad_id); + InitNewlyAddedController(kernel, aruid, npad_id); } // This function is unique to yuzu for the turbo buttons and motion to work properly @@ -468,7 +488,7 @@ void NPad::RequestPadStateUpdate(u64 aruid, Core::HID::NpadIdType npad_id) { } } -void NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { +void NPad::OnUpdate(Kernel::KernelCore& kernel, const Core::Timing::CoreTiming& core_timing) { if (ref_counter == 0) { return; } @@ -491,9 +511,12 @@ void NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { for (std::size_t i = 0; i < controller_data[aruid_index].size(); ++i) { auto& controller = controller_data[aruid_index][i]; - controller.shared_memory = - &data->shared_memory_format->npad.npad_entry[i].internal_state; + controller.shared_memory = &data->shared_memory_format->npad.npad_entry[i].internal_state; auto* npad = controller.shared_memory; + if (!npad || !controller.device) { + LOG_WARNING(Service_HID, "No device for {}", i); + continue; + } const auto& controller_type = controller.device->GetNpadStyleIndex(); @@ -510,7 +533,7 @@ void NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { continue; } - RequestPadStateUpdate(aruid, controller.device->GetNpadIdType()); + RequestPadStateUpdate(kernel, aruid, controller.device->GetNpadIdType()); auto& pad_state = controller.npad_pad_state; auto& libnx_state = controller.npad_libnx_state; auto& trigger_state = controller.npad_trigger_state; @@ -627,17 +650,17 @@ void NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { } } -Result NPad::SetSupportedNpadStyleSet(u64 aruid, Core::HID::NpadStyleSet supported_style_set) { +Result NPad::SetSupportedNpadStyleSet(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadStyleSet supported_style_set) { std::scoped_lock lock{mutex}; hid_core.SetSupportedStyleTag({supported_style_set}); const Result result = npad_resource.SetSupportedNpadStyleSet(aruid, supported_style_set); if (result.IsSuccess()) { - OnUpdate({}); + OnUpdate(kernel, {}); } return result; } -Result NPad::GetSupportedNpadStyleSet(u64 aruid, +Result NPad::GetSupportedNpadStyleSet(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadStyleSet& out_supported_style_set) const { std::scoped_lock lock{mutex}; const Result result = npad_resource.GetSupportedNpadStyleSet(out_supported_style_set, aruid); @@ -650,8 +673,7 @@ Result NPad::GetSupportedNpadStyleSet(u64 aruid, return result; } -Result NPad::GetMaskedSupportedNpadStyleSet( - u64 aruid, Core::HID::NpadStyleSet& out_supported_style_set) const { +Result NPad::GetMaskedSupportedNpadStyleSet(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadStyleSet& out_supported_style_set) const { std::scoped_lock lock{mutex}; const Result result = npad_resource.GetMaskedSupportedNpadStyleSet(out_supported_style_set, aruid); @@ -664,8 +686,7 @@ Result NPad::GetMaskedSupportedNpadStyleSet( return result; } -Result NPad::SetSupportedNpadIdType(u64 aruid, - std::span supported_npad_list) { +Result NPad::SetSupportedNpadIdType(Kernel::KernelCore& kernel, u64 aruid, std::span supported_npad_list) { std::scoped_lock lock{mutex}; if (supported_npad_list.size() > MaxSupportedNpadIdTypes) { return ResultInvalidArraySize; @@ -674,7 +695,7 @@ Result NPad::SetSupportedNpadIdType(u64 aruid, Result result = npad_resource.SetSupportedNpadIdType(aruid, supported_npad_list); if (result.IsSuccess()) { - OnUpdate({}); + OnUpdate(kernel, {}); } return result; @@ -690,28 +711,32 @@ Result NPad::GetNpadJoyHoldType(u64 aruid, NpadJoyHoldType& out_hold_type) const return npad_resource.GetNpadJoyHoldType(out_hold_type, aruid); } -Result NPad::SetNpadHandheldActivationMode(u64 aruid, NpadHandheldActivationMode mode) { +Result NPad::SetNpadHandheldActivationMode(Kernel::KernelCore& kernel, u64 aruid, NpadHandheldActivationMode mode) { std::scoped_lock lock{mutex}; Result result = npad_resource.SetNpadHandheldActivationMode(aruid, mode); if (result.IsSuccess()) { - OnUpdate({}); + OnUpdate(kernel, {}); } return result; } -Result NPad::GetNpadHandheldActivationMode(u64 aruid, NpadHandheldActivationMode& out_mode) const { +Result NPad::GetNpadHandheldActivationMode(Kernel::KernelCore& kernel, u64 aruid, NpadHandheldActivationMode& out_mode) const { std::scoped_lock lock{mutex}; return npad_resource.GetNpadHandheldActivationMode(out_mode, aruid); } -bool NPad::SetNpadMode(u64 aruid, Core::HID::NpadIdType& new_npad_id, Core::HID::NpadIdType npad_id, - NpadJoyDeviceType npad_device_type, NpadJoyAssignmentMode assignment_mode) { +bool NPad::SetNpadMode(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadIdType& new_npad_id, Core::HID::NpadIdType npad_id, NpadJoyDeviceType npad_device_type, NpadJoyAssignmentMode assignment_mode) { if (!IsNpadIdValid(npad_id)) { LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); return false; } auto& controller = GetControllerFromNpadIdType(aruid, npad_id); + if (!controller.shared_memory) { + LOG_WARNING(Service_HID, "shared_memory is null for npad_id={}", npad_id); + return false; + } + if (controller.shared_memory->assignment_mode != assignment_mode) { controller.shared_memory->assignment_mode = assignment_mode; } @@ -722,17 +747,17 @@ bool NPad::SetNpadMode(u64 aruid, Core::HID::NpadIdType& new_npad_id, Core::HID: if (assignment_mode == NpadJoyAssignmentMode::Dual) { if (controller.device->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconLeft) { - DisconnectNpad(aruid, npad_id); + DisconnectNpad(kernel, aruid, npad_id); controller.is_dual_left_connected = true; controller.is_dual_right_connected = false; - UpdateControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconDual, npad_id, true); + UpdateControllerAt(kernel, aruid, Core::HID::NpadStyleIndex::JoyconDual, npad_id, true); return false; } if (controller.device->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::JoyconRight) { - DisconnectNpad(aruid, npad_id); + DisconnectNpad(kernel, aruid, npad_id); controller.is_dual_left_connected = false; controller.is_dual_right_connected = true; - UpdateControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconDual, npad_id, true); + UpdateControllerAt(kernel, aruid, Core::HID::NpadStyleIndex::JoyconDual, npad_id, true); return false; } return false; @@ -746,58 +771,55 @@ bool NPad::SetNpadMode(u64 aruid, Core::HID::NpadIdType& new_npad_id, Core::HID: } if (controller.is_dual_left_connected && !controller.is_dual_right_connected) { - DisconnectNpad(aruid, npad_id); - UpdateControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconLeft, npad_id, true); + DisconnectNpad(kernel, aruid, npad_id); + UpdateControllerAt(kernel, aruid, Core::HID::NpadStyleIndex::JoyconLeft, npad_id, true); return false; } if (!controller.is_dual_left_connected && controller.is_dual_right_connected) { - DisconnectNpad(aruid, npad_id); - UpdateControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconRight, npad_id, true); + DisconnectNpad(kernel, aruid, npad_id); + UpdateControllerAt(kernel, aruid, Core::HID::NpadStyleIndex::JoyconRight, npad_id, true); return false; } // We have two controllers connected to the same npad_id we need to split them new_npad_id = hid_core.GetFirstDisconnectedNpadId(); auto& controller_2 = GetControllerFromNpadIdType(aruid, new_npad_id); - DisconnectNpad(aruid, npad_id); + DisconnectNpad(kernel, aruid, npad_id); if (npad_device_type == NpadJoyDeviceType::Left) { - UpdateControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconLeft, npad_id, true); + UpdateControllerAt(kernel, aruid, Core::HID::NpadStyleIndex::JoyconLeft, npad_id, true); controller_2.is_dual_left_connected = false; controller_2.is_dual_right_connected = true; - UpdateControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconDual, new_npad_id, true); + UpdateControllerAt(kernel, aruid, Core::HID::NpadStyleIndex::JoyconDual, new_npad_id, true); } else { - UpdateControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconRight, npad_id, true); + UpdateControllerAt(kernel, aruid, Core::HID::NpadStyleIndex::JoyconRight, npad_id, true); controller_2.is_dual_left_connected = true; controller_2.is_dual_right_connected = false; - UpdateControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconDual, new_npad_id, true); + UpdateControllerAt(kernel, aruid, Core::HID::NpadStyleIndex::JoyconDual, new_npad_id, true); } return true; } -Result NPad::AcquireNpadStyleSetUpdateEventHandle(u64 aruid, Kernel::KReadableEvent** out_event, - Core::HID::NpadIdType npad_id) { +Result NPad::AcquireNpadStyleSetUpdateEventHandle(Kernel::KernelCore& kernel, u64 aruid, Kernel::KReadableEvent** out_event, Core::HID::NpadIdType npad_id) { std::scoped_lock lock{mutex}; - return npad_resource.AcquireNpadStyleSetUpdateEventHandle(aruid, out_event, npad_id); + return npad_resource.AcquireNpadStyleSetUpdateEventHandle(kernel, aruid, out_event, npad_id); } -void NPad::AddNewControllerAt(u64 aruid, Core::HID::NpadStyleIndex controller, - Core::HID::NpadIdType npad_id) { - UpdateControllerAt(aruid, controller, npad_id, true); +void NPad::AddNewControllerAt(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadStyleIndex controller, Core::HID::NpadIdType npad_id) { + UpdateControllerAt(kernel, aruid, controller, npad_id, true); } -void NPad::UpdateControllerAt(u64 aruid, Core::HID::NpadStyleIndex type, - Core::HID::NpadIdType npad_id, bool connected) { +void NPad::UpdateControllerAt(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadStyleIndex type, Core::HID::NpadIdType npad_id, bool connected) { auto& controller = GetControllerFromNpadIdType(aruid, npad_id); if (!connected) { - DisconnectNpad(aruid, npad_id); + DisconnectNpad(kernel, aruid, npad_id); return; } controller.device->SetNpadStyleIndex(type); - InitNewlyAddedController(aruid, npad_id); + InitNewlyAddedController(kernel, aruid, npad_id); } -Result NPad::DisconnectNpad(u64 aruid, Core::HID::NpadIdType npad_id) { +Result NPad::DisconnectNpad(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadIdType npad_id) { if (!IsNpadIdValid(npad_id)) { LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id:{}", npad_id); return ResultInvalidNpadId; @@ -840,7 +862,7 @@ Result NPad::DisconnectNpad(u64 aruid, Core::HID::NpadIdType npad_id) { controller.is_dual_right_connected = true; controller.is_connected = false; controller.device->Disconnect(); - npad_resource.SignalStyleSetUpdateEvent(aruid, npad_id); + npad_resource.SignalStyleSetUpdateEvent(kernel, aruid, npad_id); WriteEmptyEntry(shared_memory); return ResultSuccess; } @@ -873,8 +895,7 @@ Result NPad::ResetIsSixAxisSensorDeviceNewlyAssigned( return ResultSuccess; } -Result NPad::MergeSingleJoyAsDualJoy(u64 aruid, Core::HID::NpadIdType npad_id_1, - Core::HID::NpadIdType npad_id_2) { +Result NPad::MergeSingleJoyAsDualJoy(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadIdType npad_id_1, Core::HID::NpadIdType npad_id_2) { if (!IsNpadIdValid(npad_id_1) || !IsNpadIdValid(npad_id_2)) { LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id_1:{}, npad_id_2:{}", npad_id_1, npad_id_2); @@ -928,11 +949,11 @@ Result NPad::MergeSingleJoyAsDualJoy(u64 aruid, Core::HID::NpadIdType npad_id_1, } // Disconnect the joycons and connect them as dual joycon at the first index. - DisconnectNpad(aruid, npad_id_1); - DisconnectNpad(aruid, npad_id_2); + DisconnectNpad(kernel, aruid, npad_id_1); + DisconnectNpad(kernel, aruid, npad_id_2); controller_1.is_dual_left_connected = true; controller_1.is_dual_right_connected = true; - AddNewControllerAt(aruid, Core::HID::NpadStyleIndex::JoyconDual, npad_id_1); + AddNewControllerAt(kernel, aruid, Core::HID::NpadStyleIndex::JoyconDual, npad_id_1); return ResultSuccess; } @@ -956,11 +977,9 @@ Result NPad::StopLrAssignmentMode(u64 aruid) { return result; } -Result NPad::SwapNpadAssignment(u64 aruid, Core::HID::NpadIdType npad_id_1, - Core::HID::NpadIdType npad_id_2) { +Result NPad::SwapNpadAssignment(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadIdType npad_id_1, Core::HID::NpadIdType npad_id_2) { if (!IsNpadIdValid(npad_id_1) || !IsNpadIdValid(npad_id_2)) { - LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id_1:{}, npad_id_2:{}", npad_id_1, - npad_id_2); + LOG_ERROR(Service_HID, "Invalid NpadIdType npad_id_1:{}, npad_id_2:{}", npad_id_1, npad_id_2); return ResultInvalidNpadId; } if (npad_id_1 == Core::HID::NpadIdType::Handheld || @@ -982,20 +1001,17 @@ Result NPad::SwapNpadAssignment(u64 aruid, Core::HID::NpadIdType npad_id_1, return ResultNpadNotConnected; } - UpdateControllerAt(aruid, type_index_2, npad_id_1, is_connected_2); - UpdateControllerAt(aruid, type_index_1, npad_id_2, is_connected_1); - + UpdateControllerAt(kernel, aruid, type_index_2, npad_id_1, is_connected_2); + UpdateControllerAt(kernel, aruid, type_index_1, npad_id_2, is_connected_1); return ResultSuccess; } -Result NPad::IsUnintendedHomeButtonInputProtectionEnabled(bool& out_is_enabled, u64 aruid, - Core::HID::NpadIdType npad_id) const { +Result NPad::IsUnintendedHomeButtonInputProtectionEnabled(bool& out_is_enabled, u64 aruid, Core::HID::NpadIdType npad_id) const { std::scoped_lock lock{mutex}; return npad_resource.GetHomeProtectionEnabled(out_is_enabled, aruid, npad_id); } -Result NPad::EnableUnintendedHomeButtonInputProtection(u64 aruid, Core::HID::NpadIdType npad_id, - bool is_enabled) { +Result NPad::EnableUnintendedHomeButtonInputProtection(u64 aruid, Core::HID::NpadIdType npad_id, bool is_enabled) { std::scoped_lock lock{mutex}; return npad_resource.SetHomeProtectionEnabled(aruid, npad_id, is_enabled); } @@ -1049,29 +1065,29 @@ Core::HID::NpadButton NPad::GetAndResetPressState() { return static_cast(press_state.exchange(0)); } -Result NPad::ApplyNpadSystemCommonPolicy(u64 aruid) { +Result NPad::ApplyNpadSystemCommonPolicy(Kernel::KernelCore& kernel, u64 aruid) { std::scoped_lock lock{mutex}; const Result result = npad_resource.ApplyNpadSystemCommonPolicy(aruid, false); if (result.IsSuccess()) { - OnUpdate({}); + OnUpdate(kernel, {}); } return result; } -Result NPad::ApplyNpadSystemCommonPolicyFull(u64 aruid) { +Result NPad::ApplyNpadSystemCommonPolicyFull(Kernel::KernelCore& kernel, u64 aruid) { std::scoped_lock lock{mutex}; const Result result = npad_resource.ApplyNpadSystemCommonPolicy(aruid, true); if (result.IsSuccess()) { - OnUpdate({}); + OnUpdate(kernel, {}); } return result; } -Result NPad::ClearNpadSystemCommonPolicy(u64 aruid) { +Result NPad::ClearNpadSystemCommonPolicy(Kernel::KernelCore& kernel, u64 aruid) { std::scoped_lock lock{mutex}; const Result result = npad_resource.ClearNpadSystemCommonPolicy(aruid); if (result.IsSuccess()) { - OnUpdate({}); + OnUpdate(kernel, {}); } return result; } @@ -1090,13 +1106,16 @@ Result NPad::RegisterAppletResourceUserId(u64 aruid) { void NPad::UnregisterAppletResourceUserId(u64 aruid) { // TODO: Remove this once abstract pad is emulated properly - const auto aruid_index = npad_resource.GetIndexFromAruid(aruid); + auto aruid_index = npad_resource.GetIndexFromAruid(aruid); + if (aruid_index >= AruidIndexMax) { + LOG_ERROR(Service_HID, "Invalid aruid:{:016X}", aruid); + aruid_index = 0; + } for (auto& controller : controller_data[aruid_index]) { controller.is_active = false; controller.is_connected = false; controller.shared_memory = nullptr; } - npad_resource.UnregisterAppletResourceUserId(aruid); } @@ -1139,7 +1158,11 @@ NPad::NpadControllerData& NPad::GetControllerFromNpadIdType(u64 aruid, npad_id = Core::HID::NpadIdType::Player1; } const auto npad_index = NpadIdTypeToIndex(npad_id); - const auto aruid_index = applet_resource_holder.applet_resource->GetIndexFromAruid(aruid); + auto aruid_index = applet_resource_holder.applet_resource->GetIndexFromAruid(aruid); + if (aruid_index >= AruidIndexMax) { + LOG_ERROR(Service_HID, "Invalid aruid:{:016X}", aruid); + aruid_index = 0; + } return controller_data[aruid_index][npad_index]; } @@ -1150,7 +1173,11 @@ const NPad::NpadControllerData& NPad::GetControllerFromNpadIdType( npad_id = Core::HID::NpadIdType::Player1; } const auto npad_index = NpadIdTypeToIndex(npad_id); - const auto aruid_index = applet_resource_holder.applet_resource->GetIndexFromAruid(aruid); + auto aruid_index = applet_resource_holder.applet_resource->GetIndexFromAruid(aruid); + if (aruid_index >= AruidIndexMax) { + LOG_ERROR(Service_HID, "Invalid aruid:{:016X}", aruid); + aruid_index = 0; + } return controller_data[aruid_index][npad_index]; } diff --git a/src/hid_core/resources/npad/npad.h b/src/hid_core/resources/npad/npad.h index 99e761127d..3e10f02a7c 100644 --- a/src/hid_core/resources/npad/npad.h +++ b/src/hid_core/resources/npad/npad.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -61,37 +64,31 @@ public: void FreeAppletResourceId(u64 aruid); // When the controller is requesting an update for the shared memory - void OnUpdate(const Core::Timing::CoreTiming& core_timing); + void OnUpdate(Kernel::KernelCore& kernel, const Core::Timing::CoreTiming& core_timing); - Result SetSupportedNpadStyleSet(u64 aruid, Core::HID::NpadStyleSet supported_style_set); - Result GetSupportedNpadStyleSet(u64 aruid, - Core::HID::NpadStyleSet& out_supported_style_set) const; - Result GetMaskedSupportedNpadStyleSet(u64 aruid, - Core::HID::NpadStyleSet& out_supported_style_set) const; - - Result SetSupportedNpadIdType(u64 aruid, - std::span supported_npad_list); + Result SetSupportedNpadStyleSet(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadStyleSet supported_style_set); + Result GetSupportedNpadStyleSet(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadStyleSet& out_supported_style_set) const; + Result GetMaskedSupportedNpadStyleSet(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadStyleSet& out_supported_style_set) const; + Result SetSupportedNpadIdType(Kernel::KernelCore& kernel, u64 aruid, std::span supported_npad_list); Result SetNpadJoyHoldType(u64 aruid, NpadJoyHoldType hold_type); Result GetNpadJoyHoldType(u64 aruid, NpadJoyHoldType& out_hold_type) const; - Result SetNpadHandheldActivationMode(u64 aruid, NpadHandheldActivationMode mode); - Result GetNpadHandheldActivationMode(u64 aruid, NpadHandheldActivationMode& out_mode) const; + Result SetNpadHandheldActivationMode(Kernel::KernelCore& kernel, u64 aruid, NpadHandheldActivationMode mode); + Result GetNpadHandheldActivationMode(Kernel::KernelCore& kernel, u64 aruid, NpadHandheldActivationMode& out_mode) const; - bool SetNpadMode(u64 aruid, Core::HID::NpadIdType& new_npad_id, Core::HID::NpadIdType npad_id, + bool SetNpadMode(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadIdType& new_npad_id, Core::HID::NpadIdType npad_id, NpadJoyDeviceType npad_device_type, NpadJoyAssignmentMode assignment_mode); - Result AcquireNpadStyleSetUpdateEventHandle(u64 aruid, Kernel::KReadableEvent** out_event, + Result AcquireNpadStyleSetUpdateEventHandle(Kernel::KernelCore& kernel, u64 aruid, Kernel::KReadableEvent** out_event, Core::HID::NpadIdType npad_id); // Adds a new controller at an index. - void AddNewControllerAt(u64 aruid, Core::HID::NpadStyleIndex controller, - Core::HID::NpadIdType npad_id); + void AddNewControllerAt(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadStyleIndex controller, Core::HID::NpadIdType npad_id); // Adds a new controller at an index with connection status. - void UpdateControllerAt(u64 aruid, Core::HID::NpadStyleIndex controller, - Core::HID::NpadIdType npad_id, bool connected); + void UpdateControllerAt(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadStyleIndex controller, Core::HID::NpadIdType npad_id, bool connected); - Result DisconnectNpad(u64 aruid, Core::HID::NpadIdType npad_id); + Result DisconnectNpad(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadIdType npad_id); Result IsFirmwareUpdateAvailableForSixAxisSensor( u64 aruid, const Core::HID::SixAxisSensorHandle& sixaxis_handle, @@ -110,20 +107,18 @@ public: void ConnectAllDisconnectedControllers(); void ClearAllControllers(); - Result MergeSingleJoyAsDualJoy(u64 aruid, Core::HID::NpadIdType npad_id_1, - Core::HID::NpadIdType npad_id_2); + Result MergeSingleJoyAsDualJoy(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadIdType npad_id_1, Core::HID::NpadIdType npad_id_2); Result StartLrAssignmentMode(u64 aruid); Result StopLrAssignmentMode(u64 aruid); - Result SwapNpadAssignment(u64 aruid, Core::HID::NpadIdType npad_id_1, - Core::HID::NpadIdType npad_id_2); + Result SwapNpadAssignment(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadIdType npad_id_1, Core::HID::NpadIdType npad_id_2); // Logical OR for all buttons presses on all controllers // Specifically for cheat engine and other features. Core::HID::NpadButton GetAndResetPressState(); - Result ApplyNpadSystemCommonPolicy(u64 aruid); - Result ApplyNpadSystemCommonPolicyFull(u64 aruid); - Result ClearNpadSystemCommonPolicy(u64 aruid); + Result ApplyNpadSystemCommonPolicy(Kernel::KernelCore& kernel, u64 aruid); + Result ApplyNpadSystemCommonPolicyFull(Kernel::KernelCore& kernel, u64 aruid); + Result ClearNpadSystemCommonPolicy(Kernel::KernelCore& kernel, u64 aruid); void SetRevision(u64 aruid, NpadRevision revision); NpadRevision GetRevision(u64 aruid); @@ -180,9 +175,9 @@ private: int callback_key{}; }; - void ControllerUpdate(Core::HID::ControllerTriggerType type, std::size_t controller_idx); - void InitNewlyAddedController(u64 aruid, Core::HID::NpadIdType npad_id); - void RequestPadStateUpdate(u64 aruid, Core::HID::NpadIdType npad_id); + void ControllerUpdate(Kernel::KernelCore& kernel, Core::HID::ControllerTriggerType type, std::size_t controller_idx); + void InitNewlyAddedController(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadIdType npad_id); + void RequestPadStateUpdate(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadIdType npad_id); void WriteEmptyEntry(NpadInternalState* npad); NpadControllerData& GetControllerFromHandle( @@ -209,7 +204,6 @@ private: NpadVibration vibration_handler{}; std::atomic press_state{}; - std::array, AruidIndexMax> - controller_data{}; + std::array, AruidIndexMax> controller_data{}; }; } // namespace Service::HID diff --git a/src/hid_core/resources/npad/npad_data.cpp b/src/hid_core/resources/npad/npad_data.cpp index a490fe8a54..2e067f9e8a 100644 --- a/src/hid_core/resources/npad/npad_data.cpp +++ b/src/hid_core/resources/npad/npad_data.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -24,7 +24,7 @@ void NPadData::SetNpadAnalogStickUseCenterClamp(bool is_enabled) { } bool NPadData::GetNpadAnalogStickUseCenterClamp() const { - return status.use_center_clamp.As(); + return bool(status.use_center_clamp); } void NPadData::SetNpadSystemExtStateEnabled(bool is_enabled) { @@ -32,7 +32,7 @@ void NPadData::SetNpadSystemExtStateEnabled(bool is_enabled) { } bool NPadData::GetNpadSystemExtState() const { - return status.system_ext_state.As(); + return bool(status.system_ext_state); } Result NPadData::SetSupportedNpadIdType(std::span list) { @@ -42,18 +42,14 @@ Result NPadData::SetSupportedNpadIdType(std::span l } supported_npad_id_types_count = list.size(); - memcpy(supported_npad_id_types.data(), list.data(), - list.size() * sizeof(Core::HID::NpadIdType)); - + std::memcpy(supported_npad_id_types.data(), list.data(), list.size() * sizeof(Core::HID::NpadIdType)); return ResultSuccess; } std::size_t NPadData::GetSupportedNpadIdType(std::span out_list) const { std::size_t out_size = (std::min)(supported_npad_id_types_count, out_list.size()); - memcpy(out_list.data(), supported_npad_id_types.data(), - out_size * sizeof(Core::HID::NpadIdType)); - + std::memcpy(out_list.data(), supported_npad_id_types.data(), out_size * sizeof(Core::HID::NpadIdType)); return out_size; } @@ -154,27 +150,27 @@ bool NPadData::IsNpadStyleIndexSupported(Core::HID::NpadStyleIndex style_index) Core::HID::NpadStyleTag style = {supported_npad_style_set}; switch (style_index) { case Core::HID::NpadStyleIndex::Fullkey: - return style.fullkey.As(); + return bool(style.fullkey); case Core::HID::NpadStyleIndex::Handheld: - return style.handheld.As(); + return bool(style.handheld); case Core::HID::NpadStyleIndex::JoyconDual: - return style.joycon_dual.As(); + return bool(style.joycon_dual); case Core::HID::NpadStyleIndex::JoyconLeft: - return style.joycon_left.As(); + return bool(style.joycon_left); case Core::HID::NpadStyleIndex::JoyconRight: - return style.joycon_right.As(); + return bool(style.joycon_right); case Core::HID::NpadStyleIndex::GameCube: - return style.gamecube.As(); + return bool(style.gamecube); case Core::HID::NpadStyleIndex::Pokeball: - return style.palma.As(); + return bool(style.palma); case Core::HID::NpadStyleIndex::NES: - return style.lark.As(); + return bool(style.lark); case Core::HID::NpadStyleIndex::SNES: - return style.lucia.As(); + return bool(style.lucia); case Core::HID::NpadStyleIndex::N64: - return style.lagoon.As(); + return bool(style.lagoon); case Core::HID::NpadStyleIndex::SegaGenesis: - return style.lager.As(); + return bool(style.lager); default: return false; } @@ -185,7 +181,7 @@ void NPadData::SetLrAssignmentMode(bool is_enabled) { } bool NPadData::GetLrAssignmentMode() const { - return status.lr_assignment_mode.As(); + return bool(status.lr_assignment_mode); } void NPadData::SetAssigningSingleOnSlSrPress(bool is_enabled) { @@ -193,7 +189,7 @@ void NPadData::SetAssigningSingleOnSlSrPress(bool is_enabled) { } bool NPadData::GetAssigningSingleOnSlSrPress() const { - return status.assigning_single_on_sl_sr_press.As(); + return bool(status.assigning_single_on_sl_sr_press); } void NPadData::SetHomeProtectionEnabled(bool is_enabled, Core::HID::NpadIdType npad_id) { diff --git a/src/hid_core/resources/npad/npad_resource.cpp b/src/hid_core/resources/npad/npad_resource.cpp index 08546f8dc4..a1b483d7b8 100644 --- a/src/hid_core/resources/npad/npad_resource.cpp +++ b/src/hid_core/resources/npad/npad_resource.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -13,7 +13,9 @@ namespace Service::HID { -NPadResource::NPadResource(KernelHelpers::ServiceContext& context) : service_context{context} {} +NPadResource::NPadResource(Kernel::KernelCore& kernel_, KernelHelpers::ServiceContext& context) + : service_context{context} +{} NPadResource::~NPadResource() = default; @@ -69,6 +71,9 @@ Result NPadResource::RegisterAppletResourceUserId(u64 aruid) { void NPadResource::UnregisterAppletResourceUserId(u64 aruid) { const u64 aruid_index = GetIndexFromAruid(aruid); + if (aruid_index >= AruidIndexMax) { + return; + } FreeAppletResourceId(aruid); if (aruid_index < AruidIndexMax) { @@ -505,9 +510,7 @@ Result NPadResource::IsAssigningSingleOnSlSrPressEnabled(bool& is_enabled, u64 a return ResultSuccess; } -Result NPadResource::AcquireNpadStyleSetUpdateEventHandle(u64 aruid, - Kernel::KReadableEvent** out_event, - Core::HID::NpadIdType npad_id) { +Result NPadResource::AcquireNpadStyleSetUpdateEventHandle(Kernel::KernelCore& kernel, u64 aruid, Kernel::KReadableEvent** out_event, Core::HID::NpadIdType npad_id) { const u64 aruid_index = GetIndexFromAruid(aruid); if (aruid_index >= AruidIndexMax) { return ResultNpadNotConnected; @@ -526,26 +529,25 @@ Result NPadResource::AcquireNpadStyleSetUpdateEventHandle(u64 aruid, *out_event = &controller_state.style_set_update_event->GetReadableEvent(); if (controller_state.is_styleset_update_event_initialized) { - controller_state.style_set_update_event->Signal(); + controller_state.style_set_update_event->Signal(kernel); } return ResultSuccess; } -Result NPadResource::SignalStyleSetUpdateEvent(u64 aruid, Core::HID::NpadIdType npad_id) { +Result NPadResource::SignalStyleSetUpdateEvent(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadIdType npad_id) { const u64 aruid_index = GetIndexFromAruid(aruid); if (aruid_index >= AruidIndexMax) { return ResultNpadNotConnected; } - auto controller = state[aruid_index].controller_state[NpadIdTypeToIndex(npad_id)]; - if (controller.is_styleset_update_event_initialized) { - controller.style_set_update_event->Signal(); + auto& controller_state = state[aruid_index].controller_state[NpadIdTypeToIndex(npad_id)]; + if (controller_state.is_styleset_update_event_initialized) { + controller_state.style_set_update_event->Signal(kernel); } return ResultSuccess; } -Result NPadResource::GetHomeProtectionEnabled(bool& is_enabled, u64 aruid, - Core::HID::NpadIdType npad_id) const { +Result NPadResource::GetHomeProtectionEnabled(bool& is_enabled, u64 aruid, Core::HID::NpadIdType npad_id) const { const u64 aruid_index = GetIndexFromAruid(aruid); if (aruid_index >= AruidIndexMax) { return ResultNpadNotConnected; @@ -555,8 +557,7 @@ Result NPadResource::GetHomeProtectionEnabled(bool& is_enabled, u64 aruid, return ResultSuccess; } -Result NPadResource::SetHomeProtectionEnabled(u64 aruid, Core::HID::NpadIdType npad_id, - bool is_enabled) { +Result NPadResource::SetHomeProtectionEnabled(u64 aruid, Core::HID::NpadIdType npad_id, bool is_enabled) { const u64 aruid_index = GetIndexFromAruid(aruid); if (aruid_index >= AruidIndexMax) { return ResultNpadNotConnected; diff --git a/src/hid_core/resources/npad/npad_resource.h b/src/hid_core/resources/npad/npad_resource.h index 8ee5702fd4..2d381c4642 100644 --- a/src/hid_core/resources/npad/npad_resource.h +++ b/src/hid_core/resources/npad/npad_resource.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -20,6 +23,7 @@ class System; } namespace Kernel { +class KernelCore; class KReadableEvent; } @@ -37,8 +41,7 @@ struct NpadState { DataStatusFlag flag{}; u64 aruid{}; NPadData data{}; - std::array, MaxSupportedNpadIdTypes> - button_config; + std::array, MaxSupportedNpadIdTypes> button_config; std::array controller_state; NpadRevision npad_revision; }; @@ -46,7 +49,7 @@ struct NpadState { /// Handles Npad request from HID interfaces class NPadResource final { public: - explicit NPadResource(KernelHelpers::ServiceContext& context); + explicit NPadResource(Kernel::KernelCore& kernel, KernelHelpers::ServiceContext& context); ~NPadResource(); NPadData* GetActiveData(); @@ -81,11 +84,9 @@ public: Result GetNpadJoyHoldType(NpadJoyHoldType& hold_type, u64 aruid) const; Result SetNpadHandheldActivationMode(u64 aruid, NpadHandheldActivationMode activation_mode); - Result GetNpadHandheldActivationMode(NpadHandheldActivationMode& activation_mode, - u64 aruid) const; + Result GetNpadHandheldActivationMode(NpadHandheldActivationMode& activation_mode, u64 aruid) const; - Result SetSupportedNpadIdType(u64 aruid, - std::span supported_npad_list); + Result SetSupportedNpadIdType(u64 aruid, std::span supported_npad_list); bool IsControllerSupported(u64 aruid, Core::HID::NpadStyleIndex style_index) const; Result SetLrAssignmentMode(u64 aruid, bool is_enabled); @@ -94,28 +95,20 @@ public: Result SetAssigningSingleOnSlSrPress(u64 aruid, bool is_enabled); Result IsAssigningSingleOnSlSrPressEnabled(bool& is_enabled, u64 aruid) const; - Result AcquireNpadStyleSetUpdateEventHandle(u64 aruid, Kernel::KReadableEvent** out_event, - Core::HID::NpadIdType npad_id); - Result SignalStyleSetUpdateEvent(u64 aruid, Core::HID::NpadIdType npad_id); - - Result GetHomeProtectionEnabled(bool& is_enabled, u64 aruid, - Core::HID::NpadIdType npad_id) const; + Result AcquireNpadStyleSetUpdateEventHandle(Kernel::KernelCore& kernel, u64 aruid, Kernel::KReadableEvent** out_event, Core::HID::NpadIdType npad_id); + Result SignalStyleSetUpdateEvent(Kernel::KernelCore& kernel, u64 aruid, Core::HID::NpadIdType npad_id); + Result GetHomeProtectionEnabled(bool& is_enabled, u64 aruid, Core::HID::NpadIdType npad_id) const; Result SetHomeProtectionEnabled(u64 aruid, Core::HID::NpadIdType npad_id, bool is_enabled); Result SetNpadAnalogStickUseCenterClamp(u64 aruid, bool is_enabled); - Result SetButtonConfig(u64 aruid, Core::HID::NpadIdType npad_id, std::size_t index, - Core::HID::NpadButton button_config); - Core::HID::NpadButton GetButtonConfig(u64 aruid, Core::HID::NpadIdType npad_id, - std::size_t index, Core::HID::NpadButton mask, - bool is_enabled); + Result SetButtonConfig(u64 aruid, Core::HID::NpadIdType npad_id, std::size_t index, Core::HID::NpadButton button_config); + Core::HID::NpadButton GetButtonConfig(u64 aruid, Core::HID::NpadIdType npad_id, std::size_t index, Core::HID::NpadButton mask, bool is_enabled); void ResetButtonConfig(); - Result SetNpadCaptureButtonAssignment(u64 aruid, Core::HID::NpadStyleSet npad_style_set, - Core::HID::NpadButton button_assignment); + Result SetNpadCaptureButtonAssignment(u64 aruid, Core::HID::NpadStyleSet npad_style_set, Core::HID::NpadButton button_assignment); Result ClearNpadCaptureButtonAssignment(u64 aruid); - std::size_t GetNpadCaptureButtonAssignment(std::span out_list, - u64 aruid) const; + std::size_t GetNpadCaptureButtonAssignment(std::span out_list, u64 aruid) const; Result SetNpadSystemExtStateEnabled(u64 aruid, bool is_enabled); @@ -124,9 +117,8 @@ private: AruidRegisterList registration_list{}; std::array state{}; u64 active_data_aruid{}; + KernelHelpers::ServiceContext& service_context; NpadJoyHoldType default_hold_type{}; s32 ref_counter{}; - - KernelHelpers::ServiceContext& service_context; }; } // namespace Service::HID diff --git a/src/hid_core/resources/npad/npad_types.h b/src/hid_core/resources/npad/npad_types.h index 68b803c3d3..2f7ac2d4f7 100644 --- a/src/hid_core/resources/npad/npad_types.h +++ b/src/hid_core/resources/npad/npad_types.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -6,6 +6,8 @@ #pragma once +#include + #include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" diff --git a/src/hid_core/resources/palma/palma.cpp b/src/hid_core/resources/palma/palma.cpp index 0652455f5f..1a1a3aae18 100644 --- a/src/hid_core/resources/palma/palma.cpp +++ b/src/hid_core/resources/palma/palma.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project @@ -77,7 +77,7 @@ Result Palma::PlayPalmaActivity(const PalmaConnectionHandle& handle, u64 palma_a operation.operation = PackedPalmaOperationType::PlayActivity; operation.result = PalmaResultSuccess; operation.data = {}; - operation_complete_event->Signal(); + operation_complete_event->Signal(hid_core.kernel); return ResultSuccess; } @@ -96,7 +96,7 @@ Result Palma::ReadPalmaStep(const PalmaConnectionHandle& handle) { operation.operation = PackedPalmaOperationType::ReadStep; operation.result = PalmaResultSuccess; operation.data = {}; - operation_complete_event->Signal(); + operation_complete_event->Signal(hid_core.kernel); return ResultSuccess; } @@ -125,7 +125,7 @@ Result Palma::ReadPalmaUniqueCode(const PalmaConnectionHandle& handle) { operation.operation = PackedPalmaOperationType::ReadUniqueCode; operation.result = PalmaResultSuccess; operation.data = {}; - operation_complete_event->Signal(); + operation_complete_event->Signal(hid_core.kernel); return ResultSuccess; } @@ -136,7 +136,7 @@ Result Palma::SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle& handle) { operation.operation = PackedPalmaOperationType::SetUniqueCodeInvalid; operation.result = PalmaResultSuccess; operation.data = {}; - operation_complete_event->Signal(); + operation_complete_event->Signal(hid_core.kernel); return ResultSuccess; } @@ -149,7 +149,7 @@ Result Palma::WritePalmaRgbLedPatternEntry(const PalmaConnectionHandle& handle, operation.operation = PackedPalmaOperationType::WriteRgbLedPatternEntry; operation.result = PalmaResultSuccess; operation.data = {}; - operation_complete_event->Signal(); + operation_complete_event->Signal(hid_core.kernel); return ResultSuccess; } @@ -161,7 +161,7 @@ Result Palma::WritePalmaWaveEntry(const PalmaConnectionHandle& handle, PalmaWave operation.operation = PackedPalmaOperationType::WriteWaveEntry; operation.result = PalmaResultSuccess; operation.data = {}; - operation_complete_event->Signal(); + operation_complete_event->Signal(hid_core.kernel); return ResultSuccess; } @@ -174,7 +174,7 @@ Result Palma::SetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& operation.operation = PackedPalmaOperationType::ReadDataBaseIdentificationVersion; operation.result = PalmaResultSuccess; operation.data[0] = {}; - operation_complete_event->Signal(); + operation_complete_event->Signal(hid_core.kernel); return ResultSuccess; } @@ -186,7 +186,7 @@ Result Palma::GetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& operation.result = PalmaResultSuccess; operation.data = {}; operation.data[0] = static_cast(database_id_version); - operation_complete_event->Signal(); + operation_complete_event->Signal(hid_core.kernel); return ResultSuccess; } diff --git a/src/hid_core/resources/shared_memory_format.h b/src/hid_core/resources/shared_memory_format.h index b9deeec302..0d74689869 100644 --- a/src/hid_core/resources/shared_memory_format.h +++ b/src/hid_core/resources/shared_memory_format.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -196,7 +196,7 @@ struct ConsoleSixAxisSensorSharedMemoryFormat { bool is_seven_six_axis_sensor_at_rest{}; INSERT_PADDING_BYTES(3); // padding f32 verticalization_error{}; - Common::Vec3f gyro_bias{}; + Common::Vec gyro_bias{}; INSERT_PADDING_BYTES(4); // padding }; static_assert(sizeof(ConsoleSixAxisSensorSharedMemoryFormat) == 0x20, diff --git a/src/hid_core/resources/shared_memory_holder.cpp b/src/hid_core/resources/shared_memory_holder.cpp index ada593d8ba..c94829821a 100644 --- a/src/hid_core/resources/shared_memory_holder.cpp +++ b/src/hid_core/resources/shared_memory_holder.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -9,17 +12,10 @@ #include "hid_core/resources/shared_memory_holder.h" namespace Service::HID { -SharedMemoryHolder::SharedMemoryHolder() {} - -SharedMemoryHolder::~SharedMemoryHolder() { - Finalize(); -} Result SharedMemoryHolder::Initialize(Core::System& system) { shared_memory = Kernel::KSharedMemory::Create(system.Kernel()); - const Result result = shared_memory->Initialize( - system.DeviceMemory(), nullptr, Kernel::Svc::MemoryPermission::None, - Kernel::Svc::MemoryPermission::Read, sizeof(SharedMemoryFormat)); + const Result result = shared_memory->Initialize(system.Kernel(), system.DeviceMemory(), nullptr, Kernel::Svc::MemoryPermission::None, Kernel::Svc::MemoryPermission::Read, sizeof(SharedMemoryFormat)); if (result.IsError()) { return result; } @@ -31,9 +27,9 @@ Result SharedMemoryHolder::Initialize(Core::System& system) { return ResultSuccess; } -void SharedMemoryHolder::Finalize() { +void SharedMemoryHolder::Finalize(Core::System& system) { if (address != nullptr) { - shared_memory->Close(); + shared_memory->Close(system.Kernel()); } is_created = false; is_mapped = false; diff --git a/src/hid_core/resources/shared_memory_holder.h b/src/hid_core/resources/shared_memory_holder.h index 943407c00a..5fcaddecc0 100644 --- a/src/hid_core/resources/shared_memory_holder.h +++ b/src/hid_core/resources/shared_memory_holder.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -19,17 +22,13 @@ struct SharedMemoryFormat; // This is nn::hid::detail::SharedMemoryHolder class SharedMemoryHolder { public: - SharedMemoryHolder(); - ~SharedMemoryHolder(); - Result Initialize(Core::System& system); - void Finalize(); + void Finalize(Core::System& system); bool IsMapped(); SharedMemoryFormat* GetAddress(); Kernel::KSharedMemory* GetHandle(); -private: bool is_owner{}; bool is_created{}; bool is_mapped{}; diff --git a/src/hid_core/resources/six_axis/seven_six_axis.cpp b/src/hid_core/resources/six_axis/seven_six_axis.cpp index d84ef31e10..13dd5f943a 100644 --- a/src/hid_core/resources/six_axis/seven_six_axis.cpp +++ b/src/hid_core/resources/six_axis/seven_six_axis.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -6,6 +9,7 @@ #include "core/core.h" #include "core/core_timing.h" #include "core/frontend/emu_window.h" +#include "core/hle/kernel/k_process.h" #include "core/memory.h" #include "hid_core/frontend/emulated_console.h" #include "hid_core/frontend/emulated_devices.h" @@ -24,7 +28,7 @@ void SevenSixAxis::OnInit() {} void SevenSixAxis::OnRelease() {} void SevenSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing) { - if (!IsControllerActivated() || transfer_memory == 0) { + if (!IsControllerActivated() || transfer_memory == 0 || transfer_memory_owner == nullptr) { seven_sixaxis_lifo.buffer_count = 0; seven_sixaxis_lifo.buffer_tail = 0; return; @@ -42,21 +46,19 @@ void SevenSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing) { next_seven_sixaxis_state.accel = motion_status.accel; next_seven_sixaxis_state.gyro = motion_status.gyro; next_seven_sixaxis_state.quaternion = { - { - motion_status.quaternion.xyz.y, - motion_status.quaternion.xyz.x, - -motion_status.quaternion.w, - }, - -motion_status.quaternion.xyz.z, + motion_status.quaternion[1], + motion_status.quaternion[0], + -motion_status.quaternion[3], + -motion_status.quaternion[2], }; - seven_sixaxis_lifo.WriteNextEntry(next_seven_sixaxis_state); - system.ApplicationMemory().WriteBlock(transfer_memory, &seven_sixaxis_lifo, - sizeof(seven_sixaxis_lifo)); + transfer_memory_owner->GetMemory().WriteBlock(transfer_memory, &seven_sixaxis_lifo, + sizeof(seven_sixaxis_lifo)); } -void SevenSixAxis::SetTransferMemoryAddress(Common::ProcessAddress t_mem) { +void SevenSixAxis::SetTransferMemoryAddress(Common::ProcessAddress t_mem, Kernel::KProcess* owner) { transfer_memory = t_mem; + transfer_memory_owner = owner; } void SevenSixAxis::ResetTimestamp() { diff --git a/src/hid_core/resources/six_axis/seven_six_axis.h b/src/hid_core/resources/six_axis/seven_six_axis.h index 0a26c77c91..a59d697356 100644 --- a/src/hid_core/resources/six_axis/seven_six_axis.h +++ b/src/hid_core/resources/six_axis/seven_six_axis.h @@ -1,14 +1,21 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once #include "common/common_types.h" -#include "common/quaternion.h" +#include "common/vector_math.h" #include "common/typed_address.h" #include "hid_core/resources/controller_base.h" #include "hid_core/resources/ring_lifo.h" +namespace Kernel { +class KProcess; +} + namespace Core { class System; } // namespace Core @@ -33,7 +40,7 @@ public: void OnUpdate(const Core::Timing::CoreTiming& core_timing) override; // Called on InitializeSevenSixAxisSensor - void SetTransferMemoryAddress(Common::ProcessAddress t_mem); + void SetTransferMemoryAddress(Common::ProcessAddress t_mem, Kernel::KProcess* owner); // Called on ResetSevenSixAxisSensorTimestamp void ResetTimestamp(); @@ -44,9 +51,9 @@ private: u64 timestamp{}; u64 sampling_number{}; u64 unknown{}; - Common::Vec3f accel{}; - Common::Vec3f gyro{}; - Common::Quaternion quaternion{}; + Common::Vec accel{}; + Common::Vec gyro{}; + Common::Vec quaternion{}; }; static_assert(sizeof(SevenSixAxisState) == 0x48, "SevenSixAxisState is an invalid size"); @@ -58,6 +65,7 @@ private: SevenSixAxisState next_seven_sixaxis_state{}; Common::ProcessAddress transfer_memory{}; + Kernel::KProcess* transfer_memory_owner{}; Core::HID::EmulatedConsole* console = nullptr; Core::System& system; diff --git a/src/hid_core/resources/six_axis/six_axis.cpp b/src/hid_core/resources/six_axis/six_axis.cpp index b407a5c76e..53a56d2661 100644 --- a/src/hid_core/resources/six_axis/six_axis.cpp +++ b/src/hid_core/resources/six_axis/six_axis.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -93,9 +96,9 @@ void SixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing) { .accel = {0, 0, -1.0f}, .orientation = { - Common::Vec3f{1.0f, 0, 0}, - Common::Vec3f{0, 1.0f, 0}, - Common::Vec3f{0, 0, 1.0f}, + Common::Vec{1.0f, 0, 0}, + Common::Vec{0, 1.0f, 0}, + Common::Vec{0, 0, 1.0f}, }, .attribute = {1}, }; diff --git a/src/hid_core/resources/touch_screen/gesture_handler.cpp b/src/hid_core/resources/touch_screen/gesture_handler.cpp index 8e5ed8ff7e..8549c1b91e 100644 --- a/src/hid_core/resources/touch_screen/gesture_handler.cpp +++ b/src/hid_core/resources/touch_screen/gesture_handler.cpp @@ -1,9 +1,10 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +#include #include "common/math_util.h" #include "hid_core/resources/touch_screen/gesture_handler.h" @@ -214,7 +215,7 @@ void GestureHandler::UpdatePanEvent(GestureState& next_state, GestureType& type) if (std::abs(angle_between_two_lines) > AngleThreshold) { type = GestureType::Rotate; next_state.scale = 0; - next_state.rotation_angle = angle_between_two_lines * 180.0f / Common::PI; + next_state.rotation_angle = angle_between_two_lines * 180.0f / float(std::numbers::pi_v); } } diff --git a/src/hid_core/resources/touch_screen/touch_screen_resource.cpp b/src/hid_core/resources/touch_screen/touch_screen_resource.cpp index 5d77fe5719..51932678da 100644 --- a/src/hid_core/resources/touch_screen/touch_screen_resource.cpp +++ b/src/hid_core/resources/touch_screen/touch_screen_resource.cpp @@ -486,27 +486,17 @@ void TouchResource::ReadTouchInput() { SanitizeInput(current_touch_state); std::scoped_lock lock{*input_mutex}; - if (current_touch_state.entry_count == previous_touch_state.entry_count) { - if (current_touch_state.entry_count < 1) { - return; - } + if (current_touch_state.entry_count == previous_touch_state.entry_count && current_touch_state.entry_count >= 1) { bool has_moved = false; - for (std::size_t i = 0; i < static_cast(current_touch_state.entry_count); - i++) { - s32 delta_x = std::abs(static_cast(current_touch_state.states[i].position.x) - - static_cast(previous_touch_state.states[i].position.x)); - s32 delta_y = std::abs(static_cast(current_touch_state.states[i].position.y) - - static_cast(previous_touch_state.states[i].position.y)); - if (delta_x > 1 || delta_y > 1) { - has_moved = true; - } + for (std::size_t i = 0; !has_moved && i < std::size_t(current_touch_state.entry_count); i++) { + s32 delta_x = std::abs(s32(current_touch_state.states[i].position.x) - s32(previous_touch_state.states[i].position.x)); + s32 delta_y = std::abs(s32(current_touch_state.states[i].position.y) - s32(previous_touch_state.states[i].position.y)); + has_moved |= (delta_x > 1 || delta_y > 1); } - if (!has_moved) { - return; + if (has_moved) { + input_event->Signal(system.Kernel()); } } - - input_event->Signal(); } void TouchResource::OnTouchUpdate(s64 timestamp) { @@ -558,9 +548,18 @@ void TouchResource::OnTouchUpdate(s64 timestamp) { } auto& touch_shared = applet_data->shared_memory_format->touch_screen; - StorePreviousTouchState(previous_touch_state, data.finger_map, current_touch_state, - applet_data->flag.enable_touchscreen.As()); - touch_shared.touch_screen_lifo.WriteNextEntry(current_touch_state); + + if (applet_data->flag.enable_touchscreen) { + StorePreviousTouchState(previous_touch_state, data.finger_map, current_touch_state, true); + touch_shared.touch_screen_lifo.WriteNextEntry(current_touch_state); + } else { + TouchScreenState denied{}; + denied.sampling_number = current_touch_state.sampling_number; + denied.entry_count = 0; + data.finger_map.finger_count = 0; + data.finger_map.finger_ids = {}; + touch_shared.touch_screen_lifo.WriteNextEntry(denied); + } } } } diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 406ee967b0..840602f4ce 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -44,7 +44,7 @@ if (MSVC) /we4800 # Implicit conversion from 'type' to bool. Possible information loss ) else() - target_compile_options(input_common PRIVATE $<$:-Werror=conversion>) + target_compile_options(input_common PRIVATE $<$:-Wconversion>) endif() if (ANDROID) @@ -79,8 +79,8 @@ else() helpers/joycon_protocol/rumble.cpp helpers/joycon_protocol/rumble.h) - target_link_libraries(input_common PRIVATE SDL2::SDL2) - target_compile_definitions(input_common PRIVATE HAVE_SDL2) + target_link_libraries(input_common PRIVATE SDL3::SDL3) + target_compile_definitions(input_common PRIVATE HAVE_SDL3) endif() if (ENABLE_LIBUSB) diff --git a/src/input_common/drivers/android.cpp b/src/input_common/drivers/android.cpp index d40fa66aed..966abbef94 100644 --- a/src/input_common/drivers/android.cpp +++ b/src/input_common/drivers/android.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -111,7 +114,7 @@ std::set Android::GetDeviceAxes(JNIEnv* env, jobject& j_device) const { std::set axes; for (int i = 0; i < env->GetArrayLength(j_axes); ++i) { jobject axis = env->GetObjectArrayElement(j_axes, i); - axes.insert(env->GetIntField(axis, Common::Android::GetIntegerValueField())); + axes.insert(env->CallIntMethod(axis, Common::Android::GetIntegerValueMethod())); } return axes; } @@ -157,8 +160,8 @@ Common::ParamPackage Android::BuildButtonParamPackageForButton(PadIdentifier ide bool Android::MatchVID(Common::UUID device, const std::vector& vids) const { for (size_t i = 0; i < vids.size(); ++i) { - auto fucker = device.RawString(); - if (fucker.find(vids[i]) != std::string::npos) { + auto dev_str = device.RawString(); + if (dev_str.find(vids[i]) != std::string::npos) { return true; } } diff --git a/src/input_common/drivers/android.h b/src/input_common/drivers/android.h index 18678de602..c4663aec5c 100644 --- a/src/input_common/drivers/android.h +++ b/src/input_common/drivers/android.h @@ -90,7 +90,7 @@ public: Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; private: - ankerl::unordered_dense::map input_devices; + ::Common::unordered_map input_devices; /// Returns the correct identifier corresponding to the player index PadIdentifier GetIdentifier(const std::string& guid, size_t port) const; diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp index fb2d75e384..ded92d3f1d 100644 --- a/src/input_common/drivers/joycon.cpp +++ b/src/input_common/drivers/joycon.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h index 112e970e15..820c4b2eaa 100644 --- a/src/input_common/drivers/joycon.h +++ b/src/input_common/drivers/joycon.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -6,7 +9,7 @@ #include #include #include -#include +#include #include "input_common/input_engine.h" diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp index 7e029d2cdf..4ac259d03b 100644 --- a/src/input_common/drivers/mouse.cpp +++ b/src/input_common/drivers/mouse.cpp @@ -1,20 +1,19 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#include #include #include #include "common/param_package.h" #include "common/settings.h" -#include "common/thread.h" +#include "common/steady_clock.h" #include "input_common/drivers/mouse.h" namespace InputCommon { -constexpr int update_time = 10; constexpr float default_panning_sensitivity = 0.0010f; constexpr float default_stick_sensitivity = 0.0006f; constexpr float default_deadzone_counterweight = 0.01f; @@ -72,117 +71,100 @@ Mouse::Mouse(std::string input_engine_) : InputEngine(std::move(input_engine_)) wheel_position = {}; last_mouse_change = {}; last_motion_change = {}; +} - update_thread = std::jthread([this](std::stop_token stop_token) { - Common::SetCurrentThreadName("Mouse"); - while (!stop_token.stop_requested()) { - UpdateStickInput(); - UpdateMotionInput(); - std::this_thread::sleep_for(std::chrono::milliseconds(update_time)); +void Mouse::UpdateStickInput(Common::SteadyClock::time_point timestamp) { + if (IsMousePanningEnabled()) { + const float length = last_mouse_change.Length(); + + // Prevent input from exceeding the max range (1.0f) too much, + // but allow some room to make it easier to sustain + if (length > maximum_stick_range) { + last_mouse_change /= length; + last_mouse_change *= maximum_stick_range; } - }); + + SetAxis(identifier, mouse_axis_x, last_mouse_change[0]); + SetAxis(identifier, mouse_axis_y, -last_mouse_change[1]); + + // Decay input over time + const float clamped_length = (std::min)(1.0f, length); + const float decay_strength = Settings::values.mouse_panning_decay_strength.GetValue(); + const float decay = 1 - clamped_length * clamped_length * decay_strength * 0.01f; + const float min_decay = Settings::values.mouse_panning_min_decay.GetValue(); + const float clamped_decay = (std::min)(1 - min_decay / 100.0f, decay); + last_mouse_change *= clamped_decay; + } } -void Mouse::UpdateStickInput() { - if (!IsMousePanningEnabled()) { - return; - } - - const float length = last_mouse_change.Length(); - - // Prevent input from exceeding the max range (1.0f) too much, - // but allow some room to make it easier to sustain - if (length > maximum_stick_range) { - last_mouse_change /= length; - last_mouse_change *= maximum_stick_range; - } - - SetAxis(identifier, mouse_axis_x, last_mouse_change.x); - SetAxis(identifier, mouse_axis_y, -last_mouse_change.y); - - // Decay input over time - const float clamped_length = (std::min)(1.0f, length); - const float decay_strength = Settings::values.mouse_panning_decay_strength.GetValue(); - const float decay = 1 - clamped_length * clamped_length * decay_strength * 0.01f; - const float min_decay = Settings::values.mouse_panning_min_decay.GetValue(); - const float clamped_decay = (std::min)(1 - min_decay / 100.0f, decay); - last_mouse_change *= clamped_decay; -} - -void Mouse::UpdateMotionInput() { +void Mouse::UpdateMotionInput(Common::SteadyClock::time_point timestamp) { const float sensitivity = IsMousePanningEnabled() ? default_motion_panning_sensitivity : default_motion_sensitivity; - const float rotation_velocity = std::sqrt(last_motion_change.x * last_motion_change.x + - last_motion_change.y * last_motion_change.y); + const float rotation_velocity = std::sqrt(last_motion_change[0] * last_motion_change[0] + + last_motion_change[1] * last_motion_change[1]); // Clamp rotation speed if (rotation_velocity > maximum_rotation_speed / sensitivity) { const float multiplier = maximum_rotation_speed / rotation_velocity / sensitivity; - last_motion_change.x = last_motion_change.x * multiplier; - last_motion_change.y = last_motion_change.y * multiplier; + last_motion_change[0] = last_motion_change[0] * multiplier; + last_motion_change[1] = last_motion_change[1] * multiplier; } - const BasicMotion motion_data{ - .gyro_x = last_motion_change.x * sensitivity, - .gyro_y = last_motion_change.y * sensitivity, - .gyro_z = last_motion_change.z * sensitivity, + if (IsMousePanningEnabled()) { + last_motion_change[0] = 0; + last_motion_change[1] = 0; + } + last_motion_change[2] = 0; + + SetMotion(motion_identifier, 0, BasicMotion{ + .gyro_x = last_motion_change[0] * sensitivity, + .gyro_y = last_motion_change[1] * sensitivity, + .gyro_z = last_motion_change[2] * sensitivity, .accel_x = 0, .accel_y = 0, .accel_z = 0, - .delta_timestamp = update_time * 1000, - }; - - if (IsMousePanningEnabled()) { - last_motion_change.x = 0; - last_motion_change.y = 0; - } - last_motion_change.z = 0; - - SetMotion(motion_identifier, 0, motion_data); + .delta_timestamp = u64(std::chrono::duration_cast(timestamp - last_notify_timestamp).count()), + }); } void Mouse::Move(int x, int y, int center_x, int center_y) { if (IsMousePanningEnabled()) { - const auto mouse_change = - (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast(); - const float x_sensitivity = - Settings::values.mouse_panning_x_sensitivity.GetValue() * default_panning_sensitivity; - const float y_sensitivity = - Settings::values.mouse_panning_y_sensitivity.GetValue() * default_panning_sensitivity; - const float deadzone_counterweight = - Settings::values.mouse_panning_deadzone_counterweight.GetValue() * - default_deadzone_counterweight; - - last_motion_change += {-mouse_change.y * x_sensitivity, -mouse_change.x * y_sensitivity, 0}; - last_mouse_change.x += mouse_change.x * x_sensitivity; - last_mouse_change.y += mouse_change.y * y_sensitivity; - - // Bind the mouse change to [0 <= deadzone_counterweight <= 1.0] + auto const mouse_change_int = Common::Vec(x, y) - Common::Vec(center_x, center_y); + auto const mouse_change = Common::Vec(float(mouse_change_int[0]), float(mouse_change_int[1])); + auto const x_sensitivity = Settings::values.mouse_panning_x_sensitivity.GetValue() * default_panning_sensitivity; + auto const y_sensitivity = Settings::values.mouse_panning_y_sensitivity.GetValue() * default_panning_sensitivity; + auto const deadzone_cw = Settings::values.mouse_panning_deadzone_counterweight.GetValue() * default_deadzone_counterweight; + last_motion_change += {-mouse_change[1] * x_sensitivity, -mouse_change[0] * y_sensitivity, 0}; + last_mouse_change[0] += mouse_change[0] * x_sensitivity; + last_mouse_change[1] += mouse_change[1] * y_sensitivity; + // Bind the mouse change to [0 <= deadzone_cw <= 1.0] const float length = last_mouse_change.Length(); - if (length < deadzone_counterweight && length != 0.0f) { + if (length < deadzone_cw && length != 0.0f) { last_mouse_change /= length; - last_mouse_change *= deadzone_counterweight; + last_mouse_change *= deadzone_cw; + } + } else { + if (button_pressed) { + const auto mouse_move = Common::Vec(x, y) - mouse_origin; + const float x_sensitivity = Settings::values.mouse_panning_x_sensitivity.GetValue() * default_stick_sensitivity; + const float y_sensitivity = Settings::values.mouse_panning_y_sensitivity.GetValue() * default_stick_sensitivity; + SetAxis(identifier, mouse_axis_x, float(mouse_move[0]) * x_sensitivity); + SetAxis(identifier, mouse_axis_y, float(-mouse_move[1]) * y_sensitivity); + last_motion_change = { + float(-mouse_move[1]) * x_sensitivity, + float(-mouse_move[0]) * y_sensitivity, + last_motion_change[2], + }; } - - return; } +} - if (button_pressed) { - const auto mouse_move = Common::MakeVec(x, y) - mouse_origin; - const float x_sensitivity = - Settings::values.mouse_panning_x_sensitivity.GetValue() * default_stick_sensitivity; - const float y_sensitivity = - Settings::values.mouse_panning_y_sensitivity.GetValue() * default_stick_sensitivity; - SetAxis(identifier, mouse_axis_x, static_cast(mouse_move.x) * x_sensitivity); - SetAxis(identifier, mouse_axis_y, static_cast(-mouse_move.y) * y_sensitivity); - - last_motion_change = { - static_cast(-mouse_move.y) * x_sensitivity, - static_cast(-mouse_move.x) * y_sensitivity, - last_motion_change.z, - }; - } +void Mouse::NotifyChanged() { + auto const timestamp = Common::SteadyClock::Now(); + UpdateStickInput(timestamp); + UpdateMotionInput(timestamp); + last_notify_timestamp = timestamp; } void Mouse::MouseMove(f32 touch_x, f32 touch_y) { @@ -224,18 +206,18 @@ void Mouse::ReleaseButton(MouseButton button) { SetAxis(identifier, mouse_axis_y, 0); } - last_motion_change.x = 0; - last_motion_change.y = 0; + last_motion_change[0] = 0; + last_motion_change[1] = 0; button_pressed = false; } void Mouse::MouseWheelChange(int x, int y) { - wheel_position.x += x; - wheel_position.y += y; - last_motion_change.z += static_cast(y); - SetAxis(identifier, wheel_axis_x, static_cast(wheel_position.x)); - SetAxis(identifier, wheel_axis_y, static_cast(wheel_position.y)); + wheel_position[0] += x; + wheel_position[1] += y; + last_motion_change[2] += static_cast(y); + SetAxis(identifier, wheel_axis_x, f32(wheel_position[0])); + SetAxis(identifier, wheel_axis_y, f32(wheel_position[1])); } void Mouse::ReleaseAllButtons() { diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h index 2e5e989067..1f7cb7e966 100644 --- a/src/input_common/drivers/mouse.h +++ b/src/input_common/drivers/mouse.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -7,7 +7,9 @@ #pragma once #include +#include +#include "common/steady_clock.h" #include "common/polyfill_thread.h" #include "common/vector_math.h" #include "input_common/input_engine.h" @@ -93,24 +95,27 @@ public: void ReleaseAllButtons(); + /// @brief Notifies we changed something about the mouse state and we must update + void NotifyChanged(); + std::vector GetInputDevices() const override; AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; private: - void UpdateStickInput(); - void UpdateMotionInput(); + void UpdateStickInput(Common::SteadyClock::time_point timestamp); + void UpdateMotionInput(Common::SteadyClock::time_point timestamp); bool IsMousePanningEnabled(); Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; - Common::Vec2 mouse_origin; - Common::Vec2 last_mouse_position; - Common::Vec2 last_mouse_change; - Common::Vec3 last_motion_change; - Common::Vec2 wheel_position; + Common::Vec mouse_origin; + Common::Vec last_mouse_position; + Common::Vec last_mouse_change; + Common::Vec last_motion_change; + Common::Vec wheel_position; + Common::SteadyClock::time_point last_notify_timestamp{}; bool button_pressed = false; - std::jthread update_thread; }; } // namespace InputCommon diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index 948f650fb1..c3e9a066b3 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -3,6 +3,8 @@ // SPDX-FileCopyrightText: 2018 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include #include "common/logging.h" #include "common/math_util.h" #include "common/param_package.h" @@ -15,29 +17,79 @@ namespace InputCommon { namespace { Common::UUID GetGUID(SDL_Joystick* joystick) { - const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); + const SDL_GUID guid = SDL_GetJoystickGUID(joystick); std::array data{}; std::memcpy(data.data(), guid.data, sizeof(data)); // Clear controller name crc std::memset(data.data() + 2, 0, sizeof(u16)); return Common::UUID{data}; } + +using GamepadBindings = std::vector; + +SDL_GamepadBinding EmptyBinding() { + SDL_GamepadBinding binding{}; + binding.input_type = SDL_GAMEPAD_BINDTYPE_NONE; + return binding; +} + +GamepadBindings GetBindings(SDL_Gamepad* controller) { + if (controller == nullptr) { + return {}; + } + + int binding_count = 0; + SDL_GamepadBinding** bindings = SDL_GetGamepadBindings(controller, &binding_count); + if (bindings == nullptr) { + return {}; + } + + GamepadBindings cached_bindings{}; + cached_bindings.reserve(static_cast(binding_count)); + for (int i = 0; i < binding_count; ++i) { + if (const auto* current = bindings[i]) { + cached_bindings.emplace_back(*current); + } + } + SDL_free(bindings); + return cached_bindings; +} + +template +SDL_GamepadBinding FindBinding(const GamepadBindings& bindings, Predicate matches) { + const auto it = std::find_if(bindings.begin(), bindings.end(), matches); + return it != bindings.end() ? *it : EmptyBinding(); +} + +SDL_GamepadBinding GetBindingForButton(const GamepadBindings& bindings, SDL_GamepadButton button) { + return FindBinding(bindings, [button](const SDL_GamepadBinding& current) { + return current.output_type == SDL_GAMEPAD_BINDTYPE_BUTTON && + current.output.button == static_cast(button); + }); +} + +SDL_GamepadBinding GetBindingForAxis(const GamepadBindings& bindings, SDL_GamepadAxis axis) { + return FindBinding(bindings, [axis](const SDL_GamepadBinding& current) { + return current.output_type == SDL_GAMEPAD_BINDTYPE_AXIS && + current.output.axis.axis == static_cast(axis); + }); +} } // Anonymous namespace -static int SDLEventWatcher(void* user_data, SDL_Event* event) { +static bool SDLEventWatcher(void* user_data, SDL_Event* event) { auto* const sdl_state = static_cast(user_data); sdl_state->HandleGameControllerEvent(*event); - return 0; + return true; } class SDLJoystick { public: SDLJoystick(Common::UUID guid_, int port_, SDL_Joystick* joystick, - SDL_GameController* game_controller) - : guid{guid_}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, - sdl_controller{game_controller, &SDL_GameControllerClose} { + SDL_Gamepad* game_controller) + : guid{guid_}, port{port_}, sdl_joystick{joystick, &SDL_CloseJoystick}, + sdl_controller{game_controller, &SDL_CloseGamepad} { EnableMotion(); } @@ -45,30 +97,49 @@ public: if (!sdl_controller) { return; } - SDL_GameController* controller = sdl_controller.get(); + SDL_Gamepad* controller = sdl_controller.get(); if (HasMotion()) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_FALSE); - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_FALSE); + SDL_SetGamepadSensorEnabled(controller, SDL_SENSOR_ACCEL, false); + SDL_SetGamepadSensorEnabled(controller, SDL_SENSOR_GYRO, false); } - has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) == SDL_TRUE; - has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) == SDL_TRUE; + has_accel = SDL_GamepadHasSensor(controller, SDL_SENSOR_ACCEL); + has_gyro = SDL_GamepadHasSensor(controller, SDL_SENSOR_GYRO); if (has_accel) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); + if (!SDL_SetGamepadSensorEnabled(controller, SDL_SENSOR_ACCEL, true)) { + LOG_WARNING(Input, "Failed to enable accelerometer sensor: {}", SDL_GetError()); + } } if (has_gyro) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); + if (!SDL_SetGamepadSensorEnabled(controller, SDL_SENSOR_GYRO, true)) { + LOG_WARNING(Input, "Failed to enable gyroscope sensor: {}", SDL_GetError()); + } } + + LOG_INFO(Input, "Controller motion capabilities: accel={} gyro={}", has_accel, has_gyro); } bool HasMotion() const { return has_gyro || has_accel; } - bool UpdateMotion(SDL_ControllerSensorEvent event) { + bool UpdateMotion(SDL_GamepadSensorEvent event) { constexpr float gravity_constant = 9.80665f; std::scoped_lock lock{mutex}; - const u64 time_difference = event.timestamp - last_motion_update; - last_motion_update = event.timestamp; + const u64 sensor_timestamp = event.sensor_timestamp != 0 ? event.sensor_timestamp + : event.timestamp; + + if (last_motion_update == 0) { + last_motion_update = sensor_timestamp; + return false; + } + if (sensor_timestamp < last_motion_update) { + return false; + } + + // SDL3 reports sensor timestamps in nanoseconds, while the input stack expects + // delta timestamps in microseconds. + const u64 time_difference = (sensor_timestamp - last_motion_update) / 1000; + last_motion_update = sensor_timestamp; switch (event.sensor) { case SDL_SENSOR_ACCEL: { motion.accel_x = -event.data[0] / gravity_constant; @@ -77,9 +148,9 @@ public: break; } case SDL_SENSOR_GYRO: { - motion.gyro_x = event.data[0] / (Common::PI * 2); - motion.gyro_y = -event.data[2] / (Common::PI * 2); - motion.gyro_z = event.data[1] / (Common::PI * 2); + motion.gyro_x = event.data[0] / (f32(std::numbers::pi_v) * 2); + motion.gyro_y = -event.data[2] / (f32(std::numbers::pi_v) * 2); + motion.gyro_z = event.data[1] / (f32(std::numbers::pi_v) * 2); break; } } @@ -102,7 +173,7 @@ public: } motion_error_count = 0; - motion.delta_timestamp = time_difference * 1000; + motion.delta_timestamp = time_difference; return true; } @@ -136,26 +207,41 @@ public: f32 high_amplitude = vibration.high_amplitude * high_frequency_scale; if (sdl_controller) { - return SDL_GameControllerRumble(sdl_controller.get(), static_cast(low_amplitude), - static_cast(high_amplitude), - rumble_max_duration_ms) != -1; + return SDL_RumbleGamepad(sdl_controller.get(), static_cast(low_amplitude), + static_cast(high_amplitude), rumble_max_duration_ms); } else if (sdl_joystick) { - return SDL_JoystickRumble(sdl_joystick.get(), static_cast(low_amplitude), + return SDL_RumbleJoystick(sdl_joystick.get(), static_cast(low_amplitude), static_cast(high_amplitude), - rumble_max_duration_ms) != -1; + rumble_max_duration_ms); } return false; } bool HasHDRumble() const { + constexpr Uint16 valve_vendor_id = 0x28DE; + const auto is_known_hd_type = [](SDL_GamepadType type) { + return type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO || + type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT || + type == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT || + type == SDL_GAMEPAD_TYPE_PS5; + }; + + // Valve hardware doesn't have any enums in SDL, so we have to support it manually. + // Since they have HD rumble, we can assume that all their hardware supports it, even if we can't detect the exact type. if (sdl_controller) { - const auto type = SDL_GameControllerGetType(sdl_controller.get()); - return (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) || - (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT) || - (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) || - (type == SDL_CONTROLLER_TYPE_PS5); + if (is_known_hd_type(SDL_GetGamepadType(sdl_controller.get())) || + SDL_GetGamepadVendor(sdl_controller.get()) == valve_vendor_id) { + return true; + } } + + if (sdl_joystick) { + if (SDL_GetJoystickVendor(sdl_joystick.get()) == valve_vendor_id) { + return true; + } + } + return false; } @@ -201,11 +287,11 @@ public: return sdl_joystick.get(); } - SDL_GameController* GetSDLGameController() const { + SDL_Gamepad* GetSDLGameController() const { return sdl_controller.get(); } - void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) { + void SetSDLJoystick(SDL_Joystick* joystick, SDL_Gamepad* controller) { sdl_joystick.reset(joystick); sdl_controller.reset(controller); } @@ -232,20 +318,36 @@ public: return false; } - Common::Input::BatteryLevel GetBatteryLevel(SDL_JoystickPowerLevel battery_level) { - switch (battery_level) { - case SDL_JOYSTICK_POWER_EMPTY: - return Common::Input::BatteryLevel::Empty; - case SDL_JOYSTICK_POWER_LOW: - return Common::Input::BatteryLevel::Low; - case SDL_JOYSTICK_POWER_MEDIUM: - return Common::Input::BatteryLevel::Medium; - case SDL_JOYSTICK_POWER_FULL: - case SDL_JOYSTICK_POWER_MAX: - return Common::Input::BatteryLevel::Full; - case SDL_JOYSTICK_POWER_WIRED: + Common::Input::BatteryLevel GetBatteryLevel(SDL_PowerState battery_level, int percent) { + if (battery_level == SDL_POWERSTATE_CHARGING) { return Common::Input::BatteryLevel::Charging; - case SDL_JOYSTICK_POWER_UNKNOWN: + } + + if (percent >= 0 && percent <= 100) { + if (percent <= 5) { + return Common::Input::BatteryLevel::Empty; + } + if (percent <= 20) { + return Common::Input::BatteryLevel::Critical; + } + if (percent <= 40) { + return Common::Input::BatteryLevel::Low; + } + if (percent <= 70) { + return Common::Input::BatteryLevel::Medium; + } + return Common::Input::BatteryLevel::Full; + } + + switch (battery_level) { + case SDL_POWERSTATE_ON_BATTERY: + return Common::Input::BatteryLevel::Medium; + case SDL_POWERSTATE_NO_BATTERY: + return Common::Input::BatteryLevel::None; + case SDL_POWERSTATE_CHARGED: + return Common::Input::BatteryLevel::Full; + case SDL_POWERSTATE_ERROR: + case SDL_POWERSTATE_UNKNOWN: default: return Common::Input::BatteryLevel::None; } @@ -253,28 +355,28 @@ public: std::string GetControllerName() const { if (sdl_controller) { - switch (SDL_GameControllerGetType(sdl_controller.get())) { - case SDL_CONTROLLER_TYPE_XBOX360: + switch (SDL_GetGamepadType(sdl_controller.get())) { + case SDL_GAMEPAD_TYPE_XBOX360: return "Xbox 360 Controller"; - case SDL_CONTROLLER_TYPE_XBOXONE: + case SDL_GAMEPAD_TYPE_XBOXONE: return "Xbox One Controller"; - case SDL_CONTROLLER_TYPE_PS3: + case SDL_GAMEPAD_TYPE_PS3: return "DualShock 3 Controller"; - case SDL_CONTROLLER_TYPE_PS4: + case SDL_GAMEPAD_TYPE_PS4: return "DualShock 4 Controller"; - case SDL_CONTROLLER_TYPE_PS5: + case SDL_GAMEPAD_TYPE_PS5: return "DualSense Controller"; default: break; } - const auto name = SDL_GameControllerName(sdl_controller.get()); + const auto name = SDL_GetGamepadName(sdl_controller.get()); if (name) { return name; } } if (sdl_joystick) { - const auto name = SDL_JoystickName(sdl_joystick.get()); + const auto name = SDL_GetJoystickName(sdl_joystick.get()); if (name) { return name; } @@ -286,8 +388,8 @@ public: private: Common::UUID guid; int port; - std::unique_ptr sdl_joystick; - std::unique_ptr sdl_controller; + std::unique_ptr sdl_joystick; + std::unique_ptr sdl_controller; mutable std::mutex mutex; u64 last_motion_update{}; @@ -323,7 +425,10 @@ std::shared_ptr SDLDriver::GetSDLJoystickByGUID(const std::string& } std::shared_ptr SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { - auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); + auto sdl_joystick = SDL_GetJoystickFromID(sdl_id); + if (sdl_joystick == nullptr) { + return nullptr; + } const auto guid = GetGUID(sdl_joystick); std::scoped_lock lock{joystick_map_mutex}; @@ -345,34 +450,70 @@ std::shared_ptr SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl return *vec_it; } -void SDLDriver::InitJoystick(int joystick_index) { - SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); - SDL_GameController* sdl_gamecontroller = nullptr; +std::shared_ptr SDLDriver::GetSDLJoystickByGamepadID(SDL_JoystickID sdl_id) { + auto* const sdl_gamepad = SDL_GetGamepadFromID(sdl_id); + if (sdl_gamepad == nullptr) { + return nullptr; + } - if (SDL_IsGameController(joystick_index)) { - sdl_gamecontroller = SDL_GameControllerOpen(joystick_index); + auto* const sdl_joystick = SDL_GetGamepadJoystick(sdl_gamepad); + if (sdl_joystick == nullptr) { + return nullptr; + } + + const auto guid = GetGUID(sdl_joystick); + + std::scoped_lock lock{joystick_map_mutex}; + const auto map_it = joystick_map.find(guid); + if (map_it == joystick_map.end()) { + return nullptr; + } + + const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), + [sdl_joystick, sdl_gamepad](const auto& joystick) { + return joystick->GetSDLJoystick() == sdl_joystick || + joystick->GetSDLGameController() == sdl_gamepad; + }); + + if (vec_it == map_it->second.end()) { + return nullptr; + } + + return *vec_it; +} + +void SDLDriver::InitJoystick(SDL_JoystickID joystick_id) { + SDL_Joystick* sdl_joystick = SDL_OpenJoystick(joystick_id); + SDL_Gamepad* sdl_gamecontroller = nullptr; + int battery_percent = -1; + SDL_PowerState battery_state = SDL_POWERSTATE_UNKNOWN; + + if (SDL_IsGamepad(joystick_id)) { + sdl_gamecontroller = SDL_OpenGamepad(joystick_id); } if (!sdl_joystick) { - LOG_ERROR(Input, "Failed to open joystick {}", joystick_index); + LOG_ERROR(Input, "Failed to open joystick {}", joystick_id); return; } + battery_state = SDL_GetJoystickPowerInfo(sdl_joystick, &battery_percent); + const auto guid = GetGUID(sdl_joystick); if (Settings::values.enable_joycon_driver) { if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e && (guid.uuid[8] == 0x06 || guid.uuid[8] == 0x07)) { - LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index); - SDL_JoystickClose(sdl_joystick); + LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_id); + SDL_CloseJoystick(sdl_joystick); return; } } if (Settings::values.enable_procon_driver) { if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e && guid.uuid[8] == 0x09) { - LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index); - SDL_JoystickClose(sdl_joystick); + LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_id); + SDL_CloseJoystick(sdl_joystick); return; } } @@ -382,6 +523,8 @@ void SDLDriver::InitJoystick(int joystick_index) { auto joystick = std::make_shared(guid, 0, sdl_joystick, sdl_gamecontroller); PreSetController(joystick->GetPadIdentifier()); joystick->EnableMotion(); + SetBattery(joystick->GetPadIdentifier(), + joystick->GetBatteryLevel(battery_state, battery_percent)); joystick_map[guid].emplace_back(std::move(joystick)); return; } @@ -394,6 +537,8 @@ void SDLDriver::InitJoystick(int joystick_index) { if (joystick_it != joystick_guid_list.end()) { (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); (*joystick_it)->EnableMotion(); + SetBattery((*joystick_it)->GetPadIdentifier(), + (*joystick_it)->GetBatteryLevel(battery_state, battery_percent)); return; } @@ -401,6 +546,8 @@ void SDLDriver::InitJoystick(int joystick_index) { auto joystick = std::make_shared(guid, port, sdl_joystick, sdl_gamecontroller); PreSetController(joystick->GetPadIdentifier()); joystick->EnableMotion(); + SetBattery(joystick->GetPadIdentifier(), + joystick->GetBatteryLevel(battery_state, battery_percent)); joystick_guid_list.emplace_back(std::move(joystick)); } @@ -428,55 +575,60 @@ void SDLDriver::PumpEvents() const { void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) { switch (event.type) { - case SDL_JOYBUTTONUP: { + case SDL_EVENT_JOYSTICK_BUTTON_UP: { if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { const PadIdentifier identifier = joystick->GetPadIdentifier(); SetButton(identifier, event.jbutton.button, false); } break; } - case SDL_JOYBUTTONDOWN: { + case SDL_EVENT_JOYSTICK_BUTTON_DOWN: { if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { const PadIdentifier identifier = joystick->GetPadIdentifier(); SetButton(identifier, event.jbutton.button, true); } break; } - case SDL_JOYHATMOTION: { + case SDL_EVENT_JOYSTICK_HAT_MOTION: { if (const auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) { const PadIdentifier identifier = joystick->GetPadIdentifier(); SetHatButton(identifier, event.jhat.hat, event.jhat.value); } break; } - case SDL_JOYAXISMOTION: { + case SDL_EVENT_JOYSTICK_AXIS_MOTION: { if (const auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) { const PadIdentifier identifier = joystick->GetPadIdentifier(); SetAxis(identifier, event.jaxis.axis, event.jaxis.value / 32767.0f); } break; } - case SDL_CONTROLLERSENSORUPDATE: { - if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) { - if (joystick->UpdateMotion(event.csensor)) { + case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: { + auto joystick = GetSDLJoystickByGamepadID(event.gsensor.which); + if (!joystick) { + joystick = GetSDLJoystickBySDLID(event.gsensor.which); + } + if (joystick) { + if (joystick->UpdateMotion(event.gsensor)) { const PadIdentifier identifier = joystick->GetPadIdentifier(); SetMotion(identifier, 0, joystick->GetMotion()); } } break; } - case SDL_JOYBATTERYUPDATED: { + case SDL_EVENT_JOYSTICK_BATTERY_UPDATED: { if (auto joystick = GetSDLJoystickBySDLID(event.jbattery.which)) { const PadIdentifier identifier = joystick->GetPadIdentifier(); - SetBattery(identifier, joystick->GetBatteryLevel(event.jbattery.level)); + SetBattery(identifier, + joystick->GetBatteryLevel(event.jbattery.state, event.jbattery.percent)); } break; } - case SDL_JOYDEVICEREMOVED: + case SDL_EVENT_JOYSTICK_REMOVED: LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); - CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); + CloseJoystick(SDL_GetJoystickFromID(event.jdevice.which)); break; - case SDL_JOYDEVICEADDED: + case SDL_EVENT_JOYSTICK_ADDED: LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); InitJoystick(event.jdevice.which); break; @@ -496,12 +648,20 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en // Disable raw input. When enabled this setting causes SDL to die when a web applet opens SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, Settings::values.enable_raw_input ? "1" : "0"); - // Prevent SDL from adding undesired axis - SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); +#ifdef _WIN32 + if (Settings::values.disable_wgi_xinput) { + SDL_SetHintWithPriority(SDL_HINT_JOYSTICK_RAWINPUT_CORRELATE_XINPUT, "0", SDL_HINT_OVERRIDE); + SDL_SetHintWithPriority(SDL_HINT_JOYSTICK_WGI, "0", SDL_HINT_OVERRIDE); + } +#endif + + // SDL3 defaults Steam Controller Bluetooth HIDAPI support to off, which can disable gyro. + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1"); + SDL_SetHint(SDL_HINT_GAMECONTROLLER_SENSOR_FUSION, "1"); + SDL_SetHint(SDL_HINT_AUTO_UPDATE_SENSORS, "1"); // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); // Disable hidapi drivers for joycon controllers when the custom joycon driver is enabled @@ -523,16 +683,13 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en } SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, "1"); - // Share the same button mapping with non-Nintendo controllers - SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0"); - // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native // driver on Linux. SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0"); // If the frontend is going to manage the event loop, then we don't start one here - start_thread = SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) == 0; - if (start_thread && SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) { + start_thread = SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD) == 0; + if (start_thread && !SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) { LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError()); return; } @@ -540,37 +697,31 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en SDL_AddEventWatch(&SDLEventWatcher, this); initialized = true; - if (start_thread) { - vibration_thread = std::thread([this] { - Common::SetCurrentThreadName("SDL_Vibration"); - using namespace std::chrono_literals; - while (initialized) { - SendVibrations(); - std::this_thread::sleep_for(250ms); // 4 TPS - } - }); - } // Because the events for joystick connection happens before we have our event watcher added, we // can just open all the joysticks right here - for (int i = 0; i < SDL_NumJoysticks(); ++i) { - InitJoystick(i); + int joystick_count = 0; + SDL_JoystickID* joysticks = SDL_GetJoysticks(&joystick_count); + if (joysticks != nullptr) { + for (int i = 0; i < joystick_count; ++i) { + InitJoystick(joysticks[i]); + } + SDL_free(joysticks); } } SDLDriver::~SDLDriver() { CloseJoysticks(); - SDL_DelEventWatch(&SDLEventWatcher, this); + SDL_RemoveEventWatch(&SDLEventWatcher, this); initialized = false; if (start_thread) { - vibration_thread.join(); - SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD); } } std::vector SDLDriver::GetInputDevices() const { std::vector devices; - ankerl::unordered_dense::map> joycon_pairs; + ::Common::unordered_map> joycon_pairs; for (const auto& [key, value] : joystick_map) { for (const auto& joystick : value) { if (!joystick->GetSDLJoystick()) { @@ -643,10 +794,7 @@ Common::Input::DriverResult SDLDriver::SetVibration( .type = Common::Input::VibrationAmplificationType::Exponential, }; - vibration_queue.Push(VibrationRequest{ - .identifier = identifier, - .vibration = new_vibration, - }); + joystick->RumblePlay(new_vibration); return Common::Input::DriverResult::Success; } @@ -690,31 +838,6 @@ bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) { return true; } -void SDLDriver::SendVibrations() { - std::vector filtered_vibrations{}; - while (!vibration_queue.Empty()) { - VibrationRequest request; - vibration_queue.Pop(request); - const auto joystick = GetSDLJoystickByGUID(request.identifier.guid.RawString(), - static_cast(request.identifier.port)); - const auto it = std::find_if(filtered_vibrations.begin(), filtered_vibrations.end(), - [request](VibrationRequest vibration) { - return vibration.identifier == request.identifier; - }); - if (it == filtered_vibrations.end()) { - filtered_vibrations.push_back(std::move(request)); - continue; - } - *it = request; - } - - for (const auto& vibration : filtered_vibrations) { - const auto joystick = GetSDLJoystickByGUID(vibration.identifier.guid.RawString(), - static_cast(vibration.identifier.port)); - joystick->RumblePlay(vibration.vibration); - } -} - Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, const Common::UUID& guid, s32 axis, float value) const { Common::ParamPackage params{}; @@ -758,17 +881,17 @@ Common::ParamPackage SDLDriver::BuildMotionParam(int port, const Common::UUID& g } Common::ParamPackage SDLDriver::BuildParamPackageForBinding( - int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const { - switch (binding.bindType) { - case SDL_CONTROLLER_BINDTYPE_NONE: + int port, const Common::UUID& guid, const SDL_GamepadBinding& binding) const { + switch (binding.input_type) { + case SDL_GAMEPAD_BINDTYPE_NONE: break; - case SDL_CONTROLLER_BINDTYPE_AXIS: - return BuildAnalogParamPackageForButton(port, guid, binding.value.axis); - case SDL_CONTROLLER_BINDTYPE_BUTTON: - return BuildButtonParamPackageForButton(port, guid, binding.value.button); - case SDL_CONTROLLER_BINDTYPE_HAT: - return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat, - static_cast(binding.value.hat.hat_mask)); + case SDL_GAMEPAD_BINDTYPE_AXIS: + return BuildAnalogParamPackageForButton(port, guid, binding.input.axis.axis); + case SDL_GAMEPAD_BINDTYPE_BUTTON: + return BuildButtonParamPackageForButton(port, guid, binding.input.button); + case SDL_GAMEPAD_BINDTYPE_HAT: + return BuildHatParamPackageForButton(port, guid, binding.input.hat.hat, + static_cast(binding.input.hat.hat_mask)); } return {}; } @@ -795,74 +918,79 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p } const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); - auto* controller = joystick->GetSDLGameController(); - if (controller == nullptr) { - return {}; - } - // This list is missing ZL/ZR since those are not considered buttons in SDL GameController. // We will add those afterwards - ButtonBindings switch_to_sdl_button; - - switch_to_sdl_button = GetDefaultButtonBinding(joystick); - + ButtonBindings switch_to_sdl_button = GetDefaultButtonBinding(joystick); // Add the missing bindings for ZL/ZR static constexpr ZButtonBindings switch_to_sdl_axis{{ - {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT}, - {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT}, + {Settings::NativeButton::ZL, SDL_GAMEPAD_AXIS_LEFT_TRIGGER}, + {Settings::NativeButton::ZR, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER}, }}; - - // Parameters contain two joysticks return dual - if (params.Has("guid2")) { - const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); - - if (joystick2->GetSDLGameController() != nullptr) { - return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button, - switch_to_sdl_axis); + if (auto* controller = joystick->GetSDLGameController(); controller) { + // Parameters contain two joysticks return dual + if (params.Has("guid2")) { + const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); + if (joystick2->GetSDLGameController()) + return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button, switch_to_sdl_axis); } + return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis); } - - return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis); + // Default mappings for when the controller doesn't have an associated gamepad + // This fixes issues where SDL uses a backend which doesn't support gamepad remappings + ButtonMapping mapping; + for (const auto& pair : switch_to_sdl_button) { + SDL_GamepadBinding binding{}; + binding.input_type = SDL_GAMEPAD_BINDTYPE_BUTTON; + binding.input.button = pair.second; + mapping.insert_or_assign(pair.first, BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + } + for (const auto& pair : switch_to_sdl_axis) { + SDL_GamepadBinding binding{}; + binding.input_type = SDL_GAMEPAD_BINDTYPE_AXIS; + binding.input.axis.axis = pair.second; + mapping.insert_or_assign(pair.first, BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + } + return mapping; } ButtonBindings SDLDriver::GetDefaultButtonBinding( const std::shared_ptr& joystick) const { // Default SL/SR mapping for other controllers - auto sll_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; - auto srl_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; - auto slr_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; - auto srr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; + auto sll_button = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER; + auto srl_button = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER; + auto slr_button = SDL_GAMEPAD_BUTTON_LEFT_SHOULDER; + auto srr_button = SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER; if (joystick->IsJoyconLeft()) { - sll_button = SDL_CONTROLLER_BUTTON_PADDLE2; - srl_button = SDL_CONTROLLER_BUTTON_PADDLE4; + sll_button = SDL_GAMEPAD_BUTTON_LEFT_PADDLE1; + srl_button = SDL_GAMEPAD_BUTTON_LEFT_PADDLE2; } if (joystick->IsJoyconRight()) { - slr_button = SDL_CONTROLLER_BUTTON_PADDLE3; - srr_button = SDL_CONTROLLER_BUTTON_PADDLE1; + slr_button = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2; + srr_button = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1; } return { - std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B}, - {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A}, - {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y}, - {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X}, - {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, - {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, - {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, - {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START}, - {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK}, - {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, - {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, - {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, - {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, + std::pair{Settings::NativeButton::A, SDL_GAMEPAD_BUTTON_EAST}, + {Settings::NativeButton::B, SDL_GAMEPAD_BUTTON_SOUTH}, + {Settings::NativeButton::X, SDL_GAMEPAD_BUTTON_NORTH}, + {Settings::NativeButton::Y, SDL_GAMEPAD_BUTTON_WEST}, + {Settings::NativeButton::LStick, SDL_GAMEPAD_BUTTON_LEFT_STICK}, + {Settings::NativeButton::RStick, SDL_GAMEPAD_BUTTON_RIGHT_STICK}, + {Settings::NativeButton::L, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER}, + {Settings::NativeButton::R, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER}, + {Settings::NativeButton::Plus, SDL_GAMEPAD_BUTTON_START}, + {Settings::NativeButton::Minus, SDL_GAMEPAD_BUTTON_BACK}, + {Settings::NativeButton::DLeft, SDL_GAMEPAD_BUTTON_DPAD_LEFT}, + {Settings::NativeButton::DUp, SDL_GAMEPAD_BUTTON_DPAD_UP}, + {Settings::NativeButton::DRight, SDL_GAMEPAD_BUTTON_DPAD_RIGHT}, + {Settings::NativeButton::DDown, SDL_GAMEPAD_BUTTON_DPAD_DOWN}, {Settings::NativeButton::SLLeft, sll_button}, {Settings::NativeButton::SRLeft, srl_button}, {Settings::NativeButton::SLRight, slr_button}, {Settings::NativeButton::SRRight, srr_button}, - {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, - {Settings::NativeButton::Screenshot, SDL_CONTROLLER_BUTTON_MISC1}, + {Settings::NativeButton::Home, SDL_GAMEPAD_BUTTON_GUIDE}, + {Settings::NativeButton::Screenshot, SDL_GAMEPAD_BUTTON_MISC1}, }; } @@ -872,15 +1000,16 @@ ButtonMapping SDLDriver::GetSingleControllerMapping( ButtonMapping mapping; mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); auto* controller = joystick->GetSDLGameController(); + const auto bindings = GetBindings(controller); for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { - const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); + const auto binding = GetBindingForButton(bindings, sdl_button); mapping.insert_or_assign( switch_button, BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); } for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { - const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); + const auto binding = GetBindingForAxis(bindings, sdl_axis); mapping.insert_or_assign( switch_button, BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); @@ -897,29 +1026,31 @@ ButtonMapping SDLDriver::GetDualControllerMapping(const std::shared_ptrGetSDLGameController(); auto* controller2 = joystick2->GetSDLGameController(); + const auto bindings = GetBindings(controller); + const auto bindings2 = GetBindings(controller2); for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { if (IsButtonOnLeftSide(switch_button)) { - const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button); + const auto binding = GetBindingForButton(bindings2, sdl_button); mapping.insert_or_assign( switch_button, BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); continue; } - const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); + const auto binding = GetBindingForButton(bindings, sdl_button); mapping.insert_or_assign( switch_button, BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); } for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { if (IsButtonOnLeftSide(switch_button)) { - const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis); + const auto binding = GetBindingForAxis(bindings2, sdl_axis); mapping.insert_or_assign( switch_button, BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); continue; } - const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); + const auto binding = GetBindingForAxis(bindings, sdl_axis); mapping.insert_or_assign( switch_button, BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); @@ -957,46 +1088,43 @@ AnalogMapping SDLDriver::GetAnalogMappingForDevice(const Common::ParamPackage& p } AnalogMapping mapping = {}; - const auto& binding_left_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); - const auto& binding_left_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); + const auto bindings = GetBindings(controller); + const auto binding_left_x = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_LEFTX); + const auto binding_left_y = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_LEFTY); if (params.Has("guid2")) { const auto identifier = joystick2->GetPadIdentifier(); PreSetController(identifier); - PreSetAxis(identifier, binding_left_x.value.axis); - PreSetAxis(identifier, binding_left_y.value.axis); - const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis); - const auto left_offset_y = GetAxis(identifier, binding_left_y.value.axis); + PreSetAxis(identifier, binding_left_x.input.axis.axis); + PreSetAxis(identifier, binding_left_y.input.axis.axis); + const auto left_offset_x = -GetAxis(identifier, binding_left_x.input.axis.axis); + const auto left_offset_y = GetAxis(identifier, binding_left_y.input.axis.axis); mapping.insert_or_assign(Settings::NativeAnalog::LStick, - BuildParamPackageForAnalog(identifier, binding_left_x.value.axis, - binding_left_y.value.axis, + BuildParamPackageForAnalog(identifier, binding_left_x.input.axis.axis, + binding_left_y.input.axis.axis, left_offset_x, left_offset_y)); } else { const auto identifier = joystick->GetPadIdentifier(); PreSetController(identifier); - PreSetAxis(identifier, binding_left_x.value.axis); - PreSetAxis(identifier, binding_left_y.value.axis); - const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis); - const auto left_offset_y = GetAxis(identifier, binding_left_y.value.axis); + PreSetAxis(identifier, binding_left_x.input.axis.axis); + PreSetAxis(identifier, binding_left_y.input.axis.axis); + const auto left_offset_x = -GetAxis(identifier, binding_left_x.input.axis.axis); + const auto left_offset_y = GetAxis(identifier, binding_left_y.input.axis.axis); mapping.insert_or_assign(Settings::NativeAnalog::LStick, - BuildParamPackageForAnalog(identifier, binding_left_x.value.axis, - binding_left_y.value.axis, + BuildParamPackageForAnalog(identifier, binding_left_x.input.axis.axis, + binding_left_y.input.axis.axis, left_offset_x, left_offset_y)); } - const auto& binding_right_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); - const auto& binding_right_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); + const auto binding_right_x = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_RIGHTX); + const auto binding_right_y = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_RIGHTY); const auto identifier = joystick->GetPadIdentifier(); PreSetController(identifier); - PreSetAxis(identifier, binding_right_x.value.axis); - PreSetAxis(identifier, binding_right_y.value.axis); - const auto right_offset_x = -GetAxis(identifier, binding_right_x.value.axis); - const auto right_offset_y = GetAxis(identifier, binding_right_y.value.axis); + PreSetAxis(identifier, binding_right_x.input.axis.axis); + PreSetAxis(identifier, binding_right_y.input.axis.axis); + const auto right_offset_x = -GetAxis(identifier, binding_right_x.input.axis.axis); + const auto right_offset_y = GetAxis(identifier, binding_right_y.input.axis.axis); mapping.insert_or_assign(Settings::NativeAnalog::RStick, - BuildParamPackageForAnalog(identifier, binding_right_x.value.axis, - binding_right_y.value.axis, right_offset_x, + BuildParamPackageForAnalog(identifier, binding_right_x.input.axis.axis, + binding_right_y.input.axis.axis, right_offset_x, right_offset_y)); return mapping; } @@ -1102,19 +1230,16 @@ bool SDLDriver::IsStickInverted(const Common::ParamPackage& params) { const auto& axis_x = params.Get("axis_x", 0); const auto& axis_y = params.Get("axis_y", 0); - const auto& binding_left_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); - const auto& binding_right_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); - const auto& binding_left_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); - const auto& binding_right_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); + const auto bindings = GetBindings(controller); + const auto binding_left_x = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_LEFTX); + const auto binding_right_x = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_RIGHTX); + const auto binding_left_y = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_LEFTY); + const auto binding_right_y = GetBindingForAxis(bindings, SDL_GAMEPAD_AXIS_RIGHTY); - if (axis_x != binding_left_y.value.axis && axis_x != binding_right_y.value.axis) { + if (axis_x != binding_left_y.input.axis.axis && axis_x != binding_right_y.input.axis.axis) { return false; } - if (axis_y != binding_left_x.value.axis && axis_y != binding_right_x.value.axis) { + if (axis_y != binding_left_x.input.axis.axis && axis_y != binding_right_x.input.axis.axis) { return false; } return true; diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index 1677ab8289..e6751b6688 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h @@ -9,27 +9,24 @@ #include #include #include -#include +#include "common/container/unordered_map.h" -#include +#include #include "common/common_types.h" #include "common/threadsafe_queue.h" #include "input_common/input_engine.h" union SDL_Event; -using SDL_GameController = struct _SDL_GameController; -using SDL_Joystick = struct _SDL_Joystick; -using SDL_JoystickID = s32; namespace InputCommon { class SDLJoystick; using ButtonBindings = - std::array, 20>; + std::array, 20>; using ZButtonBindings = - std::array, 2>; + std::array, 2>; class SDLDriver : public InputEngine { public: @@ -46,6 +43,7 @@ public: /// Get the nth joystick with the corresponding GUID std::shared_ptr GetSDLJoystickBySDLID(SDL_JoystickID sdl_id); + std::shared_ptr GetSDLJoystickByGamepadID(SDL_JoystickID sdl_id); /** * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so @@ -72,7 +70,7 @@ public: bool IsVibrationEnabled(const PadIdentifier& identifier) override; private: - void InitJoystick(int joystick_index); + void InitJoystick(SDL_JoystickID joystick_id); void CloseJoystick(SDL_Joystick* sdl_joystick); /// Needs to be called before SDL_QuitSubSystem. @@ -92,7 +90,7 @@ private: Common::ParamPackage BuildMotionParam(int port, const Common::UUID& guid) const; Common::ParamPackage BuildParamPackageForBinding( - int port, const Common::UUID& guid, const SDL_GameControllerButtonBind& binding) const; + int port, const Common::UUID& guid, const SDL_GamepadBinding& binding) const; Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x, int axis_y, float offset_x, @@ -115,16 +113,11 @@ private: /// Returns true if the button is on the left joycon bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const; - /// Queue of vibration request to controllers - Common::SPSCQueue vibration_queue; - /// Map of GUID of a list of corresponding virtual Joysticks - ankerl::unordered_dense::map>> joystick_map; + ::Common::unordered_map>> joystick_map; std::mutex joystick_map_mutex; - bool start_thread = false; std::atomic initialized = false; - - std::thread vibration_thread; + bool start_thread; }; } // namespace InputCommon diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp index fc216e3c9f..620e0cf630 100644 --- a/src/input_common/drivers/udp_client.cpp +++ b/src/input_common/drivers/udp_client.cpp @@ -13,6 +13,7 @@ #include "common/param_package.h" #include "common/random.h" #include "common/settings.h" +#include "common/thread.h" #include "input_common/drivers/udp_client.h" #include "input_common/helpers/udp_protocol.h" @@ -31,8 +32,10 @@ public: using clock = std::chrono::system_clock; explicit Socket(const std::string& host, u16 port, SocketCallback callback_) - : callback(std::move(callback_)), timer(io_context), - socket(io_context, udp::endpoint(udp::v4(), 0)), client_id(Common::Random::Random32(0)) { + : callback(std::move(callback_)), timer(io_context) + , socket(io_context, udp::endpoint(udp::v4(), 0)) + , client_id(Common::Random::Random32(0)) + { boost::system::error_code ec{}; auto ipv4 = boost::asio::ip::make_address_v4(host, ec); if (ec.value() != boost::system::errc::success) { @@ -131,6 +134,7 @@ private: }; static void SocketLoop(Socket* socket) { + Common::SetCurrentThreadName("cemuhookWorker"); socket->StartReceive(); socket->StartSend(Socket::clock::now()); socket->Loop(); @@ -326,9 +330,11 @@ void UDPClient::OnPadData(Response::PadData data, std::size_t client) { } void UDPClient::StartCommunication(std::size_t client, const std::string& host, u16 port) { - SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, - [this](Response::PortInfo info) { OnPortInfo(info); }, - [this, client](Response::PadData data) { OnPadData(data, client); }}; + SocketCallback callback{ + [this](Response::Version version) { OnVersion(version); }, + [this](Response::PortInfo info) { OnPortInfo(info); }, + [this, client](Response::PadData data) { OnPadData(data, client); } + }; LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); clients[client].uuid = GetHostUUID(host); clients[client].host = host; @@ -353,8 +359,13 @@ PadIdentifier UDPClient::GetPadIdentifier(std::size_t pad_index) const { } Common::UUID UDPClient::GetHostUUID(const std::string& host) const { - const auto ip = boost::asio::ip::make_address_v4(host); - const auto hex_host = fmt::format("00000000-0000-0000-0000-0000{:06x}", ip.to_uint()); + boost::system::error_code ec{}; + auto ip = boost::asio::ip::make_address_v4(host, ec); + if (ec.value() != boost::system::errc::success) { + LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided", host); + ip = boost::asio::ip::address_v4{}; + } + auto const hex_host = fmt::format("00000000-0000-0000-0000-0000{:06x}", ip.to_uint()); return Common::UUID{hex_host}; } @@ -566,9 +577,7 @@ bool UDPClient::IsStickInverted(const Common::ParamPackage& params) { return true; } -void TestCommunication(const std::string& host, u16 port, - const std::function& success_callback, - const std::function& failure_callback) { +void TestCommunication(const std::string& host, u16 port, const std::function& success_callback, const std::function& failure_callback) { std::thread([=] { Common::Event success_event; SocketCallback callback{ @@ -601,40 +610,38 @@ CalibrationConfigurationJob::CalibrationConfigurationJob( u16 max_y{}; Status current_status{Status::Initialized}; - SocketCallback callback{[](Response::Version) {}, [](Response::PortInfo) {}, - [&](Response::PadData data) { - constexpr u16 CALIBRATION_THRESHOLD = 100; + SocketCallback callback{[](Response::Version) {}, [](Response::PortInfo) {}, [&](Response::PadData data) { + constexpr u16 CALIBRATION_THRESHOLD = 100; - if (current_status == Status::Initialized) { - // Receiving data means the communication is ready now - current_status = Status::Ready; - status_callback(current_status); - } - if (data.touch[0].is_active == 0) { - return; - } - LOG_DEBUG(Input, "Current touch: {} {}", data.touch[0].x, - data.touch[0].y); - min_x = (std::min)(min_x, static_cast(data.touch[0].x)); - min_y = (std::min)(min_y, static_cast(data.touch[0].y)); - if (current_status == Status::Ready) { - // First touch - min data (min_x/min_y) - current_status = Status::Stage1Completed; - status_callback(current_status); - } - if (data.touch[0].x - min_x > CALIBRATION_THRESHOLD && - data.touch[0].y - min_y > CALIBRATION_THRESHOLD) { - // Set the current position as max value and finishes - // configuration - max_x = data.touch[0].x; - max_y = data.touch[0].y; - current_status = Status::Completed; - data_callback(min_x, min_y, max_x, max_y); - status_callback(current_status); + if (current_status == Status::Initialized) { + // Receiving data means the communication is ready now + current_status = Status::Ready; + status_callback(current_status); + } + if (data.touch[0].is_active == 0) { + return; + } + LOG_DEBUG(Input, "Current touch: {} {}", data.touch[0].x, data.touch[0].y); + min_x = (std::min)(min_x, u16(data.touch[0].x)); + min_y = (std::min)(min_y, u16(data.touch[0].y)); + if (current_status == Status::Ready) { + // First touch - min data (min_x/min_y) + current_status = Status::Stage1Completed; + status_callback(current_status); + } + if (data.touch[0].x - min_x > CALIBRATION_THRESHOLD && + data.touch[0].y - min_y > CALIBRATION_THRESHOLD) { + // Set the current position as max value and finishes + // configuration + max_x = data.touch[0].x; + max_y = data.touch[0].y; + current_status = Status::Completed; + data_callback(min_x, min_y, max_x, max_y); + status_callback(current_status); - complete_event.Set(); - } - }}; + complete_event.Set(); + } + }}; Socket socket{host, port, std::move(callback)}; std::thread worker_thread{SocketLoop, &socket}; complete_event.Wait(); diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h index 792f124e14..8597d02701 100644 --- a/src/input_common/helpers/joycon_protocol/joycon_types.h +++ b/src/input_common/helpers/joycon_protocol/joycon_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -10,7 +13,9 @@ #include #include -#include +#include + +#include #include "common/bit_field.h" #include "common/common_funcs.h" diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp index 2384981f50..07e599f89e 100644 --- a/src/input_common/helpers/joycon_protocol/poller.cpp +++ b/src/input_common/helpers/joycon_protocol/poller.cpp @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "common/logging.h" +#include #include "input_common/helpers/joycon_protocol/poller.h" namespace InputCommon::Joycon { diff --git a/src/input_common/helpers/stick_from_buttons.cpp b/src/input_common/helpers/stick_from_buttons.cpp index a6be6dac1b..576fd0244c 100644 --- a/src/input_common/helpers/stick_from_buttons.cpp +++ b/src/input_common/helpers/stick_from_buttons.cpp @@ -1,8 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 #include #include +#include #include "common/math_util.h" #include "common/settings.h" #include "input_common/helpers/stick_from_buttons.h" @@ -15,9 +19,8 @@ public: // do not play nicely with the theoretical maximum range. // Using a value one lower from the maximum emulates real stick behavior. static constexpr float MAX_RANGE = 32766.0f / 32767.0f; - static constexpr float TAU = Common::PI * 2.0f; // Use wider angle to ease the transition. - static constexpr float APERTURE = TAU * 0.15f; + static constexpr float APERTURE = float((std::numbers::pi_v / 2.f)) * 0.15f; using Button = std::unique_ptr; @@ -66,13 +69,13 @@ public: bool IsAngleGreater(float old_angle, float new_angle) const { const float top_limit = new_angle + APERTURE; return (old_angle > new_angle && old_angle <= top_limit) || - (old_angle + TAU > new_angle && old_angle + TAU <= top_limit); + (old_angle + f32((std::numbers::pi_v / 2.f)) > new_angle && old_angle + f32((std::numbers::pi_v / 2.f)) <= top_limit); } bool IsAngleSmaller(float old_angle, float new_angle) const { const float bottom_limit = new_angle - APERTURE; return (old_angle >= bottom_limit && old_angle < new_angle) || - (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle); + (old_angle - f32((std::numbers::pi_v / 2.f)) >= bottom_limit && old_angle - f32((std::numbers::pi_v / 2.f)) < new_angle); } float GetAngle(std::chrono::time_point now) const { @@ -87,16 +90,16 @@ public: if (IsAngleGreater(new_angle, goal_angle)) { new_angle -= modifier_angle * time_difference; - if (new_angle < 0) { - new_angle += TAU; + if (new_angle < 0.f) { + new_angle += f32((std::numbers::pi_v / 2.f)); } if (!IsAngleGreater(new_angle, goal_angle)) { return goal_angle; } } else if (IsAngleSmaller(new_angle, goal_angle)) { new_angle += modifier_angle * time_difference; - if (new_angle >= TAU) { - new_angle -= TAU; + if (new_angle >= f32((std::numbers::pi_v / 2.f))) { + new_angle -= f32((std::numbers::pi_v / 2.f)); } if (!IsAngleSmaller(new_angle, goal_angle)) { return goal_angle; @@ -108,45 +111,14 @@ public: } void SetGoalAngle(bool r, bool l, bool u, bool d) { - // Move to the right - if (r && !u && !d) { - goal_angle = 0.0f; - } - - // Move to the upper right - if (r && u && !d) { - goal_angle = Common::PI * 0.25f; - } - - // Move up - if (u && !l && !r) { - goal_angle = Common::PI * 0.5f; - } - - // Move to the upper left - if (l && u && !d) { - goal_angle = Common::PI * 0.75f; - } - - // Move to the left - if (l && !u && !d) { - goal_angle = Common::PI; - } - - // Move to the bottom left - if (l && !u && d) { - goal_angle = Common::PI * 1.25f; - } - - // Move down - if (d && !l && !r) { - goal_angle = Common::PI * 1.5f; - } - - // Move to the bottom right - if (r && !u && d) { - goal_angle = Common::PI * 1.75f; - } + if (r && !u && !d) goal_angle = f32(std::numbers::pi_v) * 0.00f; //right + if (r && u && !d) goal_angle = f32(std::numbers::pi_v) * 0.25f; //upper right + if (u && !l && !r) goal_angle = f32(std::numbers::pi_v) * 0.50f; //up + if (l && u && !d) goal_angle = f32(std::numbers::pi_v) * 0.75f; //upper left + if (l && !u && !d) goal_angle = f32(std::numbers::pi_v) * 1.00f; //left + if (l && !u && d) goal_angle = f32(std::numbers::pi_v) * 1.25f; //bottom left + if (d && !l && !r) goal_angle = f32(std::numbers::pi_v) * 1.50f; //down + if (r && !u && d) goal_angle = f32(std::numbers::pi_v) * 1.75f; //bottom right } void UpdateUpButtonStatus(const Common::Input::CallbackStatus& button_callback) { diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp index ccfe3444ae..21d257d088 100644 --- a/src/input_common/input_engine.cpp +++ b/src/input_common/input_engine.cpp @@ -4,6 +4,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging.h" +#include "common/assert.h" #include "input_common/input_engine.h" namespace InputCommon { @@ -463,8 +464,9 @@ const std::string& InputEngine::GetEngineName() const { int InputEngine::SetCallback(InputIdentifier input_identifier) { std::scoped_lock lock{mutex_callback}; + ++last_callback_key; callback_list.insert_or_assign(last_callback_key, std::move(input_identifier)); - return last_callback_key++; + return last_callback_key; } void InputEngine::SetMappingCallback(MappingCallback callback) { @@ -474,12 +476,9 @@ void InputEngine::SetMappingCallback(MappingCallback callback) { void InputEngine::DeleteCallback(int key) { std::scoped_lock lock{mutex_callback}; - const auto& iterator = callback_list.find(key); - if (iterator == callback_list.end()) { - LOG_ERROR(Input, "Tried to delete non-existent callback {}", key); - return; - } - callback_list.erase(iterator); + auto const it = callback_list.find(key); + ASSERT_MSG(it != callback_list.end(), "Tried to delete non-existent callback {}", key); + callback_list.erase(it); } } // namespace InputCommon diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h index 7ad40027e4..3613219f56 100644 --- a/src/input_common/input_engine.h +++ b/src/input_common/input_engine.h @@ -8,7 +8,7 @@ #include #include -#include +#include "common/container/unordered_map.h" #include "common/common_types.h" #include "common/input.h" @@ -270,10 +270,10 @@ protected: private: struct ControllerData { - ankerl::unordered_dense::map buttons; - ankerl::unordered_dense::map hat_buttons; - ankerl::unordered_dense::map axes; - ankerl::unordered_dense::map motions; + ::Common::unordered_map buttons; + ::Common::unordered_map hat_buttons; + ::Common::unordered_map axes; + ::Common::unordered_map motions; Common::Input::BatteryLevel battery{}; Common::Input::BodyColorStatus color{}; Common::Input::CameraStatus camera{}; @@ -301,8 +301,8 @@ private: bool configuring{false}; const std::string input_engine; int last_callback_key = 0; - ankerl::unordered_dense::map controller_list; - ankerl::unordered_dense::map callback_list; + ::Common::unordered_map controller_list; + ::Common::unordered_map callback_list; MappingCallback mapping_callback; }; diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index b04a70590a..6c8443ff62 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2017 Citra Emulator Project @@ -25,12 +25,12 @@ #ifdef ENABLE_LIBUSB #include "input_common/drivers/gc_adapter.h" #endif -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 #include "input_common/drivers/joycon.h" #include "input_common/drivers/sdl_driver.h" #endif -#ifdef ANDROID +#ifdef __ANDROID__ #include "input_common/drivers/android.h" #endif @@ -85,12 +85,12 @@ struct InputSubsystem::Impl { RegisterEngine("cemuhookudp", udp_client); RegisterEngine("tas", tas_input); RegisterEngine("camera", camera); -#ifdef ANDROID +#ifdef __ANDROID__ RegisterEngine("android", android); #endif RegisterEngine("virtual_amiibo", virtual_amiibo); RegisterEngine("virtual_gamepad", virtual_gamepad); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 RegisterEngine("sdl", sdl); RegisterEngine("joycon", joycon); #endif @@ -119,12 +119,12 @@ struct InputSubsystem::Impl { UnregisterEngine(udp_client); UnregisterEngine(tas_input); UnregisterEngine(camera); -#ifdef ANDROID +#ifdef __ANDROID__ UnregisterEngine(android); #endif UnregisterEngine(virtual_amiibo); UnregisterEngine(virtual_gamepad); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 UnregisterEngine(sdl); UnregisterEngine(joycon); #endif @@ -138,13 +138,13 @@ struct InputSubsystem::Impl { Common::ParamPackage{{"display", "Any"}, {"engine", "any"}}, }; -#ifndef ANDROID +#ifndef __ANDROID__ auto keyboard_devices = keyboard->GetInputDevices(); devices.insert(devices.end(), keyboard_devices.begin(), keyboard_devices.end()); auto mouse_devices = mouse->GetInputDevices(); devices.insert(devices.end(), mouse_devices.begin(), mouse_devices.end()); #endif -#ifdef ANDROID +#ifdef __ANDROID__ auto android_devices = android->GetInputDevices(); devices.insert(devices.end(), android_devices.begin(), android_devices.end()); #endif @@ -154,7 +154,7 @@ struct InputSubsystem::Impl { #endif auto udp_devices = udp_client->GetInputDevices(); devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 auto joycon_devices = joycon->GetInputDevices(); devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end()); auto sdl_devices = sdl->GetInputDevices(); @@ -176,7 +176,7 @@ struct InputSubsystem::Impl { if (engine == mouse->GetEngineName()) { return mouse; } -#ifdef ANDROID +#ifdef __ANDROID__ if (engine == android->GetEngineName()) { return android; } @@ -189,7 +189,7 @@ struct InputSubsystem::Impl { if (engine == udp_client->GetEngineName()) { return udp_client; } -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 if (engine == sdl->GetEngineName()) { return sdl; } @@ -261,7 +261,7 @@ struct InputSubsystem::Impl { if (engine == mouse->GetEngineName()) { return true; } -#ifdef ANDROID +#ifdef __ANDROID__ if (engine == android->GetEngineName()) { return true; } @@ -280,7 +280,7 @@ struct InputSubsystem::Impl { if (engine == virtual_gamepad->GetEngineName()) { return true; } -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 if (engine == sdl->GetEngineName()) { return true; } @@ -294,14 +294,14 @@ struct InputSubsystem::Impl { void BeginConfiguration() { keyboard->BeginConfiguration(); mouse->BeginConfiguration(); -#ifdef ANDROID +#ifdef __ANDROID__ android->BeginConfiguration(); #endif #ifdef ENABLE_LIBUSB gcadapter->BeginConfiguration(); #endif udp_client->BeginConfiguration(); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 sdl->BeginConfiguration(); joycon->BeginConfiguration(); #endif @@ -310,14 +310,14 @@ struct InputSubsystem::Impl { void EndConfiguration() { keyboard->EndConfiguration(); mouse->EndConfiguration(); -#ifdef ANDROID +#ifdef __ANDROID__ android->EndConfiguration(); #endif #ifdef ENABLE_LIBUSB gcadapter->EndConfiguration(); #endif udp_client->EndConfiguration(); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 sdl->EndConfiguration(); joycon->EndConfiguration(); #endif @@ -325,7 +325,7 @@ struct InputSubsystem::Impl { void PumpEvents() const { update_engine->PumpEvents(); -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 sdl->PumpEvents(); #endif } @@ -350,12 +350,12 @@ struct InputSubsystem::Impl { std::shared_ptr gcadapter; #endif -#ifdef HAVE_SDL2 +#ifdef HAVE_SDL3 std::shared_ptr sdl; std::shared_ptr joycon; #endif -#ifdef ANDROID +#ifdef __ANDROID__ std::shared_ptr android; #endif }; @@ -412,7 +412,7 @@ const Camera* InputSubsystem::GetCamera() const { return impl->camera.get(); } -#ifdef ANDROID +#ifdef __ANDROID__ Android* InputSubsystem::GetAndroid() { return impl->android.get(); } diff --git a/src/input_common/main.h b/src/input_common/main.h index d0e447ec2d..3b33ef30eb 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -8,7 +8,7 @@ #include #include -#include +#include "common/container/unordered_map.h" #include namespace Common { @@ -56,9 +56,9 @@ enum class InputType { None, Button, Stick, Motion, Touch }; * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default * mapping for the device. */ -using AnalogMapping = ankerl::unordered_dense::map; -using ButtonMapping = ankerl::unordered_dense::map; -using MotionMapping = ankerl::unordered_dense::map; +using AnalogMapping = ::Common::unordered_map; +using ButtonMapping = ::Common::unordered_map; +using MotionMapping = ::Common::unordered_map; class InputSubsystem { public: diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 24b752ccc5..bb9c58e0a8 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later # SPDX-FileCopyrightText: 2022 yuzu Emulator Project @@ -28,6 +28,6 @@ if (ENABLE_WEB_SERVICE) endif() # Solaris uses /lib/amd64/libsocket.so and /lib/amd64/libnsl.so -if (PLATFORM_SUN) +if (SOLARIS) target_link_libraries(network PRIVATE socket nsl) endif() diff --git a/src/qt_common/CMakeLists.txt b/src/qt_common/CMakeLists.txt index 399fbe67a0..8858b819d6 100644 --- a/src/qt_common/CMakeLists.txt +++ b/src/qt_common/CMakeLists.txt @@ -26,14 +26,21 @@ add_library(qt_common STATIC util/compress.h util/compress.cpp util/fs.h util/fs.cpp util/mod.h util/mod.cpp + util/vk.h util/vk.cpp abstract/frontend.h abstract/frontend.cpp abstract/progress.h abstract/progress.cpp + game_list/model.h game_list/model.cpp + game_list/worker.h game_list/worker.cpp + game_list/game_list_p.h + qt_string_lookup.h qt_compat.h - discord/discord.h) + discord/discord.h + render/context.h + render/emu_thread.h render/emu_thread.cpp) if (UNIX) target_sources(qt_common PRIVATE gui_settings.cpp gui_settings.h) @@ -56,9 +63,7 @@ if (USE_DISCORD_PRESENCE) endif() # TODO(crueter) -if (ENABLE_QT) - target_link_libraries(qt_common PRIVATE Qt6::Widgets) -endif() +target_link_libraries(qt_common PRIVATE Qt6::Widgets) target_compile_definitions(qt_common PUBLIC # Use QStringBuilder for string concatenation to reduce @@ -80,7 +85,7 @@ target_compile_definitions(qt_common PUBLIC ) target_link_libraries(qt_common PRIVATE core Qt6::Core Qt6::Concurrent SimpleIni::SimpleIni QuaZip::QuaZip) -target_link_libraries(qt_common PUBLIC frozen::frozen-headers) +target_link_libraries(qt_common PUBLIC frozen::frozen-headers Vulkan::Headers) target_link_libraries(qt_common PRIVATE gamemode::headers frontend_common) if (ENABLE_OPENGL) diff --git a/src/qt_common/abstract/frontend.h b/src/qt_common/abstract/frontend.h index 9b70f6849e..48c7c6edd0 100644 --- a/src/qt_common/abstract/frontend.h +++ b/src/qt_common/abstract/frontend.h @@ -1,8 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef FRONTEND_H -#define FRONTEND_H +#pragma once #include #include "qt_common/qt_common.h" @@ -114,4 +113,3 @@ const QString GetTextInput(const QString& title = QString(), const QString& capt const QString& defaultText = QString()); } // namespace QtCommon::Frontend -#endif // FRONTEND_H diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index 506d74084f..bb92d896f8 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -23,7 +23,7 @@ namespace ConfigurationShared { std::unique_ptr InitializeTranslations(QObject* parent) { std::unique_ptr translations = std::make_unique(); - const auto& tr = [parent](const char* text) -> QString { return parent->tr(text); }; + const auto& tr = [](const char* text) -> QString { return QCoreApplication::translate("ConfigurationShared", text); }; #define INSERT(SETTINGS, ID, NAME, TOOLTIP) \ translations->insert(std::pair{SETTINGS::values.ID.Id(), std::pair{(NAME), (TOOLTIP)}}) @@ -92,12 +92,9 @@ std::unique_ptr InitializeTranslations(QObject* parent) { tr("Change the accuracy of the emulated CPU (for debugging only).")); INSERT(Settings, cpu_backend, tr("Backend:"), QString()); - INSERT(Settings, fast_cpu_time, tr("CPU Overclock"), - tr("Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see " - "reduced performance, " - "and certain games may behave improperly.\nUse Boost (1700MHz) to run at the " - "Switch's highest native " - "clock, or Fast (2000MHz) to run at 2x clock.")); + INSERT(Settings, cpu_clock, tr("CPU Clocks"), + tr("Raises the clock the emulated CPU reports, which removes some FPS limiters.\n" + "Weaker CPUs may see reduced performance, and certain games may behave improperly.")); INSERT(Settings, use_custom_cpu_ticks, QString(), QString()); INSERT(Settings, cpu_ticks, tr("Custom CPU Ticks"), @@ -149,10 +146,14 @@ std::unique_ptr InitializeTranslations(QObject* parent) { "Options lower than 1X can cause artifacts.")); INSERT(Settings, scaling_filter, tr("Window Adapting Filter:"), QString()); INSERT(Settings, fsr_sharpening_slider, tr("FSR Sharpness:"), - tr("Determines how sharpened the image will look using FSR's dynamic contrast.")); + tr("Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast.")); INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"), tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA " "can produce a more stable picture in lower resolutions.")); + INSERT(Settings, post_shader_chain, QString(), QString()); + INSERT(Settings, post_shader_preset, QString(), QString()); + INSERT(Settings, post_shader_enabled, tr("Enable post-processing effects"), + tr("Applies post-processing effects to the final image.")); INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"), tr("The method used to render the window in fullscreen.\nBorderless offers the best " "compatibility with the on-screen keyboard that some games request for " @@ -165,8 +166,9 @@ std::unique_ptr InitializeTranslations(QObject* parent) { INSERT(Settings, use_disk_shader_cache, tr("Use persistent pipeline cache"), tr("Allows saving shaders to storage for faster loading on following game " "boots.\nDisabling it is only intended for debugging.")); - INSERT(Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"), - tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled.")); + INSERT( + Settings, use_asynchronous_gpu_emulation, tr("Use asynchronous GPU emulation"), + tr("Uses an extra CPU thread for rendering.\nThis option should always remain enabled.")); INSERT(Settings, nvdec_emulation, tr("NVDEC emulation:"), tr("Specifies how videos should be decoded.\nIt can either use the CPU or the GPU for " "decoding, or perform no decoding at all (black screen on videos).\n" @@ -219,17 +221,17 @@ std::unique_ptr InitializeTranslations(QObject* parent) { tr("Controls the quality of texture rendering at oblique angles.\nSafe to set at 16x on " "most GPUs.")); INSERT(Settings, gpu_accuracy, tr("GPU Mode:"), - tr("Controls the GPU emulation mode.\nMost games render fine with Fast or Balanced " - "modes, but Accurate is still " + tr("Controls the GPU emulation mode.\nMost games render fine with Fast, but Accurate is still " "required for some.\nParticles tend to only render correctly with Accurate mode.")); INSERT(Settings, dma_accuracy, tr("DMA Accuracy:"), - tr("Controls the DMA precision accuracy. Safe precision fixes issues in some games but " - "may degrade performance.")); + tr("Controls the DMA read mode.\nUnsafe is faster, while Safe is more stable and can fix issues in some games.\nDefault follows the GPU Accuracy setting.")); + INSERT(Settings, gpu_fence_behavior, tr("GPU Fence Behavior:"), + tr("Controls the GPU fence synchronization behavior.\nImmediate is the fastest option, but can introduce some issues.\nBalanced offers better compatibility and may fix issues in some games.\nAccurate further improves compatibility at the cost of some performance.\nDefault follows the GPU Mode setting.")); INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"), tr("May reduce shader stutter.")); - INSERT(Settings, fast_gpu_time, tr("Fast GPU Time"), - tr("Overclocks the emulated GPU to increase dynamic resolution and render " - "distance.\nUse 256 for maximal performance and 512 for maximal graphics fidelity.")); + INSERT(Settings, gpu_clock, tr("GPU Clocks"), + tr("Makes the game believe GPU work finishes faster than it does, so it stops lowering " + "resolution and render distance to fit the Switch's clocks.")); INSERT(Settings, gpu_unswizzle_enabled, tr("GPU Unswizzle"), tr("Accelerates BCn 3D texture decoding using GPU compute.\n" "Disable if experiencing crashes or graphical glitches.")); @@ -269,12 +271,6 @@ std::unique_ptr InitializeTranslations(QObject* parent) { "quality and performance consistency in some games.")); INSERT(Settings, fix_bloom_effects, tr("Fix bloom effects"), tr("Removes bloom in Burnout.")); - INSERT(Settings, rescale_hack, tr("Enable Legacy Rescale Pass"), - tr("May fix rescale issues in some games by relying on behavior from the previous " - "implementation.\n" - "Legacy behavior workaround that fixes line artifacts on AMD and Intel GPUs, and " - "grey texture flicker on Nvidia GPUs in Luigis Mansion 3.")); - // Renderer (Extensions) INSERT(Settings, dyna_state, tr("Extended Dynamic State"), tr("Controls the number of features that can be used in Extended Dynamic State.\n" @@ -297,6 +293,8 @@ std::unique_ptr InitializeTranslations(QObject* parent) { tr("Controls the seed of the random number generator.\nMainly used for speedrunning.")); INSERT(Settings, rng_seed_enabled, QString(), QString()); INSERT(Settings, device_name, tr("Device Name"), tr("The name of the console.")); + INSERT(Settings, program_args, tr("Homebrew Args"), + tr("Command-line arguments passed to homebrew at launch (e.g. -noglsl).")); INSERT(Settings, custom_rtc, tr("Custom RTC Date:"), tr("This option allows to change the clock of the console.\n" "Can be used to manipulate time in games.")); @@ -338,9 +336,6 @@ std::unique_ptr InitializeTranslations(QObject* parent) { "it bypasses such prompts and directly exits the emulation.")); INSERT(UISettings, hide_mouse, tr("Hide mouse on inactivity"), tr("Hides the mouse after 2.5s of inactivity.")); - INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"), - tr("Forcibly disables the use of the controller applet in emulated programs.\n" - "When a program attempts to open the controller applet, it is immediately closed.")); INSERT(UISettings, check_for_updates, tr("Check for updates"), tr("Whether or not to check for updates upon startup.")); @@ -365,8 +360,8 @@ std::unique_ptr InitializeTranslations(QObject* parent) { std::unique_ptr ComboboxEnumeration(QObject* parent) { std::unique_ptr translations = std::make_unique(); - const auto& tr = [&](const char* text, const char* context = "") { - return parent->tr(text, context); + const auto& tr = [](const char* text, const char* context = "") { + return QCoreApplication::translate("ConfigurationShared", text, context); }; #define PAIR(ENUM, VALUE, TRANSLATION) {static_cast(Settings::ENUM::VALUE), (TRANSLATION)} @@ -376,6 +371,7 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) { { PAIR(AppletMode, HLE, tr("Custom frontend")), PAIR(AppletMode, LLE, tr("Real applet")), + PAIR(AppletMode, Disabled, tr("Disabled")), }}); translations->insert({Settings::EnumMetadata::Index(), @@ -422,7 +418,6 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) { translations->insert({Settings::EnumMetadata::Index(), { PAIR(GpuAccuracy, Low, tr("Fast")), - PAIR(GpuAccuracy, Medium, tr("Balanced")), PAIR(GpuAccuracy, High, tr("Accurate")), }}); translations->insert({Settings::EnumMetadata::Index(), @@ -431,6 +426,13 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) { PAIR(DmaAccuracy, Unsafe, tr("Unsafe (fast)")), PAIR(DmaAccuracy, Safe, tr("Safe (stable)")), }}); + translations->insert({Settings::EnumMetadata::Index(), + { + PAIR(GpuFenceBehavior, Default, tr("Default")), + PAIR(GpuFenceBehavior, Immediate, tr("Immediate")), + PAIR(GpuFenceBehavior, Balanced, tr("Balanced")), + PAIR(GpuFenceBehavior, Accurate, tr("Accurate")), + }}); translations->insert( {Settings::EnumMetadata::Index(), { @@ -488,6 +490,8 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) { PAIR(ScalingFilter, BSpline, tr("B-Spline")), PAIR(ScalingFilter, Mitchell, tr("Mitchell")), PAIR(ScalingFilter, Spline1, tr("Spline-1")), + PAIR(ScalingFilter, Sgsr, tr("Snapdragon Game Super Resolution")), + PAIR(ScalingFilter, SgsrEdge, tr("Snapdragon Game Super Resolution EdgeDir")), }}); translations->insert({Settings::EnumMetadata::Index(), { @@ -511,9 +515,6 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) { PAIR(AnisotropyMode, X4, tr("4x")), PAIR(AnisotropyMode, X8, tr("8x")), PAIR(AnisotropyMode, X16, tr("16x")), - PAIR(AnisotropyMode, X32, tr("32x")), - PAIR(AnisotropyMode, X64, tr("64x")), - PAIR(AnisotropyMode, None, tr("None")), }}); translations->insert( {Settings::EnumMetadata::Index(), @@ -625,9 +626,9 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) { }}); translations->insert({Settings::EnumMetadata::Index(), { - PAIR(CpuClock, Off, tr("Off")), - PAIR(CpuClock, Boost, tr("Boost (1700MHz)")), - PAIR(CpuClock, Fast, tr("Fast (2000MHz)")), + PAIR(CpuClock, Normal, tr("Normal")), + PAIR(CpuClock, Boost, tr("Boost")), + PAIR(CpuClock, Overclock, tr("Overclock")), }}); translations->insert( {Settings::EnumMetadata::Index(), @@ -636,11 +637,11 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) { PAIR(ConfirmStop, Ask_Based_On_Game, tr("Only if game specifies not to stop")), PAIR(ConfirmStop, Ask_Never, tr("Never ask")), }}); - translations->insert({Settings::EnumMetadata::Index(), + translations->insert({Settings::EnumMetadata::Index(), { - PAIR(GpuOverclock, Normal, tr("Off")), - PAIR(GpuOverclock, Medium, tr("Medium (256)")), - PAIR(GpuOverclock, High, tr("High (512)")), + PAIR(GpuClock, Normal, tr("Normal")), + PAIR(GpuClock, Boost, tr("Boost")), + PAIR(GpuClock, Overclock, tr("Overclock")), }}); translations->insert({Settings::EnumMetadata::Index(), { diff --git a/src/qt_common/config/shared_translation.h b/src/qt_common/config/shared_translation.h index 6529c7bf40..dbe472caa1 100644 --- a/src/qt_common/config/shared_translation.h +++ b/src/qt_common/config/shared_translation.h @@ -53,6 +53,8 @@ static const std::map scaling_filter_texts_map {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "FSR"))}, {Settings::ScalingFilter::Area, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Area"))}, {Settings::ScalingFilter::Mmpx, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "MMPX"))}, + {Settings::ScalingFilter::Sgsr, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "SGSR"))}, + {Settings::ScalingFilter::SgsrEdge, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "SGSR EdgeDir"))}, }; static const std::map use_docked_mode_texts_map = { @@ -62,7 +64,6 @@ static const std::map use_docked_mode_texts_map static const std::map gpu_accuracy_texts_map = { {Settings::GpuAccuracy::Low, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Fast"))}, - {Settings::GpuAccuracy::Medium, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Balanced"))}, {Settings::GpuAccuracy::High, QStringLiteral(QT_TRANSLATE_NOOP("MainWindow", "Accurate"))}, }; diff --git a/src/qt_common/config/uisettings.h b/src/qt_common/config/uisettings.h index 4549a36345..a790cfcc22 100644 --- a/src/qt_common/config/uisettings.h +++ b/src/qt_common/config/uisettings.h @@ -127,8 +127,6 @@ struct Values { Setting hide_mouse{ linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default, true, true}; - Setting controller_applet_disabled{linkage, false, "disableControllerApplet", - Category::UiGeneral}; // Set when Vulkan is known to crash the application bool has_broken_vulkan = false; @@ -207,12 +205,11 @@ struct Values { // Game List Setting show_add_ons{linkage, true, "show_add_ons", Category::UiGameList}; - Setting game_icon_size{linkage, 64, "game_icon_size", Category::UiGameList}; - Setting folder_icon_size{linkage, 48, "folder_icon_size", Category::UiGameList}; + Setting game_icon_size{linkage, 64, 0, 512, "game_icon_size", Category::UiGameList}; + Setting folder_icon_size{linkage, 48, 8, 512, "folder_icon_size", Category::UiGameList}; Setting row_1_text_id{linkage, 3, "row_1_text_id", Category::UiGameList}; Setting row_2_text_id{linkage, 2, "row_2_text_id", Category::UiGameList}; - Setting game_list_mode{linkage, Settings::GameListMode::TreeView, - "game_list_mode", Category::UiGameList}; + Setting game_list_mode{linkage, Settings::GameListMode::TreeView, "game_list_mode", Category::UiGameList}; Setting show_game_name{linkage, true, "show_game_name", Category::UiGameList}; std::atomic_bool is_game_list_reload_pending{false}; @@ -224,9 +221,6 @@ struct Values { // perf overlay Setting show_perf_overlay{linkage, false, "show_perf_overlay", Category::UiGameList}; - // Compatibility List - Setting show_compat{linkage, true, "show_compat", Category::UiGameList}; - // Size & File Types Column Setting show_size{linkage, true, "show_size", Category::UiGameList}; Setting show_types{linkage, true, "show_types", Category::UiGameList}; diff --git a/src/qt_common/discord/discord_impl.cpp b/src/qt_common/discord/discord_impl.cpp index c1cb5319dc..666b490347 100644 --- a/src/qt_common/discord/discord_impl.cpp +++ b/src/qt_common/discord/discord_impl.cpp @@ -74,8 +74,8 @@ static constexpr char DEFAULT_DISCORD_IMAGE[] = "https://git.eden-emu.dev/eden-emu/eden/raw/branch/master/dist/qt_themes/default/icons/256x256/" "eden.png"; -void DiscordImpl::UpdateGameStatus(bool use_default) { - const std::string url = use_default ? std::string{DEFAULT_DISCORD_IMAGE} : game_url; +void DiscordImpl::UpdateGameStatus(std::string_view game_url, bool has_boxart) { + const std::string url = std::string{has_boxart ? game_url : DEFAULT_DISCORD_IMAGE}; s64 start_time = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count(); @@ -98,7 +98,7 @@ void DiscordImpl::Update() { // Used to format Icon URL for yuzu website game compatibility page std::string icon_name = GetGameString(game_title); - game_url = fmt::format( + auto const game_url = fmt::format( "https://raw.githubusercontent.com/eden-emulator/boxart/refs/heads/master/img/{}.png", icon_name); @@ -117,7 +117,7 @@ void DiscordImpl::Update() { }; auto res = client.send(request); - UpdateGameStatus(res && res->status == 200); + UpdateGameStatus(game_url, res && res->status == 200); return; } diff --git a/src/qt_common/discord/discord_impl.h b/src/qt_common/discord/discord_impl.h index 919717664c..602e431482 100644 --- a/src/qt_common/discord/discord_impl.h +++ b/src/qt_common/discord/discord_impl.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2018 Citra Emulator Project @@ -26,11 +26,9 @@ public: private: std::string GetGameString(const std::string& title); - void UpdateGameStatus(bool use_default); + void UpdateGameStatus(std::string_view game_url, bool use_default); - std::string game_url{}; std::string game_title{}; - Core::System& system; }; diff --git a/src/yuzu/game/game_list_p.h b/src/qt_common/game_list/game_list_p.h similarity index 79% rename from src/yuzu/game/game_list_p.h rename to src/qt_common/game_list/game_list_p.h index f9d714fb8b..d113f4ee75 100644 --- a/src/yuzu/game/game_list_p.h +++ b/src/qt_common/game_list/game_list_p.h @@ -17,14 +17,13 @@ #include #include #include -#include #include "common/common_types.h" #include "common/logging.h" #include "common/string_util.h" #include "frontend_common/play_time_manager.h" #include "qt_common/config/uisettings.h" -#include "yuzu/util/util.h" +#include "qt_common/qt_common.h" enum class GameListItemType { Game = QStandardItem::UserType + 1, @@ -49,6 +48,14 @@ static QPixmap GetDefaultIcon(u32 size) { return icon; } +static QPixmap ThemeIcon(const char* name) { + const int size = UISettings::values.folder_icon_size.GetValue(); + + return QIcon::fromTheme(QLatin1String(name)) + .pixmap(size, size) + .scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); +} + class GameListItem : public QStandardItem { public: @@ -129,7 +136,7 @@ public: const std::array row_data{{ QString::fromStdString(filename), data(FileTypeRole).toString(), - QString::fromStdString(fmt::format("0x{:016X}", data(ProgramIdRole).toULongLong())), + QString::fromStdString(fmt::format("{:#016x}", data(ProgramIdRole).toULongLong())), data(TitleRole).toString(), }}; @@ -158,6 +165,7 @@ public: return QStringLiteral("%1\n %2").arg(row1, row2); } case Settings::GameListMode::GridView: + case Settings::GameListMode::CarouselView: return row1; default: break; @@ -204,7 +212,7 @@ public: setData(compatibility, CompatNumberRole); setText(tr(status.text)); setToolTip(tr(status.tooltip)); - setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole); + setData(QtCommon::CreateCirclePixmapFromColor(status.color), Qt::DecorationRole); } int type() const override { @@ -237,7 +245,7 @@ public: // representations of the data are always accurate and in the correct format. if (role == SizeRole) { qulonglong size_bytes = value.toULongLong(); - GameListItem::setData(ReadableByteSize(size_bytes), Qt::DisplayRole); + GameListItem::setData(QtCommon::ReadableByteSize(size_bytes), Qt::DisplayRole); GameListItem::setData(value, SizeRole); } else { GameListItem::setData(value, role); @@ -297,45 +305,33 @@ public: UISettings::GameDir* game_dir = &directory; setData(QVariant(UISettings::values.game_dirs.indexOf(directory)), GameDirRole); - const int icon_size = UISettings::values.folder_icon_size.GetValue(); + const char* icon_name = nullptr; + switch (dir_type) { case GameListItemType::SdmcDir: - setData( - QIcon::fromTheme(QStringLiteral("sd_card")) - .pixmap(icon_size) - .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); + icon_name = "sd_card"; setData(QObject::tr("Installed SD Titles"), Qt::DisplayRole); break; case GameListItemType::UserNandDir: - setData( - QIcon::fromTheme(QStringLiteral("chip")) - .pixmap(icon_size) - .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); + icon_name = "chip"; setData(QObject::tr("Installed NAND Titles"), Qt::DisplayRole); break; case GameListItemType::SysNandDir: - setData( - QIcon::fromTheme(QStringLiteral("chip")) - .pixmap(icon_size) - .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); + icon_name = "chip"; setData(QObject::tr("System Titles"), Qt::DisplayRole); break; case GameListItemType::CustomDir: { const QString path = QString::fromStdString(game_dir->path); - const QString icon_name = - QFileInfo::exists(path) ? QStringLiteral("folder") : QStringLiteral("bad_folder"); - setData(QIcon::fromTheme(icon_name).pixmap(icon_size).scaled( - icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); + icon_name = QFileInfo::exists(path) ? "folder" : "bad_folder"; setData(path, Qt::DisplayRole); break; } default: break; } + + if (icon_name != nullptr) + setData(ThemeIcon(icon_name), Qt::DecorationRole); } int type() const override { @@ -358,12 +354,7 @@ public: explicit GameListAddDir() { setData(type(), TypeRole); - const int icon_size = UISettings::values.folder_icon_size.GetValue(); - - setData(QIcon::fromTheme(QStringLiteral("list-add")) - .pixmap(icon_size) - .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); + setData(ThemeIcon("list-add"), Qt::DecorationRole); setData(QObject::tr("Add New Game Directory"), Qt::DisplayRole); } @@ -381,12 +372,7 @@ public: explicit GameListFavorites() { setData(type(), TypeRole); - const int icon_size = UISettings::values.folder_icon_size.GetValue(); - - setData(QIcon::fromTheme(QStringLiteral("star")) - .pixmap(icon_size) - .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); + setData(ThemeIcon("star"), Qt::DecorationRole); setData(QObject::tr("Favorites"), Qt::DisplayRole); } @@ -398,49 +384,3 @@ public: return false; } }; - -class GameList; -class QHBoxLayout; -class QTreeView; -class QLabel; -class QLineEdit; -class QToolButton; - -class GameListSearchField : public QWidget { - Q_OBJECT - -public: - explicit GameListSearchField(GameList* parent = nullptr); - - QString filterText() const; - void setFilterResult(int visible_, int total_); - - void clear(); - void setFocus(); - -private: - void changeEvent(QEvent*) override; - void RetranslateUI(); - - class KeyReleaseEater : public QObject { - public: - explicit KeyReleaseEater(GameList* gamelist_, QObject* parent = nullptr); - - private: - GameList* gamelist = nullptr; - QString edit_filter_text_old; - - protected: - // EventFilter in order to process systemkeys while editing the searchfield - bool eventFilter(QObject* obj, QEvent* event) override; - }; - int visible; - int total; - - QHBoxLayout* layout_filter = nullptr; - QTreeView* tree_view = nullptr; - QLabel* label_filter = nullptr; - QLineEdit* edit_filter = nullptr; - QLabel* label_filter_result = nullptr; - QToolButton* button_filter_close = nullptr; -}; diff --git a/src/qt_common/game_list/model.cpp b/src/qt_common/game_list/model.cpp new file mode 100644 index 0000000000..708ef576c6 --- /dev/null +++ b/src/qt_common/game_list/model.cpp @@ -0,0 +1,207 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include +#include + +#include "common/logging.h" +#include "common/settings.h" +#include "core/core.h" +#include "core/file_sys/patch_manager.h" +#include "core/file_sys/registered_cache.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "qt_common/config/uisettings.h" +#include "qt_common/qt_common.h" +#include "qt_common/util/game.h" + +#include "qt_common/game_list/game_list_p.h" +#include "qt_common/game_list/model.h" +#include "qt_common/game_list/worker.h" + +GameListModel::GameListModel(std::shared_ptr vfs_, + FileSys::ManualContentProvider* provider_, + const PlayTime::PlayTimeManager& play_time_manager_, + Core::System& system_, QObject* parent) + : QStandardItemModel{parent}, vfs{std::move(vfs_)}, provider{provider_}, + play_time_manager{play_time_manager_}, system{system_} { + watcher = new QFileSystemWatcher(this); + external_watcher = new QFileSystemWatcher(this); + + connect(watcher, &QFileSystemWatcher::directoryChanged, this, + &GameListModel::RefreshGameDirectory); + connect(external_watcher, &QFileSystemWatcher::directoryChanged, this, + &GameListModel::RefreshExternalContent); + + ResetExternalWatcher(); + + insertColumns(0, COLUMN_COUNT); + RetranslateUI(); + + setSortRole(GameListItemPath::SortRole); +} + +GameListModel::~GameListModel() = default; + +void GameListModel::PopulateAsync(QVector& game_dirs) { + emit PopulatingStarted(); + + current_worker.reset(); + removeRows(0, rowCount()); + + current_worker = std::make_unique(vfs, provider, game_dirs, + play_time_manager, system); + + connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameListModel::WorkerEvent, + Qt::QueuedConnection); + + QThreadPool::globalInstance()->start(current_worker.get()); +} + +void GameListModel::WorkerEvent() { + current_worker->ProcessEvents(this); +} + +void GameListModel::AddDirEntry(GameListDir* entry_items) { + if (m_flat) { + return; + } + invisibleRootItem()->appendRow(entry_items); +} + +void GameListModel::AddEntry(const QList& entry_items, GameListDir* parent) { + if (m_flat) { + invisibleRootItem()->appendRow(entry_items); + } else { + parent->appendRow(entry_items); + } +} + +void GameListModel::DonePopulating(const QStringList& watch_list) { + emit ShowList(!IsEmpty()); + + if (!m_flat) { + invisibleRootItem()->appendRow(new GameListAddDir()); + invisibleRootItem()->insertRow(0, new GameListFavorites()); + + for (const auto id : std::as_const(UISettings::values.favorited_ids)) { + AddFavorite(id); + } + } + + emit PopulatingCompleted(watch_list); +} + +bool GameListModel::IsEmpty() const { + for (int i = 0; i < rowCount(); i++) { + const QStandardItem* child = invisibleRootItem()->child(i); + const auto type = static_cast(child->type()); + + if (!child->hasChildren() && + (type == GameListItemType::SdmcDir || type == GameListItemType::UserNandDir || + type == GameListItemType::SysNandDir)) { + invisibleRootItem()->removeRow(child->row()); + i--; + } + } + + return !invisibleRootItem()->hasChildren(); +} + +void GameListModel::ToggleFavorite(u64 program_id) { + if (!UISettings::values.favorited_ids.contains(program_id)) { + UISettings::values.favorited_ids.append(program_id); + AddFavorite(program_id); + } else { + UISettings::values.favorited_ids.removeOne(program_id); + RemoveFavorite(program_id); + } + emit SaveConfig(); +} + +void GameListModel::AddFavorite(u64 program_id) { + auto* favorites_row = item(0); + + for (int i = 1; i < rowCount() - 1; i++) { + const auto* folder = item(i); + for (int j = 0; j < folder->rowCount(); j++) { + if (folder->child(j)->data(GameListItemPath::ProgramIdRole).toULongLong() == + program_id) { + QList list; + for (int k = 0; k < COLUMN_COUNT; k++) { + list.append(folder->child(j, k)->clone()); + } + list[0]->setData(folder->child(j)->data(GameListItem::SortRole), + GameListItem::SortRole); + list[0]->setText(folder->child(j)->data(Qt::DisplayRole).toString()); + + favorites_row->appendRow(list); + return; + } + } + } +} + +void GameListModel::RemoveFavorite(u64 program_id) { + auto* favorites_row = item(0); + + for (int i = 0; i < favorites_row->rowCount(); i++) { + const auto* game = favorites_row->child(i); + if (game->data(GameListItemPath::ProgramIdRole).toULongLong() == program_id) { + favorites_row->removeRow(i); + return; + } + } +} + +void GameListModel::Repopulate() { + current_worker.reset(); + QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs); + PopulateAsync(UISettings::values.game_dirs); +} + +void GameListModel::RefreshGameDirectory() { + ResetExternalWatcher(); + if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) { + LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); + Repopulate(); + } +} + +void GameListModel::RefreshExternalContent() { + if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) { + LOG_INFO(Frontend, "External content directory changed. Clearing metadata cache."); + QtCommon::Game::ResetMetadata(false); + Repopulate(); + } +} + +void GameListModel::ResetExternalWatcher() { + auto watch_dirs = external_watcher->directories(); + if (!watch_dirs.isEmpty()) { + external_watcher->removePaths(watch_dirs); + } + + for (const std::string& dir : Settings::values.external_content_dirs) { + external_watcher->addPath(QString::fromStdString(dir)); + } +} + +void GameListModel::RetranslateUI() { + setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name")); + setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); + setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); + setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); + setHeaderData(COLUMN_PLAY_TIME, Qt::Horizontal, tr("Play time")); +} + +QFileSystemWatcher* GameListModel::GetWatcher() const { + return watcher; +} + +void GameListModel::SetFlat(bool flat) { + m_flat = flat; +} diff --git a/src/qt_common/game_list/model.h b/src/qt_common/game_list/model.h new file mode 100644 index 0000000000..98614ae33a --- /dev/null +++ b/src/qt_common/game_list/model.h @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "common/common_types.h" +#include "frontend_common/play_time_manager.h" +#include "qt_common/config/uisettings.h" + +namespace Core { +class System; +} + +class GameListDir; +class GameListWorker; +class QStandardItem; + +namespace FileSys { +class ManualContentProvider; +class VfsFilesystem; +} // namespace FileSys + +class GameListModel : public QStandardItemModel { + Q_OBJECT + +public: + enum Column { + COLUMN_NAME, + COLUMN_FILE_TYPE, + COLUMN_SIZE, + COLUMN_PLAY_TIME, + COLUMN_ADD_ONS, + COLUMN_COUNT, + }; + + explicit GameListModel(std::shared_ptr vfs_, + FileSys::ManualContentProvider* provider_, + const PlayTime::PlayTimeManager& play_time_manager_, + Core::System& system_, QObject* parent = nullptr); + ~GameListModel() override; + + void AddDirEntry(GameListDir* entry_items); + void AddEntry(const QList& entry_items, GameListDir* parent); + void DonePopulating(const QStringList& watch_list); + + void PopulateAsync(QVector& game_dirs); + void WorkerEvent(); + + bool IsEmpty() const; + + void ToggleFavorite(u64 program_id); + + void RefreshGameDirectory(); + void RefreshExternalContent(); + void ResetExternalWatcher(); + + void RetranslateUI(); + + QFileSystemWatcher* GetWatcher() const; + + void SetFlat(bool flat); + +signals: + void ShowList(bool show); + void PopulatingCompleted(const QStringList& watch_list); + void PopulatingStarted(); + void SaveConfig(); + +private: + friend class GameListWorker; + + void AddFavorite(u64 program_id); + void RemoveFavorite(u64 program_id); + void Repopulate(); + + bool m_flat = false; + + std::shared_ptr vfs; + FileSys::ManualContentProvider* provider; + const PlayTime::PlayTimeManager& play_time_manager; + Core::System& system; + + std::unique_ptr current_worker; + QFileSystemWatcher* watcher = nullptr; + QFileSystemWatcher* external_watcher = nullptr; +}; diff --git a/src/yuzu/game/game_list_worker.cpp b/src/qt_common/game_list/worker.cpp similarity index 82% rename from src/yuzu/game/game_list_worker.cpp rename to src/qt_common/game_list/worker.cpp index 777122e135..6b6f93447d 100644 --- a/src/yuzu/game/game_list_worker.cpp +++ b/src/qt_common/game_list/worker.cpp @@ -20,21 +20,22 @@ #include "common/settings.h" #include "core/core.h" #include "core/file_sys/card_image.h" -#include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/fs_filesystem.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" -#include "core/file_sys/romfs.h" #include "core/file_sys/submission_package.h" #include "core/loader/loader.h" + #include "qt_common/config/uisettings.h" -#include "yuzu/compatibility_list.h" -#include "yuzu/game/game_list.h" -#include "yuzu/game/game_list_p.h" -#include "yuzu/game/game_list_worker.h" +#include "qt_common/qt_common.h" + +#include "qt_common/game_list/game_list_p.h" + +#include "qt_common/game_list/model.h" +#include "qt_common/game_list/worker.h" namespace { @@ -148,7 +149,7 @@ void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const bool HasSupportedFileExtension(const std::string& file_name) { const QFileInfo file = QFileInfo(QString::fromStdString(file_name)); - return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive); + return QtCommon::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive); } bool IsExtractedNCAMain(const std::string& file_name) { @@ -201,14 +202,8 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, QList MakeGameListEntry(const std::string& path, const std::string& name, const std::size_t size, const std::vector& icon, Loader::AppLoader& loader, u64 program_id, - const CompatibilityList& compatibility_list, const PlayTime::PlayTimeManager& play_time_manager, const FileSys::PatchManager& patch) { - auto const it = FindMatchingCompatibilityEntry(compatibility_list, program_id); - // The game list uses 99 as compatibility number for untested games - QString compatibility = - it != compatibility_list.end() ? it->second.first : QStringLiteral("99"); - auto const file_type = loader.GetFileType(); auto const file_type_string = QString::fromStdString(Loader::GetFileTypeString(file_type)); @@ -225,7 +220,6 @@ QList MakeGameListEntry(const std::string& path, const std::stri new GameListItemSize(size), new GameListItemPlayTime(play_time), new GameListItem(patch_versions), - new GameListItemCompat(compatibility), }; } } // Anonymous namespace @@ -233,11 +227,10 @@ QList MakeGameListEntry(const std::string& path, const std::stri GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_, QVector& game_dirs_, - const CompatibilityList& compatibility_list_, const PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_) : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, - compatibility_list{compatibility_list_}, play_time_manager{play_time_manager_}, + play_time_manager{play_time_manager_}, system{system_} { // We want the game list to manage our lifetime. setAutoDelete(false); @@ -249,9 +242,9 @@ GameListWorker::~GameListWorker() { processing_completed.Wait(); } -void GameListWorker::ProcessEvents(GameList* game_list) { +void GameListWorker::ProcessEvents(GameListModel* model) { while (true) { - std::function func; + std::function func; { // Lock queue to protect concurrent modification. std::scoped_lock lk(lock); @@ -267,7 +260,7 @@ void GameListWorker::ProcessEvents(GameList* game_list) { } // Run the function. - func(game_list); + func(model); } } @@ -333,8 +326,8 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { } auto entry = MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, - program_id, compatibility_list, play_time_manager, patch); - RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); + program_id, play_time_manager, patch); + RecordEvent([=](GameListModel* model) { model->AddEntry(entry, parent_dir); }); } } @@ -367,7 +360,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa } if (target == ScanTarget::PopulateGameList && - (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP) && + (file_type == Loader::FileType::XCI || file_type == Loader::FileType::XCZ || + file_type == Loader::FileType::NSP || file_type == Loader::FileType::NSZ) && !Loader::IsBootableGameContainer(file, file_type)) { return true; } @@ -382,15 +376,37 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa program_id, file); } else if (Settings::values.ext_content_from_game_dirs.GetValue() && (file_type == Loader::FileType::XCI || - file_type == Loader::FileType::NSP)) { + file_type == Loader::FileType::XCZ || + file_type == Loader::FileType::NSP || + file_type == Loader::FileType::NSZ)) { void(provider->AddEntriesFromContainer(file)); } } else { std::vector program_ids; loader->ReadProgramIds(program_ids); + const auto addEntry = [this, physical_name, + parent_dir](std::unique_ptr& app_loader, + const u64 id) { + std::vector icon; + [[maybe_unused]] const auto res1 = app_loader->ReadIcon(icon); + + std::string name = " "; + [[maybe_unused]] const auto res3 = app_loader->ReadTitle(name); + + const FileSys::PatchManager patch{id, system.GetFileSystemController(), + system.GetContentProvider()}; + + auto entry = MakeGameListEntry( + physical_name, name, Common::FS::GetSize(physical_name), icon, *app_loader, + id, play_time_manager, patch); + + RecordEvent([=](GameListModel* model) { model->AddEntry(entry, parent_dir); }); + }; + if (res2 == Loader::ResultStatus::Success && program_ids.size() > 1 && - (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { + (file_type == Loader::FileType::XCI || file_type == Loader::FileType::XCZ || + file_type == Loader::FileType::NSP || file_type == Loader::FileType::NSZ)) { for (const auto id : program_ids) { // dravee suggested this, only viable way to // not show sub-games in qlaunch for now. @@ -402,38 +418,10 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa continue; } - std::vector icon; - [[maybe_unused]] const auto res1 = loader->ReadIcon(icon); - - std::string name = " "; - [[maybe_unused]] const auto res3 = loader->ReadTitle(name); - - const FileSys::PatchManager patch{id, system.GetFileSystemController(), - system.GetContentProvider()}; - - auto entry = MakeGameListEntry( - physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, - id, compatibility_list, play_time_manager, patch); - - RecordEvent( - [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); + addEntry(loader, id); } } else { - std::vector icon; - [[maybe_unused]] const auto res1 = loader->ReadIcon(icon); - - std::string name = " "; - [[maybe_unused]] const auto res3 = loader->ReadTitle(name); - - const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), - system.GetContentProvider()}; - - auto entry = MakeGameListEntry( - physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, - program_id, compatibility_list, play_time_manager, patch); - - RecordEvent( - [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); + addEntry(loader, program_id); } } } else if (is_dir) { @@ -456,7 +444,7 @@ void GameListWorker::run() { provider->ClearAllEntries(); const auto DirEntryReady = [&](GameListDir* game_list_dir) { - RecordEvent([=](GameList* game_list) { game_list->AddDirEntry(game_list_dir); }); + RecordEvent([=](GameListModel* model) { model->AddDirEntry(game_list_dir); }); }; for (UISettings::GameDir& game_dir : game_dirs) { @@ -464,32 +452,36 @@ void GameListWorker::run() { break; } + GameListDir* game_list_dir; + bool scan = false; + if (game_dir.path == std::string("SDMC")) { - auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir); - DirEntryReady(game_list_dir); - AddTitlesToGameList(game_list_dir); + game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir); } else if (game_dir.path == std::string("UserNAND")) { - auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir); - DirEntryReady(game_list_dir); - AddTitlesToGameList(game_list_dir); + game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir); } else if (game_dir.path == std::string("SysNAND")) { - auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir); - DirEntryReady(game_list_dir); - AddTitlesToGameList(game_list_dir); + game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir); } else { const QString qpath = QString::fromStdString(game_dir.path); if (QDir(qpath).exists()) { watch_list.append(qpath); } - auto* const game_list_dir = new GameListDir(game_dir); - DirEntryReady(game_list_dir); + + game_list_dir = new GameListDir(game_dir); + scan = true; + } + + DirEntryReady(game_list_dir); + if (scan) { ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path, game_dir.deep_scan, game_list_dir); ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path, game_dir.deep_scan, game_list_dir); + } else { + AddTitlesToGameList(game_list_dir); } } - RecordEvent([this](GameList* game_list) { game_list->DonePopulating(watch_list); }); + RecordEvent([this](GameListModel* model) { model->DonePopulating(watch_list); }); processing_completed.Set(); } diff --git a/src/yuzu/game/game_list_worker.h b/src/qt_common/game_list/worker.h similarity index 81% rename from src/yuzu/game/game_list_worker.h rename to src/qt_common/game_list/worker.h index bf67585fe6..c007498d4b 100644 --- a/src/yuzu/game/game_list_worker.h +++ b/src/qt_common/game_list/worker.h @@ -20,14 +20,13 @@ #include "core/file_sys/registered_cache.h" #include "frontend_common/play_time_manager.h" #include "qt_common/config/uisettings.h" -#include "yuzu/compatibility_list.h" namespace Core { class System; } -class GameList; class GameListDir; +class GameListModel; class QStandardItem; namespace FileSys { @@ -46,7 +45,6 @@ public: explicit GameListWorker(std::shared_ptr vfs_, FileSys::ManualContentProvider* provider_, QVector& game_dirs_, - const CompatibilityList& compatibility_list_, const PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_); ~GameListWorker() override; @@ -58,11 +56,11 @@ public: /** * Synchronously processes any events queued by the worker. * - * AddDirEntry is called on the game list for every discovered directory. - * AddEntry is called on the game list for every discovered program. - * DonePopulating is called on the game list when processing completes. + * AddDirEntry is called on the model for every discovered directory. + * AddEntry is called on the model for every discovered program. + * DonePopulating is called on the model when processing completes. */ - void ProcessEvents(GameList* game_list); + void ProcessEvents(GameListModel* model); signals: void DataAvailable(); @@ -85,14 +83,13 @@ private: std::shared_ptr vfs; FileSys::ManualContentProvider* provider; QVector& game_dirs; - const CompatibilityList& compatibility_list; const PlayTime::PlayTimeManager& play_time_manager; QStringList watch_list; std::mutex lock; std::condition_variable cv; - std::deque> queued_events; + std::deque> queued_events; std::atomic_bool stop_requested = false; Common::Event processing_completed; diff --git a/src/qt_common/qt_common.cpp b/src/qt_common/qt_common.cpp index 3091171df8..794aeaabb1 100644 --- a/src/qt_common/qt_common.cpp +++ b/src/qt_common/qt_common.cpp @@ -1,25 +1,51 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#include "common/fs/fs.h" +#include "common/literals.h" + +#include "common/memory_detect.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "frontend_common/data_manager.h" +#include "hid_core/hid_core.h" +#include "network/network.h" #include "qt_common.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/logging.h" +#include "common/scm_rev.h" +#include "common/cpu_features.h" +#include "core/memory.h" + #include #include -#include "common/logging.h" #include "core/frontend/emu_window.h" +#include "qt_common/util/meta.h" #include +#include + +#include #include +#include #if !defined(WIN32) && !defined(__APPLE__) #include #elif defined(__APPLE__) #include -#include #endif +#ifdef _WIN32 +#include +#include +#include +#include "common/windows/timer_resolution.h" +#include "core/core_timing.h" +#endif + +using namespace Common::Literals; + namespace QtCommon { QWidget* rootObject = nullptr; @@ -27,6 +53,12 @@ QWidget* rootObject = nullptr; std::unique_ptr system = nullptr; std::shared_ptr vfs = nullptr; std::unique_ptr provider = nullptr; +std::unique_ptr emu_thread = nullptr; + +const QStringList supported_file_extensions = {QStringLiteral("nro"), QStringLiteral("nso"), + QStringLiteral("nca"), QStringLiteral("xci"), + QStringLiteral("xcz"), QStringLiteral("nsp"), + QStringLiteral("nsz"), QStringLiteral("kip")}; Core::Frontend::WindowSystemType GetWindowSystemType() { // Determine WSI type based on Qt platform. @@ -103,18 +135,148 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) } const QString tr(const char* str) { - return QGuiApplication::tr(str); + return rootObject->tr(str); } const QString tr(const std::string& str) { - return QGuiApplication::tr(str.c_str()); + return rootObject->tr(str.c_str()); +} + +static void LogRuntimes() { +#ifdef _MSC_VER + // It is possible that the name of the dll will change. + // vcruntime140.dll is for 2015 and onwards + static constexpr char runtime_dll_name[] = "vcruntime140.dll"; + UINT sz = GetFileVersionInfoSizeA(runtime_dll_name, nullptr); + bool runtime_version_inspection_worked = false; + if (sz > 0) { + std::vector buf(sz); + if (GetFileVersionInfoA(runtime_dll_name, 0, sz, buf.data())) { + VS_FIXEDFILEINFO* pvi; + sz = sizeof(VS_FIXEDFILEINFO); + if (VerQueryValueA(buf.data(), "\\", reinterpret_cast(&pvi), &sz)) { + if (pvi->dwSignature == VS_FFI_SIGNATURE) { + runtime_version_inspection_worked = true; + LOG_INFO(Frontend, "MSVC Compiler: {} Runtime: {}.{}.{}.{}", _MSC_VER, + pvi->dwProductVersionMS >> 16, pvi->dwProductVersionMS & 0xFFFF, + pvi->dwProductVersionLS >> 16, pvi->dwProductVersionLS & 0xFFFF); + } + } + } + } + if (!runtime_version_inspection_worked) { + LOG_INFO(Frontend, "Unable to inspect {}", runtime_dll_name); + } +#endif + LOG_INFO(Frontend, "Qt Compile: {} Runtime: {}", QT_VERSION_STR, qVersion()); +} + +static QString PrettyProductName() { +#ifdef _WIN32 + // After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2 + // With that notation change they changed the registry key used to denote the current version + QSettings windows_registry( + QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), + QSettings::NativeFormat); + const QString release_id = windows_registry.value(QStringLiteral("ReleaseId")).toString(); + if (release_id == QStringLiteral("2009")) { + const u32 current_build = windows_registry.value(QStringLiteral("CurrentBuild")).toUInt(); + const QString display_version = + windows_registry.value(QStringLiteral("DisplayVersion")).toString(); + const u32 ubr = windows_registry.value(QStringLiteral("UBR")).toUInt(); + u32 version = 10; + if (current_build >= 22000) { + version = 11; + } + return QStringLiteral("Windows %1 Version %2 (Build %3.%4)") + .arg(QString::number(version), display_version, QString::number(current_build), + QString::number(ubr)); + } +#endif + return QSysInfo::prettyProductName(); +} + +static void RemoveCachedContents() { + const auto cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir); + const auto offline_fonts = cache_dir / "fonts"; + const auto offline_manual = cache_dir / "offline_web_applet_manual"; + const auto offline_legal_information = cache_dir / "offline_web_applet_legal_information"; + const auto offline_system_data = cache_dir / "offline_web_applet_system_data"; + + Common::FS::RemoveDirRecursively(offline_fonts); + Common::FS::RemoveDirRecursively(offline_manual); + Common::FS::RemoveDirRecursively(offline_legal_information); + Common::FS::RemoveDirRecursively(offline_system_data); } void Init(QWidget* root) { - system = std::make_unique(); rootObject = root; + + system = std::make_unique(); vfs = std::make_unique(); provider = std::make_unique(); + + // initialization stuff + Common::FS::CreateEdenPaths(); + + system->Initialize(); + + Common::Log::Initialize(); + Common::Log::Start(); + + Network::Init(); + + QtCommon::Meta::RegisterMetaTypes(); + + // build version + const auto branch_name = std::string(Common::g_scm_branch); + const auto description = std::string(Common::g_scm_desc); + const auto build_id = std::string(Common::g_build_id); + + const auto yuzu_build = fmt::format("Eden Development Build | {}-{}", branch_name, description); + const auto override_build = + fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id); + const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build; + const auto processor_count = std::thread::hardware_concurrency(); + + // info logging + LOG_INFO(Frontend, "Eden Version: {}", yuzu_build_version); + LogRuntimes(); +#ifdef ARCHITECTURE_x86_64 + const auto& caps = Common::g_cpu_caps; + std::string cpu_string = caps.cpu_string; + if (caps.avx || caps.avx2 || caps.avx512f) { + cpu_string += " | AVX"; + if (caps.avx512f) { + cpu_string += "512"; + } else if (caps.avx2) { + cpu_string += '2'; + } + if (caps.fma) { + cpu_string += " | FMA"; + } + } + LOG_INFO(Frontend, "Host CPU: {}", cpu_string); + if (std::optional processor_core = Common::GetProcessorCount()) { + LOG_INFO(Frontend, "Host CPU Cores: {}", *processor_core); + } +#endif + LOG_INFO(Frontend, "Host CPU Threads: {}", processor_count); + LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString()); + LOG_INFO(Frontend, "Host RAM: {:.2f} GiB", + Common::GetMemInfo().TotalPhysicalMemory / f64{1_GiB}); + LOG_INFO(Frontend, "Host Swap: {:.2f} GiB", Common::GetMemInfo().TotalSwapMemory / f64{1_GiB}); +#ifdef _WIN32 + LOG_INFO(Frontend, "Host Timer Resolution: {:.4f} ms", + std::chrono::duration_cast>( + Common::Windows::SetCurrentTimerResolutionToMaximum()) + .count()); + QtCommon::system->CoreTiming().SetTimerResolutionNs( + Common::Windows::GetCurrentTimerResolution()); +#endif + + // Remove cached contents generated during the previous session + RemoveCachedContents(); } std::filesystem::path GetEdenCommand() { @@ -137,4 +299,30 @@ std::filesystem::path GetEdenCommand() { return command; } +void SetupContentProviders() { + system->SetContentProvider(std::make_unique()); + system->RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual, + provider.get()); + system->GetFileSystemController().CreateFactories(*vfs); +} + +void SetupHID() { + system->HIDCore().ReloadInputDevices(); +} + +QString ReadableByteSize(qulonglong size) { + return QString::fromStdString(FrontendCommon::DataManager::ReadableBytesSize(size)); +} + +QPixmap CreateCirclePixmapFromColor(const QColor& color) { + QPixmap circle_pixmap(16, 16); + circle_pixmap.fill(Qt::transparent); + QPainter painter(&circle_pixmap); + painter.setRenderHint(QPainter::Antialiasing); + painter.setPen(color); + painter.setBrush(color); + painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0); + return circle_pixmap; +} + } // namespace QtCommon diff --git a/src/qt_common/qt_common.h b/src/qt_common/qt_common.h index 19c09ef5d0..ea8e3f73a2 100644 --- a/src/qt_common/qt_common.h +++ b/src/qt_common/qt_common.h @@ -1,24 +1,35 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef QT_COMMON_H -#define QT_COMMON_H +#pragma once #include #include -#include + #include "core/core.h" #include "core/file_sys/registered_cache.h" +#include "core/frontend/emu_window.h" -#include +#include "qt_common/render/emu_thread.h" + +#include "core/file_sys/vfs/vfs_real.h" + +enum class StartGameType { + Normal, // Can use custom configuration + Global, // Only uses global configuration +}; namespace QtCommon { +// TODO: Remove QWidget dependency extern QWidget* rootObject; extern std::unique_ptr system; extern std::shared_ptr vfs; extern std::unique_ptr provider; +extern std::unique_ptr emu_thread; + +extern const QStringList supported_file_extensions; typedef std::function QtProgressCallback; @@ -28,9 +39,25 @@ Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) void Init(QWidget* root); +void SetupContentProviders(); +void SetupHID(); + const QString tr(const char* str); const QString tr(const std::string& str); +// TODO: Find a better place for these + +/// Convert a size in bytes into a readable format (KiB, MiB, etc.) +[[nodiscard]] QString ReadableByteSize(qulonglong size); + +/** + * Creates a circle pixmap from a specified color + * @param color The color the pixmap shall have + * @return QPixmap circle pixmap + */ +[[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color); + + + std::filesystem::path GetEdenCommand(); } // namespace QtCommon -#endif diff --git a/src/qt_common/render/context.h b/src/qt_common/render/context.h new file mode 100644 index 0000000000..a0ecade9c8 --- /dev/null +++ b/src/qt_common/render/context.h @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include "common/logging.h" +#include "common/settings.h" +#include "core/frontend/graphics_context.h" + +#ifdef HAS_OPENGL +#include +#include +#endif + +#ifdef HAS_OPENGL +class OpenGLSharedContext : public Core::Frontend::GraphicsContext { +public: + /// Create the original context that should be shared from + explicit OpenGLSharedContext(QSurface* surface_) : surface{surface_} { + QSurfaceFormat format; + format.setVersion(4, 6); + format.setProfile(QSurfaceFormat::CompatibilityProfile); + format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); + if (Settings::values.renderer_debug) { + format.setOption(QSurfaceFormat::FormatOption::DebugContext); + } + // TODO: expose a setting for buffer value (ie default/single/double/triple) + format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); + format.setSwapInterval(0); + + context = std::make_unique(); + context->setFormat(format); + if (!context->create()) { + LOG_ERROR(Frontend, "Unable to create main openGL context"); + } + } + + /// Create the shared contexts for rendering and presentation + explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface = nullptr) { + + // disable vsync for any shared contexts + auto format = share_context->format(); + const int swap_interval = + Settings::values.vsync_mode.GetValue() == Settings::VSyncMode::Immediate ? 0 : 1; + + format.setSwapInterval(main_surface ? swap_interval : 0); + + context = std::make_unique(); + context->setShareContext(share_context); + context->setFormat(format); + if (!context->create()) { + LOG_ERROR(Frontend, "Unable to create shared openGL context"); + } + + if (!main_surface) { + offscreen_surface = std::make_unique(nullptr); + offscreen_surface->setFormat(format); + offscreen_surface->create(); + surface = offscreen_surface.get(); + } else { + surface = main_surface; + } + } + + ~OpenGLSharedContext() { + DoneCurrent(); + } + + void SwapBuffers() override { + context->swapBuffers(surface); + } + + void MakeCurrent() override { + // We can't track the current state of the underlying context in this wrapper class because + // Qt may make the underlying context not current for one reason or another. In particular, + // the WebBrowser uses GL, so it seems to conflict if we aren't careful. + // Instead of always just making the context current (which does not have any caching to + // check if the underlying context is already current) we can check for the current context + // in the thread local data by calling `currentContext()` and checking if its ours. + if (QOpenGLContext::currentContext() != context.get()) { + context->makeCurrent(surface); + } + } + + void DoneCurrent() override { + context->doneCurrent(); + } + + QOpenGLContext* GetShareContext() { + return context.get(); + } + + const QOpenGLContext* GetShareContext() const { + return context.get(); + } + +private: + // Avoid using Qt parent system here since we might move the QObjects to new threads + // As a note, this means we should avoid using slots/signals with the objects too + std::unique_ptr context; + std::unique_ptr offscreen_surface{}; + QSurface* surface; +}; +#endif + +class DummyContext : public Core::Frontend::GraphicsContext {}; diff --git a/src/qt_common/render/emu_thread.cpp b/src/qt_common/render/emu_thread.cpp new file mode 100644 index 0000000000..d5e5522837 --- /dev/null +++ b/src/qt_common/render/emu_thread.cpp @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "core/core.h" +#include "core/cpu_manager.h" +#include "emu_thread.h" +#include "qt_common/qt_common.h" +#include "video_core/gpu.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/renderer_base.h" + +EmuThread::EmuThread() {} + +EmuThread::~EmuThread() = default; + +void EmuThread::run() { + Common::SetCurrentThreadName("EmuControlThread"); + + auto& gpu = QtCommon::system->GPU(); + auto stop_token = m_stop_source.get_token(); + + QtCommon::system->RegisterHostThread(); + + // Main process has been loaded. Make the context current to this thread and begin GPU and CPU + // execution. + gpu.ObtainContext(); + + emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); + if (Settings::values.use_disk_shader_cache.GetValue()) { + QtCommon::system->Renderer().ReadRasterizer()->LoadDiskResources( + QtCommon::system->GetApplicationProcessProgramID(), stop_token, + [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { + emit LoadProgress(stage, value, total); + }); + } + emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); + + gpu.ReleaseContext(); + gpu.Start(); + + QtCommon::system->GetCpuManager().OnGpuReady(); + + if (QtCommon::system->DebuggerEnabled()) { + QtCommon::system->InitializeDebugger(); + } + + while (!stop_token.stop_requested()) { + std::unique_lock lk{m_should_run_mutex}; + if (m_should_run) { + QtCommon::system->Run(); + m_stopped.Reset(); + + m_should_run_cv.wait(lk, stop_token, [&] { + return !m_should_run || m_pending_shader_cache_title.has_value(); + }); + + if (m_should_run && m_pending_shader_cache_title.has_value()) { + const u64 program_id = *m_pending_shader_cache_title; + m_pending_shader_cache_title.reset(); + + lk.unlock(); + this->ReloadDiskShaderCache(program_id); + lk.lock(); + } + } else { + QtCommon::system->Pause(); + m_stopped.Set(); + + EmulationPaused(lk); + m_should_run_cv.wait(lk, stop_token, [&] { return m_should_run; }); + EmulationResumed(lk); + } + } + + // Shutdown the main emulated process + QtCommon::system->DetachDebugger(); + QtCommon::system->ShutdownMainProcess(); +} + +void EmuThread::ReloadDiskShaderCache(u64 program_id) { + if (!Settings::values.use_disk_shader_cache.GetValue()) { + return; + } + + LOG_INFO(Frontend, "Reloading disk shader cache for {:016X}", program_id); + + auto& system = *QtCommon::system; + auto& gpu = system.GPU(); + + system.Pause(); + gpu.WaitForIdle(); + gpu.ObtainContext(); + + emit ShaderCacheReloadStarted(); + emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); + system.Renderer().ReadRasterizer()->LoadDiskResources(program_id, m_stop_source.get_token(), + [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { + emit LoadProgress(stage, value, total); + }); + emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); + emit ShaderCacheReloadFinished(); + + gpu.ReleaseContext(); +} + +// Unlock while emitting signals so that the main thread can +// continue pumping events. + +void EmuThread::EmulationPaused(std::unique_lock& lk) { + lk.unlock(); + emit DebugModeEntered(); + lk.lock(); +} + +void EmuThread::EmulationResumed(std::unique_lock& lk) { + lk.unlock(); + emit DebugModeLeft(); + lk.lock(); +} diff --git a/src/qt_common/render/emu_thread.h b/src/qt_common/render/emu_thread.h new file mode 100644 index 0000000000..f7d0376ac9 --- /dev/null +++ b/src/qt_common/render/emu_thread.h @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include +#include "common/common_types.h" +#include "common/logging.h" +#include "common/thread.h" + +namespace Core { +class System; +} // namespace Core + +namespace VideoCore { +enum class LoadCallbackStage; +} // namespace VideoCore + +class EmuThread final : public QThread { + Q_OBJECT + +public: + explicit EmuThread(); + ~EmuThread() override; + + /** + * Start emulation (on new thread) + * @warning Only call when not running! + */ + void run() override; + + /** + * Sets whether the emulation thread should run or not + * @param should_run Boolean value, set the emulation thread to running if true + */ + void SetRunning(bool should_run) { + // TODO: Prevent other threads from modifying the state until we finish. + { + // Notify the running thread to change state. + std::unique_lock run_lk{m_should_run_mutex}; + m_should_run = should_run; + m_should_run_cv.notify_one(); + } + + // Wait until paused, if pausing. + if (!should_run) { + m_stopped.Wait(); + } + } + + /** + * Check if the emulation thread is running or not + * @return True if the emulation thread is running, otherwise false + */ + bool IsRunning() const { + return m_should_run; + } + + /** + * Requests for the emulation thread to immediately stop running + */ + void ForceStop() { + LOG_WARNING(Frontend, "Force stopping EmuThread"); + m_stop_source.request_stop(); + } + + /** + * Requests that the disk shader cache be reloaded for a different title. + */ + void RequestDiskShaderCacheReload(u64 program_id) { + std::unique_lock run_lk{m_should_run_mutex}; + m_pending_shader_cache_title = program_id; + m_should_run_cv.notify_one(); + } + +private: + void EmulationPaused(std::unique_lock& lk); + void EmulationResumed(std::unique_lock& lk); + void ReloadDiskShaderCache(u64 program_id); + +private: + std::stop_source m_stop_source; + std::mutex m_should_run_mutex; + std::condition_variable_any m_should_run_cv; + Common::Event m_stopped; + bool m_should_run{true}; + std::optional m_pending_shader_cache_title; + +signals: + /** + * Emitted when the CPU has halted execution + * + * @warning When connecting to this signal from other threads, make sure to specify either + * Qt::QueuedConnection (invoke slot within the destination object's message thread) or even + * Qt::BlockingQueuedConnection (additionally block source thread until slot returns) + */ + void DebugModeEntered(); + + /** + * Emitted right before the CPU continues execution + * + * @warning When connecting to this signal from other threads, make sure to specify either + * Qt::QueuedConnection (invoke slot within the destination object's message thread) or even + * Qt::BlockingQueuedConnection (additionally block source thread until slot returns) + */ + void DebugModeLeft(); + + void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total); + + void ShaderCacheReloadStarted(); + void ShaderCacheReloadFinished(); +}; diff --git a/src/qt_common/util/applet.h b/src/qt_common/util/applet.h index f88aa066b2..41294466da 100644 --- a/src/qt_common/util/applet.h +++ b/src/qt_common/util/applet.h @@ -1,9 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef QT_APPLET_UTIL_H -#define QT_APPLET_UTIL_H +#pragma once // TODO namespace QtCommon::Applets {} -#endif // QT_APPLET_UTIL_H diff --git a/src/qt_common/util/content.cpp b/src/qt_common/util/content.cpp index 4d9b324608..2838cd854e 100644 --- a/src/qt_common/util/content.cpp +++ b/src/qt_common/util/content.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +#include "core/file_sys/card_image.h" #include "qt_common/util/content.h" #include "qt_common/util/game.h" @@ -27,9 +28,8 @@ bool CheckGameFirmware(u64 program_id) { !FirmwareManager::CheckFirmwarePresence(*system)) { auto result = QtCommon::Frontend::Warning( tr("Game Requires Firmware"), - tr("The game you are trying to launch requires firmware to boot or to get past the " - "opening menu. Please " - "dump and install firmware, or press \"OK\" to launch anyways."), + tr("This game may require firmware to function properly, and you don't have any installed. " + "Please install firmware before launching, or press \"OK\" to launch anyways."), QtCommon::Frontend::Ok | QtCommon::Frontend::Cancel); return result == QtCommon::Frontend::Ok; @@ -181,15 +181,13 @@ void InstallFirmware(const QString& location, bool recursive) { QString UnzipFirmwareToTmp(const QString& location) { namespace fs = std::filesystem; - fs::path tmp{fs::temp_directory_path()}; - - if (!fs::create_directories(tmp / "eden" / "firmware")) { + fs::path tmp{fs::temp_directory_path() / "eden" / "firmware"}; + std::error_code ec; + fs::remove_all(tmp, ec); + if (!fs::create_directories(tmp, ec)) { return QString(); } - tmp /= "eden"; - tmp /= "firmware"; - QString qCacheDir = QString::fromStdString(tmp.string()); QFile zip(location); @@ -496,5 +494,94 @@ void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir, const std::str }); } -// TODO(crueter): Port InstallFirmware et al. from QML Branch +bool CheckKeys() { + if (!ContentManager::AreKeysPresent()) { + QtCommon::Frontend::Information( + tr("Keys not installed"), + tr("Install decryption keys and restart Eden before attempting to install firmware.")); + return false; + } + + return true; +} + +void InstallFirmware() { + if (!CheckKeys()) + return; + + const QString firmware_source_location = + QtCommon::Frontend::GetExistingDirectory(tr("Select Dumped Firmware Source Location"), {}); + + if (!firmware_source_location.isEmpty()) + QtCommon::Content::InstallFirmware(firmware_source_location, false); +} + +void InstallFirmwareZip() { + if (!CheckKeys()) + return; + + const QString firmware_zip_location = QtCommon::Frontend::GetOpenFileName( + tr("Select Dumped Firmware ZIP"), {}, tr("Zipped Archives (*.zip)")); + + if (firmware_zip_location.isEmpty()) + return; + + const QString qCacheDir = QtCommon::Content::UnzipFirmwareToTmp(firmware_zip_location); + + // In this case, it has to be done recursively, since sometimes people + // will pack it into a subdirectory after dumping + if (!qCacheDir.isEmpty()) { + QtCommon::Content::InstallFirmware(qCacheDir, true); + std::error_code ec; + std::filesystem::remove_all(std::filesystem::temp_directory_path() / "eden" / "firmware", + ec); + + if (ec) { + QtCommon::Frontend::Warning( + tr("Firmware cleanup failed"), + tr("Failed to clean up extracted firmware cache.\n" + "Check write permissions in the system temp directory and try " + "again.\nOS reported error: %1") + .arg(QString::fromStdString(ec.message()))); + } + } +} + +void configureFilesystemProvider(const std::string& filepath) { + // Ensure all NCAs are registered before launching the game + const auto file = QtCommon::vfs->OpenFile(filepath, FileSys::OpenMode::Read); + if (!file) { + return; + } + + auto loader = Loader::GetLoader(*QtCommon::system, file); + if (!loader) { + return; + } + + const auto file_type = loader->GetFileType(); + if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { + return; + } + + u64 program_id = 0; + const auto res2 = loader->ReadProgramId(program_id); + if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { + QtCommon::provider->AddEntry(FileSys::TitleType::Application, + FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), + program_id, file); + } else if (res2 == Loader::ResultStatus::Success && + (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { + const auto nsp = file_type == Loader::FileType::NSP + ? std::make_shared(file) + : FileSys::XCI{file}.GetSecurePartitionNSP(); + for (const auto& title : nsp->GetNCAs()) { + for (const auto& entry : title.second) { + QtCommon::provider->AddEntry(entry.first.first, entry.first.second, title.first, + entry.second->GetBaseFile()); + } + } + } +} + } // namespace QtCommon::Content diff --git a/src/qt_common/util/content.h b/src/qt_common/util/content.h index 20dacf540a..6b0cbb5965 100644 --- a/src/qt_common/util/content.h +++ b/src/qt_common/util/content.h @@ -1,8 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef QT_CONTENT_UTIL_H -#define QT_CONTENT_UTIL_H +#pragma once #include #include "common/common_types.h" @@ -37,9 +36,12 @@ inline const QString GetKeyInstallResultString(FirmwareManager::KeyInstallResult } void InstallFirmware(const QString& location, bool recursive); - QString UnzipFirmwareToTmp(const QString& location); +bool CheckKeys(); +void InstallFirmware(); +void InstallFirmwareZip(); + // Keys // void InstallKeys(); @@ -54,7 +56,9 @@ void ExportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& void ImportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id = "", std::function callback = {}); +// Loader // +void configureFilesystemProvider(const std::string& filepath); + // Profiles // void FixProfiles(); } // namespace QtCommon::Content -#endif // QT_CONTENT_UTIL_H diff --git a/src/qt_common/util/fs.h b/src/qt_common/util/fs.h index 5ab59e2a45..8f42beaaa3 100644 --- a/src/qt_common/util/fs.h +++ b/src/qt_common/util/fs.h @@ -1,12 +1,12 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +#pragma once + #include #include #include "common/common_types.h" -#pragma once - namespace QtCommon::FS { void LinkRyujinx(std::filesystem::path& from, std::filesystem::path& to); diff --git a/src/qt_common/util/game.cpp b/src/qt_common/util/game.cpp index e037fdae6c..ddbe979765 100644 --- a/src/qt_common/util/game.cpp +++ b/src/qt_common/util/game.cpp @@ -11,7 +11,6 @@ #include "qt_common/abstract/frontend.h" #include "qt_common/config/uisettings.h" #include "qt_common/qt_common.h" -#include "yuzu/util/util.h" #include #include @@ -22,6 +21,7 @@ #include #include "common/scope_exit.h" #include "common/string_util.h" +#include "common/fs/file.h" #else #include #include "fmt/ostream.h" @@ -367,6 +367,7 @@ void ResetMetadata(bool show_message) { // Uhhh // +// TODO(crueter): Make QtCommon::Shortcut // Messages in pre-defined message boxes for less code spaghetti inline constexpr bool CreateShortcutMessagesGUI(ShortcutMessages imsg, const QString& game_title) { int result = 0; @@ -533,4 +534,110 @@ void CreateHomeMenuShortcut(ShortcutTarget target) { CreateShortcut(game_path, QLaunchId, "Switch Home Menu", target, "-qlaunch", false); } +bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image) { +#if defined(WIN32) +#pragma pack(push, 2) + struct IconDir { + WORD id_reserved; + WORD id_type; + WORD id_count; + }; + + struct IconDirEntry { + BYTE width; + BYTE height; + BYTE color_count; + BYTE reserved; + WORD planes; + WORD bit_count; + DWORD bytes_in_res; + DWORD image_offset; + }; +#pragma pack(pop) + + const QImage source_image = image.convertToFormat(QImage::Format_RGB32); + constexpr std::array scale_sizes{256, 128, 64, 48, 32, 24, 16}; + constexpr int bytes_per_pixel = 4; + + const IconDir icon_dir{ + .id_reserved = 0, + .id_type = 1, + .id_count = static_cast(scale_sizes.size()), + }; + + Common::FS::IOFile icon_file(icon_path.string(), Common::FS::FileAccessMode::Write, + Common::FS::FileType::BinaryFile); + if (!icon_file.IsOpen()) { + return false; + } + + if (!icon_file.Write(icon_dir)) { + return false; + } + + std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size()); + for (std::size_t i = 0; i < scale_sizes.size(); i++) { + const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel; + const IconDirEntry icon_entry{ + .width = static_cast(scale_sizes[i]), + .height = static_cast(scale_sizes[i]), + .color_count = 0, + .reserved = 0, + .planes = 1, + .bit_count = bytes_per_pixel * 8, + .bytes_in_res = static_cast(sizeof(BITMAPINFOHEADER) + image_size), + .image_offset = static_cast(image_offset), + }; + image_offset += icon_entry.bytes_in_res; + if (!icon_file.Write(icon_entry)) { + return false; + } + } + + for (std::size_t i = 0; i < scale_sizes.size(); i++) { + const QImage scaled_image = source_image.scaled( + scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + const BITMAPINFOHEADER info_header{ + .biSize = sizeof(BITMAPINFOHEADER), + .biWidth = scaled_image.width(), + .biHeight = scaled_image.height() * 2, + .biPlanes = 1, + .biBitCount = bytes_per_pixel * 8, + .biCompression = BI_RGB, + .biSizeImage{}, + .biXPelsPerMeter{}, + .biYPelsPerMeter{}, + .biClrUsed{}, + .biClrImportant{}, + }; + + if (!icon_file.Write(info_header)) { + return false; + } + + for (int y = 0; y < scaled_image.height(); y++) { + const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y); + std::vector line_data(scaled_image.width() * bytes_per_pixel); + std::memcpy(line_data.data(), line, line_data.size()); + if (!icon_file.Write(line_data)) { + return false; + } + } + } + icon_file.Close(); + + return true; +#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"); + } else { + LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string()); + } + return true; +#else + return false; +#endif +} + } // namespace QtCommon::Game diff --git a/src/qt_common/util/game.h b/src/qt_common/util/game.h index 0d7f03fa86..8722574ed7 100644 --- a/src/qt_common/util/game.h +++ b/src/qt_common/util/game.h @@ -1,8 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef QT_GAME_UTIL_H -#define QT_GAME_UTIL_H +#pragma once #include #include @@ -69,6 +68,12 @@ void CreateShortcut(const std::string& game_path, const u64 program_id, std::string GetShortcutPath(ShortcutTarget target); void CreateHomeMenuShortcut(ShortcutTarget target); -} // namespace QtCommon::Game +/** + * Saves a windows icon to a file + * @param path The icons path + * @param image The image to save + * @return bool If the operation succeeded + */ +[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image); -#endif // QT_GAME_UTIL_H +} // namespace QtCommon::Game diff --git a/src/qt_common/util/meta.h b/src/qt_common/util/meta.h index ae6dbc49d8..d408b5759c 100644 --- a/src/qt_common/util/meta.h +++ b/src/qt_common/util/meta.h @@ -1,8 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef QT_META_H -#define QT_META_H +#pragma once #include @@ -12,4 +11,3 @@ namespace QtCommon::Meta { void RegisterMetaTypes(); } // namespace QtCommon::Meta -#endif // QT_META_H diff --git a/src/qt_common/util/mod.cpp b/src/qt_common/util/mod.cpp index 4e7fd54e79..9256d4e142 100644 --- a/src/qt_common/util/mod.cpp +++ b/src/qt_common/util/mod.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include +#include #include #include "frontend_common/mod_manager.h" #include "mod.h" @@ -33,10 +34,13 @@ QStringList GetModFolders(const QString& root, const QString& fallbackName) { std_path = paths[0]; QString default_name; - if (!fallbackName.isEmpty()) - default_name = fallbackName; - else if (!paths.empty()) + + // If this is an atmosphere-packed mod, the default name will end up as the game's title ID. + // So in this case ignore it and use the zip name instead + if (!paths.empty() && std_path.string().find("atmosphere") == std::string::npos) default_name = QString::fromStdString(std_path.filename().string()); + else if (!fallbackName.isEmpty()) + default_name = fallbackName; else default_name = root.split(QLatin1Char('/')).last(); @@ -83,8 +87,9 @@ QStringList GetModFolders(const QString& root, const QString& fallbackName) { // now make a temp directory... const auto mod_dir = fs::temp_directory_path() / "eden" / "mod" / name.toStdString(); const auto tmp = mod_dir / to_make; - fs::remove_all(mod_dir); - if (!fs::create_directories(tmp)) { + std::error_code ec; + fs::remove_all(mod_dir, ec); + if (!fs::create_directories(tmp, ec)) { LOG_ERROR(Frontend, "Failed to create temporary directory {}", tmp.string()); return {}; } @@ -116,12 +121,12 @@ QStringList GetModFolders(const QString& root, const QString& fallbackName) { const QString ExtractMod(const QString& path) { namespace fs = std::filesystem; fs::path tmp{fs::temp_directory_path() / "eden" / "unzip_mod"}; - - fs::remove_all(tmp); - if (!fs::create_directories(tmp)) { + std::error_code ec; + fs::remove_all(tmp, ec); + if (!fs::create_directories(tmp, ec)) { QtCommon::Frontend::Critical(tr("Mod Extract Failed"), - tr("Failed to create temporary directory %1") - .arg(QString::fromStdString(tmp.string()))); + tr("Failed to create temporary directory %1") + .arg(QString::fromStdString(tmp.string()))); return QString(); } diff --git a/src/qt_common/util/path.h b/src/qt_common/util/path.h index aff0009f34..ba77625226 100644 --- a/src/qt_common/util/path.h +++ b/src/qt_common/util/path.h @@ -1,8 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef QT_PATH_UTIL_H -#define QT_PATH_UTIL_H +#pragma once #include #include "common/common_types.h" @@ -10,5 +9,3 @@ namespace QtCommon::Path { bool OpenShaderCache(u64 program_id, QObject* parent); } - -#endif // QT_PATH_UTIL_H diff --git a/src/qt_common/util/rom.h b/src/qt_common/util/rom.h index 5ceadaf46b..6ea5082082 100644 --- a/src/qt_common/util/rom.h +++ b/src/qt_common/util/rom.h @@ -1,16 +1,11 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef QT_ROM_UTIL_H -#define QT_ROM_UTIL_H +#pragma once #include #include "qt_common/qt_common.h" namespace QtCommon::ROM { - -bool RomFSRawCopy(size_t total_size, size_t& read_size, QtProgressCallback callback, - const FileSys::VirtualDir& src, const FileSys::VirtualDir& dest, bool full); - +bool RomFSRawCopy(size_t total_size, size_t& read_size, QtProgressCallback callback, const FileSys::VirtualDir& src, const FileSys::VirtualDir& dest, bool full); } -#endif // QT_ROM_UTIL_H diff --git a/src/yuzu/vk_device_info.cpp b/src/qt_common/util/vk.cpp similarity index 98% rename from src/yuzu/vk_device_info.cpp rename to src/qt_common/util/vk.cpp index 1e8e140d77..8ca7f4a665 100644 --- a/src/yuzu/vk_device_info.cpp +++ b/src/qt_common/util/vk.cpp @@ -11,13 +11,13 @@ #include "common/dynamic_library.h" #include "common/logging.h" +#include "qt_common/util/vk.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_instance.h" #include "video_core/vulkan_common/vulkan_library.h" #include "video_core/vulkan_common/vulkan_surface.h" #include "video_core/vulkan_common/vulkan_wrapper.h" #include "vulkan/vulkan_core.h" -#include "yuzu/vk_device_info.h" class QWindow; diff --git a/src/qt_common/util/vk.h b/src/qt_common/util/vk.h new file mode 100644 index 0000000000..3e6730f643 --- /dev/null +++ b/src/qt_common/util/vk.h @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 + +#pragma once + +#include +#include +#include +#include "common/common_types.h" +#include "common/settings_enums.h" +#include "vulkan/vulkan_core.h" + +static const std::vector default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR, + VK_PRESENT_MODE_FIFO_KHR}; + +// Converts a setting to a present mode (or vice versa) +static inline constexpr VkPresentModeKHR VSyncSettingToMode(Settings::VSyncMode mode) { + switch (mode) { + case Settings::VSyncMode::Immediate: + return VK_PRESENT_MODE_IMMEDIATE_KHR; + case Settings::VSyncMode::Mailbox: + return VK_PRESENT_MODE_MAILBOX_KHR; + case Settings::VSyncMode::Fifo: + return VK_PRESENT_MODE_FIFO_KHR; + case Settings::VSyncMode::FifoRelaxed: + return VK_PRESENT_MODE_FIFO_RELAXED_KHR; + default: + return VK_PRESENT_MODE_FIFO_KHR; + } +} + +static inline constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode) { + switch (mode) { + case VK_PRESENT_MODE_IMMEDIATE_KHR: + return Settings::VSyncMode::Immediate; + case VK_PRESENT_MODE_MAILBOX_KHR: + return Settings::VSyncMode::Mailbox; + case VK_PRESENT_MODE_FIFO_KHR: + return Settings::VSyncMode::Fifo; + case VK_PRESENT_MODE_FIFO_RELAXED_KHR: + return Settings::VSyncMode::FifoRelaxed; + default: + return Settings::VSyncMode::Fifo; + } +} + +class QWindow; + +namespace Settings { +enum class VSyncMode : u32; +} + +namespace VkDeviceInfo { +// Short class to record Vulkan driver information for configuration purposes +class Record { +public: + explicit Record(std::string_view name, const std::vector& vsync_modes, + bool has_broken_compute); + ~Record(); + + const std::string name; + const std::vector vsync_support; + const bool has_broken_compute; +}; + +// TODO(crueter): Port some configure_graphics.cpp stuff +void PopulateRecords(std::vector& records, QWindow* window); +} // namespace VkDeviceInfo diff --git a/src/shader_recompiler/CMakeLists.txt b/src/shader_recompiler/CMakeLists.txt index d02ee268f0..cc9ce95d92 100644 --- a/src/shader_recompiler/CMakeLists.txt +++ b/src/shader_recompiler/CMakeLists.txt @@ -6,55 +6,6 @@ add_library(shader_recompiler STATIC backend/bindings.h - backend/glasm/emit_glasm.cpp - backend/glasm/emit_glasm.h - backend/glasm/emit_glasm_barriers.cpp - backend/glasm/emit_glasm_bitwise_conversion.cpp - backend/glasm/emit_glasm_composite.cpp - backend/glasm/emit_glasm_context_get_set.cpp - backend/glasm/emit_glasm_control_flow.cpp - backend/glasm/emit_glasm_convert.cpp - backend/glasm/emit_glasm_floating_point.cpp - backend/glasm/emit_glasm_image.cpp - backend/glasm/emit_glasm_instructions.h - backend/glasm/emit_glasm_integer.cpp - backend/glasm/emit_glasm_logical.cpp - backend/glasm/emit_glasm_memory.cpp - backend/glasm/emit_glasm_not_implemented.cpp - backend/glasm/emit_glasm_select.cpp - backend/glasm/emit_glasm_shared_memory.cpp - backend/glasm/emit_glasm_special.cpp - backend/glasm/emit_glasm_undefined.cpp - backend/glasm/emit_glasm_warp.cpp - backend/glasm/glasm_emit_context.cpp - backend/glasm/glasm_emit_context.h - backend/glasm/reg_alloc.cpp - backend/glasm/reg_alloc.h - backend/glsl/emit_glsl.cpp - backend/glsl/emit_glsl.h - backend/glsl/emit_glsl_atomic.cpp - backend/glsl/emit_glsl_barriers.cpp - backend/glsl/emit_glsl_bitwise_conversion.cpp - backend/glsl/emit_glsl_composite.cpp - backend/glsl/emit_glsl_context_get_set.cpp - backend/glsl/emit_glsl_control_flow.cpp - backend/glsl/emit_glsl_convert.cpp - backend/glsl/emit_glsl_floating_point.cpp - backend/glsl/emit_glsl_image.cpp - backend/glsl/emit_glsl_instructions.h - backend/glsl/emit_glsl_integer.cpp - backend/glsl/emit_glsl_logical.cpp - backend/glsl/emit_glsl_memory.cpp - backend/glsl/emit_glsl_not_implemented.cpp - backend/glsl/emit_glsl_select.cpp - backend/glsl/emit_glsl_shared_memory.cpp - backend/glsl/emit_glsl_special.cpp - backend/glsl/emit_glsl_undefined.cpp - backend/glsl/emit_glsl_warp.cpp - backend/glsl/glsl_emit_context.cpp - backend/glsl/glsl_emit_context.h - backend/glsl/var_alloc.cpp - backend/glsl/var_alloc.h backend/spirv/emit_spirv.cpp backend/spirv/emit_spirv.h backend/spirv/emit_spirv_atomic.cpp @@ -221,6 +172,7 @@ add_library(shader_recompiler STATIC ir_opt/constant_propagation_pass.cpp ir_opt/dead_code_elimination_pass.cpp ir_opt/dual_vertex_pass.cpp + ir_opt/geometry_compaction_pass.cpp ir_opt/global_memory_to_storage_buffer_pass.cpp ir_opt/identity_removal_pass.cpp ir_opt/layer_pass.cpp @@ -239,9 +191,60 @@ add_library(shader_recompiler STATIC program_header.h runtime_info.h shader_info.h - varying_state.h + varying_state.h) -) +if (ENABLE_OPENGL) + target_sources(shader_recompiler PRIVATE + backend/glasm/emit_glasm.cpp + backend/glasm/emit_glasm.h + backend/glasm/emit_glasm_barriers.cpp + backend/glasm/emit_glasm_bitwise_conversion.cpp + backend/glasm/emit_glasm_composite.cpp + backend/glasm/emit_glasm_context_get_set.cpp + backend/glasm/emit_glasm_control_flow.cpp + backend/glasm/emit_glasm_convert.cpp + backend/glasm/emit_glasm_floating_point.cpp + backend/glasm/emit_glasm_image.cpp + backend/glasm/emit_glasm_instructions.h + backend/glasm/emit_glasm_integer.cpp + backend/glasm/emit_glasm_logical.cpp + backend/glasm/emit_glasm_memory.cpp + backend/glasm/emit_glasm_not_implemented.cpp + backend/glasm/emit_glasm_select.cpp + backend/glasm/emit_glasm_shared_memory.cpp + backend/glasm/emit_glasm_special.cpp + backend/glasm/emit_glasm_undefined.cpp + backend/glasm/emit_glasm_warp.cpp + backend/glasm/glasm_emit_context.cpp + backend/glasm/glasm_emit_context.h + backend/glasm/reg_alloc.cpp + backend/glasm/reg_alloc.h + backend/glsl/emit_glsl.cpp + backend/glsl/emit_glsl.h + backend/glsl/emit_glsl_atomic.cpp + backend/glsl/emit_glsl_barriers.cpp + backend/glsl/emit_glsl_bitwise_conversion.cpp + backend/glsl/emit_glsl_composite.cpp + backend/glsl/emit_glsl_context_get_set.cpp + backend/glsl/emit_glsl_control_flow.cpp + backend/glsl/emit_glsl_convert.cpp + backend/glsl/emit_glsl_floating_point.cpp + backend/glsl/emit_glsl_image.cpp + backend/glsl/emit_glsl_instructions.h + backend/glsl/emit_glsl_integer.cpp + backend/glsl/emit_glsl_logical.cpp + backend/glsl/emit_glsl_memory.cpp + backend/glsl/emit_glsl_not_implemented.cpp + backend/glsl/emit_glsl_select.cpp + backend/glsl/emit_glsl_shared_memory.cpp + backend/glsl/emit_glsl_special.cpp + backend/glsl/emit_glsl_undefined.cpp + backend/glsl/emit_glsl_warp.cpp + backend/glsl/glsl_emit_context.cpp + backend/glsl/glsl_emit_context.h + backend/glsl/var_alloc.cpp + backend/glsl/var_alloc.h) +endif() target_link_libraries(shader_recompiler PUBLIC common fmt::fmt sirit::sirit) @@ -253,7 +256,7 @@ if (MSVC) ) else() target_compile_options(shader_recompiler PRIVATE - $<$:-Werror=conversion> + $<$:-Wconversion> # Bracket depth determines maximum size of a fold expression in Clang since 9c9974c3ccb6. # And this in turns limits the size of a std::array. $<$:-fbracket-depth=1024> diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h index 6572db86dc..6f522ab5c2 100644 --- a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h +++ b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -665,6 +665,8 @@ void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU3 const IR::Value& clamp, const IR::Value& segmentation_mask); void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 index, const IR::Value& clamp, const IR::Value& segmentation_mask); +void EmitQuadBroadcast(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 lane); +void EmitQuadSwap(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 direction); void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a, ScalarF32 op_b, ScalarU32 swizzle); void EmitDPdxFine(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a); diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_warp.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_warp.cpp index fb6a597d4e..7c07ec3247 100644 --- a/src/shader_recompiler/backend/glasm/emit_glasm_warp.cpp +++ b/src/shader_recompiler/backend/glasm/emit_glasm_warp.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -97,6 +100,24 @@ void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, Sca Shuffle(ctx, inst, value, index, clamp, segmentation_mask, "XOR"); } +void EmitQuadBroadcast(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 lane) { + const Register ret{ctx.reg_alloc.Define(inst)}; + ctx.Add("AND.U RC.x,{}.threadid,~3;" + "AND.U RC.y,{},3;" + "OR.U RC.x,RC.x,RC.y;" + "SHFIDX.U {},{},RC.x,0x1C03;" + "MOV.U {}.x,{}.y;", + ctx.stage_name, lane, ret, value, ret, ret); +} + +void EmitQuadSwap(EmitContext& ctx, IR::Inst& inst, ScalarU32 value, ScalarU32 direction) { + const Register ret{ctx.reg_alloc.Define(inst)}; + ctx.Add("ADD.U RC.x,{},1;" + "SHFXOR.U {},{},RC.x,0x1C03;" + "MOV.U {}.x,{}.y;", + direction, ret, value, ret, ret); +} + void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, ScalarF32 op_a, ScalarF32 op_b, ScalarU32 swizzle) { const auto ret{ctx.reg_alloc.Define(inst)}; diff --git a/src/shader_recompiler/backend/glsl/emit_glsl.cpp b/src/shader_recompiler/backend/glsl/emit_glsl.cpp index 66ecfc9f74..8ec180bc42 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl.cpp +++ b/src/shader_recompiler/backend/glsl/emit_glsl.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -28,11 +31,6 @@ struct FuncTraits { using ArgType = std::tuple_element_t>; }; -template -void SetDefinition(EmitContext& ctx, IR::Inst* inst, Args... args) { - inst->SetDefinition(func(ctx, std::forward(args)...)); -} - template auto Arg(EmitContext& ctx, const IR::Value& arg) { if constexpr (std::is_same_v) { @@ -53,21 +51,10 @@ auto Arg(EmitContext& ctx, const IR::Value& arg) { template void Invoke(EmitContext& ctx, IR::Inst* inst, std::index_sequence) { using Traits = FuncTraits; - if constexpr (std::is_same_v) { - if constexpr (is_first_arg_inst) { - SetDefinition( - ctx, inst, *inst, - Arg>(ctx, inst->Arg(I))...); - } else { - SetDefinition( - ctx, inst, Arg>(ctx, inst->Arg(I))...); - } + if constexpr (is_first_arg_inst) { + func(ctx, *inst, Arg>(ctx, inst->Arg(I))...); } else { - if constexpr (is_first_arg_inst) { - func(ctx, *inst, Arg>(ctx, inst->Arg(I))...); - } else { - func(ctx, Arg>(ctx, inst->Arg(I))...); - } + func(ctx, Arg>(ctx, inst->Arg(I))...); } } diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h index d508b84cec..96aac502d5 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h +++ b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -743,6 +743,10 @@ void EmitShuffleDown(EmitContext& ctx, IR::Inst& inst, std::string_view value, void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, std::string_view value, std::string_view index, std::string_view clamp, std::string_view segmentation_mask); +void EmitQuadBroadcast(EmitContext& ctx, IR::Inst& inst, std::string_view value, + std::string_view lane); +void EmitQuadSwap(EmitContext& ctx, IR::Inst& inst, std::string_view value, + std::string_view direction); void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, std::string_view op_a, std::string_view op_b, std::string_view swizzle); void EmitDPdxFine(EmitContext& ctx, IR::Inst& inst, std::string_view op_a); diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp index f9be5de1c0..b2f65550fb 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp +++ b/src/shader_recompiler/backend/glsl/emit_glsl_warp.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -200,6 +203,18 @@ void EmitShuffleButterfly(EmitContext& ctx, IR::Inst& inst, std::string_view val ctx.AddU32("{}=shfl_in_bounds?shfl_result:{};", inst, value); } +void EmitQuadBroadcast(EmitContext& ctx, IR::Inst& inst, std::string_view value, + std::string_view lane) { + const auto src_thread_id{fmt::format("(({}&~3)|({}& 3))", THREAD_ID, lane)}; + ctx.AddU32("{}=readInvocationARB({},{});", inst, value, src_thread_id); +} + +void EmitQuadSwap(EmitContext& ctx, IR::Inst& inst, std::string_view value, + std::string_view direction) { + const auto src_thread_id{fmt::format("({}^({}+1))", THREAD_ID, direction)}; + ctx.AddU32("{}=readInvocationARB({},{});", inst, value, src_thread_id); +} + void EmitFSwizzleAdd(EmitContext& ctx, IR::Inst& inst, std::string_view op_a, std::string_view op_b, std::string_view swizzle) { const auto mask{fmt::format("({}>>((gl_SubGroupInvocationARB&3)<<1))&3", swizzle)}; diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index fc102cadac..a903480c78 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -4,6 +4,7 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include #include @@ -322,6 +323,11 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) { if (ctx.runtime_info.force_early_z) { ctx.AddExecutionMode(main, spv::ExecutionMode::EarlyFragmentTests); } + if (ctx.profile.support_shader_quad_control && program.info.uses_quad_shuffles) { + ctx.AddExtension("SPV_KHR_quad_control"); + ctx.AddCapability(spv::Capability::QuadControlKHR); + ctx.AddExecutionMode(main, spv::ExecutionMode::RequireFullQuadsKHR); + } break; default: throw NotImplementedException("Stage {}", program.stage); @@ -335,7 +341,7 @@ void SetupDenormControl(const Profile& profile, const IR::Program& program, Emit if (info.uses_fp32_denorms_flush && info.uses_fp32_denorms_preserve) { LOG_DEBUG(Shader_SPIRV, "Fp32 denorm flush and preserve on the same shader"); } else if (info.uses_fp32_denorms_flush) { - if (profile.support_fp32_denorm_flush) { + if (profile.support_fp32_denorm_flush && !profile.has_broken_fp32_denorm_flush) { ctx.AddCapability(spv::Capability::DenormFlushToZero); ctx.AddExecutionMode(main_func, spv::ExecutionMode::DenormFlushToZero, 32U); } else { @@ -403,6 +409,9 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct if (info.uses_sampled_1d) { ctx.AddCapability(spv::Capability::Sampled1D); } + if (info.uses_image_1d) { + ctx.AddCapability(spv::Capability::Image1D); + } if (info.uses_sparse_residency) { ctx.AddCapability(spv::Capability::SparseResidency); } @@ -431,15 +440,24 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct ctx.AddCapability(spv::Capability::DrawParameters); } if ((info.uses_subgroup_vote || info.uses_subgroup_invocation_id || - info.uses_subgroup_shuffles) && - profile.support_vote) { + info.uses_subgroup_shuffles || info.uses_subgroup_mask) && + profile.support_vote && profile.SupportsSubgroupStage(ctx.stage)) { ctx.AddCapability(spv::Capability::GroupNonUniformBallot); ctx.AddCapability(spv::Capability::GroupNonUniformShuffle); + if (info.uses_subgroup_shuffles && profile.support_shuffle_relative) { + ctx.AddCapability(spv::Capability::GroupNonUniformShuffleRelative); + } if (!profile.warp_size_potentially_larger_than_guest) { // vote ops are only used when not taking the long path ctx.AddCapability(spv::Capability::GroupNonUniformVote); } } + if (info.uses_quad_shuffles) { + if (profile.support_quad_shuffles) { + ctx.AddCapability(spv::Capability::GroupNonUniformQuad); + } + ctx.AddCapability(spv::Capability::GroupNonUniformShuffle); + } if (info.uses_int64_bit_atomics && profile.support_int64_atomics) { ctx.AddCapability(spv::Capability::Int64Atomics); } @@ -462,6 +480,23 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct ctx.AddCapability(spv::Capability::ImageGatherExtended); ctx.AddCapability(spv::Capability::ImageQuery); ctx.AddCapability(spv::Capability::SampledBuffer); + if (!ctx.non_uniform_ids.empty()) { + if (ctx.profile.supported_spirv < 0x00010500) + ctx.AddExtension("SPV_EXT_descriptor_indexing"); + ctx.AddCapability(spv::Capability::ShaderNonUniform); + if (ctx.uses_nonuniform_sampled_image) { + ctx.AddCapability(spv::Capability::SampledImageArrayNonUniformIndexing); + } + if (ctx.uses_nonuniform_storage_image) { + ctx.AddCapability(spv::Capability::StorageImageArrayNonUniformIndexing); + } + if (ctx.uses_nonuniform_uniform_texel_buffer) { + ctx.AddCapability(spv::Capability::UniformTexelBufferArrayNonUniformIndexing); + } + if (ctx.uses_nonuniform_storage_texel_buffer) { + ctx.AddCapability(spv::Capability::StorageTexelBufferArrayNonUniformIndexing); + } + } } void PatchPhiNodes(IR::Program& program, EmitContext& ctx) { @@ -490,6 +525,29 @@ void PatchPhiNodes(IR::Program& program, EmitContext& ctx) { return { ctx.Def(phi->Arg(phi_arg)), parent }; }); } + +void RewriteOpcodes(std::vector& code, std::span> rewrites) { + if (rewrites.empty()) { + return; + } + size_t offset = 5; + while (offset + 2 < code.size()) { + const u32 word_count{code[offset] >> 16}; + const auto opcode{static_cast(code[offset] & 0xFFFFu)}; + if (word_count == 0) { + return; + } + if (opcode == spv::Op::OpGroupNonUniformShuffleXor || + opcode == spv::Op::OpGroupNonUniformQuadBroadcast) { + const auto it{std::ranges::find(rewrites, code[offset + 2], + &std::pair::first)}; + if (it != rewrites.end()) { + code[offset] = (code[offset] & 0xFFFF0000u) | static_cast(it->second); + } + } + offset += word_count; + } +} } // Anonymous namespace std::vector EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info, IR::Program& program, Bindings& bindings) { @@ -504,7 +562,9 @@ std::vector EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_in SetupCapabilities(profile, program.info, ctx); SetupTransformFeedbackCapabilities(ctx, main); PatchPhiNodes(program, ctx); - return ctx.Assemble(); + std::vector code{ctx.Assemble()}; + RewriteOpcodes(code, ctx.opcode_rewrites); + return code; } Id EmitPhi(EmitContext& ctx, IR::Inst* inst) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp index 0ce73f289b..d1c08c1131 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -15,7 +18,7 @@ Id SharedPointer(EmitContext& ctx, Id offset, u32 index_offset = 0) { if (index_offset > 0) { index = ctx.OpIAdd(ctx.U32[1], index, ctx.Const(index_offset)); } - return ctx.profile.support_explicit_workgroup_layout + return ctx.uses_explicit_workgroup_layout ? ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index) : ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index); } @@ -155,7 +158,7 @@ Id EmitSharedAtomicExchange32(EmitContext& ctx, Id offset, Id value) { } Id EmitSharedAtomicExchange64(EmitContext& ctx, Id offset, Id value) { - if (ctx.profile.support_int64_atomics && ctx.profile.support_explicit_workgroup_layout) { + if (ctx.profile.support_shared_int64_atomics && ctx.uses_explicit_workgroup_layout) { const Id shift_id{ctx.Const(3U)}; const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; const Id pointer{ diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index fb9d3dbbb2..28f9869e22 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -202,7 +202,8 @@ void EmitGetIndirectBranchVariable(EmitContext&) { } Id EmitGetCbufU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8) { + if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8 && + ctx.profile.support_uniform_and_storage_buffer_8bit) { const Id load{GetCbuf(ctx, ctx.U8, &UniformDefinitions::U8, sizeof(u8), binding, offset, ctx.load_const_func_u8)}; return ctx.OpUConvert(ctx.U32[1], load); @@ -219,7 +220,8 @@ Id EmitGetCbufU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& of } Id EmitGetCbufS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8) { + if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int8 && + ctx.profile.support_uniform_and_storage_buffer_8bit) { const Id load{GetCbuf(ctx, ctx.S8, &UniformDefinitions::S8, sizeof(s8), binding, offset, ctx.load_const_func_u8)}; return ctx.OpSConvert(ctx.U32[1], load); @@ -236,7 +238,8 @@ Id EmitGetCbufS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& of } Id EmitGetCbufU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16) { + if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16 && + ctx.profile.support_uniform_and_storage_buffer_16bit) { const Id load{GetCbuf(ctx, ctx.U16, &UniformDefinitions::U16, sizeof(u16), binding, offset, ctx.load_const_func_u16)}; return ctx.OpUConvert(ctx.U32[1], load); @@ -253,7 +256,8 @@ Id EmitGetCbufU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& o } Id EmitGetCbufS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16) { + if (ctx.profile.support_descriptor_aliasing && ctx.profile.support_int16 && + ctx.profile.support_uniform_and_storage_buffer_16bit) { const Id load{GetCbuf(ctx, ctx.S16, &UniformDefinitions::S16, sizeof(s16), binding, offset, ctx.load_const_func_u16)}; return ctx.OpSConvert(ctx.U32[1], load); @@ -620,12 +624,14 @@ Id EmitRenderArea(EmitContext& ctx) { } Id EmitLoadLocal(EmitContext& ctx, Id word_offset) { - const Id pointer{ctx.OpAccessChain(ctx.private_u32, ctx.local_memory, word_offset)}; + const Id pointer{ + ctx.OpAccessChain(ctx.private_u32, ctx.local_memory, word_offset, ctx.Const(0U))}; return ctx.OpLoad(ctx.U32[1], pointer); } void EmitWriteLocal(EmitContext& ctx, Id word_offset, Id value) { - const Id pointer{ctx.OpAccessChain(ctx.private_u32, ctx.local_memory, word_offset)}; + const Id pointer{ + ctx.OpAccessChain(ctx.private_u32, ctx.local_memory, word_offset, ctx.Const(0U))}; ctx.OpStore(pointer, value); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp index 115e9ed4ed..9fa84cceb7 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_convert.cpp @@ -151,7 +151,7 @@ Id EmitConvertU32U64(EmitContext& ctx, Id value) { } Id EmitConvertF16F32(EmitContext& ctx, Id value) { -#ifdef ANDROID +#ifdef __ANDROID__ return ctx.OpFConvert(ctx.F16[1], value); #else const auto result = ctx.OpFConvert(ctx.F16[1], value); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index 62a82dce9d..5d4983b9f7 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -14,38 +14,59 @@ namespace Shader::Backend::SPIRV { namespace { -class DescriptorIndex { -public: - explicit DescriptorIndex(EmitContext& ctx, const IR::Value& index) - : id{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)}, - is_non_uniform{ctx.profile.support_sampled_image_array_nonuniform_indexing && - !index.IsImmediate()} { - if (!is_non_uniform) { - return; - } - if (ctx.profile.supported_spirv < 0x00010400) { - ctx.AddExtension("SPV_EXT_descriptor_indexing"); - } - ctx.AddCapability(spv::Capability::ShaderNonUniform); - ctx.AddCapability(spv::Capability::SampledImageArrayNonUniformIndexing); - Decorate(ctx, id); - } - Id Value() const { - return id; - } - - void Decorate(EmitContext& ctx, Id object) const { - if (is_non_uniform) { - ctx.Decorate(object, spv::Decoration::NonUniform); - } - } - -private: - Id id; - bool is_non_uniform; +enum class NonUniformKind { + SampledImage, + StorageImage, + UniformTexelBuffer, + StorageTexelBuffer, }; +[[nodiscard]] bool IsNonUniformSupported(const Profile& profile, NonUniformKind kind) noexcept { + switch (kind) { + case NonUniformKind::SampledImage: + return profile.support_sampled_image_array_nonuniform_indexing; + case NonUniformKind::StorageImage: + return profile.support_storage_image_array_nonuniform_indexing; + case NonUniformKind::UniformTexelBuffer: + return profile.support_uniform_texel_buffer_array_nonuniform_indexing; + case NonUniformKind::StorageTexelBuffer: + return profile.support_storage_texel_buffer_array_nonuniform_indexing; + } + return false; +} + +void DecorateNonUniform(EmitContext& ctx, Id object) { + if (ctx.non_uniform_ids.contains(object.value)) { + return; + } + ctx.Decorate(object, spv::Decoration::NonUniform); + ctx.non_uniform_ids.insert(object.value); +} + +[[nodiscard]] bool MarkNonUniform(EmitContext& ctx, Id idx, const IR::Value& index, + NonUniformKind kind) { + if (index.IsImmediate() || !IsNonUniformSupported(ctx.profile, kind)) { + return false; + } + DecorateNonUniform(ctx, idx); + switch (kind) { + case NonUniformKind::SampledImage: + ctx.uses_nonuniform_sampled_image = true; + break; + case NonUniformKind::StorageImage: + ctx.uses_nonuniform_storage_image = true; + break; + case NonUniformKind::UniformTexelBuffer: + ctx.uses_nonuniform_uniform_texel_buffer = true; + break; + case NonUniformKind::StorageTexelBuffer: + ctx.uses_nonuniform_storage_texel_buffer = true; + break; + } + return true; +} + class ImageOperands { public: [[maybe_unused]] static constexpr bool ImageSampleOffsetAllowed = false; @@ -221,11 +242,14 @@ private: Id Texture(EmitContext& ctx, IR::TextureInstInfo info, [[maybe_unused]] const IR::Value& index) { const TextureDefinition& def{ctx.textures.at(info.descriptor_index)}; if (def.count > 1) { - const DescriptorIndex idx{ctx, index}; - const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, idx.Value())}; - idx.Decorate(ctx, pointer); + auto const idx = index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index); + const bool non_uniform{MarkNonUniform(ctx, idx, index, NonUniformKind::SampledImage)}; + const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, idx)}; const Id object{ctx.OpLoad(def.sampled_type, pointer)}; - idx.Decorate(ctx, object); + if (non_uniform) { + DecorateNonUniform(ctx, pointer); + DecorateNonUniform(ctx, object); + } return object; } else { return ctx.OpLoad(def.sampled_type, def.id); @@ -237,20 +261,30 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind const TextureBufferDefinition& def{ctx.texture_buffers.at(info.descriptor_index)}; if (def.count > 1) { const Id idx{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)}; + const bool non_uniform{ + MarkNonUniform(ctx, idx, index, NonUniformKind::UniformTexelBuffer)}; const Id ptr{ctx.OpAccessChain(ctx.image_buffer_type, def.id, idx)}; - return ctx.OpLoad(ctx.image_buffer_type, ptr); + const Id object{ctx.OpLoad(ctx.image_buffer_type, ptr)}; + if (non_uniform) { + DecorateNonUniform(ctx, ptr); + DecorateNonUniform(ctx, object); + } + return object; } return ctx.OpLoad(ctx.image_buffer_type, def.id); } else { const TextureDefinition& def{ctx.textures.at(info.descriptor_index)}; if (def.count > 1) { - const DescriptorIndex idx{ctx, index}; - const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx.Value())}; - idx.Decorate(ctx, ptr); - const Id object{ctx.OpLoad(def.sampled_type, ptr)}; - idx.Decorate(ctx, object); - const Id image{ctx.OpImage(def.image_type, object)}; - idx.Decorate(ctx, image); + auto const idx = index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index); + const bool non_uniform{MarkNonUniform(ctx, idx, index, NonUniformKind::SampledImage)}; + const Id ptr = ctx.OpAccessChain(def.pointer_type, def.id, idx); + const Id object = ctx.OpLoad(def.sampled_type, ptr); + const Id image = ctx.OpImage(def.image_type, object); + if (non_uniform) { + DecorateNonUniform(ctx, ptr); + DecorateNonUniform(ctx, object); + DecorateNonUniform(ctx, image); + } return image; } return ctx.OpImage(def.image_type, ctx.OpLoad(def.sampled_type, def.id)); @@ -262,16 +296,29 @@ std::pair Image(EmitContext& ctx, const IR::Value& index, IR::TextureI const ImageBufferDefinition def{ctx.image_buffers.at(info.descriptor_index)}; if (def.count > 1) { const Id idx{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)}; + const bool non_uniform{ + MarkNonUniform(ctx, idx, index, NonUniformKind::StorageTexelBuffer)}; const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx)}; - return {ctx.OpLoad(def.image_type, ptr), def.is_integer}; + const Id image{ctx.OpLoad(def.image_type, ptr)}; + if (non_uniform) { + DecorateNonUniform(ctx, ptr); + DecorateNonUniform(ctx, image); + } + return {image, def.is_integer}; } return {ctx.OpLoad(def.image_type, def.id), def.is_integer}; } else { const ImageDefinition def{ctx.images.at(info.descriptor_index)}; if (def.count > 1) { const Id idx{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)}; + const bool non_uniform{MarkNonUniform(ctx, idx, index, NonUniformKind::StorageImage)}; const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx)}; - return {ctx.OpLoad(def.image_type, ptr), def.is_integer}; + const Id image{ctx.OpLoad(def.image_type, ptr)}; + if (non_uniform) { + DecorateNonUniform(ctx, ptr); + DecorateNonUniform(ctx, image); + } + return {image, def.is_integer}; } return {ctx.OpLoad(def.image_type, def.id), def.is_integer}; } @@ -284,6 +331,13 @@ bool IsTextureMsaa(EmitContext& ctx, const IR::TextureInstInfo& info) { return ctx.textures.at(info.descriptor_index).is_multisample; } +bool IsTextureInteger(EmitContext& ctx, const IR::TextureInstInfo& info) { + if (info.type == TextureType::Buffer) { + return false; + } + return ctx.textures.at(info.descriptor_index).is_integer; +} + Id Decorate(EmitContext& ctx, IR::Inst* inst, Id sample) { const auto info{inst->Flags()}; if (info.relaxed_precision != 0) { @@ -504,11 +558,14 @@ Id EmitBoundImageWrite(EmitContext&) { Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id bias_lc, const IR::Value& offset) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; + Id color; if (ctx.stage == Stage::Fragment) { const ImageOperands operands(ctx, info.has_bias != 0, false, info.has_lod_clamp != 0, bias_lc, offset); - return Emit(&EmitContext::OpImageSparseSampleImplicitLod, - &EmitContext::OpImageSampleImplicitLod, ctx, inst, ctx.F32[4], + color = Emit(&EmitContext::OpImageSparseSampleImplicitLod, + &EmitContext::OpImageSampleImplicitLod, ctx, inst, result_type, Texture(ctx, info, index), coords, operands.MaskOptional(), operands.Span()); } else { // We can't use implicit lods on non-fragment stages on SPIR-V. Maxwell hardware behaves as @@ -516,26 +573,29 @@ Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& // derivatives const Id lod{ctx.Const(0.0f)}; const ImageOperands operands(ctx, false, true, info.has_lod_clamp != 0, lod, offset); - return Emit(&EmitContext::OpImageSparseSampleExplicitLod, - &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], + color = Emit(&EmitContext::OpImageSparseSampleExplicitLod, + &EmitContext::OpImageSampleExplicitLod, ctx, inst, result_type, Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); } + return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color; } Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id lod, const IR::Value& offset) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; const ImageOperands operands(ctx, false, true, false, lod, offset); Id result = Emit(&EmitContext::OpImageSparseSampleExplicitLod, - &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], + &EmitContext::OpImageSampleExplicitLod, ctx, inst, result_type, Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); -#ifdef ANDROID - if (Settings::values.fix_bloom_effects.GetValue()) { +#ifdef __ANDROID__ + if (!is_integer && Settings::values.fix_bloom_effects.GetValue()) { result = ctx.OpVectorTimesScalar(ctx.F32[4], result, ctx.Const(0.98f)); } #endif - return result; + return is_integer ? ctx.OpBitcast(ctx.F32[4], result) : result; } Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, @@ -571,30 +631,39 @@ Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Va Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, const IR::Value& offset, const IR::Value& offset2) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; const ImageOperands operands(ctx, offset, offset2); if (ctx.profile.need_gather_subpixel_offset) { coords = ImageGatherSubpixelOffset(ctx, info, TextureImage(ctx, info, index), coords); } - return Emit(&EmitContext::OpImageSparseGather, &EmitContext::OpImageGather, ctx, inst, - ctx.F32[4], Texture(ctx, info, index), coords, ctx.Const(info.gather_component), - operands.MaskOptional(), operands.Span()); + const Id color{Emit(&EmitContext::OpImageSparseGather, &EmitContext::OpImageGather, ctx, inst, + result_type, Texture(ctx, info, index), coords, + ctx.Const(info.gather_component), operands.MaskOptional(), + operands.Span())}; + return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color; } Id EmitImageGatherDref(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, const IR::Value& offset, const IR::Value& offset2, Id dref) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; const ImageOperands operands(ctx, offset, offset2); if (ctx.profile.need_gather_subpixel_offset) { coords = ImageGatherSubpixelOffset(ctx, info, TextureImage(ctx, info, index), coords); } - return Emit(&EmitContext::OpImageSparseDrefGather, &EmitContext::OpImageDrefGather, ctx, inst, - ctx.F32[4], Texture(ctx, info, index), coords, dref, operands.MaskOptional(), - operands.Span()); + const Id color{Emit(&EmitContext::OpImageSparseDrefGather, &EmitContext::OpImageDrefGather, + ctx, inst, result_type, Texture(ctx, info, index), coords, dref, + operands.MaskOptional(), operands.Span())}; + return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color; } Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id offset, Id lod, Id ms) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; AddOffsetToCoordinates(ctx, info, coords, offset); if (info.type == TextureType::Buffer) { lod = Id{}; @@ -604,8 +673,10 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id c lod = Id{}; } const ImageOperands operands(lod, ms); - return Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, ctx.F32[4], - TextureImage(ctx, info, index), coords, operands.MaskOptional(), operands.Span()); + const Id color{Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, + result_type, TextureImage(ctx, info, index), coords, + operands.MaskOptional(), operands.Span())}; + return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color; } Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id lod, @@ -650,14 +721,17 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, I Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords, Id derivatives, const IR::Value& offset, Id lod_clamp) { const auto info{inst->Flags()}; + const bool is_integer{IsTextureInteger(ctx, info)}; + const Id result_type{is_integer ? ctx.U32[4] : ctx.F32[4]}; const auto operands = info.num_derivatives == 3 ? ImageOperands(ctx, info.has_lod_clamp != 0, derivatives, ctx.Def(offset), {}, lod_clamp) : ImageOperands(ctx, info.has_lod_clamp != 0, derivatives, info.num_derivatives, offset, lod_clamp); - return Emit(&EmitContext::OpImageSparseSampleExplicitLod, - &EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4], - Texture(ctx, info, index), coords, operands.Mask(), operands.Span()); + const Id color{Emit(&EmitContext::OpImageSparseSampleExplicitLod, + &EmitContext::OpImageSampleExplicitLod, ctx, inst, result_type, + Texture(ctx, info, index), coords, operands.Mask(), operands.Span())}; + return is_integer ? ctx.OpBitcast(ctx.F32[4], color) : color; } Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 171977c85c..165ec3fe84 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -622,6 +622,8 @@ Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clam Id segmentation_mask); Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp, Id segmentation_mask); +Id EmitQuadBroadcast(EmitContext& ctx, Id value, Id lane); +Id EmitQuadSwap(EmitContext& ctx, Id value, Id direction); Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle); Id EmitDPdxFine(EmitContext& ctx, Id op_a); Id EmitDPdyFine(EmitContext& ctx, Id op_a); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp index bdcbccfde9..bef4421fed 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_memory.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -156,7 +159,8 @@ void EmitWriteGlobal128(EmitContext& ctx, Id address, Id value) { } Id EmitLoadStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_int8 && ctx.profile.support_descriptor_aliasing) { + if (ctx.profile.support_int8 && ctx.profile.support_storage_buffer_8bit && + ctx.profile.support_descriptor_aliasing) { return ctx.OpUConvert(ctx.U32[1], LoadStorage(ctx, binding, offset, ctx.U8, ctx.storage_types.U8, sizeof(u8), &StorageDefinitions::U8)); @@ -167,7 +171,8 @@ Id EmitLoadStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Value } Id EmitLoadStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_int8 && ctx.profile.support_descriptor_aliasing) { + if (ctx.profile.support_int8 && ctx.profile.support_storage_buffer_8bit && + ctx.profile.support_descriptor_aliasing) { return ctx.OpSConvert(ctx.U32[1], LoadStorage(ctx, binding, offset, ctx.S8, ctx.storage_types.S8, sizeof(s8), &StorageDefinitions::S8)); @@ -178,7 +183,8 @@ Id EmitLoadStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Value } Id EmitLoadStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_int16 && ctx.profile.support_descriptor_aliasing) { + if (ctx.profile.support_int16 && ctx.profile.support_storage_buffer_16bit && + ctx.profile.support_descriptor_aliasing) { return ctx.OpUConvert(ctx.U32[1], LoadStorage(ctx, binding, offset, ctx.U16, ctx.storage_types.U16, sizeof(u16), &StorageDefinitions::U16)); @@ -189,7 +195,8 @@ Id EmitLoadStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::Valu } Id EmitLoadStorageS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset) { - if (ctx.profile.support_int16 && ctx.profile.support_descriptor_aliasing) { + if (ctx.profile.support_int16 && ctx.profile.support_storage_buffer_16bit && + ctx.profile.support_descriptor_aliasing) { return ctx.OpSConvert(ctx.U32[1], LoadStorage(ctx, binding, offset, ctx.S16, ctx.storage_types.S16, sizeof(s16), &StorageDefinitions::S16)); @@ -227,7 +234,8 @@ Id EmitLoadStorage128(EmitContext& ctx, const IR::Value& binding, const IR::Valu void EmitWriteStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, Id value) { - if (ctx.profile.support_int8) { + if (ctx.profile.support_int8 && ctx.profile.support_storage_buffer_8bit && + ctx.profile.support_descriptor_aliasing) { WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U8, value), ctx.storage_types.U8, sizeof(u8), &StorageDefinitions::U8); } else { @@ -237,7 +245,8 @@ void EmitWriteStorageU8(EmitContext& ctx, const IR::Value& binding, const IR::Va void EmitWriteStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, Id value) { - if (ctx.profile.support_int8) { + if (ctx.profile.support_int8 && ctx.profile.support_storage_buffer_8bit && + ctx.profile.support_descriptor_aliasing) { WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S8, value), ctx.storage_types.S8, sizeof(s8), &StorageDefinitions::S8); } else { @@ -247,7 +256,8 @@ void EmitWriteStorageS8(EmitContext& ctx, const IR::Value& binding, const IR::Va void EmitWriteStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, Id value) { - if (ctx.profile.support_int16) { + if (ctx.profile.support_int16 && ctx.profile.support_storage_buffer_16bit && + ctx.profile.support_descriptor_aliasing) { WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.U16, value), ctx.storage_types.U16, sizeof(u16), &StorageDefinitions::U16); } else { @@ -257,7 +267,8 @@ void EmitWriteStorageU16(EmitContext& ctx, const IR::Value& binding, const IR::V void EmitWriteStorageS16(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset, Id value) { - if (ctx.profile.support_int16) { + if (ctx.profile.support_int16 && ctx.profile.support_storage_buffer_16bit && + ctx.profile.support_descriptor_aliasing) { WriteStorage(ctx, binding, offset, ctx.OpSConvert(ctx.S16, value), ctx.storage_types.S16, sizeof(s16), &StorageDefinitions::S16); } else { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp index df05dad74a..dc398aa296 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -28,7 +31,7 @@ std::pair ExtractArgs(EmitContext& ctx, Id offset, u32 mask, u32 count) } // Anonymous namespace Id EmitLoadSharedU8(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{ ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, pointer)); @@ -39,7 +42,7 @@ Id EmitLoadSharedU8(EmitContext& ctx, Id offset) { } Id EmitLoadSharedS8(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{ ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, pointer)); @@ -50,7 +53,7 @@ Id EmitLoadSharedS8(EmitContext& ctx, Id offset) { } Id EmitLoadSharedU16(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer)); } else { @@ -60,7 +63,7 @@ Id EmitLoadSharedU16(EmitContext& ctx, Id offset) { } Id EmitLoadSharedS16(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer)); } else { @@ -70,7 +73,7 @@ Id EmitLoadSharedS16(EmitContext& ctx, Id offset) { } Id EmitLoadSharedU32(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2)}; return ctx.OpLoad(ctx.U32[1], pointer); } else { @@ -79,7 +82,7 @@ Id EmitLoadSharedU32(EmitContext& ctx, Id offset) { } Id EmitLoadSharedU64(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)}; return ctx.OpLoad(ctx.U32[2], pointer); } else { @@ -94,7 +97,7 @@ Id EmitLoadSharedU64(EmitContext& ctx, Id offset) { } Id EmitLoadSharedU128(EmitContext& ctx, Id offset) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)}; return ctx.OpLoad(ctx.U32[4], pointer); } @@ -110,7 +113,7 @@ Id EmitLoadSharedU128(EmitContext& ctx, Id offset) { } void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{ ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)}; ctx.OpStore(pointer, ctx.OpUConvert(ctx.U8, value)); @@ -120,7 +123,7 @@ void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) { } void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)}; ctx.OpStore(pointer, ctx.OpUConvert(ctx.U16, value)); } else { @@ -130,7 +133,7 @@ void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) { void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) { Id pointer{}; - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { pointer = Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2); } else { const Id shift{ctx.Const(2U)}; @@ -141,7 +144,7 @@ void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) { } void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)}; ctx.OpStore(pointer, value); return; @@ -156,7 +159,7 @@ void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) { } void EmitWriteSharedU128(EmitContext& ctx, Id offset, Id value) { - if (ctx.profile.support_explicit_workgroup_layout) { + if (ctx.uses_explicit_workgroup_layout) { const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)}; ctx.OpStore(pointer, value); return; diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp index 77ff8c5731..8d83509961 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -10,7 +13,14 @@ Id SubgroupScope(EmitContext& ctx) { return ctx.Const(static_cast(spv::Scope::Subgroup)); } +bool StageSupportsSubgroups(EmitContext& ctx) { + return ctx.profile.SupportsSubgroupStage(ctx.stage); +} + Id GetThreadId(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.u32_zero_value; + } return ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id); } @@ -67,17 +77,57 @@ Id GetMaxThreadId(EmitContext& ctx, Id thread_id, Id clamp, Id segmentation_mask return ComputeMaxThreadId(ctx, min_thread_id, clamp, not_seg_mask); } -Id SelectValue(EmitContext& ctx, Id in_range, Id value, Id src_thread_id) { - return ctx.OpSelect( - ctx.U32[1], in_range, - ctx.OpGroupNonUniformShuffle(ctx.U32[1], SubgroupScope(ctx), value, src_thread_id), value); -} - -Id AddPartitionBase(EmitContext& ctx, Id thread_id) { +Id HostThreadId(EmitContext& ctx, Id thread_id) { + if (!ctx.profile.warp_size_potentially_larger_than_guest) { + return thread_id; + } const Id partition_idx{ctx.OpShiftRightLogical(ctx.U32[1], GetThreadId(ctx), ctx.Const(5u))}; const Id partition_base{ctx.OpShiftLeftLogical(ctx.U32[1], partition_idx, ctx.Const(5u))}; return ctx.OpIAdd(ctx.U32[1], thread_id, partition_base); } + +Id GuestLane(EmitContext& ctx, Id index) { + return ctx.OpBitwiseAnd(ctx.U32[1], index, ctx.Const(31U)); +} + +Id ShuffleAbsolute(EmitContext& ctx, Id value, Id src_thread_id) { + if (!ctx.profile.has_broken_spirv_subgroup_shuffle) { + return ctx.OpGroupNonUniformShuffle(ctx.U32[1], SubgroupScope(ctx), value, src_thread_id); + } + Id result{ctx.u32_zero_value}; + for (u32 lane = 0; lane < ctx.profile.max_subgroup_size; ++lane) { + const Id read{ + ctx.OpGroupNonUniformBroadcast(ctx.U32[1], SubgroupScope(ctx), value, ctx.Const(lane))}; + const Id matches{ctx.OpIEqual(ctx.U1, src_thread_id, ctx.Const(lane))}; + result = ctx.OpSelect(ctx.U32[1], matches, read, result); + } + return result; +} + +Id ShuffleRelative(EmitContext& ctx, Id value, Id delta, Id src_thread_id, spv::Op op) { + if (!ctx.profile.support_shuffle_relative) { + return ShuffleAbsolute(ctx, value, HostThreadId(ctx, src_thread_id)); + } + const Id result{ctx.OpGroupNonUniformShuffleXor(ctx.U32[1], SubgroupScope(ctx), value, delta)}; + ctx.opcode_rewrites.emplace_back(result.value, op); + return result; +} + +Id BroadcastLane(EmitContext& ctx, Id value, u32 lane) { + Id result{ + ctx.OpGroupNonUniformBroadcast(ctx.U32[1], SubgroupScope(ctx), value, ctx.Const(lane))}; + if (!ctx.profile.warp_size_potentially_larger_than_guest) { + return result; + } + const Id partition_idx{ctx.OpShiftRightLogical(ctx.U32[1], GetThreadId(ctx), ctx.Const(5u))}; + for (u32 base = 32; base < ctx.profile.max_subgroup_size; base += 32) { + const Id read{ctx.OpGroupNonUniformBroadcast(ctx.U32[1], SubgroupScope(ctx), value, + ctx.Const(base + lane))}; + const Id matches{ctx.OpIEqual(ctx.U1, partition_idx, ctx.Const(base >> 5))}; + result = ctx.OpSelect(ctx.U32[1], matches, read, result); + } + return result; +} } // Anonymous namespace Id EmitLaneId(EmitContext& ctx) { @@ -89,6 +139,9 @@ Id EmitLaneId(EmitContext& ctx) { } Id EmitVoteAll(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return pred; + } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAll(ctx.U1, SubgroupScope(ctx), pred); } @@ -102,6 +155,9 @@ Id EmitVoteAll(EmitContext& ctx, Id pred) { } Id EmitVoteAny(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return pred; + } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAny(ctx.U1, SubgroupScope(ctx), pred); } @@ -115,6 +171,9 @@ Id EmitVoteAny(EmitContext& ctx, Id pred) { } Id EmitVoteEqual(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.true_value; + } if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpGroupNonUniformAllEqual(ctx.U1, SubgroupScope(ctx), pred); } @@ -129,6 +188,9 @@ Id EmitVoteEqual(EmitContext& ctx, Id pred) { } Id EmitSubgroupBallot(EmitContext& ctx, Id pred) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.OpSelect(ctx.U32[1], pred, ctx.Const(1u), ctx.u32_zero_value); + } const Id ballot{ctx.OpGroupNonUniformBallot(ctx.U32[4], SubgroupScope(ctx), pred)}; if (!ctx.profile.warp_size_potentially_larger_than_guest) { return ctx.OpCompositeExtract(ctx.U32[1], ballot, 0U); @@ -137,22 +199,37 @@ Id EmitSubgroupBallot(EmitContext& ctx, Id pred) { } Id EmitSubgroupEqMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.Const(1u); + } return LoadMask(ctx, ctx.subgroup_mask_eq); } Id EmitSubgroupLtMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.u32_zero_value; + } return LoadMask(ctx, ctx.subgroup_mask_lt); } Id EmitSubgroupLeMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.Const(1u); + } return LoadMask(ctx, ctx.subgroup_mask_le); } Id EmitSubgroupGtMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.u32_zero_value; + } return LoadMask(ctx, ctx.subgroup_mask_gt); } Id EmitSubgroupGeMask(EmitContext& ctx) { + if (!StageSupportsSubgroups(ctx)) { + return ctx.Const(1u); + } return LoadMask(ctx, ctx.subgroup_mask_ge); } @@ -163,66 +240,101 @@ Id EmitShuffleIndex(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id cla const Id min_thread_id{ComputeMinThreadId(ctx, thread_id, segmentation_mask)}; const Id max_thread_id{ComputeMaxThreadId(ctx, min_thread_id, clamp, not_seg_mask)}; - const Id lhs{ctx.OpBitwiseAnd(ctx.U32[1], index, not_seg_mask)}; - Id src_thread_id{ctx.OpBitwiseOr(ctx.U32[1], lhs, min_thread_id)}; + const Id lhs{ctx.OpBitwiseAnd(ctx.U32[1], GuestLane(ctx, index), not_seg_mask)}; + const Id src_thread_id{ctx.OpBitwiseOr(ctx.U32[1], lhs, min_thread_id)}; const Id in_range{ctx.OpSLessThanEqual(ctx.U1, src_thread_id, max_thread_id)}; - if (ctx.profile.warp_size_potentially_larger_than_guest) { - src_thread_id = AddPartitionBase(ctx, src_thread_id); - } - SetInBoundsFlag(inst, in_range); - return SelectValue(ctx, in_range, value, src_thread_id); + if (!StageSupportsSubgroups(ctx)) { + return value; + } + const IR::Value lane{inst->Arg(1).Resolve()}; + const IR::Value segment{inst->Arg(3).Resolve()}; + if (lane.IsImmediate() && segment.IsImmediate() && segment.U32() == 0) { + return ctx.OpSelect(ctx.U32[1], in_range, BroadcastLane(ctx, value, lane.U32() & 31), + value); + } + const Id shuffled{ShuffleAbsolute(ctx, value, HostThreadId(ctx, src_thread_id))}; + return ctx.OpSelect(ctx.U32[1], in_range, shuffled, value); } Id EmitShuffleUp(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp, Id segmentation_mask) { + if (!StageSupportsSubgroups(ctx)) { + SetInBoundsFlag(inst, ctx.false_value); + return value; + } + const Id delta{GuestLane(ctx, index)}; const Id thread_id{EmitLaneId(ctx)}; const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)}; - Id src_thread_id{ctx.OpISub(ctx.U32[1], thread_id, index)}; + const Id src_thread_id{ctx.OpISub(ctx.U32[1], thread_id, delta)}; const Id in_range{ctx.OpSGreaterThanEqual(ctx.U1, src_thread_id, max_thread_id)}; - if (ctx.profile.warp_size_potentially_larger_than_guest) { - src_thread_id = AddPartitionBase(ctx, src_thread_id); - } - SetInBoundsFlag(inst, in_range); - return SelectValue(ctx, in_range, value, src_thread_id); + const Id shuffled{ShuffleRelative(ctx, value, delta, src_thread_id, + spv::Op::OpGroupNonUniformShuffleUp)}; + return ctx.OpSelect(ctx.U32[1], in_range, shuffled, value); } Id EmitShuffleDown(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp, Id segmentation_mask) { + if (!StageSupportsSubgroups(ctx)) { + SetInBoundsFlag(inst, ctx.false_value); + return value; + } + const Id delta{GuestLane(ctx, index)}; const Id thread_id{EmitLaneId(ctx)}; const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)}; - Id src_thread_id{ctx.OpIAdd(ctx.U32[1], thread_id, index)}; + const Id src_thread_id{ctx.OpIAdd(ctx.U32[1], thread_id, delta)}; const Id in_range{ctx.OpSLessThanEqual(ctx.U1, src_thread_id, max_thread_id)}; - if (ctx.profile.warp_size_potentially_larger_than_guest) { - src_thread_id = AddPartitionBase(ctx, src_thread_id); - } - SetInBoundsFlag(inst, in_range); - return SelectValue(ctx, in_range, value, src_thread_id); + const Id shuffled{ShuffleRelative(ctx, value, delta, src_thread_id, + spv::Op::OpGroupNonUniformShuffleDown)}; + return ctx.OpSelect(ctx.U32[1], in_range, shuffled, value); } Id EmitShuffleButterfly(EmitContext& ctx, IR::Inst* inst, Id value, Id index, Id clamp, Id segmentation_mask) { + if (!StageSupportsSubgroups(ctx)) { + SetInBoundsFlag(inst, ctx.false_value); + return value; + } + const Id mask{GuestLane(ctx, index)}; const Id thread_id{EmitLaneId(ctx)}; const Id max_thread_id{GetMaxThreadId(ctx, thread_id, clamp, segmentation_mask)}; - Id src_thread_id{ctx.OpBitwiseXor(ctx.U32[1], thread_id, index)}; + const Id src_thread_id{ctx.OpBitwiseXor(ctx.U32[1], thread_id, mask)}; const Id in_range{ctx.OpSLessThanEqual(ctx.U1, src_thread_id, max_thread_id)}; - if (ctx.profile.warp_size_potentially_larger_than_guest) { - src_thread_id = AddPartitionBase(ctx, src_thread_id); - } - SetInBoundsFlag(inst, in_range); - return SelectValue(ctx, in_range, value, src_thread_id); + const Id shuffled{ctx.OpGroupNonUniformShuffleXor(ctx.U32[1], SubgroupScope(ctx), value, mask)}; + return ctx.OpSelect(ctx.U32[1], in_range, shuffled, value); +} + +Id EmitQuadBroadcast(EmitContext& ctx, Id value, Id lane) { + if (ctx.profile.support_quad_shuffles) { + return ctx.OpGroupNonUniformQuadBroadcast(ctx.U32[1], SubgroupScope(ctx), value, lane); + } + const Id base{ctx.OpBitwiseAnd(ctx.U32[1], GetThreadId(ctx), ctx.Const(~3u))}; + const Id local_lane{ctx.OpBitwiseAnd(ctx.U32[1], lane, ctx.Const(3u))}; + const Id src_thread_id{ctx.OpBitwiseOr(ctx.U32[1], base, local_lane)}; + return ShuffleAbsolute(ctx, value, src_thread_id); +} + +Id EmitQuadSwap(EmitContext& ctx, Id value, Id direction) { + if (ctx.profile.support_quad_shuffles) { + const Id result{ + ctx.OpGroupNonUniformQuadBroadcast(ctx.U32[1], SubgroupScope(ctx), value, direction)}; + ctx.opcode_rewrites.emplace_back(result.value, spv::Op::OpGroupNonUniformQuadSwap); + return result; + } + const Id xor_mask{ctx.OpIAdd(ctx.U32[1], direction, ctx.Const(1u))}; + return ctx.OpGroupNonUniformShuffleXor(ctx.U32[1], SubgroupScope(ctx), value, xor_mask); } Id EmitFSwizzleAdd(EmitContext& ctx, Id op_a, Id op_b, Id swizzle) { const Id three{ctx.Const(3U)}; - Id mask{ctx.OpLoad(ctx.U32[1], ctx.subgroup_local_invocation_id)}; + Id mask{GetThreadId(ctx)}; mask = ctx.OpBitwiseAnd(ctx.U32[1], mask, three); mask = ctx.OpShiftLeftLogical(ctx.U32[1], mask, ctx.Const(1U)); mask = ctx.OpShiftRightLogical(ctx.U32[1], swizzle, mask); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 169e83d9fd..b562e82983 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -30,7 +30,7 @@ enum class Operation { Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) { const spv::ImageFormat format{spv::ImageFormat::Unknown}; - const Id type{ctx.F32[1]}; + const Id type{desc.is_integer ? ctx.U32[1] : ctx.F32[1]}; const bool depth{desc.is_depth}; const bool ms{desc.is_multisample}; switch (desc.type) { @@ -371,7 +371,7 @@ Id CasFunction(EmitContext& ctx, Operation operation, Id value_type) { Id CasLoop(EmitContext& ctx, Operation operation, Id array_pointer, Id element_pointer, Id value_type, Id memory_type, spv::Scope scope) { const bool is_shared{scope == spv::Scope::Workgroup}; - const bool is_struct{!is_shared || ctx.profile.support_explicit_workgroup_layout}; + const bool is_struct{!is_shared || ctx.uses_explicit_workgroup_layout}; const Id cas_func{CasFunction(ctx, operation, value_type)}; const Id zero{ctx.u32_zero_value}; const Id scope_id{ctx.Const(static_cast(scope))}; @@ -591,7 +591,8 @@ void EmitContext::DefineLocalMemory(const IR::Program& program) { return; } const u32 num_elements{Common::DivCeil(program.local_memory_size, 4U)}; - const Id type{TypeArray(U32[1], Const(num_elements))}; + const Id element_type{TypeStruct(U32[1])}; + const Id type{TypeArray(element_type, Const(num_elements))}; const Id pointer{TypePointer(spv::StorageClass::Private, type)}; local_memory = AddGlobalVariable(pointer, spv::StorageClass::Private); if (profile.supported_spirv >= 0x00010400) { @@ -600,6 +601,10 @@ void EmitContext::DefineLocalMemory(const IR::Program& program) { } void EmitContext::DefineSharedMemory(const IR::Program& program) { + uses_explicit_workgroup_layout = + profile.support_explicit_workgroup_layout && + (!program.info.uses_int8 || profile.support_workgroup_layout_8bit_access) && + (!program.info.uses_int16 || profile.support_workgroup_layout_16bit_access); if (program.shared_memory_size == 0) { return; } @@ -620,18 +625,18 @@ void EmitContext::DefineSharedMemory(const IR::Program& program) { return std::make_tuple(variable, element_pointer, pointer); }}; - if (profile.support_explicit_workgroup_layout) { + if (uses_explicit_workgroup_layout) { AddExtension("SPV_KHR_workgroup_memory_explicit_layout"); AddCapability(spv::Capability::WorkgroupMemoryExplicitLayoutKHR); - if (program.info.uses_int8) { + if (program.info.uses_int8 && profile.support_int8) { AddCapability(spv::Capability::WorkgroupMemoryExplicitLayout8BitAccessKHR); std::tie(shared_memory_u8, shared_u8, std::ignore) = make(U8, 1); } - if (program.info.uses_int16) { + if (program.info.uses_int16 && profile.support_int16) { AddCapability(spv::Capability::WorkgroupMemoryExplicitLayout16BitAccessKHR); std::tie(shared_memory_u16, shared_u16, std::ignore) = make(U16, 2); } - if (program.info.uses_int64) { + if (program.info.uses_int64 && profile.support_int64) { std::tie(shared_memory_u64, shared_u64, std::ignore) = make(U64, 8); } std::tie(shared_memory_u32, shared_u32, shared_memory_u32_type) = make(U32[1], 4); @@ -1126,7 +1131,7 @@ void EmitContext::DefineConstantBuffers(const Info& info, u32& binding) { } IR::Type types{info.used_constant_buffer_types | info.used_indirect_cbuf_types}; if (True(types & IR::Type::U8)) { - if (profile.support_int8) { + if (profile.support_int8 && profile.support_uniform_and_storage_buffer_8bit) { DefineConstBuffers(*this, info, &UniformDefinitions::U8, binding, U8, 'u', sizeof(u8)); DefineConstBuffers(*this, info, &UniformDefinitions::S8, binding, S8, 's', sizeof(s8)); } else { @@ -1134,7 +1139,7 @@ void EmitContext::DefineConstantBuffers(const Info& info, u32& binding) { } } if (True(types & IR::Type::U16)) { - if (profile.support_int16) { + if (profile.support_int16 && profile.support_uniform_and_storage_buffer_16bit) { DefineConstBuffers(*this, info, &UniformDefinitions::U16, binding, U16, 'u', sizeof(u16)); DefineConstBuffers(*this, info, &UniformDefinitions::S16, binding, S16, 's', @@ -1196,10 +1201,18 @@ void EmitContext::DefineConstantBufferIndirectFunctions(const Info& info) { IR::Type types{info.used_indirect_cbuf_types}; bool supports_aliasing = profile.support_descriptor_aliasing; if (supports_aliasing && True(types & IR::Type::U8)) { - load_const_func_u8 = make_accessor(U8, &UniformDefinitions::U8); + if (profile.support_int8 && profile.support_uniform_and_storage_buffer_8bit) { + load_const_func_u8 = make_accessor(U8, &UniformDefinitions::U8); + } else { + types |= IR::Type::U32; + } } if (supports_aliasing && True(types & IR::Type::U16)) { - load_const_func_u16 = make_accessor(U16, &UniformDefinitions::U16); + if (profile.support_int16 && profile.support_uniform_and_storage_buffer_16bit) { + load_const_func_u16 = make_accessor(U16, &UniformDefinitions::U16); + } else { + types |= IR::Type::U32; + } } if (supports_aliasing && True(types & IR::Type::F32)) { load_const_func_f32 = make_accessor(F32[1], &UniformDefinitions::F32); @@ -1221,15 +1234,18 @@ void EmitContext::DefineStorageBuffers(const Info& info, u32& binding) { } AddExtension("SPV_KHR_storage_buffer_storage_class"); - const IR::Type used_types{profile.support_descriptor_aliasing ? info.used_storage_buffer_types - : IR::Type::U32}; - if (profile.support_int8 && True(used_types & IR::Type::U8)) { + IR::Type used_types{profile.support_descriptor_aliasing ? info.used_storage_buffer_types + : IR::Type::U32}; + used_types |= IR::Type::U32; + if (profile.support_int8 && profile.support_storage_buffer_8bit && + True(used_types & IR::Type::U8)) { DefineSsbos(*this, storage_types.U8, &StorageDefinitions::U8, info, binding, U8, sizeof(u8)); DefineSsbos(*this, storage_types.S8, &StorageDefinitions::S8, info, binding, S8, sizeof(u8)); } - if (profile.support_int16 && True(used_types & IR::Type::U16)) { + if (profile.support_int16 && profile.support_storage_buffer_16bit && + True(used_types & IR::Type::U16)) { DefineSsbos(*this, storage_types.U16, &StorageDefinitions::U16, info, binding, U16, sizeof(u16)); DefineSsbos(*this, storage_types.S16, &StorageDefinitions::S16, info, binding, S16, @@ -1375,6 +1391,7 @@ void EmitContext::DefineTextures(const Info& info, u32& binding, u32& scaling_in .image_type = image_type, .count = desc.count, .is_multisample = desc.is_multisample, + .is_integer = desc.is_integer, }); if (profile.supported_spirv >= 0x00010400) { interfaces.push_back(id); @@ -1438,7 +1455,7 @@ void EmitContext::DefineInputs(const IR::Program& program) { if (info.uses_is_helper_invocation) { is_helper_invocation = DefineInput(*this, U1, false, spv::BuiltIn::HelperInvocation); } - if (info.uses_subgroup_mask) { + if (info.uses_subgroup_mask && profile.SupportsSubgroupStage(stage)) { subgroup_mask_eq = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupEqMaskKHR); subgroup_mask_lt = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLtMaskKHR); subgroup_mask_le = DefineInput(*this, U32[4], false, spv::BuiltIn::SubgroupLeMaskKHR); @@ -1452,9 +1469,10 @@ void EmitContext::DefineInputs(const IR::Program& program) { Decorate(subgroup_mask_ge, spv::Decoration::Flat); } } - if (info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles || - (profile.warp_size_potentially_larger_than_guest && - (info.uses_subgroup_vote || info.uses_subgroup_mask))) { + if ((info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles || + (profile.warp_size_potentially_larger_than_guest && + (info.uses_subgroup_vote || info.uses_subgroup_mask))) && + profile.SupportsSubgroupStage(stage)) { AddCapability(spv::Capability::GroupNonUniform); subgroup_local_invocation_id = DefineInput(*this, U32[1], false, spv::BuiltIn::SubgroupLocalInvocationId); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index de56809a98..a5821e9e93 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -9,6 +9,7 @@ #include #include +#include "common/container/unordered_set.h" #include "shader_recompiler/backend/bindings.h" #include "shader_recompiler/frontend/ir/program.h" @@ -41,6 +42,7 @@ struct TextureDefinition { Id image_type; u32 count; bool is_multisample; + bool is_integer; }; struct TextureBufferDefinition { @@ -309,6 +311,7 @@ public: Id local_memory{}; + bool uses_explicit_workgroup_layout{}; Id shared_memory_u8{}; Id shared_memory_u16{}; Id shared_memory_u32{}; @@ -358,6 +361,7 @@ public: Id frag_depth{}; std::vector interfaces; + std::vector> opcode_rewrites; Id load_const_func_u8{}; Id load_const_func_u16{}; @@ -366,6 +370,14 @@ public: Id load_const_func_u32x2{}; Id load_const_func_u32x4{}; + // Sirit::Id doesn't play nice with *::set<> + ::Common::unordered_set non_uniform_ids; + + bool uses_nonuniform_sampled_image{}; + bool uses_nonuniform_storage_image{}; + bool uses_nonuniform_uniform_texel_buffer{}; + bool uses_nonuniform_storage_texel_buffer{}; + private: void DefineCommonTypes(const Info& info); void DefineCommonConstants(); diff --git a/src/shader_recompiler/environment.h b/src/shader_recompiler/environment.h index 0a6e4bfcaa..184b56ebf3 100644 --- a/src/shader_recompiler/environment.h +++ b/src/shader_recompiler/environment.h @@ -7,7 +7,7 @@ #pragma once #include -#include +#include "common/container/unordered_map.h" #include "common/common_types.h" #include "shader_recompiler/program_header.h" @@ -38,10 +38,11 @@ struct CbufWordKeyHash { struct HandleKey { u32 index, offset, shift_left; u32 sec_index, sec_offset, sec_shift_left; + u32 count; bool has_secondary; constexpr bool operator==(const HandleKey& o) const noexcept { - return std::tie(index, offset, shift_left, sec_index, sec_offset, sec_shift_left, has_secondary) - == std::tie(o.index, o.offset, o.shift_left, o.sec_index, o.sec_offset, o.sec_shift_left, o.has_secondary); + return std::tie(index, offset, shift_left, sec_index, sec_offset, sec_shift_left, count, has_secondary) + == std::tie(o.index, o.offset, o.shift_left, o.sec_index, o.sec_offset, o.sec_shift_left, o.count, o.has_secondary); } }; struct HandleKeyHash { @@ -50,8 +51,8 @@ struct HandleKeyHash { h ^= (size_t(k.shift_left) << 1); h ^= (size_t(k.sec_index) << 33) ^ (size_t(k.sec_offset) << 2); h ^= (size_t(k.sec_shift_left) << 3); - h ^= k.has_secondary ? 0x9e3779b97f4a7c15ULL : 0ULL; - return h; + h ^= (size_t(k.count) << 7); + return h ^ (k.has_secondary ? 0x9e3779b97f4a7c15ULL : 0ULL); } }; @@ -125,9 +126,9 @@ protected: u32 start_address{}; bool is_proprietary_driver{}; public: - ankerl::unordered_dense::map cbuf_word_cache; - ankerl::unordered_dense::map handle_cache; - ankerl::unordered_dense::map track_cache; + ::Common::unordered_map cbuf_word_cache; + ::Common::unordered_map handle_cache; + ::Common::unordered_map track_cache; }; } // namespace Shader diff --git a/src/shader_recompiler/frontend/ir/attribute.h b/src/shader_recompiler/frontend/ir/attribute.h index 943c8d673f..4adf980949 100644 --- a/src/shader_recompiler/frontend/ir/attribute.h +++ b/src/shader_recompiler/frontend/ir/attribute.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -240,6 +240,14 @@ constexpr size_t NUM_FIXEDFNCTEXTURE = 10; return attribute >= Attribute::Generic0X && attribute <= Attribute::Generic31X; } +[[nodiscard]] inline bool IsLegacyAttribute(Attribute attribute) noexcept { + return (attribute >= Attribute::ColorFrontDiffuseR && + attribute <= Attribute::ColorBackSpecularA) || + attribute == Attribute::FogCoordinate || + (attribute >= Attribute::FixedFncTexture0S && + attribute <= Attribute::FixedFncTexture9Q); +} + [[nodiscard]] inline u32 GenericAttributeIndex(Attribute attribute) { if (!IsGeneric(attribute)) throw InvalidArgument("Attribute is not generic {}", attribute); diff --git a/src/shader_recompiler/frontend/ir/breadth_first_search.h b/src/shader_recompiler/frontend/ir/breadth_first_search.h index ec55425549..afe153f180 100644 --- a/src/shader_recompiler/frontend/ir/breadth_first_search.h +++ b/src/shader_recompiler/frontend/ir/breadth_first_search.h @@ -1,8 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include #include #include #include @@ -13,9 +17,10 @@ namespace Shader::IR { -template -auto BreadthFirstSearch(const Value& value, Pred&& pred) +template +auto BreadthFirstSearch(Value value, Pred&& pred, Resolve&& resolve = {}) -> std::invoke_result_t { + value = resolve(value); if (value.IsImmediate()) { // Nothing to do with immediates return std::nullopt; @@ -36,7 +41,7 @@ auto BreadthFirstSearch(const Value& value, Pred&& pred) } // Visit the right most arguments first for (size_t arg = inst->NumArgs(); arg--;) { - const Value arg_value{inst->Arg(arg)}; + const Value arg_value{resolve(inst->Arg(arg))}; if (arg_value.IsImmediate()) { continue; } diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.cpp b/src/shader_recompiler/frontend/ir/ir_emitter.cpp index 171161ffd1..95815d7d51 100644 --- a/src/shader_recompiler/frontend/ir/ir_emitter.cpp +++ b/src/shader_recompiler/frontend/ir/ir_emitter.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -2100,6 +2100,14 @@ U32 IREmitter::ShuffleButterfly(const IR::U32& value, const IR::U32& index, cons return Inst(Opcode::ShuffleButterfly, value, index, clamp, seg_mask); } +U32 IREmitter::QuadBroadcast(const IR::U32& value, const IR::U32& lane) { + return Inst(Opcode::QuadBroadcast, value, lane); +} + +U32 IREmitter::QuadSwap(const IR::U32& value, const IR::U32& direction) { + return Inst(Opcode::QuadSwap, value, direction); +} + F32 IREmitter::FSwizzleAdd(const F32& a, const F32& b, const U32& swizzle, FpControl control) { return Inst(Opcode::FSwizzleAdd, Flags{control}, a, b, swizzle); } diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.h b/src/shader_recompiler/frontend/ir/ir_emitter.h index 725eac3951..f22f0f75e2 100644 --- a/src/shader_recompiler/frontend/ir/ir_emitter.h +++ b/src/shader_recompiler/frontend/ir/ir_emitter.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -394,6 +394,8 @@ public: const IR::U32& seg_mask); [[nodiscard]] U32 ShuffleButterfly(const IR::U32& value, const IR::U32& index, const IR::U32& clamp, const IR::U32& seg_mask); + [[nodiscard]] U32 QuadBroadcast(const IR::U32& value, const IR::U32& lane); + [[nodiscard]] U32 QuadSwap(const IR::U32& value, const IR::U32& direction); [[nodiscard]] F32 FSwizzleAdd(const F32& a, const F32& b, const U32& swizzle, FpControl control = {}); diff --git a/src/shader_recompiler/frontend/ir/modifiers.h b/src/shader_recompiler/frontend/ir/modifiers.h index c20c2401f7..991c51b1d6 100644 --- a/src/shader_recompiler/frontend/ir/modifiers.h +++ b/src/shader_recompiler/frontend/ir/modifiers.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -43,6 +46,10 @@ union TextureInstInfo { BitField<25, 2, u32> num_derivatives; BitField<27, 3, ImageFormat> image_format; BitField<30, 1, u32> ndv_is_active; + /// Only meaningful for the sampled-texture family (ImageSampleImplicitLod, ImageFetch, + /// ImageGather, etc.); unused/zero for storage-image opcodes, which carry their own + /// is_integer via ImageDescriptor/ImageBufferDescriptor instead. + BitField<31, 1, u32> is_integer; }; static_assert(sizeof(TextureInstInfo) <= sizeof(u32)); diff --git a/src/shader_recompiler/frontend/ir/opcodes.cpp b/src/shader_recompiler/frontend/ir/opcodes.cpp index 9e4d9e65a8..94ba0f6ae0 100644 --- a/src/shader_recompiler/frontend/ir/opcodes.cpp +++ b/src/shader_recompiler/frontend/ir/opcodes.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -5,6 +8,27 @@ namespace Shader::IR { +namespace Detail { + +OpcodeMeta META_TABLE[534] = { +#define OPCODE(name_token, type_token, ...) \ + { \ + .name{#name_token}, \ + .type = type_token, \ + .arg_types{__VA_ARGS__}, \ + }, +#include "opcodes.inc" +#undef OPCODE +}; + +u8 NUM_ARGS[534] = { +#define OPCODE(name_token, type_token, ...) u8(CalculateNumArgsOf(Opcode::name_token)), +#include "opcodes.inc" +#undef OPCODE +}; + +} + std::string_view NameOf(Opcode op) { return Detail::META_TABLE[static_cast(op)].name; } diff --git a/src/shader_recompiler/frontend/ir/opcodes.h b/src/shader_recompiler/frontend/ir/opcodes.h index 3d6a3539bb..50f9d7b155 100644 --- a/src/shader_recompiler/frontend/ir/opcodes.h +++ b/src/shader_recompiler/frontend/ir/opcodes.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -30,69 +30,54 @@ struct OpcodeMeta { }; // using enum Type; -constexpr Type Void{Type::Void}; -constexpr Type Opaque{Type::Opaque}; -constexpr Type Reg{Type::Reg}; -constexpr Type Pred{Type::Pred}; -constexpr Type Attribute{Type::Attribute}; -constexpr Type Patch{Type::Patch}; -constexpr Type U1{Type::U1}; -constexpr Type U8{Type::U8}; -constexpr Type U16{Type::U16}; -constexpr Type U32{Type::U32}; -constexpr Type U64{Type::U64}; -constexpr Type F16{Type::F16}; -constexpr Type F32{Type::F32}; -constexpr Type F64{Type::F64}; -constexpr Type U32x2{Type::U32x2}; -constexpr Type U32x3{Type::U32x3}; -constexpr Type U32x4{Type::U32x4}; -constexpr Type F16x2{Type::F16x2}; -constexpr Type F16x3{Type::F16x3}; -constexpr Type F16x4{Type::F16x4}; -constexpr Type F32x2{Type::F32x2}; -constexpr Type F32x3{Type::F32x3}; -constexpr Type F32x4{Type::F32x4}; -constexpr Type F64x2{Type::F64x2}; -constexpr Type F64x3{Type::F64x3}; -constexpr Type F64x4{Type::F64x4}; +static constexpr Type Void{Type::Void}; +static constexpr Type Opaque{Type::Opaque}; +static constexpr Type Reg{Type::Reg}; +static constexpr Type Pred{Type::Pred}; +static constexpr Type Attribute{Type::Attribute}; +static constexpr Type Patch{Type::Patch}; +static constexpr Type U1{Type::U1}; +static constexpr Type U8{Type::U8}; +static constexpr Type U16{Type::U16}; +static constexpr Type U32{Type::U32}; +static constexpr Type U64{Type::U64}; +static constexpr Type F16{Type::F16}; +static constexpr Type F32{Type::F32}; +static constexpr Type F64{Type::F64}; +static constexpr Type U32x2{Type::U32x2}; +static constexpr Type U32x3{Type::U32x3}; +static constexpr Type U32x4{Type::U32x4}; +static constexpr Type F16x2{Type::F16x2}; +static constexpr Type F16x3{Type::F16x3}; +static constexpr Type F16x4{Type::F16x4}; +static constexpr Type F32x2{Type::F32x2}; +static constexpr Type F32x3{Type::F32x3}; +static constexpr Type F32x4{Type::F32x4}; +static constexpr Type F64x2{Type::F64x2}; +static constexpr Type F64x3{Type::F64x3}; +static constexpr Type F64x4{Type::F64x4}; -constexpr OpcodeMeta META_TABLE[]{ -#define OPCODE(name_token, type_token, ...) \ - { \ - .name{#name_token}, \ - .type = type_token, \ - .arg_types{__VA_ARGS__}, \ - }, -#include "opcodes.inc" -#undef OPCODE -}; -constexpr size_t CalculateNumArgsOf(Opcode op) { - const auto& arg_types{META_TABLE[static_cast(op)].arg_types}; - return static_cast( - std::distance(arg_types.begin(), std::ranges::find(arg_types, Type::Void))); +extern OpcodeMeta META_TABLE[534]; +constexpr size_t CalculateNumArgsOf(Opcode op) noexcept { + const auto& arg_types = META_TABLE[size_t(op)].arg_types; + return size_t(std::distance(arg_types.begin(), std::ranges::find(arg_types, Type::Void))); } - -constexpr u8 NUM_ARGS[]{ -#define OPCODE(name_token, type_token, ...) static_cast(CalculateNumArgsOf(Opcode::name_token)), -#include "opcodes.inc" -#undef OPCODE -}; +extern u8 NUM_ARGS[534]; } // namespace Detail /// Get return type of an opcode [[nodiscard]] inline Type TypeOf(Opcode op) noexcept { - return Detail::META_TABLE[static_cast(op)].type; + return Detail::META_TABLE[size_t(op)].type; } /// Get the number of arguments an opcode accepts [[nodiscard]] inline size_t NumArgsOf(Opcode op) noexcept { - return static_cast(Detail::NUM_ARGS[static_cast(op)]); + return size_t(Detail::NUM_ARGS[size_t(op)]); } /// Get the required type of an argument of an opcode [[nodiscard]] inline Type ArgTypeOf(Opcode op, size_t arg_index) noexcept { - return Detail::META_TABLE[static_cast(op)].arg_types[arg_index]; + return Detail::META_TABLE[size_t(op)].arg_types[arg_index]; } /// Get the name of an opcode diff --git a/src/shader_recompiler/frontend/ir/opcodes.inc b/src/shader_recompiler/frontend/ir/opcodes.inc index 960009d89e..fee0afaf33 100644 --- a/src/shader_recompiler/frontend/ir/opcodes.inc +++ b/src/shader_recompiler/frontend/ir/opcodes.inc @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -579,6 +582,8 @@ OPCODE(ShuffleIndex, U32, U32, OPCODE(ShuffleUp, U32, U32, U32, U32, U32, ) OPCODE(ShuffleDown, U32, U32, U32, U32, U32, ) OPCODE(ShuffleButterfly, U32, U32, U32, U32, U32, ) +OPCODE(QuadBroadcast, U32, U32, U32, ) +OPCODE(QuadSwap, U32, U32, U32, ) OPCODE(FSwizzleAdd, F32, F32, F32, U32, ) OPCODE(DPdxFine, F32, F32, ) OPCODE(DPdyFine, F32, F32, ) diff --git a/src/shader_recompiler/frontend/maxwell/decode.cpp b/src/shader_recompiler/frontend/maxwell/decode.cpp index 92027e8ad5..251b370efa 100644 --- a/src/shader_recompiler/frontend/maxwell/decode.cpp +++ b/src/shader_recompiler/frontend/maxwell/decode.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -46,7 +46,7 @@ Opcode Decode(u64 insn) { return Opcode::name; #include "maxwell.inc" #undef INST - ASSERT_MSG(false, "Invalid insn 0x{:016x}", insn); + ASSERT_MSG(false, "Invalid insn {:#016x}", insn); return Opcode::NOP; } diff --git a/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp b/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp index eaf81f023d..329d44443e 100644 --- a/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp +++ b/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include @@ -174,7 +174,7 @@ std::string DumpExpr(const Statement* stmt) { for (auto stmt = tree.begin(); stmt != tree.end(); ++stmt) { switch (stmt->type) { case StatementType::Code: - ret += fmt::format("{} Block {:04x} -> {:04x} (0x{:016x});\n", indent, + ret += fmt::format("{} Block {:04x} -> {:04x} ({:#016x});\n", indent, stmt->block->begin.Offset(), stmt->block->end.Offset(), reinterpret_cast(stmt->block)); break; @@ -390,7 +390,7 @@ private: std::optional return_label) { Statement* const false_stmt{pool.Create(Identity{}, IR::Condition{false}, &root_stmt)}; Tree& root{root_stmt.children}; - ankerl::unordered_dense::map local_labels; + ::Common::unordered_map local_labels; local_labels.reserve(function.blocks.size()); for (Flow::Block& block : function.blocks) { diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/internal_stage_buffer_entry_read.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/internal_stage_buffer_entry_read.cpp index e6345de65e..46660563a1 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/internal_stage_buffer_entry_read.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/internal_stage_buffer_entry_read.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -53,7 +53,7 @@ IR::U32 skewBytes(IR::IREmitter& ir, SZ sizeRead) { } // Anonymous namespace void TranslatorVisitor::ISBERD(u64 insn) { - LOG_DEBUG(Shader, "called with insn={:#X}", insn); + LOG_DEBUG(Shader, "called with insn={:#x}", insn); union { u64 raw; diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp index e3745ce084..d4990af7a6 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -163,12 +166,24 @@ void TranslatorVisitor::IPA(u64 insn) { const IR::Attribute attribute{ipa.attribute}; IR::F32 value{is_indexed ? ir.GetAttributeIndexed(X(ipa.index_reg)) : ir.GetAttribute(attribute)}; - if (IR::IsGeneric(attribute)) { - const ProgramHeader& sph{env.SPH()}; - const u32 attr_index{IR::GenericAttributeIndex(attribute)}; - const u32 element{static_cast(attribute) % 4}; - const std::array input_map{sph.ps.GenericInputMap(attr_index)}; - const bool is_perspective{input_map[element] == Shader::PixelImap::Perspective}; + const bool is_legacy{IR::IsLegacyAttribute(attribute)}; + if (IR::IsGeneric(attribute) || is_legacy) { + bool is_perspective{is_legacy && + ipa.interpolation_mode != InterpolationMode::Sc}; + if (!is_legacy) { + const ProgramHeader& sph{env.SPH()}; + const u32 attr_index{IR::GenericAttributeIndex(attribute)}; + const std::array input_map{sph.ps.GenericInputMap(attr_index)}; + Shader::PixelImap effective_imap{Shader::PixelImap::Unused}; + for (const Shader::PixelImap component : input_map) { + if (component != Shader::PixelImap::Unused) { + effective_imap = component; + break; + } + } + is_perspective = effective_imap == Shader::PixelImap::Perspective || + effective_imap == Shader::PixelImap::Unused; + } if (is_perspective) { const IR::F32 position_w{ir.GetAttribute(IR::Attribute::PositionW)}; value = ir.FPMul(value, position_w); diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_local_shared.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_local_shared.cpp index a198b2b760..a52b7d50b9 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_local_shared.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_local_shared.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -152,7 +155,7 @@ void TranslatorVisitor::STL(u64 insn) { if (offset.IsImmediate()) { // TODO: Support storing out of bounds at runtime if (offset.U32() >= env.LocalMemorySize()) { - LOG_WARNING(Shader, "Storing local memory at 0x{:x} with a size of 0x{:x}, dropping", + LOG_WARNING(Shader, "Storing local memory at {:#x} with a size of {:#x}, dropping", offset.U32(), env.LocalMemorySize()); return; } diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/warp_shuffle.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/warp_shuffle.cpp index 972eec8276..2a19237ab2 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/warp_shuffle.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/warp_shuffle.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -36,7 +36,10 @@ enum class ShuffleMode : u64 { } } -void Shuffle(TranslatorVisitor& v, u64 insn, const IR::U32& index, const IR::U32& mask) { +constexpr u32 QUAD_MASK = (28u << 8) | 3u; + +void Shuffle(TranslatorVisitor& v, u64 insn, const IR::U32& index, const IR::U32& mask, + bool index_is_imm, u32 index_imm, bool mask_is_imm, u32 mask_imm) { union { u64 insn; BitField<0, 8, IR::Reg> dest_reg; @@ -45,6 +48,21 @@ void Shuffle(TranslatorVisitor& v, u64 insn, const IR::U32& index, const IR::U32 BitField<48, 3, IR::Pred> pred; } const shfl{insn}; + const bool is_quad_candidate{mask_is_imm && mask_imm == QUAD_MASK && index_is_imm && + v.env.ShaderStage() == Stage::Fragment}; + if (is_quad_candidate) { + if (shfl.mode == ShuffleMode::IDX && index_imm <= 3) { + v.X(shfl.dest_reg, v.ir.QuadBroadcast(v.X(shfl.src_reg), v.ir.Imm32(index_imm))); + v.ir.SetPred(shfl.pred, v.ir.Imm1(true)); + return; + } + if (shfl.mode == ShuffleMode::BFLY && index_imm >= 1 && index_imm <= 3) { + v.X(shfl.dest_reg, v.ir.QuadSwap(v.X(shfl.src_reg), v.ir.Imm32(index_imm - 1))); + v.ir.SetPred(shfl.pred, v.ir.Imm1(true)); + return; + } + } + const IR::U32 result{ShuffleOperation(v.ir, v.X(shfl.src_reg), index, mask, shfl.mode)}; v.ir.SetPred(shfl.pred, v.ir.GetInBoundsFromOp(result)); v.X(shfl.dest_reg, result); @@ -59,11 +77,14 @@ void TranslatorVisitor::SHFL(u64 insn) { BitField<29, 1, u64> src_b_flag; BitField<34, 13, u64> src_b_imm; } const flags{insn}; - const IR::U32 src_a{flags.src_a_flag != 0 ? ir.Imm32(static_cast(flags.src_a_imm)) - : GetReg20(insn)}; - const IR::U32 src_b{flags.src_b_flag != 0 ? ir.Imm32(static_cast(flags.src_b_imm)) - : GetReg39(insn)}; - Shuffle(*this, insn, src_a, src_b); + const bool index_is_imm{flags.src_a_flag != 0}; + const bool mask_is_imm{flags.src_b_flag != 0}; + const IR::U32 src_a{index_is_imm ? ir.Imm32(static_cast(flags.src_a_imm)) + : GetReg20(insn)}; + const IR::U32 src_b{mask_is_imm ? ir.Imm32(static_cast(flags.src_b_imm)) + : GetReg39(insn)}; + Shuffle(*this, insn, src_a, src_b, index_is_imm, static_cast(flags.src_a_imm), + mask_is_imm, static_cast(flags.src_b_imm)); } } // namespace Shader::Maxwell diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp index f156192c13..0cc4b3b844 100644 --- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp @@ -5,7 +5,10 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include +#include #include +#include #include #include @@ -132,13 +135,7 @@ void AddNVNStorageBuffers(IR::Program& program) { } } -bool IsLegacyAttribute(IR::Attribute attribute) { - return (attribute >= IR::Attribute::ColorFrontDiffuseR && - attribute <= IR::Attribute::ColorBackSpecularA) || - attribute == IR::Attribute::FogCoordinate || - (attribute >= IR::Attribute::FixedFncTexture0S && - attribute <= IR::Attribute::FixedFncTexture9Q); -} +using IR::IsLegacyAttribute; //rescoped to attribute.h to make it visible in load_store_attribute.cpp IPA std::map GenerateLegacyToGenericMappings( const VaryingState& state, std::queue unused_generics, @@ -178,7 +175,17 @@ std::map GenerateLegacyToGenericMappings( void EmitGeometryPassthrough(IR::IREmitter& ir, const IR::Program& program, const Shader::VaryingState& passthrough_mask, bool passthrough_position, - std::optional passthrough_layer_attr) { + std::optional passthrough_layer_attr, + std::optional viewport_mask_reg) { + constexpr std::array CULLED_POSITION{2.0f, 2.0f, 2.0f, 1.0f}; + IR::U1 culled{ir.Imm1(false)}; + IR::U32 viewport{ir.Imm32(0)}; + if (viewport_mask_reg) { + const IR::U32 mask{ir.GetReg(*viewport_mask_reg)}; + const IR::U32 lowest_bit{ir.BitwiseAnd(mask, IR::U32{ir.INeg(mask)})}; + culled = ir.IEqual(mask, ir.Imm32(0)); + viewport = IR::U32{ir.Select(culled, ir.Imm32(0), ir.FindUMsb(lowest_bit))}; + } for (u32 i = 0; i < program.output_vertices; i++) { // Assign generics from input for (u32 j = 0; j < 32; j++) { @@ -196,10 +203,19 @@ void EmitGeometryPassthrough(IR::IREmitter& ir, const IR::Program& program, if (passthrough_position) { // Assign position from input const IR::Attribute attr = IR::Attribute::PositionX; - ir.SetAttribute(attr + 0, ir.GetAttribute(attr + 0, ir.Imm32(i)), ir.Imm32(0)); - ir.SetAttribute(attr + 1, ir.GetAttribute(attr + 1, ir.Imm32(i)), ir.Imm32(0)); - ir.SetAttribute(attr + 2, ir.GetAttribute(attr + 2, ir.Imm32(i)), ir.Imm32(0)); - ir.SetAttribute(attr + 3, ir.GetAttribute(attr + 3, ir.Imm32(i)), ir.Imm32(0)); + for (u32 component = 0; component < 4; ++component) { + IR::F32 value{ir.GetAttribute(attr + component, ir.Imm32(i))}; + if (viewport_mask_reg) { + value = IR::F32{ + ir.Select(culled, ir.Imm32(CULLED_POSITION[component]), value)}; + } + ir.SetAttribute(attr + component, value, ir.Imm32(0)); + } + } + + if (viewport_mask_reg) { + ir.SetAttribute(IR::Attribute::ViewportIndex, ir.BitCast(viewport), + ir.Imm32(0)); } if (passthrough_layer_attr) { @@ -225,25 +241,100 @@ u32 GetOutputTopologyVertices(OutputTopology output_topology) { } } +std::optional FindFreeRegister(const IR::Program& program) { + std::bitset used; + for (IR::Block* const block : program.blocks) { + for (const IR::Inst& inst : block->Instructions()) { + const IR::Opcode opcode{inst.GetOpcode()}; + if (opcode == IR::Opcode::GetRegister || opcode == IR::Opcode::SetRegister) { + used.set(IR::RegIndex(inst.Arg(0).Reg())); + } + } + } + for (size_t index = IR::NUM_USER_REGS; index-- > 0;) { + if (!used.test(index)) { + return static_cast(index); + } + } + return std::nullopt; +} + +std::optional LowerViewportMask(const IR::Program& program) { + const std::optional reg{FindFreeRegister(program)}; + if (!reg || program.blocks.empty()) { + return std::nullopt; + } + bool stores_mask{}; + for (IR::Block* const block : program.blocks) { + for (IR::Inst& inst : block->Instructions()) { + if (inst.GetOpcode() != IR::Opcode::SetAttribute || + inst.Arg(0).Attribute() != IR::Attribute::ViewportMask) { + continue; + } + IR::IREmitter ir{*block, IR::Block::InstructionList::s_iterator_to(inst)}; + ir.SetReg(*reg, ir.BitCast(IR::F32{inst.Arg(1)})); + inst.Invalidate(); + stores_mask = true; + } + } + if (!stores_mask) { + return std::nullopt; + } + IR::Block& entry{*program.blocks.front()}; + IR::IREmitter ir{entry, entry.begin()}; + ir.SetReg(*reg, ir.Imm32(1)); + return reg; +} + void LowerGeometryPassthrough(const IR::Program& program, const HostTranslateInfo& host_info) { + std::optional viewport_mask_reg; + if (!host_info.support_viewport_mask) { + viewport_mask_reg = LowerViewportMask(program); + } for (IR::Block* const block : program.blocks) { for (IR::Inst& inst : block->Instructions()) { if (inst.GetOpcode() == IR::Opcode::Epilogue) { IR::IREmitter ir{*block, IR::Block::InstructionList::s_iterator_to(inst)}; EmitGeometryPassthrough( ir, program, program.info.passthrough, - program.info.passthrough.AnyComponent(IR::Attribute::PositionX), {}); + program.info.passthrough.AnyComponent(IR::Attribute::PositionX), {}, + viewport_mask_reg); } } } } +void TightenOutputVertices(IR::Program& program) { + if (program.stage != Stage::Geometry || program.is_geometry_passthrough) { + return; + } + const bool has_loops = std::ranges::any_of(program.syntax_list, [](const auto& node) { + return node.type == IR::AbstractSyntaxNode::Type::Loop; + }); + if (has_loops) { + return; + } + u32 num_emits = 0; + for (IR::Block* const block : program.blocks) { + for (const IR::Inst& inst : block->Instructions()) { + if (inst.GetOpcode() == IR::Opcode::EmitVertex) { + ++num_emits; + } + } + } + if (num_emits != 0) { + program.output_vertices = (std::min)(program.output_vertices, num_emits); + } +} } // Anonymous namespace IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPool& block_pool, Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info) { + HostTranslateInfo normalized_host_info{host_info}; + normalized_host_info.ApplyDescriptorLimitPolicy(); + IR::Program program; - program.syntax_list = BuildASL(inst_pool, block_pool, env, cfg, host_info); + program.syntax_list = BuildASL(inst_pool, block_pool, env, cfg, normalized_host_info); program.blocks = GenerateBlocks(program.syntax_list); program.post_order_blocks = PostOrder(program.syntax_list.front()); program.stage = env.ShaderStage(); @@ -266,9 +357,9 @@ IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPool> (i % 32)) & 1) == 0; } - if (!host_info.support_geometry_shader_passthrough) { + if (!normalized_host_info.support_geometry_shader_passthrough) { program.output_vertices = GetOutputTopologyVertices(program.output_topology); - LowerGeometryPassthrough(program, host_info); + LowerGeometryPassthrough(program, normalized_host_info); } } break; @@ -283,16 +374,16 @@ IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPool& inst_pool, ObjectPool& inst_pool, IR::IREmitter ir{*current_block}; EmitGeometryPassthrough(ir, program, program.info.stores, true, - source_program.info.emulated_layer); + source_program.info.emulated_layer, std::nullopt); IR::Block* return_block{block_pool.Create(inst_pool)}; IR::IREmitter{*return_block}.Epilogue(); diff --git a/src/shader_recompiler/host_translate_info.h b/src/shader_recompiler/host_translate_info.h index 4a9261d5f5..44df85ccb2 100644 --- a/src/shader_recompiler/host_translate_info.h +++ b/src/shader_recompiler/host_translate_info.h @@ -6,6 +6,8 @@ #pragma once +#include "common/common_types.h" + namespace Shader { // Try to keep entries here to a minimum @@ -13,20 +15,54 @@ namespace Shader { /// Misc information about the host struct HostTranslateInfo { + static constexpr u32 DEFAULT_DESCRIPTOR_LIMIT = 1024; + + u64 min_ssbo_alignment{}; ///< Minimum alignment supported by the device for SSBOs + u32 max_per_stage_descriptor_sampled_images{}; ///< maximum sampled descriptors per stage + u32 max_per_stage_resources{}; ///< maximum resources per stage + u32 max_descriptor_set_samplers{}; + u32 max_descriptor_set_uniform_buffers{}; + u32 max_descriptor_set_uniform_buffers_dynamic{}; + u32 max_descriptor_set_storage_buffers{}; + u32 max_descriptor_set_storage_buffers_dynamic{}; + u32 max_descriptor_set_sampled_images{}; + u32 max_descriptor_set_storage_images{}; + u32 max_descriptor_set_input_attachements{}; bool support_float64{}; ///< True when the device supports 64-bit floats bool support_float16{}; ///< True when the device supports 16-bit floats bool support_int64{}; ///< True when the device supports 64-bit integers bool needs_demote_reorder{}; ///< True when the device needs DemoteToHelperInvocation reordered bool support_snorm_render_buffer{}; ///< True when the device supports SNORM render buffers bool support_viewport_index_layer{}; ///< True when the device supports gl_Layer in VS - u32 min_ssbo_alignment{}; ///< Minimum alignment supported by the device for SSBOs - u32 max_per_stage_descriptor_sampled_images{1024}; ///< maximum sampled descriptors per stage - u32 max_per_stage_resources{4096}; ///< maximum resources per stage - u32 max_descriptor_set_sampled_images{1024}; ///< maximum sampled descriptors per set + bool support_viewport_mask{}; bool support_geometry_shader_passthrough{}; ///< True when the device supports geometry ///< passthrough shaders bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional ///< control flow + bool single_lane_geometry_subgroups{}; + + void ApplyDescriptorLimitPolicy() noexcept { + if (min_ssbo_alignment == 0) { + min_ssbo_alignment = 1; + } + ApplyDescriptorLimitFallback(max_per_stage_descriptor_sampled_images); + ApplyDescriptorLimitFallback(max_per_stage_resources); + ApplyDescriptorLimitFallback(max_descriptor_set_samplers); + ApplyDescriptorLimitFallback(max_descriptor_set_uniform_buffers); + ApplyDescriptorLimitFallback(max_descriptor_set_uniform_buffers_dynamic); + ApplyDescriptorLimitFallback(max_descriptor_set_storage_buffers); + ApplyDescriptorLimitFallback(max_descriptor_set_storage_buffers_dynamic); + ApplyDescriptorLimitFallback(max_descriptor_set_sampled_images); + ApplyDescriptorLimitFallback(max_descriptor_set_storage_images); + ApplyDescriptorLimitFallback(max_descriptor_set_input_attachements); + } + +private: + static void ApplyDescriptorLimitFallback(u32& limit) noexcept { + if (limit == 0) { + limit = DEFAULT_DESCRIPTOR_LIMIT; + } + } }; } // namespace Shader diff --git a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp index 2bfa3227a8..9010c2bf68 100644 --- a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp +++ b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -498,6 +498,10 @@ void VisitUsages(Info& info, IR::Inst& inst) { case IR::Opcode::ShuffleButterfly: info.uses_subgroup_shuffles = true; break; + case IR::Opcode::QuadBroadcast: + case IR::Opcode::QuadSwap: + info.uses_quad_shuffles = true; + break; case IR::Opcode::GetCbufU8: case IR::Opcode::GetCbufS8: case IR::Opcode::GetCbufU16: @@ -569,6 +573,8 @@ void VisitUsages(Info& info, IR::Inst& inst) { case IR::Opcode::ImageRead: { const auto flags{inst.Flags()}; info.uses_typeless_image_reads |= flags.image_format == ImageFormat::Typeless; + info.uses_image_1d |= + flags.type == TextureType::Color1D || flags.type == TextureType::ColorArray1D; info.uses_sparse_residency |= inst.GetAssociatedPseudoOperation(IR::Opcode::GetSparseFromOp) != nullptr; break; @@ -577,6 +583,8 @@ void VisitUsages(Info& info, IR::Inst& inst) { const auto flags{inst.Flags()}; info.uses_typeless_image_writes |= flags.image_format == ImageFormat::Typeless; info.uses_image_buffers |= flags.type == TextureType::Buffer; + info.uses_image_1d |= + flags.type == TextureType::Color1D || flags.type == TextureType::ColorArray1D; break; } case IR::Opcode::SubgroupEqMask: @@ -761,9 +769,13 @@ void VisitUsages(Info& info, IR::Inst& inst) { case IR::Opcode::ImageAtomicAnd32: case IR::Opcode::ImageAtomicOr32: case IR::Opcode::ImageAtomicXor32: - case IR::Opcode::ImageAtomicExchange32: + case IR::Opcode::ImageAtomicExchange32: { + const auto flags{inst.Flags()}; info.uses_atomic_image_u32 = true; + info.uses_image_1d |= + flags.type == TextureType::Color1D || flags.type == TextureType::ColorArray1D; break; + } default: break; } diff --git a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp index 57c08588fb..6285fef16f 100644 --- a/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp +++ b/src/shader_recompiler/ir_opt/constant_propagation_pass.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -651,6 +651,29 @@ IR::Value GetThroughCast(IR::Value value, IR::Opcode expected_cast) { return value; } +u32 QuadButterflyMask(const IR::Inst& inst) { + if (inst.GetOpcode() == IR::Opcode::QuadSwap) { + const IR::Value direction{inst.Arg(1)}; + if (!direction.IsImmediate()) { + return 0; + } + return direction.U32() + 1; + } + if (inst.GetOpcode() != IR::Opcode::ShuffleButterfly) { + return 0; + } + const IR::Value index{inst.Arg(1)}; + const IR::Value clamp{inst.Arg(2)}; + const IR::Value segmentation_mask{inst.Arg(3)}; + if (!index.IsImmediate() || !clamp.IsImmediate() || !segmentation_mask.IsImmediate()) { + return 0; + } + if (clamp.U32() != 3 || segmentation_mask.U32() != 28) { + return 0; + } + return index.U32(); +} + void FoldFSwizzleAdd(IR::Block& block, IR::Inst& inst) { const IR::Value swizzle{inst.Arg(2)}; if (!swizzle.IsImmediate()) { @@ -666,7 +689,8 @@ void FoldFSwizzleAdd(IR::Block& block, IR::Inst& inst) { return; } IR::Inst* const inst2{value_1.InstRecursive()}; - if (inst2->GetOpcode() != IR::Opcode::ShuffleButterfly) { + const u32 lane_mask{QuadButterflyMask(*inst2)}; + if (lane_mask == 0) { return; } const IR::Value value_3{GetThroughCast(inst2->Arg(0).Resolve(), IR::Opcode::BitCastU32F32)}; @@ -678,24 +702,15 @@ void FoldFSwizzleAdd(IR::Block& block, IR::Inst& inst) { return; } } - const IR::Value index{inst2->Arg(1)}; - const IR::Value clamp{inst2->Arg(2)}; - const IR::Value segmentation_mask{inst2->Arg(3)}; - if (!index.IsImmediate() || !clamp.IsImmediate() || !segmentation_mask.IsImmediate()) { - return; - } - if (clamp.U32() != 3 || segmentation_mask.U32() != 28) { - return; - } if (swizzle_value == 0x99) { // DPdxFine - if (index.U32() == 1) { + if (lane_mask == 1) { IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; inst.ReplaceUsesWith(ir.DPdxFine(IR::F32{inst.Arg(1)})); } } else if (swizzle_value == 0xA5) { // DPdyFine - if (index.U32() == 2) { + if (lane_mask == 2) { IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; inst.ReplaceUsesWith(ir.DPdyFine(IR::F32{inst.Arg(1)})); } @@ -709,6 +724,13 @@ bool FindGradient3DDerivatives(std::array& results, IR::Value coor const auto check_through_shuffle = [](IR::Value input, IR::Value& result) { const IR::Value value_1{GetThroughCast(input.Resolve(), IR::Opcode::BitCastF32U32)}; IR::Inst* const inst2{value_1.InstRecursive()}; + if (inst2->GetOpcode() == IR::Opcode::QuadBroadcast) { + if (!inst2->Arg(1).Resolve().IsImmediate()) { + return false; + } + result = GetThroughCast(inst2->Arg(0).Resolve(), IR::Opcode::BitCastU32F32); + return true; + } if (inst2->GetOpcode() != IR::Opcode::ShuffleIndex) { return false; } diff --git a/src/shader_recompiler/ir_opt/geometry_compaction_pass.cpp b/src/shader_recompiler/ir_opt/geometry_compaction_pass.cpp new file mode 100644 index 0000000000..98cfa47401 --- /dev/null +++ b/src/shader_recompiler/ir_opt/geometry_compaction_pass.cpp @@ -0,0 +1,129 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "shader_recompiler/frontend/ir/ir_emitter.h" +#include "shader_recompiler/host_translate_info.h" +#include "shader_recompiler/ir_opt/passes.h" + +namespace Shader::Optimization { +namespace { +constexpr int MAX_BALLOT_DEPTH = 8; + +struct SlotAtomic { + IR::Block* block; + IR::Inst* atomic; + IR::Inst* ballot; +}; + +IR::Inst* FindBallot(const IR::Value& value, int depth) { + if (depth > MAX_BALLOT_DEPTH || value.IsImmediate()) { + return nullptr; + } + IR::Inst* const inst{value.InstRecursive()}; + if (inst->GetOpcode() == IR::Opcode::SubgroupBallot) { + return inst; + } + if (inst->GetOpcode() == IR::Opcode::Phi) { + return nullptr; + } + for (size_t index = 0; index < inst->NumArgs(); ++index) { + if (IR::Inst* const ballot{FindBallot(inst->Arg(index), depth + 1)}) { + return ballot; + } + } + return nullptr; +} + +bool IsShuffle(IR::Opcode opcode) { + switch (opcode) { + case IR::Opcode::ShuffleIndex: + case IR::Opcode::ShuffleUp: + case IR::Opcode::ShuffleDown: + case IR::Opcode::ShuffleButterfly: + return true; + default: + return false; + } +} + +IR::U32 PrimitiveSlot(IR::IREmitter& ir, u32 invocations, const IR::U32& amount) { + IR::U32 key{ir.GetAttributeU32(IR::Attribute::PrimitiveId)}; + if (invocations > 1) { + key = ir.IAdd(ir.IMul(key, ir.Imm32(invocations)), ir.InvocationId()); + } + return ir.IMul(key, amount); +} + +bool MergesAtomic(const IR::Inst& phi, const IR::Block& block, const IR::Inst& atomic) { + for (size_t index = 0; index < phi.NumArgs(); ++index) { + const IR::Value arg{phi.Arg(index)}; + if (phi.PhiBlock(index) == &block && !arg.IsImmediate() && + arg.InstRecursive() == &atomic) { + return true; + } + } + return false; +} + +void RewriteMergedSlots(IR::Block& block, IR::Inst& atomic, u32 invocations) { + for (IR::Block* const successor : block.ImmSuccessors()) { + for (IR::Inst& phi : successor->Instructions()) { + if (phi.GetOpcode() != IR::Opcode::Phi || !MergesAtomic(phi, block, atomic)) { + continue; + } + for (size_t index = 0; index < phi.NumArgs(); ++index) { + const IR::Value arg{phi.Arg(index)}; + if (arg.IsImmediate() || !IsShuffle(arg.InstRecursive()->GetOpcode())) { + continue; + } + IR::IREmitter ir{*phi.PhiBlock(index)}; + const IR::U32 amount{arg.InstRecursive()->Arg(0)}; + phi.SetArg(index, PrimitiveSlot(ir, invocations, amount)); + } + } + } +} + +void RewriteAtomic(IR::Block& block, IR::Inst& atomic, u32 invocations) { + const auto insert_point{IR::Block::InstructionList::s_iterator_to(atomic)}; + IR::IREmitter ir{block, insert_point}; + const IR::U32 amount{atomic.Arg(2)}; + const IR::U32 slot{PrimitiveSlot(ir, invocations, amount)}; + block.PrependNewInst(insert_point, IR::Opcode::StorageAtomicUMax32, + {atomic.Arg(0), atomic.Arg(1), ir.IAdd(slot, amount)}); + atomic.ReplaceUsesWith(slot); +} + +void NeutralizePredicate(IR::Inst& ballot) { + const IR::Value pred{ballot.Arg(0)}; + if (!pred.IsImmediate()) { + pred.InstRecursive()->ReplaceUsesWith(IR::Value{true}); + } +} +} + +void GeometryCompactionPass(IR::Program& program, const HostTranslateInfo& host_info) { + if (program.stage != Stage::Geometry || !host_info.single_lane_geometry_subgroups) { + return; + } + boost::container::small_vector slot_atomics; + for (IR::Block* const block : program.post_order_blocks) { + for (IR::Inst& inst : block->Instructions()) { + if (inst.GetOpcode() != IR::Opcode::StorageAtomicIAdd32) { + continue; + } + if (IR::Inst* const ballot{FindBallot(inst.Arg(2), 0)}) { + slot_atomics.push_back({block, &inst, ballot}); + } + } + } + for (const SlotAtomic& slot_atomic : slot_atomics) { + RewriteMergedSlots(*slot_atomic.block, *slot_atomic.atomic, program.invocations); + RewriteAtomic(*slot_atomic.block, *slot_atomic.atomic, program.invocations); + NeutralizePredicate(*slot_atomic.ballot); + } +} + +} diff --git a/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp b/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp index 2d4feca02c..f49d54e7ab 100644 --- a/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp +++ b/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -545,22 +548,11 @@ void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateIn IR::Block* const block{storage_inst.block}; IR::Inst* const inst{storage_inst.inst}; const IR::U32 offset{ - StorageOffset(*block, *inst, storage_buffer, host_info.min_ssbo_alignment)}; + StorageOffset(*block, *inst, storage_buffer, u32(host_info.min_ssbo_alignment))}; Replace(*block, *inst, index, offset); } } -template -static u32 Add(Descriptors& descriptors, const Descriptor& desc, Func&& pred) { - // TODO: Handle arrays - const auto it{std::ranges::find_if(descriptors, pred)}; - if (it != descriptors.end()) { - return static_cast(std::distance(descriptors.begin(), it)); - } - descriptors.push_back(desc); - return static_cast(descriptors.size()) - 1; -} - void JoinStorageInfo(Info& base, Info& source) { auto& descriptors = base.storage_buffers_descriptors; for (auto& desc : source.storage_buffers_descriptors) { diff --git a/src/shader_recompiler/ir_opt/lower_fp64_to_fp32.cpp b/src/shader_recompiler/ir_opt/lower_fp64_to_fp32.cpp index 5db7a38add..9a05609857 100644 --- a/src/shader_recompiler/ir_opt/lower_fp64_to_fp32.cpp +++ b/src/shader_recompiler/ir_opt/lower_fp64_to_fp32.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 diff --git a/src/shader_recompiler/ir_opt/lower_int64_to_int32.cpp b/src/shader_recompiler/ir_opt/lower_int64_to_int32.cpp index cdb58f46b3..dbca9643d8 100644 --- a/src/shader_recompiler/ir_opt/lower_int64_to_int32.cpp +++ b/src/shader_recompiler/ir_opt/lower_int64_to_int32.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -181,6 +184,72 @@ void ShiftRightArithmetic64To32(IR::Block& block, IR::Inst& inst) { inst.ReplaceUsesWith(ir.CompositeConstruct(ret_lo, ret_hi)); } +void IAbs64To32(IR::Block& block, IR::Inst& inst) { + IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst)); + const auto [lo, hi]{Unpack(ir, inst.Arg(0))}; + + const IR::U32 neg_lo{ir.IAdd(ir.BitwiseNot(lo), ir.Imm32(1))}; + const IR::U32 carry{IR::U32{ir.Select(ir.GetCarryFromOp(neg_lo), ir.Imm32(1u), ir.Imm32(0u))}}; + const IR::U32 neg_hi{ir.IAdd(ir.BitwiseNot(hi), carry)}; + + const IR::U1 is_negative{ir.INotEqual(ir.BitwiseAnd(hi, ir.Imm32(0x80000000u)), ir.Imm32(0u))}; + const IR::U32 ret_lo{IR::U32{ir.Select(is_negative, neg_lo, lo)}}; + const IR::U32 ret_hi{IR::U32{ir.Select(is_negative, neg_hi, hi)}}; + inst.ReplaceUsesWith(ir.CompositeConstruct(ret_lo, ret_hi)); +} + +void SelectU64To32(IR::Block& block, IR::Inst& inst) { + IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst)); + const IR::U1 condition{inst.Arg(0)}; + const auto [true_lo, true_hi]{Unpack(ir, inst.Arg(1))}; + const auto [false_lo, false_hi]{Unpack(ir, inst.Arg(2))}; + + const IR::U32 ret_lo{IR::U32{ir.Select(condition, true_lo, false_lo)}}; + const IR::U32 ret_hi{IR::U32{ir.Select(condition, true_hi, false_hi)}}; + inst.ReplaceUsesWith(ir.CompositeConstruct(ret_lo, ret_hi)); +} + +void UndefU64To32(IR::Block& block, IR::Inst& inst) { + IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst)); + inst.ReplaceUsesWith(ir.CompositeConstruct(ir.Imm32(0u), ir.Imm32(0u))); +} + +void ConvertU64U32To32(IR::Block& block, IR::Inst& inst) { + IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst)); + inst.ReplaceUsesWith(ir.CompositeConstruct(IR::U32{inst.Arg(0)}, ir.Imm32(0u))); +} + +void ConvertU32U64To32(IR::Block& block, IR::Inst& inst) { + IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst)); + inst.ReplaceUsesWith(Unpack(ir, inst.Arg(0)).first); +} + +void IntToFloat64To32(IR::Block& block, IR::Inst& inst, bool is_signed, size_t dest_bitsize) { + IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst)); + const auto [lo, hi]{Unpack(ir, inst.Arg(0))}; + const IR::F32 low{ir.ConvertUToF(32, 32, lo)}; + const IR::F32 high{is_signed ? IR::F32{ir.ConvertSToF(32, 32, hi)} + : IR::F32{ir.ConvertUToF(32, 32, hi)}}; + const IR::F32 combined{ir.FPFma(high, ir.Imm32(4294967296.0f), low)}; + if (dest_bitsize == 32) { + inst.ReplaceUsesWith(combined); + } else { + inst.ReplaceUsesWith(ir.FPConvert(dest_bitsize, combined)); + } +} + +void FloatToInt64To32(IR::Block& block, IR::Inst& inst, bool is_signed, size_t src_bitsize) { + IR::IREmitter ir(block, IR::Block::InstructionList::s_iterator_to(inst)); + const IR::F32 value{src_bitsize == 32 ? IR::F32{inst.Arg(0)} + : IR::F32{ir.FPConvert(32, IR::F16F32F64{inst.Arg(0)})}}; + const IR::F32 high_f{ir.FPFloor(ir.FPMul(value, ir.Imm32(1.0f / 4294967296.0f)))}; + const IR::U32 hi{is_signed ? IR::U32{ir.ConvertFToS(32, high_f)} + : IR::U32{ir.ConvertFToU(32, high_f)}}; + const IR::F32 low_f{ir.FPFma(high_f, ir.FPNeg(ir.Imm32(4294967296.0f)), value)}; + const IR::U32 lo{IR::U32{ir.ConvertFToU(32, low_f)}}; + inst.ReplaceUsesWith(ir.CompositeConstruct(lo, hi)); +} + void Lower(IR::Block& block, IR::Inst& inst) { switch (inst.GetOpcode()) { case IR::Opcode::PackUint2x32: @@ -218,6 +287,62 @@ void Lower(IR::Block& block, IR::Inst& inst) { return inst.ReplaceOpcode(IR::Opcode::GlobalAtomicXor32x2); case IR::Opcode::GlobalAtomicExchange64: return inst.ReplaceOpcode(IR::Opcode::GlobalAtomicExchange32x2); + case IR::Opcode::StorageAtomicIAdd64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicIAdd32x2); + case IR::Opcode::StorageAtomicSMin64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicSMin32x2); + case IR::Opcode::StorageAtomicUMin64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicUMin32x2); + case IR::Opcode::StorageAtomicSMax64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicSMax32x2); + case IR::Opcode::StorageAtomicUMax64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicUMax32x2); + case IR::Opcode::StorageAtomicAnd64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicAnd32x2); + case IR::Opcode::StorageAtomicOr64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicOr32x2); + case IR::Opcode::StorageAtomicXor64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicXor32x2); + case IR::Opcode::StorageAtomicExchange64: + return inst.ReplaceOpcode(IR::Opcode::StorageAtomicExchange32x2); + case IR::Opcode::BitCastU64F64: + return inst.ReplaceOpcode(IR::Opcode::UnpackDouble2x32); + case IR::Opcode::BitCastF64U64: + return inst.ReplaceOpcode(IR::Opcode::PackDouble2x32); + case IR::Opcode::UndefU64: + return UndefU64To32(block, inst); + case IR::Opcode::SelectU64: + return SelectU64To32(block, inst); + case IR::Opcode::IAbs64: + return IAbs64To32(block, inst); + case IR::Opcode::ConvertU64U32: + return ConvertU64U32To32(block, inst); + case IR::Opcode::ConvertU32U64: + return ConvertU32U64To32(block, inst); + case IR::Opcode::ConvertS64F16: + return FloatToInt64To32(block, inst, true, 16); + case IR::Opcode::ConvertS64F32: + return FloatToInt64To32(block, inst, true, 32); + case IR::Opcode::ConvertS64F64: + return FloatToInt64To32(block, inst, true, 64); + case IR::Opcode::ConvertU64F16: + return FloatToInt64To32(block, inst, false, 16); + case IR::Opcode::ConvertU64F32: + return FloatToInt64To32(block, inst, false, 32); + case IR::Opcode::ConvertU64F64: + return FloatToInt64To32(block, inst, false, 64); + case IR::Opcode::ConvertF16S64: + return IntToFloat64To32(block, inst, true, 16); + case IR::Opcode::ConvertF32S64: + return IntToFloat64To32(block, inst, true, 32); + case IR::Opcode::ConvertF64S64: + return IntToFloat64To32(block, inst, true, 64); + case IR::Opcode::ConvertF16U64: + return IntToFloat64To32(block, inst, false, 16); + case IR::Opcode::ConvertF32U64: + return IntToFloat64To32(block, inst, false, 32); + case IR::Opcode::ConvertF64U64: + return IntToFloat64To32(block, inst, false, 64); default: break; } diff --git a/src/shader_recompiler/ir_opt/passes.h b/src/shader_recompiler/ir_opt/passes.h index 1e637cb23c..bf325a4cd9 100644 --- a/src/shader_recompiler/ir_opt/passes.h +++ b/src/shader_recompiler/ir_opt/passes.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -16,6 +19,7 @@ void CollectShaderInfoPass(Environment& env, IR::Program& program); void ConditionalBarrierPass(IR::Program& program); void ConstantPropagationPass(Environment& env, IR::Program& program); void DeadCodeEliminationPass(IR::Program& program); +void GeometryCompactionPass(IR::Program& program, const HostTranslateInfo& host_info); void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateInfo& host_info); void IdentityRemovalPass(IR::Program& program); void LowerFp64ToFp32(IR::Program& program); @@ -28,6 +32,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo void LayerPass(IR::Program& program, const HostTranslateInfo& host_info); void VendorWorkaroundPass(IR::Program& program); void VerificationPass(const IR::Program& program); +bool HasBrokenPattern(const IR::Program& program); // Dual Vertex void VertexATransformPass(IR::Program& program); diff --git a/src/shader_recompiler/ir_opt/rescaling_pass.cpp b/src/shader_recompiler/ir_opt/rescaling_pass.cpp index 1e639096a8..cae8110a26 100644 --- a/src/shader_recompiler/ir_opt/rescaling_pass.cpp +++ b/src/shader_recompiler/ir_opt/rescaling_pass.cpp @@ -67,18 +67,11 @@ void VisitMark(IR::Block& block, IR::Inst& inst) { if (must_patch_outside) { const auto it{IR::Block::InstructionList::s_iterator_to(inst)}; IR::IREmitter ir{block, it}; - if (Settings::values.rescale_hack.GetValue()) { - const IR::F32 new_inst{&*block.PrependNewInst(it, inst)}; - const IR::F32 up_factor{ir.FPRecip(ir.ResolutionDownFactor())}; - const IR::Value converted{ir.FPMul(new_inst, up_factor)}; - inst.ReplaceUsesWith(converted); - } else { - IR::Inst* const new_inst{&*block.PrependNewInst(it, inst)}; - const IR::F32 new_bitcast{ir.ConvertUToF(32, 32, IR::Value{new_inst})}; - const IR::F32 up_factor{ir.FPRecip(ir.ResolutionDownFactor())}; - const IR::Value converted{ir.FPMul(new_bitcast, up_factor)}; - inst.ReplaceUsesWith(converted); - } + IR::Inst* const new_inst{&*block.PrependNewInst(it, inst)}; + const IR::F32 new_bitcast{ir.ConvertUToF(32, 32, IR::Value{new_inst})}; + const IR::F32 up_factor{ir.FPRecip(ir.ResolutionDownFactor())}; + const IR::Value converted{ir.FPMul(new_bitcast, up_factor)}; + inst.ReplaceUsesWith(converted); } break; } diff --git a/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp b/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp index 2bde38a661..b5cd233992 100644 --- a/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp +++ b/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp @@ -19,7 +19,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp index 55b6f7213c..fae69ab91b 100644 --- a/src/shader_recompiler/ir_opt/texture_pass.cpp +++ b/src/shader_recompiler/ir_opt/texture_pass.cpp @@ -7,11 +7,12 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include #include +#include "common/settings.h" #include "shader_recompiler/environment.h" #include "shader_recompiler/frontend/ir/basic_block.h" #include "shader_recompiler/frontend/ir/breadth_first_search.h" @@ -30,72 +31,117 @@ struct TextureInst { using TextureInstVector = boost::container::small_vector; -constexpr u32 DESCRIPTOR_SIZE = 8; -constexpr u32 DESCRIPTOR_SIZE_SHIFT = static_cast(std::countr_zero(DESCRIPTOR_SIZE)); -constexpr u32 DYNAMIC_DESCRIPTOR_CBUF_BYTES = 16 * 1024; -constexpr u32 MAX_DYNAMIC_DESCRIPTOR_COUNT = 1024; - -u32 DynamicDescriptorSizeShift(const IR::U32& dynamic_offset) { - const IR::Inst* const inst{dynamic_offset.InstRecursive()}; - if (!inst || inst->GetOpcode() != IR::Opcode::ShiftLeftLogical32) { - return DESCRIPTOR_SIZE_SHIFT; +const IR::Block* ConditionalHeader(const IR::Block* body) { + const auto predecessors{body->ImmPredecessors()}; + const auto successors{body->ImmSuccessors()}; + if (predecessors.size() != 1 || successors.size() != 1) return nullptr; + const IR::Block* const header{predecessors[0]}; + const auto targets{header->ImmSuccessors()}; + if (targets.size() != 2 || targets[0] != body || targets[1] != successors[0] || + targets[1]->ImmPredecessors().size() != 2 || header->empty() || + header->back().GetOpcode() != IR::Opcode::ConditionRef) { + return nullptr; } - const IR::Value shift{inst->Arg(1)}; - if (!shift.IsImmediate()) { - return DESCRIPTOR_SIZE_SHIFT; - } - const u32 size_shift{shift.U32()}; - return size_shift >= DESCRIPTOR_SIZE_SHIFT && size_shift < 31 ? size_shift - : DESCRIPTOR_SIZE_SHIFT; + return header; } -u32 DynamicDescriptorCount(u32 base_offset, u32 size_shift) { - if (size_shift >= 31 || base_offset >= DYNAMIC_DESCRIPTOR_CBUF_BYTES) { - return 1; +std::pair Condition(IR::Value value) { + bool negated{}; + while (const IR::Inst* inst = value.TryInstRecursive()) { + if (inst->GetOpcode() == IR::Opcode::LogicalNot) negated = !negated; + else if (inst->GetOpcode() != IR::Opcode::ConditionRef) break; + value = inst->Arg(0); } - const u32 stride{1U << size_shift}; - const u32 available{DYNAMIC_DESCRIPTOR_CBUF_BYTES - base_offset}; - if (available < DESCRIPTOR_SIZE) { - return 1; + return {value.Resolve(), negated}; +} + +IR::Value ResolveConditionalPhi(IR::Value value, const IR::Block* block) { + if (!value.Resolve().IsPhi() || !(block = ConditionalHeader(block))) return value; + const auto [condition, negated]{Condition(block->back().Arg(0))}; + boost::container::small_vector visited; + while (value.Resolve().IsPhi() && std::ranges::find(visited, block) == visited.end()) { + visited.push_back(block); + const IR::Inst* const phi{value.InstRecursive()}; + const auto predecessors{block->ImmPredecessors()}; + if (predecessors.size() == 1) { + block = predecessors[0]; + continue; + } + if (predecessors.size() != 2 || phi->NumArgs() != 2) break; + const IR::Block* body{predecessors[0]}; + const IR::Block* header{ConditionalHeader(body)}; + if (header != predecessors[1]) { + body = predecessors[1]; + header = ConditionalHeader(body); + if (header != predecessors[0]) break; + } + const auto [other_condition, other_negated]{Condition(header->back().Arg(0))}; + if (condition == other_condition && (phi->PhiBlock(0) == header || phi->PhiBlock(0) == body)) { + const IR::Block* const incoming{negated == other_negated ? body : header}; + value = phi->Arg(phi->PhiBlock(0) == incoming ? 0 : 1); + } + block = header; } - const u32 available_count{1U + (available - DESCRIPTOR_SIZE) / stride}; - return std::min(MAX_DYNAMIC_DESCRIPTOR_COUNT, available_count); + return value; +} + +constexpr u32 DESCRIPTOR_SIZE = 8; +constexpr u32 DESCRIPTOR_SIZE_SHIFT = u32(std::countr_zero(DESCRIPTOR_SIZE)); +constexpr u32 DESCRIPTOR_MAX_COUNT = 1024; + +u32 DynamicDescriptorSizeShift(const IR::U32& dynamic_offset) { + const IR::Inst* const inst = dynamic_offset.InstRecursive(); + if (!inst || inst->GetOpcode() != IR::Opcode::ShiftLeftLogical32) + return DESCRIPTOR_SIZE_SHIFT; + const IR::Value shift = inst->Arg(1); + if (!shift.IsImmediate()) + return DESCRIPTOR_SIZE_SHIFT; + const u32 size_shift = shift.U32(); + return size_shift >= DESCRIPTOR_SIZE_SHIFT && size_shift < 31 ? size_shift : DESCRIPTOR_SIZE_SHIFT; +} + +u32 DynamicDescriptorCount(u32 base_offset, u32 size_shift, u32 max_descriptors) { + auto const descriptor_limit = (std::max)(1U, max_descriptors); + auto const max_cbuf_bytes = 16 * descriptor_limit; + if (size_shift >= 31 || base_offset >= max_cbuf_bytes) + return 1; + auto const stride = 1U << size_shift; + auto const available = max_cbuf_bytes - base_offset; + if (available < DESCRIPTOR_SIZE) + return 1; + auto const available_count = 1U + (available - DESCRIPTOR_SIZE) / stride; + return std::min(descriptor_limit, available_count); } u32 SaturatingSub(u32 lhs, u32 rhs) { return lhs > rhs ? lhs - rhs : 0; } -template -u32 StaticDescriptorCount(const Descriptors& descriptors) { - u32 count{}; - for (const auto& desc : descriptors) { - if (desc.count <= 1) { - count += desc.count; - } - } - return count; +template +[[nodiscard]] u32 StaticDescriptorCount(T const& descriptors) noexcept { + return std::accumulate(descriptors.cbegin(), descriptors.cend(), 0U, [](auto const& acc, auto const& e) { + return acc + (e.count <= 1 ? e.count : 0); + }); } -u32 DynamicSampledTextureCap(const Info& info, const HostTranslateInfo& host_info, - u32 dynamic_arrays) { - if (dynamic_arrays == 0) { - return MAX_DYNAMIC_DESCRIPTOR_COUNT; +u32 DynamicSampledTextureCap(const Info& info, const HostTranslateInfo& host_info, u32 dynamic_arrays) { + auto const sampled_limit = (std::max)(1U, std::min(host_info.max_per_stage_descriptor_sampled_images, + host_info.max_descriptor_set_sampled_images)); + auto const resource_limit = (std::max)(1U, host_info.max_per_stage_resources); + if (dynamic_arrays > 0) { + auto const sampled_static_count = StaticDescriptorCount(info.texture_buffer_descriptors) + StaticDescriptorCount(info.texture_descriptors); + auto const resource_static_count = + NumDescriptors(info.constant_buffer_descriptors) + + NumDescriptors(info.storage_buffers_descriptors) + + sampled_static_count + NumDescriptors(info.image_buffer_descriptors) + + NumDescriptors(info.image_descriptors); + auto const sampled_budget = SaturatingSub(sampled_limit, sampled_static_count); + auto const resource_budget = SaturatingSub(resource_limit, resource_static_count); + auto const sampled_cap = sampled_budget / dynamic_arrays; + auto const resource_cap = resource_budget / dynamic_arrays; + return (std::max)(1U, (std::min)(sampled_cap, resource_cap)); } - const u32 sampled_static_count{StaticDescriptorCount(info.texture_buffer_descriptors) + - StaticDescriptorCount(info.texture_descriptors)}; - const u32 resource_static_count{ - NumDescriptors(info.constant_buffer_descriptors) + - NumDescriptors(info.storage_buffers_descriptors) + sampled_static_count + - NumDescriptors(info.image_buffer_descriptors) + NumDescriptors(info.image_descriptors)}; - const u32 sampled_limit{std::min(host_info.max_per_stage_descriptor_sampled_images, - host_info.max_descriptor_set_sampled_images)}; - const u32 sampled_budget{SaturatingSub(sampled_limit, sampled_static_count)}; - const u32 resource_budget{SaturatingSub(host_info.max_per_stage_resources, - resource_static_count)}; - const u32 sampled_cap{sampled_budget / dynamic_arrays}; - const u32 resource_cap{resource_budget / dynamic_arrays}; - return std::max(1U, std::min({MAX_DYNAMIC_DESCRIPTOR_COUNT, sampled_cap, resource_cap})); + return (std::min)({DESCRIPTOR_MAX_COUNT, sampled_limit, resource_limit}); } IR::Opcode IndexedInstruction(const IR::Inst& inst) { @@ -269,21 +315,25 @@ bool IsTextureInstruction(const IR::Inst& inst) { static inline u32 ReadCbufCached(Environment& env, u32 index, u32 offset) { const CbufWordKey k{index, offset}; - if (auto it = env.cbuf_word_cache.find(k); it != env.cbuf_word_cache.end()) return it->second; + if (auto it = env.cbuf_word_cache.find(k); it != env.cbuf_word_cache.end()) + return it->second; const u32 v = env.ReadCbufValue(index, offset); env.cbuf_word_cache.emplace(k, v); return v; } static inline u32 GetTextureHandleCached(Environment& env, const ConstBufferAddr& cbuf) { - const u32 sec_idx = cbuf.has_secondary ? cbuf.secondary_index : cbuf.index; - const u32 sec_off = cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset; - const HandleKey hk{cbuf.index, cbuf.offset, cbuf.shift_left, - sec_idx, sec_off, cbuf.secondary_shift_left, cbuf.has_secondary}; - if (auto it = env.handle_cache.find(hk); it != env.handle_cache.end()) return it->second; - + // Must all be uniquely different variables + // If has secondary, then it will be cbuf.secondary_{index|offset}, else its 0. + // So we can just hand it out the raw variable without using sec_idx or sec_off + // because comparing 0 against 0 will yield true. + const HandleKey hk{cbuf.index, cbuf.offset, cbuf.shift_left, cbuf.secondary_index, cbuf.secondary_offset, cbuf.secondary_shift_left, cbuf.count, cbuf.has_secondary}; + if (auto it = env.handle_cache.find(hk); it != env.handle_cache.end()) + return it->second; + const u32 sec_idx = cbuf.has_secondary ? cbuf.secondary_index : cbuf.index; + const u32 sec_off = cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset; const u32 lhs = ReadCbufCached(env, cbuf.index, cbuf.offset) << cbuf.shift_left; - const u32 rhs = ReadCbufCached(env, sec_idx, sec_off) << cbuf.secondary_shift_left; + const u32 rhs = ReadCbufCached(env, sec_idx, sec_off) << cbuf.secondary_shift_left; const u32 handle = lhs | rhs; env.handle_cache.emplace(hk, handle); return handle; @@ -303,21 +353,23 @@ static inline bool IsTexturePixelFormatIntegerCached(Environment& env, } -std::optional Track(const IR::Value& value, Environment& env); -static inline std::optional TrackCached(const IR::Value& v, Environment& env) { - if (const IR::Inst* key = v.InstRecursive()) { +std::optional Track(const IR::Value& value, Environment& env, const HostTranslateInfo& host_info, const IR::Block* block); +static inline std::optional TrackCached(const IR::Value& v, Environment& env, const HostTranslateInfo& host_info, const IR::Block* block) { + if (const IR::Inst* key = v.TryInstRecursive()) { if (auto it = env.track_cache.find(key); it != env.track_cache.end()) return it->second; - auto found = Track(v, env); + auto found = Track(v, env, host_info, block); if (found) env.track_cache.emplace(key, *found); return found; } - return Track(v, env); + return Track(v, env, host_info, block); } -std::optional TryGetConstBuffer(const IR::Inst* inst, Environment& env); +std::optional TryGetConstBuffer(const IR::Inst* inst, Environment& env, const HostTranslateInfo& host_info, const IR::Block* block); -std::optional Track(const IR::Value& value, Environment& env) { - return IR::BreadthFirstSearch(value, [&env](const IR::Inst* inst) { return TryGetConstBuffer(inst, env); }); +std::optional Track(const IR::Value& value, Environment& env, const HostTranslateInfo& host_info, const IR::Block* block) { + return IR::BreadthFirstSearch(value, [&env, &host_info, block](const IR::Inst* inst) { + return TryGetConstBuffer(inst, env, host_info, block); + }, [&](IR::Value operand) { return ResolveConditionalPhi(operand, block); }); } std::optional TryGetConstant(IR::Value& value, Environment& env) { @@ -341,13 +393,13 @@ std::optional TryGetConstant(IR::Value& value, Environment& env) { return ReadCbufCached(env, index_number, offset_number); } -std::optional TryGetConstBuffer(const IR::Inst* inst, Environment& env) { +std::optional TryGetConstBuffer(const IR::Inst* inst, Environment& env, const HostTranslateInfo& host_info, const IR::Block* block) { switch (inst->GetOpcode()) { default: return std::nullopt; case IR::Opcode::BitwiseOr32: { - std::optional lhs{TrackCached(inst->Arg(0), env)}; - std::optional rhs{TrackCached(inst->Arg(1), env)}; + std::optional lhs{TrackCached(inst->Arg(0), env, host_info, block)}; + std::optional rhs{TrackCached(inst->Arg(1), env, host_info, block)}; if (!lhs || !rhs) { return std::nullopt; } @@ -377,12 +429,11 @@ std::optional TryGetConstBuffer(const IR::Inst* inst, Environme if (!shift.IsImmediate()) { return std::nullopt; } - std::optional lhs{TrackCached(inst->Arg(0), env)}; + std::optional lhs{TrackCached(inst->Arg(0), env, host_info, block)}; if (lhs) { lhs->shift_left = shift.U32(); } return lhs; - break; } case IR::Opcode::BitwiseAnd32: { IR::Value op1{inst->Arg(0)}; @@ -406,7 +457,7 @@ std::optional TryGetConstBuffer(const IR::Inst* inst, Environme return std::nullopt; } while (false); } - std::optional lhs{TrackCached(op1, env)}; + std::optional lhs{TrackCached(op1, env, host_info, block)}; if (lhs) { lhs->shift_left = static_cast(std::countr_zero(op2.U32())); } @@ -452,7 +503,10 @@ std::optional TryGetConstBuffer(const IR::Inst* inst, Environme } else { return std::nullopt; } - const u32 size_shift{DynamicDescriptorSizeShift(dynamic_offset)}; + auto const size_shift = DynamicDescriptorSizeShift(dynamic_offset); + auto const sampled_limit = (std::max)(1U, (std::min)(host_info.max_per_stage_descriptor_sampled_images, + host_info.max_descriptor_set_sampled_images)); + auto const resource_limit = (std::max)(1U, host_info.max_per_stage_resources); return ConstBufferAddr{ .index = index.U32(), .offset = base_offset, @@ -461,15 +515,15 @@ std::optional TryGetConstBuffer(const IR::Inst* inst, Environme .secondary_offset = 0, .secondary_shift_left = 0, .dynamic_offset = dynamic_offset, - .count = DynamicDescriptorCount(base_offset, size_shift), + .count = DynamicDescriptorCount(base_offset, size_shift, (std::min)({DESCRIPTOR_MAX_COUNT, sampled_limit, resource_limit})), .has_secondary = false, }; } -TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) { +TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst, const HostTranslateInfo& host_info) { ConstBufferAddr addr; if (IsBindless(inst)) { - const std::optional track_addr{TrackCached(inst.Arg(0), env)}; + const std::optional track_addr{TrackCached(inst.Arg(0), env, host_info, block)}; if (!track_addr) { throw NotImplementedException("Failed to track bindless texture constant buffer"); @@ -505,15 +559,15 @@ u32 GetTextureHandle(Environment& env, const ConstBufferAddr& cbuf) { return lhs_raw | rhs_raw; } - [[maybe_unused]]TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) { +[[maybe_unused]] TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) { return env.ReadTextureType(GetTextureHandle(env, cbuf)); } - [[maybe_unused]]TexturePixelFormat ReadTexturePixelFormat(Environment& env, const ConstBufferAddr& cbuf) { +[[maybe_unused]] TexturePixelFormat ReadTexturePixelFormat(Environment& env, const ConstBufferAddr& cbuf) { return env.ReadTexturePixelFormat(GetTextureHandle(env, cbuf)); } - [[maybe_unused]]bool IsTexturePixelFormatInteger(Environment& env, const ConstBufferAddr& cbuf) { +[[maybe_unused]] bool IsTexturePixelFormatInteger(Environment& env, const ConstBufferAddr& cbuf) { return env.IsTexturePixelFormatInteger(GetTextureHandle(env, cbuf)); } @@ -566,6 +620,7 @@ public: })}; // TODO: Read this from TIC texture_descriptors[index].is_multisample |= desc.is_multisample; + texture_descriptors[index].is_integer |= desc.is_integer; return index; } @@ -670,11 +725,12 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo env.track_cache.clear(); TextureInstVector to_replace; for (IR::Block* const block : program.post_order_blocks) { + env.track_cache.clear(); for (IR::Inst& inst : block->Instructions()) { if (!IsTextureInstruction(inst)) { continue; } - to_replace.push_back(MakeInst(env, block, inst)); + to_replace.push_back(MakeInst(env, block, inst, host_info)); } } // Sort instructions to visit textures by constant buffer index, then by offset @@ -688,8 +744,22 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo program.info.texture_descriptors, program.info.image_descriptors, }; - const u32 sampled_dynamic_cap{ - DynamicSampledTextureCap(program.info, host_info, DynamicSampledTextureArrayCount(to_replace))}; + const u32 sampled_dynamic_cap = DynamicSampledTextureCap(program.info, host_info, DynamicSampledTextureArrayCount(to_replace)); + bool has_last_is_integer{false}; + u32 last_cbuf_index{}; + u32 last_cbuf_offset{}; + bool last_is_integer{false}; + const auto is_texture_pixel_format_integer{[&](const ConstBufferAddr& cbuf_addr) { + if (has_last_is_integer && last_cbuf_index == cbuf_addr.index && + last_cbuf_offset == cbuf_addr.offset) { + return last_is_integer; + } + last_is_integer = IsTexturePixelFormatIntegerCached(env, cbuf_addr); + last_cbuf_index = cbuf_addr.index; + last_cbuf_offset = cbuf_addr.offset; + has_last_is_integer = true; + return last_is_integer; + }}; for (TextureInst& texture_inst : to_replace) { // TODO: Handle arrays IR::Inst* const inst{texture_inst.inst}; @@ -733,9 +803,8 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo break; } u32 index; - const u32 size_shift{cbuf.count > 1 ? DynamicDescriptorSizeShift(cbuf.dynamic_offset) - : DESCRIPTOR_SIZE_SHIFT}; - u32 count{cbuf.count}; + u32 size_shift = cbuf.count > 1 ? DynamicDescriptorSizeShift(cbuf.dynamic_offset) : DESCRIPTOR_SIZE_SHIFT; + u32 count = cbuf.count; switch (inst->GetOpcode()) { case IR::Opcode::ImageRead: case IR::Opcode::ImageAtomicIAdd32: @@ -755,7 +824,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo } const bool is_written{inst->GetOpcode() != IR::Opcode::ImageRead}; const bool is_read{inst->GetOpcode() != IR::Opcode::ImageWrite}; - const bool is_integer{IsTexturePixelFormatIntegerCached(env, cbuf)}; + const bool is_integer{is_texture_pixel_format_integer(cbuf)}; if (flags.type == TextureType::Buffer) { index = descriptors.Add(ImageBufferDescriptor{ .format = flags.image_format, @@ -797,10 +866,12 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo }); } else { count = std::min(count, sampled_dynamic_cap); + const bool is_integer{is_texture_pixel_format_integer(cbuf)}; index = descriptors.Add(TextureDescriptor{ .type = flags.type, .is_depth = flags.is_depth != 0, .is_multisample = is_multisample, + .is_integer = is_integer, .has_secondary = cbuf.has_secondary, .cbuf_index = cbuf.index, .cbuf_offset = cbuf.offset, @@ -811,6 +882,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo .count = count, .size_shift = size_shift, }); + flags.is_integer.Assign(is_integer ? 1 : 0); } break; } @@ -821,8 +893,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo const auto insert_point{IR::Block::InstructionList::s_iterator_to(*inst)}; IR::IREmitter ir{*texture_inst.block, insert_point}; const IR::U32 shift{ir.Imm32(size_shift)}; - inst->SetArg(0, ir.UMin(ir.ShiftRightLogical(cbuf.dynamic_offset, shift), - ir.Imm32(count - 1))); + inst->SetArg(0, ir.UMin(ir.ShiftRightLogical(cbuf.dynamic_offset, shift), ir.Imm32(count - 1))); } else { inst->SetArg(0, IR::Value{}); } diff --git a/src/shader_recompiler/ir_opt/vendor_workaround_pass.cpp b/src/shader_recompiler/ir_opt/vendor_workaround_pass.cpp index 08c658cb81..5842f6135b 100644 --- a/src/shader_recompiler/ir_opt/vendor_workaround_pass.cpp +++ b/src/shader_recompiler/ir_opt/vendor_workaround_pass.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -76,4 +79,44 @@ void VendorWorkaroundPass(IR::Program& program) { } } +bool HasBrokenPattern(const IR::Program& program) { + if (program.stage != Stage::Fragment) { + return false; + } + for (const IR::Block* block : program.post_order_blocks) { + for (const IR::Inst& inst : block->Instructions()) { + switch (inst.GetOpcode()) { + case IR::Opcode::ShuffleIndex: + case IR::Opcode::ShuffleUp: + case IR::Opcode::ShuffleDown: + case IR::Opcode::ShuffleButterfly: + break; + default: + continue; + } + const IR::Value shuffle_arg{inst.Arg(0)}; + if (shuffle_arg.IsImmediate()) { + continue; + } + const IR::Inst* bitcast{shuffle_arg.InstRecursive()}; + if (!bitcast || bitcast->GetOpcode() != IR::Opcode::BitCastU32F32) { + continue; + } + const IR::Value bitcast_arg{bitcast->Arg(0)}; + if (bitcast_arg.IsImmediate()) { + continue; + } + const IR::Inst* attribute{bitcast_arg.InstRecursive()}; + if (!attribute || attribute->GetOpcode() != IR::Opcode::GetAttribute) { + continue; + } + const IR::Attribute attr{attribute->Arg(0).Attribute()}; + if (attr == IR::Attribute::PositionX || attr == IR::Attribute::PositionY) { + return true; + } + } + } + return false; +} + } // namespace Shader::Optimization diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index ff19f0710f..8e9c83f888 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -10,12 +10,18 @@ namespace Shader { +enum class Stage : u32; + struct Profile { u32 supported_spirv{0x00010000}; bool unified_descriptor_binding{}; bool support_descriptor_aliasing{}; bool support_int8{}; + bool support_uniform_and_storage_buffer_8bit{}; + bool support_storage_buffer_8bit{}; bool support_int16{}; + bool support_uniform_and_storage_buffer_16bit{}; + bool support_storage_buffer_16bit{}; bool support_int64{}; bool support_vertex_instance_id{}; bool support_float_controls{}; @@ -29,12 +35,19 @@ struct Profile { bool support_fp32_signed_zero_nan_preserve{}; bool support_fp64_signed_zero_nan_preserve{}; bool support_explicit_workgroup_layout{}; + bool support_workgroup_layout_8bit_access{}; + bool support_workgroup_layout_16bit_access{}; + bool support_shader_quad_control{}; + bool support_quad_shuffles{}; bool support_vote{}; + bool support_shuffle_relative{}; + u32 supported_subgroup_stages{0x7F}; bool support_viewport_index_layer_non_geometry{}; bool support_viewport_mask{}; bool support_typeless_image_loads{}; bool support_demote_to_helper_invocation{}; bool support_int64_atomics{}; + bool support_shared_int64_atomics{}; bool support_derivative_control{}; bool support_geometry_shader_passthrough{}; bool support_native_ndc{}; @@ -49,6 +62,9 @@ struct Profile { bool support_multi_viewport{}; bool support_geometry_streams{}; bool support_sampled_image_array_nonuniform_indexing{}; + bool support_storage_image_array_nonuniform_indexing{}; + bool support_uniform_texel_buffer_array_nonuniform_indexing{}; + bool support_storage_texel_buffer_array_nonuniform_indexing{}; bool warp_size_potentially_larger_than_guest{}; @@ -73,6 +89,8 @@ struct Profile { bool has_broken_signed_operations{}; /// Float controls break when fp16 is enabled bool has_broken_fp16_float_controls{}; + /// Declaring fp32 denorm flush to zero miscompiles on some drivers + bool has_broken_fp32_denorm_flush{}; /// Dynamic vec4 indexing is broken on some OpenGL drivers bool has_gl_component_indexing_bug{}; /// The precise type qualifier is broken in the fragment stage of some drivers @@ -85,6 +103,8 @@ struct Profile { bool ignore_nan_fp_comparisons{}; /// Some drivers have broken support for OpVectorExtractDynamic on subgroup mask inputs bool has_broken_spirv_subgroup_mask_vector_extract_dynamic{}; + bool has_broken_spirv_subgroup_shuffle{}; + u32 max_subgroup_size{}; u32 gl_max_compute_smem_size{}; @@ -92,8 +112,11 @@ struct Profile { bool has_broken_robust{}; u64 min_ssbo_alignment{}; - u32 max_user_clip_distances{}; + + bool SupportsSubgroupStage(Stage stage) const { + return (supported_subgroup_stages & (1u << static_cast(stage))) != 0; + } }; } // namespace Shader diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h index dfacc06802..71309e040e 100644 --- a/src/shader_recompiler/shader_info.h +++ b/src/shader_recompiler/shader_info.h @@ -209,6 +209,7 @@ struct TextureDescriptor { TextureType type; bool is_depth; bool is_multisample; + bool is_integer; bool has_secondary; u32 cbuf_index; u32 cbuf_offset; @@ -251,6 +252,7 @@ struct Info { bool uses_is_helper_invocation{}; bool uses_subgroup_invocation_id{}; bool uses_subgroup_shuffles{}; + bool uses_quad_shuffles{}; std::array uses_patches{}; std::array interpolation{}; diff --git a/src/tests/common/fibers.cpp b/src/tests/common/fibers.cpp index ce17a5c52c..93f6cd4fdb 100644 --- a/src/tests/common/fibers.cpp +++ b/src/tests/common/fibers.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include @@ -39,7 +39,7 @@ public: private: mutable std::mutex mutex; - ankerl::unordered_dense::map ids; + ::Common::unordered_map ids; }; class TestControl1 { diff --git a/src/tests/common/host_memory.cpp b/src/tests/common/host_memory.cpp index cb040c942d..71354a6c59 100644 --- a/src/tests/common/host_memory.cpp +++ b/src/tests/common/host_memory.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -15,12 +18,19 @@ static constexpr auto PERMS = Common::MemoryPermission::ReadWrite; static constexpr auto HEAP = false; TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") { - { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); } - { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); } + { + HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); + } + { + HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); + } } TEST_CASE("HostMemory: Simple map", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x5000, 0x8000, 0x1000, PERMS, HEAP); volatile u8* const data = mem.VirtualBasePointer() + 0x5000; @@ -30,6 +40,7 @@ TEST_CASE("HostMemory: Simple map", "[common]") { TEST_CASE("HostMemory: Simple mirror map", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP); mem.Map(0x8000, 0x4000, 0x1000, PERMS, HEAP); @@ -41,6 +52,7 @@ TEST_CASE("HostMemory: Simple mirror map", "[common]") { TEST_CASE("HostMemory: Simple unmap", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP); volatile u8* const data = mem.VirtualBasePointer() + 0x5000; @@ -52,6 +64,7 @@ TEST_CASE("HostMemory: Simple unmap", "[common]") { TEST_CASE("HostMemory: Simple unmap and remap", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x5000, 0x3000, 0x2000, PERMS, HEAP); volatile u8* const data = mem.VirtualBasePointer() + 0x5000; @@ -69,6 +82,7 @@ TEST_CASE("HostMemory: Simple unmap and remap", "[common]") { TEST_CASE("HostMemory: Nieche allocation", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x0000, 0, 0x20000, PERMS, HEAP); mem.Unmap(0x0000, 0x4000, HEAP); mem.Map(0x1000, 0, 0x2000, PERMS, HEAP); @@ -78,6 +92,7 @@ TEST_CASE("HostMemory: Nieche allocation", "[common]") { TEST_CASE("HostMemory: Full unmap", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); mem.Unmap(0x8000, 0x4000, HEAP); mem.Map(0x6000, 0, 0x16000, PERMS, HEAP); @@ -85,6 +100,7 @@ TEST_CASE("HostMemory: Full unmap", "[common]") { TEST_CASE("HostMemory: Right out of bounds unmap", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x0000, 0, 0x4000, PERMS, HEAP); mem.Unmap(0x2000, 0x4000, HEAP); mem.Map(0x2000, 0x80000, 0x4000, PERMS, HEAP); @@ -92,6 +108,7 @@ TEST_CASE("HostMemory: Right out of bounds unmap", "[common]") { TEST_CASE("HostMemory: Left out of bounds unmap", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); mem.Unmap(0x6000, 0x4000, HEAP); mem.Map(0x8000, 0, 0x2000, PERMS, HEAP); @@ -99,6 +116,7 @@ TEST_CASE("HostMemory: Left out of bounds unmap", "[common]") { TEST_CASE("HostMemory: Multiple placeholder unmap", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x0000, 0, 0x4000, PERMS, HEAP); mem.Map(0x4000, 0, 0x1b000, PERMS, HEAP); mem.Unmap(0x3000, 0x1c000, HEAP); @@ -107,6 +125,7 @@ TEST_CASE("HostMemory: Multiple placeholder unmap", "[common]") { TEST_CASE("HostMemory: Unmap between placeholders", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x0000, 0, 0x4000, PERMS, HEAP); mem.Map(0x4000, 0, 0x4000, PERMS, HEAP); mem.Unmap(0x2000, 0x4000, HEAP); @@ -115,6 +134,7 @@ TEST_CASE("HostMemory: Unmap between placeholders", "[common]") { TEST_CASE("HostMemory: Unmap to origin", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x4000, 0, 0x4000, PERMS, HEAP); mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); mem.Unmap(0x4000, 0x4000, HEAP); @@ -124,6 +144,7 @@ TEST_CASE("HostMemory: Unmap to origin", "[common]") { TEST_CASE("HostMemory: Unmap to right", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x4000, 0, 0x4000, PERMS, HEAP); mem.Map(0x8000, 0, 0x4000, PERMS, HEAP); mem.Unmap(0x8000, 0x4000, HEAP); @@ -132,6 +153,7 @@ TEST_CASE("HostMemory: Unmap to right", "[common]") { TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP); volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000; @@ -144,6 +166,7 @@ TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") { TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP); volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000; @@ -158,6 +181,7 @@ TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") { TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x4000, 0x10000, 0x4000, PERMS, HEAP); volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000; @@ -172,6 +196,7 @@ TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") { TEST_CASE("HostMemory: Partial sparse middle unmap and check bindings", "[common]") { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); + REQUIRE(mem.BackingBasePointer() != nullptr); mem.Map(0x4000, 0x10000, 0x2000, PERMS, HEAP); mem.Map(0x6000, 0x20000, 0x2000, PERMS, HEAP); diff --git a/src/tests/video_core/memory_tracker.cpp b/src/tests/video_core/memory_tracker.cpp index 5fd8037446..c2236848b5 100644 --- a/src/tests/video_core/memory_tracker.cpp +++ b/src/tests/video_core/memory_tracker.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include "common/container/unordered_map.h" #include #include #include @@ -65,7 +65,7 @@ public: } private: - ankerl::unordered_dense::map page_table; + ::Common::unordered_map page_table; std::vector> calls; size_t update_calls = 0; }; diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index be53b71393..64d14ab2a6 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -20,6 +20,7 @@ add_library(video_core STATIC buffer_cache/buffer_cache.h buffer_cache/memory_tracker_base.h buffer_cache/usage_tracker.h + buffer_cache/virtual_range_cache.h buffer_cache/word_manager.h cache_types.h capture.h @@ -130,6 +131,8 @@ add_library(video_core STATIC renderer_vulkan/present/present_push_constants.h renderer_vulkan/present/smaa.cpp renderer_vulkan/present/smaa.h + renderer_vulkan/present/sgsr.cpp + renderer_vulkan/present/sgsr.h renderer_vulkan/present/util.cpp renderer_vulkan/present/util.h renderer_vulkan/present/window_adapt_pass.cpp @@ -156,12 +159,16 @@ add_library(video_core STATIC renderer_vulkan/vk_compute_pass.h renderer_vulkan/vk_compute_pipeline.cpp renderer_vulkan/vk_compute_pipeline.h + renderer_vulkan/vk_descriptor_buffer.cpp + renderer_vulkan/vk_descriptor_buffer.h renderer_vulkan/vk_descriptor_pool.cpp renderer_vulkan/vk_descriptor_pool.h renderer_vulkan/vk_fence_manager.cpp renderer_vulkan/vk_fence_manager.h renderer_vulkan/vk_graphics_pipeline.cpp renderer_vulkan/vk_graphics_pipeline.h + renderer_vulkan/vk_multi_range_buffer.cpp + renderer_vulkan/vk_multi_range_buffer.h renderer_vulkan/vk_master_semaphore.cpp renderer_vulkan/vk_master_semaphore.h renderer_vulkan/vk_pipeline_cache.cpp @@ -263,6 +270,82 @@ add_library(video_core STATIC video_core.h ) +if (ENABLE_LSFG) + target_sources(video_core PRIVATE + frame_gen/lossless_dll.cpp + frame_gen/lossless_dll.h + frame_gen/lsfg_translate.cpp + frame_gen/lsfg_translate.h + renderer_vulkan/present/frame_gen.cpp + renderer_vulkan/present/frame_gen.h + renderer_vulkan/present/frame_gen_pacer.cpp + renderer_vulkan/present/frame_gen_pacer.h + renderer_vulkan/present/lsfg_alpha.cpp + renderer_vulkan/present/lsfg_alpha.h + renderer_vulkan/present/lsfg_beta.cpp + renderer_vulkan/present/lsfg_beta.h + renderer_vulkan/present/lsfg_chain.cpp + renderer_vulkan/present/lsfg_chain.h + renderer_vulkan/present/lsfg_common.cpp + renderer_vulkan/present/lsfg_common.h + renderer_vulkan/present/lsfg_delta.cpp + renderer_vulkan/present/lsfg_delta.h + renderer_vulkan/present/lsfg_gamma.cpp + renderer_vulkan/present/lsfg_gamma.h + renderer_vulkan/present/lsfg_generate.cpp + renderer_vulkan/present/lsfg_generate.h + renderer_vulkan/present/lsfg_mipmaps.cpp + renderer_vulkan/present/lsfg_mipmaps.h + renderer_vulkan/present/lsfg_shaders.cpp + renderer_vulkan/present/lsfg_shaders.h + ) + target_compile_definitions(video_core PUBLIC HAS_LSFG) +endif() + +if (ENABLE_RESHADE) + set(BUNDLED_FX_DIR ${CMAKE_SOURCE_DIR}/dist/post_shaders) + set(BUNDLED_FX_HEADER ${CMAKE_CURRENT_BINARY_DIR}/bundled_fx_effects.h) + file(GLOB BUNDLED_FX_FILES ${BUNDLED_FX_DIR}/*.fx) + + add_custom_command( + OUTPUT ${BUNDLED_FX_HEADER} + COMMAND ${CMAKE_COMMAND} -P + ${CMAKE_CURRENT_SOURCE_DIR}/post_processing/GenerateBundledEffects.cmake + ${BUNDLED_FX_DIR} ${BUNDLED_FX_HEADER} + DEPENDS ${BUNDLED_FX_FILES} + ) + + set(BUNDLED_PRESET_DIR ${CMAKE_SOURCE_DIR}/dist/post_presets) + set(BUNDLED_PRESET_HEADER ${CMAKE_CURRENT_BINARY_DIR}/bundled_fx_presets.h) + file(GLOB BUNDLED_PRESET_FILES ${BUNDLED_PRESET_DIR}/*.fxp) + + add_custom_command( + OUTPUT ${BUNDLED_PRESET_HEADER} + COMMAND ${CMAKE_COMMAND} -P + ${CMAKE_CURRENT_SOURCE_DIR}/post_processing/GenerateBundledPresets.cmake + ${BUNDLED_PRESET_DIR} ${BUNDLED_PRESET_HEADER} + DEPENDS ${BUNDLED_PRESET_FILES} + ) + + target_sources(video_core PRIVATE + post_processing/fx_chain.cpp + post_processing/fx_chain.h + post_processing/fx_compile.cpp + post_processing/fx_compile.h + post_processing/fx_effect.cpp + post_processing/fx_effect.h + post_processing/fx_preset.cpp + post_processing/fx_preset.h + renderer_vulkan/present/post_process.cpp + renderer_vulkan/present/post_process.h + ${BUNDLED_FX_HEADER} + ${BUNDLED_PRESET_HEADER} + ) + target_include_directories(video_core PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + target_link_libraries(video_core PRIVATE reshadefx::reshadefx) + target_compile_definitions(video_core PUBLIC HAS_RESHADE) +endif() + if (ENABLE_OPENGL) target_sources(video_core PRIVATE renderer_opengl/present/filters.cpp @@ -332,7 +415,7 @@ if (YUZU_USE_EXTERNAL_FFMPEG) add_dependencies(video_core ffmpeg-build) endif() -target_include_directories(video_core PRIVATE ${FFmpeg_INCLUDE_DIR}) +target_include_directories(video_core PUBLIC ${FFmpeg_INCLUDE_DIR}) target_link_libraries(video_core PRIVATE ${FFmpeg_LIBRARIES}) target_link_options(video_core PRIVATE ${FFmpeg_LDFLAGS}) @@ -369,7 +452,7 @@ else() $<$:-Wno-shadow> $<$:-Wno-unused-local-typedef>) else() - target_compile_options(video_core PRIVATE $<$:-Werror=conversion>) + target_compile_options(video_core PRIVATE $<$:-Wconversion>) endif() target_compile_options(video_core PRIVATE $<$:-Wno-sign-conversion>) diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h index bec2dac246..d7eb21612f 100644 --- a/src/video_core/buffer_cache/buffer_base.h +++ b/src/video_core/buffer_cache/buffer_base.h @@ -121,12 +121,21 @@ public: return size_bytes; } + u64 getWriteTick() const noexcept { + return write_tick; + } + + void setWriteTick(u64 write_tick_) { + write_tick = write_tick_; + } + private: VAddr cpu_addr = 0; BufferFlagBits flags{}; int stream_score = 0; size_t lru_id = SIZE_MAX; size_t size_bytes = 0; + u64 write_tick = 0; }; } // namespace VideoCommon diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 60e0e8449b..f30c85911f 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -112,9 +112,16 @@ void BufferCache

::TickFrame() { async_buffers_death_ring.clear(); } +template +void BufferCache

::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t size) { + if constexpr (requires { runtime.BindMultiRangeStorageBuffer(u64{}, bool{}); }) { + virtual_ranges.Unmap(as_id, gpu_addr, size); + } +} + template void BufferCache

::WriteMemory(DAddr device_addr, u64 size) { - if (memory_tracker.IsRegionGpuModified(device_addr, size)) { + if (IsRegionGpuModified(device_addr, size)) { ClearDownload(device_addr, size); gpu_modified_ranges.Subtract(device_addr, size); } @@ -208,8 +215,8 @@ bool BufferCache

::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am BufferId buffer_b; do { channel_state->has_deleted_buffers = false; - buffer_a = FindBuffer(*cpu_src_address, static_cast(amount)); - buffer_b = FindBuffer(*cpu_dest_address, static_cast(amount)); + buffer_a = FindBuffer(*cpu_src_address, static_cast(amount), false); + buffer_b = FindBuffer(*cpu_dest_address, static_cast(amount), false); } while (channel_state->has_deleted_buffers); auto& src_buffer = slot_buffers[buffer_a]; auto& dest_buffer = slot_buffers[buffer_b]; @@ -242,6 +249,10 @@ bool BufferCache

::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am runtime.CopyBuffer(dest_buffer, src_buffer, copies, true); if (has_new_downloads) { memory_tracker.MarkRegionAsGpuModified(*cpu_dest_address, amount); + const bool should_sync = Settings::IsGPUFenceBehaviorBalanced() || Settings::IsGPUFenceBehaviorAccurate(); + if (should_sync) { + runtime.Finish(); + } } Tegra::Memory::DeviceGuestMemoryScoped @@ -265,7 +276,7 @@ bool BufferCache

::DMAClear(GPUVAddr dst_address, u64 amount, u32 value) { ClearDownload(*cpu_dst_address, size); gpu_modified_ranges.Subtract(*cpu_dst_address, size); - const BufferId buffer = FindBuffer(*cpu_dst_address, static_cast(size)); + const BufferId buffer = FindBuffer(*cpu_dst_address, static_cast(size), false); Buffer& dest_buffer = slot_buffers[buffer]; const u32 offset = dest_buffer.Offset(*cpu_dst_address); runtime.ClearBuffer(dest_buffer, offset, size, value); @@ -287,7 +298,7 @@ std::pair BufferCache

::ObtainBuffer(GPUVAddr gpu_ad template std::pair BufferCache

::ObtainCPUBuffer( DAddr device_addr, u32 size, ObtainBufferSynchronize sync_info, ObtainBufferOperation post_op) { - const BufferId buffer_id = FindBuffer(device_addr, size); + const BufferId buffer_id = FindBuffer(device_addr, size, false); Buffer& buffer = slot_buffers[buffer_id]; // synchronize op @@ -304,11 +315,8 @@ std::pair BufferCache

::ObtainCPUBuffer( MarkWrittenBuffer(buffer_id, device_addr, size); break; case ObtainBufferOperation::DiscardWrite: { - const DAddr device_addr_start = Common::AlignDown(device_addr, 64); - const DAddr device_addr_end = Common::AlignUp(device_addr + size, 64); - const size_t new_size = device_addr_end - device_addr_start; - ClearDownload(device_addr_start, new_size); - gpu_modified_ranges.Subtract(device_addr_start, new_size); + ClearDownload(device_addr, size); + gpu_modified_ranges.Subtract(device_addr, size); break; } default: @@ -754,7 +762,7 @@ void BufferCache

::BindHostIndexBuffer() { } } if constexpr (HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) { - const u32 new_offset = offset + draw_state.index_buffer.first * draw_state.index_buffer.FormatSizeInBytes(); + const u32 new_offset = offset + draw_state.index_buffer.first * u32(draw_state.index_buffer.FormatSizeInBytes()); runtime.BindIndexBuffer(buffer, new_offset, size); } else { buffer.MarkUsage(offset, size); @@ -801,7 +809,7 @@ void BufferCache

::UpdateVertexBufferSlot(u32 index, const Binding& binding) { template void BufferCache

::BindHostVertexBuffers() { -#ifdef ANDROID +#ifdef __ANDROID__ const bool use_optimized_vertex_buffers = Settings::values.use_optimized_vertex_buffers.GetValue(); #else constexpr bool use_optimized_vertex_buffers = true; @@ -998,11 +1006,85 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 channel_state->fast_bound_uniform_buffers[stage] &= ~(1u << binding_index); } +template +void BufferCache

::ResolveMultiRangeStorage(Binding& binding, bool is_written, + std::vector& pool) { + binding.segment_first = 0; + binding.segment_count = 0; + if constexpr (requires { runtime.BindMultiRangeStorageBuffer(u64{}, bool{}); }) { + if (binding.gpu_addr == 0 || binding.size == 0) { + return; + } + if (is_written && !runtime.PrefersSparseSources()) { + return; + } + const VirtualSegments* found = + virtual_ranges.Query(*gpu_memory, binding.gpu_addr, binding.size); + if (!found || found->size() < 2) { + return; + } + const VirtualSegments segments = *found; + const u32 first = static_cast(pool.size()); + const bool prefer_sparse = runtime.PrefersSparseSources(); + for (const VirtualSegment& segment : segments) { + const BufferId buffer_id = + FindBuffer(segment.device_addr, segment.size, prefer_sparse); + if (!buffer_id) { + pool.resize(first); + return; + } + pool.push_back(MultiRangeSegment{ + .buffer_id = buffer_id, + .device_addr = segment.device_addr, + .size = segment.size, + }); + } + binding.segment_first = first; + binding.segment_count = static_cast(segments.size()); + } +} + +template +bool BufferCache

::BindMultiRangeStorage(const Binding& binding, bool is_written, + std::span pool) { + if constexpr (requires { runtime.BindMultiRangeStorageBuffer(u64{}, bool{}); }) { + if (binding.segment_count < 2) { + return false; + } + if (binding.segment_first + binding.segment_count > pool.size()) { + return false; + } + const u64 key = (static_cast(gpu_memory->GetID()) << 48) ^ binding.gpu_addr; + runtime.ResetMultiRange(); + for (u32 index = 0; index < binding.segment_count; ++index) { + const MultiRangeSegment& segment = pool[binding.segment_first + index]; + Buffer& buffer = slot_buffers[segment.buffer_id]; + TouchBuffer(buffer, segment.buffer_id); + if (SynchronizeBuffer(buffer, segment.device_addr, segment.size)) { + runtime.InvalidateMultiRange(key); + } + const u32 offset = buffer.Offset(segment.device_addr); + buffer.MarkUsage(offset, segment.size); + if (is_written) { + MarkWrittenBuffer(segment.buffer_id, segment.device_addr, segment.size); + } + runtime.PushMultiRangeSource(buffer, offset, segment.size); + } + return runtime.BindMultiRangeStorageBuffer(key, is_written); + } else { + return false; + } +} + template void BufferCache

::BindHostGraphicsStorageBuffers(size_t stage) { u32 binding_index = 0; ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) { const Binding& binding = channel_state->storage_buffers[stage][index]; + const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0; + if (BindMultiRangeStorage(binding, is_written, graphics_segments)) { + return; + } Buffer& buffer = slot_buffers[binding.buffer_id]; TouchBuffer(buffer, binding.buffer_id); const u32 size = binding.size; @@ -1010,7 +1092,6 @@ void BufferCache

::BindHostGraphicsStorageBuffers(size_t stage) { const u32 offset = buffer.Offset(binding.device_addr); buffer.MarkUsage(offset, size); - const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0; if (is_written) { MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size); @@ -1139,6 +1220,11 @@ void BufferCache

::BindHostComputeStorageBuffers() { u32 binding_index = 0; ForEachEnabledBit(channel_state->enabled_compute_storage_buffers, [&](u32 index) { const Binding& binding = channel_state->compute_storage_buffers[index]; + const bool is_written = + ((channel_state->written_compute_storage_buffers >> index) & 1) != 0; + if (BindMultiRangeStorage(binding, is_written, compute_segments)) { + return; + } Buffer& buffer = slot_buffers[binding.buffer_id]; TouchBuffer(buffer, binding.buffer_id); const u32 size = binding.size; @@ -1146,11 +1232,9 @@ void BufferCache

::BindHostComputeStorageBuffers() { const u32 offset = buffer.Offset(binding.device_addr); buffer.MarkUsage(offset, size); - const bool is_written = - ((channel_state->written_compute_storage_buffers >> index) & 1) != 0; if (is_written) { - MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size); + MarkWrittenBuffer(binding.buffer_id, binding.device_addr, size, true); } if constexpr (NEEDS_BIND_STORAGE_INDEX) { @@ -1193,6 +1277,7 @@ void BufferCache

::BindHostComputeTextureBuffers() { template void BufferCache

::DoUpdateGraphicsBuffers(bool is_indexed) { + graphics_segments.clear(); BufferOperations([&]() { if (is_indexed) { UpdateIndexBuffer(); @@ -1212,6 +1297,7 @@ void BufferCache

::DoUpdateGraphicsBuffers(bool is_indexed) { template void BufferCache

::DoUpdateComputeBuffers() { + compute_segments.clear(); BufferOperations([&]() { UpdateComputeUniformBuffers(); UpdateComputeStorageBuffers(); @@ -1234,11 +1320,11 @@ void BufferCache

::UpdateIndexBuffer() { auto inline_index_size = static_cast(draw_state.inline_index_draw_indexes.size()); u32 buffer_size = Common::AlignUp(inline_index_size, CACHING_PAGESIZE); if (inline_buffer_id == NULL_BUFFER_ID) [[unlikely]] { - inline_buffer_id = CreateBuffer(0, buffer_size); + inline_buffer_id = CreateBuffer(0, buffer_size, false); } if (slot_buffers[inline_buffer_id].SizeBytes() < buffer_size) [[unlikely]] { slot_buffers.erase(inline_buffer_id); - inline_buffer_id = CreateBuffer(0, buffer_size); + inline_buffer_id = CreateBuffer(0, buffer_size, false); } channel_state->index_buffer = Binding{ .device_addr = 0, @@ -1252,8 +1338,7 @@ void BufferCache

::UpdateIndexBuffer() { const GPUVAddr gpu_addr_end = index_buffer_ref.EndAddress(); const std::optional device_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin); const u32 address_size = static_cast(gpu_addr_end - gpu_addr_begin); - const u32 draw_size = - (index_buffer_ref.count + index_buffer_ref.first) * index_buffer_ref.FormatSizeInBytes(); + const u32 draw_size = (index_buffer_ref.count + index_buffer_ref.first) * u32(index_buffer_ref.FormatSizeInBytes()); const u32 size = (std::min)(address_size, draw_size); if (size == 0 || !device_addr) { channel_state->index_buffer = NULL_BINDING; @@ -1262,7 +1347,7 @@ void BufferCache

::UpdateIndexBuffer() { channel_state->index_buffer = Binding{ .device_addr = *device_addr, .size = size, - .buffer_id = FindBuffer(*device_addr, size), + .buffer_id = FindBuffer(*device_addr, size, false), }; } @@ -1299,7 +1384,7 @@ void BufferCache

::UpdateVertexBuffer(u32 index) { if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || size >= 64_MiB) { size = static_cast(gpu_memory->MaxContinuousRange(gpu_addr_begin, size)); } - const BufferId buffer_id = FindBuffer(*device_addr, size); + const BufferId buffer_id = FindBuffer(*device_addr, size, false); const Binding binding{ .device_addr = *device_addr, .size = size, @@ -1320,7 +1405,7 @@ void BufferCache

::UpdateDrawIndirect() { binding = Binding{ .device_addr = *device_addr, .size = static_cast(size), - .buffer_id = FindBuffer(*device_addr, static_cast(size)), + .buffer_id = FindBuffer(*device_addr, static_cast(size), false), }; }; if (current_draw_indirect->include_count) { @@ -1344,7 +1429,7 @@ void BufferCache

::UpdateUniformBuffers(size_t stage) { channel_state->dirty_uniform_buffers[stage] |= 1U << index; } // Resolve buffer - binding.buffer_id = FindBuffer(binding.device_addr, binding.size); + binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false); }); } @@ -1353,8 +1438,10 @@ void BufferCache

::UpdateStorageBuffers(size_t stage) { ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) { // Resolve buffer Binding& binding = channel_state->storage_buffers[stage][index]; - const BufferId buffer_id = FindBuffer(binding.device_addr, binding.size); + const BufferId buffer_id = FindBuffer(binding.device_addr, binding.size, false); binding.buffer_id = buffer_id; + const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0; + ResolveMultiRangeStorage(binding, is_written, graphics_segments); }); } @@ -1362,7 +1449,7 @@ template void BufferCache

::UpdateTextureBuffers(size_t stage) { ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) { Binding& binding = channel_state->texture_buffers[stage][index]; - binding.buffer_id = FindBuffer(binding.device_addr, binding.size); + binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false); }); } @@ -1386,7 +1473,7 @@ void BufferCache

::UpdateTransformFeedbackBuffer(u32 index) { channel_state->transform_feedback_buffers[index] = NULL_BINDING; return; } - const BufferId buffer_id = FindBuffer(*device_addr, size); + const BufferId buffer_id = FindBuffer(*device_addr, size, false); channel_state->transform_feedback_buffers[index] = Binding{ .device_addr = *device_addr, .size = size, @@ -1408,7 +1495,7 @@ void BufferCache

::UpdateComputeUniformBuffers() { binding.size = cbuf.size; } } - binding.buffer_id = FindBuffer(binding.device_addr, binding.size); + binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false); }); } @@ -1417,7 +1504,10 @@ void BufferCache

::UpdateComputeStorageBuffers() { ForEachEnabledBit(channel_state->enabled_compute_storage_buffers, [&](u32 index) { // Resolve buffer Binding& binding = channel_state->compute_storage_buffers[index]; - binding.buffer_id = FindBuffer(binding.device_addr, binding.size); + binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false); + const bool is_written = + ((channel_state->written_compute_storage_buffers >> index) & 1) != 0; + ResolveMultiRangeStorage(binding, is_written, compute_segments); }); } @@ -1425,32 +1515,59 @@ template void BufferCache

::UpdateComputeTextureBuffers() { ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) { Binding& binding = channel_state->compute_texture_buffers[index]; - binding.buffer_id = FindBuffer(binding.device_addr, binding.size); + binding.buffer_id = FindBuffer(binding.device_addr, binding.size, false); }); } template -void BufferCache

::MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u32 size) { +void BufferCache

::MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u32 size, bool needs_sync) { + if constexpr (!IS_OPENGL) { + if (needs_sync) { + Buffer& buffer = slot_buffers[buffer_id]; + buffer.setWriteTick(runtime.CurrentTick()); + } + } memory_tracker.MarkRegionAsGpuModified(device_addr, size); gpu_modified_ranges.Add(device_addr, size); uncommitted_gpu_modified_ranges.Add(device_addr, size); } template -BufferId BufferCache

::FindBuffer(DAddr device_addr, u32 size) { +BufferId BufferCache

::FindBuffer(DAddr device_addr, u32 size, bool sparse_compatible) { if (device_addr == 0) { return NULL_BUFFER_ID; } const u64 page = device_addr >> CACHING_PAGEBITS; const BufferId buffer_id = page_table[page]; - if (!buffer_id) { - return CreateBuffer(device_addr, size); + if (buffer_id) { + Buffer& buffer = slot_buffers[buffer_id]; + if (buffer.IsInBounds(device_addr, size)) { + const bool should_sync = Settings::IsGPUFenceBehaviorAccurate(); + if (should_sync) { + SynchronizeBufferWrites(buffer); + } + bool usable = true; + if constexpr (requires { buffer.IsSparseCompatible(); }) { + if (sparse_compatible && !buffer.IsSparseCompatible()) { + usable = false; + } + } + if (usable) { + return buffer_id; + } + } } - const Buffer& buffer = slot_buffers[buffer_id]; - if (buffer.IsInBounds(device_addr, size)) { - return buffer_id; + return CreateBuffer(device_addr, size, sparse_compatible); +} + +template +void BufferCache

::SynchronizeBufferWrites(Buffer& buffer) { + if constexpr (!IS_OPENGL) { + const u64 buffer_tick = buffer.getWriteTick(); + if (!runtime.IsFree(buffer_tick)) { + runtime.Wait(buffer_tick); + } } - return CreateBuffer(device_addr, size); } template @@ -1547,7 +1664,7 @@ void BufferCache

::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, .size = overlap.SizeBytes(), }); new_buffer.MarkUsage(copies[0].dst_offset, copies[0].size); - runtime.CopyBuffer(new_buffer, overlap, FixSmallVectorADL(copies), true); + runtime.CopyBuffer(new_buffer, overlap, copies, true); #ifdef YUZU_LEGACY if (immediately_free) runtime.Finish(); @@ -1556,13 +1673,15 @@ void BufferCache

::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, } template -BufferId BufferCache

::CreateBuffer(DAddr device_addr, u32 wanted_size) { +BufferId BufferCache

::CreateBuffer(DAddr device_addr, u32 wanted_size, + bool sparse_compatible) { DAddr device_addr_end = Common::AlignUp(device_addr + wanted_size, CACHING_PAGESIZE); device_addr = Common::AlignDown(device_addr, CACHING_PAGESIZE); wanted_size = static_cast(device_addr_end - device_addr); const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size); const u32 size = static_cast(overlap.end - overlap.begin); - const BufferId new_buffer_id = slot_buffers.insert(runtime, overlap.begin, size); + const BufferId new_buffer_id = + slot_buffers.insert(runtime, overlap.begin, size, sparse_compatible); auto& new_buffer = slot_buffers[new_buffer_id]; const size_t size_bytes = new_buffer.SizeBytes(); runtime.ClearBuffer(new_buffer, 0, size_bytes, 0); @@ -1619,26 +1738,64 @@ void BufferCache

::TouchBuffer(Buffer& buffer, BufferId buffer_id) noexcept { template bool BufferCache

::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 size) { - upload_copies.clear(); - u64 total_size_bytes = 0; - u64 largest_copy = 0; - const DAddr buffer_start = buffer.cpu_addr_cached; - memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) { - upload_copies.push_back(BufferCopy{ - .src_offset = total_size_bytes, - .dst_offset = device_addr_out - buffer_start, - .size = range_size, +#ifdef __ANDROID__ + const bool use_optimized_vertex_buffers = Settings::values.use_optimized_vertex_buffers.GetValue(); +#else + constexpr bool use_optimized_vertex_buffers = true; +#endif + + if (use_optimized_vertex_buffers) { + upload_copies.clear(); + u64 total_size_bytes = 0; + u64 largest_copy = 0; + const DAddr buffer_start = buffer.cpu_addr_cached; + const auto add_upload = [&](DAddr start, DAddr end) { + if (start == end) return; + const u64 range_size = end - start; + upload_copies.push_back(BufferCopy{ + .src_offset = total_size_bytes, + .dst_offset = start - buffer_start, + .size = range_size, + }); + total_size_bytes += range_size; + largest_copy = (std::max)(largest_copy, range_size); + }; + memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) { + DAddr upload_start = device_addr_out; + gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, [&](DAddr gpu_start, DAddr gpu_end) { + add_upload(upload_start, gpu_start); + upload_start = gpu_end; + }); + add_upload(upload_start, device_addr_out + range_size); }); - total_size_bytes += range_size; - largest_copy = (std::max)(largest_copy, range_size); - }); - if (total_size_bytes == 0) { - return true; + if (total_size_bytes == 0) { + return true; + } + const std::span copies_span(upload_copies.data(), upload_copies.size()); + UploadMemory(buffer, total_size_bytes, largest_copy, copies_span); + any_buffer_uploaded = true; + return false; + } else { + boost::container::small_vector copies; + u64 total_size_bytes = 0; + u64 largest_copy = 0; + DAddr buffer_start = buffer.CpuAddr(); + memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) { + copies.push_back(BufferCopy{ + .src_offset = total_size_bytes, + .dst_offset = device_addr_out - buffer_start, + .size = range_size, + }); + total_size_bytes += range_size; + largest_copy = (std::max)(largest_copy, range_size); + }); + if (total_size_bytes == 0) { + return true; + } + const std::span copies_span(copies.data(), copies.size()); + UploadMemory(buffer, total_size_bytes, largest_copy, copies_span); + return false; } - const std::span copies_span(upload_copies.data(), upload_copies.size()); - UploadMemory(buffer, total_size_bytes, largest_copy, copies_span); - any_buffer_uploaded = true; - return false; } template @@ -1688,7 +1845,6 @@ void BufferCache

::MappedUploadMemory([[maybe_unused]] Buffer& buffer, u8* const src_pointer = staging_pointer.data() + copy.src_offset; const DAddr device_addr = buffer.CpuAddr() + copy.dst_offset; device_memory.ReadBlockUnsafe(device_addr, src_pointer, copy.size); - // Apply the staging offset copy.src_offset += upload_staging.offset; } @@ -1721,7 +1877,7 @@ void BufferCache

::InlineMemoryImplementation(DAddr dest_address, size_t copy_ ClearDownload(dest_address, copy_size); gpu_modified_ranges.Subtract(dest_address, copy_size); - BufferId buffer_id = FindBuffer(dest_address, static_cast(copy_size)); + BufferId buffer_id = FindBuffer(dest_address, static_cast(copy_size), false); auto& buffer = slot_buffers[buffer_id]; SynchronizeBuffer(buffer, dest_address, static_cast(copy_size)); @@ -1807,6 +1963,9 @@ void BufferCache

::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64 template void BufferCache

::DeleteBuffer(BufferId buffer_id, bool do_not_mark) { + if constexpr (requires { runtime.OnBufferDeleted(slot_buffers[buffer_id]); }) { + runtime.OnBufferDeleted(slot_buffers[buffer_id]); + } bool dirty_index{false}; boost::container::small_vector dirty_vertex_buffers; const auto scalar_replace = [buffer_id](Binding& binding) { @@ -1880,23 +2039,17 @@ Binding BufferCache

::StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index, return NULL_BINDING; } - // xbzk: New size logic. Fixes MCI. - // If ever the * comment below prove wrong, the 'if' block may be removed. const auto size = [&]() { - const bool is_nvn_cbuf = cbuf_index == 0; - if (is_nvn_cbuf) { - // * The NVN driver buffer (index 0) is known to pack the SSBO address followed by its size. - const u64 next_qword = gpu_memory->Read(ssbo_addr + 8); - const u32 upper_32 = static_cast(next_qword >> 32); - // Hardware-based detection: GPU addresses have non-zero upper bits - if (upper_32 == 0) { - // This is a size field, not a GPU address - return static_cast(next_qword); // Return lower_32 - } + const u32 memory_layout_size = + static_cast(gpu_memory->GetMemoryLayoutSize(gpu_addr)); + const u64 next_qword = gpu_memory->Read(ssbo_addr + 8); + const u32 packed_size = static_cast(next_qword); + const bool next_qword_is_size = static_cast(next_qword >> 32) == 0 && + packed_size != 0 && + packed_size <= memory_layout_size; + if (next_qword_is_size) { + return packed_size; } - // Fall through: either not NVN cbuf (Doom Eternal & +), or NVN but ssbo_addr+8 is a GPU address (MCI) - const u32 memory_layout_size = static_cast(gpu_memory->GetMemoryLayoutSize(gpu_addr)); - // Cap at 8MB to prevent allocator overflow from misinterpreted addresses return (std::min)(memory_layout_size, static_cast(8_MiB)); }(); @@ -1916,9 +2069,14 @@ Binding BufferCache

::StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index, // The end address used for size calculation does not need to be aligned const DAddr cpu_end = Common::AlignUp(*device_addr + size, Core::DEVICE_PAGESIZE); + u32 binding_size = static_cast(cpu_end - *aligned_device_addr); + if (is_written) { + binding_size = aligned_size; + } const Binding binding{ .device_addr = *aligned_device_addr, - .size = is_written ? aligned_size : static_cast(cpu_end - *aligned_device_addr), + .gpu_addr = aligned_gpu_addr, + .size = binding_size, .buffer_id = BufferId{}, }; return binding; diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 473cc6842e..26bf3dd9ed 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -16,7 +16,7 @@ #include #include -#include +#include "common/container/unordered_map.h" #include #include @@ -29,6 +29,7 @@ #include "common/settings.h" #include "common/slot_vector.h" #include "video_core/buffer_cache/buffer_base.h" +#include "video_core/buffer_cache/virtual_range_cache.h" #include "video_core/control/channel_state_cache.h" #include "video_core/delayed_destruction_ring.h" #include "video_core/dirty_flags.h" @@ -81,8 +82,17 @@ static constexpr u32 DEFAULT_SKIP_CACHE_SIZE = static_cast(4_KiB); struct Binding { DAddr device_addr{}; + GPUVAddr gpu_addr{}; u32 size{}; BufferId buffer_id; + u32 segment_first{}; + u32 segment_count{}; +}; + +struct MultiRangeSegment { + BufferId buffer_id; + DAddr device_addr{}; + u32 size{}; }; struct TextureBufferBinding : Binding { @@ -215,6 +225,14 @@ public: void TickFrame(); + bool BindMultiRangeStorage(const Binding& binding, bool is_written, + std::span pool); + + void ResolveMultiRangeStorage(Binding& binding, bool is_written, + std::vector& pool); + + void UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t size); + void WriteMemory(DAddr device_addr, u64 size); void CachedWriteMemory(DAddr device_addr, u64 size); @@ -412,15 +430,18 @@ private: void UpdateComputeTextureBuffers(); - void MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u32 size); + void MarkWrittenBuffer(BufferId buffer_id, DAddr device_addr, u32 size, bool needs_sync = false); - [[nodiscard]] BufferId FindBuffer(DAddr device_addr, u32 size); + [[nodiscard]] BufferId FindBuffer(DAddr device_addr, u32 size, bool sparse_compatible); + + void SynchronizeBufferWrites(Buffer& buffer); [[nodiscard]] OverlapResult ResolveOverlaps(DAddr device_addr, u32 wanted_size); void JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, bool accumulate_stream_score); - [[nodiscard]] BufferId CreateBuffer(DAddr device_addr, u32 wanted_size); + [[nodiscard]] BufferId CreateBuffer(DAddr device_addr, u32 wanted_size, + bool sparse_compatible); void Register(BufferId buffer_id); @@ -511,6 +532,9 @@ private: using TickType = u64; }; Common::LeastRecentlyUsedCache lru_cache; + VirtualRangeCache virtual_ranges; + std::vector graphics_segments; + std::vector compute_segments; u64 frame_tick = 0; u64 total_used_memory = 0; u64 minimum_memory = 0; diff --git a/src/video_core/buffer_cache/memory_tracker_base.h b/src/video_core/buffer_cache/memory_tracker_base.h index 2c571271d6..b8547f3149 100644 --- a/src/video_core/buffer_cache/memory_tracker_base.h +++ b/src/video_core/buffer_cache/memory_tracker_base.h @@ -11,7 +11,7 @@ #include #include #include -#include +#include "common/container/unordered_set.h" #include #include "common/alignment.h" @@ -257,7 +257,7 @@ private: std::array top_tier{}; std::deque> manager_pool; std::deque free_managers; - ankerl::unordered_dense::set cached_pages; + ::Common::unordered_set cached_pages; DeviceTracker* device_tracker = nullptr; }; diff --git a/src/video_core/buffer_cache/usage_tracker.h b/src/video_core/buffer_cache/usage_tracker.h index ae511ccb69..6f84e463e3 100644 --- a/src/video_core/buffer_cache/usage_tracker.h +++ b/src/video_core/buffer_cache/usage_tracker.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -9,13 +12,13 @@ namespace VideoCommon { class UsageTracker { - static constexpr size_t BYTES_PER_BIT_SHIFT = 6; - static constexpr size_t PAGE_SHIFT = 6 + BYTES_PER_BIT_SHIFT; - static constexpr size_t PAGE_BYTES = 1 << PAGE_SHIFT; - + // PAGE_SHIFT is a macro on FreeBSD + static constexpr size_t BUFFER_BYTES_PER_BITSHIFT = 6; + static constexpr size_t BUFFER_PAGE_SHIFT = 6 + BUFFER_BYTES_PER_BITSHIFT; + static constexpr size_t BUFFER_PAGE_BYTES = 1 << BUFFER_PAGE_SHIFT; public: explicit UsageTracker(size_t size) { - const size_t num_pages = (size >> PAGE_SHIFT) + 1; + const size_t num_pages = (size >> BUFFER_PAGE_SHIFT) + 1; pages.resize(num_pages, 0ULL); } @@ -24,8 +27,8 @@ public: } void Track(u64 offset, u64 size) noexcept { - const size_t page = offset >> PAGE_SHIFT; - const size_t page_end = (offset + size) >> PAGE_SHIFT; + const size_t page = offset >> BUFFER_PAGE_SHIFT; + const size_t page_end = (offset + size) >> BUFFER_PAGE_SHIFT; if (page_end < page || page_end >= pages.size()) { return; } @@ -37,13 +40,13 @@ public: pages[i] = ~u64{0}; } const size_t offset_end = offset + size; - const size_t offset_end_page_aligned = Common::AlignDown(offset_end, PAGE_BYTES); + const size_t offset_end_page_aligned = Common::AlignDown(offset_end, BUFFER_PAGE_BYTES); TrackPage(page_end, offset_end_page_aligned, offset_end - offset_end_page_aligned); } [[nodiscard]] bool IsUsed(u64 offset, u64 size) const noexcept { - const size_t page = offset >> PAGE_SHIFT; - const size_t page_end = (offset + size) >> PAGE_SHIFT; + const size_t page = offset >> BUFFER_PAGE_SHIFT; + const size_t page_end = (offset + size) >> BUFFER_PAGE_SHIFT; if (page_end < page || page_end >= pages.size()) { return false; } @@ -56,23 +59,23 @@ public: } } const size_t offset_end = offset + size; - const size_t offset_end_page_aligned = Common::AlignDown(offset_end, PAGE_BYTES); + const size_t offset_end_page_aligned = Common::AlignDown(offset_end, BUFFER_PAGE_BYTES); return IsPageUsed(page_end, offset_end_page_aligned, offset_end - offset_end_page_aligned); } private: void TrackPage(u64 page, u64 offset, u64 size) noexcept { - const size_t offset_in_page = offset % PAGE_BYTES; - const size_t first_bit = offset_in_page >> BYTES_PER_BIT_SHIFT; - const size_t num_bits = std::min(size, PAGE_BYTES) >> BYTES_PER_BIT_SHIFT; + const size_t offset_in_page = offset % BUFFER_PAGE_BYTES; + const size_t first_bit = offset_in_page >> BUFFER_BYTES_PER_BITSHIFT; + const size_t num_bits = std::min(size, BUFFER_PAGE_BYTES) >> BUFFER_BYTES_PER_BITSHIFT; const size_t mask = ~u64{0} >> (64 - num_bits); pages[page] |= (~u64{0} & mask) << first_bit; } bool IsPageUsed(u64 page, u64 offset, u64 size) const noexcept { - const size_t offset_in_page = offset % PAGE_BYTES; - const size_t first_bit = offset_in_page >> BYTES_PER_BIT_SHIFT; - const size_t num_bits = std::min(size, PAGE_BYTES) >> BYTES_PER_BIT_SHIFT; + const size_t offset_in_page = offset % BUFFER_PAGE_BYTES; + const size_t first_bit = offset_in_page >> BUFFER_BYTES_PER_BITSHIFT; + const size_t num_bits = std::min(size, BUFFER_PAGE_BYTES) >> BUFFER_BYTES_PER_BITSHIFT; const size_t mask = ~u64{0} >> (64 - num_bits); const size_t mask2 = (~u64{0} & mask) << first_bit; return (pages[page] & mask2) != 0; diff --git a/src/video_core/buffer_cache/virtual_range_cache.h b/src/video_core/buffer_cache/virtual_range_cache.h new file mode 100644 index 0000000000..f4284d6abb --- /dev/null +++ b/src/video_core/buffer_cache/virtual_range_cache.h @@ -0,0 +1,173 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "common/common_types.h" +#include "common/container/unordered_map.h" +#include "video_core/memory_manager.h" + +namespace VideoCommon { + +struct VirtualSegment { + GPUVAddr gpu_addr; + DAddr device_addr; + u32 size; +}; + +using VirtualSegments = boost::container::small_vector; + +class VirtualRangeCache { +public: + static constexpr size_t MAX_ENTRIES = 8192; + static constexpr size_t MAX_DEFERRED = 4096; + + const VirtualSegments* Query(Tegra::MemoryManager& memory, GPUVAddr gpu_addr, u32 size) { + if (has_deferred.load(std::memory_order_acquire)) { + ApplyDeferred(); + } + if (entries.size() > MAX_ENTRIES) { + entries.clear(); + } + const size_t as_id = memory.GetID(); + const u64 key = MakeKey(as_id, gpu_addr); + const auto it = entries.find(key); + if (it != entries.end() && it->second.as_id == as_id && + it->second.gpu_addr == gpu_addr && it->second.size == size) { + return &it->second.segments; + } + Entry entry{}; + entry.as_id = as_id; + entry.gpu_addr = gpu_addr; + entry.size = size; + const auto ranges = memory.GetSubmappedRange(gpu_addr, size); + GPUVAddr expected = gpu_addr; + bool contiguous = true; + for (const auto& [range_addr, range_size] : ranges) { + if (range_addr != expected || range_size == 0) { + contiguous = false; + break; + } + const std::optional device_addr = memory.GpuToCpuAddress(range_addr); + if (!device_addr || *device_addr == 0) { + contiguous = false; + break; + } + if (range_size > static_cast((std::numeric_limits::max)())) { + contiguous = false; + break; + } + entry.segments.push_back(VirtualSegment{ + .gpu_addr = range_addr, + .device_addr = *device_addr, + .size = static_cast(range_size), + }); + expected += range_size; + } + if (!contiguous || expected != gpu_addr + size) { + entry.segments.clear(); + } + const auto result = entries.insert_or_assign(key, std::move(entry)); + return &result.first->second.segments; + } + + void Unmap(size_t as_id, GPUVAddr gpu_addr, u64 size) { + if (size == 0) { + return; + } + { + std::scoped_lock lock{deferred_mutex}; + if (!deferred.empty()) { + DeferredUnmap& last = deferred.back(); + if (last.as_id == as_id && last.gpu_addr + last.size == gpu_addr) { + last.size += size; + has_deferred.store(true, std::memory_order_release); + return; + } + } + if (deferred.size() >= MAX_DEFERRED) { + deferred.clear(); + deferred_overflow = true; + } else { + deferred.push_back(DeferredUnmap{ + .as_id = as_id, + .gpu_addr = gpu_addr, + .size = size, + }); + } + } + has_deferred.store(true, std::memory_order_release); + } + +private: + struct Entry { + VirtualSegments segments; + size_t as_id{}; + GPUVAddr gpu_addr{}; + u32 size{}; + }; + + struct DeferredUnmap { + size_t as_id; + GPUVAddr gpu_addr; + u64 size; + }; + + static u64 MakeKey(size_t as_id, GPUVAddr gpu_addr) { + return (static_cast(as_id) << 48) ^ gpu_addr; + } + + void ApplyDeferred() { + std::vector pending; + bool overflow = false; + { + std::scoped_lock lock{deferred_mutex}; + has_deferred.store(false, std::memory_order_release); + pending.swap(deferred); + overflow = deferred_overflow; + deferred_overflow = false; + } + if (overflow) { + entries.clear(); + return; + } + if (pending.empty() || entries.empty()) { + return; + } + for (auto it = entries.begin(); it != entries.end();) { + const Entry& entry = it->second; + const GPUVAddr entry_end = entry.gpu_addr + entry.size; + bool overlaps = false; + for (const DeferredUnmap& unmap : pending) { + if (unmap.as_id != entry.as_id) { + continue; + } + if (entry.gpu_addr < unmap.gpu_addr + unmap.size && unmap.gpu_addr < entry_end) { + overlaps = true; + break; + } + } + if (overlaps) { + it = entries.erase(it); + } else { + ++it; + } + } + } + + ::Common::unordered_map entries; + std::vector deferred; + std::mutex deferred_mutex; + std::atomic has_deferred{false}; + bool deferred_overflow{}; +}; + +} // namespace VideoCommon diff --git a/src/video_core/cdma_pusher.cpp b/src/video_core/cdma_pusher.cpp index 1f0f8b5a38..d04b738779 100644 --- a/src/video_core/cdma_pusher.cpp +++ b/src/video_core/cdma_pusher.cpp @@ -20,8 +20,7 @@ namespace Tegra { CDmaPusher::CDmaPusher(Host1x::Host1x& host1x_, s32 id) - : host_processor(host1x_) - , host1x{host1x_} + : host1x{host1x_} , current_class{ChClassId(id)} { thread = std::jthread([this](std::stop_token stop_token) { @@ -84,7 +83,7 @@ CDmaPusher::CDmaPusher(Host1x::Host1x& host1x_, s32 id) break; } default: - LOG_ERROR(HW_GPU, "Bad command at index {} (bytes {:#X}), buffer size {}", i - 1, (i - 1) * sizeof(u32), command_list.size()); + LOG_ERROR(HW_GPU, "Bad command at index {} (bytes {:#x}), buffer size {}", i - 1, (i - 1) * sizeof(u32), command_list.size()); UNIMPLEMENTED_MSG("ChSubmission mode {} is not implemented!", u32(mode)); break; } @@ -98,8 +97,8 @@ CDmaPusher::~CDmaPusher() = default; void CDmaPusher::ExecuteCommand(u32 method, u32 arg) { switch (current_class) { case ChClassId::Control: - LOG_TRACE(Service_NVDRV, "Class {} method {:#X} arg 0x{:X}", u32(current_class), method, arg); - host_processor.ProcessMethod(Host1x::Control::Method(method), arg); + LOG_TRACE(Service_NVDRV, "Class {} method {:#x} arg {:#x}", u32(current_class), method, arg); + host_processor.ProcessMethod(host1x, Host1x::Control::Method(method), arg); break; default: thi_regs.reg_array[method] = arg; @@ -114,7 +113,7 @@ void CDmaPusher::ExecuteCommand(u32 method, u32 arg) { break; } case ThiMethod::SetMethod1: - LOG_TRACE(Service_NVDRV, "Class {} method {:#X} arg 0x{:X}", u32(current_class), u32(thi_regs.method_0), arg); + LOG_TRACE(Service_NVDRV, "Class {} method {:#x} arg {:#x}", u32(current_class), u32(thi_regs.method_0), arg); ProcessMethod(thi_regs.method_0, arg); break; default: diff --git a/src/video_core/control/channel_state.cpp b/src/video_core/control/channel_state.cpp index d07c7e2a83..12a452dfc0 100644 --- a/src/video_core/control/channel_state.cpp +++ b/src/video_core/control/channel_state.cpp @@ -16,29 +16,58 @@ #include "video_core/memory_manager.h" namespace Tegra::Control { +namespace { -ChannelState::ChannelState(s32 bind_id_) : bind_id{bind_id_}, initialized{} {} +// Match NVK/Nouveau's initial pushbuffer subchannel layout. +constexpr u32 Nvk3DSubchannel = 0; +constexpr u32 NvkComputeSubchannel = 1; +constexpr u32 Nvk2DSubchannel = 3; +constexpr u32 NvkCopySubchannel = 4; -void ChannelState::Init(Core::System& system, GPU& gpu, u64 program_id_) { +void BindNvkDefaultSubchannels(ChannelState::Payload& payload) { + auto& dma_pusher = payload.dma_pusher; + dma_pusher.BindSubchannel(&payload.maxwell_3d, Nvk3DSubchannel, Engines::EngineTypes::Maxwell3D); + dma_pusher.BindSubchannel(&payload.kepler_compute, NvkComputeSubchannel, + Engines::EngineTypes::KeplerCompute); + // Subchannel 2 is M2MF there; Eden does not expose a 0x9039 engine yet. + dma_pusher.BindSubchannel(&payload.fermi_2d, Nvk2DSubchannel, Engines::EngineTypes::Fermi2D); + dma_pusher.BindSubchannel(&payload.maxwell_dma, NvkCopySubchannel, + Engines::EngineTypes::MaxwellDMA); +} + +} // Anonymous namespace + +ChannelState::Payload::Payload(Core::System& system, MemoryManager& memory_manager, ChannelState& channel_state) + : maxwell_3d(memory_manager) + , fermi_2d(memory_manager) + , kepler_compute(memory_manager) + , maxwell_dma(memory_manager) + , kepler_memory(memory_manager) + , nv01_timer(memory_manager) + , dma_pusher(system, memory_manager, channel_state) +{} + +ChannelState::ChannelState(s32 bind_id_) + : bind_id{bind_id_} +{} + +void ChannelState::Init(Core::System& system, u64 program_id_) { ASSERT(memory_manager); program_id = program_id_; - dma_pusher.emplace(system, gpu, *memory_manager, *this); - maxwell_3d.emplace(system, *memory_manager); - fermi_2d.emplace(*memory_manager); - kepler_compute.emplace(system, *memory_manager); - maxwell_dma.emplace(system, *memory_manager); - kepler_memory.emplace(system, *memory_manager); + payload.emplace(system, *memory_manager, *this); + BindNvkDefaultSubchannels(*payload); initialized = true; } void ChannelState::BindRasterizer(VideoCore::RasterizerInterface* rasterizer) { - dma_pusher->BindRasterizer(rasterizer); + payload->dma_pusher.BindRasterizer(rasterizer); memory_manager->BindRasterizer(rasterizer); - maxwell_3d->BindRasterizer(rasterizer); - fermi_2d->BindRasterizer(rasterizer); - kepler_memory->BindRasterizer(rasterizer); - kepler_compute->BindRasterizer(rasterizer); - maxwell_dma->BindRasterizer(rasterizer); + payload->maxwell_3d.BindRasterizer(rasterizer); + payload->fermi_2d.BindRasterizer(rasterizer); + payload->kepler_memory.BindRasterizer(rasterizer); + payload->kepler_compute.BindRasterizer(rasterizer); + payload->maxwell_dma.BindRasterizer(rasterizer); + //payload->nv01_timer.BindRasterizer(rasterizer); } } // namespace Tegra::Control diff --git a/src/video_core/control/channel_state.h b/src/video_core/control/channel_state.h index 2984d2e09e..80811c1458 100644 --- a/src/video_core/control/channel_state.h +++ b/src/video_core/control/channel_state.h @@ -14,6 +14,7 @@ #include "video_core/engines/kepler_compute.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/engines/maxwell_dma.h" +#include "video_core/engines/nv01_timer.h" #include "video_core/dma_pusher.h" namespace Core { @@ -34,28 +35,33 @@ namespace Control { struct ChannelState { explicit ChannelState(s32 bind_id); - void Init(Core::System& system, GPU& gpu, u64 program_id); + void Init(Core::System& system, u64 program_id); void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); - /// 3D engine - std::optional maxwell_3d; - /// 2D engine - std::optional fermi_2d; - /// Compute engine - std::optional kepler_compute; - /// DMA engine - std::optional maxwell_dma; - /// Inline memory engine - std::optional kepler_memory; - /// NV01 Timer - std::optional nv01_timer; - std::optional dma_pusher; - std::shared_ptr memory_manager; + struct Payload { + explicit Payload(Core::System& system, MemoryManager& memory_manager, ChannelState& channel_state); + + /// 3D engine + Engines::Maxwell3D maxwell_3d; + /// 2D engine + Engines::Fermi2D fermi_2d; + /// Compute engine + Engines::KeplerCompute kepler_compute; + /// DMA engine + Engines::MaxwellDMA maxwell_dma; + /// Inline memory engine + Engines::KeplerMemory kepler_memory; + /// NV01 Timer + Engines::Nv01Timer nv01_timer; + DmaPusher dma_pusher; + }; + std::optional payload; + MemoryManager* memory_manager = nullptr; s32 bind_id = -1; u64 program_id = 0; - bool initialized{}; + bool initialized = false; }; } // namespace Control diff --git a/src/video_core/control/channel_state_cache.cpp b/src/video_core/control/channel_state_cache.cpp index f8c6a762d2..bd794d939a 100644 --- a/src/video_core/control/channel_state_cache.cpp +++ b/src/video_core/control/channel_state_cache.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -6,8 +9,11 @@ namespace VideoCommon { ChannelInfo::ChannelInfo(Tegra::Control::ChannelState& channel_state) - : maxwell3d{*channel_state.maxwell_3d}, kepler_compute{*channel_state.kepler_compute}, - gpu_memory{*channel_state.memory_manager}, program_id{channel_state.program_id} {} + : maxwell3d{channel_state.payload->maxwell_3d} + , kepler_compute{channel_state.payload->kepler_compute} + , gpu_memory{*channel_state.memory_manager} + , program_id{channel_state.program_id} + {} template class VideoCommon::ChannelSetupCaches; diff --git a/src/video_core/control/channel_state_cache.h b/src/video_core/control/channel_state_cache.h index f8b8abcac6..cb7cc891f5 100644 --- a/src/video_core/control/channel_state_cache.h +++ b/src/video_core/control/channel_state_cache.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include "common/common_types.h" @@ -78,7 +78,7 @@ public: protected: static constexpr size_t UNSET_CHANNEL{(std::numeric_limits::max)()}; - P* channel_state; + P* channel_state = nullptr; size_t current_channel_id{UNSET_CHANNEL}; size_t current_address_space{}; Tegra::Engines::Maxwell3D* maxwell3d{}; @@ -88,14 +88,14 @@ protected: std::deque

channel_storage; std::deque free_channel_ids; - ankerl::unordered_dense::map channel_map; + ::Common::unordered_map channel_map; std::vector active_channel_ids; struct AddressSpaceRef { size_t ref_count; size_t storage_id; Tegra::MemoryManager* gpu_memory; }; - ankerl::unordered_dense::map address_spaces; + ::Common::unordered_map address_spaces; mutable std::mutex config_mutex; virtual void OnGPUASRegister([[maybe_unused]] size_t map_id) {} diff --git a/src/video_core/control/channel_state_cache.inc b/src/video_core/control/channel_state_cache.inc index d882d8222f..5bb15f8907 100644 --- a/src/video_core/control/channel_state_cache.inc +++ b/src/video_core/control/channel_state_cache.inc @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -41,7 +44,7 @@ void ChannelSetupCaches

::CreateChannel(struct Tegra::Control::ChannelState& c AddressSpaceRef new_gpu_mem_ref{ .ref_count = 1, .storage_id = address_spaces.size(), - .gpu_memory = channel.memory_manager.get(), + .gpu_memory = channel.memory_manager, }; address_spaces.emplace(channel.memory_manager->GetID(), new_gpu_mem_ref); OnGPUASRegister(channel.memory_manager->GetID()); diff --git a/src/video_core/control/scheduler.cpp b/src/video_core/control/scheduler.cpp index bd3b8b860e..8af13e415c 100644 --- a/src/video_core/control/scheduler.cpp +++ b/src/video_core/control/scheduler.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2021 yuzu Emulator Project @@ -12,11 +12,8 @@ #include "video_core/gpu.h" namespace Tegra::Control { -Scheduler::Scheduler(GPU& gpu_) : gpu{gpu_} {} -Scheduler::~Scheduler() = default; - -void Scheduler::Push(s32 channel, CommandList&& entries) { +void Scheduler::Push(GPU& gpu, s32 channel, CommandList&& entries) { std::shared_ptr channel_state; { std::unique_lock lk(scheduling_guard); @@ -27,8 +24,8 @@ void Scheduler::Push(s32 channel, CommandList&& entries) { } // Process commands outside the lock to reduce contention. // Multiple channels can prepare their commands in parallel. - channel_state->dma_pusher->Push(std::move(entries)); - channel_state->dma_pusher->DispatchCalls(); + channel_state->payload->dma_pusher.Push(std::move(entries)); + channel_state->payload->dma_pusher.DispatchCalls(); } void Scheduler::DeclareChannel(std::shared_ptr new_channel) { diff --git a/src/video_core/control/scheduler.h b/src/video_core/control/scheduler.h index 9c2b9de32b..342bea5025 100644 --- a/src/video_core/control/scheduler.h +++ b/src/video_core/control/scheduler.h @@ -8,7 +8,7 @@ #include #include -#include +#include "common/container/unordered_map.h" #include "video_core/dma_pusher.h" @@ -22,17 +22,13 @@ struct ChannelState; class Scheduler { public: - explicit Scheduler(GPU& gpu_); - ~Scheduler(); - - void Push(s32 channel, CommandList&& entries); + void Push(GPU& gpu, s32 channel, CommandList&& entries); void DeclareChannel(std::shared_ptr new_channel); private: - ankerl::unordered_dense::map> channels; + ::Common::unordered_map> channels; std::mutex scheduling_guard; - GPU& gpu; }; } // namespace Control diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp index 3844a8e2f9..2c0ba007ef 100644 --- a/src/video_core/dma_pusher.cpp +++ b/src/video_core/dma_pusher.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -12,37 +12,32 @@ #include "video_core/guest_memory.h" #include "video_core/memory_manager.h" #include "video_core/rasterizer_interface.h" -#include "video_core/texture_cache/util.h" - -#ifdef _MSC_VER -#include -#endif namespace Tegra { constexpr u32 MacroRegistersStart = 0xE00; -[[maybe_unused]] constexpr u32 ComputeInline = 0x6D; +constexpr u32 ComputeInline = 0x6D; -DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_, - Control::ChannelState& channel_state_) - : gpu{gpu_}, system{system_}, memory_manager{memory_manager_}, puller{gpu_, memory_manager_, - *this, channel_state_}, signal_sync{false}, synced{false} {} +DmaPusher::DmaPusher(Core::System& system_, MemoryManager& memory_manager_, Control::ChannelState& channel_state_) + : system{system_} + , memory_manager{memory_manager_} + , channel_state{channel_state_} + , signal_sync{false} + , synced{false} +{} DmaPusher::~DmaPusher() = default; void DmaPusher::DispatchCalls() { - dma_pushbuffer_subindex = 0; - dma_state.is_last_call = true; - while (system.IsPoweredOn()) { if (!Step()) { break; } } - gpu.FlushCommands(); - gpu.OnCommandListEnd(); + system.GPU().FlushCommands(); + system.GPU().OnCommandListEnd(); } bool DmaPusher::Step() { @@ -78,12 +73,18 @@ bool DmaPusher::Step() { synced = false; } - if (header.size > 0 && dma_state.method >= MacroRegistersStart && subchannels[dma_state.subchannel]) { - subchannels[dma_state.subchannel]->current_dirty = memory_manager.IsMemoryDirty(dma_state.dma_get, header.size * sizeof(u32)); - } - if (header.size > 0) { - if (Settings::IsDMALevelDefault() ? (Settings::IsGPULevelMedium() || Settings::IsGPULevelHigh()) : Settings::IsDMALevelSafe()) { + if (subchannels[dma_state.subchannel] && dma_state.method_count) { + const auto engine = subchannel_type[dma_state.subchannel]; + const bool kepler_payload = engine == Engines::EngineTypes::KeplerCompute && dma_state.method == ComputeInline && dma_state.non_incrementing; + const bool macro_payload = engine == Engines::EngineTypes::Maxwell3D && dma_state.method >= MacroRegistersStart; + if (kepler_payload || macro_payload) { + const size_t words = std::min(dma_state.method_count, header.size); + subchannels[dma_state.subchannel]->current_dirty = memory_manager.IsMemoryDirty(dma_state.dma_get, words * sizeof(u32)); + } + } + const bool use_safe = Settings::IsDMALevelDefault() ? Settings::IsGPULevelHigh() : Settings::IsDMALevelSafe(); + if (use_safe) { Tegra::Memory::GpuGuestMemoryheaders(memory_manager, dma_state.dma_get, header.size, &command_headers); ProcessCommands(headers); } else { @@ -171,9 +172,9 @@ void DmaPusher::SetState(const CommandHeader& command_header) { dma_state.method_count = command_header.method_count; } -void DmaPusher::CallMethod(u32 argument) const { +void DmaPusher::CallMethod(u32 argument) { if (dma_state.method < non_puller_methods) { - puller.CallPullerMethod(Engines::Puller::MethodCall{ + puller.CallPullerMethod(*this, Engines::Puller::MethodCall{ dma_state.method, argument, dma_state.subchannel, @@ -184,27 +185,26 @@ void DmaPusher::CallMethod(u32 argument) const { if (!subchannel->execution_mask[dma_state.method]) { subchannel->method_sink.emplace_back(dma_state.method, argument); } else { - subchannel->ConsumeSink(); + subchannel->ConsumeSink(system); subchannel->current_dma_segment = dma_state.dma_get + dma_state.dma_word_offset; - subchannel->CallMethod(dma_state.method, argument, dma_state.is_last_call); + subchannel->CallMethod(system, dma_state.method, argument, dma_state.is_last_call); } } } -void DmaPusher::CallMultiMethod(const u32* base_start, u32 num_methods) const { +void DmaPusher::CallMultiMethod(const u32* base_start, u32 num_methods) { if (dma_state.method < non_puller_methods) { - puller.CallMultiMethod(dma_state.method, dma_state.subchannel, base_start, num_methods, dma_state.method_count); + puller.CallMultiMethod(*this, dma_state.method, dma_state.subchannel, base_start, num_methods, dma_state.method_count); } else { auto subchannel = subchannels[dma_state.subchannel]; - subchannel->ConsumeSink(); + subchannel->ConsumeSink(system); subchannel->current_dma_segment = dma_state.dma_get + dma_state.dma_word_offset; - subchannel->CallMultiMethod(dma_state.method, base_start, num_methods, dma_state.method_count); + subchannel->CallMultiMethod(system, dma_state.method, base_start, num_methods, dma_state.method_count); } } void DmaPusher::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) { rasterizer = rasterizer_; - puller.BindRasterizer(rasterizer); } } // namespace Tegra diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h index f850513603..62af628511 100644 --- a/src/video_core/dma_pusher.h +++ b/src/video_core/dma_pusher.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -109,25 +109,21 @@ inline CommandHeader BuildCommandHeader(BufferMethods method, u32 arg_count, Sub struct CommandList final { CommandList() = default; explicit CommandList(std::size_t size) : command_lists(size) {} - explicit CommandList( - boost::container::small_vector&& prefetch_command_list_) + explicit CommandList(boost::container::small_vector&& prefetch_command_list_) : prefetch_command_list{std::move(prefetch_command_list_)} {} boost::container::small_vector command_lists; boost::container::small_vector prefetch_command_list; }; -/** - * The DmaPusher class implements DMA submission to FIFOs, providing an area of memory that the - * emulated app fills with commands and tells PFIFO to process. The pushbuffers are then assembled - * into a "command stream" consisting of 32-bit words that make up "commands". - * See https://envytools.readthedocs.io/en/latest/hw/fifo/dma-pusher.html#fifo-dma-pusher for - * details on this implementation. - */ +/// @brief The DmaPusher class implements DMA submission to FIFOs, providing an area of memory that the +/// emulated app fills with commands and tells PFIFO to process. The pushbuffers are then assembled +/// into a "command stream" consisting of 32-bit words that make up "commands". +/// See https://envytools.readthedocs.io/en/latest/hw/fifo/dma-pusher.html#fifo-dma-pusher for +/// details on this implementation. class DmaPusher final { public: - explicit DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_, - Control::ChannelState& channel_state_); + explicit DmaPusher(Core::System& system_, MemoryManager& memory_manager_, Control::ChannelState& channel_state_); ~DmaPusher(); void Push(CommandList&& entries) { @@ -136,8 +132,7 @@ public: void DispatchCalls(); - void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id, - Engines::EngineTypes engine_type) { + void BindSubchannel(Engines::EngineInterface* engine, u32 subchannel_id, Engines::EngineTypes engine_type) { subchannels[subchannel_id] = engine; subchannel_type[subchannel_id] = engine_type; } @@ -152,11 +147,11 @@ private: void SetState(const CommandHeader& command_header); - void CallMethod(u32 argument) const; - void CallMultiMethod(const u32* base_start, u32 num_methods) const; + void CallMethod(u32 argument); + void CallMultiMethod(const u32* base_start, u32 num_methods); - Common::ScratchBuffer - command_headers; ///< Buffer for list of commands fetched at once +public: + Common::ScratchBuffer command_headers; ///< Buffer for list of commands fetched at once std::queue dma_pushbuffer; ///< Queue of command lists to be processed std::size_t dma_pushbuffer_subindex{}; ///< Index within a command list within the pushbuffer @@ -172,24 +167,24 @@ private: bool is_last_call; }; + Core::System& system; + MemoryManager& memory_manager; + Control::ChannelState& channel_state; + DmaState dma_state{}; - bool dma_increment_once{}; - - const bool ib_enable{true}; ///< IB mode enabled - std::array subchannels{}; std::array subchannel_type; - GPU& gpu; - Core::System& system; - MemoryManager& memory_manager; - mutable Engines::Puller puller; - - VideoCore::RasterizerInterface* rasterizer; - bool signal_sync; - bool synced; + Engines::Puller puller; std::mutex sync_mutex; std::condition_variable sync_cv; + + VideoCore::RasterizerInterface* rasterizer = nullptr; + + const bool ib_enable : 1 = true; ///< IB mode enabled + bool dma_increment_once : 1 = false; + bool signal_sync : 1 = false; + bool synced : 1 = false; }; } // namespace Tegra diff --git a/src/video_core/engines/draw_manager.cpp b/src/video_core/engines/draw_manager.cpp index 079c7bdc09..5891c25622 100644 --- a/src/video_core/engines/draw_manager.cpp +++ b/src/video_core/engines/draw_manager.cpp @@ -4,6 +4,8 @@ // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + #include "common/settings.h" #include "video_core/dirty_flags.h" #include "video_core/engines/maxwell_3d.h" @@ -130,6 +132,44 @@ void Maxwell3D::DrawManager::SetInlineIndexBuffer(Maxwell3D& maxwell3d, u32 inde draw_state.draw_mode = DrawMode::InlineIndex; } +void Maxwell3D::DrawManager::SetInlineIndexBuffer(Maxwell3D& maxwell3d, u32 method, + const u32* base_start, u32 amount) { + auto& index_buffer = draw_state.inline_index_draw_indexes; + switch (method) { + case MAXWELL3D_REG_INDEX(draw_inline_index): { + const auto* const bytes = reinterpret_cast(base_start); + index_buffer.insert(index_buffer.end(), bytes, bytes + size_t(amount) * sizeof(u32)); + break; + } + case MAXWELL3D_REG_INDEX(inline_index_2x16.even): { + const size_t offset = index_buffer.size(); + index_buffer.resize(offset + size_t(amount) * 2 * sizeof(u32)); + u8* dst = index_buffer.data() + offset; + for (u32 i = 0; i < amount; ++i) { + const u32 word = base_start[i]; + const u32 indexes[2]{word & 0xFFFF, word >> 16}; + std::memcpy(dst, indexes, sizeof(indexes)); + dst += sizeof(indexes); + } + break; + } + case MAXWELL3D_REG_INDEX(inline_index_4x8.index0): { + const size_t offset = index_buffer.size(); + index_buffer.resize(offset + size_t(amount) * 4 * sizeof(u32)); + u8* dst = index_buffer.data() + offset; + for (u32 i = 0; i < amount; ++i) { + const u32 word = base_start[i]; + const u32 indexes[4]{word & 0xFF, (word >> 8) & 0xFF, (word >> 16) & 0xFF, + word >> 24}; + std::memcpy(dst, indexes, sizeof(indexes)); + dst += sizeof(indexes); + } + break; + } + } + draw_state.draw_mode = DrawMode::InlineIndex; +} + void Maxwell3D::DrawManager::DrawBegin(Maxwell3D& maxwell3d) { auto reset_instance_count = maxwell3d.regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First; auto increment_instance_count = maxwell3d.regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent; diff --git a/src/video_core/engines/engine_interface.h b/src/video_core/engines/engine_interface.h index bf3bd66aca..83dbf13059 100644 --- a/src/video_core/engines/engine_interface.h +++ b/src/video_core/engines/engine_interface.h @@ -12,6 +12,10 @@ #include "common/common_types.h" +namespace Core { +class System; +} + namespace Tegra::Engines { enum class EngineTypes : u32 { @@ -28,28 +32,25 @@ public: virtual ~EngineInterface() = default; /// Write the value to the register identified by method. - virtual void CallMethod(u32 method, u32 method_argument, bool is_last_call) = 0; + virtual void CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) = 0; /// Write multiple values to the register identified by method. - virtual void CallMultiMethod(u32 method, const u32* base_start, u32 amount, - u32 methods_pending) = 0; + virtual void CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) = 0; - void ConsumeSink() { - if (method_sink.empty()) { - return; + void ConsumeSink(Core::System& system) { + if (!method_sink.empty()) { + ConsumeSinkImpl(system); } - ConsumeSinkImpl(); } std::bitset<(std::numeric_limits::max)()> execution_mask{}; std::vector> method_sink{}; - bool current_dirty{}; GPUVAddr current_dma_segment; - + bool current_dirty{}; protected: - virtual void ConsumeSinkImpl() { + virtual void ConsumeSinkImpl(Core::System& system) { for (auto [method, value] : method_sink) { - CallMethod(method, value, true); + CallMethod(system, method, value, true); } method_sink.clear(); } diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp index b442c5cc76..aeff5e156f 100644 --- a/src/video_core/engines/fermi_2d.cpp +++ b/src/video_core/engines/fermi_2d.cpp @@ -36,9 +36,8 @@ void Fermi2D::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) { rasterizer = rasterizer_; } -void Fermi2D::CallMethod(u32 method, u32 method_argument, bool is_last_call) { - ASSERT_MSG(method < Regs::NUM_REGS, - "Invalid Fermi2D register, increase the size of the Regs structure"); +void Fermi2D::CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) { + ASSERT_MSG(method < Regs::NUM_REGS, "Invalid Fermi2D register, increase the size of the Regs structure"); regs.reg_array[method] = method_argument; if (method == FERMI2D_REG_INDEX(pixels_from_memory.src_y0) + 1) { @@ -46,13 +45,13 @@ void Fermi2D::CallMethod(u32 method, u32 method_argument, bool is_last_call) { } } -void Fermi2D::CallMultiMethod(u32 method, const u32* base_start, u32 amount, u32 methods_pending) { +void Fermi2D::CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) { for (u32 i = 0; i < amount; ++i) { - CallMethod(method, base_start[i], methods_pending - i <= 1); + CallMethod(system, method, base_start[i], methods_pending - i <= 1); } } -void Fermi2D::ConsumeSinkImpl() { +void Fermi2D::ConsumeSinkImpl(Core::System& system) { for (auto [method, value] : method_sink) { regs.reg_array[method] = value; } @@ -60,8 +59,7 @@ void Fermi2D::ConsumeSinkImpl() { } void Fermi2D::Blit() { - LOG_DEBUG(HW_GPU, "called. source address=0x{:x}, destination address=0x{:x}", - regs.src.Address(), regs.dst.Address()); + LOG_DEBUG(HW_GPU, "called. source address={:#x}, destination address={:#x}", regs.src.Address(), regs.dst.Address()); UNIMPLEMENTED_IF_MSG(regs.operation != Operation::SrcCopy, "Operation is not copy"); UNIMPLEMENTED_IF_MSG(regs.src.layer != 0, "Source layer is not zero"); diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h index 705b323e15..7fddf9fdc1 100644 --- a/src/video_core/engines/fermi_2d.h +++ b/src/video_core/engines/fermi_2d.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -44,11 +47,10 @@ public: void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); /// Write the value to the register identified by method. - void CallMethod(u32 method, u32 method_argument, bool is_last_call) override; + void CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) override; /// Write multiple values to the register identified by method. - void CallMultiMethod(u32 method, const u32* base_start, u32 amount, - u32 methods_pending) override; + void CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) override; enum class Origin : u32 { Center = 0, @@ -311,7 +313,7 @@ private: /// registers. void Blit(); - void ConsumeSinkImpl() override; + void ConsumeSinkImpl(Core::System& system) override; }; #define ASSERT_REG_POSITION(field_name, position) \ diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp index 7b4efeb1e0..96a12539a2 100644 --- a/src/video_core/engines/kepler_compute.cpp +++ b/src/video_core/engines/kepler_compute.cpp @@ -16,8 +16,10 @@ namespace Tegra::Engines { -KeplerCompute::KeplerCompute(Core::System& system_, MemoryManager& memory_manager_) - : system{system_}, memory_manager{memory_manager_}, upload_state{memory_manager, regs.upload} { +KeplerCompute::KeplerCompute(MemoryManager& memory_manager_) + : memory_manager{memory_manager_} + , upload_state{memory_manager, regs.upload} +{ execution_mask.reset(); execution_mask[KEPLER_COMPUTE_REG_INDEX(exec_upload)] = true; execution_mask[KEPLER_COMPUTE_REG_INDEX(data_upload)] = true; @@ -31,16 +33,15 @@ void KeplerCompute::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) upload_state.BindRasterizer(rasterizer); } -void KeplerCompute::ConsumeSinkImpl() { +void KeplerCompute::ConsumeSinkImpl(Core::System& system) { for (auto [method, value] : method_sink) { regs.reg_array[method] = value; } method_sink.clear(); } -void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_call) { - ASSERT_MSG(method < Regs::NUM_REGS, - "Invalid KeplerCompute register, increase the size of the Regs structure"); +void KeplerCompute::CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) { + ASSERT_MSG(method < Regs::NUM_REGS, "Invalid KeplerCompute register, increase the size of the Regs structure"); regs.reg_array[method] = method_argument; @@ -48,13 +49,16 @@ void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_cal case KEPLER_COMPUTE_REG_INDEX(exec_upload): { UploadInfo info{.upload_address = upload_address, .exec_address = upload_state.ExecTargetAddress(), - .copy_size = upload_state.GetUploadSize()}; + .copy_size = upload_state.GetUploadSize(), + .was_dirty = upload_dirty}; uploads.push_back(info); upload_state.ProcessExec(regs.exec_upload.linear != 0); break; } case KEPLER_COMPUTE_REG_INDEX(data_upload): { upload_address = current_dma_segment; + upload_dirty = current_dirty; + current_dirty = false; upload_state.ProcessData(method_argument, is_last_call); break; } @@ -63,9 +67,11 @@ void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_cal for (auto& data : uploads) { const GPUVAddr offset = data.exec_address - launch_desc_loc; - if (offset / sizeof(u32) == LAUNCH_REG_INDEX(grid_dim_x) && - memory_manager.IsMemoryDirty(data.upload_address, data.copy_size)) { - indirect_compute = {data.upload_address}; + if (offset / sizeof(u32) == LAUNCH_REG_INDEX(grid_dim_x)) { + const bool source_dirty = memory_manager.IsMemoryDirty(data.upload_address, data.copy_size); + if (data.was_dirty || source_dirty) { + indirect_compute = {data.upload_address}; + } } } uploads.clear(); @@ -78,16 +84,17 @@ void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_cal } } -void KeplerCompute::CallMultiMethod(u32 method, const u32* base_start, u32 amount, - u32 methods_pending) { +void KeplerCompute::CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) { switch (method) { case KEPLER_COMPUTE_REG_INDEX(data_upload): upload_address = current_dma_segment; + upload_dirty = current_dirty; + current_dirty = false; upload_state.ProcessData(base_start, amount); return; default: for (u32 i = 0; i < amount; i++) { - CallMethod(method, base_start[i], methods_pending - i <= 1); + CallMethod(system, method, base_start[i], methods_pending - i <= 1); } break; } diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h index 735e05fb43..078d3d45be 100644 --- a/src/video_core/engines/kepler_compute.h +++ b/src/video_core/engines/kepler_compute.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -42,7 +45,7 @@ namespace Tegra::Engines { class KeplerCompute final : public EngineInterface { public: - explicit KeplerCompute(Core::System& system, MemoryManager& memory_manager); + explicit KeplerCompute(MemoryManager& memory_manager); ~KeplerCompute(); /// Binds a rasterizer to this engine. @@ -199,11 +202,10 @@ public: "KeplerCompute LaunchParams has wrong size"); /// Write the value to the register identified by method. - void CallMethod(u32 method, u32 method_argument, bool is_last_call) override; + void CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) override; /// Write multiple values to the register identified by method. - void CallMultiMethod(u32 method, const u32* base_start, u32 amount, - u32 methods_pending) override; + void CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) override; std::optional GetIndirectComputeAddress() const { return indirect_compute; @@ -212,7 +214,7 @@ public: private: void ProcessLaunch(); - void ConsumeSinkImpl() override; + void ConsumeSinkImpl(Core::System& system) override; /// Retrieves information about a specific TIC entry from the TIC buffer. Texture::TICEntry GetTICEntry(u32 tic_index) const; @@ -220,16 +222,17 @@ private: /// Retrieves information about a specific TSC entry from the TSC buffer. Texture::TSCEntry GetTSCEntry(u32 tsc_index) const; - Core::System& system; MemoryManager& memory_manager; VideoCore::RasterizerInterface* rasterizer = nullptr; Upload::State upload_state; GPUVAddr upload_address; + bool upload_dirty{}; struct UploadInfo { GPUVAddr upload_address; GPUVAddr exec_address; u32 copy_size; + bool was_dirty; }; std::vector uploads; std::optional indirect_compute{}; diff --git a/src/video_core/engines/kepler_memory.cpp b/src/video_core/engines/kepler_memory.cpp index 5d4c4720d3..3af5f91271 100644 --- a/src/video_core/engines/kepler_memory.cpp +++ b/src/video_core/engines/kepler_memory.cpp @@ -14,8 +14,9 @@ namespace Tegra::Engines { -KeplerMemory::KeplerMemory(Core::System& system_, MemoryManager& memory_manager) - : system{system_}, upload_state{memory_manager, regs.upload} {} +KeplerMemory::KeplerMemory(MemoryManager& memory_manager) + : upload_state{memory_manager, regs.upload} +{} KeplerMemory::~KeplerMemory() = default; @@ -27,16 +28,15 @@ void KeplerMemory::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) { execution_mask[KEPLERMEMORY_REG_INDEX(data)] = true; } -void KeplerMemory::ConsumeSinkImpl() { +void KeplerMemory::ConsumeSinkImpl(Core::System& system) { for (auto [method, value] : method_sink) { regs.reg_array[method] = value; } method_sink.clear(); } -void KeplerMemory::CallMethod(u32 method, u32 method_argument, bool is_last_call) { - ASSERT_MSG(method < Regs::NUM_REGS, - "Invalid KeplerMemory register, increase the size of the Regs structure"); +void KeplerMemory::CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) { + ASSERT_MSG(method < Regs::NUM_REGS, "Invalid KeplerMemory register, increase the size of the Regs structure"); regs.reg_array[method] = method_argument; @@ -52,15 +52,14 @@ void KeplerMemory::CallMethod(u32 method, u32 method_argument, bool is_last_call } } -void KeplerMemory::CallMultiMethod(u32 method, const u32* base_start, u32 amount, - u32 methods_pending) { +void KeplerMemory::CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) { switch (method) { case KEPLERMEMORY_REG_INDEX(data): upload_state.ProcessData(base_start, amount); return; default: for (u32 i = 0; i < amount; i++) { - CallMethod(method, base_start[i], methods_pending - i <= 1); + CallMethod(system, method, base_start[i], methods_pending - i <= 1); } break; } diff --git a/src/video_core/engines/kepler_memory.h b/src/video_core/engines/kepler_memory.h index fb1eecbba9..aa0952d17a 100644 --- a/src/video_core/engines/kepler_memory.h +++ b/src/video_core/engines/kepler_memory.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -36,18 +39,17 @@ namespace Tegra::Engines { class KeplerMemory final : public EngineInterface { public: - explicit KeplerMemory(Core::System& system_, MemoryManager& memory_manager); + explicit KeplerMemory(MemoryManager& memory_manager); ~KeplerMemory() override; /// Binds a rasterizer to this engine. void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); /// Write the value to the register identified by method. - void CallMethod(u32 method, u32 method_argument, bool is_last_call) override; + void CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) override; /// Write multiple values to the register identified by method. - void CallMultiMethod(u32 method, const u32* base_start, u32 amount, - u32 methods_pending) override; + void CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) override; struct Regs { static constexpr size_t NUM_REGS = 0x7F; @@ -73,9 +75,7 @@ public: } regs{}; private: - void ConsumeSinkImpl() override; - - Core::System& system; + void ConsumeSinkImpl(Core::System& system) override; Upload::State upload_state; }; diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 9aaa99f7ff..e7da9d9138 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -25,9 +25,8 @@ namespace Tegra::Engines { /// First register id that is actually a Macro call. constexpr u32 MacroRegistersStart = 0xE00; -Maxwell3D::Maxwell3D(Core::System& system_, MemoryManager& memory_manager_) +Maxwell3D::Maxwell3D(MemoryManager& memory_manager_) : draw_manager() - , system{system_} , memory_manager{memory_manager_} #ifdef ARCHITECTURE_x86_64 , macro_engine(bool(Settings::values.disable_macro_jit)) @@ -201,28 +200,23 @@ bool Maxwell3D::IsMethodExecutable(u32 method) { } } -void Maxwell3D::ProcessMacro(u32 method, const u32* base_start, u32 amount, bool is_last_call) { +void Maxwell3D::ProcessMacro(Core::System& system, u32 method, const u32* base_start, u32 amount, bool is_last_call) { if (executing_macro == 0) { // A macro call must begin by writing the macro method's register, not its argument. - ASSERT_MSG((method % 2) == 0, - "Can't start macro execution by writing to the ARGS register"); + ASSERT((method % 2) == 0 && "Can't start macro execution by writing to the ARGS register"); executing_macro = method; } macro_params.insert(macro_params.end(), base_start, base_start + amount); - for (size_t i = 0; i < amount; i++) { - macro_addresses.push_back(current_dma_segment + i * sizeof(u32)); - } macro_segments.emplace_back(current_dma_segment, amount); current_macro_dirty |= current_dirty; current_dirty = false; // Call the macro when there are no more parameters in the command buffer if (is_last_call) { - ConsumeSink(); - CallMacroMethod(executing_macro, macro_params); + ConsumeSink(system); + CallMacroMethod(system, executing_macro, macro_params); macro_params.clear(); - macro_addresses.clear(); macro_segments.clear(); current_macro_dirty = false; } @@ -270,35 +264,24 @@ u32 Maxwell3D::GetMaxCurrentVertices() { size_t Maxwell3D::EstimateIndexBufferSize() { GPUVAddr start_address = regs.index_buffer.StartAddress(); GPUVAddr end_address = regs.index_buffer.EndAddress(); - static constexpr std::array max_sizes = {(std::numeric_limits::max)(), - (std::numeric_limits::max)(), - (std::numeric_limits::max)()}; - const size_t byte_size = regs.index_buffer.FormatSizeInBytes(); - const size_t log2_byte_size = Common::Log2Ceil64(byte_size); - const size_t cap{GetMaxCurrentVertices() * 4 * byte_size}; - const size_t lower_cap = - std::min(static_cast(end_address - start_address), cap); - return std::min( - memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[log2_byte_size]) / - byte_size, - lower_cap); + auto const byte_size = regs.index_buffer.FormatSizeInBytes(); + auto const max_size = 1ull << (byte_size * CHAR_BIT); + auto const upper_cap = GetMaxCurrentVertices() * 4 * byte_size; + auto const lower_cap = std::min(size_t(end_address - start_address), upper_cap); + return std::min(memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_size) / byte_size, lower_cap); } u32 Maxwell3D::ProcessShadowRam(u32 method, u32 argument) { // Keep track of the register value in shadow_state when requested. - const auto control = shadow_state.shadow_ram_control; - if (control == Regs::ShadowRamControl::Track || - control == Regs::ShadowRamControl::TrackWithFilter) { - shadow_state.reg_array[method] = argument; - return argument; - } - if (control == Regs::ShadowRamControl::Replay) { + auto const c = shadow_state.shadow_ram_control; + if (c == Regs::ShadowRamControl::Track || c == Regs::ShadowRamControl::TrackWithFilter) + return shadow_state.reg_array[method] = argument; + else if (c == Regs::ShadowRamControl::Replay) return shadow_state.reg_array[method]; - } return argument; } -void Maxwell3D::ConsumeSinkImpl() { +void Maxwell3D::ConsumeSinkImpl(Core::System& system) { const auto control = shadow_state.shadow_ram_control; if (control == Regs::ShadowRamControl::Track || control == Regs::ShadowRamControl::TrackWithFilter) { for (auto [method, value] : method_sink) { @@ -317,10 +300,8 @@ void Maxwell3D::ConsumeSinkImpl() { void Maxwell3D::ProcessDirtyRegisters(u32 method, u32 argument) { regs.reg_array[method] = argument; - - for (const auto& table : dirty.tables) { + for (auto const& table : dirty.tables) dirty.flags[table[method]] = true; - } } void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argument, bool is_last_call) { @@ -391,7 +372,7 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume } } -void Maxwell3D::CallMacroMethod(u32 method, const std::vector& parameters) { +void Maxwell3D::CallMacroMethod(Core::System& system, u32 method, const std::vector& parameters) { // Reset the current macro. executing_macro = 0; @@ -400,11 +381,11 @@ void Maxwell3D::CallMacroMethod(u32 method, const std::vector& parameters) ((method - MacroRegistersStart) >> 1) % static_cast(macro_positions.size()); // Execute the current macro. - macro_engine.Execute(*this, macro_positions[entry], parameters); + macro_engine.Execute(system, *this, macro_positions[entry], parameters); draw_manager.DrawDeferred(*this); } -void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) { +void Maxwell3D::CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) { // It is an error to write to a register other than the current macro's ARG register before // it has finished execution. if (executing_macro != 0) { @@ -414,7 +395,7 @@ void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) { // Methods after 0xE00 are special, they're actually triggers for some microcode that was // uploaded to the GPU during initialization. if (method >= MacroRegistersStart) { - ProcessMacro(method, &method_argument, 1, is_last_call); + ProcessMacro(system, method, &method_argument, 1, is_last_call); return; } @@ -424,12 +405,11 @@ void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) { ProcessMethodCall(method, argument, method_argument, is_last_call); } -void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount, - u32 methods_pending) { +void Maxwell3D::CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) { // Methods after 0xE00 are special, they're actually triggers for some microcode that was // uploaded to the GPU during initialization. if (method >= MacroRegistersStart) { - ProcessMacro(method, base_start, amount, amount == methods_pending); + ProcessMacro(system, method, base_start, amount, amount == methods_pending); return; } switch (method) { @@ -456,9 +436,17 @@ void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount, upload_state.ProcessData(base_start, amount); return; } + case MAXWELL3D_REG_INDEX(draw_inline_index): + case MAXWELL3D_REG_INDEX(inline_index_2x16.even): + case MAXWELL3D_REG_INDEX(inline_index_4x8.index0): + if (shadow_state.shadow_ram_control != Regs::ShadowRamControl::Replay) { + ProcessInlineIndexMultiData(method, base_start, amount); + break; + } + [[fallthrough]]; default: for (u32 i = 0; i < amount; i++) { - CallMethod(method, base_start[i], methods_pending - i <= 1); + CallMethod(system, method, base_start[i], methods_pending - i <= 1); } break; } @@ -480,7 +468,7 @@ void Maxwell3D::ProcessFirmwareCall4() { regs.shadow_scratch[0] = 1; } -void Maxwell3D::StampQueryResult(u64 payload, bool long_query) { +void Maxwell3D::StampQueryResult(Core::System& system, u64 payload, bool long_query) { const GPUVAddr sequence_address{regs.report_semaphore.Address()}; if (long_query) { memory_manager.Write(sequence_address + sizeof(u64), system.GPU().GetTicks()); @@ -642,6 +630,15 @@ void Maxwell3D::ProcessCBData(u32 value) { ProcessCBMultiData(&value, 1); } +void Maxwell3D::ProcessInlineIndexMultiData(u32 method, const u32* start_base, u32 amount) { + if (amount == 0) { + return; + } + const u32 argument = ProcessShadowRam(method, start_base[amount - 1]); + ProcessDirtyRegisters(method, argument); + draw_manager.SetInlineIndexBuffer(*this, method, start_base, amount); +} + Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const { const GPUVAddr tic_address_gpu{regs.tex_header.Address() + tic_index * sizeof(Texture::TICEntry)}; diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 3ac79e0eb8..0cab2ae986 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -55,7 +55,7 @@ namespace Tegra::Engines { class Maxwell3D final : public EngineInterface { public: - explicit Maxwell3D(Core::System& system, MemoryManager& memory_manager); + explicit Maxwell3D(MemoryManager& memory_manager); ~Maxwell3D(); /// Binds a rasterizer to this engine. @@ -2215,7 +2215,7 @@ public: u32 first; u32 count; - unsigned FormatSizeInBytes() const { + size_t FormatSizeInBytes() const { switch (format) { case IndexFormat::UnsignedByte: return 1; @@ -2224,7 +2224,7 @@ public: case IndexFormat::UnsignedInt: return 4; } - ASSERT(false); + UNREACHABLE(); return 1; } @@ -2257,7 +2257,7 @@ public: /// Returns whether the vertex array specified by index is supposed to be /// accessed per instance or not. bool IsInstancingEnabled(std::size_t index) const { - return bool(is_instanced[index]); //FUCK YOU MSVC + return bool(is_instanced[index]); } }; @@ -3077,6 +3077,7 @@ public: void DrawArrayIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology); void DrawIndexedIndirect(Maxwell3D& maxwell3d, Maxwell3D::Regs::PrimitiveTopology topology, u32 index_first, u32 index_count); void SetInlineIndexBuffer(Maxwell3D& maxwell3d, u32 index); + void SetInlineIndexBuffer(Maxwell3D& maxwell3d, u32 method, const u32* base_start, u32 amount); void DrawBegin(Maxwell3D& maxwell3d); void DrawEnd(Maxwell3D& maxwell3d, u32 instance_count = 1, bool force_draw = false); void DrawIndexSmall(Maxwell3D& maxwell3d, u32 argument); @@ -3110,7 +3111,7 @@ public: void SetHLEReplacementAttributeType(u32 bank, u32 offset, HLEReplacementAttributeType name); - ankerl::unordered_dense::map replace_table; + ::Common::unordered_map replace_table; static_assert(sizeof(Regs) == Regs::NUM_REGS * sizeof(u32), "Maxwell3D Regs has wrong size"); static_assert(std::is_trivially_copyable_v, "Maxwell3D Regs must be trivially copyable"); @@ -3129,11 +3130,10 @@ public: u32 GetRegisterValue(u32 method) const; /// Write the value to the register identified by method. - void CallMethod(u32 method, u32 method_argument, bool is_last_call) override; + void CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) override; /// Write multiple values to the register identified by method. - void CallMultiMethod(u32 method, const u32* base_start, u32 amount, - u32 methods_pending) override; + void CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) override; bool ShouldExecute() const { return execute_on; @@ -3148,9 +3148,9 @@ public: } struct DirtyState { - using Flags = std::bitset<(std::numeric_limits::max)()>; + using Flags = std::bitset<(std::numeric_limits::max)() + 1>; using Table = std::array; - using Tables = std::array; + using Tables = std::array, 2>; Flags flags; Tables tables{}; @@ -3159,7 +3159,14 @@ public: DrawManager draw_manager; GPUVAddr GetMacroAddress(size_t index) const { - return macro_addresses[index]; + size_t base = 0; + for (const auto& [addr, count] : macro_segments) { + if (index < base + count) { + return addr + (index - base) * sizeof(u32); + } + base += count; + } + return 0; } void RefreshParameters() { @@ -3187,16 +3194,18 @@ public: void ProcessCBData(u32 value); void ProcessCBMultiData(const u32* start_base, u32 amount); + void ProcessInlineIndexMultiData(u32 method, const u32* start_base, u32 amount); + private: void InitializeRegisterDefaults(); - void ProcessMacro(u32 method, const u32* base_start, u32 amount, bool is_last_call); + void ProcessMacro(Core::System& system, u32 method, const u32* base_start, u32 amount, bool is_last_call); u32 ProcessShadowRam(u32 method, u32 argument); void ProcessDirtyRegisters(u32 method, u32 argument); - void ConsumeSinkImpl() override; + void ConsumeSinkImpl(Core::System& system) override; void ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argument, bool is_last_call); @@ -3212,7 +3221,7 @@ private: * @param method Method to call * @param parameters Arguments to the method call */ - void CallMacroMethod(u32 method, const std::vector& parameters); + void CallMacroMethod(Core::System& system, u32 method, const std::vector& parameters); /// Handles writes to the macro uploading register. void ProcessMacroUpload(u32 data); @@ -3227,7 +3236,7 @@ private: void ProcessQueryGet(); /// Writes the query result accordingly. - void StampQueryResult(u64 payload, bool long_query); + void StampQueryResult(Core::System& system, u64 payload, bool long_query); /// Handles conditional rendering. void ProcessQueryCondition(); @@ -3242,7 +3251,6 @@ private: bool IsMethodExecutable(u32 method); - Core::System& system; MemoryManager& memory_manager; VideoCore::RasterizerInterface* rasterizer = nullptr; @@ -3263,7 +3271,6 @@ private: bool execute_on{true}; std::vector> macro_segments; - std::vector macro_addresses; bool current_macro_dirty{}; }; diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index 089d118a09..ed4675ed12 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -21,8 +21,9 @@ namespace Tegra::Engines { using namespace Texture; -MaxwellDMA::MaxwellDMA(Core::System& system_, MemoryManager& memory_manager_) - : system{system_}, memory_manager{memory_manager_} { +MaxwellDMA::MaxwellDMA(MemoryManager& memory_manager_) + : memory_manager{memory_manager_} +{ execution_mask.reset(); execution_mask[offsetof(Regs, launch_dma) / sizeof(u32)] = true; } @@ -33,14 +34,14 @@ void MaxwellDMA::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) { rasterizer = rasterizer_; } -void MaxwellDMA::ConsumeSinkImpl() { +void MaxwellDMA::ConsumeSinkImpl(Core::System& system) { for (auto [method, value] : method_sink) { regs.reg_array[method] = value; } method_sink.clear(); } -void MaxwellDMA::CallMethod(u32 method, u32 method_argument, bool is_last_call) { +void MaxwellDMA::CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) { ASSERT_MSG(method < NUM_REGS, "Invalid MaxwellDMA register"); regs.reg_array[method] = method_argument; @@ -50,16 +51,14 @@ void MaxwellDMA::CallMethod(u32 method, u32 method_argument, bool is_last_call) } } -void MaxwellDMA::CallMultiMethod(u32 method, const u32* base_start, u32 amount, - u32 methods_pending) { +void MaxwellDMA::CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) { for (u32 i = 0; i < amount; ++i) { - CallMethod(method, base_start[i], methods_pending - i <= 1); + CallMethod(system, method, base_start[i], methods_pending - i <= 1); } } void MaxwellDMA::Launch() { - LOG_TRACE(Render_OpenGL, "DMA copy 0x{:x} -> 0x{:x}", static_cast(regs.offset_in), - static_cast(regs.offset_out)); + LOG_TRACE(Render_OpenGL, "DMA copy {:#x} -> {:#x}", static_cast(regs.offset_in), GPUVAddr(regs.offset_out)); // TODO(Subv): Perform more research and implement all features of this engine. const LaunchDMA& launch = regs.launch_dma; diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h index 99341e431c..9cf2813d31 100644 --- a/src/video_core/engines/maxwell_dma.h +++ b/src/video_core/engines/maxwell_dma.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -238,15 +241,14 @@ public: void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); - explicit MaxwellDMA(Core::System& system_, MemoryManager& memory_manager_); + explicit MaxwellDMA(MemoryManager& memory_manager_); ~MaxwellDMA() override; /// Write the value to the register identified by method. - void CallMethod(u32 method, u32 method_argument, bool is_last_call) override; + void CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) override; /// Write multiple values to the register identified by method. - void CallMultiMethod(u32 method, const u32* base_start, u32 amount, - u32 methods_pending) override; + void CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) override; private: /// Performs the copy from the source buffer to the destination buffer as configured in the @@ -261,9 +263,7 @@ private: void ReleaseSemaphore(); - void ConsumeSinkImpl() override; - - Core::System& system; + void ConsumeSinkImpl(Core::System& system) override; MemoryManager& memory_manager; VideoCore::RasterizerInterface* rasterizer = nullptr; diff --git a/src/video_core/engines/nv01_timer.h b/src/video_core/engines/nv01_timer.h index add9886f41..9e7d1a1acf 100644 --- a/src/video_core/engines/nv01_timer.h +++ b/src/video_core/engines/nv01_timer.h @@ -26,27 +26,23 @@ class MemoryManager; namespace Tegra::Engines { class Nv01Timer final : public EngineInterface { public: - explicit Nv01Timer(Core::System& system_, MemoryManager& memory_manager) - : system{system_} - {} - ~Nv01Timer() override; + explicit Nv01Timer(MemoryManager& memory_manager) noexcept {} + ~Nv01Timer() noexcept override {} /// Write the value to the register identified by method. - void CallMethod(u32 method, u32 method_argument, bool is_last_call) override { + void CallMethod(Core::System& system, u32 method, u32 method_argument, bool is_last_call) override { LOG_DEBUG(HW_GPU, "method={}, argument={}, is_last_call={}", method, method_argument, is_last_call); } /// Write multiple values to the register identified by method. - void CallMultiMethod(u32 method, const u32* base_start, u32 amount, u32 methods_pending) override { + void CallMultiMethod(Core::System& system, u32 method, const u32* base_start, u32 amount, u32 methods_pending) override { LOG_DEBUG(HW_GPU, "method={}, base_start={}, amount={}, pending={}", method, fmt::ptr(base_start), amount, methods_pending); } struct Regs { - // No fucking idea INSERT_PADDING_BYTES_NOINIT(0x48); } regs{}; private: - void ConsumeSinkImpl() override {} - Core::System& system; + void ConsumeSinkImpl(Core::System& system) override {} }; } diff --git a/src/video_core/engines/puller.cpp b/src/video_core/engines/puller.cpp index 4030f93d49..0b05d31718 100644 --- a/src/video_core/engines/puller.cpp +++ b/src/video_core/engines/puller.cpp @@ -22,37 +22,40 @@ namespace Tegra::Engines { -Puller::Puller(GPU& gpu_, MemoryManager& memory_manager_, DmaPusher& dma_pusher_, - Control::ChannelState& channel_state_) - : gpu{gpu_}, memory_manager{memory_manager_}, dma_pusher{dma_pusher_}, channel_state{ - channel_state_} {} +namespace { -Puller::~Puller() = default; +constexpr u32 Gf100BindClassMask = 0xffff; +constexpr u32 Gf100BindValidMask = 0x1f0000 | Gf100BindClassMask; -void Puller::ProcessBindMethod(const MethodCall& method_call) { - // Bind the current subchannel to the desired engine id. - LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", method_call.subchannel, - method_call.argument); - const auto engine_id = static_cast(method_call.argument); +} // Anonymous namespace + +void Puller::ProcessBindMethod(DmaPusher& dma_pusher, const MethodCall& method_call) { + LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {:#x}", method_call.subchannel, method_call.argument); + u32 engine = method_call.argument; + if ((engine & ~Gf100BindClassMask) != 0 && (engine & ~Gf100BindValidMask) == 0) { + engine &= Gf100BindClassMask; + } + + const auto engine_id = static_cast(engine); bound_engines[method_call.subchannel] = engine_id; switch (engine_id) { case EngineID::FERMI_TWOD_A: - dma_pusher.BindSubchannel(&*channel_state.fermi_2d, method_call.subchannel, EngineTypes::Fermi2D); + dma_pusher.BindSubchannel(&dma_pusher.channel_state.payload->fermi_2d, method_call.subchannel, EngineTypes::Fermi2D); break; case EngineID::MAXWELL_B: - dma_pusher.BindSubchannel(&*channel_state.maxwell_3d, method_call.subchannel, EngineTypes::Maxwell3D); + dma_pusher.BindSubchannel(&dma_pusher.channel_state.payload->maxwell_3d, method_call.subchannel, EngineTypes::Maxwell3D); break; case EngineID::KEPLER_COMPUTE_B: - dma_pusher.BindSubchannel(&*channel_state.kepler_compute, method_call.subchannel, EngineTypes::KeplerCompute); + dma_pusher.BindSubchannel(&dma_pusher.channel_state.payload->kepler_compute, method_call.subchannel, EngineTypes::KeplerCompute); break; case EngineID::MAXWELL_DMA_COPY_A: - dma_pusher.BindSubchannel(&*channel_state.maxwell_dma, method_call.subchannel, EngineTypes::MaxwellDMA); + dma_pusher.BindSubchannel(&dma_pusher.channel_state.payload->maxwell_dma, method_call.subchannel, EngineTypes::MaxwellDMA); break; case EngineID::KEPLER_INLINE_TO_MEMORY_B: - dma_pusher.BindSubchannel(&*channel_state.kepler_memory, method_call.subchannel, EngineTypes::KeplerMemory); + dma_pusher.BindSubchannel(&dma_pusher.channel_state.payload->kepler_memory, method_call.subchannel, EngineTypes::KeplerMemory); break; case EngineID::NV01_TIMER: - dma_pusher.BindSubchannel(&*channel_state.nv01_timer, method_call.subchannel, EngineTypes::Nv01Timer); + dma_pusher.BindSubchannel(&dma_pusher.channel_state.payload->nv01_timer, method_call.subchannel, EngineTypes::Nv01Timer); break; default: UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id); @@ -60,15 +63,15 @@ void Puller::ProcessBindMethod(const MethodCall& method_call) { } } -void Puller::ProcessFenceActionMethod() { +void Puller::ProcessFenceActionMethod(DmaPusher& dma_pusher) { switch (regs.fence_action.op) { case Puller::FenceOperation::Acquire: // UNIMPLEMENTED_MSG("Channel Scheduling pending."); // WaitFence(regs.fence_action.syncpoint_id, regs.fence_value); - rasterizer->ReleaseFences(); + dma_pusher.rasterizer->ReleaseFences(); break; case Puller::FenceOperation::Increment: - rasterizer->SignalSyncPoint(regs.fence_action.syncpoint_id); + dma_pusher.rasterizer->SignalSyncPoint(regs.fence_action.syncpoint_id); break; default: UNIMPLEMENTED_MSG("Unimplemented operation {}", regs.fence_action.op.Value()); @@ -76,37 +79,35 @@ void Puller::ProcessFenceActionMethod() { } } -void Puller::ProcessSemaphoreTriggerMethod() { +void Puller::ProcessSemaphoreTriggerMethod(DmaPusher& dma_pusher) { const auto semaphoreOperationMask = 0xF; - const auto op = - static_cast(regs.semaphore_trigger & semaphoreOperationMask); + const auto op = GpuSemaphoreOperation(regs.semaphore_trigger & semaphoreOperationMask); if (op == GpuSemaphoreOperation::WriteLong) { const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()}; const u32 payload = regs.semaphore_sequence; - rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload, - VideoCommon::QueryPropertiesFlags::HasTimeout, payload, 0); + dma_pusher.rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload, VideoCommon::QueryPropertiesFlags::HasTimeout, payload, 0); } else { do { - const u32 word{memory_manager.Read(regs.semaphore_address.SemaphoreAddress())}; + const u32 word = dma_pusher.memory_manager.Read(regs.semaphore_address.SemaphoreAddress()); regs.acquire_source = true; regs.acquire_value = regs.semaphore_sequence; if (op == GpuSemaphoreOperation::AcquireEqual) { regs.acquire_active = true; regs.acquire_mode = false; if (word != regs.acquire_value) { - rasterizer->ReleaseFences(); + dma_pusher.rasterizer->ReleaseFences(); continue; } } else if (op == GpuSemaphoreOperation::AcquireGequal) { regs.acquire_active = true; regs.acquire_mode = true; if (word < regs.acquire_value) { - rasterizer->ReleaseFences(); + dma_pusher.rasterizer->ReleaseFences(); continue; } } else if (op == GpuSemaphoreOperation::AcquireMask) { if (word && regs.semaphore_sequence == 0) { - rasterizer->ReleaseFences(); + dma_pusher.rasterizer->ReleaseFences(); continue; } } else { @@ -116,21 +117,20 @@ void Puller::ProcessSemaphoreTriggerMethod() { } } -void Puller::ProcessSemaphoreRelease() { +void Puller::ProcessSemaphoreRelease(DmaPusher& dma_pusher) { const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()}; const u32 payload = regs.semaphore_release; - rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload, - VideoCommon::QueryPropertiesFlags::IsAFence, payload, 0); + dma_pusher.rasterizer->Query(sequence_address, VideoCommon::QueryType::Payload, VideoCommon::QueryPropertiesFlags::IsAFence, payload, 0); } -void Puller::ProcessSemaphoreAcquire() { - u32 word = memory_manager.Read(regs.semaphore_address.SemaphoreAddress()); +void Puller::ProcessSemaphoreAcquire(DmaPusher& dma_pusher) { + u32 word = dma_pusher.memory_manager.Read(regs.semaphore_address.SemaphoreAddress()); const auto value = regs.semaphore_acquire; while (word != value) { regs.acquire_active = true; regs.acquire_value = value; - rasterizer->ReleaseFences(); - word = memory_manager.Read(regs.semaphore_address.SemaphoreAddress()); + dma_pusher.rasterizer->ReleaseFences(); + word = dma_pusher.memory_manager.Read(regs.semaphore_address.SemaphoreAddress()); // TODO(kemathe73) figure out how to do the acquire_timeout regs.acquire_mode = false; regs.acquire_source = false; @@ -138,13 +138,13 @@ void Puller::ProcessSemaphoreAcquire() { } /// Calls a GPU puller method. -void Puller::CallPullerMethod(const MethodCall& method_call) { +void Puller::CallPullerMethod(DmaPusher& dma_pusher, const MethodCall& method_call) { regs.reg_array[method_call.method] = method_call.argument; const auto method = static_cast(method_call.method); switch (method) { case BufferMethods::BindObject: { - ProcessBindMethod(method_call); + ProcessBindMethod(dma_pusher, method_call); break; } case BufferMethods::Nop: @@ -155,16 +155,16 @@ void Puller::CallPullerMethod(const MethodCall& method_call) { case BufferMethods::WrcacheFlush: break; case BufferMethods::RefCnt: - rasterizer->SignalReference(); + dma_pusher.rasterizer->SignalReference(); break; case BufferMethods::SyncpointOperation: - ProcessFenceActionMethod(); + ProcessFenceActionMethod(dma_pusher); break; case BufferMethods::WaitForIdle: - rasterizer->WaitForIdle(); + dma_pusher.rasterizer->WaitForIdle(); break; case BufferMethods::SemaphoreOperation: { - ProcessSemaphoreTriggerMethod(); + ProcessSemaphoreTriggerMethod(dma_pusher); break; } case BufferMethods::NonStallInterrupt: { @@ -177,7 +177,7 @@ void Puller::CallPullerMethod(const MethodCall& method_call) { } case BufferMethods::MemOpB: { // Implement this better. - rasterizer->InvalidateGPUCache(); + dma_pusher.rasterizer->InvalidateGPUCache(); break; } case BufferMethods::MemOpC: @@ -186,11 +186,11 @@ void Puller::CallPullerMethod(const MethodCall& method_call) { break; } case BufferMethods::SemaphoreAcquire: { - ProcessSemaphoreAcquire(); + ProcessSemaphoreAcquire(dma_pusher); break; } case BufferMethods::SemaphoreRelease: { - ProcessSemaphoreRelease(); + ProcessSemaphoreRelease(dma_pusher); break; } case BufferMethods::Yield: { @@ -205,27 +205,26 @@ void Puller::CallPullerMethod(const MethodCall& method_call) { } /// Calls a GPU engine method. -void Puller::CallEngineMethod(const MethodCall& method_call) { +void Puller::CallEngineMethod(DmaPusher& dma_pusher, const MethodCall& method_call) { const EngineID engine = bound_engines[method_call.subchannel]; - switch (engine) { case EngineID::FERMI_TWOD_A: - channel_state.fermi_2d->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); + dma_pusher.channel_state.payload->fermi_2d.CallMethod(dma_pusher.system, method_call.method, method_call.argument, method_call.IsLastCall()); break; case EngineID::MAXWELL_B: - channel_state.maxwell_3d->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); + dma_pusher.channel_state.payload->maxwell_3d.CallMethod(dma_pusher.system, method_call.method, method_call.argument, method_call.IsLastCall()); break; case EngineID::KEPLER_COMPUTE_B: - channel_state.kepler_compute->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); + dma_pusher.channel_state.payload->kepler_compute.CallMethod(dma_pusher.system, method_call.method, method_call.argument, method_call.IsLastCall()); break; case EngineID::MAXWELL_DMA_COPY_A: - channel_state.maxwell_dma->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); + dma_pusher.channel_state.payload->maxwell_dma.CallMethod(dma_pusher.system, method_call.method, method_call.argument, method_call.IsLastCall()); break; case EngineID::KEPLER_INLINE_TO_MEMORY_B: - channel_state.kepler_memory->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); + dma_pusher.channel_state.payload->kepler_memory.CallMethod(dma_pusher.system, method_call.method, method_call.argument, method_call.IsLastCall()); break; case EngineID::NV01_TIMER: - channel_state.nv01_timer->CallMethod(method_call.method, method_call.argument, method_call.IsLastCall()); + dma_pusher.channel_state.payload->nv01_timer.CallMethod(dma_pusher.system, method_call.method, method_call.argument, method_call.IsLastCall()); break; default: UNIMPLEMENTED_MSG("Unimplemented engine"); @@ -234,28 +233,26 @@ void Puller::CallEngineMethod(const MethodCall& method_call) { } /// Calls a GPU engine multivalue method. -void Puller::CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount, - u32 methods_pending) { +void Puller::CallEngineMultiMethod(DmaPusher& dma_pusher, u32 method, u32 subchannel, const u32* base_start, u32 amount, u32 methods_pending) { const EngineID engine = bound_engines[subchannel]; - switch (engine) { case EngineID::FERMI_TWOD_A: - channel_state.fermi_2d->CallMultiMethod(method, base_start, amount, methods_pending); + dma_pusher.channel_state.payload->fermi_2d.CallMultiMethod(dma_pusher.system, method, base_start, amount, methods_pending); break; case EngineID::MAXWELL_B: - channel_state.maxwell_3d->CallMultiMethod(method, base_start, amount, methods_pending); + dma_pusher.channel_state.payload->maxwell_3d.CallMultiMethod(dma_pusher.system, method, base_start, amount, methods_pending); break; case EngineID::KEPLER_COMPUTE_B: - channel_state.kepler_compute->CallMultiMethod(method, base_start, amount, methods_pending); + dma_pusher.channel_state.payload->kepler_compute.CallMultiMethod(dma_pusher.system, method, base_start, amount, methods_pending); break; case EngineID::MAXWELL_DMA_COPY_A: - channel_state.maxwell_dma->CallMultiMethod(method, base_start, amount, methods_pending); + dma_pusher.channel_state.payload->maxwell_dma.CallMultiMethod(dma_pusher.system, method, base_start, amount, methods_pending); break; case EngineID::KEPLER_INLINE_TO_MEMORY_B: - channel_state.kepler_memory->CallMultiMethod(method, base_start, amount, methods_pending); + dma_pusher.channel_state.payload->kepler_memory.CallMultiMethod(dma_pusher.system, method, base_start, amount, methods_pending); break; case EngineID::NV01_TIMER: - channel_state.nv01_timer->CallMultiMethod(method, base_start, amount, methods_pending); + dma_pusher.channel_state.payload->nv01_timer.CallMultiMethod(dma_pusher.system, method, base_start, amount, methods_pending); break; default: UNIMPLEMENTED_MSG("Unimplemented engine"); @@ -264,31 +261,26 @@ void Puller::CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_s } /// Calls a GPU method. -void Puller::CallMethod(const MethodCall& method_call) { - LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method_call.method, - method_call.subchannel); - +void Puller::CallMethod(DmaPusher& dma_pusher, const MethodCall& method_call) { + LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method_call.method, method_call.subchannel); ASSERT(method_call.subchannel < bound_engines.size()); - if (ExecuteMethodOnEngine(method_call.method)) { - CallEngineMethod(method_call); + if (ExecuteMethodOnEngine(dma_pusher, method_call.method)) { + CallEngineMethod(dma_pusher, method_call); } else { - CallPullerMethod(method_call); + CallPullerMethod(dma_pusher, method_call); } } /// Calls a GPU multivalue method. -void Puller::CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount, - u32 methods_pending) { +void Puller::CallMultiMethod(DmaPusher& dma_pusher, u32 method, u32 subchannel, const u32* base_start, u32 amount, u32 methods_pending) { LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method, subchannel); - ASSERT(subchannel < bound_engines.size()); - - if (ExecuteMethodOnEngine(method)) { - CallEngineMultiMethod(method, subchannel, base_start, amount, methods_pending); + if (ExecuteMethodOnEngine(dma_pusher, method)) { + CallEngineMultiMethod(dma_pusher, method, subchannel, base_start, amount, methods_pending); } else { for (u32 i = 0; i < amount; i++) { - CallPullerMethod(MethodCall{ + CallPullerMethod(dma_pusher, MethodCall{ method, base_start[i], subchannel, @@ -298,13 +290,9 @@ void Puller::CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, } } -void Puller::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) { - rasterizer = rasterizer_; -} - /// Determines where the method should be executed. -[[nodiscard]] bool Puller::ExecuteMethodOnEngine(u32 method) { - const auto buffer_method = static_cast(method); +[[nodiscard]] bool Puller::ExecuteMethodOnEngine(DmaPusher& dma_pusher, u32 method) { + const auto buffer_method = BufferMethods(method); return buffer_method >= BufferMethods::NonPullerMethods; } diff --git a/src/video_core/engines/puller.h b/src/video_core/engines/puller.h index fe5102e3ed..e8994f5640 100644 --- a/src/video_core/engines/puller.h +++ b/src/video_core/engines/puller.h @@ -70,32 +70,13 @@ public: BitField<8, 24, u32> syncpoint_id; }; - explicit Puller(GPU& gpu_, MemoryManager& memory_manager_, DmaPusher& dma_pusher, - Control::ChannelState& channel_state); - ~Puller(); - - void CallMethod(const MethodCall& method_call); - - void CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount, - u32 methods_pending); - - void BindRasterizer(VideoCore::RasterizerInterface* rasterizer); - - void CallPullerMethod(const MethodCall& method_call); - - void CallEngineMethod(const MethodCall& method_call); - - void CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount, - u32 methods_pending); - + void CallMethod(DmaPusher& dma_pusher, const MethodCall& method_call); + void CallMultiMethod(DmaPusher& dma_pusher, u32 method, u32 subchannel, const u32* base_start, u32 amount, u32 methods_pending); + void BindRasterizer(DmaPusher& dma_pusher, VideoCore::RasterizerInterface* rasterizer); + void CallPullerMethod(DmaPusher& dma_pusher, const MethodCall& method_call); + void CallEngineMethod(DmaPusher& dma_pusher, const MethodCall& method_call); + void CallEngineMultiMethod(DmaPusher& dma_pusher, u32 method, u32 subchannel, const u32* base_start, u32 amount, u32 methods_pending); private: - Tegra::GPU& gpu; - - MemoryManager& memory_manager; - DmaPusher& dma_pusher; - Control::ChannelState& channel_state; - VideoCore::RasterizerInterface* rasterizer = nullptr; - static constexpr std::size_t NUM_REGS = 0x800; struct Regs { static constexpr size_t NUM_REGS = 0x40; @@ -139,12 +120,12 @@ private: }; } regs{}; - void ProcessBindMethod(const MethodCall& method_call); - void ProcessFenceActionMethod(); - void ProcessSemaphoreAcquire(); - void ProcessSemaphoreRelease(); - void ProcessSemaphoreTriggerMethod(); - [[nodiscard]] bool ExecuteMethodOnEngine(u32 method); + void ProcessBindMethod(DmaPusher& dma_pusher, const MethodCall& method_call); + void ProcessFenceActionMethod(DmaPusher& dma_pusher); + void ProcessSemaphoreAcquire(DmaPusher& dma_pusher); + void ProcessSemaphoreRelease(DmaPusher& dma_pusher); + void ProcessSemaphoreTriggerMethod(DmaPusher& dma_pusher); + [[nodiscard]] bool ExecuteMethodOnEngine(DmaPusher& dma_pusher, u32 method); /// Mapping of command subchannels to their bound engine ids std::array bound_engines{}; @@ -157,8 +138,7 @@ private: }; #define ASSERT_REG_POSITION(field_name, position) \ - static_assert(offsetof(Regs, field_name) == position * 4, \ - "Field " #field_name " has invalid position") + static_assert(offsetof(Regs, field_name) == position * 4, "Field " #field_name " has invalid position") ASSERT_REG_POSITION(semaphore_address, 0x4); ASSERT_REG_POSITION(semaphore_sequence, 0x6); diff --git a/src/video_core/engines/sw_blitter/converter.cpp b/src/video_core/engines/sw_blitter/converter.cpp index 6682c96444..3aeaa11d10 100644 --- a/src/video_core/engines/sw_blitter/converter.cpp +++ b/src/video_core/engines/sw_blitter/converter.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include #include "common/assert.h" @@ -924,7 +924,7 @@ public: }; struct ConverterFactory::ConverterFactoryImpl { - ankerl::unordered_dense::map> converters_cache; + ::Common::unordered_map> converters_cache; }; ConverterFactory::ConverterFactory() { diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h index e4c4329e81..776c01e3fa 100644 --- a/src/video_core/fence_manager.h +++ b/src/video_core/fence_manager.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -72,11 +72,11 @@ public: } void SignalFence(std::function&& func) { + const bool delay_fence = Settings::IsGPUFenceBehaviorDefault() ? Settings::IsGPULevelHigh() : Settings::IsGPUFenceBehaviorBalanced() || Settings::IsGPUFenceBehaviorAccurate(); + const bool should_flush = ShouldFlush(); if constexpr (!can_async_check) { TryReleasePendingFences(); } - const bool should_flush = ShouldFlush(); - const bool delay_fence = Settings::IsGPULevelHigh() || (Settings::IsGPULevelMedium() && should_flush); CommitAsyncFlushes(); TFence new_fence = CreateFence(!should_flush); if constexpr (can_async_check) { @@ -195,6 +195,7 @@ private: void ReleaseThreadFunc(std::stop_token stop_token) { Common::SetCurrentThreadName("GPUFencingThread"); Common::SetCurrentThreadPriority(Common::ThreadPriority::High); + Common::SetCurrentThreadToPerformanceCores(); TFence current_fence; std::deque> current_operations; diff --git a/src/video_core/frame_gen/lossless_dll.cpp b/src/video_core/frame_gen/lossless_dll.cpp new file mode 100644 index 0000000000..6be1e5530f --- /dev/null +++ b/src/video_core/frame_gen/lossless_dll.cpp @@ -0,0 +1,524 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include + +#include "common/cityhash.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/fs_paths.h" +#include "common/fs/path_util.h" +#include "video_core/frame_gen/lossless_dll.h" +#include "video_core/frame_gen/lsfg_translate.h" + +namespace VideoCore::FrameGen { + +namespace { + +constexpr u16 DOS_MAGIC = 0x5A4D; +constexpr u32 PE_SIGNATURE = 0x00004550; +constexpr u16 PE32_MAGIC = 0x010B; +constexpr u16 PE32_PLUS_MAGIC = 0x020B; + +constexpr size_t DOS_LFANEW_OFFSET = 0x3C; +constexpr size_t COFF_HEADER_SIZE = 20; +constexpr size_t OPTIONAL_HEADER_SIZE_OFFSET = 16; +constexpr size_t SECTION_HEADER_SIZE = 40; +constexpr size_t DATA_DIRECTORY_ENTRY_SIZE = 8; +constexpr size_t DATA_DIRECTORY_OFFSET_PE32 = 96; +constexpr size_t DATA_DIRECTORY_OFFSET_PE32_PLUS = 112; +constexpr size_t RESOURCE_DATA_DIRECTORY_INDEX = 2; + +constexpr size_t RESOURCE_DIRECTORY_SIZE = 16; +constexpr size_t RESOURCE_NAMED_COUNT_OFFSET = 12; +constexpr size_t RESOURCE_ID_COUNT_OFFSET = 14; +constexpr size_t RESOURCE_ENTRY_SIZE = 8; +constexpr u32 RESOURCE_SUBDIRECTORY_FLAG = 0x80000000; +constexpr u32 RESOURCE_TYPE_RCDATA = 10; + +constexpr u32 MIPMAPS_SHADER_ID = 255; +constexpr u32 GENERATE_SHADER_ID = 256; +constexpr u32 PERFORMANCE_SHADER_ID_FIRST = 280; +constexpr u32 PERFORMANCE_SHADER_ID_LAST = 302; + +constexpr u32 CACHE_MAGIC = 0x4746534C; +constexpr u32 CACHE_VERSION = 3; + +struct CacheHeader { + u32 magic; + u32 version; + u64 source_size; + u64 source_hash; + u32 module_count; +}; + +struct Section { + u32 virtual_address; + u32 virtual_size; + u32 raw_address; + u32 raw_size; +}; + +struct ResourceEntry { + u32 id; + u32 offset; + bool is_directory; + bool is_named; +}; + +class ImageReader { +public: + explicit ImageReader(std::span image_) : image{image_} {} + + template + [[nodiscard]] bool Read(size_t offset, T& out_value) const { + if (offset > image.size() || image.size() - offset < sizeof(T)) { + return false; + } + std::memcpy(&out_value, image.data() + offset, sizeof(T)); + return true; + } + + [[nodiscard]] bool Slice(size_t offset, size_t size, std::span& out_slice) const { + if (offset > image.size() || image.size() - offset < size) { + return false; + } + out_slice = image.subspan(offset, size); + return true; + } + +private: + std::span image; +}; + +[[nodiscard]] std::optional FindPeHeader(const ImageReader& reader) { + u16 dos_magic{}; + if (!reader.Read(0, dos_magic) || dos_magic != DOS_MAGIC) { + return std::nullopt; + } + + u32 pe_offset{}; + if (!reader.Read(DOS_LFANEW_OFFSET, pe_offset)) { + return std::nullopt; + } + + u32 pe_signature{}; + if (!reader.Read(pe_offset, pe_signature) || pe_signature != PE_SIGNATURE) { + return std::nullopt; + } + + return static_cast(pe_offset); +} + +[[nodiscard]] std::optional FindDataDirectory(const ImageReader& reader, + size_t optional_header_offset) { + u16 optional_magic{}; + if (!reader.Read(optional_header_offset, optional_magic)) { + return std::nullopt; + } + + switch (optional_magic) { + case PE32_MAGIC: + return optional_header_offset + DATA_DIRECTORY_OFFSET_PE32; + case PE32_PLUS_MAGIC: + return optional_header_offset + DATA_DIRECTORY_OFFSET_PE32_PLUS; + default: + return std::nullopt; + } +} + +[[nodiscard]] bool ReadSections(const ImageReader& reader, size_t pe_offset, + std::vector

& out_sections) { + u16 section_count{}; + u16 optional_header_size{}; + if (!reader.Read(pe_offset + 4 + 2, section_count) || + !reader.Read(pe_offset + 4 + OPTIONAL_HEADER_SIZE_OFFSET, optional_header_size)) { + return false; + } + + const size_t table_offset = pe_offset + 4 + COFF_HEADER_SIZE + optional_header_size; + out_sections.reserve(section_count); + for (size_t i = 0; i < section_count; ++i) { + const size_t offset = table_offset + i * SECTION_HEADER_SIZE; + Section section{}; + if (!reader.Read(offset + 8, section.virtual_size) || + !reader.Read(offset + 12, section.virtual_address) || + !reader.Read(offset + 16, section.raw_size) || + !reader.Read(offset + 20, section.raw_address)) { + return false; + } + out_sections.push_back(section); + } + return true; +} + +[[nodiscard]] std::optional RvaToFileOffset(std::span sections, u32 rva) { + for (const Section& section : sections) { + const u32 span = std::max(section.virtual_size, section.raw_size); + if (span == 0 || rva < section.virtual_address) { + continue; + } + const u32 relative = rva - section.virtual_address; + if (relative < span) { + return static_cast(section.raw_address) + relative; + } + } + return std::nullopt; +} + +[[nodiscard]] bool ReadResourceEntries(const ImageReader& reader, size_t directory_offset, + std::vector& out_entries) { + u16 named_count{}; + u16 id_count{}; + if (!reader.Read(directory_offset + RESOURCE_NAMED_COUNT_OFFSET, named_count) || + !reader.Read(directory_offset + RESOURCE_ID_COUNT_OFFSET, id_count)) { + return false; + } + + const size_t total = size_t{named_count} + size_t{id_count}; + out_entries.clear(); + out_entries.reserve(total); + for (size_t i = 0; i < total; ++i) { + const size_t offset = directory_offset + RESOURCE_DIRECTORY_SIZE + i * RESOURCE_ENTRY_SIZE; + u32 name{}; + u32 data{}; + if (!reader.Read(offset, name) || !reader.Read(offset + 4, data)) { + return false; + } + out_entries.push_back(ResourceEntry{ + .id = name & ~RESOURCE_SUBDIRECTORY_FLAG, + .offset = data & ~RESOURCE_SUBDIRECTORY_FLAG, + .is_directory = (data & RESOURCE_SUBDIRECTORY_FLAG) != 0, + .is_named = (name & RESOURCE_SUBDIRECTORY_FLAG) != 0, + }); + } + return true; +} + +[[nodiscard]] bool ReadResourceLeaf(const ImageReader& reader, std::span sections, + size_t leaf_offset, std::span& out_data) { + u32 data_rva{}; + u32 data_size{}; + if (!reader.Read(leaf_offset, data_rva) || !reader.Read(leaf_offset + 4, data_size) || + data_size == 0) { + return false; + } + + const std::optional data_offset = RvaToFileOffset(sections, data_rva); + if (!data_offset) { + return false; + } + return reader.Slice(*data_offset, data_size, out_data); +} + +using ResourceSpans = std::map>; + +[[nodiscard]] bool CollectRcData(const ImageReader& reader, std::span sections, + size_t resource_base, ResourceSpans& out_resources) { + std::vector type_entries; + if (!ReadResourceEntries(reader, resource_base, type_entries)) { + return false; + } + + for (const ResourceEntry& type_entry : type_entries) { + if (type_entry.is_named || type_entry.id != RESOURCE_TYPE_RCDATA || + !type_entry.is_directory) { + continue; + } + + std::vector name_entries; + if (!ReadResourceEntries(reader, resource_base + type_entry.offset, name_entries)) { + return false; + } + + for (const ResourceEntry& name_entry : name_entries) { + if (name_entry.is_named || !name_entry.is_directory) { + continue; + } + + std::vector language_entries; + if (!ReadResourceEntries(reader, resource_base + name_entry.offset, language_entries)) { + return false; + } + + for (const ResourceEntry& language_entry : language_entries) { + if (language_entry.is_directory) { + continue; + } + std::span data; + if (!ReadResourceLeaf(reader, sections, resource_base + language_entry.offset, + data)) { + continue; + } + out_resources.insert_or_assign(name_entry.id, data); + break; + } + } + } + + return true; +} + +[[nodiscard]] std::vector PerformanceShaderIds() { + std::vector ids{MIPMAPS_SHADER_ID, GENERATE_SHADER_ID}; + for (u32 id = PERFORMANCE_SHADER_ID_FIRST; id <= PERFORMANCE_SHADER_ID_LAST; ++id) { + ids.push_back(id); + } + return ids; +} + +template +[[nodiscard]] bool HasPerformanceShaders(const Map& resources) { + const std::vector ids = PerformanceShaderIds(); + return std::ranges::all_of(ids, [&](u32 id) { return resources.contains(id); }); +} + +template +[[nodiscard]] bool HasNativeShaders(const Map& resources) { + return std::ranges::all_of(PerformanceShaderIds(), [&](u32 id) { + const auto hit = resources.find(id + PerformanceShader::NATIVE_FP16_OFFSET); + return hit != resources.end() && IsSpirvModule(hit->second); + }); +} + +[[nodiscard]] LosslessStatus TranslateAll(const ResourceSpans& resources, + ShaderModules& out_modules) { + out_modules.clear(); + for (const u32 id : PerformanceShaderIds()) { + const auto hit = resources.find(id + PerformanceShader::NATIVE_FP16_OFFSET); + if (hit == resources.end()) { + return LosslessStatus::MissingShaders; + } + std::vector adopted = AdoptSpirvModule(hit->second); + if (adopted.empty()) { + return LosslessStatus::TranslationFailed; + } + out_modules.emplace(id, std::move(adopted)); + } + return LosslessStatus::Ok; +} + +[[nodiscard]] bool WriteShaderCache(const std::filesystem::path& path, const CacheHeader& header, + const ShaderModules& modules) { + Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write, + Common::FS::FileType::BinaryFile}; + if (!file.IsOpen() || file.Write(header) != 1) { + return false; + } + + for (const auto& [id, words] : modules) { + const u32 word_count = static_cast(words.size()); + if (file.Write(id) != 1 || file.Write(word_count) != 1 || + file.Write(words) != words.size()) { + return false; + } + } + return file.Flush(); +} + +[[nodiscard]] bool ReadShaderCache(const std::filesystem::path& path, u64 source_size, + u64 source_hash, ShaderModules& out_modules) { + if (!Common::FS::Exists(path)) { + return false; + } + + Common::FS::IOFile file{path, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; + CacheHeader header{}; + if (!file.IsOpen() || file.Read(header) != 1) { + return false; + } + if (header.magic != CACHE_MAGIC || header.version != CACHE_VERSION || + header.source_size != source_size || header.source_hash != source_hash) { + return false; + } + + out_modules.clear(); + for (u32 i = 0; i < header.module_count; ++i) { + u32 id{}; + u32 word_count{}; + if (file.Read(id) != 1 || file.Read(word_count) != 1 || word_count == 0) { + return false; + } + + std::vector words(word_count); + if (file.Read(words) != words.size()) { + return false; + } + out_modules.emplace(id, std::move(words)); + } + + return HasPerformanceShaders(out_modules); +} + +[[nodiscard]] LosslessStatus ReadImageFile(const std::filesystem::path& path, + std::vector& out_image) { + if (!Common::FS::Exists(path)) { + return LosslessStatus::NotInstalled; + } + + Common::FS::IOFile file{path, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; + if (!file.IsOpen()) { + return LosslessStatus::UnreadableFile; + } + + out_image.resize(static_cast(file.GetSize())); + if (out_image.empty() || file.Read(out_image) != out_image.size()) { + return LosslessStatus::UnreadableFile; + } + return LosslessStatus::Ok; +} + +[[nodiscard]] LosslessStatus ParseShaderSpans(std::span image, + ResourceSpans& out_resources) { + const ImageReader reader{image}; + const std::optional pe_offset = FindPeHeader(reader); + if (!pe_offset) { + return LosslessStatus::NotPortableExecutable; + } + + const std::optional data_directory = + FindDataDirectory(reader, *pe_offset + 4 + COFF_HEADER_SIZE); + if (!data_directory) { + return LosslessStatus::NotPortableExecutable; + } + + std::vector
sections; + if (!ReadSections(reader, *pe_offset, sections)) { + return LosslessStatus::NotPortableExecutable; + } + + u32 resource_rva{}; + if (!reader.Read(*data_directory + RESOURCE_DATA_DIRECTORY_INDEX * DATA_DIRECTORY_ENTRY_SIZE, + resource_rva) || + resource_rva == 0) { + return LosslessStatus::MissingShaders; + } + + const std::optional resource_base = RvaToFileOffset(sections, resource_rva); + if (!resource_base) { + return LosslessStatus::NotPortableExecutable; + } + + out_resources.clear(); + if (!CollectRcData(reader, sections, *resource_base, out_resources)) { + return LosslessStatus::MissingShaders; + } + + if (!HasNativeShaders(out_resources)) { + return LosslessStatus::MissingShaders; + } + return LosslessStatus::Ok; +} + +} // Anonymous namespace + +std::filesystem::path GetLosslessDllPath() { + return Common::FS::GetEdenPath(Common::FS::EdenPath::LosslessDir) / LOSSLESS_DLL_FILE; +} + +std::filesystem::path GetShaderCachePath() { + return Common::FS::GetEdenPath(Common::FS::EdenPath::LosslessDir) / LOSSLESS_CACHE_FILE; +} + +LosslessStatus ReadShaderResources(const std::filesystem::path& path, + ShaderResources& out_resources) { + std::vector image; + const LosslessStatus read_status = ReadImageFile(path, image); + if (read_status != LosslessStatus::Ok) { + return read_status; + } + + ResourceSpans spans; + const LosslessStatus parse_status = ParseShaderSpans(image, spans); + if (parse_status != LosslessStatus::Ok) { + return parse_status; + } + + out_resources.clear(); + for (const auto& [id, data] : spans) { + out_resources.emplace(id, std::vector{data.begin(), data.end()}); + } + return LosslessStatus::Ok; +} + +LosslessStatus ValidateLosslessDll(const std::filesystem::path& path) { + std::vector image; + const LosslessStatus read_status = ReadImageFile(path, image); + if (read_status != LosslessStatus::Ok) { + return read_status; + } + + ResourceSpans spans; + return ParseShaderSpans(image, spans); +} + +LosslessStatus GetInstalledLosslessStatus() { + return ValidateLosslessDll(GetLosslessDllPath()); +} + +LosslessStatus LoadShaderModules(ShaderModules& out_modules) { + std::vector image; + const LosslessStatus read_status = ReadImageFile(GetLosslessDllPath(), image); + if (read_status != LosslessStatus::Ok) { + return read_status; + } + + const u64 source_size = image.size(); + const u64 source_hash = + Common::CityHash64(reinterpret_cast(image.data()), image.size()); + const std::filesystem::path cache_path = GetShaderCachePath(); + + ResourceSpans spans; + const LosslessStatus parse_status = ParseShaderSpans(image, spans); + if (parse_status != LosslessStatus::Ok) { + return parse_status; + } + + if (ReadShaderCache(cache_path, source_size, source_hash, out_modules)) { + return LosslessStatus::Ok; + } + + const LosslessStatus translate_status = TranslateAll(spans, out_modules); + if (translate_status != LosslessStatus::Ok) { + return translate_status; + } + + const CacheHeader header{ + .magic = CACHE_MAGIC, + .version = CACHE_VERSION, + .source_size = source_size, + .source_hash = source_hash, + .module_count = static_cast(out_modules.size()), + }; + if (!WriteShaderCache(cache_path, header, out_modules)) { + void(Common::FS::RemoveFile(cache_path)); + return LosslessStatus::CacheUnusable; + } + + return LosslessStatus::Ok; +} + +LosslessStatus BuildShaderCache() { + ShaderModules modules; + return LoadShaderModules(modules); +} + +bool RemoveInstalledLosslessDll() { + const std::filesystem::path cache_path = GetShaderCachePath(); + if (Common::FS::Exists(cache_path)) { + void(Common::FS::RemoveFile(cache_path)); + } + + const std::filesystem::path path = GetLosslessDllPath(); + if (!Common::FS::Exists(path)) { + return true; + } + return Common::FS::RemoveFile(path); +} + +} // namespace VideoCore::FrameGen diff --git a/src/video_core/frame_gen/lossless_dll.h b/src/video_core/frame_gen/lossless_dll.h new file mode 100644 index 0000000000..53e4974521 --- /dev/null +++ b/src/video_core/frame_gen/lossless_dll.h @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "common/common_types.h" + +namespace VideoCore::FrameGen { + +enum class LosslessStatus : u32 { + Ok, + NotInstalled, + UnreadableFile, + NotPortableExecutable, + MissingShaders, + TranslationFailed, + CacheUnusable, +}; + +using ShaderResources = std::map>; +using ShaderModules = std::map>; + +namespace PerformanceShader { +constexpr u32 MIPMAPS = 255; +constexpr u32 GENERATE = 256; +constexpr std::array ALPHA{290, 291, 292, 293}; +constexpr std::array BETA{298, 299, 300, 301, 302}; +constexpr std::array GAMMA{280, 282, 283, 284, 285}; +constexpr std::array DELTA{280, 286, 287, 288, 289, 281, 294, 295, 296, 297}; + +constexpr u32 NATIVE_FP16_OFFSET = 49; +} // namespace PerformanceShader + +[[nodiscard]] std::filesystem::path GetLosslessDllPath(); + +[[nodiscard]] std::filesystem::path GetShaderCachePath(); + +[[nodiscard]] LosslessStatus ReadShaderResources(const std::filesystem::path& path, + ShaderResources& out_resources); + +[[nodiscard]] LosslessStatus ValidateLosslessDll(const std::filesystem::path& path); + +[[nodiscard]] LosslessStatus GetInstalledLosslessStatus(); + +[[nodiscard]] LosslessStatus BuildShaderCache(); + +[[nodiscard]] LosslessStatus LoadShaderModules(ShaderModules& out_modules); + +bool RemoveInstalledLosslessDll(); + +} // namespace VideoCore::FrameGen diff --git a/src/video_core/frame_gen/lsfg_translate.cpp b/src/video_core/frame_gen/lsfg_translate.cpp new file mode 100644 index 0000000000..4063465d93 --- /dev/null +++ b/src/video_core/frame_gen/lsfg_translate.cpp @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include + +#include "video_core/frame_gen/lsfg_translate.h" + +namespace VideoCore::FrameGen { + +namespace { + +constexpr u32 SPIRV_MAGIC = 0x07230203; +constexpr u32 SPIRV_WORD_COUNT_SHIFT = 16; +constexpr u32 SPIRV_OPCODE_MASK = 0xffff; +constexpr u32 SPIRV_OP_FUNCTION = 54; +constexpr u32 SPIRV_OP_DECORATE = 71; +constexpr u32 SPIRV_DECORATION_BINDING = 33; +constexpr u32 SPIRV_DECORATION_DESCRIPTOR_SET = 34; + +constexpr u32 DECORATION_LITERAL_WORD = 3; +constexpr size_t SPIRV_HEADER_WORDS = 5; + +void RenumberBindingsInOrder(std::vector& words) { + struct Slot { + u32 set; + u32 binding; + size_t literal_offset; + }; + + std::map sets; + std::vector slots; + + size_t offset = SPIRV_HEADER_WORDS; + while (offset + 1 <= words.size()) { + const u32 length = words[offset] >> SPIRV_WORD_COUNT_SHIFT; + const u32 opcode = words[offset] & SPIRV_OPCODE_MASK; + if (length == 0 || offset + length > words.size()) { + return; + } + if (opcode == SPIRV_OP_FUNCTION) { + break; + } + if (opcode == SPIRV_OP_DECORATE && length >= 4) { + if (words[offset + 2] == SPIRV_DECORATION_DESCRIPTOR_SET) { + sets[words[offset + 1]] = words[offset + 3]; + } else if (words[offset + 2] == SPIRV_DECORATION_BINDING) { + slots.push_back(Slot{0, words[offset + 3], offset + DECORATION_LITERAL_WORD}); + } + } + offset += length; + } + + for (Slot& slot : slots) { + const auto hit = sets.find(words[slot.literal_offset - 2]); + slot.set = hit == sets.end() ? 0 : hit->second; + } + + std::ranges::stable_sort(slots, [](const Slot& lhs, const Slot& rhs) { + return std::tie(lhs.set, lhs.binding) < std::tie(rhs.set, rhs.binding); + }); + + for (size_t i = 0; i < slots.size(); ++i) { + words[slots[i].literal_offset] = static_cast(i); + } +} + +} // Anonymous namespace + +bool IsSpirvModule(std::span blob) { + if (blob.size() < SPIRV_HEADER_WORDS * sizeof(u32) || blob.size() % sizeof(u32) != 0) { + return false; + } + u32 magic{}; + std::memcpy(&magic, blob.data(), sizeof(magic)); + return magic == SPIRV_MAGIC; +} + +std::vector AdoptSpirvModule(std::span blob) { + if (!IsSpirvModule(blob)) { + return {}; + } + + std::vector words(blob.size() / sizeof(u32)); + std::memcpy(words.data(), blob.data(), blob.size()); + + RenumberBindingsInOrder(words); + return words; +} + +} // namespace VideoCore::FrameGen diff --git a/src/video_core/frame_gen/lsfg_translate.h b/src/video_core/frame_gen/lsfg_translate.h new file mode 100644 index 0000000000..5d21128c3e --- /dev/null +++ b/src/video_core/frame_gen/lsfg_translate.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +#include "common/common_types.h" + +namespace VideoCore::FrameGen { + +[[nodiscard]] bool IsSpirvModule(std::span blob); + +[[nodiscard]] std::vector AdoptSpirvModule(std::span blob); + +} // namespace VideoCore::FrameGen diff --git a/src/video_core/framebuffer_config.cpp b/src/video_core/framebuffer_config.cpp index e28d41f84c..7145b02c07 100644 --- a/src/video_core/framebuffer_config.cpp +++ b/src/video_core/framebuffer_config.cpp @@ -1,11 +1,35 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 + #include "common/assert.h" #include "video_core/framebuffer_config.h" namespace Tegra { +std::span FilterLayerStack( + std::span layers, Service::Nvnflinger::LayerStackId stack, + std::vector& scratch) { + const u32 bit = Service::Nvnflinger::LayerStackBit(stack); + + if (std::ranges::all_of(layers, + [bit](const auto& layer) { return (layer.layer_stack_mask & bit) != 0; })) + return layers; + + scratch.clear(); + for (const auto& layer : layers) { + if ((layer.layer_stack_mask & bit) != 0) { + scratch.push_back(layer); + } + } + + return scratch; +} + Common::Rectangle NormalizeCrop(const FramebufferConfig& framebuffer, u32 texture_width, u32 texture_height) { f32 left, top, right, bottom; diff --git a/src/video_core/framebuffer_config.h b/src/video_core/framebuffer_config.h index 8b2a49de5e..1fc70583f9 100644 --- a/src/video_core/framebuffer_config.h +++ b/src/video_core/framebuffer_config.h @@ -1,11 +1,18 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include +#include + #include "common/common_types.h" #include "common/math_util.h" #include "core/hle/service/nvnflinger/buffer_transform_flags.h" +#include "core/hle/service/nvnflinger/hwc_layer.h" #include "core/hle/service/nvnflinger/pixel_format.h" #include "core/hle/service/nvnflinger/ui/fence.h" @@ -30,9 +37,17 @@ struct FramebufferConfig { Service::android::BufferTransformFlags transform_flags{}; Common::Rectangle crop_rect{}; BlendMode blending{}; + u32 layer_stack_mask{Service::Nvnflinger::DefaultLayerStackMask}; }; Common::Rectangle NormalizeCrop(const FramebufferConfig& framebuffer, u32 texture_width, u32 texture_height); +/** + * Returns the subset of layers belonging to a stack. + */ +std::span FilterLayerStack(std::span layers, + Service::Nvnflinger::LayerStackId stack, + std::vector& scratch); + } // namespace Tegra diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 5f4054212f..1621b24125 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "common/assert.h" #include "common/settings.h" @@ -39,31 +40,45 @@ namespace Tegra { +namespace { +constexpr u64 GpuClockMultiplier(Settings::GpuClock clock) { + switch (clock) { + case Settings::GpuClock::Boost: + return 256; + case Settings::GpuClock::Overclock: + return 512; + default: + return 1; + } +} +} // Anonymous namespace + struct GPU::Impl { - explicit Impl(GPU& gpu_, Core::System& system_, bool is_async_, bool use_nvdec_) - : gpu{gpu_}, system{system_}, host1x{system.Host1x()}, use_nvdec{use_nvdec_}, - shader_notify{std::make_unique()}, is_async{is_async_}, - gpu_thread{system_, is_async_}, scheduler{std::make_unique(gpu)} {} + explicit Impl(Core::System& system_, bool is_async_, bool use_nvdec_) + : system{system_} + , gpu_thread{system_} + , use_nvdec{use_nvdec_} + , shader_notify() + , is_async{is_async_} + {} ~Impl() = default; std::shared_ptr CreateChannel(s32 channel_id) { auto channel_state = std::make_shared(channel_id); channels.emplace(channel_id, channel_state); - scheduler->DeclareChannel(channel_state); + scheduler.DeclareChannel(channel_state); return channel_state; } void BindChannel(s32 channel_id) { - if (bound_channel == channel_id) { - return; + if (bound_channel != channel_id) { + auto it = channels.find(channel_id); + ASSERT(it != channels.end()); + bound_channel = channel_id; + current_channel = it->second.get(); + renderer->ReadRasterizer()->BindChannel(*current_channel); } - auto it = channels.find(channel_id); - ASSERT(it != channels.end()); - bound_channel = channel_id; - current_channel = it->second.get(); - - rasterizer->BindChannel(*current_channel); } std::shared_ptr AllocateChannel() { @@ -71,13 +86,13 @@ struct GPU::Impl { } void InitChannel(Control::ChannelState& to_init, u64 program_id) { - to_init.Init(system, gpu, program_id); - to_init.BindRasterizer(rasterizer); - rasterizer->InitializeChannel(to_init); + to_init.Init(system, program_id); + to_init.BindRasterizer(renderer->ReadRasterizer()); + renderer->ReadRasterizer()->InitializeChannel(to_init); } void InitAddressSpace(Tegra::MemoryManager& memory_manager) { - memory_manager.BindRasterizer(rasterizer); + memory_manager.BindRasterizer(renderer->ReadRasterizer()); } void ReleaseChannel(Control::ChannelState& to_release) { @@ -87,26 +102,26 @@ struct GPU::Impl { /// Binds a renderer to the GPU. void BindRenderer(std::unique_ptr renderer_) { renderer = std::move(renderer_); - rasterizer = renderer->ReadRasterizer(); - host1x.MemoryManager().BindInterface(rasterizer); - host1x.gmmu_manager.BindRasterizer(rasterizer); + system.Host1x().memory_manager.BindInterface(renderer->ReadRasterizer()); + system.Host1x().gmmu_manager.BindRasterizer(renderer->ReadRasterizer()); } /// Flush all current written commands into the host GPU for execution. void FlushCommands() { - rasterizer->FlushCommands(); + renderer->ReadRasterizer()->FlushCommands(); } /// Synchronizes CPU writes with Host GPU memory. void InvalidateGPUCache() { - std::function callback_writes( - [this](PAddr address, size_t size) { rasterizer->OnCacheInvalidation(address, size); }); + std::function callback_writes([this](PAddr address, size_t size) { + renderer->ReadRasterizer()->OnCacheInvalidation(address, size); + }); system.GatherGPUDirtyMemory(callback_writes); } /// Signal the ending of command list. void OnCommandListEnd() { - rasterizer->ReleaseFences(false); + renderer->ReadRasterizer()->ReleaseFences(false); Settings::UpdateGPUAccuracy(); } @@ -115,7 +130,7 @@ struct GPU::Impl { [[nodiscard]] u64 RequestSyncOperation(Func&& action) { std::unique_lock lck{sync_request_mutex}; const u64 fence = ++last_sync_fence; - sync_requests.emplace_back(action); + sync_requests.emplace_back(std::forward(action)); return fence; } @@ -129,6 +144,12 @@ struct GPU::Impl { sync_request_cv.wait(lck, [this, fence] { return CurrentSyncRequestFence() >= fence; }); } + void WaitForIdle() { + const u64 fence = RequestSyncOperation([] {}); + gpu_thread.TickGPU(is_async); + WaitForSyncOperation(fence); + } + /// Tick pending requests within the GPU. void TickWork() { std::unique_lock lck{sync_request_mutex}; @@ -143,79 +164,9 @@ struct GPU::Impl { } } - /// Returns a reference to the Maxwell3D GPU engine. - [[nodiscard]] Engines::Maxwell3D& Maxwell3D() { - ASSERT(current_channel); - return *current_channel->maxwell_3d; - } - - /// Returns a const reference to the Maxwell3D GPU engine. - [[nodiscard]] const Engines::Maxwell3D& Maxwell3D() const { - ASSERT(current_channel); - return *current_channel->maxwell_3d; - } - - /// Returns a reference to the KeplerCompute GPU engine. - [[nodiscard]] Engines::KeplerCompute& KeplerCompute() { - ASSERT(current_channel); - return *current_channel->kepler_compute; - } - - /// Returns a reference to the KeplerCompute GPU engine. - [[nodiscard]] const Engines::KeplerCompute& KeplerCompute() const { - ASSERT(current_channel); - return *current_channel->kepler_compute; - } - - /// Returns a reference to the GPU DMA pusher. - [[nodiscard]] Tegra::DmaPusher& DmaPusher() { - ASSERT(current_channel); - return *current_channel->dma_pusher; - } - - /// Returns a const reference to the GPU DMA pusher. - [[nodiscard]] const Tegra::DmaPusher& DmaPusher() const { - ASSERT(current_channel); - return *current_channel->dma_pusher; - } - - /// Returns a reference to the underlying renderer. - [[nodiscard]] VideoCore::RendererBase& Renderer() { - return *renderer; - } - - /// Returns a const reference to the underlying renderer. - [[nodiscard]] const VideoCore::RendererBase& Renderer() const { - return *renderer; - } - - /// Returns a reference to the shader notifier. - [[nodiscard]] VideoCore::ShaderNotify& ShaderNotify() { - return *shader_notify; - } - - /// Returns a const reference to the shader notifier. - [[nodiscard]] const VideoCore::ShaderNotify& ShaderNotify() const { - return *shader_notify; - } - [[nodiscard]] u64 GetTicks() const { - u64 gpu_tick = system.CoreTiming().GetGPUTicks(); - Settings::GpuOverclock overclock = Settings::values.fast_gpu_time.GetValue(); - - if (overclock != Settings::GpuOverclock::Normal) { - gpu_tick /= 256 * u64(overclock); - } - - return gpu_tick; - } - - [[nodiscard]] bool IsAsync() const { - return is_async; - } - - [[nodiscard]] bool UseNvdec() const { - return use_nvdec; + const u64 gpu_tick = system.CoreTiming().GetGPUTicks(); + return gpu_tick / GpuClockMultiplier(Settings::values.gpu_clock.GetValue()); } void RendererFrameEndNotify() { @@ -227,10 +178,11 @@ struct GPU::Impl { /// core timing events. void Start() { Settings::UpdateGPUAccuracy(); - gpu_thread.StartThread(*renderer, renderer->Context(), *scheduler); + gpu_thread.StartThread(*renderer, renderer->Context(), scheduler); } void NotifyShutdown() { + gpu_thread.NotifyShutdown(); std::unique_lock lk{sync_mutex}; shutting_down.store(true, std::memory_order::relaxed); sync_cv.notify_all(); @@ -251,48 +203,58 @@ struct GPU::Impl { /// Push GPU command entries to be processed void PushGPUEntries(s32 channel, Tegra::CommandList&& entries) { - gpu_thread.SubmitList(channel, std::move(entries)); + gpu_thread.SubmitList(channel, std::move(entries), is_async); } /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory void FlushRegion(DAddr addr, u64 size) { - gpu_thread.FlushRegion(addr, size); + gpu_thread.FlushRegion(addr, size, is_async); } VideoCore::RasterizerDownloadArea OnCPURead(DAddr addr, u64 size) { - auto raster_area = rasterizer->GetFlushArea(addr, size); + auto raster_area = renderer->ReadRasterizer()->GetFlushArea(addr, size); if (raster_area.preemtive) { return raster_area; } raster_area.preemtive = true; const u64 fence = RequestSyncOperation([this, &raster_area]() { - rasterizer->FlushRegion(raster_area.start_address, - raster_area.end_address - raster_area.start_address); + renderer->ReadRasterizer()->FlushRegion(raster_area.start_address, raster_area.end_address - raster_area.start_address); }); - gpu_thread.TickGPU(); + gpu_thread.TickGPU(is_async); WaitForSyncOperation(fence); return raster_area; } /// Notify rasterizer that any caches of the specified region should be invalidated - void InvalidateRegion(DAddr addr, u64 size) { + void InvalidateRegion(DAddr addr, u64 size, bool preserve_gpu_writes) { + if (preserve_gpu_writes) { + VideoCore::RasterizerInterface* rasterizer = renderer->ReadRasterizer(); + if (!rasterizer->TryCacheInvalidation(addr, size)) { + const u64 fence = RequestSyncOperation([rasterizer, addr, size] { + rasterizer->FlushRegion(addr, size, VideoCommon::CacheType::BufferCache); + rasterizer->OnCacheInvalidation(addr, size); + }); + gpu_thread.TickGPU(is_async); + WaitForSyncOperation(fence); + } + return; + } gpu_thread.InvalidateRegion(addr, size); } bool OnCPUWrite(DAddr addr, u64 size) { - return rasterizer->OnCPUWrite(addr, size); + return renderer->ReadRasterizer()->OnCPUWrite(addr, size); } /// Notify rasterizer that any caches of the specified region should be flushed and invalidated void FlushAndInvalidateRegion(DAddr addr, u64 size) { - gpu_thread.FlushAndInvalidateRegion(addr, size); + gpu_thread.FlushAndInvalidateRegion(addr, size, is_async); } - void RequestComposite(std::vector&& layers, - std::vector&& fences) { - size_t num_fences{fences.size()}; + void RequestComposite(std::vector&& layers, std::vector&& fences) { + const size_t num_fences{fences.size()}; size_t current_request_counter{}; - { + if (num_fences != 0) { std::unique_lock lk(request_swap_mutex); if (free_swap_counters.empty()) { current_request_counter = request_swap_counters.size(); @@ -303,13 +265,15 @@ struct GPU::Impl { free_swap_counters.pop_front(); } } - const auto wait_fence = - RequestSyncOperation([this, current_request_counter, &layers, &fences, num_fences] { - auto& syncpoint_manager = host1x.GetSyncpointManager(); + pending_composite_fence = RequestSyncOperation( + [this, current_request_counter, num_fences, composite_layers = std::move(layers), + composite_fences = std::move(fences)] { if (num_fences == 0) { - renderer->Composite(layers); + renderer->Composite(composite_layers); + return; } - const auto executer = [this, current_request_counter, layers_copy = layers]() { + auto& syncpoint_manager = system.Host1x().GetSyncpointManager(); + const auto executer = [this, current_request_counter, composite_layers]() { { std::unique_lock lk(request_swap_mutex); if (--request_swap_counters[current_request_counter] != 0) { @@ -317,14 +281,26 @@ struct GPU::Impl { } free_swap_counters.push_back(current_request_counter); } - renderer->Composite(layers_copy); + renderer->Composite(composite_layers); }; for (size_t i = 0; i < num_fences; i++) { - syncpoint_manager.RegisterGuestAction(fences[i].id, fences[i].value, executer); + syncpoint_manager.RegisterGuestAction(composite_fences[i].id, + composite_fences[i].value, executer); } }); - gpu_thread.TickGPU(); - WaitForSyncOperation(wait_fence); + gpu_thread.TickGPU(is_async); + } + + void WaitForComposite() { + const u64 fence = pending_composite_fence; + if (fence == 0) { + return; + } + pending_composite_fence = 0; + if (shutting_down.load(std::memory_order_relaxed)) { + return; + } + WaitForSyncOperation(fence); } std::vector GetAppletCaptureBuffer() { @@ -332,23 +308,24 @@ struct GPU::Impl { const auto wait_fence = RequestSyncOperation([&] { out = renderer->GetAppletCaptureBuffer(); }); - gpu_thread.TickGPU(); + gpu_thread.TickGPU(is_async); WaitForSyncOperation(wait_fence); return out; } - GPU& gpu; Core::System& system; - Host1x::Host1x& host1x; + + // Destruction of thread must be done before all (non trivial) + // previous members has been destroyed + VideoCommon::GPUThread::ThreadManager gpu_thread; std::unique_ptr renderer; - VideoCore::RasterizerInterface* rasterizer = nullptr; const bool use_nvdec; s32 new_channel_id{1}; /// Shader build notifier - std::unique_ptr shader_notify; + VideoCore::ShaderNotify shader_notify; /// When true, we are about to shut down emulation session, so terminate outstanding tasks std::atomic_bool shutting_down{}; @@ -369,21 +346,22 @@ struct GPU::Impl { const bool is_async; - VideoCommon::GPUThread::ThreadManager gpu_thread; std::unique_ptr cpu_context; - std::unique_ptr scheduler; - ankerl::unordered_dense::map> channels; + Tegra::Control::Scheduler scheduler; + ::Common::unordered_map> channels; Tegra::Control::ChannelState* current_channel; s32 bound_channel{-1}; std::deque free_swap_counters; std::deque request_swap_counters; std::mutex request_swap_mutex; + u64 pending_composite_fence{}; }; GPU::GPU(Core::System& system, bool is_async, bool use_nvdec) - : impl{std::make_unique(*this, system, is_async, use_nvdec)} {} + : impl{std::make_unique(system, is_async, use_nvdec)} +{} GPU::~GPU() = default; @@ -424,8 +402,9 @@ void GPU::OnCommandListEnd() { } u64 GPU::RequestFlush(DAddr addr, std::size_t size) { - return impl->RequestSyncOperation( - [this, addr, size]() { impl->rasterizer->FlushRegion(addr, size); }); + return impl->RequestSyncOperation([this, addr, size]() { + impl->renderer->ReadRasterizer()->FlushRegion(addr, size); + }); } u64 GPU::CurrentSyncRequestFence() const { @@ -442,52 +421,52 @@ void GPU::TickWork() { /// Gets a mutable reference to the Host1x interface Host1x::Host1x& GPU::Host1x() { - return impl->host1x; + return impl->system.Host1x(); } /// Gets an immutable reference to the Host1x interface. const Host1x::Host1x& GPU::Host1x() const { - return impl->host1x; + return impl->system.Host1x(); } Engines::Maxwell3D& GPU::Maxwell3D() { - return impl->Maxwell3D(); + return impl->current_channel->payload->maxwell_3d; } const Engines::Maxwell3D& GPU::Maxwell3D() const { - return impl->Maxwell3D(); + return impl->current_channel->payload->maxwell_3d; } Engines::KeplerCompute& GPU::KeplerCompute() { - return impl->KeplerCompute(); + return impl->current_channel->payload->kepler_compute; } const Engines::KeplerCompute& GPU::KeplerCompute() const { - return impl->KeplerCompute(); + return impl->current_channel->payload->kepler_compute; } Tegra::DmaPusher& GPU::DmaPusher() { - return impl->DmaPusher(); + return impl->current_channel->payload->dma_pusher; } const Tegra::DmaPusher& GPU::DmaPusher() const { - return impl->DmaPusher(); + return impl->current_channel->payload->dma_pusher; } VideoCore::RendererBase& GPU::Renderer() { - return impl->Renderer(); + return *impl->renderer; } const VideoCore::RendererBase& GPU::Renderer() const { - return impl->Renderer(); + return *impl->renderer; } VideoCore::ShaderNotify& GPU::ShaderNotify() { - return impl->ShaderNotify(); + return impl->shader_notify; } const VideoCore::ShaderNotify& GPU::ShaderNotify() const { - return impl->ShaderNotify(); + return impl->shader_notify; } void GPU::RequestComposite(std::vector&& layers, @@ -495,6 +474,10 @@ void GPU::RequestComposite(std::vector&& layers, impl->RequestComposite(std::move(layers), std::move(fences)); } +void GPU::WaitForComposite() { + impl->WaitForComposite(); +} + std::vector GPU::GetAppletCaptureBuffer() { return impl->GetAppletCaptureBuffer(); } @@ -504,11 +487,11 @@ u64 GPU::GetTicks() const { } bool GPU::IsAsync() const { - return impl->IsAsync(); + return impl->is_async; } bool GPU::UseNvdec() const { - return impl->UseNvdec(); + return impl->use_nvdec; } void GPU::RendererFrameEndNotify() { @@ -523,6 +506,10 @@ void GPU::NotifyShutdown() { impl->NotifyShutdown(); } +void GPU::WaitForIdle() { + impl->WaitForIdle(); +} + void GPU::ObtainContext() { impl->ObtainContext(); } @@ -543,8 +530,8 @@ void GPU::FlushRegion(DAddr addr, u64 size) { impl->FlushRegion(addr, size); } -void GPU::InvalidateRegion(DAddr addr, u64 size) { - impl->InvalidateRegion(addr, size); +void GPU::InvalidateRegion(DAddr addr, u64 size, bool preserve_gpu_writes) { + impl->InvalidateRegion(addr, size, preserve_gpu_writes); } bool GPU::OnCPUWrite(DAddr addr, u64 size) { diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 538c4da85a..974d214e10 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -168,6 +168,8 @@ public: void WaitForSyncOperation(u64 fence); + void WaitForIdle(); + /// Tick pending requests within the GPU. void TickWork(); @@ -218,6 +220,8 @@ public: void RequestComposite(std::vector&& layers, std::vector&& fences); + void WaitForComposite(); + std::vector GetAppletCaptureBuffer(); /// Performs any additional setup necessary in order to begin GPU emulation. @@ -244,7 +248,7 @@ public: void FlushRegion(DAddr addr, u64 size); /// Notify rasterizer that any caches of the specified region should be invalidated - void InvalidateRegion(DAddr addr, u64 size); + void InvalidateRegion(DAddr addr, u64 size, bool preserve_gpu_writes = false); /// Notify rasterizer that CPU is trying to write this area. It returns true if the area is /// sensible, false otherwise, addr and size must be a valid combination diff --git a/src/video_core/gpu_logging/freedreno_debug.cpp b/src/video_core/gpu_logging/freedreno_debug.cpp index 8b65bd0cd5..e01c154031 100644 --- a/src/video_core/gpu_logging/freedreno_debug.cpp +++ b/src/video_core/gpu_logging/freedreno_debug.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#ifdef ANDROID +#ifdef __ANDROID__ #include "video_core/gpu_logging/freedreno_debug.h" #include "common/logging.h" @@ -49,4 +49,4 @@ std::string FreedrenoDebugger::GetBreadcrumbs() { } // namespace GPU::Logging::Freedreno -#endif // ANDROID +#endif // __ANDROID__ diff --git a/src/video_core/gpu_logging/freedreno_debug.h b/src/video_core/gpu_logging/freedreno_debug.h index feb57a0b2a..7e4f812f46 100644 --- a/src/video_core/gpu_logging/freedreno_debug.h +++ b/src/video_core/gpu_logging/freedreno_debug.h @@ -3,7 +3,7 @@ #pragma once -#ifdef ANDROID +#ifdef __ANDROID__ #include @@ -29,4 +29,4 @@ private: } // namespace GPU::Logging::Freedreno -#endif // ANDROID +#endif // __ANDROID__ diff --git a/src/video_core/gpu_logging/gpu_logging.cpp b/src/video_core/gpu_logging/gpu_logging.cpp index 5f5e376e99..c7c3c4137f 100644 --- a/src/video_core/gpu_logging/gpu_logging.cpp +++ b/src/video_core/gpu_logging/gpu_logging.cpp @@ -4,6 +4,7 @@ #include "video_core/gpu_logging/gpu_logging.h" #include +#include #include #include "common/fs/file.h" @@ -241,7 +242,7 @@ void GPULogger::LogMemoryAllocation(uintptr_t memory, u64 size, u32 memory_flags } const auto log_entry = fmt::format( - "[{}] [Memory] Allocated {} at 0x{:x} (Device:{}, Host:{})\n", FormatTimestamp(timestamp), + "[{}] [Memory] Allocated {} at {:#x} (Device:{}, Host:{})\n", FormatTimestamp(timestamp), FormatMemorySize(size), memory, is_device_local ? "Yes" : "No", is_host_visible ? "Yes" : "No"); WriteToLog(log_entry); @@ -273,20 +274,19 @@ void GPULogger::LogMemoryDeallocation(uintptr_t memory) { if (size > 0) { const auto log_entry = - fmt::format("[{}] [Memory] Deallocated {} at 0x{:x}\n", FormatTimestamp(timestamp), + fmt::format("[{}] [Memory] Deallocated {} at {:#x}\n", FormatTimestamp(timestamp), FormatMemorySize(size), memory); WriteToLog(log_entry); } } void GPULogger::LogShaderCompilation(const std::string& shader_name, - const std::string& shader_info, - std::span spirv_code) { + const std::string& shader_info) { if (!initialized || current_level == LogLevel::Off) { return; } - if (!dump_shaders && current_level < LogLevel::Verbose) { + if (current_level < LogLevel::Verbose) { return; } @@ -294,38 +294,36 @@ void GPULogger::LogShaderCompilation(const std::string& shader_name, std::chrono::steady_clock::now().time_since_epoch()); const auto log_entry = fmt::format("[{}] [Shader] Compiled: {} ({})\n", - FormatTimestamp(timestamp), shader_name, shader_info); + FormatTimestamp(timestamp), shader_name, shader_info); WriteToLog(log_entry); +} - // Dump SPIR-V binary if enabled and we have data - if (dump_shaders && !spirv_code.empty()) { - using namespace Common::FS; - const auto& log_dir = GetEdenPath(EdenPath::LogDir); - const auto shaders_dir = log_dir / "shaders"; +bool IsActive() noexcept { + return Settings::values.gpu_log_level.GetValue() != Settings::GpuLogLevel::Off; +} - // Create directory on first dump - if (!shader_dump_dir_created) { - [[maybe_unused]] const bool created = CreateDir(shaders_dir); - shader_dump_dir_created = true; - } - - // Write SPIR-V binary file - const auto shader_path = shaders_dir / fmt::format("{}.spv", shader_name); - auto shader_file = std::make_unique( - shader_path, FileAccessMode::Write, FileType::BinaryFile); - - if (shader_file->IsOpen()) { - const size_t bytes_to_write = spirv_code.size() * sizeof(u32); - static_cast(shader_file->WriteSpan(spirv_code)); - shader_file->Close(); - - const auto dump_log = fmt::format("[{}] [Shader] Dumped SPIR-V: {} ({} bytes)\n", - FormatTimestamp(timestamp), shader_path.string(), bytes_to_write); - WriteToLog(dump_log); - } else { - LOG_WARNING(Render_Vulkan, "[GPU Logging] Failed to dump shader: {}", shader_path.string()); - } +void DumpSpirvShader(u64 shader_hash, std::span spirv_code) { + if (spirv_code.empty()) { + return; } + + using namespace Common::FS; + const auto& dump_dir = GetEdenPath(EdenPath::DumpDir); + + // Ensure DumpDir exists once. CreateDir is idempotent, so guarded to skip the syscall. + static std::once_flag dump_dir_flag; + std::call_once(dump_dir_flag, [&dump_dir]() { + [[maybe_unused]] const bool created = CreateDir(dump_dir); + }); + + const auto shader_path = dump_dir / fmt::format("{:016x}_{:016x}.spv", + Settings::GetCurrentProgramID(), shader_hash); + Common::FS::IOFile shader_file(shader_path, FileAccessMode::Write, FileType::BinaryFile); + if (!shader_file.IsOpen()) { + LOG_WARNING(Render_Vulkan, "[Shader Dump] Failed to open {}", shader_path.string()); + return; + } + static_cast(shader_file.WriteSpan(spirv_code)); } void GPULogger::LogPipelineStateChange(const std::string& state_info) { @@ -657,10 +655,6 @@ void GPULogger::EnableVulkanCallTracking(bool enabled) { track_vulkan_calls = enabled; } -void GPULogger::EnableShaderDumps(bool enabled) { - dump_shaders = enabled; -} - void GPULogger::EnableMemoryTracking(bool enabled) { track_memory = enabled; } diff --git a/src/video_core/gpu_logging/gpu_logging.h b/src/video_core/gpu_logging/gpu_logging.h index 134fa94c0f..5685e16bc7 100644 --- a/src/video_core/gpu_logging/gpu_logging.h +++ b/src/video_core/gpu_logging/gpu_logging.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include "common/common_types.h" @@ -87,8 +87,7 @@ public: void LogVulkanCall(const std::string& call_name, const std::string& params, int result); void LogMemoryAllocation(uintptr_t memory, u64 size, u32 memory_flags); void LogMemoryDeallocation(uintptr_t memory); - void LogShaderCompilation(const std::string& shader_name, const std::string& shader_info, - std::span spirv_code = {}); + void LogShaderCompilation(const std::string& shader_name, const std::string& shader_info); void LogPipelineStateChange(const std::string& state_info); void LogDriverDebugInfo(const std::string& debug_info); @@ -121,7 +120,6 @@ public: // Settings void SetLogLevel(LogLevel level); void EnableVulkanCallTracking(bool enabled); - void EnableShaderDumps(bool enabled); void EnableMemoryTracking(bool enabled); void EnableDriverDebugInfo(bool enabled); void SetRingBufferSize(size_t entries); @@ -154,7 +152,7 @@ private: mutable std::mutex ring_buffer_mutex; // Memory tracking - ankerl::unordered_dense::map memory_allocations; + ::Common::unordered_map memory_allocations; mutable std::mutex memory_mutex; // Statistics @@ -171,7 +169,6 @@ private: // Feature flags bool track_vulkan_calls = true; - bool dump_shaders = false; bool track_memory = false; bool capture_driver_debug = false; @@ -179,15 +176,16 @@ private: std::set used_extensions; mutable std::mutex extension_mutex; - // Shader dump directory (created on demand) - bool shader_dump_dir_created = false; - // Stored state for crash dumps std::string stored_driver_debug_info; std::string stored_pipeline_state; mutable std::mutex state_mutex; }; +[[nodiscard]] bool IsActive() noexcept; + +void DumpSpirvShader(u64 shader_hash, std::span spirv_code); + // Helper to get stage name from index inline const char* GetShaderStageName(size_t stage_index) { static constexpr std::array stage_names{ diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index d7c8ac391c..a01375e7be 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -19,8 +19,9 @@ namespace VideoCommon::GPUThread { -ThreadManager::ThreadManager(Core::System& system_, bool is_async_) - : system{system_}, is_async{is_async_} {} +ThreadManager::ThreadManager(Core::System& system_) + : system{system_} +{} ThreadManager::~ThreadManager() = default; @@ -29,6 +30,7 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten thread = std::jthread([&](std::stop_token stop_token) { Common::SetCurrentThreadName("GPU"); Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical); + Common::SetCurrentThreadToPerformanceCores(); system.RegisterHostThread(); auto current_context = context.Acquire(); @@ -39,7 +41,7 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten break; } if (auto* submit_list = std::get_if(&next.data)) { - scheduler.Push(submit_list->channel, std::move(submit_list->entries)); + scheduler.Push(system.GPU(), submit_list->channel, std::move(submit_list->entries)); } else if (std::holds_alternative(next.data)) { system.GPU().TickWork(); } else if (const auto* flush = std::get_if(&next.data)) { @@ -60,41 +62,40 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten }); } -void ThreadManager::SubmitList(s32 channel, Tegra::CommandList&& entries) { - PushCommand(SubmitListCommand(channel, std::move(entries))); +void ThreadManager::SubmitList(s32 channel, Tegra::CommandList&& entries, bool is_async) { + PushCommand(SubmitListCommand(channel, std::move(entries)), false, is_async); } -void ThreadManager::FlushRegion(DAddr addr, u64 size) { +void ThreadManager::FlushRegion(DAddr addr, u64 size, bool is_async) { if (!is_async) { // Always flush with synchronous GPU mode - PushCommand(FlushRegionCommand(addr, size)); + PushCommand(FlushRegionCommand(addr, size), false, is_async); } - return; } -void ThreadManager::TickGPU() { - PushCommand(GPUTickCommand()); +void ThreadManager::TickGPU(bool is_async) { + PushCommand(GPUTickCommand(), false, is_async); } void ThreadManager::InvalidateRegion(DAddr addr, u64 size) { rasterizer->OnCacheInvalidation(addr, size); } -void ThreadManager::FlushAndInvalidateRegion(DAddr addr, u64 size) { +void ThreadManager::FlushAndInvalidateRegion(DAddr addr, u64 size, bool is_async) { if (Settings::IsGPULevelHigh()) { if (!is_async) { - PushCommand(FlushRegionCommand(addr, size)); + PushCommand(FlushRegionCommand(addr, size), false, is_async); } else { auto& gpu = system.GPU(); const u64 fence = gpu.RequestFlush(addr, size); - TickGPU(); + TickGPU(is_async); gpu.WaitForSyncOperation(fence); } } rasterizer->OnCacheInvalidation(addr, size); } -u64 ThreadManager::PushCommand(CommandData&& command_data, bool block) { +u64 ThreadManager::PushCommand(CommandData&& command_data, bool block, bool is_async) { if (!is_async) { // In synchronous GPU mode, block the caller until the command has executed block = true; @@ -113,4 +114,11 @@ u64 ThreadManager::PushCommand(CommandData&& command_data, bool block) { return fence; } +void ThreadManager::NotifyShutdown() { + if (thread.joinable()) { + thread.request_stop(); + thread.join(); + } +} + } // namespace VideoCommon::GPUThread diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h index ac1283a338..631a680db4 100644 --- a/src/video_core/gpu_thread.h +++ b/src/video_core/gpu_thread.h @@ -15,6 +15,7 @@ #include "common/bounded_threadsafe_queue.h" #include "common/polyfill_thread.h" +#include "video_core/dma_pusher.h" #include "video_core/framebuffer_config.h" namespace Tegra { @@ -103,7 +104,7 @@ struct SynchState final { /// Class used to manage the GPU thread class ThreadManager final { public: - explicit ThreadManager(Core::System& system_, bool is_async_); + explicit ThreadManager(Core::System& system_); ~ThreadManager(); /// Creates and starts the GPU thread. @@ -111,27 +112,27 @@ public: Tegra::Control::Scheduler& scheduler); /// Push GPU command entries to be processed - void SubmitList(s32 channel, Tegra::CommandList&& entries); + void SubmitList(s32 channel, Tegra::CommandList&& entries, bool is_async); /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory - void FlushRegion(DAddr addr, u64 size); + void FlushRegion(DAddr addr, u64 size, bool is_async); /// Notify rasterizer that any caches of the specified region should be invalidated void InvalidateRegion(DAddr addr, u64 size); /// Notify rasterizer that any caches of the specified region should be flushed and invalidated - void FlushAndInvalidateRegion(DAddr addr, u64 size); + void FlushAndInvalidateRegion(DAddr addr, u64 size, bool is_async); - void TickGPU(); + void TickGPU(bool is_async); + + void NotifyShutdown(); private: /// Pushes a command to be executed by the GPU thread - u64 PushCommand(CommandData&& command_data, bool block = false); + u64 PushCommand(CommandData&& command_data, bool block, bool is_async); Core::System& system; - const bool is_async; VideoCore::RasterizerInterface* rasterizer = nullptr; - SynchState state; std::jthread thread; }; diff --git a/src/video_core/host1x/codec_types.h b/src/video_core/host1x/codec_types.h index 3a10afa8df..b2f80c822a 100644 --- a/src/video_core/host1x/codec_types.h +++ b/src/video_core/host1x/codec_types.h @@ -8,6 +8,7 @@ #include #include +#include #include "common/bit_field.h" #include "common/common_funcs.h" diff --git a/src/video_core/host1x/codecs/decoder.cpp b/src/video_core/host1x/codecs/decoder.cpp index c75059db6f..1d6d3e8af6 100644 --- a/src/video_core/host1x/codecs/decoder.cpp +++ b/src/video_core/host1x/codecs/decoder.cpp @@ -20,58 +20,54 @@ Decoder::Decoder(Host1x::Host1x& host1x_, s32 id_, const Host1x::NvdecCommon::Nv Decoder::~Decoder() = default; +void Decoder::SetFrameDimensions(s32 width, s32 height) { + if (width <= 0 || height <= 0) { + frame_dimensions.reset(); + return; + } + frame_dimensions = FFmpeg::FrameDimensions{width, height}; +} + void Decoder::Decode() { if (!initialized) { return; } const auto packet_data = ComposeFrame(); - // Send assembled bitstream to decoder. - if (!decode_api.SendPacket(packet_data)) { - return; - } - - // Only receive/store visible frames. - if (vp9_hidden_frame) { - return; - } - - // Receive output frames from decoder. - auto frame = decode_api.ReceiveFrame(); - - if (!frame) { - return; - } - - if (IsInterlaced()) { - auto [luma_top, luma_bottom, chroma_top, chroma_bottom] = GetInterlacedOffsets(); - auto frame_copy = frame; - - if (!frame.get()) { - LOG_ERROR(HW_GPU, - "Nvdec {} failed to decode interlaced frame for top {:#X} bottom 0x{:X}", id, - luma_top, luma_bottom); - } - - if (UsingDecodeOrder()) { - host1x.frame_queue.PushDecodeOrder(id, luma_top, std::move(frame)); - host1x.frame_queue.PushDecodeOrder(id, luma_bottom, std::move(frame_copy)); - } else { - host1x.frame_queue.PushPresentOrder(id, luma_top, std::move(frame)); - host1x.frame_queue.PushPresentOrder(id, luma_bottom, std::move(frame_copy)); - } + FFmpeg::FrameOffsets offsets{}; + offsets.hidden = vp9_hidden_frame; + offsets.interlaced = IsInterlaced(); + if (offsets.interlaced) { + std::tie(offsets.luma, offsets.luma_bottom, std::ignore, std::ignore) = + GetInterlacedOffsets(); } else { - auto [luma_offset, chroma_offset] = GetProgressiveOffsets(); + std::tie(offsets.luma, std::ignore) = GetProgressiveOffsets(); + } - if (!frame.get()) { - LOG_ERROR(HW_GPU, "Nvdec {} failed to decode progressive frame for luma {:#X}", id, - luma_offset); - } + // Send assembled bitstream to decoder. + if (!decode_api.SendPacket(packet_data, offsets, GetFrameDimensions())) { + return; + } + auto push = [&](u64 luma, std::shared_ptr frame) { if (UsingDecodeOrder()) { - host1x.frame_queue.PushDecodeOrder(id, luma_offset, std::move(frame)); + host1x.frame_queue.PushDecodeOrder(id, luma, std::move(frame)); } else { - host1x.frame_queue.PushPresentOrder(id, luma_offset, std::move(frame)); + host1x.frame_queue.PushPresentOrder(id, luma, std::move(frame)); + } + }; + + while (auto result = decode_api.ReceiveFrame()) { + auto& [frame, frame_offsets] = *result; + if (!frame) { + continue; + } + if (frame_offsets.interlaced) { + auto frame_copy = frame; + push(frame_offsets.luma, std::move(frame)); + push(frame_offsets.luma_bottom, std::move(frame_copy)); + } else { + push(frame_offsets.luma, std::move(frame)); } } } diff --git a/src/video_core/host1x/codecs/decoder.h b/src/video_core/host1x/codecs/decoder.h index 9fca89aa40..5b0d9ebe62 100644 --- a/src/video_core/host1x/codecs/decoder.h +++ b/src/video_core/host1x/codecs/decoder.h @@ -10,7 +10,8 @@ #include #include #include -#include +#include +#include "common/container/unordered_map.h" #include #include "common/common_types.h" @@ -46,12 +47,19 @@ protected: virtual std::tuple GetInterlacedOffsets() = 0; virtual bool IsInterlaced() = 0; + void SetFrameDimensions(s32 width, s32 height); + + std::optional GetFrameDimensions() const { + return frame_dimensions; + } + FFmpeg::DecodeApi decode_api; Host1x::Host1x& host1x; const Host1x::NvdecCommon::NvdecRegisters& regs; s32 id; bool initialized : 1 = false; bool vp9_hidden_frame : 1 = false; + std::optional frame_dimensions; }; } // namespace Tegra diff --git a/src/video_core/host1x/codecs/h264.cpp b/src/video_core/host1x/codecs/h264.cpp index f439ac3828..e3a6f5d182 100644 --- a/src/video_core/host1x/codecs/h264.cpp +++ b/src/video_core/host1x/codecs/h264.cpp @@ -52,6 +52,10 @@ bool H264::IsInterlaced() { std::span H264::ComposeFrame() { host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_context, sizeof(H264DecoderContext)); + const auto& params = current_context.h264_parameter_set; + SetFrameDimensions(static_cast(params.pic_width_in_mbs) * 16, + static_cast(params.frame_height_in_mbs) * 16); + const s64 frame_number = current_context.h264_parameter_set.frame_number.Value(); if (!is_first_frame && frame_number != 0) { frame_scratch.resize_destructive(current_context.stream_len); diff --git a/src/video_core/host1x/codecs/vp8.cpp b/src/video_core/host1x/codecs/vp8.cpp index 00fe6e4499..33d781816a 100644 --- a/src/video_core/host1x/codecs/vp8.cpp +++ b/src/video_core/host1x/codecs/vp8.cpp @@ -35,6 +35,7 @@ std::tuple VP8::GetInterlacedOffsets() { std::span VP8::ComposeFrame() { host1x.gmmu_manager.ReadBlock(regs.picture_info_offset.Address(), ¤t_context, sizeof(VP8PictureInfo)); + SetFrameDimensions(current_context.frame_width, current_context.frame_height); const bool is_key_frame = current_context.key_frame == 1u; const auto bitstream_size = size_t(current_context.vld_buffer_size); diff --git a/src/video_core/host1x/codecs/vp9.cpp b/src/video_core/host1x/codecs/vp9.cpp index 1085e08939..dcc6261e5c 100644 --- a/src/video_core/host1x/codecs/vp9.cpp +++ b/src/video_core/host1x/codecs/vp9.cpp @@ -841,6 +841,7 @@ std::span VP9::ComposeFrame() { { Vp9FrameContainer curr_frame = GetCurrentFrame(); current_frame_info = curr_frame.info; + SetFrameDimensions(current_frame_info.frame_size.width, current_frame_info.frame_size.height); bitstream = std::move(curr_frame.bit_stream); } // The uncompressed header routine sets PrevProb parameters needed for the compressed header diff --git a/src/video_core/host1x/control.cpp b/src/video_core/host1x/control.cpp index 53b3063557..494a100c10 100644 --- a/src/video_core/host1x/control.cpp +++ b/src/video_core/host1x/control.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2021 yuzu Emulator Project @@ -10,26 +10,22 @@ namespace Tegra::Host1x { -Control::Control(Host1x& host1x_) : host1x(host1x_) {} - -Control::~Control() = default; - -void Control::ProcessMethod(Method method, u32 argument) { +void Control::ProcessMethod(Host1x& host1x, Method method, u32 argument) { switch (method) { case Method::LoadSyncptPayload32: syncpoint_value = argument; break; case Method::WaitSyncpt: case Method::WaitSyncpt32: - Execute(argument); + Execute(host1x, argument); break; default: - UNIMPLEMENTED_MSG("Control method {:#X}", static_cast(method)); + UNIMPLEMENTED_MSG("Control method {:#x}", u32(method)); break; } } -void Control::Execute(u32 data) { +void Control::Execute(Host1x& host1x, u32 data) { LOG_TRACE(Service_NVDRV, "Control wait syncpt {} value {}", data, syncpoint_value); host1x.GetSyncpointManager().WaitHost(data, syncpoint_value); } diff --git a/src/video_core/host1x/control.h b/src/video_core/host1x/control.h index bd8a2d7ad4..4d87e63b32 100644 --- a/src/video_core/host1x/control.h +++ b/src/video_core/host1x/control.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2021 yuzu Emulator Project // SPDX-FileCopyrightText: 2021 Skyline Team and Contributors // SPDX-License-Identifier: GPL-3.0-or-later @@ -19,17 +22,11 @@ public: WaitSyncpt32 = 0x50, }; - explicit Control(Host1x& host1x); - ~Control(); - /// Writes the method into the state, Invoke Execute() if encountered - void ProcessMethod(Method method, u32 argument); - -private: + void ProcessMethod(Host1x& host1x, Method method, u32 argument); /// For Host1x, execute is waiting on a syncpoint previously written into the state - void Execute(u32 data); + void Execute(Host1x& host1x, u32 data); - Host1x& host1x; u32 syncpoint_value{}; }; diff --git a/src/video_core/host1x/ffmpeg.cpp b/src/video_core/host1x/ffmpeg.cpp index 80e48692f7..676e5820a5 100644 --- a/src/video_core/host1x/ffmpeg.cpp +++ b/src/video_core/host1x/ffmpeg.cpp @@ -4,6 +4,10 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include + #include "common/assert.h" #include "common/logging.h" #include "common/scope_exit.h" @@ -28,7 +32,7 @@ namespace { constexpr AVPixelFormat PreferredGpuFormat = AV_PIX_FMT_NV12; constexpr AVPixelFormat PreferredCpuFormat = AV_PIX_FMT_YUV420P; constexpr std::array PreferredGpuDecoders = { -#if defined (_WIN32) +#if defined(_WIN32) AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_D3D11VA, AV_HWDEVICE_TYPE_DXVA2, @@ -39,7 +43,7 @@ constexpr std::array PreferredGpuDecoders = { AV_HWDEVICE_TYPE_DRM, #elif defined(__APPLE__) AV_HWDEVICE_TYPE_VIDEOTOOLBOX, -#elif defined(ANDROID) +#elif defined(__ANDROID__) AV_HWDEVICE_TYPE_MEDIACODEC, #elif defined(__unix__) AV_HWDEVICE_TYPE_CUDA, @@ -84,6 +88,52 @@ std::string AVError(int errnum) { return errbuf; } +#if defined(__ANDROID__) +size_t FindNalStartCode(std::span data, size_t i) { + const size_t n = data.size(); + if (i + 3 < n && data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 0 && data[i + 3] == 1) { + return 4; + } + if (i + 2 < n && data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) { + return 3; + } + return 0; +} + +std::vector ExtractH264ParameterSetExtradata(std::span packet) { + std::vector extradata; + const size_t size = packet.size(); + size_t i = 0; + while (i < size) { + const size_t sc = FindNalStartCode(packet, i); + if (sc == 0) { + ++i; + continue; + } + const size_t nal_start = i + sc; + if (nal_start >= size) { + break; + } + const u8 nal_type = packet[nal_start] & 0x1F; + + size_t j = nal_start + 1; + while (j < size && FindNalStartCode(packet, j) == 0) { + ++j; + } + + if (nal_type == 7 || nal_type == 8) { + constexpr u8 start[4] = {0, 0, 0, 1}; + extradata.insert(extradata.end(), start, start + sizeof(start)); + extradata.insert(extradata.end(), packet.begin() + nal_start, packet.begin() + j); + } else if (nal_type == 1 || nal_type == 5) { + break; + } + i = j; + } + return extradata; +} +#endif + } Packet::Packet(std::span data) { @@ -118,7 +168,25 @@ Decoder::Decoder(Tegra::Host1x::NvdecCommon::VideoCodec codec) { return AV_CODEC_ID_NONE; } }(); - m_codec = avcodec_find_decoder(av_codec); + +#if defined(__ANDROID__) + if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Gpu) { + const char* mc_name = nullptr; + switch (av_codec) { + case AV_CODEC_ID_H264: mc_name = "h264_mediacodec"; break; + // Disabled: causes crashes in Diablo II + // case AV_CODEC_ID_VP8: mc_name = "vp8_mediacodec"; break; + case AV_CODEC_ID_VP9: mc_name = "vp9_mediacodec"; break; + default: break; + } + if (mc_name) { + m_codec = avcodec_find_decoder_by_name(mc_name); + } + } +#endif + if (!m_codec) { + m_codec = avcodec_find_decoder(av_codec); + } } bool Decoder::SupportsDecodingOnDevice(AVPixelFormat* out_pix_fmt, AVHWDeviceType type) const { @@ -214,6 +282,10 @@ DecoderContext::DecoderContext(const Decoder& decoder) : m_decoder{decoder} { av_opt_set(m_codec_context->priv_data, "tune", "zerolatency", 0); m_codec_context->thread_count = 0; m_codec_context->thread_type &= ~FF_THREAD_FRAME; +#if defined(__ANDROID__) + m_codec_context->flags |= AV_CODEC_FLAG_LOW_DELAY; + m_codec_context->flags2 |= AV_CODEC_FLAG2_FAST; +#endif } DecoderContext::~DecoderContext() { @@ -227,15 +299,25 @@ void DecoderContext::InitializeHardwareDecoder(const HardwareContext& context, A m_codec_context->pix_fmt = hw_pix_fmt; } -bool DecoderContext::OpenContext(const Decoder& decoder) { +bool DecoderContext::OpenContext(const Decoder& decoder, std::span extradata) { + if (!extradata.empty()) { + av_freep(&m_codec_context->extradata); + m_codec_context->extradata = static_cast( + av_mallocz(extradata.size() + AV_INPUT_BUFFER_PADDING_SIZE)); + if (!m_codec_context->extradata) { + LOG_ERROR(HW_GPU, "Failed to allocate extradata"); + return false; + } + std::memcpy(m_codec_context->extradata, extradata.data(), extradata.size()); + m_codec_context->extradata_size = static_cast(extradata.size()); + } + if (const int ret = avcodec_open2(m_codec_context, decoder.GetCodec(), nullptr); ret < 0) { LOG_ERROR(HW_GPU, "avcodec_open2 error: {}", AVError(ret)); return false; } - if (!m_codec_context->hw_device_ctx) { - LOG_INFO(HW_GPU, "Using FFmpeg CPU decoder"); - } + LOG_INFO(HW_GPU, "Using decoder {}", decoder.GetCodec()->name); return true; } @@ -281,6 +363,13 @@ void DecodeApi::Reset() { m_hardware_context.reset(); m_decoder_context.reset(); m_decoder.reset(); + m_opened = false; + m_defer_android_mediacodec_open = false; + m_needs_h264_extradata = false; + m_next_pts = 0; + while (!m_pending_offsets.empty()) { + m_pending_offsets.pop(); + } } bool DecodeApi::Initialize(Tegra::Host1x::NvdecCommon::VideoCodec codec) { @@ -288,29 +377,90 @@ bool DecodeApi::Initialize(Tegra::Host1x::NvdecCommon::VideoCodec codec) { m_decoder.emplace(codec); m_decoder_context.emplace(*m_decoder); + bool is_mediacodec = false; +#if defined(__ANDROID__) + const std::string_view decoder_name = m_decoder->GetCodec() ? m_decoder->GetCodec()->name : ""; + is_mediacodec = decoder_name == "h264_mediacodec" || + decoder_name == "vp8_mediacodec" || + decoder_name == "vp9_mediacodec"; +#endif + // Enable GPU decoding if requested. - if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Gpu) { + if (!is_mediacodec && + Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Gpu) { m_hardware_context.emplace(); m_hardware_context->InitializeForDecoder(*m_decoder_context, *m_decoder); } +#if defined(__ANDROID__) + m_defer_android_mediacodec_open = is_mediacodec; + m_needs_h264_extradata = decoder_name == "h264_mediacodec"; + if (m_defer_android_mediacodec_open) { + return true; + } +#endif + // Open the decoder context. if (!m_decoder_context->OpenContext(*m_decoder)) { this->Reset(); return false; } + m_opened = true; return true; } -bool DecodeApi::SendPacket(std::span packet_data) { +bool DecodeApi::SendPacket(std::span packet_data, const FrameOffsets& offsets, + std::optional dimensions) { + if (!m_opened) { + std::vector extradata; +#if defined(__ANDROID__) + if (m_defer_android_mediacodec_open) { + if (!dimensions) { + return true; + } + + auto* ctx = m_decoder_context->GetCodecContext(); + ctx->width = dimensions->width; + ctx->height = dimensions->height; + ctx->coded_width = dimensions->width; + ctx->coded_height = dimensions->height; + } + + if (m_needs_h264_extradata) { + extradata = ExtractH264ParameterSetExtradata(packet_data); + if (extradata.empty()) { + return true; + } + } +#endif + if (!m_decoder_context->OpenContext(*m_decoder, extradata)) { + this->Reset(); + return false; + } + m_opened = true; + } + if (!offsets.hidden) { + m_pending_offsets.push(offsets); + } FFmpeg::Packet packet(packet_data); + packet.GetPacket()->pts = m_next_pts; + packet.GetPacket()->dts = m_next_pts; + ++m_next_pts; return m_decoder_context->SendPacket(packet); } -std::shared_ptr DecodeApi::ReceiveFrame() { - // Receive raw frame from decoder. - return m_decoder_context->ReceiveFrame(); +std::optional DecodeApi::ReceiveFrame() { + auto frame = m_decoder_context->ReceiveFrame(); + if (!frame) { + return std::nullopt; + } + FrameOffsets offsets{}; + if (!m_pending_offsets.empty()) { + offsets = m_pending_offsets.front(); + m_pending_offsets.pop(); + } + return DecodedFrame{std::move(frame), offsets}; } } diff --git a/src/video_core/host1x/ffmpeg.h b/src/video_core/host1x/ffmpeg.h index fdb6908bb6..00cd3f5d4d 100644 --- a/src/video_core/host1x/ffmpeg.h +++ b/src/video_core/host1x/ffmpeg.h @@ -179,7 +179,7 @@ public: ~DecoderContext(); void InitializeHardwareDecoder(const HardwareContext& context, AVPixelFormat hw_pix_fmt); - bool OpenContext(const Decoder& decoder); + bool OpenContext(const Decoder& decoder, std::span extradata = {}); bool SendPacket(const Packet& packet); std::shared_ptr ReceiveFrame(); @@ -198,6 +198,18 @@ private: bool m_decode_order{}; }; +struct FrameOffsets { + bool interlaced{}; + bool hidden{}; + u64 luma{}; + u64 luma_bottom{}; +}; + +struct FrameDimensions { + s32 width{}; + s32 height{}; +}; + class DecodeApi { public: YUZU_NON_COPYABLE(DecodeApi); @@ -213,13 +225,24 @@ public: return m_decoder_context->UsingDecodeOrder(); } - bool SendPacket(std::span packet_data); - std::shared_ptr ReceiveFrame(); + bool SendPacket(std::span packet_data, const FrameOffsets& offsets, + std::optional dimensions = std::nullopt); + + struct DecodedFrame { + std::shared_ptr frame; + FrameOffsets offsets; + }; + std::optional ReceiveFrame(); private: std::optional m_decoder; std::optional m_decoder_context; std::optional m_hardware_context; + bool m_opened{}; + bool m_defer_android_mediacodec_open{}; + bool m_needs_h264_extradata{}; + s64 m_next_pts{}; + std::queue m_pending_offsets; }; } // namespace FFmpeg diff --git a/src/video_core/host1x/host1x.cpp b/src/video_core/host1x/host1x.cpp index b4e4a38c80..590b0e0ff2 100644 --- a/src/video_core/host1x/host1x.cpp +++ b/src/video_core/host1x/host1x.cpp @@ -27,22 +27,22 @@ void Host1x::StartDevice(s32 fd, ChannelType type, u32 syncpt) { #ifdef YUZU_LEGACY std::call_once(nvdec_first_init, []() {std::this_thread::sleep_for(std::chrono::milliseconds{500});}); // HACK: For Astroneer #endif - devices[fd] = std::make_unique(*this, fd, syncpt); + devices[fd].emplace(*this, fd, syncpt); break; case ChannelType::VIC: #ifdef YUZU_LEGACY std::call_once(vic_first_init, []() {std::this_thread::sleep_for(std::chrono::milliseconds{500});}); // HACK: For Astroneer #endif - devices[fd] = std::make_unique(*this, fd, syncpt); + devices[fd].emplace(*this, fd, syncpt); break; default: - LOG_ERROR(HW_GPU, "Unimplemented host1x device {}", static_cast(type)); + LOG_ERROR(HW_GPU, "Unimplemented host1x device {}", u32(type)); break; } } void Host1x::StopDevice(s32 fd, ChannelType type) { - devices.erase(fd); + devices[fd].emplace(); } } // namespace Tegra::Host1x diff --git a/src/video_core/host1x/host1x.h b/src/video_core/host1x/host1x.h index 838e749072..3b886a59d5 100644 --- a/src/video_core/host1x/host1x.h +++ b/src/video_core/host1x/host1x.h @@ -6,12 +6,16 @@ #pragma once -#include +#include "common/container/unordered_map.h" #include -#include +#include #include "common/common_types.h" +// fd types? +#include "video_core/host1x/nvdec.h" +#include "video_core/host1x/vic.h" + #include "common/address_space.h" #include "video_core/cdma_pusher.h" #include "video_core/host1x/gpu_device_memory_manager.h" @@ -31,118 +35,90 @@ class Nvdec; class FrameQueue { public: + struct FrameDevice { + std::deque>> m_presentation_order; + std::unordered_map> m_decode_order; + }; + void Open(s32 fd) { std::scoped_lock l{m_mutex}; - m_presentation_order.insert({fd, {}}); - m_decode_order.insert({fd, {}}); + m_frame_devices.insert_or_assign(fd, FrameDevice{}); } void Close(s32 fd) { std::scoped_lock l{m_mutex}; - m_presentation_order.erase(fd); - m_decode_order.erase(fd); + m_frame_devices.erase(fd); } s32 VicFindNvdecFdFromOffset(u64 search_offset) { std::scoped_lock l{m_mutex}; - for (auto& map : m_presentation_order) { - for (auto& [offset, frame] : map.second) { - if (offset == search_offset) { - return map.first; - } - } - } - for (auto& map : m_decode_order) { - for (auto& [offset, frame] : map.second) { - if (offset == search_offset) { - return map.first; - } - } + for (auto const& [fd, dev] : m_frame_devices) { + for (auto const& [offset, frame] : dev.m_presentation_order) + if (offset == search_offset) + return fd; + for (auto const& [offset, frame] : dev.m_decode_order) + if (offset == search_offset) + return fd; } return -1; } void PushPresentOrder(s32 fd, u64 offset, std::shared_ptr&& frame) { std::scoped_lock l{m_mutex}; - auto map = m_presentation_order.find(fd); - if (map == m_presentation_order.end()) { - return; + if (auto const it = m_frame_devices.find(fd); it != m_frame_devices.end()) { + if (it->second.m_presentation_order.size() >= MAX_PRESENT_QUEUE) + it->second.m_presentation_order.pop_front(); + it->second.m_presentation_order.emplace_back(offset, std::move(frame)); } - - if (map->second.size() >= MAX_PRESENT_QUEUE) { - map->second.pop_front(); - } - - map->second.emplace_back(offset, std::move(frame)); } void PushDecodeOrder(s32 fd, u64 offset, std::shared_ptr&& frame) { std::scoped_lock l{m_mutex}; - auto map = m_decode_order.find(fd); - if (map == m_decode_order.end()) { - return; - } - - map->second.insert_or_assign(offset, std::move(frame)); - - if (map->second.size() > MAX_DECODE_MAP) { - auto it = map->second.begin(); - std::advance(it, map->second.size() - MAX_DECODE_MAP); - map->second.erase(map->second.begin(), it); + if (auto const it = m_frame_devices.find(fd); it != m_frame_devices.end()) { + it->second.m_decode_order.insert_or_assign(offset, std::move(frame)); + if (it->second.m_decode_order.size() > MAX_DECODE_MAP) { + auto it2 = it->second.m_decode_order.begin(); + std::advance(it2, it->second.m_decode_order.size() - MAX_DECODE_MAP); + it->second.m_decode_order.erase(it->second.m_decode_order.begin(), it2); + } } } std::shared_ptr GetFrame(s32 fd, u64 offset) { - if (fd == -1) { - return {}; + if (fd != -1) { + std::scoped_lock l{m_mutex}; + if (auto const it = m_frame_devices.find(fd); it != m_frame_devices.end()) { + if (it->second.m_presentation_order.size() > 0) + return GetPresentOrderLocked(fd); + if (it->second.m_decode_order.size() > 0) + return GetDecodeOrderLocked(fd, offset); + } } - - std::scoped_lock l{m_mutex}; - - auto present_map = m_presentation_order.find(fd); - if (present_map != m_presentation_order.end() && !present_map->second.empty()) { - return GetPresentOrderLocked(fd); - } - - auto decode_map = m_decode_order.find(fd); - if (decode_map != m_decode_order.end() && !decode_map->second.empty()) { - return GetDecodeOrderLocked(fd, offset); - } - return {}; } private: std::shared_ptr GetPresentOrderLocked(s32 fd) { - auto map = m_presentation_order.find(fd); - if (map == m_presentation_order.end() || map->second.empty()) { - return {}; + if (auto const it = m_frame_devices.find(fd); it != m_frame_devices.end()) { + auto frame = std::move(it->second.m_presentation_order.front().second); + it->second.m_presentation_order.pop_front(); + return frame; } - - auto frame = std::move(map->second.front().second); - map->second.pop_front(); - return frame; + return {}; } std::shared_ptr GetDecodeOrderLocked(s32 fd, u64 offset) { - auto map = m_decode_order.find(fd); - if (map == m_decode_order.end() || map->second.empty()) { - return {}; + if (auto const it = m_frame_devices.find(fd); it != m_frame_devices.end()) { + if (auto const it2 = it->second.m_decode_order.find(offset); it2 != it->second.m_decode_order.end()) { + // TODO: this "mapped" prevents us from fully embracing ankerl + return std::move(it->second.m_decode_order.extract(it2).mapped()); + } } - - auto it = map->second.find(offset); - if (it == map->second.end()) { - return {}; - } - // TODO: this "mapped" prevents us from fully embracing ankerl - return std::move(map->second.extract(it).mapped()); + return {}; } - using FramePtr = std::shared_ptr; - std::mutex m_mutex{}; - ankerl::unordered_dense::map>> m_presentation_order; - ankerl::unordered_dense::map> m_decode_order; + ::Common::unordered_map m_frame_devices; static constexpr size_t MAX_PRESENT_QUEUE = 100; static constexpr size_t MAX_DECODE_MAP = 200; @@ -196,11 +172,11 @@ public: void StopDevice(s32 fd, ChannelType type); void PushEntries(s32 fd, ChCommandHeaderList&& entries) { - auto it = devices.find(fd); - if (it == devices.end()) { - return; + if (auto const nvdec = std::get_if(&devices[fd])) { + nvdec->PushEntries(std::move(entries)); + } else if (auto const vic = std::get_if(&devices[fd])) { + vic->PushEntries(std::move(entries)); } - it->second->PushEntries(std::move(entries)); } Core::System& system; @@ -209,7 +185,11 @@ public: Tegra::MemoryManager gmmu_manager; Common::FlatAllocator allocator; FrameQueue frame_queue; - ankerl::unordered_dense::map> devices; + std::array, 1024> devices; #ifdef YUZU_LEGACY std::once_flag nvdec_first_init; std::once_flag vic_first_init; diff --git a/src/video_core/host1x/nvdec.cpp b/src/video_core/host1x/nvdec.cpp index f2e5c358d8..5dbc6a417e 100644 --- a/src/video_core/host1x/nvdec.cpp +++ b/src/video_core/host1x/nvdec.cpp @@ -31,6 +31,7 @@ Nvdec::Nvdec(Host1x& host1x_, s32 id_, u32 syncpt) Nvdec::~Nvdec() { LOG_INFO(HW_GPU, "Destroying nvdec {}", id); + host1x.frame_queue.Close(id); } void Nvdec::ProcessMethod(u32 method, u32 argument) { diff --git a/src/video_core/host1x/nvdec_common.h b/src/video_core/host1x/nvdec_common.h index dfd8bb3770..0857a9f95c 100644 --- a/src/video_core/host1x/nvdec_common.h +++ b/src/video_core/host1x/nvdec_common.h @@ -1,8 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include +#include +#include + #include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" diff --git a/src/video_core/host1x/vic.cpp b/src/video_core/host1x/vic.cpp index 906714cc16..1a89f3de06 100644 --- a/src/video_core/host1x/vic.cpp +++ b/src/video_core/host1x/vic.cpp @@ -39,7 +39,7 @@ extern "C" { #include "video_core/textures/decoders.h" #if defined(ARCHITECTURE_x86_64) -#include "common/x64/cpu_detect.h" +#include "common/cpu_features.h" #endif #if defined(ARCHITECTURE_x86_64) \ @@ -55,9 +55,8 @@ namespace Tegra::Host1x { namespace { static bool HasSSE41() { -#if defined(ARCHITECTURE_x86_64) - static bool has_sse41 = Common::GetCPUCaps().sse4_1; - return has_sse41; +#ifdef ARCHITECTURE_x86_64 + return Common::g_cpu_caps.sse4_1; #else return false; #endif @@ -99,7 +98,7 @@ Vic::~Vic() noexcept { } void Vic::ProcessMethod(u32 method, u32 arg) noexcept { - LOG_TRACE(HW_GPU, "Vic {} method {:#X}", id, u32(method)); + LOG_TRACE(HW_GPU, "Vic {} method {:#x}", id, u32(method)); regs.reg_array[method] = arg; switch (Method(method * sizeof(u32))) { case Method::Execute: @@ -119,6 +118,7 @@ void Vic::Execute() noexcept { output_surface.resize(output_width * output_height); if (Settings::values.nvdec_emulation.GetValue() != Settings::NvdecEmulation::Off) { + bool decoded_frame = false; for (size_t i = 0; i < config.slot_structs.size(); i++) { if (auto& slot_config = config.slot_structs[i]; slot_config.config.slot_enable) { auto const luma_offset = regs.surfaces[i][SurfaceIndex::Current].luma.Address(); @@ -137,11 +137,17 @@ void Vic::Execute() noexcept { break; } Blend(config, slot_config, config.output_surface_config.out_pixel_format); + decoded_frame = true; } else { - LOG_ERROR(HW_GPU, "Vic {} failed to get frame with offset {:#X}", id, luma_offset); + LOG_TRACE(HW_GPU, "Vic {} failed to get frame with offset {:#X}", id, luma_offset); } } } + if (decoded_frame) { + has_decoded_frame = true; + } else if (!has_decoded_frame) { + return; + } } else { // Fill the frame with black, as otherwise they can have random data and be very glitchy. std::fill(output_surface.begin(), output_surface.end(), Pixel{}); @@ -408,15 +414,15 @@ void Vic::ReadInterlacedY8__V8U8_N420(const SlotStruct& slot, std::span offsets, std::shared_ptr frame, bool planar) noexcept { switch (slot.config.frame_format) { - case DXVAHD_FRAME_FORMAT::PROGRESSIVE: + case DxvhadFrameFormat::Progressive: ReadProgressiveY8__V8U8_N420(slot, offsets, std::move(frame), planar, false); break; - case DXVAHD_FRAME_FORMAT::TOP_FIELD: + case DxvhadFrameFormat::TopField: ReadInterlacedY8__V8U8_N420(slot, offsets, std::move(frame), planar, true); break; - case DXVAHD_FRAME_FORMAT::BOTTOM_FIELD: + case DxvhadFrameFormat::BottomField: ReadInterlacedY8__V8U8_N420(slot, offsets, std::move(frame), planar, false); break; default: @@ -861,15 +867,15 @@ void Vic::WriteY8__V8U8_N420(const OutputSurfaceConfig& output_surface_config) n }; switch (output_surface_config.out_block_kind) { - case BLK_KIND::GENERIC_16Bx2: { + case BlkKind::Generic_16Bx2: { u32 const block_height = u32(output_surface_config.out_block_height); auto const out_luma_swizzle_size = Texture::CalculateSize(true, BytesPerPixel, out_luma_width, out_luma_height, 1, block_height, 0); auto const out_chroma_swizzle_size = Texture::CalculateSize(true, BytesPerPixel * 2, out_chroma_width, out_chroma_height, 1, block_height, 0); LOG_TRACE(HW_GPU, "Writing Y8__V8U8_N420 swizzled frame\n" - "\tinput surface {}x{} stride {} size {:#X}\n" - "\toutput luma {}x{} stride {} size {:#X} block height {} swizzled size 0x{:X}\n", - "\toutput chroma {}x{} stride {} size {:#X} block height {} swizzled size 0x{:X}", + "\tinput surface {}x{} stride {} size {:#x}\n" + "\toutput luma {}x{} stride {} size {:#x} block height {} swizzled size {:#x}\n", + "\toutput chroma {}x{} stride {} size {:#x} block height {} swizzled size {:#x}", surface_width, surface_height, surface_stride * BytesPerPixel, surface_stride * surface_height * BytesPerPixel, out_luma_width, out_luma_height, out_luma_stride, out_luma_size, block_height, out_luma_swizzle_size, out_chroma_width, @@ -890,11 +896,11 @@ void Vic::WriteY8__V8U8_N420(const OutputSurfaceConfig& output_surface_config) n Texture::SwizzleTexture(out_chroma, chroma_scratch, BytesPerPixel, out_chroma_width, out_chroma_height, 1, block_height, 0, 1); } } break; - case BLK_KIND::PITCH: { + case BlkKind::Pitch: { LOG_TRACE(HW_GPU, "Writing Y8__V8U8_N420 swizzled frame\n" - "\tinput surface {}x{} stride {} size {:#X}\n" - "\toutput luma {}x{} stride {} size {:#X} block height {} swizzled size 0x{:X}\n", - "\toutput chroma {}x{} stride {} size {:#X} block height {} swizzled size 0x{:X}", + "\tinput surface {}x{} stride {} size {:#x}\n" + "\toutput luma {}x{} stride {} size {:#x} block height {} swizzled size {:#x}\n", + "\toutput chroma {}x{} stride {} size {:#x} block height {} swizzled size {:#x}", surface_width, surface_height, surface_stride * BytesPerPixel, surface_stride * surface_height * BytesPerPixel, out_luma_width, out_luma_height, out_luma_stride, out_luma_size, out_chroma_width, out_chroma_height, out_chroma_stride, @@ -957,7 +963,7 @@ void Vic::WriteABGR(const OutputSurfaceConfig& output_surface_config, VideoPixel auto pixel1213 = _mm_load_si128((__m128i*)&inp[src + x + 12]); auto pixel1415 = _mm_load_si128((__m128i*)&inp[src + x + 14]); - // Right-shift the channels by 16 to un-do the left shit on read and bring the range + // Right-shift the channels by 16 to un-do the left shift on read and bring the range // back to 8-bit. pixel01 = _mm_srli_epi16(pixel01, 2); pixel23 = _mm_srli_epi16(pixel23, 2); @@ -1033,12 +1039,12 @@ void Vic::WriteABGR(const OutputSurfaceConfig& output_surface_config, VideoPixel }; switch (output_surface_config.out_block_kind) { - case BLK_KIND::GENERIC_16Bx2: { + case BlkKind::Generic_16Bx2: { const u32 block_height = u32(output_surface_config.out_block_height); auto const out_swizzle_size = Texture::CalculateSize(true, BytesPerPixel, out_luma_width, out_luma_height, 1, block_height, 0); LOG_TRACE(HW_GPU, "Writing ABGR swizzled frame\n" - "\tinput surface {}x{} stride {} size {:#X}\n" - "\toutput surface {}x{} stride {} size {:#X} block height {} swizzled size 0x{:X}", + "\tinput surface {}x{} stride {} size {:#x}\n" + "\toutput surface {}x{} stride {} size {:#x} block height {} swizzled size {:#x}", surface_width, surface_height, surface_stride * BytesPerPixel, surface_stride * surface_height * BytesPerPixel, out_luma_width, out_luma_height, out_luma_stride, out_luma_size, block_height, out_swizzle_size); @@ -1052,10 +1058,10 @@ void Vic::WriteABGR(const OutputSurfaceConfig& output_surface_config, VideoPixel Texture::SwizzleTexture(out_luma, luma_scratch, BytesPerPixel, out_luma_width, out_luma_height, 1, block_height, 0, 1); } } break; - case BLK_KIND::PITCH: { + case BlkKind::Pitch: { LOG_TRACE(HW_GPU, "Writing ABGR pitch frame\n" - "\tinput surface {}x{} stride {} size {:#X}" - "\toutput surface {}x{} stride {} size {:#X}", + "\tinput surface {}x{} stride {} size {:#x}" + "\toutput surface {}x{} stride {} size {:#x}", surface_width, surface_height, surface_stride, surface_stride * surface_height * BytesPerPixel, out_luma_width, out_luma_height, out_luma_stride, out_luma_size); diff --git a/src/video_core/host1x/vic.h b/src/video_core/host1x/vic.h index d728b38f91..1a580474b5 100644 --- a/src/video_core/host1x/vic.h +++ b/src/video_core/host1x/vic.h @@ -6,16 +6,13 @@ #pragma once -#include -#include #include -#include -#include +#include #include "common/common_types.h" #include "common/scratch_buffer.h" #include "video_core/cdma_pusher.h" -#include "video_core/host1x/host1x.h" +#include "video_core/host1x/ffmpeg.h" namespace Tegra::Host1x { class Host1x; @@ -138,52 +135,53 @@ enum SurfaceIndex : u32 { CombinedMotion = 7, }; -enum class DXVAHD_ALPHA_FILL_MODE : u32 { - OPAQUE = 0, - BACKGROUND = 1, - DESTINATION = 2, - SOURCE_STREAM = 3, - COMPOSITED = 4, - SOURCE_ALPHA = 5, +// Note: these will inevitably collide with Win32 defines if you use their UPPER_SNAKE_CASE naming +enum class DxvhadAlphaFillMode : u32 { + Opaque = 0, + Background = 1, + Destination = 2, + SourceStream = 3, + Composited = 4, + SourceAlpha = 5, }; -enum class DXVAHD_FRAME_FORMAT : u64 { - PROGRESSIVE = 0, - INTERLACED_TOP_FIELD_FIRST = 1, - INTERLACED_BOTTOM_FIELD_FIRST = 2, - TOP_FIELD = 3, - BOTTOM_FIELD = 4, - SUBPIC_PROGRESSIVE = 5, - SUBPIC_INTERLACED_TOP_FIELD_FIRST = 6, - SUBPIC_INTERLACED_BOTTOM_FIELD_FIRST = 7, - SUBPIC_TOP_FIELD = 8, - SUBPIC_BOTTOM_FIELD = 9, - TOP_FIELD_CHROMA_BOTTOM = 10, - BOTTOM_FIELD_CHROMA_TOP = 11, - SUBPIC_TOP_FIELD_CHROMA_BOTTOM = 12, - SUBPIC_BOTTOM_FIELD_CHROMA_TOP = 13, +enum class DxvhadFrameFormat : u64 { + Progressive = 0, + InterlacedTopFieldFirst = 1, + InterlacedBottomFieldFirst = 2, + TopField = 3, + BottomField = 4, + SubpicProgressive = 5, + SubpicInterlacedTopFieldFirst = 6, + SubpicInterlacedBottomFieldFirst = 7, + SubpicTopField = 8, + SubpicBottomField = 9, + TopFieldChromaBottom = 10, + BottomFieldChromaTop = 11, + SubpicTopFieldChromaBottom = 12, + SubpicBottomFieldChromaTop = 13, }; -enum class DXVAHD_DEINTERLACE_MODE_PRIVATE : u64 { - WEAVE = 0, - BOB_FIELD = 1, - BOB = 2, - NEWBOB = 3, - DISI1 = 4, - WEAVE_LUMA_BOB_FIELD_CHROMA = 5, - MAX = 0xF, +enum class DxvhadDeinterlaceModePrivate : u64 { + Weave = 0, + BobField = 1, + Bob = 2, + Newbob = 3, + Disi1 = 4, + WeaveLumaBobFieldChroma = 5, + Max = 0xF, }; -enum class BLK_KIND { - PITCH = 0, - GENERIC_16Bx2 = 1, +enum class BlkKind { + Pitch = 0, + Generic_16Bx2 = 1, // These are unsupported in the vic - BL_NAIVE = 2, - BL_KEPLER_XBAR_RAW = 3, - VP2_TILED = 15, + BlNaive = 2, + BlKeplerXbarRaw = 3, + Vp2Tiled = 15, }; -enum class BLEND_SRCFACTC : u32 { +enum class BlendSrcFactC : u32 { K1 = 0, K1_TIMES_DST = 1, NEG_K1_TIMES_DST = 2, @@ -191,7 +189,7 @@ enum class BLEND_SRCFACTC : u32 { ZERO = 4, }; -enum class BLEND_DSTFACTC : u32 { +enum class BlendDstFactC : u32 { K1 = 0, K2 = 1, K1_TIMES_DST = 2, @@ -201,7 +199,7 @@ enum class BLEND_DSTFACTC : u32 { ONE = 6, }; -enum class BLEND_SRCFACTA : u32 { +enum class BlendSrcFactA : u32 { K1 = 0, K2 = 1, NEG_K1_TIMES_DST = 2, @@ -209,7 +207,7 @@ enum class BLEND_SRCFACTA : u32 { MAX = 7, }; -enum class BLEND_DSTFACTA : u32 { +enum class BlendDstFactA : u32 { K2 = 0, NEG_K1_TIMES_SRC = 1, ZERO = 2, @@ -232,7 +230,7 @@ static_assert(sizeof(PipeConfig) == 0x10, "PipeConfig has the wrong size!"); struct OutputConfig { union { - BitField<0, 3, DXVAHD_ALPHA_FILL_MODE> alpha_fill_mode; + BitField<0, 3, DxvhadAlphaFillMode> alpha_fill_mode; BitField<3, 3, u64> alpha_fill_slot; BitField<6, 10, u64> background_a; BitField<16, 10, u64> background_r; @@ -265,7 +263,7 @@ struct OutputSurfaceConfig { BitField<0, 7, VideoPixelFormat> out_pixel_format; BitField<7, 2, u32> out_chroma_loc_horiz; BitField<9, 2, u32> out_chroma_loc_vert; - BitField<11, 4, BLK_KIND> out_block_kind; + BitField<11, 4, BlkKind> out_block_kind; BitField<15, 4, u32> out_block_height; // in gobs, log2 BitField<19, 3, u32> reserved0; BitField<22, 10, u32> reserved1; @@ -365,7 +363,7 @@ struct SlotConfig { BitField<14, 1, u64> prev_prev_motion_field_enable; BitField<15, 1, u64> combined_motion_field_enable; - BitField<16, 4, DXVAHD_FRAME_FORMAT> frame_format; + BitField<16, 4, DxvhadFrameFormat> frame_format; BitField<20, 2, u64> filter_length_y; // 0: 1-tap, 1: 2-tap, 2: 5-tap, 3: 10-tap BitField<22, 2, u64> filter_length_x; BitField<24, 12, u64> panoramic; @@ -377,7 +375,7 @@ struct SlotConfig { BitField<10, 10, u64> filter_detail; BitField<20, 10, u64> chroma_noise; BitField<30, 10, u64> chroma_detail; - BitField<40, 4, DXVAHD_DEINTERLACE_MODE_PRIVATE> deinterlace_mode; + BitField<40, 4, DxvhadDeinterlaceModePrivate> deinterlace_mode; BitField<44, 3, u64> motion_accumulation_weight; BitField<47, 11, u64> noise_iir; BitField<58, 4, u64> light_level; @@ -484,13 +482,13 @@ struct BlendingSlotStruct { BitField<26, 6, u32> reserved1; }; union { - BitField<0, 3, BLEND_SRCFACTC> src_factor_color_match_select; + BitField<0, 3, BlendSrcFactC> src_factor_color_match_select; BitField<3, 1, u32> reserved2; - BitField<4, 3, BLEND_DSTFACTC> dst_factor_color_match_select; + BitField<4, 3, BlendDstFactC> dst_factor_color_match_select; BitField<7, 1, u32> reserved3; - BitField<8, 3, BLEND_SRCFACTA> src_factor_a_match_select; + BitField<8, 3, BlendSrcFactA> src_factor_a_match_select; BitField<11, 1, u32> reserved4; - BitField<12, 3, BLEND_DSTFACTA> dst_factor_a_match_select; + BitField<12, 3, BlendDstFactA> dst_factor_a_match_select; BitField<15, 1, u32> reserved5; BitField<16, 4, u32> reserved6; BitField<20, 4, u32> reserved7; @@ -624,13 +622,14 @@ private: VicRegisters regs{}; Common::ScratchBuffer swizzle_scratch; - Common::ScratchBuffer output_surface; - Common::ScratchBuffer slot_surface; + Common::ScratchBuffer output_surface; + Common::ScratchBuffer slot_surface; Common::ScratchBuffer luma_scratch; Common::ScratchBuffer chroma_scratch; s32 id; s32 nvdec_id{-1}; + bool has_decoded_frame{}; u32 syncpoint; }; diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index 60b399ccba..c4e7ac3f6f 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -17,6 +17,10 @@ set(SHADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/astc_decoder.comp ${CMAKE_CURRENT_SOURCE_DIR}/blit_color_float.frag ${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d.comp + ${CMAKE_CURRENT_SOURCE_DIR}/blit_color_msaa.frag + ${CMAKE_CURRENT_SOURCE_DIR}/blit_depth.frag + ${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_msaa.frag + ${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_stencil_msaa.frag ${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d.comp ${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_bcn.comp ${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d24s8.frag @@ -26,7 +30,13 @@ set(SHADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/convert_depth_to_float.frag ${CMAKE_CURRENT_SOURCE_DIR}/convert_float_to_depth.frag ${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa.comp + ${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa.frag + ${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa_depth.frag + ${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa_depth_stencil.frag ${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.comp + ${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.frag + ${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa_depth.frag + ${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa_depth_stencil.frag ${CMAKE_CURRENT_SOURCE_DIR}/convert_s8d24_to_abgr8.frag ${CMAKE_CURRENT_SOURCE_DIR}/full_screen_triangle.vert ${CMAKE_CURRENT_SOURCE_DIR}/fxaa.frag @@ -76,9 +86,14 @@ set(SHADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/vulkan_quad_indexed.comp ${CMAKE_CURRENT_SOURCE_DIR}/vulkan_turbo_mode.comp ${CMAKE_CURRENT_SOURCE_DIR}/vulkan_uint8.comp + + # Snapdragon Game Super Resolution + ${CMAKE_CURRENT_SOURCE_DIR}/sgsr1_shader.vert + ${CMAKE_CURRENT_SOURCE_DIR}/sgsr1_shader_mobile.frag + ${CMAKE_CURRENT_SOURCE_DIR}/sgsr1_shader_mobile_edge_direction.frag ) -if (PLATFORM_HAIKU) +if (HAIKUOS) # glslangValidator WILL crash, glslang will not set(GLSLANGVALIDATOR "glslang") else() @@ -90,7 +105,7 @@ if ("${GLSLANGVALIDATOR}" STREQUAL "GLSLANGVALIDATOR-NOTFOUND") message(FATAL_ERROR "Required program `glslangValidator` not found.") endif() -set(GLSL_FLAGS "") +set(GLSL_FLAGS "-DUseUniformBlock=1") set(SPIR_V_VERSION "spirv1.3") set(QUIET_FLAG "--quiet") @@ -153,6 +168,44 @@ foreach(SOURCE_FILE IN ITEMS ${SHADER_FILES}) endif() endforeach() +# Integer variants of the MSAA conversion shaders. They only differ from the float +# source in the sampler and output types, so they are generated from it via defines. +set(SHADER_TYPE_VARIANTS + "convert_msaa_to_non_msaa.frag|sint|isampler2DMS|ivec4" + "convert_msaa_to_non_msaa.frag|uint|usampler2DMS|uvec4" + "convert_non_msaa_to_msaa.frag|sint|isampler2D|ivec4" + "convert_non_msaa_to_msaa.frag|uint|usampler2D|uvec4" +) + +foreach(VARIANT IN ITEMS ${SHADER_TYPE_VARIANTS}) + string(REPLACE "|" ";" VARIANT_PARTS ${VARIANT}) + list(GET VARIANT_PARTS 0 VARIANT_FILENAME) + list(GET VARIANT_PARTS 1 VARIANT_SUFFIX) + list(GET VARIANT_PARTS 2 VARIANT_SAMPLER) + list(GET VARIANT_PARTS 3 VARIANT_TEXEL) + + set(VARIANT_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/${VARIANT_FILENAME}) + get_filename_component(VARIANT_STEM ${VARIANT_FILENAME} NAME_WE) + get_filename_component(VARIANT_EXT ${VARIANT_FILENAME} EXT) + string(REPLACE "." "" VARIANT_EXT ${VARIANT_EXT}) + set(VARIANT_NAME ${VARIANT_STEM}_${VARIANT_SUFFIX}_${VARIANT_EXT}) + + string(TOUPPER ${VARIANT_NAME}_SPV VARIANT_VARIABLE_NAME) + set(VARIANT_HEADER_FILE ${SHADER_DIR}/${VARIANT_NAME}_spv.h) + add_custom_command( + OUTPUT + ${VARIANT_HEADER_FILE} + COMMAND + ${GLSLANGVALIDATOR} -V ${QUIET_FLAG} -I"${FIDELITYFX_INCLUDE_DIR}" ${GLSL_FLAGS} + -DSAMPLER_TYPE=${VARIANT_SAMPLER} -DTEXEL_TYPE=${VARIANT_TEXEL} + --variable-name ${VARIANT_VARIABLE_NAME} -o ${VARIANT_HEADER_FILE} ${VARIANT_SOURCE} + --target-env ${SPIR_V_VERSION} + MAIN_DEPENDENCY + ${VARIANT_SOURCE} + ) + set(SHADER_HEADERS ${SHADER_HEADERS} ${VARIANT_HEADER_FILE}) +endforeach() + foreach(FILEPATH IN ITEMS ${FIDELITYFX_FILES}) get_filename_component(FILENAME ${FILEPATH} NAME) string(REPLACE "." "_" HEADER_NAME ${FILENAME}) diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index da21b4bde8..e059c683ff 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -40,7 +43,11 @@ layout(binding = BINDING_INPUT_BUFFER, std430) readonly restrict buffer InputBuf uvec4 astc_data[]; }; +#ifdef VULKAN +layout(binding = BINDING_OUTPUT_IMAGE) uniform writeonly restrict image2DArray dest_image; +#else layout(binding = BINDING_OUTPUT_IMAGE, rgba8) uniform writeonly restrict image2DArray dest_image; +#endif const uint GOB_SIZE_X_SHIFT = 6; const uint GOB_SIZE_Y_SHIFT = 3; @@ -70,14 +77,8 @@ uvec4 local_buff; uvec4 color_endpoint_data; int color_bitsread = 0; -// Global "vector" to be pushed into when decoding -// At most will require BLOCK_WIDTH x BLOCK_HEIGHT in single plane mode -// At most will require BLOCK_WIDTH x BLOCK_HEIGHT x 2 in dual plane mode -// So the maximum would be 144 (12 x 12) elements, x 2 for two planes -#define DIVCEIL(number, divisor) (number + divisor - 1) / divisor -#define ARRAY_NUM_ELEMENTS 144 -#define VECTOR_ARRAY_SIZE DIVCEIL(ARRAY_NUM_ELEMENTS * 2, 4) -uint result_vector[ARRAY_NUM_ELEMENTS * 2]; +#define MAX_WEIGHT_VALUES 64 +uint result_vector[MAX_WEIGHT_VALUES]; int result_index = 0; uint result_vector_max_index; @@ -485,7 +486,7 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits, o A = ReplicateBitTo9((bitval & 1)); switch (encoding) { case JUST_BITS: - color_values[++out_index] = FastReplicateTo8(bitval, bitlen); + color_values[out_index++] = FastReplicateTo8(bitval, bitlen); break; case TRIT: { D = QuintTritValue(val); @@ -564,7 +565,7 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits, o uint T = (D * C) + B; T ^= A; T = (A & 0x80) | (T >> 2); - color_values[++out_index] = T; + color_values[out_index++] = T; } } } @@ -589,17 +590,169 @@ ivec4 BlueContract(int a, int r, int g, int b) { return ivec4(a, (r + b) >> 1, (g + b) >> 1, b); } +bool IsHDRColorEndpointMode(uint cem) { + return cem == 2u || cem == 3u || cem == 7u || cem == 11u || cem == 14u || cem == 15u; +} + +int SignExtend(int value, uint nbits) { + int sign_bit = 1 << (nbits - 1u); + return (value ^ sign_bit) - sign_bit; +} + +void DecodeHDREndpointMode7(uint v0, uint v1, uint v2, uint v3, out ivec3 e0, out ivec3 e1) { + uint modeval = ((v0 & 0xC0u) >> 6u) | ((v1 & 0x80u) >> 5u) | ((v2 & 0x80u) >> 4u); + + uint majcomp; + uint mode; + if ((modeval & 0xCu) != 0xCu) { + majcomp = modeval >> 2u; + mode = modeval & 3u; + } else if (modeval != 0xFu) { + majcomp = modeval & 3u; + mode = 4u; + } else { + majcomp = 0u; + mode = 5u; + } + + int red = int(v0 & 0x3Fu); + int green = int(v1 & 0x1Fu); + int blue = int(v2 & 0x1Fu); + int scale = int(v3 & 0x1Fu); + + uint x0 = (v1 >> 6u) & 1u; + uint x1 = (v1 >> 5u) & 1u; + uint x2 = (v2 >> 6u) & 1u; + uint x3 = (v2 >> 5u) & 1u; + uint x4 = (v3 >> 7u) & 1u; + uint x5 = (v3 >> 6u) & 1u; + uint x6 = (v3 >> 5u) & 1u; + + uint ohm = 1u << mode; + if ((ohm & 0x30u) != 0u) green |= int(x0 << 6u); + if ((ohm & 0x3Au) != 0u) green |= int(x1 << 5u); + if ((ohm & 0x30u) != 0u) blue |= int(x2 << 6u); + if ((ohm & 0x3Au) != 0u) blue |= int(x3 << 5u); + if ((ohm & 0x3Du) != 0u) scale |= int(x6 << 5u); + if ((ohm & 0x2Du) != 0u) scale |= int(x5 << 6u); + if ((ohm & 0x04u) != 0u) scale |= int(x4 << 7u); + if ((ohm & 0x3Bu) != 0u) red |= int(x4 << 6u); + if ((ohm & 0x04u) != 0u) red |= int(x3 << 6u); + if ((ohm & 0x10u) != 0u) red |= int(x5 << 7u); + if ((ohm & 0x0Fu) != 0u) red |= int(x2 << 7u); + if ((ohm & 0x05u) != 0u) red |= int(x1 << 8u); + if ((ohm & 0x0Au) != 0u) red |= int(x0 << 8u); + if ((ohm & 0x05u) != 0u) red |= int(x0 << 9u); + if ((ohm & 0x02u) != 0u) red |= int(x6 << 9u); + if ((ohm & 0x01u) != 0u) red |= int(x3 << 10u); + if ((ohm & 0x02u) != 0u) red |= int(x5 << 10u); + + int shamts[6] = int[](1, 1, 2, 3, 4, 5); + int shamt = shamts[mode]; + red <<= shamt; + green <<= shamt; + blue <<= shamt; + scale <<= shamt; + + if (mode != 5u) { + green = red - green; + blue = red - blue; + } + + if (majcomp == 1u) { + int t = red; red = green; green = t; + } + if (majcomp == 2u) { + int t = red; red = blue; blue = t; + } + + e1 = ivec3(clamp(red, 0, 0xFFF), clamp(green, 0, 0xFFF), clamp(blue, 0, 0xFFF)); + e0 = ivec3(clamp(red - scale, 0, 0xFFF), clamp(green - scale, 0, 0xFFF), + clamp(blue - scale, 0, 0xFFF)); +} + +void DecodeHDREndpointMode11(uint v0, uint v1, uint v2, uint v3, uint v4, uint v5, out ivec3 e0, + out ivec3 e1) { + uint majcomp = ((v4 & 0x80u) >> 7u) | ((v5 & 0x80u) >> 6u); + if (majcomp == 3u) { + e0 = ivec3(int(v0 << 4u), int(v2 << 4u), int((v4 & 0x7Fu) << 5u)); + e1 = ivec3(int(v1 << 4u), int(v3 << 4u), int((v5 & 0x7Fu) << 5u)); + return; + } + + uint mode = ((v1 & 0x80u) >> 7u) | ((v2 & 0x80u) >> 6u) | ((v3 & 0x80u) >> 5u); + int va = int(v0 | ((v1 & 0x40u) << 2u)); + int vb0 = int(v2 & 0x3Fu); + int vb1 = int(v3 & 0x3Fu); + int vc = int(v1 & 0x3Fu); + int vd0 = int(v4 & 0x7Fu); + int vd1 = int(v5 & 0x7Fu); + + int dbitstab[8] = int[](7, 6, 7, 6, 5, 6, 5, 6); + vd0 = SignExtend(vd0, uint(dbitstab[mode])); + vd1 = SignExtend(vd1, uint(dbitstab[mode])); + + uint x0 = (v2 >> 6u) & 1u; + uint x1 = (v3 >> 6u) & 1u; + uint x2 = (v4 >> 6u) & 1u; + uint x3 = (v5 >> 6u) & 1u; + uint x4 = (v4 >> 5u) & 1u; + uint x5 = (v5 >> 5u) & 1u; + + uint ohm = 1u << mode; + if ((ohm & 0xA4u) != 0u) va |= int(x0 << 9u); + if ((ohm & 0x08u) != 0u) va |= int(x2 << 9u); + if ((ohm & 0x50u) != 0u) va |= int(x4 << 9u); + if ((ohm & 0x50u) != 0u) va |= int(x5 << 10u); + if ((ohm & 0xA0u) != 0u) va |= int(x1 << 10u); + if ((ohm & 0xC0u) != 0u) va |= int(x2 << 11u); + if ((ohm & 0x04u) != 0u) vc |= int(x1 << 6u); + if ((ohm & 0xE8u) != 0u) vc |= int(x3 << 6u); + if ((ohm & 0x20u) != 0u) vc |= int(x2 << 7u); + if ((ohm & 0x5Bu) != 0u) vb0 |= int(x0 << 6u); + if ((ohm & 0x5Bu) != 0u) vb1 |= int(x1 << 6u); + if ((ohm & 0x12u) != 0u) vb0 |= int(x2 << 7u); + if ((ohm & 0x12u) != 0u) vb1 |= int(x3 << 7u); + + int shamt = (int(mode) >> 1) ^ 3; + va <<= shamt; + vb0 <<= shamt; + vb1 <<= shamt; + vc <<= shamt; + vd0 <<= shamt; + vd1 <<= shamt; + + int r1 = clamp(va, 0, 0xFFF); + int g1 = clamp(va - vb0, 0, 0xFFF); + int b1 = clamp(va - vb1, 0, 0xFFF); + int r0 = clamp(va - vc, 0, 0xFFF); + int g0 = clamp(va - vb0 - vc - vd0, 0, 0xFFF); + int b0 = clamp(va - vb1 - vc - vd1, 0, 0xFFF); + + if (majcomp == 1u) { + int t; + t = r0; r0 = g0; g0 = t; + t = r1; r1 = g1; g1 = t; + } else if (majcomp == 2u) { + int t; + t = r0; r0 = b0; b0 = t; + t = r1; r1 = b1; b1 = t; + } + e0 = ivec3(r0, g0, b0); + e1 = ivec3(r1, g1, b1); +} + void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode, uint color_values[32], inout uint colvals_index) { #define READ_UINT_VALUES(N) \ uvec4 V[2]; \ for (uint i = 0; i < N; i++) { \ - V[i / 4][i % 4] = color_values[++colvals_index]; \ + V[i / 4][i % 4] = color_values[colvals_index++]; \ } #define READ_INT_VALUES(N) \ ivec4 V[2]; \ for (uint i = 0; i < N; i++) { \ - V[i / 4][i % 4] = int(color_values[++colvals_index]); \ + V[i / 4][i % 4] = int(color_values[colvals_index++]); \ } switch (color_endpoint_mode) { @@ -715,8 +868,86 @@ void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode, ui } break; } + case 2: { + READ_UINT_VALUES(2) + uint y0, y1; + if (V[0].y >= V[0].x) { + y0 = V[0].x << 4u; + y1 = V[0].y << 4u; + } else { + y0 = (V[0].y << 4u) + 8u; + y1 = (V[0].x << 4u) - 8u; + } + ep1 = uvec4(0x780u, y0, y0, y0); + ep2 = uvec4(0x780u, y1, y1, y1); + break; + } + case 3: { + READ_UINT_VALUES(2) + uint y0, d; + if ((V[0].x & 0x80u) != 0u) { + y0 = ((V[0].y & 0xE0u) << 4u) | ((V[0].x & 0x7Fu) << 2u); + d = (V[0].y & 0x1Fu) << 2u; + } else { + y0 = ((V[0].y & 0xF0u) << 4u) | ((V[0].x & 0x7Fu) << 1u); + d = (V[0].y & 0x0Fu) << 1u; + } + const uint y1 = min(y0 + d, 0xFFFu); + ep1 = uvec4(0x780u, y0, y0, y0); + ep2 = uvec4(0x780u, y1, y1, y1); + break; + } + case 7: { + READ_UINT_VALUES(4) + ivec3 e0, e1; + DecodeHDREndpointMode7(V[0].x, V[0].y, V[0].z, V[0].w, e0, e1); + ep1 = uvec4(0x780u, uint(e0.x), uint(e0.y), uint(e0.z)); + ep2 = uvec4(0x780u, uint(e1.x), uint(e1.y), uint(e1.z)); + break; + } + case 11: { + READ_UINT_VALUES(6) + ivec3 e0, e1; + DecodeHDREndpointMode11(V[0].x, V[0].y, V[0].z, V[0].w, V[1].x, V[1].y, e0, e1); + ep1 = uvec4(0x780u, uint(e0.x), uint(e0.y), uint(e0.z)); + ep2 = uvec4(0x780u, uint(e1.x), uint(e1.y), uint(e1.z)); + break; + } + case 14: { + READ_UINT_VALUES(8) + ivec3 e0, e1; + DecodeHDREndpointMode11(V[0].x, V[0].y, V[0].z, V[0].w, V[1].x, V[1].y, e0, e1); + ep1 = uvec4(V[1].z, uint(e0.x), uint(e0.y), uint(e0.z)); + ep2 = uvec4(V[1].w, uint(e1.x), uint(e1.y), uint(e1.z)); + break; + } + case 15: { + READ_UINT_VALUES(8) + ivec3 e0, e1; + DecodeHDREndpointMode11(V[0].x, V[0].y, V[0].z, V[0].w, V[1].x, V[1].y, e0, e1); + const uint mode = ((V[1].z >> 7u) & 1u) | ((V[1].w >> 6u) & 2u); + int a6 = int(V[1].z & 0x7Fu); + int a7 = int(V[1].w & 0x7Fu); + int alpha0, alpha1; + if (mode == 3u) { + alpha0 = a6 << 5; + alpha1 = a7 << 5; + } else { + a6 |= (a7 << int(mode + 1u)) & 0x780; + a7 &= int(0x3Fu >> mode); + a7 ^= int(0x20u >> mode); + a7 -= int(0x20u >> mode); + a6 <<= int(4u - mode); + a7 <<= int(4u - mode); + a7 += a6; + alpha0 = a6; + alpha1 = clamp(a7, 0, 0xFFF); + } + ep1 = uvec4(uint(alpha0), uint(e0.x), uint(e0.y), uint(e0.z)); + ep2 = uvec4(uint(alpha1), uint(e1.x), uint(e1.y), uint(e1.z)); + break; + } default: { - // HDR mode, or more likely a bug computing the color_endpoint_mode ep1 = uvec4(0xFF, 0xFF, 0, 0); ep2 = uvec4(0xFF, 0xFF, 0, 0); break; @@ -988,6 +1219,10 @@ void DecompressBlock(ivec3 coord) { FillError(coord); return; } + if (GetNumWeightValues(size_params, dual_plane) > MAX_WEIGHT_VALUES) { + FillError(coord); + return; + } uint partition_index = 1; uvec4 color_endpoint_mode = uvec4(0); uint ced_pointer = 0; @@ -1002,6 +1237,10 @@ void DecompressBlock(ivec3 coord) { const uint base_mode = base_cem & 3; const uint max_weight = DecodeMaxWeight(mode); const uint weight_bits = GetPackedBitSize(size_params, dual_plane, max_weight); + if (weight_bits < 24 || weight_bits > 96) { + FillError(coord); + return; + } uint remaining_bits = 128 - weight_bits - total_bitsread; uint extra_cem_bits = 0; if (base_mode > 0) { @@ -1016,6 +1255,7 @@ void DecompressBlock(ivec3 coord) { extra_cem_bits += 8; break; default: + FillError(coord); return; } } @@ -1025,6 +1265,7 @@ void DecompressBlock(ivec3 coord) { if (remaining_bits > 128) { // Bad data, more remaining bits than 4 bytes // return early + FillError(coord); return; } // Read color data... @@ -1112,13 +1353,42 @@ void DecompressBlock(ivec3 coord) { if (num_partitions > 1) { local_partition = Select2DPartition(partition_index, i, j, num_partitions); } - const uvec4 C0 = ReplicateByteTo16(endpoints0[local_partition]); - const uvec4 C1 = ReplicateByteTo16(endpoints1[local_partition]); + const uint local_cem = color_endpoint_mode[local_partition]; const uvec4 weight_vec = GetUnquantizedWeightVector(j, i, size_params, plane_index, dual_plane); - const vec4 Cf = - vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64); - const vec4 p = (Cf / 65535.0f); - imageStore(dest_image, coord + ivec3(i, j, 0), p.gbar); + + vec4 p; + if (IsHDRColorEndpointMode(local_cem)) { + const uvec4 C0 = endpoints0[local_partition] << 4u; + const uvec4 C1 = endpoints1[local_partition] << 4u; + const uvec4 C = (C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64u; + const uvec4 E = (C & uvec4(0xF800u)) >> 11u; + const uvec4 M = C & uvec4(0x7FFu); + const uvec4 Mt_lo = 3u * M; + const uvec4 Mt_mid = 4u * M - 512u; + const uvec4 Mt_hi = 5u * M - 2048u; + const uvec4 Mt = + mix(Mt_lo, mix(Mt_mid, Mt_hi, greaterThanEqual(M, uvec4(1536u))), + greaterThanEqual(M, uvec4(512u))); + const uvec4 Cf = (E << 10u) + (Mt >> 3u); + const uvec4 half_bits = mix(Cf, uvec4(0x7BFFu), greaterThanEqual(Cf, uvec4(0x7C00u))); + p = vec4(unpackHalf2x16(half_bits.x).x, unpackHalf2x16(half_bits.y).x, + unpackHalf2x16(half_bits.z).x, unpackHalf2x16(half_bits.w).x); + + if (local_cem == 14u) { + const uint a0 = ReplicateByteTo16(uvec4(endpoints0[local_partition].x)).x; + const uint a1 = ReplicateByteTo16(uvec4(endpoints1[local_partition].x)).x; + const uint Ca = (a0 * (64u - weight_vec.x) + a1 * weight_vec.x + 32u) / 64u; + p.x = float(Ca) / 65535.0f; + } + } else { + const uvec4 C0 = ReplicateByteTo16(endpoints0[local_partition]); + const uvec4 C1 = ReplicateByteTo16(endpoints1[local_partition]); + const vec4 Cf = + vec4((C0 * (uvec4(64) - weight_vec) + C1 * weight_vec + uvec4(32)) / 64); + p = Cf / 65535.0f; + } + + imageStore(dest_image, coord + ivec3(i, j, 0), clamp(p, 0.0f, 1.0f).gbar); } } } diff --git a/src/video_core/host_shaders/blit_color_msaa.frag b/src/video_core/host_shaders/blit_color_msaa.frag new file mode 100644 index 0000000000..b25d0342c3 --- /dev/null +++ b/src/video_core/host_shaders/blit_color_msaa.frag @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 450 core + +layout(binding = 0) uniform sampler2DMS tex; + +layout(location = 0) in vec2 texcoord; +layout(location = 0) out vec4 color; + +void main() { + color = texelFetch(tex, ivec2(texcoord), gl_SampleID); +} diff --git a/src/video_core/host_shaders/blit_depth.frag b/src/video_core/host_shaders/blit_depth.frag new file mode 100644 index 0000000000..9e19c767aa --- /dev/null +++ b/src/video_core/host_shaders/blit_depth.frag @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 450 core + +layout(binding = 0) uniform sampler2D depth_tex; + +layout(location = 0) in vec2 texcoord; + +void main() { + gl_FragDepth = textureLod(depth_tex, texcoord, 0).r; +} diff --git a/src/video_core/host_shaders/blit_depth_msaa.frag b/src/video_core/host_shaders/blit_depth_msaa.frag new file mode 100644 index 0000000000..9e955d471e --- /dev/null +++ b/src/video_core/host_shaders/blit_depth_msaa.frag @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 450 core + +layout(binding = 0) uniform sampler2DMS depth_tex; + +layout(location = 0) in vec2 texcoord; + +void main() { + gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), gl_SampleID).r; +} diff --git a/src/video_core/host_shaders/blit_depth_stencil_msaa.frag b/src/video_core/host_shaders/blit_depth_stencil_msaa.frag new file mode 100644 index 0000000000..7d4555bfd4 --- /dev/null +++ b/src/video_core/host_shaders/blit_depth_stencil_msaa.frag @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 450 core +#extension GL_ARB_shader_stencil_export : require + +layout(binding = 0) uniform sampler2DMS depth_tex; +layout(binding = 1) uniform usampler2DMS stencil_tex; + +layout(location = 0) in vec2 texcoord; + +void main() { + gl_FragDepth = texelFetch(depth_tex, ivec2(texcoord), gl_SampleID).r; + gl_FragStencilRefARB = int(texelFetch(stencil_tex, ivec2(texcoord), gl_SampleID).r); +} diff --git a/src/video_core/host_shaders/block_linear_unswizzle_2d.comp b/src/video_core/host_shaders/block_linear_unswizzle_2d.comp index 121f60cbd1..2a3446ca66 100644 --- a/src/video_core/host_shaders/block_linear_unswizzle_2d.comp +++ b/src/video_core/host_shaders/block_linear_unswizzle_2d.comp @@ -11,9 +11,8 @@ #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { #define END_PUSH_CONSTANTS }; #define UNIFORM(n) -#define BINDING_SWIZZLE_BUFFER 0 -#define BINDING_INPUT_BUFFER 1 -#define BINDING_OUTPUT_IMAGE 2 +#define BINDING_INPUT_BUFFER 0 +#define BINDING_OUTPUT_IMAGE 1 #else // ^^^ Vulkan ^^^ // vvv OpenGL vvv @@ -26,8 +25,7 @@ #define BEGIN_PUSH_CONSTANTS #define END_PUSH_CONSTANTS #define UNIFORM(n) layout (location = n) uniform -#define BINDING_SWIZZLE_BUFFER 0 -#define BINDING_INPUT_BUFFER 1 +#define BINDING_INPUT_BUFFER 0 #define BINDING_OUTPUT_IMAGE 0 #endif @@ -43,10 +41,6 @@ UNIFORM(6) uint block_height; UNIFORM(7) uint block_height_mask; END_PUSH_CONSTANTS -layout(binding = BINDING_SWIZZLE_BUFFER, std430) readonly buffer SwizzleTable { - uint swizzle_table[]; -}; - #if HAS_EXTENDED_TYPES layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; }; layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; }; @@ -71,9 +65,19 @@ const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHI const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1, GOB_SIZE_Y - 1); +uint SwizzleTable(uint pos) { + const uint t[8] = uint[]( + 0x12100200, 0x13110301, 0x16140604, 0x17150705, + 0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d + ); + const uint i = pos >> 4; + const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff; + return (h << 4) | (pos & 0xf); +} + uint SwizzleOffset(uvec2 pos) { pos = pos & SWIZZLE_MASK; - return swizzle_table[pos.y * 64 + pos.x]; + return SwizzleTable(pos.y * 64 + pos.x); } uvec4 ReadTexel(uint offset) { diff --git a/src/video_core/host_shaders/block_linear_unswizzle_3d.comp b/src/video_core/host_shaders/block_linear_unswizzle_3d.comp index 7fc98accb9..34e4f93307 100644 --- a/src/video_core/host_shaders/block_linear_unswizzle_3d.comp +++ b/src/video_core/host_shaders/block_linear_unswizzle_3d.comp @@ -11,9 +11,8 @@ #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { #define END_PUSH_CONSTANTS }; #define UNIFORM(n) -#define BINDING_SWIZZLE_BUFFER 0 -#define BINDING_INPUT_BUFFER 1 -#define BINDING_OUTPUT_IMAGE 2 +#define BINDING_INPUT_BUFFER 0 +#define BINDING_OUTPUT_IMAGE 1 #else // ^^^ Vulkan ^^^ // vvv OpenGL vvv @@ -26,8 +25,7 @@ #define BEGIN_PUSH_CONSTANTS #define END_PUSH_CONSTANTS #define UNIFORM(n) layout (location = n) uniform -#define BINDING_SWIZZLE_BUFFER 0 -#define BINDING_INPUT_BUFFER 1 +#define BINDING_INPUT_BUFFER 0 #define BINDING_OUTPUT_IMAGE 0 #endif @@ -45,10 +43,6 @@ UNIFORM(8) uint block_depth; UNIFORM(9) uint block_depth_mask; END_PUSH_CONSTANTS -layout(binding = BINDING_SWIZZLE_BUFFER, std430) readonly buffer SwizzleTable { - uint swizzle_table[]; -}; - #if HAS_EXTENDED_TYPES layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; }; layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; }; @@ -73,9 +67,19 @@ const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHI const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1, GOB_SIZE_Y - 1); +uint SwizzleTable(uint pos) { + const uint t[8] = uint[]( + 0x12100200, 0x13110301, 0x16140604, 0x17150705, + 0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d + ); + const uint i = pos >> 4; + const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff; + return (h << 4) | (pos & 0xf); +} + uint SwizzleOffset(uvec2 pos) { pos = pos & SWIZZLE_MASK; - return swizzle_table[pos.y * 64 + pos.x]; + return SwizzleTable(pos.y * 64 + pos.x); } uvec4 ReadTexel(uint offset) { diff --git a/src/video_core/host_shaders/block_linear_unswizzle_3d_bcn.comp b/src/video_core/host_shaders/block_linear_unswizzle_3d_bcn.comp index 6e5a300ecd..e084837f25 100644 --- a/src/video_core/host_shaders/block_linear_unswizzle_3d_bcn.comp +++ b/src/video_core/host_shaders/block_linear_unswizzle_3d_bcn.comp @@ -10,9 +10,8 @@ #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { #define END_PUSH_CONSTANTS }; #define UNIFORM(n) - #define BINDING_SWIZZLE_BUFFER 0 - #define BINDING_INPUT_BUFFER 1 - #define BINDING_OUTPUT_BUFFER 2 + #define BINDING_INPUT_BUFFER 0 + #define BINDING_OUTPUT_BUFFER 1 #else #extension GL_NV_gpu_shader5 : enable #ifdef GL_NV_gpu_shader5 @@ -23,7 +22,6 @@ #define BEGIN_PUSH_CONSTANTS #define END_PUSH_CONSTANTS #define UNIFORM(n) layout(location = n) uniform - #define BINDING_SWIZZLE_BUFFER 0 #define BINDING_INPUT_BUFFER 1 #define BINDING_OUTPUT_BUFFER 0 #endif @@ -66,13 +64,9 @@ END_PUSH_CONSTANTS #endif // --- Buffers --- -layout(binding = BINDING_SWIZZLE_BUFFER, std430) readonly buffer SwizzleTable { - uint swizzle_table[]; -}; - #if HAS_EXTENDED_TYPES - layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; }; - layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; }; +layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU8 { uint8_t u8data[]; }; +layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU16 { uint16_t u16data[]; }; #endif layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; }; layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; }; @@ -96,10 +90,20 @@ const uint GOB_SIZE_Z_SHIFT = 0; const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_SHIFT; const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1u, GOB_SIZE_Y - 1u); +uint SwizzleTable(uint pos) { + const uint t[8] = uint[]( + 0x12100200, 0x13110301, 0x16140604, 0x17150705, + 0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d + ); + const uint i = pos >> 4; + const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff; + return (h << 4) | (pos & 0xf); +} + // --- Helpers --- uint SwizzleOffset(uvec2 pos) { pos &= SWIZZLE_MASK; - return swizzle_table[pos.y * 64u + pos.x]; + return SwizzleTable(pos.y * 64u + pos.x); } uvec4 ReadTexel(uint offset) { diff --git a/src/video_core/host_shaders/convert_msaa_to_non_msaa.frag b/src/video_core/host_shaders/convert_msaa_to_non_msaa.frag new file mode 100644 index 0000000000..8f7ec34d1a --- /dev/null +++ b/src/video_core/host_shaders/convert_msaa_to_non_msaa.frag @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 450 core + +#ifndef SAMPLER_TYPE +#define SAMPLER_TYPE sampler2DMS +#endif +#ifndef TEXEL_TYPE +#define TEXEL_TYPE vec4 +#endif + +layout(binding = 0) uniform SAMPLER_TYPE msaa_in; + +layout(push_constant) uniform PushConstants { + ivec2 dst_offset; + ivec2 src_offset; + ivec2 scale; +}; + +layout(location = 0) out TEXEL_TYPE frag_color; + +void main() { + const ivec2 coord = ivec2(gl_FragCoord.xy) - dst_offset + src_offset; + const ivec2 msaa_coord = coord / scale; + const ivec2 sample_offset = coord % scale; + const int sample_id = sample_offset.x + scale.x * sample_offset.y; + frag_color = texelFetch(msaa_in, msaa_coord, sample_id); +} diff --git a/src/video_core/host_shaders/convert_msaa_to_non_msaa_depth.frag b/src/video_core/host_shaders/convert_msaa_to_non_msaa_depth.frag new file mode 100644 index 0000000000..26fdd48b36 --- /dev/null +++ b/src/video_core/host_shaders/convert_msaa_to_non_msaa_depth.frag @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 450 core + +layout(binding = 0) uniform sampler2DMS msaa_in; + +layout(push_constant) uniform PushConstants { + ivec2 dst_offset; + ivec2 src_offset; + ivec2 scale; +}; + +void main() { + const ivec2 coord = ivec2(gl_FragCoord.xy) - dst_offset + src_offset; + const ivec2 msaa_coord = coord / scale; + const ivec2 sample_offset = coord % scale; + const int sample_id = sample_offset.x + scale.x * sample_offset.y; + gl_FragDepth = texelFetch(msaa_in, msaa_coord, sample_id).r; +} diff --git a/src/video_core/host_shaders/convert_msaa_to_non_msaa_depth_stencil.frag b/src/video_core/host_shaders/convert_msaa_to_non_msaa_depth_stencil.frag new file mode 100644 index 0000000000..cd34a1a025 --- /dev/null +++ b/src/video_core/host_shaders/convert_msaa_to_non_msaa_depth_stencil.frag @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 450 core +#extension GL_ARB_shader_stencil_export : require + +layout(binding = 0) uniform sampler2DMS depth_tex; +layout(binding = 1) uniform usampler2DMS stencil_tex; + +layout(push_constant) uniform PushConstants { + ivec2 dst_offset; + ivec2 src_offset; + ivec2 scale; +}; + +void main() { + const ivec2 coord = ivec2(gl_FragCoord.xy) - dst_offset + src_offset; + const ivec2 msaa_coord = coord / scale; + const ivec2 sample_offset = coord % scale; + const int sample_id = sample_offset.x + scale.x * sample_offset.y; + gl_FragDepth = texelFetch(depth_tex, msaa_coord, sample_id).r; + gl_FragStencilRefARB = int(texelFetch(stencil_tex, msaa_coord, sample_id).r); +} diff --git a/src/video_core/host_shaders/convert_non_msaa_to_msaa.frag b/src/video_core/host_shaders/convert_non_msaa_to_msaa.frag new file mode 100644 index 0000000000..b5a93068a9 --- /dev/null +++ b/src/video_core/host_shaders/convert_non_msaa_to_msaa.frag @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 450 core + +#ifndef SAMPLER_TYPE +#define SAMPLER_TYPE sampler2D +#endif +#ifndef TEXEL_TYPE +#define TEXEL_TYPE vec4 +#endif + +layout(binding = 0) uniform SAMPLER_TYPE img_in; + +layout(push_constant) uniform PushConstants { + ivec2 dst_offset; + ivec2 src_offset; + ivec2 scale; +}; + +layout(location = 0) out TEXEL_TYPE frag_color; + +void main() { + const ivec2 msaa_coord = ivec2(gl_FragCoord.xy) - dst_offset; + const ivec2 sample_offset = ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x); + const ivec2 coord = msaa_coord * scale + sample_offset + src_offset; + frag_color = texelFetch(img_in, coord, 0); +} diff --git a/src/video_core/host_shaders/convert_non_msaa_to_msaa_depth.frag b/src/video_core/host_shaders/convert_non_msaa_to_msaa_depth.frag new file mode 100644 index 0000000000..f883b3196f --- /dev/null +++ b/src/video_core/host_shaders/convert_non_msaa_to_msaa_depth.frag @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 450 core + +layout(binding = 0) uniform sampler2D img_in; + +layout(push_constant) uniform PushConstants { + ivec2 dst_offset; + ivec2 src_offset; + ivec2 scale; +}; + +void main() { + const ivec2 msaa_coord = ivec2(gl_FragCoord.xy) - dst_offset; + const ivec2 sample_offset = ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x); + const ivec2 coord = msaa_coord * scale + sample_offset + src_offset; + gl_FragDepth = texelFetch(img_in, coord, 0).r; +} diff --git a/src/video_core/host_shaders/convert_non_msaa_to_msaa_depth_stencil.frag b/src/video_core/host_shaders/convert_non_msaa_to_msaa_depth_stencil.frag new file mode 100644 index 0000000000..d9a308f5c3 --- /dev/null +++ b/src/video_core/host_shaders/convert_non_msaa_to_msaa_depth_stencil.frag @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 450 core +#extension GL_ARB_shader_stencil_export : require + +layout(binding = 0) uniform sampler2D depth_tex; +layout(binding = 1) uniform usampler2D stencil_tex; + +layout(push_constant) uniform PushConstants { + ivec2 dst_offset; + ivec2 src_offset; + ivec2 scale; +}; + +void main() { + const ivec2 msaa_coord = ivec2(gl_FragCoord.xy) - dst_offset; + const ivec2 sample_offset = ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x); + const ivec2 coord = msaa_coord * scale + sample_offset + src_offset; + gl_FragDepth = texelFetch(depth_tex, coord, 0).r; + gl_FragStencilRefARB = int(texelFetch(stencil_tex, coord, 0).r); +} diff --git a/src/video_core/host_shaders/sgsr1_shader.vert b/src/video_core/host_shaders/sgsr1_shader.vert new file mode 100644 index 0000000000..e995997060 --- /dev/null +++ b/src/video_core/host_shaders/sgsr1_shader.vert @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#version 450 + +layout(push_constant) uniform constants { + vec2 scale; + vec2 size; + vec2 resize_factor; + vec2 crop_offset; + float edge_sharpness; +}; +layout(location = 0) out highp vec2 texcoord; + +void main() { + float x = float((gl_VertexIndex & 1) << 2); + float y = float((gl_VertexIndex & 2) << 1); + gl_Position = vec4(x - 1.0f, y - 1.0f, 0.0, 1.0f) * vec4(sign(resize_factor), 1.f, 1.f); + texcoord = crop_offset + vec2(x, y) * abs(resize_factor) * 0.5; +} diff --git a/src/video_core/host_shaders/sgsr1_shader_mobile.frag b/src/video_core/host_shaders/sgsr1_shader_mobile.frag new file mode 100644 index 0000000000..3453ca2cae --- /dev/null +++ b/src/video_core/host_shaders/sgsr1_shader_mobile.frag @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved. +// SPDX-License-Identifier: BSD-3-Clause + +#version 460 core + +precision highp float; +precision highp int; + +// Operation modes: RGBA -> 1, RGBY -> 3, LERP -> 4 +#define OPERATION_MODE 1 +#define EDGE_THRESHOLD (8.0 / 255.0) + +layout(push_constant) uniform constants { + vec2 scale; + vec2 size; + vec2 resize_factor; + vec2 crop_offset; + float edge_sharpness; +}; +layout(set = 0, binding = 0) uniform sampler2D sampler0; +layout(location=0) in vec2 texcoord; +layout(location=0) out vec4 frag_color; + +vec4 weightY(vec4 dx, vec4 dy, vec4 std) { + vec4 x = ((dx * dx) + (dy * dy)) * 0.55f + std; + return (x - 1.f) * (x - 4.f) * 3.8125f; // approx. of (x - 1) * (x - 4)^3 +} + +void main() { + vec4 color = textureLod(sampler0, texcoord.xy, 0.0f); + // image coord + vec2 icoord = (texcoord * size + vec2(-0.5f, 0.5f)); + vec2 icoord_pixel = floor(icoord); + vec2 coord = icoord_pixel * scale; + vec2 pl = icoord - icoord_pixel; + // left: 0, right: 1, upDown: 2 + mat3x4 dg = mat3x4( + textureGather(sampler0, coord, 1), + textureGather(sampler0, coord + vec2(2.f * scale.x, 0.0f), 1), + vec4( + textureGather(sampler0, coord + vec2(scale.x, -scale.y), 1).wz, + textureGather(sampler0, coord + vec2(scale.x, +scale.y), 1).yx + ) + ); + float edgeVote = abs(dg[0].z - dg[0].y) + abs(color.y - dg[0].y) + abs(color.y - dg[0].z); + if (edgeVote > EDGE_THRESHOLD) { + float mean = (dg[0].y + dg[0].z + dg[1].x + dg[1].w) * 0.25f; + dg = dg - mean; + vec4 sum = abs(dg[0]) + abs(dg[1]) + abs(dg[2]); + float std = 2.181818f / (sum.x + sum.y + sum.z + sum.w); + mat2x4 w = mat2x4( + weightY( + pl.xxxx + vec4(+1.0f, +0.0f, +0.0f, +1.0f), + pl.yyyy + vec4(-1.0f, -1.0f, +0.0f, +0.0f), + clamp(abs(dg[0]) * std, 0.0f, 1.0f) + ) + weightY( + pl.xxxx + vec4(-1.0f, -2.0f, -2.0f, -1.0f), + pl.yyyy + vec4(-1.0f, -1.0f, +0.0f, +0.0f), + clamp(abs(dg[1]) * std, 0.0f, 1.0f) + ) + weightY( + pl.xxxx + vec4(+0.0f, -1.0f, -1.0f, +0.0f), + pl.yyyy + vec4(+1.0f, +1.0f, -2.0f, -2.0f), + clamp(abs(dg[2]) * std, 0.0f, 1.0f) + ), + dg[0] + dg[1] + dg[2] + ); + // compute final y with bounds + vec2 yb = vec2( + min(min(dg[0].y, dg[0].z), min(dg[1].x, dg[1].w)), // min + max(max(dg[0].y, dg[0].z), max(dg[1].x, dg[1].w)) // max + ); + vec2 fvy = vec2( + w[0].x + w[0].y + w[0].z + w[0].w, + w[1].x + w[1].y + w[1].z + w[1].w + ); + float fy = clamp((fvy.y / fvy.x) * edge_sharpness, yb[0], yb[1]); + // Smooth high contrast input + float dy = clamp(fy - color.y + mean, -23.0f / 255.0f, 23.0f / 255.0f); + color = clamp(color + dy, 0.0f, 1.0f); + } + color.w = 1.0f; //assume alpha channel is not used + frag_color.xyzw = color; +} \ No newline at end of file diff --git a/src/video_core/host_shaders/sgsr1_shader_mobile_edge_direction.frag b/src/video_core/host_shaders/sgsr1_shader_mobile_edge_direction.frag new file mode 100644 index 0000000000..66417e9c46 --- /dev/null +++ b/src/video_core/host_shaders/sgsr1_shader_mobile_edge_direction.frag @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved. +// SPDX-License-Identifier: BSD-3-Clause + +#version 460 core + +//precision float; +//precision int; + +// Operation modes: RGBA -> 1, RGBY -> 3, LERP -> 4 +#define OperationMode 1 +#define EdgeThreshold 8.0/255.0 + +layout( push_constant ) uniform constants { + vec4 ViewportInfo[1]; + vec2 ResizeFactor; + vec2 CropOffset; + float EdgeSharpness; +}; +layout(set = 0, binding = 0) uniform sampler2D ps0; +layout(location=0) in vec2 in_TEXCOORD0; +layout(location=0) out vec4 out_Target0; + +float fastLanczos2(float x) { + float wA = x-4.0; + float wB = x*wA-wA; + wA *= wA; + return wB*wA; +} + +vec2 weightY(float dx, float dy, float c, vec3 data) { + float std = data.x; + vec2 dir = data.yz; + float edgeDis = ((dx*dir.y)+(dy*dir.x)); + float x = (((dx*dx)+(dy*dy))+((edgeDis*edgeDis)*((clamp(((c*c)*std),0.0,1.0)*0.7)+-1.0))); + float w = fastLanczos2(x); + return vec2(w, w * c); +} + +vec2 edgeDirection(vec4 left, vec4 right) { + vec2 dir; + float RxLz = (right.x + (-left.z)); + float RwLy = (right.w + (-left.y)); + vec2 delta; + delta.x = (RxLz + RwLy); + delta.y = (RxLz + (-RwLy)); + float lengthInv = inversesqrt((delta.x * delta.x+ 3.075740e-05) + (delta.y * delta.y)); + dir.x = (delta.x * lengthInv); + dir.y = (delta.y * lengthInv); + return dir; +} + +void main() { + vec4 color; + if(OperationMode == 1) + color.xyz = textureLod(ps0, in_TEXCOORD0.xy, 0.0).xyz; + else + color.xyzw = textureLod(ps0, in_TEXCOORD0.xy, 0.0).xyzw; + + if ( OperationMode!=4) { + vec2 imgCoord = ((in_TEXCOORD0.xy*ViewportInfo[0].zw)+vec2(-0.5,0.5)); + vec2 imgCoordPixel = floor(imgCoord); + vec2 coord = (imgCoordPixel*ViewportInfo[0].xy); + vec2 pl = imgCoord - imgCoordPixel; + vec4 left = textureGather(ps0, coord, OperationMode); + float edgeVote = abs(left.z - left.y) + abs(color[OperationMode] - left.y) + abs(color[OperationMode] - left.z) ; + if(edgeVote > EdgeThreshold) { + coord.x += ViewportInfo[0].x; + + vec2 IR_highp_vec2_0 = coord + vec2(ViewportInfo[0].x, 0.0); + vec4 right = textureGather(ps0, IR_highp_vec2_0, OperationMode); + vec4 upDown; + vec2 IR_highp_vec2_1 = coord + vec2(0.0, -ViewportInfo[0].y); + upDown.xy = textureGather(ps0, IR_highp_vec2_1, OperationMode).wz; + vec2 IR_highp_vec2_2 = coord + vec2(0.0, ViewportInfo[0].y); + upDown.zw = textureGather(ps0, IR_highp_vec2_2, OperationMode).yx; + + float mean = (left.y+left.z+right.x+right.w)*0.25; + left = left - vec4(mean); + right = right - vec4(mean); + upDown = upDown - vec4(mean); + color.w =color[OperationMode] - mean; + + float sum = (((((abs(left.x)+abs(left.y))+abs(left.z))+abs(left.w))+(((abs(right.x)+abs(right.y))+abs(right.z))+abs(right.w)))+(((abs(upDown.x)+abs(upDown.y))+abs(upDown.z))+abs(upDown.w))); + float sumMean = 1.014185e+01/sum; + float std = (sumMean*sumMean); + + vec3 data = vec3(std, edgeDirection(left, right)); + vec2 aWY = weightY(pl.x, pl.y+1.0, upDown.x,data); + aWY += weightY(pl.x-1.0, pl.y+1.0, upDown.y,data); + aWY += weightY(pl.x-1.0, pl.y-2.0, upDown.z,data); + aWY += weightY(pl.x, pl.y-2.0, upDown.w,data); + aWY += weightY(pl.x+1.0, pl.y-1.0, left.x,data); + aWY += weightY(pl.x, pl.y-1.0, left.y,data); + aWY += weightY(pl.x, pl.y, left.z,data); + aWY += weightY(pl.x+1.0, pl.y, left.w,data); + aWY += weightY(pl.x-1.0, pl.y-1.0, right.x,data); + aWY += weightY(pl.x-2.0, pl.y-1.0, right.y,data); + aWY += weightY(pl.x-2.0, pl.y, right.z,data); + aWY += weightY(pl.x-1.0, pl.y, right.w,data); + + float finalY = aWY.y/aWY.x; + float maxY = max(max(left.y,left.z),max(right.x,right.w)); + float minY = min(min(left.y,left.z),min(right.x,right.w)); + float deltaY = clamp(EdgeSharpness*finalY, minY, maxY) -color.w; + + //smooth high contrast input + deltaY = clamp(deltaY, -23.0 / 255.0, 23.0 / 255.0); + + color.x = clamp((color.x+deltaY),0.0,1.0); + color.y = clamp((color.y+deltaY),0.0,1.0); + color.z = clamp((color.z+deltaY),0.0,1.0); + } + } + color.w = 1.0; //assume alpha channel is not used + out_Target0.xyzw = color; +} \ No newline at end of file diff --git a/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag b/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag index 484b99773c..78581cea52 100644 --- a/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag +++ b/src/video_core/host_shaders/vulkan_blit_depth_stencil.frag @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -5,11 +8,11 @@ #extension GL_ARB_shader_stencil_export : require layout(binding = 0) uniform sampler2D depth_tex; -layout(binding = 1) uniform isampler2D stencil_tex; +layout(binding = 1) uniform usampler2D stencil_tex; layout(location = 0) in vec2 texcoord; void main() { gl_FragDepth = textureLod(depth_tex, texcoord, 0).r; - gl_FragStencilRefARB = textureLod(stencil_tex, texcoord, 0).r; + gl_FragStencilRefARB = int(textureLod(stencil_tex, texcoord, 0).r); } diff --git a/src/video_core/macro.cpp b/src/video_core/macro.cpp index 606162ba8d..86f528af15 100644 --- a/src/video_core/macro.cpp +++ b/src/video_core/macro.cpp @@ -11,17 +11,9 @@ #include #include + #ifdef ARCHITECTURE_x86_64 -// xbyak hates human beings -#ifdef __GNUC__ -#pragma GCC diagnostic ignored "-Wconversion" -#pragma GCC diagnostic ignored "-Wshadow" -#endif -#ifdef __clang__ -#pragma clang diagnostic ignored "-Wconversion" -#pragma clang diagnostic ignored "-Wshadow" -#endif -#include +#include "common/x64/xbyak.h" #endif #include "common/assert.h" @@ -39,10 +31,6 @@ #include "common/assert.h" #include "common/bit_field.h" #include "common/logging.h" -#ifdef ARCHITECTURE_x86_64 -#include "common/x64/xbyak_abi.h" -#include "common/x64/xbyak_util.h" -#endif #include "video_core/engines/maxwell_3d.h" namespace Tegra { @@ -76,10 +64,10 @@ bool IsTopologySafe(Maxwell3D::Regs::PrimitiveTopology topology) { } // Anonymous namespace -void HLE_DrawArraysIndirect::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { +void HLE_DrawArraysIndirect::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { auto topology = static_cast(parameters[0]); if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { - Fallback(maxwell3d, parameters); + Fallback(system, maxwell3d, parameters); return; } @@ -105,7 +93,7 @@ void HLE_DrawArraysIndirect::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters) { +void HLE_DrawArraysIndirect::Fallback(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters) { SCOPE_EXIT { if (extended) { maxwell3d.engine_state = Maxwell3D::EngineHint::None; @@ -135,10 +123,10 @@ void HLE_DrawArraysIndirect::Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { +void HLE_DrawIndexedIndirect::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { auto topology = static_cast(parameters[0]); if (!maxwell3d.AnyParametersDirty() || !IsTopologySafe(topology)) { - Fallback(maxwell3d, parameters); + Fallback(system, maxwell3d, parameters); return; } @@ -173,7 +161,7 @@ void HLE_DrawIndexedIndirect::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters) { +void HLE_DrawIndexedIndirect::Fallback(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters) { maxwell3d.RefreshParameters(); const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]); const u32 element_base = parameters[4]; @@ -196,7 +184,7 @@ void HLE_DrawIndexedIndirect::Fallback(Engines::Maxwell3D& maxwell3d, std::span< maxwell3d.replace_table.clear(); } } -void HLE_MultiLayerClear::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { +void HLE_MultiLayerClear::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { maxwell3d.RefreshParameters(); ASSERT(parameters.size() == 1); @@ -208,47 +196,44 @@ void HLE_MultiLayerClear::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { +void HLE_MultiDrawIndexedIndirectCount::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { const auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[2]); - if (!IsTopologySafe(topology)) { - Fallback(maxwell3d, parameters); - return; + if (IsTopologySafe(topology)) { + const u32 start_indirect = parameters[0]; + const u32 end_indirect = parameters[1]; + if (start_indirect >= end_indirect) { + // Nothing to do. + return; + } + const u32 padding = parameters[3]; // padding is in words + // size of each indirect segment + const u32 indirect_words = 5 + padding; + const u32 stride = indirect_words * sizeof(u32); + const std::size_t draw_count = end_indirect - start_indirect; + const u32 estimate = u32(maxwell3d.EstimateIndexBufferSize()); + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + auto& params = maxwell3d.draw_manager.indirect_state; + params.is_byte_count = false; + params.is_indexed = true; + params.include_count = true; + params.count_start_address = maxwell3d.GetMacroAddress(4); + params.indirect_start_address = maxwell3d.GetMacroAddress(5); + params.buffer_size = stride * draw_count; + params.max_draw_counts = draw_count; + params.stride = stride; + maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; + maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; + maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); + maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); + maxwell3d.SetHLEReplacementAttributeType(0, 0x648, Maxwell3D::HLEReplacementAttributeType::DrawID); + maxwell3d.draw_manager.DrawIndexedIndirect(maxwell3d, topology, 0, estimate); + maxwell3d.engine_state = Maxwell3D::EngineHint::None; + maxwell3d.replace_table.clear(); + } else { + Fallback(system, maxwell3d, parameters); } - - const u32 start_indirect = parameters[0]; - const u32 end_indirect = parameters[1]; - if (start_indirect >= end_indirect) { - // Nothing to do. - return; - } - - const u32 padding = parameters[3]; // padding is in words - - // size of each indirect segment - const u32 indirect_words = 5 + padding; - const u32 stride = indirect_words * sizeof(u32); - const std::size_t draw_count = end_indirect - start_indirect; - const u32 estimate = static_cast(maxwell3d.EstimateIndexBufferSize()); - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - auto& params = maxwell3d.draw_manager.indirect_state; - params.is_byte_count = false; - params.is_indexed = true; - params.include_count = true; - params.count_start_address = maxwell3d.GetMacroAddress(4); - params.indirect_start_address = maxwell3d.GetMacroAddress(5); - params.buffer_size = stride * draw_count; - params.max_draw_counts = draw_count; - params.stride = stride; - maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; - maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; - maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); - maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - maxwell3d.SetHLEReplacementAttributeType(0, 0x648, Maxwell3D::HLEReplacementAttributeType::DrawID); - maxwell3d.draw_manager.DrawIndexedIndirect(maxwell3d, topology, 0, estimate); - maxwell3d.engine_state = Maxwell3D::EngineHint::None; - maxwell3d.replace_table.clear(); } -void HLE_MultiDrawIndexedIndirectCount::Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters) { +void HLE_MultiDrawIndexedIndirectCount::Fallback(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters) { SCOPE_EXIT { // Clean everything. maxwell3d.regs.vertex_id_base = 0x0; @@ -262,7 +247,7 @@ void HLE_MultiDrawIndexedIndirectCount::Fallback(Engines::Maxwell3D& maxwell3d, // Nothing to do. return; } - const auto topology = static_cast(parameters[2]); + const auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[2]); const u32 padding = parameters[3]; const std::size_t max_draws = parameters[4]; const u32 indirect_words = 5 + padding; @@ -277,41 +262,41 @@ void HLE_MultiDrawIndexedIndirectCount::Fallback(Engines::Maxwell3D& maxwell3d, maxwell3d.engine_state = Maxwell3D::EngineHint::OnHLEMacro; maxwell3d.SetHLEReplacementAttributeType(0, 0x640, Maxwell3D::HLEReplacementAttributeType::BaseVertex); maxwell3d.SetHLEReplacementAttributeType(0, 0x644, Maxwell3D::HLEReplacementAttributeType::BaseInstance); - maxwell3d.CallMethod(0x8e3, 0x648, true); - maxwell3d.CallMethod(0x8e4, static_cast(index), true); + maxwell3d.CallMethod(system, 0x8e3, 0x648, true); + maxwell3d.CallMethod(system, 0x8e4, u32(index), true); maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true; maxwell3d.draw_manager.DrawIndex(maxwell3d, topology, parameters[base + 2], parameters[base], base_vertex, base_instance, parameters[base + 1]); } } -void HLE_DrawIndirectByteCount::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { +void HLE_DrawIndirectByteCount::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { const bool force = maxwell3d.Rasterizer().HasDrawTransformFeedback(); - if (!force) { - Fallback(maxwell3d, parameters); - return; + if (force) { + auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[0] & 0xFFFFU); + auto& params = maxwell3d.draw_manager.indirect_state; + params.is_byte_count = true; + params.is_indexed = false; + params.include_count = false; + params.count_start_address = 0; + params.indirect_start_address = maxwell3d.GetMacroAddress(2); + params.buffer_size = 4; + params.max_draw_counts = 1; + params.stride = parameters[1]; + maxwell3d.regs.draw.begin = parameters[0]; + maxwell3d.regs.draw_auto_stride = parameters[1]; + maxwell3d.regs.draw_auto_byte_count = parameters[2]; + maxwell3d.draw_manager.DrawArrayIndirect(maxwell3d, topology); + } else { + Fallback(system, maxwell3d, parameters); } - auto topology = Maxwell3D::Regs::PrimitiveTopology(parameters[0] & 0xFFFFU); - auto& params = maxwell3d.draw_manager.indirect_state; - params.is_byte_count = true; - params.is_indexed = false; - params.include_count = false; - params.count_start_address = 0; - params.indirect_start_address = maxwell3d.GetMacroAddress(2); - params.buffer_size = 4; - params.max_draw_counts = 1; - params.stride = parameters[1]; - maxwell3d.regs.draw.begin = parameters[0]; - maxwell3d.regs.draw_auto_stride = parameters[1]; - maxwell3d.regs.draw_auto_byte_count = parameters[2]; - maxwell3d.draw_manager.DrawArrayIndirect(maxwell3d, topology); } -void HLE_DrawIndirectByteCount::Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters) { +void HLE_DrawIndirectByteCount::Fallback(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters) { maxwell3d.RefreshParameters(); maxwell3d.regs.draw.begin = parameters[0]; maxwell3d.regs.draw_auto_stride = parameters[1]; maxwell3d.regs.draw_auto_byte_count = parameters[2]; maxwell3d.draw_manager.DrawArray(maxwell3d, maxwell3d.regs.draw.topology, 0, maxwell3d.regs.draw_auto_byte_count / maxwell3d.regs.draw_auto_stride, 0, 1); } -void HLE_C713C83D8F63CCF3::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { +void HLE_C713C83D8F63CCF3::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { maxwell3d.RefreshParameters(); const u32 offset = (parameters[0] & 0x3FFFFFFF) << 2; const u32 address = maxwell3d.regs.shadow_scratch[24]; @@ -321,7 +306,7 @@ void HLE_C713C83D8F63CCF3::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { +void HLE_D7333D26E0A93EDE::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { maxwell3d.RefreshParameters(); const size_t index = parameters[0]; const u32 address = maxwell3d.regs.shadow_scratch[42 + index]; @@ -331,7 +316,7 @@ void HLE_D7333D26E0A93EDE::Execute(Engines::Maxwell3D& maxwell3d, std::span> 24) & 0xFF; const_buffer.address_low = address << 8; } -void HLE_BindShader::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { +void HLE_BindShader::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { maxwell3d.RefreshParameters(); auto& regs = maxwell3d.regs; const u32 index = parameters[0]; @@ -355,7 +340,7 @@ void HLE_BindShader::Execute(Engines::Maxwell3D& maxwell3d, std::span bind_group.raw_config = 0x11; maxwell3d.ProcessCBBind(bind_group_id); } -void HLE_SetRasterBoundingBox::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { +void HLE_SetRasterBoundingBox::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { maxwell3d.RefreshParameters(); const u32 raster_mode = parameters[0]; auto& regs = maxwell3d.regs; @@ -364,7 +349,7 @@ void HLE_SetRasterBoundingBox::Execute(Engines::Maxwell3D& maxwell3d, std::span< regs.raster_bounding_box.raw = raster_mode & 0xFFFFF00F; regs.raster_bounding_box.pad.Assign(scratch_data & raster_enabled); } -void HLE_ClearConstBuffer::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { +void HLE_ClearConstBuffer::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { static constexpr std::array zeroes{}; //must be bigger than either 7000 or 5F00 maxwell3d.RefreshParameters(); auto& regs = maxwell3d.regs; @@ -374,7 +359,7 @@ void HLE_ClearConstBuffer::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { +void HLE_ClearMemory::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { maxwell3d.RefreshParameters(); const u32 needed_memory = parameters[2] / sizeof(u32); if (needed_memory > zero_memory.size()) { @@ -385,10 +370,10 @@ void HLE_ClearMemory::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { +void HLE_TransformFeedbackSetup::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method) { maxwell3d.RefreshParameters(); auto& regs = maxwell3d.regs; regs.transform_feedback_enabled = 1; @@ -400,8 +385,8 @@ void HLE_TransformFeedbackSetup::Execute(Engines::Maxwell3D& maxwell3d, std::spa regs.upload.line_count = 1; regs.upload.dest.address_high = parameters[0]; regs.upload.dest.address_low = parameters[1]; - maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true); - maxwell3d.CallMethod(size_t(MAXWELL3D_REG_INDEX(inline_data)), regs.transform_feedback.controls[0].stride, true); + maxwell3d.CallMethod(system, size_t(MAXWELL3D_REG_INDEX(launch_dma)), 0x1011, true); + maxwell3d.CallMethod(system, size_t(MAXWELL3D_REG_INDEX(inline_data)), regs.transform_feedback.controls[0].stride, true); maxwell3d.Rasterizer().RegisterTransformFeedback(regs.upload.dest.Address()); } @@ -432,16 +417,8 @@ void HLE_TransformFeedbackSetup::Execute(Engines::Maxwell3D& maxwell3d, std::spa default: return std::monostate{}; } } -[[nodiscard]] inline bool CanBeHLEProgram(u64 hash) noexcept { - switch (hash) { -#define HLE_MACRO_ELEM(HASH, TY, VAL) case HASH: return true; - HLE_MACRO_LIST -#undef HLE_MACRO_ELEM - default: return false; - } -} -void MacroInterpreterImpl::Execute(Engines::Maxwell3D& maxwell3d, std::span params, u32 method) { +void MacroInterpreterImpl::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span params, u32 method) { Reset(); registers[1] = params[0]; @@ -451,7 +428,7 @@ void MacroInterpreterImpl::Execute(Engines::Maxwell3D& maxwell3d, std::span> opcode.bf_src_bit) & opcode.GetBitfieldMask(); dst &= ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit); dst |= src << opcode.bf_dst_bit; - ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, dst); + ProcessResult(system, maxwell3d, opcode.result_operation, opcode.dst, dst); break; } case Macro::Operation::ExtractShiftLeftImmediate: { @@ -513,7 +490,7 @@ bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slo u32 result = ((src >> dst) & opcode.GetBitfieldMask()) << opcode.bf_dst_bit; - ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result); + ProcessResult(system, maxwell3d, opcode.result_operation, opcode.dst, result); break; } case Macro::Operation::ExtractShiftLeftRegister: { @@ -522,12 +499,12 @@ bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slo u32 result = ((src >> opcode.bf_src_bit) & opcode.GetBitfieldMask()) << dst; - ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result); + ProcessResult(system, maxwell3d, opcode.result_operation, opcode.dst, result); break; } case Macro::Operation::Read: { u32 result = Read(maxwell3d, GetRegister(opcode.src_a) + opcode.immediate); - ProcessResult(maxwell3d, opcode.result_operation, opcode.dst, result); + ProcessResult(system, maxwell3d, opcode.result_operation, opcode.dst, result); break; } case Macro::Operation::Branch: { @@ -543,7 +520,7 @@ bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slo delayed_pc = base_address + opcode.GetBranchTarget(); // Execute one more instruction due to the delay slot. - return Step(maxwell3d, true); + return Step(system, maxwell3d, true); } break; } @@ -556,7 +533,7 @@ bool MacroInterpreterImpl::Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slo // cause an exit if it's executed inside a delay slot. if (opcode.is_exit && !is_delay_slot) { // Exit has a delay slot, execute the next instruction - Step(maxwell3d, true); + Step(system, maxwell3d, true); return false; } return true; @@ -603,7 +580,7 @@ u32 MacroInterpreterImpl::GetALUResult(Macro::ALUOperation operation, u32 src_a, } /// Performs the result operation on the input result and stores it in the specified register (if necessary). -void MacroInterpreterImpl::ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::ResultOperation operation, u32 reg, u32 result) { +void MacroInterpreterImpl::ProcessResult(Core::System& system, Engines::Maxwell3D& maxwell3d, Macro::ResultOperation operation, u32 reg, u32 result) { switch (operation) { case Macro::ResultOperation::IgnoreAndFetch: // Fetch parameter and ignore result. @@ -621,12 +598,12 @@ void MacroInterpreterImpl::ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::R case Macro::ResultOperation::FetchAndSend: // Fetch parameter and send result. SetRegister(reg, FetchParameter()); - Send(maxwell3d, result); + Send(system, maxwell3d, result); break; case Macro::ResultOperation::MoveAndSend: // Move and send result. SetRegister(reg, result); - Send(maxwell3d, result); + Send(system, maxwell3d, result); break; case Macro::ResultOperation::FetchAndSetMethod: // Fetch parameter and use result as Method Address. @@ -637,13 +614,13 @@ void MacroInterpreterImpl::ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::R // Move result and use as Method Address, then fetch and send parameter. SetRegister(reg, result); SetMethodAddress(result); - Send(maxwell3d, FetchParameter()); + Send(system, maxwell3d, FetchParameter()); break; case Macro::ResultOperation::MoveAndSetMethodSend: // Move result and use as Method Address, then send bits 12:17 of result. SetRegister(reg, result); SetMethodAddress(result); - Send(maxwell3d, (result >> 12) & 0b111111); + Send(system, maxwell3d, (result >> 12) & 0b111111); break; default: UNIMPLEMENTED_MSG("Unimplemented result operation {}", operation); @@ -684,8 +661,8 @@ void MacroInterpreterImpl::SetRegister(u32 register_id, u32 value) { } /// Calls a GPU Engine method with the input parameter. -void MacroInterpreterImpl::Send(Engines::Maxwell3D& maxwell3d, u32 value) { - maxwell3d.CallMethod(method_address.address, value, true); +void MacroInterpreterImpl::Send(Core::System& system, Engines::Maxwell3D& maxwell3d, u32 value) { + maxwell3d.CallMethod(system, method_address.address, value, true); // Increment the method address by the method increment. method_address.address.Assign(method_address.address.Value() + method_address.increment.Value()); } @@ -736,34 +713,35 @@ static const auto default_cg_mode = nullptr; //Allow RWE #endif struct MacroJITx64Impl final : public Xbyak::CodeGenerator, public DynamicCachedMacro { - explicit MacroJITx64Impl(std::span code_) + explicit MacroJITx64Impl(Core::System& system, std::span code_) : Xbyak::CodeGenerator(MAX_CODE_SIZE, default_cg_mode) , code{code_} { - Compile(); + Compile(system); } - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, u32 method) override; + void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, u32 method) override; - void Compile_ALU(Macro::Opcode opcode); - void Compile_AddImmediate(Macro::Opcode opcode); - void Compile_ExtractInsert(Macro::Opcode opcode); - void Compile_ExtractShiftLeftImmediate(Macro::Opcode opcode); - void Compile_ExtractShiftLeftRegister(Macro::Opcode opcode); - void Compile_Read(Macro::Opcode opcode); + void Compile_ALU(Core::System& system, Macro::Opcode opcode); + void Compile_AddImmediate(Core::System& system, Macro::Opcode opcode); + void Compile_ExtractInsert(Core::System& system, Macro::Opcode opcode); + void Compile_ExtractShiftLeftImmediate(Core::System& system, Macro::Opcode opcode); + void Compile_ExtractShiftLeftRegister(Core::System& system, Macro::Opcode opcode); + void Compile_Read(Core::System& system, Macro::Opcode opcode); void Compile_Branch(Macro::Opcode opcode); void Optimizer_ScanFlags(); - void Compile(); - bool Compile_NextInstruction(); + void Compile(Core::System& system); + bool Compile_NextInstruction(Core::System& system); Xbyak::Reg32 Compile_FetchParameter(); Xbyak::Reg32 Compile_GetRegister(u32 index, Xbyak::Reg32 dst); - void Compile_ProcessResult(Macro::ResultOperation operation, u32 reg); - void Compile_Send(Xbyak::Reg32 value); + void Compile_ProcessResult(Core::System& system, Macro::ResultOperation operation, u32 reg); + void Compile_Send(Core::System& system, Xbyak::Reg32 value); Macro::Opcode GetOpCode() const; struct JITState { - Engines::Maxwell3D* maxwell3d{}; + Engines::Maxwell3D* maxwell3d = nullptr; + Core::System* system = nullptr; std::array registers{}; u32 carry_flag{}; }; @@ -789,15 +767,16 @@ struct MacroJITx64Impl final : public Xbyak::CodeGenerator, public DynamicCached std::span code; }; -void MacroJITx64Impl::Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, u32 method) { +void MacroJITx64Impl::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, u32 method) { ASSERT_OR_EXECUTE(program != nullptr, { return; }); JITState state{}; state.maxwell3d = &maxwell3d; + state.system = &system; state.registers = {}; program(&state, parameters.data(), parameters.data() + parameters.size()); } -void MacroJITx64Impl::Compile_ALU(Macro::Opcode opcode) { +void MacroJITx64Impl::Compile_ALU(Core::System& system, Macro::Opcode opcode) { const bool is_a_zero = opcode.src_a == 0; const bool is_b_zero = opcode.src_b == 0; const bool valid_operation = !is_a_zero && !is_b_zero; @@ -914,10 +893,10 @@ void MacroJITx64Impl::Compile_ALU(Macro::Opcode opcode) { UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", opcode.alu_operation.Value()); break; } - Compile_ProcessResult(opcode.result_operation, opcode.dst); + Compile_ProcessResult(system, opcode.result_operation, opcode.dst); } -void MacroJITx64Impl::Compile_AddImmediate(Macro::Opcode opcode) { +void MacroJITx64Impl::Compile_AddImmediate(Core::System& system, Macro::Opcode opcode) { if (optimizer.skip_dummy_addimmediate) { // Games tend to use this as an exit instruction placeholder. It's to encode an instruction // without doing anything. In our case we can just not emit anything. @@ -952,10 +931,10 @@ void MacroJITx64Impl::Compile_AddImmediate(Macro::Opcode opcode) { sub(result, opcode.immediate * -1); } } - Compile_ProcessResult(opcode.result_operation, opcode.dst); + Compile_ProcessResult(system, opcode.result_operation, opcode.dst); } -void MacroJITx64Impl::Compile_ExtractInsert(Macro::Opcode opcode) { +void MacroJITx64Impl::Compile_ExtractInsert(Core::System& system, Macro::Opcode opcode) { auto dst = Compile_GetRegister(opcode.src_a, RESULT); auto src = Compile_GetRegister(opcode.src_b, eax); @@ -966,10 +945,10 @@ void MacroJITx64Impl::Compile_ExtractInsert(Macro::Opcode opcode) { shl(src, opcode.bf_dst_bit); or_(dst, src); - Compile_ProcessResult(opcode.result_operation, opcode.dst); + Compile_ProcessResult(system, opcode.result_operation, opcode.dst); } -void MacroJITx64Impl::Compile_ExtractShiftLeftImmediate(Macro::Opcode opcode) { +void MacroJITx64Impl::Compile_ExtractShiftLeftImmediate(Core::System& system, Macro::Opcode opcode) { const auto dst = Compile_GetRegister(opcode.src_a, ecx); const auto src = Compile_GetRegister(opcode.src_b, RESULT); @@ -977,10 +956,10 @@ void MacroJITx64Impl::Compile_ExtractShiftLeftImmediate(Macro::Opcode opcode) { and_(src, opcode.GetBitfieldMask()); shl(src, opcode.bf_dst_bit); - Compile_ProcessResult(opcode.result_operation, opcode.dst); + Compile_ProcessResult(system, opcode.result_operation, opcode.dst); } -void MacroJITx64Impl::Compile_ExtractShiftLeftRegister(Macro::Opcode opcode) { +void MacroJITx64Impl::Compile_ExtractShiftLeftRegister(Core::System& system, Macro::Opcode opcode) { const auto dst = Compile_GetRegister(opcode.src_a, ecx); const auto src = Compile_GetRegister(opcode.src_b, RESULT); @@ -988,10 +967,10 @@ void MacroJITx64Impl::Compile_ExtractShiftLeftRegister(Macro::Opcode opcode) { and_(src, opcode.GetBitfieldMask()); shl(src, dst.cvt8()); - Compile_ProcessResult(opcode.result_operation, opcode.dst); + Compile_ProcessResult(system, opcode.result_operation, opcode.dst); } -void MacroJITx64Impl::Compile_Read(Macro::Opcode opcode) { +void MacroJITx64Impl::Compile_Read(Core::System& system, Macro::Opcode opcode) { if (optimizer.zero_reg_skip && opcode.src_a == 0) { if (opcode.immediate == 0) { xor_(RESULT, RESULT); @@ -1017,23 +996,21 @@ void MacroJITx64Impl::Compile_Read(Macro::Opcode opcode) { int3(); L(pass_range_check); } - mov(rax, qword[STATE]); - mov(RESULT, - dword[rax + offsetof(Engines::Maxwell3D, regs) + - offsetof(Engines::Maxwell3D::Regs, reg_array) + RESULT.cvt64() * sizeof(u32)]); - - Compile_ProcessResult(opcode.result_operation, opcode.dst); + mov(rax, qword[STATE + offsetof(JITState, maxwell3d)]); + mov(RESULT, dword[rax + offsetof(Engines::Maxwell3D, regs) + offsetof(Engines::Maxwell3D::Regs, reg_array) + RESULT.cvt64() * sizeof(u32)]); + Compile_ProcessResult(system, opcode.result_operation, opcode.dst); } -static void MacroJIT_SendThunk(Engines::Maxwell3D* maxwell3d, Macro::MethodAddress method_address, u32 value) { - maxwell3d->CallMethod(method_address.address, value, true); +static void MacroJIT_SendThunk(Core::System* system, Engines::Maxwell3D* maxwell3d, Macro::MethodAddress method_address, u32 value) { + maxwell3d->CallMethod(*system, method_address.address, value, true); } -void MacroJITx64Impl::Compile_Send(Xbyak::Reg32 value) { +void MacroJITx64Impl::Compile_Send(Core::System& system, Xbyak::Reg32 value) { Common::X64::ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); - mov(Common::X64::ABI_PARAM1, qword[STATE]); - mov(Common::X64::ABI_PARAM2.cvt32(), METHOD_ADDRESS); - mov(Common::X64::ABI_PARAM3.cvt32(), value); + mov(Common::X64::ABI_PARAM1, qword[STATE + offsetof(JITState, system)]); + mov(Common::X64::ABI_PARAM2, qword[STATE + offsetof(JITState, maxwell3d)]); + mov(Common::X64::ABI_PARAM3.cvt32(), METHOD_ADDRESS); + mov(Common::X64::ABI_PARAM4.cvt32(), value); Common::X64::CallFarFunction(*this, &MacroJIT_SendThunk); Common::X64::ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); @@ -1057,9 +1034,8 @@ void MacroJITx64Impl::Compile_Send(Xbyak::Reg32 value) { } void MacroJITx64Impl::Compile_Branch(Macro::Opcode opcode) { - ASSERT_MSG(!is_delay_slot, "Executing a branch in a delay slot is not valid"); - const s32 jump_address = - static_cast(pc) + static_cast(opcode.GetBranchTarget() / sizeof(s32)); + ASSERT(!is_delay_slot && "Executing a branch in a delay slot is not valid"); + const s32 jump_address = s32(pc) + s32(opcode.GetBranchTarget() / sizeof(s32)); Xbyak::Label end; auto value = Compile_GetRegister(opcode.src_a, eax); @@ -1128,7 +1104,9 @@ void MacroJITx64Impl::Optimizer_ScanFlags() { } } -void MacroJITx64Impl::Compile() { +void MacroJITx64Impl::Compile(Core::System& system) { + // Matching PROTECT_RE needed for W^X systems + setProtectMode(Xbyak::CodeArray::ProtectMode::PROTECT_RW); labels.fill(Xbyak::Label()); Common::X64::ABI_PushRegistersAndAdjustStack(*this, Common::X64::ABI_ALL_CALLEE_SAVED, 8); @@ -1168,7 +1146,7 @@ void MacroJITx64Impl::Compile() { next_opcode = {}; } pc = i; - Compile_NextInstruction(); + Compile_NextInstruction(system); } L(end_of_code); @@ -1176,10 +1154,11 @@ void MacroJITx64Impl::Compile() { Common::X64::ABI_PopRegistersAndAdjustStack(*this, Common::X64::ABI_ALL_CALLEE_SAVED, 8); ret(); ready(); + setProtectMode(Xbyak::CodeArray::ProtectMode::PROTECT_RE); program = getCode(); } -bool MacroJITx64Impl::Compile_NextInstruction() { +bool MacroJITx64Impl::Compile_NextInstruction(Core::System& system) { const auto opcode = GetOpCode(); if (labels[pc].getAddress()) { return false; @@ -1189,22 +1168,22 @@ bool MacroJITx64Impl::Compile_NextInstruction() { switch (opcode.operation) { case Macro::Operation::ALU: - Compile_ALU(opcode); + Compile_ALU(system, opcode); break; case Macro::Operation::AddImmediate: - Compile_AddImmediate(opcode); + Compile_AddImmediate(system, opcode); break; case Macro::Operation::ExtractInsert: - Compile_ExtractInsert(opcode); + Compile_ExtractInsert(system, opcode); break; case Macro::Operation::ExtractShiftLeftImmediate: - Compile_ExtractShiftLeftImmediate(opcode); + Compile_ExtractShiftLeftImmediate(system, opcode); break; case Macro::Operation::ExtractShiftLeftRegister: - Compile_ExtractShiftLeftRegister(opcode); + Compile_ExtractShiftLeftRegister(system, opcode); break; case Macro::Operation::Read: - Compile_Read(opcode); + Compile_Read(system, opcode); break; case Macro::Operation::Branch: Compile_Branch(opcode); @@ -1247,7 +1226,7 @@ bool MacroJITx64Impl::Compile_NextInstruction() { } static void MacroJIT_ErrorThunk(uintptr_t parameter, uintptr_t max_parameter) { - LOG_CRITICAL(HW_GPU, "Macro JIT: invalid parameter access 0x{:x} (0x{:x} is the last parameter)", parameter, max_parameter - sizeof(u32)); + LOG_CRITICAL(HW_GPU, "Macro JIT: invalid parameter access {:#x} ({:#x} is the last parameter)", parameter, max_parameter - sizeof(u32)); } Xbyak::Reg32 MacroJITx64Impl::Compile_FetchParameter() { @@ -1276,7 +1255,7 @@ Xbyak::Reg32 MacroJITx64Impl::Compile_GetRegister(u32 index, Xbyak::Reg32 dst) { return dst; } -void MacroJITx64Impl::Compile_ProcessResult(Macro::ResultOperation operation, u32 reg) { +void MacroJITx64Impl::Compile_ProcessResult(Core::System& system, Macro::ResultOperation operation, u32 reg) { const auto SetRegister = [this](u32 reg_index, const Xbyak::Reg32& result) { // Register 0 is supposed to always return 0. NOP is implemented as a store to the zero // register. @@ -1301,12 +1280,12 @@ void MacroJITx64Impl::Compile_ProcessResult(Macro::ResultOperation operation, u3 case Macro::ResultOperation::FetchAndSend: // Fetch parameter and send result. SetRegister(reg, Compile_FetchParameter()); - Compile_Send(RESULT); + Compile_Send(system, RESULT); break; case Macro::ResultOperation::MoveAndSend: // Move and send result. SetRegister(reg, RESULT); - Compile_Send(RESULT); + Compile_Send(system, RESULT); break; case Macro::ResultOperation::FetchAndSetMethod: // Fetch parameter and use result as Method Address. @@ -1317,7 +1296,7 @@ void MacroJITx64Impl::Compile_ProcessResult(Macro::ResultOperation operation, u3 // Move result and use as Method Address, then fetch and send parameter. SetRegister(reg, RESULT); SetMethodAddress(RESULT); - Compile_Send(Compile_FetchParameter()); + Compile_Send(system, Compile_FetchParameter()); break; case Macro::ResultOperation::MoveAndSetMethodSend: // Move result and use as Method Address, then send bits 12:17 of result. @@ -1325,7 +1304,7 @@ void MacroJITx64Impl::Compile_ProcessResult(Macro::ResultOperation operation, u3 SetMethodAddress(RESULT); shr(RESULT, 12); and_(RESULT, 0b111111); - Compile_Send(RESULT); + Compile_Send(system, RESULT); break; default: UNIMPLEMENTED_MSG("Unimplemented macro operation {}", operation); @@ -1341,22 +1320,14 @@ Macro::Opcode MacroJITx64Impl::GetOpCode() const { #endif static void Dump(u64 hash, std::span code, bool decompiled = false) { - const auto base_dir{Common::FS::GetEdenPath(Common::FS::EdenPath::DumpDir)}; - const auto macro_dir{base_dir / "macros"}; - if (!Common::FS::CreateDir(base_dir) || !Common::FS::CreateDir(macro_dir)) { - LOG_ERROR(Common_Filesystem, "Failed to create macro dump directories"); + const auto dump_dir{Common::FS::GetEdenPath(Common::FS::EdenPath::DumpDir)}; + if (!Common::FS::CreateDir(dump_dir)) { + LOG_ERROR(Common_Filesystem, "Failed to create dump directory"); return; } - auto name{macro_dir / fmt::format("{:016x}.macro", hash)}; - - if (decompiled) { - auto new_name{macro_dir / fmt::format("decompiled_{:016x}.macro", hash)}; - if (Common::FS::Exists(name)) { - (void)Common::FS::RenameFile(name, new_name); - return; - } - name = new_name; - } + const char* const variant_suffix = decompiled ? "jit" : "raw"; + const auto name{dump_dir / fmt::format("{:016x}_{:016x}_{}.macro", + Settings::GetCurrentProgramID(), hash, variant_suffix)}; std::fstream macro_file(name, std::ios::out | std::ios::binary); if (!macro_file) { @@ -1366,87 +1337,99 @@ static void Dump(u64 hash, std::span code, bool decompiled = false) { macro_file.write(reinterpret_cast(code.data()), code.size_bytes()); } -void MacroEngine::Execute(Engines::Maxwell3D& maxwell3d, u32 method, std::span parameters) { - auto const execute_variant = [&maxwell3d, ¶meters, method](AnyCachedMacro& acm) { - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if(&acm)) - a->Execute(maxwell3d, parameters, method); - if (auto a = std::get_if>(&acm)) - a->get()->Execute(maxwell3d, parameters, method); - }; - if (auto const it = macro_cache.find(method); it != macro_cache.end()) { - auto& ci = it->second; - if (!CanBeHLEProgram(ci.hash) || Settings::values.disable_macro_hle) - maxwell3d.RefreshParameters(); //LLE must reload parameters - execute_variant(ci.program); - } else { - // Macro not compiled, check if it's uploaded and if so, compile it - std::optional mid_method; - const auto macro_code = uploaded_macro_code.find(method); - if (macro_code == uploaded_macro_code.end()) { - for (const auto& [method_base, code] : uploaded_macro_code) { - if (method >= method_base && (method - method_base) < code.size()) { - mid_method = method_base; - break; - } - } - if (!mid_method.has_value()) { - ASSERT_MSG(false, "Macro 0x{0:x} was not uploaded", method); - return; - } - } - auto& ci = macro_cache[method]; - if (mid_method) { - const auto& macro_cached = uploaded_macro_code[mid_method.value()]; - const auto rebased_method = method - mid_method.value(); - auto& code = uploaded_macro_code[method]; - code.resize(macro_cached.size() - rebased_method); - std::memcpy(code.data(), macro_cached.data() + rebased_method, code.size() * sizeof(u32)); - ci.hash = Common::HashValue(code); - ci.program = Compile(maxwell3d, code); - } else { - ci.program = Compile(maxwell3d, macro_code->second); - ci.hash = Common::HashValue(macro_code->second); - } - if (CanBeHLEProgram(ci.hash) && !Settings::values.disable_macro_hle) { - ci.program = GetHLEProgram(ci.hash); - } else { +void MacroEngine::Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, u32 method, + std::span parameters) { + const auto execute_variant = [&system, &maxwell3d, ¶meters, + method](AnyCachedMacro& cached) { + if (std::holds_alternative(cached) || + std::holds_alternative>(cached) || + Settings::values.disable_macro_hle) { maxwell3d.RefreshParameters(); } - execute_variant(ci.program); - if (Settings::values.dump_macros) { - Dump(ci.hash, macro_code->second, !std::holds_alternative(ci.program)); + + if (auto program = std::get_if(&cached)) + return program->Execute(system, maxwell3d, parameters, method); + if (auto program = std::get_if(&cached)) + return program->Execute(system, maxwell3d, parameters, method); + if (auto program = std::get_if(&cached)) + return program->Execute(system, maxwell3d, parameters, method); + if (auto program = std::get_if(&cached)) + return program->Execute(system, maxwell3d, parameters, method); + if (auto program = std::get_if(&cached)) + return program->Execute(system, maxwell3d, parameters, method); + if (auto program = std::get_if(&cached)) + return program->Execute(system, maxwell3d, parameters, method); + if (auto program = std::get_if(&cached)) + return program->Execute(system, maxwell3d, parameters, method); + if (auto program = std::get_if(&cached)) + return program->Execute(system, maxwell3d, parameters, method); + if (auto program = std::get_if(&cached)) + return program->Execute(system, maxwell3d, parameters, method); + if (auto program = std::get_if(&cached)) + return program->Execute(system, maxwell3d, parameters, method); + if (auto program = std::get_if(&cached)) + return program->Execute(system, maxwell3d, parameters, method); + if (auto program = std::get_if(&cached)) + return program->Execute(system, maxwell3d, parameters, method); + if (auto program = std::get_if(&cached)) + return program->Execute(system, maxwell3d, parameters, method); + if (auto program = std::get_if>(&cached)) + return program->get()->Execute(system, maxwell3d, parameters, method); + + UNREACHABLE(); + }; + if (auto const it = macro_cache.find(method); it != macro_cache.end()) { + execute_variant(it->second.program); + return; + } + + // Macro not compiled, check if it's uploaded and if so, compile it + std::span code; + auto macro_code = uploaded_macro_code.find(method); + if (macro_code == uploaded_macro_code.end()) { + std::optional mid_method; + for (const auto& [method_base, uploaded_code] : uploaded_macro_code) { + if (method >= method_base && (method - method_base) < uploaded_code.size()) { + mid_method = method_base; + break; + } } + if (!mid_method) { + ASSERT_MSG(false, "Macro 0x{0:x} was not uploaded", method); + return; + } + + const auto source = uploaded_macro_code.find(*mid_method); + ASSERT(source != uploaded_macro_code.end()); + const auto rebased_method = method - *mid_method; + std::vector rebased_code(source->second.begin() + rebased_method, + source->second.end()); + const auto [it, inserted] = uploaded_macro_code.emplace(method, std::move(rebased_code)); + ASSERT(inserted); + code = it->second; + } else { + code = macro_code->second; + } + + auto& ci = macro_cache[method]; + ci.hash = Common::HashRange(code.begin(), code.end()); + if (!Settings::values.disable_macro_hle) { + ci.program = GetHLEProgram(ci.hash); + } + if (std::holds_alternative(ci.program)) { + ci.program = Compile(system, maxwell3d, code); + } + + execute_variant(ci.program); + if (Settings::values.dump_macros) { + Dump(ci.hash, code, !std::holds_alternative(ci.program)); } } -AnyCachedMacro MacroEngine::Compile(Engines::Maxwell3D& maxwell3d, std::span code) { +AnyCachedMacro MacroEngine::Compile(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span code) { #ifdef ARCHITECTURE_x86_64 if (!is_interpreted) - return std::make_unique(code); + return std::make_unique(system, code); #endif return MacroInterpreterImpl(code); } diff --git a/src/video_core/macro.h b/src/video_core/macro.h index a9a8f2de04..90519a5429 100644 --- a/src/video_core/macro.h +++ b/src/video_core/macro.h @@ -10,10 +10,14 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include "common/bit_field.h" #include "common/common_types.h" +namespace Core { +class System; +} + namespace Tegra { namespace Engines { @@ -106,61 +110,61 @@ struct HLEMacro { /// also assigning the base vertex/instance. struct HLE_DrawArraysIndirect final { HLE_DrawArraysIndirect(bool extended_) noexcept : extended{extended_} {} - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); - void Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters); + void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + void Fallback(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters); bool extended; }; /// @note: these macros have two versions, a normal and extended version, with the extended version /// also assigning the base vertex/instance. struct HLE_DrawIndexedIndirect final { explicit HLE_DrawIndexedIndirect(bool extended_) noexcept : extended{extended_} {} - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); - void Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters); + void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + void Fallback(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters); bool extended; }; struct HLE_MultiLayerClear final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); }; struct HLE_MultiDrawIndexedIndirectCount final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); - void Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters); + void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + void Fallback(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters); }; struct HLE_DrawIndirectByteCount final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); - void Fallback(Engines::Maxwell3D& maxwell3d, std::span parameters); + void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + void Fallback(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters); }; struct HLE_C713C83D8F63CCF3 final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); }; struct HLE_D7333D26E0A93EDE final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); }; struct HLE_BindShader final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); }; struct HLE_SetRasterBoundingBox final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); }; struct HLE_ClearConstBuffer final { HLE_ClearConstBuffer(size_t base_size_) noexcept : base_size{base_size_} {} - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); size_t base_size; }; struct HLE_ClearMemory final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); std::vector zero_memory; }; struct HLE_TransformFeedbackSetup final { - void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); + void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, [[maybe_unused]] u32 method); }; struct MacroInterpreterImpl final { MacroInterpreterImpl() {} MacroInterpreterImpl(std::span code_) : code{code_} {} - void Execute(Engines::Maxwell3D& maxwell3d, std::span params, u32 method); + void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span params, u32 method); void Reset(); - bool Step(Engines::Maxwell3D& maxwell3d, bool is_delay_slot); + bool Step(Core::System& system, Engines::Maxwell3D& maxwell3d, bool is_delay_slot); u32 GetALUResult(Macro::ALUOperation operation, u32 src_a, u32 src_b); - void ProcessResult(Engines::Maxwell3D& maxwell3d, Macro::ResultOperation operation, u32 reg, u32 result); + void ProcessResult(Core::System& system, Engines::Maxwell3D& maxwell3d, Macro::ResultOperation operation, u32 reg, u32 result); bool EvaluateBranchCondition(Macro::BranchCondition cond, u32 value) const; Macro::Opcode GetOpcode() const; u32 GetRegister(u32 register_id) const; @@ -169,7 +173,7 @@ struct MacroInterpreterImpl final { [[nodiscard]] inline void SetMethodAddress(u32 address) noexcept { method_address.raw = address; } - void Send(Engines::Maxwell3D& maxwell3d, u32 value); + void Send(Core::System& system, Engines::Maxwell3D& maxwell3d, u32 value); u32 Read(Engines::Maxwell3D& maxwell3d, u32 method) const; u32 FetchParameter(); /// General purpose macro registers. @@ -192,7 +196,7 @@ struct DynamicCachedMacro { /// Executes the macro code with the specified input parameters. /// @param parameters The parameters of the macro /// @param method The method to execute - virtual void Execute(Engines::Maxwell3D& maxwell3d, std::span parameters, u32 method) = 0; + virtual void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span parameters, u32 method) = 0; }; using AnyCachedMacro = std::variant< @@ -227,14 +231,14 @@ struct MacroEngine { uploaded_macro_code.erase(method); } // Compiles the macro if its not in the cache, and executes the compiled macro - void Execute(Engines::Maxwell3D& maxwell3d, u32 method, std::span parameters); - AnyCachedMacro Compile(Engines::Maxwell3D& maxwell3d, std::span code); + void Execute(Core::System& system, Engines::Maxwell3D& maxwell3d, u32 method, std::span parameters); + AnyCachedMacro Compile(Core::System& system, Engines::Maxwell3D& maxwell3d, std::span code); struct CacheInfo { AnyCachedMacro program; u64 hash{}; }; - ankerl::unordered_dense::map macro_cache; - ankerl::unordered_dense::map> uploaded_macro_code; + ::Common::unordered_map macro_cache; + ::Common::unordered_map> uploaded_macro_code; bool is_interpreted; }; diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index c583fbdf7d..8ffdcb6276 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -46,8 +46,8 @@ MemoryManager::MemoryManager(Core::System& system_, MaxwellDeviceMemoryManager& page_table_mask = page_table_size - 1; big_page_table_mask = big_page_table_size - 1; + big_page_table_dev.ResizeAndClear(big_page_table_size); big_entries.resize(big_page_table_size / 32, 0); - big_page_table_dev.resize(big_page_table_size); big_page_continuous.resize(big_page_table_size / continuous_bits, 0); entries.resize(page_table_size / 32, 0); } @@ -58,9 +58,8 @@ MemoryManager::MemoryManager(Core::System& system_, u64 address_space_bits_, GPU MemoryManager::~MemoryManager() = default; -template -MemoryManager::EntryType MemoryManager::GetEntry(size_t position) const { - if constexpr (is_big_page) { +MemoryManager::EntryType MemoryManager::GetEntry(size_t position, bool is_big_page) const { + if (is_big_page) { position = position >> big_page_bits; const u64 entry_mask = big_entries[position / 32]; const size_t sub_index = position % 32; @@ -73,9 +72,8 @@ MemoryManager::EntryType MemoryManager::GetEntry(size_t position) const { } } -template -void MemoryManager::SetEntry(size_t position, MemoryManager::EntryType entry) { - if constexpr (is_big_page) { +void MemoryManager::SetEntry(size_t position, MemoryManager::EntryType entry, bool is_big_page) { + if (is_big_page) { position = position >> big_page_bits; const u64 entry_mask = big_entries[position / 32]; const size_t sub_index = position % 32; @@ -108,23 +106,21 @@ inline void MemoryManager::SetBigPageContinuous(size_t big_page_index, bool valu (~(1ULL << sub_index) & continuous_mask) | (value ? 1ULL << sub_index : 0); } -template -GPUVAddr MemoryManager::PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] DAddr dev_addr, size_t size, - PTEKind kind) { +GPUVAddr MemoryManager::PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] DAddr dev_addr, size_t size, PTEKind kind, MemoryManager::EntryType entry_type) { [[maybe_unused]] u64 remaining_size{size}; - if constexpr (entry_type == EntryType::Mapped) { + if (entry_type == EntryType::Mapped) { page_table.ReserveRange(gpu_addr, size); } for (u64 offset{}; offset < size; offset += page_size) { const GPUVAddr current_gpu_addr = gpu_addr + offset; - [[maybe_unused]] const auto current_entry_type = GetEntry(current_gpu_addr); - SetEntry(current_gpu_addr, entry_type); + [[maybe_unused]] const auto current_entry_type = GetEntry(current_gpu_addr, false); + SetEntry(current_gpu_addr, entry_type, false); if (current_entry_type != entry_type) { rasterizer->ModifyGPUMemory(unique_identifier, current_gpu_addr, page_size); } - if constexpr (entry_type == EntryType::Mapped) { + if (entry_type == EntryType::Mapped) { const DAddr current_dev_addr = dev_addr + offset; - const auto index = PageEntryIndex(current_gpu_addr); + const auto index = PageEntryIndex(current_gpu_addr, false); const u32 sub_value = static_cast(current_dev_addr >> cpu_page_bits); page_table[index] = sub_value; } @@ -134,22 +130,20 @@ GPUVAddr MemoryManager::PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] DAddr de return gpu_addr; } -template -GPUVAddr MemoryManager::BigPageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] DAddr dev_addr, - size_t size, PTEKind kind) { +GPUVAddr MemoryManager::BigPageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] DAddr dev_addr, size_t size, PTEKind kind, MemoryManager::EntryType entry_type) { [[maybe_unused]] u64 remaining_size{size}; for (u64 offset{}; offset < size; offset += big_page_size) { const GPUVAddr current_gpu_addr = gpu_addr + offset; - [[maybe_unused]] const auto current_entry_type = GetEntry(current_gpu_addr); - SetEntry(current_gpu_addr, entry_type); + [[maybe_unused]] const auto current_entry_type = GetEntry(current_gpu_addr, true); + SetEntry(current_gpu_addr, entry_type, true); if (current_entry_type != entry_type) { rasterizer->ModifyGPUMemory(unique_identifier, current_gpu_addr, big_page_size); } - if constexpr (entry_type == EntryType::Mapped) { + if (entry_type == EntryType::Mapped) { const DAddr current_dev_addr = dev_addr + offset; - const auto index = PageEntryIndex(current_gpu_addr); + const auto index = PageEntryIndex(current_gpu_addr, true); const u32 sub_value = static_cast(current_dev_addr >> cpu_page_bits); - big_page_table_dev[index] = sub_value; + big_page_table_dev.Set(index, sub_value); const bool is_continuous = ([&] { uintptr_t base_ptr{ reinterpret_cast(memory.GetPointer(current_dev_addr))}; @@ -181,19 +175,16 @@ void MemoryManager::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) rasterizer = rasterizer_; } -GPUVAddr MemoryManager::Map(GPUVAddr gpu_addr, DAddr dev_addr, std::size_t size, PTEKind kind, - bool is_big_pages) { - if (is_big_pages) [[likely]] { - return BigPageTableOp(gpu_addr, dev_addr, size, kind); - } - return PageTableOp(gpu_addr, dev_addr, size, kind); +GPUVAddr MemoryManager::Map(GPUVAddr gpu_addr, DAddr dev_addr, std::size_t size, PTEKind kind, bool is_big_pages) { + if (is_big_pages) + return BigPageTableOp(gpu_addr, dev_addr, size, kind, EntryType::Mapped); + return PageTableOp(gpu_addr, dev_addr, size, kind, EntryType::Mapped); } GPUVAddr MemoryManager::MapSparse(GPUVAddr gpu_addr, std::size_t size, bool is_big_pages) { - if (is_big_pages) [[likely]] { - return BigPageTableOp(gpu_addr, 0, size, PTEKind::INVALID); - } - return PageTableOp(gpu_addr, 0, size, PTEKind::INVALID); + if (is_big_pages) + return BigPageTableOp(gpu_addr, 0, size, PTEKind::INVALID, EntryType::Reserved); + return PageTableOp(gpu_addr, 0, size, PTEKind::INVALID, EntryType::Reserved); } void MemoryManager::Unmap(GPUVAddr gpu_addr, std::size_t size) { @@ -207,26 +198,21 @@ void MemoryManager::Unmap(GPUVAddr gpu_addr, std::size_t size) { } page_stash.clear(); - BigPageTableOp(gpu_addr, 0, size, PTEKind::INVALID); - PageTableOp(gpu_addr, 0, size, PTEKind::INVALID); + BigPageTableOp(gpu_addr, 0, size, PTEKind::INVALID, EntryType::Free); + PageTableOp(gpu_addr, 0, size, PTEKind::INVALID, EntryType::Free); } std::optional MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) const { if (!IsWithinGPUAddressRange(gpu_addr)) [[unlikely]] { return std::nullopt; } - if (GetEntry(gpu_addr) != EntryType::Mapped) [[unlikely]] { - if (GetEntry(gpu_addr) != EntryType::Mapped) { + if (GetEntry(gpu_addr, true) != EntryType::Mapped) [[unlikely]] { + if (GetEntry(gpu_addr, false) != EntryType::Mapped) return std::nullopt; - } - - const DAddr dev_addr_base = static_cast(page_table[PageEntryIndex(gpu_addr)]) - << cpu_page_bits; + const DAddr dev_addr_base = DAddr(page_table[PageEntryIndex(gpu_addr, false)]) << cpu_page_bits; return dev_addr_base + (gpu_addr & page_mask); } - - const DAddr dev_addr_base = - static_cast(big_page_table_dev[PageEntryIndex(gpu_addr)]) << cpu_page_bits; + const DAddr dev_addr_base = DAddr(big_page_table_dev[PageEntryIndex(gpu_addr, true)]) << cpu_page_bits; return dev_addr_base + (gpu_addr & big_page_mask); } @@ -299,10 +285,8 @@ const u8* MemoryManager::GetPointer(GPUVAddr gpu_addr) const { #pragma inline_recursion(on) #endif -template -inline void MemoryManager::MemoryOperation(GPUVAddr gpu_src_addr, std::size_t size, - FuncMapped&& func_mapped, FuncReserved&& func_reserved, - FuncUnmapped&& func_unmapped) const { +template +inline void MemoryManager::MemoryOperation(GPUVAddr gpu_src_addr, std::size_t size, bool is_big_page, FuncMapped&& func_mapped, FuncReserved&& func_reserved, FuncUnmapped&& func_unmapped) const { using FuncMappedReturn = typename std::invoke_result::type; using FuncReservedReturn = @@ -315,7 +299,7 @@ inline void MemoryManager::MemoryOperation(GPUVAddr gpu_src_addr, std::size_t si u64 used_page_size; u64 used_page_mask; u64 used_page_bits; - if constexpr (is_big_pages) { + if (is_big_page) { used_page_size = big_page_size; used_page_mask = big_page_mask; used_page_bits = big_page_bits; @@ -332,7 +316,7 @@ inline void MemoryManager::MemoryOperation(GPUVAddr gpu_src_addr, std::size_t si while (remaining_size > 0) { const std::size_t copy_amount{ (std::min)(static_cast(used_page_size) - page_offset, remaining_size)}; - auto entry = GetEntry(current_address); + auto entry = GetEntry(current_address, is_big_page); if (entry == EntryType::Mapped) [[likely]] { if constexpr (BOOL_BREAK_MAPPED) { if (func_mapped(page_index, page_offset, copy_amount)) { @@ -367,18 +351,14 @@ inline void MemoryManager::MemoryOperation(GPUVAddr gpu_src_addr, std::size_t si } } -template -void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, - [[maybe_unused]] VideoCommon::CacheType which) const { - auto set_to_zero = [&]([[maybe_unused]] std::size_t page_index, - [[maybe_unused]] std::size_t offset, std::size_t copy_amount) { +void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, [[maybe_unused]] VideoCommon::CacheType which, bool unsafe) const { + auto set_to_zero = [&]([[maybe_unused]] std::size_t page_index, [[maybe_unused]] std::size_t offset, std::size_t copy_amount) { std::memset(dest_buffer, 0, copy_amount); dest_buffer = static_cast(dest_buffer) + copy_amount; }; auto mapped_normal = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { - const DAddr dev_addr_base = - (static_cast(page_table[page_index]) << cpu_page_bits) + offset; - if constexpr (is_safe) { + const DAddr dev_addr_base = (DAddr(page_table[page_index]) << cpu_page_bits) + offset; + if (!unsafe) { rasterizer->FlushRegion(dev_addr_base, copy_amount, which); } u8* physical = memory.GetPointer(dev_addr_base); @@ -386,9 +366,8 @@ void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std: dest_buffer = static_cast(dest_buffer) + copy_amount; }; auto mapped_big = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { - const DAddr dev_addr_base = - (static_cast(big_page_table_dev[page_index]) << cpu_page_bits) + offset; - if constexpr (is_safe) { + const DAddr dev_addr_base = (DAddr(big_page_table_dev[page_index]) << cpu_page_bits) + offset; + if (!unsafe) { rasterizer->FlushRegion(dev_addr_base, copy_amount, which); } if (!IsBigPageContinuous(page_index)) [[unlikely]] { @@ -399,35 +378,28 @@ void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std: } dest_buffer = static_cast(dest_buffer) + copy_amount; }; - auto read_short_pages = [&](std::size_t page_index, std::size_t offset, - std::size_t copy_amount) { + auto read_short_pages = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { GPUVAddr base = (page_index << big_page_bits) + offset; - MemoryOperation(base, copy_amount, mapped_normal, set_to_zero, set_to_zero); + MemoryOperation(base, copy_amount, false, mapped_normal, set_to_zero, set_to_zero); }; - MemoryOperation(gpu_src_addr, size, mapped_big, set_to_zero, read_short_pages); + MemoryOperation(gpu_src_addr, size, true, mapped_big, set_to_zero, read_short_pages); } -void MemoryManager::ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, - VideoCommon::CacheType which) const { - ReadBlockImpl(gpu_src_addr, dest_buffer, size, which); +void MemoryManager::ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, VideoCommon::CacheType which) const { + ReadBlockImpl(gpu_src_addr, dest_buffer, size, which, false); } -void MemoryManager::ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer, - const std::size_t size) const { - ReadBlockImpl(gpu_src_addr, dest_buffer, size, VideoCommon::CacheType::None); +void MemoryManager::ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer, const std::size_t size) const { + ReadBlockImpl(gpu_src_addr, dest_buffer, size, VideoCommon::CacheType::None, true); } -template -void MemoryManager::WriteBlockImpl(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size, - [[maybe_unused]] VideoCommon::CacheType which) { - auto just_advance = [&]([[maybe_unused]] std::size_t page_index, - [[maybe_unused]] std::size_t offset, std::size_t copy_amount) { +void MemoryManager::WriteBlockImpl(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size, [[maybe_unused]] VideoCommon::CacheType which, bool unsafe) { + auto just_advance = [&]([[maybe_unused]] std::size_t page_index, [[maybe_unused]] std::size_t offset, std::size_t copy_amount) { src_buffer = static_cast(src_buffer) + copy_amount; }; auto mapped_normal = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { - const DAddr dev_addr_base = - (static_cast(page_table[page_index]) << cpu_page_bits) + offset; - if constexpr (is_safe) { + const DAddr dev_addr_base = (DAddr(page_table[page_index]) << cpu_page_bits) + offset; + if (!unsafe) { rasterizer->InvalidateRegion(dev_addr_base, copy_amount, which); } u8* physical = memory.GetPointer(dev_addr_base); @@ -435,9 +407,8 @@ void MemoryManager::WriteBlockImpl(GPUVAddr gpu_dest_addr, const void* src_buffe src_buffer = static_cast(src_buffer) + copy_amount; }; auto mapped_big = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { - const DAddr dev_addr_base = - (static_cast(big_page_table_dev[page_index]) << cpu_page_bits) + offset; - if constexpr (is_safe) { + const DAddr dev_addr_base = (DAddr(big_page_table_dev[page_index]) << cpu_page_bits) + offset; + if (!unsafe) { rasterizer->InvalidateRegion(dev_addr_base, copy_amount, which); } if (!IsBigPageContinuous(page_index)) [[unlikely]] { @@ -448,26 +419,23 @@ void MemoryManager::WriteBlockImpl(GPUVAddr gpu_dest_addr, const void* src_buffe } src_buffer = static_cast(src_buffer) + copy_amount; }; - auto write_short_pages = [&](std::size_t page_index, std::size_t offset, - std::size_t copy_amount) { + auto write_short_pages = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { GPUVAddr base = (page_index << big_page_bits) + offset; - MemoryOperation(base, copy_amount, mapped_normal, just_advance, just_advance); + MemoryOperation(base, copy_amount, false, mapped_normal, just_advance, just_advance); }; - MemoryOperation(gpu_dest_addr, size, mapped_big, just_advance, write_short_pages); + MemoryOperation(gpu_dest_addr, size, true, mapped_big, just_advance, write_short_pages); } -void MemoryManager::WriteBlock(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size, - VideoCommon::CacheType which) { - WriteBlockImpl(gpu_dest_addr, src_buffer, size, which); +void MemoryManager::WriteBlock(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size, VideoCommon::CacheType which) { + WriteBlockImpl(gpu_dest_addr, src_buffer, size, which, false); } -void MemoryManager::WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buffer, - std::size_t size) { - WriteBlockImpl(gpu_dest_addr, src_buffer, size, VideoCommon::CacheType::None); +void MemoryManager::WriteBlockUnsafe(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size) { + WriteBlockImpl(gpu_dest_addr, src_buffer, size, VideoCommon::CacheType::None, true); } void MemoryManager::WriteBlockCached(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size) { - WriteBlockImpl(gpu_dest_addr, src_buffer, size, VideoCommon::CacheType::None); + WriteBlockImpl(gpu_dest_addr, src_buffer, size, VideoCommon::CacheType::None, true); accumulator.Add(gpu_dest_addr, size); } @@ -478,21 +446,18 @@ void MemoryManager::FlushRegion(GPUVAddr gpu_addr, size_t size, [[maybe_unused]] std::size_t copy_amount) {}; auto mapped_normal = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { - const DAddr dev_addr_base = - (static_cast(page_table[page_index]) << cpu_page_bits) + offset; + const DAddr dev_addr_base = (DAddr(page_table[page_index]) << cpu_page_bits) + offset; rasterizer->FlushRegion(dev_addr_base, copy_amount, which); }; auto mapped_big = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { - const DAddr dev_addr_base = - (static_cast(big_page_table_dev[page_index]) << cpu_page_bits) + offset; + const DAddr dev_addr_base = (DAddr(big_page_table_dev[page_index]) << cpu_page_bits) + offset; rasterizer->FlushRegion(dev_addr_base, copy_amount, which); }; - auto flush_short_pages = [&](std::size_t page_index, std::size_t offset, - std::size_t copy_amount) { + auto flush_short_pages = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { GPUVAddr base = (page_index << big_page_bits) + offset; - MemoryOperation(base, copy_amount, mapped_normal, do_nothing, do_nothing); + MemoryOperation(base, copy_amount, false, mapped_normal, do_nothing, do_nothing); }; - MemoryOperation(gpu_addr, size, mapped_big, do_nothing, flush_short_pages); + MemoryOperation(gpu_addr, size, true, mapped_big, do_nothing, flush_short_pages); } bool MemoryManager::IsMemoryDirty(GPUVAddr gpu_addr, size_t size, @@ -517,10 +482,10 @@ bool MemoryManager::IsMemoryDirty(GPUVAddr gpu_addr, size_t size, auto check_short_pages = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { GPUVAddr base = (page_index << big_page_bits) + offset; - MemoryOperation(base, copy_amount, mapped_normal, do_nothing, do_nothing); + MemoryOperation(base, copy_amount, false, mapped_normal, do_nothing, do_nothing); return result; }; - MemoryOperation(gpu_addr, size, mapped_big, do_nothing, check_short_pages); + MemoryOperation(gpu_addr, size, true, mapped_big, do_nothing, check_short_pages); return result; } @@ -557,10 +522,10 @@ size_t MemoryManager::MaxContinuousRange(GPUVAddr gpu_addr, size_t size) const { auto check_short_pages = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { GPUVAddr base = (page_index << big_page_bits) + offset; - MemoryOperation(base, copy_amount, short_check, fail, fail); + MemoryOperation(base, copy_amount, false, short_check, fail, fail); return result; }; - MemoryOperation(gpu_addr, size, big_check, fail, check_short_pages); + MemoryOperation(gpu_addr, size, true, big_check, fail, check_short_pages); return range_so_far; } @@ -576,21 +541,18 @@ void MemoryManager::InvalidateRegion(GPUVAddr gpu_addr, size_t size, [[maybe_unused]] std::size_t copy_amount) {}; auto mapped_normal = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { - const DAddr dev_addr_base = - (static_cast(page_table[page_index]) << cpu_page_bits) + offset; + const DAddr dev_addr_base = (DAddr(page_table[page_index]) << cpu_page_bits) + offset; rasterizer->InvalidateRegion(dev_addr_base, copy_amount, which); }; auto mapped_big = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { - const DAddr dev_addr_base = - (static_cast(big_page_table_dev[page_index]) << cpu_page_bits) + offset; + const DAddr dev_addr_base = (DAddr(big_page_table_dev[page_index]) << cpu_page_bits) + offset; rasterizer->InvalidateRegion(dev_addr_base, copy_amount, which); }; - auto invalidate_short_pages = [&](std::size_t page_index, std::size_t offset, - std::size_t copy_amount) { + auto invalidate_short_pages = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { GPUVAddr base = (page_index << big_page_bits) + offset; - MemoryOperation(base, copy_amount, mapped_normal, do_nothing, do_nothing); + MemoryOperation(base, copy_amount, false, mapped_normal, do_nothing, do_nothing); }; - MemoryOperation(gpu_addr, size, mapped_big, do_nothing, invalidate_short_pages); + MemoryOperation(gpu_addr, size, true, mapped_big, do_nothing, invalidate_short_pages); } void MemoryManager::CopyBlock(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, std::size_t size, @@ -602,7 +564,7 @@ void MemoryManager::CopyBlock(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, std } bool MemoryManager::IsGranularRange(GPUVAddr gpu_addr, std::size_t size) const { - if (GetEntry(gpu_addr) == EntryType::Mapped) [[likely]] { + if (GetEntry(gpu_addr, true) == EntryType::Mapped) [[likely]] { size_t page_index = gpu_addr >> big_page_bits; if (IsBigPageContinuous(page_index)) [[likely]] { const std::size_t page{(page_index & big_page_mask) + size}; @@ -611,7 +573,7 @@ bool MemoryManager::IsGranularRange(GPUVAddr gpu_addr, std::size_t size) const { const std::size_t page{(gpu_addr & Core::DEVICE_PAGEMASK) + size}; return page <= Core::DEVICE_PAGESIZE; } - if (GetEntry(gpu_addr) != EntryType::Mapped) { + if (GetEntry(gpu_addr, false) != EntryType::Mapped) { return false; } const std::size_t page{(gpu_addr & Core::DEVICE_PAGEMASK) + size}; @@ -649,10 +611,10 @@ bool MemoryManager::IsContinuousRange(GPUVAddr gpu_addr, std::size_t size) const auto check_short_pages = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { GPUVAddr base = (page_index << big_page_bits) + offset; - MemoryOperation(base, copy_amount, short_check, fail, fail); + MemoryOperation(base, copy_amount, false, short_check, fail, fail); return !result; }; - MemoryOperation(gpu_addr, size, big_check, fail, check_short_pages); + MemoryOperation(gpu_addr, size, true, big_check, fail, check_short_pages); return result; } @@ -665,13 +627,12 @@ bool MemoryManager::IsFullyMappedRange(GPUVAddr gpu_addr, std::size_t size) cons }; auto pass = [&]([[maybe_unused]] std::size_t page_index, [[maybe_unused]] std::size_t offset, [[maybe_unused]] std::size_t copy_amount) { return false; }; - auto check_short_pages = [&](std::size_t page_index, std::size_t offset, - std::size_t copy_amount) { + auto check_short_pages = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { GPUVAddr base = (page_index << big_page_bits) + offset; - MemoryOperation(base, copy_amount, pass, pass, fail); + MemoryOperation(base, copy_amount, false, pass, pass, fail); return !result; }; - MemoryOperation(gpu_addr, size, pass, fail, check_short_pages); + MemoryOperation(gpu_addr, size, true, pass, fail, check_short_pages); return result; } @@ -683,13 +644,9 @@ MemoryManager::GetSubmappedRange(GPUVAddr gpu_addr, std::size_t size) const { } template -void MemoryManager::GetSubmappedRangeImpl( - GPUVAddr gpu_addr, std::size_t size, - boost::container::small_vector< - std::pair, std::size_t>, 32>& result) +void MemoryManager::GetSubmappedRangeImpl(GPUVAddr gpu_addr, std::size_t size, boost::container::small_vector, std::size_t>, 32>& result) const { - std::optional, std::size_t>> - last_segment{}; + std::optional, std::size_t>> last_segment{}; std::optional old_page_addr{}; const auto split = [&last_segment, &result]([[maybe_unused]] std::size_t page_index, [[maybe_unused]] std::size_t offset, @@ -745,9 +702,9 @@ void MemoryManager::GetSubmappedRangeImpl( }; auto do_short_pages = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { GPUVAddr base = (page_index << big_page_bits) + offset; - MemoryOperation(base, copy_amount, extend_size_short, split, split); + MemoryOperation(base, copy_amount, false, extend_size_short, split, split); }; - MemoryOperation(gpu_addr, size, extend_size_big, split, do_short_pages); + MemoryOperation(gpu_addr, size, true, extend_size_big, split, do_short_pages); split(0, 0, 0); } @@ -756,7 +713,7 @@ void MemoryManager::FlushCaching() { if (accumulator.InvalidateAll([this](GPUVAddr addr, size_t size) { GetSubmappedRangeImpl(addr, size, page_stash2); })) { - rasterizer->InnerInvalidation(VideoCommon::FixSmallVectorADL(page_stash2)); + rasterizer->InnerInvalidation(page_stash2); page_stash2.clear(); } } diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h index 868198677c..08978567d2 100644 --- a/src/video_core/memory_manager.h +++ b/src/video_core/memory_manager.h @@ -17,7 +17,7 @@ #include "common/multi_level_page_table.h" #include "common/range_map.h" #include "common/scratch_buffer.h" -#include "common/virtual_buffer.h" +#include "common/sparse_large_vector.h" #include "video_core/invalidation_accumulator.h" #include "video_core/cache_types.h" #include "video_core/host1x/gpu_device_memory_manager.h" @@ -45,7 +45,7 @@ public: static constexpr bool HAS_FLUSH_INVALIDATION = true; - size_t GetID() const { + inline size_t GetID() const noexcept { return unique_identifier; } @@ -66,16 +66,15 @@ public: [[nodiscard]] const u8* GetPointer(GPUVAddr addr) const; template - [[nodiscard]] T* GetPointer(GPUVAddr addr) { - const auto address{GpuToCpuAddress(addr)}; - if (!address) { + [[nodiscard]] inline T* GetPointer(GPUVAddr addr) noexcept { + const auto address = GpuToCpuAddress(addr); + if (!address) return {}; - } return memory.GetPointer(*address); } template - [[nodiscard]] const T* GetPointer(GPUVAddr addr) const { + [[nodiscard]] inline const T* GetPointer(GPUVAddr addr) const noexcept { return GetPointer(addr); } @@ -85,12 +84,9 @@ public: * in the Host Memory counterpart. Note: This functions cause Host GPU Memory * Flushes and Invalidations, respectively to each operation. */ - void ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, - VideoCommon::CacheType which = VideoCommon::CacheType::All) const; - void WriteBlock(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size, - VideoCommon::CacheType which = VideoCommon::CacheType::All); - void CopyBlock(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, std::size_t size, - VideoCommon::CacheType which = VideoCommon::CacheType::All); + void ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, VideoCommon::CacheType which = VideoCommon::CacheType::All) const; + void WriteBlock(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size, VideoCommon::CacheType which = VideoCommon::CacheType::All); + void CopyBlock(GPUVAddr gpu_dest_addr, GPUVAddr gpu_src_addr, std::size_t size, VideoCommon::CacheType which = VideoCommon::CacheType::All); /** * ReadBlockUnsafe and WriteBlockUnsafe are special versions of ReadBlock and @@ -160,21 +156,14 @@ public: u8* GetSpan(const GPUVAddr src_addr, const std::size_t size); private: - template - inline void MemoryOperation(GPUVAddr gpu_src_addr, std::size_t size, FuncMapped&& func_mapped, - FuncReserved&& func_reserved, FuncUnmapped&& func_unmapped) const; + template + inline void MemoryOperation(GPUVAddr gpu_src_addr, std::size_t size, bool is_big_page, FuncMapped&& func_mapped, FuncReserved&& func_reserved, FuncUnmapped&& func_unmapped) const; - template - void ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, - VideoCommon::CacheType which) const; + void ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, VideoCommon::CacheType which, bool unsafe) const; + void WriteBlockImpl(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size, VideoCommon::CacheType which, bool unsafe); - template - void WriteBlockImpl(GPUVAddr gpu_dest_addr, const void* src_buffer, std::size_t size, - VideoCommon::CacheType which); - - template - [[nodiscard]] std::size_t PageEntryIndex(GPUVAddr gpu_addr) const { - if constexpr (is_big_page) { + [[nodiscard]] std::size_t PageEntryIndex(GPUVAddr gpu_addr, bool is_big_page) const { + if (is_big_page) { return (gpu_addr >> big_page_bits) & big_page_table_mask; } else { return (gpu_addr >> page_bits) & page_table_mask; @@ -187,9 +176,7 @@ private: template void GetSubmappedRangeImpl( GPUVAddr gpu_addr, std::size_t size, - boost::container::small_vector< - std::pair, std::size_t>, 32>& - result) const; + boost::container::small_vector, std::size_t>, 32>& result) const; Core::System& system; MaxwellDeviceMemoryManager& memory; @@ -219,23 +206,15 @@ private: std::vector entries; std::vector big_entries; - template - GPUVAddr PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] DAddr dev_addr, size_t size, - PTEKind kind); + GPUVAddr PageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] DAddr dev_addr, size_t size, PTEKind kind, EntryType entry_type); + GPUVAddr BigPageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] DAddr dev_addr, size_t size, PTEKind kind, EntryType entry_type); - template - GPUVAddr BigPageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] DAddr dev_addr, size_t size, - PTEKind kind); - - template - inline EntryType GetEntry(size_t position) const; - - template - inline void SetEntry(size_t position, EntryType entry); + inline EntryType GetEntry(size_t position, bool is_big_page) const; + inline void SetEntry(size_t position, EntryType entry, bool is_big_page); Common::MultiLevelPageTable page_table; Common::RangeMap kind_map; - Common::VirtualBuffer big_page_table_dev; + Common::SparseLargeVector big_page_table_dev; std::vector big_page_continuous; boost::container::small_vector, 32> page_stash{}; diff --git a/src/video_core/post_processing/GenerateBundledEffects.cmake b/src/video_core/post_processing/GenerateBundledEffects.cmake new file mode 100644 index 0000000000..d3aca02571 --- /dev/null +++ b/src/video_core/post_processing/GenerateBundledEffects.cmake @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +set(EFFECT_DIR ${CMAKE_ARGV3}) +set(HEADER_FILE ${CMAKE_ARGV4}) + +file(GLOB EFFECT_FILES ${EFFECT_DIR}/*.fx) +list(SORT EFFECT_FILES) + +set(ENTRIES "") +foreach(EFFECT_FILE IN LISTS EFFECT_FILES) + get_filename_component(EFFECT_NAME ${EFFECT_FILE} NAME) + file(READ ${EFFECT_FILE} EFFECT_BODY) + + string(REGEX REPLACE ";" "{{SEMICOLON}}" EFFECT_BODY "${EFFECT_BODY}") + string(REGEX REPLACE "\n" ";" EFFECT_BODY "${EFFECT_BODY}") + + set(EFFECT_TEXT "") + foreach(LINE IN LISTS EFFECT_BODY) + string(CONCAT EFFECT_TEXT "${EFFECT_TEXT}" " R\"(${LINE}\n)\"\n") + endforeach() + string(REGEX REPLACE "{{SEMICOLON}}" ";" EFFECT_TEXT "${EFFECT_TEXT}") + + string(CONCAT ENTRIES "${ENTRIES}" + " {\n \"${EFFECT_NAME}\",\n${EFFECT_TEXT} },\n") +endforeach() + +get_filename_component(OUTPUT_DIR ${HEADER_FILE} DIRECTORY) +make_directory(${OUTPUT_DIR}) + +file(WRITE ${HEADER_FILE} +"// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +namespace VideoCore { + +struct BundledFxEffect { + std::string_view name; + std::string_view source; +}; + +constexpr BundledFxEffect BUNDLED_FX_EFFECTS[]{ +${ENTRIES}}; + +} // namespace VideoCore +") diff --git a/src/video_core/post_processing/GenerateBundledPresets.cmake b/src/video_core/post_processing/GenerateBundledPresets.cmake new file mode 100644 index 0000000000..369a46748d --- /dev/null +++ b/src/video_core/post_processing/GenerateBundledPresets.cmake @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +set(PRESET_DIR ${CMAKE_ARGV3}) +set(HEADER_FILE ${CMAKE_ARGV4}) + +file(GLOB PRESET_FILES ${PRESET_DIR}/*.fxp) +list(SORT PRESET_FILES) + +set(ENTRIES "") +foreach(PRESET_FILE IN LISTS PRESET_FILES) + get_filename_component(PRESET_NAME ${PRESET_FILE} NAME) + file(READ ${PRESET_FILE} PRESET_BODY) + + string(REGEX REPLACE ";" "{{SEMICOLON}}" PRESET_BODY "${PRESET_BODY}") + string(REGEX REPLACE "\n" ";" PRESET_BODY "${PRESET_BODY}") + + set(PRESET_TEXT "") + foreach(LINE IN LISTS PRESET_BODY) + string(CONCAT PRESET_TEXT "${PRESET_TEXT}" " R\"(${LINE}\n)\"\n") + endforeach() + string(REGEX REPLACE "{{SEMICOLON}}" ";" PRESET_TEXT "${PRESET_TEXT}") + + string(CONCAT ENTRIES "${ENTRIES}" + " {\n \"${PRESET_NAME}\",\n${PRESET_TEXT} },\n") +endforeach() + +get_filename_component(OUTPUT_DIR ${HEADER_FILE} DIRECTORY) +make_directory(${OUTPUT_DIR}) + +file(WRITE ${HEADER_FILE} +"// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +namespace VideoCore { + +struct BundledFxPreset { + std::string_view name; + std::string_view source; +}; + +constexpr BundledFxPreset BUNDLED_FX_PRESETS[]{ +${ENTRIES}}; + +} // namespace VideoCore +") diff --git a/src/video_core/post_processing/fx_chain.cpp b/src/video_core/post_processing/fx_chain.cpp new file mode 100644 index 0000000000..882fdaf726 --- /dev/null +++ b/src/video_core/post_processing/fx_chain.cpp @@ -0,0 +1,321 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include + +#include "common/settings.h" +#include "video_core/post_processing/fx_chain.h" +#include "video_core/post_processing/fx_effect.h" + +namespace VideoCore { + +namespace { + +std::vector Split(std::string_view value, char separator) { + std::vector out; + size_t start = 0; + while (start <= value.size()) { + size_t end = value.find(separator, start); + if (end == std::string_view::npos) { + end = value.size(); + } + out.push_back(value.substr(start, end - start)); + start = end + 1; + } + return out; +} + +bool IsSerializableName(std::string_view value) { + return value.find_first_of(";|,=") == std::string_view::npos; +} + +} // Anonymous namespace + +std::vector ParseFxChain(std::string_view value) { + std::vector parsed; + + for (const std::string_view record : Split(value, ';')) { + if (record.empty()) { + continue; + } + + const auto fields = Split(record, '|'); + if (fields.size() < 2 || fields[0].empty() || fields[1].empty()) { + continue; + } + + FxChainEntry entry; + entry.file = std::string(fields[0]); + entry.technique = std::string(fields[1]); + + if (fields.size() >= 3) { + for (const std::string_view assignment : Split(fields[2], ',')) { + const size_t equals = assignment.find('='); + if (equals == std::string_view::npos) { + continue; + } + const std::string name(assignment.substr(0, equals)); + if (name.empty()) { + continue; + } + + std::array components{}; + size_t index = 0; + for (const std::string_view piece : Split(assignment.substr(equals + 1), '/')) { + if (index >= components.size()) { + break; + } + const std::string text(piece); + if (!text.empty()) { + components[index] = std::strtof(text.c_str(), nullptr); + } + ++index; + } + entry.values.emplace(name, components); + } + } + + parsed.push_back(std::move(entry)); + } + + return parsed; +} + +std::string SerializeFxChain(std::span entries) { + std::string out; + + for (const auto& entry : entries) { + if (!IsSerializableName(entry.file) || !IsSerializableName(entry.technique)) { + continue; + } + if (!out.empty()) { + out += ';'; + } + out += fmt::format("{}|{}|", entry.file, entry.technique); + + bool first = true; + for (const auto& [name, value] : entry.values) { + if (!IsSerializableName(name)) { + continue; + } + if (!first) { + out += ','; + } + first = false; + out += name; + out += '='; + for (size_t i = 0; i < value.size(); ++i) { + if (i > 0) { + out += '/'; + } + out += fmt::format("{}", value[i]); + } + } + } + + return out; +} + +void UseGlobalFxSettings() { + Settings::values.post_shader_chain.SetGlobal(true); + Settings::values.post_shader_preset.SetGlobal(true); + Settings::values.post_shader_enabled.SetGlobal(true); +} + +void UsePerGameFxSettings() { + const std::string chain = Settings::values.post_shader_chain.GetValue(); + const std::string preset = Settings::values.post_shader_preset.GetValue(); + const bool enabled = Settings::values.post_shader_enabled.GetValue(); + + Settings::values.post_shader_chain.SetGlobal(false); + Settings::values.post_shader_preset.SetGlobal(false); + Settings::values.post_shader_enabled.SetGlobal(false); + + Settings::values.post_shader_chain.SetValue(chain); + Settings::values.post_shader_preset.SetValue(preset); + Settings::values.post_shader_enabled.SetValue(enabled); +} + +FxChain& FxChain::Instance() { + static FxChain instance; + return instance; +} + +FxChainSnapshot FxChain::Snapshot() const { + std::scoped_lock lock{mutex}; + return FxChainSnapshot{ + .entries = entries, + .generation = generation.load(std::memory_order_relaxed), + }; +} + +std::vector FxChain::Entries() const { + std::scoped_lock lock{mutex}; + return entries; +} + +size_t FxChain::Size() const { + std::scoped_lock lock{mutex}; + return entries.size(); +} + +void FxChain::Append(std::string_view file, std::string_view technique) { + { + std::scoped_lock lock{mutex}; + FxChainEntry entry; + entry.file = std::string(file); + entry.technique = std::string(technique); + entries.push_back(std::move(entry)); + } + generation.fetch_add(1, std::memory_order_relaxed); +} + +void FxChain::Replace(size_t index, std::string_view file, std::string_view technique) { + { + std::scoped_lock lock{mutex}; + if (index >= entries.size()) { + return; + } + if (entries[index].file == file && entries[index].technique == technique) { + return; + } + FxChainEntry entry; + entry.file = std::string(file); + entry.technique = std::string(technique); + entries[index] = std::move(entry); + } + generation.fetch_add(1, std::memory_order_relaxed); +} + +void FxChain::Remove(size_t index) { + { + std::scoped_lock lock{mutex}; + if (index >= entries.size()) { + return; + } + entries.erase(entries.begin() + static_cast(index)); + } + generation.fetch_add(1, std::memory_order_relaxed); +} + +void FxChain::Move(size_t index, int delta) { + { + std::scoped_lock lock{mutex}; + if (index >= entries.size()) { + return; + } + const std::ptrdiff_t target = static_cast(index) + delta; + if (target < 0 || target >= static_cast(entries.size())) { + return; + } + std::swap(entries[index], entries[static_cast(target)]); + } + generation.fetch_add(1, std::memory_order_relaxed); +} + +void FxChain::Clear() { + { + std::scoped_lock lock{mutex}; + entries.clear(); + } + generation.fetch_add(1, std::memory_order_relaxed); +} + +void FxChain::SetEntries(std::vector next) { + { + std::scoped_lock lock{mutex}; + entries = std::move(next); + } + generation.fetch_add(1, std::memory_order_relaxed); +} + +void FxChain::SetValue(size_t index, std::string_view uniform, const std::array& value) { + std::scoped_lock lock{mutex}; + if (index >= entries.size()) { + return; + } + entries[index].values[std::string(uniform)] = value; +} + +std::array FxChain::GetValue(size_t index, std::string_view uniform) const { + std::scoped_lock lock{mutex}; + if (index >= entries.size()) { + return {}; + } + const auto it = entries[index].values.find(std::string(uniform)); + if (it == entries[index].values.end()) { + return {}; + } + return it->second; +} + +std::map> FxChain::EntryValues(size_t index) const { + std::scoped_lock lock{mutex}; + if (index >= entries.size()) { + return {}; + } + return entries[index].values; +} + +bool FxChain::HasValue(size_t index, std::string_view uniform) const { + std::scoped_lock lock{mutex}; + if (index >= entries.size()) { + return false; + } + return entries[index].values.contains(std::string(uniform)); +} + +void FxChain::ResetValues(size_t index) { + std::scoped_lock lock{mutex}; + if (index >= entries.size()) { + return; + } + entries[index].values.clear(); +} + +void FxChain::LoadFromSettings() { + auto parsed = ParseFxChain(Settings::values.post_shader_chain.GetValue()); + + std::scoped_lock lock{mutex}; + if (entries == parsed) { + return; + } + entries = std::move(parsed); + generation.fetch_add(1, std::memory_order_relaxed); +} + +void FxChain::StoreToSettings() const { + std::string serialized; + { + std::scoped_lock lock{mutex}; + serialized = SerializeFxChain(entries); + } + Settings::values.post_shader_chain.SetValue(serialized); +} + +void FxChain::DropUnknownEntries() { + bool changed = false; + { + std::scoped_lock lock{mutex}; + const auto removed = std::remove_if(entries.begin(), entries.end(), [](const FxChainEntry& entry) { + const FxEffectDesc* effect = FindFxEffect(entry.file); + if (effect == nullptr || !effect->Valid()) { + return true; + } + return std::find(effect->techniques.begin(), effect->techniques.end(), + entry.technique) == effect->techniques.end(); + }); + if (removed != entries.end()) { + entries.erase(removed, entries.end()); + changed = true; + } + } + if (changed) { + generation.fetch_add(1, std::memory_order_relaxed); + } +} + +} // namespace VideoCore diff --git a/src/video_core/post_processing/fx_chain.h b/src/video_core/post_processing/fx_chain.h new file mode 100644 index 0000000000..066f7e428d --- /dev/null +++ b/src/video_core/post_processing/fx_chain.h @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/common_types.h" + +namespace VideoCore { + +struct FxChainEntry { + std::string file; + std::string technique; + std::map> values; + + bool operator==(const FxChainEntry&) const = default; +}; + +struct FxChainSnapshot { + std::vector entries; + u64 generation{}; +}; + +std::vector ParseFxChain(std::string_view value); + +std::string SerializeFxChain(std::span entries); + +void UseGlobalFxSettings(); + +void UsePerGameFxSettings(); + +class FxChain { +public: + static FxChain& Instance(); + + FxChainSnapshot Snapshot() const; + + std::vector Entries() const; + + size_t Size() const; + + void Append(std::string_view file, std::string_view technique); + + void Replace(size_t index, std::string_view file, std::string_view technique); + + void Remove(size_t index); + + void Move(size_t index, int delta); + + void Clear(); + + void SetEntries(std::vector next); + + void SetValue(size_t index, std::string_view uniform, const std::array& value); + + std::array GetValue(size_t index, std::string_view uniform) const; + + std::map> EntryValues(size_t index) const; + + bool HasValue(size_t index, std::string_view uniform) const; + + void ResetValues(size_t index); + + void LoadFromSettings(); + + void StoreToSettings() const; + + void DropUnknownEntries(); + +private: + FxChain() = default; + + mutable std::mutex mutex; + std::vector entries; + std::atomic generation{1}; +}; + +} // namespace VideoCore diff --git a/src/video_core/post_processing/fx_compile.cpp b/src/video_core/post_processing/fx_compile.cpp new file mode 100644 index 0000000000..5ee11e9187 --- /dev/null +++ b/src/video_core/post_processing/fx_compile.cpp @@ -0,0 +1,105 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include + +#include "effect_codegen.hpp" +#include "effect_parser.hpp" +#include "effect_preprocessor.hpp" + +#include "common/fs/fs.h" +#include "common/fs/fs_util.h" +#include "video_core/post_processing/fx_compile.h" +#include "video_core/post_processing/fx_effect.h" + +namespace VideoCore { + +FxCompileResult CompileFxEffect(const std::filesystem::path& path, u32 width, u32 height, + u32 color_bit_depth) { + FxCompileResult result; + + if (!Common::FS::Exists(path)) { + result.error = "Effect file not found: " + Common::FS::PathToUTF8String(path); + return result; + } + + reshadefx::preprocessor preprocessor; + preprocessor.add_macro_definition("__RESHADE__", "60800"); + preprocessor.add_macro_definition("__RESHADE_PERFORMANCE_MODE__", "1"); + preprocessor.add_macro_definition("__RENDERER__", "0x20000"); + preprocessor.add_macro_definition("__VENDOR__", "0"); + preprocessor.add_macro_definition("__DEVICE__", "0"); + preprocessor.add_macro_definition("__APPLICATION__", "0"); + preprocessor.add_macro_definition("BUFFER_WIDTH", std::to_string(width)); + preprocessor.add_macro_definition("BUFFER_HEIGHT", std::to_string(height)); + preprocessor.add_macro_definition("BUFFER_RCP_WIDTH", "(1.0 / BUFFER_WIDTH)"); + preprocessor.add_macro_definition("BUFFER_RCP_HEIGHT", "(1.0 / BUFFER_HEIGHT)"); + preprocessor.add_macro_definition("BUFFER_COLOR_DEPTH", std::to_string(color_bit_depth)); + preprocessor.add_macro_definition("BUFFER_COLOR_BIT_DEPTH", std::to_string(color_bit_depth)); + + for (const auto& include : GetFxIncludePaths(path)) { + preprocessor.add_include_path(include); + } + + if (!preprocessor.append_file(path)) { + result.error = preprocessor.errors(); + if (result.error.empty()) { + result.error = "Failed to preprocess " + Common::FS::PathToUTF8String(path); + } + return result; + } + + std::unique_ptr backend( + reshadefx::create_codegen_spirv(true, false, false, false, true)); + + reshadefx::parser parser; + if (!parser.parse(preprocessor.output(), backend.get())) { + result.error = parser.errors(); + if (result.error.empty()) { + result.error = "Failed to parse " + Common::FS::PathToUTF8String(path); + } + return result; + } + + result.module = backend->module(); + + std::set wanted; + for (const auto& technique : result.module.techniques) { + for (const auto& pass : technique.passes) { + if (!pass.vs_entry_point.empty()) { + wanted.insert(pass.vs_entry_point); + } + if (!pass.ps_entry_point.empty()) { + wanted.insert(pass.ps_entry_point); + } + } + } + + for (const auto& name : wanted) { + std::string binary; + std::string assembly; + std::string errors; + if (!backend->assemble_code_for_entry_point(name, binary, assembly, errors)) { + result.error = "Failed to assemble entry point '" + name + "': " + errors; + return result; + } + if (binary.size() % sizeof(u32) != 0) { + result.error = "Entry point '" + name + "' produced a malformed SPIR-V module"; + return result; + } + + std::vector words(binary.size() / sizeof(u32)); + std::memcpy(words.data(), binary.data(), binary.size()); + result.entry_points.emplace(name, std::move(words)); + } + + if (result.entry_points.empty()) { + result.error = "Effect declares no usable entry points"; + } + + return result; +} + +} // namespace VideoCore diff --git a/src/video_core/post_processing/fx_compile.h b/src/video_core/post_processing/fx_compile.h new file mode 100644 index 0000000000..f4b70f3a2c --- /dev/null +++ b/src/video_core/post_processing/fx_compile.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "common/common_types.h" +#include "effect_module.hpp" + +namespace VideoCore { + +struct FxCompileResult { + reshadefx::effect_module module; + std::map> entry_points; + std::string error; + + bool Succeeded() const { + return error.empty() && !entry_points.empty(); + } +}; + +FxCompileResult CompileFxEffect(const std::filesystem::path& path, u32 width, u32 height, + u32 color_bit_depth); + +} // namespace VideoCore diff --git a/src/video_core/post_processing/fx_effect.cpp b/src/video_core/post_processing/fx_effect.cpp new file mode 100644 index 0000000000..7bb36b0467 --- /dev/null +++ b/src/video_core/post_processing/fx_effect.cpp @@ -0,0 +1,307 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "bundled_fx_effects.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/fs_util.h" +#include "common/fs/path_util.h" +#include "common/logging.h" +#include "video_core/post_processing/fx_compile.h" +#include "video_core/post_processing/fx_effect.h" + +namespace VideoCore { + +namespace { + +constexpr u32 CATALOG_PROBE_WIDTH = 1280; +constexpr u32 CATALOG_PROBE_HEIGHT = 720; +constexpr u32 CATALOG_PROBE_DEPTH = 8; + +std::vector catalog; +bool catalog_scanned = false; + +const reshadefx::annotation* FindAnnotation(const std::vector& annotations, + std::string_view name) { + const auto it = std::find_if(annotations.begin(), annotations.end(), + [&](const reshadefx::annotation& a) { return a.name == name; }); + if (it == annotations.end()) { + return nullptr; + } + return &*it; +} + +std::string AnnotationString(const std::vector& annotations, + std::string_view name) { + const reshadefx::annotation* a = FindAnnotation(annotations, name); + if (a == nullptr) { + return std::string(); + } + return a->value.string_data; +} + +bool AnnotationFloat(const std::vector& annotations, std::string_view name, + f32& out) { + const reshadefx::annotation* a = FindAnnotation(annotations, name); + if (a == nullptr) { + return false; + } + if (a->type.is_floating_point()) { + out = a->value.as_float[0]; + return true; + } + if (a->type.is_integral()) { + out = static_cast(a->value.as_int[0]); + return true; + } + return false; +} + +FxUiType ParseUiType(std::string_view value) { + if (value == "slider") { + return FxUiType::Slider; + } + if (value == "drag") { + return FxUiType::Drag; + } + if (value == "combo") { + return FxUiType::Combo; + } + if (value == "radio") { + return FxUiType::Radio; + } + if (value == "check" || value == "checkbox") { + return FxUiType::CheckBox; + } + if (value == "color") { + return FxUiType::Color; + } + if (value == "input") { + return FxUiType::InputBox; + } + return FxUiType::Hidden; +} + +std::vector SplitItems(const std::string& items) { + std::vector out; + std::string current; + for (const char c : items) { + if (c == '\0') { + out.push_back(current); + current.clear(); + continue; + } + current += c; + } + if (!current.empty()) { + out.push_back(current); + } + return out; +} + +FxUniformDesc DescribeUniform(const reshadefx::uniform& info) { + FxUniformDesc desc; + desc.name = info.name; + desc.components = std::min(info.type.components(), 4); + + if (info.type.is_boolean()) { + desc.kind = FxUniformKind::Boolean; + } else if (info.type.is_integral()) { + desc.kind = FxUniformKind::Integer; + } else { + desc.kind = FxUniformKind::Floating; + } + + desc.label = AnnotationString(info.annotations, "ui_label"); + if (desc.label.empty()) { + desc.label = info.name; + } + desc.tooltip = AnnotationString(info.annotations, "ui_tooltip"); + desc.category = AnnotationString(info.annotations, "ui_category"); + desc.ui_type = ParseUiType(AnnotationString(info.annotations, "ui_type")); + desc.items = SplitItems(AnnotationString(info.annotations, "ui_items")); + + if (desc.kind == FxUniformKind::Boolean) { + desc.ui_min = 0.0f; + desc.ui_max = 1.0f; + desc.ui_step = 1.0f; + } else if (desc.kind == FxUniformKind::Integer) { + desc.ui_min = 0.0f; + desc.ui_max = 100.0f; + desc.ui_step = 1.0f; + } + + void(AnnotationFloat(info.annotations, "ui_min", desc.ui_min)); + void(AnnotationFloat(info.annotations, "ui_max", desc.ui_max)); + void(AnnotationFloat(info.annotations, "ui_step", desc.ui_step)); + + if (desc.ui_step <= 0.0f) { + desc.ui_step = 0.01f; + if (desc.kind != FxUniformKind::Floating) { + desc.ui_step = 1.0f; + } + } + if (desc.ui_max < desc.ui_min) { + std::swap(desc.ui_min, desc.ui_max); + } + + if (info.has_initializer_value) { + for (u32 i = 0; i < desc.components; ++i) { + if (desc.kind == FxUniformKind::Floating) { + desc.default_value[i] = info.initializer_value.as_float[i]; + } else { + desc.default_value[i] = static_cast(info.initializer_value.as_int[i]); + } + } + } + + return desc; +} + +FxEffectDesc DescribeEffect(const std::filesystem::path& path, const std::filesystem::path& root) { + FxEffectDesc desc; + desc.file = Common::FS::PathToUTF8String(std::filesystem::relative(path, root)); + desc.name = Common::FS::PathToUTF8String(path.stem()); + + const auto compiled = + CompileFxEffect(path, CATALOG_PROBE_WIDTH, CATALOG_PROBE_HEIGHT, CATALOG_PROBE_DEPTH); + if (!compiled.Succeeded()) { + desc.error = compiled.error; + return desc; + } + + for (const auto& technique : compiled.module.techniques) { + desc.techniques.push_back(technique.name); + } + + if (!compiled.module.techniques.empty()) { + const auto& annotations = compiled.module.techniques.front().annotations; + desc.label = AnnotationString(annotations, "ui_label"); + desc.description = AnnotationString(annotations, "ui_tooltip"); + } + if (desc.label.empty()) { + desc.label = desc.name; + } + + for (const auto& uniform : compiled.module.uniforms) { + FxUniformDesc uniform_desc = DescribeUniform(uniform); + if (uniform_desc.ui_type == FxUiType::Hidden) { + continue; + } + desc.uniforms.push_back(std::move(uniform_desc)); + } + + return desc; +} + +} // Anonymous namespace + +std::filesystem::path GetFxRootDirectory() { + return Common::FS::GetEdenPath(Common::FS::EdenPath::PostShaderDir); +} + +std::vector GetFxIncludePaths(const std::filesystem::path& effect_path) { + const auto root = GetFxRootDirectory(); + std::vector paths; + paths.push_back(effect_path.parent_path()); + paths.push_back(root); + paths.push_back(root / "Shaders"); + + const auto last = std::unique(paths.begin(), paths.end()); + paths.erase(last, paths.end()); + return paths; +} + +std::filesystem::path ResolveFxTexturePath(const std::filesystem::path& effect_path, + std::string_view source) { + const auto root = GetFxRootDirectory(); + const std::filesystem::path name{source}; + + const std::array candidates{ + effect_path.parent_path() / name, + root / "Textures" / name, + root / name, + }; + + for (const auto& candidate : candidates) { + if (Common::FS::Exists(candidate)) { + return candidate; + } + } + + return std::filesystem::path(); +} + +void ReloadFxCatalog() { + catalog.clear(); + catalog_scanned = true; + + const auto root = GetFxRootDirectory(); + if (!Common::FS::Exists(root) && !Common::FS::CreateDirs(root)) { + return; + } + + for (const auto& bundled : BUNDLED_FX_EFFECTS) { + const auto path = root / bundled.name; + if (Common::FS::Exists(path)) { + continue; + } + void(Common::FS::WriteStringToFile(path, Common::FS::FileType::TextFile, bundled.source)); + } + + std::vector effect_files; + Common::FS::IterateDirEntriesRecursively( + root, + [&](const std::filesystem::directory_entry& entry) { + if (entry.path().extension() == ".fx") { + effect_files.push_back(entry.path()); + } + return true; + }, + Common::FS::DirEntryFilter::File); + + std::sort(effect_files.begin(), effect_files.end()); + + for (const auto& file : effect_files) { + FxEffectDesc desc = DescribeEffect(file, root); + if (!desc.error.empty()) { + LOG_WARNING(Render, "Post-processing effect '{}' failed to compile:\n{}", desc.file, + desc.error); + } + catalog.push_back(std::move(desc)); + } + + const size_t usable = std::count_if(catalog.begin(), catalog.end(), + [](const FxEffectDesc& d) { return d.Valid(); }); + LOG_INFO(Render, "Loaded {} post-processing effects ({} usable)", catalog.size(), usable); +} + +const std::vector& GetFxCatalog() { + if (!catalog_scanned) { + ReloadFxCatalog(); + } + return catalog; +} + +const FxEffectDesc* FindFxEffect(std::string_view file) { + const auto& effects = GetFxCatalog(); + const auto it = std::find_if(effects.begin(), effects.end(), + [&](const FxEffectDesc& d) { return d.file == file; }); + if (it == effects.end()) { + return nullptr; + } + return &*it; +} + +const FxUniformDesc* FindFxUniform(const FxEffectDesc& effect, std::string_view name) { + const auto it = std::find_if(effect.uniforms.begin(), effect.uniforms.end(), + [&](const FxUniformDesc& u) { return u.name == name; }); + if (it == effect.uniforms.end()) { + return nullptr; + } + return &*it; +} + +} // namespace VideoCore diff --git a/src/video_core/post_processing/fx_effect.h b/src/video_core/post_processing/fx_effect.h new file mode 100644 index 0000000000..bedf68809a --- /dev/null +++ b/src/video_core/post_processing/fx_effect.h @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "common/common_types.h" + +namespace VideoCore { + +enum class FxUniformKind { + Boolean, + Integer, + Floating, +}; + +enum class FxUiType { + Hidden, + Slider, + Drag, + Combo, + Radio, + CheckBox, + Color, + InputBox, +}; + +struct FxUniformDesc { + std::string name; + std::string label; + std::string tooltip; + std::string category; + FxUniformKind kind{FxUniformKind::Floating}; + u32 components{1}; + FxUiType ui_type{FxUiType::Hidden}; + f32 ui_min{0.0f}; + f32 ui_max{1.0f}; + f32 ui_step{0.01f}; + std::vector items; + std::array default_value{}; +}; + +struct FxEffectDesc { + std::string file; + std::string name; + std::string label; + std::string description; + std::vector techniques; + std::vector uniforms; + std::string error; + + bool Valid() const { + return error.empty() && !techniques.empty(); + } +}; + +std::filesystem::path GetFxRootDirectory(); + +std::vector GetFxIncludePaths(const std::filesystem::path& effect_path); + +std::filesystem::path ResolveFxTexturePath(const std::filesystem::path& effect_path, + std::string_view source); + +void ReloadFxCatalog(); + +const std::vector& GetFxCatalog(); + +const FxEffectDesc* FindFxEffect(std::string_view file); + +const FxUniformDesc* FindFxUniform(const FxEffectDesc& effect, std::string_view name); + +} // namespace VideoCore diff --git a/src/video_core/post_processing/fx_preset.cpp b/src/video_core/post_processing/fx_preset.cpp new file mode 100644 index 0000000000..bd66a7d807 --- /dev/null +++ b/src/video_core/post_processing/fx_preset.cpp @@ -0,0 +1,261 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "bundled_fx_presets.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/settings.h" +#include "video_core/post_processing/fx_chain.h" +#include "video_core/post_processing/fx_preset.h" + +namespace VideoCore { + +namespace { + +std::vector preset_catalog; +bool preset_catalog_scanned = false; + +std::string Trim(std::string_view value) { + size_t begin = 0; + while (begin < value.size() && (value[begin] == ' ' || value[begin] == '\t')) { + ++begin; + } + size_t end = value.size(); + while (end > begin) { + const char c = value[end - 1]; + if (c != ' ' && c != '\t' && c != '\r' && c != '\n') { + break; + } + --end; + } + return std::string(value.substr(begin, end - begin)); +} + +FxPresetDesc ParsePreset(std::string_view text) { + FxPresetDesc desc; + + size_t start = 0; + while (start <= text.size()) { + size_t end = text.find('\n', start); + if (end == std::string_view::npos) { + end = text.size(); + } + const std::string_view line = text.substr(start, end - start); + start = end + 1; + + const size_t equals = line.find('='); + if (equals == std::string_view::npos) { + continue; + } + + const std::string key = Trim(line.substr(0, equals)); + const std::string value = Trim(line.substr(equals + 1)); + + if (key == "name") { + desc.name = value; + } else if (key == "description") { + desc.description = value; + } else if (key == "chain") { + desc.chain = value; + } + } + + return desc; +} + +std::string PresetFileName(std::string_view name) { + std::string out; + for (const char c : name) { + if (c == ' ') { + out += '_'; + continue; + } + const bool keep = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '-' || c == '_'; + if (keep) { + out += c; + } + } + if (out.empty()) { + out = "preset"; + } + return out + ".fxp"; +} + +std::string ComposePreset(const FxPresetDesc& desc) { + std::string out; + out += "name="; + out += desc.name; + out += '\n'; + out += "description="; + out += desc.description; + out += '\n'; + out += "chain="; + out += desc.chain; + out += '\n'; + return out; +} + +} // Anonymous namespace + +std::filesystem::path GetFxPresetDirectory() { + return Common::FS::GetEdenPath(Common::FS::EdenPath::PostPresetDir); +} + +void ReloadFxPresetCatalog() { + preset_catalog.clear(); + preset_catalog_scanned = true; + + const auto root = GetFxPresetDirectory(); + if (!Common::FS::Exists(root) && !Common::FS::CreateDirs(root)) { + return; + } + + std::vector bundled_names; + for (const auto& bundled : BUNDLED_FX_PRESETS) { + const auto path = root / bundled.name; + bundled_names.emplace_back(bundled.name); + if (Common::FS::Exists(path)) { + continue; + } + void(Common::FS::WriteStringToFile(path, Common::FS::FileType::TextFile, bundled.source)); + } + + std::vector preset_files; + Common::FS::IterateDirEntriesRecursively( + root, + [&](const std::filesystem::directory_entry& entry) { + if (entry.path().extension() == ".fxp") { + preset_files.push_back(entry.path()); + } + return true; + }, + Common::FS::DirEntryFilter::File); + + std::sort(preset_files.begin(), preset_files.end()); + + for (const auto& file : preset_files) { + const std::string text = + Common::FS::ReadStringFromFile(file, Common::FS::FileType::TextFile); + + FxPresetDesc desc = ParsePreset(text); + if (desc.name.empty() || desc.chain.empty()) { + continue; + } + + desc.file = Common::FS::PathToUTF8String(file.filename()); + desc.bundled = std::find(bundled_names.begin(), bundled_names.end(), desc.file) != + bundled_names.end(); + preset_catalog.push_back(std::move(desc)); + } +} + +const std::vector& GetFxPresetCatalog() { + if (!preset_catalog_scanned) { + ReloadFxPresetCatalog(); + } + return preset_catalog; +} + +const FxPresetDesc* FindFxPreset(std::string_view name) { + const auto& presets = GetFxPresetCatalog(); + const auto it = std::find_if(presets.begin(), presets.end(), + [&](const FxPresetDesc& p) { return p.name == name; }); + if (it == presets.end()) { + return nullptr; + } + return &*it; +} + +bool ApplyFxPreset(std::string_view name) { + const FxPresetDesc* preset = FindFxPreset(name); + if (preset == nullptr) { + return false; + } + + FxChain::Instance().SetEntries(ParseFxChain(preset->chain)); + FxChain::Instance().DropUnknownEntries(); + FxChain::Instance().StoreToSettings(); + SetActiveFxPreset(preset->name); + return true; +} + +bool SaveFxPreset(std::string_view name, std::string_view description) { + if (!IsFxPresetName(name)) { + return false; + } + + const auto root = GetFxPresetDirectory(); + if (!Common::FS::Exists(root) && !Common::FS::CreateDirs(root)) { + return false; + } + + FxPresetDesc desc; + desc.name = std::string(name); + desc.description = std::string(description); + desc.chain = SerializeFxChain(FxChain::Instance().Entries()); + + const std::string body = ComposePreset(desc); + const std::string file = PresetFileName(name); + if (Common::FS::WriteStringToFile(root / file, Common::FS::FileType::TextFile, body) != + body.size()) { + return false; + } + + ReloadFxPresetCatalog(); + SetActiveFxPreset(name); + return true; +} + +bool DeleteFxPreset(std::string_view name) { + const FxPresetDesc* preset = FindFxPreset(name); + if (preset == nullptr || preset->bundled) { + return false; + } + + const auto path = GetFxPresetDirectory() / preset->file; + if (!Common::FS::RemoveFile(path)) { + return false; + } + + if (GetActiveFxPreset() == name) { + SetActiveFxPreset(std::string_view()); + } + ReloadFxPresetCatalog(); + return true; +} + +bool IsFxPresetName(std::string_view name) { + if (name.empty()) { + return false; + } + return name.find_first_of("\r\n=") == std::string_view::npos; +} + +std::string GetActiveFxPreset() { + return Settings::values.post_shader_preset.GetValue(); +} + +void SetActiveFxPreset(std::string_view name) { + Settings::values.post_shader_preset.SetValue(std::string(name)); +} + +bool IsActiveFxPresetModified() { + const std::string active = GetActiveFxPreset(); + if (active.empty()) { + return false; + } + + const FxPresetDesc* preset = FindFxPreset(active); + if (preset == nullptr) { + return true; + } + + return SerializeFxChain(FxChain::Instance().Entries()) != + SerializeFxChain(ParseFxChain(preset->chain)); +} + +} // namespace VideoCore diff --git a/src/video_core/post_processing/fx_preset.h b/src/video_core/post_processing/fx_preset.h new file mode 100644 index 0000000000..23372b9495 --- /dev/null +++ b/src/video_core/post_processing/fx_preset.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include + +namespace VideoCore { + +struct FxPresetDesc { + std::string file; + std::string name; + std::string description; + std::string chain; + bool bundled{}; +}; + +std::filesystem::path GetFxPresetDirectory(); + +void ReloadFxPresetCatalog(); + +const std::vector& GetFxPresetCatalog(); + +const FxPresetDesc* FindFxPreset(std::string_view name); + +bool ApplyFxPreset(std::string_view name); + +bool SaveFxPreset(std::string_view name, std::string_view description); + +bool DeleteFxPreset(std::string_view name); + +bool IsFxPresetName(std::string_view name); + +std::string GetActiveFxPreset(); + +void SetActiveFxPreset(std::string_view name); + +bool IsActiveFxPresetModified(); + +} // namespace VideoCore diff --git a/src/video_core/pte_kind.h b/src/video_core/pte_kind.h index 591d7214b2..d1495d94b0 100644 --- a/src/video_core/pte_kind.h +++ b/src/video_core/pte_kind.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -9,28 +12,22 @@ namespace Tegra { // https://github.com/NVIDIA/open-gpu-doc/blob/master/manuals/volta/gv100/dev_mmu.ref.txt enum class PTEKind : u8 { - INVALID = 0xff, - PITCH = 0x00, - Z16 = 0x01, - Z16_2C = 0x02, - Z16_MS2_2C = 0x03, - Z16_MS4_2C = 0x04, - Z16_MS8_2C = 0x05, - Z16_MS16_2C = 0x06, - Z16_2Z = 0x07, - Z16_MS2_2Z = 0x08, - Z16_MS4_2Z = 0x09, - Z16_MS8_2Z = 0x0a, - Z16_MS16_2Z = 0x0b, - Z16_2CZ = 0x36, - Z16_MS2_2CZ = 0x37, - Z16_MS4_2CZ = 0x38, - Z16_MS8_2CZ = 0x39, - Z16_MS16_2CZ = 0x5f, - Z16_4CZ = 0x0c, - Z16_MS2_4CZ = 0x0d, - Z16_MS4_4CZ = 0x0e, - Z16_MS8_4CZ = 0x0f, + PITCH = 0x0, + Z16 = 0x1, + Z16_2C = 0x2, + Z16_MS2_2C = 0x3, + Z16_MS4_2C = 0x4, + Z16_MS8_2C = 0x5, + Z16_MS16_2C = 0x6, + Z16_2Z = 0x7, + Z16_MS2_2Z = 0x8, + Z16_MS4_2Z = 0x9, + Z16_MS8_2Z = 0xa, + Z16_MS16_2Z = 0xb, + Z16_4CZ = 0xc, + Z16_MS2_4CZ = 0xd, + Z16_MS4_4CZ = 0xe, + Z16_MS8_4CZ = 0xf, Z16_MS16_4CZ = 0x10, S8Z24 = 0x11, S8Z24_1Z = 0x12, @@ -43,7 +40,7 @@ enum class PTEKind : u8 { S8Z24_MS4_2CZ = 0x19, S8Z24_MS8_2CZ = 0x1a, S8Z24_MS16_2CZ = 0x1b, - S8Z24_2CS = 0x1c, + S8Z24_2CS = 0x1C, S8Z24_MS2_2CS = 0x1d, S8Z24_MS4_2CS = 0x1e, S8Z24_MS8_2CS = 0x1f, @@ -57,6 +54,8 @@ enum class PTEKind : u8 { V8Z24_MS4_VC4 = 0x27, V8Z24_MS8_VC8 = 0x28, V8Z24_MS8_VC24 = 0x29, + S8 = 0x2a, + S8_2S = 0x2b, V8Z24_MS4_VC12_1ZV = 0x2e, V8Z24_MS4_VC4_1ZV = 0x2f, V8Z24_MS8_VC8_1ZV = 0x30, @@ -99,15 +98,9 @@ enum class PTEKind : u8 { Z24S8_MS8_4CSZV = 0x59, Z24S8_MS16_4CSZV = 0x5a, Z24V8_MS4_VC12 = 0x5b, - Z24V8_MS4_VC4 = 0x5c, + Z24V8_MS4_VC4 = 0x5C, Z24V8_MS8_VC8 = 0x5d, Z24V8_MS8_VC24 = 0x5e, - YUV_B8C1_2Y = 0x60, - YUV_B8C2_2Y = 0x61, - YUV_B10C1_2Y = 0x62, - YUV_B10C2_2Y = 0x6b, - YUV_B12C1_2Y = 0x6c, - YUV_B12C2_2Y = 0x6d, Z24V8_MS4_VC12_1ZV = 0x63, Z24V8_MS4_VC4_1ZV = 0x64, Z24V8_MS8_VC8_1ZV = 0x65, @@ -129,7 +122,7 @@ enum class PTEKind : u8 { Z24V8_MS8_VC8_4CSZV = 0x79, Z24V8_MS8_VC24_4CSZV = 0x7a, ZF32 = 0x7b, - ZF32_1Z = 0x7c, + ZF32_1Z = 0x7C, ZF32_MS2_1Z = 0x7d, ZF32_MS4_1Z = 0x7e, ZF32_MS8_1Z = 0x7f, @@ -198,6 +191,9 @@ enum class PTEKind : u8 { ZF32_X24S8_MS4_1CS = 0xc6, ZF32_X24S8_MS8_1CS = 0xc7, ZF32_X24S8_MS16_1CS = 0xc8, + SMASKED_MESSAGE = 0xca, + SMHOST_MESSAGE = 0xcb, + C64_MS2_2CRA = 0xcd, ZF32_X24S8_2CSZV = 0xce, ZF32_X24S8_MS2_2CSZV = 0xcf, ZF32_X24S8_MS4_2CSZV = 0xd0, @@ -208,9 +204,6 @@ enum class PTEKind : u8 { ZF32_X24S8_MS4_2CS = 0xd5, ZF32_X24S8_MS8_2CS = 0xd6, ZF32_X24S8_MS16_2CS = 0xd7, - S8 = 0x2a, - S8_2S = 0x2b, - GENERIC_16BX2 = 0xfe, C32_2C = 0xd8, C32_2CBR = 0xd9, C32_2CBA = 0xda, @@ -218,13 +211,12 @@ enum class PTEKind : u8 { C32_2BRA = 0xdc, C32_MS2_2C = 0xdd, C32_MS2_2CBR = 0xde, - C32_MS2_4CBRA = 0xcc, + C32_MS2_2CRA = 0xcc, C32_MS4_2C = 0xdf, C32_MS4_2CBR = 0xe0, C32_MS4_2CBA = 0xe1, C32_MS4_2CRA = 0xe2, C32_MS4_2BRA = 0xe3, - C32_MS4_4CBRA = 0x2c, C32_MS8_MS16_2C = 0xe4, C32_MS8_MS16_2CRA = 0xe5, C64_2C = 0xe6, @@ -234,13 +226,11 @@ enum class PTEKind : u8 { C64_2BRA = 0xea, C64_MS2_2C = 0xeb, C64_MS2_2CBR = 0xec, - C64_MS2_4CBRA = 0xcd, C64_MS4_2C = 0xed, C64_MS4_2CBR = 0xee, C64_MS4_2CBA = 0xef, C64_MS4_2CRA = 0xf0, C64_MS4_2BRA = 0xf1, - C64_MS4_4CBRA = 0x2d, C64_MS8_MS16_2C = 0xf2, C64_MS8_MS16_2CRA = 0xf3, C128_2C = 0xf4, @@ -253,8 +243,8 @@ enum class PTEKind : u8 { C128_MS8_MS16_2CR = 0xfb, X8C24 = 0xfc, PITCH_NO_SWIZZLE = 0xfd, - SMSKED_MESSAGE = 0xca, - SMHOST_MESSAGE = 0xcb, + GENERIC_16BX2 = 0xfe, + INVALID = 0xff, }; constexpr bool IsPitchKind(PTEKind kind) { diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h index ebb87fe65a..55a68eb1e5 100644 --- a/src/video_core/query_cache.h +++ b/src/video_core/query_cache.h @@ -15,7 +15,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include "common/assert.h" @@ -350,7 +350,7 @@ private: mutable std::recursive_mutex mutex; - ankerl::unordered_dense::map> cached_queries; + ::Common::unordered_map> cached_queries; std::array streams; diff --git a/src/video_core/query_cache/query_cache.h b/src/video_core/query_cache/query_cache.h index 6bed91a53e..b8c1bc4f45 100644 --- a/src/video_core/query_cache/query_cache.h +++ b/src/video_core/query_cache/query_cache.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include "common/assert.h" @@ -260,7 +260,7 @@ void QueryCacheBase::CounterReport(GPUVAddr addr, QueryType counter_type }; u8* pointer = impl->device_memory.template GetPointer(cpu_addr); u8* pointer_timestamp = impl->device_memory.template GetPointer(cpu_addr + 8); - bool is_synced = !Settings::IsGPULevelHigh() && is_fence; + bool is_synced = (Settings::IsGPUFenceBehaviorDefault() ? !Settings::IsGPULevelHigh() : !Settings::IsGPUFenceBehaviorBalanced() && !Settings::IsGPUFenceBehaviorAccurate()) && is_fence; std::function operation([this, is_synced, streamer, query_base = query, query_location, pointer, pointer_timestamp] { if (True(query_base->flags & QueryFlagBits::IsInvalidated)) { diff --git a/src/video_core/query_cache/query_cache_base.h b/src/video_core/query_cache/query_cache_base.h index 242e24388f..d73e9039f7 100644 --- a/src/video_core/query_cache/query_cache_base.h +++ b/src/video_core/query_cache/query_cache_base.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include "common/assert.h" @@ -155,12 +155,12 @@ protected: const auto in_range2 = [&](const std::pair& pair) { return in_range(pair.first); }; - std::erase_if(contents, in_range2); + erase_if(contents, in_range2); } } } - using ContentCache = ankerl::unordered_dense::map>; + using ContentCache = ::Common::unordered_map>; void InvalidateQuery(QueryLocation location); bool IsQueryDirty(QueryLocation location); @@ -168,7 +168,7 @@ protected: void RequestGuestHostSync(); void UnregisterPending(); - ankerl::unordered_dense::map> cached_queries; + ::Common::unordered_map> cached_queries; std::mutex cache_mutex; struct QueryCacheBaseImpl; diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index 481efbf53b..b527c0e2a9 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -111,6 +114,12 @@ public: /// Notify rasterizer that any caches of the specified region are desync with guest virtual void OnCacheInvalidation(PAddr addr, u64 size) = 0; + virtual bool TryCacheInvalidation(DAddr addr, u64 size) { + if (MustFlushRegion(addr, size, VideoCommon::CacheType::BufferCache)) return false; + OnCacheInvalidation(addr, size); + return true; + } + virtual bool OnCPUWrite(PAddr addr, u64 size) = 0; /// Sync memory between guest and host. diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index 9b6fb69af3..e19a70ea01 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -36,7 +36,8 @@ bool RendererBase::IsScreenshotPending() const { } void RendererBase::RequestScreenshot(void* data, std::function callback, - const Layout::FramebufferLayout& layout) { + const Layout::FramebufferLayout& layout, + Service::Nvnflinger::LayerStackId layer_stack) { if (renderer_settings.screenshot_requested) { LOG_ERROR(Render, "A screenshot is already requested or in progress, ignoring the request"); return; @@ -48,6 +49,7 @@ void RendererBase::RequestScreenshot(void* data, std::function callb renderer_settings.screenshot_bits = data; renderer_settings.screenshot_complete_callback = async_callback; renderer_settings.screenshot_framebuffer_layout = layout; + renderer_settings.screenshot_layer_stack = layer_stack; renderer_settings.screenshot_requested = true; } diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 67427f9372..46f8276074 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -26,6 +29,7 @@ struct RendererSettings { void* screenshot_bits{}; std::function screenshot_complete_callback; Layout::FramebufferLayout screenshot_framebuffer_layout; + Service::Nvnflinger::LayerStackId screenshot_layer_stack{Service::Nvnflinger::LayerStackId::Default}; }; class RendererBase { @@ -88,9 +92,11 @@ public: /// Returns true if a screenshot is being processed bool IsScreenshotPending() const; - /// Request a screenshot of the next frame + /// Request a screenshot of the next frame. void RequestScreenshot(void* data, std::function callback, - const Layout::FramebufferLayout& layout); + const Layout::FramebufferLayout& layout, + Service::Nvnflinger::LayerStackId layer_stack = + Service::Nvnflinger::LayerStackId::Default); protected: Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle. diff --git a/src/video_core/renderer_opengl/gl_blit_screen.cpp b/src/video_core/renderer_opengl/gl_blit_screen.cpp index 4b75e1b949..23e5eb7481 100644 --- a/src/video_core/renderer_opengl/gl_blit_screen.cpp +++ b/src/video_core/renderer_opengl/gl_blit_screen.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project @@ -115,6 +115,8 @@ void BlitScreen::CreateWindowAdapt() { window_adapt = MakeMmpx(device); break; case Settings::ScalingFilter::Fsr: + case Settings::ScalingFilter::Sgsr: + case Settings::ScalingFilter::SgsrEdge: case Settings::ScalingFilter::Bilinear: default: window_adapt = MakeBilinear(device); diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index 59829b667f..fb65a132d0 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -52,11 +52,11 @@ constexpr std::array PROGRAM_LUT{ Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params) : VideoCommon::BufferBase(null_params) {} -Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_) +Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_, bool) : VideoCommon::BufferBase(cpu_addr_, size_bytes_) { buffer.Create(); if (runtime.device.HasDebuggingToolAttached()) { - const std::string name = fmt::format("Buffer 0x{:x}", CpuAddr()); + const std::string name = fmt::format("Buffer {:#x}", CpuAddr()); glObjectLabel(GL_BUFFER, buffer.handle, static_cast(name.size()), name.data()); } glNamedBufferData(buffer.handle, SizeBytes(), nullptr, GL_DYNAMIC_DRAW); diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h index a0acfc48c1..6fee58a0eb 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.h +++ b/src/video_core/renderer_opengl/gl_buffer_cache.h @@ -8,7 +8,7 @@ #include #include -#include +#include "common/container/unordered_map.h" #include "common/common_types.h" #include "video_core/buffer_cache/buffer_cache_base.h" @@ -23,7 +23,8 @@ class BufferCacheRuntime; class Buffer : public VideoCommon::BufferBase { public: - explicit Buffer(BufferCacheRuntime&, DAddr cpu_addr, u64 size_bytes); + explicit Buffer(BufferCacheRuntime&, DAddr cpu_addr, u64 size_bytes, + bool sparse_compatible); explicit Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams); void ImmediateUpload(size_t offset, std::span data) noexcept; @@ -242,7 +243,7 @@ private: u32 index_buffer_offset = 0; u64 device_access_memory; - ankerl::unordered_dense::map tfb_objects; + ::Common::unordered_map tfb_objects; }; struct BufferCacheParams { diff --git a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp index d1c61be743..f0a7baf9aa 100644 --- a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp @@ -90,7 +90,7 @@ void ComputePipeline::Configure() { desc.is_written); ++ssbo_index; } - texture_cache.SynchronizeComputeDescriptors(); + texture_cache.SynchronizeDescriptors(true); boost::container::static_vector views; boost::container::static_vector samplers; @@ -148,14 +148,14 @@ void ComputePipeline::Configure() { const auto handle{read_handle(desc, index)}; views.push_back({handle.first}); - VideoCommon::SamplerId sampler = texture_cache.GetComputeSamplerId(handle.second); + VideoCommon::SamplerId sampler = texture_cache.GetSamplerId(handle.second, true); samplers.push_back(sampler); } } for (const auto& desc : info.image_descriptors) { add_image(desc, desc.is_written); } - texture_cache.FillComputeImageViews(std::span(views.data(), views.size())); + texture_cache.FillImageViews(std::span(views.data(), views.size()), true); if (!is_built) { WaitForBuild(); diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp index 0829e6dd33..bc5803dae8 100644 --- a/src/video_core/renderer_opengl/gl_device.cpp +++ b/src/video_core/renderer_opengl/gl_device.cpp @@ -59,7 +59,7 @@ std::vector GetExtensions() { std::vector extensions; for (GLint index = 0; index < num_extensions; ++index) { auto const* p = reinterpret_cast(glGetStringi(GL_EXTENSIONS, GLuint(index))); - if (p != nullptr) // Fuck you? - sincerely, buggy mesa drivers + if (p != nullptr) extensions.push_back(std::string{p}); } return extensions; @@ -203,6 +203,7 @@ Device::Device(Core::Frontend::EmuWindow& emu_window) { max_varyings = GetInteger(GL_MAX_VARYING_VECTORS); max_compute_shared_memory_size = GetInteger(GL_MAX_COMPUTE_SHARED_MEMORY_SIZE); max_glasm_storage_buffer_blocks = GetInteger(GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS); + max_user_clip_distances = GetInteger(GL_MAX_CLIP_DISTANCES); has_warp_intrinsics = GLAD_GL_NV_gpu_shader5 && GLAD_GL_NV_shader_thread_group && GLAD_GL_NV_shader_thread_shuffle; has_shader_ballot = GLAD_GL_ARB_shader_ballot; diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h index 17b5a828f2..200e77e6e4 100644 --- a/src/video_core/renderer_opengl/gl_device.h +++ b/src/video_core/renderer_opengl/gl_device.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -47,6 +47,10 @@ public: return max_compute_shared_memory_size; } + u32 GetMaxUserClipDistances() const { + return max_user_clip_distances; + } + u32 GetMaxGLASMStorageBufferBlocks() const { return max_glasm_storage_buffer_blocks; } @@ -202,6 +206,7 @@ private: u32 max_varyings{}; u32 max_compute_shared_memory_size{}; u32 max_glasm_storage_buffer_blocks{}; + u32 max_user_clip_distances{}; bool has_warp_intrinsics{}; bool has_shader_ballot{}; diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp index ee3498428e..83545463ac 100644 --- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp @@ -283,7 +283,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { size_t views_index{}; size_t samplers_index{}; - texture_cache.SynchronizeGraphicsDescriptors(); + texture_cache.SynchronizeDescriptors(false); buffer_cache.SetUniformBuffersState(enabled_uniform_buffer_masks, &uniform_buffer_sizes); buffer_cache.runtime.SetBaseUniformBindings(base_uniform_bindings); @@ -354,7 +354,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { const auto handle{read_handle(desc, index)}; views[views_index++] = {handle.first}; - VideoCommon::SamplerId sampler{texture_cache.GetGraphicsSamplerId(handle.second)}; + VideoCommon::SamplerId sampler{texture_cache.GetSamplerId(handle.second, false)}; samplers[samplers_index++] = sampler; } } @@ -379,7 +379,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { if constexpr (Spec::enabled_stages[4]) { config_stage(4); } - texture_cache.FillGraphicsImageViews(std::span(views.data(), views_index)); + texture_cache.FillImageViews(std::span(views.data(), views_index), false, Spec::has_images); texture_cache.UpdateRenderTargets(false); state_tracker.BindFramebuffer(texture_cache.GetFramebuffer()->Handle()); diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 70f244809f..1d77d28c46 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -353,13 +353,13 @@ void RasterizerOpenGL::DrawTexture() { gpu.TickWork(); }; - texture_cache.SynchronizeGraphicsDescriptors(); + texture_cache.SynchronizeDescriptors(false); texture_cache.UpdateRenderTargets(false); SyncState(); const auto& draw_texture_state = maxwell3d->draw_manager.draw_texture_state; - const auto& sampler = texture_cache.GetGraphicsSampler(draw_texture_state.src_sampler); + const auto& sampler = texture_cache.GetSampler(draw_texture_state.src_sampler, true); const auto& texture = texture_cache.GetImageView(draw_texture_state.src_texture); const auto Scale = [&](auto dim) -> s32 { diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 9275dbf01c..277cf222fa 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -54,7 +54,7 @@ using VideoCommon::LoadPipelines; using VideoCommon::SerializePipeline; using Context = ShaderContext::Context; -constexpr u32 CACHE_VERSION = 14; +constexpr u32 CACHE_VERSION = 15; template auto MakeSpan(Container& container) { @@ -231,6 +231,7 @@ ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .has_broken_unsigned_image_offsets = true, .has_broken_signed_operations = true, .has_broken_fp16_float_controls = false, + .has_broken_fp32_denorm_flush = false, .has_gl_component_indexing_bug = device.HasComponentIndexingBug(), .has_gl_precise_bug = device.HasPreciseBug(), .has_gl_cbuf_ftou_bug = device.HasCbufFtouBug(), @@ -238,19 +239,38 @@ ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .ignore_nan_fp_comparisons = true, .gl_max_compute_smem_size = device.GetMaxComputeSharedMemorySize(), .min_ssbo_alignment = device.GetShaderStorageBufferAlignment(), - .max_user_clip_distances = 8, + // Use the host limit, but never more than the guest can produce. Maxwell exposes 8 clip + // distances and the SPIR-V output array is sized for at most 8, so clamping here keeps a + // host that reports a different count from under- or over-running that array. + .max_user_clip_distances = + std::min(device.GetMaxUserClipDistances(), Maxwell::Regs::NumClipDistances), }, host_info{ - .support_float64 = true, - .support_float16 = false, - .support_int64 = device.HasShaderInt64(), - .needs_demote_reorder = device.IsAmd(), - .support_snorm_render_buffer = false, - .support_viewport_index_layer = device.HasVertexViewportLayer(), - .min_ssbo_alignment = static_cast(device.GetShaderStorageBufferAlignment()), - .support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(), - .support_conditional_barrier = device.SupportsConditionalBarriers(), + .min_ssbo_alignment = static_cast(device.GetShaderStorageBufferAlignment()), + .max_per_stage_descriptor_sampled_images = + Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT, + .max_per_stage_resources = Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT, + .max_descriptor_set_samplers = Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT, + .max_descriptor_set_uniform_buffers = Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT, + .max_descriptor_set_uniform_buffers_dynamic = + Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT, + .max_descriptor_set_storage_buffers = Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT, + .max_descriptor_set_storage_buffers_dynamic = + Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT, + .max_descriptor_set_sampled_images = Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT, + .max_descriptor_set_storage_images = Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT, + .max_descriptor_set_input_attachements = + Shader::HostTranslateInfo::DEFAULT_DESCRIPTOR_LIMIT, + .support_float64 = true, + .support_float16 = false, + .support_int64 = device.HasShaderInt64(), + .needs_demote_reorder = device.IsAmd(), + .support_snorm_render_buffer = false, + .support_viewport_index_layer = device.HasVertexViewportLayer(), + .support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(), + .support_conditional_barrier = device.SupportsConditionalBarriers(), } { + host_info.ApplyDescriptorLimitPolicy(); if (use_asynchronous_shaders) { workers = CreateWorkers(); } @@ -394,6 +414,11 @@ GraphicsPipeline* ShaderCache::BuiltPipeline(GraphicsPipeline* pipeline) const n if (!use_asynchronous_shaders) { return pipeline; } + // If something is using depth, we can assume that games are not rendering anything which + // will be used one time. + if (maxwell3d->regs.zeta_enable) { + return nullptr; + } // If games are using a small index count, we can assume these are full screen quads. // Usually these shaders are only used once for building textures so we can assume they // can't be built async @@ -449,7 +474,7 @@ std::unique_ptr ShaderCache::CreateGraphicsPipeline( std::span envs, bool use_shader_workers, bool force_context_flush) try { auto hash = key.Hash(); - LOG_INFO(Render_OpenGL, "0x{:016x}", hash); + LOG_INFO(Render_OpenGL, "{:#016x}", hash); size_t env_index{}; u32 total_storage_buffers{}; std::array programs; @@ -477,7 +502,7 @@ std::unique_ptr ShaderCache::CreateGraphicsPipeline( const u32 cfg_offset = u32(env.StartAddress() + sizeof(Shader::ProgramHeader)); Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0); - if (Settings::values.dump_shaders) { + if (Settings::values.dump_guest_shaders) { env.Dump(hash, key.unique_hashes[index]); } @@ -570,11 +595,11 @@ std::unique_ptr ShaderCache::CreateComputePipeline( ShaderContext::ShaderPools& pools, const ComputePipelineKey& key, Shader::Environment& env, bool force_context_flush) try { auto hash = key.Hash(); - LOG_INFO(Render_OpenGL, "0x{:016x}", hash); + LOG_INFO(Render_OpenGL, "{:#016x}", hash); Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()}; - if (Settings::values.dump_shaders) { + if (Settings::values.dump_guest_shaders) { env.Dump(hash, key.unique_hash); } diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 02e54539fc..794b6773cb 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -7,7 +7,7 @@ #pragma once #include -#include +#include "common/container/unordered_map.h" #include "common/common_types.h" #include "common/thread_worker.h" @@ -81,8 +81,8 @@ private: GraphicsPipeline* current_pipeline{}; ShaderContext::ShaderPools main_pools; - ankerl::unordered_dense::map> graphics_cache; - ankerl::unordered_dense::map> compute_cache; + ::Common::unordered_map> graphics_cache; + ::Common::unordered_map> compute_cache; Shader::Profile profile; Shader::HostTranslateInfo host_info; diff --git a/src/video_core/renderer_opengl/gl_state_tracker.cpp b/src/video_core/renderer_opengl/gl_state_tracker.cpp index d53b422cac..dc91723300 100644 --- a/src/video_core/renderer_opengl/gl_state_tracker.cpp +++ b/src/video_core/renderer_opengl/gl_state_tracker.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -204,7 +207,7 @@ void SetupDirtyMisc(Tables& tables) { } // Anonymous namespace void StateTracker::SetupTables(Tegra::Control::ChannelState& channel_state) { - auto& tables{channel_state.maxwell_3d->dirty.tables}; + auto& tables{channel_state.payload->maxwell_3d.dirty.tables}; SetupDirtyFlags(tables); SetupDirtyColorMasks(tables); SetupDirtyViewports(tables); @@ -231,7 +234,7 @@ void StateTracker::SetupTables(Tegra::Control::ChannelState& channel_state) { } void StateTracker::ChangeChannel(Tegra::Control::ChannelState& channel_state) { - flags = &channel_state.maxwell_3d->dirty.flags; + flags = &channel_state.payload->maxwell_3d.dirty.flags; } void StateTracker::InvalidateState() { diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index 958988f27e..680cb2179f 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -402,7 +402,7 @@ OGLTexture MakeImage(const VideoCommon::ImageInfo& info, GLenum gl_internal_form ASSERT(false); break; default: - ASSERT_MSG(false, "Invalid target=0x{:x}", target); + ASSERT_MSG(false, "Invalid target={:#x}", target); break; } return texture; diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index dfcef4b0b6..8a830fa0c9 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h @@ -75,6 +75,8 @@ public: void Finish(); + void FlushDeferredClear() {} + StagingBufferMap UploadStagingBuffer(size_t size, bool deferred = false); StagingBufferMap DownloadStagingBuffer(size_t size, bool deferred = false); @@ -157,7 +159,7 @@ private: UtilShaders util_shaders; FormatConversionPass format_conversion_pass; - std::array, 3> format_properties; + std::array<::Common::unordered_map, 3> format_properties; bool has_broken_texture_view_formats = false; OGLTexture null_image_1d_array; @@ -370,6 +372,7 @@ struct TextureCacheParams { static constexpr bool HAS_EMULATED_COPIES = true; static constexpr bool HAS_DEVICE_MEMORY_INFO = true; static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true; + static constexpr bool HAS_MSAA_DOWNLOADS = false; using Runtime = OpenGL::TextureCacheRuntime; using Image = OpenGL::Image; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 1c72147113..f9ec32e652 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -199,7 +199,10 @@ void RendererOpenGL::RenderScreenshot(std::span return; } - RenderToBuffer(framebuffers, renderer_settings.screenshot_framebuffer_layout, + const auto screenshot_layers = Tegra::FilterLayerStack( + framebuffers, renderer_settings.screenshot_layer_stack, screenshot_layer_scratch); + + RenderToBuffer(screenshot_layers, renderer_settings.screenshot_framebuffer_layout, renderer_settings.screenshot_bits); renderer_settings.screenshot_complete_callback(true); @@ -208,6 +211,12 @@ void RendererOpenGL::RenderScreenshot(std::span void RendererOpenGL::RenderAppletCaptureLayer( std::span framebuffers) { + const auto capture_layers = Tegra::FilterLayerStack( + framebuffers, Service::Nvnflinger::LayerStackId::LastFrame, applet_capture_layers); + + if (capture_layers.empty()) + return; + GLint old_read_fb; GLint old_draw_fb; glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fb); @@ -217,7 +226,7 @@ void RendererOpenGL::RenderAppletCaptureLayer( glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, capture_renderbuffer.handle); - blit_applet->DrawScreen(framebuffers, VideoCore::Capture::Layout, true); + blit_applet->DrawScreen(capture_layers, VideoCore::Capture::Layout, true); glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fb); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb); diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index f694dc413a..5a6757e805 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2014 Citra Emulator Project @@ -61,6 +61,9 @@ private: void RenderScreenshot(std::span framebuffers); void RenderAppletCaptureLayer(std::span framebuffers); + std::vector applet_capture_layers; + std::vector screenshot_layer_scratch; + Core::Frontend::EmuWindow& emu_window; Tegra::MaxwellDeviceMemoryManager& device_memory; Tegra::GPU& gpu; diff --git a/src/video_core/renderer_opengl/util_shaders.cpp b/src/video_core/renderer_opengl/util_shaders.cpp index c437013e6a..7ced8c77b4 100644 --- a/src/video_core/renderer_opengl/util_shaders.cpp +++ b/src/video_core/renderer_opengl/util_shaders.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -56,10 +59,8 @@ UtilShaders::UtilShaders(ProgramManager& program_manager_) copy_bc4_program(MakeProgram(OPENGL_COPY_BC4_COMP)), convert_s8d24_program(MakeProgram(OPENGL_CONVERT_S8D24_COMP)), convert_ms_to_nonms_program(MakeProgram(CONVERT_MSAA_TO_NON_MSAA_COMP)), - convert_nonms_to_ms_program(MakeProgram(CONVERT_NON_MSAA_TO_MSAA_COMP)) { - const auto swizzle_table = Tegra::Texture::MakeSwizzleTable(); - swizzle_table_buffer.Create(); - glNamedBufferStorage(swizzle_table_buffer.handle, sizeof(swizzle_table), &swizzle_table, 0); + convert_nonms_to_ms_program(MakeProgram(CONVERT_NON_MSAA_TO_MSAA_COMP)) +{ } UtilShaders::~UtilShaders() = default; @@ -116,13 +117,11 @@ void UtilShaders::ASTCDecode(Image& image, const StagingBufferMap& map, void UtilShaders::BlockLinearUpload2D(Image& image, const StagingBufferMap& map, std::span swizzles) { static constexpr Extent3D WORKGROUP_SIZE{32, 32, 1}; - static constexpr GLuint BINDING_SWIZZLE_BUFFER = 0; - static constexpr GLuint BINDING_INPUT_BUFFER = 1; + static constexpr GLuint BINDING_INPUT_BUFFER = 0; static constexpr GLuint BINDING_OUTPUT_IMAGE = 0; program_manager.BindComputeProgram(block_linear_unswizzle_2d_program.handle); glFlushMappedNamedBufferRange(map.buffer, map.offset, image.guest_size_bytes); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, BINDING_SWIZZLE_BUFFER, swizzle_table_buffer.handle); const GLenum store_format = StoreFormat(BytesPerBlock(image.info.format)); for (const SwizzleParameters& swizzle : swizzles) { @@ -153,14 +152,11 @@ void UtilShaders::BlockLinearUpload2D(Image& image, const StagingBufferMap& map, void UtilShaders::BlockLinearUpload3D(Image& image, const StagingBufferMap& map, std::span swizzles) { static constexpr Extent3D WORKGROUP_SIZE{16, 8, 8}; - - static constexpr GLuint BINDING_SWIZZLE_BUFFER = 0; - static constexpr GLuint BINDING_INPUT_BUFFER = 1; + static constexpr GLuint BINDING_INPUT_BUFFER = 0; static constexpr GLuint BINDING_OUTPUT_IMAGE = 0; glFlushMappedNamedBufferRange(map.buffer, map.offset, image.guest_size_bytes); program_manager.BindComputeProgram(block_linear_unswizzle_3d_program.handle); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, BINDING_SWIZZLE_BUFFER, swizzle_table_buffer.handle); const GLenum store_format = StoreFormat(BytesPerBlock(image.info.format)); for (const SwizzleParameters& swizzle : swizzles) { diff --git a/src/video_core/renderer_opengl/util_shaders.h b/src/video_core/renderer_opengl/util_shaders.h index feecd404ca..b290b3e9ed 100644 --- a/src/video_core/renderer_opengl/util_shaders.h +++ b/src/video_core/renderer_opengl/util_shaders.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -45,9 +48,6 @@ public: private: ProgramManager& program_manager; - - OGLBuffer swizzle_table_buffer; - OGLProgram astc_decoder_program; OGLProgram block_linear_unswizzle_2d_program; OGLProgram block_linear_unswizzle_3d_program; diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp index 789f4da2ed..ddd52e9426 100644 --- a/src/video_core/renderer_vulkan/blit_image.cpp +++ b/src/video_core/renderer_vulkan/blit_image.cpp @@ -8,14 +8,29 @@ #include "video_core/renderer_vulkan/vk_texture_cache.h" +#include "common/div_ceil.h" #include "common/settings.h" #include "video_core/host_shaders/blit_color_float_frag_spv.h" +#include "video_core/host_shaders/blit_color_msaa_frag_spv.h" +#include "video_core/host_shaders/blit_depth_frag_spv.h" +#include "video_core/host_shaders/blit_depth_msaa_frag_spv.h" +#include "video_core/host_shaders/blit_depth_stencil_msaa_frag_spv.h" #include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h" #include "video_core/host_shaders/convert_abgr8_to_d32f_frag_spv.h" #include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h" #include "video_core/host_shaders/convert_d32f_to_abgr8_frag_spv.h" #include "video_core/host_shaders/convert_depth_to_float_frag_spv.h" #include "video_core/host_shaders/convert_float_to_depth_frag_spv.h" +#include "video_core/host_shaders/convert_msaa_to_non_msaa_frag_spv.h" +#include "video_core/host_shaders/convert_msaa_to_non_msaa_depth_frag_spv.h" +#include "video_core/host_shaders/convert_msaa_to_non_msaa_depth_stencil_frag_spv.h" +#include "video_core/host_shaders/convert_msaa_to_non_msaa_sint_frag_spv.h" +#include "video_core/host_shaders/convert_msaa_to_non_msaa_uint_frag_spv.h" +#include "video_core/host_shaders/convert_non_msaa_to_msaa_frag_spv.h" +#include "video_core/host_shaders/convert_non_msaa_to_msaa_sint_frag_spv.h" +#include "video_core/host_shaders/convert_non_msaa_to_msaa_uint_frag_spv.h" +#include "video_core/host_shaders/convert_non_msaa_to_msaa_depth_frag_spv.h" +#include "video_core/host_shaders/convert_non_msaa_to_msaa_depth_stencil_frag_spv.h" #include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h" #include "video_core/host_shaders/full_screen_triangle_vert_spv.h" #include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h" @@ -24,11 +39,13 @@ #include "video_core/host_shaders/vulkan_depthstencil_clear_frag_spv.h" #include "video_core/renderer_vulkan/blit_image.h" #include "video_core/renderer_vulkan/maxwell_to_vk.h" +#include "video_core/renderer_vulkan/vk_render_pass_cache.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_shader_util.h" #include "video_core/renderer_vulkan/vk_state_tracker.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" #include "video_core/surface.h" +#include "video_core/texture_cache/samples_helper.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_wrapper.h" @@ -74,6 +91,12 @@ struct PushConstants { std::array tex_offset; }; +struct MSAACopyPushConstants { + std::array dst_offset; + std::array src_offset; + std::array scale; +}; + template inline constexpr VkDescriptorSetLayoutBinding TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING{ .binding = binding, @@ -241,6 +264,21 @@ constexpr VkPipelineDepthStencilStateCreateInfo PIPELINE_DEPTH_STENCIL_STATE_CRE .maxDepthBounds = 0.0f, }; +constexpr VkPipelineDepthStencilStateCreateInfo PIPELINE_DEPTH_ONLY_STATE_CREATE_INFO{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .depthTestEnable = VK_TRUE, + .depthWriteEnable = VK_TRUE, + .depthCompareOp = VK_COMPARE_OP_ALWAYS, + .depthBoundsTestEnable = VK_FALSE, + .stencilTestEnable = VK_FALSE, + .front = VkStencilOpState{}, + .back = VkStencilOpState{}, + .minDepthBounds = 0.0f, + .maxDepthBounds = 0.0f, +}; + template inline constexpr VkSamplerCreateInfo SAMPLER_CREATE_INFO{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, @@ -436,7 +474,7 @@ void TransitionImageLayout(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayo .layerCount = 1, }, }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, barrier); } @@ -474,6 +512,57 @@ void RecordShaderReadBarrier(Scheduler& scheduler, const ImageView& image_view) }); } +[[nodiscard]] VkSampleCountFlagBits SampleCountFlag(u32 num_samples) { + switch (num_samples) { + case 2: + return VK_SAMPLE_COUNT_2_BIT; + case 4: + return VK_SAMPLE_COUNT_4_BIT; + case 8: + return VK_SAMPLE_COUNT_8_BIT; + case 16: + return VK_SAMPLE_COUNT_16_BIT; + default: + return VK_SAMPLE_COUNT_1_BIT; + } +} + +[[nodiscard]] MSAACopyFormatClass FormatClass(VideoCore::Surface::PixelFormat format) { + if (!VideoCore::Surface::IsPixelFormatInteger(format)) { + return MSAACopyFormatClass::Float; + } + if (VideoCore::Surface::IsPixelFormatSignedInteger(format)) { + return MSAACopyFormatClass::SignedInteger; + } + return MSAACopyFormatClass::UnsignedInteger; +} + +[[nodiscard]] vk::ImageView MakeMSAACopyView(const vk::Device& device, VkImage image, + VkFormat format, u32 base_level, u32 base_layer, + VkImageAspectFlags aspect_mask) { + return device.CreateImageView(VkImageViewCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = format, + .components{ + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = base_level, + .levelCount = 1, + .baseArrayLayer = base_layer, + .layerCount = 1, + }, + }); +} + void BeginRenderPass(vk::CommandBuffer& cmdbuf, const Framebuffer* framebuffer) { const VkRenderPass render_pass = framebuffer->RenderPass(); const VkFramebuffer framebuffer_handle = framebuffer->Handle(); @@ -502,9 +591,9 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_, two_textures_set_layout(device.GetLogical().CreateDescriptorSetLayout( TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_CREATE_INFO)), one_texture_descriptor_allocator{ - descriptor_pool.Allocator(*one_texture_set_layout, TEXTURE_DESCRIPTOR_BANK_INFO<1>)}, + descriptor_pool.Allocator(device_, scheduler_, *one_texture_set_layout, TEXTURE_DESCRIPTOR_BANK_INFO<1>)}, two_textures_descriptor_allocator{ - descriptor_pool.Allocator(*two_textures_set_layout, TEXTURE_DESCRIPTOR_BANK_INFO<2>)}, + descriptor_pool.Allocator(device_, scheduler_, *two_textures_set_layout, TEXTURE_DESCRIPTOR_BANK_INFO<2>)}, one_texture_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo( one_texture_set_layout.address(), PUSH_CONSTANT_RANGE))), @@ -514,11 +603,24 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_, PUSH_CONSTANT_RANGE))), clear_color_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo( nullptr, PUSH_CONSTANT_RANGE))), + msaa_copy_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo( + one_texture_set_layout.address(), + PUSH_CONSTANT_RANGE))), + msaa_copy_depth_stencil_pipeline_layout( + device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo( + two_textures_set_layout.address(), + PUSH_CONSTANT_RANGE))), full_screen_vert(BuildShader(device, FULL_SCREEN_TRIANGLE_VERT_SPV)), blit_color_to_color_frag(BuildShader(device, BLIT_COLOR_FLOAT_FRAG_SPV)), + blit_color_msaa_frag(BuildShader(device, BLIT_COLOR_MSAA_FRAG_SPV)), blit_depth_stencil_frag(device.IsExtShaderStencilExportSupported() ? BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV) : vk::ShaderModule{}), + blit_depth_frag(BuildShader(device, BLIT_DEPTH_FRAG_SPV)), + blit_depth_msaa_frag(BuildShader(device, BLIT_DEPTH_MSAA_FRAG_SPV)), + blit_depth_stencil_msaa_frag(device.IsExtShaderStencilExportSupported() + ? BuildShader(device, BLIT_DEPTH_STENCIL_MSAA_FRAG_SPV) + : vk::ShaderModule{}), clear_color_vert(BuildShader(device, VULKAN_COLOR_CLEAR_VERT_SPV)), clear_color_frag(BuildShader(device, VULKAN_COLOR_CLEAR_FRAG_SPV)), clear_stencil_frag(BuildShader(device, VULKAN_DEPTHSTENCIL_CLEAR_FRAG_SPV)), @@ -531,6 +633,26 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_, convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)), convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)), convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)), + convert_msaa_to_non_msaa_frag(BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_FRAG_SPV)), + convert_msaa_to_non_msaa_sint_frag( + BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_SINT_FRAG_SPV)), + convert_msaa_to_non_msaa_uint_frag( + BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_UINT_FRAG_SPV)), + convert_msaa_to_non_msaa_depth_frag( + BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_DEPTH_FRAG_SPV)), + convert_msaa_to_non_msaa_depth_stencil_frag( + BuildShader(device, CONVERT_MSAA_TO_NON_MSAA_DEPTH_STENCIL_FRAG_SPV)), + convert_non_msaa_to_msaa_frag(BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_FRAG_SPV)), + convert_non_msaa_to_msaa_sint_frag( + BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_SINT_FRAG_SPV)), + convert_non_msaa_to_msaa_uint_frag( + BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_UINT_FRAG_SPV)), + convert_non_msaa_to_msaa_depth_frag( + BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_DEPTH_FRAG_SPV)), + convert_non_msaa_to_msaa_depth_stencil_frag( + device.IsExtShaderStencilExportSupported() + ? BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_DEPTH_STENCIL_FRAG_SPV) + : vk::ShaderModule{}), linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO)), nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO)) {} @@ -545,24 +667,13 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, const ImageV .renderpass = dst_framebuffer->RenderPass(), .operation = operation, }; - const VkPipelineLayout layout = *one_texture_pipeline_layout; - const VkSampler sampler = is_linear ? *linear_sampler : *nearest_sampler; - const VkPipeline pipeline = FindOrEmplaceColorPipeline(key); - const VkImageView src_view = src_image_view.Handle(Shader::TextureType::Color2D); - - RecordShaderReadBarrier(scheduler, src_image_view); - scheduler.RequestRenderpass(dst_framebuffer); - scheduler.Record([this, dst_region, src_region, pipeline, layout, sampler, - src_view](vk::CommandBuffer cmdbuf) { - const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit(); - UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view); - cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, - nullptr); - BindBlitState(cmdbuf, layout, dst_region, src_region); - cmdbuf.Draw(3, 1, 0, 0); - }); - scheduler.InvalidateState(); + VkSampler sampler = *nearest_sampler; + if (is_linear) { + sampler = *linear_sampler; + } + BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region, + FindOrEmplaceColorPipeline(key), sampler, + src_image_view.Handle(Shader::TextureType::Color2D), VK_NULL_HANDLE, false); } void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView src_image_view, @@ -591,33 +702,29 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView }); } -void BlitImageHelper::BlitDepthStencil(const Framebuffer* dst_framebuffer, - ImageView& src_image_view, - const Region2D& dst_region, const Region2D& src_region, - Tegra::Engines::Fermi2D::Filter filter, - Tegra::Engines::Fermi2D::Operation operation) { - if (!device.IsExtShaderStencilExportSupported()) { - return; +void BlitImageHelper::BlitImpl(const Framebuffer* dst_framebuffer, + const ImageView& src_image_view, const Region2D& dst_region, + const Region2D& src_region, VkPipeline pipeline, VkSampler sampler, + VkImageView src_view, VkImageView src_stencil_view, + bool blit_stencil) { + VkPipelineLayout layout = *one_texture_pipeline_layout; + if (blit_stencil) { + layout = *two_textures_pipeline_layout; } - ASSERT(filter == Tegra::Engines::Fermi2D::Filter::Point); - ASSERT(operation == Tegra::Engines::Fermi2D::Operation::SrcCopy); - const BlitImagePipelineKey key{ - .renderpass = dst_framebuffer->RenderPass(), - .operation = operation, - }; - const VkPipelineLayout layout = *two_textures_pipeline_layout; - const VkSampler sampler = *nearest_sampler; - const VkPipeline pipeline = FindOrEmplaceDepthStencilPipeline(key); - const VkImageView src_depth_view = src_image_view.DepthView(); - const VkImageView src_stencil_view = src_image_view.StencilView(); RecordShaderReadBarrier(scheduler, src_image_view); scheduler.RequestRenderpass(dst_framebuffer); - scheduler.Record([dst_region, src_region, pipeline, layout, sampler, src_depth_view, - src_stencil_view, this](vk::CommandBuffer cmdbuf) { - const VkDescriptorSet descriptor_set = two_textures_descriptor_allocator.Commit(); - UpdateTwoTexturesDescriptorSet(device, descriptor_set, sampler, src_depth_view, - src_stencil_view); + scheduler.Record([this, dst_region, src_region, pipeline, layout, sampler, src_view, + src_stencil_view, blit_stencil](vk::CommandBuffer cmdbuf) { + VkDescriptorSet descriptor_set = VK_NULL_HANDLE; + if (blit_stencil) { + descriptor_set = two_textures_descriptor_allocator.Commit(); + UpdateTwoTexturesDescriptorSet(device, descriptor_set, sampler, src_view, + src_stencil_view); + } else { + descriptor_set = one_texture_descriptor_allocator.Commit(); + UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view); + } cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, nullptr); @@ -627,6 +734,82 @@ void BlitImageHelper::BlitDepthStencil(const Framebuffer* dst_framebuffer, scheduler.InvalidateState(); } +void BlitImageHelper::BlitColorMSAA(const Framebuffer* dst_framebuffer, + const ImageView& src_image_view, const Region2D& dst_region, + const Region2D& src_region) { + const BlitMSAAPipelineKey key{ + .renderpass = dst_framebuffer->RenderPass(), + .samples = dst_framebuffer->Samples(), + }; + BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region, + FindOrEmplaceBlitColorMSAAPipeline(key), *nearest_sampler, + src_image_view.Handle(Shader::TextureType::Color2D), VK_NULL_HANDLE, false); +} + +void BlitImageHelper::BlitDepthStencilMSAA(const Framebuffer* dst_framebuffer, + ImageView& src_image_view, const Region2D& dst_region, + const Region2D& src_region) { + const bool blit_stencil = + dst_framebuffer->HasAspectStencilBit() && device.IsExtShaderStencilExportSupported(); + const BlitMSAAPipelineKey key{ + .renderpass = dst_framebuffer->RenderPass(), + .samples = dst_framebuffer->Samples(), + }; + VkImageView src_stencil_view = VK_NULL_HANDLE; + if (blit_stencil) { + src_stencil_view = src_image_view.StencilView(); + } + BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region, + FindOrEmplaceBlitDepthStencilMSAAPipeline(key, blit_stencil), *nearest_sampler, + src_image_view.DepthView(), src_stencil_view, blit_stencil); +} + +void BlitImageHelper::BlitDepth(const Framebuffer* dst_framebuffer, ImageView& src_image_view, + const Region2D& dst_region, const Region2D& src_region) { + BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region, + FindOrEmplaceBlitDepthPipeline(dst_framebuffer->RenderPass()), *nearest_sampler, + src_image_view.DepthView(), VK_NULL_HANDLE, false); +} + +void BlitImageHelper::ResolveDepthStencil(const Framebuffer* dst_framebuffer, + ImageView& src_image_view, const Region2D& dst_region, + const Region2D& src_region) { + const bool resolve_stencil = + dst_framebuffer->HasAspectStencilBit() && device.IsExtShaderStencilExportSupported(); + VkImageView src_stencil_view = VK_NULL_HANDLE; + if (resolve_stencil) { + src_stencil_view = src_image_view.StencilView(); + } + BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region, + FindOrEmplaceResolveDepthStencilPipeline(dst_framebuffer->RenderPass(), + resolve_stencil), + *nearest_sampler, src_image_view.DepthView(), src_stencil_view, resolve_stencil); +} + +void BlitImageHelper::BlitDepthStencil(const Framebuffer* dst_framebuffer, + ImageView& src_image_view, + const Region2D& dst_region, const Region2D& src_region, + Tegra::Engines::Fermi2D::Filter filter, + Tegra::Engines::Fermi2D::Operation operation) { + ASSERT(filter == Tegra::Engines::Fermi2D::Filter::Point); + ASSERT(operation == Tegra::Engines::Fermi2D::Operation::SrcCopy); + const bool blit_stencil = device.IsExtShaderStencilExportSupported(); + const BlitImagePipelineKey key{ + .renderpass = dst_framebuffer->RenderPass(), + .operation = operation, + }; + VkPipeline pipeline{}; + VkImageView src_stencil_view = VK_NULL_HANDLE; + if (blit_stencil) { + pipeline = FindOrEmplaceDepthStencilPipeline(key); + src_stencil_view = src_image_view.StencilView(); + } else { + pipeline = FindOrEmplaceBlitDepthPipeline(key.renderpass); + } + BlitImpl(dst_framebuffer, src_image_view, dst_region, src_region, pipeline, *nearest_sampler, + src_image_view.DepthView(), src_stencil_view, blit_stencil); +} + void BlitImageHelper::ConvertD32ToR32(const Framebuffer* dst_framebuffer, const ImageView& src_image_view) { ConvertDepthToColorPipeline(convert_d32_to_r32_pipeline, dst_framebuffer->RenderPass()); @@ -739,6 +922,224 @@ void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool scheduler.InvalidateState(); } +void BlitImageHelper::CopyMSAAImpl(VkRenderPass renderpass, VkPipeline pipeline, + VkPipelineLayout layout, VkImage dst_image, + VkFormat dst_vk_format, VkImage src_image, + VkFormat src_vk_format, s32 scale_x, s32 scale_y, + std::span copies, + const MSAACopyAspectInfo& aspect_info, bool copy_stencil) { + while (!msaa_copy_resources.empty() && scheduler.IsFree(msaa_copy_resources.front().tick)) { + msaa_copy_resources.pop_front(); + } + const VkSampler sampler = *nearest_sampler; + for (const VideoCommon::ImageCopy& copy : copies) { + const s32 num_layers = (std::min)(copy.src_subresource.num_layers, + copy.dst_subresource.num_layers); + for (s32 layer = 0; layer < num_layers; ++layer) { + const u32 src_level = static_cast(copy.src_subresource.base_level); + const u32 src_layer = static_cast(copy.src_subresource.base_layer + layer); + vk::ImageView src_view = + MakeMSAACopyView(device.GetLogical(), src_image, src_vk_format, src_level, + src_layer, aspect_info.src_view_aspect); + vk::ImageView src_stencil_view; + if (copy_stencil) { + src_stencil_view = + MakeMSAACopyView(device.GetLogical(), src_image, src_vk_format, src_level, + src_layer, VK_IMAGE_ASPECT_STENCIL_BIT); + } + vk::ImageView dst_view = + MakeMSAACopyView(device.GetLogical(), dst_image, dst_vk_format, + static_cast(copy.dst_subresource.base_level), + static_cast(copy.dst_subresource.base_layer + layer), + aspect_info.attachment_aspect); + const VkOffset2D dst_offset{copy.dst_offset.x, copy.dst_offset.y}; + const VkExtent2D dst_extent{copy.extent.width, copy.extent.height}; + const VkRect2D render_area{ + .offset = dst_offset, + .extent = dst_extent, + }; + vk::Framebuffer framebuffer = device.GetLogical().CreateFramebuffer(VkFramebufferCreateInfo{ + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .renderPass = renderpass, + .attachmentCount = 1, + .pAttachments = dst_view.address(), + .width = static_cast(dst_offset.x) + dst_extent.width, + .height = static_cast(dst_offset.y) + dst_extent.height, + .layers = 1, + }); + const MSAACopyPushConstants push_constants{ + .dst_offset = {dst_offset.x, dst_offset.y}, + .src_offset = {copy.src_offset.x, copy.src_offset.y}, + .scale = {scale_x, scale_y}, + }; + VkImageView src_stencil_handle = VK_NULL_HANDLE; + if (copy_stencil) { + src_stencil_handle = *src_stencil_view; + } + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([this, pipeline, layout, sampler, renderpass, + framebuffer_handle = *framebuffer, src_view_handle = *src_view, + src_stencil_handle, src = src_image, dst = dst_image, render_area, + aspect_info, push_constants](vk::CommandBuffer cmdbuf) { + const VkImageSubresourceRange barrier_range{ + .aspectMask = aspect_info.barrier_aspect, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }; + const std::array pre_barriers{ + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = aspect_info.pre_src_access, + .dstAccessMask = aspect_info.pre_src_dst_access, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = src, + .subresourceRange = barrier_range, + }, + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = aspect_info.pre_src_access, + .dstAccessMask = aspect_info.pre_dst_dst_access, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = dst, + .subresourceRange = barrier_range, + }, + }; + cmdbuf.PipelineBarrier(aspect_info.pre_src_stages, aspect_info.pre_dst_stages, 0, + nullptr, nullptr, pre_barriers); + const VkRenderPassBeginInfo renderpass_bi{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .pNext = nullptr, + .renderPass = renderpass, + .framebuffer = framebuffer_handle, + .renderArea = render_area, + .clearValueCount = 0, + .pClearValues = nullptr, + }; + cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); + VkDescriptorSet descriptor_set = VK_NULL_HANDLE; + if (src_stencil_handle != VK_NULL_HANDLE) { + descriptor_set = two_textures_descriptor_allocator.Commit(); + UpdateTwoTexturesDescriptorSet(device, descriptor_set, sampler, src_view_handle, + src_stencil_handle); + } else { + descriptor_set = one_texture_descriptor_allocator.Commit(); + UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view_handle); + } + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, + nullptr); + const VkViewport viewport{ + .x = static_cast(render_area.offset.x), + .y = static_cast(render_area.offset.y), + .width = static_cast(render_area.extent.width), + .height = static_cast(render_area.extent.height), + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + cmdbuf.SetViewport(0, viewport); + cmdbuf.SetScissor(0, render_area); + cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, push_constants); + cmdbuf.Draw(3, 1, 0, 0); + cmdbuf.EndRenderPass(); + const VkImageMemoryBarrier post_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = aspect_info.post_src_access, + .dstAccessMask = aspect_info.post_dst_access, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = dst, + .subresourceRange = barrier_range, + }; + cmdbuf.PipelineBarrier(aspect_info.post_src_stages, aspect_info.post_dst_stages, 0, + post_barrier); + }); + msaa_copy_resources.push_back(MSAACopyResources{ + .tick = scheduler.CurrentTick(), + .src_view = std::move(src_view), + .dst_view = std::move(dst_view), + .framebuffer = std::move(framebuffer), + }); + if (copy_stencil) { + msaa_copy_resources.push_back(MSAACopyResources{ + .tick = scheduler.CurrentTick(), + .src_view = std::move(src_stencil_view), + .dst_view = vk::ImageView{}, + .framebuffer = vk::Framebuffer{}, + }); + } + } + } + scheduler.InvalidateState(); +} + +void BlitImageHelper::CopyMSAA(RenderPassCache& render_pass_cache, VkImage dst_image, + VideoCore::Surface::PixelFormat dst_format, VkImage src_image, + VideoCore::Surface::PixelFormat src_format, u32 num_samples, + std::span copies, + bool msaa_to_non_msaa) { + const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(static_cast(num_samples)); + const s32 scale_x = 1 << samples_x; + const s32 scale_y = 1 << samples_y; + VkSampleCountFlagBits samples = SampleCountFlag(num_samples); + if (msaa_to_non_msaa) { + samples = VK_SAMPLE_COUNT_1_BIT; + } + RenderPassKey renderpass_key{}; + renderpass_key.color_formats.fill(VideoCore::Surface::PixelFormat::Invalid); + renderpass_key.color_formats[0] = dst_format; + renderpass_key.depth_format = VideoCore::Surface::PixelFormat::Invalid; + renderpass_key.samples = samples; + const VkRenderPass renderpass = render_pass_cache.Get(renderpass_key); + const MSAACopyPipelineKey key{ + .renderpass = renderpass, + .samples = samples, + .msaa_to_non_msaa = msaa_to_non_msaa, + .format_class = FormatClass(dst_format), + }; + const MSAACopyAspectInfo aspect_info{ + .src_view_aspect = VK_IMAGE_ASPECT_COLOR_BIT, + .attachment_aspect = VK_IMAGE_ASPECT_COLOR_BIT, + .barrier_aspect = VK_IMAGE_ASPECT_COLOR_BIT, + .pre_src_access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT | + VK_ACCESS_TRANSFER_WRITE_BIT, + .pre_src_dst_access = VK_ACCESS_SHADER_READ_BIT, + .pre_dst_dst_access = + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .pre_src_stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, + .pre_dst_stages = + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .post_src_access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .post_dst_access = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT, + .post_src_stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .post_dst_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, + }; + const VkFormat src_vk_format = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, src_format).format; + const VkFormat dst_vk_format = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, dst_format).format; + CopyMSAAImpl(renderpass, FindOrEmplaceMSAACopyPipeline(key), *msaa_copy_pipeline_layout, + dst_image, dst_vk_format, src_image, src_vk_format, scale_x, scale_y, copies, + aspect_info, false); +} + void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, const ImageView& src_image_view) { const VkPipelineLayout layout = *one_texture_pipeline_layout; @@ -880,7 +1281,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceColorPipeline(const BlitImagePipelineKe .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = 0, - })); + }, device.StaticPipelineCache())); return *blit_color_pipelines.back(); } @@ -905,14 +1306,14 @@ VkPipeline BlitImageHelper::FindOrEmplaceDepthStencilPipeline(const BlitImagePip .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, - .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO, .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, .layout = *two_textures_pipeline_layout, .renderPass = key.renderpass, .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = 0, - })); + }, device.StaticPipelineCache())); return *blit_depth_stencil_pipelines.back(); } @@ -965,7 +1366,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearColorPipeline(const BlitImagePipel .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = 0, - })); + }, device.StaticPipelineCache())); return *clear_color_pipelines.back(); } @@ -1021,18 +1422,111 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline( .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = 0, - })); + }, device.StaticPipelineCache())); return *clear_stencil_pipelines.back(); } -void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) { - if (pipeline) { - return; +VkPipeline BlitImageHelper::FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPipelineKey& key) { + const auto it = std::ranges::find(blit_msaa_color_keys, key); + if (it != blit_msaa_color_keys.end()) { + return *blit_msaa_color_pipelines[std::distance(blit_msaa_color_keys.begin(), it)]; } - VkShaderModule frag_shader = *convert_float_to_depth_frag; - const std::array stages = MakeStages(*full_screen_vert, frag_shader); + blit_msaa_color_keys.push_back(key); + const std::array stages = MakeStages(*full_screen_vert, *blit_color_msaa_frag); + const VkPipelineMultisampleStateCreateInfo multisample_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .rasterizationSamples = key.samples, + .sampleShadingEnable = VK_TRUE, + .minSampleShading = 1.0f, + .pSampleMask = nullptr, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, + }; const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); - pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{ + blit_msaa_color_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pInputAssemblyState = &input_assembly_ci, + .pTessellationState = nullptr, + .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pMultisampleState = &multisample_ci, + .pDepthStencilState = nullptr, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, + .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .layout = *one_texture_pipeline_layout, + .renderPass = key.renderpass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + }, device.StaticPipelineCache())); + return *blit_msaa_color_pipelines.back(); +} + +VkPipeline BlitImageHelper::FindOrEmplaceBlitDepthStencilMSAAPipeline( + const BlitMSAAPipelineKey& key, bool blit_stencil) { + auto& keys = blit_stencil ? blit_msaa_depth_stencil_keys : blit_msaa_depth_keys; + auto& pipelines = blit_stencil ? blit_msaa_depth_stencil_pipelines : blit_msaa_depth_pipelines; + const auto it = std::ranges::find(keys, key); + if (it != keys.end()) { + return *pipelines[std::distance(keys.begin(), it)]; + } + keys.push_back(key); + const std::array stages = + MakeStages(*full_screen_vert, + blit_stencil ? *blit_depth_stencil_msaa_frag : *blit_depth_msaa_frag); + const VkPipelineMultisampleStateCreateInfo multisample_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .rasterizationSamples = key.samples, + .sampleShadingEnable = VK_TRUE, + .minSampleShading = 1.0f, + .pSampleMask = nullptr, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, + }; + const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); + pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pInputAssemblyState = &input_assembly_ci, + .pTessellationState = nullptr, + .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pMultisampleState = &multisample_ci, + .pDepthStencilState = blit_stencil ? &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO + : &PIPELINE_DEPTH_ONLY_STATE_CREATE_INFO, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO, + .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .layout = blit_stencil ? *two_textures_pipeline_layout : *one_texture_pipeline_layout, + .renderPass = key.renderpass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + })); + return *pipelines.back(); +} + +VkPipeline BlitImageHelper::FindOrEmplaceBlitDepthPipeline(VkRenderPass renderpass) { + const auto it = std::ranges::find(blit_depth_keys, renderpass); + if (it != blit_depth_keys.end()) { + return *blit_depth_pipelines[std::distance(blit_depth_keys.begin(), it)]; + } + blit_depth_keys.push_back(renderpass); + const std::array stages = MakeStages(*full_screen_vert, *blit_depth_frag); + const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); + blit_depth_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = 0, @@ -1044,7 +1538,7 @@ void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRend .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, - .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .pDepthStencilState = &PIPELINE_DEPTH_ONLY_STATE_CREATE_INFO, .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO, .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, .layout = *one_texture_pipeline_layout, @@ -1052,17 +1546,24 @@ void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRend .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = 0, - }); + })); + return *blit_depth_pipelines.back(); } -void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) { - if (pipeline) { - return; +VkPipeline BlitImageHelper::FindOrEmplaceResolveDepthStencilPipeline(VkRenderPass renderpass, + bool resolve_stencil) { + auto& keys = resolve_stencil ? resolve_depth_stencil_keys : resolve_depth_keys; + auto& pipelines = resolve_stencil ? resolve_depth_stencil_pipelines : resolve_depth_pipelines; + const auto it = std::ranges::find(keys, renderpass); + if (it != keys.end()) { + return *pipelines[std::distance(keys.begin(), it)]; } - VkShaderModule frag_shader = *convert_depth_to_float_frag; - const std::array stages = MakeStages(*full_screen_vert, frag_shader); + keys.push_back(renderpass); + const std::array stages = + MakeStages(*full_screen_vert, + resolve_stencil ? *blit_depth_stencil_msaa_frag : *blit_depth_msaa_frag); const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); - pipeline = device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{ + pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = 0, @@ -1074,15 +1575,217 @@ void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRend .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, - .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, - .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, + .pDepthStencilState = resolve_stencil ? &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO + : &PIPELINE_DEPTH_ONLY_STATE_CREATE_INFO, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO, .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, - .layout = *one_texture_pipeline_layout, + .layout = resolve_stencil ? *two_textures_pipeline_layout : *one_texture_pipeline_layout, .renderPass = renderpass, .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = 0, - }); + }, device.StaticPipelineCache())); + return *pipelines.back(); +} + +void BlitImageHelper::CopyMSAADepth(RenderPassCache& render_pass_cache, VkImage dst_image, + VideoCore::Surface::PixelFormat dst_format, VkImage src_image, + VideoCore::Surface::PixelFormat src_format, u32 num_samples, + std::span copies, + bool copy_stencil, bool msaa_to_non_msaa) { + const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(static_cast(num_samples)); + const s32 scale_x = 1 << samples_x; + const s32 scale_y = 1 << samples_y; + VkSampleCountFlagBits samples = SampleCountFlag(num_samples); + if (msaa_to_non_msaa) { + samples = VK_SAMPLE_COUNT_1_BIT; + } + RenderPassKey renderpass_key{}; + renderpass_key.color_formats.fill(VideoCore::Surface::PixelFormat::Invalid); + renderpass_key.depth_format = dst_format; + renderpass_key.samples = samples; + const VkRenderPass renderpass = render_pass_cache.Get(renderpass_key); + const MSAACopyPipelineKey key{ + .renderpass = renderpass, + .samples = samples, + .msaa_to_non_msaa = msaa_to_non_msaa, + .format_class = MSAACopyFormatClass::Float, + }; + VkImageAspectFlags attachment_aspect = VK_IMAGE_ASPECT_DEPTH_BIT; + if (VideoCore::Surface::GetFormatType(dst_format) == + VideoCore::Surface::SurfaceType::DepthStencil) { + attachment_aspect |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + VkPipelineLayout layout = *msaa_copy_pipeline_layout; + if (copy_stencil) { + layout = *msaa_copy_depth_stencil_pipeline_layout; + } + const MSAACopyAspectInfo aspect_info{ + .src_view_aspect = VK_IMAGE_ASPECT_DEPTH_BIT, + .attachment_aspect = attachment_aspect, + .barrier_aspect = attachment_aspect, + .pre_src_access = + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + .pre_src_dst_access = VK_ACCESS_SHADER_READ_BIT, + .pre_dst_dst_access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .pre_src_stages = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_TRANSFER_BIT, + .pre_dst_stages = + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, + .post_src_access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .post_dst_access = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT, + .post_src_stages = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .post_dst_stages = vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, + }; + const VkFormat src_vk_format = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, src_format).format; + const VkFormat dst_vk_format = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, dst_format).format; + CopyMSAAImpl(renderpass, FindOrEmplaceMSAACopyDepthPipeline(key, copy_stencil), layout, + dst_image, dst_vk_format, src_image, src_vk_format, scale_x, scale_y, copies, + aspect_info, copy_stencil); +} + +VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyPipeline(const MSAACopyPipelineKey& key) { + const auto it = std::ranges::find(msaa_copy_keys, key); + if (it != msaa_copy_keys.end()) { + return *msaa_copy_pipelines[std::distance(msaa_copy_keys.begin(), it)]; + } + msaa_copy_keys.push_back(key); + VkShaderModule frag_module = key.msaa_to_non_msaa ? *convert_msaa_to_non_msaa_frag + : *convert_non_msaa_to_msaa_frag; + if (key.format_class == MSAACopyFormatClass::SignedInteger) { + frag_module = key.msaa_to_non_msaa ? *convert_msaa_to_non_msaa_sint_frag + : *convert_non_msaa_to_msaa_sint_frag; + } else if (key.format_class == MSAACopyFormatClass::UnsignedInteger) { + frag_module = key.msaa_to_non_msaa ? *convert_msaa_to_non_msaa_uint_frag + : *convert_non_msaa_to_msaa_uint_frag; + } + const std::array stages = MakeStages(*clear_color_vert, frag_module); + const VkPipelineMultisampleStateCreateInfo multisample_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .rasterizationSamples = key.samples, + .sampleShadingEnable = key.msaa_to_non_msaa ? VK_FALSE : VK_TRUE, + .minSampleShading = key.msaa_to_non_msaa ? 0.0f : 1.0f, + .pSampleMask = nullptr, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, + }; + const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); + msaa_copy_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pInputAssemblyState = &input_assembly_ci, + .pTessellationState = nullptr, + .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pMultisampleState = &multisample_ci, + .pDepthStencilState = nullptr, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, + .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .layout = *msaa_copy_pipeline_layout, + .renderPass = key.renderpass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + }, device.StaticPipelineCache())); + return *msaa_copy_pipelines.back(); +} + +VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyDepthPipeline(const MSAACopyPipelineKey& key, + bool copy_stencil) { + auto& keys = copy_stencil ? msaa_copy_depth_stencil_keys : msaa_copy_depth_keys; + auto& pipelines = copy_stencil ? msaa_copy_depth_stencil_pipelines : msaa_copy_depth_pipelines; + const auto it = std::ranges::find(keys, key); + if (it != keys.end()) { + return *pipelines[std::distance(keys.begin(), it)]; + } + keys.push_back(key); + VkShaderModule frag_module; + if (key.msaa_to_non_msaa) { + frag_module = copy_stencil ? *convert_msaa_to_non_msaa_depth_stencil_frag + : *convert_msaa_to_non_msaa_depth_frag; + } else { + frag_module = copy_stencil ? *convert_non_msaa_to_msaa_depth_stencil_frag + : *convert_non_msaa_to_msaa_depth_frag; + } + const std::array stages = MakeStages(*clear_color_vert, frag_module); + const VkPipelineMultisampleStateCreateInfo multisample_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .rasterizationSamples = key.samples, + .sampleShadingEnable = key.msaa_to_non_msaa ? VK_FALSE : VK_TRUE, + .minSampleShading = key.msaa_to_non_msaa ? 0.0f : 1.0f, + .pSampleMask = nullptr, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, + }; + static constexpr VkStencilOpState REPLACE_STENCIL_OP{ + .failOp = VK_STENCIL_OP_REPLACE, + .passOp = VK_STENCIL_OP_REPLACE, + .depthFailOp = VK_STENCIL_OP_REPLACE, + .compareOp = VK_COMPARE_OP_ALWAYS, + .compareMask = 0xFF, + .writeMask = 0xFF, + .reference = 0, + }; + const VkPipelineDepthStencilStateCreateInfo depth_stencil_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .depthTestEnable = VK_TRUE, + .depthWriteEnable = VK_TRUE, + .depthCompareOp = VK_COMPARE_OP_ALWAYS, + .depthBoundsTestEnable = VK_FALSE, + .stencilTestEnable = copy_stencil ? VK_TRUE : VK_FALSE, + .front = copy_stencil ? REPLACE_STENCIL_OP : VkStencilOpState{}, + .back = copy_stencil ? REPLACE_STENCIL_OP : VkStencilOpState{}, + .minDepthBounds = 0.0f, + .maxDepthBounds = 0.0f, + }; + const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = + GetPipelineInputAssemblyStateCreateInfo(device); + pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pInputAssemblyState = &input_assembly_ci, + .pTessellationState = nullptr, + .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pMultisampleState = &multisample_ci, + .pDepthStencilState = &depth_stencil_ci, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO, + .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .layout = copy_stencil ? *msaa_copy_depth_stencil_pipeline_layout + : *msaa_copy_pipeline_layout, + .renderPass = key.renderpass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + })); + return *pipelines.back(); +} + +void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) { + ConvertPipeline(pipeline, renderpass, false); +} + +void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) { + ConvertPipeline(pipeline, renderpass, true); } void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass renderpass, @@ -1106,14 +1809,15 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .pDepthStencilState = is_target_depth ? &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO : nullptr, - .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, + .pColorBlendState = is_target_depth ? &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO + : &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, .layout = single_texture ? *one_texture_pipeline_layout : *two_textures_pipeline_layout, .renderPass = renderpass, .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = 0, - }); + }, device.StaticPipelineCache()); } void BlitImageHelper::ConvertPipelineColorTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass, @@ -1156,7 +1860,7 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = 0, - }); + }, device.StaticPipelineCache()); } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h index 34f1b25f0f..62e6743a23 100644 --- a/src/video_core/renderer_vulkan/blit_image.h +++ b/src/video_core/renderer_vulkan/blit_image.h @@ -6,8 +6,12 @@ #pragma once +#include +#include + #include "video_core/engines/fermi_2d.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" +#include "video_core/surface.h" #include "video_core/texture_cache/types.h" #include "video_core/vulkan_common/vulkan_wrapper.h" @@ -20,6 +24,7 @@ using VideoCommon::Region2D; class Device; class Framebuffer; class ImageView; +class RenderPassCache; class StateTracker; class Scheduler; @@ -40,6 +45,28 @@ struct BlitDepthStencilPipelineKey { u32 stencil_ref; }; +enum class MSAACopyFormatClass : u32 { + Float, + SignedInteger, + UnsignedInteger, +}; + +struct MSAACopyPipelineKey { + constexpr auto operator<=>(const MSAACopyPipelineKey&) const noexcept = default; + + VkRenderPass renderpass; + VkSampleCountFlagBits samples; + bool msaa_to_non_msaa; + MSAACopyFormatClass format_class; +}; + +struct BlitMSAAPipelineKey { + constexpr auto operator<=>(const BlitMSAAPipelineKey&) const noexcept = default; + + VkRenderPass renderpass; + VkSampleCountFlagBits samples; +}; + class BlitImageHelper { public: explicit BlitImageHelper(const Device& device, Scheduler& scheduler, @@ -55,6 +82,18 @@ public: VkImage src_image, VkSampler src_sampler, const Region2D& dst_region, const Region2D& src_region, const Extent3D& src_size); + void BlitColorMSAA(const Framebuffer* dst_framebuffer, const ImageView& src_image_view, + const Region2D& dst_region, const Region2D& src_region); + + void BlitDepthStencilMSAA(const Framebuffer* dst_framebuffer, ImageView& src_image_view, + const Region2D& dst_region, const Region2D& src_region); + + void BlitDepth(const Framebuffer* dst_framebuffer, ImageView& src_image_view, + const Region2D& dst_region, const Region2D& src_region); + + void ResolveDepthStencil(const Framebuffer* dst_framebuffer, ImageView& src_image_view, + const Region2D& dst_region, const Region2D& src_region); + void BlitDepthStencil(const Framebuffer* dst_framebuffer, ImageView& src_image_view, const Region2D& dst_region, const Region2D& src_region, Tegra::Engines::Fermi2D::Filter filter, @@ -84,7 +123,45 @@ public: void ClearDepthStencil(const Framebuffer* dst_framebuffer, bool depth_clear, f32 clear_depth, u8 stencil_mask, u32 stencil_ref, u32 stencil_compare_mask, const Region2D& dst_region); + + void CopyMSAA(RenderPassCache& render_pass_cache, VkImage dst_image, + VideoCore::Surface::PixelFormat dst_format, VkImage src_image, + VideoCore::Surface::PixelFormat src_format, u32 num_samples, + std::span copies, bool msaa_to_non_msaa); + + void CopyMSAADepth(RenderPassCache& render_pass_cache, VkImage dst_image, + VideoCore::Surface::PixelFormat dst_format, VkImage src_image, + VideoCore::Surface::PixelFormat src_format, u32 num_samples, + std::span copies, bool copy_stencil, + bool msaa_to_non_msaa); + private: + struct MSAACopyAspectInfo { + VkImageAspectFlags src_view_aspect; + VkImageAspectFlags attachment_aspect; + VkImageAspectFlags barrier_aspect; + VkAccessFlags pre_src_access; + VkAccessFlags pre_src_dst_access; + VkAccessFlags pre_dst_dst_access; + VkPipelineStageFlags pre_src_stages; + VkPipelineStageFlags pre_dst_stages; + VkAccessFlags post_src_access; + VkAccessFlags post_dst_access; + VkPipelineStageFlags post_src_stages; + VkPipelineStageFlags post_dst_stages; + }; + + void BlitImpl(const Framebuffer* dst_framebuffer, const ImageView& src_image_view, + const Region2D& dst_region, const Region2D& src_region, VkPipeline pipeline, + VkSampler sampler, VkImageView src_view, VkImageView src_stencil_view, + bool blit_stencil); + + void CopyMSAAImpl(VkRenderPass renderpass, VkPipeline pipeline, VkPipelineLayout layout, + VkImage dst_image, VkFormat dst_vk_format, VkImage src_image, + VkFormat src_vk_format, s32 scale_x, s32 scale_y, + std::span copies, + const MSAACopyAspectInfo& aspect_info, bool copy_stencil); + void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, const ImageView& src_image_view); @@ -98,6 +175,16 @@ private: [[nodiscard]] VkPipeline FindOrEmplaceClearColorPipeline(const BlitImagePipelineKey& key); [[nodiscard]] VkPipeline FindOrEmplaceClearStencilPipeline( const BlitDepthStencilPipelineKey& key); + [[nodiscard]] VkPipeline FindOrEmplaceMSAACopyPipeline(const MSAACopyPipelineKey& key); + + [[nodiscard]] VkPipeline FindOrEmplaceMSAACopyDepthPipeline(const MSAACopyPipelineKey& key, + bool copy_stencil); + [[nodiscard]] VkPipeline FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPipelineKey& key); + [[nodiscard]] VkPipeline FindOrEmplaceBlitDepthStencilMSAAPipeline( + const BlitMSAAPipelineKey& key, bool blit_stencil); + [[nodiscard]] VkPipeline FindOrEmplaceBlitDepthPipeline(VkRenderPass renderpass); + [[nodiscard]] VkPipeline FindOrEmplaceResolveDepthStencilPipeline(VkRenderPass renderpass, + bool resolve_stencil); void ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, bool is_target_depth); @@ -125,9 +212,15 @@ private: vk::PipelineLayout one_texture_pipeline_layout; vk::PipelineLayout two_textures_pipeline_layout; vk::PipelineLayout clear_color_pipeline_layout; + vk::PipelineLayout msaa_copy_pipeline_layout; + vk::PipelineLayout msaa_copy_depth_stencil_pipeline_layout; vk::ShaderModule full_screen_vert; vk::ShaderModule blit_color_to_color_frag; + vk::ShaderModule blit_color_msaa_frag; vk::ShaderModule blit_depth_stencil_frag; + vk::ShaderModule blit_depth_frag; + vk::ShaderModule blit_depth_msaa_frag; + vk::ShaderModule blit_depth_stencil_msaa_frag; vk::ShaderModule clear_color_vert; vk::ShaderModule clear_color_frag; vk::ShaderModule clear_stencil_frag; @@ -138,6 +231,16 @@ private: vk::ShaderModule convert_d32f_to_abgr8_frag; vk::ShaderModule convert_d24s8_to_abgr8_frag; vk::ShaderModule convert_s8d24_to_abgr8_frag; + vk::ShaderModule convert_msaa_to_non_msaa_frag; + vk::ShaderModule convert_msaa_to_non_msaa_sint_frag; + vk::ShaderModule convert_msaa_to_non_msaa_uint_frag; + vk::ShaderModule convert_msaa_to_non_msaa_depth_frag; + vk::ShaderModule convert_msaa_to_non_msaa_depth_stencil_frag; + vk::ShaderModule convert_non_msaa_to_msaa_frag; + vk::ShaderModule convert_non_msaa_to_msaa_sint_frag; + vk::ShaderModule convert_non_msaa_to_msaa_uint_frag; + vk::ShaderModule convert_non_msaa_to_msaa_depth_frag; + vk::ShaderModule convert_non_msaa_to_msaa_depth_stencil_frag; vk::Sampler linear_sampler; vk::Sampler nearest_sampler; @@ -149,6 +252,31 @@ private: std::vector clear_color_pipelines; std::vector clear_stencil_keys; std::vector clear_stencil_pipelines; + std::vector msaa_copy_keys; + std::vector msaa_copy_pipelines; + std::vector msaa_copy_depth_keys; + std::vector msaa_copy_depth_pipelines; + std::vector msaa_copy_depth_stencil_keys; + std::vector msaa_copy_depth_stencil_pipelines; + std::vector blit_msaa_color_keys; + std::vector blit_msaa_color_pipelines; + std::vector blit_depth_keys; + std::vector blit_depth_pipelines; + std::vector blit_msaa_depth_keys; + std::vector blit_msaa_depth_pipelines; + std::vector blit_msaa_depth_stencil_keys; + std::vector blit_msaa_depth_stencil_pipelines; + std::vector resolve_depth_keys; + std::vector resolve_depth_pipelines; + std::vector resolve_depth_stencil_keys; + std::vector resolve_depth_stencil_pipelines; + struct MSAACopyResources { + u64 tick; + vk::ImageView src_view; + vk::ImageView dst_view; + vk::Framebuffer framebuffer; + }; + std::deque msaa_copy_resources; vk::Pipeline convert_d32_to_r32_pipeline; vk::Pipeline convert_r32_to_d32_pipeline; vk::Pipeline convert_d16_to_r16_pipeline; diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp index c6aa28fa23..0ced890929 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.cpp @@ -39,6 +39,55 @@ constexpr std::array POLYGON_OFFSET_ENABLE_LUT = { POLYGON, // Patches }; +constexpr std::array TOPOLOGY_CLASS_REPRESENTATIVE_LUT = { + Maxwell::PrimitiveTopology::Points, // Points + Maxwell::PrimitiveTopology::Lines, // Lines + Maxwell::PrimitiveTopology::LineLoop, // LineLoop + Maxwell::PrimitiveTopology::LineStrip, // LineStrip + Maxwell::PrimitiveTopology::Triangles, // Triangles + Maxwell::PrimitiveTopology::Triangles, // TriangleStrip + Maxwell::PrimitiveTopology::Triangles, // TriangleFan + Maxwell::PrimitiveTopology::Triangles, // Quads + Maxwell::PrimitiveTopology::Triangles, // QuadStrip + Maxwell::PrimitiveTopology::Triangles, // Polygon + Maxwell::PrimitiveTopology::LinesAdjacency, // LinesAdjacency + Maxwell::PrimitiveTopology::LinesAdjacency, // LineStripAdjacency + Maxwell::PrimitiveTopology::TrianglesAdjacency, // TrianglesAdjacency + Maxwell::PrimitiveTopology::TrianglesAdjacency, // TriangleStripAdjacency + Maxwell::PrimitiveTopology::Patches, // Patches +}; + +bool IsDualSourceBlendFactor(Maxwell::Blend::Factor factor) { + using F = Maxwell::Blend::Factor; + switch (factor) { + case F::Source1Color_D3D: + case F::OneMinusSource1Color_D3D: + case F::Source1Alpha_D3D: + case F::OneMinusSource1Alpha_D3D: + case F::Source1Color_GL: + case F::OneMinusSource1Color_GL: + case F::Source1Alpha_GL: + case F::OneMinusSource1Alpha_GL: + return true; + default: + return false; + } +} + +bool ComputeAttachment0DualSourceBlend(const Maxwell& regs) { + if (!regs.blend.enable[0]) { + return false; + } + const auto uses_dual_source = [](const auto& blend) { + return IsDualSourceBlendFactor(blend.color_source) || + IsDualSourceBlendFactor(blend.color_dest) || + IsDualSourceBlendFactor(blend.alpha_source) || + IsDualSourceBlendFactor(blend.alpha_dest); + }; + return regs.blend_per_target_enabled ? uses_dual_source(regs.blend_per_target[0]) + : uses_dual_source(regs.blend); +} + void RefreshXfbState(VideoCommon::TransformFeedbackState& state, const Maxwell& regs) { std::ranges::transform(regs.transform_feedback.controls, state.layouts.begin(), [](const auto& layout) { @@ -56,12 +105,16 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe const Maxwell& regs = maxwell3d.regs; const auto topology_ = maxwell3d.draw_manager.draw_state.topology; + driver_id = features.driver_id; + driver_version = features.driver_version; + raw1 = 0; extended_dynamic_state.Assign(features.has_extended_dynamic_state ? 1 : 0); extended_dynamic_state_2.Assign(features.has_extended_dynamic_state_2 ? 1 : 0); extended_dynamic_state_2_logic_op.Assign(features.has_extended_dynamic_state_2_logic_op ? 1 : 0); extended_dynamic_state_3_blend.Assign(features.has_extended_dynamic_state_3_blend ? 1 : 0); extended_dynamic_state_3_enables.Assign(features.has_extended_dynamic_state_3_enables ? 1 : 0); + color_write_enable_dynamic.Assign(features.has_color_write_enable ? 1 : 0); dynamic_vertex_input.Assign(features.has_dynamic_vertex_input ? 1 : 0); xfb_enabled.Assign(regs.transform_feedback_enabled != 0); ndc_minus_one_to_one.Assign(regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1 : 0); @@ -71,8 +124,13 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe tessellation_clockwise.Assign(regs.tessellation.params.output_primitives.Value() == Maxwell::Tessellation::OutputPrimitives::Triangles_CW); patch_control_points_minus_one.Assign(regs.patch_vertices - 1); - topology.Assign(topology_); + const bool can_collapse_topology_class = + features.has_extended_dynamic_state && features.has_extended_dynamic_state_2; + topology.Assign(can_collapse_topology_class + ? TOPOLOGY_CLASS_REPRESENTATIVE_LUT[static_cast(topology_)] + : topology_); msaa_mode.Assign(regs.anti_alias_samples_mode); + attachment0_dual_source_blend.Assign(ComputeAttachment0DualSourceBlend(regs) ? 1 : 0); raw2 = 0; @@ -187,6 +245,15 @@ void FixedPipelineState::Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFe maxwell3d.dirty.flags[Dirty::Blending] = false; for (size_t index = 0; index < attachments.size(); ++index) { attachments[index].Refresh(regs, index); + auto& attachment = attachments[index]; + if (color_write_enable_dynamic && attachment.mask_r == 0 && + attachment.mask_g == 0 && attachment.mask_b == 0 && + attachment.mask_a == 0) { + attachment.mask_r.Assign(1); + attachment.mask_g.Assign(1); + attachment.mask_b.Assign(1); + attachment.mask_a.Assign(1); + } } } } diff --git a/src/video_core/renderer_vulkan/fixed_pipeline_state.h b/src/video_core/renderer_vulkan/fixed_pipeline_state.h index 44157d686d..36a4b4b7a2 100644 --- a/src/video_core/renderer_vulkan/fixed_pipeline_state.h +++ b/src/video_core/renderer_vulkan/fixed_pipeline_state.h @@ -21,6 +21,8 @@ namespace Vulkan { using Maxwell = Tegra::Engines::Maxwell3D::Regs; struct DynamicFeatures { + u32 driver_id; + u32 driver_version; bool has_extended_dynamic_state; bool has_extended_dynamic_state_2; bool has_extended_dynamic_state_2_logic_op; @@ -31,6 +33,7 @@ struct DynamicFeatures { bool has_dynamic_state3_logic_op_enable; bool has_dynamic_state3_line_stipple_enable; bool has_dynamic_vertex_input; + bool has_color_write_enable; bool has_provoking_vertex; bool has_provoking_vertex_first_mode; bool has_provoking_vertex_last_mode; @@ -208,6 +211,9 @@ struct FixedPipelineState { BitField<12, 2, u32> tessellation_spacing; BitField<14, 1, u32> tessellation_clockwise; BitField<15, 5, u32> patch_control_points_minus_one; + BitField<20, 1, u32> color_write_enable_dynamic; + + BitField<21, 1, u32> attachment0_dual_source_blend; BitField<24, 4, Maxwell::PrimitiveTopology> topology; BitField<28, 4, Tegra::Texture::MsaaMode> msaa_mode; @@ -228,6 +234,8 @@ struct FixedPipelineState { }; std::array color_formats; + u32 driver_id; + u32 driver_version; u32 alpha_test_ref; u32 point_size; diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index 930f06daf6..6c18ade926 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp @@ -217,6 +217,16 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with SURFACE_FORMAT_ELEM(VK_FORMAT_ASTC_6x5_UNORM_BLOCK, 0, ASTC_2D_6X5_UNORM) \ SURFACE_FORMAT_ELEM(VK_FORMAT_ASTC_6x5_SRGB_BLOCK, 0, ASTC_2D_6X5_SRGB) \ SURFACE_FORMAT_ELEM(VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, 0, E5B9G9R9_FLOAT) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, 0, ETC2_RGB_UNORM) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK, 0, ETC2_RGBA_UNORM) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK, 0, ETC2_RGB_PTA_UNORM) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, 0, ETC2_RGB_SRGB) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, 0, ETC2_RGBA_SRGB) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, 0, ETC2_RGB_PTA_SRGB) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11_UNORM_BLOCK, 0, EAC_R11_UNORM) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11_SNORM_BLOCK, 0, EAC_R11_SNORM) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11G11_UNORM_BLOCK, 0, EAC_R11G11_UNORM) \ + SURFACE_FORMAT_ELEM(VK_FORMAT_EAC_R11G11_SNORM_BLOCK, 0, EAC_R11G11_SNORM) \ /* Depth formats */ \ SURFACE_FORMAT_ELEM(VK_FORMAT_D32_SFLOAT, usage_attachable, D32_FLOAT) \ SURFACE_FORMAT_ELEM(VK_FORMAT_D16_UNORM, usage_attachable, D16_UNORM) \ @@ -253,8 +263,8 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with break; } } - // Transcode on hardware that doesn't support BCn natively if (!device.IsOptimalBcnSupported() && VideoCore::Surface::IsPixelFormatBCn(pixel_format)) { + // Transcode on hardware that doesn't support BCn natively if (pixel_format == PixelFormat::BC4_SNORM) { tuple.format = VK_FORMAT_R8_SNORM; } else if (pixel_format == PixelFormat::BC4_UNORM) { @@ -270,6 +280,19 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with } else { tuple.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32; } + } else if (!device.IsOptimalEtc2Supported() && VideoCore::Surface::IsPixelFormatETC2(pixel_format)) { + // Transcode on hardware that doesn't support ETC2 natively + if (pixel_format == PixelFormat::EAC_R11_SNORM) { + tuple.format = VK_FORMAT_R8_SNORM; + } else if (pixel_format == PixelFormat::EAC_R11_UNORM) { + tuple.format = VK_FORMAT_R8_UNORM; + } else if (pixel_format == PixelFormat::EAC_R11G11_SNORM) { + tuple.format = VK_FORMAT_R8G8_SNORM; + } else if (pixel_format == PixelFormat::EAC_R11G11_UNORM) { + tuple.format = VK_FORMAT_R8G8_UNORM; + } else { + tuple.format = is_srgb ? VK_FORMAT_A8B8G8R8_SRGB_PACK32 : VK_FORMAT_A8B8G8R8_UNORM_PACK32; + } } bool const attachable = (tuple.usage & usage_attachable) != 0; bool const storage = (tuple.usage & usage_storage) != 0; diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h index 451a02c7e6..07de25f18f 100644 --- a/src/video_core/renderer_vulkan/pipeline_helper.h +++ b/src/video_core/renderer_vulkan/pipeline_helper.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include @@ -15,6 +16,7 @@ #include "shader_recompiler/shader_info.h" #include "video_core/renderer_vulkan/vk_texture_cache.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" +#include "video_core/surface.h" #include "video_core/texture_cache/types.h" #include "video_core/vulkan_common/vulkan_device.h" @@ -22,25 +24,194 @@ namespace Vulkan { using Shader::Backend::SPIRV::NUM_TEXTURE_AND_IMAGE_SCALING_WORDS; +[[nodiscard]] inline std::optional PixelFormatFromImageFormat( + Shader::ImageFormat format) { + switch (format) { + case Shader::ImageFormat::Typeless: + return std::nullopt; + case Shader::ImageFormat::R8_UINT: + return PixelFormat::R8_UINT; + case Shader::ImageFormat::R8_SINT: + return PixelFormat::R8_SINT; + case Shader::ImageFormat::R16_UINT: + return PixelFormat::R16_UINT; + case Shader::ImageFormat::R16_SINT: + return PixelFormat::R16_SINT; + case Shader::ImageFormat::R32_UINT: + return PixelFormat::R32_UINT; + case Shader::ImageFormat::R32G32_UINT: + return PixelFormat::R32G32_UINT; + case Shader::ImageFormat::R32G32B32A32_UINT: + return PixelFormat::R32G32B32A32_UINT; + } + return std::nullopt; +} + +[[nodiscard]] inline VkDeviceSize DescriptorSizeForType(const Device& device, + VkDescriptorType type) { + const auto& props = device.DescriptorBufferProperties(); + const bool robust = device.IsRobustBufferAccessEnabled(); + switch (type) { + case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: + return robust ? props.robustUniformBufferDescriptorSize : props.uniformBufferDescriptorSize; + case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: + return robust ? props.robustStorageBufferDescriptorSize : props.storageBufferDescriptorSize; + case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: + return robust ? props.robustUniformTexelBufferDescriptorSize + : props.uniformTexelBufferDescriptorSize; + case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: + return robust ? props.robustStorageTexelBufferDescriptorSize + : props.storageTexelBufferDescriptorSize; + case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: + return props.combinedImageSamplerDescriptorSize; + case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: + return props.storageImageDescriptorSize; + default: + return 0; + } +} + +struct DescriptorBufferBinding { + VkDescriptorType type; + u32 count; + VkDeviceSize offset; + VkDeviceSize stride; +}; + +struct DescriptorBufferLayout { + VkDeviceSize size{}; + boost::container::small_vector bindings; + + [[nodiscard]] bool Empty() const noexcept { + return bindings.empty(); + } +}; + +inline void WriteDescriptorBuffer(const Device& device, const DescriptorBufferLayout& layout, + const DescriptorUpdateEntry* payload, u8* host) { + const vk::Device& dev = device.GetLogical(); + for (const DescriptorBufferBinding& binding : layout.bindings) { + for (u32 index = 0; index < binding.count; ++index) { + const DescriptorUpdateEntry& entry = *(payload++); + const VkDescriptorAddressInfoEXT address_info{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_ADDRESS_INFO_EXT, + .pNext = nullptr, + .address = entry.address.address, + .range = entry.address.range, + .format = entry.address.format, + }; + VkDescriptorGetInfoEXT get_info{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_GET_INFO_EXT, + .pNext = nullptr, + .type = binding.type, + .data{}, + }; + switch (binding.type) { + case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: + get_info.data.pUniformBuffer = &address_info; + break; + case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: + get_info.data.pStorageBuffer = &address_info; + break; + case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: + get_info.data.pUniformTexelBuffer = &address_info; + break; + case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: + get_info.data.pStorageTexelBuffer = &address_info; + break; + case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: + get_info.data.pCombinedImageSampler = &entry.image; + break; + case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: + get_info.data.pStorageImage = &entry.image; + break; + default: + continue; + } + dev.GetDescriptorEXT(get_info, binding.stride, + host + binding.offset + index * binding.stride); + } + } +} + +[[nodiscard]] inline u32 NumDescriptorEntries(const Shader::Info& info) { + return Shader::NumDescriptors(info.constant_buffer_descriptors) + + Shader::NumDescriptors(info.storage_buffers_descriptors) + + Shader::NumDescriptors(info.texture_buffer_descriptors) + + Shader::NumDescriptors(info.image_buffer_descriptors) + + Shader::NumDescriptors(info.texture_descriptors) + + Shader::NumDescriptors(info.image_descriptors); +} + class DescriptorLayoutBuilder { public: DescriptorLayoutBuilder(const Device& device_) : device{&device_} {} bool CanUsePushDescriptor() const noexcept { - return device->IsKhrPushDescriptorSupported() && - num_descriptors <= device->MaxPushDescriptors(); + if (!device->IsKhrPushDescriptorSupported() || + num_descriptors > device->MaxPushDescriptors()) { + return false; + } + return !device->IsExtDescriptorBufferSupported() || + device->DescriptorBufferProperties().bufferlessPushDescriptors; } - // TODO(crueter): utilize layout binding flags - vk::DescriptorSetLayout CreateDescriptorSetLayout(bool use_push_descriptor) const { + bool CanUseDescriptorBuffer() const noexcept { + const auto& props = device->DescriptorBufferProperties(); + if (!device->IsExtDescriptorBufferSupported() || bindings.empty() || + !props.combinedImageSamplerDescriptorSingleArray) { + return false; + } + return !props.bufferlessPushDescriptors || !CanUsePushDescriptor(); + } + + DescriptorBufferLayout MakeDescriptorBufferLayout(VkDescriptorSetLayout layout) const { + DescriptorBufferLayout result; + if (!layout) { + return result; + } + const vk::Device& dev = device->GetLogical(); + result.size = dev.GetDescriptorSetLayoutSizeEXT(layout); + result.bindings.reserve(bindings.size()); + for (const VkDescriptorSetLayoutBinding& entry : bindings) { + result.bindings.push_back(DescriptorBufferBinding{ + .type = entry.descriptorType, + .count = entry.descriptorCount, + .offset = dev.GetDescriptorSetLayoutBindingOffsetEXT(layout, entry.binding), + .stride = DescriptorSizeForType(*device, entry.descriptorType), + }); + } + return result; + } + + vk::DescriptorSetLayout CreateDescriptorSetLayout(bool use_push_descriptor, + bool use_descriptor_buffer = false) const { if (bindings.empty()) { return nullptr; } - const VkDescriptorSetLayoutCreateFlags flags = - use_push_descriptor ? VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR : 0; + VkDescriptorSetLayoutCreateFlags flags = 0; + if (use_push_descriptor) { + flags |= VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR; + } + if (use_descriptor_buffer) { + flags |= VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT; + } + boost::container::small_vector binding_flags; + VkDescriptorSetLayoutBindingFlagsCreateInfo binding_flags_ci{}; + const void* pnext = nullptr; + if (!use_push_descriptor && device->IsDescriptorBindingPartiallyBoundSupported()) { + binding_flags.assign(bindings.size(), VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT); + binding_flags_ci = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO, + .pNext = nullptr, + .bindingCount = static_cast(binding_flags.size()), + .pBindingFlags = binding_flags.data(), + }; + pnext = &binding_flags_ci; + } return device->GetLogical().CreateDescriptorSetLayout({ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, - .pNext = nullptr, + .pNext = pnext, .flags = flags, .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data(), @@ -194,13 +365,14 @@ inline void PushImageDescriptors(TextureCache& texture_cache, const VideoCommon::ImageViewId image_view_id{(views++)->id}; const VideoCommon::SamplerId sampler_id{*(samplers++)}; ImageView& image_view{texture_cache.GetImageView(image_view_id)}; - const VkImageView vk_image_view{image_view.Handle(desc.type)}; - const Sampler& sampler{texture_cache.GetSampler(sampler_id)}; - const bool use_fallback_sampler{sampler.HasAddedAnisotropy() && - !image_view.SupportsAnisotropy()}; - const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() - : sampler.Handle()}; - guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler); + VkImageView vk_image_view{image_view.Handle(desc.type)}; + if (vk_image_view == VK_NULL_HANDLE) { + const VkImageView null_image_view{texture_cache.GetImageView(VideoCommon::NULL_IMAGE_VIEW_ID).Handle(desc.type)}; + if (null_image_view != VK_NULL_HANDLE) vk_image_view = null_image_view; + } + Sampler& sampler{texture_cache.GetSampler(sampler_id)}; + guest_descriptor_queue.AddSampledImage(vk_image_view, + sampler.HandleFor(image_view, desc.is_depth)); const bool element_rescaled{texture_cache.IsRescaling(image_view)}; is_rescaled |= element_rescaled; } diff --git a/src/video_core/renderer_vulkan/pipeline_statistics.cpp b/src/video_core/renderer_vulkan/pipeline_statistics.cpp index ad288436a8..04f42c71a6 100644 --- a/src/video_core/renderer_vulkan/pipeline_statistics.cpp +++ b/src/video_core/renderer_vulkan/pipeline_statistics.cpp @@ -31,14 +31,13 @@ static u64 GetUint64(const VkPipelineExecutableStatisticKHR& statistic) { } } -PipelineStatistics::PipelineStatistics(const Device& device_) : device{device_} {} +PipelineStatistics::PipelineStatistics(const Device& device_) {} -void PipelineStatistics::Collect(VkPipeline pipeline) { - const auto& dev{device.GetLogical()}; - const std::vector properties{dev.GetPipelineExecutablePropertiesKHR(pipeline)}; +void PipelineStatistics::Collect(const Device& device, VkPipeline pipeline) { + const std::vector properties{device.GetLogical().GetPipelineExecutablePropertiesKHR(pipeline)}; const u32 num_executables{static_cast(properties.size())}; for (u32 executable = 0; executable < num_executables; ++executable) { - const auto statistics{dev.GetPipelineExecutableStatisticsKHR(pipeline, executable)}; + const auto statistics{device.GetLogical().GetPipelineExecutableStatisticsKHR(pipeline, executable)}; if (statistics.empty()) { continue; } diff --git a/src/video_core/renderer_vulkan/pipeline_statistics.h b/src/video_core/renderer_vulkan/pipeline_statistics.h index 197bb09363..83e70b704f 100644 --- a/src/video_core/renderer_vulkan/pipeline_statistics.h +++ b/src/video_core/renderer_vulkan/pipeline_statistics.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -16,9 +19,7 @@ class Device; class PipelineStatistics { public: explicit PipelineStatistics(const Device& device_); - - void Collect(VkPipeline pipeline); - + void Collect(const Device& device, VkPipeline pipeline); void Report() const; private: @@ -30,8 +31,6 @@ private: u64 branches_count{}; u64 basic_block_count{}; }; - - const Device& device; mutable std::mutex mutex; std::vector collected_stats; }; diff --git a/src/video_core/renderer_vulkan/present/anti_alias_pass.h b/src/video_core/renderer_vulkan/present/anti_alias_pass.h index 4990e87502..8061d01208 100644 --- a/src/video_core/renderer_vulkan/present/anti_alias_pass.h +++ b/src/video_core/renderer_vulkan/present/anti_alias_pass.h @@ -11,12 +11,12 @@ namespace Vulkan { class Scheduler; +class Device; class AntiAliasPass { public: virtual ~AntiAliasPass() = default; - virtual void Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image, - VkImageView* inout_image_view) = 0; + virtual void Draw(const Device& device, Scheduler& scheduler, size_t image_index, VkImage* inout_image, VkImageView* inout_image_view) = 0; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/frame_gen.cpp b/src/video_core/renderer_vulkan/present/frame_gen.cpp new file mode 100644 index 0000000000..e6d857323b --- /dev/null +++ b/src/video_core/renderer_vulkan/present/frame_gen.cpp @@ -0,0 +1,362 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include + +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/settings.h" +#include "video_core/renderer_vulkan/present/frame_gen.h" +#include "video_core/renderer_vulkan/present/util.h" +#include "video_core/renderer_vulkan/vk_present_manager.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +namespace { + +constexpr size_t COLOR_CHANNELS = 4; +constexpr u64 LSFG_REQUIRED_FRAMES = 2; +constexpr u32 LSFG_RECURRENCE_FRAMES = 2; + +[[nodiscard]] f32 ManualFlowScale() { + return static_cast(Settings::values.frame_gen_flow_scale.GetValue()) / 100.0f; +} + +[[nodiscard]] f32 ConfiguredFlowScale(VkExtent2D guest_extent, VkExtent2D presented_extent) { + if (!Settings::values.frame_gen_flow_scale_auto.GetValue()) { + return ManualFlowScale(); + } + if (guest_extent.width == 0 || presented_extent.width == 0) { + return 1.0f; + } + + const f32 rendered_width = static_cast(guest_extent.width) * + Settings::values.resolution_info.up_factor; + const f32 ratio = rendered_width / static_cast(presented_extent.width); + + constexpr f32 FLOW_SCALE_STEPS = 20.0f; + const f32 stepped = std::ceil(ratio * FLOW_SCALE_STEPS) / FLOW_SCALE_STEPS; + return std::clamp(stepped, 0.25f, 1.0f); +} + +bool IsBlueFirst(VkFormat format) { + return format == VK_FORMAT_B8G8R8A8_UNORM || format == VK_FORMAT_B8G8R8A8_SRGB; +} + +VkDeviceSize BytesPerTexel(VkFormat format) { + switch (format) { + case VK_FORMAT_R8_UNORM: + return 1; + case VK_FORMAT_R16G16B16A16_SFLOAT: + return 8; + default: + return COLOR_CHANNELS; + } +} + +void WritePortablePixmap(const std::filesystem::path& path, const std::string& magic, + VkExtent2D extent, std::span pixels) { + Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write, + Common::FS::FileType::BinaryFile}; + if (!file.IsOpen()) { + return; + } + + const std::string header = magic + "\n" + std::to_string(extent.width) + " " + + std::to_string(extent.height) + "\n255\n"; + if (file.Write(header) != header.size()) { + return; + } + + void(file.Write(pixels)); + void(file.Flush()); +} + +void WriteGrayscalePgm(const std::filesystem::path& path, VkExtent2D extent, + std::span pixels) { + const size_t expected = static_cast(extent.width) * extent.height; + WritePortablePixmap(path, "P5", extent, pixels.subspan(0, std::min(expected, pixels.size()))); +} + +void WriteRaw(const std::filesystem::path& path, std::span pixels) { + Common::FS::IOFile file{path, Common::FS::FileAccessMode::Write, + Common::FS::FileType::BinaryFile}; + if (!file.IsOpen()) { + return; + } + void(file.Write(pixels)); + void(file.Flush()); +} + +void WriteColorPpm(const std::filesystem::path& path, VkExtent2D extent, + std::span pixels, bool blue_first) { + const size_t pixel_count = static_cast(extent.width) * extent.height; + if (pixels.size() < pixel_count * COLOR_CHANNELS) { + return; + } + + std::vector rgb(pixel_count * 3); + for (size_t i = 0; i < pixel_count; ++i) { + const u8 first = pixels[i * COLOR_CHANNELS]; + const u8 green = pixels[i * COLOR_CHANNELS + 1]; + const u8 third = pixels[i * COLOR_CHANNELS + 2]; + rgb[i * 3] = blue_first ? third : first; + rgb[i * 3 + 1] = green; + rgb[i * 3 + 2] = blue_first ? first : third; + } + + WritePortablePixmap(path, "P6", extent, rgb); +} + +VkImageMemoryBarrier MakeTransitionBarrier(VkImage image, VkAccessFlags src_access, + VkAccessFlags dst_access, VkImageLayout old_layout, + VkImageLayout new_layout) { + return VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = src_access, + .dstAccessMask = dst_access, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; +} + +VkImageCopy MakeCopyRegion(VkExtent2D extent) { + return VkImageCopy{ + .srcSubresource{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .srcOffset = {}, + .dstSubresource{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .dstOffset = {}, + .extent = {.width = extent.width, .height = extent.height, .depth = 1}, + }; +} + +void CopyPresentedFrame(vk::CommandBuffer cmdbuf, VkImage source, LsfgImage& destination, + VkExtent2D extent) { + const auto make_barrier = MakeTransitionBarrier; + + const std::array before{ + make_barrier(source, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT, + VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL), + make_barrier(destination.Handle(), VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + destination.Layout(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL), + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, {}, before); + + cmdbuf.CopyImage(source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, destination.Handle(), + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, MakeCopyRegion(extent)); + + const std::array after{ + make_barrier(source, VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL), + make_barrier(destination.Handle(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL), + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + 0, {}, {}, after); + + destination.SetLayout(VK_IMAGE_LAYOUT_GENERAL); +} + +} // Anonymous namespace + +FrameGen::FrameGen(MemoryAllocator& memory_allocator_, Scheduler& scheduler_) + : memory_allocator{memory_allocator_}, scheduler{scheduler_} {} + +FrameGen::~FrameGen() = default; + +void FrameGen::Process(const Device& device, Frame* frame, VkFormat format, + VkExtent2D guest_extent) { + generated = false; + + if (!shaders) { + shaders.emplace(device); + } + + if (unavailable || !Settings::values.frame_gen.GetValue()) { + if (chain) { + scheduler.Finish(); + chain.reset(); + } + warm_streak = 0; + return; + } + + if (!shaders->IsValid()) { + unavailable = true; + return; + } + + if (!frame->storage_view) { + warm_streak = 0; + return; + } + + peak_guest_extent.width = std::max(peak_guest_extent.width, guest_extent.width); + peak_guest_extent.height = std::max(peak_guest_extent.height, guest_extent.height); + + const VkExtent2D extent{.width = frame->width, .height = frame->height}; + const f32 flow_scale = ConfiguredFlowScale(peak_guest_extent, extent); + if (!chain || built_extent.width != extent.width || built_extent.height != extent.height || + built_format != format || built_flow_scale != flow_scale) { + Rebuild(device, extent, format, flow_scale); + } + + const u64 count = frame_count++; + last_count = count; + last_generations = plan.generations; + + const bool warm = plan.warm && count + 1 >= LSFG_REQUIRED_FRAMES; + warm_streak = warm ? warm_streak + 1 : 0; + generated = warm && warm_streak >= LSFG_RECURRENCE_FRAMES && plan.generations > 0; + + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([this, source = *frame->image, extent, count, + dispatch = warm](vk::CommandBuffer cmdbuf) { + CopyPresentedFrame(cmdbuf, source, chain->Input(count), extent); + if (dispatch) { + chain->DispatchShared(cmdbuf, count); + } + }); + + const bool dump_requested = generated && Settings::values.frame_gen_dump_flow.GetValue(); + if (!dump_requested) { + dumped = false; + } else if (!dumped) { + DumpDebugImages(count); + dumped = true; + } +} + +size_t FrameGen::WantedGenerations(size_t capacity) { + if (unavailable) { + plan = {}; + return 0; + } + plan = pacer.Plan(capacity); + return plan.generations; +} + +size_t FrameGen::GeneratedFrameCount() const { + return generated ? last_generations : 0; +} + +void FrameGen::GenerateInto(const Device& device, Frame* destination, size_t generation) { + chain->SetTarget(device, last_generations, generation, destination->index, + *destination->storage_view); + + const VkExtent2D extent{.width = destination->width, .height = destination->height}; + + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([this, count = last_count, generation_count = last_generations, generation, + target = destination->index, image = *destination->image, + extent](vk::CommandBuffer cmdbuf) { + chain->DispatchGeneration(cmdbuf, count, generation_count, generation, target, image, + extent); + }); +} + +void FrameGen::Rebuild(const Device& device, VkExtent2D extent, VkFormat format, f32 flow_scale) { + scheduler.Finish(); + chain.reset(); + + built_flow_scale = flow_scale; + + chain.emplace(device, memory_allocator, *shaders, extent, format, built_flow_scale); + built_extent = extent; + built_format = format; + frame_count = 0; + warm_streak = 0; + generated = false; +} + +void FrameGen::DumpDebugImages(u64 count) { + const std::filesystem::path directory = + Common::FS::GetEdenPath(Common::FS::EdenPath::LosslessDir) / "debug"; + if (!Common::FS::CreateDirs(directory)) { + return; + } + + const auto dump = [&](const std::string& name, LsfgImage& image) { + const VkExtent2D extent = image.Extent(); + const VkFormat format = image.Format(); + const VkDeviceSize texel_size = BytesPerTexel(format); + const VkDeviceSize size = + static_cast(extent.width) * extent.height * texel_size; + + vk::Buffer buffer = CreateWrappedBuffer(memory_allocator, size, MemoryUsage::Download); + + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record( + [handle = image.Handle(), dst = *buffer, extent](vk::CommandBuffer cmdbuf) { + DownloadColorImage( + cmdbuf, handle, dst, + VkExtent3D{.width = extent.width, .height = extent.height, .depth = 1}); + }); + scheduler.Finish(); + + buffer.Invalidate(); + const std::span mapped = buffer.Mapped(); + + if (format == LSFG_FLOW_FORMAT) { + WriteGrayscalePgm(directory / (name + ".pgm"), extent, mapped); + } else if (texel_size == COLOR_CHANNELS) { + WriteColorPpm(directory / (name + ".ppm"), extent, mapped, IsBlueFirst(format)); + } else { + WriteRaw(directory / (name + "_" + std::to_string(extent.width) + "x" + + std::to_string(extent.height) + ".f16"), + mapped.subspan(0, std::min(size, mapped.size()))); + } + }; + + dump("in0", chain->Input(0)); + dump("in1", chain->Input(1)); + + for (size_t level = 0; level < LSFG_MIP_LEVELS; ++level) { + dump("flow_mip" + std::to_string(level), chain->FlowLevel(level)); + } + for (size_t index = 0; index < 2; ++index) { + dump("alpha0_" + std::to_string(index), chain->AlphaOutput(0, count, index)); + dump("alpha6_" + std::to_string(index), chain->AlphaOutput(LSFG_MIP_LEVELS - 1, count, + index)); + } + for (size_t level = 0; level < LSFG_BETA_OUTPUTS; ++level) { + dump("beta_" + std::to_string(level), chain->BetaOutput(level)); + } + + dump("gamma0", chain->GammaOutput(0)); + dump("gamma6", chain->GammaOutput(LSFG_MIP_LEVELS - 1)); + dump("delta2_out1", chain->DeltaOutput1(LSFG_DELTA_INSTANCES - 1)); + dump("delta2_out2", chain->DeltaOutput2(LSFG_DELTA_INSTANCES - 1)); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/frame_gen.h b/src/video_core/renderer_vulkan/present/frame_gen.h new file mode 100644 index 0000000000..19c6d04289 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/frame_gen.h @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" +#include "video_core/renderer_vulkan/present/frame_gen_pacer.h" +#include "video_core/renderer_vulkan/present/lsfg_chain.h" +#include "video_core/renderer_vulkan/present/lsfg_shaders.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" + +namespace Vulkan { + +class Device; +class Scheduler; +struct Frame; + +class FrameGen { +public: + explicit FrameGen(MemoryAllocator& memory_allocator, Scheduler& scheduler); + ~FrameGen(); + + void Process(const Device& device, Frame* frame, VkFormat format, VkExtent2D guest_extent); + + [[nodiscard]] size_t WantedGenerations(size_t capacity); + + [[nodiscard]] size_t GeneratedFrameCount() const; + + void GenerateInto(const Device& device, Frame* destination, size_t generation); + +private: + void Rebuild(const Device& device, VkExtent2D extent, VkFormat format, f32 flow_scale); + void DumpDebugImages(u64 count); + + MemoryAllocator& memory_allocator; + Scheduler& scheduler; + + std::optional shaders; + std::optional chain; + FrameGenPacer pacer; + FrameGenPlan plan{}; + VkExtent2D peak_guest_extent{}; + VkExtent2D built_extent{}; + VkFormat built_format{VK_FORMAT_UNDEFINED}; + f32 built_flow_scale{}; + u64 frame_count{}; + u64 last_count{}; + size_t last_generations{}; + u32 warm_streak{}; + bool generated{}; + bool unavailable{}; + bool dumped{}; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/frame_gen_pacer.cpp b/src/video_core/renderer_vulkan/present/frame_gen_pacer.cpp new file mode 100644 index 0000000000..78a8c83b5a --- /dev/null +++ b/src/video_core/renderer_vulkan/present/frame_gen_pacer.cpp @@ -0,0 +1,240 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include + +#include "common/settings.h" +#include "video_core/renderer_vulkan/present/frame_gen_pacer.h" + +namespace Vulkan { + +namespace { + +using Clock = std::chrono::steady_clock; + +constexpr f32 INTERVAL_SMOOTHING = 0.25f; +constexpr f32 MINIMUM_BASE_RATE = 10.0f; +constexpr f32 BURST_CADENCE_RATIO = 3.0f; +constexpr f32 BURST_TARGET_RATIO = 2.0f; +constexpr f32 PROBE_THROUGHPUT_TOLERANCE = 0.95f; +constexpr f32 PROBE_BASE_COLLAPSE_RATIO = 0.70f; +constexpr f32 PROBE_MARGINAL_GAIN = 1.15f; +constexpr f32 TARGET_SATISFIED_RATIO = 0.95f; +constexpr f32 UNLOADED_BASE_RETENTION = 0.75f; +constexpr f32 CREDIT_EPSILON = 1.0e-4f; +constexpr u32 MAX_PROBE_FAILURES = 4; + +constexpr auto STABILIZATION_DURATION = std::chrono::seconds(1); +constexpr auto PROBE_DURATION = std::chrono::seconds(1); +constexpr auto DEFICIT_DURATION = std::chrono::seconds(1); +constexpr auto PROBE_STEP_DELAY = std::chrono::milliseconds(250); + +[[nodiscard]] Clock::duration ProbeBackoff(u32 failures) { + switch (failures) { + case 1: + return std::chrono::seconds(5); + case 2: + return std::chrono::seconds(15); + case 3: + return std::chrono::seconds(30); + default: + return std::chrono::seconds(60); + } +} + +} // Anonymous namespace + +FrameGenPlan FrameGenPacer::Plan(size_t capacity) { + const size_t ceiling = std::min(capacity, Settings::FrameGenMaxGenerations()); + if (ceiling == 0) { + Reset(); + return {}; + } + + const Clock::time_point now = Clock::now(); + const size_t previous_generations = std::exchange(issued_generations, 0); + if (!last_frame) { + last_frame = now; + return {}; + } + + const Clock::duration interval = now - *last_frame; + const f32 interval_seconds = std::chrono::duration(interval).count(); + last_frame = now; + + if (interval_seconds <= 0.0f) { + Stabilize(now); + return {}; + } + + const f32 target_rate = static_cast(Settings::values.frame_gen_target_rate.GetValue()); + + if (smoothed_interval > 0.0f) { + f32 burst_threshold = BURST_CADENCE_RATIO / smoothed_interval; + if (target_rate > 0.0f) { + burst_threshold = std::max(burst_threshold, target_rate * BURST_TARGET_RATIO); + } + if (1.0f / interval_seconds > burst_threshold) { + DeferEvaluations(interval); + output_credit = 0.0f; + return {}; + } + } + + if (interval_seconds > 1.0f / MINIMUM_BASE_RATE) { + Stabilize(now); + return {}; + } + + smoothed_interval = smoothed_interval > 0.0f + ? smoothed_interval + + (interval_seconds - smoothed_interval) * INTERVAL_SMOOTHING + : interval_seconds; + + if (previous_generations == 0) { + const f32 measured = 1.0f / smoothed_interval; + unloaded_base_rate = + unloaded_base_rate > 0.0f + ? unloaded_base_rate + (measured - unloaded_base_rate) * INTERVAL_SMOOTHING + : measured; + } + + if (stable_until) { + if (now < *stable_until) { + return {}; + } + stable_until.reset(); + } + + if (target_rate == 0.0f) { + limit = std::min(Settings::FrameGenGenerations(), ceiling); + output_credit = 0.0f; + issued_generations = limit; + return {.generations = limit, .warm = limit > 0}; + } + + UpdateLimit(now, 1.0f / smoothed_interval, target_rate, ceiling); + + const size_t allowed = std::min(limit, ceiling); + const f32 desired_outputs = smoothed_interval * target_rate; + if (allowed == 0 || desired_outputs <= 1.0f) { + output_credit = 0.0f; + return {}; + } + + output_credit += desired_outputs; + const size_t outputs = + std::max(1, static_cast(std::floor(output_credit + CREDIT_EPSILON))); + const size_t generations = std::min(outputs - 1, allowed); + + output_credit -= static_cast(generations + 1); + if (output_credit < 0.0f) { + output_credit = 0.0f; + } else if (generations == allowed && output_credit >= 1.0f) { + output_credit = std::fmod(output_credit, 1.0f); + } + + issued_generations = generations; + return {.generations = generations, .warm = true}; +} + +void FrameGenPacer::UpdateLimit(Clock::time_point now, f32 base_rate, f32 target_rate, + size_t ceiling) { + limit = std::min(limit, ceiling); + + if (probe_until) { + if (now < *probe_until) { + return; + } + probe_until.reset(); + output_credit = 0.0f; + + const f32 previous_output = + std::min(target_rate, probe_base_rate * static_cast(probe_previous_limit + 1)); + const f32 current_output = + std::min(target_rate, base_rate * static_cast(limit + 1)); + + const bool throughput_regressed = + current_output < previous_output * PROBE_THROUGHPUT_TOLERANCE; + const bool collapsed_for_marginal_gain = + base_rate < probe_base_rate * PROBE_BASE_COLLAPSE_RATIO && + current_output < previous_output * PROBE_MARGINAL_GAIN; + const bool emulation_slowed = unloaded_base_rate > 0.0f && + base_rate < unloaded_base_rate * UNLOADED_BASE_RETENTION; + + if (throughput_regressed || collapsed_for_marginal_gain || emulation_slowed) { + limit = probe_previous_limit; + probe_failures = std::min(probe_failures + 1, MAX_PROBE_FAILURES); + next_probe = now + ProbeBackoff(probe_failures); + deficit_since.reset(); + return; + } + + probe_failures = 0; + next_probe = now + PROBE_STEP_DELAY; + } + + if (base_rate * static_cast(limit + 1) >= target_rate * TARGET_SATISFIED_RATIO || + limit >= ceiling) { + deficit_since.reset(); + return; + } + + if (!deficit_since) { + deficit_since = now; + return; + } + if (now - *deficit_since < DEFICIT_DURATION) { + return; + } + if (next_probe && now < *next_probe) { + return; + } + + probe_previous_limit = limit; + probe_base_rate = base_rate; + ++limit; + probe_until = now + PROBE_DURATION; + deficit_since.reset(); + output_credit = 0.0f; +} + +void FrameGenPacer::DeferEvaluations(Clock::duration amount) { + const auto defer = [amount](std::optional& deadline) { + if (deadline) { + *deadline += amount; + } + }; + defer(stable_until); + defer(probe_until); + defer(next_probe); + deficit_since.reset(); +} + +void FrameGenPacer::Stabilize(Clock::time_point now) { + stable_until = now + STABILIZATION_DURATION; + probe_until.reset(); + deficit_since.reset(); + smoothed_interval = 0.0f; + output_credit = 0.0f; +} + +void FrameGenPacer::Reset() { + last_frame.reset(); + stable_until.reset(); + probe_until.reset(); + next_probe.reset(); + deficit_since.reset(); + smoothed_interval = 0.0f; + output_credit = 0.0f; + probe_base_rate = 0.0f; + unloaded_base_rate = 0.0f; + issued_generations = 0; + probe_previous_limit = 0; + limit = 0; + probe_failures = 0; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/frame_gen_pacer.h b/src/video_core/renderer_vulkan/present/frame_gen_pacer.h new file mode 100644 index 0000000000..7f4b23920c --- /dev/null +++ b/src/video_core/renderer_vulkan/present/frame_gen_pacer.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +#include "common/common_types.h" + +namespace Vulkan { + +struct FrameGenPlan { + size_t generations{}; + bool warm{}; +}; + +class FrameGenPacer { +public: + [[nodiscard]] FrameGenPlan Plan(size_t capacity); + + void Reset(); + +private: + using Clock = std::chrono::steady_clock; + + void Stabilize(Clock::time_point now); + void DeferEvaluations(Clock::duration amount); + void UpdateLimit(Clock::time_point now, f32 base_rate, f32 target_rate, size_t ceiling); + + std::optional last_frame; + std::optional stable_until; + std::optional probe_until; + std::optional next_probe; + std::optional deficit_since; + f32 smoothed_interval{}; + f32 output_credit{}; + f32 probe_base_rate{}; + f32 unloaded_base_rate{}; + size_t issued_generations{}; + size_t probe_previous_limit{}; + size_t limit{}; + u32 probe_failures{}; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/fsr.cpp b/src/video_core/renderer_vulkan/present/fsr.cpp index ba6252ed95..894fb144f0 100644 --- a/src/video_core/renderer_vulkan/present/fsr.cpp +++ b/src/video_core/renderer_vulkan/present/fsr.cpp @@ -25,74 +25,74 @@ using namespace FSR; using PushConstants = std::array; -FSR::FSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, - VkExtent2D extent) - : m_device{device}, m_memory_allocator{memory_allocator}, - m_image_count{image_count}, m_extent{extent} { - - CreateImages(); - CreateRenderPasses(); - CreateSampler(); - CreateShaders(); - CreateDescriptorPool(); - CreateDescriptorSetLayout(); - CreateDescriptorSets(); - CreatePipelineLayouts(); - CreatePipelines(); +FSR::FSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, VkExtent2D extent) + : m_memory_allocator{memory_allocator} + , m_image_count{image_count} + , m_extent{extent} +{ + CreateImages(device); + CreateRenderPasses(device); + CreateSampler(device); + CreateShaders(device); + CreateDescriptorPool(device); + CreateDescriptorSetLayout(device); + CreateDescriptorSets(device); + CreatePipelineLayouts(device); + CreatePipelines(device); } -void FSR::CreateImages() { +void FSR::CreateImages(const Device& device) { m_dynamic_images.resize(m_image_count); for (auto& images : m_dynamic_images) { images.images[Easu] = CreateWrappedImage(m_memory_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); images.images[Rcas] = CreateWrappedImage(m_memory_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); - images.image_views[Easu] = CreateWrappedImageView(m_device, images.images[Easu], VK_FORMAT_R16G16B16A16_SFLOAT); - images.image_views[Rcas] = CreateWrappedImageView(m_device, images.images[Rcas], VK_FORMAT_R16G16B16A16_SFLOAT); + images.image_views[Easu] = CreateWrappedImageView(device, images.images[Easu], VK_FORMAT_R16G16B16A16_SFLOAT); + images.image_views[Rcas] = CreateWrappedImageView(device, images.images[Rcas], VK_FORMAT_R16G16B16A16_SFLOAT); } } -void FSR::CreateRenderPasses() { - m_renderpass = CreateWrappedRenderPass(m_device, VK_FORMAT_R16G16B16A16_SFLOAT); +void FSR::CreateRenderPasses(const Device& device) { + m_renderpass = CreateWrappedRenderPass(device, VK_FORMAT_R16G16B16A16_SFLOAT); for (auto& images : m_dynamic_images) { - images.framebuffers[Easu] = CreateWrappedFramebuffer(m_device, m_renderpass, images.image_views[Easu], m_extent); - images.framebuffers[Rcas] = CreateWrappedFramebuffer(m_device, m_renderpass, images.image_views[Rcas], m_extent); + images.framebuffers[Easu] = CreateWrappedFramebuffer(device, m_renderpass, images.image_views[Easu], m_extent); + images.framebuffers[Rcas] = CreateWrappedFramebuffer(device, m_renderpass, images.image_views[Rcas], m_extent); } } -void FSR::CreateSampler() { - m_sampler = CreateBilinearSampler(m_device); +void FSR::CreateSampler(const Device& device) { + m_sampler = CreateBilinearSampler(device); } -void FSR::CreateShaders() { - m_vert_shader = BuildShader(m_device, VULKAN_FIDELITYFX_FSR_VERT_SPV); +void FSR::CreateShaders(const Device& device) { + m_vert_shader = BuildShader(device, VULKAN_FIDELITYFX_FSR_VERT_SPV); - if (m_device.IsFloat16Supported()) { - m_easu_shader = BuildShader(m_device, VULKAN_FIDELITYFX_FSR_EASU_FP16_FRAG_SPV); - m_rcas_shader = BuildShader(m_device, VULKAN_FIDELITYFX_FSR_RCAS_FP16_FRAG_SPV); + if (device.IsFloat16Supported()) { + m_easu_shader = BuildShader(device, VULKAN_FIDELITYFX_FSR_EASU_FP16_FRAG_SPV); + m_rcas_shader = BuildShader(device, VULKAN_FIDELITYFX_FSR_RCAS_FP16_FRAG_SPV); } else { - m_easu_shader = BuildShader(m_device, VULKAN_FIDELITYFX_FSR_EASU_FP32_FRAG_SPV); - m_rcas_shader = BuildShader(m_device, VULKAN_FIDELITYFX_FSR_RCAS_FP32_FRAG_SPV); + m_easu_shader = BuildShader(device, VULKAN_FIDELITYFX_FSR_EASU_FP32_FRAG_SPV); + m_rcas_shader = BuildShader(device, VULKAN_FIDELITYFX_FSR_RCAS_FP32_FRAG_SPV); } } -void FSR::CreateDescriptorPool() { +void FSR::CreateDescriptorPool(const Device& device) { // EASU: 1 descriptor // RCAS: 1 descriptor // 2 descriptors, 2 descriptor sets per invocation - m_descriptor_pool = CreateWrappedDescriptorPool(m_device, 2 * m_image_count, 2 * m_image_count); + m_descriptor_pool = CreateWrappedDescriptorPool(device, 2 * m_image_count, 2 * m_image_count); } -void FSR::CreateDescriptorSetLayout() { - m_descriptor_set_layout = CreateWrappedDescriptorSetLayout(m_device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); +void FSR::CreateDescriptorSetLayout(const Device& device) { + m_descriptor_set_layout = CreateWrappedDescriptorSetLayout(device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); } -void FSR::CreateDescriptorSets() { +void FSR::CreateDescriptorSets(const Device& device) { std::vector layouts(MaxFsrStage, *m_descriptor_set_layout); for (auto& images : m_dynamic_images) images.descriptor_sets = CreateWrappedDescriptorSets(m_descriptor_pool, layouts); } -void FSR::CreatePipelineLayouts() { +void FSR::CreatePipelineLayouts(const Device& device) { const VkPushConstantRange range{ .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, .offset = 0, @@ -108,17 +108,17 @@ void FSR::CreatePipelineLayouts() { .pPushConstantRanges = &range, }; - m_pipeline_layout = m_device.GetLogical().CreatePipelineLayout(ci); + m_pipeline_layout = device.GetLogical().CreatePipelineLayout(ci); } -void FSR::CreatePipelines() { - m_easu_pipeline = CreateWrappedPipeline(m_device, m_renderpass, m_pipeline_layout, +void FSR::CreatePipelines(const Device& device) { + m_easu_pipeline = CreateWrappedPipeline(device, m_renderpass, m_pipeline_layout, std::tie(m_vert_shader, m_easu_shader)); - m_rcas_pipeline = CreateWrappedPipeline(m_device, m_renderpass, m_pipeline_layout, + m_rcas_pipeline = CreateWrappedPipeline(device, m_renderpass, m_pipeline_layout, std::tie(m_vert_shader, m_rcas_shader)); } -void FSR::UpdateDescriptorSets(VkImageView image_view, size_t image_index) { +void FSR::UpdateDescriptorSets(const Device& device, VkImageView image_view, size_t image_index) { Images& images = m_dynamic_images[image_index]; std::vector image_infos; image_infos.reserve(2); @@ -126,10 +126,10 @@ void FSR::UpdateDescriptorSets(VkImageView image_view, size_t image_index) { CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, images.descriptor_sets[Easu], 0), CreateWriteDescriptorSet(image_infos, *m_sampler, *images.image_views[Easu], images.descriptor_sets[Rcas], 0) }; - m_device.GetLogical().UpdateDescriptorSets(updates, {}); + device.GetLogical().UpdateDescriptorSets(updates, {}); } -void FSR::UploadImages(Scheduler& scheduler) { +void FSR::UploadImages(const Device& device, Scheduler& scheduler) { if (!m_images_ready) { m_images_ready = true; scheduler.Record([&](vk::CommandBuffer cmdbuf) { @@ -142,7 +142,7 @@ void FSR::UploadImages(Scheduler& scheduler) { } } -VkImageView FSR::Draw(Scheduler& scheduler, size_t image_index, VkImage source_image, +VkImageView FSR::Draw(const Device& device, Scheduler& scheduler, size_t image_index, VkImage source_image, VkImageView source_image_view, VkExtent2D input_image_extent, const Common::Rectangle& crop_rect) { Images& images = m_dynamic_images[image_index]; @@ -179,8 +179,8 @@ VkImageView FSR::Draw(Scheduler& scheduler, size_t image_index, VkImage source_i static_cast(Settings::values.fsr_sharpening_slider.GetValue()) / 100.0f; FsrRcasCon(rcas_con.data(), sharpening); - UploadImages(scheduler); - UpdateDescriptorSets(source_image_view, image_index); + UploadImages(device, scheduler); + UpdateDescriptorSets(device, source_image_view, image_index); scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([=](vk::CommandBuffer cmdbuf) { diff --git a/src/video_core/renderer_vulkan/present/fsr.h b/src/video_core/renderer_vulkan/present/fsr.h index 8602e81465..7762480757 100644 --- a/src/video_core/renderer_vulkan/present/fsr.h +++ b/src/video_core/renderer_vulkan/present/fsr.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -14,27 +17,25 @@ class Scheduler; class FSR { public: - explicit FSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, - VkExtent2D extent); - VkImageView Draw(Scheduler& scheduler, size_t image_index, VkImage source_image, + explicit FSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, VkExtent2D extent); + VkImageView Draw(const Device& device, Scheduler& scheduler, size_t image_index, VkImage source_image, VkImageView source_image_view, VkExtent2D input_image_extent, const Common::Rectangle& crop_rect); private: - void CreateImages(); - void CreateRenderPasses(); - void CreateSampler(); - void CreateShaders(); - void CreateDescriptorPool(); - void CreateDescriptorSetLayout(); - void CreateDescriptorSets(); - void CreatePipelineLayouts(); - void CreatePipelines(); + void CreateImages(const Device& device); + void CreateRenderPasses(const Device& device); + void CreateSampler(const Device& device); + void CreateShaders(const Device& device); + void CreateDescriptorPool(const Device& device); + void CreateDescriptorSetLayout(const Device& device); + void CreateDescriptorSets(const Device& device); + void CreatePipelineLayouts(const Device& device); + void CreatePipelines(const Device& device); - void UploadImages(Scheduler& scheduler); - void UpdateDescriptorSets(VkImageView image_view, size_t image_index); + void UploadImages(const Device& device, Scheduler& scheduler); + void UpdateDescriptorSets(const Device& device, VkImageView image_view, size_t image_index); - const Device& m_device; MemoryAllocator& m_memory_allocator; const size_t m_image_count; const VkExtent2D m_extent; diff --git a/src/video_core/renderer_vulkan/present/fxaa.cpp b/src/video_core/renderer_vulkan/present/fxaa.cpp index bdafd1f4d0..62f79d490c 100644 --- a/src/video_core/renderer_vulkan/present/fxaa.cpp +++ b/src/video_core/renderer_vulkan/present/fxaa.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -14,61 +17,60 @@ namespace Vulkan { FXAA::FXAA(const Device& device, MemoryAllocator& allocator, size_t image_count, VkExtent2D extent) - : m_device(device), m_allocator(allocator), m_extent(extent), - m_image_count(static_cast(image_count)) { - CreateImages(); - CreateRenderPasses(); - CreateSampler(); - CreateShaders(); - CreateDescriptorPool(); - CreateDescriptorSetLayouts(); - CreateDescriptorSets(); - CreatePipelineLayouts(); - CreatePipelines(); + : m_extent(extent) + , m_image_count(u32(image_count)) +{ + CreateImages(device, allocator); + CreateRenderPasses(device); + CreateSampler(device); + CreateShaders(device); + CreateDescriptorPool(device); + CreateDescriptorSetLayouts(device); + CreateDescriptorSets(device); + CreatePipelineLayouts(device); + CreatePipelines(device); } FXAA::~FXAA() = default; -void FXAA::CreateImages() { +void FXAA::CreateImages(const Device& device, MemoryAllocator& allocator) { for (u32 i = 0; i < m_image_count; i++) { Image& image = m_dynamic_images.emplace_back(); - - image.image = CreateWrappedImage(m_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); - image.image_view = - CreateWrappedImageView(m_device, image.image, VK_FORMAT_R16G16B16A16_SFLOAT); + image.image = CreateWrappedImage(allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); + image.image_view = CreateWrappedImageView(device, image.image, VK_FORMAT_R16G16B16A16_SFLOAT); } } -void FXAA::CreateRenderPasses() { - m_renderpass = CreateWrappedRenderPass(m_device, VK_FORMAT_R16G16B16A16_SFLOAT); +void FXAA::CreateRenderPasses(const Device& device) { + m_renderpass = CreateWrappedRenderPass(device, VK_FORMAT_R16G16B16A16_SFLOAT); for (auto& image : m_dynamic_images) { image.framebuffer = - CreateWrappedFramebuffer(m_device, m_renderpass, image.image_view, m_extent); + CreateWrappedFramebuffer(device, m_renderpass, image.image_view, m_extent); } } -void FXAA::CreateSampler() { - m_sampler = CreateWrappedSampler(m_device); +void FXAA::CreateSampler(const Device& device) { + m_sampler = CreateWrappedSampler(device); } -void FXAA::CreateShaders() { - m_vertex_shader = CreateWrappedShaderModule(m_device, FXAA_VERT_SPV); - m_fragment_shader = CreateWrappedShaderModule(m_device, FXAA_FRAG_SPV); +void FXAA::CreateShaders(const Device& device) { + m_vertex_shader = CreateWrappedShaderModule(device, FXAA_VERT_SPV); + m_fragment_shader = CreateWrappedShaderModule(device, FXAA_FRAG_SPV); } -void FXAA::CreateDescriptorPool() { +void FXAA::CreateDescriptorPool(const Device& device) { // 2 descriptors, 1 descriptor set per image - m_descriptor_pool = CreateWrappedDescriptorPool(m_device, 2 * m_image_count, m_image_count); + m_descriptor_pool = CreateWrappedDescriptorPool(device, 2 * m_image_count, m_image_count); } -void FXAA::CreateDescriptorSetLayouts() { +void FXAA::CreateDescriptorSetLayouts(const Device& device) { m_descriptor_set_layout = - CreateWrappedDescriptorSetLayout(m_device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + CreateWrappedDescriptorSetLayout(device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); } -void FXAA::CreateDescriptorSets() { +void FXAA::CreateDescriptorSets(const Device& device) { VkDescriptorSetLayout layout = *m_descriptor_set_layout; for (auto& images : m_dynamic_images) { @@ -76,30 +78,28 @@ void FXAA::CreateDescriptorSets() { } } -void FXAA::CreatePipelineLayouts() { - m_pipeline_layout = CreateWrappedPipelineLayout(m_device, m_descriptor_set_layout); +void FXAA::CreatePipelineLayouts(const Device& device) { + m_pipeline_layout = CreateWrappedPipelineLayout(device, m_descriptor_set_layout); } -void FXAA::CreatePipelines() { - m_pipeline = CreateWrappedPipeline(m_device, m_renderpass, m_pipeline_layout, +void FXAA::CreatePipelines(const Device& device) { + m_pipeline = CreateWrappedPipeline(device, m_renderpass, m_pipeline_layout, std::tie(m_vertex_shader, m_fragment_shader)); } -void FXAA::UpdateDescriptorSets(VkImageView image_view, size_t image_index) { +void FXAA::UpdateDescriptorSets(const Device& device, VkImageView image_view, size_t image_index) { Image& image = m_dynamic_images[image_index]; std::vector image_infos; std::vector updates; image_infos.reserve(2); - updates.push_back( - CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, image.descriptor_sets[0], 0)); - updates.push_back( - CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, image.descriptor_sets[0], 1)); + updates.push_back(CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, image.descriptor_sets[0], 0)); + updates.push_back(CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, image.descriptor_sets[0], 1)); - m_device.GetLogical().UpdateDescriptorSets(updates, {}); + device.GetLogical().UpdateDescriptorSets(updates, {}); } -void FXAA::UploadImages(Scheduler& scheduler) { +void FXAA::UploadImages(const Device& device, Scheduler& scheduler) { if (m_images_ready) { return; } @@ -114,8 +114,7 @@ void FXAA::UploadImages(Scheduler& scheduler) { m_images_ready = true; } -void FXAA::Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image, - VkImageView* inout_image_view) { +void FXAA::Draw(const Device& device, Scheduler& scheduler, size_t image_index, VkImage* inout_image, VkImageView* inout_image_view) { const Image& image{m_dynamic_images[image_index]}; const VkImage input_image{*inout_image}; const VkImage output_image{*image.image}; @@ -126,8 +125,8 @@ void FXAA::Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image, const VkPipelineLayout layout{*m_pipeline_layout}; const VkExtent2D extent{m_extent}; - UploadImages(scheduler); - UpdateDescriptorSets(*inout_image_view, image_index); + UploadImages(device, scheduler); + UpdateDescriptorSets(device, *inout_image_view, image_index); scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([=](vk::CommandBuffer cmdbuf) { diff --git a/src/video_core/renderer_vulkan/present/fxaa.h b/src/video_core/renderer_vulkan/present/fxaa.h index 97a2e5c1cb..7eaf31f81c 100644 --- a/src/video_core/renderer_vulkan/present/fxaa.h +++ b/src/video_core/renderer_vulkan/present/fxaa.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -15,38 +18,23 @@ class StagingBufferPool; class FXAA final : public AntiAliasPass { public: - explicit FXAA(const Device& device, MemoryAllocator& allocator, size_t image_count, - VkExtent2D extent); + explicit FXAA(const Device& device, MemoryAllocator& allocator, size_t image_count, VkExtent2D extent); ~FXAA() override; - void Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image, - VkImageView* inout_image_view) override; + void Draw(const Device& device, Scheduler& scheduler, size_t image_index, VkImage* inout_image, VkImageView* inout_image_view) override; private: - void CreateImages(); - void CreateRenderPasses(); - void CreateSampler(); - void CreateShaders(); - void CreateDescriptorPool(); - void CreateDescriptorSetLayouts(); - void CreateDescriptorSets(); - void CreatePipelineLayouts(); - void CreatePipelines(); - void UpdateDescriptorSets(VkImageView image_view, size_t image_index); - void UploadImages(Scheduler& scheduler); - - const Device& m_device; - MemoryAllocator& m_allocator; - const VkExtent2D m_extent; - const u32 m_image_count; - - vk::ShaderModule m_vertex_shader{}; - vk::ShaderModule m_fragment_shader{}; - vk::DescriptorPool m_descriptor_pool{}; - vk::DescriptorSetLayout m_descriptor_set_layout{}; - vk::PipelineLayout m_pipeline_layout{}; - vk::Pipeline m_pipeline{}; - vk::RenderPass m_renderpass{}; + void CreateImages(const Device& device, MemoryAllocator& allocator); + void CreateRenderPasses(const Device& device); + void CreateSampler(const Device& device); + void CreateShaders(const Device& device); + void CreateDescriptorPool(const Device& device); + void CreateDescriptorSetLayouts(const Device& device); + void CreateDescriptorSets(const Device& device); + void CreatePipelineLayouts(const Device& device); + void CreatePipelines(const Device& device); + void UpdateDescriptorSets(const Device& device, VkImageView image_view, size_t image_index); + void UploadImages(const Device& device, Scheduler& scheduler); struct Image { vk::DescriptorSets descriptor_sets{}; @@ -55,9 +43,17 @@ private: vk::ImageView image_view{}; }; std::vector m_dynamic_images{}; - bool m_images_ready{}; - + const VkExtent2D m_extent; + const u32 m_image_count; + vk::ShaderModule m_vertex_shader{}; + vk::ShaderModule m_fragment_shader{}; + vk::DescriptorPool m_descriptor_pool{}; + vk::DescriptorSetLayout m_descriptor_set_layout{}; + vk::PipelineLayout m_pipeline_layout{}; + vk::Pipeline m_pipeline{}; + vk::RenderPass m_renderpass{}; vk::Sampler m_sampler{}; + bool m_images_ready{}; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/layer.cpp b/src/video_core/renderer_vulkan/present/layer.cpp index b462c672cc..9f4aaf390f 100644 --- a/src/video_core/renderer_vulkan/present/layer.cpp +++ b/src/video_core/renderer_vulkan/present/layer.cpp @@ -15,8 +15,13 @@ #include "common/settings.h" #include "video_core/framebuffer_config.h" #include "video_core/renderer_vulkan/present/fsr.h" +#include "video_core/renderer_vulkan/present/sgsr.h" #include "video_core/renderer_vulkan/present/fxaa.h" #include "video_core/renderer_vulkan/present/layer.h" +#ifdef HAS_RESHADE +#include "video_core/post_processing/fx_chain.h" +#include "video_core/renderer_vulkan/present/post_process.h" +#endif #include "video_core/renderer_vulkan/present/present_push_constants.h" #include "video_core/renderer_vulkan/present/smaa.h" #include "video_core/renderer_vulkan/present/util.h" @@ -55,15 +60,21 @@ VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) { } // Anonymous namespace -Layer::Layer(const Device& device_, MemoryAllocator& memory_allocator_, Scheduler& scheduler_, - Tegra::MaxwellDeviceMemoryManager& device_memory_, size_t image_count_, - VkExtent2D output_size, VkDescriptorSetLayout layout, const PresentFilters& filters_) - : device(device_), memory_allocator(memory_allocator_), scheduler(scheduler_), - device_memory(device_memory_), filters(filters_), image_count(image_count_) { - CreateDescriptorPool(); - CreateDescriptorSets(layout); +Layer::Layer(const Device& device, MemoryAllocator& memory_allocator_, Scheduler& scheduler_, Tegra::MaxwellDeviceMemoryManager& device_memory_, size_t image_count_, VkExtent2D output_size, VkDescriptorSetLayout layout, const PresentFilters& filters_) + : memory_allocator(memory_allocator_) + , scheduler(scheduler_) + , device_memory(device_memory_) + , filters(filters_) + , image_count(image_count_) +{ + CreateDescriptorPool(device); + CreateDescriptorSets(device, layout); if (filters.get_scaling_filter() == Settings::ScalingFilter::Fsr) { - fsr.emplace(device, memory_allocator, image_count, output_size); + sr_filter.emplace(device, memory_allocator, image_count, output_size); + } else if (filters.get_scaling_filter() == Settings::ScalingFilter::Sgsr) { + sr_filter.emplace(device, memory_allocator, image_count, output_size, false); + } else if (filters.get_scaling_filter() == Settings::ScalingFilter::SgsrEdge) { + sr_filter.emplace(device, memory_allocator, image_count, output_size, true); } } @@ -71,7 +82,7 @@ Layer::~Layer() { ReleaseRawImages(); } -void Layer::ConfigureDraw(PresentPushConstants* out_push_constants, +void Layer::ConfigureDraw(const Device& device, PresentPushConstants* out_push_constants, VkDescriptorSet* out_descriptor_set, RasterizerVulkan& rasterizer, VkSampler sampler, size_t image_index, const Tegra::FramebufferConfig& framebuffer, @@ -83,9 +94,15 @@ void Layer::ConfigureDraw(PresentPushConstants* out_push_constants, const u32 scaled_width = texture_info ? texture_info->scaled_width : texture_width; const u32 scaled_height = texture_info ? texture_info->scaled_height : texture_height; const bool use_accelerated = texture_info.has_value(); + const bool is_applet = + (framebuffer.layer_stack_mask & Service::Nvnflinger::LayerStackBit( + Service::Nvnflinger::LayerStackId::Recording)) == 0; - RefreshResources(framebuffer); - SetAntiAliasPass(); + RefreshResources(device, framebuffer); + SetAntiAliasPass(device); +#ifdef HAS_RESHADE + SetPostProcessPass(device, is_applet); +#endif // Finish any pending renderpass scheduler.RequestOutsideRenderPassOperationContext(); @@ -103,39 +120,51 @@ void Layer::ConfigureDraw(PresentPushConstants* out_push_constants, texture_info ? texture_info->image_view : *raw_image_views[image_index]; if (auto* fxaa = std::get_if(&anti_alias)) { - fxaa->Draw(scheduler, image_index, &source_image, &source_image_view); + fxaa->Draw(device, scheduler, image_index, &source_image, &source_image_view); } else if (auto* smaa = std::get_if(&anti_alias)) { - smaa->Draw(scheduler, image_index, &source_image, &source_image_view); + smaa->Draw(device, scheduler, image_index, &source_image, &source_image_view); } +#ifdef HAS_RESHADE + if (post_process.has_value()) { + post_process->Draw(device, scheduler, image_index, &source_image, &source_image_view); + } +#endif + auto crop_rect = Tegra::NormalizeCrop(framebuffer, texture_width, texture_height); const VkExtent2D render_extent{ .width = scaled_width, .height = scaled_height, }; - if (fsr) { - source_image_view = fsr->Draw(scheduler, image_index, source_image, source_image_view, render_extent, crop_rect); + if (auto* fsr = std::get_if(&sr_filter)) { + source_image_view = fsr->Draw(device, scheduler, image_index, source_image, source_image_view, render_extent, crop_rect); crop_rect = {0, 0, 1, 1}; + } else if (auto* sgsr = std::get_if(&sr_filter)) { + if (!is_applet) { + source_image_view = sgsr->Draw(device, scheduler, image_index, source_image, + source_image_view, render_extent, crop_rect); + crop_rect = {0, 0, 1, 1}; + } } - SetMatrixData(*out_push_constants, layout); - SetVertexData(*out_push_constants, layout, crop_rect); + SetMatrixData(device, *out_push_constants, layout); + SetVertexData(device, *out_push_constants, layout, crop_rect); - UpdateDescriptorSet(source_image_view, sampler, image_index); + UpdateDescriptorSet(device, source_image_view, sampler, image_index); *out_descriptor_set = descriptor_sets[image_index]; } -void Layer::CreateDescriptorPool() { +void Layer::CreateDescriptorPool(const Device& device) { descriptor_pool = CreateWrappedDescriptorPool(device, image_count, image_count); } -void Layer::CreateDescriptorSets(VkDescriptorSetLayout layout) { +void Layer::CreateDescriptorSets(const Device& device, VkDescriptorSetLayout layout) { const std::vector layouts(image_count, layout); descriptor_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts); } -void Layer::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer) { +void Layer::CreateStagingBuffer(const Device& device, const Tegra::FramebufferConfig& framebuffer) { const VkBufferCreateInfo ci{ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .pNext = nullptr, @@ -151,7 +180,7 @@ void Layer::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer) { buffer = memory_allocator.CreateBuffer(ci, MemoryUsage::Upload); } -void Layer::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) { +void Layer::CreateRawImages(const Device& device, const Tegra::FramebufferConfig& framebuffer) { const auto format = GetFormat(framebuffer); resource_ticks.resize(image_count); raw_images.resize(image_count); @@ -164,7 +193,7 @@ void Layer::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) { } } -void Layer::RefreshResources(const Tegra::FramebufferConfig& framebuffer) { +void Layer::RefreshResources(const Device& device, const Tegra::FramebufferConfig& framebuffer) { if (framebuffer.width == raw_width && framebuffer.height == raw_height && framebuffer.pixel_format == pixel_format && !raw_images.empty()) { return; @@ -176,11 +205,11 @@ void Layer::RefreshResources(const Tegra::FramebufferConfig& framebuffer) { anti_alias.emplace(); ReleaseRawImages(); - CreateStagingBuffer(framebuffer); - CreateRawImages(framebuffer); + CreateStagingBuffer(device, framebuffer); + CreateRawImages(device, framebuffer); } -void Layer::SetAntiAliasPass() { +void Layer::SetAntiAliasPass(const Device& device) { if (!std::holds_alternative(anti_alias) && anti_alias_setting == filters.get_anti_aliasing()) return; @@ -204,6 +233,47 @@ void Layer::SetAntiAliasPass() { } } +#ifdef HAS_RESHADE +void Layer::SetPostProcessPass(const Device& device, bool is_applet) { + if (is_applet) { + post_process.reset(); + return; + } + const VkExtent2D render_area{ + .width = Settings::values.resolution_info.ScaleUp(raw_width), + .height = Settings::values.resolution_info.ScaleUp(raw_height), + }; + + const u64 generation = VideoCore::FxChain::Instance().Snapshot().generation; + const bool enabled = Settings::values.post_shader_enabled.GetValue(); + + if (post_process_generation == generation && post_process_enabled == enabled && + post_process_extent.width == render_area.width && + post_process_extent.height == render_area.height) { + return; + } + + for (const u64 tick : resource_ticks) { + scheduler.Wait(tick); + } + + post_process_generation = generation; + post_process_enabled = enabled; + post_process_extent = render_area; + post_process.reset(); + + if (!enabled || VideoCore::FxChain::Instance().Size() == 0) { + return; + } + + post_process.emplace(device, memory_allocator, scheduler, image_count, render_area); + + if (post_process->Empty()) { + post_process.reset(); + } +} +#endif + void Layer::ReleaseRawImages() { for (const u64 tick : resource_ticks) { scheduler.Wait(tick); @@ -221,20 +291,17 @@ u64 Layer::GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer, return GetSizeInBytes(framebuffer) * image_index; } -void Layer::SetMatrixData(PresentPushConstants& data, - const Layout::FramebufferLayout& layout) const { - data.modelview_matrix = - MakeOrthographicMatrix(static_cast(layout.width), static_cast(layout.height)); +void Layer::SetMatrixData(const Device& device, PresentPushConstants& data, const Layout::FramebufferLayout& layout) const { + data.modelview_matrix = MakeOrthographicMatrix(f32(layout.width), static_cast(layout.height)); } -void Layer::SetVertexData(PresentPushConstants& data, const Layout::FramebufferLayout& layout, - const Common::Rectangle& crop) const { +void Layer::SetVertexData(const Device& device, PresentPushConstants& data, const Layout::FramebufferLayout& layout, const Common::Rectangle& crop) const { // Map the coordinates to the screen. const auto& screen = layout.screen; - const auto x = static_cast(screen.left); - const auto y = static_cast(screen.top); - const auto w = static_cast(screen.GetWidth()); - const auto h = static_cast(screen.GetHeight()); + const auto x = f32(screen.left); + const auto y = f32(screen.top); + const auto w = f32(screen.GetWidth()); + const auto h = f32(screen.GetHeight()); data.vertices[0] = ScreenRectVertex(x, y, crop.left, crop.top); data.vertices[1] = ScreenRectVertex(x + w, y, crop.right, crop.top); @@ -242,7 +309,7 @@ void Layer::SetVertexData(PresentPushConstants& data, const Layout::FramebufferL data.vertices[3] = ScreenRectVertex(x + w, y + h, crop.right, crop.bottom); } -void Layer::UpdateDescriptorSet(VkImageView image_view, VkSampler sampler, size_t image_index) { +void Layer::UpdateDescriptorSet(const Device& device, VkImageView image_view, VkSampler sampler, size_t image_index) { const VkDescriptorImageInfo image_info{ .sampler = sampler, .imageView = image_view, diff --git a/src/video_core/renderer_vulkan/present/layer.h b/src/video_core/renderer_vulkan/present/layer.h index d38b81823e..b965ef7c49 100644 --- a/src/video_core/renderer_vulkan/present/layer.h +++ b/src/video_core/renderer_vulkan/present/layer.h @@ -13,7 +13,11 @@ #include "video_core/host1x/gpu_device_memory_manager.h" #include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/renderer_vulkan/present/fsr.h" +#include "video_core/renderer_vulkan/present/sgsr.h" #include "video_core/renderer_vulkan/present/fxaa.h" +#ifdef HAS_RESHADE +#include "video_core/renderer_vulkan/present/post_process.h" +#endif #include "video_core/renderer_vulkan/present/smaa.h" namespace Layout { @@ -51,33 +55,34 @@ public: const PresentFilters& filters); ~Layer(); - void ConfigureDraw(PresentPushConstants* out_push_constants, + void ConfigureDraw(const Device& device, PresentPushConstants* out_push_constants, VkDescriptorSet* out_descriptor_set, RasterizerVulkan& rasterizer, VkSampler sampler, size_t image_index, const Tegra::FramebufferConfig& framebuffer, const Layout::FramebufferLayout& layout); private: - void CreateDescriptorPool(); - void CreateDescriptorSets(VkDescriptorSetLayout layout); - void CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer); - void CreateRawImages(const Tegra::FramebufferConfig& framebuffer); + void CreateDescriptorPool(const Device& device); + void CreateDescriptorSets(const Device& device, VkDescriptorSetLayout layout); + void CreateStagingBuffer(const Device& device, const Tegra::FramebufferConfig& framebuffer); + void CreateRawImages(const Device& device, const Tegra::FramebufferConfig& framebuffer); - void RefreshResources(const Tegra::FramebufferConfig& framebuffer); - void SetAntiAliasPass(); + void RefreshResources(const Device& device, const Tegra::FramebufferConfig& framebuffer); + void SetAntiAliasPass(const Device& device); +#ifdef HAS_RESHADE + void SetPostProcessPass(const Device& device, bool is_applet); +#endif void ReleaseRawImages(); u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const; u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer, size_t image_index) const; - void SetMatrixData(PresentPushConstants& data, const Layout::FramebufferLayout& layout) const; - void SetVertexData(PresentPushConstants& data, const Layout::FramebufferLayout& layout, - const Common::Rectangle& crop) const; - void UpdateDescriptorSet(VkImageView image_view, VkSampler sampler, size_t image_index); + void SetMatrixData(const Device& device, PresentPushConstants& data, const Layout::FramebufferLayout& layout) const; + void SetVertexData(const Device& device, PresentPushConstants& data, const Layout::FramebufferLayout& layout, const Common::Rectangle& crop) const; + void UpdateDescriptorSet(const Device& device, VkImageView image_view, VkSampler sampler, size_t image_index); void UpdateRawImage(const Tegra::FramebufferConfig& framebuffer, size_t image_index); private: - const Device& device; MemoryAllocator& memory_allocator; Scheduler& scheduler; Tegra::MaxwellDeviceMemoryManager& device_memory; @@ -95,7 +100,13 @@ private: Settings::AntiAliasing anti_alias_setting{}; std::variant anti_alias{}; - std::optional fsr{}; + std::variant sr_filter{}; +#ifdef HAS_RESHADE + std::optional post_process{}; + u64 post_process_generation{}; + bool post_process_enabled{}; + VkExtent2D post_process_extent{}; +#endif std::vector resource_ticks{}; }; diff --git a/src/video_core/renderer_vulkan/present/lsfg_alpha.cpp b/src/video_core/renderer_vulkan/present/lsfg_alpha.cpp new file mode 100644 index 0000000000..a735dd11f4 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_alpha.cpp @@ -0,0 +1,140 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "video_core/frame_gen/lossless_dll.h" +#include "video_core/renderer_vulkan/present/lsfg_alpha.h" +#include "video_core/renderer_vulkan/present/lsfg_shaders.h" +#include "video_core/renderer_vulkan/present/util.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +namespace { + +constexpr u32 DISPATCH_TILE_SHIFT = 3; + +[[nodiscard]] u32 GroupCount(u32 size) { + return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT; +} + +[[nodiscard]] VkExtent2D HalveExtent(VkExtent2D extent) { + return VkExtent2D{ + .width = (extent.width + 1) >> 1, + .height = (extent.height + 1) >> 1, + }; +} + +} // Anonymous namespace + +LsfgAlphaPasses::LsfgAlphaPasses(const Device& device, const LsfgShaders& shaders) { + using namespace VideoCore::FrameGen::PerformanceShader; + + passes[0] = LsfgPass(device, shaders, ALPHA[0], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[1] = LsfgPass(device, shaders, ALPHA[1], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[2] = LsfgPass(device, shaders, ALPHA[2], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[3] = LsfgPass(device, shaders, ALPHA[3], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); +} + +LsfgAlpha::LsfgAlpha(const Device& device, MemoryAllocator& memory_allocator, + const LsfgAlphaPasses& passes_, LsfgResources& resources, + vk::DescriptorPool& descriptor_pool, LsfgImage& input_) + : passes{&passes_}, input{&input_} { + const VkExtent2D half_extent = HalveExtent(input->Extent()); + const VkExtent2D quarter_extent = HalveExtent(half_extent); + + temp1 = LsfgImage(device, memory_allocator, half_extent); + temp2 = LsfgImage(device, memory_allocator, half_extent); + for (size_t i = 0; i < temp3.size(); ++i) { + temp3[i] = LsfgImage(device, memory_allocator, quarter_extent); + for (size_t j = 0; j < LSFG_HISTORY_SLOTS; ++j) { + out_images[j][i] = LsfgImage(device, memory_allocator, quarter_extent); + } + } + + std::vector layouts; + for (size_t i = 0; i < LSFG_ALPHA_STAGES - 1; ++i) { + layouts.push_back(passes->Get(i).SetLayout()); + } + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + layouts.push_back(passes->Get(3).SetLayout()); + } + owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts); + + for (size_t i = 0; i < LSFG_ALPHA_STAGES - 1; ++i) { + descriptor_sets[i] = owned_sets[i]; + } + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + last_descriptor_sets[i] = owned_sets[LSFG_ALPHA_STAGES - 1 + i]; + } + + const VkSampler sampler = resources.GetSampler(); + + LsfgDescriptorWriter(descriptor_sets[0]) + .AddSampler(sampler) + .AddSampledImage(*input) + .AddStorageImage(temp1) + .Build(device); + LsfgDescriptorWriter(descriptor_sets[1]) + .AddSampler(sampler) + .AddSampledImage(temp1) + .AddStorageImage(temp2) + .Build(device); + LsfgDescriptorWriter(descriptor_sets[2]) + .AddSampler(sampler) + .AddSampledImage(temp2) + .AddStorageImages(temp3) + .Build(device); + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + LsfgDescriptorWriter(last_descriptor_sets[i]) + .AddSampler(sampler) + .AddSampledImages(temp3) + .AddStorageImages(out_images[i]) + .Build(device); + } +} + +void LsfgAlpha::PushBarriers(LsfgBarriers& barriers, u64 frame_count, size_t stage) { + switch (stage) { + case 0: + barriers.WriteToRead(*input).ReadToWrite(temp1); + break; + case 1: + barriers.WriteToRead(temp1).ReadToWrite(temp2); + break; + case 2: + barriers.WriteToRead(temp2).ReadToWriteAll(temp3); + break; + default: + barriers.WriteToReadAll(temp3).ReadToWriteAll(out_images[frame_count % LSFG_HISTORY_SLOTS]); + break; + } +} + +void LsfgAlpha::DispatchStage(vk::CommandBuffer cmdbuf, u64 frame_count, size_t stage) { + const VkExtent2D extent = stage < 2 ? temp1.Extent() : temp3[0].Extent(); + const VkDescriptorSet set = stage < LSFG_ALPHA_STAGES - 1 + ? descriptor_sets[stage] + : last_descriptor_sets[frame_count % LSFG_HISTORY_SLOTS]; + + passes->Get(stage).BindSet(cmdbuf, set); + cmdbuf.Dispatch(GroupCount(extent.width), GroupCount(extent.height), 1); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/lsfg_alpha.h b/src/video_core/renderer_vulkan/present/lsfg_alpha.h new file mode 100644 index 0000000000..1dc8864431 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_alpha.h @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" +#include "video_core/renderer_vulkan/present/lsfg_common.h" + +namespace Vulkan { + +class Device; +class LsfgShaders; + +constexpr size_t LSFG_ALPHA_STAGES = 4; + +class LsfgAlphaPasses { +public: + LsfgAlphaPasses() = default; + LsfgAlphaPasses(const Device& device, const LsfgShaders& shaders); + + [[nodiscard]] const LsfgPass& Get(size_t stage) const { + return passes[stage]; + } + +private: + std::array passes; +}; + +class LsfgAlpha { +public: + LsfgAlpha() = default; + LsfgAlpha(const Device& device, MemoryAllocator& memory_allocator, + const LsfgAlphaPasses& passes_, LsfgResources& resources, + vk::DescriptorPool& descriptor_pool, LsfgImage& input); + + void PushBarriers(LsfgBarriers& barriers, u64 frame_count, size_t stage); + void DispatchStage(vk::CommandBuffer cmdbuf, u64 frame_count, size_t stage); + + [[nodiscard]] LsfgImageHistory& Outputs() { + return out_images; + } + +private: + const LsfgAlphaPasses* passes{}; + LsfgImage* input{}; + + std::array descriptor_sets{}; + std::array last_descriptor_sets{}; + vk::DescriptorSets owned_sets; + + LsfgImage temp1; + LsfgImage temp2; + LsfgImagePair temp3; + LsfgImageHistory out_images; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/lsfg_beta.cpp b/src/video_core/renderer_vulkan/present/lsfg_beta.cpp new file mode 100644 index 0000000000..6dddc7c21d --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_beta.cpp @@ -0,0 +1,147 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "video_core/frame_gen/lossless_dll.h" +#include "video_core/renderer_vulkan/present/lsfg_beta.h" +#include "video_core/renderer_vulkan/present/lsfg_shaders.h" +#include "video_core/renderer_vulkan/present/util.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +namespace { + +constexpr u32 DISPATCH_TILE_SHIFT = 3; +constexpr u32 OUTPUT_TILE_SHIFT = 5; + +[[nodiscard]] u32 GroupCount(u32 size, u32 shift) { + return (size + (1u << shift) - 1) >> shift; +} + +} // Anonymous namespace + +LsfgBeta::LsfgBeta(const Device& device, MemoryAllocator& memory_allocator, + const LsfgShaders& shaders, LsfgResources& resources, + vk::DescriptorPool& descriptor_pool, LsfgImageHistory& inputs_) + : inputs{&inputs_} { + using namespace VideoCore::FrameGen::PerformanceShader; + + passes[0] = LsfgPass(device, shaders, BETA[0], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + for (size_t i = 1; i < LSFG_BETA_STAGES - 1; ++i) { + passes[i] = LsfgPass(device, shaders, BETA[i], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + } + passes[4] = LsfgPass(device, shaders, BETA[4], + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + + const VkExtent2D extent = (*inputs)[0][0].Extent(); + for (size_t i = 0; i < temp1.size(); ++i) { + temp1[i] = LsfgImage(device, memory_allocator, extent); + temp2[i] = LsfgImage(device, memory_allocator, extent); + } + for (size_t i = 0; i < LSFG_BETA_OUTPUTS; ++i) { + const VkExtent2D level_extent{ + .width = extent.width >> i, + .height = extent.height >> i, + }; + out_images[i] = LsfgImage(device, memory_allocator, level_extent, LSFG_FLOW_FORMAT); + } + + std::vector layouts; + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + layouts.push_back(passes[0].SetLayout()); + } + for (size_t i = 1; i < LSFG_BETA_STAGES; ++i) { + layouts.push_back(passes[i].SetLayout()); + } + owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts); + + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + first_descriptor_sets[i] = owned_sets[i]; + } + for (size_t i = 0; i < LSFG_BETA_STAGES - 1; ++i) { + descriptor_sets[i] = owned_sets[LSFG_HISTORY_SLOTS + i]; + } + + const VkSampler sampler = resources.GetSampler(); + const VkSampler border_sampler = resources.GetSampler( + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + LsfgDescriptorWriter(first_descriptor_sets[i]) + .AddSampler(border_sampler) + .AddSampledImages((*inputs)[(i + 1) % LSFG_HISTORY_SLOTS]) + .AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS]) + .AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS]) + .AddStorageImages(temp1) + .Build(device); + } + LsfgDescriptorWriter(descriptor_sets[0]) + .AddSampler(sampler) + .AddSampledImages(temp1) + .AddStorageImages(temp2) + .Build(device); + LsfgDescriptorWriter(descriptor_sets[1]) + .AddSampler(sampler) + .AddSampledImages(temp2) + .AddStorageImages(temp1) + .Build(device); + LsfgDescriptorWriter(descriptor_sets[2]) + .AddSampler(sampler) + .AddSampledImages(temp1) + .AddStorageImages(temp2) + .Build(device); + LsfgDescriptorWriter(descriptor_sets[3]) + .AddUniformBuffer(resources.GetBuffer(0.5f), LsfgResources::BufferSize()) + .AddSampler(sampler) + .AddSampledImages(temp2) + .AddStorageImages(out_images) + .Build(device); +} + +void LsfgBeta::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) { + const VkExtent2D extent = temp1[0].Extent(); + const u32 groups_x = GroupCount(extent.width, DISPATCH_TILE_SHIFT); + const u32 groups_y = GroupCount(extent.height, DISPATCH_TILE_SHIFT); + + LsfgBarriers barriers(cmdbuf); + for (auto& slot : *inputs) { + barriers.WriteToReadAll(slot); + } + barriers.ReadToWriteAll(temp1).Build(); + + passes[0].Bind(cmdbuf, first_descriptor_sets[frame_count % LSFG_HISTORY_SLOTS]); + cmdbuf.Dispatch(groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); + passes[1].Bind(cmdbuf, descriptor_sets[0]); + cmdbuf.Dispatch(groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(temp1).Build(); + passes[2].Bind(cmdbuf, descriptor_sets[1]); + cmdbuf.Dispatch(groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); + passes[3].Bind(cmdbuf, descriptor_sets[2]); + cmdbuf.Dispatch(groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(out_images).Build(); + passes[4].Bind(cmdbuf, descriptor_sets[3]); + cmdbuf.Dispatch(GroupCount(extent.width, OUTPUT_TILE_SHIFT), + GroupCount(extent.height, OUTPUT_TILE_SHIFT), 1); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/lsfg_beta.h b/src/video_core/renderer_vulkan/present/lsfg_beta.h new file mode 100644 index 0000000000..1e24b235cf --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_beta.h @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" +#include "video_core/renderer_vulkan/present/lsfg_common.h" + +namespace Vulkan { + +class Device; +class LsfgShaders; + +constexpr size_t LSFG_BETA_STAGES = 5; +constexpr size_t LSFG_BETA_OUTPUTS = 6; + +class LsfgBeta { +public: + LsfgBeta() = default; + LsfgBeta(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders, + LsfgResources& resources, vk::DescriptorPool& descriptor_pool, + LsfgImageHistory& inputs); + + void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count); + + [[nodiscard]] LsfgImage& Output(size_t level) { + return out_images[level]; + } + +private: + LsfgImageHistory* inputs{}; + + std::array passes; + std::array first_descriptor_sets{}; + std::array descriptor_sets{}; + vk::DescriptorSets owned_sets; + + LsfgImagePair temp1; + LsfgImagePair temp2; + std::array out_images; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/lsfg_chain.cpp b/src/video_core/renderer_vulkan/present/lsfg_chain.cpp new file mode 100644 index 0000000000..972b13a1c4 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_chain.cpp @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "video_core/renderer_vulkan/present/lsfg_chain.h" +#include "video_core/renderer_vulkan/present/lsfg_shaders.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +namespace { + +constexpr u32 FIXED_DESCRIPTOR_SETS = 64; +constexpr u32 DESCRIPTOR_SETS_PER_SLOT = 112; +constexpr size_t FIRST_DELTA_LEVEL = 4; + +} // Anonymous namespace + +LsfgChain::LsfgChain(const Device& device, MemoryAllocator& memory_allocator, + const LsfgShaders& shaders, VkExtent2D extent, VkFormat format, + f32 flow_scale) + : resources{device, memory_allocator, flow_scale}, + descriptor_pool{CreateLsfgDescriptorPool( + device, FIXED_DESCRIPTOR_SETS + + DESCRIPTOR_SETS_PER_SLOT * static_cast(LSFG_GENERATION_SLOTS))} { + for (auto& image : frames) { + image = LsfgImage(device, memory_allocator, extent, format); + } + + mipmaps = LsfgMipmaps(device, memory_allocator, shaders, resources, descriptor_pool, frames, + flow_scale); + + alpha_passes = LsfgAlphaPasses(device, shaders); + for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) { + alpha[i] = LsfgAlpha(device, memory_allocator, alpha_passes, resources, descriptor_pool, + mipmaps.Output(i)); + } + + beta = LsfgBeta(device, memory_allocator, shaders, resources, descriptor_pool, + alpha[0].Outputs()); + + for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) { + const size_t level = LSFG_MIP_LEVELS - 1 - i; + gamma[i] = LsfgGamma(device, memory_allocator, shaders, resources, descriptor_pool, + alpha[level].Outputs(), + beta.Output(std::min(level, LSFG_BETA_OUTPUTS - 1)), + i == 0 ? nullptr : &gamma[i - 1].Output()); + + if (i < FIRST_DELTA_LEVEL) { + continue; + } + + const size_t index = i - FIRST_DELTA_LEVEL; + delta[index] = LsfgDelta( + device, memory_allocator, shaders, resources, descriptor_pool, alpha[level].Outputs(), + beta.Output(level), i == FIRST_DELTA_LEVEL ? nullptr : &gamma[i - 1].Output(), + i == FIRST_DELTA_LEVEL ? nullptr : &delta[index - 1].Output1(), + i == FIRST_DELTA_LEVEL ? nullptr : &delta[index - 1].Output2()); + } + + generate = LsfgGenerate(device, shaders, resources, descriptor_pool, frames, + gamma[LSFG_MIP_LEVELS - 1].Output(), + delta[LSFG_DELTA_INSTANCES - 1].Output1(), + delta[LSFG_DELTA_INSTANCES - 1].Output2()); +} + +void LsfgChain::DispatchShared(vk::CommandBuffer cmdbuf, u64 frame_count) { + mipmaps.Dispatch(cmdbuf, frame_count); + + for (size_t stage = 0; stage < LSFG_ALPHA_STAGES; ++stage) { + LsfgBarriers barriers(cmdbuf); + for (auto& level : alpha) { + level.PushBarriers(barriers, frame_count, stage); + } + barriers.Build(); + + alpha_passes.Get(stage).BindPipeline(cmdbuf); + for (auto& level : alpha) { + level.DispatchStage(cmdbuf, frame_count, stage); + } + } + + beta.Dispatch(cmdbuf, frame_count); +} + +void LsfgChain::DispatchGeneration(vk::CommandBuffer cmdbuf, u64 frame_count, + size_t generation_count, size_t generation, u32 target, + VkImage image, VkExtent2D extent) { + const size_t slot = LsfgGenerationSlot(generation_count, generation); + for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) { + gamma[i].Dispatch(cmdbuf, frame_count, slot); + if (i >= FIRST_DELTA_LEVEL) { + delta[i - FIRST_DELTA_LEVEL].Dispatch(cmdbuf, frame_count, slot); + } + } + generate.Dispatch(cmdbuf, frame_count, slot, target, image, extent); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/lsfg_chain.h b/src/video_core/renderer_vulkan/present/lsfg_chain.h new file mode 100644 index 0000000000..c8879f74a9 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_chain.h @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" +#include "video_core/renderer_vulkan/present/lsfg_alpha.h" +#include "video_core/renderer_vulkan/present/lsfg_beta.h" +#include "video_core/renderer_vulkan/present/lsfg_common.h" +#include "video_core/renderer_vulkan/present/lsfg_delta.h" +#include "video_core/renderer_vulkan/present/lsfg_gamma.h" +#include "video_core/renderer_vulkan/present/lsfg_generate.h" +#include "video_core/renderer_vulkan/present/lsfg_mipmaps.h" + +namespace Vulkan { + +class Device; +class LsfgShaders; + +constexpr size_t LSFG_DELTA_INSTANCES = 3; + +class LsfgChain { +public: + LsfgChain(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders, + VkExtent2D extent, VkFormat format, f32 flow_scale); + + LsfgChain(const LsfgChain&) = delete; + LsfgChain& operator=(const LsfgChain&) = delete; + + void DispatchShared(vk::CommandBuffer cmdbuf, u64 frame_count); + + void DispatchGeneration(vk::CommandBuffer cmdbuf, u64 frame_count, size_t generation_count, + size_t generation, u32 target, VkImage image, VkExtent2D extent); + + void SetTarget(const Device& device, size_t generation_count, size_t generation, u32 target, + VkImageView view) { + generate.SetTarget(device, LsfgGenerationSlot(generation_count, generation), target, view); + } + + [[nodiscard]] LsfgImage& Input(u64 frame_count) { + return frames[frame_count % frames.size()]; + } + + [[nodiscard]] LsfgImage& FlowLevel(size_t level) { + return mipmaps.Output(level); + } + + [[nodiscard]] LsfgImage& AlphaOutput(size_t level, u64 frame_count, size_t index) { + return alpha[level].Outputs()[frame_count % LSFG_HISTORY_SLOTS][index]; + } + + [[nodiscard]] LsfgImage& BetaOutput(size_t level) { + return beta.Output(level); + } + + [[nodiscard]] LsfgImage& GammaOutput(size_t index) { + return gamma[index].Output(); + } + + [[nodiscard]] LsfgImage& DeltaOutput1(size_t index) { + return delta[index].Output1(); + } + + [[nodiscard]] LsfgImage& DeltaOutput2(size_t index) { + return delta[index].Output2(); + } + +private: + LsfgResources resources; + vk::DescriptorPool descriptor_pool; + + LsfgImagePair frames; + LsfgMipmaps mipmaps; + LsfgAlphaPasses alpha_passes; + std::array alpha; + LsfgBeta beta; + std::array gamma; + std::array delta; + LsfgGenerate generate; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/lsfg_common.cpp b/src/video_core/renderer_vulkan/present/lsfg_common.cpp new file mode 100644 index 0000000000..d2df2755e5 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_common.cpp @@ -0,0 +1,339 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include "video_core/renderer_vulkan/present/lsfg_common.h" +#include "video_core/renderer_vulkan/present/lsfg_shaders.h" +#include "video_core/renderer_vulkan/present/util.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +namespace { + +constexpr u32 DESCRIPTORS_PER_TYPE = 4096; + +struct LsfgConstants { + std::array input_offset; + u32 first_iter; + u32 first_iter_s; + u32 advanced_color_kind; + u32 hdr_support; + f32 resolution_inv_scale; + f32 timestamp; + f32 ui_threshold; + std::array padding; +}; +static_assert(sizeof(LsfgConstants) == 48); + +vk::Image CreateChainImage(MemoryAllocator& memory_allocator, VkExtent2D extent, VkFormat format) { + const VkImageCreateInfo image_ci{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .imageType = VK_IMAGE_TYPE_2D, + .format = format, + .extent = {.width = extent.width, .height = extent.height, .depth = 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }; + return memory_allocator.CreateImage(image_ci); +} + +vk::Buffer CreateUniformBuffer(MemoryAllocator& memory_allocator, VkDeviceSize size) { + const VkBufferCreateInfo buffer_ci{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = size, + .usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; + return memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::Upload); +} + +VkImageMemoryBarrier MakeBarrier(const LsfgImage& image, VkAccessFlags src_access, + VkAccessFlags dst_access) { + return VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = src_access, + .dstAccessMask = dst_access, + .oldLayout = image.Layout(), + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image.Handle(), + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; +} + +} // Anonymous namespace + +LsfgImage::LsfgImage(const Device& device, MemoryAllocator& memory_allocator, VkExtent2D extent_, + VkFormat format_) + : extent{std::max(1u, extent_.width), std::max(1u, extent_.height)}, format{format_} { + image = CreateChainImage(memory_allocator, extent, format); + view = CreateWrappedImageView(device, image, format); +} + +LsfgBarriers& LsfgBarriers::Push(LsfgImage& image, VkAccessFlags src_access, + VkAccessFlags dst_access) { + barriers.push_back(MakeBarrier(image, src_access, dst_access)); + image.SetLayout(VK_IMAGE_LAYOUT_GENERAL); + return *this; +} + +LsfgBarriers& LsfgBarriers::WriteToRead(LsfgImage& image) { + return Push(image, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); +} + +LsfgBarriers& LsfgBarriers::ReadToWrite(LsfgImage& image) { + return Push(image, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT); +} + +LsfgBarriers& LsfgBarriers::WriteToRead(LsfgImage* image) { + return image == nullptr ? *this : WriteToRead(*image); +} + +LsfgBarriers& LsfgBarriers::ReadToWrite(LsfgImage* image) { + return image == nullptr ? *this : ReadToWrite(*image); +} + +LsfgBarriers& LsfgBarriers::DiscardToWrite(VkImage image) { + barriers.push_back(VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }); + return *this; +} + +VkDeviceSize LsfgResources::BufferSize() { + return sizeof(LsfgConstants); +} + +VkSampler LsfgResources::GetSampler(VkSamplerAddressMode address_mode, VkCompareOp compare_op, + bool white_border) { + const u64 key = static_cast(address_mode) | (static_cast(compare_op) << 8) | + (static_cast(white_border) << 16); + + const auto it = samplers.find(key); + if (it != samplers.end()) { + return *it->second; + } + + const auto [entry, inserted] = + samplers.emplace(key, CreateLsfgSampler(*device, address_mode, compare_op, white_border)); + return *entry->second; +} + +VkBuffer LsfgResources::GetBuffer(f32 timestamp, bool first_iter, bool first_iter_s) { + u32 timestamp_bits{}; + std::memcpy(×tamp_bits, ×tamp, sizeof(timestamp_bits)); + const u64 key = static_cast(timestamp_bits) | (static_cast(first_iter) << 32) | + (static_cast(first_iter_s) << 33); + + const auto it = buffers.find(key); + if (it != buffers.end()) { + return *it->second; + } + + vk::Buffer buffer = CreateUniformBuffer(*memory_allocator, sizeof(LsfgConstants)); + + const LsfgConstants constants{ + .input_offset = {0, 0}, + .first_iter = first_iter ? 1u : 0u, + .first_iter_s = first_iter_s ? 1u : 0u, + .advanced_color_kind = 0, + .hdr_support = 0, + .resolution_inv_scale = 1.0f / flow_scale, + .timestamp = timestamp, + .ui_threshold = 0.5f, + .padding = {0, 0, 0}, + }; + + const std::span mapped = buffer.Mapped(); + std::memcpy(mapped.data(), &constants, sizeof(constants)); + buffer.Flush(); + + const auto [entry, inserted] = buffers.emplace(key, std::move(buffer)); + return *entry->second; +} + +void LsfgBarriers::Build() { + if (barriers.empty()) { + return; + } + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, {}, {}, barriers); + barriers.clear(); +} + +LsfgDescriptorWriter& LsfgDescriptorWriter::PushImage(VkDescriptorType type, VkSampler sampler, + VkImageView view) { + image_infos.push_back(VkDescriptorImageInfo{ + .sampler = sampler, + .imageView = view, + .imageLayout = view == VK_NULL_HANDLE ? VK_IMAGE_LAYOUT_UNDEFINED + : VK_IMAGE_LAYOUT_GENERAL, + }); + writes.push_back(VkWriteDescriptorSet{ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = set, + .dstBinding = binding++, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = type, + .pImageInfo = &image_infos.back(), + .pBufferInfo = nullptr, + .pTexelBufferView = nullptr, + }); + return *this; +} + +LsfgDescriptorWriter& LsfgDescriptorWriter::AddSampler(VkSampler sampler) { + return PushImage(VK_DESCRIPTOR_TYPE_SAMPLER, sampler, VK_NULL_HANDLE); +} + +LsfgDescriptorWriter& LsfgDescriptorWriter::AddSampledImage(const LsfgImage& image) { + return PushImage(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_NULL_HANDLE, image.View()); +} + +LsfgDescriptorWriter& LsfgDescriptorWriter::AddSampledImage(const LsfgImage* image) { + return PushImage(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_NULL_HANDLE, + image == nullptr ? VK_NULL_HANDLE : image->View()); +} + +LsfgDescriptorWriter& LsfgDescriptorWriter::AddStorageImage(const LsfgImage& image) { + return PushImage(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_NULL_HANDLE, image.View()); +} + +LsfgDescriptorWriter& LsfgDescriptorWriter::AddStorageView(VkImageView view) { + return PushImage(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_NULL_HANDLE, view); +} + +LsfgDescriptorWriter& LsfgDescriptorWriter::AddUniformBuffer(VkBuffer buffer, VkDeviceSize size) { + buffer_infos.push_back(VkDescriptorBufferInfo{ + .buffer = buffer, + .offset = 0, + .range = size, + }); + writes.push_back(VkWriteDescriptorSet{ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = set, + .dstBinding = binding++, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .pImageInfo = nullptr, + .pBufferInfo = &buffer_infos.back(), + .pTexelBufferView = nullptr, + }); + return *this; +} + +void LsfgDescriptorWriter::Build(const Device& device) { + if (writes.empty()) { + return; + } + device.GetLogical().UpdateDescriptorSets(writes, {}); + writes.clear(); +} + +LsfgPass::LsfgPass(const Device& device, const LsfgShaders& shaders, u32 shader_id, + LsfgBindings bindings) { + std::vector types; + for (const auto& [count, type] : bindings) { + types.insert(types.end(), count, type); + } + descriptor_count = static_cast(types.size()); + + descriptor_set_layout = CreateWrappedDescriptorSetLayout( + device, std::span{types}, VK_SHADER_STAGE_COMPUTE_BIT); + pipeline_layout = CreateWrappedPipelineLayout(device, descriptor_set_layout); + pipeline = CreateWrappedComputePipeline(device, pipeline_layout, shaders.Get(shader_id)); +} + +void LsfgPass::Bind(vk::CommandBuffer cmdbuf, VkDescriptorSet set) const { + BindPipeline(cmdbuf); + BindSet(cmdbuf, set); +} + +void LsfgPass::BindPipeline(vk::CommandBuffer cmdbuf) const { + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline); +} + +void LsfgPass::BindSet(vk::CommandBuffer cmdbuf, VkDescriptorSet set) const { + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, 0, set, {}); +} + +vk::DescriptorPool CreateLsfgDescriptorPool(const Device& device, u32 max_sets) { + return CreateWrappedDescriptorPool( + device, DESCRIPTORS_PER_TYPE, max_sets, + {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_SAMPLER, + VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}); +} + +vk::Sampler CreateLsfgSampler(const Device& device, VkSamplerAddressMode address_mode, + VkCompareOp compare_op, bool white_border) { + return device.GetLogical().CreateSampler(VkSamplerCreateInfo{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .magFilter = VK_FILTER_LINEAR, + .minFilter = VK_FILTER_LINEAR, + .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, + .addressModeU = address_mode, + .addressModeV = address_mode, + .addressModeW = address_mode, + .mipLodBias = 0.0f, + .anisotropyEnable = VK_FALSE, + .maxAnisotropy = 0.0f, + .compareEnable = VK_FALSE, + .compareOp = compare_op, + .minLod = 0.0f, + .maxLod = VK_LOD_CLAMP_NONE, + .borderColor = white_border ? VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE + : VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, + .unnormalizedCoordinates = VK_FALSE, + }); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/lsfg_common.h b/src/video_core/renderer_vulkan/present/lsfg_common.h new file mode 100644 index 0000000000..d1e3271a6d --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_common.h @@ -0,0 +1,227 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "common/common_types.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +class Device; +class LsfgShaders; + +constexpr VkFormat LSFG_DEFAULT_FORMAT = VK_FORMAT_R8G8B8A8_UNORM; +constexpr VkFormat LSFG_FLOW_FORMAT = VK_FORMAT_R8_UNORM; +constexpr VkFormat LSFG_MOTION_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT; + +constexpr size_t LSFG_HISTORY_SLOTS = 3; +constexpr size_t LSFG_MAX_TARGETS = 7; +constexpr size_t LSFG_MAX_GENERATIONS = 3; + +constexpr size_t LSFG_GENERATION_SLOTS = LSFG_MAX_GENERATIONS * (LSFG_MAX_GENERATIONS + 1) / 2; + +[[nodiscard]] constexpr size_t LsfgGenerationSlot(size_t generation_count, size_t generation) { + return (generation_count - 1) * generation_count / 2 + generation; +} + +[[nodiscard]] constexpr f32 LsfgTimestamp(size_t generation, size_t generation_count) { + return static_cast(generation + 1) / static_cast(generation_count + 1); +} + +[[nodiscard]] constexpr size_t LsfgSlotCount(size_t slot) { + size_t count = 1; + while (LsfgGenerationSlot(count + 1, 0) <= slot) { + ++count; + } + return count; +} + +[[nodiscard]] constexpr f32 LsfgSlotTimestamp(size_t slot) { + const size_t count = LsfgSlotCount(slot); + return LsfgTimestamp(slot - LsfgGenerationSlot(count, 0), count); +} + +class LsfgImage { +public: + LsfgImage() = default; + LsfgImage(const Device& device, MemoryAllocator& memory_allocator, VkExtent2D extent_, + VkFormat format = LSFG_DEFAULT_FORMAT); + + [[nodiscard]] VkImage Handle() const { + return *image; + } + + [[nodiscard]] VkImageView View() const { + return *view; + } + + [[nodiscard]] VkExtent2D Extent() const { + return extent; + } + + [[nodiscard]] VkFormat Format() const { + return format; + } + + [[nodiscard]] VkImageLayout Layout() const { + return layout; + } + + void SetLayout(VkImageLayout new_layout) { + layout = new_layout; + } + +private: + vk::Image image; + vk::ImageView view; + VkExtent2D extent{}; + VkFormat format{VK_FORMAT_UNDEFINED}; + VkImageLayout layout{VK_IMAGE_LAYOUT_UNDEFINED}; +}; + +using LsfgImagePair = std::array; +using LsfgImageHistory = std::array; + +class LsfgResources { +public: + LsfgResources() = default; + LsfgResources(const Device& device_, MemoryAllocator& memory_allocator_, f32 flow_scale_) + : device{&device_}, memory_allocator{&memory_allocator_}, flow_scale{flow_scale_} {} + + [[nodiscard]] VkSampler GetSampler( + VkSamplerAddressMode address_mode = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, + VkCompareOp compare_op = VK_COMPARE_OP_NEVER, bool white_border = false); + + [[nodiscard]] VkBuffer GetBuffer(f32 timestamp = 0.0f, bool first_iter = false, + bool first_iter_s = false); + + [[nodiscard]] static VkDeviceSize BufferSize(); + +private: + const Device* device{}; + MemoryAllocator* memory_allocator{}; + f32 flow_scale{1.0f}; + + std::map samplers; + std::map buffers; +}; + +class LsfgBarriers { +public: + explicit LsfgBarriers(vk::CommandBuffer cmdbuf_) : cmdbuf{cmdbuf_} {} + + LsfgBarriers& WriteToRead(LsfgImage& image); + LsfgBarriers& ReadToWrite(LsfgImage& image); + LsfgBarriers& WriteToRead(LsfgImage* image); + LsfgBarriers& ReadToWrite(LsfgImage* image); + LsfgBarriers& DiscardToWrite(VkImage image); + + template + LsfgBarriers& WriteToReadAll(Range& images) { + for (auto& image : images) { + WriteToRead(image); + } + return *this; + } + + template + LsfgBarriers& ReadToWriteAll(Range& images) { + for (auto& image : images) { + ReadToWrite(image); + } + return *this; + } + + void Build(); + +private: + LsfgBarriers& Push(LsfgImage& image, VkAccessFlags src_access, VkAccessFlags dst_access); + + vk::CommandBuffer cmdbuf; + std::vector barriers; +}; + +class LsfgDescriptorWriter { +public: + explicit LsfgDescriptorWriter(VkDescriptorSet set_) : set{set_} {} + + LsfgDescriptorWriter& AddSampler(VkSampler sampler); + LsfgDescriptorWriter& AddSampledImage(const LsfgImage& image); + LsfgDescriptorWriter& AddSampledImage(const LsfgImage* image); + LsfgDescriptorWriter& AddStorageImage(const LsfgImage& image); + LsfgDescriptorWriter& AddStorageView(VkImageView view); + LsfgDescriptorWriter& AddUniformBuffer(VkBuffer buffer, VkDeviceSize size); + + template + LsfgDescriptorWriter& AddSampledImages(const Range& images) { + for (const auto& image : images) { + AddSampledImage(image); + } + return *this; + } + + template + LsfgDescriptorWriter& AddStorageImages(const Range& images) { + for (const auto& image : images) { + AddStorageImage(image); + } + return *this; + } + + void Build(const Device& device); + +private: + LsfgDescriptorWriter& PushImage(VkDescriptorType type, VkSampler sampler, VkImageView view); + + VkDescriptorSet set; + u32 binding{}; + std::deque image_infos; + std::deque buffer_infos; + std::vector writes; +}; + +using LsfgBindings = std::initializer_list>; + +class LsfgPass { +public: + LsfgPass() = default; + LsfgPass(const Device& device, const LsfgShaders& shaders, u32 shader_id, + LsfgBindings bindings); + + [[nodiscard]] VkDescriptorSetLayout SetLayout() const { + return *descriptor_set_layout; + } + + [[nodiscard]] u32 DescriptorCount() const { + return descriptor_count; + } + + void Bind(vk::CommandBuffer cmdbuf, VkDescriptorSet set) const; + void BindPipeline(vk::CommandBuffer cmdbuf) const; + void BindSet(vk::CommandBuffer cmdbuf, VkDescriptorSet set) const; + +private: + vk::DescriptorSetLayout descriptor_set_layout; + vk::PipelineLayout pipeline_layout; + vk::Pipeline pipeline; + u32 descriptor_count{}; +}; + +[[nodiscard]] vk::DescriptorPool CreateLsfgDescriptorPool(const Device& device, u32 max_sets); + +[[nodiscard]] vk::Sampler CreateLsfgSampler(const Device& device, VkSamplerAddressMode address_mode, + VkCompareOp compare_op, bool white_border); + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/lsfg_delta.cpp b/src/video_core/renderer_vulkan/present/lsfg_delta.cpp new file mode 100644 index 0000000000..dc6eec383f --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_delta.cpp @@ -0,0 +1,283 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "video_core/frame_gen/lossless_dll.h" +#include "video_core/renderer_vulkan/present/lsfg_delta.h" +#include "video_core/renderer_vulkan/present/lsfg_shaders.h" +#include "video_core/renderer_vulkan/present/util.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +namespace { + +constexpr u32 DISPATCH_TILE_SHIFT = 3; + +[[nodiscard]] u32 GroupCount(u32 size) { + return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT; +} + +} // Anonymous namespace + +LsfgDelta::LsfgDelta(const Device& device, MemoryAllocator& memory_allocator, + const LsfgShaders& shaders, LsfgResources& resources, + vk::DescriptorPool& descriptor_pool, LsfgImageHistory& inputs_, + LsfgImage& flow_input_, LsfgImage* previous_gamma_, LsfgImage* previous1_, + LsfgImage* previous2_) + : inputs{&inputs_}, flow_input{&flow_input_}, previous_gamma{previous_gamma_}, + previous1{previous1_}, previous2{previous2_} { + using namespace VideoCore::FrameGen::PerformanceShader; + + passes[0] = LsfgPass(device, shaders, DELTA[0], + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLER}, + {5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[1] = LsfgPass(device, shaders, DELTA[1], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[2] = LsfgPass(device, shaders, DELTA[2], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[3] = LsfgPass(device, shaders, DELTA[3], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[4] = LsfgPass(device, shaders, DELTA[4], + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLER}, + {4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[5] = LsfgPass(device, shaders, DELTA[5], + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLER}, + {6, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + for (size_t i = 6; i < LSFG_DELTA_STAGES - 1; ++i) { + passes[i] = LsfgPass(device, shaders, DELTA[i], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + } + passes[9] = LsfgPass(device, shaders, DELTA[9], + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + + const VkExtent2D extent = (*inputs)[0][0].Extent(); + for (auto& image : temp1) { + image = LsfgImage(device, memory_allocator, extent); + } + for (auto& image : temp2) { + image = LsfgImage(device, memory_allocator, extent); + } + out_image1 = LsfgImage(device, memory_allocator, extent, LSFG_MOTION_FORMAT); + out_image2 = LsfgImage(device, memory_allocator, extent, LSFG_MOTION_FORMAT); + + std::vector layouts; + for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) { + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + layouts.push_back(passes[0].SetLayout()); + } + for (size_t i = 1; i <= 4; ++i) { + layouts.push_back(passes[i].SetLayout()); + } + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + layouts.push_back(passes[5].SetLayout()); + } + for (size_t i = 6; i < LSFG_DELTA_STAGES; ++i) { + layouts.push_back(passes[i].SetLayout()); + } + } + owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts); + + const VkSampler sampler = resources.GetSampler(); + const VkSampler border_sampler = resources.GetSampler( + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + const VkSampler edge_sampler = + resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false); + size_t next = 0; + for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) { + Generation& pass = generations[slot]; + const VkBuffer buffer = + resources.GetBuffer(LsfgSlotTimestamp(slot), false, previous_gamma == nullptr); + + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + pass.first_descriptor_sets[i] = owned_sets[next++]; + } + for (size_t i = 0; i < 4; ++i) { + pass.descriptor_sets[i] = owned_sets[next++]; + } + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + pass.sixth_descriptor_sets[i] = owned_sets[next++]; + } + for (size_t i = 4; i < LSFG_DELTA_STAGES - 2; ++i) { + pass.descriptor_sets[i] = owned_sets[next++]; + } + + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + LsfgDescriptorWriter(pass.first_descriptor_sets[i]) + .AddUniformBuffer(buffer, LsfgResources::BufferSize()) + .AddSampler(border_sampler) + .AddSampler(edge_sampler) + .AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS]) + .AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS]) + .AddSampledImage(previous_gamma) + .AddStorageImages(temp1) + .Build(device); + LsfgDescriptorWriter(pass.sixth_descriptor_sets[i]) + .AddUniformBuffer(buffer, LsfgResources::BufferSize()) + .AddSampler(border_sampler) + .AddSampler(edge_sampler) + .AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS]) + .AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS]) + .AddSampledImage(previous_gamma) + .AddSampledImage(previous1) + .AddStorageImage(temp2[0]) + .Build(device); + } + LsfgDescriptorWriter(pass.descriptor_sets[0]) + .AddSampler(sampler) + .AddSampledImages(temp1) + .AddStorageImages(temp2) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[1]) + .AddSampler(sampler) + .AddSampledImages(temp2) + .AddStorageImage(temp1[0]) + .AddStorageImage(temp1[1]) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[2]) + .AddSampler(sampler) + .AddSampledImage(temp1[0]) + .AddSampledImage(temp1[1]) + .AddStorageImages(temp2) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[3]) + .AddUniformBuffer(buffer, LsfgResources::BufferSize()) + .AddSampler(sampler) + .AddSampler(edge_sampler) + .AddSampledImages(temp2) + .AddSampledImage(previous_gamma) + .AddSampledImage(*flow_input) + .AddStorageImage(out_image1) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[4]) + .AddSampler(sampler) + .AddSampledImage(temp2[0]) + .AddStorageImage(temp1[0]) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[5]) + .AddSampler(sampler) + .AddSampledImage(temp1[0]) + .AddStorageImage(temp2[0]) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[6]) + .AddSampler(sampler) + .AddSampledImage(temp2[0]) + .AddStorageImage(temp1[0]) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[7]) + .AddUniformBuffer(buffer, LsfgResources::BufferSize()) + .AddSampler(sampler) + .AddSampler(edge_sampler) + .AddSampledImage(temp1[0]) + .AddSampledImage(previous2) + .AddStorageImage(out_image2) + .Build(device); + } +} + +void LsfgDelta::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot) { + const Generation& pass = generations[slot]; + + const VkExtent2D extent = temp1[0].Extent(); + const u32 groups_x = GroupCount(extent.width); + const u32 groups_y = GroupCount(extent.height); + + const size_t history = frame_count % LSFG_HISTORY_SLOTS; + const size_t previous_history = (frame_count + 2) % LSFG_HISTORY_SLOTS; + + LsfgBarriers(cmdbuf) + .WriteToReadAll((*inputs)[previous_history]) + .WriteToReadAll((*inputs)[history]) + .WriteToRead(previous_gamma) + .ReadToWriteAll(temp1) + .Build(); + passes[0].Bind(cmdbuf, pass.first_descriptor_sets[history]); + cmdbuf.Dispatch(groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); + passes[1].Bind(cmdbuf, pass.descriptor_sets[0]); + cmdbuf.Dispatch(groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf).WriteToReadAll(temp2).ReadToWriteAll(temp1).Build(); + passes[2].Bind(cmdbuf, pass.descriptor_sets[1]); + cmdbuf.Dispatch(groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); + passes[3].Bind(cmdbuf, pass.descriptor_sets[2]); + cmdbuf.Dispatch(groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToReadAll(temp2) + .WriteToRead(previous_gamma) + .WriteToRead(*flow_input) + .ReadToWrite(out_image1) + .Build(); + passes[4].Bind(cmdbuf, pass.descriptor_sets[3]); + cmdbuf.Dispatch(groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToReadAll((*inputs)[previous_history]) + .WriteToReadAll((*inputs)[history]) + .WriteToRead(previous_gamma) + .WriteToRead(previous1) + .ReadToWriteAll(temp2) + .Build(); + passes[5].Bind(cmdbuf, pass.sixth_descriptor_sets[history]); + cmdbuf.Dispatch(groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToReadAll(temp2) + .ReadToWrite(temp1[0]) + .ReadToWrite(temp1[1]) + .Build(); + passes[6].Bind(cmdbuf, pass.descriptor_sets[4]); + cmdbuf.Dispatch(groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToRead(temp1[0]) + .WriteToRead(temp1[1]) + .ReadToWriteAll(temp2) + .Build(); + passes[7].Bind(cmdbuf, pass.descriptor_sets[5]); + cmdbuf.Dispatch(groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToReadAll(temp2) + .ReadToWrite(temp1[0]) + .ReadToWrite(temp1[1]) + .Build(); + passes[8].Bind(cmdbuf, pass.descriptor_sets[6]); + cmdbuf.Dispatch(groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToRead(temp1[0]) + .WriteToRead(temp1[1]) + .WriteToRead(previous2) + .ReadToWrite(out_image2) + .Build(); + passes[9].Bind(cmdbuf, pass.descriptor_sets[7]); + cmdbuf.Dispatch(groups_x, groups_y, 1); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/lsfg_delta.h b/src/video_core/renderer_vulkan/present/lsfg_delta.h new file mode 100644 index 0000000000..1bbcfe6745 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_delta.h @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" +#include "video_core/renderer_vulkan/present/lsfg_common.h" + +namespace Vulkan { + +class Device; +class LsfgShaders; + +constexpr size_t LSFG_DELTA_STAGES = 10; +constexpr size_t LSFG_DELTA_TEMPS = 3; + +class LsfgDelta { +public: + LsfgDelta() = default; + LsfgDelta(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders, + LsfgResources& resources, vk::DescriptorPool& descriptor_pool, + LsfgImageHistory& inputs, LsfgImage& flow_input, LsfgImage* previous_gamma, + LsfgImage* previous1, LsfgImage* previous2); + + void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot); + + [[nodiscard]] LsfgImage& Output1() { + return out_image1; + } + + [[nodiscard]] LsfgImage& Output2() { + return out_image2; + } + +private: + struct Generation { + std::array first_descriptor_sets{}; + std::array sixth_descriptor_sets{}; + std::array descriptor_sets{}; + }; + + LsfgImageHistory* inputs{}; + LsfgImage* flow_input{}; + LsfgImage* previous_gamma{}; + LsfgImage* previous1{}; + LsfgImage* previous2{}; + + std::array passes; + std::array generations{}; + vk::DescriptorSets owned_sets; + + std::array temp1; + LsfgImagePair temp2; + LsfgImage out_image1; + LsfgImage out_image2; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/lsfg_gamma.cpp b/src/video_core/renderer_vulkan/present/lsfg_gamma.cpp new file mode 100644 index 0000000000..067d124b6f --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_gamma.cpp @@ -0,0 +1,185 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "video_core/frame_gen/lossless_dll.h" +#include "video_core/renderer_vulkan/present/lsfg_gamma.h" +#include "video_core/renderer_vulkan/present/lsfg_shaders.h" +#include "video_core/renderer_vulkan/present/util.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +namespace { + +constexpr u32 DISPATCH_TILE_SHIFT = 3; + +[[nodiscard]] u32 GroupCount(u32 size) { + return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT; +} + +} // Anonymous namespace + +LsfgGamma::LsfgGamma(const Device& device, MemoryAllocator& memory_allocator, + const LsfgShaders& shaders, LsfgResources& resources, + vk::DescriptorPool& descriptor_pool, LsfgImageHistory& inputs_, + LsfgImage& flow_input_, LsfgImage* previous_) + : inputs{&inputs_}, flow_input{&flow_input_}, previous{previous_} { + using namespace VideoCore::FrameGen::PerformanceShader; + + passes[0] = LsfgPass(device, shaders, GAMMA[0], + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLER}, + {5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[1] = LsfgPass(device, shaders, GAMMA[1], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[2] = LsfgPass(device, shaders, GAMMA[2], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[3] = LsfgPass(device, shaders, GAMMA[3], + {{1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + passes[4] = LsfgPass(device, shaders, GAMMA[4], + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLER}, + {4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + + const VkExtent2D extent = (*inputs)[0][0].Extent(); + for (auto& image : temp1) { + image = LsfgImage(device, memory_allocator, extent); + } + for (auto& image : temp2) { + image = LsfgImage(device, memory_allocator, extent); + } + out_image = LsfgImage(device, memory_allocator, extent, LSFG_MOTION_FORMAT); + + std::vector layouts; + for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) { + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + layouts.push_back(passes[0].SetLayout()); + } + for (size_t i = 1; i < LSFG_GAMMA_STAGES; ++i) { + layouts.push_back(passes[i].SetLayout()); + } + } + owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts); + + const VkSampler sampler = resources.GetSampler(); + const VkSampler border_sampler = resources.GetSampler( + VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, VK_COMPARE_OP_NEVER, true); + const VkSampler edge_sampler = + resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false); + + size_t next = 0; + for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) { + Generation& pass = generations[slot]; + const VkBuffer buffer = + resources.GetBuffer(LsfgSlotTimestamp(slot), previous == nullptr); + + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + pass.first_descriptor_sets[i] = owned_sets[next++]; + } + for (size_t i = 0; i < LSFG_GAMMA_STAGES - 1; ++i) { + pass.descriptor_sets[i] = owned_sets[next++]; + } + + for (size_t i = 0; i < LSFG_HISTORY_SLOTS; ++i) { + LsfgDescriptorWriter(pass.first_descriptor_sets[i]) + .AddUniformBuffer(buffer, LsfgResources::BufferSize()) + .AddSampler(border_sampler) + .AddSampler(edge_sampler) + .AddSampledImages((*inputs)[(i + 2) % LSFG_HISTORY_SLOTS]) + .AddSampledImages((*inputs)[i % LSFG_HISTORY_SLOTS]) + .AddSampledImage(previous) + .AddStorageImages(temp1) + .Build(device); + } + LsfgDescriptorWriter(pass.descriptor_sets[0]) + .AddSampler(sampler) + .AddSampledImages(temp1) + .AddStorageImages(temp2) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[1]) + .AddSampler(sampler) + .AddSampledImages(temp2) + .AddStorageImage(temp1[0]) + .AddStorageImage(temp1[1]) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[2]) + .AddSampler(sampler) + .AddSampledImage(temp1[0]) + .AddSampledImage(temp1[1]) + .AddStorageImages(temp2) + .Build(device); + LsfgDescriptorWriter(pass.descriptor_sets[3]) + .AddUniformBuffer(buffer, LsfgResources::BufferSize()) + .AddSampler(sampler) + .AddSampler(edge_sampler) + .AddSampledImages(temp2) + .AddSampledImage(previous) + .AddSampledImage(*flow_input) + .AddStorageImage(out_image) + .Build(device); + } +} + +void LsfgGamma::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot) { + const Generation& pass = generations[slot]; + + const VkExtent2D extent = temp1[0].Extent(); + const u32 groups_x = GroupCount(extent.width); + const u32 groups_y = GroupCount(extent.height); + + const size_t history = frame_count % LSFG_HISTORY_SLOTS; + const size_t previous_history = (frame_count + 2) % LSFG_HISTORY_SLOTS; + + LsfgBarriers(cmdbuf) + .WriteToReadAll((*inputs)[previous_history]) + .WriteToReadAll((*inputs)[history]) + .WriteToRead(previous) + .ReadToWriteAll(temp1) + .Build(); + passes[0].Bind(cmdbuf, pass.first_descriptor_sets[history]); + cmdbuf.Dispatch(groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf).WriteToReadAll(temp1).ReadToWriteAll(temp2).Build(); + passes[1].Bind(cmdbuf, pass.descriptor_sets[0]); + cmdbuf.Dispatch(groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToReadAll(temp2) + .ReadToWrite(temp1[0]) + .ReadToWrite(temp1[1]) + .Build(); + passes[2].Bind(cmdbuf, pass.descriptor_sets[1]); + cmdbuf.Dispatch(groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToRead(temp1[0]) + .WriteToRead(temp1[1]) + .ReadToWriteAll(temp2) + .Build(); + passes[3].Bind(cmdbuf, pass.descriptor_sets[2]); + cmdbuf.Dispatch(groups_x, groups_y, 1); + + LsfgBarriers(cmdbuf) + .WriteToReadAll(temp2) + .WriteToRead(previous) + .WriteToRead(*flow_input) + .ReadToWrite(out_image) + .Build(); + passes[4].Bind(cmdbuf, pass.descriptor_sets[3]); + cmdbuf.Dispatch(groups_x, groups_y, 1); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/lsfg_gamma.h b/src/video_core/renderer_vulkan/present/lsfg_gamma.h new file mode 100644 index 0000000000..848c380bfe --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_gamma.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" +#include "video_core/renderer_vulkan/present/lsfg_common.h" + +namespace Vulkan { + +class Device; +class LsfgShaders; + +constexpr size_t LSFG_GAMMA_STAGES = 5; +constexpr size_t LSFG_GAMMA_TEMPS = 3; + +class LsfgGamma { +public: + LsfgGamma() = default; + LsfgGamma(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders, + LsfgResources& resources, vk::DescriptorPool& descriptor_pool, + LsfgImageHistory& inputs, LsfgImage& flow_input, LsfgImage* previous); + + void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot); + + [[nodiscard]] LsfgImage& Output() { + return out_image; + } + +private: + struct Generation { + std::array first_descriptor_sets{}; + std::array descriptor_sets{}; + }; + + LsfgImageHistory* inputs{}; + LsfgImage* flow_input{}; + LsfgImage* previous{}; + + std::array passes; + std::array generations{}; + vk::DescriptorSets owned_sets; + + std::array temp1; + LsfgImagePair temp2; + LsfgImage out_image; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/lsfg_generate.cpp b/src/video_core/renderer_vulkan/present/lsfg_generate.cpp new file mode 100644 index 0000000000..4473b59a2f --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_generate.cpp @@ -0,0 +1,130 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "video_core/frame_gen/lossless_dll.h" +#include "video_core/renderer_vulkan/present/lsfg_generate.h" +#include "video_core/renderer_vulkan/present/lsfg_shaders.h" +#include "video_core/renderer_vulkan/present/util.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +namespace { + +constexpr u32 DISPATCH_TILE_SHIFT = 4; + +[[nodiscard]] u32 GroupCount(u32 size) { + return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT; +} + +VkImageMemoryBarrier MakeTargetBarrier(VkImage image, VkAccessFlags src_access, + VkAccessFlags dst_access, VkImageLayout old_layout) { + return VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = src_access, + .dstAccessMask = dst_access, + .oldLayout = old_layout, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; +} + +} // Anonymous namespace + +LsfgGenerate::LsfgGenerate(const Device& device, const LsfgShaders& shaders, + LsfgResources& resources, vk::DescriptorPool& descriptor_pool, + LsfgImagePair& frames_, LsfgImage& motion_, LsfgImage& detail1_, + LsfgImage& detail2_) + : frames{&frames_}, motion{&motion_}, detail1{&detail1_}, detail2{&detail2_} { + using namespace VideoCore::FrameGen::PerformanceShader; + + pass = LsfgPass(device, shaders, GENERATE, + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {2, VK_DESCRIPTOR_TYPE_SAMPLER}, + {5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + + sampler = resources.GetSampler(); + edge_sampler = + resources.GetSampler(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, VK_COMPARE_OP_ALWAYS, false); + + const std::vector layouts( + LSFG_GENERATION_SLOTS * LSFG_MAX_TARGETS * 2, pass.SetLayout()); + owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts); + + size_t next = 0; + for (size_t slot = 0; slot < LSFG_GENERATION_SLOTS; ++slot) { + Generation& target = generations[slot]; + target.buffer = resources.GetBuffer(LsfgSlotTimestamp(slot)); + + for (auto& entry : target.targets) { + for (auto& set : entry.descriptor_sets) { + set = owned_sets[next++]; + } + } + } +} + +void LsfgGenerate::SetTarget(const Device& device, size_t slot, u32 target, VkImageView view) { + Target& entry = generations[slot].targets[target]; + if (entry.view == view) { + return; + } + entry.view = view; + + for (size_t i = 0; i < entry.descriptor_sets.size(); ++i) { + LsfgDescriptorWriter(entry.descriptor_sets[i]) + .AddUniformBuffer(generations[slot].buffer, LsfgResources::BufferSize()) + .AddSampler(sampler) + .AddSampler(edge_sampler) + .AddSampledImage((*frames)[1 - i]) + .AddSampledImage((*frames)[i]) + .AddSampledImage(*motion) + .AddSampledImage(*detail1) + .AddSampledImage(*detail2) + .AddStorageView(view) + .Build(device); + } +} + +void LsfgGenerate::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot, u32 target, + VkImage image, VkExtent2D extent) { + const Target& entry = generations[slot].targets[target]; + + LsfgBarriers(cmdbuf) + .WriteToReadAll(*frames) + .WriteToRead(*motion) + .WriteToRead(*detail1) + .WriteToRead(*detail2) + .DiscardToWrite(image) + .Build(); + + pass.Bind(cmdbuf, entry.descriptor_sets[frame_count % entry.descriptor_sets.size()]); + cmdbuf.Dispatch(GroupCount(extent.width), GroupCount(extent.height), 1); + + const std::array after{MakeTargetBarrier( + image, VK_ACCESS_SHADER_WRITE_BIT, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT, + VK_IMAGE_LAYOUT_GENERAL)}; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, {}, {}, after); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/lsfg_generate.h b/src/video_core/renderer_vulkan/present/lsfg_generate.h new file mode 100644 index 0000000000..75cbd8d2e4 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_generate.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" +#include "video_core/renderer_vulkan/present/lsfg_common.h" + +namespace Vulkan { + +class Device; +class LsfgShaders; + +class LsfgGenerate { +public: + LsfgGenerate() = default; + LsfgGenerate(const Device& device, const LsfgShaders& shaders, LsfgResources& resources, + vk::DescriptorPool& descriptor_pool, LsfgImagePair& frames, LsfgImage& motion, + LsfgImage& detail1, LsfgImage& detail2); + + void SetTarget(const Device& device, size_t slot, u32 target, VkImageView view); + + void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count, size_t slot, u32 target, + VkImage image, VkExtent2D extent); + +private: + struct Target { + std::array descriptor_sets{}; + VkImageView view{}; + }; + + struct Generation { + std::array targets{}; + VkBuffer buffer{}; + }; + + LsfgImagePair* frames{}; + LsfgImage* motion{}; + LsfgImage* detail1{}; + LsfgImage* detail2{}; + VkSampler sampler{}; + VkSampler edge_sampler{}; + + LsfgPass pass; + std::array generations{}; + vk::DescriptorSets owned_sets; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/lsfg_mipmaps.cpp b/src/video_core/renderer_vulkan/present/lsfg_mipmaps.cpp new file mode 100644 index 0000000000..3f31f58d68 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_mipmaps.cpp @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include "video_core/frame_gen/lossless_dll.h" +#include "video_core/renderer_vulkan/present/lsfg_mipmaps.h" +#include "video_core/renderer_vulkan/present/lsfg_shaders.h" +#include "video_core/renderer_vulkan/present/util.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +namespace { + +constexpr u32 DISPATCH_TILE_SHIFT = 6; + +[[nodiscard]] u32 GroupCount(u32 size) { + return (size + (1u << DISPATCH_TILE_SHIFT) - 1) >> DISPATCH_TILE_SHIFT; +} + +} // Anonymous namespace + +LsfgMipmaps::LsfgMipmaps(const Device& device, MemoryAllocator& memory_allocator, + const LsfgShaders& shaders, LsfgResources& resources, + vk::DescriptorPool& descriptor_pool, LsfgImagePair& frames_, + f32 flow_scale) + : frames{&frames_} { + using namespace VideoCore::FrameGen::PerformanceShader; + + pass = LsfgPass(device, shaders, MIPMAPS, + {{1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + {1, VK_DESCRIPTOR_TYPE_SAMPLER}, + {1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE}, + {LSFG_MIP_LEVELS, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE}}); + + const VkExtent2D input_extent = (*frames)[0].Extent(); + flow_extent = VkExtent2D{ + .width = std::max(1u, static_cast(static_cast(input_extent.width) * flow_scale)), + .height = std::max(1u, static_cast(static_cast(input_extent.height) * flow_scale)), + }; + + for (size_t i = 0; i < LSFG_MIP_LEVELS; ++i) { + const VkExtent2D level_extent{ + .width = flow_extent.width >> i, + .height = flow_extent.height >> i, + }; + out_images[i] = LsfgImage(device, memory_allocator, level_extent, LSFG_FLOW_FORMAT); + } + + const std::vector layouts(descriptor_sets.size(), pass.SetLayout()); + owned_sets = CreateWrappedDescriptorSets(descriptor_pool, layouts); + + const VkSampler sampler = resources.GetSampler(); + const VkBuffer buffer = resources.GetBuffer(); + + for (size_t i = 0; i < descriptor_sets.size(); ++i) { + descriptor_sets[i] = owned_sets[i]; + LsfgDescriptorWriter(descriptor_sets[i]) + .AddUniformBuffer(buffer, LsfgResources::BufferSize()) + .AddSampler(sampler) + .AddSampledImage((*frames)[i]) + .AddStorageImages(out_images) + .Build(device); + } +} + +void LsfgMipmaps::Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count) { + const size_t slot = frame_count % descriptor_sets.size(); + + LsfgBarriers(cmdbuf).WriteToRead((*frames)[slot]).ReadToWriteAll(out_images).Build(); + + pass.Bind(cmdbuf, descriptor_sets[slot]); + cmdbuf.Dispatch(GroupCount(flow_extent.width), GroupCount(flow_extent.height), 1); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/lsfg_mipmaps.h b/src/video_core/renderer_vulkan/present/lsfg_mipmaps.h new file mode 100644 index 0000000000..7fa5b7b224 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_mipmaps.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2025 lsfg-vk +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" +#include "video_core/renderer_vulkan/present/lsfg_common.h" + +namespace Vulkan { + +class Device; +class LsfgShaders; + +constexpr size_t LSFG_MIP_LEVELS = 7; + +class LsfgMipmaps { +public: + LsfgMipmaps() = default; + LsfgMipmaps(const Device& device, MemoryAllocator& memory_allocator, const LsfgShaders& shaders, + LsfgResources& resources, vk::DescriptorPool& descriptor_pool, + LsfgImagePair& frames, f32 flow_scale); + + void Dispatch(vk::CommandBuffer cmdbuf, u64 frame_count); + + [[nodiscard]] LsfgImage& Output(size_t level) { + return out_images[level]; + } + +private: + LsfgImagePair* frames{}; + + LsfgPass pass; + std::array descriptor_sets{}; + vk::DescriptorSets owned_sets; + + VkExtent2D flow_extent{}; + std::array out_images; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp b/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp new file mode 100644 index 0000000000..51c7e4c106 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_shaders.cpp @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "video_core/frame_gen/lossless_dll.h" +#include "video_core/renderer_vulkan/present/lsfg_shaders.h" +#include "video_core/renderer_vulkan/present/util.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +LsfgShaders::LsfgShaders(const Device& device) { + if (!device.IsVulkanMemoryModelSupported() || !device.HasNullDescriptor() || + !device.IsFloat16Supported()) { + return; + } + + VideoCore::FrameGen::ShaderModules code; + if (VideoCore::FrameGen::LoadShaderModules(code) != VideoCore::FrameGen::LosslessStatus::Ok) { + return; + } + + for (const auto& [id, words] : code) { + modules.emplace(id, CreateWrappedShaderModule(device, words)); + } + valid = true; +} + +VkShaderModule LsfgShaders::Get(u32 shader_id) const { + const auto hit = modules.find(shader_id); + return hit == modules.end() ? VK_NULL_HANDLE : *hit->second; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/lsfg_shaders.h b/src/video_core/renderer_vulkan/present/lsfg_shaders.h new file mode 100644 index 0000000000..cab37110d6 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/lsfg_shaders.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +class Device; + +class LsfgShaders { +public: + explicit LsfgShaders(const Device& device); + + [[nodiscard]] bool IsValid() const { + return valid; + } + + [[nodiscard]] VkShaderModule Get(u32 shader_id) const; + +private: + std::map modules; + bool valid{}; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/post_process.cpp b/src/video_core/renderer_vulkan/present/post_process.cpp new file mode 100644 index 0000000000..07d75b7025 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/post_process.cpp @@ -0,0 +1,899 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include + +#include "common/fs/fs.h" +#include "common/fs/fs_util.h" +#include "common/logging.h" +#include "video_core/post_processing/fx_chain.h" +#include "video_core/post_processing/fx_compile.h" +#include "video_core/post_processing/fx_effect.h" +#include "video_core/renderer_vulkan/present/post_process.h" +#include "video_core/renderer_vulkan/present/util.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +namespace { + +constexpr VkFormat BACKBUFFER_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT; +constexpr size_t NO_TEXTURE = ~size_t{0}; + +VkFormat ToVkFormat(reshadefx::texture_format format) { + switch (format) { + case reshadefx::texture_format::r8: + return VK_FORMAT_R8_UNORM; + case reshadefx::texture_format::r16f: + return VK_FORMAT_R16_SFLOAT; + case reshadefx::texture_format::r32f: + return VK_FORMAT_R32_SFLOAT; + case reshadefx::texture_format::rg8: + return VK_FORMAT_R8G8_UNORM; + case reshadefx::texture_format::rg16: + return VK_FORMAT_R16G16_UNORM; + case reshadefx::texture_format::rg16f: + return VK_FORMAT_R16G16_SFLOAT; + case reshadefx::texture_format::rg32f: + return VK_FORMAT_R32G32_SFLOAT; + case reshadefx::texture_format::rgba8: + return VK_FORMAT_R8G8B8A8_UNORM; + case reshadefx::texture_format::rgba16: + return VK_FORMAT_R16G16B16A16_UNORM; + case reshadefx::texture_format::rgba16f: + return VK_FORMAT_R16G16B16A16_SFLOAT; + case reshadefx::texture_format::rgba32f: + return VK_FORMAT_R32G32B32A32_SFLOAT; + case reshadefx::texture_format::rgb10a2: + return VK_FORMAT_A2B10G10R10_UNORM_PACK32; + default: + return VK_FORMAT_R8G8B8A8_UNORM; + } +} + +VkSamplerAddressMode ToAddressMode(reshadefx::texture_address_mode mode) { + switch (mode) { + case reshadefx::texture_address_mode::wrap: + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + case reshadefx::texture_address_mode::mirror: + return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + case reshadefx::texture_address_mode::border: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + case reshadefx::texture_address_mode::clamp: + default: + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + } +} + +VkBlendFactor ToBlendFactor(reshadefx::blend_factor func) { + switch (func) { + case reshadefx::blend_factor::zero: + return VK_BLEND_FACTOR_ZERO; + case reshadefx::blend_factor::source_color: + return VK_BLEND_FACTOR_SRC_COLOR; + case reshadefx::blend_factor::source_alpha: + return VK_BLEND_FACTOR_SRC_ALPHA; + case reshadefx::blend_factor::one_minus_source_color: + return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; + case reshadefx::blend_factor::one_minus_source_alpha: + return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + case reshadefx::blend_factor::dest_color: + return VK_BLEND_FACTOR_DST_COLOR; + case reshadefx::blend_factor::dest_alpha: + return VK_BLEND_FACTOR_DST_ALPHA; + case reshadefx::blend_factor::one_minus_dest_color: + return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR; + case reshadefx::blend_factor::one_minus_dest_alpha: + return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA; + case reshadefx::blend_factor::one: + default: + return VK_BLEND_FACTOR_ONE; + } +} + +VkBlendOp ToBlendOp(reshadefx::blend_op op) { + switch (op) { + case reshadefx::blend_op::subtract: + return VK_BLEND_OP_SUBTRACT; + case reshadefx::blend_op::reverse_subtract: + return VK_BLEND_OP_REVERSE_SUBTRACT; + case reshadefx::blend_op::min: + return VK_BLEND_OP_MIN; + case reshadefx::blend_op::max: + return VK_BLEND_OP_MAX; + case reshadefx::blend_op::add: + default: + return VK_BLEND_OP_ADD; + } +} + +VkPrimitiveTopology ToTopology(reshadefx::primitive_topology topology) { + switch (topology) { + case reshadefx::primitive_topology::point_list: + return VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + case reshadefx::primitive_topology::line_list: + return VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + case reshadefx::primitive_topology::line_strip: + return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; + case reshadefx::primitive_topology::triangle_strip: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + case reshadefx::primitive_topology::triangle_list: + default: + return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + } +} + +vk::RenderPass CreateFxRenderPass(const Device& device, VkFormat format, bool clear) { + VkAttachmentLoadOp load_op = VK_ATTACHMENT_LOAD_OP_LOAD; + VkImageLayout initial_layout = VK_IMAGE_LAYOUT_GENERAL; + if (clear) { + load_op = VK_ATTACHMENT_LOAD_OP_CLEAR; + initial_layout = VK_IMAGE_LAYOUT_UNDEFINED; + } + + const VkAttachmentDescription attachment{ + .flags = 0, + .format = format, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = load_op, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = initial_layout, + .finalLayout = VK_IMAGE_LAYOUT_GENERAL, + }; + + const VkAttachmentReference reference{ + .attachment = 0, + .layout = VK_IMAGE_LAYOUT_GENERAL, + }; + + const VkSubpassDescription subpass{ + .flags = 0, + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .inputAttachmentCount = 0, + .pInputAttachments = nullptr, + .colorAttachmentCount = 1, + .pColorAttachments = &reference, + .pResolveAttachments = nullptr, + .pDepthStencilAttachment = nullptr, + .preserveAttachmentCount = 0, + .pPreserveAttachments = nullptr, + }; + + return device.GetLogical().CreateRenderPass(VkRenderPassCreateInfo{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .attachmentCount = 1, + .pAttachments = &attachment, + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 0, + .pDependencies = nullptr, + }); +} + +vk::Pipeline CreateFxPipeline(const Device& device, vk::RenderPass& renderpass, + vk::PipelineLayout& layout, VkShaderModule vertex_shader, + VkShaderModule fragment_shader, + const reshadefx::pass& pass) { + const std::array stages{{ + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stage = VK_SHADER_STAGE_VERTEX_BIT, + .module = vertex_shader, + .pName = pass.vs_entry_point.c_str(), + .pSpecializationInfo = nullptr, + }, + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = fragment_shader, + .pName = pass.ps_entry_point.c_str(), + .pSpecializationInfo = nullptr, + }, + }}; + + constexpr VkPipelineVertexInputStateCreateInfo vertex_input{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .vertexBindingDescriptionCount = 0, + .pVertexBindingDescriptions = nullptr, + .vertexAttributeDescriptionCount = 0, + .pVertexAttributeDescriptions = nullptr, + }; + + const VkPipelineInputAssemblyStateCreateInfo input_assembly{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .topology = ToTopology(pass.topology), + .primitiveRestartEnable = VK_FALSE, + }; + + constexpr VkPipelineViewportStateCreateInfo viewport_state{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .viewportCount = 1, + .pViewports = nullptr, + .scissorCount = 1, + .pScissors = nullptr, + }; + + constexpr VkPipelineRasterizationStateCreateInfo rasterization{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .depthClampEnable = VK_FALSE, + .rasterizerDiscardEnable = VK_FALSE, + .polygonMode = VK_POLYGON_MODE_FILL, + .cullMode = VK_CULL_MODE_NONE, + .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, + .depthBiasEnable = VK_FALSE, + .depthBiasConstantFactor = 0.0f, + .depthBiasClamp = 0.0f, + .depthBiasSlopeFactor = 0.0f, + .lineWidth = 1.0f, + }; + + constexpr VkPipelineMultisampleStateCreateInfo multisampling{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + .sampleShadingEnable = VK_FALSE, + .minSampleShading = 0.0f, + .pSampleMask = nullptr, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, + }; + + VkBool32 blend_enable = VK_FALSE; + if (pass.blend_enable[0]) { + blend_enable = VK_TRUE; + } + + const VkPipelineColorBlendAttachmentState blending{ + .blendEnable = blend_enable, + .srcColorBlendFactor = ToBlendFactor(pass.source_color_blend_factor[0]), + .dstColorBlendFactor = ToBlendFactor(pass.dest_color_blend_factor[0]), + .colorBlendOp = ToBlendOp(pass.color_blend_op[0]), + .srcAlphaBlendFactor = ToBlendFactor(pass.source_alpha_blend_factor[0]), + .dstAlphaBlendFactor = ToBlendFactor(pass.dest_alpha_blend_factor[0]), + .alphaBlendOp = ToBlendOp(pass.alpha_blend_op[0]), + .colorWriteMask = static_cast(pass.render_target_write_mask[0] & 0xF), + }; + + const VkPipelineColorBlendStateCreateInfo color_blend{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .logicOpEnable = VK_FALSE, + .logicOp = VK_LOGIC_OP_COPY, + .attachmentCount = 1, + .pAttachments = &blending, + .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f}, + }; + + constexpr std::array dynamic_states{ + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR, + }; + + const VkPipelineDynamicStateCreateInfo dynamic_state{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .dynamicStateCount = static_cast(dynamic_states.size()), + .pDynamicStates = dynamic_states.data(), + }; + + return device.GetLogical().CreateGraphicsPipeline(VkGraphicsPipelineCreateInfo{ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &vertex_input, + .pInputAssemblyState = &input_assembly, + .pTessellationState = nullptr, + .pViewportState = &viewport_state, + .pRasterizationState = &rasterization, + .pMultisampleState = &multisampling, + .pDepthStencilState = nullptr, + .pColorBlendState = &color_blend, + .pDynamicState = &dynamic_state, + .layout = *layout, + .renderPass = *renderpass, + .subpass = 0, + .basePipelineHandle = nullptr, + .basePipelineIndex = 0, + }); +} + +} // Anonymous namespace + +PostProcessChain::PostProcessChain(const Device& device, MemoryAllocator& allocator, + Scheduler& scheduler, size_t image_count, VkExtent2D extent) + : m_extent(extent) + , m_image_count(u32(image_count)) +{ + m_start = std::chrono::steady_clock::now(); + m_previous = m_start; + + CreatePingPongImages(device, allocator); + + m_fallback_sampler = CreateWrappedSampler(device); + m_fallback_image = CreateWrappedImage(allocator, VkExtent2D{1, 1}, VK_FORMAT_R8G8B8A8_UNORM); + m_fallback_view = CreateWrappedImageView(device, m_fallback_image, VK_FORMAT_R8G8B8A8_UNORM); + + if (!BuildEffects(device, allocator, scheduler)) { + m_effects.clear(); + } +} + +PostProcessChain::~PostProcessChain() = default; + +bool PostProcessChain::Empty() const { + return m_effects.empty(); +} + +void PostProcessChain::CreatePingPongImages(const Device& device, MemoryAllocator& allocator) { + m_frames.resize(m_image_count); + for (auto& frame : m_frames) { + for (size_t i = 0; i < frame.images.size(); ++i) { + frame.images[i] = CreateWrappedImage(allocator, m_extent, BACKBUFFER_FORMAT); + frame.views[i] = CreateWrappedImageView(device, frame.images[i], BACKBUFFER_FORMAT); + } + } +} + +bool PostProcessChain::BuildEffects(const Device& device, MemoryAllocator& allocator, + Scheduler& scheduler) { + const auto snapshot = VideoCore::FxChain::Instance().Snapshot(); + if (snapshot.entries.empty()) { + return true; + } + + const auto root = VideoCore::GetFxRootDirectory(); + + for (size_t entry_index = 0; entry_index < snapshot.entries.size(); ++entry_index) { + const auto& entry = snapshot.entries[entry_index]; + const auto path = root / entry.file; + + const auto compiled = VideoCore::CompileFxEffect(path, m_extent.width, m_extent.height, 8); + if (!compiled.Succeeded()) { + LOG_ERROR(Render_Vulkan, "Post-processing effect '{}' failed to compile:\n{}", + entry.file, compiled.error); + continue; + } + + const auto& module = compiled.module; + + const auto technique = std::find_if( + module.techniques.begin(), module.techniques.end(), + [&](const reshadefx::technique& t) { return t.name == entry.technique; }); + if (technique == module.techniques.end()) { + LOG_ERROR(Render_Vulkan, "Effect '{}' has no technique '{}'", entry.file, + entry.technique); + continue; + } + + Effect effect; + effect.entry_index = entry_index; + effect.file = entry.file; + effect.uniform_size = module.total_uniform_size; + for (const auto& [name, words] : compiled.entry_points) { + effect.shaders.emplace(name, CreateWrappedShaderModule(device, words)); + } + + for (const auto& texture : module.textures) { + Texture out; + out.name = texture.unique_name; + out.extent = VkExtent2D{texture.width, texture.height}; + out.format = ToVkFormat(texture.format); + + if (texture.semantic == "COLOR") { + out.is_backbuffer = true; + effect.textures.push_back(std::move(out)); + continue; + } + if (texture.semantic == "DEPTH") { + effect.textures.push_back(std::move(out)); + continue; + } + + out.image = CreateWrappedImage(allocator, out.extent, out.format); + out.view = CreateWrappedImageView(device, out.image, out.format); + effect.textures.push_back(std::move(out)); + } + + for (const auto& sampler : module.samplers) { + Sampler out; + out.texture_index = NO_TEXTURE; + for (size_t i = 0; i < effect.textures.size(); ++i) { + if (effect.textures[i].name == sampler.texture_name) { + out.texture_index = i; + break; + } + } + + VkFilter mag_filter = VK_FILTER_LINEAR; + VkFilter min_filter = VK_FILTER_LINEAR; + VkSamplerMipmapMode mip_mode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + const u32 filter = static_cast(sampler.filter); + if ((filter & 0x10) == 0) { + min_filter = VK_FILTER_NEAREST; + } + if ((filter & 0x04) == 0) { + mag_filter = VK_FILTER_NEAREST; + } + if ((filter & 0x01) == 0) { + mip_mode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + } + + out.sampler = device.GetLogical().CreateSampler(VkSamplerCreateInfo{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .magFilter = mag_filter, + .minFilter = min_filter, + .mipmapMode = mip_mode, + .addressModeU = ToAddressMode(sampler.address_u), + .addressModeV = ToAddressMode(sampler.address_v), + .addressModeW = ToAddressMode(sampler.address_w), + .mipLodBias = sampler.lod_bias, + .anisotropyEnable = VK_FALSE, + .maxAnisotropy = 1.0f, + .compareEnable = VK_FALSE, + .compareOp = VK_COMPARE_OP_NEVER, + .minLod = sampler.min_lod, + .maxLod = sampler.max_lod, + .borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, + .unnormalizedCoordinates = VK_FALSE, + }); + + effect.samplers.push_back(std::move(out)); + } + + for (const auto& uniform : module.uniforms) { + UniformWrite write; + write.name = uniform.name; + write.offset = uniform.offset; + write.components = std::min(uniform.type.components(), 4); + write.kind = UniformKind::Floating; + if (uniform.type.is_boolean()) { + write.kind = UniformKind::Boolean; + } else if (uniform.type.is_integral()) { + write.kind = UniformKind::Integer; + } + + for (const auto& annotation : uniform.annotations) { + if (annotation.name != "source") { + continue; + } + const std::string& source = annotation.value.string_data; + if (source == "frametime") { + write.source = UniformSource::FrameTime; + } else if (source == "framecount") { + write.source = UniformSource::FrameCount; + } else if (source == "timer") { + write.source = UniformSource::Timer; + } else if (source == "random") { + write.source = UniformSource::Random; + } else if (source == "pingpong") { + write.source = UniformSource::PingPong; + } + } + + if (uniform.has_initializer_value) { + for (u32 i = 0; i < write.components; ++i) { + if (write.kind == UniformKind::Floating) { + write.fallback[i] = uniform.initializer_value.as_float[i]; + } else { + write.fallback[i] = static_cast(uniform.initializer_value.as_int[i]); + } + } + } + + write.args = {0.0f, 1.0f, 1.0f, 0.0f}; + for (const auto& annotation : uniform.annotations) { + if (annotation.name == "min" && annotation.type.is_floating_point()) { + write.args[0] = annotation.value.as_float[0]; + } + if (annotation.name == "max" && annotation.type.is_floating_point()) { + write.args[1] = annotation.value.as_float[0]; + } + if (annotation.name == "step" && annotation.type.is_floating_point()) { + write.args[2] = annotation.value.as_float[0]; + } + } + write.state = write.args[0]; + + effect.uniforms.push_back(std::move(write)); + } + + effect.uniform_layout = CreateWrappedDescriptorSetLayout( + device, std::array{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT); + + for (const auto& pass : technique->passes) { + Pass out; + out.num_vertices = pass.num_vertices; + out.clear = pass.clear_render_targets != 0; + out.target_texture = NO_TEXTURE; + out.extent = m_extent; + + const std::string& target = pass.render_target_names[0]; + if (target.empty()) { + out.writes_backbuffer = true; + } else { + for (size_t i = 0; i < effect.textures.size(); ++i) { + if (effect.textures[i].name == target) { + out.target_texture = i; + out.extent = effect.textures[i].extent; + break; + } + } + if (out.target_texture == NO_TEXTURE) { + LOG_WARNING(Render_Vulkan, "Effect '{}' pass targets unknown texture '{}'", + entry.file, target); + out.writes_backbuffer = true; + } + } + + if (pass.viewport_width != 0 && pass.viewport_height != 0) { + out.extent = VkExtent2D{pass.viewport_width, pass.viewport_height}; + } + + VkFormat target_format = BACKBUFFER_FORMAT; + if (!out.writes_backbuffer) { + target_format = effect.textures[out.target_texture].format; + } + + u32 binding_count = 0; + for (const auto& binding : pass.sampler_bindings) { + SamplerBinding entry_binding; + entry_binding.binding = binding.entry_point_binding; + entry_binding.sampler_index = binding.index; + out.sampler_bindings.push_back(entry_binding); + binding_count = std::max(binding_count, binding.entry_point_binding + 1); + } + + const std::vector sampler_types( + std::max(binding_count, 1), VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + out.sampler_layout = CreateWrappedDescriptorSetLayout( + device, sampler_types, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT); + + const std::array set_layouts{*effect.uniform_layout, *out.sampler_layout}; + out.pipeline_layout = + device.GetLogical().CreatePipelineLayout(VkPipelineLayoutCreateInfo{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .setLayoutCount = static_cast(set_layouts.size()), + .pSetLayouts = set_layouts.data(), + .pushConstantRangeCount = 0, + .pPushConstantRanges = nullptr, + }); + + const auto vertex_shader = effect.shaders.find(pass.vs_entry_point); + const auto fragment_shader = effect.shaders.find(pass.ps_entry_point); + if (vertex_shader == effect.shaders.end() || + fragment_shader == effect.shaders.end()) { + LOG_WARNING(Render_Vulkan, "Effect '{}' pass references a missing entry point", + entry.file); + continue; + } + + out.renderpass = CreateFxRenderPass(device, target_format, out.clear); + out.pipeline = CreateFxPipeline(device, out.renderpass, out.pipeline_layout, + *vertex_shader->second, *fragment_shader->second, pass); + + if (out.writes_backbuffer) { + out.backbuffer_slot = static_cast(effect.backbuffer_pass_count % 2); + ++effect.backbuffer_pass_count; + for (u32 image = 0; image < m_image_count; ++image) { + for (size_t slot = 0; slot < 2; ++slot) { + out.framebuffers.push_back(CreateWrappedFramebuffer( + device, out.renderpass, m_frames[image].views[slot], out.extent)); + } + } + } else { + out.framebuffers.push_back( + CreateWrappedFramebuffer(device, out.renderpass, + effect.textures[out.target_texture].view, out.extent)); + } + + effect.passes.push_back(std::move(out)); + } + + if (effect.passes.empty()) { + LOG_WARNING(Render_Vulkan, "Effect '{}' technique '{}' has no passes", entry.file, + entry.technique); + continue; + } + + const u32 buffer_size = std::max(effect.uniform_size, 4); + for (u32 i = 0; i < m_image_count; ++i) { + effect.uniform_buffers.push_back( + CreateWrappedBuffer(allocator, buffer_size, MemoryUsage::Upload)); + } + + size_t sampler_descriptor_count = 0; + size_t sampler_set_count = 0; + for (const auto& pass : effect.passes) { + sampler_descriptor_count += + m_image_count * std::max(pass.sampler_bindings.size(), 1); + sampler_set_count += m_image_count; + } + + effect.descriptor_pool = CreateWrappedDescriptorPool( + device, m_image_count + sampler_descriptor_count, m_image_count + sampler_set_count, + {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); + + const std::vector uniform_layouts(m_image_count, + *effect.uniform_layout); + effect.uniform_sets = CreateWrappedDescriptorSets(effect.descriptor_pool, uniform_layouts); + + for (auto& pass : effect.passes) { + const std::vector layouts(m_image_count, *pass.sampler_layout); + pass.sampler_sets = CreateWrappedDescriptorSets(effect.descriptor_pool, layouts); + } + + m_effects.push_back(std::move(effect)); + } + + return true; +} + +void PostProcessChain::PrepareImages(const Device& device, Scheduler& scheduler) { + if (m_images_ready) { + return; + } + + scheduler.Record([this](vk::CommandBuffer cmdbuf) { + ClearColorImage(cmdbuf, *m_fallback_image); + for (auto& frame : m_frames) { + for (auto& image : frame.images) { + ClearColorImage(cmdbuf, *image); + } + } + for (auto& effect : m_effects) { + for (auto& texture : effect.textures) { + if (texture.image) { + ClearColorImage(cmdbuf, *texture.image); + } + } + } + }); + scheduler.Finish(); + + m_images_ready = true; +} + +void PostProcessChain::UpdateUniforms(Effect& effect, size_t image_index, f32 delta_seconds) { + if (effect.uniform_size == 0) { + return; + } + + static thread_local std::mt19937 rng{std::random_device{}()}; + + std::vector staging(effect.uniform_size, 0); + const f32 elapsed = + std::chrono::duration(std::chrono::steady_clock::now() - m_start).count(); + const auto overrides = VideoCore::FxChain::Instance().EntryValues(effect.entry_index); + + for (auto& uniform : effect.uniforms) { + std::array value = uniform.fallback; + + const auto override = overrides.find(uniform.name); + if (override != overrides.end()) { + value = override->second; + } + + switch (uniform.source) { + case UniformSource::FrameTime: + value[0] = delta_seconds * 1000.0f; + break; + case UniformSource::FrameCount: + value[0] = static_cast(m_frame_count); + break; + case UniformSource::Timer: + value[0] = elapsed * 1000.0f; + break; + case UniformSource::Random: { + const int low = static_cast(uniform.args[0]); + int high = static_cast(uniform.args[1]); + if (high <= low) { + high = low + 1; + } + std::uniform_int_distribution dist(low, high); + value[0] = static_cast(dist(rng)); + break; + } + case UniformSource::PingPong: { + const f32 min_value = uniform.args[0]; + f32 max_value = uniform.args[1]; + if (max_value <= min_value) { + max_value = min_value + 1.0f; + } + f32 step = uniform.args[2]; + if (step == 0.0f) { + step = 1.0f; + } + uniform.state += uniform.direction * step * delta_seconds; + if (uniform.state >= max_value) { + uniform.state = max_value; + uniform.direction = -1.0f; + } + if (uniform.state <= min_value) { + uniform.state = min_value; + uniform.direction = 1.0f; + } + value[0] = uniform.state; + value[1] = uniform.direction; + break; + } + case UniformSource::Value: + default: + break; + } + + for (u32 i = 0; i < uniform.components; ++i) { + const size_t offset = uniform.offset + i * sizeof(u32); + if (offset + sizeof(u32) > staging.size()) { + break; + } + if (uniform.kind == UniformKind::Floating) { + const f32 element = value[i]; + std::memcpy(staging.data() + offset, &element, sizeof(f32)); + } else { + const s32 element = static_cast(value[i]); + std::memcpy(staging.data() + offset, &element, sizeof(s32)); + } + } + } + + const std::span mapped = effect.uniform_buffers[image_index].Mapped(); + if (mapped.size() >= staging.size()) { + std::memcpy(mapped.data(), staging.data(), staging.size()); + effect.uniform_buffers[image_index].Flush(); + } +} + +void PostProcessChain::UpdateDescriptors(const Device& device, Effect& effect, Pass& pass, + size_t image_index, VkImageView backbuffer_view) { + std::vector image_infos; + std::vector writes; + image_infos.reserve(pass.sampler_bindings.size() + 1); + + const VkDescriptorSet sampler_set = pass.sampler_sets[image_index]; + + for (const auto& binding : pass.sampler_bindings) { + VkImageView view = *m_fallback_view; + VkSampler handle = *m_fallback_sampler; + + if (binding.sampler_index < effect.samplers.size()) { + const Sampler& sampler = effect.samplers[binding.sampler_index]; + if (sampler.sampler) { + handle = *sampler.sampler; + } + if (sampler.texture_index != NO_TEXTURE) { + const Texture& texture = effect.textures[sampler.texture_index]; + if (texture.is_backbuffer) { + view = backbuffer_view; + } else if (texture.view) { + view = *texture.view; + } + } + } + + writes.push_back( + CreateWriteDescriptorSet(image_infos, handle, view, sampler_set, binding.binding)); + } + + const VkDescriptorBufferInfo buffer_info{ + .buffer = *effect.uniform_buffers[image_index], + .offset = 0, + .range = VK_WHOLE_SIZE, + }; + + writes.push_back(VkWriteDescriptorSet{ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .pNext = nullptr, + .dstSet = effect.uniform_sets[image_index], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + .pImageInfo = nullptr, + .pBufferInfo = &buffer_info, + .pTexelBufferView = nullptr, + }); + + device.GetLogical().UpdateDescriptorSets(writes, {}); +} + +void PostProcessChain::Draw(const Device& device, Scheduler& scheduler, size_t image_index, + VkImage* inout_image, VkImageView* inout_image_view) { + if (m_effects.empty()) { + return; + } + + PrepareImages(device, scheduler); + + const auto now = std::chrono::steady_clock::now(); + const f32 delta_seconds = std::chrono::duration(now - m_previous).count(); + m_previous = now; + ++m_frame_count; + + FrameImages& frame = m_frames[image_index]; + VkImage current_image = *inout_image; + VkImageView current_view = *inout_image_view; + u32 slot = 0; + + for (auto& effect : m_effects) { + UpdateUniforms(effect, image_index, delta_seconds); + + for (size_t pass_index = 0; pass_index < effect.passes.size(); ++pass_index) { + Pass& pass = effect.passes[pass_index]; + + UpdateDescriptors(device, effect, pass, image_index, current_view); + + VkFramebuffer framebuffer{}; + VkImage target_image{}; + if (pass.writes_backbuffer) { + const u32 target_slot = (slot + 1) % 2; + framebuffer = *pass.framebuffers[image_index * 2 + target_slot]; + target_image = *frame.images[target_slot]; + } else { + framebuffer = *pass.framebuffers[0]; + target_image = *effect.textures[pass.target_texture].image; + } + + const VkImage source_image = current_image; + const VkRenderPass renderpass = *pass.renderpass; + const VkPipeline pipeline = *pass.pipeline; + const VkPipelineLayout layout = *pass.pipeline_layout; + const VkDescriptorSet uniform_set = effect.uniform_sets[image_index]; + const VkDescriptorSet sampler_set = pass.sampler_sets[image_index]; + const VkExtent2D extent = pass.extent; + const u32 vertices = pass.num_vertices; + + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([=](vk::CommandBuffer cmdbuf) { + TransitionImageLayout(cmdbuf, source_image, VK_IMAGE_LAYOUT_GENERAL); + TransitionImageLayout(cmdbuf, target_image, VK_IMAGE_LAYOUT_GENERAL); + BeginRenderPass(cmdbuf, renderpass, framebuffer, extent); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, + std::array{uniform_set, sampler_set}, {}); + cmdbuf.Draw(vertices, 1, 0, 0); + cmdbuf.EndRenderPass(); + TransitionImageLayout(cmdbuf, target_image, VK_IMAGE_LAYOUT_GENERAL); + }); + + if (pass.writes_backbuffer) { + slot = (slot + 1) % 2; + current_image = *frame.images[slot]; + current_view = *frame.views[slot]; + } + } + } + + *inout_image = current_image; + *inout_image_view = current_view; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/post_process.h b/src/video_core/renderer_vulkan/present/post_process.h new file mode 100644 index 0000000000..9d42bd4751 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/post_process.h @@ -0,0 +1,140 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "common/common_types.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +class Device; +class Scheduler; + +class PostProcessChain { +public: + explicit PostProcessChain(const Device& device, MemoryAllocator& allocator, Scheduler& scheduler, + size_t image_count, VkExtent2D extent); + ~PostProcessChain(); + + void Draw(const Device& device, Scheduler& scheduler, size_t image_index, VkImage* inout_image, + VkImageView* inout_image_view); + + bool Empty() const; + +private: + enum class UniformKind : u32 { + Boolean, + Integer, + Floating, + }; + + enum class UniformSource : u32 { + Value, + FrameTime, + FrameCount, + Timer, + Random, + PingPong, + }; + + struct UniformWrite { + std::string name; + u32 offset{}; + u32 components{}; + UniformKind kind{UniformKind::Floating}; + UniformSource source{UniformSource::Value}; + std::array fallback{}; + std::array args{}; + f32 state{}; + f32 direction{1.0f}; + }; + + struct Texture { + std::string name; + vk::Image image{}; + vk::ImageView view{}; + VkExtent2D extent{}; + VkFormat format{}; + bool is_backbuffer{}; + }; + + struct Sampler { + vk::Sampler sampler{}; + size_t texture_index{}; + }; + + struct SamplerBinding { + u32 binding{}; + size_t sampler_index{}; + }; + + struct Pass { + vk::RenderPass renderpass{}; + vk::Pipeline pipeline{}; + vk::DescriptorSetLayout sampler_layout{}; + vk::PipelineLayout pipeline_layout{}; + vk::DescriptorSets sampler_sets{}; + std::vector sampler_bindings{}; + std::vector framebuffers{}; + size_t target_texture{}; + VkExtent2D extent{}; + u32 num_vertices{3}; + bool clear{}; + bool writes_backbuffer{}; + u32 backbuffer_slot{}; + }; + + struct Effect { + size_t entry_index{}; + std::string file{}; + std::map shaders{}; + std::vector textures{}; + std::vector samplers{}; + std::vector passes{}; + std::vector uniforms{}; + u32 uniform_size{}; + std::vector uniform_buffers{}; + vk::DescriptorSetLayout uniform_layout{}; + vk::DescriptorPool descriptor_pool{}; + vk::DescriptorSets uniform_sets{}; + size_t backbuffer_pass_count{}; + u32 backbuffer_slots{1}; + }; + + struct FrameImages { + std::array images{}; + std::array views{}; + }; + + bool BuildEffects(const Device& device, MemoryAllocator& allocator, Scheduler& scheduler); + void CreatePingPongImages(const Device& device, MemoryAllocator& allocator); + void PrepareImages(const Device& device, Scheduler& scheduler); + void UpdateUniforms(Effect& effect, size_t image_index, f32 delta_seconds); + void UpdateDescriptors(const Device& device, Effect& effect, Pass& pass, size_t image_index, + VkImageView backbuffer_view); + + const VkExtent2D m_extent; + const u32 m_image_count; + + std::vector m_effects{}; + std::vector m_frames{}; + + vk::Sampler m_fallback_sampler{}; + vk::Image m_fallback_image{}; + vk::ImageView m_fallback_view{}; + + std::chrono::steady_clock::time_point m_start{}; + std::chrono::steady_clock::time_point m_previous{}; + u64 m_frame_count{}; + bool m_images_ready{}; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/sgsr.cpp b/src/video_core/renderer_vulkan/present/sgsr.cpp new file mode 100644 index 0000000000..fa37225e43 --- /dev/null +++ b/src/video_core/renderer_vulkan/present/sgsr.cpp @@ -0,0 +1,146 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "common/common_types.h" +#include "common/div_ceil.h" +#include "common/settings.h" + +//#include "video_core/sgsr.h" +#include "video_core/host_shaders/sgsr1_shader_mobile_frag_spv.h" +#include "video_core/host_shaders/sgsr1_shader_mobile_edge_direction_frag_spv.h" +#include "video_core/host_shaders/sgsr1_shader_vert_spv.h" +#include "video_core/renderer_vulkan/present/sgsr.h" +#include "video_core/renderer_vulkan/present/util.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +using PushConstants = std::array; + +SGSR::SGSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, VkExtent2D extent, bool edge_dir) + : m_memory_allocator{memory_allocator} + , m_image_count{image_count} + , m_extent{extent} + , m_edge_dir{edge_dir} +{ + // Not finished yet initializing at ctor time? + m_dynamic_images.resize(m_image_count); + for (auto& images : m_dynamic_images) { + images.image = CreateWrappedImage(m_memory_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); + images.image_view = CreateWrappedImageView(device, images.image, VK_FORMAT_R16G16B16A16_SFLOAT); + } + + m_renderpass = CreateWrappedRenderPass(device, VK_FORMAT_R16G16B16A16_SFLOAT); + for (auto& images : m_dynamic_images) + images.framebuffer = CreateWrappedFramebuffer(device, m_renderpass, images.image_view, m_extent); + + m_sampler = CreateBilinearSampler(device); + m_vert_shader = BuildShader(device, SGSR1_SHADER_VERT_SPV); + m_stage_shader = m_edge_dir + ? BuildShader(device, SGSR1_SHADER_MOBILE_EDGE_DIRECTION_FRAG_SPV) + : BuildShader(device, SGSR1_SHADER_MOBILE_FRAG_SPV); + // 2 descriptors, 2 descriptor sets per invocation + m_descriptor_pool = CreateWrappedDescriptorPool(device, m_image_count, m_image_count); + m_descriptor_set_layout = CreateWrappedDescriptorSetLayout(device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); + + VkDescriptorSetLayout layout = *m_descriptor_set_layout; + for (auto& images : m_dynamic_images) + images.descriptor_sets = CreateWrappedDescriptorSets(m_descriptor_pool, layout); + + const VkPushConstantRange range{ + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, + .offset = 0, + .size = sizeof(PushConstants), + }; + VkPipelineLayoutCreateInfo ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .setLayoutCount = 1, + .pSetLayouts = m_descriptor_set_layout.address(), + .pushConstantRangeCount = 1, + .pPushConstantRanges = &range, + }; + m_pipeline_layout = device.GetLogical().CreatePipelineLayout(ci); + m_stage_pipeline = CreateWrappedPipeline(device, m_renderpass, m_pipeline_layout, std::tie(m_vert_shader, m_stage_shader)); +} + +void SGSR::UpdateDescriptorSets(const Device& device, VkImageView image_view, size_t image_index) { + Images& images = m_dynamic_images[image_index]; + std::vector image_infos; + std::vector updates; + image_infos.reserve(1); + updates.push_back(CreateWriteDescriptorSet(image_infos, *m_sampler, image_view, images.descriptor_sets[0], 0)); + device.GetLogical().UpdateDescriptorSets(updates, {}); +} + +void SGSR::UploadImages(const Device& device, Scheduler& scheduler) { + if (!m_images_ready) { + scheduler.Record([&](vk::CommandBuffer cmdbuf) { + for (auto& image : m_dynamic_images) + ClearColorImage(cmdbuf, *image.image); + }); + scheduler.Finish(); + m_images_ready = true; + } +} + +VkImageView SGSR::Draw(const Device& device, Scheduler& scheduler, size_t image_index, VkImage source_image, VkImageView source_image_view, VkExtent2D input_image_extent, const Common::Rectangle& crop_rect) { + Images& images = m_dynamic_images[image_index]; + auto const output_image = *images.image; + auto const descriptor_set = images.descriptor_sets[0]; + auto const framebuffer = *images.framebuffer; + auto const pipeline = *m_stage_pipeline; + + VkPipelineLayout layout = *m_pipeline_layout; + VkRenderPass renderpass = *m_renderpass; + VkExtent2D extent = m_extent; + + const f32 input_image_width = f32(input_image_extent.width); + const f32 input_image_height = f32(input_image_extent.height); + const f32 crop_width = (crop_rect.right - crop_rect.left) * input_image_width; + const f32 crop_height = (crop_rect.bottom - crop_rect.top) * input_image_height; + static constexpr f32 EDGE_SHARPNESS_MAX = 2.0f; + const f32 edge_sharpness = + EDGE_SHARPNESS_MAX - f32(Settings::values.fsr_sharpening_slider.GetValue()) / 200.0f; + + // layout( push_constant ) uniform constants { + // highp vec4 ViewportInfo[1]; + // highp vec2 ResizeFactor; + // highp vec2 CropOffset; + // highp float EdgeSharpness; + // }; + PushConstants viewport_con{}; + viewport_con[0] = std::bit_cast(1.f / input_image_width); + viewport_con[1] = std::bit_cast(1.f / input_image_height); + viewport_con[2] = std::bit_cast(input_image_width); + viewport_con[3] = std::bit_cast(input_image_height); + viewport_con[4] = std::bit_cast(crop_width / input_image_width); + viewport_con[5] = std::bit_cast(crop_height / input_image_height); + viewport_con[6] = std::bit_cast((std::min)(crop_rect.left, crop_rect.right)); + viewport_con[7] = std::bit_cast((std::min)(crop_rect.top, crop_rect.bottom)); + viewport_con[8] = std::bit_cast(edge_sharpness); + + UploadImages(device, scheduler); + UpdateDescriptorSets(device, source_image_view, image_index); + + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([=](vk::CommandBuffer cmdbuf) { + TransitionImageLayout(cmdbuf, source_image, VK_IMAGE_LAYOUT_GENERAL); + TransitionImageLayout(cmdbuf, output_image, VK_IMAGE_LAYOUT_GENERAL); + BeginRenderPass(cmdbuf, renderpass, framebuffer, extent); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, {}); + cmdbuf.PushConstants(layout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, viewport_con); + cmdbuf.Draw(3, 1, 0, 0); + cmdbuf.EndRenderPass(); + TransitionImageLayout(cmdbuf, output_image, VK_IMAGE_LAYOUT_GENERAL); + }); + return *images.image_view; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/sgsr.h b/src/video_core/renderer_vulkan/present/sgsr.h new file mode 100644 index 0000000000..42ba00898f --- /dev/null +++ b/src/video_core/renderer_vulkan/present/sgsr.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/math_util.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +class Device; +class Scheduler; + +class SGSR { +public: + static constexpr size_t SGSR_STAGE_COUNT = 1; + explicit SGSR(const Device& device, MemoryAllocator& memory_allocator, size_t image_count, VkExtent2D extent, bool edge_dir); + VkImageView Draw(const Device& device, Scheduler& scheduler, size_t image_index, VkImage source_image, VkImageView source_image_view, VkExtent2D input_image_extent, const Common::Rectangle& crop_rect); +private: + void Initialize(const Device& device); + void UploadImages(const Device& device, Scheduler& scheduler); + void UpdateDescriptorSets(const Device& device, VkImageView image_view, size_t image_index); + + MemoryAllocator& m_memory_allocator; + const size_t m_image_count; + const VkExtent2D m_extent; + + vk::DescriptorPool m_descriptor_pool; + vk::DescriptorSetLayout m_descriptor_set_layout; + vk::PipelineLayout m_pipeline_layout; + vk::ShaderModule m_vert_shader; + vk::ShaderModule m_stage_shader; + vk::Pipeline m_stage_pipeline; + vk::RenderPass m_renderpass; + vk::Sampler m_sampler; + + struct Images { + vk::DescriptorSets descriptor_sets; + vk::Image image; + vk::ImageView image_view; + vk::Framebuffer framebuffer; + }; + std::vector m_dynamic_images; + bool m_images_ready{}; + bool m_edge_dir{}; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/smaa.cpp b/src/video_core/renderer_vulkan/present/smaa.cpp index 686112f2f0..28b0427fa0 100644 --- a/src/video_core/renderer_vulkan/present/smaa.cpp +++ b/src/video_core/renderer_vulkan/present/smaa.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project @@ -27,22 +27,24 @@ namespace Vulkan { SMAA::SMAA(const Device& device, MemoryAllocator& allocator, size_t image_count, VkExtent2D extent) - : m_device(device), m_allocator(allocator), m_extent(extent), - m_image_count(static_cast(image_count)) { - CreateImages(); - CreateRenderPasses(); - CreateSampler(); - CreateShaders(); - CreateDescriptorPool(); - CreateDescriptorSetLayouts(); - CreateDescriptorSets(); - CreatePipelineLayouts(); - CreatePipelines(); + : m_allocator(allocator) + , m_extent(extent) + , m_image_count(u32(image_count)) +{ + CreateImages(device); + CreateRenderPasses(device); + CreateSampler(device); + CreateShaders(device); + CreateDescriptorPool(device); + CreateDescriptorSetLayouts(device); + CreateDescriptorSets(device); + CreatePipelineLayouts(device); + CreatePipelines(device); } SMAA::~SMAA() = default; -void SMAA::CreateImages() { +void SMAA::CreateImages(const Device& device) { static constexpr VkExtent2D area_extent{AREATEX_WIDTH, AREATEX_HEIGHT}; static constexpr VkExtent2D search_extent{SEARCHTEX_WIDTH, SEARCHTEX_HEIGHT}; @@ -50,9 +52,9 @@ void SMAA::CreateImages() { m_static_images[Search] = CreateWrappedImage(m_allocator, search_extent, VK_FORMAT_R8_UNORM); m_static_image_views[Area] = - CreateWrappedImageView(m_device, m_static_images[Area], VK_FORMAT_R8G8_UNORM); + CreateWrappedImageView(device, m_static_images[Area], VK_FORMAT_R8G8_UNORM); m_static_image_views[Search] = - CreateWrappedImageView(m_device, m_static_images[Search], VK_FORMAT_R8_UNORM); + CreateWrappedImageView(device, m_static_images[Search], VK_FORMAT_R8_UNORM); for (u32 i = 0; i < m_image_count; i++) { Images& images = m_dynamic_images.emplace_back(); @@ -64,39 +66,39 @@ void SMAA::CreateImages() { CreateWrappedImage(m_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT); images.image_views[Blend] = - CreateWrappedImageView(m_device, images.images[Blend], VK_FORMAT_R16G16B16A16_SFLOAT); + CreateWrappedImageView(device, images.images[Blend], VK_FORMAT_R16G16B16A16_SFLOAT); images.image_views[Edges] = - CreateWrappedImageView(m_device, images.images[Edges], VK_FORMAT_R16G16_SFLOAT); + CreateWrappedImageView(device, images.images[Edges], VK_FORMAT_R16G16_SFLOAT); images.image_views[Output] = - CreateWrappedImageView(m_device, images.images[Output], VK_FORMAT_R16G16B16A16_SFLOAT); + CreateWrappedImageView(device, images.images[Output], VK_FORMAT_R16G16B16A16_SFLOAT); } } -void SMAA::CreateRenderPasses() { - m_renderpasses[EdgeDetection] = CreateWrappedRenderPass(m_device, VK_FORMAT_R16G16_SFLOAT); +void SMAA::CreateRenderPasses(const Device& device) { + m_renderpasses[EdgeDetection] = CreateWrappedRenderPass(device, VK_FORMAT_R16G16_SFLOAT); m_renderpasses[BlendingWeightCalculation] = - CreateWrappedRenderPass(m_device, VK_FORMAT_R16G16B16A16_SFLOAT); + CreateWrappedRenderPass(device, VK_FORMAT_R16G16B16A16_SFLOAT); m_renderpasses[NeighborhoodBlending] = - CreateWrappedRenderPass(m_device, VK_FORMAT_R16G16B16A16_SFLOAT); + CreateWrappedRenderPass(device, VK_FORMAT_R16G16B16A16_SFLOAT); for (auto& images : m_dynamic_images) { images.framebuffers[EdgeDetection] = CreateWrappedFramebuffer( - m_device, m_renderpasses[EdgeDetection], images.image_views[Edges], m_extent); + device, m_renderpasses[EdgeDetection], images.image_views[Edges], m_extent); images.framebuffers[BlendingWeightCalculation] = - CreateWrappedFramebuffer(m_device, m_renderpasses[BlendingWeightCalculation], + CreateWrappedFramebuffer(device, m_renderpasses[BlendingWeightCalculation], images.image_views[Blend], m_extent); images.framebuffers[NeighborhoodBlending] = CreateWrappedFramebuffer( - m_device, m_renderpasses[NeighborhoodBlending], images.image_views[Output], m_extent); + device, m_renderpasses[NeighborhoodBlending], images.image_views[Output], m_extent); } } -void SMAA::CreateSampler() { - m_sampler = CreateWrappedSampler(m_device); +void SMAA::CreateSampler(const Device& device) { + m_sampler = CreateWrappedSampler(device); } -void SMAA::CreateShaders() { +void SMAA::CreateShaders(const Device& device) { // These match the order of the SMAAStage enum static constexpr std::array vert_shader_sources{ ARRAY_TO_SPAN(SMAA_EDGE_DETECTION_VERT_SPV), @@ -110,33 +112,33 @@ void SMAA::CreateShaders() { }; for (size_t i = 0; i < MaxSMAAStage; i++) { - m_vertex_shaders[i] = CreateWrappedShaderModule(m_device, vert_shader_sources[i]); - m_fragment_shaders[i] = CreateWrappedShaderModule(m_device, frag_shader_sources[i]); + m_vertex_shaders[i] = CreateWrappedShaderModule(device, vert_shader_sources[i]); + m_fragment_shaders[i] = CreateWrappedShaderModule(device, frag_shader_sources[i]); } } -void SMAA::CreateDescriptorPool() { +void SMAA::CreateDescriptorPool(const Device& device) { // Edge detection: 1 descriptor // Blending weight calculation: 3 descriptors // Neighborhood blending: 2 descriptors // 6 descriptors, 3 descriptor sets per image - m_descriptor_pool = CreateWrappedDescriptorPool(m_device, 6 * m_image_count, 3 * m_image_count); + m_descriptor_pool = CreateWrappedDescriptorPool(device, 6 * m_image_count, 3 * m_image_count); } -void SMAA::CreateDescriptorSetLayouts() { +void SMAA::CreateDescriptorSetLayouts(const Device& device) { m_descriptor_set_layouts[EdgeDetection] = - CreateWrappedDescriptorSetLayout(m_device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); + CreateWrappedDescriptorSetLayout(device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); m_descriptor_set_layouts[BlendingWeightCalculation] = - CreateWrappedDescriptorSetLayout(m_device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + CreateWrappedDescriptorSetLayout(device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); m_descriptor_set_layouts[NeighborhoodBlending] = - CreateWrappedDescriptorSetLayout(m_device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + CreateWrappedDescriptorSetLayout(device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); } -void SMAA::CreateDescriptorSets() { +void SMAA::CreateDescriptorSets(const Device& device) { std::vector layouts(m_descriptor_set_layouts.size()); std::ranges::transform(m_descriptor_set_layouts, layouts.begin(), [](auto& layout) { return *layout; }); @@ -146,21 +148,21 @@ void SMAA::CreateDescriptorSets() { } } -void SMAA::CreatePipelineLayouts() { +void SMAA::CreatePipelineLayouts(const Device& device) { for (size_t i = 0; i < MaxSMAAStage; i++) { - m_pipeline_layouts[i] = CreateWrappedPipelineLayout(m_device, m_descriptor_set_layouts[i]); + m_pipeline_layouts[i] = CreateWrappedPipelineLayout(device, m_descriptor_set_layouts[i]); } } -void SMAA::CreatePipelines() { +void SMAA::CreatePipelines(const Device& device) { for (size_t i = 0; i < MaxSMAAStage; i++) { m_pipelines[i] = - CreateWrappedPipeline(m_device, m_renderpasses[i], m_pipeline_layouts[i], + CreateWrappedPipeline(device, m_renderpasses[i], m_pipeline_layouts[i], std::tie(m_vertex_shaders[i], m_fragment_shaders[i])); } } -void SMAA::UpdateDescriptorSets(VkImageView image_view, size_t image_index) { +void SMAA::UpdateDescriptorSets(const Device& device, VkImageView image_view, size_t image_index) { Images& images = m_dynamic_images[image_index]; std::vector image_infos; std::vector updates; @@ -184,10 +186,10 @@ void SMAA::UpdateDescriptorSets(VkImageView image_view, size_t image_index) { updates.push_back(CreateWriteDescriptorSet(image_infos, *m_sampler, *images.image_views[Blend], images.descriptor_sets[NeighborhoodBlending], 1)); - m_device.GetLogical().UpdateDescriptorSets(updates, {}); + device.GetLogical().UpdateDescriptorSets(updates, {}); } -void SMAA::UploadImages(Scheduler& scheduler) { +void SMAA::UploadImages(const Device& device, Scheduler& scheduler) { if (m_images_ready) { return; } @@ -195,9 +197,9 @@ void SMAA::UploadImages(Scheduler& scheduler) { static constexpr VkExtent2D area_extent{AREATEX_WIDTH, AREATEX_HEIGHT}; static constexpr VkExtent2D search_extent{SEARCHTEX_WIDTH, SEARCHTEX_HEIGHT}; - UploadImage(m_device, m_allocator, scheduler, m_static_images[Area], area_extent, + UploadImage(device, m_allocator, scheduler, m_static_images[Area], area_extent, VK_FORMAT_R8G8_UNORM, ARRAY_TO_SPAN(areaTexBytes)); - UploadImage(m_device, m_allocator, scheduler, m_static_images[Search], search_extent, + UploadImage(device, m_allocator, scheduler, m_static_images[Search], search_extent, VK_FORMAT_R8_UNORM, ARRAY_TO_SPAN(searchTexBytes)); scheduler.Record([&](vk::CommandBuffer cmdbuf) { @@ -212,8 +214,7 @@ void SMAA::UploadImages(Scheduler& scheduler) { m_images_ready = true; } -void SMAA::Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image, - VkImageView* inout_image_view) { +void SMAA::Draw(const Device& device, Scheduler& scheduler, size_t image_index, VkImage* inout_image, VkImageView* inout_image_view) { Images& images = m_dynamic_images[image_index]; VkImage input_image = *inout_image; @@ -232,8 +233,8 @@ void SMAA::Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image, *images.framebuffers[BlendingWeightCalculation]; VkFramebuffer neighborhood_blending_framebuffer = *images.framebuffers[NeighborhoodBlending]; - UploadImages(scheduler); - UpdateDescriptorSets(*inout_image_view, image_index); + UploadImages(device, scheduler); + UpdateDescriptorSets(device, *inout_image_view, image_index); scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([=, this](vk::CommandBuffer cmdbuf) { diff --git a/src/video_core/renderer_vulkan/present/smaa.h b/src/video_core/renderer_vulkan/present/smaa.h index fdf6def070..a547161395 100644 --- a/src/video_core/renderer_vulkan/present/smaa.h +++ b/src/video_core/renderer_vulkan/present/smaa.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -16,11 +19,10 @@ class StagingBufferPool; class SMAA final : public AntiAliasPass { public: - explicit SMAA(const Device& device, MemoryAllocator& allocator, size_t image_count, - VkExtent2D extent); + explicit SMAA(const Device& device, MemoryAllocator& allocator, size_t image_count, VkExtent2D extent); ~SMAA() override; - void Draw(Scheduler& scheduler, size_t image_index, VkImage* inout_image, + void Draw(const Device& device, Scheduler& scheduler, size_t image_index, VkImage* inout_image, VkImageView* inout_image_view) override; private: @@ -44,19 +46,18 @@ private: MaxDynamicImage = 3, }; - void CreateImages(); - void CreateRenderPasses(); - void CreateSampler(); - void CreateShaders(); - void CreateDescriptorPool(); - void CreateDescriptorSetLayouts(); - void CreateDescriptorSets(); - void CreatePipelineLayouts(); - void CreatePipelines(); - void UpdateDescriptorSets(VkImageView image_view, size_t image_index); - void UploadImages(Scheduler& scheduler); + void CreateImages(const Device& device); + void CreateRenderPasses(const Device& device); + void CreateSampler(const Device& device); + void CreateShaders(const Device& device); + void CreateDescriptorPool(const Device& device); + void CreateDescriptorSetLayouts(const Device& device); + void CreateDescriptorSets(const Device& device); + void CreatePipelineLayouts(const Device& device); + void CreatePipelines(const Device& device); + void UpdateDescriptorSets(const Device& device, VkImageView image_view, size_t image_index); + void UploadImages(const Device& device, Scheduler& scheduler); - const Device& m_device; MemoryAllocator& m_allocator; const VkExtent2D m_extent; const u32 m_image_count; diff --git a/src/video_core/renderer_vulkan/present/util.cpp b/src/video_core/renderer_vulkan/present/util.cpp index a2c4727703..2ea7870f01 100644 --- a/src/video_core/renderer_vulkan/present/util.cpp +++ b/src/video_core/renderer_vulkan/present/util.cpp @@ -69,7 +69,7 @@ void TransitionImageLayout(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayo .layerCount = 1, }, }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, barrier); } @@ -171,10 +171,10 @@ void DownloadColorImage(vk::CommandBuffer& cmdbuf, VkImage image, VkBuffer buffe .imageOffset{.x = 0, .y = 0, .z = 0}, .imageExtent{extent}, }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, read_barrier); cmdbuf.CopyImageToBuffer(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer, copy); - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, memory_write_barrier, nullptr, image_write_barrier); } @@ -320,15 +320,16 @@ vk::DescriptorPool CreateWrappedDescriptorPool(const Device& device, size_t max_ }); } -vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout( - const Device& device, std::initializer_list types) { +vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout(const Device& device, + std::span types, + VkShaderStageFlags stages) { std::vector bindings(types.size()); for (size_t i = 0; i < types.size(); i++) { bindings[i] = { .binding = static_cast(i), - .descriptorType = std::data(types)[i], + .descriptorType = types[i], .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, + .stageFlags = stages, .pImmutableSamplers = nullptr, }; } @@ -342,6 +343,13 @@ vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout( }); } +vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout( + const Device& device, std::initializer_list types, + VkShaderStageFlags stages) { + return CreateWrappedDescriptorSetLayout( + device, std::span{std::data(types), types.size()}, stages); +} + vk::DescriptorSets CreateWrappedDescriptorSets(vk::DescriptorPool& pool, vk::Span layouts) { return pool.Allocate(VkDescriptorSetAllocateInfo{ @@ -353,6 +361,28 @@ vk::DescriptorSets CreateWrappedDescriptorSets(vk::DescriptorPool& pool, }); } +vk::Pipeline CreateWrappedComputePipeline(const Device& device, vk::PipelineLayout& layout, + VkShaderModule shader) { + return device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{ + .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stage = + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stage = VK_SHADER_STAGE_COMPUTE_BIT, + .module = shader, + .pName = "main", + .pSpecializationInfo = nullptr, + }, + .layout = *layout, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + }); +} + vk::PipelineLayout CreateWrappedPipelineLayout(const Device& device, vk::DescriptorSetLayout& layout) { return device.GetLogical().CreatePipelineLayout(VkPipelineLayoutCreateInfo{ @@ -491,7 +521,7 @@ static vk::Pipeline CreateWrappedPipelineImpl( .subpass = 0, .basePipelineHandle = 0, .basePipelineIndex = 0, - }); + }, device.StaticPipelineCache()); } vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass, diff --git a/src/video_core/renderer_vulkan/present/util.h b/src/video_core/renderer_vulkan/present/util.h index 38cc6203c5..ecc263286d 100644 --- a/src/video_core/renderer_vulkan/present/util.h +++ b/src/video_core/renderer_vulkan/present/util.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -38,7 +38,11 @@ vk::DescriptorPool CreateWrappedDescriptorPool(const Device& device, size_t max_ std::initializer_list types = { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout( - const Device& device, std::initializer_list types); + const Device& device, std::initializer_list types, + VkShaderStageFlags stages = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT); +vk::DescriptorSetLayout CreateWrappedDescriptorSetLayout(const Device& device, + std::span types, + VkShaderStageFlags stages); vk::DescriptorSets CreateWrappedDescriptorSets(vk::DescriptorPool& pool, vk::Span layouts); vk::PipelineLayout CreateWrappedPipelineLayout(const Device& device, @@ -46,6 +50,8 @@ vk::PipelineLayout CreateWrappedPipelineLayout(const Device& device, vk::Pipeline CreateWrappedPipeline(const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout, std::tuple shaders); +vk::Pipeline CreateWrappedComputePipeline(const Device& device, vk::PipelineLayout& layout, + VkShaderModule shader); vk::Pipeline CreateWrappedPremultipliedBlendingPipeline( const Device& device, vk::RenderPass& renderpass, vk::PipelineLayout& layout, std::tuple shaders); diff --git a/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp b/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp index 22ffacf119..549b0660c1 100644 --- a/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp +++ b/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -15,19 +18,20 @@ namespace Vulkan { -WindowAdaptPass::WindowAdaptPass(const Device& device_, VkFormat frame_format, - vk::Sampler&& sampler_, vk::ShaderModule&& fragment_shader_) - : device(device_), sampler(std::move(sampler_)), fragment_shader(std::move(fragment_shader_)) { - CreateDescriptorSetLayout(); - CreatePipelineLayout(); - CreateVertexShader(); - CreateRenderPass(frame_format); - CreatePipelines(); +WindowAdaptPass::WindowAdaptPass(const Device& device, VkFormat frame_format, vk::Sampler&& sampler_, vk::ShaderModule&& fragment_shader_) + : sampler(std::move(sampler_)) + , fragment_shader(std::move(fragment_shader_)) +{ + CreateDescriptorSetLayout(device); + CreatePipelineLayout(device); + CreateVertexShader(device); + CreateRenderPass(device, frame_format); + CreatePipelines(device); } WindowAdaptPass::~WindowAdaptPass() = default; -void WindowAdaptPass::Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, size_t image_index, +void WindowAdaptPass::Draw(const Device& device, RasterizerVulkan& rasterizer, Scheduler& scheduler, size_t image_index, std::list& layers, std::span configs, const Layout::FramebufferLayout& layout, Frame* dst) { @@ -60,7 +64,7 @@ void WindowAdaptPass::Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, s break; } - layer_it->ConfigureDraw(&push_constants[i], &descriptor_sets[i], rasterizer, *sampler, + layer_it->ConfigureDraw(device, &push_constants[i], &descriptor_sets[i], rasterizer, *sampler, image_index, configs[i], layout); layer_it++; } @@ -111,12 +115,12 @@ VkRenderPass WindowAdaptPass::GetRenderPass() { return *render_pass; } -void WindowAdaptPass::CreateDescriptorSetLayout() { +void WindowAdaptPass::CreateDescriptorSetLayout(const Device& device) { descriptor_set_layout = CreateWrappedDescriptorSetLayout(device, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER}); } -void WindowAdaptPass::CreatePipelineLayout() { +void WindowAdaptPass::CreatePipelineLayout(const Device& device) { const VkPushConstantRange range{ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, .offset = 0, @@ -134,15 +138,15 @@ void WindowAdaptPass::CreatePipelineLayout() { }); } -void WindowAdaptPass::CreateVertexShader() { +void WindowAdaptPass::CreateVertexShader(const Device& device) { vertex_shader = BuildShader(device, VULKAN_PRESENT_VERT_SPV); } -void WindowAdaptPass::CreateRenderPass(VkFormat frame_format) { +void WindowAdaptPass::CreateRenderPass(const Device& device, VkFormat frame_format) { render_pass = CreateWrappedRenderPass(device, frame_format, VK_IMAGE_LAYOUT_UNDEFINED); } -void WindowAdaptPass::CreatePipelines() { +void WindowAdaptPass::CreatePipelines(const Device& device) { opaque_pipeline = CreateWrappedPipeline(device, render_pass, pipeline_layout, std::tie(vertex_shader, fragment_shader)); premultiplied_pipeline = CreateWrappedPremultipliedBlendingPipeline( diff --git a/src/video_core/renderer_vulkan/present/window_adapt_pass.h b/src/video_core/renderer_vulkan/present/window_adapt_pass.h index cf667a4fc6..58d6fc01c0 100644 --- a/src/video_core/renderer_vulkan/present/window_adapt_pass.h +++ b/src/video_core/renderer_vulkan/present/window_adapt_pass.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -30,7 +33,7 @@ public: vk::ShaderModule&& fragment_shader); ~WindowAdaptPass(); - void Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, size_t image_index, + void Draw(const Device& device, RasterizerVulkan& rasterizer, Scheduler& scheduler, size_t image_index, std::list& layers, std::span configs, const Layout::FramebufferLayout& layout, Frame* dst); @@ -38,14 +41,12 @@ public: VkRenderPass GetRenderPass(); private: - void CreateDescriptorSetLayout(); - void CreatePipelineLayout(); - void CreateVertexShader(); - void CreateRenderPass(VkFormat frame_format); - void CreatePipelines(); + void CreateDescriptorSetLayout(const Device& device); + void CreatePipelineLayout(const Device& device); + void CreateVertexShader(const Device& device); + void CreateRenderPass(const Device& device, VkFormat frame_format); + void CreatePipelines(const Device& device); -private: - const Device& device; vk::DescriptorSetLayout descriptor_set_layout; vk::PipelineLayout pipeline_layout; vk::Sampler sampler; diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 2deec13ace..27ca531186 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -25,6 +24,9 @@ #include "video_core/gpu.h" #include "video_core/present.h" #include "video_core/renderer_vulkan/present/util.h" +#ifdef HAS_RESHADE +#include "video_core/post_processing/fx_chain.h" +#endif #include "video_core/renderer_vulkan/renderer_vulkan.h" #include "video_core/renderer_vulkan/vk_blit_screen.h" #include "video_core/renderer_vulkan/vk_rasterizer.h" @@ -50,6 +52,23 @@ constexpr VkExtent2D CaptureImageSize{ .height = VideoCore::Capture::LinearHeight, }; +#ifdef HAS_LSFG +[[nodiscard]] VkExtent2D GuestExtent(std::span framebuffers) { + if (framebuffers.empty()) { + return VkExtent2D{}; + } + + const auto& framebuffer = framebuffers.front(); + if (framebuffer.crop_rect.IsEmpty()) { + return VkExtent2D{.width = framebuffer.width, .height = framebuffer.height}; + } + return VkExtent2D{ + .width = static_cast(framebuffer.crop_rect.GetWidth()), + .height = static_cast(framebuffer.crop_rect.GetHeight()), + }; +} +#endif + constexpr VkExtent3D CaptureImageExtent{ .width = VideoCore::Capture::LinearWidth, .height = VideoCore::Capture::LinearHeight, @@ -90,11 +109,10 @@ std::string BuildCommaSeparatedExtensions( } // Anonymous namespace -Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, - VkSurfaceKHR surface) { +Device CreateDevice(const vk::Instance& instance, const vk::InstanceDispatch& dld, VkSurfaceKHR surface) { const std::vector devices = instance.EnumeratePhysicalDevices(); - const s32 device_index = Settings::values.vulkan_device.GetValue(); - if (device_index < 0 || device_index >= static_cast(devices.size())) { + const u32 device_index = Settings::values.vulkan_device.GetValue(); + if (device_index >= u32(devices.size())) { LOG_ERROR(Render_Vulkan, "Invalid device index {}!", device_index); throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED); } @@ -157,13 +175,21 @@ try present_manager, scheduler, PresentFiltersForAppletCapture) - , rasterizer(render_window, gpu, device_memory, device, memory_allocator, state_tracker, scheduler) { + , rasterizer(render_window, gpu, device_memory, device, memory_allocator, state_tracker, scheduler) +#ifdef HAS_LSFG + , frame_gen(memory_allocator, scheduler) +#endif +{ if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) { turbo_mode.emplace(instance, dld); scheduler.RegisterOnSubmit([this] { turbo_mode->QueueSubmitted(); }); } +#ifdef HAS_RESHADE + VideoCore::FxChain::Instance().LoadFromSettings(); +#endif + Report(); } catch (const vk::Exception& exception) { LOG_ERROR(Render_Vulkan, "Vulkan initialization failed with error: {}", exception.what()); @@ -172,11 +198,7 @@ try RendererVulkan::~RendererVulkan() { scheduler.RegisterOnSubmit([] {}); - scheduler.Finish(); - { - std::scoped_lock lock{scheduler.submit_mutex}; - void(device.GetLogical().WaitIdle()); - } + void(device.GetLogical().WaitIdle()); } void RendererVulkan::Composite(std::span framebuffers) { @@ -194,12 +216,33 @@ void RendererVulkan::Composite(std::span framebu Frame* frame = present_manager.GetRenderFrame(); scheduler.RequestOutsideRenderPassOperationContext(); - blit_swapchain.DrawToFrame(rasterizer, frame, framebuffers, - render_window.GetFramebufferLayout(), swapchain.GetImageCount(), + blit_swapchain.DrawToFrame(device, rasterizer, frame, framebuffers, + render_window.GetFramebufferLayout(), + present_manager.SwapchainImageCount(), swapchain.GetImageViewFormat()); + +#ifdef HAS_LSFG + void(frame_gen.WantedGenerations(present_manager.MaxExtraFrames())); + + frame_gen.Process(device, frame, present_manager.SwapchainImageFormat(), + GuestExtent(framebuffers)); + + const size_t generated_frames = frame_gen.GeneratedFrameCount(); + for (size_t generation = 0; generation < generated_frames; ++generation) { + Frame* generated = present_manager.GetRenderFrame(); + blit_swapchain.PrepareFrame(device, generated, render_window.GetFramebufferLayout()); + frame_gen.GenerateInto(device, generated, generation); + scheduler.Flush(*generated->render_ready); + present_manager.Present(generated); + } +#endif + scheduler.Flush(*frame->render_ready); present_manager.Present(frame); +#ifdef HAS_LSFG + scheduler.DispatchWork(); +#endif gpu.RendererFrameEndNotify(); rasterizer.TickFrame(); @@ -232,12 +275,12 @@ vk::Buffer RendererVulkan::RenderToBuffer(std::span return; } + const auto screenshot_layers = Tegra::FilterLayerStack( + framebuffers, renderer_settings.screenshot_layer_stack, screenshot_layer_scratch); + const auto& layout{renderer_settings.screenshot_framebuffer_layout}; - const auto dst_buffer = RenderToBuffer(framebuffers, layout, VK_FORMAT_B8G8R8A8_UNORM, + const auto dst_buffer = RenderToBuffer(screenshot_layers, layout, VK_FORMAT_B8G8R8A8_UNORM, layout.width * layout.height * 4); std::memcpy(renderer_settings.screenshot_bits, dst_buffer.Mapped().data(), @@ -298,15 +344,21 @@ std::vector RendererVulkan::GetAppletCaptureBuffer() { void RendererVulkan::RenderAppletCaptureLayer( std::span framebuffers) { + const auto capture_layers = Tegra::FilterLayerStack( + framebuffers, Service::Nvnflinger::LayerStackId::LastFrame, applet_capture_layers); + + if (capture_layers.empty()) + return; + if (!applet_frame.image) { applet_frame.image = CreateWrappedImage(memory_allocator, CaptureImageSize, CaptureFormat); applet_frame.image_view = CreateWrappedImageView(device, applet_frame.image, CaptureFormat); - applet_frame.framebuffer = blit_applet.CreateFramebuffer( + applet_frame.framebuffer = blit_applet.CreateFramebuffer(device, VideoCore::Capture::Layout, *applet_frame.image_view, CaptureFormat); } scheduler.RequestOutsideRenderPassOperationContext(); - blit_applet.DrawToFrame(rasterizer, &applet_frame, framebuffers, VideoCore::Capture::Layout, 1, + blit_applet.DrawToFrame(device, rasterizer, &applet_frame, capture_layers, VideoCore::Capture::Layout, 1, CaptureFormat); } diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index 4fb88b29de..87422dd935 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -13,6 +13,9 @@ #include "common/dynamic_library.h" #include "video_core/host1x/gpu_device_memory_manager.h" #include "video_core/renderer_base.h" +#ifdef HAS_LSFG +#include "video_core/renderer_vulkan/present/frame_gen.h" +#endif #include "video_core/renderer_vulkan/vk_blit_screen.h" #include "video_core/renderer_vulkan/vk_present_manager.h" #include "video_core/renderer_vulkan/vk_rasterizer.h" @@ -72,6 +75,9 @@ private: void RenderScreenshot(std::span framebuffers); void RenderAppletCaptureLayer(std::span framebuffers); + std::vector applet_capture_layers; + std::vector screenshot_layer_scratch; + Tegra::MaxwellDeviceMemoryManager& device_memory; Tegra::GPU& gpu; @@ -95,6 +101,9 @@ private: BlitScreen blit_capture; BlitScreen blit_applet; RasterizerVulkan rasterizer; +#ifdef HAS_LSFG + FrameGen frame_gen; +#endif std::optional turbo_mode; Frame applet_frame; diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp index 75a8c3bf91..205382f81a 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp @@ -8,7 +8,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include -#include +#include "common/settings.h" #include "video_core/framebuffer_config.h" #include "video_core/present.h" #include "video_core/renderer_vulkan/present/filters.h" @@ -19,26 +19,26 @@ namespace Vulkan { -BlitScreen::BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory_, const Device& device_, - MemoryAllocator& memory_allocator_, PresentManager& present_manager_, - Scheduler& scheduler_, const PresentFilters& filters_) - : device_memory{device_memory_}, device{device_}, memory_allocator{memory_allocator_}, - present_manager{present_manager_}, scheduler{scheduler_}, filters{filters_}, - image_count{1}, image_index{0}, - swapchain_view_format{VK_FORMAT_B8G8R8A8_UNORM} {} +BlitScreen::BlitScreen(Tegra::MaxwellDeviceMemoryManager& device_memory_, const Device& device_, MemoryAllocator& memory_allocator_, PresentManager& present_manager_, Scheduler& scheduler_, const PresentFilters& filters_) + : device_memory{device_memory_} + , memory_allocator{memory_allocator_} + , present_manager{present_manager_} + , scheduler{scheduler_} + , filters{filters_} + , image_count{1} + , image_index{0} + , swapchain_view_format{VK_FORMAT_B8G8R8A8_UNORM} +{} BlitScreen::~BlitScreen() = default; -void BlitScreen::WaitIdle() { +void BlitScreen::WaitIdle(const Device& device) { present_manager.WaitPresent(); scheduler.Finish(); - { - std::scoped_lock lock{scheduler.submit_mutex}; - device.GetLogical().WaitIdle(); - } + device.GetLogical().WaitIdle(); } -void BlitScreen::SetWindowAdaptPass() { +void BlitScreen::SetWindowAdaptPass(const Device& device) { layers.clear(); scaling_filter = filters.get_scaling_filter(); @@ -77,6 +77,8 @@ void BlitScreen::SetWindowAdaptPass() { window_adapt = MakeMmpx(device, swapchain_view_format); break; case Settings::ScalingFilter::Fsr: + case Settings::ScalingFilter::Sgsr: + case Settings::ScalingFilter::SgsrEdge: case Settings::ScalingFilter::Bilinear: default: window_adapt = MakeBilinear(device, swapchain_view_format); @@ -84,7 +86,23 @@ void BlitScreen::SetWindowAdaptPass() { } } -void BlitScreen::DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame, +void BlitScreen::PrepareFrame(const Device& device, Frame* frame, + const Layout::FramebufferLayout& layout) { + if (!window_adapt) { + return; + } + + if (frame->width != layout.width || frame->height != layout.height) { + WaitIdle(device); + } else if (!present_manager.NeedsStorage(frame, true)) { + return; + } + + present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format, + window_adapt->GetRenderPass(), true); +} + +void BlitScreen::DrawToFrame(const Device& device, RasterizerVulkan& rasterizer, Frame* frame, std::span framebuffers, const Layout::FramebufferLayout& layout, size_t current_swapchain_image_count, @@ -108,16 +126,20 @@ void BlitScreen::DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame, swapchain_view_format = current_swapchain_view_format; } + const bool storage_required = Settings::values.frame_gen.GetValue(); if (resource_update_required) { - WaitIdle(); - SetWindowAdaptPass(); + WaitIdle(device); + SetWindowAdaptPass(device); if (presentation_recreate_required) { present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format, - window_adapt->GetRenderPass()); + window_adapt->GetRenderPass(), storage_required); } image_index = 0; + } else if (present_manager.NeedsStorage(frame, storage_required)) { + present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format, + window_adapt->GetRenderPass(), true); } const VkExtent2D window_size{ @@ -133,22 +155,22 @@ void BlitScreen::DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame, } } - window_adapt->Draw(rasterizer, scheduler, image_index, layers, framebuffers, layout, frame); + window_adapt->Draw(device, rasterizer, scheduler, image_index, layers, framebuffers, layout, frame); if (++image_index >= image_count) { image_index = 0; } } -vk::Framebuffer BlitScreen::CreateFramebuffer(const Layout::FramebufferLayout& layout, +vk::Framebuffer BlitScreen::CreateFramebuffer(const Device& device, const Layout::FramebufferLayout& layout, VkImageView image_view, VkFormat current_view_format) { bool format_updated = swapchain_view_format != current_view_format; swapchain_view_format = current_view_format; if (!window_adapt || scaling_filter != filters.get_scaling_filter() || format_updated) { - WaitIdle(); - SetWindowAdaptPass(); + WaitIdle(device); + SetWindowAdaptPass(device); image_index = 0; } @@ -157,10 +179,10 @@ vk::Framebuffer BlitScreen::CreateFramebuffer(const Layout::FramebufferLayout& l .height = layout.height, }; - return CreateFramebuffer(image_view, extent, window_adapt->GetRenderPass()); + return CreateFramebuffer(device, image_view, extent, window_adapt->GetRenderPass()); } -vk::Framebuffer BlitScreen::CreateFramebuffer(const VkImageView& image_view, VkExtent2D extent, +vk::Framebuffer BlitScreen::CreateFramebuffer(const Device& device, const VkImageView& image_view, VkExtent2D extent, VkRenderPass render_pass) { return device.GetLogical().CreateFramebuffer(VkFramebufferCreateInfo{ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h index 531c57fc5c..56b429ae53 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.h +++ b/src/video_core/renderer_vulkan/vk_blit_screen.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -52,23 +55,24 @@ public: Scheduler& scheduler, const PresentFilters& filters); ~BlitScreen(); - void DrawToFrame(RasterizerVulkan& rasterizer, Frame* frame, + void DrawToFrame(const Device& device, RasterizerVulkan& rasterizer, Frame* frame, std::span framebuffers, const Layout::FramebufferLayout& layout, size_t current_swapchain_image_count, VkFormat current_swapchain_view_format); - [[nodiscard]] vk::Framebuffer CreateFramebuffer(const Layout::FramebufferLayout& layout, + void PrepareFrame(const Device& device, Frame* frame, const Layout::FramebufferLayout& layout); + + [[nodiscard]] vk::Framebuffer CreateFramebuffer(const Device& device, const Layout::FramebufferLayout& layout, VkImageView image_view, VkFormat current_view_format); private: - void WaitIdle(); - void SetWindowAdaptPass(); - vk::Framebuffer CreateFramebuffer(const VkImageView& image_view, VkExtent2D extent, + void WaitIdle(const Device& device); + void SetWindowAdaptPass(const Device& device); + vk::Framebuffer CreateFramebuffer(const Device& device, const VkImageView& image_view, VkExtent2D extent, VkRenderPass render_pass); Tegra::MaxwellDeviceMemoryManager& device_memory; - const Device& device; MemoryAllocator& memory_allocator; PresentManager& present_manager; Scheduler& scheduler; diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index a359502046..493de2cc20 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -56,7 +56,8 @@ size_t BytesPerIndex(VkIndexType index_type) { } } -vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allocator, u64 size) { +vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allocator, u64 size, + VkDeviceSize sparse_alignment) { VkBufferUsageFlags flags = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | @@ -69,6 +70,9 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo if (device.IsExtConditionalRendering()) { flags |= VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT; } + if (device.IsBufferDeviceAddressSupported()) { + flags |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + } const VkBufferCreateInfo buffer_ci = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .pNext = nullptr, @@ -79,26 +83,45 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo .queueFamilyIndexCount = 0, .pQueueFamilyIndices = nullptr, }; + if (sparse_alignment > 1) { + return memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal, sparse_alignment); + } return memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal); } } // Anonymous namespace Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_params) - : VideoCommon::BufferBase(null_params), tracker{4096} { + : VideoCommon::BufferBase(null_params), scheduler{&runtime.scheduler}, tracker{4096} { if (runtime.device.HasNullDescriptor()) { return; } device = &runtime.device; buffer = runtime.CreateNullBuffer(); is_null = true; + if (device->IsBufferDeviceAddressSupported()) { + device_address = device->GetLogical().GetBufferDeviceAddress(*buffer); + } } -Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_) +Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_, + bool sparse_compatible_) : VideoCommon::BufferBase(cpu_addr_, size_bytes_), device{&runtime.device}, - buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes())}, tracker{SizeBytes()} { + scheduler{&runtime.scheduler}, + buffer{CreateBuffer(*device, runtime.memory_allocator, SizeBytes(), + runtime.SparseAlignmentFor(sparse_compatible_))}, + tracker{SizeBytes()} { + sparse_compatible = sparse_compatible_; if (runtime.device.HasDebuggingToolAttached()) { - buffer.SetObjectNameEXT(fmt::format("Buffer 0x{:x}", CpuAddr()).c_str()); + buffer.SetObjectNameEXT(fmt::format("Buffer {:#x}", CpuAddr()).c_str()); } + if (device->IsBufferDeviceAddressSupported()) { + device_address = device->GetLogical().GetBufferDeviceAddress(*buffer); + } +} + +void Buffer::MarkUsage(u64 offset, u64 size) noexcept { + tracker.Track(offset, size); + last_usage_tick = scheduler->CurrentTick(); } VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format) { @@ -333,7 +356,8 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_}, staging_pool{staging_pool_}, guest_descriptor_queue{guest_descriptor_queue_}, quad_index_pass(device, scheduler, descriptor_pool, staging_pool, - compute_pass_descriptor_queue) { + compute_pass_descriptor_queue), + multi_range_buffers(device_) { const VkDriverIdKHR driver_id = device.GetDriverID(); limit_dynamic_storage_buffers = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || driver_id == VK_DRIVER_ID_ARM_PROPRIETARY; @@ -358,6 +382,10 @@ StagingBufferRef BufferCacheRuntime::DownloadStagingBuffer(size_t size, bool def return staging_pool.Request(size, MemoryUsage::Download, deferred); } +VkFormat BufferCacheRuntime::TexelBufferFormat(VideoCore::Surface::PixelFormat format) const { + return MaxwellToVK::SurfaceFormat(device, FormatType::Buffer, false, format).format; +} + void BufferCacheRuntime::FreeDeferredStagingBuffer(StagingBufferRef& ref) { staging_pool.FreeDeferred(ref); } @@ -384,10 +412,24 @@ u32 BufferCacheRuntime::GetStorageBufferAlignment() const { void BufferCacheRuntime::TickFrame(Common::SlotVector& slot_buffers) noexcept { for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) { - it->ResetUsageTracking(); + if (scheduler.IsFree(it->LastUsageTick())) { + it->ResetUsageTracking(); + } } } +u64 BufferCacheRuntime::CurrentTick() { + return scheduler.CurrentTick(); +} + +bool BufferCacheRuntime::IsFree(u64 tick) { + return scheduler.IsFree(tick); +} + +void BufferCacheRuntime::Wait(u64 tick) { + scheduler.Wait(tick); +} + void BufferCacheRuntime::Finish() { scheduler.Finish(); } @@ -429,7 +471,7 @@ void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer, if (src_buffer == staging_pool.StreamBuf() && can_reorder_upload) { scheduler.RecordWithUploadBuffer([src_buffer, dst_buffer, vk_copies]( vk::CommandBuffer, vk::CommandBuffer upload_cmdbuf) { - upload_cmdbuf.CopyBuffer(src_buffer, dst_buffer, VideoCommon::FixSmallVectorADL(vk_copies)); + upload_cmdbuf.CopyBuffer(src_buffer, dst_buffer, vk_copies); }); return; } @@ -437,13 +479,13 @@ void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer, scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([src_buffer, dst_buffer, vk_copies, barrier](vk::CommandBuffer cmdbuf) { if (barrier) { - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER); } - cmdbuf.CopyBuffer(src_buffer, dst_buffer, VideoCommon::FixSmallVectorADL(vk_copies)); + cmdbuf.CopyBuffer(src_buffer, dst_buffer, vk_copies); if (barrier) { cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER); + vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, WRITE_BARRIER); } }); } @@ -457,7 +499,7 @@ void BufferCacheRuntime::PreCopyBarrier() { }; scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([](vk::CommandBuffer cmdbuf) { - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER); }); } @@ -471,7 +513,7 @@ void BufferCacheRuntime::PostCopyBarrier() { }; scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([](vk::CommandBuffer cmdbuf) { - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, WRITE_BARRIER); }); } @@ -495,14 +537,45 @@ void BufferCacheRuntime::ClearBuffer(VkBuffer dest_buffer, u32 offset, size_t si scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([dest_buffer, offset, size, value](vk::CommandBuffer cmdbuf) { - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER); cmdbuf.FillBuffer(dest_buffer, offset, size, value); - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, WRITE_BARRIER); }); } +bool BufferCacheRuntime::BindMultiRangeStorageBuffer(u64 key, bool is_written) { + if (multi_range_sources.empty() || multi_range_total == 0) { + return false; + } + const MultiRangeRef ref = multi_range_buffers.Get(device, scheduler, memory_allocator, key, + multi_range_sources, multi_range_total); + if (ref.handle == VK_NULL_HANDLE) { + return false; + } + if (is_written && !ref.sparse) { + return false; + } + if (ref.needs_gather) { + PreCopyBarrier(); + VkDeviceSize dst_offset = 0; + for (const MultiRangeSource& source : multi_range_sources) { + const std::array copy{VideoCommon::BufferCopy{ + .src_offset = u64(source.offset), + .dst_offset = u64(dst_offset), + .size = size_t(source.size), + }}; + CopyBuffer(ref.handle, source.handle, copy, false); + dst_offset += source.size; + } + PostCopyBarrier(); + multi_range_buffers.MarkGathered(key); + } + guest_descriptor_queue.AddBuffer(ref.handle, ref.address, 0, ref.size); + return true; +} + void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, IndexFormat index_format, u32 base_vertex, u32 num_indices, VkBuffer buffer, u32 offset, [[maybe_unused]] u32 size) { @@ -670,6 +743,9 @@ vk::Buffer BufferCacheRuntime::CreateNullBuffer() { if (device.IsExtTransformFeedbackSupported()) { create_info.usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT; } + if (device.IsBufferDeviceAddressSupported()) { + create_info.usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + } vk::Buffer ret = memory_allocator.CreateBuffer(create_info, MemoryUsage::DeviceLocal); if (device.HasDebuggingToolAttached()) { ret.SetObjectNameEXT("Null buffer"); diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index b73fcd162b..fff1f8164d 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -8,11 +8,14 @@ #include +#include + #include "video_core/buffer_cache/buffer_cache_base.h" #include "video_core/buffer_cache/memory_tracker_base.h" #include "video_core/buffer_cache/usage_tracker.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/renderer_vulkan/vk_compute_pass.h" +#include "video_core/renderer_vulkan/vk_multi_range_buffer.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" #include "video_core/surface.h" @@ -31,7 +34,8 @@ class BufferCacheRuntime; class Buffer : public VideoCommon::BufferBase { public: explicit Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params); - explicit Buffer(BufferCacheRuntime& runtime, VAddr cpu_addr_, u64 size_bytes_); + explicit Buffer(BufferCacheRuntime& runtime, VAddr cpu_addr_, u64 size_bytes_, + bool sparse_compatible_); [[nodiscard]] VkBufferView View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format); @@ -39,18 +43,32 @@ public: return *buffer; } + [[nodiscard]] VkDeviceAddress DeviceAddress() const noexcept { + return device_address; + } + + [[nodiscard]] bool IsSparseCompatible() const noexcept { + return sparse_compatible; + } + + [[nodiscard]] vk::MemoryLocation Location() const noexcept { + return buffer.Location(); + } + [[nodiscard]] bool IsRegionUsed(u64 offset, u64 size) const noexcept { return tracker.IsUsed(offset, size); } - void MarkUsage(u64 offset, u64 size) noexcept { - tracker.Track(offset, size); - } + void MarkUsage(u64 offset, u64 size) noexcept; void ResetUsageTracking() noexcept { tracker.Reset(); } + [[nodiscard]] u64 LastUsageTick() const noexcept { + return last_usage_tick; + } + operator VkBuffer() const noexcept { return *buffer; } @@ -64,10 +82,14 @@ private: }; const Device* device{}; + Scheduler* scheduler{}; vk::Buffer buffer; std::vector views; VideoCommon::UsageTracker tracker; + VkDeviceAddress device_address{}; + u64 last_usage_tick{}; bool is_null{}; + bool sparse_compatible{}; }; class QuadArrayIndexBuffer; @@ -88,6 +110,12 @@ public: void TickFrame(Common::SlotVector& slot_buffers) noexcept; + u64 CurrentTick(); + + bool IsFree(u64 tick); + + void Wait(u64 tick); + void Finish(); u64 GetDeviceLocalMemory() const; @@ -110,7 +138,7 @@ public: void PreCopyBarrier(); - void CopyBuffer(VkBuffer src_buffer, VkBuffer dst_buffer, + void CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer, std::span copies, bool barrier, bool can_reorder_upload = false); @@ -135,22 +163,65 @@ public: [[maybe_unused]] u32 binding_index, u32 size) { const StagingBufferRef ref = staging_pool.Request(size, MemoryUsage::Upload); - BindBuffer(ref.buffer, static_cast(ref.offset), size); + guest_descriptor_queue.AddBuffer(ref.buffer, ref.device_address, + static_cast(ref.offset), size); return ref.mapped_span; } - void BindUniformBuffer(VkBuffer buffer, u32 offset, u32 size) { + [[nodiscard]] VkDeviceSize SparseAlignmentFor(bool sparse_compatible) const noexcept { + if (!sparse_compatible || !multi_range_buffers.use_sparse) { + return 0; + } + return multi_range_buffers.block_size; + } + + [[nodiscard]] bool PrefersSparseSources() const noexcept { + return multi_range_buffers.use_sparse; + } + + void ResetMultiRange() noexcept { + multi_range_sources.clear(); + multi_range_total = 0; + } + + void PushMultiRangeSource(const Buffer& buffer, u32 offset, u32 size) { + const vk::MemoryLocation location = buffer.Location(); + multi_range_sources.push_back(MultiRangeSource{ + .handle = buffer.Handle(), + .memory = location.memory, + .memory_offset = location.offset, + .offset = offset, + .size = size, + .write_tick = buffer.getWriteTick(), + .memory_type = location.memory_type, + }); + multi_range_total += size; + } + + bool BindMultiRangeStorageBuffer(u64 key, bool is_written); + + void InvalidateMultiRange(u64 key) { + multi_range_buffers.Invalidate(key); + } + + void OnBufferDeleted(const Buffer& buffer) { + multi_range_buffers.DropOwner(scheduler, buffer.Handle()); + } + + void BindUniformBuffer(const Buffer& buffer, u32 offset, u32 size) { BindBuffer(buffer, offset, size); } - void BindStorageBuffer(VkBuffer buffer, u32 offset, u32 size, + void BindStorageBuffer(const Buffer& buffer, u32 offset, u32 size, [[maybe_unused]] bool is_written) { BindBuffer(buffer, offset, size); } void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, VideoCore::Surface::PixelFormat format) { - guest_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format)); + guest_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format), + buffer.DeviceAddress(), offset, size, + TexelBufferFormat(format)); } bool ShouldLimitDynamicStorageBuffers() const { @@ -162,10 +233,17 @@ public: } private: - void BindBuffer(VkBuffer buffer, u32 offset, u32 size) { - guest_descriptor_queue.AddBuffer(buffer, offset, size); + void BindBuffer(const Buffer& buffer, u32 offset, u32 size) { + const VkBuffer handle = buffer.Handle(); + if (handle == VK_NULL_HANDLE) { + guest_descriptor_queue.AddBuffer(handle, 0, 0, VK_WHOLE_SIZE); + } else { + guest_descriptor_queue.AddBuffer(handle, buffer.DeviceAddress(), offset, size); + } } + VkFormat TexelBufferFormat(VideoCore::Surface::PixelFormat format) const; + void ReserveNullBuffer(); vk::Buffer CreateNullBuffer(); @@ -183,6 +261,10 @@ private: std::unique_ptr uint8_pass; QuadIndexedPass quad_index_pass; + MultiRangeBufferCache multi_range_buffers; + boost::container::small_vector multi_range_sources; + VkDeviceSize multi_range_total{}; + bool limit_dynamic_storage_buffers = false; u32 max_dynamic_storage_buffers = (std::numeric_limits::max)(); }; diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp index f198b65d69..ab58560170 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp @@ -17,8 +17,6 @@ #include "common/div_ceil.h" #include "common/vector_math.h" #include "video_core/host_shaders/astc_decoder_comp_spv.h" -#include "video_core/host_shaders/convert_msaa_to_non_msaa_comp_spv.h" -#include "video_core/host_shaders/convert_non_msaa_to_msaa_comp_spv.h" #include "video_core/host_shaders/queries_prefix_scan_sum_comp_spv.h" #include "video_core/host_shaders/queries_prefix_scan_sum_nosubgroups_comp_spv.h" #include "video_core/host_shaders/resolve_conditional_render_comp_spv.h" @@ -26,6 +24,7 @@ #include "video_core/host_shaders/vulkan_uint8_comp_spv.h" #include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h" #include "video_core/renderer_vulkan/vk_compute_pass.h" +#include "video_core/surface.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" @@ -139,33 +138,6 @@ constexpr DescriptorBankInfo ASTC_BANK_INFO{ .score = 2, }; -constexpr std::array MSAA_DESCRIPTOR_SET_BINDINGS{{ - { - .binding = 0, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - .pImmutableSamplers = nullptr, - }, - { - .binding = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - .pImmutableSamplers = nullptr, - }, -}}; - -constexpr DescriptorBankInfo MSAA_BANK_INFO{ - .uniform_buffers = 0, - .storage_buffers = 0, - .texture_buffers = 0, - .image_buffers = 0, - .textures = 0, - .images = 2, - .score = 2, -}; - constexpr VkDescriptorUpdateTemplateEntry INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE{ .dstBinding = 0, .dstArrayElement = 0, @@ -184,15 +156,6 @@ constexpr VkDescriptorUpdateTemplateEntry QUERIES_SCAN_DESCRIPTOR_UPDATE_TEMPLAT .stride = sizeof(DescriptorUpdateEntry), }; -constexpr VkDescriptorUpdateTemplateEntry MSAA_DESCRIPTOR_UPDATE_TEMPLATE{ - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 2, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .offset = 0, - .stride = sizeof(DescriptorUpdateEntry), -}; - constexpr std::array ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY{{ { @@ -234,7 +197,7 @@ struct ConditionalRenderingResolvePushConstants { }; } // Anonymous namespace -ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool, +ComputePass::ComputePass(const Device& device_, Scheduler& scheduler, DescriptorPool& descriptor_pool, vk::Span bindings, vk::Span templates, const DescriptorBankInfo& bank_info, @@ -270,7 +233,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool, .pipelineLayout = *layout, .set = 0, }); - descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, bank_info); + descriptor_allocator = descriptor_pool.Allocator(device, scheduler, *descriptor_set_layout, bank_info); } if (code.empty()) { return; @@ -305,7 +268,7 @@ ComputePass::ComputePass(const Device& device_, DescriptorPool& descriptor_pool, .layout = *layout, .basePipelineHandle = {}, .basePipelineIndex = 0, - }); + }, device.StaticPipelineCache()); } ComputePass::~ComputePass() = default; @@ -313,7 +276,7 @@ ComputePass::~ComputePass() = default; Uint8Pass::Uint8Pass(const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool, StagingBufferPool& staging_buffer_pool_, ComputePassDescriptorQueue& compute_pass_descriptor_queue_) - : ComputePass(device_, descriptor_pool, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS, + : ComputePass(device_, scheduler_, descriptor_pool, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS, INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO, {}, VULKAN_UINT8_COMP_SPV), scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, @@ -326,7 +289,7 @@ std::pair Uint8Pass::Assemble(u32 num_vertices, VkBuffer const u32 staging_size = static_cast(num_vertices * sizeof(u16)); const auto staging = staging_buffer_pool.Request(staging_size, MemoryUsage::DeviceLocal); - compute_pass_descriptor_queue.Acquire(); + compute_pass_descriptor_queue.Acquire(scheduler, 2); compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, num_vertices); compute_pass_descriptor_queue.AddBuffer(staging.buffer, staging.offset, staging_size); const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; @@ -355,7 +318,7 @@ QuadIndexedPass::QuadIndexedPass(const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, StagingBufferPool& staging_buffer_pool_, ComputePassDescriptorQueue& compute_pass_descriptor_queue_) - : ComputePass(device_, descriptor_pool_, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS, + : ComputePass(device_, scheduler_, descriptor_pool_, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS, INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO, COMPUTE_PUSH_CONSTANT_RANGE, VULKAN_QUAD_INDEXED_COMP_SPV), scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, @@ -384,7 +347,7 @@ std::pair QuadIndexedPass::Assemble( const std::size_t staging_size = num_tri_vertices * sizeof(u32); const auto staging = staging_buffer_pool.Request(staging_size, MemoryUsage::DeviceLocal); - compute_pass_descriptor_queue.Acquire(); + compute_pass_descriptor_queue.Acquire(scheduler, 2); compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, input_size); compute_pass_descriptor_queue.AddBuffer(staging.buffer, staging.offset, staging_size); const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; @@ -416,7 +379,7 @@ std::pair QuadIndexedPass::Assemble( ConditionalRenderingResolvePass::ConditionalRenderingResolvePass( const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, ComputePassDescriptorQueue& compute_pass_descriptor_queue_) - : ComputePass(device_, descriptor_pool_, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS, + : ComputePass(device_, scheduler_, descriptor_pool_, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS, INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO, COMPUTE_PUSH_CONSTANT_RANGE, RESOLVE_CONDITIONAL_RENDER_COMP_SPV), @@ -429,7 +392,7 @@ void ConditionalRenderingResolvePass::Resolve(VkBuffer dst_buffer, VkBuffer src_ } const size_t compare_size = compare_to_zero ? 8 : 24; - compute_pass_descriptor_queue.Acquire(); + compute_pass_descriptor_queue.Acquire(scheduler, 2); compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, compare_size); compute_pass_descriptor_queue.AddBuffer(dst_buffer, 0, sizeof(u32)); const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; @@ -454,8 +417,8 @@ void ConditionalRenderingResolvePass::Resolve(VkBuffer dst_buffer, VkBuffer src_ const VkDescriptorSet set = descriptor_allocator.Commit(); device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier); + cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier); cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline); cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, uniforms); @@ -470,7 +433,7 @@ QueriesPrefixScanPass::QueriesPrefixScanPass( const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, ComputePassDescriptorQueue& compute_pass_descriptor_queue_) : ComputePass( - device_, descriptor_pool_, QUERIES_SCAN_DESCRIPTOR_SET_BINDINGS, + device_, scheduler_, descriptor_pool_, QUERIES_SCAN_DESCRIPTOR_SET_BINDINGS, QUERIES_SCAN_DESCRIPTOR_UPDATE_TEMPLATE, QUERIES_SCAN_BANK_INFO, COMPUTE_PUSH_CONSTANT_RANGE, device_.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_BASIC_BIT) && @@ -498,7 +461,7 @@ void QueriesPrefixScanPass::Run(VkBuffer accumulation_buffer, VkBuffer dst_buffe static constexpr size_t DISPATCH_SIZE = 2048U; size_t runs_to_do = std::min(current_runs, DISPATCH_SIZE); current_runs -= runs_to_do; - compute_pass_descriptor_queue.Acquire(); + compute_pass_descriptor_queue.Acquire(scheduler, 3); compute_pass_descriptor_queue.AddBuffer(src_buffer, 0, number_of_sums * sizeof(u64)); compute_pass_descriptor_queue.AddBuffer(dst_buffer, 0, number_of_sums * sizeof(u64)); compute_pass_descriptor_queue.AddBuffer(accumulation_buffer, 0, sizeof(u64)); @@ -537,7 +500,7 @@ void QueriesPrefixScanPass::Run(VkBuffer accumulation_buffer, VkBuffer dst_buffe cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, uniforms); cmdbuf.Dispatch(1, 1, 1); cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, write_barrier); + vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, write_barrier); }); } } @@ -547,7 +510,7 @@ ASTCDecoderPass::ASTCDecoderPass(const Device& device_, Scheduler& scheduler_, StagingBufferPool& staging_buffer_pool_, ComputePassDescriptorQueue& compute_pass_descriptor_queue_, MemoryAllocator& memory_allocator_) - : ComputePass(device_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS, + : ComputePass(device_, scheduler_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS, ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO, COMPUTE_PUSH_CONSTANT_RANGE, ASTC_DECODER_COMP_SPV), scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, @@ -589,9 +552,9 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }; - cmdbuf.PipelineBarrier(is_initialized ? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT - : VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier); + cmdbuf.PipelineBarrier(is_initialized ? vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER + : VkPipelineStageFlags(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT), + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier); cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline); }); for (const VideoCommon::SwizzleParameters& swizzle : swizzles) { @@ -600,7 +563,7 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 8U); const u32 num_dispatches_z = image.info.resources.layers; - compute_pass_descriptor_queue.Acquire(); + compute_pass_descriptor_queue.Acquire(scheduler, 2); compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset, image.guest_size_bytes - swizzle.buffer_offset); compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level)); @@ -648,76 +611,13 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map, }, }; cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, image_barrier); + vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, image_barrier); }); scheduler.Finish(); } -constexpr u32 BL3D_BINDING_SWIZZLE_TABLE = 0; -constexpr u32 BL3D_BINDING_INPUT_BUFFER = 1; -constexpr u32 BL3D_BINDING_OUTPUT_BUFFER = 2; - -constexpr std::array BL3D_DESCRIPTOR_SET_BINDINGS{{ - { - .binding = BL3D_BINDING_SWIZZLE_TABLE, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // swizzle_table[] - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - .pImmutableSamplers = nullptr, - }, - { - .binding = BL3D_BINDING_INPUT_BUFFER, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // block-linear input - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - .pImmutableSamplers = nullptr, - }, - { - .binding = BL3D_BINDING_OUTPUT_BUFFER, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, - .pImmutableSamplers = nullptr, - }, -}}; - -constexpr DescriptorBankInfo BL3D_BANK_INFO{ - .uniform_buffers = 0, - .storage_buffers = 3, - .texture_buffers = 0, - .image_buffers = 0, - .textures = 0, - .images = 0, - .score = 3, -}; - -constexpr std::array - BL3D_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY{{ - { - .dstBinding = BL3D_BINDING_SWIZZLE_TABLE, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .offset = BL3D_BINDING_SWIZZLE_TABLE * sizeof(DescriptorUpdateEntry), - .stride = sizeof(DescriptorUpdateEntry), - }, - { - .dstBinding = BL3D_BINDING_INPUT_BUFFER, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .offset = BL3D_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry), - .stride = sizeof(DescriptorUpdateEntry), - }, - { - .dstBinding = BL3D_BINDING_OUTPUT_BUFFER, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - .offset = BL3D_BINDING_OUTPUT_BUFFER * sizeof(DescriptorUpdateEntry), - .stride = sizeof(DescriptorUpdateEntry), - } - }}; +constexpr u32 BL3D_BINDING_INPUT_BUFFER = 0; +constexpr u32 BL3D_BINDING_OUTPUT_BUFFER = 1; struct alignas(16) BlockLinearUnswizzle3DPushConstants { u32 blocks_dim[3]; // Offset 0 @@ -745,11 +645,50 @@ BlockLinearUnswizzle3DPass::BlockLinearUnswizzle3DPass( DescriptorPool& descriptor_pool_, StagingBufferPool& staging_buffer_pool_, ComputePassDescriptorQueue& compute_pass_descriptor_queue_) - : ComputePass( - device_, descriptor_pool_, - BL3D_DESCRIPTOR_SET_BINDINGS, - BL3D_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, - BL3D_BANK_INFO, + : ComputePass(device_, scheduler_, descriptor_pool_, + std::array{{ + { + .binding = BL3D_BINDING_INPUT_BUFFER, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, // block-linear input + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .pImmutableSamplers = nullptr, + }, + { + .binding = BL3D_BINDING_OUTPUT_BUFFER, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, + .pImmutableSamplers = nullptr, + }, + }}, + std::array{{ + { + .dstBinding = BL3D_BINDING_INPUT_BUFFER, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .offset = BL3D_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry), + .stride = sizeof(DescriptorUpdateEntry), + }, + { + .dstBinding = BL3D_BINDING_OUTPUT_BUFFER, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + .offset = BL3D_BINDING_OUTPUT_BUFFER * sizeof(DescriptorUpdateEntry), + .stride = sizeof(DescriptorUpdateEntry), + } + }}, + DescriptorBankInfo{ + .uniform_buffers = 0, + .storage_buffers = 2, + .texture_buffers = 0, + .image_buffers = 0, + .textures = 0, + .images = 0, + .score = 2, + }, COMPUTE_PUSH_CONSTANT_RANGE, BLOCK_LINEAR_UNSWIZZLE_3D_BCN_COMP_SPV), scheduler{scheduler_}, @@ -769,10 +708,8 @@ void BlockLinearUnswizzle3DPass::Unswizzle( const u32 MAX_BATCH_SLICES = (std::min)(z_count, image.info.size.depth); - if (!image.has_compute_unswizzle_buffer) { - // Allocate exactly what this batch needs - image.AllocateComputeUnswizzleBuffer(MAX_BATCH_SLICES); - } + // Allocate or grow to cover this batch's slice count + image.AllocateComputeUnswizzleBuffer(MAX_BATCH_SLICES); ASSERT(swizzles.size() == 1); const auto& sw = swizzles[0]; @@ -821,9 +758,7 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk( pc.blocks_dim[1] = blocks_y; pc.blocks_dim[2] = z_count; // Only process the count - compute_pass_descriptor_queue.Acquire(); - compute_pass_descriptor_queue.AddBuffer(*image.runtime->swizzle_table_buffer, 0, - image.runtime->swizzle_table_size); + compute_pass_descriptor_queue.Acquire(scheduler, 3); compute_pass_descriptor_queue.AddBuffer(swizzled.buffer, sw.buffer_offset + swizzled.offset, image.guest_size_bytes - sw.buffer_offset); @@ -937,100 +872,4 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk( }); } -MSAACopyPass::MSAACopyPass(const Device& device_, Scheduler& scheduler_, - DescriptorPool& descriptor_pool_, - StagingBufferPool& staging_buffer_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue_) - : ComputePass(device_, descriptor_pool_, MSAA_DESCRIPTOR_SET_BINDINGS, - MSAA_DESCRIPTOR_UPDATE_TEMPLATE, MSAA_BANK_INFO, {}, - CONVERT_NON_MSAA_TO_MSAA_COMP_SPV), - scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_}, - compute_pass_descriptor_queue{compute_pass_descriptor_queue_} { - const auto make_msaa_pipeline = [this](size_t i, std::span code) { - modules[i] = device.GetLogical().CreateShaderModule({ - .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .codeSize = static_cast(code.size_bytes()), - .pCode = code.data(), - }); - pipelines[i] = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{ - .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .stage{ - .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .stage = VK_SHADER_STAGE_COMPUTE_BIT, - .module = *modules[i], - .pName = "main", - .pSpecializationInfo = nullptr, - }, - .layout = *layout, - .basePipelineHandle = {}, - .basePipelineIndex = 0, - }); - }; - make_msaa_pipeline(0, CONVERT_NON_MSAA_TO_MSAA_COMP_SPV); - make_msaa_pipeline(1, CONVERT_MSAA_TO_NON_MSAA_COMP_SPV); -} - -MSAACopyPass::~MSAACopyPass() = default; - -void MSAACopyPass::CopyImage(Image& dst_image, Image& src_image, - std::span copies, - bool msaa_to_non_msaa) { - const VkPipeline msaa_pipeline = *pipelines[msaa_to_non_msaa ? 1 : 0]; - scheduler.RequestOutsideRenderPassOperationContext(); - for (const VideoCommon::ImageCopy& copy : copies) { - ASSERT(copy.src_subresource.base_layer == 0); - ASSERT(copy.src_subresource.num_layers == 1); - ASSERT(copy.dst_subresource.base_layer == 0); - ASSERT(copy.dst_subresource.num_layers == 1); - - compute_pass_descriptor_queue.Acquire(); - compute_pass_descriptor_queue.AddImage( - src_image.StorageImageView(copy.src_subresource.base_level)); - compute_pass_descriptor_queue.AddImage( - dst_image.StorageImageView(copy.dst_subresource.base_level)); - const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()}; - - const Common::Vec3 num_dispatches = { - Common::DivCeil(copy.extent.width, 8U), - Common::DivCeil(copy.extent.height, 8U), - copy.extent.depth, - }; - - scheduler.Record([this, dst = dst_image.Handle(), msaa_pipeline, num_dispatches, - descriptor_data](vk::CommandBuffer cmdbuf) { - const VkDescriptorSet set = descriptor_allocator.Commit(); - device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data); - cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, msaa_pipeline); - cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {}); - cmdbuf.Dispatch(num_dispatches.x, num_dispatches.y, num_dispatches.z); - const VkImageMemoryBarrier write_barrier{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - .pNext = nullptr, - .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, - .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, - .oldLayout = VK_IMAGE_LAYOUT_GENERAL, - .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = dst, - .subresourceRange{ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, write_barrier); - }); - } -} - } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h index 0e5badce01..3d30aa6bdc 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pass.h +++ b/src/video_core/renderer_vulkan/vk_compute_pass.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -35,7 +35,7 @@ struct StagingBufferRef; class ComputePass { public: - explicit ComputePass(const Device& device, DescriptorPool& descriptor_pool, + explicit ComputePass(const Device& device, Scheduler& scheduler, DescriptorPool& descriptor_pool, vk::Span bindings, vk::Span templates, const DescriptorBankInfo& bank_info, @@ -164,23 +164,4 @@ private: ComputePassDescriptorQueue& compute_pass_descriptor_queue; }; - -class MSAACopyPass final : public ComputePass { -public: - explicit MSAACopyPass(const Device& device_, Scheduler& scheduler_, - DescriptorPool& descriptor_pool_, StagingBufferPool& staging_buffer_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue_); - ~MSAACopyPass(); - - void CopyImage(Image& dst_image, Image& src_image, - std::span copies, bool msaa_to_non_msaa); - -private: - Scheduler& scheduler; - StagingBufferPool& staging_buffer_pool; - ComputePassDescriptorQueue& compute_pass_descriptor_queue; - std::array modules; - std::array pipelines; -}; - } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 490dd7acfe..29356cfcec 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -22,6 +22,7 @@ #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/gpu_logging/gpu_logging.h" +#include "common/logging.h" #include "common/settings.h" namespace Vulkan { @@ -30,34 +31,55 @@ using Shader::ImageBufferDescriptor; using Shader::Backend::SPIRV::RESCALING_LAYOUT_WORDS_OFFSET; using Tegra::Texture::TexturePair; -ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipeline_cache_, +ComputePipeline::ComputePipeline(const Device& device_, Scheduler& scheduler, vk::PipelineCache& pipeline_cache_, DescriptorPool& descriptor_pool, GuestDescriptorQueue& guest_descriptor_queue_, + DescriptorBufferRing& descriptor_buffer_ring_, Common::ThreadWorker* thread_worker, PipelineStatistics* pipeline_statistics, VideoCore::ShaderNotify* shader_notify, const Shader::Info& info_, - vk::ShaderModule spv_module_) + vk::ShaderModule spv_module_, u64 shader_hash_) : device{device_}, - pipeline_cache(pipeline_cache_), guest_descriptor_queue{guest_descriptor_queue_}, info{info_}, - spv_module(std::move(spv_module_)) { + pipeline_cache(pipeline_cache_), guest_descriptor_queue{guest_descriptor_queue_}, + descriptor_buffer_ring{descriptor_buffer_ring_}, info{info_}, + shader_hash{shader_hash_}, spv_module(std::move(spv_module_)) { if (shader_notify) { shader_notify->MarkShaderBuilding(); } std::copy_n(info.constant_buffer_used_sizes.begin(), uniform_buffer_sizes.size(), uniform_buffer_sizes.begin()); + num_descriptor_entries = NumDescriptorEntries(info); - auto func{[this, &descriptor_pool, shader_notify, pipeline_statistics] { - DescriptorLayoutBuilder builder{device}; - builder.Add(info, VK_SHADER_STAGE_COMPUTE_BIT); + DescriptorLayoutBuilder builder{device}; + builder.Add(info, VK_SHADER_STAGE_COMPUTE_BIT); - uses_push_descriptor = builder.CanUsePushDescriptor(); - descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor); - pipeline_layout = builder.CreatePipelineLayout(*descriptor_set_layout); + uses_push_descriptor = builder.CanUsePushDescriptor(); + uses_descriptor_buffer = builder.CanUseDescriptorBuffer() && descriptor_buffer_ring.IsValid(); + descriptor_set_layout = + builder.CreateDescriptorSetLayout(uses_push_descriptor, uses_descriptor_buffer); + if (uses_descriptor_buffer) { + descriptor_buffer_layout = builder.MakeDescriptorBufferLayout(*descriptor_set_layout); + if (!descriptor_buffer_ring.CanAllocate(descriptor_buffer_layout.size)) { + LOG_DEBUG(Render_Vulkan, + "Compute shader {:016X} needs {} descriptor bytes per dispatch, falling " + "back to sets", + shader_hash, descriptor_buffer_layout.size); + uses_descriptor_buffer = false; + descriptor_buffer_layout = {}; + descriptor_set_layout = builder.CreateDescriptorSetLayout(false); + } + } + pipeline_layout = builder.CreatePipelineLayout(*descriptor_set_layout); + if (!uses_descriptor_buffer) { descriptor_update_template = builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, uses_push_descriptor); if (!uses_push_descriptor) { - descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, info); + descriptor_allocator = + descriptor_pool.Allocator(device, scheduler, *descriptor_set_layout, info); } + } + + auto func{[this, shader_notify, pipeline_statistics] { const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT, .pNext = nullptr, @@ -67,7 +89,10 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) { flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; } - pipeline = device.GetLogical().CreateComputePipeline(VkComputePipelineCreateInfo{ + if (uses_descriptor_buffer) { + flags |= VK_PIPELINE_CREATE_DESCRIPTOR_BUFFER_BIT_EXT; + } + const VkComputePipelineCreateInfo compute_ci{ .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .pNext = nullptr, .flags = flags, @@ -84,17 +109,30 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel .layout = *pipeline_layout, .basePipelineHandle = 0, .basePipelineIndex = 0, - }, *pipeline_cache); + }; + try { + pipeline = device.GetLogical().CreateComputePipeline(compute_ci, *pipeline_cache); + } catch (const vk::Exception& exception) { + LOG_CRITICAL(Render_Vulkan, "Adreno rejected compute shader {:016X}: {}", shader_hash, + exception.what()); + std::scoped_lock lock{build_mutex}; + is_built = true; + build_condvar.notify_one(); + if (shader_notify) { + shader_notify->MarkShaderComplete(); + } + return; + } // Log compute pipeline creation - if (Settings::values.gpu_logging_enabled.GetValue()) { + if (GPU::Logging::IsActive()) { GPU::Logging::GPULogger::GetInstance().LogPipelineStateChange( "ComputePipeline created" ); } if (pipeline_statistics) { - pipeline_statistics->Collect(*pipeline); + pipeline_statistics->Collect(device, *pipeline); } std::scoped_lock lock{build_mutex}; is_built = true; @@ -110,10 +148,10 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel } } -void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, +bool ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, Tegra::MemoryManager& gpu_memory, Scheduler& scheduler, BufferCache& buffer_cache, TextureCache& texture_cache) { - guest_descriptor_queue.Acquire(); + guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries, uses_descriptor_buffer); buffer_cache.SetComputeUniformBufferState(info.constant_buffer_mask, &uniform_buffer_sizes); buffer_cache.UnbindComputeStorageBuffers(); @@ -125,7 +163,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, ++ssbo_index; } - texture_cache.SynchronizeComputeDescriptors(); + texture_cache.SynchronizeDescriptors(true); boost::container::small_vector views; boost::container::small_vector samplers; @@ -173,14 +211,14 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, const auto handle{read_handle(desc, index)}; views.push_back({handle.first}); - VideoCommon::SamplerId sampler = texture_cache.GetComputeSamplerId(handle.second); + VideoCommon::SamplerId sampler = texture_cache.GetSamplerId(handle.second, true); samplers.push_back(sampler); } } for (const auto& desc : info.image_descriptors) { add_image(desc, desc.is_written); } - texture_cache.FillComputeImageViews(std::span(views.data(), views.size())); + texture_cache.FillImageViews(std::span(views.data(), views.size()), true); buffer_cache.UnbindComputeTextureBuffers(); size_t index{}; @@ -192,8 +230,14 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, is_written = desc.is_written; } ImageView& image_view = texture_cache.GetImageView(views[index].id); + PixelFormat format{image_view.format}; + if constexpr (is_image) { + if (const auto explicit_format{PixelFormatFromImageFormat(desc.format)}) { + format = *explicit_format; + } + } buffer_cache.BindComputeTextureBuffer(index, image_view.GpuAddr(), - image_view.BufferSize(), image_view.format, + image_view.BufferSize(), format, is_written, is_image); ++index; } @@ -223,15 +267,41 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, } // Log compute pipeline binding - if (Settings::values.gpu_logging_enabled.GetValue() && + if (GPU::Logging::IsActive() && Settings::values.gpu_log_vulkan_calls.GetValue()) { GPU::Logging::GPULogger::GetInstance().LogPipelineBind(true, "compute pipeline"); } - const void* const descriptor_data{guest_descriptor_queue.UpdateData()}; + const DescriptorUpdateEntry* const descriptor_data{guest_descriptor_queue.UpdateData()}; + VkDeviceSize descriptor_buffer_offset{}; + u32 descriptor_buffer_chunk{}; + if (uses_descriptor_buffer) { + const DescriptorBufferRing::Allocation alloc{ + descriptor_buffer_ring.Allocate(scheduler, descriptor_buffer_layout.size)}; + if (!alloc.host) { + LOG_DEBUG(Render_Vulkan, "Failed to reserve descriptor memory, skipping dispatch"); + return false; + } + WriteDescriptorBuffer(device, descriptor_buffer_layout, descriptor_data, alloc.host); + descriptor_buffer_offset = alloc.offset; + descriptor_buffer_chunk = alloc.chunk; + } + + const bool bind_descriptor_buffer{ + uses_descriptor_buffer && scheduler.UpdateDescriptorBufferChunk(descriptor_buffer_chunk)}; + const bool is_rescaling = !info.texture_descriptors.empty() || !info.image_descriptors.empty(); - scheduler.Record([this, descriptor_data, is_rescaling, + scheduler.Record([this, descriptor_data, is_rescaling, descriptor_buffer_offset, + descriptor_buffer_chunk, bind_descriptor_buffer, rescaling_data = rescaling.Data()](vk::CommandBuffer cmdbuf) { + if (bind_descriptor_buffer) { + const VkDescriptorBufferBindingInfoEXT binding_info{ + descriptor_buffer_ring.BindingInfo(descriptor_buffer_chunk)}; + cmdbuf.BindDescriptorBuffersEXT(binding_info); + } + if (!pipeline) { + return; + } cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline); if (!descriptor_set_layout) { return; @@ -241,7 +311,11 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, RESCALING_LAYOUT_WORDS_OFFSET, sizeof(rescaling_data), rescaling_data.data()); } - if (uses_push_descriptor) { + if (uses_descriptor_buffer) { + const u32 buffer_index{}; + cmdbuf.SetDescriptorBufferOffsetsEXT(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, + 0, buffer_index, descriptor_buffer_offset); + } else if (uses_push_descriptor) { cmdbuf.PushDescriptorSetWithTemplateKHR(*descriptor_update_template, *pipeline_layout, 0, descriptor_data); } else { @@ -252,6 +326,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, descriptor_set, nullptr); } }); + return true; } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h index aa84c00e12..fba0519b58 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h @@ -13,7 +13,9 @@ #include "common/common_types.h" #include "common/thread_worker.h" #include "shader_recompiler/shader_info.h" +#include "video_core/renderer_vulkan/pipeline_helper.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h" +#include "video_core/renderer_vulkan/vk_descriptor_buffer.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_texture_cache.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" @@ -31,13 +33,14 @@ class Scheduler; class ComputePipeline { public: - explicit ComputePipeline(const Device& device, vk::PipelineCache& pipeline_cache, + explicit ComputePipeline(const Device& device, Scheduler& scheduler, vk::PipelineCache& pipeline_cache, DescriptorPool& descriptor_pool, GuestDescriptorQueue& guest_descriptor_queue, + DescriptorBufferRing& descriptor_buffer_ring, Common::ThreadWorker* thread_worker, PipelineStatistics* pipeline_statistics, VideoCore::ShaderNotify* shader_notify, const Shader::Info& info, - vk::ShaderModule spv_module); + vk::ShaderModule spv_module, u64 shader_hash); ComputePipeline& operator=(ComputePipeline&&) noexcept = delete; ComputePipeline(ComputePipeline&&) noexcept = delete; @@ -45,20 +48,30 @@ public: ComputePipeline& operator=(const ComputePipeline&) = delete; ComputePipeline(const ComputePipeline&) = delete; - void Configure(Tegra::Engines::KeplerCompute& kepler_compute, Tegra::MemoryManager& gpu_memory, - Scheduler& scheduler, BufferCache& buffer_cache, TextureCache& texture_cache); + [[nodiscard]] bool Configure(Tegra::Engines::KeplerCompute& kepler_compute, + Tegra::MemoryManager& gpu_memory, Scheduler& scheduler, + BufferCache& buffer_cache, TextureCache& texture_cache); + + bool IsBound() const noexcept { + return static_cast(pipeline); + } private: const Device& device; vk::PipelineCache& pipeline_cache; GuestDescriptorQueue& guest_descriptor_queue; + DescriptorBufferRing& descriptor_buffer_ring; Shader::Info info; + u64 shader_hash{}; + u32 num_descriptor_entries{}; VideoCommon::ComputeUniformBufferSizes uniform_buffer_sizes{}; vk::ShaderModule spv_module; vk::DescriptorSetLayout descriptor_set_layout; bool uses_push_descriptor{false}; + bool uses_descriptor_buffer{false}; + DescriptorBufferLayout descriptor_buffer_layout; DescriptorAllocator descriptor_allocator; vk::PipelineLayout pipeline_layout; vk::DescriptorUpdateTemplate descriptor_update_template; diff --git a/src/video_core/renderer_vulkan/vk_descriptor_buffer.cpp b/src/video_core/renderer_vulkan/vk_descriptor_buffer.cpp new file mode 100644 index 0000000000..279847fa99 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_descriptor_buffer.cpp @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "common/alignment.h" +#include "common/assert.h" +#include "common/logging.h" +#include "video_core/renderer_vulkan/vk_descriptor_buffer.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +DescriptorBufferRing::DescriptorBufferRing(const Device& device_, + MemoryAllocator& memory_allocator) + : device{device_} { + if (!device.IsExtDescriptorBufferSupported() || !device.IsBufferDeviceAddressSupported()) { + return; + } + const VkPhysicalDeviceDescriptorBufferPropertiesEXT& props{device.DescriptorBufferProperties()}; + alignment = std::max(props.descriptorBufferOffsetAlignment, 1); + + const VkDeviceSize max_bound{(std::min)({props.maxSamplerDescriptorBufferRange, + props.maxResourceDescriptorBufferRange, + props.samplerDescriptorBufferAddressSpaceSize, + props.resourceDescriptorBufferAddressSpaceSize, + props.descriptorBufferAddressSpaceSize})}; + const VkDeviceSize frame_size{device.IsTiler() ? TILER_FRAME_SIZE : DESKTOP_FRAME_SIZE}; + const VkDeviceSize chunk_size{ + Common::AlignDown((std::min)(frame_size, max_bound), alignment)}; + if (chunk_size <= alignment) { + LOG_DEBUG(Render_Vulkan, "Descriptor buffer binding limit of {} is unusable, disabling", + max_bound); + return; + } + chunk_capacity = chunk_size - alignment; + chunks_per_frame = static_cast(frame_size / chunk_size); + + const VkBufferCreateInfo buffer_ci{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = chunk_size, + .usage = VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT | + VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; + const size_t total_chunks{chunks_per_frame * FRAMES_IN_FLIGHT}; + chunks.reserve(total_chunks); + chunk_addresses.reserve(total_chunks); + chunk_hosts.reserve(total_chunks); + for (size_t index = 0; index < total_chunks; ++index) { + vk::Buffer buffer{memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::Upload)}; + if (!buffer.IsHostVisible()) { + LOG_DEBUG(Render_Vulkan, "Descriptor buffer is not host visible, disabling"); + chunks.clear(); + return; + } + if (!buffer.IsHostCoherent()) { + LOG_DEBUG(Render_Vulkan, "Descriptor buffer is not host coherent, disabling"); + chunks.clear(); + return; + } + if (device.HasDebuggingToolAttached()) { + buffer.SetObjectNameEXT("Descriptor buffer"); + } + const VkDeviceAddress raw_address{device.GetLogical().GetBufferDeviceAddress(*buffer)}; + const VkDeviceAddress address{Common::AlignUp(raw_address, alignment)}; + chunk_addresses.push_back(address); + chunk_hosts.push_back(buffer.Mapped().data() + (address - raw_address)); + chunks.push_back(std::move(buffer)); + } +} + +DescriptorBufferRing::~DescriptorBufferRing() = default; + +void DescriptorBufferRing::TickFrame() { + if (++frame_index >= FRAMES_IN_FLIGHT) { + frame_index = 0; + } + chunk_cursor = 0; + cursor = 0; + ++generation; + frame_reused = true; +} + +void DescriptorBufferRing::TouchFrame(Scheduler& scheduler) { + frame_ticks[frame_index] = scheduler.CurrentTick(); +} + +DescriptorBufferRing::Allocation DescriptorBufferRing::Allocate(Scheduler& scheduler, + VkDeviceSize size) { + ASSERT(!chunks.empty()); + if (!CanAllocate(size)) { + LOG_DEBUG(Render_Vulkan, "Descriptor set of {} bytes exceeds chunk capacity {}", size, + chunk_capacity); + return Allocation{}; + } + const VkDeviceSize needed{Common::AlignUp(size, alignment)}; + if (frame_reused) { + frame_reused = false; + scheduler.Wait(frame_ticks[frame_index]); + } + if (cursor + needed > chunk_capacity) { + if (chunk_cursor + 1 < chunks_per_frame) { + ++chunk_cursor; + } else { + LOG_DEBUG(Render_Vulkan, "Descriptor buffer frame exhausted, stalling on the GPU"); + scheduler.Finish(); + chunk_cursor = 0; + ++generation; + } + cursor = 0; + } + const size_t chunk{frame_index * chunks_per_frame + chunk_cursor}; + const VkDeviceSize offset{cursor}; + cursor += needed; + frame_ticks[frame_index] = scheduler.CurrentTick(); + return Allocation{ + .host = chunk_hosts[chunk] + offset, + .offset = offset, + .chunk = static_cast(chunk), + .generation = generation, + }; +} + +VkDescriptorBufferBindingInfoEXT DescriptorBufferRing::BindingInfo(u32 chunk) const noexcept { + return VkDescriptorBufferBindingInfoEXT{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_BUFFER_BINDING_INFO_EXT, + .pNext = nullptr, + .address = chunk_addresses[chunk], + .usage = VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT | + VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT, + }; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_descriptor_buffer.h b/src/video_core/renderer_vulkan/vk_descriptor_buffer.h new file mode 100644 index 0000000000..6f96bcc326 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_descriptor_buffer.h @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +#include "common/alignment.h" +#include "common/common_types.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +class Device; +class Scheduler; + +class DescriptorBufferRing final { + static constexpr size_t FRAMES_IN_FLIGHT = 8; + static constexpr VkDeviceSize TILER_FRAME_SIZE = 2 * 1024 * 1024; + static constexpr VkDeviceSize DESKTOP_FRAME_SIZE = 4 * 1024 * 1024; + +public: + explicit DescriptorBufferRing(const Device& device_, MemoryAllocator& memory_allocator); + ~DescriptorBufferRing(); + + struct Allocation { + u8* host{}; + VkDeviceSize offset{}; + u32 chunk{}; + u64 generation{}; + }; + + [[nodiscard]] u64 CurrentGeneration() const noexcept { + return generation; + } + + void TouchFrame(Scheduler& scheduler); + + [[nodiscard]] bool CanAllocate(VkDeviceSize size) const noexcept { + return Common::AlignUp(size, alignment) <= chunk_capacity; + } + + void TickFrame(); + + [[nodiscard]] Allocation Allocate(Scheduler& scheduler, VkDeviceSize size); + + [[nodiscard]] VkDescriptorBufferBindingInfoEXT BindingInfo(u32 chunk) const noexcept; + + [[nodiscard]] bool IsValid() const noexcept { + return !chunks.empty(); + } + +private: + const Device& device; + std::vector chunks; + std::vector chunk_addresses; + std::vector chunk_hosts; + VkDeviceSize alignment{1}; + VkDeviceSize chunk_capacity{}; + size_t chunks_per_frame{}; + size_t frame_index{}; + size_t chunk_cursor{}; + VkDeviceSize cursor{}; + u64 generation{1}; + std::array frame_ticks{}; + bool frame_reused{}; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp index 3af9758a31..b020d99b93 100644 --- a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -125,27 +125,22 @@ vk::DescriptorSets DescriptorAllocator::AllocateDescriptors(size_t count) { throw vk::Exception(VK_ERROR_OUT_OF_POOL_MEMORY); } -DescriptorPool::DescriptorPool(const Device& device_, Scheduler& scheduler) - : device{device_}, master_semaphore{scheduler.GetMasterSemaphore()} {} - +DescriptorPool::DescriptorPool(const Device& device_, Scheduler& scheduler) {} DescriptorPool::~DescriptorPool() = default; -DescriptorAllocator DescriptorPool::Allocator(VkDescriptorSetLayout layout, - std::span infos) { - return Allocator(layout, MakeBankInfo(infos)); +DescriptorAllocator DescriptorPool::Allocator(const Device& device, Scheduler& scheduler, VkDescriptorSetLayout layout, std::span infos) { + return Allocator(device, scheduler, layout, MakeBankInfo(infos)); } -DescriptorAllocator DescriptorPool::Allocator(VkDescriptorSetLayout layout, - const Shader::Info& info) { - return Allocator(layout, MakeBankInfo(std::array{info})); +DescriptorAllocator DescriptorPool::Allocator(const Device& device, Scheduler& scheduler, VkDescriptorSetLayout layout, const Shader::Info& info) { + return Allocator(device, scheduler, layout, MakeBankInfo(std::array{info})); } -DescriptorAllocator DescriptorPool::Allocator(VkDescriptorSetLayout layout, - const DescriptorBankInfo& info) { - return DescriptorAllocator(device, master_semaphore, Bank(info), layout); +DescriptorAllocator DescriptorPool::Allocator(const Device& device, Scheduler& scheduler, VkDescriptorSetLayout layout, const DescriptorBankInfo& info) { + return DescriptorAllocator(device, scheduler.GetMasterSemaphore(), Bank(device, info), layout); } -DescriptorBank& DescriptorPool::Bank(const DescriptorBankInfo& reqs) { +DescriptorBank& DescriptorPool::Bank(const Device& device, const DescriptorBankInfo& reqs) { std::shared_lock read_lock{banks_mutex}; const auto it = std::ranges::find_if(bank_infos, [&reqs](const DescriptorBankInfo& bank) { return std::abs(bank.score - reqs.score) < SCORE_THRESHOLD && bank.IsSuperset(reqs); diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.h b/src/video_core/renderer_vulkan/vk_descriptor_pool.h index 4aada5a006..4efeb60373 100644 --- a/src/video_core/renderer_vulkan/vk_descriptor_pool.h +++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -68,17 +71,12 @@ public: DescriptorPool& operator=(const DescriptorPool&) = delete; DescriptorPool(const DescriptorPool&) = delete; - DescriptorAllocator Allocator(VkDescriptorSetLayout layout, - std::span infos); - DescriptorAllocator Allocator(VkDescriptorSetLayout layout, const Shader::Info& info); - DescriptorAllocator Allocator(VkDescriptorSetLayout layout, const DescriptorBankInfo& info); + DescriptorAllocator Allocator(const Device& device, Scheduler& scheduler, VkDescriptorSetLayout layout, std::span infos); + DescriptorAllocator Allocator(const Device& device, Scheduler& scheduler, VkDescriptorSetLayout layout, const Shader::Info& info); + DescriptorAllocator Allocator(const Device& device, Scheduler& scheduler, VkDescriptorSetLayout layout, const DescriptorBankInfo& info); private: - DescriptorBank& Bank(const DescriptorBankInfo& reqs); - - const Device& device; - MasterSemaphore& master_semaphore; - + DescriptorBank& Bank(const Device& device, const DescriptorBankInfo& reqs); std::shared_mutex banks_mutex; std::vector bank_infos; std::vector> banks; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 43fbefe425..8b5b0bc9c5 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include @@ -49,7 +50,6 @@ using VideoCore::Surface::PixelFormatFromDepthFormat; using VideoCore::Surface::PixelFormatFromRenderTargetFormat; constexpr size_t NUM_STAGES = Maxwell::MaxShaderStage; -constexpr size_t INLINE_IMAGE_ELEMENTS = 64; DescriptorLayoutBuilder MakeBuilder(const Device& device, std::span infos) { DescriptorLayoutBuilder builder{device}; @@ -125,8 +125,8 @@ PixelFormat DecodeFormat(u8 encoded_format) { return PixelFormatFromRenderTargetFormat(format); } -RenderPassKey MakeRenderPassKey(const FixedPipelineState& state) { - RenderPassKey key; +RenderPassKey MakeRenderPassKey(const FixedPipelineState& state, const Device& device) { + RenderPassKey key{}; std::ranges::transform(state.color_formats, key.color_formats.begin(), DecodeFormat); if (state.depth_enabled != 0) { const auto depth_format{static_cast(state.depth_format.Value())}; @@ -135,6 +135,11 @@ RenderPassKey MakeRenderPassKey(const FixedPipelineState& state) { key.depth_format = PixelFormat::Invalid; } key.samples = MaxwellToVK::MsaaMode(state.msaa_mode); + const bool has_color = std::ranges::any_of(key.color_formats, [](PixelFormat format) { + return format != PixelFormat::Invalid; + }); + key.resolve_color = + key.samples != VK_SAMPLE_COUNT_1_BIT && has_color && device.IsTiler(); return key; } @@ -246,13 +251,15 @@ GraphicsPipeline::GraphicsPipeline( Scheduler& scheduler_, BufferCache& buffer_cache_, TextureCache& texture_cache_, vk::PipelineCache& pipeline_cache_, VideoCore::ShaderNotify* shader_notify, const Device& device_, DescriptorPool& descriptor_pool, - GuestDescriptorQueue& guest_descriptor_queue_, Common::ThreadWorker* worker_thread, + GuestDescriptorQueue& guest_descriptor_queue_, DescriptorBufferRing& descriptor_buffer_ring_, + Common::ThreadWorker* worker_thread, PipelineStatistics* pipeline_statistics, RenderPassCache& render_pass_cache, const GraphicsPipelineCacheKey& key_, std::array stages, const std::array& infos) : key{key_}, device{device_}, texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, pipeline_cache(pipeline_cache_), scheduler{scheduler_}, - guest_descriptor_queue{guest_descriptor_queue_}, spv_modules{std::move(stages)} { + guest_descriptor_queue{guest_descriptor_queue_}, + descriptor_buffer_ring{descriptor_buffer_ring_}, spv_modules{std::move(stages)} { if (shader_notify) { shader_notify->MarkShaderBuilding(); } @@ -269,27 +276,56 @@ GraphicsPipeline::GraphicsPipeline( num_textures += Shader::NumDescriptors(info->texture_descriptors); num_image_elements += Shader::NumDescriptors(info->texture_descriptors); num_image_elements += Shader::NumDescriptors(info->image_descriptors); + num_descriptor_entries += NumDescriptorEntries(*info); } fragment_has_color0_output = stage_infos[NUM_STAGES - 1].stores_frag_color[0]; - auto func{[this, shader_notify, &render_pass_cache, &descriptor_pool, pipeline_statistics] { - DescriptorLayoutBuilder builder{MakeBuilder(device, stage_infos)}; - uses_push_descriptor = builder.CanUsePushDescriptor(); - descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor); - if (!uses_push_descriptor) { - descriptor_allocator = descriptor_pool.Allocator(*descriptor_set_layout, stage_infos); + DescriptorLayoutBuilder builder{MakeBuilder(device, stage_infos)}; + uses_push_descriptor = builder.CanUsePushDescriptor(); + uses_descriptor_buffer = builder.CanUseDescriptorBuffer() && descriptor_buffer_ring.IsValid(); + descriptor_set_layout = + builder.CreateDescriptorSetLayout(uses_push_descriptor, uses_descriptor_buffer); + if (uses_descriptor_buffer) { + descriptor_buffer_layout = builder.MakeDescriptorBufferLayout(*descriptor_set_layout); + if (!descriptor_buffer_ring.CanAllocate(descriptor_buffer_layout.size)) { + LOG_WARNING(Render_Vulkan, + "Graphics pipeline {:016X} needs {} descriptor bytes per draw, falling back " + "to sets", + key.Hash(), descriptor_buffer_layout.size); + uses_descriptor_buffer = false; + descriptor_buffer_layout = {}; + descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor); } + } - const VkDescriptorSetLayout set_layout{*descriptor_set_layout}; - pipeline_layout = builder.CreatePipelineLayout(set_layout); + const VkDescriptorSetLayout set_layout{*descriptor_set_layout}; + pipeline_layout = builder.CreatePipelineLayout(set_layout); + if (!uses_descriptor_buffer) { descriptor_update_template = builder.CreateTemplate(set_layout, *pipeline_layout, uses_push_descriptor); + if (!uses_push_descriptor) { + descriptor_allocator = + descriptor_pool.Allocator(device, scheduler, set_layout, stage_infos); + } + } - const VkRenderPass render_pass{render_pass_cache.Get(MakeRenderPassKey(key.state))}; + auto func{[this, shader_notify, &render_pass_cache, pipeline_statistics] { + const VkRenderPass render_pass{render_pass_cache.Get(MakeRenderPassKey(key.state, device))}; Validate(); - MakePipeline(render_pass); + try { + MakePipeline(render_pass); + } catch (const vk::Exception& exception) { + LOG_CRITICAL(Render_Vulkan, "Graphics pipeline build failed: {}", exception.what()); + std::scoped_lock lock{build_mutex}; + is_built = true; + build_condvar.notify_one(); + if (shader_notify) { + shader_notify->MarkShaderComplete(); + } + return; + } if (pipeline_statistics) { - pipeline_statistics->Collect(*pipeline); + pipeline_statistics->Collect(device, *pipeline); } std::scoped_lock lock{build_mutex}; @@ -314,12 +350,12 @@ void GraphicsPipeline::AddTransition(GraphicsPipeline* transition) { template bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { - small_vector views; - small_vector samplers; + boost::container::small_vector views; + boost::container::small_vector samplers; views.reserve(num_image_elements); samplers.reserve(num_textures); - texture_cache.SynchronizeGraphicsDescriptors(); + texture_cache.SynchronizeDescriptors(false); buffer_cache.SetUniformBuffersState(enabled_uniform_buffer_masks, &uniform_buffer_sizes); @@ -384,7 +420,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { const auto handle{read_handle(desc, index)}; views.push_back({handle.first}); - VideoCommon::SamplerId sampler{texture_cache.GetGraphicsSamplerId(handle.second)}; + VideoCommon::SamplerId sampler{texture_cache.GetSamplerId(handle.second, false)}; samplers.push_back(sampler); } } @@ -413,7 +449,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { } ASSERT(views.size() == num_image_elements); ASSERT(samplers.size() == num_textures); - texture_cache.FillGraphicsImageViews(std::span(views.data(), views.size())); + texture_cache.FillImageViews(std::span(views.data(), views.size()), false, Spec::has_images); VideoCommon::ImageViewInOut* texture_buffer_it{views.data()}; const auto bind_stage_info{[&](size_t stage) LAMBDA_FORCEINLINE { @@ -426,8 +462,14 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { is_written = desc.is_written; } ImageView& image_view{texture_cache.GetImageView(texture_buffer_it->id)}; + PixelFormat format{image_view.format}; + if constexpr (is_image) { + if (const auto explicit_format{PixelFormatFromImageFormat(desc.format)}) { + format = *explicit_format; + } + } buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(), - image_view.BufferSize(), image_view.format, + image_view.BufferSize(), format, is_written, is_image); ++index; ++texture_buffer_it; @@ -474,7 +516,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { buffer_cache.UpdateGraphicsBuffers(is_indexed); buffer_cache.BindHostGeometryBuffers(is_indexed); - guest_descriptor_queue.Acquire(); + guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries, uses_descriptor_buffer); RescalingPushConstant rescaling; RenderAreaPushConstant render_area; @@ -513,13 +555,46 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { texture_cache.UpdateRenderTargets(false); texture_cache.CheckFeedbackLoop(std::span{views.data(), views.size()}); - ConfigureDraw(rescaling, render_area); - - return true; + if (IsBuilt() && !pipeline) { + return false; + } + return ConfigureDraw(rescaling, render_area); } -void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, +bool GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, const RenderAreaPushConstant& render_area) { + const void* const descriptor_data{guest_descriptor_queue.UpdateData()}; + + VkDeviceSize descriptor_buffer_offset{}; + u32 descriptor_buffer_chunk{}; + if (descriptor_set_layout && uses_descriptor_buffer) { + const auto* const entries = static_cast(descriptor_data); + const bool reuse_allocation = + last_descriptor_buffer_generation == descriptor_buffer_ring.CurrentGeneration() && + last_descriptor_payload.size() == num_descriptor_entries && + std::memcmp(last_descriptor_payload.data(), entries, + num_descriptor_entries * sizeof(DescriptorUpdateEntry)) == 0; + if (reuse_allocation) { + descriptor_buffer_offset = last_descriptor_buffer_offset; + descriptor_buffer_chunk = last_descriptor_buffer_chunk; + descriptor_buffer_ring.TouchFrame(scheduler); + } else { + const DescriptorBufferRing::Allocation alloc{ + descriptor_buffer_ring.Allocate(scheduler, descriptor_buffer_layout.size)}; + if (!alloc.host) { + LOG_DEBUG(Render_Vulkan, "Failed to reserve descriptor memory, skipping draw"); + return false; + } + WriteDescriptorBuffer(device, descriptor_buffer_layout, entries, alloc.host); + descriptor_buffer_offset = alloc.offset; + descriptor_buffer_chunk = alloc.chunk; + last_descriptor_buffer_offset = alloc.offset; + last_descriptor_buffer_chunk = alloc.chunk; + last_descriptor_buffer_generation = alloc.generation; + last_descriptor_payload.assign(entries, entries + num_descriptor_entries); + } + } + scheduler.RequestRenderpass(texture_cache.GetFramebuffer()); if (!is_built.load(std::memory_order::relaxed)) { // Wait for the pipeline to be built @@ -531,20 +606,42 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, const bool is_rescaling{texture_cache.IsRescaling()}; const bool update_rescaling{scheduler.UpdateRescaling(is_rescaling)}; const bool bind_pipeline{scheduler.UpdateGraphicsPipeline(this)}; + const bool bind_descriptor_buffer{ + descriptor_set_layout && uses_descriptor_buffer && + scheduler.UpdateDescriptorBufferChunk(descriptor_buffer_chunk)}; // Log graphics pipeline binding - if (bind_pipeline && Settings::values.gpu_logging_enabled.GetValue() && + if (bind_pipeline && GPU::Logging::IsActive() && Settings::values.gpu_log_vulkan_calls.GetValue()) { - const std::string pipeline_info = fmt::format("hash=0x{:016x}", key.Hash()); + const std::string pipeline_info = fmt::format("hash={:#016x}", key.Hash()); GPU::Logging::GPULogger::GetInstance().LogPipelineBind(false, pipeline_info); } - const void* const descriptor_data{guest_descriptor_queue.UpdateData()}; - scheduler.Record([this, descriptor_data, bind_pipeline, rescaling_data = rescaling.Data(), - is_rescaling, update_rescaling, + bool update_descriptors = true; + if (descriptor_set_layout && !uses_push_descriptor && !uses_descriptor_buffer) { + const auto* const entries = static_cast(descriptor_data); + update_descriptors = + bind_pipeline || last_descriptor_payload.size() != num_descriptor_entries || + std::memcmp(last_descriptor_payload.data(), entries, + num_descriptor_entries * sizeof(DescriptorUpdateEntry)) != 0; + if (update_descriptors) { + last_descriptor_payload.assign(entries, entries + num_descriptor_entries); + } + } + scheduler.Record([this, descriptor_data, bind_pipeline, update_descriptors, + descriptor_buffer_offset, descriptor_buffer_chunk, bind_descriptor_buffer, + rescaling_data = rescaling.Data(), is_rescaling, update_rescaling, uses_render_area = render_area.uses_render_area, render_area_data = render_area.words](vk::CommandBuffer cmdbuf) { + if (bind_descriptor_buffer) { + const VkDescriptorBufferBindingInfoEXT binding_info{ + descriptor_buffer_ring.BindingInfo(descriptor_buffer_chunk)}; + cmdbuf.BindDescriptorBuffersEXT(binding_info); + } if (bind_pipeline) { + if (!pipeline) { + return; + } cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline); } cmdbuf.PushConstants(*pipeline_layout, VK_SHADER_STAGE_ALL_GRAPHICS, @@ -565,10 +662,14 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, if (!descriptor_set_layout) { return; } - if (uses_push_descriptor) { + if (uses_descriptor_buffer) { + const u32 buffer_index{}; + cmdbuf.SetDescriptorBufferOffsetsEXT(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, + 0, buffer_index, descriptor_buffer_offset); + } else if (uses_push_descriptor) { cmdbuf.PushDescriptorSetWithTemplateKHR(*descriptor_update_template, *pipeline_layout, 0, descriptor_data); - } else { + } else if (update_descriptors) { const VkDescriptorSet descriptor_set{descriptor_allocator.Commit()}; const vk::Device& dev{device.GetLogical()}; dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data); @@ -576,6 +677,7 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, descriptor_set, nullptr); } }); + return true; } void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { @@ -865,18 +967,17 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE_EXT, VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT, VK_DYNAMIC_STATE_STENCIL_OP_EXT, + VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT, }; dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end()); - // VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT is part of EDS1 - // Only use it if VIDS is not active (VIDS replaces it with full vertex input control) + // VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT if (!key.state.dynamic_vertex_input) { dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT); } } - // VK_DYNAMIC_STATE_VERTEX_INPUT_EXT (VIDS) - Independent from EDS - // Provides full dynamic vertex input control, replaces VERTEX_INPUT_BINDING_STRIDE + // VK_DYNAMIC_STATE_VERTEX_INPUT_EXT if (key.state.dynamic_vertex_input) { dynamic_states.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_EXT); } @@ -906,6 +1007,11 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { dynamic_states.insert(dynamic_states.end(), extended3.begin(), extended3.end()); } + // VK_EXT_color_write_enable fallback for fully on/off render targets when EDS3 blending is not available. + if (!key.state.extended_dynamic_state_3_blend && key.state.color_write_enable_dynamic) { + dynamic_states.push_back(VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT); + } + // EDS3 - Enables (composite: per-feature) if (key.state.extended_dynamic_state_3_enables) { if (device.SupportsDynamicState3DepthClampEnable()) { @@ -963,6 +1069,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) { flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; } + if (uses_descriptor_buffer) { + flags |= VK_PIPELINE_CREATE_DESCRIPTOR_BUFFER_BIT_EXT; + } pipeline = device.GetLogical().CreateGraphicsPipeline({ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, @@ -987,7 +1096,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { }, *pipeline_cache); // Log graphics pipeline creation - if (Settings::values.gpu_logging_enabled.GetValue()) { + if (GPU::Logging::IsActive()) { const std::string pipeline_info = fmt::format( "GraphicsPipeline created: stages={}, attachments={}", shader_stages.size(), diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index 1a41e50a36..b1ac5a3fcc 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -12,14 +12,18 @@ #include #include #include +#include #include "common/thread_worker.h" #include "shader_recompiler/shader_info.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/renderer_vulkan/fixed_pipeline_state.h" +#include "video_core/renderer_vulkan/pipeline_helper.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h" +#include "video_core/renderer_vulkan/vk_descriptor_buffer.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_texture_cache.h" +#include "video_core/renderer_vulkan/vk_update_descriptor.h" #include "video_core/vulkan_common/vulkan_wrapper.h" namespace VideoCore { @@ -76,7 +80,8 @@ public: Scheduler& scheduler, BufferCache& buffer_cache, TextureCache& texture_cache, vk::PipelineCache& pipeline_cache, VideoCore::ShaderNotify* shader_notify, const Device& device, DescriptorPool& descriptor_pool, - GuestDescriptorQueue& guest_descriptor_queue, Common::ThreadWorker* worker_thread, + GuestDescriptorQueue& guest_descriptor_queue, + DescriptorBufferRing& descriptor_buffer_ring, Common::ThreadWorker* worker_thread, PipelineStatistics* pipeline_statistics, RenderPassCache& render_pass_cache, const GraphicsPipelineCacheKey& key, std::array stages, const std::array& infos); @@ -132,7 +137,7 @@ private: template bool ConfigureImpl(bool is_indexed); - void ConfigureDraw(const RescalingPushConstant& rescaling, + bool ConfigureDraw(const RescalingPushConstant& rescaling, const RenderAreaPushConstant& render_are); void MakePipeline(VkRenderPass render_pass); @@ -148,6 +153,7 @@ private: vk::PipelineCache& pipeline_cache; Scheduler& scheduler; GuestDescriptorQueue& guest_descriptor_queue; + DescriptorBufferRing& descriptor_buffer_ring; bool (*configure_func)(GraphicsPipeline*, bool){}; @@ -159,6 +165,7 @@ private: std::array stage_infos; std::array enabled_uniform_buffer_masks{}; VideoCommon::UniformBufferSizes uniform_buffer_sizes{}; + u32 num_descriptor_entries{}; size_t num_image_elements{}; u32 num_textures{}; bool fragment_has_color0_output{}; @@ -169,10 +176,17 @@ private: vk::DescriptorUpdateTemplate descriptor_update_template; vk::Pipeline pipeline; + DescriptorBufferLayout descriptor_buffer_layout; + std::vector last_descriptor_payload; + VkDeviceSize last_descriptor_buffer_offset{}; + u32 last_descriptor_buffer_chunk{}; + u64 last_descriptor_buffer_generation{}; + std::condition_variable build_condvar; std::mutex build_mutex; std::atomic_bool is_built{false}; bool uses_push_descriptor{false}; + bool uses_descriptor_buffer{false}; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp index b535097b36..be5a1144ce 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp @@ -130,6 +130,70 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, VkSemaphore wait_semaphore, u64 host_tick) { const VkSemaphore timeline_semaphore = *semaphore; + if (device.HasSynchronization2()) { + const std::array cmdbuffer_infos{{ + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .pNext = nullptr, + .commandBuffer = *upload_cmdbuf, + .deviceMask = 0, + }, + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .pNext = nullptr, + .commandBuffer = *cmdbuf, + .deviceMask = 0, + }, + }}; + + std::array signal_infos{{ + { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = timeline_semaphore, + .value = host_tick, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, + .deviceIndex = 0, + }, + {}, + }}; + u32 num_signal_semaphores = 1; + if (signal_semaphore) { + signal_infos[1] = VkSemaphoreSubmitInfo{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = signal_semaphore, + .value = 0, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, + .deviceIndex = 0, + }; + num_signal_semaphores = 2; + } + + const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; + const VkSemaphoreSubmitInfo wait_info{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = wait_semaphore, + .value = 0, + .stageMask = static_cast(wait_stage_mask), + .deviceIndex = 0, + }; + + const VkSubmitInfo2 submit_info2{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, + .pNext = nullptr, + .flags = 0, + .waitSemaphoreInfoCount = num_wait_semaphores, + .pWaitSemaphoreInfos = num_wait_semaphores ? &wait_info : nullptr, + .commandBufferInfoCount = static_cast(cmdbuffer_infos.size()), + .pCommandBufferInfos = cmdbuffer_infos.data(), + .signalSemaphoreInfoCount = num_signal_semaphores, + .pSignalSemaphoreInfos = signal_infos.data(), + }; + return device.GetGraphicsQueue().Submit2(submit_info2); + } + const u32 num_signal_semaphores = signal_semaphore ? 2 : 1; const std::array signal_values{host_tick, u64(0)}; const std::array signal_semaphores{timeline_semaphore, signal_semaphore}; @@ -172,6 +236,66 @@ VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf, VkSemaphore signal_semaphore, VkSemaphore wait_semaphore, u64 host_tick) { + if (device.HasSynchronization2()) { + const std::array cmdbuffer_infos{{ + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .pNext = nullptr, + .commandBuffer = *upload_cmdbuf, + .deviceMask = 0, + }, + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .pNext = nullptr, + .commandBuffer = *cmdbuf, + .deviceMask = 0, + }, + }}; + + const u32 num_signal_semaphores = signal_semaphore ? 1 : 0; + const VkSemaphoreSubmitInfo signal_info{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = signal_semaphore, + .value = 0, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, + .deviceIndex = 0, + }; + + const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; + const VkSemaphoreSubmitInfo wait_info{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = wait_semaphore, + .value = 0, + .stageMask = static_cast(wait_stage_mask), + .deviceIndex = 0, + }; + + const VkSubmitInfo2 submit_info2{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, + .pNext = nullptr, + .flags = 0, + .waitSemaphoreInfoCount = num_wait_semaphores, + .pWaitSemaphoreInfos = num_wait_semaphores ? &wait_info : nullptr, + .commandBufferInfoCount = static_cast(cmdbuffer_infos.size()), + .pCommandBufferInfos = cmdbuffer_infos.data(), + .signalSemaphoreInfoCount = num_signal_semaphores, + .pSignalSemaphoreInfos = num_signal_semaphores ? &signal_info : nullptr, + }; + + auto fence = GetFreeFence(); + auto result = device.GetGraphicsQueue().Submit2(submit_info2, *fence); + + if (result == VK_SUCCESS) { + std::scoped_lock lock{wait_mutex}; + wait_queue.emplace(host_tick, std::move(fence)); + wait_cv.notify_one(); + } + + return result; + } + const u32 num_signal_semaphores = signal_semaphore ? 1 : 0; const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; diff --git a/src/video_core/renderer_vulkan/vk_multi_range_buffer.cpp b/src/video_core/renderer_vulkan/vk_multi_range_buffer.cpp new file mode 100644 index 0000000000..4d84390002 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_multi_range_buffer.cpp @@ -0,0 +1,338 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include + +#include "video_core/renderer_vulkan/vk_multi_range_buffer.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +MultiRangeBufferCache::MultiRangeBufferCache(const Device& device) { + sparse_usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + if (device.IsBufferDeviceAddressSupported()) { + sparse_usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + } + if (!device.IsSparseBindingSupported()) { + return; + } + u32 memory_type_bits = 0; + const VkDeviceSize queried = QueryBlockSize(device, memory_type_bits); + if (queried == 0 || memory_type_bits == 0) { + return; + } + block_size = queried; + sparse_memory_type_bits = memory_type_bits; + use_sparse = true; +} + +VkDeviceSize MultiRangeBufferCache::QueryBlockSize(const Device& device, + u32& memory_type_bits) const { + const VkDevice logical = *device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + const VkBufferCreateInfo probe_ci{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = VK_BUFFER_CREATE_SPARSE_BINDING_BIT | VK_BUFFER_CREATE_SPARSE_ALIASED_BIT, + .size = DEFAULT_BLOCK_SIZE, + .usage = sparse_usage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; + VkBuffer probe{}; + if (dld.vkCreateBuffer(logical, &probe_ci, nullptr, &probe) != VK_SUCCESS) { + return 0; + } + const SparseBuffer owned{probe, logical, dld}; + const VkBufferMemoryRequirementsInfo2 reqs_info{ + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2, + .pNext = nullptr, + .buffer = probe, + }; + VkMemoryRequirements2 reqs2{ + .sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2, + .pNext = nullptr, + .memoryRequirements = {}, + }; + dld.vkGetBufferMemoryRequirements2(logical, &reqs_info, &reqs2); + memory_type_bits = reqs2.memoryRequirements.memoryTypeBits; + return reqs2.memoryRequirements.alignment; +} + +u64 MultiRangeBufferCache::HashSources(std::span sources) const { + u64 hash = 0xcbf29ce484222325ULL; + const auto mix = [&hash](u64 value) { + hash ^= value; + hash *= 0x100000001b3ULL; + }; + for (const MultiRangeSource& source : sources) { + mix(u64(source.handle)); + mix(u64(source.offset)); + mix(u64(source.size)); + } + return hash; +} + +u64 MultiRangeBufferCache::HashContent(std::span sources) const { + u64 hash = 0xcbf29ce484222325ULL; + for (const MultiRangeSource& source : sources) { + hash ^= source.write_tick; + hash *= 0x100000001b3ULL; + } + return hash; +} + +bool MultiRangeBufferCache::CanBindSparse(std::span sources) const { + return use_sparse && + std::none_of(sources.begin(), sources.end(), + [block = block_size, bits = sparse_memory_type_bits](auto const& e) { + const VkDeviceSize memory_offset = e.memory_offset + e.offset; + return e.memory == VK_NULL_HANDLE || e.memory_type >= 32 || + ((bits >> e.memory_type) & 1) == 0 || + (memory_offset % block) != 0 || (e.size % block) != 0; + }); +} + +SparseBuffer MultiRangeBufferCache::CreateSparse(const Device& device, Scheduler& scheduler, + std::span sources, + VkDeviceSize total) { + const VkDevice logical = *device.GetLogical(); + const auto& dld = device.GetDispatchLoader(); + const VkBufferCreateInfo buffer_ci{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = VK_BUFFER_CREATE_SPARSE_BINDING_BIT | VK_BUFFER_CREATE_SPARSE_ALIASED_BIT, + .size = total, + .usage = sparse_usage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; + VkBuffer raw{}; + if (dld.vkCreateBuffer(logical, &buffer_ci, nullptr, &raw) != VK_SUCCESS) { + return SparseBuffer{}; + } + SparseBuffer handle{raw, logical, dld}; + std::vector binds; + binds.reserve(sources.size()); + VkDeviceSize resource_offset = 0; + for (const MultiRangeSource& source : sources) { + binds.push_back(VkSparseMemoryBind{ + .resourceOffset = resource_offset, + .size = source.size, + .memory = source.memory, + .memoryOffset = source.memory_offset + source.offset, + .flags = 0, + }); + resource_offset += source.size; + } + const VkSparseBufferMemoryBindInfo buffer_bind{ + .buffer = raw, + .bindCount = static_cast(binds.size()), + .pBinds = binds.data(), + }; + const VkBindSparseInfo bind_info{ + .sType = VK_STRUCTURE_TYPE_BIND_SPARSE_INFO, + .pNext = nullptr, + .waitSemaphoreCount = 0, + .pWaitSemaphores = nullptr, + .bufferBindCount = 1, + .pBufferBinds = &buffer_bind, + .imageOpaqueBindCount = 0, + .pImageOpaqueBinds = nullptr, + .imageBindCount = 0, + .pImageBinds = nullptr, + .signalSemaphoreCount = 0, + .pSignalSemaphores = nullptr, + }; + const VkFenceCreateInfo fence_ci{ + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + }; + vk::Fence fence = device.GetLogical().CreateFence(fence_ci); + VkResult bind_result = VK_ERROR_UNKNOWN; + { + std::scoped_lock lock{scheduler.submit_mutex}; + bind_result = device.GetGraphicsQueue().BindSparse(bind_info, *fence); + } + if (bind_result != VK_SUCCESS) { + return SparseBuffer{}; + } + fence.Wait(); + return handle; +} + +void MultiRangeBufferCache::RetireEntry(Scheduler& scheduler, Entry& entry) { + if (!entry.sparse_handle && !entry.gathered) { + return; + } + if (retired.size() == retired.capacity()) { + DrainRetired(scheduler); + } + if (retired.size() == retired.capacity()) { + u64 oldest = retired.front().tick; + for (const Retired& item : retired) { + if (item.tick < oldest) { + oldest = item.tick; + } + } + scheduler.Wait(oldest); + DrainRetired(scheduler); + } + retired.push_back(Retired{ + .handle = std::move(entry.sparse_handle), + .gathered = std::move(entry.gathered), + .tick = scheduler.CurrentTick(), + }); +} + +void MultiRangeBufferCache::DrainRetired(Scheduler& scheduler) { + size_t index = 0; + while (index < retired.size()) { + if (scheduler.IsFree(retired[index].tick)) { + if (index + 1 != retired.size()) { + retired[index] = std::move(retired.back()); + } + retired.pop_back(); + } else { + ++index; + } + } +} + +MultiRangeRef MultiRangeBufferCache::Get(const Device& device, Scheduler& scheduler, + MemoryAllocator& memory_allocator, u64 key, + std::span sources, + VkDeviceSize total) { + if (sources.empty() || total == 0) { + return MultiRangeRef{}; + } + if (!retired.empty()) { + DrainRetired(scheduler); + } + const u64 geometry = HashSources(sources); + const u64 content = HashContent(sources); + const auto it = entries.find(key); + if (it != entries.end() && it->second.geometry == geometry && it->second.size == total) { + Entry& entry = it->second; + if (entry.content != content) { + entry.content = content; + entry.dirty = true; + } + MultiRangeRef ref{ + .handle = *entry.sparse_handle, + .address = entry.address, + .size = entry.size, + .sparse = true, + .needs_gather = false, + }; + if (!entry.sparse_handle) { + ref.handle = *entry.gathered; + ref.sparse = false; + ref.needs_gather = entry.dirty; + } + return ref; + } + if (it != entries.end()) { + RetireEntry(scheduler, it->second); + entries.erase(it); + } + + Entry entry{}; + entry.geometry = geometry; + entry.content = content; + entry.size = total; + if (CanBindSparse(sources)) { + entry.sparse_handle = CreateSparse(device, scheduler, sources, total); + if (entry.sparse_handle) { + entry.owners.reserve(sources.size()); + for (const MultiRangeSource& source : sources) { + entry.owners.push_back(source.handle); + } + } + } + if (!entry.sparse_handle) { + VkBufferUsageFlags flags = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT | + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + if (device.IsBufferDeviceAddressSupported()) { + flags |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + } + const VkBufferCreateInfo gather_ci{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = total, + .usage = flags, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; + entry.gathered = memory_allocator.CreateBuffer(gather_ci, MemoryUsage::DeviceLocal); + entry.dirty = true; + } + if (device.IsBufferDeviceAddressSupported()) { + VkBuffer address_handle = *entry.sparse_handle; + if (!entry.sparse_handle) { + address_handle = *entry.gathered; + } + entry.address = device.GetLogical().GetBufferDeviceAddress(address_handle); + } + + MultiRangeRef ref{ + .handle = *entry.sparse_handle, + .address = entry.address, + .size = entry.size, + .sparse = true, + .needs_gather = false, + }; + if (!entry.sparse_handle) { + ref.handle = *entry.gathered; + ref.sparse = false; + ref.needs_gather = true; + } + entries.emplace(key, std::move(entry)); + return ref; +} + +void MultiRangeBufferCache::MarkGathered(u64 key) { + if (auto const it = entries.find(key); it != entries.end()) { + it->second.dirty = false; + } +} + +void MultiRangeBufferCache::DropOwner(Scheduler& scheduler, VkBuffer owner) { + if (owner == VK_NULL_HANDLE) { + return; + } + for (auto it = entries.begin(); it != entries.end();) { + Entry& entry = it->second; + bool owned = false; + for (const VkBuffer handle : entry.owners) { + if (handle == owner) { + owned = true; + break; + } + } + if (!owned) { + ++it; + continue; + } + RetireEntry(scheduler, entry); + it = entries.erase(it); + } +} + +void MultiRangeBufferCache::Invalidate(u64 key) { + if (auto const it = entries.find(key); it != entries.end()) { + it->second.dirty = true; + } +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_multi_range_buffer.h b/src/video_core/renderer_vulkan/vk_multi_range_buffer.h new file mode 100644 index 0000000000..f40d3aa1f8 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_multi_range_buffer.h @@ -0,0 +1,105 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +#include + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/container/unordered_map.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +using SparseBuffer = vk::Handle; + +class Device; +class Scheduler; + +struct MultiRangeSource { + VkBuffer handle{}; + VkDeviceMemory memory{}; + VkDeviceSize memory_offset{}; + VkDeviceSize offset{}; + VkDeviceSize size{}; + u64 write_tick{}; + u32 memory_type{}; +}; + +struct MultiRangeRef { + VkBuffer handle{}; + VkDeviceAddress address{}; + VkDeviceSize size{}; + bool sparse{}; + bool needs_gather{}; +}; + +class MultiRangeBufferCache final { +public: + static constexpr VkDeviceSize DEFAULT_BLOCK_SIZE = 64 * 1024; + static constexpr size_t MAX_RETIRED = 256; + + explicit MultiRangeBufferCache(const Device& device); + + YUZU_NON_COPYABLE(MultiRangeBufferCache); + + [[nodiscard]] MultiRangeRef Get(const Device& device, Scheduler& scheduler, + MemoryAllocator& memory_allocator, u64 key, + std::span sources, + VkDeviceSize total); + + void MarkGathered(u64 key); + + void Invalidate(u64 key); + + void DropOwner(Scheduler& scheduler, VkBuffer owner); + + VkDeviceSize block_size{DEFAULT_BLOCK_SIZE}; + bool use_sparse{}; + +private: + struct Retired { + SparseBuffer handle; + vk::Buffer gathered; + u64 tick{}; + }; + + struct Entry { + vk::Buffer gathered; + SparseBuffer sparse_handle; + std::vector owners; + VkDeviceAddress address{}; + VkDeviceSize size{}; + u64 geometry{}; + u64 content{}; + bool dirty{true}; + }; + + [[nodiscard]] u64 HashSources(std::span sources) const; + + [[nodiscard]] u64 HashContent(std::span sources) const; + + [[nodiscard]] bool CanBindSparse(std::span sources) const; + + [[nodiscard]] SparseBuffer CreateSparse(const Device& device, Scheduler& scheduler, + std::span sources, + VkDeviceSize total); + + [[nodiscard]] VkDeviceSize QueryBlockSize(const Device& device, u32& memory_type_bits) const; + + void RetireEntry(Scheduler& scheduler, Entry& entry); + + void DrainRetired(Scheduler& scheduler); + + ::Common::unordered_map entries; + boost::container::static_vector retired; + u32 sparse_memory_type_bits{}; + VkBufferUsageFlags sparse_usage{}; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index e365425600..d4ffcd8317 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -23,6 +23,7 @@ #include "shader_recompiler/environment.h" #include "shader_recompiler/frontend/maxwell/control_flow.h" #include "shader_recompiler/frontend/maxwell/translate_program.h" +#include "shader_recompiler/ir_opt/passes.h" #include "shader_recompiler/program_header.h" #include "video_core/engines/kepler_compute.h" #include "video_core/engines/maxwell_3d.h" @@ -45,6 +46,10 @@ #include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/gpu_logging/gpu_logging.h" +#ifdef __ANDROID__ +#include "../../android/app/src/main/jni/android_settings.h" +#endif + namespace Vulkan { namespace { @@ -58,7 +63,9 @@ using VideoCommon::FileEnvironment; using VideoCommon::GenericEnvironment; using VideoCommon::GraphicsEnvironment; -constexpr u32 CACHE_VERSION = 16; +constexpr u32 CACHE_VERSION = 18; +constexpr size_t VULKAN_CACHE_FLUSH_PIPELINES = 128; +constexpr size_t VULKAN_CACHE_FLUSH_MIN_SECONDS = 30; constexpr std::array VULKAN_CACHE_MAGIC_NUMBER{'y', 'u', 'z', 'u', 'v', 'k', 'c', 'h'}; template @@ -242,32 +249,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span program key.state.UnpackComparisonOp(key.state.alpha_test_func.Value())); info.alpha_test_reference = std::bit_cast(key.state.alpha_test_ref); - // Check for dual source blending - const auto& blend0 = key.state.attachments[0]; - if (blend0.enable != 0) { - using F = Maxwell::Blend::Factor; - const auto src_rgb = blend0.SourceRGBFactor(); - const auto dst_rgb = blend0.DestRGBFactor(); - const auto src_a = blend0.SourceAlphaFactor(); - const auto dst_a = blend0.DestAlphaFactor(); - info.dual_source_blend = - src_rgb == F::Source1Color_D3D || src_rgb == F::OneMinusSource1Color_D3D || - src_rgb == F::Source1Alpha_D3D || src_rgb == F::OneMinusSource1Alpha_D3D || - src_rgb == F::Source1Color_GL || src_rgb == F::OneMinusSource1Color_GL || - src_rgb == F::Source1Alpha_GL || src_rgb == F::OneMinusSource1Alpha_GL || - dst_rgb == F::Source1Color_D3D || dst_rgb == F::OneMinusSource1Color_D3D || - dst_rgb == F::Source1Alpha_D3D || dst_rgb == F::OneMinusSource1Alpha_D3D || - dst_rgb == F::Source1Color_GL || dst_rgb == F::OneMinusSource1Color_GL || - dst_rgb == F::Source1Alpha_GL || dst_rgb == F::OneMinusSource1Alpha_GL || - src_a == F::Source1Color_D3D || src_a == F::OneMinusSource1Color_D3D || - src_a == F::Source1Alpha_D3D || src_a == F::OneMinusSource1Alpha_D3D || - src_a == F::Source1Color_GL || src_a == F::OneMinusSource1Color_GL || - src_a == F::Source1Alpha_GL || src_a == F::OneMinusSource1Alpha_GL || - dst_a == F::Source1Color_D3D || dst_a == F::OneMinusSource1Color_D3D || - dst_a == F::Source1Alpha_D3D || dst_a == F::OneMinusSource1Alpha_D3D || - dst_a == F::Source1Color_GL || dst_a == F::OneMinusSource1Color_GL || - dst_a == F::Source1Alpha_GL || dst_a == F::OneMinusSource1Alpha_GL; - } + info.dual_source_blend = key.state.attachment0_dual_source_blend != 0; if (device.IsMoltenVK()) { for (size_t i = 0; i < 8; ++i) { @@ -324,14 +306,14 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span program size_t GetTotalPipelineWorkers() { const size_t max_core_threads = std::max(static_cast(std::thread::hardware_concurrency()), 2ULL) - 1ULL; -#ifdef ANDROID - // Leave at least one core free on Android. Previously we reserved two, but - // shipping builds benefit from one extra compilation worker. - constexpr size_t free_cores = 1ULL; - if (max_core_threads <= free_cores) { +#ifdef __ANDROID__ + const int configured = AndroidSettings::values.pipeline_worker_count.GetValue(); + const int clamped = std::clamp(configured, 2, 8); + const size_t desired = static_cast(clamped); + if (desired == 0) { return 1ULL; } - return max_core_threads - free_cores; + return std::min(max_core_threads, desired); #else return max_core_threads; #endif @@ -361,25 +343,51 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, GuestDescriptorQueue& guest_descriptor_queue_, + DescriptorBufferRing& descriptor_buffer_ring_, RenderPassCache& render_pass_cache_, BufferCache& buffer_cache_, TextureCache& texture_cache_, VideoCore::ShaderNotify& shader_notify_) : VideoCommon::ShaderCache{device_memory_}, device{device_}, scheduler{scheduler_}, descriptor_pool{descriptor_pool_}, guest_descriptor_queue{guest_descriptor_queue_}, + descriptor_buffer_ring{descriptor_buffer_ring_}, render_pass_cache{render_pass_cache_}, buffer_cache{buffer_cache_}, texture_cache{texture_cache_}, shader_notify{shader_notify_}, use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()}, use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()}, workers(device.HasBrokenParallelShaderCompiling() ? 1ULL : GetTotalPipelineWorkers(), - "VkPipelineBuilder"), - serialization_thread(1, "VkPipelineSerialization") { + "VkPipelineBuilder", {}, Common::ThreadPlacement::Background), + serialization_thread(1, "VkPipelineSerialization", {}, + Common::ThreadPlacement::Background) { const auto& float_control{device.FloatControlProperties()}; const VkDriverId driver_id{device.GetDriverID()}; + const VkShaderStageFlags subgroup_stages{device.GetSubgroupSupportedStages()}; + const auto subgroup_stage_bit{[subgroup_stages](VkShaderStageFlags flag, Shader::Stage stage) { + return (subgroup_stages & flag) != 0 ? (1u << static_cast(stage)) : 0u; + }}; + u32 supported_subgroup_stages{ + subgroup_stage_bit(VK_SHADER_STAGE_VERTEX_BIT, Shader::Stage::VertexA) | + subgroup_stage_bit(VK_SHADER_STAGE_VERTEX_BIT, Shader::Stage::VertexB) | + subgroup_stage_bit(VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, + Shader::Stage::TessellationControl) | + subgroup_stage_bit(VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, + Shader::Stage::TessellationEval) | + subgroup_stage_bit(VK_SHADER_STAGE_GEOMETRY_BIT, Shader::Stage::Geometry) | + subgroup_stage_bit(VK_SHADER_STAGE_FRAGMENT_BIT, Shader::Stage::Fragment) | + subgroup_stage_bit(VK_SHADER_STAGE_COMPUTE_BIT, Shader::Stage::Compute)}; + if (driver_id == VK_DRIVER_ID_MESA_TURNIP) { + supported_subgroup_stages &= ~(1u << static_cast(Shader::Stage::Geometry)); + } profile = Shader::Profile{ .supported_spirv = device.SupportedSpirvVersion(), .unified_descriptor_binding = true, .support_descriptor_aliasing = device.IsDescriptorAliasingSupported(), .support_int8 = device.IsInt8Supported(), + .support_uniform_and_storage_buffer_8bit = + device.IsUniformAndStorageBuffer8BitAccessSupported(), + .support_storage_buffer_8bit = device.IsStorageBuffer8BitAccessSupported(), .support_int16 = device.IsShaderInt16Supported(), + .support_uniform_and_storage_buffer_16bit = + device.IsUniformAndStorageBuffer16BitAccessSupported(), + .support_storage_buffer_16bit = device.IsStorageBuffer16BitAccessSupported(), .support_int64 = device.IsShaderInt64Supported(), .support_vertex_instance_id = false, .support_float_controls = device.IsKhrShaderFloatControlsSupported(), @@ -398,7 +406,16 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .support_fp64_signed_zero_nan_preserve = float_control.shaderSignedZeroInfNanPreserveFloat64 != VK_FALSE, .support_explicit_workgroup_layout = device.IsKhrWorkgroupMemoryExplicitLayoutSupported(), + .support_workgroup_layout_8bit_access = + device.IsWorkgroupMemoryExplicitLayout8BitAccessSupported(), + .support_workgroup_layout_16bit_access = + device.IsWorkgroupMemoryExplicitLayout16BitAccessSupported(), + .support_shader_quad_control = device.IsKhrShaderQuadControlSupported(), + .support_quad_shuffles = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_QUAD_BIT), .support_vote = device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_VOTE_BIT), + .support_shuffle_relative = + device.IsSubgroupFeatureSupported(VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT), + .supported_subgroup_stages = supported_subgroup_stages, .support_viewport_index_layer_non_geometry = device.IsExtShaderViewportIndexLayerSupported(), .support_viewport_mask = device.IsNvViewportArray2Supported(), @@ -406,6 +423,7 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .support_demote_to_helper_invocation = device.IsExtShaderDemoteToHelperInvocationSupported(), .support_int64_atomics = device.IsExtShaderAtomicInt64Supported(), + .support_shared_int64_atomics = device.IsSharedInt64AtomicsSupported(), .support_derivative_control = true, .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(), .support_native_ndc = device.IsExtDepthClipControlSupported(), @@ -414,6 +432,12 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, .support_geometry_streams = device.AreTransformFeedbackGeometryStreamsSupported(), .support_sampled_image_array_nonuniform_indexing = device.IsSampledImageArrayNonUniformIndexingSupported(), + .support_storage_image_array_nonuniform_indexing = + device.IsStorageImageArrayNonUniformIndexingSupported(), + .support_uniform_texel_buffer_array_nonuniform_indexing = + device.IsUniformTexelBufferArrayNonUniformIndexingSupported(), + .support_storage_texel_buffer_array_nonuniform_indexing = + device.IsStorageTexelBufferArrayNonUniformIndexingSupported(), .warp_size_potentially_larger_than_guest = device.IsWarpSizePotentiallyBiggerThanGuest(), @@ -423,23 +447,40 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE || driver_id == VK_DRIVER_ID_MESA_RADV || driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS || - driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA, + driver_id == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA || + driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || + driver_id == VK_DRIVER_ID_MESA_TURNIP, .has_broken_spirv_clamp = driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS, .has_broken_spirv_position_input = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY, .has_broken_unsigned_image_offsets = false, .has_broken_signed_operations = false, .has_broken_fp16_float_controls = driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY, + .has_broken_fp32_denorm_flush = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY, .ignore_nan_fp_comparisons = false, .has_broken_spirv_subgroup_mask_vector_extract_dynamic = - driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY, + driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY && + device.GetDriverVersion() < VK_MAKE_VERSION(512, 672, 0), + .has_broken_spirv_subgroup_shuffle = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY, + .max_subgroup_size = device.GetMaxSubgroupSize(), .has_broken_robust = device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Pascal, .min_ssbo_alignment = device.GetStorageBufferAlignment(), - .max_user_clip_distances = device.GetMaxUserClipDistances(), + .max_user_clip_distances = device.GetMaxUserClipDistances() }; host_info = Shader::HostTranslateInfo{ + .min_ssbo_alignment = device.GetStorageBufferAlignment(), + .max_per_stage_descriptor_sampled_images = device.GetMaxPerStageDescriptorSampledImages(), + .max_per_stage_resources = device.GetMaxPerStageResources(), + .max_descriptor_set_samplers = device.GetMaxDescriptorSetSamplers(), + .max_descriptor_set_uniform_buffers = device.GetMaxDescriptorSetUniformBuffers(), + .max_descriptor_set_uniform_buffers_dynamic = device.GetMaxDescriptorSetUniformBuffersDynamic(), + .max_descriptor_set_storage_buffers = device.GetMaxDescriptorSetStorageBuffers(), + .max_descriptor_set_storage_buffers_dynamic = device.GetMaxDescriptorSetStorageBuffersDynamic(), + .max_descriptor_set_sampled_images = device.GetMaxDescriptorSetSampledImages(), + .max_descriptor_set_storage_images = device.GetMaxDescriptorSetStorageImages(), + .max_descriptor_set_input_attachements = device.GetMaxDescriptorSetInputAttachments(), .support_float64 = device.IsFloat64Supported(), .support_float16 = device.IsFloat16Supported(), .support_int64 = device.IsShaderInt64Supported(), @@ -448,13 +489,12 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, driver_id == VK_DRIVER_ID_SAMSUNG_PROPRIETARY, .support_snorm_render_buffer = true, .support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(), - .min_ssbo_alignment = static_cast(device.GetStorageBufferAlignment()), - .max_per_stage_descriptor_sampled_images = device.GetMaxPerStageDescriptorSampledImages(), - .max_per_stage_resources = device.GetMaxPerStageResources(), - .max_descriptor_set_sampled_images = device.GetMaxDescriptorSetSampledImages(), + .support_viewport_mask = device.IsNvViewportArray2Supported(), .support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(), .support_conditional_barrier = device.SupportsConditionalBarriers(), + .single_lane_geometry_subgroups = !profile.SupportsSubgroupStage(Shader::Stage::Geometry), }; + host_info.ApplyDescriptorLimitPolicy(); if (device.GetMaxVertexInputAttributes() < Maxwell::NumVertexAttributes) { LOG_WARNING(Render_Vulkan, "maxVertexInputAttributes is too low: {} < {}", @@ -468,6 +508,8 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, LOG_INFO(Render_Vulkan, "DynamicState setting value: {}", u32(Settings::values.dyna_state.GetValue())); dynamic_features = {}; + dynamic_features.driver_id = device.GetDriverID(); + dynamic_features.driver_version = device.GetDriverVersion(); // User granularity enforced in vulkan_device.cpp switch statement: // Level 0: Core Dynamic States only @@ -489,11 +531,16 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, device.IsExtExtendedDynamicState3BlendingSupported(); dynamic_features.has_extended_dynamic_state_3_enables = device.IsExtExtendedDynamicState3EnablesSupported(); + dynamic_features.has_color_write_enable = + device.IsExtColorWriteEnableSupported(); dynamic_features.has_dynamic_state3_depth_clamp_enable = + dynamic_features.has_extended_dynamic_state_3_enables && device.SupportsDynamicState3DepthClampEnable(); dynamic_features.has_dynamic_state3_logic_op_enable = + dynamic_features.has_extended_dynamic_state_3_enables && device.SupportsDynamicState3LogicOpEnable(); dynamic_features.has_dynamic_state3_line_stipple_enable = + dynamic_features.has_extended_dynamic_state_3_enables && device.SupportsDynamicState3LineStippleEnable(); // VIDS: Independent toggle (not affected by dyna_state levels) @@ -561,6 +608,14 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading if (title_id == 0) { return; } + if (!pipeline_cache_filename.empty()) { + serialization_thread.WaitForRequests(); + if (use_vulkan_pipeline_cache && !vulkan_pipeline_cache_filename.empty()) { + SerializeVulkanPipelineCache(vulkan_pipeline_cache_filename, vulkan_pipeline_cache, + CACHE_VERSION); + } + } + const auto shader_dir{Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir)}; const auto base_dir{shader_dir / fmt::format("{:016x}", title_id)}; if (!Common::FS::CreateDir(shader_dir) || !Common::FS::CreateDir(base_dir)) { @@ -618,7 +673,10 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading dynamic_features.has_extended_dynamic_state_3_blend || (key.state.extended_dynamic_state_3_enables != 0) != dynamic_features.has_extended_dynamic_state_3_enables || - (key.state.dynamic_vertex_input != 0) != dynamic_features.has_dynamic_vertex_input) { + (key.state.color_write_enable_dynamic != 0) != + dynamic_features.has_color_write_enable || + (key.state.dynamic_vertex_input != 0) != + dynamic_features.has_dynamic_vertex_input) { return; } @@ -668,6 +726,10 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading if (use_vulkan_pipeline_cache) { SerializeVulkanPipelineCache(vulkan_pipeline_cache_filename, vulkan_pipeline_cache, CACHE_VERSION); + size_t size = 0; + vulkan_pipeline_cache.Read(&size, nullptr); + last_cache_size.store(size, std::memory_order_relaxed); + last_flush = std::chrono::steady_clock::now(); } if (state.statistics) { @@ -675,6 +737,35 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading } } +void PipelineCache::QueueVulkanPipelineCacheFlush() { + if (!use_vulkan_pipeline_cache || vulkan_pipeline_cache_filename.empty()) { + return; + } + if (++pipelines_since_flush < VULKAN_CACHE_FLUSH_PIPELINES) { + return; + } + const auto now = std::chrono::steady_clock::now(); + const auto megabytes = last_cache_size.load(std::memory_order_relaxed) / (1024 * 1024); + const std::chrono::seconds interval{ + std::max(VULKAN_CACHE_FLUSH_MIN_SECONDS, megabytes)}; + if (last_flush.time_since_epoch().count() != 0 && now - last_flush < interval) { + return; + } + if (flush_in_flight.exchange(true, std::memory_order_acq_rel)) { + return; + } + pipelines_since_flush = 0; + last_flush = now; + serialization_thread.QueueWork([this] { + SerializeVulkanPipelineCache(vulkan_pipeline_cache_filename, vulkan_pipeline_cache, + CACHE_VERSION); + size_t size = 0; + vulkan_pipeline_cache.Read(&size, nullptr); + last_cache_size.store(size, std::memory_order_relaxed); + flush_in_flight.store(false, std::memory_order_release); + }); +} + GraphicsPipeline* PipelineCache::CurrentGraphicsPipelineSlowPath() { const auto [pair, is_new]{graphics_cache.try_emplace(graphics_key)}; auto& pipeline{pair->second}; @@ -698,6 +789,11 @@ GraphicsPipeline* PipelineCache::BuiltPipeline(GraphicsPipeline* pipeline) const if (!use_asynchronous_shaders) { return pipeline; } + // If something is using depth, we can assume that games are not rendering anything which + // will be used one time. + if (maxwell3d->regs.zeta_enable) { + return nullptr; + } // If games are using a small index count, we can assume these are full screen quads. // Usually these shaders are only used once for building textures so we can assume they // can't be built async @@ -713,7 +809,7 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline( std::span envs, PipelineStatistics* statistics, bool build_in_parallel) try { auto hash = key.Hash(); - LOG_INFO(Render_Vulkan, "0x{:016x}", hash); + LOG_DEBUG(Render_Vulkan, "{:#016x}", hash); size_t env_index{0}; std::array programs; const bool uses_vertex_a{key.unique_hashes[0] != 0}; @@ -749,7 +845,12 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline( programs[index] = MergeDualVertexPrograms(program_va, program_vb, env); } - if (Settings::values.dump_shaders) { + if (device.GetDriverID() != VK_DRIVER_ID_NVIDIA_PROPRIETARY && Shader::Optimization::HasBrokenPattern(programs[index])) { + LOG_WARNING(Render_Vulkan, "0x{:016x}: Skipping shader with known issues", key.unique_hashes[index]); + return nullptr; + } + + if (Settings::values.dump_guest_shaders) { env.Dump(hash, key.unique_hashes[index]); } @@ -781,14 +882,22 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline( device.SaveShader(code); modules[stage_index] = BuildShader(device, code); - // Log shader compilation to GPU logger (with SPIR-V binary dump if enabled) - if (Settings::values.gpu_logging_enabled.GetValue()) { + // Text log + .spv dump. Text log is gated by gpu_log_level != Off; .spv dump + // is independent and gated only by gpu_log_shader_dumps. + const bool should_log = GPU::Logging::IsActive(); + const bool should_dump = Settings::values.gpu_log_shader_dumps.GetValue(); + if (should_log || should_dump) { static constexpr std::array stage_names{"vertex", "tess_control", "tess_eval", "geometry", "fragment"}; const std::string shader_name = fmt::format("shader_{:016x}_{}", key.unique_hashes[index], stage_names[stage_index]); - const std::string shader_info = fmt::format("SPIR-V size: {} bytes, hash: {:016x}", - code.size() * sizeof(u32), key.unique_hashes[index]); - GPU::Logging::GPULogger::GetInstance().LogShaderCompilation(shader_name, shader_info, - std::span(code.data(), code.size())); + if (should_log) { + const std::string shader_info = fmt::format("SPIR-V size: {} bytes, hash: {:016x}", + code.size() * sizeof(u32), key.unique_hashes[index]); + GPU::Logging::GPULogger::GetInstance().LogShaderCompilation(shader_name, shader_info); + } + if (should_dump) { + GPU::Logging::DumpSpirvShader(key.unique_hashes[index], + std::span(code.data(), code.size())); + } } if (device.HasDebuggingToolAttached()) { @@ -800,8 +909,8 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline( Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr}; return std::make_unique( scheduler, buffer_cache, texture_cache, vulkan_pipeline_cache, &shader_notify, device, - descriptor_pool, guest_descriptor_queue, thread_worker, statistics, render_pass_cache, key, - std::move(modules), infos); + descriptor_pool, guest_descriptor_queue, descriptor_buffer_ring, thread_worker, statistics, + render_pass_cache, key, std::move(modules), infos); } catch (const Shader::Exception& exception) { auto hash = key.Hash(); @@ -841,6 +950,7 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline() { } SerializePipeline(key, env_ptrs, pipeline_cache_filename, CACHE_VERSION); }); + QueueVulkanPipelineCacheFlush(); return pipeline; } @@ -860,6 +970,7 @@ std::unique_ptr PipelineCache::CreateComputePipeline( SerializePipeline(key, std::array{&env_}, pipeline_cache_filename, CACHE_VERSION); }); + QueueVulkanPipelineCacheFlush(); return pipeline; } @@ -868,16 +979,16 @@ std::unique_ptr PipelineCache::CreateComputePipeline( PipelineStatistics* statistics, bool build_in_parallel) try { auto hash = key.Hash(); if (device.HasBrokenCompute()) { - LOG_ERROR(Render_Vulkan, "Skipping 0x{:016x}", hash); + LOG_ERROR(Render_Vulkan, "Skipping {:#016x}", hash); return nullptr; } - LOG_INFO(Render_Vulkan, "0x{:016x}", hash); + LOG_DEBUG(Render_Vulkan, "{:#016x}", hash); Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()}; // Dump it before error. - if (Settings::values.dump_shaders) { + if (Settings::values.dump_guest_shaders) { env.Dump(hash, key.unique_hash); } @@ -889,7 +1000,7 @@ std::unique_ptr PipelineCache::CreateComputePipeline( const u32 max_shared_memory = device.GetMaxComputeSharedMemorySize(); if (needs_shared_mem_clamp && program.shared_memory_size > max_shared_memory) { LOG_WARNING(Render_Vulkan, - "Compute shader 0x{:016x} requests {}KB shared memory but device max is {}KB - clamping", + "Compute shader {:#016x} requests {}KB shared memory but device max is {}KB - clamping", key.unique_hash, program.shared_memory_size / 1024, max_shared_memory / 1024); @@ -899,13 +1010,20 @@ std::unique_ptr PipelineCache::CreateComputePipeline( device.SaveShader(code); vk::ShaderModule spv_module{BuildShader(device, code)}; - // Log compute shader compilation to GPU logger (with SPIR-V binary dump if enabled) - if (Settings::values.gpu_logging_enabled.GetValue()) { + // Text log + .spv dump. Same split as the graphics path. + const bool should_log = GPU::Logging::IsActive(); + const bool should_dump = Settings::values.gpu_log_shader_dumps.GetValue(); + if (should_log || should_dump) { const std::string shader_name = fmt::format("shader_{:016x}_compute", key.unique_hash); - const std::string shader_info = fmt::format("SPIR-V size: {} bytes, hash: {:016x}", - code.size() * sizeof(u32), key.unique_hash); - GPU::Logging::GPULogger::GetInstance().LogShaderCompilation(shader_name, shader_info, - std::span(code.data(), code.size())); + if (should_log) { + const std::string shader_info = fmt::format("SPIR-V size: {} bytes, hash: {:016x}", + code.size() * sizeof(u32), key.unique_hash); + GPU::Logging::GPULogger::GetInstance().LogShaderCompilation(shader_name, shader_info); + } + if (should_dump) { + GPU::Logging::DumpSpirvShader(key.unique_hash, + std::span(code.data(), code.size())); + } } if (device.HasDebuggingToolAttached()) { @@ -913,9 +1031,11 @@ std::unique_ptr PipelineCache::CreateComputePipeline( spv_module.SetObjectNameEXT(name.c_str()); } Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr}; - return std::make_unique(device, vulkan_pipeline_cache, descriptor_pool, - guest_descriptor_queue, thread_worker, statistics, - &shader_notify, program.info, std::move(spv_module)); + return std::make_unique(device, scheduler, vulkan_pipeline_cache, descriptor_pool, + guest_descriptor_queue, descriptor_buffer_ring, + thread_worker, statistics, + &shader_notify, program.info, std::move(spv_module), + key.unique_hash); } catch (const Shader::Exception& exception) { LOG_ERROR(Render_Vulkan, "{}", exception.what()); diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 490f9da81a..71dfb46e3d 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -7,11 +7,13 @@ #pragma once #include +#include +#include #include #include #include #include -#include +#include "common/container/unordered_map.h" #include #include "common/common_types.h" @@ -105,6 +107,7 @@ public: explicit PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, const Device& device, Scheduler& scheduler, DescriptorPool& descriptor_pool, GuestDescriptorQueue& guest_descriptor_queue, + DescriptorBufferRing& descriptor_buffer_ring, RenderPassCache& render_pass_cache, BufferCache& buffer_cache, TextureCache& texture_cache, VideoCore::ShaderNotify& shader_notify_); ~PipelineCache(); @@ -143,10 +146,13 @@ private: vk::PipelineCache LoadVulkanPipelineCache(const std::filesystem::path& filename, u32 expected_cache_version); + void QueueVulkanPipelineCacheFlush(); + const Device& device; Scheduler& scheduler; DescriptorPool& descriptor_pool; GuestDescriptorQueue& guest_descriptor_queue; + DescriptorBufferRing& descriptor_buffer_ring; RenderPassCache& render_pass_cache; BufferCache& buffer_cache; TextureCache& texture_cache; @@ -157,8 +163,8 @@ private: GraphicsPipelineCacheKey graphics_key{}; GraphicsPipeline* current_pipeline{}; - ankerl::unordered_dense::map> compute_cache; - ankerl::unordered_dense::map> graphics_cache; + ::Common::unordered_map> compute_cache; + ::Common::unordered_map> graphics_cache; ShaderPools main_pools; @@ -169,6 +175,10 @@ private: std::filesystem::path vulkan_pipeline_cache_filename; vk::PipelineCache vulkan_pipeline_cache; + size_t pipelines_since_flush{}; + std::chrono::steady_clock::time_point last_flush{}; + std::atomic last_cache_size{}; + std::atomic_bool flush_in_flight{}; Common::ThreadWorker workers; Common::ThreadWorker serialization_thread; diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp index de854554c7..9298764546 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp @@ -7,6 +7,9 @@ #include "common/settings.h" #include "common/thread.h" #include "core/frontend/emu_window.h" +#ifdef HAS_LSFG +#include "video_core/renderer_vulkan/present/lsfg_common.h" +#endif #include "video_core/renderer_vulkan/vk_present_manager.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_swapchain.h" @@ -19,6 +22,21 @@ namespace Vulkan { namespace { +constexpr size_t MAX_FRAMES_IN_FLIGHT = 7; +constexpr u32 MAX_PRESENT_ATTEMPTS = 3; +#ifdef HAS_LSFG +static_assert(MAX_FRAMES_IN_FLIGHT <= LSFG_MAX_TARGETS); +#endif + +bool CanStoreToFrame(const vk::PhysicalDevice& physical_device, VkFormat format) { +#ifdef HAS_LSFG + const VkFormatProperties props{physical_device.GetFormatProperties(format)}; + return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0; +#else + return false; +#endif +} + bool CanBlitToSwapchain(const vk::PhysicalDevice& physical_device, VkFormat format) { const VkFormatProperties props{physical_device.GetFormatProperties(format)}; return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT); @@ -110,6 +128,7 @@ PresentManager::PresentManager(const vk::Instance& instance_, , swapchain{swapchain_} , surface{surface_} , blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())} + , storage_supported{CanStoreToFrame(device.GetPhysical(), swapchain.GetImageFormat())} , use_present_thread{Settings::values.async_presentation.GetValue()} { SetImageCount(); @@ -127,6 +146,7 @@ PresentManager::PresentManager(const vk::Instance& instance_, frames.resize(image_count); for (u32 i = 0; i < frames.size(); i++) { Frame& frame = frames[i]; + frame.index = i; frame.cmdbuf = vk::CommandBuffer{cmdbuffers[i], device.GetDispatchLoader()}; frame.render_ready = dld.CreateSemaphore({ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, @@ -138,6 +158,7 @@ PresentManager::PresentManager(const vk::Instance& instance_, .pNext = nullptr, .flags = VK_FENCE_CREATE_SIGNALED_BIT, }); + frame.storage_capable = storage_supported; free_queue.push_back(&frame); } @@ -179,13 +200,27 @@ void PresentManager::Present(Frame* frame) { } } +size_t PresentManager::MaxExtraFrames() const { + return image_count - 1; +} + +bool PresentManager::NeedsStorage(const Frame* frame, bool required) const { + return required && frame->storage_capable && !frame->storage_view; +} + void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat image_view_format, - VkRenderPass rd) { + VkRenderPass rd, bool storage) { auto& dld = device.GetLogical(); frame->width = width; frame->height = height; + const bool with_storage = storage && frame->storage_capable; + VkImageUsageFlags storage_usage = 0; + if (with_storage) { + storage_usage = VK_IMAGE_USAGE_STORAGE_BIT; + } + frame->image = memory_allocator.CreateImage({ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .pNext = nullptr, @@ -202,7 +237,8 @@ void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | storage_usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, .pQueueFamilyIndices = nullptr, @@ -233,6 +269,33 @@ void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat }, }); + frame->storage_view = vk::ImageView{}; + if (with_storage) { + frame->storage_view = dld.CreateImageView({ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = *frame->image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = swapchain.GetImageFormat(), + .components = + { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = + { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }); + } + const VkImageView image_view{*frame->image_view}; frame->framebuffer = dld.CreateFramebuffer({ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, @@ -266,6 +329,8 @@ void PresentManager::WaitPresent() { void PresentManager::PresentThread(std::stop_token token) { Common::SetCurrentThreadName("VulkanPresent"); + Common::SetCurrentThreadPriority(Common::ThreadPriority::High); + Common::SetCurrentThreadToPerformanceCores(); while (!token.stop_requested()) { std::unique_lock lock{queue_mutex}; // Wait for presentation frames @@ -279,7 +344,7 @@ void PresentManager::PresentThread(std::stop_token token) { // By exchanging the lock ownership we take the swapchain lock // before the queue lock goes out of scope. This way the swapchain // lock in WaitPresent is guaranteed to occur after here. - std::exchange(lock, std::unique_lock{swapchain_mutex}); + void(std::exchange(lock, std::unique_lock{swapchain_mutex})); CopyToSwapchain(frame); // Free the frame for reuse @@ -299,17 +364,46 @@ void PresentManager::SetImageCount() { // We cannot have more than 7 images in flight at any given time. // FRAMES_IN_FLIGHT is 8, and the cache TICKS_TO_DESTROY is 8. // Mali drivers will give us 6. - image_count = std::min(swapchain.GetImageCount(), 7); +#ifdef HAS_LSFG + const size_t generations = Settings::FrameGenMaxGenerations(); + const size_t queued_composites = Settings::values.frame_gen_queue_target.GetValue() + 1; + image_count = + std::clamp((generations + 1) * queued_composites, swapchain.GetImageCount(), + MAX_FRAMES_IN_FLIGHT); +#else + image_count = std::min(swapchain.GetImageCount(), MAX_FRAMES_IN_FLIGHT); +#endif + swapchain_image_count = swapchain.GetImageCount(); + swapchain_image_format = swapchain.GetImageFormat(); +} + +void PresentManager::DiscardFrame(Frame* frame) { + static constexpr VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + const VkSemaphore render_ready = *frame->render_ready; + const VkSubmitInfo submit_info{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = nullptr, + .waitSemaphoreCount = 1U, + .pWaitSemaphores = &render_ready, + .pWaitDstStageMask = &wait_stage, + .commandBufferCount = 0U, + .pCommandBuffers = nullptr, + .signalSemaphoreCount = 0U, + .pSignalSemaphores = nullptr, + }; + + std::scoped_lock submit_lock{scheduler.submit_mutex}; + void(device.GetGraphicsQueue().Submit(submit_info, *frame->present_done)); } void PresentManager::CopyToSwapchain(Frame* frame) { bool requires_recreation = false; - while (true) { + for (u32 attempt = 0; attempt < MAX_PRESENT_ATTEMPTS; ++attempt) { try { // Recreate surface and swapchain if needed. if (requires_recreation) { -#ifdef ANDROID +#ifdef __ANDROID__ surface = CreateSurface(instance, render_window.GetWindowInfo()); #endif RecreateSwapchain(frame); @@ -325,6 +419,8 @@ void PresentManager::CopyToSwapchain(Frame* frame) { requires_recreation = true; } } + + DiscardFrame(frame); } void PresentManager::CopyToSwapchainImpl(Frame* frame) { @@ -429,7 +525,7 @@ void PresentManager::CopyToSwapchainImpl(Frame* frame) { }, }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, {}, + cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, VK_PIPELINE_STAGE_TRANSFER_BIT, {}, {}, {}, pre_barriers); if (blit_supported) { diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h index c51f8ed77f..ab2b76f10f 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.h +++ b/src/video_core/renderer_vulkan/vk_present_manager.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -28,12 +29,15 @@ class Swapchain; struct Frame { u32 width; u32 height; + u32 index; vk::Image image; vk::ImageView image_view; + vk::ImageView storage_view; vk::Framebuffer framebuffer; vk::CommandBuffer cmdbuf; vk::Semaphore render_ready; vk::Fence present_done; + bool storage_capable{}; }; class PresentManager { @@ -55,11 +59,19 @@ public: /// Recreates the present frame to match the provided parameters void RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat image_view_format, - VkRenderPass rd); + VkRenderPass rd, bool storage); + + [[nodiscard]] bool NeedsStorage(const Frame* frame, bool required) const; /// Waits for the present thread to finish presenting all queued frames. void WaitPresent(); + /// How many additional frames can be queued without stalling the render thread + [[nodiscard]] size_t MaxExtraFrames() const; + + [[nodiscard]] std::size_t SwapchainImageCount() const { return swapchain_image_count; } + [[nodiscard]] VkFormat SwapchainImageFormat() const { return swapchain_image_format; } + private: void PresentThread(std::stop_token token); @@ -69,6 +81,8 @@ private: void RecreateSwapchain(Frame* frame); + void DiscardFrame(Frame* frame); + void SetImageCount(); private: @@ -90,8 +104,11 @@ private: std::mutex free_mutex; std::jthread present_thread; bool blit_supported; + bool storage_supported; bool use_present_thread; - std::size_t image_count{}; + std::atomic image_count{}; + std::atomic swapchain_image_count{}; + std::atomic swapchain_image_format{}; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp index 98062a78dc..f63c924e39 100644 --- a/src/video_core/renderer_vulkan/vk_query_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include #include "video_core/renderer_vulkan/vk_texture_cache.h" @@ -41,8 +41,8 @@ class SamplesQueryBank : public VideoCommon::BankBase { public: static constexpr size_t BANK_SIZE = 256; static constexpr size_t QUERY_SIZE = 8; - explicit SamplesQueryBank(const Device& device_, size_t index_) - : BankBase(BANK_SIZE), device{device_}, index{index_} { + explicit SamplesQueryBank(const Device& device_, Scheduler& scheduler_, size_t index_) + : BankBase(BANK_SIZE), device{device_}, scheduler{scheduler_}, index{index_} { const auto& dev = device.GetLogical(); query_pool = dev.CreateQueryPool({ .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, @@ -60,12 +60,28 @@ public: void Reset() override { ASSERT(references == 0); VideoCommon::BankBase::Reset(); - const auto& dev = device.GetLogical(); - dev.ResetQueryPool(*query_pool, 0, BANK_SIZE); + if (device.IsHostQueryResetSupported()) { + const auto& dev = device.GetLogical(); + dev.ResetQueryPool(*query_pool, 0, BANK_SIZE); + } else { + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([pool = *query_pool](vk::CommandBuffer cmdbuf) { + cmdbuf.ResetQueryPool(pool, 0, BANK_SIZE); + }); + } host_results.fill(0ULL); next_bank = 0; } + void AddReference(size_t how_many = 1) { + BankBase::AddReference(how_many); + last_used_tick = scheduler.CurrentTick(); + } + + [[nodiscard]] bool IsDead() const { + return BankBase::IsDead() && scheduler.IsFree(last_used_tick); + } + void Sync(size_t start, size_t size) { const auto& dev = device.GetLogical(); const VkResult query_result = dev.GetQueryResults( @@ -98,9 +114,11 @@ public: private: const Device& device; + Scheduler& scheduler; const size_t index; vk::QueryPool query_pool; std::array host_results; + u64 last_used_tick{}; }; using BaseStreamer = VideoCommon::SimpleStreamer; @@ -126,16 +144,14 @@ public: current_query = nullptr; amend_value = 0; accumulation_value = 0; - queries_prefix_scan_pass = std::make_unique( - device, scheduler, descriptor_pool, compute_pass_descriptor_queue); + queries_prefix_scan_pass.emplace(device, scheduler, descriptor_pool, compute_pass_descriptor_queue); const VkBufferCreateInfo buffer_ci = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .pNext = nullptr, .flags = 0, .size = 8, - .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | - VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, + .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, .pQueueFamilyIndices = nullptr, @@ -157,9 +173,8 @@ public: ReserveHostQuery(); scheduler.Record([query_pool = current_query_pool, - query_index = current_bank_slot](vk::CommandBuffer cmdbuf) { + query_index = current_bank_slot](vk::CommandBuffer cmdbuf) { const bool use_precise = Settings::IsGPULevelHigh(); - cmdbuf.ResetQueryPool(query_pool, static_cast(query_index), 1); cmdbuf.BeginQuery(query_pool, static_cast(query_index), use_precise ? VK_QUERY_CONTROL_PRECISE_BIT : 0); }); @@ -221,7 +236,8 @@ public: } PauseCounter(); const auto driver_id = device.GetDriverID(); - if (driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) { + if (driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || + driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) { pending_sync.clear(); sync_values_stash.clear(); return; @@ -230,7 +246,7 @@ public: sync_values_stash.emplace_back(); std::vector* sync_values = &sync_values_stash.back(); sync_values->reserve(num_slots_used); - ankerl::unordered_dense::map> offsets; + ::Common::unordered_map> offsets; resolve_buffers.clear(); size_t resolve_buffer_index = ObtainBuffer(num_slots_used); resolve_buffers.push_back(resolve_buffer_index); @@ -412,7 +428,7 @@ private: template void ApplyBanksWideOp(std::vector& queries, Func&& func) { std::conditional_t>, - ankerl::unordered_dense::map>> + ::Common::unordered_map>> indexer; for (auto q : queries) { auto* query = GetQuery(q); @@ -434,7 +450,7 @@ private: void ReserveBank() { current_bank_id = bank_pool.ReserveBank([this](std::deque& queue, size_t index) { - queue.emplace_back(device, index); + queue.emplace_back(device, scheduler, index); }); if (current_bank) { current_bank->next_bank = current_bank_id + 1; @@ -529,7 +545,7 @@ private: template size_t ObtainBuffer(size_t num_needed) { - const size_t log_2 = std::max(11U, Common::Log2Ceil64(num_needed)); + const size_t log_2 = std::max(11U, Common::Log2Ceil(num_needed)); if constexpr (is_resolve) { if (resolve_table[log_2] != 0) { return resolve_table[log_2] - 1; @@ -592,8 +608,7 @@ private: VideoCommon::HostQueryBase* current_query; bool has_started{}; std::mutex flush_guard; - - std::unique_ptr queries_prefix_scan_pass; + std::optional queries_prefix_scan_pass; }; // Transform feedback queries @@ -624,6 +639,15 @@ public: VideoCommon::BankBase::Reset(); } + void AddReference(size_t how_many = 1) { + BankBase::AddReference(how_many); + last_used_tick = scheduler.CurrentTick(); + } + + [[nodiscard]] bool IsDead() const { + return BankBase::IsDead() && scheduler.IsFree(last_used_tick); + } + void Sync(StagingBufferRef& stagging_buffer, size_t extra_offset, size_t start, size_t size) { scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([this, dst_buffer = stagging_buffer.buffer, extra_offset, start, @@ -649,6 +673,7 @@ private: Scheduler& scheduler; const size_t index; vk::Buffer buffer; + u64 last_used_tick{}; }; class PrimitivesSucceededStreamer; @@ -732,7 +757,7 @@ public: void SyncWrites() override { CloseCounter(); - ankerl::unordered_dense::map> sync_values_stash; + ::Common::unordered_map> sync_values_stash; for (auto q : pending_sync) { auto* query = GetQuery(q); if (True(query->flags & VideoCommon::QueryFlagBits::IsRewritten)) { @@ -789,7 +814,7 @@ public: new_query->flags |= VideoCommon::QueryFlagBits::IsFinalValueSynced; return index; } - + scheduler.RequestOutsideRenderPassOperationContext(); CloseCounter(); auto [bank_slot, data_slot] = ProduceCounterBuffer(slot); @@ -846,7 +871,7 @@ public: scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER); + vk::PIPELINE_STAGE_HOST, 0, WRITE_BARRIER); }); std::scoped_lock lk(flush_guard); @@ -926,9 +951,7 @@ private: return; } has_flushed_end_pending = false; - - // Refresh buffer state before ending transform feedback to ensure counters_count is up-to-date. - UpdateBuffers(); + if (buffers_count == 0) { LOG_DEBUG(Render_Vulkan, "EndTransformFeedbackEXT called with no counters (buffers_count=0)"); scheduler.Record([](vk::CommandBuffer cmdbuf) { @@ -1194,7 +1217,7 @@ public: // Protect against stride == 0 (avoid divide-by-zero). Use fallback stride=1 and warn. u64 safe_stride = query->stride == 0 ? 1 : query->stride; if (query->stride == 0) { - LOG_WARNING(Render_Vulkan, "TransformFeedback query has stride 0; using 1 to avoid div-by-zero (addr=0x{:x})", query->dependant_address); + LOG_WARNING(Render_Vulkan, "TransformFeedback query has stride 0; using 1 to avoid div-by-zero (addr={:#x})", query->dependant_address); } if (query->dependant_manage) { auto* dependant_query = tfb_streamer.GetQuery(query->dependant_index); @@ -1266,41 +1289,23 @@ private: } // namespace struct QueryCacheRuntimeImpl { - QueryCacheRuntimeImpl(QueryCacheRuntime& runtime, VideoCore::RasterizerInterface* rasterizer_, - Tegra::MaxwellDeviceMemoryManager& device_memory_, - Vulkan::BufferCache& buffer_cache_, const Device& device_, - const MemoryAllocator& memory_allocator_, Scheduler& scheduler_, - StagingBufferPool& staging_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue, - DescriptorPool& descriptor_pool, TextureCache& texture_cache_) - : rasterizer{rasterizer_}, device_memory{device_memory_}, buffer_cache{buffer_cache_}, - device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_}, - staging_pool{staging_pool_}, guest_streamer(0, runtime), - sample_streamer(static_cast(QueryType::ZPassPixelCount64), runtime, rasterizer, - texture_cache_, device, scheduler, memory_allocator, - compute_pass_descriptor_queue, descriptor_pool), - tfb_streamer(static_cast(QueryType::StreamingByteCount), runtime, device, - scheduler, memory_allocator, staging_pool), - primitives_succeeded_streamer( - static_cast(QueryType::StreamingPrimitivesSucceeded), runtime, tfb_streamer, - device_memory_), - primitives_needed_minus_succeeded_streamer( - static_cast(QueryType::StreamingPrimitivesNeededMinusSucceeded), runtime, 0u), - hcr_setup{}, hcr_is_set{}, is_hcr_running{}, maxwell3d{} { + QueryCacheRuntimeImpl(QueryCacheRuntime& runtime, VideoCore::RasterizerInterface* rasterizer_, Tegra::MaxwellDeviceMemoryManager& device_memory_, Vulkan::BufferCache& buffer_cache_, const Device& device_, const MemoryAllocator& memory_allocator_, Scheduler& scheduler_, StagingBufferPool& staging_pool_, ComputePassDescriptorQueue& compute_pass_descriptor_queue, DescriptorPool& descriptor_pool, TextureCache& texture_cache_) + : rasterizer{rasterizer_}, device_memory{device_memory_}, buffer_cache{buffer_cache_} + , device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_} + , staging_pool{staging_pool_}, guest_streamer(0, runtime) + , sample_streamer(size_t(QueryType::ZPassPixelCount64), runtime, rasterizer, texture_cache_, device, scheduler, memory_allocator, compute_pass_descriptor_queue, descriptor_pool) + , tfb_streamer(size_t(QueryType::StreamingByteCount), runtime, device, scheduler, memory_allocator, staging_pool) + , primitives_succeeded_streamer(size_t(QueryType::StreamingPrimitivesSucceeded), runtime, tfb_streamer, device_memory_) + , primitives_needed_minus_succeeded_streamer(size_t(QueryType::StreamingPrimitivesNeededMinusSucceeded), runtime, 0u) + , hcr_setup{}, hcr_is_set{}, is_hcr_running{}, maxwell3d{} { hcr_setup.sType = VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT; hcr_setup.pNext = nullptr; hcr_setup.flags = 0; - const bool has_conditional_rendering = device.IsExtConditionalRendering(); - if (has_conditional_rendering) { - conditional_resolve_pass = std::make_unique( - device, scheduler, descriptor_pool, compute_pass_descriptor_queue); - } - - VkBufferUsageFlags hcr_buffer_usage = - VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; - if (has_conditional_rendering) { + VkBufferUsageFlags hcr_buffer_usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + if (device.IsExtConditionalRendering()) { + conditional_resolve_pass.emplace(device, scheduler, descriptor_pool, compute_pass_descriptor_queue); hcr_buffer_usage |= VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT; } @@ -1339,7 +1344,7 @@ struct QueryCacheRuntimeImpl { std::vector> copies_setup; // Host conditional rendering data - std::unique_ptr conditional_resolve_pass; + std::optional conditional_resolve_pass; vk::Buffer hcr_resolve_buffer; VkConditionalRenderingBeginInfoEXT hcr_setup; VkBuffer hcr_buffer; @@ -1351,13 +1356,7 @@ struct QueryCacheRuntimeImpl { Maxwell3D* maxwell3d; }; -QueryCacheRuntime::QueryCacheRuntime(VideoCore::RasterizerInterface* rasterizer, - Tegra::MaxwellDeviceMemoryManager& device_memory_, - Vulkan::BufferCache& buffer_cache_, const Device& device_, - const MemoryAllocator& memory_allocator_, - Scheduler& scheduler_, StagingBufferPool& staging_pool_, - ComputePassDescriptorQueue& compute_pass_descriptor_queue, - DescriptorPool& descriptor_pool, TextureCache& texture_cache_) { +QueryCacheRuntime::QueryCacheRuntime(VideoCore::RasterizerInterface* rasterizer, Tegra::MaxwellDeviceMemoryManager& device_memory_, Vulkan::BufferCache& buffer_cache_, const Device& device_, const MemoryAllocator& memory_allocator_, Scheduler& scheduler_, StagingBufferPool& staging_pool_, ComputePassDescriptorQueue& compute_pass_descriptor_queue, DescriptorPool& descriptor_pool, TextureCache& texture_cache_) { impl = std::make_unique( *this, rasterizer, device_memory_, buffer_cache_, device_, memory_allocator_, scheduler_, staging_pool_, compute_pass_descriptor_queue, descriptor_pool, texture_cache_); @@ -1520,7 +1519,7 @@ bool QueryCacheRuntime::HostConditionalRenderingCompareValues(VideoCommon::Looku auto driver_id = impl->device.GetDriverID(); const bool is_gpu_high = Settings::IsGPULevelHigh(); - if ((!is_gpu_high && driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) || driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) { + if ((!is_gpu_high && driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) || driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY || driver_id == VK_DRIVER_ID_ARM_PROPRIETARY || driver_id == VK_DRIVER_ID_MESA_TURNIP) { EndHostConditionalRendering(); return true; } @@ -1587,13 +1586,13 @@ void QueryCacheRuntime::Barriers(bool is_prebarrier) { impl->scheduler.RequestOutsideRenderPassOperationContext(); if (is_prebarrier) { impl->scheduler.Record([](vk::CommandBuffer cmdbuf) { - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER); }); } else { impl->scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER); + vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER_HOST, 0, WRITE_BARRIER); }); } } @@ -1686,8 +1685,7 @@ void QueryCacheRuntime::SyncValues(std::span values, VkBuffer ba } impl->scheduler.RequestOutsideRenderPassOperationContext(); - impl->scheduler.Record([src_buffer, dst_buffers = std::move(impl->buffers_to_upload_to), - vk_copies = std::move(impl->copies_setup)](vk::CommandBuffer cmdbuf) { + impl->scheduler.Record([src_buffer, dst_buffers = std::move(impl->buffers_to_upload_to), vk_copies = std::move(impl->copies_setup)](vk::CommandBuffer cmdbuf) { size_t size = dst_buffers.size(); for (size_t i = 0; i < size; i++) { cmdbuf.CopyBuffer(src_buffer, dst_buffers[i].first, vk_copies[i]); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 3c3367cfd8..264ffb28b5 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -203,7 +203,10 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra : gpu{gpu_}, device_memory{device_memory_}, device{device_}, memory_allocator{memory_allocator_}, state_tracker{state_tracker_}, scheduler{scheduler_}, staging_pool(device, memory_allocator, scheduler), descriptor_pool(device, scheduler), - guest_descriptor_queue(device, scheduler), compute_pass_descriptor_queue(device, scheduler), + guest_descriptor_queue(device, UpdateDescriptorQueue::GUEST_FRAME_PAYLOAD_SIZE, + device.IsExtDescriptorBufferSupported()), + compute_pass_descriptor_queue(device, UpdateDescriptorQueue::COMPUTE_FRAME_PAYLOAD_SIZE), + descriptor_buffer_ring(device, memory_allocator), blit_image(device, scheduler, state_tracker, descriptor_pool), render_pass_cache(device), texture_cache_runtime{ device, scheduler, memory_allocator, staging_pool, @@ -216,14 +219,18 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra staging_pool, compute_pass_descriptor_queue, descriptor_pool, texture_cache), query_cache(gpu, *this, device_memory, query_cache_runtime), pipeline_cache(device_memory, device, scheduler, descriptor_pool, guest_descriptor_queue, - render_pass_cache, buffer_cache, texture_cache, gpu.ShaderNotify()), + descriptor_buffer_ring, render_pass_cache, buffer_cache, texture_cache, + gpu.ShaderNotify()), accelerate_dma(buffer_cache, texture_cache, scheduler), fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler), wfi_event(device.GetLogical().CreateEvent()) { scheduler.SetQueryCache(query_cache); } -RasterizerVulkan::~RasterizerVulkan() = default; +RasterizerVulkan::~RasterizerVulkan() { + scheduler.WaitWorker(); + scheduler.Finish(); +} template void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) { @@ -270,7 +277,7 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) { }); // Log draw call - if (Settings::values.gpu_logging_enabled.GetValue() && + if (GPU::Logging::IsActive() && Settings::values.gpu_log_vulkan_calls.GetValue()) { const std::string params = is_indexed ? fmt::format("vertices={}, instances={}, firstIndex={}, baseVertex={}, baseInstance={}", @@ -331,7 +338,7 @@ void RasterizerVulkan::DrawIndirect() { }); // Log indirect draw call - if (Settings::values.gpu_logging_enabled.GetValue() && + if (GPU::Logging::IsActive() && Settings::values.gpu_log_vulkan_calls.GetValue()) { const std::string log_params = fmt::format("drawCount={}, stride={}", params.max_draw_counts, params.stride); @@ -351,7 +358,7 @@ void RasterizerVulkan::DrawTexture() { FlushWork(); std::scoped_lock l{texture_cache.mutex}; - texture_cache.SynchronizeGraphicsDescriptors(); + texture_cache.SynchronizeDescriptors(false); texture_cache.UpdateRenderTargets(false); UpdateDynamicStates(); @@ -359,7 +366,7 @@ void RasterizerVulkan::DrawTexture() { query_cache.NotifySegment(true); query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, maxwell3d->regs.zpass_pixel_count_enable); const auto& draw_texture_state = maxwell3d->draw_manager.draw_texture_state; - const auto& sampler = texture_cache.GetGraphicsSampler(draw_texture_state.src_sampler); + const auto& sampler = texture_cache.GetSampler(draw_texture_state.src_sampler, false); const auto& texture = texture_cache.GetImageView(draw_texture_state.src_texture); const auto* framebuffer = texture_cache.GetFramebuffer(); @@ -407,18 +414,22 @@ void RasterizerVulkan::Clear(u32 layer_count) { texture_cache.UpdateRenderTargets(true); const Framebuffer* const framebuffer = texture_cache.GetFramebuffer(); const VkExtent2D render_area = framebuffer->RenderArea(); - scheduler.RequestRenderpass(framebuffer); - query_cache.NotifySegment(true); - query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, maxwell3d->regs.zpass_pixel_count_enable); + constexpr bool ENABLE_DEFERRED_CLEAR = true; + const bool color_full_channels = regs.clear_surface.R && regs.clear_surface.G && + regs.clear_surface.B && regs.clear_surface.A; + const bool stencil_partial = use_stencil && framebuffer->HasAspectStencilBit() && + regs.stencil_front_mask != 0xFF && regs.stencil_front_mask != 0; + const bool ds_used = use_depth || use_stencil; + const bool ds_deferrable = + !ds_used || ((!framebuffer->HasAspectDepthBit() || use_depth) && + (!framebuffer->HasAspectStencilBit() || use_stencil) && !stencil_partial); u32 up_scale = 1; u32 down_shift = 0; if (texture_cache.IsRescaling()) { up_scale = Settings::values.resolution_info.up_scale; down_shift = Settings::values.resolution_info.down_shift; } - UpdateViewportsState(regs); - VkRect2D default_scissor{}; default_scissor.offset.x = 0; default_scissor.offset.y = 0; @@ -474,6 +485,22 @@ void RasterizerVulkan::Clear(u32 layer_count) { return; } + const bool clear_covers_render_area = + clear_rect.rect.offset.x == 0 && clear_rect.rect.offset.y == 0 && + clear_rect.rect.extent.width >= render_area.width && + clear_rect.rect.extent.height >= render_area.height; + const bool can_defer_clear = ENABLE_DEFERRED_CLEAR && (!regs.clear_control.use_scissor || clear_covers_render_area) && + regs.clear_surface.layer == 0 && + !scheduler.IsRenderPassActive() && + (!use_color || color_full_channels) && ds_deferrable; + if (!can_defer_clear) { + scheduler.RequestRenderpass(framebuffer); + } + + query_cache.NotifySegment(true); + query_cache.CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, maxwell3d->regs.zpass_pixel_count_enable); + UpdateViewportsState(regs); + const u32 color_attachment = regs.clear_surface.RT; if (use_color && framebuffer->HasAspectColorBit(color_attachment)) { const auto format = VideoCore::Surface::PixelFormatFromRenderTargetFormat(regs.rt[color_attachment].format); @@ -491,15 +518,19 @@ void RasterizerVulkan::Clear(u32 layer_count) { clear_value.color.int32[i] = s32(f32(s64(int_size - 1) << 1) * (regs.clear_color[i] - 0.5f)); } - if (regs.clear_surface.R && regs.clear_surface.G && regs.clear_surface.B && regs.clear_surface.A) { - scheduler.Record([color_attachment, clear_value, clear_rect](vk::CommandBuffer cmdbuf) { - const VkClearAttachment attachment{ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .colorAttachment = color_attachment, - .clearValue = clear_value, - }; - cmdbuf.ClearAttachments(attachment, clear_rect); - }); + if (color_full_channels) { + if (can_defer_clear) { + scheduler.DeferColorClear(framebuffer, color_attachment, clear_value); + } else { + scheduler.Record([color_attachment, clear_value, clear_rect](vk::CommandBuffer cmdbuf) { + const VkClearAttachment attachment{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .colorAttachment = color_attachment, + .clearValue = clear_value, + }; + cmdbuf.ClearAttachments(attachment, clear_rect); + }); + } } else { u8 color_mask = u8(regs.clear_surface.R | regs.clear_surface.G << 1 | regs.clear_surface.B << 2 | regs.clear_surface.A << 3); Region2D dst_region = { @@ -533,6 +564,11 @@ void RasterizerVulkan::Clear(u32 layer_count) { blit_image.ClearDepthStencil(framebuffer, use_depth, regs.clear_depth, u8(regs.stencil_front_mask), regs.clear_stencil, regs.stencil_front_func_mask, dst_region); + } else if (can_defer_clear) { + VkClearValue ds_value{}; + ds_value.depthStencil.depth = regs.clear_depth; + ds_value.depthStencil.stencil = regs.clear_stencil; + scheduler.DeferDepthStencilClear(framebuffer, ds_value); } else { scheduler.Record([clear_depth = regs.clear_depth, clear_stencil = regs.clear_stencil, clear_rect, aspect_flags](vk::CommandBuffer cmdbuf) { @@ -555,7 +591,10 @@ void RasterizerVulkan::DispatchCompute() { return; } std::scoped_lock lock{texture_cache.mutex, buffer_cache.mutex}; - pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, texture_cache); + if (!pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, + texture_cache)) { + return; + } const auto& qmd{kepler_compute->launch_description}; auto indirect_address = kepler_compute->GetIndirectComputeAddress(); @@ -566,13 +605,20 @@ void RasterizerVulkan::DispatchCompute() { const auto [buffer, offset] = buffer_cache.ObtainBuffer(*indirect_address, 12, sync_info, post_op); scheduler.RequestOutsideRenderPassOperationContext(); - scheduler.Record([indirect_buffer = buffer->Handle(), + scheduler.Record([pipeline, indirect_buffer = buffer->Handle(), indirect_offset = offset](vk::CommandBuffer cmdbuf) { + if (!pipeline->IsBound()) { + return; + } cmdbuf.DispatchIndirect(indirect_buffer, indirect_offset); }); return; } const std::array dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z}; + const std::array max_dim{device.GetMaxComputeWorkGroupCount()}; + if (dim[0] > max_dim[0] || dim[1] > max_dim[1] || dim[2] > max_dim[2]) { + return; + } scheduler.RequestOutsideRenderPassOperationContext(); static constexpr VkMemoryBarrier READ_BARRIER{ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, @@ -580,12 +626,17 @@ void RasterizerVulkan::DispatchCompute() { .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, }; - scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, READ_BARRIER); }); - scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); }); + scheduler.Record([pipeline, dim](vk::CommandBuffer cmdbuf) { + if (!pipeline->IsBound()) { + return; + } + cmdbuf.Dispatch(dim[0], dim[1], dim[2]); + }); // Log compute dispatch - if (Settings::values.gpu_logging_enabled.GetValue() && + if (GPU::Logging::IsActive() && Settings::values.gpu_log_vulkan_calls.GetValue()) { const std::string params = fmt::format("groupCountX={}, groupCountY={}, groupCountZ={}", dim[0], dim[1], dim[2]); @@ -747,6 +798,18 @@ void RasterizerVulkan::OnCacheInvalidation(DAddr addr, u64 size) { pipeline_cache.InvalidateRegion(addr, size); } +bool RasterizerVulkan::TryCacheInvalidation(DAddr addr, u64 size) { + if (addr == 0 || size == 0) return true; + { + std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex}; + if (buffer_cache.IsRegionGpuModified(addr, size)) return false; + texture_cache.WriteMemory(addr, size); + buffer_cache.WriteMemory(addr, size); + } + pipeline_cache.InvalidateRegion(addr, size); + return true; +} + void RasterizerVulkan::InvalidateGPUCache() { gpu.InvalidateGPUCache(); } @@ -768,6 +831,7 @@ void RasterizerVulkan::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) { std::scoped_lock lock{texture_cache.mutex}; texture_cache.UnmapGPUMemory(as_id, addr, size); } + buffer_cache.UnmapGPUMemory(as_id, addr, size); } void RasterizerVulkan::SignalFence(std::function&& func) { @@ -842,6 +906,7 @@ void RasterizerVulkan::TickFrame() { draw_counter = 0; guest_descriptor_queue.TickFrame(); compute_pass_descriptor_queue.TickFrame(); + descriptor_buffer_ring.TickFrame(); fence_manager.TickFrame(); staging_pool.TickFrame(); { @@ -928,13 +993,13 @@ void RasterizerVulkan::LoadDiskResources(u64 title_id, std::stop_token stop_load } void RasterizerVulkan::FlushWork() { -#ifdef ANDROID +#ifdef __ANDROID__ static constexpr u32 DRAWS_TO_DISPATCH = 512; static constexpr u32 CHECK_MASK = 3; #else static constexpr u32 DRAWS_TO_DISPATCH = 4096; static constexpr u32 CHECK_MASK = 7; -#endif // ANDROID +#endif // __ANDROID__ static_assert(DRAWS_TO_DISPATCH % (CHECK_MASK + 1) == 0); if ((++draw_counter & CHECK_MASK) != CHECK_MASK) { @@ -1011,12 +1076,12 @@ void RasterizerVulkan::UpdateDynamicStates() { auto& regs = maxwell3d->regs; auto& flags = maxwell3d->dirty.flags; const auto topology = maxwell3d->draw_manager.draw_state.topology; - if (state_tracker.ChangePrimitiveTopology(topology)) { + const bool topology_changed = state_tracker.ChangePrimitiveTopology(topology); + if (topology_changed) { flags[Dirty::DepthBiasEnable] = true; flags[Dirty::PrimitiveRestartEnable] = true; } - // Core Dynamic States (Vulkan 1.0) - Always active regardless of dyna_state setting UpdateViewportsState(regs); UpdateScissorsState(regs); UpdateDepthBias(regs); @@ -1025,7 +1090,6 @@ void RasterizerVulkan::UpdateDynamicStates() { UpdateStencilFaces(regs); UpdateLineWidth(regs); - // EDS1: CullMode, DepthCompare, FrontFace, StencilOp, DepthBoundsTest, DepthTest, DepthWrite, StencilTest if (device.IsExtExtendedDynamicStateSupported()) { UpdateCullMode(regs); UpdateDepthCompareOp(regs); @@ -1037,21 +1101,24 @@ void RasterizerVulkan::UpdateDynamicStates() { UpdateDepthWriteEnable(regs); UpdateStencilTestEnable(regs); } + if (topology_changed) { + scheduler.Record([topology_vk = MaxwellToVK::PrimitiveTopology(device, topology)]( + vk::CommandBuffer cmdbuf) { + cmdbuf.SetPrimitiveTopologyEXT(topology_vk); + }); + } } - // EDS2: PrimitiveRestart, RasterizerDiscard, DepthBias enable/disable if (device.IsExtExtendedDynamicState2Supported()) { UpdatePrimitiveRestartEnable(regs); UpdateRasterizerDiscardEnable(regs); UpdateDepthBiasEnable(regs); } - // EDS2 Extras: LogicOp operation selection if (device.IsExtExtendedDynamicState2ExtrasSupported()) { UpdateLogicOp(regs); } - // EDS3 Enables: LogicOpEnable, DepthClamp, LineStipple, ConservativeRaster if (device.IsExtExtendedDynamicState3EnablesSupported()) { using namespace Tegra::Engines; // AMD Workaround: LogicOp incompatible with float render targets @@ -1076,12 +1143,12 @@ void RasterizerVulkan::UpdateDynamicStates() { UpdateAlphaToOneEnable(regs); } - // EDS3 Blending: ColorBlendEnable, ColorBlendEquation, ColorWriteMask if (device.IsExtExtendedDynamicState3BlendingSupported()) { UpdateBlending(regs); + } else if (device.IsExtColorWriteEnableSupported()) { + UpdateColorWriteEnable(regs); } - // Vertex Input Dynamic State: Independent from EDS levels if (device.IsExtVertexInputDynamicStateSupported()) { if (auto* gp = pipeline_cache.CurrentGraphicsPipeline(); gp && gp->HasDynamicVertexInput()) { UpdateVertexInput(regs); @@ -1094,7 +1161,6 @@ void RasterizerVulkan::HandleTransformFeedback() { const auto& regs = maxwell3d->regs; if (!device.IsExtTransformFeedbackSupported()) { - // If the guest enabled transform feedback, warn once that the device lacks support. if (regs.transform_feedback_enabled != 0) { std::call_once(warn_unsupported, [&] { LOG_WARNING(Render_Vulkan, "Transform feedback requested by guest but VK_EXT_transform_feedback is unavailable; queries disabled"); @@ -1110,7 +1176,7 @@ void RasterizerVulkan::HandleTransformFeedback() { regs.transform_feedback_enabled); if (regs.transform_feedback_enabled != 0) { // Log extension usage for transform feedback - if (Settings::values.gpu_logging_enabled.GetValue()) { + if (GPU::Logging::IsActive()) { GPU::Logging::GPULogger::GetInstance().LogExtensionUsage( "VK_EXT_transform_feedback", "HandleTransformFeedback"); } @@ -1305,15 +1371,22 @@ void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs) } if (update_references) { [&]() { + bool changed; if (regs.stencil_two_side_enable) { - if (!state_tracker.CheckStencilReferenceFront(regs.stencil_front_ref) && - !state_tracker.CheckStencilReferenceBack(regs.stencil_back_ref)) { - return; - } + const bool front_changed = + state_tracker.CheckStencilReferenceFront(regs.stencil_front_ref); + const bool back_changed = + state_tracker.CheckStencilReferenceBack(regs.stencil_back_ref); + changed = front_changed || back_changed; } else { - if (!state_tracker.CheckStencilReferenceFront(regs.stencil_front_ref)) { - return; - } + const bool front_changed = + state_tracker.CheckStencilReferenceFront(regs.stencil_front_ref); + const bool back_changed = + state_tracker.CheckStencilReferenceBack(regs.stencil_front_ref); + changed = front_changed || back_changed; + } + if (!changed) { + return; } scheduler.Record([front_ref = regs.stencil_front_ref, back_ref = regs.stencil_back_ref, two_sided = regs.stencil_two_side_enable](vk::CommandBuffer cmdbuf) { @@ -1330,15 +1403,22 @@ void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs) } if (update_write_mask) { [&]() { + bool changed; if (regs.stencil_two_side_enable) { - if (!state_tracker.CheckStencilWriteMaskFront(regs.stencil_front_mask) && - !state_tracker.CheckStencilWriteMaskBack(regs.stencil_back_mask)) { - return; - } + const bool front_changed = + state_tracker.CheckStencilWriteMaskFront(regs.stencil_front_mask); + const bool back_changed = + state_tracker.CheckStencilWriteMaskBack(regs.stencil_back_mask); + changed = front_changed || back_changed; } else { - if (!state_tracker.CheckStencilWriteMaskFront(regs.stencil_front_mask)) { - return; - } + const bool front_changed = + state_tracker.CheckStencilWriteMaskFront(regs.stencil_front_mask); + const bool back_changed = + state_tracker.CheckStencilWriteMaskBack(regs.stencil_front_mask); + changed = front_changed || back_changed; + } + if (!changed) { + return; } scheduler.Record([front_write_mask = regs.stencil_front_mask, back_write_mask = regs.stencil_back_mask, @@ -1356,15 +1436,22 @@ void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs) } if (update_compare_masks) { [&]() { + bool changed; if (regs.stencil_two_side_enable) { - if (!state_tracker.CheckStencilCompareMaskFront(regs.stencil_front_func_mask) && - !state_tracker.CheckStencilCompareMaskBack(regs.stencil_back_func_mask)) { - return; - } + const bool front_changed = + state_tracker.CheckStencilCompareMaskFront(regs.stencil_front_func_mask); + const bool back_changed = + state_tracker.CheckStencilCompareMaskBack(regs.stencil_back_func_mask); + changed = front_changed || back_changed; } else { - if (!state_tracker.CheckStencilCompareMaskFront(regs.stencil_front_func_mask)) { - return; - } + const bool front_changed = + state_tracker.CheckStencilCompareMaskFront(regs.stencil_front_func_mask); + const bool back_changed = + state_tracker.CheckStencilCompareMaskBack(regs.stencil_front_func_mask); + changed = front_changed || back_changed; + } + if (!changed) { + return; } scheduler.Record([front_test_mask = regs.stencil_front_func_mask, back_test_mask = regs.stencil_back_func_mask, @@ -1723,9 +1810,16 @@ void RasterizerVulkan::UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs) { if (state_tracker.TouchBlendEnable()) { std::array setup_enables{}; - std::ranges::transform( - regs.blend.enable, setup_enables.begin(), - [&](const auto& is_enabled) { return is_enabled != 0 ? VK_TRUE : VK_FALSE; }); + for (size_t index = 0; index < Maxwell::NumRenderTargets; index++) { + bool is_integer = false; + if (regs.rt[index].format != Tegra::RenderTargetFormat::NONE) { + const auto format = + VideoCore::Surface::PixelFormatFromRenderTargetFormat(regs.rt[index].format); + is_integer = IsPixelFormatInteger(format); + } + setup_enables[index] = + (!is_integer && regs.blend.enable[index] != 0) ? VK_TRUE : VK_FALSE; + } scheduler.Record([setup_enables](vk::CommandBuffer cmdbuf) { cmdbuf.SetColorBlendEnableEXT(0, setup_enables); }); @@ -1774,6 +1868,20 @@ void RasterizerVulkan::UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs) { } } +void RasterizerVulkan::UpdateColorWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs) { + if (!state_tracker.TouchColorMask()) { + return; + } + std::array setup_enables{}; + for (size_t index = 0; index < Maxwell::NumRenderTargets; index++) { + const auto& mask = regs.color_mask[regs.color_mask_common ? 0 : index]; + setup_enables[index] = (mask.R || mask.G || mask.B || mask.A) ? VK_TRUE : VK_FALSE; + } + scheduler.Record([setup_enables](vk::CommandBuffer cmdbuf) { + cmdbuf.SetColorWriteEnableEXT(setup_enables); + }); +} + void RasterizerVulkan::UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) { if (!state_tracker.TouchStencilTestEnable()) { return; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 841933d31d..ae9929ca0b 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -17,6 +17,7 @@ #include "video_core/rasterizer_interface.h" #include "video_core/renderer_vulkan/blit_image.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h" +#include "video_core/renderer_vulkan/vk_descriptor_buffer.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_fence_manager.h" #include "video_core/renderer_vulkan/vk_pipeline_cache.h" @@ -105,6 +106,7 @@ public: VideoCommon::CacheType which = VideoCommon::CacheType::All) override; void InnerInvalidation(std::span> sequences) override; void OnCacheInvalidation(DAddr addr, u64 size) override; + bool TryCacheInvalidation(DAddr addr, u64 size) override; bool OnCPUWrite(DAddr addr, u64 size) override; void InvalidateGPUCache() override; void UnmapMemory(DAddr addr, u64 size) override; @@ -191,6 +193,7 @@ private: void UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateLogicOp(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateBlending(Tegra::Engines::Maxwell3D::Regs& regs); + void UpdateColorWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs); void UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs); @@ -206,6 +209,7 @@ private: DescriptorPool descriptor_pool; GuestDescriptorQueue guest_descriptor_queue; ComputePassDescriptorQueue compute_pass_descriptor_queue; + DescriptorBufferRing descriptor_buffer_ring; BlitImageHelper blit_image; RenderPassCache render_pass_cache; diff --git a/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp b/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp index 118b2a0832..4d8fa15d16 100644 --- a/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_render_pass_cache.cpp @@ -4,7 +4,7 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#include "common/container/unordered_map.h" #include @@ -44,7 +44,9 @@ using VideoCore::Surface::SurfaceType; } VkAttachmentDescription AttachmentDescription(const Device& device, PixelFormat format, - VkSampleCountFlagBits samples) { + VkSampleCountFlagBits samples, + VkAttachmentLoadOp load_op, + VkAttachmentStoreOp store_op) { using MaxwellToVK::SurfaceFormat; const SurfaceType surface_type = GetSurfaceType(format); @@ -55,18 +57,80 @@ using VideoCore::Surface::SurfaceType; .flags = {}, .format = SurfaceFormat(device, FormatType::Optimal, true, format).format, .samples = samples, - .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, - .storeOp = VK_ATTACHMENT_STORE_OP_STORE, - .stencilLoadOp = has_stencil ? VK_ATTACHMENT_LOAD_OP_LOAD - : VK_ATTACHMENT_LOAD_OP_DONT_CARE, - .stencilStoreOp = has_stencil ? VK_ATTACHMENT_STORE_OP_STORE - : VK_ATTACHMENT_STORE_OP_DONT_CARE, + .loadOp = load_op, + .storeOp = store_op, + .stencilLoadOp = has_stencil ? load_op : VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = has_stencil ? store_op : VK_ATTACHMENT_STORE_OP_DONT_CARE, .initialLayout = VK_IMAGE_LAYOUT_GENERAL, .finalLayout = VK_IMAGE_LAYOUT_GENERAL, }; } + + struct ResolveAspects { + bool depth; + bool stencil; + }; + + struct ResolveModes { + VkResolveModeFlagBits depth; + VkResolveModeFlagBits stencil; + }; + + constexpr ResolveAspects GetResolveAspects(PixelFormat format) { + const SurfaceType surface_type = GetSurfaceType(format); + return ResolveAspects{ + .depth = surface_type == SurfaceType::Depth || + surface_type == SurfaceType::DepthStencil, + .stencil = surface_type == SurfaceType::Stencil || + surface_type == SurfaceType::DepthStencil, + }; + } + + ResolveModes PickResolveModes(const Device& device, PixelFormat format) { + constexpr VkResolveModeFlagBits mode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT; + + const ResolveAspects aspects = GetResolveAspects(format); + const bool depth_mode_supported = (device.GetDepthResolveModes() & mode) != 0; + const bool stencil_mode_supported = (device.GetStencilResolveModes() & mode) != 0; + + ResolveModes modes{ + .depth = VK_RESOLVE_MODE_NONE, + .stencil = VK_RESOLVE_MODE_NONE, + }; + if (aspects.depth && depth_mode_supported) { + modes.depth = mode; + } + if (aspects.stencil && stencil_mode_supported) { + modes.stencil = mode; + } + if (modes.depth == modes.stencil || device.SupportsIndependentResolveNone()) { + return modes; + } + if (modes.depth != VK_RESOLVE_MODE_NONE && stencil_mode_supported) { + modes.stencil = mode; + } else if (modes.stencil != VK_RESOLVE_MODE_NONE && depth_mode_supported) { + modes.depth = mode; + } + return modes; + } } // Anonymous namespace +bool SupportsDepthStencilResolve(const Device& device, PixelFormat depth_format) { + if (depth_format == PixelFormat::Invalid || !device.IsKhrDepthStencilResolveSupported()) { + return false; + } + const ResolveAspects aspects = GetResolveAspects(depth_format); + if (!aspects.depth && !aspects.stencil) { + return false; + } + const ResolveModes modes = PickResolveModes(device, depth_format); + if ((aspects.depth && modes.depth == VK_RESOLVE_MODE_NONE) || + (aspects.stencil && modes.stencil == VK_RESOLVE_MODE_NONE)) { + return false; + } + return modes.depth == modes.stencil || device.SupportsIndependentResolveNone(); +} + RenderPassCache::RenderPassCache(const Device& device_) : device{&device_} {} VkRenderPass RenderPassCache::Get(const RenderPassKey& key) { @@ -75,7 +139,9 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) { if (!is_new) { return *pair->second; } - boost::container::static_vector descriptions; + static constexpr size_t MAX_ATTACHMENTS = + 2 * std::tuple_size_v + 2; + boost::container::static_vector descriptions; std::array references{}; u32 num_attachments{}; u32 num_colors{}; @@ -87,7 +153,14 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) { .layout = VK_IMAGE_LAYOUT_GENERAL, }; if (is_valid) { - descriptions.push_back(AttachmentDescription(*device, format, key.samples)); + const VkAttachmentLoadOp load_op = (key.color_clear_mask & (1u << index)) != 0 + ? VK_ATTACHMENT_LOAD_OP_CLEAR + : VK_ATTACHMENT_LOAD_OP_LOAD; + const VkAttachmentStoreOp store_op = (key.color_discard_mask & (1u << index)) != 0 + ? VK_ATTACHMENT_STORE_OP_DONT_CARE + : VK_ATTACHMENT_STORE_OP_STORE; + descriptions.push_back( + AttachmentDescription(*device, format, key.samples, load_op, store_op)); num_attachments = static_cast(index + 1); ++num_colors; } @@ -99,7 +172,50 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) { .attachment = num_colors, .layout = VK_IMAGE_LAYOUT_GENERAL, }; - descriptions.push_back(AttachmentDescription(*device, key.depth_format, key.samples)); + const VkAttachmentLoadOp depth_load_op = key.depth_stencil_clear + ? VK_ATTACHMENT_LOAD_OP_CLEAR + : VK_ATTACHMENT_LOAD_OP_LOAD; + const VkAttachmentStoreOp depth_store_op = key.depth_stencil_discard + ? VK_ATTACHMENT_STORE_OP_DONT_CARE + : VK_ATTACHMENT_STORE_OP_STORE; + descriptions.push_back(AttachmentDescription(*device, key.depth_format, key.samples, + depth_load_op, depth_store_op)); + } + std::array resolve_references{}; + const bool do_resolve_color = + key.resolve_color && key.samples != VK_SAMPLE_COUNT_1_BIT && num_colors > 0; + if (do_resolve_color) { + for (size_t index = 0; index < key.color_formats.size(); ++index) { + const PixelFormat format{key.color_formats[index]}; + const bool is_valid{format != PixelFormat::Invalid}; + resolve_references[index] = VkAttachmentReference{ + .attachment = is_valid ? static_cast(descriptions.size()) : VK_ATTACHMENT_UNUSED, + .layout = VK_IMAGE_LAYOUT_GENERAL, + }; + if (is_valid) { + VkAttachmentDescription resolve_desc = + AttachmentDescription(*device, format, VK_SAMPLE_COUNT_1_BIT, + VK_ATTACHMENT_LOAD_OP_DONT_CARE, + VK_ATTACHMENT_STORE_OP_STORE); + resolve_desc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + descriptions.push_back(resolve_desc); + } + } + } + const bool do_resolve_depth_stencil = key.resolve_depth_stencil && has_depth && + key.samples != VK_SAMPLE_COUNT_1_BIT && + SupportsDepthStencilResolve(*device, key.depth_format); + VkAttachmentReference depth_resolve_reference{}; + if (do_resolve_depth_stencil) { + depth_resolve_reference = VkAttachmentReference{ + .attachment = static_cast(descriptions.size()), + .layout = VK_IMAGE_LAYOUT_GENERAL, + }; + VkAttachmentDescription resolve_desc = + AttachmentDescription(*device, key.depth_format, VK_SAMPLE_COUNT_1_BIT, + VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_STORE); + resolve_desc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + descriptions.push_back(resolve_desc); } const VkSubpassDescription subpass{ .flags = 0, @@ -108,7 +224,7 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) { .pInputAttachments = nullptr, .colorAttachmentCount = num_attachments, .pColorAttachments = references.data(), - .pResolveAttachments = nullptr, + .pResolveAttachments = do_resolve_color ? resolve_references.data() : nullptr, .pDepthStencilAttachment = has_depth ? &depth_reference : nullptr, .preserveAttachmentCount = 0, .pPreserveAttachments = nullptr, @@ -125,6 +241,92 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) { .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT }; + + if (device->IsKhrCreateRenderPass2Supported()) { + boost::container::static_vector descriptions2; + for (const VkAttachmentDescription& description : descriptions) { + descriptions2.push_back(VkAttachmentDescription2{ + .sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2, + .pNext = nullptr, + .flags = description.flags, + .format = description.format, + .samples = description.samples, + .loadOp = description.loadOp, + .storeOp = description.storeOp, + .stencilLoadOp = description.stencilLoadOp, + .stencilStoreOp = description.stencilStoreOp, + .initialLayout = description.initialLayout, + .finalLayout = description.finalLayout, + }); + } + const auto promote = [](const VkAttachmentReference& reference) { + return VkAttachmentReference2{ + .sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2, + .pNext = nullptr, + .attachment = reference.attachment, + .layout = reference.layout, + .aspectMask = 0, + }; + }; + std::array references2{}; + std::array resolve_references2{}; + for (size_t index = 0; index < references.size(); ++index) { + references2[index] = promote(references[index]); + resolve_references2[index] = promote(resolve_references[index]); + } + const VkAttachmentReference2 depth_reference2 = promote(depth_reference); + const VkAttachmentReference2 depth_resolve_reference2 = promote(depth_resolve_reference); + const ResolveModes resolve_modes = PickResolveModes(*device, key.depth_format); + const VkSubpassDescriptionDepthStencilResolve depth_stencil_resolve{ + .sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE, + .pNext = nullptr, + .depthResolveMode = resolve_modes.depth, + .stencilResolveMode = resolve_modes.stencil, + .pDepthStencilResolveAttachment = &depth_resolve_reference2, + }; + const VkSubpassDescription2 subpass2{ + .sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2, + .pNext = do_resolve_depth_stencil ? &depth_stencil_resolve : nullptr, + .flags = 0, + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .viewMask = 0, + .inputAttachmentCount = 0, + .pInputAttachments = nullptr, + .colorAttachmentCount = num_attachments, + .pColorAttachments = references2.data(), + .pResolveAttachments = do_resolve_color ? resolve_references2.data() : nullptr, + .pDepthStencilAttachment = has_depth ? &depth_reference2 : nullptr, + .preserveAttachmentCount = 0, + .pPreserveAttachments = nullptr, + }; + const VkSubpassDependency2 dependency2{ + .sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2, + .pNext = nullptr, + .srcSubpass = dependency.srcSubpass, + .dstSubpass = dependency.dstSubpass, + .srcStageMask = dependency.srcStageMask, + .dstStageMask = dependency.dstStageMask, + .srcAccessMask = dependency.srcAccessMask, + .dstAccessMask = dependency.dstAccessMask, + .dependencyFlags = dependency.dependencyFlags, + .viewOffset = 0, + }; + pair->second = device->GetLogical().CreateRenderPass2({ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2, + .pNext = nullptr, + .flags = 0, + .attachmentCount = static_cast(descriptions2.size()), + .pAttachments = descriptions2.empty() ? nullptr : descriptions2.data(), + .subpassCount = 1, + .pSubpasses = &subpass2, + .dependencyCount = 1, + .pDependencies = &dependency2, + .correlatedViewMaskCount = 0, + .pCorrelatedViewMasks = nullptr, + }); + return *pair->second; + } + pair->second = device->GetLogical().CreateRenderPass({ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .pNext = nullptr, diff --git a/src/video_core/renderer_vulkan/vk_render_pass_cache.h b/src/video_core/renderer_vulkan/vk_render_pass_cache.h index 5c7b3c2aed..0edad4639e 100644 --- a/src/video_core/renderer_vulkan/vk_render_pass_cache.h +++ b/src/video_core/renderer_vulkan/vk_render_pass_cache.h @@ -7,8 +7,9 @@ #pragma once #include -#include +#include "common/container/unordered_map.h" +#include "common/container_hash.h" #include "video_core/surface.h" #include "video_core/vulkan_common/vulkan_wrapper.h" @@ -20,6 +21,12 @@ struct RenderPassKey { std::array color_formats; VideoCore::Surface::PixelFormat depth_format; VkSampleCountFlagBits samples; + bool resolve_color; + bool resolve_depth_stencil; + u32 color_clear_mask; + bool depth_stencil_clear; + u32 color_discard_mask; + bool depth_stencil_discard; }; } // namespace Vulkan @@ -27,13 +34,28 @@ struct RenderPassKey { namespace std { template <> struct hash { + static_assert(std::tuple_size_v <= 8); + static_assert(static_cast(VideoCore::Surface::PixelFormat::Invalid) <= 0xFF); + static_assert(static_cast(VideoCore::Surface::PixelFormat::Max) <= 0xFF); + static_assert(VK_SAMPLE_COUNT_64_BIT <= 0xFF); + [[nodiscard]] size_t operator()(const Vulkan::RenderPassKey& key) const noexcept { - size_t value = static_cast(key.depth_format) << 48; - value ^= static_cast(key.samples) << 52; - for (size_t i = 0; i < key.color_formats.size(); ++i) { - value ^= static_cast(key.color_formats[i]) << (i * 6); + u64 formats = 0; + for (size_t index = 0; index < key.color_formats.size(); ++index) { + formats |= static_cast(key.color_formats[index]) << (index * 8); } - return value; + const u64 state = static_cast(key.depth_format) | + (static_cast(key.samples) << 8) | + (static_cast(key.color_clear_mask) << 16) | + (static_cast(key.color_discard_mask) << 24) | + (static_cast(key.resolve_color) << 32) | + (static_cast(key.depth_stencil_clear) << 33) | + (static_cast(key.resolve_depth_stencil) << 34) | + (static_cast(key.depth_stencil_discard) << 35); + size_t seed = 0; + Common::HashCombine(seed, formats); + Common::HashCombine(seed, state); + return seed; } }; } // namespace std @@ -42,6 +64,9 @@ namespace Vulkan { class Device; +[[nodiscard]] bool SupportsDepthStencilResolve(const Device& device, + VideoCore::Surface::PixelFormat depth_format); + class RenderPassCache { public: explicit RenderPassCache(const Device& device_); @@ -50,7 +75,7 @@ public: private: const Device* device{}; - ankerl::unordered_dense::map cache; + ::Common::unordered_map cache; std::mutex mutex; }; diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.cpp b/src/video_core/renderer_vulkan/vk_resource_pool.cpp index 6572f82ba9..92e66b789c 100644 --- a/src/video_core/renderer_vulkan/vk_resource_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_resource_pool.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -12,10 +15,7 @@ ResourcePool::ResourcePool(MasterSemaphore& master_semaphore_, size_t grow_step_ : master_semaphore{&master_semaphore_}, grow_step{grow_step_} {} size_t ResourcePool::CommitResource() { - // Refresh semaphore to query updated results - master_semaphore->Refresh(); - const u64 gpu_tick = master_semaphore->KnownGpuTick(); - const auto search = [this, gpu_tick](size_t begin, size_t end) -> std::optional { + const auto search = [this](size_t begin, size_t end, u64 gpu_tick) -> std::optional { for (size_t iterator = begin; iterator < end; ++iterator) { if (gpu_tick >= ticks[iterator]) { ticks[iterator] = master_semaphore->CurrentTick(); @@ -24,11 +24,17 @@ size_t ResourcePool::CommitResource() { } return std::nullopt; }; - // Try to find a free resource from the hinted position to the end. - std::optional found = search(hint_iterator, ticks.size()); + const auto find_free = [&](u64 gpu_tick) -> std::optional { + std::optional result = search(hint_iterator, ticks.size(), gpu_tick); + if (!result) { + result = search(0, hint_iterator, gpu_tick); + } + return result; + }; + std::optional found = find_free(master_semaphore->KnownGpuTick()); if (!found) { - // Search from beginning to the hinted position. - found = search(0, hint_iterator); + master_semaphore->Refresh(); + found = find_free(master_semaphore->KnownGpuTick()); if (!found) { // Both searches failed, the pool is full; handle it. const size_t free_resource = ManageOverflow(); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index fdaf9baacc..4a7a7d5e78 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -27,7 +27,6 @@ namespace Vulkan { - void Scheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf) { auto command = first; @@ -48,15 +47,6 @@ Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_) master_semaphore{std::make_unique(device)}, command_pool{std::make_unique(*master_semaphore, device)} { - /*// PRE-OPTIMIZATION: Warm up the pool to prevent mid-frame spikes - { - std::scoped_lock rl{reserve_mutex}; - chunk_reserve.reserve(2048); // Prevent vector resizing - for (int i = 0; i < 1024; ++i) { - chunk_reserve.push_back(std::make_unique()); - } - }*/ - AcquireNewChunk(); AllocateWorkerCommandBuffer(); worker_thread = std::jthread([this](std::stop_token token) { WorkerThread(token); }); @@ -93,41 +83,37 @@ void Scheduler::WaitWorker() { } void Scheduler::DispatchWork() { - if (chunk->Empty()) { - return; + if (chunk && !chunk->Empty()) { + { + std::scoped_lock ql{queue_mutex}; + work_queue.push(std::move(chunk)); + } + event_cv.notify_all(); + AcquireNewChunk(); } - { - std::scoped_lock ql{queue_mutex}; - work_queue.push(std::move(chunk)); - } - event_cv.notify_all(); - AcquireNewChunk(); } -void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) { - const VkRenderPass renderpass = framebuffer->RenderPass(); +void Scheduler::BeginRenderPassImpl(const Framebuffer* framebuffer, VkRenderPass renderpass, + const VkClearValue* clear_values, u32 clear_value_count) { const VkFramebuffer framebuffer_handle = framebuffer->Handle(); const VkExtent2D render_area = framebuffer->RenderArea(); - if (renderpass == state.renderpass && framebuffer_handle == state.framebuffer && - render_area.width == state.render_area.width && - render_area.height == state.render_area.height) { - return; - } - EndRenderPass(); state.renderpass = renderpass; state.framebuffer = framebuffer_handle; state.render_area = render_area; - // Log render pass begin - if (Settings::values.gpu_logging_enabled.GetValue() && - Settings::values.gpu_log_vulkan_calls.GetValue()) { - const std::string render_pass_info = fmt::format( - "renderArea={}x{}, numImages={}", - render_area.width, render_area.height, framebuffer->NumImages()); + if (GPU::Logging::IsActive() && Settings::values.gpu_log_vulkan_calls.GetValue()) { + const std::string render_pass_info = + fmt::format("renderArea={}x{}, numImages={}", render_area.width, render_area.height, + framebuffer->NumImages()); GPU::Logging::GPULogger::GetInstance().LogRenderPassBegin(render_pass_info); } - Record([renderpass, framebuffer_handle, render_area](vk::CommandBuffer cmdbuf) { + std::array values{}; + for (u32 i = 0; i < clear_value_count && i < values.size(); ++i) { + values[i] = clear_values[i]; + } + Record([renderpass, framebuffer_handle, render_area, values, clear_value_count]( + vk::CommandBuffer cmdbuf) { const VkRenderPassBeginInfo renderpass_bi{ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .pNext = nullptr, @@ -138,14 +124,99 @@ void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) { .offset = {.x = 0, .y = 0}, .extent = render_area, }, - .clearValueCount = 0, - .pClearValues = nullptr, + .clearValueCount = clear_value_count, + .pClearValues = clear_value_count != 0 ? values.data() : nullptr, }; cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); }); num_renderpass_images = framebuffer->NumImages(); renderpass_images = framebuffer->Images(); renderpass_image_ranges = framebuffer->ImageRanges(); + framebuffer->MarkResolveShadowsUpToDate(); +} + +void Scheduler::RealizeDeferredClear() { + if (deferred_clear.framebuffer == nullptr) { + return; + } + const DeferredClear dc = deferred_clear; + deferred_clear = {}; + + std::array clear_values{}; + u32 count = 0; + const RenderPassKey& base = dc.framebuffer->RenderPassKeyBase(); + for (u32 slot = 0; slot < 8; ++slot) { + if (base.color_formats[slot] == VideoCore::Surface::PixelFormat::Invalid) { + continue; + } + clear_values[count++] = dc.color_values[slot]; + } + if (base.depth_format != VideoCore::Surface::PixelFormat::Invalid) { + clear_values[count++] = dc.depth_stencil_value; + } + const u32 color_discard_mask = + dc.framebuffer->DiscardsMsaaColor() ? dc.color_clear_mask : 0u; + const bool depth_stencil_discard = + dc.depth_stencil && dc.framebuffer->DiscardsMsaaDepthStencil(); + const VkRenderPass renderpass = dc.framebuffer->RenderPassVariant( + dc.color_clear_mask, dc.depth_stencil, color_discard_mask, depth_stencil_discard); + EndRenderPass(); + BeginRenderPassImpl(dc.framebuffer, renderpass, clear_values.data(), count); +} + +bool Scheduler::DeferColorClear(const Framebuffer* framebuffer, u32 rt_slot, + const VkClearValue& value) { + if (IsRenderPassActive()) { + return false; + } + if (deferred_clear.framebuffer != nullptr && deferred_clear.framebuffer != framebuffer) { + RealizeDeferredClear(); + EndRenderPass(); + } + deferred_clear.framebuffer = framebuffer; + deferred_clear.color_clear_mask |= 1u << rt_slot; + deferred_clear.color_values[rt_slot] = value; + return true; +} + +bool Scheduler::DeferDepthStencilClear(const Framebuffer* framebuffer, const VkClearValue& value) { + if (IsRenderPassActive()) { + return false; + } + if (deferred_clear.framebuffer != nullptr && deferred_clear.framebuffer != framebuffer) { + RealizeDeferredClear(); + EndRenderPass(); + } + deferred_clear.framebuffer = framebuffer; + deferred_clear.depth_stencil = true; + deferred_clear.depth_stencil_value = value; + return true; +} + +void Scheduler::FlushDeferredClear() { + if (deferred_clear.framebuffer == nullptr) { + return; + } + RealizeDeferredClear(); + EndRenderPass(); +} + +void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) { + if (deferred_clear.framebuffer == framebuffer) { + RealizeDeferredClear(); + return; + } + const VkRenderPass renderpass = framebuffer->RenderPass(); + const VkFramebuffer framebuffer_handle = framebuffer->Handle(); + const VkExtent2D render_area = framebuffer->RenderArea(); + if (renderpass == state.renderpass && framebuffer_handle == state.framebuffer && + render_area.width == state.render_area.width && + render_area.height == state.render_area.height) { + return; + } + // Ends any active pass and realizes a deferred clear + EndRenderPass(); + BeginRenderPassImpl(framebuffer, renderpass, nullptr, 0); } void Scheduler::RequestOutsideRenderPassOperationContext() { @@ -187,8 +258,19 @@ bool Scheduler::UpdateRescaling(bool is_rescaling) { return true; } +bool Scheduler::UpdateDescriptorBufferChunk(u32 descriptor_chunk) { + if (state.descriptor_buffer_bound && descriptor_chunk == state.descriptor_buffer_chunk) { + return false; + } + state.descriptor_buffer_bound = true; + state.descriptor_buffer_chunk = descriptor_chunk; + return true; +} + void Scheduler::WorkerThread(std::stop_token stop_token) { Common::SetCurrentThreadName("VulkanWorker"); + Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical); + Common::SetCurrentThreadToPerformanceCores(); const auto TryPopQueue{[this](auto& work) -> bool { if (work_queue.empty()) { @@ -218,7 +300,7 @@ void Scheduler::WorkerThread(std::stop_token stop_token) { // Exchange lock ownership so that we take the execution lock before // the queue lock goes out of scope. This allows us to force execution // to complete in the next step. - std::exchange(lk, std::unique_lock{execution_mutex}); + void(std::exchange(lk, std::unique_lock{execution_mutex})); // Perform the work, tracking whether the chunk was a submission // before executing. @@ -270,8 +352,7 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, }; - upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER); + upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER); upload_cmdbuf.End(); cmdbuf.End(); @@ -284,7 +365,7 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se cmdbuf, upload_cmdbuf, signal_semaphore, wait_semaphore, signal_value)) { case VK_SUCCESS: // Log successful queue submission - if (Settings::values.gpu_logging_enabled.GetValue() && + if (GPU::Logging::IsActive() && Settings::values.gpu_log_vulkan_calls.GetValue()) { GPU::Logging::GPULogger::GetInstance().LogVulkanCall( "vkQueueSubmit", "", VK_SUCCESS); @@ -310,6 +391,7 @@ void Scheduler::AllocateNewContext() { void Scheduler::InvalidateState() { state.graphics_pipeline = nullptr; state.rescaling_defined = false; + state.descriptor_buffer_bound = false; state_tracker.InvalidateCommandBufferState(); } @@ -320,6 +402,7 @@ void Scheduler::EndPendingOperations() { void Scheduler::EndRenderPass() { + RealizeDeferredClear(); if (!state.renderpass) { return; } @@ -327,7 +410,7 @@ void Scheduler::EndRenderPass() query_cache->CounterClose(VideoCommon::QueryType::StreamingByteCount); // Log render pass end - if (Settings::values.gpu_logging_enabled.GetValue() && + if (GPU::Logging::IsActive() && Settings::values.gpu_log_vulkan_calls.GetValue()) { GPU::Logging::GPULogger::GetInstance().LogRenderPassEnd(); } @@ -337,7 +420,9 @@ void Scheduler::EndRenderPass() Record([num_images = num_renderpass_images, images = renderpass_images, - ranges = renderpass_image_ranges](vk::CommandBuffer cmdbuf) { + ranges = renderpass_image_ranges, + has_transform_feedback = device.IsExtTransformFeedbackSupported()]( + vk::CommandBuffer cmdbuf) { std::array barriers; for (size_t i = 0; i < num_images; ++i) { const VkImageSubresourceRange& range = ranges[i]; @@ -375,8 +460,19 @@ void Scheduler::EndRenderPass() } cmdbuf.EndRenderPass(); cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, nullptr, nullptr, vk::Span(barriers.data(), num_images)); + if (has_transform_feedback) { + static constexpr VkMemoryBarrier XFB_OUTPUT_BARRIER{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_TRANSFORM_FEEDBACK_WRITE_BIT_EXT, + .dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT, + VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, XFB_OUTPUT_BARRIER); + } }); state.renderpass = VkRenderPass{}; diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 0709c3a370..d4bc8d0ec5 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -59,6 +59,15 @@ public: /// Requests to begin a renderpass. void RequestRenderpass(const Framebuffer* framebuffer); + /// Defers a full-attachment color clear so it becomes the next render pass. + bool DeferColorClear(const Framebuffer* framebuffer, u32 rt_slot, const VkClearValue& value); + + /// Defers a full depth/stencil clear so it becomes the next render pass. + bool DeferDepthStencilClear(const Framebuffer* framebuffer, const VkClearValue& value); + + /// Realizes any pending deferred clear before its framebuffer can be moved or freed. + void FlushDeferredClear(); + /// Requests the current execution context to be able to execute operations only allowed outside /// of a renderpass. void RequestOutsideRenderPassOperationContext(); @@ -74,6 +83,9 @@ public: /// Update the rescaling state. Returns true if the state has to be updated. bool UpdateRescaling(bool is_rescaling); + /// Returns true when the descriptor buffer chunk has to be bound into the command buffer. + bool UpdateDescriptorBufferChunk(u32 descriptor_chunk); + /// Invalidates current command buffer state except for render passes void InvalidateState(); @@ -137,9 +149,10 @@ public: frame_counter++; auto target_time = start_time + frame_interval * frame_counter; if (target_time >= now) { + constexpr auto spin_tail = std::chrono::milliseconds(1); auto sleep_time = target_time - now; - if (sleep_time > std::chrono::milliseconds(15)) { - std::this_thread::sleep_for(sleep_time - std::chrono::milliseconds(1)); + if (sleep_time > spin_tail * 2) { + std::this_thread::sleep_for(sleep_time - spin_tail); } while (std::chrono::steady_clock::now() < target_time) { std::this_thread::yield(); @@ -249,8 +262,25 @@ private: bool is_rescaling = false; bool rescaling_defined = false; bool needs_state_enable_refresh = false; + u32 descriptor_buffer_chunk = 0; + bool descriptor_buffer_bound = false; }; + struct DeferredClear { + const Framebuffer* framebuffer = nullptr; + u32 color_clear_mask = 0; + std::array color_values{}; + bool depth_stencil = false; + VkClearValue depth_stencil_value{}; + }; + + /// Begins a render pass for the given framebuffer, optionally with clear values. + void BeginRenderPassImpl(const Framebuffer* framebuffer, VkRenderPass renderpass, + const VkClearValue* clear_values, u32 clear_value_count); + + /// If a deferred clear is pending. + void RealizeDeferredClear(); + void WorkerThread(std::stop_token stop_token); void AllocateWorkerCommandBuffer(); @@ -276,6 +306,8 @@ private: vk::CommandBuffer current_cmdbuf; vk::CommandBuffer current_upload_cmdbuf; + DeferredClear deferred_clear; + std::unique_ptr chunk; std::function on_submit; diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp index 08513d1534..7b6571d33d 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project @@ -27,19 +27,35 @@ using namespace Common::Literals; // Maximum potential alignment of a Vulkan buffer constexpr VkDeviceSize MAX_ALIGNMENT = 256; + // Stream buffer size in bytes +// *NIX drivers are more sensitive to increased buffers for streaming. +// Windows ones however, can intake bigger buffers and generally do not OOM. +// - GTX 960 on Windows will not OOM with 256mib +// - GT 1030 on ^NIX will OOM with 256mib +#if defined(__FreeBSD__) constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB; +#else +constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 256_MiB; +#endif size_t GetStreamBufferSize(const Device& device) { + if (!device.HasDebuggingToolAttached()) { + return MAX_STREAM_BUFFER_SIZE; + } + VkDeviceSize size{0}; - if (device.HasDebuggingToolAttached()) { - ForEachDeviceLocalHostVisibleHeap(device, [&size](size_t index, VkMemoryHeap& heap) { - size = (std::max)(size, heap.size); - }); + bool has_device_local_host_visible_heap{}; + ForEachDeviceLocalHostVisibleHeap(device, [&size, &has_device_local_host_visible_heap]( + size_t index, VkMemoryHeap& heap) { + has_device_local_host_visible_heap = true; + size = (std::max)(size, heap.size); + }); + if (has_device_local_host_visible_heap) { // If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be // loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue // as the heap will be much larger. - if (size <= 256_MiB) { + if (size <= MAX_STREAM_BUFFER_SIZE) { size = size * 40 / 100; } } else { @@ -68,10 +84,16 @@ StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& mem if (device.IsExtTransformFeedbackSupported()) { stream_ci.usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT; } + if (device.IsBufferDeviceAddressSupported()) { + stream_ci.usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + } stream_buffer = memory_allocator.CreateBuffer(stream_ci, MemoryUsage::Stream); if (device.HasDebuggingToolAttached()) { stream_buffer.SetObjectNameEXT("Stream Buffer"); } + if (device.IsBufferDeviceAddressSupported()) { + stream_buffer_address = device.GetLogical().GetBufferDeviceAddress(*stream_buffer); + } stream_pointer = stream_buffer.Mapped(); ASSERT_MSG(!stream_pointer.empty(), "Stream buffer must be host visible!"); } @@ -133,6 +155,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { iterator = Common::AlignUp(iterator + size, MAX_ALIGNMENT); return StagingBufferRef{ .buffer = *stream_buffer, + .device_address = stream_buffer_address, .offset = static_cast(offset), .mapped_span = stream_pointer.subspan(offset, size), .usage{}, @@ -158,7 +181,7 @@ StagingBufferRef StagingBufferPool::GetStagingBuffer(size_t size, MemoryUsage us std::optional StagingBufferPool::TryGetReservedBuffer(size_t size, MemoryUsage usage, bool deferred) { - StagingBuffers& cache_level = GetCache(usage)[Common::Log2Ceil64(size)]; + StagingBuffers& cache_level = GetCache(usage)[Common::Log2Ceil(size)]; const auto is_free = [this](const StagingBuffer& entry) { return !entry.deferred && scheduler.IsFree(entry.tick); @@ -179,14 +202,13 @@ std::optional StagingBufferPool::TryGetReservedBuffer(size_t s return it->Ref(); } -StagingBufferRef StagingBufferPool::CreateStagingBuffer(size_t size, MemoryUsage usage, - bool deferred) { - const u32 log2 = Common::Log2Ceil64(size); +StagingBufferRef StagingBufferPool::CreateStagingBuffer(size_t size, MemoryUsage usage, bool deferred) { + auto const log2_size = Common::Log2Ceil(u32(size)); VkBufferCreateInfo buffer_ci = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .pNext = nullptr, .flags = 0, - .size = 1ULL << log2, + .size = 1ULL << log2_size, .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, @@ -197,17 +219,25 @@ StagingBufferRef StagingBufferPool::CreateStagingBuffer(size_t size, MemoryUsage if (device.IsExtTransformFeedbackSupported()) { buffer_ci.usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT; } + if (device.IsBufferDeviceAddressSupported()) { + buffer_ci.usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + } vk::Buffer buffer = memory_allocator.CreateBuffer(buffer_ci, usage); if (device.HasDebuggingToolAttached()) { ++buffer_index; buffer.SetObjectNameEXT(fmt::format("Staging Buffer {}", buffer_index).c_str()); } const std::span mapped_span = buffer.Mapped(); - StagingBuffer& entry = GetCache(usage)[log2].entries.emplace_back(StagingBuffer{ + const VkDeviceAddress buffer_address = + device.IsBufferDeviceAddressSupported() + ? device.GetLogical().GetBufferDeviceAddress(*buffer) + : VkDeviceAddress{}; + StagingBuffer& entry = GetCache(usage)[log2_size].entries.emplace_back(StagingBuffer{ .buffer = std::move(buffer), + .device_address = buffer_address, .mapped_span = mapped_span, .usage = usage, - .log2_level = log2, + .log2_level = log2_size, .index = unique_ids++, .tick = deferred ? (std::numeric_limits::max)() : scheduler.CurrentTick(), .deferred = deferred, diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h index f63a203272..6d967abb41 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -18,6 +21,7 @@ class Scheduler; struct StagingBufferRef { VkBuffer buffer; + VkDeviceAddress device_address; VkDeviceSize offset; std::span mapped_span; MemoryUsage usage; @@ -50,6 +54,7 @@ private: struct StagingBuffer { vk::Buffer buffer; + VkDeviceAddress device_address; std::span mapped_span; MemoryUsage usage; u32 log2_level; @@ -60,6 +65,7 @@ private: StagingBufferRef Ref() const noexcept { return { .buffer = *buffer, + .device_address = device_address, .offset = 0, .mapped_span = mapped_span, .usage = usage, @@ -103,6 +109,7 @@ private: Scheduler& scheduler; vk::Buffer stream_buffer; + VkDeviceAddress stream_buffer_address{}; std::span stream_pointer; VkDeviceSize stream_buffer_size; VkDeviceSize region_size; diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp index 3f4dd89c7e..0b132c97d6 100644 --- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp +++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp @@ -24,11 +24,8 @@ using namespace Dirty; using namespace VideoCommon::Dirty; using Tegra::Engines::Maxwell3D; using Regs = Maxwell3D::Regs; -using Tables = Maxwell3D::DirtyState::Tables; -using Table = Maxwell3D::DirtyState::Table; -using Flags = Maxwell3D::DirtyState::Flags; -Flags MakeInvalidationFlags() { +Maxwell3D::DirtyState::Flags MakeInvalidationFlags() { static constexpr int INVALIDATION_FLAGS[]{ Viewports, Scissors, @@ -68,7 +65,7 @@ Flags MakeInvalidationFlags() { LineStippleEnable, LineStippleParams, }; - Flags flags{}; + Maxwell3D::DirtyState::Flags flags{}; for (const int flag : INVALIDATION_FLAGS) { flags[flag] = true; } @@ -84,7 +81,7 @@ Flags MakeInvalidationFlags() { return flags; } -void SetupDirtyViewports(Tables& tables) { +void SetupDirtyViewports(Maxwell3D::DirtyState::Tables& tables) { FillBlock(tables[0], OFF(viewport_transform), NUM(viewport_transform), Viewports); FillBlock(tables[0], OFF(viewports), NUM(viewports), Viewports); FillBlock(tables[1], OFF(surface_clip), NUM(surface_clip), Viewports); @@ -92,26 +89,26 @@ void SetupDirtyViewports(Tables& tables) { tables[1][OFF(window_origin)] = Viewports; } -void SetupDirtyScissors(Tables& tables) { +void SetupDirtyScissors(Maxwell3D::DirtyState::Tables& tables) { FillBlock(tables[0], OFF(scissor_test), NUM(scissor_test), Scissors); } -void SetupDirtyDepthBias(Tables& tables) { +void SetupDirtyDepthBias(Maxwell3D::DirtyState::Tables& tables) { auto& table = tables[0]; table[OFF(depth_bias)] = DepthBias; table[OFF(depth_bias_clamp)] = DepthBias; table[OFF(slope_scale_depth_bias)] = DepthBias; } -void SetupDirtyBlendConstants(Tables& tables) { +void SetupDirtyBlendConstants(Maxwell3D::DirtyState::Tables& tables) { FillBlock(tables[0], OFF(blend_color), NUM(blend_color), BlendConstants); } -void SetupDirtyDepthBounds(Tables& tables) { +void SetupDirtyDepthBounds(Maxwell3D::DirtyState::Tables& tables) { FillBlock(tables[0], OFF(depth_bounds), NUM(depth_bounds), DepthBounds); } -void SetupDirtyStencilProperties(Tables& tables) { +void SetupDirtyStencilProperties(Maxwell3D::DirtyState::Tables& tables) { const auto setup = [&](size_t position, u8 flag) { tables[0][position] = flag; tables[1][position] = StencilProperties; @@ -125,18 +122,18 @@ void SetupDirtyStencilProperties(Tables& tables) { setup(OFF(stencil_back_func_mask), StencilCompare); } -void SetupDirtyLineWidth(Tables& tables) { +void SetupDirtyLineWidth(Maxwell3D::DirtyState::Tables& tables) { tables[0][OFF(line_width_smooth)] = LineWidth; tables[0][OFF(line_width_aliased)] = LineWidth; } -void SetupDirtyCullMode(Tables& tables) { +void SetupDirtyCullMode(Maxwell3D::DirtyState::Tables& tables) { auto& table = tables[0]; table[OFF(gl_cull_face)] = CullMode; table[OFF(gl_cull_test_enabled)] = CullMode; } -void SetupDirtyStateEnable(Tables& tables) { +void SetupDirtyStateEnable(Maxwell3D::DirtyState::Tables& tables) { const auto setup = [&](size_t position, u8 flag) { tables[0][position] = flag; tables[1][position] = StateEnable; @@ -157,17 +154,17 @@ void SetupDirtyStateEnable(Tables& tables) { setup(OFF(anti_alias_alpha_control.alpha_to_one), AlphaToOneEnable); } -void SetupDirtyDepthCompareOp(Tables& tables) { +void SetupDirtyDepthCompareOp(Maxwell3D::DirtyState::Tables& tables) { tables[0][OFF(depth_test_func)] = DepthCompareOp; } -void SetupDirtyFrontFace(Tables& tables) { +void SetupDirtyFrontFace(Maxwell3D::DirtyState::Tables& tables) { auto& table = tables[0]; table[OFF(gl_front_face)] = FrontFace; table[OFF(window_origin)] = FrontFace; } -void SetupDirtyStencilOp(Tables& tables) { +void SetupDirtyStencilOp(Maxwell3D::DirtyState::Tables& tables) { auto& table = tables[0]; table[OFF(stencil_front_op.fail)] = StencilOp; table[OFF(stencil_front_op.zfail)] = StencilOp; @@ -182,7 +179,7 @@ void SetupDirtyStencilOp(Tables& tables) { tables[1][OFF(stencil_two_side_enable)] = StencilOp; } -void SetupDirtyBlending(Tables& tables) { +void SetupDirtyBlending(Maxwell3D::DirtyState::Tables& tables) { tables[0][OFF(color_mask_common)] = Blending; tables[1][OFF(color_mask_common)] = ColorMask; tables[0][OFF(blend_per_target_enabled)] = Blending; @@ -196,11 +193,11 @@ void SetupDirtyBlending(Tables& tables) { FillBlock(tables[1], OFF(blend_per_target), NUM(blend_per_target), BlendEquations); } -void SetupDirtySpecialOps(Tables& tables) { +void SetupDirtySpecialOps(Maxwell3D::DirtyState::Tables& tables) { tables[0][OFF(logic_op.op)] = LogicOp; } -void SetupDirtyViewportSwizzles(Tables& tables) { +void SetupDirtyViewportSwizzles(Maxwell3D::DirtyState::Tables& tables) { static constexpr size_t swizzle_offset = 6; for (size_t index = 0; index < Regs::NumViewports; ++index) { tables[1][OFF(viewport_transform) + index * NUM(viewport_transform[0]) + swizzle_offset] = @@ -208,7 +205,7 @@ void SetupDirtyViewportSwizzles(Tables& tables) { } } -void SetupDirtyVertexAttributes(Tables& tables) { +void SetupDirtyVertexAttributes(Maxwell3D::DirtyState::Tables& tables) { for (size_t i = 0; i < Regs::NumVertexAttributes; ++i) { const size_t offset = OFF(vertex_attrib_format) + i * NUM(vertex_attrib_format[0]); FillBlock(tables[0], offset, NUM(vertex_attrib_format[0]), VertexAttribute0 + i); @@ -216,7 +213,7 @@ void SetupDirtyVertexAttributes(Tables& tables) { FillBlock(tables[1], OFF(vertex_attrib_format), Regs::NumVertexAttributes, VertexInput); } -void SetupDirtyVertexBindings(Tables& tables) { +void SetupDirtyVertexBindings(Maxwell3D::DirtyState::Tables& tables) { // Do NOT include stride here, it's implicit in VertexBuffer static constexpr size_t divisor_offset = 3; for (size_t i = 0; i < Regs::NumVertexArrays; ++i) { @@ -228,7 +225,7 @@ void SetupDirtyVertexBindings(Tables& tables) { } } -void SetupRasterModes(Tables &tables) { +void SetupRasterModes(Maxwell3D::DirtyState::Tables &tables) { auto& table = tables[0]; table[OFF(line_stipple_params)] = LineStippleParams; @@ -238,7 +235,7 @@ void SetupRasterModes(Tables &tables) { } // Anonymous namespace void StateTracker::SetupTables(Tegra::Control::ChannelState& channel_state) { - auto& tables{channel_state.maxwell_3d->dirty.tables}; + auto& tables{channel_state.payload->maxwell_3d.dirty.tables}; SetupDirtyFlags(tables); SetupDirtyViewports(tables); SetupDirtyScissors(tables); @@ -261,7 +258,7 @@ void StateTracker::SetupTables(Tegra::Control::ChannelState& channel_state) { } void StateTracker::ChangeChannel(Tegra::Control::ChannelState& channel_state) { - flags = &channel_state.maxwell_3d->dirty.flags; + flags = &channel_state.payload->maxwell_3d.dirty.flags; } void StateTracker::InvalidateState() { diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 7e31f9cf67..fea5398557 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -48,6 +48,9 @@ static VkPresentModeKHR ChooseSwapPresentMode(bool has_imm, bool has_mailbox, Settings::VSyncMode setting = [has_imm, has_mailbox]() { // Choose Mailbox or Immediate if unlocked and those modes are supported const auto mode = Settings::values.vsync_mode.GetValue(); + if (Settings::values.frame_gen.GetValue()) { + return mode == Settings::VSyncMode::FifoRelaxed ? mode : Settings::VSyncMode::Fifo; + } if (Settings::values.use_speed_limit.GetValue() && Settings::values.current_speed_mode.GetValue() != Settings::SpeedMode::Turbo) { return mode; @@ -288,7 +291,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) { .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, .pQueueFamilyIndices = nullptr, -#ifdef ANDROID +#ifdef __ANDROID__ // On Android, do not allow surface rotation to deviate from the frontend. .preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR, #else @@ -313,7 +316,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) { swapchain_ci.imageFormat, // Base format MUST be first VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SRGB, -#ifdef ANDROID +#ifdef __ANDROID__ VK_FORMAT_R8G8B8A8_UNORM, // Android may use RGBA VK_FORMAT_R8G8B8A8_SRGB, #endif @@ -338,7 +341,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) { images = swapchain.GetImages(); image_count = static_cast(images.size()); -#ifdef ANDROID +#ifdef __ANDROID__ // Android is already ordered the same as Switch. image_view_format = VK_FORMAT_R8G8B8A8_UNORM; #else diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index f099db74cb..583cb6edaf 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -5,9 +5,12 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include +#include #include +#include #include #include +#include #include #include #include @@ -48,9 +51,26 @@ using VideoCore::Surface::BytesPerBlock; using VideoCore::Surface::HasAlpha; using VideoCore::Surface::IsPixelFormatASTC; using VideoCore::Surface::IsPixelFormatInteger; +using VideoCore::Surface::IsPixelFormatSRGB; using VideoCore::Surface::SurfaceType; namespace { +constexpr bool ENABLE_MSAA_TILER_RESOLVE = true; +constexpr bool ENABLE_MSAA_RESOLVE_CONSUME = true; +constexpr bool ENABLE_MSAA_COLOR_DISCARD = true; +constexpr bool ENABLE_MSAA_DEPTH_STENCIL_DISCARD = true; + +[[nodiscard]] constexpr bool NeedsExplicitBorderColorFormat(VkFormat format) { + switch (format) { + case VK_FORMAT_B4G4R4A4_UNORM_PACK16: + case VK_FORMAT_B5G6R5_UNORM_PACK16: + case VK_FORMAT_B5G5R5A1_UNORM_PACK16: + return true; + default: + return false; + } +} + constexpr VkBorderColor ConvertBorderColor(const std::array& color) { if (color == std::array{0, 0, 0, 0}) { return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; @@ -104,7 +124,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { } [[nodiscard]] VkImageUsageFlags ImageUsageFlags(const MaxwellToVK::FormatInfo& info, - PixelFormat format) { + PixelFormat format, bool allow_storage = true) { VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; if (info.attachable) { @@ -122,15 +142,33 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { break; } } - if (info.storage) { + if (info.storage && allow_storage) { usage |= VK_IMAGE_USAGE_STORAGE_BIT; } return usage; } -[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) { - const auto format_info = +[[nodiscard]] bool WillUseAcceleratedAstcDecode(const Device& device, const ImageInfo& info) { + if (!IsPixelFormatASTC(info.format) || device.IsOptimalAstcSupported()) { + return false; + } + if (Settings::values.accelerate_astc.GetValue() != Settings::AstcDecodeMode::Gpu) { + return false; + } + return Settings::values.astc_recompression.GetValue() == + Settings::AstcRecompression::Uncompressed && + info.size.depth == 1; +} + +[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info, + std::optional format_override = {}) { + auto format_info = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, info.format); + if (format_override) { + format_info.format = *format_override; + format_info.attachable = false; + format_info.storage = true; + } VkImageCreateFlags flags{}; if (info.type == ImageType::e2D && info.resources.layers >= 6 && info.size.width == info.size.height && !device.HasBrokenCubeImageCompatibility()) { @@ -140,6 +178,8 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; } const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(info.num_samples); + const bool allow_storage = + info.num_samples == 1 || device.IsStorageImageMultisampleSupported(); return VkImageCreateInfo{ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .pNext = nullptr, @@ -155,7 +195,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { .arrayLayers = static_cast(info.resources.layers), .samples = ConvertSampleCount(info.num_samples), .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = ImageUsageFlags(format_info, info.format), + .usage = ImageUsageFlags(format_info, info.format, allow_storage), .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, .pQueueFamilyIndices = nullptr, @@ -163,12 +203,25 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { }; } +[[nodiscard]] VkImageCreateInfo MakeMsaaScratchImageCreateInfo(const Device& device, + const ImageInfo& info, + VkImageUsageFlags usage) { + ImageInfo temp_info = info; + temp_info.num_samples = 1; + VkImageCreateInfo image_ci = MakeImageCreateInfo(device, temp_info); + image_ci.format = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, info.format).format; + image_ci.usage = usage; + return image_ci; +} + [[nodiscard]] vk::Image MakeImage(const Device& device, const MemoryAllocator& allocator, - const ImageInfo& info, std::span view_formats) { + const ImageInfo& info, std::span view_formats, + std::optional format_override = {}) { if (info.type == ImageType::Buffer) { return vk::Image{}; } - VkImageCreateInfo image_ci = MakeImageCreateInfo(device, info); + VkImageCreateInfo image_ci = MakeImageCreateInfo(device, info, format_override); const VkImageFormatListCreateInfo image_format_list = { .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, .pNext = nullptr, @@ -241,6 +294,10 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { } } +[[nodiscard]] bool IsLdrAstcFormat(VkFormat format) { + return format >= VK_FORMAT_ASTC_4x4_UNORM_BLOCK && format <= VK_FORMAT_ASTC_12x12_SRGB_BLOCK; +} + [[nodiscard]] VkImageAspectFlags ImageViewAspectMask(const VideoCommon::ImageViewInfo& info) { if (info.IsRenderTarget()) { return ImageAspectMask(info.format); @@ -650,6 +707,11 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im }; } +[[nodiscard]] bool HaveSameExtent(const Region2D& dst_region, const Region2D& src_region) { + return dst_region.end.x - dst_region.start.x == src_region.end.x - src_region.start.x && + dst_region.end.y - dst_region.start.y == src_region.end.y - src_region.start.y; +} + [[nodiscard]] VkImageResolve MakeImageResolve(const Region2D& dst_region, const Region2D& src_region, const VkImageSubresourceLayers& dst_layers, @@ -747,15 +809,15 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([dst_image, src_image, extent, resources, aspect_mask, resolution, is_2d, vk_filter, up_scaling](vk::CommandBuffer cmdbuf) { + const u32 scaled_width = resolution.ScaleUp(extent.width); + const u32 scaled_height = is_2d ? resolution.ScaleUp(extent.height) : extent.height; const VkOffset2D src_size{ - .x = static_cast(up_scaling ? extent.width : resolution.ScaleUp(extent.width)), - .y = static_cast(is_2d && up_scaling ? extent.height - : resolution.ScaleUp(extent.height)), + .x = static_cast(up_scaling ? extent.width : scaled_width), + .y = static_cast(up_scaling ? extent.height : scaled_height), }; const VkOffset2D dst_size{ - .x = static_cast(up_scaling ? resolution.ScaleUp(extent.width) : extent.width), - .y = static_cast(is_2d && up_scaling ? resolution.ScaleUp(extent.height) - : extent.height), + .x = static_cast(up_scaling ? scaled_width : extent.width), + .y = static_cast(up_scaling ? scaled_height : extent.height), }; boost::container::small_vector regions; regions.reserve(resources.levels); @@ -860,12 +922,12 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const .subresourceRange = subresource_range, }, }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, nullptr, nullptr, read_barriers); + cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, nullptr, nullptr, read_barriers); cmdbuf.BlitImage(src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, regions, vk_filter); - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - 0, nullptr, nullptr, write_barriers); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, + 0, nullptr, nullptr, write_barriers); }); } } // Anonymous namespace @@ -884,9 +946,6 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue, memory_allocator); } - if (device.IsStorageImageMultisampleSupported()) { - msaa_copy_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue); - } if (!device.IsKhrImageFormatListSupported()) { return; } @@ -909,40 +968,6 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched bl3d_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, compute_pass_descriptor_queue); } - - // --- Create swizzle table buffer --- - { - auto table = Tegra::Texture::MakeSwizzleTable(); - - swizzle_table_size = static_cast(table.size() * sizeof(table[0])); - - auto staging = staging_buffer_pool.Request(swizzle_table_size, MemoryUsage::Upload); - std::memcpy(staging.mapped_span.data(), table.data(), static_cast(swizzle_table_size)); - - VkBufferCreateInfo ci{ - .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, - .size = swizzle_table_size, - .usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | - VK_BUFFER_USAGE_TRANSFER_DST_BIT | - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - .sharingMode = VK_SHARING_MODE_EXCLUSIVE, - }; - swizzle_table_buffer = memory_allocator.CreateBuffer(ci, MemoryUsage::DeviceLocal); - - scheduler.RequestOutsideRenderPassOperationContext(); - scheduler.Record([staging_buf = staging.buffer, - dst_buf = *swizzle_table_buffer, - size = swizzle_table_size, - src_off = staging.offset](vk::CommandBuffer cmdbuf) { - - const VkBufferCopy region{ - .srcOffset = src_off, - .dstOffset = 0, - .size = size, - }; - cmdbuf.CopyBuffer(staging_buf, dst_buf, region); - }); - } } void TextureCacheRuntime::Finish() { @@ -1004,12 +1029,109 @@ VkBuffer TextureCacheRuntime::GetTemporaryBuffer(size_t needed_size) { return *buffers[level]; } +VkImageView TextureCacheRuntime::GetOrCreateResolveShadow(VkImage msaa_image, VkFormat format, + VkExtent2D extent, u32 layers, + VkImageAspectFlags aspect_mask) { + ResolveShadow& shadow = resolve_shadows[msaa_image]; + if (shadow.image && shadow.format == format && shadow.extent.width == extent.width && + shadow.extent.height == extent.height && shadow.layers == layers && + shadow.aspect_mask == aspect_mask) { + return *shadow.view; + } + VkImageUsageFlags shadow_usage = + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) { + shadow_usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + } else { + shadow_usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + } + if (shadow.image) { + pending_resolve_shadows.emplace_back(scheduler.CurrentTick(), std::move(shadow)); + } + shadow.image = memory_allocator.CreateImage(VkImageCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .imageType = VK_IMAGE_TYPE_2D, + .format = format, + .extent = {extent.width, extent.height, 1}, + .mipLevels = 1, + .arrayLayers = layers, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = shadow_usage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }); + shadow.view = device.GetLogical().CreateImageView(VkImageViewCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = *shadow.image, + .viewType = layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D, + .format = format, + .components{}, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = layers, + }, + }); + shadow.format = format; + shadow.extent = extent; + shadow.layers = layers; + shadow.aspect_mask = aspect_mask; + shadow.up_to_date = false; + return *shadow.view; +} + +const TextureCacheRuntime::ResolveShadow* TextureCacheRuntime::GetValidResolveShadow( + VkImage msaa_image) const { + const auto it = resolve_shadows.find(msaa_image); + if (it == resolve_shadows.end() || !it->second.up_to_date || !it->second.image) { + return nullptr; + } + return &it->second; +} + +void TextureCacheRuntime::MarkResolveShadowUpToDate(VkImage msaa_image) { + const auto it = resolve_shadows.find(msaa_image); + if (it != resolve_shadows.end()) { + it->second.up_to_date = true; + } +} + +void TextureCacheRuntime::InvalidateResolveShadow(VkImage msaa_image) { + const auto it = resolve_shadows.find(msaa_image); + if (it != resolve_shadows.end()) { + it->second.up_to_date = false; + } +} + +void TextureCacheRuntime::EraseResolveShadow(VkImage msaa_image) { + const auto it = resolve_shadows.find(msaa_image); + if (it == resolve_shadows.end()) { + return; + } + if (it->second.image) { + pending_resolve_shadows.emplace_back(scheduler.CurrentTick(), std::move(it->second)); + } + resolve_shadows.erase(it); +} + void TextureCacheRuntime::BarrierFeedbackLoop() { scheduler.RequestOutsideRenderPassOperationContext(); } void TextureCacheRuntime::ReinterpretImage(Image& dst, Image& src, std::span copies) { + if (ENABLE_MSAA_RESOLVE_CONSUME) { + InvalidateResolveShadow(dst.Handle()); + } boost::container::small_vector vk_in_copies(copies.size()); boost::container::small_vector vk_out_copies(copies.size()); const VkImageAspectFlags src_aspect_mask = src.AspectMask(); @@ -1123,19 +1245,19 @@ void TextureCacheRuntime::ReinterpretImage(Image& dst, Image& src, .subresourceRange = dst_range.SubresourceRange(dst_aspect_mask), }, }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, {}, {}, pre_barriers); + cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, {}, {}, pre_barriers); cmdbuf.CopyImageToBuffer(src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, copy_buffer, vk_in_copies); - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - 0, WRITE_BARRIER, nullptr, middle_in_barrier); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, + 0, WRITE_BARRIER, nullptr, middle_in_barrier); - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, READ_BARRIER, {}, middle_out_barrier); + cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, READ_BARRIER, {}, middle_out_barrier); cmdbuf.CopyBufferToImage(copy_buffer, dst_image, VK_IMAGE_LAYOUT_GENERAL, vk_out_copies); - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - 0, {}, {}, post_barriers); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, + 0, {}, {}, post_barriers); }); } @@ -1156,6 +1278,15 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst return; } ASSERT(src.format == dst.format); + if (is_src_msaa && !is_dst_msaa && + (aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) { + if ((aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) == 0) { + UNIMPLEMENTED_MSG("Stencil-only MSAA resolve is not supported"); + return; + } + blit_image_helper.ResolveDepthStencil(dst_framebuffer, src, dst_region, src_region); + return; + } if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { const auto format = src.format; const auto can_blit_depth_stencil = [this, format] { @@ -1181,20 +1312,26 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst ASSERT(!(is_dst_msaa && !is_src_msaa)); ASSERT(operation == Fermi2D::Operation::SrcCopy); + const bool is_msaa_to_msaa = is_src_msaa && is_dst_msaa; + if (is_msaa_to_msaa && aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT) { + blit_image_helper.BlitColorMSAA(dst_framebuffer, src, dst_region, src_region); + return; + } + if (is_msaa_to_msaa) { + blit_image_helper.BlitDepthStencilMSAA(dst_framebuffer, src, dst_region, src_region); + return; + } + + const bool is_resolve = is_src_msaa && !is_dst_msaa; + if (is_resolve && !HaveSameExtent(dst_region, src_region)) { + blit_image_helper.BlitColorMSAA(dst_framebuffer, src, dst_region, src_region); + return; + } + const VkImage dst_image = dst.ImageHandle(); const VkImage src_image = src.ImageHandle(); const VkImageSubresourceLayers dst_layers = MakeSubresourceLayers(&dst); const VkImageSubresourceLayers src_layers = MakeSubresourceLayers(&src); - const bool is_msaa_to_msaa = is_src_msaa && is_dst_msaa; - - // NVIDIA 510+ and Intel crash on MSAA->MSAA blits (scaling operations) - // Fall back to 3D helpers for MSAA scaling - if (is_msaa_to_msaa && device.CantBlitMSAA()) { - // This should be handled by NeedsScaleHelper() and use 3D helpers instead - UNIMPLEMENTED_MSG("MSAA to MSAA blit not supported on this driver"); - return; - } - const bool is_resolve = is_src_msaa && !is_dst_msaa; scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([filter, dst_region, src_region, dst_image, src_image, dst_layers, src_layers, aspect_mask, is_resolve](vk::CommandBuffer cmdbuf) { @@ -1261,8 +1398,8 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, nullptr, nullptr, read_barriers); + cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, nullptr, nullptr, read_barriers); if (is_resolve) { cmdbuf.ResolveImage(src_image, VK_IMAGE_LAYOUT_GENERAL, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, @@ -1274,8 +1411,8 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst src_image, VK_IMAGE_LAYOUT_GENERAL, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, MakeImageBlit(dst_region, src_region, dst_layers, src_layers), vk_filter); } - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - 0, write_barrier); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, + 0, write_barrier); }); } @@ -1285,131 +1422,83 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im } switch (dst_view.format) { - case PixelFormat::D24_UNORM_S8_UINT: - if (src_view.format == PixelFormat::A8B8G8R8_UNORM - || src_view.format == PixelFormat::B8G8R8A8_UNORM - || src_view.format == PixelFormat::A8B8G8R8_SRGB - || src_view.format == PixelFormat::B8G8R8A8_SRGB) { - return blit_image_helper.ConvertABGR8ToD24S8(dst, src_view); + case PixelFormat::R16_UNORM: + if (src_view.format == PixelFormat::D16_UNORM) { + return blit_image_helper.ConvertD16ToR16(dst, src_view); + } + break; + case PixelFormat::A8B8G8R8_SRGB: + case PixelFormat::B8G8R8A8_SRGB: + case PixelFormat::B8G8R8A8_UNORM: + if (src_view.format == PixelFormat::D32_FLOAT) { + return blit_image_helper.ConvertD32FToABGR8(dst, src_view); } break; case PixelFormat::A8B8G8R8_UNORM: - case PixelFormat::A8B8G8R8_SNORM: - case PixelFormat::A8B8G8R8_SINT: - case PixelFormat::A8B8G8R8_UINT: - case PixelFormat::R5G6B5_UNORM: - case PixelFormat::B5G6R5_UNORM: - case PixelFormat::A1R5G5B5_UNORM: - case PixelFormat::A2B10G10R10_UNORM: - case PixelFormat::A2B10G10R10_UINT: - case PixelFormat::A2R10G10B10_UNORM: - case PixelFormat::A1B5G5R5_UNORM: - case PixelFormat::A5B5G5R1_UNORM: - case PixelFormat::R8_UNORM: - case PixelFormat::R8_SNORM: - case PixelFormat::R8_SINT: - case PixelFormat::R8_UINT: - case PixelFormat::R16G16B16A16_FLOAT: - case PixelFormat::R16G16B16A16_UNORM: - case PixelFormat::R16G16B16A16_SNORM: - case PixelFormat::R16G16B16A16_SINT: - case PixelFormat::R16G16B16A16_UINT: - case PixelFormat::B10G11R11_FLOAT: - case PixelFormat::R32G32B32A32_UINT: - case PixelFormat::BC1_RGBA_UNORM: - case PixelFormat::BC2_UNORM: - case PixelFormat::BC3_UNORM: - case PixelFormat::BC4_UNORM: - case PixelFormat::BC4_SNORM: - case PixelFormat::BC5_UNORM: - case PixelFormat::BC5_SNORM: - case PixelFormat::BC7_UNORM: - case PixelFormat::BC6H_UFLOAT: - case PixelFormat::BC6H_SFLOAT: - case PixelFormat::ASTC_2D_4X4_UNORM: - case PixelFormat::B8G8R8A8_UNORM: - case PixelFormat::R32G32B32A32_FLOAT: - case PixelFormat::R32G32B32A32_SINT: - case PixelFormat::R32G32_FLOAT: - case PixelFormat::R32G32_SINT: - case PixelFormat::R32_FLOAT: - if ((src_view.format == PixelFormat::D32_FLOAT) && Settings::values.fix_bloom_effects.GetValue()) { - const Region2D region{ - .start = {0, 0}, - .end = {static_cast(dst->RenderArea().width), - static_cast(dst->RenderArea().height)}, - }; - return blit_image_helper.BlitColor(dst, src_view, region, region, - Tegra::Engines::Fermi2D::Filter::Point, - Tegra::Engines::Fermi2D::Operation::SrcCopy); + if (src_view.format == PixelFormat::S8_UINT_D24_UNORM) { + return blit_image_helper.ConvertD24S8ToABGR8(dst, src_view); + } + if (src_view.format == PixelFormat::D24_UNORM_S8_UINT) { + return blit_image_helper.ConvertS8D24ToABGR8(dst, src_view); + } + if (src_view.format == PixelFormat::D32_FLOAT) { + return blit_image_helper.ConvertD32FToABGR8(dst, src_view); + } + break; + case PixelFormat::R32_FLOAT: + if (src_view.format == PixelFormat::D32_FLOAT) { + return blit_image_helper.ConvertD32ToR32(dst, src_view); } break; - case PixelFormat::R16_FLOAT: - case PixelFormat::R16_UNORM: - case PixelFormat::R16_SNORM: - case PixelFormat::R16_UINT: - case PixelFormat::R16_SINT: - case PixelFormat::R16G16_UNORM: - case PixelFormat::R16G16_FLOAT: - case PixelFormat::R16G16_UINT: - case PixelFormat::R16G16_SINT: - case PixelFormat::R16G16_SNORM: - case PixelFormat::R32G32B32_FLOAT: - case PixelFormat::A8B8G8R8_SRGB: - case PixelFormat::R8G8_UNORM: - case PixelFormat::R8G8_SNORM: - case PixelFormat::R8G8_SINT: - case PixelFormat::R8G8_UINT: - case PixelFormat::R32G32_UINT: - case PixelFormat::R16G16B16X16_FLOAT: - case PixelFormat::R32_UINT: - case PixelFormat::R32_SINT: - case PixelFormat::ASTC_2D_8X8_UNORM: - case PixelFormat::ASTC_2D_8X5_UNORM: - case PixelFormat::ASTC_2D_5X4_UNORM: - case PixelFormat::B8G8R8A8_SRGB: - case PixelFormat::BC1_RGBA_SRGB: - case PixelFormat::BC2_SRGB: - case PixelFormat::BC3_SRGB: - case PixelFormat::BC7_SRGB: - case PixelFormat::A4B4G4R4_UNORM: - case PixelFormat::G4R4_UNORM: - case PixelFormat::ASTC_2D_4X4_SRGB: - case PixelFormat::ASTC_2D_8X8_SRGB: - case PixelFormat::ASTC_2D_8X5_SRGB: - case PixelFormat::ASTC_2D_5X4_SRGB: - case PixelFormat::ASTC_2D_5X5_UNORM: - case PixelFormat::ASTC_2D_5X5_SRGB: - case PixelFormat::ASTC_2D_10X8_UNORM: - case PixelFormat::ASTC_2D_10X8_SRGB: - case PixelFormat::ASTC_2D_6X6_UNORM: - case PixelFormat::ASTC_2D_6X6_SRGB: - case PixelFormat::ASTC_2D_10X6_UNORM: - case PixelFormat::ASTC_2D_10X6_SRGB: - case PixelFormat::ASTC_2D_10X5_UNORM: - case PixelFormat::ASTC_2D_10X5_SRGB: - case PixelFormat::ASTC_2D_10X10_UNORM: - case PixelFormat::ASTC_2D_10X10_SRGB: - case PixelFormat::ASTC_2D_12X10_UNORM: - case PixelFormat::ASTC_2D_12X10_SRGB: - case PixelFormat::ASTC_2D_12X12_UNORM: - case PixelFormat::ASTC_2D_12X12_SRGB: - case PixelFormat::ASTC_2D_8X6_UNORM: - case PixelFormat::ASTC_2D_8X6_SRGB: - case PixelFormat::ASTC_2D_6X5_UNORM: - case PixelFormat::ASTC_2D_6X5_SRGB: - case PixelFormat::E5B9G9R9_FLOAT: - case PixelFormat::D32_FLOAT: case PixelFormat::D16_UNORM: - case PixelFormat::X8_D24_UNORM: - case PixelFormat::S8_UINT: + if (src_view.format == PixelFormat::R16_UNORM) { + return blit_image_helper.ConvertR16ToD16(dst, src_view); + } + break; case PixelFormat::S8_UINT_D24_UNORM: - case PixelFormat::D32_FLOAT_S8_UINT: - case PixelFormat::Invalid: + if (src_view.format == PixelFormat::A8B8G8R8_UNORM || + src_view.format == PixelFormat::B8G8R8A8_UNORM) { + return blit_image_helper.ConvertABGR8ToD24S8(dst, src_view); + } + break; + case PixelFormat::D32_FLOAT: + if (src_view.format == PixelFormat::A8B8G8R8_UNORM || + src_view.format == PixelFormat::B8G8R8A8_UNORM || + src_view.format == PixelFormat::A8B8G8R8_SRGB || + src_view.format == PixelFormat::B8G8R8A8_SRGB) { + return blit_image_helper.ConvertABGR8ToD32F(dst, src_view); + } + if (src_view.format == PixelFormat::R32_FLOAT) { + return blit_image_helper.ConvertR32ToD32(dst, src_view); + } + break; + case PixelFormat::D24_UNORM_S8_UINT: + if (src_view.format == PixelFormat::A8B8G8R8_UNORM || + src_view.format == PixelFormat::B8G8R8A8_UNORM || + src_view.format == PixelFormat::A8B8G8R8_SRGB || + src_view.format == PixelFormat::B8G8R8A8_SRGB) { + return blit_image_helper.ConvertABGR8ToD24S8(dst, src_view); + } + break; default: - LOG_DEBUG(Render_Vulkan, "Unimplemented texture conversion from {} to {} format type", src_view.format, dst_view.format); break; } + + if (src_view.format == PixelFormat::D32_FLOAT && + VideoCore::Surface::GetFormatType(dst_view.format) == SurfaceType::ColorTexture && + (dst_view.format == PixelFormat::B5G6R5_UNORM || + Settings::values.fix_bloom_effects.GetValue())) { + const Region2D region{ + .start = {0, 0}, + .end = {static_cast(dst->RenderArea().width), + static_cast(dst->RenderArea().height)}, + }; + return blit_image_helper.BlitColor(dst, src_view, region, region, + Tegra::Engines::Fermi2D::Filter::Point, + Tegra::Engines::Fermi2D::Operation::SrcCopy); + } + + LOG_DEBUG(Render_Vulkan, "Unimplemented texture conversion from {} to {} format type", src_view.format, dst_view.format); } VkFormat TextureCacheRuntime::GetSupportedFormat(VkFormat requested_format, @@ -1449,6 +1538,9 @@ bool TextureCacheRuntime::IsFormatScalable(PixelFormat format) { void TextureCacheRuntime::CopyImage(Image& dst, Image& src, std::span copies) { + if (ENABLE_MSAA_RESOLVE_CONSUME) { + InvalidateResolveShadow(dst.Handle()); + } // As per the size-compatible formats section of vulkan, copy manually via ReinterpretImage // these images that aren't size-compatible if (BytesPerBlock(src.info.format) != BytesPerBlock(dst.info.format)) { @@ -1550,7 +1642,7 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, nullptr, nullptr, pre_barriers); cmdbuf.CopyImage(src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VideoCommon::FixSmallVectorADL(vk_copies)); + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, vk_copies); cmdbuf.PipelineBarrier( VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | @@ -1562,10 +1654,138 @@ void TextureCacheRuntime::CopyImage(Image& dst, Image& src, void TextureCacheRuntime::CopyImageMSAA(Image& dst, Image& src, std::span copies) { const bool msaa_to_non_msaa = src.info.num_samples > 1 && dst.info.num_samples == 1; - if (msaa_copy_pass) { - return msaa_copy_pass->CopyImage(dst, src, copies, msaa_to_non_msaa); + const u32 num_samples = msaa_to_non_msaa ? src.info.num_samples : dst.info.num_samples; + if (ENABLE_MSAA_RESOLVE_CONSUME && msaa_to_non_msaa && copies.size() == 1 && + src.info.format == dst.info.format) { + const VideoCommon::ImageCopy& copy = copies.front(); + const ResolveShadow* const shadow = GetValidResolveShadow(src.Handle()); + if (shadow != nullptr && shadow->aspect_mask == dst.AspectMask() && + copy.src_offset.x == 0 && copy.src_offset.y == 0 && + copy.src_subresource.base_level == 0 && + static_cast(copy.extent.width) <= shadow->extent.width && + static_cast(copy.extent.height) <= shadow->extent.height && + static_cast(copy.src_subresource.base_layer + copy.src_subresource.num_layers) <= + shadow->layers) { + const VkImageAspectFlags aspect_mask = shadow->aspect_mask; + VkPipelineStageFlags attachment_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + VkAccessFlags attachment_write = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + VkAccessFlags attachment_read_write = + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + if ((aspect_mask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) { + attachment_stage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + attachment_write = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + attachment_read_write = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + } + const VkImage shadow_image = *shadow->image; + const VkImage dst_image = dst.Handle(); + const VkImageCopy region{ + .srcSubresource{ + .aspectMask = aspect_mask, + .mipLevel = 0, + .baseArrayLayer = static_cast(copy.src_subresource.base_layer), + .layerCount = static_cast(copy.src_subresource.num_layers), + }, + .srcOffset = {0, 0, 0}, + .dstSubresource{ + .aspectMask = aspect_mask, + .mipLevel = static_cast(copy.dst_subresource.base_level), + .baseArrayLayer = static_cast(copy.dst_subresource.base_layer), + .layerCount = static_cast(copy.dst_subresource.num_layers), + }, + .dstOffset = {copy.dst_offset.x, copy.dst_offset.y, copy.dst_offset.z}, + .extent = {copy.extent.width, copy.extent.height, 1}, + }; + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([shadow_image, dst_image, region, aspect_mask, attachment_stage, + attachment_write, + attachment_read_write](vk::CommandBuffer cmdbuf) { + const std::array pre_barriers{ + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = attachment_write, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = shadow_image, + .subresourceRange{aspect_mask, 0, VK_REMAINING_MIP_LEVELS, 0, + VK_REMAINING_ARRAY_LAYERS}, + }, + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | attachment_write | + VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = dst_image, + .subresourceRange{aspect_mask, 0, VK_REMAINING_MIP_LEVELS, 0, + VK_REMAINING_ARRAY_LAYERS}, + }, + }; + const std::array post_barriers{ + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = 0, + .dstAccessMask = 0, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = shadow_image, + .subresourceRange{aspect_mask, 0, VK_REMAINING_MIP_LEVELS, 0, + VK_REMAINING_ARRAY_LAYERS}, + }, + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | attachment_read_write | + VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = dst_image, + .subresourceRange{aspect_mask, 0, VK_REMAINING_MIP_LEVELS, 0, + VK_REMAINING_ARRAY_LAYERS}, + }, + }; + cmdbuf.PipelineBarrier(attachment_stage | VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, nullptr, nullptr, + pre_barriers); + cmdbuf.CopyImage(shadow_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, region); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, nullptr, nullptr, + post_barriers); + }); + return; + } } - UNIMPLEMENTED_MSG("Copying images with different samples is not supported."); + const VkImageAspectFlags dst_aspect_mask = dst.AspectMask(); + if ((dst_aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0) { + const bool copies_stencil = (dst_aspect_mask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0 && + device.IsExtShaderStencilExportSupported(); + blit_image_helper.CopyMSAADepth(render_pass_cache, dst.Handle(), dst.info.format, + src.Handle(), src.info.format, num_samples, copies, + copies_stencil, msaa_to_non_msaa); + return; + } + if ((dst_aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) == 0) { + UNIMPLEMENTED_MSG("Copying images with different samples is not supported."); + return; + } + blit_image_helper.CopyMSAA(render_pass_cache, dst.Handle(), dst.info.format, src.Handle(), + src.info.format, num_samples, copies, msaa_to_non_msaa); } u64 TextureCacheRuntime::GetDeviceLocalMemory() const { @@ -1576,28 +1796,93 @@ u64 TextureCacheRuntime::GetDeviceMemoryUsage() const { return device.GetDeviceMemoryUsage(); } +bool TextureCacheRuntime::CanDownloadMsaa(const VideoCommon::ImageInfo& info) const { + const VkImageAspectFlags aspect_mask = ImageAspectMask(info.format); + if ((aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0) { + return true; + } + if ((aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) == 0) { + return false; + } + if ((aspect_mask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0) { + return device.IsExtShaderStencilExportSupported(); + } + return true; +} + bool TextureCacheRuntime::CanReportMemoryUsage() const { return device.CanReportMemoryUsage(); } -std::optional TextureCacheRuntime::GetSamplerHeapBudget() const { - return device.GetSamplerHeapBudget(); +void TextureCacheRuntime::FlushDeferredClear() { + scheduler.FlushDeferredClear(); } -void TextureCacheRuntime::TickFrame() {} +VkImage TextureCacheRuntime::AcquireMsaaScratchImage(const VkImageCreateInfo& image_ci) { + const MsaaScratchKey key{ + .format = image_ci.format, + .type = image_ci.imageType, + .width = image_ci.extent.width, + .height = image_ci.extent.height, + .depth = image_ci.extent.depth, + .levels = image_ci.mipLevels, + .layers = image_ci.arrayLayers, + .usage = image_ci.usage, + .flags = image_ci.flags, + }; + for (MsaaScratchImage& scratch : msaa_scratch_images) { + if (scratch.key != key || !scheduler.IsFree(scratch.tick)) { + continue; + } + scratch.tick = (std::numeric_limits::max)(); + scratch.unused_frames = 0; + return *scratch.image; + } + MsaaScratchImage scratch{ + .key = key, + .image = memory_allocator.CreateImage(image_ci), + .tick = (std::numeric_limits::max)(), + .unused_frames = 0, + }; + const VkImage handle = *scratch.image; + msaa_scratch_images.push_back(std::move(scratch)); + return handle; +} + +void TextureCacheRuntime::ReleaseMsaaScratchImage(VkImage image) { + for (MsaaScratchImage& scratch : msaa_scratch_images) { + if (*scratch.image == image) { + scratch.tick = scheduler.CurrentTick(); + return; + } + } +} + +void TextureCacheRuntime::TickFrame() { + static constexpr u32 MAX_UNUSED_SCRATCH_FRAMES = 60; + std::erase_if(msaa_scratch_images, [this](MsaaScratchImage& scratch) { + if (!scheduler.IsFree(scratch.tick)) { + scratch.unused_frames = 0; + return false; + } + return ++scratch.unused_frames > MAX_UNUSED_SCRATCH_FRAMES; + }); + std::erase_if(pending_resolve_shadows, [this](const auto& pending) { + return scheduler.IsFree(pending.first); + }); +} Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_, VAddr cpu_addr_) : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler}, - runtime{&runtime_}, original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info, - runtime->ViewFormats(info.format))), + runtime{&runtime_}, + original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info, + runtime->ViewFormats(info.format))), aspect_mask(ImageAspectMask(info.format)) { if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) { switch (Settings::values.accelerate_astc.GetValue()) { case Settings::AstcDecodeMode::Gpu: - if (Settings::values.astc_recompression.GetValue() == - Settings::AstcRecompression::Uncompressed && - info.size.depth == 1) { + if (WillUseAcceleratedAstcDecode(runtime->device, info)) { flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; } break; @@ -1619,25 +1904,30 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu } current_image = &Image::original_image; storage_image_views.resize(info.resources.levels); - if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported() && - Settings::values.astc_recompression.GetValue() == - Settings::AstcRecompression::Uncompressed) { + if (WillUseAcceleratedAstcDecode(runtime->device, info)) { const auto& device = runtime->device.GetLogical(); + const VkFormat storage_format = VK_FORMAT_A8B8G8R8_UNORM_PACK32; for (s32 level = 0; level < info.resources.levels; ++level) { storage_image_views[level] = - MakeStorageView(device, level, *original_image, VK_FORMAT_A8B8G8R8_UNORM_PACK32); + MakeStorageView(device, level, *original_image, storage_format); } } } Image::Image(const VideoCommon::NullImageParams& params) : VideoCommon::ImageBase{params} {} -Image::~Image() = default; +Image::~Image() { + if (ENABLE_MSAA_RESOLVE_CONSUME && runtime != nullptr) { + if (original_image) { + runtime->EraseResolveShadow(*original_image); + } + if (scaled_image) { + runtime->EraseResolveShadow(*scaled_image); + } + } +} void Image::AllocateComputeUnswizzleBuffer(u32 max_slices) { - if (has_compute_unswizzle_buffer) - return; - using VideoCore::Surface::BytesPerBlock; const u32 block_bytes = BytesPerBlock(info.format); // 8 for BC1, 16 for BC6H @@ -1654,7 +1944,12 @@ void Image::AllocateComputeUnswizzleBuffer(u32 max_slices) { static_cast(blocks_y) * static_cast(blocks_z); - compute_unswizzle_buffer_size = block_count * block_bytes; + const VkDeviceSize required_size = block_count * block_bytes; + if (has_compute_unswizzle_buffer && required_size <= compute_unswizzle_buffer_size) { + return; + } + + compute_unswizzle_buffer_size = required_size; VkBufferCreateInfo ci{ .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, @@ -1677,74 +1972,71 @@ void Image::AllocateComputeUnswizzleBuffer(u32 max_slices) { void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset, std::span copies) { // TODO: Move this to another API + if (ENABLE_MSAA_RESOLVE_CONSUME && runtime != nullptr) { + runtime->InvalidateResolveShadow(Handle()); + } const bool is_rescaled = True(flags & ImageFlagBits::Rescaled); if (is_rescaled) { ScaleDown(true); } - // Handle MSAA upload if necessary - /* WARNING, TODO: This code uses some hacks, besides being fundamentally ugly - since tropic didn't want to touch it for a long time, so it needs a rewrite from someone - better than me at vulkan. */ - // CHANGE: Gate the MSAA path more strictly and only use it for color, when the pass and device - // support are available. Avoid running the MSAA path when prerequisites aren't met, - // preventing validation and runtime issues. + const bool msaa_upload_is_depth = (aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0; const bool wants_msaa_upload = info.num_samples > 1 - && (aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0 - && runtime->CanUploadMSAA() && runtime->msaa_copy_pass.has_value() - && runtime->device.IsStorageImageMultisampleSupported(); + && ((aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0 || msaa_upload_is_depth); if (wants_msaa_upload) { - // Create a temporary non-MSAA image to upload the data first - ImageInfo temp_info = info; - temp_info.num_samples = 1; + const bool msaa_upload_copies_stencil = + msaa_upload_is_depth && (aspect_mask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0 && + runtime->device.IsExtShaderStencilExportSupported(); + VkImageAspectFlags upload_aspect_mask = aspect_mask; + if (msaa_upload_is_depth) { + upload_aspect_mask = VK_IMAGE_ASPECT_DEPTH_BIT; + if (msaa_upload_copies_stencil) { + upload_aspect_mask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + } + const VkImageCreateInfo image_ci = MakeMsaaScratchImageCreateInfo( + runtime->device, info, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); + const VkImage temp_vk_image = runtime->AcquireMsaaScratchImage(image_ci); - // CHANGE: Build a fresh VkImageCreateInfo with robust usage flags for the temp image. - // Using the target image's usage as-is could miss STORAGE/TRANSFER bits and trigger - // validation errors. - VkImageCreateInfo image_ci = MakeImageCreateInfo(runtime->device, temp_info); - image_ci.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT; - - // CHANGE: The previous stack-allocated wrapper was destroyed at function exit, - // which could destroy VkImage before the GPU used it. - auto temp_wrapper = std::make_shared(*runtime, temp_info, 0, 0); - temp_wrapper->original_image = runtime->memory_allocator.CreateImage(image_ci); - temp_wrapper->current_image = &Image::original_image; - temp_wrapper->aspect_mask = aspect_mask; - temp_wrapper->initialized = true; - - // Upload to the temporary non-MSAA image scheduler->RequestOutsideRenderPassOperationContext(); - auto vk_copies = TransformBufferImageCopies(copies, offset, temp_wrapper->aspect_mask); + auto vk_copies = TransformBufferImageCopies(copies, offset, upload_aspect_mask); const VkBuffer src_buffer = buffer; - const VkImage temp_vk_image = *temp_wrapper->original_image; - const VkImageAspectFlags vk_aspect_mask = temp_wrapper->aspect_mask; + const VkImageAspectFlags vk_aspect_mask = upload_aspect_mask; - scheduler->Record([src_buffer, temp_vk_image, vk_aspect_mask, vk_copies, - keep = temp_wrapper](vk::CommandBuffer cmdbuf) { - CopyBufferToImage(cmdbuf, src_buffer, temp_vk_image, vk_aspect_mask, false, VideoCommon::FixSmallVectorADL(vk_copies)); + scheduler->Record([src_buffer, temp_vk_image, vk_aspect_mask, + vk_copies](vk::CommandBuffer cmdbuf) { + CopyBufferToImage(cmdbuf, src_buffer, temp_vk_image, vk_aspect_mask, false, vk_copies); }); - // Use MSAACopyPass to convert from non-MSAA to MSAA - std::vector image_copies; + const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(info.num_samples); + boost::container::small_vector image_copies; image_copies.reserve(copies.size()); for (const auto& copy : copies) { VideoCommon::ImageCopy image_copy{}; - image_copy.src_offset = {0, 0, 0}; // Use zero offset for source - image_copy.dst_offset = copy.image_offset; + image_copy.src_offset = {0, 0, 0}; + image_copy.dst_offset = {copy.image_offset.x >> samples_x, + copy.image_offset.y >> samples_y, copy.image_offset.z}; image_copy.src_subresource = copy.image_subresource; image_copy.dst_subresource = copy.image_subresource; - image_copy.extent = copy.image_extent; + image_copy.extent = {copy.image_extent.width >> samples_x, + copy.image_extent.height >> samples_y, copy.image_extent.depth}; image_copies.push_back(image_copy); } - runtime->msaa_copy_pass->CopyImage(*this, *temp_wrapper, image_copies, - /*msaa_to_non_msaa=*/false); - std::exchange(initialized, true); - - const u64 tick = scheduler->Flush(); - scheduler->Wait(tick); + if (msaa_upload_is_depth) { + runtime->blit_image_helper.CopyMSAADepth(runtime->render_pass_cache, Handle(), + info.format, temp_vk_image, info.format, + info.num_samples, + {image_copies.data(), image_copies.size()}, + msaa_upload_copies_stencil, false); + } else { + runtime->blit_image_helper.CopyMSAA(runtime->render_pass_cache, Handle(), info.format, + temp_vk_image, info.format, info.num_samples, + {image_copies.data(), image_copies.size()}, false); + } + initialized = true; + runtime->ReleaseMsaaScratchImage(temp_vk_image); if (is_rescaled) { ScaleUp(); @@ -1752,7 +2044,13 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset, return; } - // Regular non-MSAA upload (original behavior preserved) + if (info.num_samples > 1) { + if (is_rescaled) { + ScaleUp(); + } + return; + } + scheduler->RequestOutsideRenderPassOperationContext(); auto vk_copies = TransformBufferImageCopies(copies, offset, aspect_mask); const VkBuffer src_buffer = buffer; @@ -1762,7 +2060,7 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset, scheduler->Record([src_buffer, vk_image, vk_aspect_mask, was_initialized, vk_copies](vk::CommandBuffer cmdbuf) { - CopyBufferToImage(cmdbuf, src_buffer, vk_image, vk_aspect_mask, was_initialized, VideoCommon::FixSmallVectorADL(vk_copies)); + CopyBufferToImage(cmdbuf, src_buffer, vk_image, vk_aspect_mask, was_initialized, vk_copies); }); if (is_rescaled) { @@ -1792,24 +2090,61 @@ void Image::DownloadMemory(std::span buffers_span, std::span o ScaleDown(); } - // RE-USE MSAA UPLOAD CODE BUT NOW FOR DOWNLOAD - if (info.num_samples > 1 && runtime->msaa_copy_pass) { - // TODO: Depth/stencil formats need special handling - if (aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT) { - ImageInfo temp_info = info; - temp_info.num_samples = 1; + if (info.num_samples > 1) { + if (runtime->CanDownloadMsaa(info)) { + const bool msaa_download_is_depth = + (aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0; + const bool msaa_download_copies_stencil = + msaa_download_is_depth && (aspect_mask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0 && + runtime->device.IsExtShaderStencilExportSupported(); + VkImageUsageFlags scratch_usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + if (msaa_download_is_depth) { + scratch_usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + } else { + scratch_usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + } + const VkImageCreateInfo image_ci = + MakeMsaaScratchImageCreateInfo(runtime->device, info, scratch_usage); + const VkImage temp_vk_image = runtime->AcquireMsaaScratchImage(image_ci); - VkImageCreateInfo image_ci = MakeImageCreateInfo(runtime->device, temp_info); - image_ci.usage = original_image.UsageFlags(); - vk::Image temp_image = runtime->memory_allocator.CreateImage(image_ci); + const VkImageAspectFlags temp_aspect_mask = aspect_mask; + const VkAccessFlags attachment_access = + msaa_download_is_depth ? (VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT) + : (VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); + const VkPipelineStageFlags attachment_stage = + msaa_download_is_depth ? (VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT) + : VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - Image temp_wrapper(*runtime, temp_info, 0, 0); - temp_wrapper.original_image = std::move(temp_image); - temp_wrapper.current_image = &Image::original_image; - temp_wrapper.aspect_mask = aspect_mask; - temp_wrapper.initialized = true; + scheduler->RequestOutsideRenderPassOperationContext(); + scheduler->Record([temp_vk_image, temp_aspect_mask, attachment_access, + attachment_stage](vk::CommandBuffer cmdbuf) { + const VkImageMemoryBarrier init_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = 0, + .dstAccessMask = attachment_access, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = temp_vk_image, + .subresourceRange{ + .aspectMask = temp_aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, attachment_stage, 0, + init_barrier); + }); - std::vector image_copies; + boost::container::small_vector image_copies; + image_copies.reserve(copies.size()); for (const auto& copy : copies) { VideoCommon::ImageCopy image_copy; image_copy.src_offset = copy.image_offset; @@ -1820,7 +2155,18 @@ void Image::DownloadMemory(std::span buffers_span, std::span o image_copies.push_back(image_copy); } - runtime->msaa_copy_pass->CopyImage(temp_wrapper, *this, image_copies, true); + if (msaa_download_is_depth) { + runtime->blit_image_helper.CopyMSAADepth( + runtime->render_pass_cache, temp_vk_image, info.format, Handle(), info.format, + info.num_samples, {image_copies.data(), image_copies.size()}, + msaa_download_copies_stencil, true); + } else { + runtime->blit_image_helper.CopyMSAA(runtime->render_pass_cache, temp_vk_image, + info.format, Handle(), info.format, + info.num_samples, + {image_copies.data(), image_copies.size()}, + true); + } boost::container::small_vector buffers_vector{}; boost::container::small_vector, 8> @@ -1832,7 +2178,7 @@ void Image::DownloadMemory(std::span buffers_span, std::span o } scheduler->RequestOutsideRenderPassOperationContext(); - scheduler->Record([buffers = std::move(buffers_vector), image = *temp_wrapper.original_image, + scheduler->Record([buffers = std::move(buffers_vector), image = temp_vk_image, aspect_mask_ = aspect_mask, vk_copies](vk::CommandBuffer cmdbuf) { const VkImageMemoryBarrier read_barrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, @@ -1852,7 +2198,7 @@ void Image::DownloadMemory(std::span buffers_span, std::span o .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, read_barrier); for (size_t index = 0; index < buffers.size(); index++) { @@ -1884,11 +2230,15 @@ void Image::DownloadMemory(std::span buffers_span, std::span o .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, memory_write_barrier, nullptr, image_write_barrier); }); - return; + runtime->ReleaseMsaaScratchImage(temp_vk_image); } + if (is_rescaled) { + ScaleUp(true); + } + return; } else { boost::container::small_vector buffers_vector{}; boost::container::small_vector, 8> @@ -1919,7 +2269,7 @@ void Image::DownloadMemory(std::span buffers_span, std::span o .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, read_barrier); for (size_t index = 0; index < buffers.size(); index++) { @@ -1951,7 +2301,7 @@ void Image::DownloadMemory(std::span buffers_span, std::span o .layerCount = VK_REMAINING_ARRAY_LAYERS, }, }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, memory_write_barrier, nullptr, image_write_barrier); }); } @@ -1974,8 +2324,11 @@ void Image::DownloadMemory(const StagingBufferRef& map, std::spandevice, FormatType::Optimal, true, info.format); + if (WillUseAcceleratedAstcDecode(runtime->device, info)) { + format_info.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32; + } view = MakeStorageView(runtime->device.GetLogical(), level, *(this->*current_image), format_info.format); } @@ -2016,7 +2369,10 @@ bool Image::ScaleUp(bool ignore) { aspect_mask = ImageAspectMask(info.format); } if (NeedsScaleHelper()) { - return BlitScaleHelper(true); + if (!BlitScaleHelper(true)) { + current_image = &Image::original_image; + return false; + } } else { BlitScale(*scheduler, *original_image, *scaled_image, info, aspect_mask, resolution); } @@ -2041,7 +2397,11 @@ bool Image::ScaleDown(bool ignore) { aspect_mask = ImageAspectMask(info.format); } if (NeedsScaleHelper()) { - return BlitScaleHelper(false); + if (!BlitScaleHelper(false)) { + current_image = &Image::scaled_image; + flags |= ImageFlagBits::Rescaled; + return false; + } } else { BlitScale(*scheduler, *scaled_image, *original_image, info, aspect_mask, resolution, false); } @@ -2067,10 +2427,11 @@ bool Image::BlitScaleHelper(bool scale_up) { blit_view = std::make_unique(*runtime, view_info, NULL_IMAGE_ID, *this); } - const u32 src_width = scale_up ? info.size.width : scaled_width; - const u32 src_height = scale_up ? info.size.height : scaled_height; - const u32 dst_width = scale_up ? scaled_width : info.size.width; - const u32 dst_height = scale_up ? scaled_height : info.size.height; + const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(info.num_samples); + const u32 src_width = (scale_up ? info.size.width : scaled_width) >> samples_x; + const u32 src_height = (scale_up ? info.size.height : scaled_height) >> samples_y; + const u32 dst_width = (scale_up ? scaled_width : info.size.width) >> samples_x; + const u32 dst_height = (scale_up ? scaled_height : info.size.height) >> samples_y; const Region2D src_region{ .start = {0, 0}, .end = {s32(src_width), s32(src_height)}, @@ -2080,21 +2441,37 @@ bool Image::BlitScaleHelper(bool scale_up) { .end = {s32(dst_width), s32(dst_height)}, }; const VkExtent2D extent{ - .width = (std::max)(scaled_width, info.size.width), - .height = (std::max)(scaled_height, info.size.height), + .width = (std::max)(scaled_width, info.size.width) >> samples_x, + .height = (std::max)(scaled_height, info.size.height) >> samples_y, }; auto* view_ptr = blit_view.get(); if (aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT) { if (!blit_framebuffer) blit_framebuffer.emplace(*runtime, view_ptr, nullptr, extent, scale_up); - runtime->blit_image_helper.BlitColor(&*blit_framebuffer, *blit_view, - dst_region, src_region, operation, BLIT_OPERATION); - } else if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { + if (info.num_samples > 1) { + runtime->blit_image_helper.BlitColorMSAA(&*blit_framebuffer, *blit_view, + dst_region, src_region); + } else { + runtime->blit_image_helper.BlitColor(&*blit_framebuffer, *blit_view, + dst_region, src_region, operation, BLIT_OPERATION); + } + } else if ((aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0) { if (!blit_framebuffer) blit_framebuffer.emplace(*runtime, nullptr, view_ptr, extent, scale_up); - runtime->blit_image_helper.BlitDepthStencil(&*blit_framebuffer, *blit_view, - dst_region, src_region, operation, BLIT_OPERATION); + const bool has_stencil = (aspect_mask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; + const bool can_blit_stencil = + has_stencil && runtime->device.IsExtShaderStencilExportSupported(); + if (info.num_samples > 1) { + runtime->blit_image_helper.BlitDepthStencilMSAA(&*blit_framebuffer, *blit_view, + dst_region, src_region); + } else if (can_blit_stencil) { + runtime->blit_image_helper.BlitDepthStencil(&*blit_framebuffer, *blit_view, + dst_region, src_region, operation, BLIT_OPERATION); + } else { + runtime->blit_image_helper.BlitDepth(&*blit_framebuffer, *blit_view, dst_region, + src_region); + } } else { // TODO: Use helper blits where applicable flags &= ~ImageFlagBits::Rescaled; @@ -2106,8 +2483,7 @@ bool Image::BlitScaleHelper(bool scale_up) { bool Image::NeedsScaleHelper() const { const auto& device = runtime->device; - const bool needs_msaa_helper = info.num_samples > 1 && device.CantBlitMSAA(); - if (needs_msaa_helper) { + if (info.num_samples > 1) { return true; } static constexpr auto OPTIMAL_FORMAT = FormatType::Optimal; @@ -2142,7 +2518,30 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI SanitizeDepthStencilSwizzle(swizzle, device->SupportsDepthStencilSwizzleOne()); } } - const auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); + auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); + if (device->ApiVersion() >= VK_API_VERSION_1_3) { + const VkFormatProperties3 properties3 = + device->GetPhysical().GetFormatProperties3(format_info.format); + supports_depth_comparison = + (properties3.optimalTilingFeatures & + VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_DEPTH_COMPARISON_BIT) != 0; + supports_minmax_filter = (properties3.optimalTilingFeatures & + VK_FORMAT_FEATURE_2_SAMPLED_IMAGE_FILTER_MINMAX_BIT) != 0; + } else { + supports_depth_comparison = true; + supports_minmax_filter = + (device->GetPhysical().GetFormatProperties(format_info.format).optimalTilingFeatures & + VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_MINMAX_BIT) != 0; + } + requires_border_color_format = NeedsExplicitBorderColorFormat(format_info.format); + swizzle_mapping = VkComponentMapping{ + .r = ComponentSwizzle(swizzle[0]), + .g = ComponentSwizzle(swizzle[1]), + .b = ComponentSwizzle(swizzle[2]), + .a = ComponentSwizzle(swizzle[3]), + }; + has_identity_swizzle = swizzle[0] == SwizzleSource::R && swizzle[1] == SwizzleSource::G && + swizzle[2] == SwizzleSource::B && swizzle[3] == SwizzleSource::A; const VkImageUsageFlags requested_view_usage = ImageUsageFlags(format_info, format); const VkImageUsageFlags image_usage = image.UsageFlags(); const VkImageUsageFlags clamped_view_usage = requested_view_usage & image_usage; @@ -2151,19 +2550,23 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI .pNext = nullptr, .usage = clamped_view_usage, }; + const VkImageViewASTCDecodeModeEXT astc_decode_mode{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_ASTC_DECODE_MODE_EXT, + .pNext = &image_view_usage, + .decodeMode = VK_FORMAT_R8G8B8A8_UNORM, + }; + const void* view_next = &image_view_usage; + if (device->IsExtAstcDecodeModeSupported() && IsLdrAstcFormat(format_info.format)) { + view_next = &astc_decode_mode; + } const VkImageViewCreateInfo create_info{ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .pNext = &image_view_usage, + .pNext = view_next, .flags = 0, .image = image.Handle(), .viewType = VkImageViewType{}, .format = format_info.format, - .components{ - .r = ComponentSwizzle(swizzle[0]), - .g = ComponentSwizzle(swizzle[1]), - .b = ComponentSwizzle(swizzle[2]), - .a = ComponentSwizzle(swizzle[3]), - }, + .components = swizzle_mapping, .subresourceRange = MakeSubresourceRange(aspect_mask, info.range), }; const auto create = [&](TextureType tex_type, std::optional num_layers) { @@ -2276,7 +2679,11 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type, Shader::ImageFormat image_format) { if (image_handle) { if (image_format == Shader::ImageFormat::Typeless) { - return Handle(texture_type); + if (!typeless_storage_view) { + auto info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format); + typeless_storage_view = MakeView(info.format, VK_IMAGE_ASPECT_COLOR_BIT, texture_type); + } + return *typeless_storage_view; } const bool is_signed = image_format == Shader::ImageFormat::R8_SINT || image_format == Shader::ImageFormat::R16_SINT; @@ -2285,7 +2692,7 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type, auto& views{is_signed ? storage_views->signeds : storage_views->unsigneds}; auto& view{views[size_t(texture_type)]}; if (!view) - view = MakeView(Format(image_format), VK_IMAGE_ASPECT_COLOR_BIT); + view = MakeView(Format(image_format), VK_IMAGE_ASPECT_COLOR_BIT, texture_type); return *view; } return VK_NULL_HANDLE; @@ -2295,13 +2702,28 @@ bool ImageView::IsRescaled() const noexcept { return (*slot_images)[image_id].IsRescaled(); } -vk::ImageView ImageView::MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask) { +vk::ImageView ImageView::MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask, + std::optional texture_type) { + VkImageViewType view_type = ImageViewType(type); + VkImageSubresourceRange subresource_range = MakeSubresourceRange(aspect_mask, range); + if (texture_type) { + view_type = ImageViewType(*texture_type); + switch (view_type) { + case VK_IMAGE_VIEW_TYPE_1D_ARRAY: + case VK_IMAGE_VIEW_TYPE_2D_ARRAY: + case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY: + break; + default: + subresource_range.layerCount = 1; + break; + } + } return device->GetLogical().CreateImageView({ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = nullptr, .flags = 0, .image = image_handle, - .viewType = ImageViewType(type), + .viewType = view_type, .format = vk_format, .components{ .r = VK_COMPONENT_SWIZZLE_IDENTITY, @@ -2309,77 +2731,222 @@ vk::ImageView ImageView::MakeView(VkFormat vk_format, VkImageAspectFlags aspect_ .b = VK_COMPONENT_SWIZZLE_IDENTITY, .a = VK_COMPONENT_SWIZZLE_IDENTITY, }, - .subresourceRange = MakeSubresourceRange(aspect_mask, range), + .subresourceRange = subresource_range, }); } +CustomBorderColorBudget::~CustomBorderColorBudget() { + Release(); +} + +CustomBorderColorBudget::CustomBorderColorBudget(CustomBorderColorBudget&& rhs) noexcept + : device_ptr{std::exchange(rhs.device_ptr, nullptr)}, held{std::exchange(rhs.held, 0)} {} + +CustomBorderColorBudget& CustomBorderColorBudget::operator=( + CustomBorderColorBudget&& rhs) noexcept { + if (this != &rhs) { + Release(); + device_ptr = std::exchange(rhs.device_ptr, nullptr); + held = std::exchange(rhs.held, 0); + } + return *this; +} + +bool CustomBorderColorBudget::TryAcquire(const Device& device, size_t count) { + if (!device.TryReserveCustomBorderColorSamplers(count)) { + return false; + } + device_ptr = &device; + held += count; + return true; +} + +void CustomBorderColorBudget::Release() noexcept { + if (device_ptr != nullptr) { + device_ptr->ReleaseCustomBorderColorSamplers(held); + } + device_ptr = nullptr; + held = 0; +} + Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& tsc) { const auto& device = runtime.device; - const bool has_custom_border_extension = runtime.device.IsExtCustomBorderColorSupported(); - const bool has_format_undefined = - has_custom_border_extension && runtime.device.IsCustomBorderColorWithoutFormatSupported(); - const bool has_custom_border_colors = - has_format_undefined && runtime.device.IsCustomBorderColorsSupported(); - const auto color = tsc.BorderColor(); + device_ptr = &device; + border_color = tsc.BorderColor(); + srgb_border_color = tsc.SrgbBorderColor(); + const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f); + default_anisotropy = static_cast(1U << tsc.max_anisotropy); + + const VkFilter mag_filter{MaxwellToVK::Sampler::Filter(tsc.mag_filter)}; + const VkFilter min_filter{MaxwellToVK::Sampler::Filter(tsc.min_filter)}; + const VkSamplerMipmapMode mipmap_mode{MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter)}; + + const VkSamplerAddressMode wrap_u{ + MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter)}; + const VkSamplerAddressMode wrap_v{ + MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter)}; + const VkSamplerAddressMode wrap_p{ + MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter)}; + const bool samples_border = wrap_u == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER || + wrap_v == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER || + wrap_p == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + + reduction_mode = MaxwellToVK::SamplerReduction(tsc.reduction_filter); + + has_added_anisotropy = max_anisotropy > default_anisotropy; + has_linear_filtering = mag_filter == VK_FILTER_LINEAR || min_filter == VK_FILTER_LINEAR || + mipmap_mode == VK_SAMPLER_MIPMAP_MODE_LINEAR; + has_depth_comparison = tsc.depth_compare_enabled != 0; + has_minmax_reduction = reduction_mode != VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT; + has_srgb_border_color = tsc.srgb_conversion != 0 && srgb_border_color != border_color; + if (has_minmax_reduction && !device.IsExtSamplerFilterMinmaxSupported()) { + LOG_WARNING(Render_Vulkan, "VK_EXT_sampler_filter_minmax is required"); + has_minmax_reduction = false; + } + has_custom_border_colors = samples_border && device.IsCustomBorderColorUsable(); + needs_swizzle_mapping = has_custom_border_colors && device.NeedsBorderColorSwizzleMapping(); + + if (has_custom_border_colors && GPU::Logging::IsActive()) { + GPU::Logging::GPULogger::GetInstance().LogExtensionUsage( + "VK_EXT_custom_border_color", "Sampler::Sampler"); + } + if (device.IsExtBorderColorSwizzleSupported() && GPU::Logging::IsActive()) { + GPU::Logging::GPULogger::GetInstance().LogExtensionUsage( + "VK_EXT_border_color_swizzle", "Sampler::Sampler"); + } + + f32 min_lod = 0.0f; + f32 max_lod = 0.25f; + if (tsc.mipmap_filter != TextureMipmapFilter::None) { + min_lod = tsc.MinLod(); + max_lod = tsc.MaxLod(); + } + base_ci = VkSamplerCreateInfo{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .magFilter = mag_filter, + .minFilter = min_filter, + .mipmapMode = mipmap_mode, + .addressModeU = wrap_u, + .addressModeV = wrap_v, + .addressModeW = wrap_p, + .mipLodBias = tsc.LodBias(), + .anisotropyEnable = static_cast(max_anisotropy > 1.0f), + .maxAnisotropy = max_anisotropy, + .compareEnable = static_cast(tsc.depth_compare_enabled), + .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), + .minLod = min_lod, + .maxLod = max_lod, + .borderColor = VK_BORDER_COLOR_FLOAT_CUSTOM_EXT, + .unnormalizedCoordinates = VK_FALSE, + }; + variants.reserve(1); + Emplace(VariantKey{}); +} + +Sampler::VariantKey Sampler::MakeKey(const ImageView& image_view, bool is_depth) const noexcept { + VariantKey key{}; + key.reduce_anisotropy = has_added_anisotropy && !image_view.SupportsAnisotropy(); + key.force_nearest = has_linear_filtering && IsPixelFormatInteger(image_view.format); + key.drop_reduction = has_minmax_reduction && !image_view.SupportsMinmaxFilter(); + key.drop_custom_border = has_custom_border_colors && image_view.RequiresBorderColorFormat(); + key.srgb_border = has_srgb_border_color && IsPixelFormatSRGB(image_view.format); + if (needs_swizzle_mapping && !key.drop_custom_border && !image_view.HasIdentitySwizzle()) { + const VkComponentMapping& mapping = image_view.Swizzle(); + key.swizzle = {mapping.r, mapping.g, mapping.b, mapping.a}; + } + return key; +} + +VkSampler Sampler::Find(const VariantKey& key) const noexcept { + const auto it = std::ranges::find(variants, key, &Variant::key); + if (it == variants.end()) { + return VK_NULL_HANDLE; + } + return *it->sampler; +} + +VkSampler Sampler::Emplace(VariantKey key) { + bool custom_border = has_custom_border_colors && !key.drop_custom_border; + if (custom_border && !custom_border_color_budget.TryAcquire(*device_ptr, 1)) { + static bool warned_budget = false; + if (!warned_budget) { + warned_budget = true; + } + custom_border = false; + key.drop_custom_border = true; + key.swizzle = {}; + if (const VkSampler existing = Find(key); existing != VK_NULL_HANDLE) { + return existing; + } + } + + std::array color = border_color; + if (key.srgb_border) { + color = srgb_border_color; + } const VkSamplerCustomBorderColorCreateInfoEXT border_ci{ .sType = VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT, .pNext = nullptr, .customBorderColor = std::bit_cast(color), .format = VK_FORMAT_UNDEFINED, }; - const void* pnext = nullptr; - if (has_custom_border_colors) { - pnext = &border_ci; - // Log extension usage for custom border color - if (Settings::values.gpu_logging_enabled.GetValue()) { - GPU::Logging::GPULogger::GetInstance().LogExtensionUsage( - "VK_EXT_custom_border_color", "Sampler::Sampler"); + const VkSamplerBorderColorComponentMappingCreateInfoEXT mapping_ci{ + .sType = VK_STRUCTURE_TYPE_SAMPLER_BORDER_COLOR_COMPONENT_MAPPING_CREATE_INFO_EXT, + .pNext = &border_ci, + .components{key.swizzle[0], key.swizzle[1], key.swizzle[2], key.swizzle[3]}, + .srgb = VK_FALSE, + }; + const void* chain = nullptr; + if (custom_border) { + chain = &border_ci; + if (key.HasSwizzle()) { + chain = &mapping_ci; } } const VkSamplerReductionModeCreateInfoEXT reduction_ci{ .sType = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT, - .pNext = pnext, - .reductionMode = MaxwellToVK::SamplerReduction(tsc.reduction_filter), + .pNext = chain, + .reductionMode = reduction_mode, }; - if (runtime.device.IsExtSamplerFilterMinmaxSupported()) { - pnext = &reduction_ci; - } else if (reduction_ci.reductionMode != VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT) { - LOG_WARNING(Render_Vulkan, "VK_EXT_sampler_filter_minmax is required"); + if (has_minmax_reduction && !key.drop_reduction) { + chain = &reduction_ci; } - // Some games have samplers with garbage. Sanitize them here. - const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f); - const auto create_sampler = [&](const f32 anisotropy) { - return device.GetLogical().CreateSampler(VkSamplerCreateInfo{ - .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, - .pNext = pnext, - .flags = 0, - .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter), - .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter), - .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter), - .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter), - .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter), - .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter), - .mipLodBias = tsc.LodBias(), - .anisotropyEnable = static_cast(anisotropy > 1.0f ? VK_TRUE : VK_FALSE), - .maxAnisotropy = anisotropy, - .compareEnable = tsc.depth_compare_enabled, - .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), - .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(), - .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(), - .borderColor = has_custom_border_colors ? VK_BORDER_COLOR_FLOAT_CUSTOM_EXT - : ConvertBorderColor(color), - .unnormalizedCoordinates = VK_FALSE, - }); - }; - - sampler = create_sampler(max_anisotropy); - - const f32 max_anisotropy_default = static_cast(1U << tsc.max_anisotropy); - if (max_anisotropy > max_anisotropy_default) { - sampler_default_anisotropy = create_sampler(max_anisotropy_default); + VkSamplerCreateInfo create_info = base_ci; + create_info.pNext = chain; + if (key.force_nearest) { + create_info.magFilter = VK_FILTER_NEAREST; + create_info.minFilter = VK_FILTER_NEAREST; + create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + create_info.anisotropyEnable = VK_FALSE; + create_info.maxAnisotropy = 1.0f; + } else if (key.reduce_anisotropy) { + create_info.anisotropyEnable = static_cast(default_anisotropy > 1.0f); + create_info.maxAnisotropy = default_anisotropy; } + if (!custom_border) { + create_info.borderColor = ConvertBorderColor(color); + } + variants.push_back(Variant{ + .key = key, + .sampler = device_ptr->GetLogical().CreateSampler(create_info), + }); + return *variants.back().sampler; +} + +VkSampler Sampler::HandleFor(const ImageView& image_view, bool is_depth) { + VariantKey key = MakeKey(image_view, is_depth); + if (variants.size() >= MAX_VARIANTS) { + key.srgb_border = false; + key.swizzle = {}; + } + if (const VkSampler existing = Find(key); existing != VK_NULL_HANDLE) { + return existing; + } + return Emplace(key); } Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span color_buffers, @@ -2406,7 +2973,7 @@ Framebuffer::~Framebuffer() = default; void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime, std::span color_buffers, ImageView* depth_buffer, bool is_rescaled_) { - boost::container::small_vector attachments; + boost::container::small_vector attachments; RenderPassKey renderpass_key{}; s32 num_layers = 1; @@ -2435,6 +3002,8 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime, ++num_images; } const size_t num_colors = attachments.size(); + VkImage depth_image = VK_NULL_HANDLE; + VkImageAspectFlags depth_aspect_mask = 0; if (depth_buffer) { width = (std::min)(width, is_rescaled ? resolution.ScaleUp(depth_buffer->size.width) : depth_buffer->size.width); @@ -2450,15 +3019,61 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime, ++num_images; has_depth = (subresource_range.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0; has_stencil = (subresource_range.aspectMask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; + depth_image = depth_buffer->ImageHandle(); + depth_aspect_mask = subresource_range.aspectMask; } else { renderpass_key.depth_format = PixelFormat::Invalid; } renderpass_key.samples = samples; + const bool do_resolve_color = ENABLE_MSAA_TILER_RESOLVE && + samples != VK_SAMPLE_COUNT_1_BIT && num_colors > 0 && runtime.device.IsTiler(); + renderpass_key.resolve_color = do_resolve_color; + + const bool do_resolve_depth_stencil = ENABLE_MSAA_TILER_RESOLVE && + samples != VK_SAMPLE_COUNT_1_BIT && depth_image != VK_NULL_HANDLE && + runtime.device.IsTiler() && + SupportsDepthStencilResolve(runtime.device, renderpass_key.depth_format); + renderpass_key.resolve_depth_stencil = do_resolve_depth_stencil; + + discard_msaa_color = + ENABLE_MSAA_RESOLVE_CONSUME && ENABLE_MSAA_COLOR_DISCARD && do_resolve_color; + discard_msaa_depth_stencil = ENABLE_MSAA_RESOLVE_CONSUME && + ENABLE_MSAA_DEPTH_STENCIL_DISCARD && do_resolve_depth_stencil; renderpass = runtime.render_pass_cache.Get(renderpass_key); + render_pass_key = renderpass_key; + render_pass_cache = &runtime.render_pass_cache; + runtime_ptr = &runtime; render_area.width = (std::min)(render_area.width, width); render_area.height = (std::min)(render_area.height, height); + if (do_resolve_color) { + const u32 layers = static_cast((std::max)(num_layers, 1)); + for (size_t index = 0; index < NUM_RT; ++index) { + const PixelFormat format = renderpass_key.color_formats[index]; + if (format == PixelFormat::Invalid) { + continue; + } + const VkFormat vk_format = + MaxwellToVK::SurfaceFormat(runtime.device, FormatType::Optimal, true, format).format; + const VkImage msaa_image = images[rt_map[index]]; + attachments.push_back(runtime.GetOrCreateResolveShadow( + msaa_image, vk_format, render_area, layers, VK_IMAGE_ASPECT_COLOR_BIT)); + resolve_shadow_images[num_resolve_shadows++] = msaa_image; + } + } + + if (do_resolve_depth_stencil) { + const u32 layers = static_cast((std::max)(num_layers, 1)); + const VkFormat vk_format = + MaxwellToVK::SurfaceFormat(runtime.device, FormatType::Optimal, true, + renderpass_key.depth_format) + .format; + attachments.push_back(runtime.GetOrCreateResolveShadow(depth_image, vk_format, render_area, + layers, depth_aspect_mask)); + resolve_shadow_images[num_resolve_shadows++] = depth_image; + } + num_color_buffers = static_cast(num_colors); framebuffer = runtime.device.GetLogical().CreateFramebuffer({ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, @@ -2473,6 +3088,45 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime, }); } +void Framebuffer::MarkResolveShadowsUpToDate() const { + if (runtime_ptr == nullptr) { + return; + } + for (u32 index = 0; index < num_resolve_shadows; ++index) { + runtime_ptr->MarkResolveShadowUpToDate(resolve_shadow_images[index]); + } +} + +VkRenderPass Framebuffer::RenderPassVariant(u32 color_clear_mask, bool depth_stencil_clear, + u32 color_discard_mask, + bool depth_stencil_discard) const { + if (color_clear_mask == 0 && !depth_stencil_clear && color_discard_mask == 0 && + !depth_stencil_discard) { + return renderpass; + } + static_assert(NUM_RT <= 8); + const u32 variant_key = color_clear_mask | (color_discard_mask << 8) | + (static_cast(depth_stencil_clear) << 16) | + (static_cast(depth_stencil_discard) << 17); + for (u32 index = 0; index < num_memoized_variants; ++index) { + if (variant_keys[index] == variant_key) { + return variant_render_passes[index]; + } + } + RenderPassKey key = render_pass_key; + key.color_clear_mask = color_clear_mask; + key.depth_stencil_clear = depth_stencil_clear; + key.color_discard_mask = color_discard_mask; + key.depth_stencil_discard = depth_stencil_discard; + const VkRenderPass variant = render_pass_cache->Get(key); + if (num_memoized_variants < variant_keys.size()) { + variant_keys[num_memoized_variants] = variant_key; + variant_render_passes[num_memoized_variants] = variant; + ++num_memoized_variants; + } + return variant; +} + void TextureCacheRuntime::AccelerateImageUpload( Image& image, const StagingBufferRef& map, std::span swizzles, @@ -2484,18 +3138,13 @@ void TextureCacheRuntime::AccelerateImageUpload( if (!Settings::values.gpu_unswizzle_enabled.GetValue() || !bl3d_unswizzle_pass) { if (IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D) { - ASSERT_MSG(false, "GPU unswizzle is disabled for BCn 3D texture"); + ASSERT(false && "GPU unswizzle is disabled for BCn 3D texture"); } ASSERT(false); return; } - if (bl3d_unswizzle_pass && - IsPixelFormatBCn(image.info.format) && - image.info.type == ImageType::e3D && - image.info.resources.levels == 1 && - image.info.resources.layers == 1) { - + if (bl3d_unswizzle_pass && IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D && image.info.resources.levels == 1 && image.info.resources.layers == 1) { return bl3d_unswizzle_pass->Unswizzle(image, map, swizzles, z_start, z_count); } @@ -2524,8 +3173,8 @@ void TextureCacheRuntime::TransitionImageLayout(Image& image) { }; scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([barrier](vk::CommandBuffer cmdbuf) { - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier); + cmdbuf.PipelineBarrier(vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, + vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, barrier); }); } } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 4bb9687ab0..2e9736f707 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -12,6 +12,7 @@ #include "shader_recompiler/shader_info.h" #include "video_core/renderer_vulkan/vk_compute_pass.h" +#include "video_core/renderer_vulkan/vk_render_pass_cache.h" #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h" #include "video_core/texture_cache/image_view_base.h" #include "video_core/vulkan_common/vulkan_memory_allocator.h" @@ -59,13 +60,19 @@ public: void TickFrame(); + void FlushDeferredClear(); + u64 GetDeviceLocalMemory() const; u64 GetDeviceMemoryUsage() const; bool CanReportMemoryUsage() const; - std::optional GetSamplerHeapBudget() const; + bool CanDownloadMsaa(const VideoCommon::ImageInfo& info) const; + + [[nodiscard]] VkImage AcquireMsaaScratchImage(const VkImageCreateInfo& image_ci); + + void ReleaseMsaaScratchImage(VkImage image); void BlitImage(Framebuffer* dst_framebuffer, ImageView& dst, ImageView& src, const Region2D& dst_region, const Region2D& src_region, @@ -87,7 +94,7 @@ public: } bool CanUploadMSAA() const noexcept { - return msaa_copy_pass.operator bool(); + return true; } void AccelerateImageUpload(Image&, const StagingBufferRef&, @@ -110,6 +117,28 @@ public: [[nodiscard]] VkBuffer GetTemporaryBuffer(size_t needed_size); + struct ResolveShadow { + vk::Image image; + vk::ImageView view; + VkFormat format = VK_FORMAT_UNDEFINED; + VkExtent2D extent{}; + u32 layers = 0; + VkImageAspectFlags aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT; + bool up_to_date = false; + }; + + [[nodiscard]] VkImageView GetOrCreateResolveShadow(VkImage msaa_image, VkFormat format, + VkExtent2D extent, u32 layers, + VkImageAspectFlags aspect_mask); + + [[nodiscard]] const ResolveShadow* GetValidResolveShadow(VkImage msaa_image) const; + + void InvalidateResolveShadow(VkImage msaa_image); + + void MarkResolveShadowUpToDate(VkImage msaa_image); + + void EraseResolveShadow(VkImage msaa_image); + std::span ViewFormats(PixelFormat format) { return view_formats[static_cast(format)]; } @@ -130,15 +159,35 @@ public: std::optional astc_decoder_pass; std::optional bl3d_unswizzle_pass; - vk::Buffer swizzle_table_buffer; - VkDeviceSize swizzle_table_size = 0; - - std::optional msaa_copy_pass; const Settings::ResolutionScalingInfo& resolution; std::array, VideoCore::Surface::MaxPixelFormat> view_formats; static constexpr size_t indexing_slots = 8 * sizeof(size_t); std::array buffers{}; + struct MsaaScratchKey { + VkFormat format; + VkImageType type; + u32 width; + u32 height; + u32 depth; + u32 levels; + u32 layers; + VkImageUsageFlags usage; + VkImageCreateFlags flags; + + bool operator==(const MsaaScratchKey&) const noexcept = default; + }; + + struct MsaaScratchImage { + MsaaScratchKey key; + vk::Image image; + u64 tick; + u32 unused_frames; + }; + + std::vector msaa_scratch_images; + ::Common::unordered_map resolve_shadows; + std::vector> pending_resolve_shadows; }; class Framebuffer { @@ -169,6 +218,14 @@ public: return renderpass; } + [[nodiscard]] const RenderPassKey& RenderPassKeyBase() const noexcept { + return render_pass_key; + } + + [[nodiscard]] VkRenderPass RenderPassVariant(u32 color_clear_mask, bool depth_stencil_clear, + u32 color_discard_mask, + bool depth_stencil_discard) const; + [[nodiscard]] VkExtent2D RenderArea() const noexcept { return render_area; } @@ -209,7 +266,21 @@ public: return is_rescaled; } + [[nodiscard]] bool DiscardsMsaaColor() const noexcept { + return discard_msaa_color; + } + + [[nodiscard]] bool DiscardsMsaaDepthStencil() const noexcept { + return discard_msaa_depth_stencil; + } + + /// Records that a render pass has begun, so its resolve attachments will hold valid contents + /// once it ends. + void MarkResolveShadowsUpToDate() const; + private: + static constexpr size_t NUM_MEMOIZED_RENDER_PASS_VARIANTS = 8; + vk::Framebuffer framebuffer; VkRenderPass renderpass{}; VkExtent2D render_area{}; @@ -222,6 +293,16 @@ private: bool has_depth{}; bool has_stencil{}; bool is_rescaled{}; + std::array resolve_shadow_images{}; + u32 num_resolve_shadows = 0; + TextureCacheRuntime* runtime_ptr{nullptr}; + RenderPassKey render_pass_key{}; + RenderPassCache* render_pass_cache{nullptr}; + bool discard_msaa_color{}; + bool discard_msaa_depth_stencil{}; + mutable std::array variant_keys{}; + mutable std::array variant_render_passes{}; + mutable u32 num_memoized_variants{}; }; class Image : public VideoCommon::ImageBase { @@ -359,6 +440,26 @@ public: return samples; } + [[nodiscard]] bool SupportsDepthComparison() const noexcept { + return supports_depth_comparison; + } + + [[nodiscard]] bool RequiresBorderColorFormat() const noexcept { + return requires_border_color_format; + } + + [[nodiscard]] bool SupportsMinmaxFilter() const noexcept { + return supports_minmax_filter; + } + + [[nodiscard]] const VkComponentMapping& Swizzle() const noexcept { + return swizzle_mapping; + } + + [[nodiscard]] bool HasIdentitySwizzle() const noexcept { + return has_identity_swizzle; + } + [[nodiscard]] GPUVAddr GpuAddr() const noexcept { return gpu_addr; } @@ -373,13 +474,15 @@ private: std::array unsigneds; }; - [[nodiscard]] vk::ImageView MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask); + [[nodiscard]] vk::ImageView MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask, + std::optional texture_type = std::nullopt); const Device* device = nullptr; const SlotVector* slot_images = nullptr; std::array image_views; std::optional storage_views; + vk::ImageView typeless_storage_view; vk::ImageView depth_view; vk::ImageView stencil_view; vk::ImageView color_view; @@ -388,29 +491,91 @@ private: VkImageView render_target = VK_NULL_HANDLE; VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; u32 buffer_size = 0; + + VkComponentMapping swizzle_mapping{}; + + bool supports_depth_comparison = false; + bool requires_border_color_format = false; + bool supports_minmax_filter = false; + bool has_identity_swizzle = true; }; class ImageAlloc : public VideoCommon::ImageAllocBase {}; +class CustomBorderColorBudget { +public: + CustomBorderColorBudget() = default; + ~CustomBorderColorBudget(); + + CustomBorderColorBudget(const CustomBorderColorBudget&) = delete; + CustomBorderColorBudget& operator=(const CustomBorderColorBudget&) = delete; + + CustomBorderColorBudget(CustomBorderColorBudget&& rhs) noexcept; + CustomBorderColorBudget& operator=(CustomBorderColorBudget&& rhs) noexcept; + + bool TryAcquire(const Device& device, size_t count); + +private: + void Release() noexcept; + + const Device* device_ptr = nullptr; + size_t held = 0; +}; + class Sampler { public: explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&); [[nodiscard]] VkSampler Handle() const noexcept { - return *sampler; + return *variants.front().sampler; } - [[nodiscard]] VkSampler HandleWithDefaultAnisotropy() const noexcept { - return *sampler_default_anisotropy; - } - - [[nodiscard]] bool HasAddedAnisotropy() const noexcept { - return static_cast(sampler_default_anisotropy); - } + [[nodiscard]] VkSampler HandleFor(const ImageView& image_view, bool is_depth); private: - vk::Sampler sampler; - vk::Sampler sampler_default_anisotropy; + struct VariantKey { + bool reduce_anisotropy; + bool force_nearest; + bool drop_reduction; + bool drop_custom_border; + bool srgb_border; + std::array swizzle; + + bool operator==(const VariantKey&) const noexcept = default; + + [[nodiscard]] bool HasSwizzle() const noexcept { + return swizzle != std::array{}; + } + }; + + struct Variant { + VariantKey key; + vk::Sampler sampler; + }; + + static constexpr size_t MAX_VARIANTS = 32; + + [[nodiscard]] VariantKey MakeKey(const ImageView& image_view, bool is_depth) const noexcept; + [[nodiscard]] VkSampler Find(const VariantKey& key) const noexcept; + VkSampler Emplace(VariantKey key); + + CustomBorderColorBudget custom_border_color_budget; + std::vector variants; + + const Device* device_ptr{nullptr}; + VkSamplerCreateInfo base_ci{}; + VkSamplerReductionModeEXT reduction_mode{VK_SAMPLER_REDUCTION_MODE_WEIGHTED_AVERAGE_EXT}; + std::array border_color{}; + std::array srgb_border_color{}; + f32 default_anisotropy{1.0f}; + + bool has_added_anisotropy{}; + bool has_linear_filtering{}; + bool has_depth_comparison{}; + bool has_minmax_reduction{}; + bool has_custom_border_colors{}; + bool has_srgb_border_color{}; + bool needs_swizzle_mapping{}; }; struct TextureCacheParams { @@ -419,6 +584,7 @@ struct TextureCacheParams { static constexpr bool HAS_EMULATED_COPIES = false; static constexpr bool HAS_DEVICE_MEMORY_INFO = true; static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true; + static constexpr bool HAS_MSAA_DOWNLOADS = true; using Runtime = Vulkan::TextureCacheRuntime; using Image = Vulkan::Image; diff --git a/src/video_core/renderer_vulkan/vk_turbo_mode.cpp b/src/video_core/renderer_vulkan/vk_turbo_mode.cpp index 54183be12c..095908b6cf 100644 --- a/src/video_core/renderer_vulkan/vk_turbo_mode.cpp +++ b/src/video_core/renderer_vulkan/vk_turbo_mode.cpp @@ -1,10 +1,10 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 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 -#if defined(ANDROID) && defined(ARCHITECTURE_arm64) +#if defined(__ANDROID__) && defined(ARCHITECTURE_arm64) #include #endif @@ -20,7 +20,7 @@ namespace Vulkan { using namespace Common::Literals; TurboMode::TurboMode(const vk::Instance& instance, const vk::InstanceDispatch& dld) -#ifndef ANDROID +#ifndef __ANDROID__ : m_device{CreateDevice(instance, dld, VK_NULL_HANDLE)}, m_allocator{m_device} #endif { @@ -40,7 +40,7 @@ void TurboMode::QueueSubmitted() { } void TurboMode::Run(std::stop_token stop_token) { -#ifndef ANDROID +#ifndef __ANDROID__ auto& dld = m_device.GetLogical(); // Allocate buffer. 2MiB should be sufficient. @@ -154,7 +154,7 @@ void TurboMode::Run(std::stop_token stop_token) { #endif while (!stop_token.stop_requested()) { -#ifdef ANDROID +#ifdef __ANDROID__ #ifdef ARCHITECTURE_arm64 adrenotools_set_turbo(true); #endif @@ -232,7 +232,7 @@ void TurboMode::Run(std::stop_token stop_token) { std::chrono::milliseconds{100}; }); } -#if defined(ANDROID) && defined(ARCHITECTURE_arm64) +#if defined(__ANDROID__) && defined(ARCHITECTURE_arm64) adrenotools_set_turbo(false); #endif } diff --git a/src/video_core/renderer_vulkan/vk_turbo_mode.h b/src/video_core/renderer_vulkan/vk_turbo_mode.h index 9341c98671..6f95cf5e74 100644 --- a/src/video_core/renderer_vulkan/vk_turbo_mode.h +++ b/src/video_core/renderer_vulkan/vk_turbo_mode.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -23,7 +26,7 @@ public: private: void Run(std::stop_token stop_token); -#ifndef ANDROID +#ifndef __ANDROID__ Device m_device; MemoryAllocator m_allocator; #endif diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp index aa7de7b632..742a8d9969 100644 --- a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp +++ b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp @@ -7,6 +7,7 @@ #include #include +#include "common/assert.h" #include "common/logging.h" #include "video_core/renderer_vulkan/vk_scheduler.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" @@ -15,8 +16,12 @@ namespace Vulkan { -UpdateDescriptorQueue::UpdateDescriptorQueue(const Device& device_, Scheduler& scheduler_) - : device{device_}, scheduler{scheduler_} { +UpdateDescriptorQueue::UpdateDescriptorQueue(const Device& device_, size_t frame_payload_size_, + bool supports_descriptor_buffer_) + : device{device_}, frame_payload_size{frame_payload_size_}, + supports_descriptor_buffer{supports_descriptor_buffer_}, + payload(frame_payload_size_ * FRAMES_IN_FLIGHT) +{ payload_start = payload.data(); payload_cursor = payload.data(); } @@ -27,17 +32,21 @@ void UpdateDescriptorQueue::TickFrame() { if (++frame_index >= FRAMES_IN_FLIGHT) { frame_index = 0; } - payload_start = payload.data() + frame_index * FRAME_PAYLOAD_SIZE; + payload_start = payload.data() + frame_index * frame_payload_size; payload_cursor = payload_start; } -void UpdateDescriptorQueue::Acquire() { - // Minimum number of entries required. - // This is the maximum number of entries a single draw call might use. - static constexpr size_t MIN_ENTRIES = 0x400; - - if (std::distance(payload_start, payload_cursor) + MIN_ENTRIES >= FRAME_PAYLOAD_SIZE) { - LOG_WARNING(Render_Vulkan, "Payload overflow, waiting for worker thread"); +void UpdateDescriptorQueue::Acquire(Scheduler& scheduler, size_t required_entries, + bool use_descriptor_buffer_) { + use_descriptor_buffer = supports_descriptor_buffer && use_descriptor_buffer_; + static constexpr size_t DEFAULT_REQUIRED_ENTRIES = 0x400; + const size_t reserve = required_entries > 0 ? required_entries : DEFAULT_REQUIRED_ENTRIES; + ASSERT_MSG(reserve < frame_payload_size, "Descriptor reservation {} >= frame capacity {}", + reserve, frame_payload_size); + const size_t used = static_cast(std::distance(payload_start, payload_cursor)); + if (used + reserve >= frame_payload_size) { + LOG_WARNING(Render_Vulkan, "Payload overflow (used={}, reserve={}, capacity={})", + used, reserve, frame_payload_size); scheduler.WaitWorker(); payload_cursor = payload_start; } diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.h b/src/video_core/renderer_vulkan/vk_update_descriptor.h index 82fce298da..9f30e65dbd 100644 --- a/src/video_core/renderer_vulkan/vk_update_descriptor.h +++ b/src/video_core/renderer_vulkan/vk_update_descriptor.h @@ -1,10 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 #pragma once -#include - +#include +#include #include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { @@ -12,36 +15,45 @@ namespace Vulkan { class Device; class Scheduler; -struct DescriptorUpdateEntry { - struct Empty {}; +struct DescriptorAddress { + VkDeviceAddress address; + VkDeviceSize range; + VkFormat format; +}; +union DescriptorUpdateEntry { DescriptorUpdateEntry() = default; DescriptorUpdateEntry(VkDescriptorImageInfo image_) : image{image_} {} DescriptorUpdateEntry(VkDescriptorBufferInfo buffer_) : buffer{buffer_} {} DescriptorUpdateEntry(VkBufferView texel_buffer_) : texel_buffer{texel_buffer_} {} - - union { - Empty empty{}; - VkDescriptorImageInfo image; - VkDescriptorBufferInfo buffer; - VkBufferView texel_buffer; - }; + DescriptorUpdateEntry(DescriptorAddress address_) : address{address_} {} + std::monostate empty{}; + VkDescriptorImageInfo image; + VkDescriptorBufferInfo buffer; + VkBufferView texel_buffer; + DescriptorAddress address; }; class UpdateDescriptorQueue final { // This should be plenty for the vast majority of cases. Most desktop platforms only // provide up to 3 swapchain images. static constexpr size_t FRAMES_IN_FLIGHT = 8; - static constexpr size_t FRAME_PAYLOAD_SIZE = 0x20000; - static constexpr size_t PAYLOAD_SIZE = FRAME_PAYLOAD_SIZE * FRAMES_IN_FLIGHT; public: - explicit UpdateDescriptorQueue(const Device& device_, Scheduler& scheduler_); + static constexpr size_t GUEST_FRAME_PAYLOAD_SIZE = 0x80000; + static constexpr size_t COMPUTE_FRAME_PAYLOAD_SIZE = 0x20000; + + explicit UpdateDescriptorQueue(const Device& device_, size_t frame_payload_size_, + bool supports_descriptor_buffer_ = false); ~UpdateDescriptorQueue(); - void TickFrame(); + [[nodiscard]] bool UsesDescriptorBuffer() const noexcept { + return use_descriptor_buffer; + } - void Acquire(); + void TickFrame(); + void Acquire(Scheduler& scheduler, size_t required_entries = 0, + bool use_descriptor_buffer_ = false); const DescriptorUpdateEntry* UpdateData() const noexcept { return upload_start; @@ -71,19 +83,46 @@ public: }; } + void AddBuffer(VkBuffer buffer, VkDeviceAddress base_address, VkDeviceSize offset, + VkDeviceSize size) { + if (!use_descriptor_buffer) { + AddBuffer(buffer, offset, size); + return; + } + *(payload_cursor++) = DescriptorAddress{ + .address = base_address == 0 ? 0 : base_address + offset, + .range = base_address == 0 ? VK_WHOLE_SIZE : size, + .format = VK_FORMAT_UNDEFINED, + }; + } + void AddTexelBuffer(VkBufferView texel_buffer) { *(payload_cursor++) = texel_buffer; } + void AddTexelBuffer(VkBufferView texel_buffer, VkDeviceAddress base_address, + VkDeviceSize offset, VkDeviceSize size, VkFormat format) { + if (!use_descriptor_buffer) { + AddTexelBuffer(texel_buffer); + return; + } + *(payload_cursor++) = DescriptorAddress{ + .address = base_address == 0 ? 0 : base_address + offset, + .range = base_address == 0 ? VK_WHOLE_SIZE : size, + .format = format, + }; + } + private: const Device& device; - Scheduler& scheduler; - + const size_t frame_payload_size; + const bool supports_descriptor_buffer; + bool use_descriptor_buffer{false}; size_t frame_index{0}; DescriptorUpdateEntry* payload_cursor = nullptr; DescriptorUpdateEntry* payload_start = nullptr; const DescriptorUpdateEntry* upload_start = nullptr; - std::array payload; + std::vector payload; }; // TODO: should these be separate classes instead? diff --git a/src/video_core/shader_cache.cpp b/src/video_core/shader_cache.cpp index 8a349bfe2a..f1aff2810f 100644 --- a/src/video_core/shader_cache.cpp +++ b/src/video_core/shader_cache.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -66,7 +66,7 @@ bool ShaderCache::RefreshStages(std::array& unique_hashes) { const GPUVAddr shader_addr{base_addr + shader_config.offset}; const std::optional cpu_shader_addr{gpu_memory->GpuToCpuAddress(shader_addr)}; if (!cpu_shader_addr) { - LOG_ERROR(HW_GPU, "Invalid GPU address for shader 0x{:016x}", shader_addr); + LOG_ERROR(HW_GPU, "Invalid GPU address for shader {:#016x}", shader_addr); last_shaders_valid = false; return false; } @@ -89,7 +89,7 @@ const ShaderInfo* ShaderCache::ComputeShader() { const GPUVAddr shader_addr{program_base + qmd.program_start}; const std::optional cpu_shader_addr{gpu_memory->GpuToCpuAddress(shader_addr)}; if (!cpu_shader_addr) { - LOG_ERROR(HW_GPU, "Invalid GPU address for shader 0x{:016x}", shader_addr); + LOG_ERROR(HW_GPU, "Invalid GPU address for shader {:#016x}", shader_addr); return nullptr; } if (const ShaderInfo* const shader = TryGet(*cpu_shader_addr)) { diff --git a/src/video_core/shader_cache.h b/src/video_core/shader_cache.h index 7d93a542ef..997f2b0371 100644 --- a/src/video_core/shader_cache.h +++ b/src/video_core/shader_cache.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include @@ -148,8 +148,8 @@ private: mutable std::mutex lookup_mutex; std::mutex invalidation_mutex; - ankerl::unordered_dense::map> lookup_cache; - ankerl::unordered_dense::map> invalidation_cache; + ::Common::unordered_map> lookup_cache; + ::Common::unordered_map> invalidation_cache; std::vector> storage; std::vector marked_for_removal; }; diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp index 5f5633d4d1..4d5db7e511 100644 --- a/src/video_core/shader_environment.cpp +++ b/src/video_core/shader_environment.cpp @@ -18,6 +18,7 @@ #include "common/fs/fs.h" #include "common/fs/path_util.h" #include "common/logging.h" +#include "common/settings.h" #include #include "shader_recompiler/environment.h" #include "video_core/engines/kepler_compute.h" @@ -73,36 +74,36 @@ static Shader::TexturePixelFormat ConvertTexturePixelFormat(const Tegra::Texture static std::string_view StageToPrefix(Shader::Stage stage) { switch (stage) { case Shader::Stage::VertexB: - return "VB"; + return "vs"; case Shader::Stage::TessellationControl: - return "TC"; + return "tc"; case Shader::Stage::TessellationEval: - return "TE"; + return "te"; case Shader::Stage::Geometry: - return "GS"; + return "gs"; case Shader::Stage::Fragment: - return "FS"; + return "fs"; case Shader::Stage::Compute: - return "CS"; + return "cs"; case Shader::Stage::VertexA: - return "VA"; + return "va"; default: - return "UK"; + return "uk"; } } -static void DumpImpl(u64 pipeline_hash, u64 shader_hash, std::span code, +static void DumpImpl(u64 /*pipeline_hash*/, u64 shader_hash, std::span code, [[maybe_unused]] u32 read_highest, [[maybe_unused]] u32 read_lowest, u32 initial_offset, Shader::Stage stage) { - const auto shader_dir{Common::FS::GetEdenPath(Common::FS::EdenPath::DumpDir)}; - const auto base_dir{shader_dir / "shaders"}; - if (!Common::FS::CreateDir(shader_dir) || !Common::FS::CreateDir(base_dir)) { - LOG_ERROR(Common_Filesystem, "Failed to create shader dump directories"); + const auto dump_dir{Common::FS::GetEdenPath(Common::FS::EdenPath::DumpDir)}; + if (!Common::FS::CreateDir(dump_dir)) { + LOG_ERROR(Common_Filesystem, "Failed to create dump directory"); return; } const auto prefix = StageToPrefix(stage); - const auto name{base_dir / - fmt::format("{:016x}_{}_{:016x}.ash", pipeline_hash, prefix, shader_hash)}; + const auto name{dump_dir / + fmt::format("{:016x}_{:016x}_{}.ash", + Settings::GetCurrentProgramID(), shader_hash, prefix)}; std::fstream shader_file(name, std::ios::out | std::ios::binary); ASSERT(initial_offset % sizeof(u64) == 0); const size_t jump_index = initial_offset / sizeof(u64); @@ -254,11 +255,14 @@ std::optional GenericEnvironment::TryFindSize() { static constexpr u64 SELF_BRANCH_A = 0xE2400FFFFF87000FULL; static constexpr u64 SELF_BRANCH_B = 0xE2400FFFFF07000FULL; + static constexpr u64 EXIT_VALUE = 0xE30000000007000FULL; + + code.resize(MAXIMUM_SIZE / INST_SIZE); + GPUVAddr guest_addr{program_base + start_address}; size_t offset{0}; size_t size{BLOCK_SIZE}; while (size <= MAXIMUM_SIZE) { - code.resize(size / INST_SIZE); u64* const data = code.data() + offset / INST_SIZE; gpu_memory->ReadBlock(guest_addr, data, BLOCK_SIZE); for (size_t index = 0; index < BLOCK_SIZE; index += INST_SIZE) { @@ -266,6 +270,9 @@ std::optional GenericEnvironment::TryFindSize() { if (inst == SELF_BRANCH_A || inst == SELF_BRANCH_B) { return offset + index; } + if (!is_proprietary_driver && inst == EXIT_VALUE) { + return offset + index + INST_SIZE; + } } guest_addr += BLOCK_SIZE; size += BLOCK_SIZE; diff --git a/src/video_core/shader_environment.h b/src/video_core/shader_environment.h index 183b6b903b..924e61ef46 100644 --- a/src/video_core/shader_environment.h +++ b/src/video_core/shader_environment.h @@ -14,7 +14,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include "common/common_types.h" @@ -79,10 +79,10 @@ protected: GPUVAddr program_base{}; std::vector code; - ankerl::unordered_dense::map texture_types; - ankerl::unordered_dense::map texture_pixel_formats; - ankerl::unordered_dense::map cbuf_values; - ankerl::unordered_dense::map cbuf_replacements; + ::Common::unordered_map texture_types; + ::Common::unordered_map texture_pixel_formats; + ::Common::unordered_map cbuf_values; + ::Common::unordered_map cbuf_replacements; u32 local_memory_size{}; u32 texture_bound{}; @@ -201,10 +201,10 @@ public: private: std::vector code; - ankerl::unordered_dense::map texture_types; - ankerl::unordered_dense::map texture_pixel_formats; - ankerl::unordered_dense::map cbuf_values; - ankerl::unordered_dense::map cbuf_replacements; + ::Common::unordered_map texture_types; + ::Common::unordered_map texture_pixel_formats; + ::Common::unordered_map cbuf_values; + ::Common::unordered_map cbuf_replacements; std::array workgroup_size{}; u32 local_memory_size{}; u32 shared_memory_size{}; @@ -219,16 +219,13 @@ void SerializePipeline(std::span key, std::span -void SerializePipeline(const Key& key, const Envs& envs, const std::filesystem::path& filename, - u32 cache_version) { - static_assert(std::is_trivially_copyable_v); - static_assert(std::has_unique_object_representations_v); - SerializePipeline(std::span(reinterpret_cast(&key), sizeof(key)), - std::span(envs.data(), envs.size()), filename, cache_version); + requires std::is_trivially_copyable_v + && std::has_unique_object_representations_v +void SerializePipeline(const Key& key, const Envs& envs, const std::filesystem::path& filename, u32 cache_version) { + SerializePipeline(std::span(reinterpret_cast(&key), sizeof(key)), std::span(envs.data(), envs.size()), filename, cache_version); } -void LoadPipelines( - std::stop_token stop_loading, const std::filesystem::path& filename, u32 expected_cache_version, +void LoadPipelines(std::stop_token stop_loading, const std::filesystem::path& filename, u32 expected_cache_version, Common::UniqueFunction load_compute, Common::UniqueFunction> load_graphics); diff --git a/src/video_core/smaa_area_tex.h b/src/video_core/smaa_area_tex.h index 40d0941c26..aa4000d315 100644 --- a/src/video_core/smaa_area_tex.h +++ b/src/video_core/smaa_area_tex.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2013 Jorge Jimenez (jorge@iryoku.com) // SPDX-FileCopyrightText: 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com) // SPDX-FileCopyrightText: 2013 Belen Masia (bmasia@unizar.es) @@ -5,8 +8,7 @@ // SPDX-FileCopyrightText: 2013 Diego Gutierrez (diegog@unizar.es) // SPDX-License-Identifier: MIT -#ifndef AREATEX_H -#define AREATEX_H +#pragma once #define AREATEX_WIDTH 160 #define AREATEX_HEIGHT 560 @@ -11219,5 +11221,3 @@ static const unsigned char areaTexBytes[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - -#endif diff --git a/src/video_core/smaa_search_tex.h b/src/video_core/smaa_search_tex.h index 61939e2046..3a74ab0a32 100644 --- a/src/video_core/smaa_search_tex.h +++ b/src/video_core/smaa_search_tex.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2013 Jorge Jimenez (jorge@iryoku.com) // SPDX-FileCopyrightText: 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com) // SPDX-FileCopyrightText: 2013 Belen Masia (bmasia@unizar.es) @@ -5,8 +8,7 @@ // SPDX-FileCopyrightText: 2013 Diego Gutierrez (diegog@unizar.es) // SPDX-License-Identifier: MIT -#ifndef SEARCHTEX_H -#define SEARCHTEX_H +#pragma once #define SEARCHTEX_WIDTH 64 #define SEARCHTEX_HEIGHT 16 @@ -84,5 +86,3 @@ static const unsigned char searchTexBytes[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; - -#endif diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index 1998849e84..4603870bfa 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2014 Citra Emulator Project @@ -336,6 +336,24 @@ bool IsPixelFormatBCn(PixelFormat format) { } } +bool IsPixelFormatETC2(PixelFormat format) { + switch (format) { + case PixelFormat::ETC2_RGB_UNORM: + case PixelFormat::ETC2_RGBA_UNORM: + case PixelFormat::ETC2_RGB_PTA_UNORM: + case PixelFormat::ETC2_RGB_SRGB: + case PixelFormat::ETC2_RGBA_SRGB: + case PixelFormat::ETC2_RGB_PTA_SRGB: + case PixelFormat::EAC_R11_UNORM: + case PixelFormat::EAC_R11_SNORM: + case PixelFormat::EAC_R11G11_UNORM: + case PixelFormat::EAC_R11G11_SNORM: + return true; + default: + return false; + } +} + bool IsPixelFormatSRGB(PixelFormat format) { switch (format) { case PixelFormat::A8B8G8R8_SRGB: @@ -344,6 +362,9 @@ bool IsPixelFormatSRGB(PixelFormat format) { case PixelFormat::BC2_SRGB: case PixelFormat::BC3_SRGB: case PixelFormat::BC7_SRGB: + case PixelFormat::ETC2_RGB_SRGB: + case PixelFormat::ETC2_RGBA_SRGB: + case PixelFormat::ETC2_RGB_PTA_SRGB: case PixelFormat::ASTC_2D_4X4_SRGB: case PixelFormat::ASTC_2D_8X8_SRGB: case PixelFormat::ASTC_2D_8X5_SRGB: diff --git a/src/video_core/surface.h b/src/video_core/surface.h index a97bcb39b5..8e1973d3b9 100644 --- a/src/video_core/surface.h +++ b/src/video_core/surface.h @@ -5,6 +5,8 @@ #pragma once +#undef PIXEL_FORMAT_LIST + #include #include #include "common/assert.h" @@ -111,6 +113,16 @@ namespace VideoCore::Surface { PIXEL_FORMAT_ELEM(ASTC_2D_6X5_UNORM, 6, 5, 128) \ PIXEL_FORMAT_ELEM(ASTC_2D_6X5_SRGB, 6, 5, 128) \ PIXEL_FORMAT_ELEM(E5B9G9R9_FLOAT, 1, 1, 32) \ + PIXEL_FORMAT_ELEM(ETC2_RGB_UNORM, 4, 4, 64) \ + PIXEL_FORMAT_ELEM(ETC2_RGBA_UNORM, 4, 4, 128) \ + PIXEL_FORMAT_ELEM(ETC2_RGB_PTA_UNORM, 4, 4, 64) \ + PIXEL_FORMAT_ELEM(ETC2_RGB_SRGB, 4, 4, 64) \ + PIXEL_FORMAT_ELEM(ETC2_RGBA_SRGB, 4, 4, 128) \ + PIXEL_FORMAT_ELEM(ETC2_RGB_PTA_SRGB, 4, 4, 64) \ + PIXEL_FORMAT_ELEM(EAC_R11_UNORM, 4, 4, 64) \ + PIXEL_FORMAT_ELEM(EAC_R11_SNORM, 4, 4, 64) \ + PIXEL_FORMAT_ELEM(EAC_R11G11_UNORM, 4, 4, 128) \ + PIXEL_FORMAT_ELEM(EAC_R11G11_SNORM, 4, 4, 128) \ /* Depth formats */ \ PIXEL_FORMAT_ELEM(D32_FLOAT, 1, 1, 32) \ PIXEL_FORMAT_ELEM(D16_UNORM, 1, 1, 16) \ @@ -122,7 +134,7 @@ namespace VideoCore::Surface { PIXEL_FORMAT_ELEM(S8_UINT_D24_UNORM, 1, 1, 32) \ PIXEL_FORMAT_ELEM(D32_FLOAT_S8_UINT, 1, 1, 64) -enum class PixelFormat { +enum class PixelFormat : u32 { #define PIXEL_FORMAT_ELEM(name, ...) name, PIXEL_FORMAT_LIST #undef PIXEL_FORMAT_ELEM @@ -181,13 +193,18 @@ constexpr u32 BitsPerBlock(PixelFormat format) noexcept { } } -#undef PIXEL_FORMAT_LIST - /// Returns the sizer in bytes of the specified pixel format constexpr u32 BytesPerBlock(PixelFormat pixel_format) { return BitsPerBlock(pixel_format) / CHAR_BIT; } +} + +#include "video_core/gpu.h" +#include "video_core/textures/texture.h" + +namespace VideoCore::Surface { + SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_type); bool SurfaceTargetIsLayered(SurfaceTarget target); bool SurfaceTargetIsArray(SurfaceTarget target); @@ -198,6 +215,7 @@ SurfaceType GetFormatType(PixelFormat pixel_format); bool HasAlpha(PixelFormat pixel_format); bool IsPixelFormatASTC(PixelFormat format); bool IsPixelFormatBCn(PixelFormat format); +bool IsPixelFormatETC2(PixelFormat format); bool IsPixelFormatSRGB(PixelFormat format); bool IsPixelFormatInteger(PixelFormat format); bool IsPixelFormatSignedInteger(PixelFormat format); diff --git a/src/video_core/texture_cache/descriptor_table.h b/src/video_core/texture_cache/descriptor_table.h index 1bad83fb4a..e40c128ab5 100644 --- a/src/video_core/texture_cache/descriptor_table.h +++ b/src/video_core/texture_cache/descriptor_table.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -6,37 +9,39 @@ #include #include +#include "common/alignment.h" #include "common/common_types.h" #include "common/div_ceil.h" +#include "common/assert.h" #include "video_core/memory_manager.h" #include "video_core/rasterizer_interface.h" namespace VideoCommon { -template +template class DescriptorTable { public: - explicit DescriptorTable(Tegra::MemoryManager& gpu_memory_) : gpu_memory{gpu_memory_} {} - - [[nodiscard]] bool Synchronize(GPUVAddr gpu_addr, u32 limit) { - [[likely]] if (current_gpu_addr == gpu_addr && current_limit == limit) { return false; } - Refresh(gpu_addr, limit); - return true; + [[nodiscard]] bool Synchronize(GPUVAddr gpu_addr, u32 limit) noexcept { + bool ret = !(current_gpu_addr == gpu_addr && current_limit == limit); + if (ret) { + Refresh(gpu_addr, limit); + } + return ret; } void Invalidate() noexcept { std::ranges::fill(read_descriptors, 0); } - [[nodiscard]] std::pair Read(u32 index) { + [[nodiscard]] std::pair Read(Tegra::MemoryManager const& gpu_memory, u32 index) noexcept { DEBUG_ASSERT(index <= current_limit); - const GPUVAddr gpu_addr = current_gpu_addr + index * sizeof(Descriptor); - std::pair result; - gpu_memory.ReadBlockUnsafe(gpu_addr, &result.first, sizeof(Descriptor)); - if (IsDescriptorRead(index)) { + const GPUVAddr gpu_addr = current_gpu_addr + index * sizeof(T); + std::pair result; + gpu_memory.ReadBlockUnsafe(gpu_addr, std::addressof(result.first), sizeof(T)); + if ((read_descriptors[index / 64] & (1ULL << (index % 64))) != 0) { result.second = result.first != descriptors[index]; } else { - MarkDescriptorAsRead(index); + read_descriptors[index / 64] |= 1ULL << (index % 64); result.second = true; } if (result.second) { @@ -45,34 +50,24 @@ public: return result; } - [[nodiscard]] u32 Limit() const noexcept { - return current_limit; - } - -private: - void Refresh(GPUVAddr gpu_addr, u32 limit) { + void Refresh(GPUVAddr gpu_addr, u32 limit) noexcept { current_gpu_addr = gpu_addr; current_limit = limit; - - const size_t num_descriptors = static_cast(limit) + 1; - read_descriptors.clear(); - read_descriptors.resize(Common::DivCeil(num_descriptors, 64U), 0); + // Mario Brothership reallocates a lot of times, so use aggressive pre-alloc sizes + // std::vector by default uses quadratic growth, but that isn't even enough to satisfy brothership + const size_t num_descriptors = ((limit + 0x80000) & (~0x7ffff)) + 1; + size_t old_size = read_descriptors.size(); + read_descriptors.resize(Common::DivCeil(num_descriptors, 64U)); + old_size = (std::min)(old_size, read_descriptors.size()); + std::fill(read_descriptors.begin(), read_descriptors.begin() + old_size, 0); + // descriptors.resize(num_descriptors); } - void MarkDescriptorAsRead(u32 index) noexcept { - read_descriptors[index / 64] |= 1ULL << (index % 64); - } - - [[nodiscard]] bool IsDescriptorRead(u32 index) const noexcept { - return (read_descriptors[index / 64] & (1ULL << (index % 64))) != 0; - } - - Tegra::MemoryManager& gpu_memory; + std::vector read_descriptors; + std::vector descriptors; GPUVAddr current_gpu_addr{}; u32 current_limit{}; - std::vector read_descriptors; - std::vector descriptors; }; } // namespace VideoCommon diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp index a9e03b375d..d6e019cddf 100644 --- a/src/video_core/texture_cache/format_lookup_table.cpp +++ b/src/video_core/texture_cache/format_lookup_table.cpp @@ -191,6 +191,29 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red, return PixelFormat::BC6H_SFLOAT; case Hash(TextureFormat::BC6H_U16, FLOAT): return PixelFormat::BC6H_UFLOAT; + /* ETC2 */ + case Hash(TextureFormat::ETC2_RGB, UNORM, LINEAR): + return PixelFormat::ETC2_RGB_UNORM; + case Hash(TextureFormat::ETC2_RGB_PTA, UNORM, LINEAR): + return PixelFormat::ETC2_RGB_PTA_UNORM; + case Hash(TextureFormat::ETC2_RGBA, UNORM, LINEAR): + return PixelFormat::ETC2_RGBA_UNORM; + case Hash(TextureFormat::ETC2_RGB, UNORM, SRGB): + return PixelFormat::ETC2_RGB_SRGB; + case Hash(TextureFormat::ETC2_RGB_PTA, UNORM, SRGB): + return PixelFormat::ETC2_RGB_PTA_SRGB; + case Hash(TextureFormat::ETC2_RGBA, UNORM, SRGB): + return PixelFormat::ETC2_RGBA_SRGB; + /* EAC */ + case Hash(TextureFormat::EAC, UNORM): + return PixelFormat::EAC_R11_UNORM; + case Hash(TextureFormat::EAC, SNORM): + return PixelFormat::EAC_R11_SNORM; + case Hash(TextureFormat::EACX2, UNORM): + return PixelFormat::EAC_R11G11_UNORM; + case Hash(TextureFormat::EACX2, SNORM): + return PixelFormat::EAC_R11G11_SNORM; + /* ASTC */ case Hash(TextureFormat::ASTC_2D_4X4, UNORM, LINEAR): return PixelFormat::ASTC_2D_4X4_UNORM; case Hash(TextureFormat::ASTC_2D_4X4, UNORM, SRGB): diff --git a/src/video_core/texture_cache/formatter.cpp b/src/video_core/texture_cache/formatter.cpp index 6e89864f45..2bdb811e90 100644 --- a/src/video_core/texture_cache/formatter.cpp +++ b/src/video_core/texture_cache/formatter.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -40,15 +40,15 @@ std::string Name(const ImageBase& image) { } switch (image.info.type) { case ImageType::e1D: - return fmt::format("Image 1D 0x{:x} {}{}", gpu_addr, width, resource); + return fmt::format("Image 1D {:#x} {}{}", gpu_addr, width, resource); case ImageType::e2D: - return fmt::format("Image 2D 0x{:x} {}x{}{}", gpu_addr, width, height, resource); + return fmt::format("Image 2D {:#x} {}x{}{}", gpu_addr, width, height, resource); case ImageType::e3D: - return fmt::format("Image 2D 0x{:x} {}x{}x{}{}", gpu_addr, width, height, depth, resource); + return fmt::format("Image 2D {:#x} {}x{}x{}{}", gpu_addr, width, height, depth, resource); case ImageType::Linear: - return fmt::format("Image Linear 0x{:x} {}x{}", gpu_addr, width, height); + return fmt::format("Image Linear {:#x} {}x{}", gpu_addr, width, height); case ImageType::Buffer: - return fmt::format("Buffer 0x{:x} {}", image.gpu_addr, image.info.size.width); + return fmt::format("Buffer {:#x} {}", image.gpu_addr, image.info.size.width); } return "Invalid"; } @@ -63,25 +63,25 @@ std::string Name(const ImageViewBase& image_view, GPUVAddr addr) { const std::string level = num_levels > 1 ? fmt::format(":{}", num_levels) : ""; switch (image_view.type) { case ImageViewType::e1D: - return fmt::format("ImageView 1D {:#X} {}{}", addr, width, level); + return fmt::format("ImageView 1D {:#x} {}{}", addr, width, level); case ImageViewType::e2D: - return fmt::format("ImageView 2D {:#X} {}x{}{}", addr, width, height, level); + return fmt::format("ImageView 2D {:#x} {}x{}{}", addr, width, height, level); case ImageViewType::Cube: - return fmt::format("ImageView Cube {:#X} {}x{}{}", addr, width, height, level); + return fmt::format("ImageView Cube {:#x} {}x{}{}", addr, width, height, level); case ImageViewType::e3D: - return fmt::format("ImageView 3D {:#X} {}x{}x{}{}", addr, width, height, depth, level); + return fmt::format("ImageView 3D {:#x} {}x{}x{}{}", addr, width, height, depth, level); case ImageViewType::e1DArray: - return fmt::format("ImageView 1DArray {:#X} {}{}|{}", addr, width, level, num_layers); + return fmt::format("ImageView 1DArray {:#x} {}{}|{}", addr, width, level, num_layers); case ImageViewType::e2DArray: - return fmt::format("ImageView 2DArray {:#X} {}x{}{}|{}", addr, width, height, level, + return fmt::format("ImageView 2DArray {:#x} {}x{}{}|{}", addr, width, height, level, num_layers); case ImageViewType::CubeArray: - return fmt::format("ImageView CubeArray {:#X} {}x{}{}|{}", addr, width, height, level, + return fmt::format("ImageView CubeArray {:#x} {}x{}{}|{}", addr, width, height, level, num_layers); case ImageViewType::Rect: - return fmt::format("ImageView Rect {:#X} {}x{}{}", addr, width, height, level); + return fmt::format("ImageView Rect {:#x} {}x{}{}", addr, width, height, level); case ImageViewType::Buffer: - return fmt::format("BufferView {:#X} {}", addr, width); + return fmt::format("BufferView {:#x} {}", addr, width); } return "Invalid"; } diff --git a/src/video_core/texture_cache/formatter.h b/src/video_core/texture_cache/formatter.h index 33c32645a2..3e2e749274 100644 --- a/src/video_core/texture_cache/formatter.h +++ b/src/video_core/texture_cache/formatter.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -17,210 +20,9 @@ struct fmt::formatter : fmt::formatter 1) { - LOG_WARNING(HW_GPU, "MSAA image downloads are not implemented"); - return false; - } return true; } diff --git a/src/video_core/texture_cache/image_base.h b/src/video_core/texture_cache/image_base.h index 0587d7b724..79fc8505d6 100644 --- a/src/video_core/texture_cache/image_base.h +++ b/src/video_core/texture_cache/image_base.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -64,7 +67,7 @@ struct ImageBase { void InsertView(const ImageViewInfo& view_info, ImageViewId image_view_id); - [[nodiscard]] bool IsSafeDownload() const noexcept; + [[nodiscard]] bool IsSafeGpuCopy() const noexcept; [[nodiscard]] bool Overlaps(VAddr overlap_cpu_addr, size_t overlap_size) const noexcept { const VAddr overlap_end = overlap_cpu_addr + overlap_size; diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index ef02972209..8b59eb6841 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 8a9a32f44a..d1728603bf 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -7,8 +10,8 @@ namespace VideoCommon { TextureCacheChannelInfo::TextureCacheChannelInfo(Tegra::Control::ChannelState& state) noexcept - : ChannelInfo(state), graphics_image_table{gpu_memory}, graphics_sampler_table{gpu_memory}, - compute_image_table{gpu_memory}, compute_sampler_table{gpu_memory} {} + : ChannelInfo(state) +{} template class VideoCommon::ChannelSetupCaches; diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 5a1c680830..e92e54f8ce 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -9,11 +9,12 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include #include "common/alignment.h" #include "common/settings.h" +#include "common/slot_vector.h" #include "video_core/control/channel_state.h" #include "video_core/dirty_flags.h" #include "video_core/engines/kepler_compute.h" @@ -128,18 +129,18 @@ void TextureCache

::RunGarbageCollector() { if (num_iterations == 0) { return true; } + --num_iterations; auto& image = slot_images[image_id]; if (True(image.flags & ImageFlagBits::IsDecoding)) { return false; } - const bool must_download = image.IsSafeDownload() && False(image.flags & ImageFlagBits::BadOverlap); + const bool must_download = IsDownloadable(image) && False(image.flags & ImageFlagBits::BadOverlap); if ((!aggressive_mode && True(image.flags & ImageFlagBits::CostlyLoad)) || (!high_priority_mode && must_download)) { return false; } - --num_iterations; if (must_download) { auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes); - const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info)); + const auto copies = FullDownloadCopies(image.info); image.DownloadMemory(map, copies); runtime.Finish(); SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span, swizzle_data_buffer); @@ -204,8 +205,8 @@ typename P::ImageView& TextureCache

::GetImageView(ImageViewId id) noexcept { template typename P::ImageView& TextureCache

::GetImageView(u32 index) noexcept { - const auto image_view_id = VisitImageView(channel_state->graphics_image_table, - channel_state->graphics_image_view_ids, index); + // Not compute! + const auto image_view_id = VisitImageView(index, false); return slot_image_views[image_view_id]; } @@ -215,16 +216,25 @@ void TextureCache

::MarkModification(ImageId id) noexcept { } template -template -void TextureCache

::FillGraphicsImageViews(std::span views) { - FillImageViews(channel_state->graphics_image_table, - channel_state->graphics_image_view_ids, views); -} - -template -void TextureCache

::FillComputeImageViews(std::span views) { - FillImageViews(channel_state->compute_image_table, channel_state->compute_image_view_ids, - views); +void TextureCache

::FillImageViews(std::span views, bool compute, bool blacklist) { + bool has_blacklisted = false; + do { + has_deleted_images = false; + if (blacklist) { + has_blacklisted = false; + } + for (ImageViewInOut& view : views) { + view.id = VisitImageView(view.index, compute); + if (blacklist) { + if (view.blacklist && view.id != NULL_IMAGE_VIEW_ID) { + const ImageViewBase& image_view = slot_image_views[view.id]; + auto& image = slot_images[image_view.image_id]; + has_blacklisted |= ScaleDown(image); + image.scale_rating = 0; + } + } + } + } while (has_deleted_images || (blacklist && has_blacklisted)); } template @@ -292,41 +302,25 @@ void TextureCache

::CheckFeedbackLoop(std::span views) { } template -typename P::Sampler* TextureCache

::GetGraphicsSampler(u32 index) { - return &slot_samplers[GetGraphicsSamplerId(index)]; +typename P::Sampler* TextureCache

::GetSampler(u32 index, bool compute) { + return &slot_samplers[GetSamplerId(index, compute)]; } template -typename P::Sampler* TextureCache

::GetComputeSampler(u32 index) { - return &slot_samplers[GetComputeSamplerId(index)]; -} - -template -SamplerId TextureCache

::GetGraphicsSamplerId(u32 index) { - if (index > channel_state->graphics_sampler_table.Limit()) { +SamplerId TextureCache

::GetSamplerId(u32 index, bool compute) { + auto& table = compute ? channel_state->compute_sampler_table : channel_state->graphics_sampler_table; + if (index > table.current_limit) { LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index); return NULL_SAMPLER_ID; } - const auto [descriptor, is_new] = channel_state->graphics_sampler_table.Read(index); - SamplerId& id = channel_state->graphics_sampler_ids[index]; + auto const map_index = index | (compute ? Common::SlotId::TAGGED_VALUE : 0); + auto const [descriptor, is_new] = table.Read(*gpu_memory, index); if (is_new) { - id = FindSampler(descriptor); + auto const id = FindSampler(descriptor, compute); + channel_state->sampler_ids.insert_or_assign(map_index, id); + return id; } - return id; -} - -template -SamplerId TextureCache

::GetComputeSamplerId(u32 index) { - if (index > channel_state->compute_sampler_table.Limit()) { - LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index); - return NULL_SAMPLER_ID; - } - const auto [descriptor, is_new] = channel_state->compute_sampler_table.Read(index); - SamplerId& id = channel_state->compute_sampler_ids[index]; - if (is_new) { - id = FindSampler(descriptor); - } - return id; + return channel_state->sampler_ids.find(map_index)->second; } template @@ -340,45 +334,31 @@ typename P::Sampler& TextureCache

::GetSampler(SamplerId id) noexcept { } template -void TextureCache

::SynchronizeGraphicsDescriptors() { - using SamplerBinding = Tegra::Engines::Maxwell3D::Regs::SamplerBinding; - const bool linked_tsc = maxwell3d->regs.sampler_binding == SamplerBinding::ViaHeaderBinding; - const u32 tic_limit = maxwell3d->regs.tex_header.limit; - const u32 tsc_limit = linked_tsc ? tic_limit : maxwell3d->regs.tex_sampler.limit; - bool bindings_changed = false; - if (channel_state->graphics_sampler_table.Synchronize(maxwell3d->regs.tex_sampler.Address(), - tsc_limit)) { - channel_state->graphics_sampler_ids.resize(tsc_limit + 1, CORRUPT_ID); - bindings_changed = true; - } - if (channel_state->graphics_image_table.Synchronize(maxwell3d->regs.tex_header.Address(), - tic_limit)) { - channel_state->graphics_image_view_ids.resize(tic_limit + 1, CORRUPT_ID); - bindings_changed = true; - } - if (bindings_changed) { - ++texture_bindings_serial; - } -} - -template -void TextureCache

::SynchronizeComputeDescriptors() { - const bool linked_tsc = kepler_compute->launch_description.linked_tsc; - const u32 tic_limit = kepler_compute->regs.tic.limit; - const u32 tsc_limit = linked_tsc ? tic_limit : kepler_compute->regs.tsc.limit; - const GPUVAddr tsc_gpu_addr = kepler_compute->regs.tsc.Address(); - bool bindings_changed = false; - if (channel_state->compute_sampler_table.Synchronize(tsc_gpu_addr, tsc_limit)) { - channel_state->compute_sampler_ids.resize(tsc_limit + 1, CORRUPT_ID); - bindings_changed = true; - } - if (channel_state->compute_image_table.Synchronize(kepler_compute->regs.tic.Address(), - tic_limit)) { - channel_state->compute_image_view_ids.resize(tic_limit + 1, CORRUPT_ID); - bindings_changed = true; - } - if (bindings_changed) { - ++texture_bindings_serial; +void TextureCache

::SynchronizeDescriptors(bool compute) { + if (compute) { + const bool linked_tsc = kepler_compute->launch_description.linked_tsc; + const u32 tic_limit = kepler_compute->regs.tic.limit; + const u32 tsc_limit = linked_tsc ? tic_limit : kepler_compute->regs.tsc.limit; + bool bindings_changed = false; + if (channel_state->compute_sampler_table.Synchronize(kepler_compute->regs.tsc.Address(), tsc_limit)) + bindings_changed = true; + if (channel_state->compute_image_table.Synchronize(kepler_compute->regs.tic.Address(), tic_limit)) + bindings_changed = true; + if (bindings_changed) { + ++texture_bindings_serial; + } + } else { + const bool linked_tsc = maxwell3d->regs.sampler_binding == Tegra::Engines::Maxwell3D::Regs::SamplerBinding::ViaHeaderBinding; + const u32 tic_limit = maxwell3d->regs.tex_header.limit; + const u32 tsc_limit = linked_tsc ? tic_limit : maxwell3d->regs.tex_sampler.limit; + bool bindings_changed = false; + if (channel_state->graphics_sampler_table.Synchronize(maxwell3d->regs.tex_sampler.Address(), tsc_limit)) + bindings_changed = true; + if (channel_state->graphics_image_table.Synchronize(maxwell3d->regs.tex_header.Address(), tic_limit)) + bindings_changed = true; + if (bindings_changed) { + ++texture_bindings_serial; + } } } @@ -557,47 +537,32 @@ typename P::Framebuffer* TextureCache

::GetFramebuffer() { } template -template -void TextureCache

::FillImageViews(DescriptorTable& table, - std::span cached_image_view_ids, - std::span views) { - bool has_blacklisted = false; - do { - has_deleted_images = false; - if constexpr (has_blacklists) { - has_blacklisted = false; - } - for (ImageViewInOut& view : views) { - view.id = VisitImageView(table, cached_image_view_ids, view.index); - if constexpr (has_blacklists) { - if (view.blacklist && view.id != NULL_IMAGE_VIEW_ID) { - const ImageViewBase& image_view{slot_image_views[view.id]}; - auto& image = slot_images[image_view.image_id]; - has_blacklisted |= ScaleDown(image); - image.scale_rating = 0; - } - } - } - } while (has_deleted_images || (has_blacklists && has_blacklisted)); -} - -template -ImageViewId TextureCache

::VisitImageView(DescriptorTable& table, - std::span cached_image_view_ids, - u32 index) { - if (index > table.Limit()) { +ImageViewId TextureCache

::VisitImageView(u32 index, bool compute) { + auto& table = compute ? channel_state->compute_image_table : channel_state->graphics_image_table; + if (index > table.current_limit) { LOG_DEBUG(HW_GPU, "Invalid image view index={}", index); return NULL_IMAGE_VIEW_ID; } - const auto [descriptor, is_new] = table.Read(index); - ImageViewId& image_view_id = cached_image_view_ids[index]; + auto const map_index = index | (compute ? Common::SlotId::TAGGED_VALUE : 0); + // Is new (on the tegra engine side)? + auto const [descriptor, is_new] = table.Read(*gpu_memory, index); if (is_new) { - image_view_id = FindImageView(descriptor); + if (IsValidEntry(*gpu_memory, descriptor)) { + // Is new (registered view) on the texture cache side? + const auto [pair, is_new_tc] = channel_state->image_views.try_emplace(descriptor); + if (is_new_tc) + pair->second = CreateImageView(descriptor); + PrepareImageView(pair->second, false, false); + channel_state->image_view_ids.insert_or_assign(map_index, pair->second); + return pair->second; + } + channel_state->image_view_ids.insert_or_assign(map_index, NULL_IMAGE_VIEW_ID); + return NULL_IMAGE_VIEW_ID; } - if (image_view_id != NULL_IMAGE_VIEW_ID) { - PrepareImageView(image_view_id, false, false); - } - return image_view_id; + auto const it = channel_state->image_view_ids.find(map_index); + if (it->second != NULL_IMAGE_VIEW_ID) + PrepareImageView(it->second, false, false); + return it->second; } template @@ -612,6 +577,7 @@ FramebufferId TextureCache

::GetFramebufferId(const RenderTargets& key) { return id ? &slot_image_views[id] : nullptr; }); ImageView* const depth_buffer = key.depth_buffer_id ? &slot_image_views[key.depth_buffer_id] : nullptr; + runtime.FlushDeferredClear(); framebuffer_id = slot_framebuffers.insert(runtime, color_buffers, depth_buffer, key); return framebuffer_id; } @@ -629,11 +595,26 @@ void TextureCache

::WriteMemory(DAddr cpu_addr, size_t size) { }); } +template +bool TextureCache

::IsDownloadable(const ImageBase& image) const noexcept { + if (!image.IsSafeGpuCopy()) { + return false; + } + if (image.info.num_samples == 1) { + return true; + } + if constexpr (P::HAS_MSAA_DOWNLOADS) { + return runtime.CanDownloadMsaa(image.info); + } else { + return false; + } +} + template void TextureCache

::DownloadMemory(DAddr cpu_addr, size_t size) { boost::container::small_vector images; - ForEachImageInRegion(cpu_addr, size, [&images](ImageId image_id, ImageBase& image) { - if (!image.IsSafeDownload()) { + ForEachImageInRegion(cpu_addr, size, [this, &images](ImageId image_id, ImageBase& image) { + if (!IsDownloadable(image)) { return; } image.flags &= ~ImageFlagBits::GpuModified; @@ -648,7 +629,7 @@ void TextureCache

::DownloadMemory(DAddr cpu_addr, size_t size) { for (const ImageId image_id : images) { Image& image = slot_images[image_id]; auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes); - const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info)); + const auto copies = FullDownloadCopies(image.info); image.DownloadMemory(map, copies); runtime.Finish(); SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span, @@ -912,7 +893,7 @@ void TextureCache

::CommitAsyncFlushes() { for (const PendingDownload& download_info : download_ids) { if (download_info.is_swizzle) { Image& image = slot_images[download_info.object_id]; - const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info)); + const auto copies = FullDownloadCopies(image.info); image.DownloadMemory(download_map, copies); download_map.offset += Common::AlignUp(image.unswizzled_size_bytes, 64); } @@ -945,7 +926,7 @@ void TextureCache

::PopAsyncFlushes() { auto& download_buffer = download_map[download_info.async_buffer_id]; if (download_info.is_swizzle) { const ImageBase& image = slot_images[download_info.object_id]; - const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info)); + const auto copies = FullDownloadCopies(image.info); download_buffer.offset -= Common::AlignUp(image.unswizzled_size_bytes, 64); std::span download_span = download_buffer.mapped_span.subspan(download_buffer.offset); @@ -983,7 +964,7 @@ void TextureCache

::PopAsyncFlushes() { continue; } Image& image = slot_images[download_info.object_id]; - const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info)); + const auto copies = FullDownloadCopies(image.info); image.DownloadMemory(download_map, copies); download_map.offset += image.unswizzled_size_bytes; } @@ -996,7 +977,7 @@ void TextureCache

::PopAsyncFlushes() { continue; } const ImageBase& image = slot_images[download_info.object_id]; - const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info)); + const auto copies = FullDownloadCopies(image.info); SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, download_span, swizzle_data_buffer); download_map.offset += image.unswizzled_size_bytes; download_span = download_span.subspan(image.unswizzled_size_bytes); @@ -1179,7 +1160,7 @@ void TextureCache

::UploadImageContents(Image& image, StagingBuffer& staging) gpu_memory->ReadBlock(gpu_addr, mapped_span.data(), mapped_span.size_bytes(), VideoCommon::CacheType::NoTextureCache); const auto uploads = FullUploadSwizzles(image.info); - runtime.AccelerateImageUpload(image, staging, FixSmallVectorADL(uploads), 0, 0); + runtime.AccelerateImageUpload(image, staging, uploads, 0, 0); return; } @@ -1187,28 +1168,15 @@ void TextureCache

::UploadImageContents(Image& image, StagingBuffer& staging) *gpu_memory, gpu_addr, image.guest_size_bytes, &swizzle_data_buffer); if (True(image.flags & ImageFlagBits::Converted)) { unswizzle_data_buffer.resize_destructive(image.unswizzled_size_bytes); - auto copies = FixSmallVectorADL(UnswizzleImage(*gpu_memory, gpu_addr, image.info, swizzle_data, unswizzle_data_buffer)); + auto copies = UnswizzleImage(*gpu_memory, gpu_addr, image.info, swizzle_data, unswizzle_data_buffer); ConvertImage(unswizzle_data_buffer, image.info, mapped_span, copies); image.UploadMemory(staging, copies); } else { - const auto copies = FixSmallVectorADL(UnswizzleImage(*gpu_memory, gpu_addr, image.info, swizzle_data, mapped_span)); + const auto copies = UnswizzleImage(*gpu_memory, gpu_addr, image.info, swizzle_data, mapped_span); image.UploadMemory(staging, copies); } } -template -ImageViewId TextureCache

::FindImageView(const TICEntry& config) { - if (!IsValidEntry(*gpu_memory, config)) { - return NULL_IMAGE_VIEW_ID; - } - const auto [pair, is_new] = channel_state->image_views.try_emplace(config); - ImageViewId& image_view_id = pair->second; - if (is_new) { - image_view_id = CreateImageView(config); - } - return image_view_id; -} - template ImageViewId TextureCache

::CreateImageView(const TICEntry& config) { const ImageInfo info(config); @@ -1350,10 +1318,10 @@ void TextureCache

::InvalidateScale(Image& image) { image.image_view_infos.clear(); for (size_t c : active_channel_ids) { auto& channel_info = channel_storage[c]; - if constexpr (ENABLE_VALIDATION) { - std::ranges::fill(channel_info.graphics_image_view_ids, CORRUPT_ID); - std::ranges::fill(channel_info.compute_image_view_ids, CORRUPT_ID); - } + + if constexpr (ENABLE_VALIDATION) + for (auto& e : channel_info.image_view_ids) + e.second = CORRUPT_ID; channel_info.graphics_image_table.Invalidate(); channel_info.compute_image_table.Invalidate(); } @@ -1433,7 +1401,7 @@ void TextureCache

::TickAsyncDecode() { auto staging = runtime.UploadStagingBuffer(MapSizeBytes(image)); std::memcpy(staging.mapped_span.data(), async_decode->decoded_data.data(), async_decode->decoded_data.size()); - image.UploadMemory(staging, FixSmallVectorADL(async_decode->copies)); + image.UploadMemory(staging, async_decode->copies); image.flags &= ~ImageFlagBits::IsDecoding; has_uploads = true; i = async_decodes.erase(i); @@ -1501,7 +1469,7 @@ void TextureCache

::TickAsyncUnswizzle() { if (z_count > 0) { const auto uploads = FullUploadSwizzles(task.info); - runtime.AccelerateImageUpload(image, task.staging_buffer, FixSmallVectorADL(uploads), z_start, z_count); + runtime.AccelerateImageUpload(image, task.staging_buffer, uploads, z_start, z_count); task.last_submitted_offset += (static_cast(z_count) * task.bytes_per_slice); } } @@ -1524,12 +1492,12 @@ template bool TextureCache

::ScaleUp(Image& image) { const bool has_copy = image.HasScaled(); const bool rescaled = image.ScaleUp(); + if (!has_copy && image.HasScaled()) { + total_used_memory += GetScaledImageSizeBytes(image); + } if (!rescaled) { return false; } - if (!has_copy) { - total_used_memory += GetScaledImageSizeBytes(image); - } InvalidateScale(image); return true; } @@ -1557,7 +1525,7 @@ ImageId TextureCache

::InsertImage(const ImageInfo& info, GPUVAddr gpu_addr, cpu_addr = std::optional(fake_addr); } } - ASSERT_MSG(cpu_addr, "Tried to insert an image to an invalid gpu_addr=0x{:x}", gpu_addr); + ASSERT_MSG(cpu_addr, "Tried to insert an image to an invalid gpu_addr={:#x}", gpu_addr); const ImageId image_id = JoinImages(info, gpu_addr, *cpu_addr); const Image& image = slot_images[image_id]; // Using "image.gpu_addr" instead of "gpu_addr" is important because it might be different @@ -1739,7 +1707,10 @@ ImageId TextureCache

::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DA for (const auto& copy_object : join_copies_to_do) { Image& overlap = slot_images[copy_object.id]; if (copy_object.is_alias) { - if (!overlap.IsSafeDownload()) { + if (!overlap.IsSafeGpuCopy()) { + continue; + } + if (overlap.info.num_samples != new_image.info.num_samples) { continue; } const auto alias_pointer = join_alias_indices.find(copy_object.id); @@ -1759,9 +1730,9 @@ ImageId TextureCache

::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, DA const u32 down_shift = can_rescale ? resolution.down_shift : 0; auto copies = MakeShrinkImageCopies(new_info, overlap.info, base, up_scale, down_shift); if (overlap.info.num_samples != new_image.info.num_samples) { - runtime.CopyImageMSAA(new_image, overlap, FixSmallVectorADL(copies)); + runtime.CopyImageMSAA(new_image, overlap, copies); } else { - runtime.CopyImage(new_image, overlap, FixSmallVectorADL(copies)); + runtime.CopyImage(new_image, overlap, copies); } new_image.modification_tick = overlap.modification_tick; } @@ -1918,95 +1889,17 @@ std::pair TextureCache

::PrepareDmaImage(ImageId dst_id, GPUVAddr ba } template -SamplerId TextureCache

::FindSampler(const TSCEntry& config) { +SamplerId TextureCache

::FindSampler(const TSCEntry& config, bool compute) { if (std::ranges::all_of(config.raw, [](u64 value) { return value == 0; })) { return NULL_SAMPLER_ID; } const auto [pair, is_new] = channel_state->samplers.try_emplace(config); if (is_new) { pair->second = slot_samplers.insert(runtime, config); - EnforceSamplerBudget(); } return pair->second; } -template -std::optional TextureCache

::QuerySamplerBudget() const { - if constexpr (requires { runtime.GetSamplerHeapBudget(); }) { - return runtime.GetSamplerHeapBudget(); - } else { - return std::nullopt; - } -} - -template -void TextureCache

::EnforceSamplerBudget() { - const auto budget = QuerySamplerBudget(); - if (!budget) { - return; - } - if (slot_samplers.size() < *budget) { - return; - } - if (!channel_state) { - return; - } - if (last_sampler_gc_frame == frame_tick) { - return; - } - last_sampler_gc_frame = frame_tick; - TrimInactiveSamplers(*budget); -} - -template -void TextureCache

::TrimInactiveSamplers(size_t budget) { - if (channel_state->samplers.empty()) { - return; - } - constexpr size_t SAMPLER_GC_SLACK = 1024; - auto mark_active = [](auto& set, SamplerId id) { - if (!id || id == CORRUPT_ID || id == NULL_SAMPLER_ID) { - return; - } - set.insert(id); - }; - ankerl::unordered_dense::set active; - active.reserve(channel_state->graphics_sampler_ids.size() + - channel_state->compute_sampler_ids.size()); - for (const SamplerId id : channel_state->graphics_sampler_ids) { - mark_active(active, id); - } - for (const SamplerId id : channel_state->compute_sampler_ids) { - mark_active(active, id); - } - - size_t removed = 0; - auto& sampler_map = channel_state->samplers; - for (auto it = sampler_map.begin(); it != sampler_map.end();) { - const SamplerId sampler_id = it->second; - if (!sampler_id || sampler_id == CORRUPT_ID) { - it = sampler_map.erase(it); - continue; - } - if (active.find(sampler_id) != active.end()) { - ++it; - continue; - } - slot_samplers.erase(sampler_id); - it = sampler_map.erase(it); - ++removed; - if (slot_samplers.size() + SAMPLER_GC_SLACK <= budget) { - break; - } - } - - if (removed != 0) { - LOG_WARNING(HW_GPU, - "Sampler cache exceeded {} entries on this driver; reclaimed {} inactive samplers", - budget, removed); - } -} - template ImageViewId TextureCache

::FindColorBuffer(size_t index) { const auto& regs = maxwell3d->regs; @@ -2243,8 +2136,7 @@ ImageViewId TextureCache

::FindOrEmplaceImageView(ImageId image_id, const Imag if (const ImageViewId image_view_id = image.FindView(info); image_view_id) { return image_view_id; } - const ImageViewId image_view_id = - slot_image_views.insert(runtime, info, image_id, image, slot_images); + const ImageViewId image_view_id = slot_image_views.insert(runtime, info, image_id, image, slot_images); image.InsertView(info, image_view_id); return image_view_id; } @@ -2298,16 +2190,16 @@ void TextureCache

::UnregisterImage(ImageId image_id) { image.flags &= ~ImageFlagBits::BadOverlap; lru_cache.Free(image.lru_index); const auto& clear_page_table = - [image_id](u64 page, ankerl::unordered_dense::map, Common::IdentityHash>& selected_page_table) { + [image_id](u64 page, ::Common::unordered_map, Common::IdentityHash>& selected_page_table) { const auto page_it = selected_page_table.find(page); if (page_it == selected_page_table.end()) { - ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << YUZU_PAGEBITS); + ASSERT_MSG(false, "Unregistering unregistered page={:#x}", page << YUZU_PAGEBITS); return; } std::vector& image_ids = page_it->second; const auto vector_it = std::ranges::find(image_ids, image_id); if (vector_it == image_ids.end()) { - ASSERT_MSG(false, "Unregistering unregistered image in page=0x{:x}", + ASSERT_MSG(false, "Unregistering unregistered image in page={:#x}", page << YUZU_PAGEBITS); return; } @@ -2321,13 +2213,13 @@ void TextureCache

::UnregisterImage(ImageId image_id) { ForEachCPUPage(image.cpu_addr, image.guest_size_bytes, [this, map_id](u64 page) { const auto page_it = page_table.find(page); if (page_it == page_table.end()) { - ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << YUZU_PAGEBITS); + ASSERT_MSG(false, "Unregistering unregistered page={:#x}", page << YUZU_PAGEBITS); return; } std::vector& image_map_ids = page_it->second; const auto vector_it = std::ranges::find(image_map_ids, map_id); if (vector_it == image_map_ids.end()) { - ASSERT_MSG(false, "Unregistering unregistered image in page=0x{:x}", + ASSERT_MSG(false, "Unregistering unregistered image in page={:#x}", page << YUZU_PAGEBITS); return; } @@ -2349,7 +2241,7 @@ void TextureCache

::UnregisterImage(ImageId image_id) { ForEachCPUPage(cpu_addr, size, [this, image_id](u64 page) { const auto page_it = page_table.find(page); if (page_it == page_table.end()) { - ASSERT_MSG(false, "Unregistering unregistered page=0x{:x}", page << YUZU_PAGEBITS); + ASSERT_MSG(false, "Unregistering unregistered page={:#x}", page << YUZU_PAGEBITS); return; } std::vector& image_map_ids = page_it->second; @@ -2437,7 +2329,7 @@ void TextureCache

::DeleteImage(ImageId image_id, bool immediate_delete) { const GPUVAddr gpu_addr = image.gpu_addr; const auto alloc_it = image_allocs_table.find(gpu_addr); if (alloc_it == image_allocs_table.end()) { - ASSERT_MSG(false, "Trying to delete an image alloc that does not exist in address 0x{:x}", + ASSERT_MSG(false, "Trying to delete an image alloc that does not exist in address {:#x}", gpu_addr); return; } @@ -2504,10 +2396,9 @@ void TextureCache

::DeleteImage(ImageId image_id, bool immediate_delete) { } for (size_t c : active_channel_ids) { auto& channel_info = channel_storage[c]; - if constexpr (ENABLE_VALIDATION) { - std::ranges::fill(channel_info.graphics_image_view_ids, CORRUPT_ID); - std::ranges::fill(channel_info.compute_image_view_ids, CORRUPT_ID); - } + if constexpr (ENABLE_VALIDATION) + for (auto& e : channel_info.image_view_ids) + e.second = CORRUPT_ID; channel_info.graphics_image_table.Invalidate(); channel_info.compute_image_table.Invalidate(); } @@ -2532,6 +2423,7 @@ void TextureCache

::RemoveImageViewReferences(std::span rem template void TextureCache

::RemoveFramebuffers(std::span removed_views) { + runtime.FlushDeferredClear(); auto it = framebuffers.begin(); while (it != framebuffers.end()) { if (it->first.Contains(removed_views)) { diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 47f52c5c99..8a2318cc72 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -14,9 +14,11 @@ #include // TODO: find out which don't require stable iters #include -#include +#include "common/container/unordered_map.h" +#include "common/container/unordered_set.h" #include #include +#include #include #include "common/common_types.h" @@ -67,7 +69,7 @@ struct AsyncDecodeContext { std::atomic_bool complete; }; -using TextureCacheGPUMap = ankerl::unordered_dense::map, Common::IdentityHash>; +using TextureCacheGPUMap = ::Common::unordered_map, Common::IdentityHash>; class TextureCacheChannelInfo : public ChannelInfo { public: @@ -76,22 +78,20 @@ public: TextureCacheChannelInfo(const TextureCacheChannelInfo& state) = delete; TextureCacheChannelInfo& operator=(const TextureCacheChannelInfo&) = delete; - DescriptorTable graphics_image_table{gpu_memory}; - DescriptorTable graphics_sampler_table{gpu_memory}; - std::vector graphics_sampler_ids; - std::vector graphics_image_view_ids; - - DescriptorTable compute_image_table{gpu_memory}; - DescriptorTable compute_sampler_table{gpu_memory}; - std::vector compute_sampler_ids; - std::vector compute_image_view_ids; + DescriptorTable graphics_image_table; + DescriptorTable graphics_sampler_table; + DescriptorTable compute_image_table; + DescriptorTable compute_sampler_table; // TODO: still relies on bad iterators :( std::unordered_map image_views; std::unordered_map samplers; - TextureCacheGPUMap* gpu_page_table; - TextureCacheGPUMap* sparse_page_table; + ::Common::unordered_map sampler_ids; + ::Common::unordered_map image_view_ids; + + TextureCacheGPUMap* gpu_page_table = nullptr; + TextureCacheGPUMap* sparse_page_table = nullptr; }; template @@ -167,27 +167,17 @@ public: /// Mark an image as modified from the GPU void MarkModification(ImageId id) noexcept; - /// Fill image_view_ids with the graphics images in indices - template - void FillGraphicsImageViews(std::span views); - - /// Fill image_view_ids with the compute images in indices - void FillComputeImageViews(std::span views); + /// Fill image_view_ids with the graphics/compute images in indices + void FillImageViews(std::span views, bool compute, bool blacklist = true); /// Handle feedback loops during draws. void CheckFeedbackLoop(std::span views); - /// Get the sampler from the graphics descriptor table in the specified index - Sampler* GetGraphicsSampler(u32 index); + /// Get the sampler from the graphics/compute descriptor table in the specified index + Sampler* GetSampler(u32 index, bool compute); - /// Get the sampler from the compute descriptor table in the specified index - Sampler* GetComputeSampler(u32 index); - - /// Get the sampler id from the graphics descriptor table in the specified index - SamplerId GetGraphicsSamplerId(u32 index); - - /// Get the sampler id from the compute descriptor table in the specified index - SamplerId GetComputeSamplerId(u32 index); + /// Get the sampler id from the graphics/compute descriptor table in the specified index + SamplerId GetSamplerId(u32 index, bool compute); /// Return a constant reference to the given sampler id [[nodiscard]] const Sampler& GetSampler(SamplerId id) const noexcept; @@ -195,11 +185,8 @@ public: /// Return a reference to the given sampler id [[nodiscard]] Sampler& GetSampler(SamplerId id) noexcept; - /// Refresh the state for graphics image view and sampler descriptors - void SynchronizeGraphicsDescriptors(); - - /// Refresh the state for compute image view and sampler descriptors - void SynchronizeComputeDescriptors(); + /// Refresh the state for graphics/compute image view and sampler descriptors + void SynchronizeDescriptors(bool compute); /// Updates the Render Targets if they can be rescaled /// @retval True if the Render Targets have been rescaled. @@ -310,29 +297,21 @@ private: /// Runs the Garbage Collector. void RunGarbageCollector(); - /// Fills image_view_ids in the image views in indices - template - void FillImageViews(DescriptorTable& table, - std::span cached_image_view_ids, - std::span views); - /// Find or create an image view in the guest descriptor table - ImageViewId VisitImageView(DescriptorTable& table, - std::span cached_image_view_ids, u32 index); + ImageViewId VisitImageView(u32 index, bool compute); /// Find or create a framebuffer with the given render target parameters FramebufferId GetFramebufferId(const RenderTargets& key); /// Refresh the contents (pixel data) of an image + [[nodiscard]] bool IsDownloadable(const ImageBase& image) const noexcept; + void RefreshContents(Image& image, ImageId image_id); /// Upload data from guest to an image template void UploadImageContents(Image& image, StagingBuffer& staging_buffer); - /// Find or create an image view from a guest descriptor - [[nodiscard]] ImageViewId FindImageView(const TICEntry& config); - /// Create a new image view from a guest descriptor [[nodiscard]] ImageViewId CreateImageView(const TICEntry& config); @@ -360,7 +339,7 @@ private: const Tegra::Engines::Fermi2D::Config& copy); /// Find or create a sampler from a guest descriptor sampler - [[nodiscard]] SamplerId FindSampler(const TSCEntry& config); + [[nodiscard]] SamplerId FindSampler(const TSCEntry& config, bool compute); /// Find or create an image view for the given color buffer index [[nodiscard]] ImageViewId FindColorBuffer(size_t index); @@ -442,9 +421,6 @@ private: void QueueAsyncDecode(Image& image, ImageId image_id); void TickAsyncDecode(); - void EnforceSamplerBudget(); - void TrimInactiveSamplers(size_t budget); - std::optional QuerySamplerBudget() const; void QueueAsyncUnswizzle(Image& image, ImageId image_id); void TickAsyncUnswizzle(); @@ -466,9 +442,9 @@ private: FramebufferId last_framebuffer_id{}; u64 last_framebuffer_serial = 0; - ankerl::unordered_dense::map framebuffers; - ankerl::unordered_dense::map, Common::IdentityHash> page_table; - ankerl::unordered_dense::map> sparse_views; + ::Common::unordered_map framebuffers; + ::Common::unordered_map, Common::IdentityHash> page_table; + ::Common::unordered_map> sparse_views; DAddr virtual_invalid_space{}; @@ -524,16 +500,16 @@ private: DelayedDestructionRing sentenced_image_view; DelayedDestructionRing sentenced_framebuffers; - ankerl::unordered_dense::map image_allocs_table; + ::Common::unordered_map image_allocs_table; Common::ScratchBuffer swizzle_data_buffer; Common::ScratchBuffer unswizzle_data_buffer; u64 modification_tick = 0; u64 frame_tick = 0; - u64 last_sampler_gc_frame = (std::numeric_limits::max)(); - Common::ThreadWorker texture_decode_worker{1, "TextureDecoder"}; + Common::ThreadWorker texture_decode_worker{1, "TextureDecoder", {}, + Common::ThreadPlacement::Efficiency}; std::vector> async_decodes; std::deque unswizzle_queue; @@ -541,17 +517,17 @@ private: // Join caching boost::container::small_vector join_overlap_ids; - ankerl::unordered_dense::set join_overlaps_found; + ::Common::unordered_set join_overlaps_found; boost::container::small_vector join_left_aliased_ids; boost::container::small_vector join_right_aliased_ids; - ankerl::unordered_dense::set join_ignore_textures; + ::Common::unordered_set join_ignore_textures; boost::container::small_vector join_bad_overlap_ids; struct JoinCopy { bool is_alias; ImageId id; }; boost::container::small_vector join_copies_to_do; - ankerl::unordered_dense::map join_alias_indices; + ::Common::unordered_map join_alias_indices; }; } // namespace VideoCommon diff --git a/src/video_core/texture_cache/util.h b/src/video_core/texture_cache/util.h index 3e8bb00032..590d85b042 100644 --- a/src/video_core/texture_cache/util.h +++ b/src/video_core/texture_cache/util.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -122,25 +122,4 @@ void DeduceBlitImages(ImageInfo& dst_info, ImageInfo& src_info, const ImageBase* [[nodiscard]] u32 MapSizeBytes(const ImageBase& image); -// TODO: Remove once Debian STABLE no longer has such outdated boost -// This is a gcc bug where ADL lookup fails for range niebloids of std::span -// for any given type of the static_vector/small_vector, etc which makes a whole mess -// for anything using std::span so we just do this terrible hack on older versions of -// GCC12 because people actually still use stable debian so... yeah -// One may say: "This is bad for performance" - to which I say, using GCC 12 you already know -// what kind of bs you will be dealing with anyways. -template -#if BOOST_VERSION >= 108100 || __GNUC__ > 12 -[[nodiscard]] boost::container::small_vector FixSmallVectorADL(const boost::container::small_vector& v) { - return v; -} -#else -[[nodiscard]] std::vector FixSmallVectorADL(const boost::container::small_vector& v) { - std::vector u; - for (auto const& e : v) - u.push_back(e); - return u; -} -#endif - } // namespace VideoCommon diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp index b7b5f5bd4a..0b6d06c49a 100644 --- a/src/video_core/textures/astc.cpp +++ b/src/video_core/textures/astc.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2016 The University of North Carolina at Chapel Hill @@ -1269,6 +1269,209 @@ static inline u32 Select2DPartition(s32 seed, s32 x, s32 y, s32 partitionCount, return SelectPartition(seed, x, y, 0, partitionCount, smallBlock); } +static constexpr bool IsHDRColorEndpointMode(u32 cem) { + switch (cem) { + case 2: + case 3: + case 7: + case 11: + case 14: + case 15: + return true; + default: + return false; + } +} + +static constexpr s32 SignExtend(s32 value, u32 nbits) { + const s32 sign_bit = 1 << (nbits - 1); + return (value ^ sign_bit) - sign_bit; +} + +struct HDREndpointRGB { + s32 r0, g0, b0; + s32 r1, g1, b1; +}; + +static void DecodeHDREndpointMode7(u32 v0, u32 v1, u32 v2, u32 v3, s32& r0, s32& g0, s32& b0, + s32& r1, s32& g1, s32& b1) { + const u32 modeval = ((v0 & 0xC0) >> 6) | ((v1 & 0x80) >> 5) | ((v2 & 0x80) >> 4); + + u32 majcomp; + u32 mode; + if ((modeval & 0xC) != 0xC) { + majcomp = modeval >> 2; + mode = modeval & 3; + } else if (modeval != 0xF) { + majcomp = modeval & 3; + mode = 4; + } else { + majcomp = 0; + mode = 5; + } + + s32 red = static_cast(v0 & 0x3f); + s32 green = static_cast(v1 & 0x1f); + s32 blue = static_cast(v2 & 0x1f); + s32 scale = static_cast(v3 & 0x1f); + + const u32 x0 = (v1 >> 6) & 1; + const u32 x1 = (v1 >> 5) & 1; + const u32 x2 = (v2 >> 6) & 1; + const u32 x3 = (v2 >> 5) & 1; + const u32 x4 = (v3 >> 7) & 1; + const u32 x5 = (v3 >> 6) & 1; + const u32 x6 = (v3 >> 5) & 1; + + const u32 ohm = 1u << mode; + if (ohm & 0x30) + green |= static_cast(x0 << 6); + if (ohm & 0x3A) + green |= static_cast(x1 << 5); + if (ohm & 0x30) + blue |= static_cast(x2 << 6); + if (ohm & 0x3A) + blue |= static_cast(x3 << 5); + if (ohm & 0x3D) + scale |= static_cast(x6 << 5); + if (ohm & 0x2D) + scale |= static_cast(x5 << 6); + if (ohm & 0x04) + scale |= static_cast(x4 << 7); + if (ohm & 0x3B) + red |= static_cast(x4 << 6); + if (ohm & 0x04) + red |= static_cast(x3 << 6); + if (ohm & 0x10) + red |= static_cast(x5 << 7); + if (ohm & 0x0F) + red |= static_cast(x2 << 7); + if (ohm & 0x05) + red |= static_cast(x1 << 8); + if (ohm & 0x0A) + red |= static_cast(x0 << 8); + if (ohm & 0x05) + red |= static_cast(x0 << 9); + if (ohm & 0x02) + red |= static_cast(x6 << 9); + if (ohm & 0x01) + red |= static_cast(x3 << 10); + if (ohm & 0x02) + red |= static_cast(x5 << 10); + + static constexpr int shamts[6] = {1, 1, 2, 3, 4, 5}; + const s32 shamt = shamts[mode]; + red <<= shamt; + green <<= shamt; + blue <<= shamt; + scale <<= shamt; + + if (mode != 5) { + green = red - green; + blue = red - blue; + } + + if (majcomp == 1) + std::swap(red, green); + if (majcomp == 2) + std::swap(red, blue); + + r1 = std::clamp(red, 0, 0xFFF); + g1 = std::clamp(green, 0, 0xFFF); + b1 = std::clamp(blue, 0, 0xFFF); + + r0 = std::clamp(red - scale, 0, 0xFFF); + g0 = std::clamp(green - scale, 0, 0xFFF); + b0 = std::clamp(blue - scale, 0, 0xFFF); +} + +static HDREndpointRGB DecodeHDREndpointMode11(u32 v0, u32 v1, u32 v2, u32 v3, u32 v4, u32 v5) { + const u32 majcomp = ((v4 & 0x80) >> 7) | ((v5 & 0x80) >> 6); + if (majcomp == 3) { + HDREndpointRGB result; + result.r0 = static_cast(v0 << 4); + result.g0 = static_cast(v2 << 4); + result.b0 = static_cast((v4 & 0x7f) << 5); + result.r1 = static_cast(v1 << 4); + result.g1 = static_cast(v3 << 4); + result.b1 = static_cast((v5 & 0x7f) << 5); + return result; + } + + const u32 mode = ((v1 & 0x80) >> 7) | ((v2 & 0x80) >> 6) | ((v3 & 0x80) >> 5); + s32 va = static_cast(v0 | ((v1 & 0x40) << 2)); + s32 vb0 = static_cast(v2 & 0x3f); + s32 vb1 = static_cast(v3 & 0x3f); + s32 vc = static_cast(v1 & 0x3f); + s32 vd0 = static_cast(v4 & 0x7f); + s32 vd1 = static_cast(v5 & 0x7f); + + static constexpr int dbitstab[8] = {7, 6, 7, 6, 5, 6, 5, 6}; + vd0 = SignExtend(vd0, dbitstab[mode]); + vd1 = SignExtend(vd1, dbitstab[mode]); + + const u32 x0 = (v2 >> 6) & 1; + const u32 x1 = (v3 >> 6) & 1; + const u32 x2 = (v4 >> 6) & 1; + const u32 x3 = (v5 >> 6) & 1; + const u32 x4 = (v4 >> 5) & 1; + const u32 x5 = (v5 >> 5) & 1; + + const u32 ohm = 1u << mode; + if (ohm & 0xA4) + va |= static_cast(x0 << 9); + if (ohm & 0x08) + va |= static_cast(x2 << 9); + if (ohm & 0x50) + va |= static_cast(x4 << 9); + if (ohm & 0x50) + va |= static_cast(x5 << 10); + if (ohm & 0xA0) + va |= static_cast(x1 << 10); + if (ohm & 0xC0) + va |= static_cast(x2 << 11); + if (ohm & 0x04) + vc |= static_cast(x1 << 6); + if (ohm & 0xE8) + vc |= static_cast(x3 << 6); + if (ohm & 0x20) + vc |= static_cast(x2 << 7); + if (ohm & 0x5B) + vb0 |= static_cast(x0 << 6); + if (ohm & 0x5B) + vb1 |= static_cast(x1 << 6); + if (ohm & 0x12) + vb0 |= static_cast(x2 << 7); + if (ohm & 0x12) + vb1 |= static_cast(x3 << 7); + + const s32 shamt = (static_cast(mode) >> 1) ^ 3; + va <<= shamt; + vb0 <<= shamt; + vb1 <<= shamt; + vc <<= shamt; + vd0 <<= shamt; + vd1 <<= shamt; + + HDREndpointRGB result; + result.r1 = std::clamp(va, 0, 0xFFF); + result.g1 = std::clamp(va - vb0, 0, 0xFFF); + result.b1 = std::clamp(va - vb1, 0, 0xFFF); + + result.r0 = std::clamp(va - vc, 0, 0xFFF); + result.g0 = std::clamp(va - vb0 - vc - vd0, 0, 0xFFF); + result.b0 = std::clamp(va - vb1 - vc - vd1, 0, 0xFFF); + + if (majcomp == 1) { + std::swap(result.r0, result.g0); + std::swap(result.r1, result.g1); + } else if (majcomp == 2) { + std::swap(result.r0, result.b0); + std::swap(result.r1, result.b1); + } + return result; +} + // Section C.2.14 static void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const u32*& colorValues, u32 colorEndpointMode) { @@ -1382,8 +1585,84 @@ static void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const u32*& colorValues, ep2.ClampByte(); } break; + case 2: { + READ_UINT_VALUES(2) + u32 y0, y1; + if (v[1] >= v[0]) { + y0 = v[0] << 4; + y1 = v[1] << 4; + } else { + y0 = (v[1] << 4) + 8; + y1 = (v[0] << 4) - 8; + } + ep1 = Pixel(0x780, y0, y0, y0); + ep2 = Pixel(0x780, y1, y1, y1); + } break; + + case 3: { + READ_UINT_VALUES(2) + u32 y0, d; + if (v[0] & 0x80) { + y0 = ((v[1] & 0xE0) << 4) | ((v[0] & 0x7F) << 2); + d = (v[1] & 0x1F) << 2; + } else { + y0 = ((v[1] & 0xF0) << 4) | ((v[0] & 0x7F) << 1); + d = (v[1] & 0x0F) << 1; + } + const u32 y1 = (std::min)(y0 + d, 0xFFFU); + ep1 = Pixel(0x780, y0, y0, y0); + ep2 = Pixel(0x780, y1, y1, y1); + } break; + + case 7: { + READ_UINT_VALUES(4) + s32 r0, g0, b0, r1, g1, b1; + DecodeHDREndpointMode7(v[0], v[1], v[2], v[3], r0, g0, b0, r1, g1, b1); + ep1 = Pixel(0x780, r0, g0, b0); + ep2 = Pixel(0x780, r1, g1, b1); + } break; + + case 11: { + READ_UINT_VALUES(6) + const HDREndpointRGB rgb = DecodeHDREndpointMode11(v[0], v[1], v[2], v[3], v[4], v[5]); + ep1 = Pixel(0x780, rgb.r0, rgb.g0, rgb.b0); + ep2 = Pixel(0x780, rgb.r1, rgb.g1, rgb.b1); + } break; + + case 14: { + READ_UINT_VALUES(8) + const HDREndpointRGB rgb = DecodeHDREndpointMode11(v[0], v[1], v[2], v[3], v[4], v[5]); + ep1 = Pixel(v[6], rgb.r0, rgb.g0, rgb.b0); + ep2 = Pixel(v[7], rgb.r1, rgb.g1, rgb.b1); + } break; + + case 15: { + READ_UINT_VALUES(8) + const HDREndpointRGB rgb = DecodeHDREndpointMode11(v[0], v[1], v[2], v[3], v[4], v[5]); + const u32 mode = ((v[6] >> 7) & 1) | ((v[7] >> 6) & 2); + s32 a6 = static_cast(v[6] & 0x7F); + s32 a7 = static_cast(v[7] & 0x7F); + s32 alpha0, alpha1; + if (mode == 3) { + alpha0 = a6 << 5; + alpha1 = a7 << 5; + } else { + a6 |= (a7 << (mode + 1)) & 0x780; + a7 &= (0x3F >> mode); + a7 ^= 0x20 >> mode; + a7 -= 0x20 >> mode; + a6 <<= (4 - mode); + a7 <<= (4 - mode); + a7 += a6; + alpha0 = a6; + alpha1 = std::clamp(a7, 0, 0xFFF); + } + ep1 = Pixel(alpha0, rgb.r0, rgb.g0, rgb.b0); + ep2 = Pixel(alpha1, rgb.r1, rgb.g1, rgb.b1); + } break; + default: - assert(false && "Unsupported color endpoint mode (is it HDR?)"); + assert(false && "Unsupported color endpoint mode"); break; } @@ -1414,6 +1693,39 @@ static void FillVoidExtentLDR(InputBitStream& strm, std::span outBuf, u32 b } } +static float HalfToFloat(u16 h) { + const u32 sign = static_cast(h & 0x8000) << 16; + u32 exp = (h & 0x7C00) >> 10; + u32 mant = h & 0x3FF; + u32 bits; + if (exp == 0) { + if (mant == 0) { + bits = sign; + } else { + s32 e = 127 - 15 + 1; + while ((mant & 0x400) == 0) { + mant <<= 1; + --e; + } + mant &= 0x3FF; + bits = sign | (static_cast(e) << 23) | (mant << 13); + } + } else if (exp == 0x1F) { + bits = sign | 0x7F800000 | (mant << 13); + } else { + bits = sign | ((exp - 15 + 127) << 23) | (mant << 13); + } + float result; + std::memcpy(&result, &bits, sizeof(result)); + return result; +} + +static u16 HalfToClampedByte(u16 half_bits) { + const float value = HalfToFloat(half_bits); + const float clamped = std::clamp(value, 0.0f, 1.0f); + return static_cast(clamped * 255.0f + 0.5f); +} + static void FillError(std::span outBuf, u32 blockWidth, u32 blockHeight) { for (u32 j = 0; j < blockHeight; j++) { for (u32 i = 0; i < blockWidth; i++) { @@ -1457,6 +1769,12 @@ static void DecompressBlock(std::span inBuf, const u32 blockWidth, return; } + if (weightParams.GetNumWeightValues() > 64) { + assert(false && "Too many weights in the weight grid"); + FillError(outBuf, blockWidth, blockHeight); + return; + } + // Read num partitions u32 nPartitions = strm.ReadBits<2>() + 1; assert(nPartitions <= 4); @@ -1493,6 +1811,11 @@ static void DecompressBlock(std::span inBuf, const u32 blockWidth, // Remaining bits are color endpoint data... u32 nWeightBits = weightParams.GetPackedBitSize(); + if (nWeightBits < 24 || nWeightBits > 96) { + assert(false && "Invalid weight bit count"); + FillError(outBuf, blockWidth, blockHeight); + return; + } s32 remainingBits = 128 - nWeightBits - static_cast(strm.GetBitsRead()); // Consider extra bits prior to texel data... @@ -1631,22 +1954,44 @@ static void DecompressBlock(std::span inBuf, const u32 blockWidth, Pixel p; for (u32 c = 0; c < 4; c++) { u32 C0 = endpoints[partition][0].Component(c); - C0 = ReplicateByteTo16(C0); u32 C1 = endpoints[partition][1].Component(c); - C1 = ReplicateByteTo16(C1); u32 plane = 0; if (weightParams.m_bDualPlane && (((planeIdx + 1) & 3) == c)) { plane = 1; } - u32 weight = weights[plane][j * blockWidth + i]; - u32 C = (C0 * (64 - weight) + C1 * weight + 32) / 64; - if (C == 65535) { - p.Component(c) = 255; + + const bool is_hdr = IsHDRColorEndpointMode(colorEndpointMode[partition]) && + !(colorEndpointMode[partition] == 14 && c == 0); + + if (is_hdr) { + C0 <<= 4; + C1 <<= 4; + const u32 C = (C0 * (64 - weight) + C1 * weight + 32) / 64; + const u32 E = (C & 0xF800) >> 11; + const u32 M = C & 0x7FF; + u32 Mt; + if (M < 512) { + Mt = 3 * M; + } else if (M >= 1536) { + Mt = 5 * M - 2048; + } else { + Mt = 4 * M - 512; + } + const u32 Cf = (E << 10) + (Mt >> 3); + const u16 half_bits = (Cf >= 0x7C00) ? u16{0x7BFF} : static_cast(Cf); + p.Component(c) = HalfToClampedByte(half_bits); } else { - double Cf = static_cast(C); - p.Component(c) = static_cast(255.0 * (Cf / 65536.0) + 0.5); + C0 = ReplicateByteTo16(C0); + C1 = ReplicateByteTo16(C1); + const u32 C = (C0 * (64 - weight) + C1 * weight + 32) / 64; + if (C == 65535) { + p.Component(c) = 255; + } else { + double Cf = static_cast(C); + p.Component(c) = static_cast(255.0 * (Cf / 65536.0) + 0.5); + } } } diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h index e704076923..1e14a38753 100644 --- a/src/video_core/textures/decoders.h +++ b/src/video_core/textures/decoders.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -23,24 +26,6 @@ constexpr u32 GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT + GOB_SIZE_Z_ constexpr u32 SWIZZLE_X_BITS = 0b100101111; constexpr u32 SWIZZLE_Y_BITS = 0b011010000; -using SwizzleTable = std::array, GOB_SIZE_Y>; - -/** - * This table represents the internal swizzle of a gob, in format 16 bytes x 2 sector packing. - * Calculates the offset of an (x, y) position within a swizzled texture. - * Taken from the Tegra X1 Technical Reference Manual. pages 1187-1188 - */ -constexpr SwizzleTable MakeSwizzleTable() { - SwizzleTable table{}; - for (u32 y = 0; y < table.size(); ++y) { - for (u32 x = 0; x < table[0].size(); ++x) { - table[y][x] = ((x % 64) / 32) * 256 + ((y % 8) / 2) * 64 + ((x % 32) / 16) * 32 + - (y % 2) * 16 + (x % 16); - } - } - return table; -} - /// Unswizzles a block linear texture into linear memory. void UnswizzleTexture(std::span output, std::span input, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth, diff --git a/src/video_core/textures/texture.cpp b/src/video_core/textures/texture.cpp index d32872e35d..dc3b0b8244 100644 --- a/src/video_core/textures/texture.cpp +++ b/src/video_core/textures/texture.cpp @@ -1,10 +1,11 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include "common/cityhash.h" #include "common/settings.h" @@ -17,53 +18,25 @@ namespace Tegra::Texture { namespace { -[[maybe_unused]] constexpr std::array SRGB_CONVERSION_LUT = { - 0.000000f, 0.000000f, 0.000000f, 0.000012f, 0.000021f, 0.000033f, 0.000046f, 0.000062f, - 0.000081f, 0.000102f, 0.000125f, 0.000151f, 0.000181f, 0.000214f, 0.000251f, 0.000293f, - 0.000338f, 0.000388f, 0.000443f, 0.000503f, 0.000568f, 0.000639f, 0.000715f, 0.000798f, - 0.000887f, 0.000983f, 0.001085f, 0.001195f, 0.001312f, 0.001437f, 0.001569f, 0.001710f, - 0.001860f, 0.002019f, 0.002186f, 0.002364f, 0.002551f, 0.002748f, 0.002955f, 0.003174f, - 0.003403f, 0.003643f, 0.003896f, 0.004160f, 0.004436f, 0.004725f, 0.005028f, 0.005343f, - 0.005672f, 0.006015f, 0.006372f, 0.006744f, 0.007130f, 0.007533f, 0.007950f, 0.008384f, - 0.008834f, 0.009301f, 0.009785f, 0.010286f, 0.010805f, 0.011342f, 0.011898f, 0.012472f, - 0.013066f, 0.013680f, 0.014313f, 0.014967f, 0.015641f, 0.016337f, 0.017054f, 0.017793f, - 0.018554f, 0.019337f, 0.020144f, 0.020974f, 0.021828f, 0.022706f, 0.023609f, 0.024536f, - 0.025489f, 0.026468f, 0.027473f, 0.028504f, 0.029563f, 0.030649f, 0.031762f, 0.032904f, - 0.034074f, 0.035274f, 0.036503f, 0.037762f, 0.039050f, 0.040370f, 0.041721f, 0.043103f, - 0.044518f, 0.045964f, 0.047444f, 0.048956f, 0.050503f, 0.052083f, 0.053699f, 0.055349f, - 0.057034f, 0.058755f, 0.060513f, 0.062307f, 0.064139f, 0.066008f, 0.067915f, 0.069861f, - 0.071845f, 0.073869f, 0.075933f, 0.078037f, 0.080182f, 0.082369f, 0.084597f, 0.086867f, - 0.089180f, 0.091535f, 0.093935f, 0.096378f, 0.098866f, 0.101398f, 0.103977f, 0.106601f, - 0.109271f, 0.111988f, 0.114753f, 0.117565f, 0.120426f, 0.123335f, 0.126293f, 0.129301f, - 0.132360f, 0.135469f, 0.138629f, 0.141841f, 0.145105f, 0.148421f, 0.151791f, 0.155214f, - 0.158691f, 0.162224f, 0.165810f, 0.169453f, 0.173152f, 0.176907f, 0.180720f, 0.184589f, - 0.188517f, 0.192504f, 0.196549f, 0.200655f, 0.204820f, 0.209046f, 0.213334f, 0.217682f, - 0.222093f, 0.226567f, 0.231104f, 0.235704f, 0.240369f, 0.245099f, 0.249894f, 0.254754f, - 0.259681f, 0.264674f, 0.269736f, 0.274864f, 0.280062f, 0.285328f, 0.290664f, 0.296070f, - 0.301546f, 0.307094f, 0.312713f, 0.318404f, 0.324168f, 0.330006f, 0.335916f, 0.341902f, - 0.347962f, 0.354097f, 0.360309f, 0.366597f, 0.372961f, 0.379403f, 0.385924f, 0.392524f, - 0.399202f, 0.405960f, 0.412798f, 0.419718f, 0.426719f, 0.433802f, 0.440967f, 0.448216f, - 0.455548f, 0.462965f, 0.470465f, 0.478052f, 0.485725f, 0.493484f, 0.501329f, 0.509263f, - 0.517285f, 0.525396f, 0.533595f, 0.541885f, 0.550265f, 0.558736f, 0.567299f, 0.575954f, - 0.584702f, 0.593542f, 0.602477f, 0.611507f, 0.620632f, 0.629852f, 0.639168f, 0.648581f, - 0.658092f, 0.667700f, 0.677408f, 0.687214f, 0.697120f, 0.707127f, 0.717234f, 0.727443f, - 0.737753f, 0.748167f, 0.758685f, 0.769305f, 0.780031f, 0.790861f, 0.801798f, 0.812839f, - 0.823989f, 0.835246f, 0.846611f, 0.858085f, 0.869668f, 0.881360f, 0.893164f, 0.905078f, - 0.917104f, 0.929242f, 0.941493f, 0.953859f, 0.966338f, 1.000000f, 1.000000f, 1.000000f, -}; +float SrgbToLinear(u32 value) { + const float encoded = static_cast(value) / 255.0f; + if (encoded <= 0.04045f) { + return encoded / 12.92f; + } + return std::pow((encoded + 0.055f) / 1.055f, 2.4f); +} } // Anonymous namespace std::array TSCEntry::BorderColor() const noexcept { - // TODO: Handle SRGB correctly. Using this breaks shadows in some games (Xenoblade). - // if (!srgb_conversion) { - // return border_color; - //} - // return {SRGB_CONVERSION_LUT[srgb_border_color_r], SRGB_CONVERSION_LUT[srgb_border_color_g], - // SRGB_CONVERSION_LUT[srgb_border_color_b], border_color[3]}; return border_color; } +std::array TSCEntry::SrgbBorderColor() const noexcept { + return {SrgbToLinear(srgb_border_color_r), SrgbToLinear(srgb_border_color_g), + SrgbToLinear(srgb_border_color_b), border_color[3]}; +} + float TSCEntry::MaxAnisotropy() const noexcept { const bool is_suitable_mipmap_filter = mipmap_filter != TextureMipmapFilter::None; const bool has_regular_lods = min_lod_clamp == 0 && max_lod_clamp >= 256; @@ -80,16 +53,16 @@ float TSCEntry::MaxAnisotropy() const noexcept { case Settings::AnisotropyMode::X4: case Settings::AnisotropyMode::X8: case Settings::AnisotropyMode::X16: - case Settings::AnisotropyMode::X32: - case Settings::AnisotropyMode::X64: - added_anisotropic = u32(anisotropic_settings) - 1U; + added_anisotropic = s32(anisotropic_settings) - 1; break; - case Settings::AnisotropyMode::Automatic: - added_anisotropic = Settings::values.resolution_info.up_scale >> Settings::values.resolution_info.down_shift; - added_anisotropic = (std::max)(added_anisotropic - 1U, 0U); + case Settings::AnisotropyMode::Automatic: { + const u32 resolution_scale = Settings::values.resolution_info.up_scale >> + Settings::values.resolution_info.down_shift; + if (resolution_scale > 1U) { + added_anisotropic = static_cast(resolution_scale - 1U); + } break; - case Settings::AnisotropyMode::None: - return 1.0f; //No use of anisotropy + } } return float(1U << (max_anisotropy + added_anisotropic)); } diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h index 7e5837b206..9fc6a631bd 100644 --- a/src/video_core/textures/texture.h +++ b/src/video_core/textures/texture.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -378,6 +381,8 @@ struct TSCEntry { std::array BorderColor() const noexcept; + std::array SrgbBorderColor() const noexcept; + float MaxAnisotropy() const noexcept; float MinLod() const { diff --git a/src/video_core/textures/workers.cpp b/src/video_core/textures/workers.cpp index 780e0c4da9..a1953c4bf4 100644 --- a/src/video_core/textures/workers.cpp +++ b/src/video_core/textures/workers.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -11,7 +11,6 @@ namespace Tegra::Texture { Common::ThreadWorker& GetThreadWorkers() { static Common::ThreadWorker workers{(std::max)(std::thread::hardware_concurrency(), 2U) / 2, "ImageTranscode"}; - return workers; } diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index 1ac7a0bc35..54fb8f6239 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -45,23 +45,23 @@ std::unique_ptr CreateRenderer(Core::System& system, Co namespace VideoCore { -std::unique_ptr CreateGPU(Core::Frontend::EmuWindow& emu_window, Core::System& system) { +/// @brief Creates an emulated GPU instance using the given system context. +void CreateGPU(std::optional& gpu, Core::Frontend::EmuWindow& emu_window, Core::System& system) { Settings::UpdateRescalingInfo(); const auto nvdec_value = Settings::values.nvdec_emulation.GetValue(); const bool use_nvdec = nvdec_value != Settings::NvdecEmulation::Off; const bool use_async = Settings::values.use_asynchronous_gpu_emulation.GetValue(); - auto gpu = std::make_unique(system, use_async, use_nvdec); + gpu.emplace(system, use_async, use_nvdec); auto context = emu_window.CreateSharedContext(); auto scope = context->Acquire(); try { auto renderer = CreateRenderer(system, emu_window, *gpu, std::move(context)); gpu->BindRenderer(std::move(renderer)); - return gpu; } catch (const std::runtime_error& exception) { scope.Cancel(); LOG_ERROR(HW_GPU, "Failed to initialize GPU: {}", exception.what()); - return nullptr; + gpu.reset(); } } diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h index f8e2444f33..1a56dd99b6 100644 --- a/src/video_core/video_core.h +++ b/src/video_core/video_core.h @@ -1,9 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 #pragma once #include +#include namespace Core { class System; @@ -20,8 +24,6 @@ class GPU; namespace VideoCore { class RendererBase; - -/// Creates an emulated GPU instance using the given system context. -std::unique_ptr CreateGPU(Core::Frontend::EmuWindow& emu_window, Core::System& system); +void CreateGPU(std::optional& gpu, Core::Frontend::EmuWindow& emu_window, Core::System& system); } // namespace VideoCore diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.cpp b/src/video_core/vulkan_common/vulkan_debug_callback.cpp index d1bd009c8f..0f9aaa9a0b 100644 --- a/src/video_core/vulkan_common/vulkan_debug_callback.cpp +++ b/src/video_core/vulkan_common/vulkan_debug_callback.cpp @@ -27,7 +27,7 @@ VkBool32 DebugUtilCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, [[maybe_unused]] void* user_data) { // Skip logging known false-positive validation errors switch (static_cast(data->messageIdNumber)) { -#ifdef ANDROID +#ifdef __ANDROID__ case 0xbf9cf353u: // VUID-vkCmdBindVertexBuffers2-pBuffers-04111 // The below are due to incorrect reporting of extendedDynamicState case 0x1093bebbu: // VUID-vkCmdSetCullMode-None-03384 @@ -76,7 +76,7 @@ VkBool32 DebugUtilCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, } // Route to GPU logger for tracking Vulkan validation messages - if (Settings::values.gpu_logging_enabled.GetValue() && + if (GPU::Logging::IsActive() && Settings::values.gpu_log_vulkan_calls.GetValue()) { // Convert severity to result code for logging (negative = error) int result_code = 0; diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 6e55306079..be09bca8d3 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -7,15 +7,20 @@ #include #include #include +#include +#include #include #include -#include +#include "common/container/unordered_map.h" +#include "common/container/unordered_set.h" #include #include #include #include "common/assert.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "common/literals.h" #include #include "common/settings.h" @@ -26,7 +31,7 @@ #include "video_core/vulkan_common/vulkan_wrapper.h" #include "video_core/gpu_logging/gpu_logging.h" -#if defined(ANDROID) && defined(ARCHITECTURE_arm64) +#if defined(__ANDROID__) && defined(ARCHITECTURE_arm64) #include #include #endif @@ -95,6 +100,12 @@ constexpr std::array VK_FORMAT_A4B4G4R4_UNORM_PACK16{ VK_FORMAT_UNDEFINED, }; +constexpr std::array B10G11R11_UFLOAT_PACK32{ + VK_FORMAT_R16G16B16A16_SFLOAT, + VK_FORMAT_A8B8G8R8_SRGB_PACK32, + VK_FORMAT_UNDEFINED, +}; + } // namespace Alternatives template @@ -127,6 +138,8 @@ constexpr const VkFormat* GetFormatAlternatives(VkFormat format) { return Alternatives::VK_FORMAT_R32G32B32_SFLOAT.data(); case VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT: return Alternatives::VK_FORMAT_A4B4G4R4_UNORM_PACK16.data(); + case VK_FORMAT_B10G11R11_UFLOAT_PACK32: + return Alternatives::B10G11R11_UFLOAT_PACK32.data(); default: return nullptr; } @@ -145,7 +158,7 @@ VkFormatFeatureFlags GetFormatFeatures(VkFormatProperties properties, FormatType } } -ankerl::unordered_dense::map GetFormatProperties(vk::PhysicalDevice physical) { +::Common::unordered_map GetFormatProperties(vk::PhysicalDevice physical) { static constexpr std::array formats{ VK_FORMAT_A1R5G5B5_UNORM_PACK16, VK_FORMAT_A2B10G10R10_SINT_PACK32, @@ -286,16 +299,26 @@ ankerl::unordered_dense::map GetFormatProperties(v VK_FORMAT_R8_UNORM, VK_FORMAT_R8_USCALED, VK_FORMAT_S8_UINT, + VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, + VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK, + VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK, + VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, + VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, + VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, + VK_FORMAT_EAC_R11_UNORM_BLOCK, + VK_FORMAT_EAC_R11_SNORM_BLOCK, + VK_FORMAT_EAC_R11G11_UNORM_BLOCK, + VK_FORMAT_EAC_R11G11_SNORM_BLOCK, }; - ankerl::unordered_dense::map format_properties; + ::Common::unordered_map format_properties; for (const auto format : formats) { format_properties.emplace(format, physical.GetFormatProperties(format)); } return format_properties; } -#if defined(ANDROID) && defined(ARCHITECTURE_arm64) -void OverrideBcnFormats(ankerl::unordered_dense::map& format_properties) { +#if defined(__ANDROID__) && defined(ARCHITECTURE_arm64) +void OverrideBcnFormats(::Common::unordered_map& format_properties) { // These properties are extracted from Adreno driver 512.687.0 constexpr VkFormatFeatureFlags tiling_features{VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT | @@ -375,6 +398,17 @@ std::vector ExtensionListForVulkan( return output; } +constexpr std::array STATIC_CACHE_MAGIC_NUMBER{'e', 'd', 'e', 'n', 's', 't', 'p', 'c'}; +constexpr u32 STATIC_CACHE_VERSION = 1; + +std::filesystem::path StaticPipelineCacheFilename() { + const auto shader_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir); + if (!Common::FS::CreateDir(shader_dir)) { + return {}; + } + return shader_dir / "vulkan_static_pipelines.bin"; +} + } // Anonymous namespace void Device::RemoveExtension(bool& extension, const std::string& extension_name) { @@ -486,21 +520,25 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR CollectToolingInfo(); if (is_qualcomm) { - // Qualcomm Adreno GPUs doesn't handle scaled vertex attributes; keep emulation enabled must_emulate_scaled_formats = true; - LOG_WARNING(Render_Vulkan, - "Qualcomm drivers require scaled vertex format emulation; forcing fallback"); - - LOG_WARNING(Render_Vulkan, - "Disabling shader float controls and 64-bit integer features on Qualcomm proprietary drivers"); - RemoveExtension(extensions.shader_float_controls, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers require scaled vertex format emulation."); + has_broken_descriptor_aliasing = true; + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken descriptor aliasing."); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken color write enable."); + RemoveExtensionFeature(extensions.color_write_enable, features.color_write_enable, + VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME); + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken shader atomic int64."); RemoveExtensionFeature(extensions.shader_atomic_int64, features.shader_atomic_int64, VK_KHR_SHADER_ATOMIC_INT64_EXTENSION_NAME); features.shader_atomic_int64.shaderBufferInt64Atomics = false; features.shader_atomic_int64.shaderSharedInt64Atomics = false; features.features.shaderInt64 = false; + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken workgroup memory explicit layout."); + RemoveExtensionFeature(extensions.workgroup_memory_explicit_layout, + features.workgroup_memory_explicit_layout, + VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME); -#if defined(ANDROID) && defined(ARCHITECTURE_arm64) +#if defined(__ANDROID__) && defined(ARCHITECTURE_arm64) // BCn patching only safe on Android 9+ (API 28+). Older versions crash on driver load. const auto major = (properties.properties.driverVersion >> 24) << 2; const auto minor = (properties.properties.driverVersion >> 12) & 0xFFFU; @@ -551,7 +589,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR features.shader_float16_int8.shaderFloat16 = false; } - // Mali/ NVIDIA proprietary drivers: Shader stencil export not supported // Use hardware depth/stencil blits instead when available if (!extensions.shader_stencil_export) { LOG_INFO(Render_Vulkan, @@ -590,21 +627,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR } } - if (is_qualcomm) { - const size_t sampler_limit = properties.properties.limits.maxSamplerAllocationCount; - if (sampler_limit > 0) { - constexpr size_t MIN_SAMPLER_BUDGET = 1024U; - const size_t reserved = sampler_limit / 4U; - const size_t derived_budget = - (std::max)(MIN_SAMPLER_BUDGET, sampler_limit - reserved); - sampler_heap_budget = derived_budget; - LOG_WARNING(Render_Vulkan, - "Qualcomm driver reports max {} samplers; reserving {} (25%) and " - "allowing Eden to use {} (75%) to avoid heap exhaustion", - sampler_limit, reserved, sampler_heap_budget); - } - } - if (extensions.sampler_filter_minmax && is_amd) { // Disable ext_sampler_filter_minmax on AMD GCN4 and lower as it is broken. if (!features.shader_float16_int8.shaderFloat16) { @@ -621,13 +643,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR features.shader_float16_int8.shaderFloat16 = false; } - if (is_intel_windows) { - LOG_WARNING(Render_Vulkan, - "Intel proprietary drivers do not support MSAA->MSAA image blits. " - "MSAA scaling will use 3D helpers. MSAA resolves work normally."); - cant_blit_msaa = true; - } - has_broken_compute = CheckBrokenCompute(properties.driver.driverID, properties.properties.driverVersion) && !Settings::values.enable_compute_pipelines.GetValue(); @@ -647,14 +662,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR } const auto dyna_state = Settings::values.dyna_state.GetValue(); - - // Base dynamic states (VIEWPORT, SCISSOR, DEPTH_BIAS, etc.) are ALWAYS active in vk_graphics_pipeline.cpp - // This slider controls EXTENDED dynamic states with accumulative levels per Vulkan specs: - // Level 0 = Core Dynamic States only (Vulkan 1.0) - // Level 1 = Core + VK_EXT_extended_dynamic_state - // Level 2 = Core + VK_EXT_extended_dynamic_state + VK_EXT_extended_dynamic_state2 - // Level 3 = Core + VK_EXT_extended_dynamic_state + VK_EXT_extended_dynamic_state2 + VK_EXT_extended_dynamic_state3 - switch (dyna_state) { case Settings::ExtendedDynamicState::Disabled: // Level 0: Disable all extended dynamic state extensions @@ -689,12 +696,42 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR break; } - // VK_EXT_vertex_input_dynamic_state is independent from EDS - // It can be enabled even without extended_dynamic_state + // VK_EXT_vertex_input_dynamic_state if (!Settings::values.vertex_input_dynamic_state.GetValue()) { RemoveExtensionFeature(extensions.vertex_input_dynamic_state, features.vertex_input_dynamic_state, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); } + // Descriptors feature list + { + auto& descriptor_indexing = features.descriptor_indexing; + descriptor_indexing.shaderInputAttachmentArrayDynamicIndexing = false; + descriptor_indexing.shaderUniformTexelBufferArrayDynamicIndexing = false; + descriptor_indexing.shaderStorageTexelBufferArrayDynamicIndexing = false; + descriptor_indexing.shaderUniformBufferArrayNonUniformIndexing = false; + descriptor_indexing.shaderStorageBufferArrayNonUniformIndexing = false; + descriptor_indexing.shaderInputAttachmentArrayNonUniformIndexing = false; + descriptor_indexing.descriptorBindingUniformBufferUpdateAfterBind = false; + descriptor_indexing.descriptorBindingSampledImageUpdateAfterBind = false; + descriptor_indexing.descriptorBindingStorageImageUpdateAfterBind = false; + descriptor_indexing.descriptorBindingStorageBufferUpdateAfterBind = false; + descriptor_indexing.descriptorBindingUniformTexelBufferUpdateAfterBind = false; + descriptor_indexing.descriptorBindingStorageTexelBufferUpdateAfterBind = false; + descriptor_indexing.descriptorBindingUpdateUnusedWhilePending = false; + descriptor_indexing.descriptorBindingVariableDescriptorCount = false; + descriptor_indexing.runtimeDescriptorArray = false; + } + + // VK_EXT_descriptor_buffer requires VK_KHR_buffer_device_address + if (extensions.descriptor_buffer && !features.buffer_device_address.bufferDeviceAddress) { + LOG_WARNING(Render_Vulkan, "Descriptor buffer needs buffer device address, disabling."); + RemoveExtensionFeature(extensions.descriptor_buffer, features.descriptor_buffer, + VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME); + } + if (!extensions.descriptor_buffer) { + RemoveExtensionFeature(extensions.buffer_device_address, features.buffer_device_address, + VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); + } + logical = vk::Device::Create(physical, queue_cis, ExtensionListForVulkan(loaded_extensions), first_next, dld); graphics_queue = logical.GetQueue(graphics_family); @@ -708,6 +745,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR if (extensions.memory_budget) { flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT; } + if (extensions.buffer_device_address) { + flags |= VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; + } const VmaAllocatorCreateInfo allocator_info{ .flags = flags, .physicalDevice = physical, @@ -726,15 +766,100 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR vk::Check(vmaCreateAllocator(&allocator_info, &allocator)); + owns_static_pipeline_cache = surface != VkSurfaceKHR{}; + LoadStaticPipelineCache(); + // Initialize GPU logging if enabled InitializeGPULogging(); } Device::~Device() { + SaveStaticPipelineCache(); ShutdownGPULogging(); vmaDestroyAllocator(allocator); } +void Device::LoadStaticPipelineCache() { + const auto create = [this](size_t size, const void* data) { + static_pipeline_cache = logical.CreatePipelineCache({ + .sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .initialDataSize = size, + .pInitialData = data, + }); + }; + if (!owns_static_pipeline_cache) { + create(0, nullptr); + return; + } + const auto filename = StaticPipelineCacheFilename(); + if (filename.empty()) { + create(0, nullptr); + return; + } + std::vector data; + try { + std::ifstream file(filename, std::ios::binary | std::ios::ate); + if (!file.is_open()) { + create(0, nullptr); + return; + } + file.exceptions(std::ifstream::failbit | std::ifstream::badbit); + const size_t total = static_cast(file.tellg()); + file.seekg(0, std::ios::beg); + std::array magic{}; + u32 version{}; + if (total < magic.size() + sizeof(version)) { + create(0, nullptr); + return; + } + file.read(magic.data(), magic.size()) + .read(reinterpret_cast(&version), sizeof(version)); + if (magic != STATIC_CACHE_MAGIC_NUMBER || version != STATIC_CACHE_VERSION) { + create(0, nullptr); + return; + } + data.resize(total - magic.size() - sizeof(version)); + file.read(data.data(), static_cast(data.size())); + } catch (const std::ios_base::failure& e) { + create(0, nullptr); + return; + } + create(data.size(), data.empty() ? nullptr : data.data()); +} + +void Device::SaveStaticPipelineCache() const { + if (!owns_static_pipeline_cache || !static_pipeline_cache) { + return; + } + const auto filename = StaticPipelineCacheFilename(); + if (filename.empty()) { + return; + } + size_t size = 0; + std::vector data; + static_pipeline_cache.Read(&size, nullptr); + if (size == 0) { + return; + } + data.resize(size); + static_pipeline_cache.Read(&size, data.data()); + try { + std::ofstream file(filename, std::ios::binary | std::ios::trunc); + file.exceptions(std::ofstream::failbit); + if (!file.is_open()) { + return; + } + file.write(STATIC_CACHE_MAGIC_NUMBER.data(), STATIC_CACHE_MAGIC_NUMBER.size()) + .write(reinterpret_cast(&STATIC_CACHE_VERSION), + sizeof(STATIC_CACHE_VERSION)) + .write(data.data(), static_cast(size)); + } catch (const std::ios_base::failure& e) { + Common::FS::RemoveFile(filename); + } +} + VkFormat Device::GetSupportedFormat(VkFormat wanted_format, VkFormatFeatureFlags wanted_usage, FormatType format_type) const { if (IsFormatSupported(wanted_format, wanted_usage, format_type)) { @@ -783,7 +908,6 @@ void Device::SaveShader(std::span spirv) const { } bool Device::ComputeIsOptimalAstcSupported() const { - // Verify hardware supports all ASTC formats with optimal tiling to avoid software conversion static constexpr std::array astc_formats = { VK_FORMAT_ASTC_4x4_UNORM_BLOCK, VK_FORMAT_ASTC_4x4_SRGB_BLOCK, VK_FORMAT_ASTC_5x4_UNORM_BLOCK, VK_FORMAT_ASTC_5x4_SRGB_BLOCK, @@ -803,14 +927,13 @@ bool Device::ComputeIsOptimalAstcSupported() const { if (!features.features.textureCompressionASTC_LDR) { return false; } - const auto format_feature_usage{VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | - VK_FORMAT_FEATURE_BLIT_SRC_BIT | - VK_FORMAT_FEATURE_BLIT_DST_BIT | - VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | - VK_FORMAT_FEATURE_TRANSFER_DST_BIT}; + const VkFormatFeatureFlags format_feature_usage{ + VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | + VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT}; for (const auto format : astc_formats) { const auto physical_format_properties{physical.GetFormatProperties(format)}; - if ((physical_format_properties.optimalTilingFeatures & format_feature_usage) == 0) { + if ((physical_format_properties.optimalTilingFeatures & format_feature_usage) != + format_feature_usage) { return false; } } @@ -923,6 +1046,12 @@ bool Device::GetSuitability(bool requires_swapchain) { FOR_EACH_VK_FEATURE_EXT(FEATURE_EXTENSION); FOR_EACH_VK_EXTENSION(EXTENSION); + extensions.depth_stencil_resolve = + extensions.depth_stencil_resolve && + (instance_version >= VK_API_VERSION_1_2 || extensions.create_renderpass2); + RemoveExtensionIfUnsuitable(extensions.depth_stencil_resolve, + VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME); + if (supported_extensions.contains(VK_KHR_ROBUSTNESS_2_EXTENSION_NAME)) { loaded_extensions.erase(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME); loaded_extensions.insert(VK_KHR_ROBUSTNESS_2_EXTENSION_NAME); @@ -956,6 +1085,10 @@ bool Device::GetSuitability(bool requires_swapchain) { CHECK_EXTENSION(VK_KHR_SWAPCHAIN_EXTENSION_NAME); } + if (instance_version < VK_API_VERSION_1_2) { + CHECK_EXTENSION(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME); + } + #undef LOG_EXTENSION #undef CHECK_EXTENSION @@ -1066,6 +1199,16 @@ bool Device::GetSuitability(bool requires_swapchain) { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR; SetNext(next, properties.push_descriptor); } + if (extensions.depth_stencil_resolve || instance_version >= VK_API_VERSION_1_2) { + properties.depth_stencil_resolve.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES; + SetNext(next, properties.depth_stencil_resolve); + } + if (extensions.descriptor_buffer) { + properties.descriptor_buffer.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_PROPERTIES_EXT; + SetNext(next, properties.descriptor_buffer); + } if (extensions.subgroup_size_control || features.subgroup_size_control.subgroupSizeControl) { properties.subgroup_size_control.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_PROPERTIES; @@ -1081,6 +1224,11 @@ bool Device::GetSuitability(bool requires_swapchain) { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_PROPERTIES_KHR; SetNext(next, properties.maintenance5); } + if (extensions.custom_border_color) { + properties.custom_border_color.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_PROPERTIES_EXT; + SetNext(next, properties.custom_border_color); + } // Perform the property fetch. physical.GetProperties2(properties2); @@ -1160,6 +1308,11 @@ bool Device::GetSuitability(bool requires_swapchain) { } void Device::RemoveUnsuitableExtensions() { + // VK_EXT_color_write_enable + extensions.color_write_enable = features.color_write_enable.colorWriteEnable; + RemoveExtensionFeatureIfUnsuitable(extensions.color_write_enable, features.color_write_enable, + VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME); + // VK_EXT_custom_border_color if (extensions.custom_border_color) { extensions.custom_border_color = @@ -1169,6 +1322,15 @@ void Device::RemoveUnsuitableExtensions() { RemoveExtensionFeatureIfUnsuitable(extensions.custom_border_color, features.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); + // VK_EXT_border_color_swizzle + if (extensions.border_color_swizzle) { + extensions.border_color_swizzle = + extensions.custom_border_color && features.border_color_swizzle.borderColorSwizzle; + } + RemoveExtensionFeatureIfUnsuitable(extensions.border_color_swizzle, + features.border_color_swizzle, + VK_EXT_BORDER_COLOR_SWIZZLE_EXTENSION_NAME); + // VK_EXT_depth_bias_control extensions.depth_bias_control = features.depth_bias_control.depthBiasControl && @@ -1181,6 +1343,11 @@ void Device::RemoveUnsuitableExtensions() { RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control, VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME); + // VK_EXT_descriptor_buffer + extensions.descriptor_buffer = features.descriptor_buffer.descriptorBuffer; + RemoveExtensionFeatureIfUnsuitable(extensions.descriptor_buffer, features.descriptor_buffer, + VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME); + // VK_EXT_extended_dynamic_state extensions.extended_dynamic_state = features.extended_dynamic_state.extendedDynamicState; RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state, @@ -1320,12 +1487,14 @@ void Device::RemoveUnsuitableExtensions() { VK_KHR_PIPELINE_EXECUTABLE_PROPERTIES_EXTENSION_NAME); } + // VK_KHR_shader_quad_control + extensions.shader_quad_control = features.shader_quad_control.shaderQuadControl; + RemoveExtensionFeatureIfUnsuitable(extensions.shader_quad_control, features.shader_quad_control, + VK_KHR_SHADER_QUAD_CONTROL_EXTENSION_NAME); + // VK_KHR_workgroup_memory_explicit_layout extensions.workgroup_memory_explicit_layout = - features.features.shaderInt16 && features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout && - features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess && - features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess && features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayoutScalarBlockLayout; RemoveExtensionFeatureIfUnsuitable(extensions.workgroup_memory_explicit_layout, features.workgroup_memory_explicit_layout, @@ -1365,6 +1534,11 @@ void Device::RemoveUnsuitableExtensions() { // VK_KHR_maintenance8 extensions.maintenance8 = loaded_extensions.contains(VK_KHR_MAINTENANCE_8_EXTENSION_NAME); RemoveExtensionIfUnsuitable(extensions.maintenance8, VK_KHR_MAINTENANCE_8_EXTENSION_NAME); + + // VK_KHR_synchronization2 + extensions.synchronization2 = features.synchronization2.synchronization2; + RemoveExtensionFeatureIfUnsuitable(extensions.synchronization2, features.synchronization2, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); } void Device::SetupFamilies(VkSurfaceKHR surface) { @@ -1396,17 +1570,34 @@ void Device::SetupFamilies(VkSurfaceKHR surface) { } if (graphics) { graphics_family = *graphics; + graphics_family_sparse_binding = + (queue_family_properties[*graphics].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) != 0; } if (present) { present_family = *present; } } -std::optional Device::GetSamplerHeapBudget() const { - if (sampler_heap_budget == 0) { - return std::nullopt; +bool Device::TryReserveCustomBorderColorSamplers(size_t count) const { + const size_t limit = properties.custom_border_color.maxCustomBorderColorSamplers; + if (limit == 0) { + return true; } - return sampler_heap_budget; + size_t used = custom_border_color_samplers_used.load(std::memory_order_relaxed); + while (used + count <= limit) { + if (custom_border_color_samplers_used.compare_exchange_weak( + used, used + count, std::memory_order_relaxed, std::memory_order_relaxed)) { + return true; + } + } + return false; +} + +void Device::ReleaseCustomBorderColorSamplers(size_t count) const { + if (count == 0) { + return; + } + custom_border_color_samplers_used.fetch_sub(count, std::memory_order_relaxed); } u64 Device::GetDeviceMemoryUsage() const { @@ -1477,12 +1668,18 @@ void Device::CollectToolingInfo() { has_nsight_graphics = has_nsight_graphics || name == "NVIDIA Nsight Graphics"; has_radeon_gpu_profiler = has_radeon_gpu_profiler || name == "Radeon GPU Profiler"; } +#ifdef _WIN32 + if (has_renderdoc) { + LOG_INFO(Render_Vulkan, + "Windows default RenderDoc output folder: %LOCALAPPDATA%\\Temp\\RenderDoc"); + } +#endif } std::vector Device::GetDeviceQueueCreateInfos() const { static constexpr float QUEUE_PRIORITY = 1.0f; - ankerl::unordered_dense::set unique_queue_families{graphics_family, present_family}; + ::Common::unordered_set unique_queue_families{graphics_family, present_family}; std::vector queue_cis; queue_cis.reserve(unique_queue_families.size()); @@ -1502,7 +1699,10 @@ std::vector Device::GetDeviceQueueCreateInfos() const { } void Device::InitializeGPULogging() { - if (!Settings::values.gpu_logging_enabled.GetValue()) { + // Get log level from settings - Off is the disable. + const auto log_level = static_cast( + static_cast(Settings::values.gpu_log_level.GetValue())); + if (log_level == GPU::Logging::LogLevel::Off) { return; } @@ -1516,18 +1716,12 @@ void Device::InitializeGPULogging() { detected_driver = GPU::Logging::DriverType::Qualcomm; } - // Get log level from settings - const auto log_level = static_cast( - static_cast(Settings::values.gpu_log_level.GetValue())); - // Initialize GPU logger GPU::Logging::GPULogger::GetInstance().Initialize(log_level, detected_driver); // Configure feature flags GPU::Logging::GPULogger::GetInstance().EnableVulkanCallTracking( Settings::values.gpu_log_vulkan_calls.GetValue()); - GPU::Logging::GPULogger::GetInstance().EnableShaderDumps( - Settings::values.gpu_log_shader_dumps.GetValue()); GPU::Logging::GPULogger::GetInstance().EnableMemoryTracking( Settings::values.gpu_log_memory_tracking.GetValue()); GPU::Logging::GPULogger::GetInstance().EnableDriverDebugInfo( @@ -1559,8 +1753,8 @@ void Device::InitializeGPULogging() { driver_info += fmt::format("Driver ID: {}\n", static_cast(driver_id)); // Vendor and device IDs - driver_info += fmt::format("Vendor ID: 0x{:04X}\n", props.vendorID); - driver_info += fmt::format("Device ID: 0x{:04X}\n", props.deviceID); + driver_info += fmt::format("Vendor ID: {:#04x}\n", props.vendorID); + driver_info += fmt::format("Device ID: {:#04x}\n", props.deviceID); // Extensions - separate QCOM extensions from others driver_info += "\n=== Loaded Vulkan Extensions ===\n"; diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 13fe1b371f..bbf3754e39 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -6,11 +6,12 @@ #pragma once +#include #include #include #include #include -#include +#include "common/container/unordered_map.h" #include #include "common/common_types.h" @@ -36,22 +37,28 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE(EXT, DescriptorIndexing, DESCRIPTOR_INDEXING, descriptor_indexing) \ FEATURE(EXT, HostQueryReset, HOST_QUERY_RESET, host_query_reset) \ FEATURE(KHR, 8BitStorage, 8BIT_STORAGE, bit8_storage) \ - FEATURE(KHR, TimelineSemaphore, TIMELINE_SEMAPHORE, timeline_semaphore) + FEATURE(KHR, BufferDeviceAddress, BUFFER_DEVICE_ADDRESS, buffer_device_address) \ + FEATURE(KHR, TimelineSemaphore, TIMELINE_SEMAPHORE, timeline_semaphore) \ + FEATURE(KHR, VulkanMemoryModel, VULKAN_MEMORY_MODEL, vulkan_memory_model) #define FOR_EACH_VK_FEATURE_1_3(FEATURE) \ FEATURE(EXT, ImageRobustness, IMAGE_ROBUSTNESS, robust_image_access) \ FEATURE(EXT, ShaderDemoteToHelperInvocation, SHADER_DEMOTE_TO_HELPER_INVOCATION, \ shader_demote_to_helper_invocation) \ FEATURE(EXT, SubgroupSizeControl, SUBGROUP_SIZE_CONTROL, subgroup_size_control) \ - FEATURE(KHR, Maintenance4, MAINTENANCE_4, maintenance4) + FEATURE(KHR, Maintenance4, MAINTENANCE_4, maintenance4) \ + FEATURE(KHR, Synchronization2, SYNCHRONIZATION_2, synchronization2) #define FOR_EACH_VK_FEATURE_1_4(FEATURE) // Define all features which may be used by the implementation and require an extension here. #define FOR_EACH_VK_FEATURE_EXT(FEATURE) \ + FEATURE(EXT, BorderColorSwizzle, BORDER_COLOR_SWIZZLE, border_color_swizzle) \ + FEATURE(EXT, ColorWriteEnable, COLOR_WRITE_ENABLE, color_write_enable) \ FEATURE(EXT, CustomBorderColor, CUSTOM_BORDER_COLOR, custom_border_color) \ FEATURE(EXT, DepthBiasControl, DEPTH_BIAS_CONTROL, depth_bias_control) \ FEATURE(EXT, DepthClipControl, DEPTH_CLIP_CONTROL, depth_clip_control) \ + FEATURE(EXT, DescriptorBuffer, DESCRIPTOR_BUFFER, descriptor_buffer) \ FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \ FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \ FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \ @@ -68,12 +75,14 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE(KHR, Maintenance6, MAINTENANCE_6, maintenance6) \ FEATURE(KHR, PipelineExecutableProperties, PIPELINE_EXECUTABLE_PROPERTIES, \ pipeline_executable_properties) \ + FEATURE(KHR, ShaderQuadControl, SHADER_QUAD_CONTROL, shader_quad_control) \ FEATURE(KHR, WorkgroupMemoryExplicitLayout, WORKGROUP_MEMORY_EXPLICIT_LAYOUT, \ workgroup_memory_explicit_layout) // Define miscellaneous extensions which may be used by the implementation here. #define FOR_EACH_VK_EXTENSION(EXTENSION) \ + EXTENSION(EXT, ASTC_DECODE_MODE, astc_decode_mode) \ EXTENSION(EXT, CONDITIONAL_RENDERING, conditional_rendering) \ EXTENSION(EXT, CONSERVATIVE_RASTERIZATION, conservative_rasterization) \ EXTENSION(EXT, DEPTH_RANGE_UNRESTRICTED, depth_range_unrestricted) \ @@ -84,6 +93,8 @@ VK_DEFINE_HANDLE(VmaAllocator) EXTENSION(EXT, SHADER_VIEWPORT_INDEX_LAYER, shader_viewport_index_layer) \ EXTENSION(EXT, TOOLING_INFO, tooling_info) \ EXTENSION(EXT, VERTEX_ATTRIBUTE_DIVISOR, vertex_attribute_divisor) \ + EXTENSION(KHR, CREATE_RENDERPASS_2, create_renderpass2) \ + EXTENSION(KHR, DEPTH_STENCIL_RESOLVE, depth_stencil_resolve) \ EXTENSION(KHR, DRAW_INDIRECT_COUNT, draw_indirect_count) \ EXTENSION(KHR, DRIVER_PROPERTIES, driver_properties) \ EXTENSION(KHR, PUSH_DESCRIPTOR, push_descriptor) \ @@ -171,6 +182,8 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE_NAME(depth_bias_control, depthBiasControl) \ FEATURE_NAME(depth_bias_control, leastRepresentableValueForceUnormRepresentation) \ FEATURE_NAME(depth_bias_control, depthBiasExact) \ + FEATURE_NAME(descriptor_indexing, descriptorBindingPartiallyBound) \ + FEATURE_NAME(descriptor_indexing, shaderSampledImageArrayNonUniformIndexing) \ FEATURE_NAME(extended_dynamic_state, extendedDynamicState) \ FEATURE_NAME(format_a4b4g4r4, formatA4B4G4R4) \ FEATURE_NAME(robust_image_access, robustImageAccess) \ @@ -180,6 +193,7 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE_NAME(robustness2, nullDescriptor) \ FEATURE_NAME(shader_float16_int8, shaderFloat16) \ FEATURE_NAME(shader_float16_int8, shaderInt8) \ + FEATURE_NAME(synchronization2, synchronization2) \ FEATURE_NAME(timeline_semaphore, timelineSemaphore) \ FEATURE_NAME(transform_feedback, transformFeedback) \ FEATURE_NAME(uniform_buffer_standard_layout, uniformBufferStandardLayout) \ @@ -259,6 +273,10 @@ public: return physical; } + VkPipelineCache StaticPipelineCache() const noexcept { + return *static_pipeline_cache; + } + /// Returns the main graphics queue. vk::Queue GetGraphicsQueue() const { return graphics_queue; @@ -299,6 +317,23 @@ public: return properties.driver.driverID; } + bool IsSparseBindingSupported() const { + return features.features.sparseBinding && graphics_family_sparse_binding; + } + + /// Returns true for tile-based deferred renderers. + bool IsTiler() const { + switch (GetDriverID()) { + case VK_DRIVER_ID_QUALCOMM_PROPRIETARY: + case VK_DRIVER_ID_ARM_PROPRIETARY: + case VK_DRIVER_ID_SAMSUNG_PROPRIETARY: + case VK_DRIVER_ID_MESA_TURNIP: + return true; + default: + return false; + } + } + bool ShouldBoostClocks() const; /// Returns uniform buffer alignment requirement. @@ -316,46 +351,41 @@ public: return properties.properties.limits.maxStorageBufferRange; } + std::array GetMaxComputeWorkGroupCount() const { + const auto& count = properties.properties.limits.maxComputeWorkGroupCount; + return {count[0], count[1], count[2]}; + } + /// Returns the maximum size for push constants. VkDeviceSize GetMaxPushConstantsSize() const { return properties.properties.limits.maxPushConstantsSize; } - /// Returns the maximum size for shared memory. - u32 GetMaxComputeSharedMemorySize() const { - return properties.properties.limits.maxComputeSharedMemorySize; - } - - /// Returns the maximum number of dynamic storage buffer descriptors per set. - u32 GetMaxDescriptorSetStorageBuffersDynamic() const { - return properties.properties.limits.maxDescriptorSetStorageBuffersDynamic; - } - - /// Returns the maximum number of dynamic uniform buffer descriptors per set. - u32 GetMaxDescriptorSetUniformBuffersDynamic() const { - return properties.properties.limits.maxDescriptorSetUniformBuffersDynamic; - } - - u32 GetMaxPerStageDescriptorSampledImages() const { - return properties.properties.limits.maxPerStageDescriptorSampledImages; - } - - u32 GetMaxPerStageResources() const { - return properties.properties.limits.maxPerStageResources; - } - - u32 GetMaxDescriptorSetSampledImages() const { - return properties.properties.limits.maxDescriptorSetSampledImages; - } +#define FN_MAX_LIMIT_LIST \ + FN_MAX_LIMIT_ELEM(ComputeSharedMemorySize) \ + FN_MAX_LIMIT_ELEM(PerStageDescriptorSampledImages) \ + FN_MAX_LIMIT_ELEM(PerStageResources) \ + FN_MAX_LIMIT_ELEM(DescriptorSetSamplers) \ + FN_MAX_LIMIT_ELEM(DescriptorSetUniformBuffers) \ + FN_MAX_LIMIT_ELEM(DescriptorSetUniformBuffersDynamic) \ + FN_MAX_LIMIT_ELEM(DescriptorSetStorageBuffers) \ + FN_MAX_LIMIT_ELEM(DescriptorSetStorageBuffersDynamic) \ + FN_MAX_LIMIT_ELEM(DescriptorSetSampledImages) \ + FN_MAX_LIMIT_ELEM(DescriptorSetStorageImages) \ + FN_MAX_LIMIT_ELEM(DescriptorSetInputAttachments) +#define FN_MAX_LIMIT_ELEM(name) \ + u32 GetMax##name() const { return properties.properties.limits.max##name; } +FN_MAX_LIMIT_LIST +#undef FN_MAX_LIMIT_ELEM +#undef FN_MAX_LIMIT_LIST /// Returns float control properties of the device. const VkPhysicalDeviceFloatControlsPropertiesKHR& FloatControlProperties() const { return properties.float_controls; } - /// Returns true if ASTC is natively supported. bool IsOptimalAstcSupported() const { - return features.features.textureCompressionASTC_LDR; + return is_optimal_astc_supported; } /// Returns true if BCn is natively supported. @@ -363,15 +393,32 @@ public: return features.features.textureCompressionBC; } + /// Returns true if ETC2 is natively supported. + bool IsOptimalEtc2Supported() const { + return features.features.textureCompressionETC2; + } + /// Returns true if descriptor aliasing is natively supported. bool IsDescriptorAliasingSupported() const { - return GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY; + return !has_broken_descriptor_aliasing; } bool IsSampledImageArrayNonUniformIndexingSupported() const { return features.descriptor_indexing.shaderSampledImageArrayNonUniformIndexing; } + bool IsStorageImageArrayNonUniformIndexingSupported() const { + return features.descriptor_indexing.shaderStorageImageArrayNonUniformIndexing; + } + + bool IsUniformTexelBufferArrayNonUniformIndexingSupported() const { + return features.descriptor_indexing.shaderUniformTexelBufferArrayNonUniformIndexing; + } + + bool IsStorageTexelBufferArrayNonUniformIndexingSupported() const { + return features.descriptor_indexing.shaderStorageTexelBufferArrayNonUniformIndexing; + } + /// Returns true if the device supports float64 natively. bool IsFloat64Supported() const { return features.features.shaderFloat64; @@ -382,11 +429,36 @@ public: return features.shader_float16_int8.shaderFloat16; } + /// Returns true if the device can run shaders built against the Vulkan memory model. + bool IsVulkanMemoryModelSupported() const { + return features.vulkan_memory_model.vulkanMemoryModel; + } + /// Returns true if the device supports int8 natively. bool IsInt8Supported() const { return features.shader_float16_int8.shaderInt8; } + /// Returns true if the device allows 8-bit integer members in uniform/storage buffers. + bool IsUniformAndStorageBuffer8BitAccessSupported() const { + return features.bit8_storage.uniformAndStorageBuffer8BitAccess; + } + + /// Returns true if the device allows 16-bit integer members in uniform/storage buffers. + bool IsUniformAndStorageBuffer16BitAccessSupported() const { + return features.bit16_storage.uniformAndStorageBuffer16BitAccess; + } + + /// Returns true if the device supports reading 8-bit values from a storage buffer. + bool IsStorageBuffer8BitAccessSupported() const { + return features.bit8_storage.storageBuffer8BitAccess; + } + + /// Returns true if the device supports reading 16-bit values from a storage buffer. + bool IsStorageBuffer16BitAccessSupported() const { + return features.bit16_storage.storageBuffer16BitAccess; + } + /// Returns true if the device supports binding multisample images as storage images. bool IsStorageImageMultisampleSupported() const { return features.features.shaderStorageImageMultisample; @@ -407,11 +479,40 @@ public: return properties.subgroup_properties.supportedOperations & feature; } + VkShaderStageFlags GetSubgroupSupportedStages() const { + return properties.subgroup_properties.supportedStages; + } + + u32 GetMaxSubgroupSize() const { + return (std::max)(properties.subgroup_properties.subgroupSize, + properties.subgroup_size_control.maxSubgroupSize); + } + /// Returns the maximum number of push descriptors. u32 MaxPushDescriptors() const { return properties.push_descriptor.maxPushDescriptors; } + /// Returns true if robust buffer access is enabled on the device. + bool IsRobustBufferAccessEnabled() const { + return features.features.robustBufferAccess == VK_TRUE; + } + + /// Returns true if the device supports descriptor buffers. + bool IsExtDescriptorBufferSupported() const { + return extensions.descriptor_buffer; + } + + /// Returns the descriptor buffer properties of the device. + const VkPhysicalDeviceDescriptorBufferPropertiesEXT& DescriptorBufferProperties() const { + return properties.descriptor_buffer; + } + + /// Returns true if the device supports buffer device address. + bool IsBufferDeviceAddressSupported() const { + return extensions.buffer_device_address; + } + /// Returns true if formatless image load is supported. bool IsFormatlessImageLoadSupported() const { return features.features.shaderStorageImageReadWithoutFormat; @@ -487,6 +588,23 @@ public: return extensions.workgroup_memory_explicit_layout; } + bool IsWorkgroupMemoryExplicitLayout8BitAccessSupported() const { + return extensions.workgroup_memory_explicit_layout && + features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout8BitAccess && + features.shader_float16_int8.shaderInt8; + } + + bool IsWorkgroupMemoryExplicitLayout16BitAccessSupported() const { + return extensions.workgroup_memory_explicit_layout && + features.workgroup_memory_explicit_layout.workgroupMemoryExplicitLayout16BitAccess && + features.features.shaderInt16; + } + + /// Returns true if the device supports VK_KHR_shader_quad_control. + bool IsKhrShaderQuadControlSupported() const { + return extensions.shader_quad_control && features.shader_quad_control.shaderQuadControl; + } + /// Returns true if the device supports VK_KHR_image_format_list. bool IsKhrImageFormatListSupported() const { return extensions.image_format_list || instance_version >= VK_API_VERSION_1_2; @@ -513,11 +631,36 @@ public: } /// Returns true if the device supports VK_EXT_shader_stencil_export. - /// Note: Most Mali/NVIDIA drivers don't support this. Use hardware blits as fallback. bool IsExtShaderStencilExportSupported() const { return extensions.shader_stencil_export; } + /// Returns true if the device supports VK_KHR_create_renderpass2. + bool IsKhrCreateRenderPass2Supported() const { + return extensions.create_renderpass2 || instance_version >= VK_API_VERSION_1_2; + } + + /// Returns true if the device supports VK_KHR_depth_stencil_resolve. + bool IsKhrDepthStencilResolveSupported() const { + return (extensions.depth_stencil_resolve || instance_version >= VK_API_VERSION_1_2) && + IsKhrCreateRenderPass2Supported(); + } + + /// Returns the supported resolve modes for the depth aspect. + VkResolveModeFlags GetDepthResolveModes() const { + return properties.depth_stencil_resolve.supportedDepthResolveModes; + } + + /// Returns the supported resolve modes for the stencil aspect. + VkResolveModeFlags GetStencilResolveModes() const { + return properties.depth_stencil_resolve.supportedStencilResolveModes; + } + + /// Returns true if only one of the depth and stencil aspects may be resolved. + bool SupportsIndependentResolveNone() const { + return properties.depth_stencil_resolve.independentResolveNone == VK_TRUE; + } + /// Returns true if depth/stencil operations can be performed efficiently. /// Either through shader export or hardware blits. bool CanPerformDepthStencilOperations() const { @@ -550,6 +693,11 @@ public: return extensions.subgroup_size_control; } + /// Returns true if vkResetQueryPool (host-side query reset) is supported. + bool IsHostQueryResetSupported() const { + return features.host_query_reset.hostQueryReset != VK_FALSE; + } + /// Returns true if the device supports VK_EXT_transform_feedback. bool IsExtTransformFeedbackSupported() const { return extensions.transform_feedback; @@ -571,19 +719,38 @@ public: return features.transform_feedback.geometryStreams; } - /// Returns true if the device supports VK_EXT_custom_border_color. - bool IsExtCustomBorderColorSupported() const { - return extensions.custom_border_color; + /// Returns true if custom border colors can be created without a format. + bool IsCustomBorderColorUsable() const { + return extensions.custom_border_color && + features.custom_border_color.customBorderColors && + features.custom_border_color.customBorderColorWithoutFormat; } - /// Returns true if customBorderColors feature is available. - bool IsCustomBorderColorsSupported() const { - return features.custom_border_color.customBorderColors; + /// Takes budget for samplers carrying a custom border color, false when exhausted. + bool TryReserveCustomBorderColorSamplers(size_t count) const; + + /// Gives back budget taken by TryReserveCustomBorderColorSamplers. + void ReleaseCustomBorderColorSamplers(size_t count) const; + + /// Returns true if the device supports VK_EXT_color_write_enable. + bool IsExtColorWriteEnableSupported() const { + return extensions.color_write_enable; } - /// Returns true if customBorderColorWithoutFormat feature is available. - bool IsCustomBorderColorWithoutFormatSupported() const { - return features.custom_border_color.customBorderColorWithoutFormat; + /// Returns true if the device supports VK_EXT_border_color_swizzle. + bool IsExtBorderColorSwizzleSupported() const { + return extensions.border_color_swizzle; + } + + /// Returns true if samplers must be carried with border color swizzle mapping. + bool NeedsBorderColorSwizzleMapping() const { + return extensions.border_color_swizzle && + !features.border_color_swizzle.borderColorSwizzleFromImage; + } + + /// Returns true if borderColorSwizzleFromImage is available. + bool IsBorderColorSwizzleFromImageSupported() const { + return features.border_color_swizzle.borderColorSwizzleFromImage; } /// Returns true if the device supports VK_EXT_extended_dynamic_state. @@ -720,12 +887,31 @@ public: return extensions.shader_atomic_int64; } + bool IsSharedInt64AtomicsSupported() const { + return extensions.shader_atomic_int64 && + features.shader_atomic_int64.shaderSharedInt64Atomics; + } + bool IsExtConditionalRendering() const { return extensions.conditional_rendering; } + bool IsExtAstcDecodeModeSupported() const { + return extensions.astc_decode_mode; + } + + /// Returns true if descriptor bindings is partially bound. + bool IsDescriptorBindingPartiallyBoundSupported() const { + return features.descriptor_indexing.descriptorBindingPartiallyBound; + } + bool HasTimelineSemaphore() const; + /// Returns true if the device supports VK_KHR_synchronization2. + bool HasSynchronization2() const { + return extensions.synchronization2; + } + /// Returns the minimum supported version of SPIR-V. u32 SupportedSpirvVersion() const { if (instance_version >= VK_API_VERSION_1_3) { @@ -757,8 +943,6 @@ public: return has_broken_parallel_compiling; } - std::optional GetSamplerHeapBudget() const; - /// Returns the vendor name reported from Vulkan. std::string_view GetVendorName() const { return properties.driver.driverName; @@ -787,10 +971,6 @@ public: return supports_d24_depth; } - bool CantBlitMSAA() const { - return cant_blit_msaa; - } - bool MustEmulateScaledFormats() const { return must_emulate_scaled_formats; } @@ -960,6 +1140,9 @@ private: /// Returns true if the device natively supports blitting depth stencil images. bool TestDepthStencilBlits(VkFormat format) const; + void LoadStaticPipelineCache(); + void SaveStaticPipelineCache() const; + private: VkInstance instance; ///< Vulkan instance. VmaAllocator allocator; ///< VMA allocator. @@ -968,9 +1151,12 @@ private: vk::Device logical; ///< Logical device. vk::Queue graphics_queue; ///< Main graphics queue. vk::Queue present_queue; ///< Main present queue. + vk::PipelineCache static_pipeline_cache; + bool owns_static_pipeline_cache{}; u32 instance_version{}; ///< Vulkan instance version. u32 graphics_family{}; ///< Main graphics queue family index. u32 present_family{}; ///< Main present queue family index. + bool graphics_family_sparse_binding{}; struct Extensions { #define EXTENSION(prefix, macro_name, var_name) bool var_name{}; @@ -1010,9 +1196,12 @@ private: VkPhysicalDeviceSubgroupProperties subgroup_properties{}; VkPhysicalDeviceFloatControlsProperties float_controls{}; VkPhysicalDevicePushDescriptorPropertiesKHR push_descriptor{}; + VkPhysicalDeviceDescriptorBufferPropertiesEXT descriptor_buffer{}; VkPhysicalDeviceSubgroupSizeControlProperties subgroup_size_control{}; VkPhysicalDeviceTransformFeedbackPropertiesEXT transform_feedback{}; VkPhysicalDeviceMaintenance5PropertiesKHR maintenance5{}; + VkPhysicalDeviceDepthStencilResolveProperties depth_stencil_resolve{}; + VkPhysicalDeviceCustomBorderColorPropertiesEXT custom_border_color{}; VkPhysicalDeviceProperties properties{}; }; @@ -1034,12 +1223,12 @@ private: bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device. bool has_broken_compute{}; ///< Compute shaders can cause crashes bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit + bool has_broken_descriptor_aliasing{}; ///< Miscompiles descriptors aliased on one binding bool has_broken_parallel_compiling{}; ///< Has broken parallel shader compiling. bool has_renderdoc{}; ///< Has RenderDoc attached bool has_nsight_graphics{}; ///< Has Nsight Graphics attached bool has_radeon_gpu_profiler{}; ///< Has Radeon GPU Profiler attached. bool supports_d24_depth{}; ///< Supports D24 depth buffers. - bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting. bool must_emulate_scaled_formats{}; ///< Requires scaled vertex format emulation bool dynamic_state3_blending{}; ///< Has blending features of dynamic_state3. bool dynamic_state3_enables{}; ///< Has at least one enable feature of dynamic_state3. @@ -1051,7 +1240,7 @@ private: bool dynamic_state3_alpha_to_coverage{}; bool dynamic_state3_alpha_to_one{}; bool supports_conditional_barriers{}; ///< Allows barriers in conditional control flow. - size_t sampler_heap_budget{}; ///< Sampler budget for buggy drivers (0 = unlimited). + mutable std::atomic custom_border_color_samplers_used{}; u64 device_access_memory{}; ///< Total size of device local memory in bytes. u32 sets_per_pool{}; ///< Sets per Description Pool NvidiaArchitecture nvidia_arch{NvidiaArchitecture::Arch_AmpereOrNewer}; @@ -1062,7 +1251,7 @@ private: std::vector valid_heap_memory; ///< Heaps used. /// Format properties dictionary. - ankerl::unordered_dense::map format_properties; + ::Common::unordered_map format_properties; /// Nsight Aftermath GPU crash tracker std::unique_ptr nsight_aftermath_tracker; diff --git a/src/video_core/vulkan_common/vulkan_instance.cpp b/src/video_core/vulkan_common/vulkan_instance.cpp index 398342296e..7f3d8ed41f 100644 --- a/src/video_core/vulkan_common/vulkan_instance.cpp +++ b/src/video_core/vulkan_common/vulkan_instance.cpp @@ -36,8 +36,8 @@ namespace { } [[nodiscard]] std::vector RequiredExtensions( - const vk::InstanceDispatch& dld, Core::Frontend::WindowSystemType window_type, - bool enable_validation) { + const vk::InstanceDispatch& dld, std::vector const& properties, + Core::Frontend::WindowSystemType window_type, bool enable_validation) { std::vector extensions; extensions.reserve(6); switch (window_type) { @@ -74,14 +74,14 @@ namespace { if (window_type != Core::Frontend::WindowSystemType::Headless) { extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); } - if (auto const properties = vk::EnumerateInstanceExtensionProperties(dld); properties) { + // Probe optional extensions against the same snapshot the caller verifies against, so the + // check here and the verification in CreateInstance can never disagree (see TOCTOU note below). #ifdef __APPLE__ - if (AreExtensionsSupported(dld, *properties, std::array{VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME})) - extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + if (AreExtensionsSupported(dld, properties, std::array{VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME})) + extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); #endif - if (enable_validation && AreExtensionsSupported(dld, *properties, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME})) - extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - } + if (enable_validation && AreExtensionsSupported(dld, properties, std::array{VK_EXT_DEBUG_UTILS_EXTENSION_NAME})) + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); return extensions; } @@ -128,9 +128,19 @@ vk::Instance CreateInstance(const Common::DynamicLibrary& library, vk::InstanceD LOG_ERROR(Render_Vulkan, "Failed to load Vulkan function pointers"); throw vk::Exception(VK_ERROR_INITIALIZATION_FAILED); } - std::vector const extensions = RequiredExtensions(dld, window_type, enable_validation); + // Enumerate instance extensions exactly once. RequiredExtensions() used to enumerate a second + // time internally; if the driver returned a different set between the two calls (a real TOCTOU + // seen on some AMD iGPU drivers), an extension added to the list could be missing from the + // verification snapshot, throwing EXTENSION_NOT_PRESENT on an otherwise valid launch. Sharing + // one snapshot for both the optional-extension probe and the final check removes that window. auto const properties = vk::EnumerateInstanceExtensionProperties(dld); - if (!properties || !AreExtensionsSupported(dld, *properties, extensions)) + if (!properties) { + LOG_ERROR(Render_Vulkan, "Failed to query instance extension properties"); + throw vk::Exception(VK_ERROR_EXTENSION_NOT_PRESENT); + } + std::vector const extensions = + RequiredExtensions(dld, *properties, window_type, enable_validation); + if (!AreExtensionsSupported(dld, *properties, extensions)) throw vk::Exception(VK_ERROR_EXTENSION_NOT_PRESENT); std::vector layers = Layers(enable_validation); RemoveUnavailableLayers(dld, layers); diff --git a/src/video_core/vulkan_common/vulkan_library.cpp b/src/video_core/vulkan_common/vulkan_library.cpp index 271b622e65..d174a9d3c8 100644 --- a/src/video_core/vulkan_common/vulkan_library.cpp +++ b/src/video_core/vulkan_common/vulkan_library.cpp @@ -16,7 +16,7 @@ namespace Vulkan { std::shared_ptr OpenLibrary( [[maybe_unused]] Core::Frontend::GraphicsContext* context) { LOG_DEBUG(Render_Vulkan, "Looking for a Vulkan library"); -#if defined(ANDROID) && defined(ARCHITECTURE_arm64) +#if defined(__ANDROID__) && defined(ARCHITECTURE_arm64) // Android manages its Vulkan driver from the frontend. return context->GetDriverLibrary(); #else diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp index 9c7cd8a61f..f4721543ae 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp @@ -26,348 +26,383 @@ #include "common/settings.h" namespace Vulkan { - namespace { +namespace { // Helpers translating MemoryUsage to flags/usage - [[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; + [[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; + } - [[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferredVmaFlags(MemoryUsage usage) { - if (usage == MemoryUsage::Download) { - return VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; - } - return usage != MemoryUsage::DeviceLocal ? VK_MEMORY_PROPERTY_HOST_COHERENT_BIT - : VkMemoryPropertyFlagBits{}; + [[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferredVmaFlags(MemoryUsage usage) { + if (usage == MemoryUsage::Download) { + return VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; } + 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]] 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; + [[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; } - - -// 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 + return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + } +} // 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} { - // Log GPU memory allocation - if (Settings::values.gpu_logging_enabled.GetValue() && - Settings::values.gpu_log_memory_tracking.GetValue()) { - GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation( - reinterpret_cast(memory), - static_cast(size), - 0 // Memory property flags (not easily available from VMA) +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} { + // Log GPU memory allocation + if (GPU::Logging::IsActive() && + Settings::values.gpu_log_memory_tracking.GetValue()) { + GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation( + reinterpret_cast(memory), + static_cast(size), + 0 // Memory property flags (not easily available from VMA) + ); + } +} + +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) { + // Log GPU memory deallocation + if (GPU::Logging::IsActive() && + Settings::values.gpu_log_memory_tracking.GetValue() && + memory != VK_NULL_HANDLE) { + GPU::Logging::GPULogger::GetInstance().LogMemoryDeallocation( + reinterpret_cast(memory) ); } - } - 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) { + if (mapped_ptr) { vmaUnmapMemory(allocator, allocation); mapped_ptr = nullptr; } + vmaFreeMemory(allocator, allocation); } + allocation = nullptr; + allocator = nullptr; + memory = VK_NULL_HANDLE; + offset = 0; + size = 0; +} - void MemoryCommit::Release() { - if (allocation && allocator) { - // Log GPU memory deallocation - if (Settings::values.gpu_logging_enabled.GetValue() && - Settings::values.gpu_log_memory_tracking.GetValue() && - memory != VK_NULL_HANDLE) { - GPU::Logging::GPULogger::GetInstance().LogMemoryDeallocation( - reinterpret_cast(memory) - ); - } +MemoryAllocator::MemoryAllocator(const Device &device_) + : device{device_}, allocator{device.GetAllocator()}, + properties{device_.GetPhysical().GetMemoryProperties().memoryProperties}, + buffer_image_granularity{ + device_.GetPhysical().GetProperties().limits.bufferImageGranularity} { - 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); - } + // 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, - }; - - VkImage handle{}; - VmaAllocation allocation{}; - VmaAllocationInfo alloc_info{}; - vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info)); - - // Log GPU memory allocation for images - if (Settings::values.gpu_logging_enabled.GetValue() && - Settings::values.gpu_log_memory_tracking.GetValue()) { - GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation( - reinterpret_cast(alloc_info.deviceMemory), - static_cast(alloc_info.size), - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT - ); - } - - 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); - - // Log GPU memory allocation for buffers - if (Settings::values.gpu_logging_enabled.GetValue() && - Settings::values.gpu_log_memory_tracking.GetValue()) { - GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation( - reinterpret_cast(alloc_info.deviceMemory), - static_cast(alloc_info.size), - 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()); - } - - 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); - - VmaAllocation a{}; - VmaAllocationInfo info{}; - - VkResult res = vmaAllocateMemory(allocator, &reqs, &ci, &a, &info); - - 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); - - // 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); } - } + }); + } +} - vk::Check(res); - return MemoryCommit(allocator, a, info); +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{}; + VmaAllocationInfo alloc_info{}; + vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info)); + + // Log GPU memory allocation for images + if (GPU::Logging::IsActive() && + Settings::values.gpu_log_memory_tracking.GetValue()) { + GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation( + reinterpret_cast(alloc_info.deviceMemory), + static_cast(alloc_info.size), + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT + ); } - MemoryCommit MemoryAllocator::Commit(const vk::Buffer &buffer, MemoryUsage usage) { - // Allocate memory appropriate for this buffer automatically - const auto vma_usage = MemoryUsageVma(usage); + return vk::Image(handle, ci.usage, *device.GetLogical(), allocator, allocation, + device.GetDispatchLoader()); +} - 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; +vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const { + // MESA will do memcpy() if not marked as host cached, so just force mark it for most buffers + auto const anv_flags = (usage == MemoryUsage::Stream + && device.GetDriverID() == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA) + ? VK_MEMORY_PROPERTY_HOST_CACHED_BIT : 0; + const VmaAllocationCreateInfo alloc_ci = { + .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage), + .usage = MemoryUsageVma(usage), + .requiredFlags = 0, + .preferredFlags = MemoryUsagePreferredVmaFlags(usage) | anv_flags, + .memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types, + .pool = VK_NULL_HANDLE, + .pUserData = nullptr, + .priority = 0.f, + }; - const VkBuffer raw = *buffer; + VkBuffer handle{}; + VmaAllocationInfo alloc_info{}; + VmaAllocation allocation{}; + VkMemoryPropertyFlags property_flags{}; - VmaAllocation a{}; - VmaAllocationInfo info{}; + vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info)); + vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags); - // 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); - } - } - - vk::Check(res); - vk::Check(vmaBindBufferMemory2(allocator, a, 0, raw, nullptr)); - return MemoryCommit(allocator, a, info); + // Log GPU memory allocation for buffers + if (GPU::Logging::IsActive() && + Settings::values.gpu_log_memory_tracking.GetValue()) { + GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation( + reinterpret_cast(alloc_info.deviceMemory), + static_cast(alloc_info.size), + 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; + const vk::MemoryLocation location{ + .memory = alloc_info.deviceMemory, + .offset = alloc_info.offset, + .memory_type = alloc_info.memoryType, + }; + return vk::Buffer(handle, *device.GetLogical(), allocator, allocation, mapped_data, is_coherent, + location, device.GetDispatchLoader()); +} + +vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage, + VkDeviceSize min_alignment) const { + if (min_alignment <= 1) { + return CreateBuffer(ci, usage); + } + VkMemoryPropertyFlags anv_flags = 0; + if (usage == MemoryUsage::Stream && + device.GetDriverID() == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA) { + anv_flags = VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + } + u32 memory_type_bits = valid_memory_types; + if (usage == MemoryUsage::Stream) { + memory_type_bits = 0u; + } + const VmaAllocationCreateInfo alloc_ci = { + .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage), + .usage = MemoryUsageVma(usage), + .requiredFlags = 0, + .preferredFlags = MemoryUsagePreferredVmaFlags(usage) | anv_flags, + .memoryTypeBits = memory_type_bits, + .pool = VK_NULL_HANDLE, + .pUserData = nullptr, + .priority = 0.f, + }; + + VkBuffer handle{}; + VmaAllocationInfo alloc_info{}; + VmaAllocation allocation{}; + VkMemoryPropertyFlags property_flags{}; + + vk::Check(vmaCreateBufferWithAlignment(allocator, &ci, &alloc_ci, min_alignment, &handle, + &allocation, &alloc_info)); + vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags); + + u8 *data = reinterpret_cast(alloc_info.pMappedData); + std::span mapped_data{}; + if (data) { + mapped_data = std::span{data, ci.size}; + } + const bool is_coherent = (property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0; + + const vk::MemoryLocation location{ + .memory = alloc_info.deviceMemory, + .offset = alloc_info.offset, + .memory_type = alloc_info.memoryType, + }; + return vk::Buffer(handle, *device.GetLogical(), allocator, allocation, mapped_data, is_coherent, + location, device.GetDispatchLoader()); +} + +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); + + VmaAllocation a{}; + VmaAllocationInfo info{}; + + VkResult res = vmaAllocateMemory(allocator, &reqs, &ci, &a, &info); + + 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); + + // 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); + } + } + + 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); + } + } + + vk::Check(res); + vk::Check(vmaBindBufferMemory2(allocator, a, 0, raw, nullptr)); + return MemoryCommit(allocator, a, info); +} } // 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 581f2e66d2..a0f9081fc0 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.h +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -107,6 +107,9 @@ namespace Vulkan { vk::Buffer CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const; + vk::Buffer CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage, + VkDeviceSize min_alignment) const; + /** * Commits a memory with the specified requirements. * diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index 871ce52678..bb43fe399f 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -123,7 +123,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkCmdEndDebugUtilsLabelEXT); X(vkCmdFillBuffer); X(vkCmdPipelineBarrier); - X(vkCmdResetQueryPool); + X(vkCmdPipelineBarrier2); X(vkCmdPushConstants); X(vkCmdPushDescriptorSetWithTemplateKHR); X(vkCmdSetBlendConstants); @@ -162,8 +162,10 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkCmdSetStencilTestEnableEXT); X(vkCmdSetVertexInputEXT); X(vkCmdSetColorWriteMaskEXT); + X(vkCmdSetColorWriteEnableEXT); X(vkCmdSetColorBlendEnableEXT); X(vkCmdSetColorBlendEquationEXT); + X(vkCmdResetQueryPool); X(vkCmdResolveImage); X(vkCreateBuffer); X(vkCreateBufferView); @@ -182,6 +184,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkCreatePipelineLayout); X(vkCreateQueryPool); X(vkCreateRenderPass); + X(vkCreateRenderPass2); X(vkCreateSampler); X(vkCreateSemaphore); X(vkCreateShaderModule); @@ -226,7 +229,9 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkGetPipelineExecutableStatisticsKHR); X(vkGetSemaphoreCounterValue); X(vkMapMemory); + X(vkQueueBindSparse); X(vkQueueSubmit); + X(vkQueueSubmit2); X(vkResetFences); X(vkResetQueryPool); X(vkSetDebugUtilsObjectNameEXT); @@ -234,6 +239,12 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkUnmapMemory); X(vkUpdateDescriptorSetWithTemplate); X(vkUpdateDescriptorSets); + X(vkGetBufferDeviceAddress); + X(vkGetDescriptorSetLayoutSizeEXT); + X(vkGetDescriptorSetLayoutBindingOffsetEXT); + X(vkGetDescriptorEXT); + X(vkCmdBindDescriptorBuffersEXT); + X(vkCmdSetDescriptorBufferOffsetsEXT); X(vkWaitForFences); X(vkWaitSemaphores); @@ -253,6 +264,18 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { Proc(dld.vkCmdDrawIndirectCount, dld, "vkCmdDrawIndirectCountKHR", device); Proc(dld.vkCmdDrawIndexedIndirectCount, dld, "vkCmdDrawIndexedIndirectCountKHR", device); } + + // Synchronization2 is core in Vulkan 1.3, otherwise requires VK_KHR_synchronization2 + if (!dld.vkCmdPipelineBarrier2) { + Proc(dld.vkCmdPipelineBarrier2, dld, "vkCmdPipelineBarrier2KHR", device); + } + if (!dld.vkQueueSubmit2) { + Proc(dld.vkQueueSubmit2, dld, "vkQueueSubmit2KHR", device); + } + + if (!dld.vkCreateRenderPass2) { + Proc(dld.vkCreateRenderPass2, dld, "vkCreateRenderPass2KHR", device); + } #undef X } @@ -290,6 +313,7 @@ bool Load(VkInstance instance, InstanceDispatch& dld) noexcept { X(vkDestroyDebugReportCallbackEXT); X(vkDestroySurfaceKHR); X(vkGetPhysicalDeviceFeatures2); + X(vkGetPhysicalDeviceFormatProperties2); X(vkGetPhysicalDeviceProperties2); X(vkGetPhysicalDeviceSurfaceCapabilitiesKHR); X(vkGetPhysicalDeviceSurfaceFormatsKHR); @@ -437,7 +461,7 @@ Instance Instance::Create(u32 version, Span layers, SpanvkCreateRenderPass2(handle, &ci, nullptr, &object)); + return RenderPass(object, handle, *dld); +} + DescriptorSetLayout Device::CreateDescriptorSetLayout( const VkDescriptorSetLayoutCreateInfo& ci) const { VkDescriptorSetLayout object; @@ -726,10 +756,15 @@ PipelineLayout Device::CreatePipelineLayout(const VkPipelineLayoutCreateInfo& ci return PipelineLayout(object, handle, *dld); } -Pipeline Device::CreateGraphicsPipeline(const VkGraphicsPipelineCreateInfo& ci, - VkPipelineCache cache) const { - VkPipeline object; - Check(dld->vkCreateGraphicsPipelines(handle, cache, 1, &ci, nullptr, &object)); +Pipeline Device::CreateGraphicsPipeline(const VkGraphicsPipelineCreateInfo& ci, VkPipelineCache cache) const { + VkPipeline object = VK_NULL_HANDLE; + auto const result = dld->vkCreateGraphicsPipelines(handle, cache, 1, &ci, nullptr, &object); + // Adreno 5xx drivers do not properly return when a graphics pipeline fails to be created + // Some (unkown) Mali drivers also do not properly return + // This result code is out of spec, but should be handled as "kinda working" + if (result == VK_INCOMPLETE) + return Pipeline(object, handle, *dld); + Check(result); return Pipeline(object, handle, *dld); } @@ -900,6 +935,23 @@ VkFormatProperties PhysicalDevice::GetFormatProperties(VkFormat format) const no return properties; } +VkFormatProperties3 PhysicalDevice::GetFormatProperties3(VkFormat format) const noexcept { + VkFormatProperties3 properties3{ + .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_3, + .pNext = nullptr, + .linearTilingFeatures = 0, + .optimalTilingFeatures = 0, + .bufferFeatures = 0, + }; + VkFormatProperties2 properties2{ + .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2, + .pNext = &properties3, + .formatProperties = {}, + }; + dld->vkGetPhysicalDeviceFormatProperties2(physical_device, format, &properties2); + return properties3; +} + std::vector PhysicalDevice::EnumerateDeviceExtensionProperties() const { u32 num; dld->vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &num, nullptr); diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index 4a3baad2c4..97d2ba3700 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -146,6 +147,18 @@ inline VkResult Filter(VkResult result) { return result; } +inline constexpr VkPipelineStageFlags PIPELINE_STAGE_GRAPHICS_COMPUTE = + VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + +inline constexpr VkPipelineStageFlags PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER = + PIPELINE_STAGE_GRAPHICS_COMPUTE | VK_PIPELINE_STAGE_TRANSFER_BIT; + +inline constexpr VkPipelineStageFlags PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER_HOST = + PIPELINE_STAGE_GRAPHICS_COMPUTE_TRANSFER | VK_PIPELINE_STAGE_HOST_BIT; + +inline constexpr VkPipelineStageFlags PIPELINE_STAGE_HOST = VK_PIPELINE_STAGE_HOST_BIT; + + /// Table holding Vulkan instance function pointers. struct InstanceDispatch { PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{}; @@ -167,6 +180,7 @@ struct InstanceDispatch { PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr{}; PFN_vkGetPhysicalDeviceFeatures2 vkGetPhysicalDeviceFeatures2{}; PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties{}; + PFN_vkGetPhysicalDeviceFormatProperties2 vkGetPhysicalDeviceFormatProperties2{}; PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties{}; PFN_vkGetPhysicalDeviceMemoryProperties2 vkGetPhysicalDeviceMemoryProperties2{}; PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties{}; @@ -225,9 +239,10 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkCmdEndTransformFeedbackEXT vkCmdEndTransformFeedbackEXT{}; PFN_vkCmdFillBuffer vkCmdFillBuffer{}; PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier{}; - PFN_vkCmdResetQueryPool vkCmdResetQueryPool{}; + PFN_vkCmdPipelineBarrier2 vkCmdPipelineBarrier2{}; PFN_vkCmdPushConstants vkCmdPushConstants{}; PFN_vkCmdPushDescriptorSetWithTemplateKHR vkCmdPushDescriptorSetWithTemplateKHR{}; + PFN_vkCmdResetQueryPool vkCmdResetQueryPool{}; PFN_vkCmdResolveImage vkCmdResolveImage{}; PFN_vkCmdSetBlendConstants vkCmdSetBlendConstants{}; PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT{}; @@ -264,6 +279,7 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkCmdSetVertexInputEXT vkCmdSetVertexInputEXT{}; PFN_vkCmdSetViewport vkCmdSetViewport{}; PFN_vkCmdSetColorWriteMaskEXT vkCmdSetColorWriteMaskEXT{}; + PFN_vkCmdSetColorWriteEnableEXT vkCmdSetColorWriteEnableEXT{}; PFN_vkCmdSetColorBlendEnableEXT vkCmdSetColorBlendEnableEXT{}; PFN_vkCmdSetColorBlendEquationEXT vkCmdSetColorBlendEquationEXT{}; PFN_vkCmdWaitEvents vkCmdWaitEvents{}; @@ -284,6 +300,7 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkCreatePipelineLayout vkCreatePipelineLayout{}; PFN_vkCreateQueryPool vkCreateQueryPool{}; PFN_vkCreateRenderPass vkCreateRenderPass{}; + PFN_vkCreateRenderPass2 vkCreateRenderPass2{}; PFN_vkCreateSampler vkCreateSampler{}; PFN_vkCreateSemaphore vkCreateSemaphore{}; PFN_vkCreateShaderModule vkCreateShaderModule{}; @@ -328,13 +345,21 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkGetQueryPoolResults vkGetQueryPoolResults{}; PFN_vkGetSemaphoreCounterValue vkGetSemaphoreCounterValue{}; PFN_vkMapMemory vkMapMemory{}; + PFN_vkQueueBindSparse vkQueueBindSparse{}; PFN_vkQueueSubmit vkQueueSubmit{}; + PFN_vkQueueSubmit2 vkQueueSubmit2{}; PFN_vkResetFences vkResetFences{}; PFN_vkResetQueryPool vkResetQueryPool{}; PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT{}; PFN_vkSetDebugUtilsObjectTagEXT vkSetDebugUtilsObjectTagEXT{}; PFN_vkUnmapMemory vkUnmapMemory{}; PFN_vkUpdateDescriptorSetWithTemplate vkUpdateDescriptorSetWithTemplate{}; + PFN_vkGetBufferDeviceAddress vkGetBufferDeviceAddress{}; + PFN_vkGetDescriptorSetLayoutSizeEXT vkGetDescriptorSetLayoutSizeEXT{}; + PFN_vkGetDescriptorSetLayoutBindingOffsetEXT vkGetDescriptorSetLayoutBindingOffsetEXT{}; + PFN_vkGetDescriptorEXT vkGetDescriptorEXT{}; + PFN_vkCmdBindDescriptorBuffersEXT vkCmdBindDescriptorBuffersEXT{}; + PFN_vkCmdSetDescriptorBufferOffsetsEXT vkCmdSetDescriptorBufferOffsetsEXT{}; PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets{}; PFN_vkWaitForFences vkWaitForFences{}; PFN_vkWaitSemaphores vkWaitSemaphores{}; @@ -442,7 +467,7 @@ public: return handle != Type{}; } -#ifndef ANDROID +#ifndef __ANDROID__ /** * Releases ownership of the managed handle. * The caller is responsible for managing the lifetime of the returned handle. @@ -524,7 +549,7 @@ public: return handle != Type{}; } -#ifndef ANDROID +#ifndef __ANDROID__ /** * Releases ownership of the managed handle. * The caller is responsible for managing the lifetime of the returned handle. @@ -716,13 +741,20 @@ private: const DeviceDispatch* dld = nullptr; }; +struct MemoryLocation { + VkDeviceMemory memory{}; + VkDeviceSize offset{}; + u32 memory_type{}; +}; + class Buffer { public: explicit Buffer(VkBuffer handle_, VkDevice owner_, VmaAllocator allocator_, VmaAllocation allocation_, std::span mapped_, bool is_coherent_, - const DeviceDispatch& dld_) noexcept + MemoryLocation location_, const DeviceDispatch& dld_) noexcept : handle{handle_}, owner{owner_}, allocator{allocator_}, - allocation{allocation_}, mapped{mapped_}, is_coherent{is_coherent_}, dld{&dld_} {} + allocation{allocation_}, mapped{mapped_}, location{location_}, + is_coherent{is_coherent_}, dld{&dld_} {} Buffer() = default; Buffer(const Buffer&) = delete; @@ -730,7 +762,7 @@ public: Buffer(Buffer&& rhs) noexcept : handle{std::exchange(rhs.handle, VkBuffer{})}, owner{rhs.owner}, allocator{rhs.allocator}, - allocation{rhs.allocation}, mapped{rhs.mapped}, + allocation{rhs.allocation}, mapped{rhs.mapped}, location{rhs.location}, is_coherent{rhs.is_coherent}, dld{rhs.dld} {} Buffer& operator=(Buffer&& rhs) noexcept { @@ -740,6 +772,7 @@ public: allocator = rhs.allocator; allocation = rhs.allocation; mapped = rhs.mapped; + location = rhs.location; is_coherent = rhs.is_coherent; dld = rhs.dld; return *this; @@ -776,12 +809,21 @@ public: return !mapped.empty(); } + /// Returns true if host writes are visible to the device. + bool IsHostCoherent() const noexcept { + return is_coherent; + } + void Flush() const; void Invalidate() const; void SetObjectNameEXT(const char* name) const; + MemoryLocation Location() const noexcept { + return location; + } + private: void Release() const noexcept; @@ -790,6 +832,7 @@ private: VmaAllocator allocator = nullptr; VmaAllocation allocation = nullptr; std::span mapped = {}; + MemoryLocation location{}; bool is_coherent = false; const DeviceDispatch* dld = nullptr; }; @@ -808,6 +851,17 @@ public: return dld->vkQueueSubmit(queue, submit_infos.size(), submit_infos.data(), fence); } + /// Submits using VK_KHR_synchronization2 / Vulkan 1.3 vkQueueSubmit2. + VkResult Submit2(Span submit_infos, + VkFence fence = VK_NULL_HANDLE) const noexcept { + return dld->vkQueueSubmit2(queue, submit_infos.size(), submit_infos.data(), fence); + } + + VkResult BindSparse(Span bind_infos, + VkFence fence = VK_NULL_HANDLE) const noexcept { + return dld->vkQueueBindSparse(queue, bind_infos.size(), bind_infos.data(), fence); + } + VkResult Present(const VkPresentInfoKHR& present_info) const noexcept { return dld->vkQueuePresentKHR(queue, &present_info); } @@ -1011,6 +1065,8 @@ public: [[nodiscard]] RenderPass CreateRenderPass(const VkRenderPassCreateInfo& ci) const; + [[nodiscard]] RenderPass CreateRenderPass2(const VkRenderPassCreateInfo2& ci) const; + [[nodiscard]] DescriptorSetLayout CreateDescriptorSetLayout( const VkDescriptorSetLayoutCreateInfo& ci) const; @@ -1064,6 +1120,34 @@ public: dld->vkUpdateDescriptorSetWithTemplate(handle, set, update_template, data); } + [[nodiscard]] VkDeviceAddress GetBufferDeviceAddress(VkBuffer buffer) const noexcept { + const VkBufferDeviceAddressInfo info{ + .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, + .pNext = nullptr, + .buffer = buffer, + }; + return dld->vkGetBufferDeviceAddress(handle, &info); + } + + [[nodiscard]] VkDeviceSize GetDescriptorSetLayoutSizeEXT( + VkDescriptorSetLayout layout) const noexcept { + VkDeviceSize size{}; + dld->vkGetDescriptorSetLayoutSizeEXT(handle, layout, &size); + return size; + } + + [[nodiscard]] VkDeviceSize GetDescriptorSetLayoutBindingOffsetEXT( + VkDescriptorSetLayout layout, u32 binding) const noexcept { + VkDeviceSize offset{}; + dld->vkGetDescriptorSetLayoutBindingOffsetEXT(handle, layout, binding, &offset); + return offset; + } + + void GetDescriptorEXT(const VkDescriptorGetInfoEXT& info, size_t size, + void* descriptor) const noexcept { + dld->vkGetDescriptorEXT(handle, &info, size, descriptor); + } + VkResult AcquireNextImageKHR(VkSwapchainKHR swapchain, u64 timeout, VkSemaphore semaphore, VkFence fence, u32* image_index) const noexcept { return dld->vkAcquireNextImageKHR(handle, swapchain, timeout, semaphore, fence, @@ -1108,6 +1192,8 @@ public: VkFormatProperties GetFormatProperties(VkFormat) const noexcept; + VkFormatProperties3 GetFormatProperties3(VkFormat) const noexcept; + std::vector EnumerateDeviceExtensionProperties() const; std::vector GetQueueFamilyProperties() const; @@ -1169,8 +1255,8 @@ public: dld->vkCmdEndQuery(handle, query_pool, query); } - void ResetQueryPool(VkQueryPool query_pool, u32 first_query, u32 query_count) const noexcept { - dld->vkCmdResetQueryPool(handle, query_pool, first_query, query_count); + void ResetQueryPool(VkQueryPool query_pool, u32 first, u32 count) const noexcept { + dld->vkCmdResetQueryPool(handle, query_pool, first, count); } void BindDescriptorSets(VkPipelineBindPoint bind_point, VkPipelineLayout layout, u32 first, @@ -1280,6 +1366,72 @@ public: VkDependencyFlags dependency_flags, Span memory_barriers, Span buffer_barriers, Span image_barriers) const noexcept { + static constexpr u32 MaxBarriers = 16; + if (dld->vkCmdPipelineBarrier2 && memory_barriers.size() <= MaxBarriers && + buffer_barriers.size() <= MaxBarriers && image_barriers.size() <= MaxBarriers) { + const auto src_stage_mask2 = static_cast(src_stage_mask); + const auto dst_stage_mask2 = static_cast(dst_stage_mask); + + std::array memory_barriers2; + for (u32 i = 0; i < memory_barriers.size(); ++i) { + memory_barriers2[i] = VkMemoryBarrier2{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = src_stage_mask2, + .srcAccessMask = static_cast(memory_barriers[i].srcAccessMask), + .dstStageMask = dst_stage_mask2, + .dstAccessMask = static_cast(memory_barriers[i].dstAccessMask), + }; + } + std::array buffer_barriers2; + for (u32 i = 0; i < buffer_barriers.size(); ++i) { + const auto& barrier = buffer_barriers[i]; + buffer_barriers2[i] = VkBufferMemoryBarrier2{ + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = src_stage_mask2, + .srcAccessMask = static_cast(barrier.srcAccessMask), + .dstStageMask = dst_stage_mask2, + .dstAccessMask = static_cast(barrier.dstAccessMask), + .srcQueueFamilyIndex = barrier.srcQueueFamilyIndex, + .dstQueueFamilyIndex = barrier.dstQueueFamilyIndex, + .buffer = barrier.buffer, + .offset = barrier.offset, + .size = barrier.size, + }; + } + std::array image_barriers2; + for (u32 i = 0; i < image_barriers.size(); ++i) { + const auto& barrier = image_barriers[i]; + image_barriers2[i] = VkImageMemoryBarrier2{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = src_stage_mask2, + .srcAccessMask = static_cast(barrier.srcAccessMask), + .dstStageMask = dst_stage_mask2, + .dstAccessMask = static_cast(barrier.dstAccessMask), + .oldLayout = barrier.oldLayout, + .newLayout = barrier.newLayout, + .srcQueueFamilyIndex = barrier.srcQueueFamilyIndex, + .dstQueueFamilyIndex = barrier.dstQueueFamilyIndex, + .image = barrier.image, + .subresourceRange = barrier.subresourceRange, + }; + } + const VkDependencyInfo dependency_info{ + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .pNext = nullptr, + .dependencyFlags = dependency_flags, + .memoryBarrierCount = memory_barriers.size(), + .pMemoryBarriers = memory_barriers2.data(), + .bufferMemoryBarrierCount = buffer_barriers.size(), + .pBufferMemoryBarriers = buffer_barriers2.data(), + .imageMemoryBarrierCount = image_barriers.size(), + .pImageMemoryBarriers = image_barriers2.data(), + }; + dld->vkCmdPipelineBarrier2(handle, &dependency_info); + return; + } dld->vkCmdPipelineBarrier(handle, src_stage_mask, dst_stage_mask, dependency_flags, memory_barriers.size(), memory_barriers.data(), buffer_barriers.size(), buffer_barriers.data(), @@ -1309,6 +1461,18 @@ public: PipelineBarrier(src_stage_mask, dst_stage_mask, dependency_flags, {}, {}, image_barrier); } + void BindDescriptorBuffersEXT(Span bindings) const noexcept { + dld->vkCmdBindDescriptorBuffersEXT(handle, bindings.size(), bindings.data()); + } + + void SetDescriptorBufferOffsetsEXT(VkPipelineBindPoint bind_point, VkPipelineLayout layout, + u32 first_set, Span buffer_indices, + Span offsets) const noexcept { + dld->vkCmdSetDescriptorBufferOffsetsEXT(handle, bind_point, layout, first_set, + buffer_indices.size(), buffer_indices.data(), + offsets.data()); + } + void CopyBufferToImage(VkBuffer src_buffer, VkImage dst_image, VkImageLayout dst_image_layout, Span regions) const noexcept { dld->vkCmdCopyBufferToImage(handle, src_buffer, dst_image, dst_image_layout, regions.size(), @@ -1350,10 +1514,9 @@ public: } template - void PushConstants(VkPipelineLayout layout, VkShaderStageFlags flags, - const T& data) const noexcept { - static_assert(std::is_trivially_copyable_v, " is not trivially copyable"); - dld->vkCmdPushConstants(handle, layout, flags, 0, static_cast(sizeof(T)), &data); + requires std::is_trivially_copyable_v + void PushConstants(VkPipelineLayout layout, VkShaderStageFlags flags, const T& data) const noexcept { + dld->vkCmdPushConstants(handle, layout, flags, 0, u32(sizeof(T)), std::addressof(data)); } void SetViewport(u32 first, Span viewports) const noexcept { @@ -1504,6 +1667,10 @@ public: dld->vkCmdSetColorWriteMaskEXT(handle, first, masks.size(), masks.data()); } + void SetColorWriteEnableEXT(Span enables) const noexcept { + dld->vkCmdSetColorWriteEnableEXT(handle, enables.size(), enables.data()); + } + void SetColorBlendEnableEXT(u32 first, Span enables) const noexcept { dld->vkCmdSetColorBlendEnableEXT(handle, first, enables.size(), enables.data()); } diff --git a/src/web_service/announce_room_json.cpp b/src/web_service/announce_room_json.cpp index b80caa49dc..c3a44257ef 100644 --- a/src/web_service/announce_room_json.cpp +++ b/src/web_service/announce_room_json.cpp @@ -6,7 +6,6 @@ #include #include -#include "common/detached_tasks.h" #include "common/logging.h" #include "web_service/announce_room_json.h" #include "web_service/web_backend.h" @@ -136,13 +135,14 @@ AnnounceMultiplayerRoom::RoomList RoomJson::GetRoomList() { void RoomJson::Delete() { if (room_id.empty()) { LOG_ERROR(WebService, "Room must be registered to be deleted"); - return; + } else { + // This jthread won't be destroyed until after the dtor has been ran + // Once the thread finishes it will stay resident on the vector -- destroyed and freed by dtor() + // this is still valid while in dtor, so... yeah + detached_tasks.emplace_back([this](std::stop_token stop_token) { + client.DeleteJson(fmt::format("/lobby/{}", room_id), "", false); + }); } - Common::DetachedTasks::AddTask([host_{this->host}, username_{this->username}, - token_{this->token}, room_id_{this->room_id}]() { - // create a new client here because the this->client might be destroyed. - Client{host_, username_, token_}.DeleteJson(fmt::format("/lobby/{}", room_id_), "", false); - }); } } // namespace WebService diff --git a/src/web_service/announce_room_json.h b/src/web_service/announce_room_json.h index 32c08858dd..a991325349 100644 --- a/src/web_service/announce_room_json.h +++ b/src/web_service/announce_room_json.h @@ -1,8 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include +#include #include #include #include "common/announce_multiplayer_room.h" @@ -30,6 +35,7 @@ public: void Delete() override; private: + std::vector detached_tasks; AnnounceMultiplayerRoom::Room room; Client client; std::string host; diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index ff0312660b..b93080f2a1 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -8,7 +8,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) # Set the RPATH for Qt Libraries # This must be done before the `yuzu` target is created -if (YUZU_USE_BUNDLED_QT AND PLATFORM_LINUX) +if (YUZU_USE_BUNDLED_QT AND LINUX) set(CMAKE_BUILD_RPATH "${CMAKE_BINARY_DIR}/bin/lib/") endif() @@ -37,9 +37,6 @@ add_executable(yuzu applets/qt_web_browser_scripts.h bootmanager.cpp bootmanager.h - compatdb.ui - compatibility_list.cpp - compatibility_list.h configuration/configuration_shared.cpp configuration/configuration_shared.h configuration/configure.ui @@ -156,13 +153,12 @@ add_executable(yuzu debugger/controller.cpp debugger/controller.h - game/game_list.cpp - game/game_list.h - game/game_list_p.h - game/game_list_worker.cpp - game/game_list_worker.h - game/game_card.h - game/game_card.cpp + game/game_list.cpp game/game_list.h + game/game_grid.cpp game/game_grid.h + + game/game_tree.h game/game_tree.cpp + game/game_card.h game/game_card.cpp + game/search_field.h game/search_field.cpp hotkeys.cpp hotkeys.h @@ -217,22 +213,16 @@ add_executable(yuzu util/url_request_interceptor.h util/util.cpp util/util.h - vk_device_info.cpp - vk_device_info.h - compatdb.cpp - compatdb.h - user_data_migration.cpp - user_data_migration.h + + user_data_migration.h user_data_migration.cpp yuzu.qrc yuzu.rc migration_dialog.h migration_dialog.cpp - migration_worker.h - migration_worker.cpp + migration_worker.h migration_worker.cpp + libqt_common.h libqt_common.cpp - deps_dialog.cpp - deps_dialog.h - deps_dialog.ui + deps_dialog.cpp deps_dialog.h deps_dialog.ui data_dialog.h data_dialog.cpp data_dialog.ui data_widget.ui @@ -244,9 +234,9 @@ add_executable(yuzu configuration/addon/mod_select_dialog.h configuration/addon/mod_select_dialog.cpp configuration/addon/mod_select_dialog.ui render/performance_overlay.h render/performance_overlay.cpp render/performance_overlay.ui - libqt_common.h libqt_common.cpp updater/update_dialog.h updater/update_dialog.cpp updater/update_dialog.ui - + game/common.h + game/carousel.h game/carousel.cpp ) set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden") @@ -260,14 +250,17 @@ if (YUZU_CRASH_DUMPS) target_compile_definitions(yuzu PRIVATE YUZU_CRASH_DUMPS) endif() +if (ENABLE_RESHADE) + target_sources(yuzu PRIVATE + configuration/configure_post_processing.cpp + configuration/configure_post_processing.h) +endif() + if (CXX_CLANG) target_compile_definitions(yuzu PRIVATE $<$,15>:CANNOT_EXPLICITLY_INSTANTIATE>) endif() -file(GLOB COMPAT_LIST - ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc - ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) file(GLOB_RECURSE ICONS ${PROJECT_SOURCE_DIR}/dist/icons/*) file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*) @@ -359,7 +352,6 @@ endif() target_sources(yuzu PRIVATE - ${COMPAT_LIST} ${ICONS} ${LANGUAGES} ${THEMES}) @@ -421,10 +413,6 @@ if (UNIX AND NOT APPLE) target_link_libraries(yuzu PRIVATE Qt6::DBus) endif() -if (YUZU_ENABLE_COMPATIBILITY_REPORTING) - target_compile_definitions(yuzu PRIVATE YUZU_ENABLE_COMPATIBILITY_REPORTING) -endif() - if (ENABLE_WEB_SERVICE) target_compile_definitions(yuzu PRIVATE ENABLE_WEB_SERVICE) endif() @@ -466,6 +454,6 @@ if (NOT MSVC AND (APPLE OR NOT YUZU_STATIC_BUILD)) endif() # Remember that the linker is incredibly stupid. -target_link_libraries(yuzu PRIVATE OpenSSL::SSL OpenSSL::Crypto SDL2::SDL2) +target_link_libraries(yuzu PRIVATE OpenSSL::SSL OpenSSL::Crypto SDL3::SDL3) create_target_directory_groups(yuzu) diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index dd55034e4e..308db3621b 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -6,8 +6,6 @@ #include #include #include -#include -#include #ifdef HAS_OPENGL #include @@ -24,7 +22,11 @@ #include #include #include + +#include "input_common/drivers/camera.h" + #endif + #include #include #include @@ -45,16 +47,13 @@ #include #endif -#include "common/polyfill_thread.h" #include "common/scm_rev.h" #include "common/settings.h" #include "common/settings_input.h" -#include "common/thread.h" #include "core/core.h" #include "core/cpu_manager.h" #include "core/frontend/framebuffer_layout.h" #include "core/frontend/graphics_context.h" -#include "input_common/drivers/camera.h" #include "input_common/drivers/keyboard.h" #include "input_common/drivers/mouse.h" #include "input_common/drivers/tas_input.h" @@ -62,188 +61,19 @@ #include "input_common/main.h" #include "qt_common/qt_common.h" #include "video_core/gpu.h" -#include "video_core/rasterizer_interface.h" #include "video_core/renderer_base.h" #include "yuzu/bootmanager.h" #include "yuzu/main_window.h" +#include "qt_common/render/context.h" +#include "qt_common/render/emu_thread.h" + class QObject; class QPaintEngine; class QSurface; constexpr int default_mouse_constrain_timeout = 10; - -EmuThread::EmuThread(Core::System& system) : m_system{system} {} - -EmuThread::~EmuThread() = default; - -void EmuThread::run() { - Common::SetCurrentThreadName("EmuControlThread"); - - auto& gpu = m_system.GPU(); - auto stop_token = m_stop_source.get_token(); - - m_system.RegisterHostThread(); - - // Main process has been loaded. Make the context current to this thread and begin GPU and CPU - // execution. - gpu.ObtainContext(); - - emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); - if (Settings::values.use_disk_shader_cache.GetValue()) { - m_system.Renderer().ReadRasterizer()->LoadDiskResources( - m_system.GetApplicationProcessProgramID(), stop_token, - [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { - emit LoadProgress(stage, value, total); - }); - } - emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); - - gpu.ReleaseContext(); - gpu.Start(); - - m_system.GetCpuManager().OnGpuReady(); - - if (m_system.DebuggerEnabled()) { - m_system.InitializeDebugger(); - } - - while (!stop_token.stop_requested()) { - std::unique_lock lk{m_should_run_mutex}; - if (m_should_run) { - m_system.Run(); - m_stopped.Reset(); - - m_should_run_cv.wait(lk, stop_token, [&] { return !m_should_run; }); - } else { - m_system.Pause(); - m_stopped.Set(); - - EmulationPaused(lk); - m_should_run_cv.wait(lk, stop_token, [&] { return m_should_run; }); - EmulationResumed(lk); - } - } - - // Shutdown the main emulated process - m_system.DetachDebugger(); - m_system.ShutdownMainProcess(); -} - -// Unlock while emitting signals so that the main thread can -// continue pumping events. - -void EmuThread::EmulationPaused(std::unique_lock& lk) { - lk.unlock(); - emit DebugModeEntered(); - lk.lock(); -} - -void EmuThread::EmulationResumed(std::unique_lock& lk) { - lk.unlock(); - emit DebugModeLeft(); - lk.lock(); -} - -#ifdef HAS_OPENGL -class OpenGLSharedContext : public Core::Frontend::GraphicsContext { -public: - /// Create the original context that should be shared from - explicit OpenGLSharedContext(QSurface* surface_) : surface{surface_} { - QSurfaceFormat format; - format.setVersion(4, 6); - format.setProfile(QSurfaceFormat::CompatibilityProfile); - format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); - if (Settings::values.renderer_debug) { - format.setOption(QSurfaceFormat::FormatOption::DebugContext); - } - // TODO: expose a setting for buffer value (ie default/single/double/triple) - format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); - format.setSwapInterval(0); - - context = std::make_unique(); - context->setFormat(format); - if (!context->create()) { - LOG_ERROR(Frontend, "Unable to create main openGL context"); - } - } - - /// Create the shared contexts for rendering and presentation - explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface = nullptr) { - - // disable vsync for any shared contexts - auto format = share_context->format(); - const int swap_interval = - Settings::values.vsync_mode.GetValue() == Settings::VSyncMode::Immediate ? 0 : 1; - - format.setSwapInterval(main_surface ? swap_interval : 0); - - context = std::make_unique(); - context->setShareContext(share_context); - context->setFormat(format); - if (!context->create()) { - LOG_ERROR(Frontend, "Unable to create shared openGL context"); - } - - if (!main_surface) { - offscreen_surface = std::make_unique(nullptr); - offscreen_surface->setFormat(format); - offscreen_surface->create(); - surface = offscreen_surface.get(); - } else { - surface = main_surface; - } - } - - ~OpenGLSharedContext() { - DoneCurrent(); - } - - void SwapBuffers() override { - if (auto window = static_cast(surface)) { - if (!window->isExposed()) { - LOG_DEBUG(Frontend, "SwapBuffers ignored: window not exposed"); - return; - } - } - - context->swapBuffers(surface); - } - - void MakeCurrent() override { - // We can't track the current state of the underlying context in this wrapper class because - // Qt may make the underlying context not current for one reason or another. In particular, - // the WebBrowser uses GL, so it seems to conflict if we aren't careful. - // Instead of always just making the context current (which does not have any caching to - // check if the underlying context is already current) we can check for the current context - // in the thread local data by calling `currentContext()` and checking if its ours. - if (QOpenGLContext::currentContext() != context.get()) { - context->makeCurrent(surface); - } - } - - void DoneCurrent() override { - context->doneCurrent(); - } - - QOpenGLContext* GetShareContext() { - return context.get(); - } - - const QOpenGLContext* GetShareContext() const { - return context.get(); - } - -private: - // Avoid using Qt parent system here since we might move the QObjects to new threads - // As a note, this means we should avoid using slots/signals with the objects too - std::unique_ptr context; - std::unique_ptr offscreen_surface{}; - QSurface* surface; -}; -#endif - -class DummyContext : public Core::Frontend::GraphicsContext {}; +constexpr int default_mouse_update_timeout = 5; class RenderWidget : public QWidget { public: @@ -285,11 +115,9 @@ struct NullRenderWidget : public RenderWidget { explicit NullRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {} }; -GRenderWindow::GRenderWindow(MainWindow* parent, EmuThread* emu_thread_, - std::shared_ptr input_subsystem_, - Core::System& system_) - : QWidget(parent), emu_thread(emu_thread_), input_subsystem{std::move(input_subsystem_)}, - system{system_} { +GRenderWindow::GRenderWindow(MainWindow* parent, + std::shared_ptr input_subsystem_) + : QWidget(parent), input_subsystem{std::move(input_subsystem_)} { setWindowTitle(QStringLiteral("Eden %1 | %2-%3") .arg(QString::fromUtf8(Common::g_build_name), QString::fromUtf8(Common::g_scm_branch), @@ -311,6 +139,10 @@ GRenderWindow::GRenderWindow(MainWindow* parent, EmuThread* emu_thread_, mouse_constrain_timer.setInterval(default_mouse_constrain_timeout); connect(&mouse_constrain_timer, &QTimer::timeout, this, &GRenderWindow::ConstrainMouse); + + mouse_update_timer.setInterval(default_mouse_update_timeout); + connect(&mouse_update_timer, &QTimer::timeout, this, &GRenderWindow::UpdateMouse); + mouse_update_timer.start(); } void GRenderWindow::ExecuteProgram(std::size_t program_index) { @@ -689,8 +521,7 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { // Constrain mouse for mouse emulation with mouse panning if (Settings::values.mouse_panning && Settings::values.mouse_enabled) { const auto [clamped_mouse_x, clamped_mouse_y] = ClipToTouchScreen(x, y); - QCursor::setPos(mapToGlobal( - QPoint{static_cast(clamped_mouse_x), static_cast(clamped_mouse_y)})); + QCursor::setPos(mapToGlobal(QPoint{int(clamped_mouse_x), int(clamped_mouse_y)})); } mouse_constrain_timer.stop(); @@ -708,11 +539,7 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { } void GRenderWindow::ConstrainMouse() { - if (emu_thread == nullptr || !Settings::values.mouse_panning) { - mouse_constrain_timer.stop(); - return; - } - if (!this->isActiveWindow()) { + if (QtCommon::emu_thread == nullptr || Settings::values.mouse_panning || !this->isActiveWindow()) { mouse_constrain_timer.stop(); return; } @@ -721,15 +548,16 @@ void GRenderWindow::ConstrainMouse() { const auto pos = mapFromGlobal(QCursor::pos()); const int new_pos_x = std::clamp(pos.x(), 0, width()); const int new_pos_y = std::clamp(pos.y(), 0, height()); - QCursor::setPos(mapToGlobal(QPoint{new_pos_x, new_pos_y})); - return; + } else { + const int center_x = width() / 2; + const int center_y = height() / 2; + QCursor::setPos(mapToGlobal(QPoint{center_x, center_y})); } +} - const int center_x = width() / 2; - const int center_y = height() / 2; - - QCursor::setPos(mapToGlobal(QPoint{center_x, center_y})); +void GRenderWindow::UpdateMouse() { + input_subsystem->GetMouse()->NotifyChanged(); // required to reset mouse once it's no longer moved } void GRenderWindow::wheelEvent(QWheelEvent* event) { @@ -882,8 +710,8 @@ bool GRenderWindow::event(QEvent* event) { void GRenderWindow::focusOutEvent(QFocusEvent* event) { QWidget::focusOutEvent(event); input_subsystem->GetKeyboard()->ReleaseAllKeys(); - input_subsystem->GetMouse()->ReleaseAllButtons(); input_subsystem->GetTouchScreen()->ReleaseAllTouch(); + input_subsystem->GetMouse()->ReleaseAllButtons(); } void GRenderWindow::resizeEvent(QResizeEvent* event) { @@ -964,7 +792,7 @@ void GRenderWindow::ReleaseRenderTarget() { } void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) { - auto& renderer = system.Renderer(); + auto& renderer = QtCommon::system->Renderer(); if (renderer.IsScreenshotPending()) { LOG_WARNING(Render, @@ -989,7 +817,12 @@ void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) { screenshot_image.bits(), [=, this](bool invert_y) { const std::string std_screenshot_path = screenshot_path.toStdString(); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + if (screenshot_image.flipped(invert_y ? Qt::Vertical : (Qt::Orientations)0) + .save(screenshot_path)) { +#else if (screenshot_image.mirrored(false, invert_y).save(screenshot_path)) { +#endif LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path); } else { LOG_ERROR(Frontend, "Failed to save screenshot to \"{}\"", std_screenshot_path); @@ -1026,7 +859,8 @@ bool GRenderWindow::InitializeOpenGL() { return true; #else - QMessageBox::warning(this, tr("OpenGL not available!"), tr("Eden has not been compiled with OpenGL support.")); + QMessageBox::warning(this, tr("OpenGL not available!"), + tr("Eden has not been compiled with OpenGL support.")); return false; #endif } @@ -1072,8 +906,7 @@ bool GRenderWindow::LoadOpenGL() { tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you " "have the latest graphics driver.

GL Renderer:
%1

Unsupported " "extensions:
%2") - .arg(renderer) - .arg(missing_ext.join(QStringLiteral("
")))); + .arg(renderer, missing_ext.join(QStringLiteral("
")))); // Non fatal } return true; @@ -1101,14 +934,6 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const { return missing_ext; } -void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread_) { - emu_thread = emu_thread_; -} - -void GRenderWindow::OnEmulationStopping() { - emu_thread = nullptr; -} - void GRenderWindow::showEvent(QShowEvent* event) { QWidget::showEvent(event); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index e763cd9868..bb18bbb3bb 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -6,12 +6,9 @@ #pragma once -#include #include #include -#include #include -#include #include #include @@ -27,9 +24,6 @@ #include #include "common/common_types.h" -#include "common/logging.h" -#include "common/polyfill_thread.h" -#include "common/thread.h" #include "core/frontend/emu_window.h" class MainWindow; @@ -46,6 +40,7 @@ class QShowEvent; class QTouchEvent; class QWheelEvent; +class EmuThread; namespace Core { class System; } // namespace Core @@ -63,96 +58,12 @@ namespace VideoCore { enum class LoadCallbackStage; } // namespace VideoCore -class EmuThread final : public QThread { - Q_OBJECT - -public: - explicit EmuThread(Core::System& system); - ~EmuThread() override; - - /** - * Start emulation (on new thread) - * @warning Only call when not running! - */ - void run() override; - - /** - * Sets whether the emulation thread should run or not - * @param should_run Boolean value, set the emulation thread to running if true - */ - void SetRunning(bool should_run) { - // TODO: Prevent other threads from modifying the state until we finish. - { - // Notify the running thread to change state. - std::unique_lock run_lk{m_should_run_mutex}; - m_should_run = should_run; - m_should_run_cv.notify_one(); - } - - // Wait until paused, if pausing. - if (!should_run) { - m_stopped.Wait(); - } - } - - /** - * Check if the emulation thread is running or not - * @return True if the emulation thread is running, otherwise false - */ - bool IsRunning() const { - return m_should_run; - } - - /** - * Requests for the emulation thread to immediately stop running - */ - void ForceStop() { - LOG_WARNING(Frontend, "Force stopping EmuThread"); - m_stop_source.request_stop(); - } - -private: - void EmulationPaused(std::unique_lock& lk); - void EmulationResumed(std::unique_lock& lk); - -private: - Core::System& m_system; - - std::stop_source m_stop_source; - std::mutex m_should_run_mutex; - std::condition_variable_any m_should_run_cv; - Common::Event m_stopped; - bool m_should_run{true}; - -signals: - /** - * Emitted when the CPU has halted execution - * - * @warning When connecting to this signal from other threads, make sure to specify either - * Qt::QueuedConnection (invoke slot within the destination object's message thread) or even - * Qt::BlockingQueuedConnection (additionally block source thread until slot returns) - */ - void DebugModeEntered(); - - /** - * Emitted right before the CPU continues execution - * - * @warning When connecting to this signal from other threads, make sure to specify either - * Qt::QueuedConnection (invoke slot within the destination object's message thread) or even - * Qt::BlockingQueuedConnection (additionally block source thread until slot returns) - */ - void DebugModeLeft(); - - void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total); -}; - class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow { Q_OBJECT public: - explicit GRenderWindow(MainWindow* parent, EmuThread* emu_thread_, - std::shared_ptr input_subsystem_, - Core::System& system_); + explicit GRenderWindow(MainWindow* parent, + std::shared_ptr input_subsystem_); ~GRenderWindow() override; // EmuWindow implementation. @@ -217,8 +128,6 @@ public: void Exit(); public slots: - void OnEmulationStarting(EmuThread* emu_thread_); - void OnEmulationStopping(); void OnFramebufferSizeChanged(); signals: @@ -235,6 +144,7 @@ private: void TouchUpdateEvent(const QTouchEvent* event); void TouchEndEvent(); void ConstrainMouse(); + void UpdateMouse(); void RequestCameraCapture(); void OnCameraCapture(int requestId, const QImage& img); @@ -247,7 +157,6 @@ private: bool LoadOpenGL(); QStringList GetUnsupportedGLExtensions() const; - EmuThread* emu_thread; std::shared_ptr input_subsystem; // Main context that will be shared with all other contexts that are requested. @@ -276,8 +185,7 @@ private: #endif QTimer mouse_constrain_timer; - - Core::System& system; + QTimer mouse_update_timer; protected: void showEvent(QShowEvent* event) override; diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp deleted file mode 100644 index b47cdd2116..0000000000 --- a/src/yuzu/compatdb.cpp +++ /dev/null @@ -1,206 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 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 - -#include -#include -#include -#include -#include "common/logging.h" -#include "ui_compatdb.h" -#include "yuzu/compatdb.h" - -CompatDB::CompatDB(QWidget* parent) - : QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), - ui{std::make_unique()} { - ui->setupUi(this); - - connect(ui->radioButton_GameBoot_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_GameBoot_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_Gameplay_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_Gameplay_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_NoFreeze_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_NoFreeze_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_Complete_Yes, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_Complete_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_Graphical_Major, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_Graphical_Minor, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_Graphical_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_Audio_Major, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_Audio_Minor, &QRadioButton::clicked, this, &CompatDB::EnableNext); - connect(ui->radioButton_Audio_No, &QRadioButton::clicked, this, &CompatDB::EnableNext); - - connect(button(NextButton), &QPushButton::clicked, this, &CompatDB::Submit); - connect(&testcase_watcher, &QFutureWatcher::finished, this, - &CompatDB::OnTestcaseSubmitted); -} - -CompatDB::~CompatDB() = default; - -enum class CompatDBPage { - Intro = 0, - GameBoot = 1, - GamePlay = 2, - Freeze = 3, - Completion = 4, - Graphical = 5, - Audio = 6, - Final = 7, -}; - -void CompatDB::Submit() { - QButtonGroup* compatibility_GameBoot = new QButtonGroup(this); - compatibility_GameBoot->addButton(ui->radioButton_GameBoot_Yes, 0); - compatibility_GameBoot->addButton(ui->radioButton_GameBoot_No, 1); - - QButtonGroup* compatibility_Gameplay = new QButtonGroup(this); - compatibility_Gameplay->addButton(ui->radioButton_Gameplay_Yes, 0); - compatibility_Gameplay->addButton(ui->radioButton_Gameplay_No, 1); - - QButtonGroup* compatibility_NoFreeze = new QButtonGroup(this); - compatibility_NoFreeze->addButton(ui->radioButton_NoFreeze_Yes, 0); - compatibility_NoFreeze->addButton(ui->radioButton_NoFreeze_No, 1); - - QButtonGroup* compatibility_Complete = new QButtonGroup(this); - compatibility_Complete->addButton(ui->radioButton_Complete_Yes, 0); - compatibility_Complete->addButton(ui->radioButton_Complete_No, 1); - - QButtonGroup* compatibility_Graphical = new QButtonGroup(this); - compatibility_Graphical->addButton(ui->radioButton_Graphical_Major, 0); - compatibility_Graphical->addButton(ui->radioButton_Graphical_Minor, 1); - compatibility_Graphical->addButton(ui->radioButton_Graphical_No, 2); - - QButtonGroup* compatibility_Audio = new QButtonGroup(this); - compatibility_Audio->addButton(ui->radioButton_Audio_Major, 0); - compatibility_Graphical->addButton(ui->radioButton_Audio_Minor, 1); - compatibility_Audio->addButton(ui->radioButton_Audio_No, 2); - - const int compatibility = static_cast(CalculateCompatibility()); - - switch ((static_cast(currentId()))) { - case CompatDBPage::Intro: - break; - case CompatDBPage::GameBoot: - if (compatibility_GameBoot->checkedId() == -1) { - button(NextButton)->setEnabled(false); - } - break; - case CompatDBPage::GamePlay: - if (compatibility_Gameplay->checkedId() == -1) { - button(NextButton)->setEnabled(false); - } - break; - case CompatDBPage::Freeze: - if (compatibility_NoFreeze->checkedId() == -1) { - button(NextButton)->setEnabled(false); - } - break; - case CompatDBPage::Completion: - if (compatibility_Complete->checkedId() == -1) { - button(NextButton)->setEnabled(false); - } - break; - case CompatDBPage::Graphical: - if (compatibility_Graphical->checkedId() == -1) { - button(NextButton)->setEnabled(false); - } - break; - case CompatDBPage::Audio: - if (compatibility_Audio->checkedId() == -1) { - button(NextButton)->setEnabled(false); - } - break; - case CompatDBPage::Final: - back(); - LOG_INFO(Frontend, "Compatibility Rating: {}", compatibility); - - button(NextButton)->setEnabled(false); - button(NextButton)->setText(tr("Submitting")); - button(CancelButton)->setVisible(false); - break; - default: - LOG_ERROR(Frontend, "Unexpected page: {}", currentId()); - break; - } -} - -int CompatDB::nextId() const { - switch ((static_cast(currentId()))) { - case CompatDBPage::Intro: - return static_cast(CompatDBPage::GameBoot); - case CompatDBPage::GameBoot: - if (ui->radioButton_GameBoot_No->isChecked()) { - return static_cast(CompatDBPage::Final); - } - return static_cast(CompatDBPage::GamePlay); - case CompatDBPage::GamePlay: - if (ui->radioButton_Gameplay_No->isChecked()) { - return static_cast(CompatDBPage::Final); - } - return static_cast(CompatDBPage::Freeze); - case CompatDBPage::Freeze: - if (ui->radioButton_NoFreeze_No->isChecked()) { - return static_cast(CompatDBPage::Final); - } - return static_cast(CompatDBPage::Completion); - case CompatDBPage::Completion: - if (ui->radioButton_Complete_No->isChecked()) { - return static_cast(CompatDBPage::Final); - } - return static_cast(CompatDBPage::Graphical); - case CompatDBPage::Graphical: - return static_cast(CompatDBPage::Audio); - case CompatDBPage::Audio: - return static_cast(CompatDBPage::Final); - case CompatDBPage::Final: - return -1; - default: - LOG_ERROR(Frontend, "Unexpected page: {}", currentId()); - return static_cast(CompatDBPage::Intro); - } -} - -CompatibilityStatus CompatDB::CalculateCompatibility() const { - if (ui->radioButton_GameBoot_No->isChecked()) { - return CompatibilityStatus::WontBoot; - } - - if (ui->radioButton_Gameplay_No->isChecked()) { - return CompatibilityStatus::IntroMenu; - } - - if (ui->radioButton_NoFreeze_No->isChecked() || ui->radioButton_Complete_No->isChecked()) { - return CompatibilityStatus::Ingame; - } - - if (ui->radioButton_Graphical_Major->isChecked() || ui->radioButton_Audio_Major->isChecked()) { - return CompatibilityStatus::Ingame; - } - - if (ui->radioButton_Graphical_Minor->isChecked() || ui->radioButton_Audio_Minor->isChecked()) { - return CompatibilityStatus::Playable; - } - - return CompatibilityStatus::Perfect; -} - -void CompatDB::OnTestcaseSubmitted() { - if (!testcase_watcher.result()) { - QMessageBox::critical(this, tr("Communication error"), - tr("An error occurred while sending the Testcase")); - button(NextButton)->setEnabled(true); - button(NextButton)->setText(tr("Next")); - button(CancelButton)->setVisible(true); - } else { - next(); - // older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a - // workaround - button(CancelButton)->setVisible(false); - } -} - -void CompatDB::EnableNext() { - button(NextButton)->setEnabled(true); -} diff --git a/src/yuzu/compatdb.h b/src/yuzu/compatdb.h deleted file mode 100644 index 4e788d09c4..0000000000 --- a/src/yuzu/compatdb.h +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include - -namespace Ui { -class CompatDB; -} - -enum class CompatibilityStatus { - Perfect = 0, - Playable = 1, - // Unused: Okay = 2, - Ingame = 3, - IntroMenu = 4, - WontBoot = 5, -}; - -class CompatDB : public QWizard { - Q_OBJECT - -public: - explicit CompatDB(QWidget* parent = nullptr); - ~CompatDB(); - int nextId() const override; - -private: - QFutureWatcher testcase_watcher; - - std::unique_ptr ui; - - void Submit(); - CompatibilityStatus CalculateCompatibility() const; - void OnTestcaseSubmitted(); - void EnableNext(); -}; diff --git a/src/yuzu/compatdb.ui b/src/yuzu/compatdb.ui deleted file mode 100644 index 6a2632a1ce..0000000000 --- a/src/yuzu/compatdb.ui +++ /dev/null @@ -1,398 +0,0 @@ - - - CompatDB - - - - 0 - 0 - 600 - 482 - - - - - 500 - 410 - - - - Report Compatibility - - - QWizard::WizardOption::DisabledBackButtonOnLastPage|QWizard::WizardOption::HelpButtonOnRight|QWizard::WizardOption::NoBackButtonOnStartPage - - - - Report Game Compatibility - - - 0 - - - - - - <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://eden-emulator.github.io/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">eden Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of eden you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected eden account</li></ul></body></html> - - - true - - - true - - - - - - - Qt::Orientation::Vertical - - - - 20 - 0 - - - - - - - - - Report Game Compatibility - - - 1 - - - - - - - 10 - - - - <html><head/><body><p>Does the game boot?</p></body></html> - - - true - - - - - - - Qt::Orientation::Vertical - - - - 20 - 0 - - - - - - - - Yes The game starts to output video or audio - - - - - - - No The game doesn't get past the "Launching..." screen - - - - - - - - Report Game Compatibility - - - 2 - - - - - - Yes The game gets past the intro/menu and into gameplay - - - - - - - No The game crashes or freezes while loading or using the menu - - - - - - - - 10 - - - - <html><head/><body><p>Does the game reach gameplay?</p></body></html> - - - true - - - - - - - Qt::Orientation::Vertical - - - - 20 - 0 - - - - - - - - - Report Game Compatibility - - - 3 - - - - - - Yes The game works without crashes - - - - - - - No The game crashes or freezes during gameplay - - - - - - - - 10 - - - - <html><head/><body><p>Does the game work without crashing, freezing or locking up during gameplay?</p></body></html> - - - true - - - - - - - Qt::Orientation::Vertical - - - - 20 - 0 - - - - - - - - - Report Game Compatibility - - - 4 - - - - - - Yes The game can be finished without any workarounds - - - - - - - No The game can't progress past a certain area - - - - - - - - 10 - - - - <html><head/><body><p>Is the game completely playable from start to finish?</p></body></html> - - - true - - - - - - - Qt::Orientation::Vertical - - - - 20 - 0 - - - - - - - - - Report Game Compatibility - - - 5 - - - - - - Major The game has major graphical errors - - - - - - - Minor The game has minor graphical errors - - - - - - - None Everything is rendered as it looks on the Nintendo Switch - - - - - - - - 10 - - - - <html><head/><body><p>Does the game have any graphical glitches?</p></body></html> - - - true - - - - - - - Qt::Orientation::Vertical - - - - 20 - 0 - - - - - - - - - Report Game Compatibility - - - 6 - - - - - - Major The game has major audio errors - - - - - - - Minor The game has minor audio errors - - - - - - - None Audio is played perfectly - - - - - - - - 10 - - - - <html><head/><body><p>Does the game have any audio glitches / missing effects?</p></body></html> - - - true - - - - - - - Qt::Orientation::Vertical - - - - 20 - 0 - - - - - - - - - Thank you for your submission! - - - 7 - - - - - - diff --git a/src/yuzu/compatibility_list.cpp b/src/yuzu/compatibility_list.cpp deleted file mode 100644 index d08c542eff..0000000000 --- a/src/yuzu/compatibility_list.cpp +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include - -#include "yuzu/compatibility_list.h" - -CompatibilityList::const_iterator FindMatchingCompatibilityEntry( - const CompatibilityList& compatibility_list, u64 program_id) { - return std::find_if(compatibility_list.begin(), compatibility_list.end(), - [program_id](const auto& element) { - std::string pid = fmt::format("{:016X}", program_id); - return element.first == pid; - }); -} diff --git a/src/yuzu/compatibility_list.h b/src/yuzu/compatibility_list.h deleted file mode 100644 index bb1e4b6205..0000000000 --- a/src/yuzu/compatibility_list.h +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 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 - -#pragma once - -#include -#include - -#include - -#include "common/common_types.h" - -using CompatibilityList = ankerl::unordered_dense::map>; - -CompatibilityList::const_iterator FindMatchingCompatibilityEntry( - const CompatibilityList& compatibility_list, u64 program_id); diff --git a/src/yuzu/configuration/addon/mod_select_dialog.cpp b/src/yuzu/configuration/addon/mod_select_dialog.cpp index 49f556a66a..f025a3b390 100644 --- a/src/yuzu/configuration/addon/mod_select_dialog.cpp +++ b/src/yuzu/configuration/addon/mod_select_dialog.cpp @@ -33,8 +33,8 @@ ModSelectDialog::ModSelectDialog(const QStringList& mods, QWidget* parent) ui->treeView->expandAll(); int rows = item_model->rowCount(); - int height = - 4 + ui->treeView->contentsMargins().top() * 4 + ui->treeView->contentsMargins().bottom() * 4; + int height = 4 + ui->treeView->contentsMargins().top() * 4 + + ui->treeView->contentsMargins().bottom() * 4; int width = 0; for (int i = 0; i < rows; ++i) { diff --git a/src/yuzu/configuration/configure_applets.h b/src/yuzu/configuration/configure_applets.h index 54f494d2f1..b794b49e0b 100644 --- a/src/yuzu/configuration/configure_applets.h +++ b/src/yuzu/configuration/configure_applets.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -8,7 +11,6 @@ class QCheckBox; class QLineEdit; -class QComboBox; class QDateTimeEdit; namespace Core { class System; diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp index b31ca849a6..8c7b15e4e0 100644 --- a/src/yuzu/configuration/configure_cpu.cpp +++ b/src/yuzu/configuration/configure_cpu.cpp @@ -76,7 +76,7 @@ void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) { } else if (setting->Id() == Settings::values.cpu_backend.Id()) { backend_layout->addWidget(widget); backend_combobox = widget->combobox; - } else if (setting->Id() == Settings::values.fast_cpu_time.Id() || setting->Id() == Settings::values.cpu_ticks.Id()) { + } else if (setting->Id() == Settings::values.cpu_ticks.Id()) { ui->general_layout->addWidget(widget); } else { // Presently, all other settings here are unsafe checkboxes diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 9db48c084b..47abf66036 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -51,8 +51,12 @@ void ConfigureDebug::SetConfiguration() { ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue()); ui->extended_logging->setChecked(Settings::values.extended_logging.GetValue()); ui->perform_vulkan_check->setChecked(Settings::values.perform_vulkan_check.GetValue()); - ui->serial_battery_edit->setText(QString::fromStdString(std::to_string(Settings::values.serial_battery.GetValue()))); - ui->serial_board_edit->setText(QString::fromStdString(std::to_string(Settings::values.serial_unit.GetValue()))); + ui->serial_battery_edit->setText( + QString::number(Settings::values.serial_battery.GetValue()) + ); + ui->serial_board_edit->setText( + QString::number(Settings::values.serial_unit.GetValue()) + ); #ifdef YUZU_USE_QT_WEB_ENGINE ui->disable_web_applet->setChecked(Settings::values.disable_web_applet.GetValue()); #else @@ -77,8 +81,8 @@ void ConfigureDebug::SetConfiguration() { ui->enable_shader_feedback->setChecked(Settings::values.renderer_shader_feedback.GetValue()); ui->enable_nsight_aftermath->setEnabled(runtime_lock); ui->enable_nsight_aftermath->setChecked(Settings::values.enable_nsight_aftermath.GetValue()); - ui->dump_shaders->setEnabled(runtime_lock); - ui->dump_shaders->setChecked(Settings::values.dump_shaders.GetValue()); + ui->dump_guest_shaders->setEnabled(runtime_lock); + ui->dump_guest_shaders->setChecked(Settings::values.dump_guest_shaders.GetValue()); ui->dump_macros->setEnabled(runtime_lock); ui->dump_macros->setChecked(Settings::values.dump_macros.GetValue()); ui->disable_macro_jit->setEnabled(runtime_lock); @@ -90,6 +94,12 @@ void ConfigureDebug::SetConfiguration() { Settings::values.disable_shader_loop_safety_checks.GetValue()); ui->perform_vulkan_check->setChecked(Settings::values.perform_vulkan_check.GetValue()); ui->debug_knobs_spinbox->setValue(Settings::values.debug_knobs.GetValue()); + + ui->gpu_log_level->setEnabled(runtime_lock); + ui->gpu_log_level->setCurrentIndex( + static_cast(Settings::values.gpu_log_level.GetValue())); + ui->gpu_log_shader_dumps->setEnabled(runtime_lock); + ui->gpu_log_shader_dumps->setChecked(Settings::values.gpu_log_shader_dumps.GetValue()); #ifdef YUZU_USE_QT_WEB_ENGINE ui->disable_web_applet->setChecked(Settings::values.disable_web_applet.GetValue()); #else @@ -119,7 +129,7 @@ void ConfigureDebug::ApplyConfiguration() { Settings::values.disable_buffer_reorder = ui->disable_buffer_reorder->isChecked(); Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked(); Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked(); - Settings::values.dump_shaders = ui->dump_shaders->isChecked(); + Settings::values.dump_guest_shaders = ui->dump_guest_shaders->isChecked(); Settings::values.dump_macros = ui->dump_macros->isChecked(); Settings::values.disable_shader_loop_safety_checks = ui->disable_loop_safety_checks->isChecked(); @@ -131,6 +141,9 @@ void ConfigureDebug::ApplyConfiguration() { Settings::values.serial_battery = ui->serial_battery_edit->text().toUInt(); Settings::values.serial_unit = ui->serial_board_edit->text().toUInt(); Settings::values.debug_knobs = ui->debug_knobs_spinbox->value(); + Settings::values.gpu_log_level = + static_cast(ui->gpu_log_level->currentIndex()); + Settings::values.gpu_log_shader_dumps = ui->gpu_log_shader_dumps->isChecked(); Debugger::ToggleConsole(); Common::Log::Filter filter; filter.ParseFilterString(Settings::values.log_filter.GetValue()); diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index f76f3b012d..4e49c55f37 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -157,7 +157,7 @@ Logging - + @@ -197,7 +197,77 @@ - + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + GPU Logging/Level + + + + + + + Detail level for GPU logs. Off disables logging entirely. + + + + Off + + + + + Errors + + + + + Standard + + + + + Verbose + + + + + All + + + + + + + + + + + Show Log in Console + + + + true @@ -210,15 +280,20 @@ - - - - Show Log in Console - - - - + + + + 1 + 0 + + + + + 250 + 0 + + Open Log Location @@ -270,7 +345,7 @@ - + true @@ -294,15 +369,15 @@ - + true - When checked, it will dump all the original assembler shaders from the disk shader cache or game as found + When checked, it will dump original Maxwell guest shader bytecode (the input to the recompiler) as .ash files under DumpDir/shaders/. Useful for inspection with nvdisasm. - Dump Game Shaders + Dump Guest (Maxwell) Shaders @@ -313,7 +388,7 @@ - + true @@ -326,7 +401,7 @@ - + true @@ -352,7 +427,7 @@ - + Qt::Orientation::Vertical @@ -388,6 +463,16 @@ + + + + When checked, it will dump the recompiler's output SPIR-V binaries (.spv) under LogDir/shaders/. Inspect via SPIRV-Tools (spirv-dis / spirv-cross / spirv-val) or RenderDoc. + + + Dump SPIR-V Shaders + + + @@ -514,31 +599,65 @@ - - - - 0 - - - 65535 - - - Bitmask for quick development toggles - - - Set debug knobs (bitmask) - - - 16-bit debug knob set for quick development toggles - - - (bitmask) - - - Debug Knobs: - - - + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Debug Knobs: + + + + + + + 0 + + + 65535 + + + Bitmask for quick development toggles + + + Set debug knobs (bitmask) + + + 16-bit debug knob set for quick development toggles + + + (bitmask) + + + + + + @@ -579,7 +698,7 @@ - + Qt::Orientation::Vertical diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index d4a16e9f73..547008262e 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -10,8 +10,8 @@ #include "common/settings_enums.h" #include "core/core.h" #include "qt_common/config/uisettings.h" +#include "qt_common/util/vk.h" #include "ui_configure.h" -#include "vk_device_info.h" #include "yuzu/configuration/configure_applets.h" #include "yuzu/configuration/configure_audio.h" #include "yuzu/configuration/configure_cpu.h" diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h index 5c504c9a16..73db11238e 100644 --- a/src/yuzu/configuration/configure_dialog.h +++ b/src/yuzu/configuration/configure_dialog.h @@ -11,8 +11,8 @@ #include #include "configuration/shared_widget.h" #include "qt_common/config/shared_translation.h" +#include "qt_common/util/vk.h" #include "yuzu/configuration/configuration_shared.h" -#include "yuzu/vk_device_info.h" namespace Core { class System; diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index 54835aea5a..641f65d82a 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -4,14 +4,7 @@ // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include #include -#include -#include -#include -#include -#include -#include #include #include #include @@ -34,52 +27,19 @@ #include #include "common/common_types.h" -#include "common/dynamic_library.h" -#include "common/logging.h" #include "common/settings.h" #include "common/settings_enums.h" #include "core/core.h" #include "qt_common/config/uisettings.h" -#include "qt_common/qt_common.h" +#include "qt_common/util/vk.h" #include "ui_configure_graphics.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_graphics.h" #include "yuzu/configuration/shared_widget.h" -#include "yuzu/vk_device_info.h" -static const std::vector default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR, - VK_PRESENT_MODE_FIFO_KHR}; - -// Converts a setting to a present mode (or vice versa) -static constexpr VkPresentModeKHR VSyncSettingToMode(Settings::VSyncMode mode) { - switch (mode) { - case Settings::VSyncMode::Immediate: - return VK_PRESENT_MODE_IMMEDIATE_KHR; - case Settings::VSyncMode::Mailbox: - return VK_PRESENT_MODE_MAILBOX_KHR; - case Settings::VSyncMode::Fifo: - return VK_PRESENT_MODE_FIFO_KHR; - case Settings::VSyncMode::FifoRelaxed: - return VK_PRESENT_MODE_FIFO_RELAXED_KHR; - default: - return VK_PRESENT_MODE_FIFO_KHR; - } -} - -static constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode) { - switch (mode) { - case VK_PRESENT_MODE_IMMEDIATE_KHR: - return Settings::VSyncMode::Immediate; - case VK_PRESENT_MODE_MAILBOX_KHR: - return Settings::VSyncMode::Mailbox; - case VK_PRESENT_MODE_FIFO_KHR: - return Settings::VSyncMode::Fifo; - case VK_PRESENT_MODE_FIFO_RELAXED_KHR: - return Settings::VSyncMode::FifoRelaxed; - default: - return Settings::VSyncMode::Fifo; - } -} +#ifdef HAS_RESHADE +#include "yuzu/configuration/configure_post_processing.h" +#endif ConfigureGraphics::ConfigureGraphics( const Core::System& system_, std::vector& records_, @@ -260,12 +220,18 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) { std::vector hold_api; for (const auto setting : Settings::values.linkage.by_category[Settings::Category::Renderer]) { +#ifndef HAS_RESHADE + if (setting->Id() == Settings::values.post_shader_enabled.Id()) { + continue; + } +#endif + ConfigurationShared::Widget* widget = [&]() { if (setting->Id() == Settings::values.fsr_sharpening_slider.Id()) { // FSR needs a reversed slider and a 0.5 multiplier return builder.BuildWidget( setting, apply_funcs, ConfigurationShared::RequestType::ReverseSlider, true, - 0.5f, nullptr, tr("%", "FSR sharpening percentage (e.g. 50%)")); + 0.5f, nullptr, tr("%", "FSR/SGSR sharpening percentage (e.g. 50%)")); } else { return builder.BuildWidget(setting, apply_funcs); } @@ -339,6 +305,23 @@ void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) { // Keep track of the resolution combobox to update other UI tabs that need it resolution_combobox = widget->combobox; hold_graphics.emplace(setting->Id(), widget); +#ifdef HAS_RESHADE + } else if (setting->Id() == Settings::values.post_shader_enabled.Id()) { + QPushButton* post_shader_button = new QPushButton(tr("Configure Effects..."), widget); + post_shader_button->setVisible(widget->checkbox->isChecked()); + + connect(post_shader_button, &QAbstractButton::clicked, this, [this]() { + ConfigurePostProcessing dialog(this); + dialog.exec(); + }); + connect(widget->checkbox, &QCheckBox::toggled, post_shader_button, + &QWidget::setVisible); + + QBoxLayout* row = qobject_cast(widget->layout()); + row->insertWidget(1, post_shader_button); + + hold_graphics.emplace(setting->Id(), widget); +#endif } else { hold_graphics.emplace(setting->Id(), widget); } diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index 3009edf4b6..9002af437b 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2016 Citra Emulator Project @@ -19,7 +19,7 @@ #include "common/common_types.h" #include "common/settings_enums.h" #include "qt_common/config/shared_translation.h" -#include "vk_device_info.h" +#include "qt_common/util/vk.h" #include "yuzu/configuration/configuration_shared.h" class QPushButton; diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp index d8f0ab7e6c..47b30322aa 100644 --- a/src/yuzu/configuration/configure_input_advanced.cpp +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -99,6 +99,7 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(Core::HID::HIDCore& hid_core_, QW #ifndef _WIN32 ui->enable_raw_input->setVisible(false); + ui->disable_wgi_xinput->setVisible(false); #endif LoadConfiguration(); @@ -139,6 +140,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() { Settings::values.emulate_analog_keyboard = ui->emulate_analog_keyboard->isChecked(); Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked(); Settings::values.enable_raw_input = ui->enable_raw_input->isChecked(); + Settings::values.disable_wgi_xinput = ui->disable_wgi_xinput->isChecked(); Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked(); Settings::values.controller_navigation = ui->controller_navigation->isChecked(); Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked(); @@ -174,6 +176,7 @@ void ConfigureInputAdvanced::LoadConfiguration() { ui->emulate_analog_keyboard->setChecked(Settings::values.emulate_analog_keyboard.GetValue()); ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled); ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue()); + ui->disable_wgi_xinput->setChecked(Settings::values.disable_wgi_xinput.GetValue()); ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue()); ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue()); ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue()); diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui index f489d93965..a082523216 100644 --- a/src/yuzu/configuration/configure_input_advanced.ui +++ b/src/yuzu/configuration/configure_input_advanced.ui @@ -2757,6 +2757,22 @@ + + + + Aimed to disable SDL GUIDE button hack: synthetic GUIDE(HOME) event when SELECT(MINUS) + START(PLUS) pressed. May impact Win related trigger/rumble/etc stuff + + + + 0 + 23 + + + + Disable SDL WGI/XInput (Requires restart) + + + diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 821825f6e1..2750cd4a13 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -1546,12 +1546,14 @@ void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) { const auto button = GRenderWindow::QtButtonToMouseButton(event->button()); input_subsystem->GetMouse()->PressButton(0, 0, button); + input_subsystem->GetMouse()->NotifyChanged(); } void ConfigureInputPlayer::wheelEvent(QWheelEvent* event) { const int x = event->angleDelta().x(); const int y = event->angleDelta().y(); input_subsystem->GetMouse()->MouseWheelChange(x, y); + input_subsystem->GetMouse()->NotifyChanged(); } void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index 790a860002..faf4e021ec 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp @@ -1612,7 +1612,7 @@ void PlayerControlPreview::DrawGCBody(QPainter& p, const QPointF center) { std::array qbody; std::array left_hex; std::array right_hex; - constexpr float angle = 2 * 3.1415f / 8; + constexpr float angle = 2.f * float(M_PI) / 8.f; for (std::size_t point = 0; point < gc_left_body.size() / 2; ++point) { const float body_x = gc_left_body[point * 2 + 0]; @@ -2936,10 +2936,10 @@ void PlayerControlPreview::DrawArrow(QPainter& p, const QPointF center, const Di } // Draw motion functions -void PlayerControlPreview::Draw3dCube(QPainter& p, QPointF center, const Common::Vec3f& euler, +void PlayerControlPreview::Draw3dCube(QPainter& p, QPointF center, const Common::Vec& euler, float size) { - std::array cube{ - Common::Vec3f{-0.7f, -1, -0.5f}, + std::array, 8> cube{ + Common::Vec{-0.7f, -1, -0.5f}, {-0.7f, 1, -0.5f}, {0.7f, 1, -0.5f}, {0.7f, -1, -0.5f}, @@ -2949,30 +2949,38 @@ void PlayerControlPreview::Draw3dCube(QPainter& p, QPointF center, const Common: {0.7f, -1, 0.5f}, }; - for (Common::Vec3f& point : cube) { - point.RotateFromOrigin(euler.x, euler.y, euler.z); + for (Common::Vec& point : cube) { + float temp = point[1]; + point[1] = std::cos(euler[0]) * point[1] - std::sin(euler[0]) * point[2]; + point[2] = std::sin(euler[0]) * temp + std::cos(euler[0]) * point[2]; + temp = point[0]; + point[0] = std::cos(euler[1]) * point[0] + std::sin(euler[1]) * point[2]; + point[2] = -std::sin(euler[1]) * temp + std::cos(euler[1]) * point[2]; + temp = point[0]; + point[0] = std::cos(euler[2]) * point[0] - std::sin(euler[2]) * point[1]; + point[1] = std::sin(euler[2]) * temp + std::cos(euler[2]) * point[1]; point *= size; } const std::array front_face{ - center + QPointF{cube[0].x, cube[0].y}, - center + QPointF{cube[1].x, cube[1].y}, - center + QPointF{cube[2].x, cube[2].y}, - center + QPointF{cube[3].x, cube[3].y}, + center + QPointF{cube[0][0], cube[0][1]}, + center + QPointF{cube[1][0], cube[1][1]}, + center + QPointF{cube[2][0], cube[2][1]}, + center + QPointF{cube[3][0], cube[3][1]}, }; const std::array back_face{ - center + QPointF{cube[4].x, cube[4].y}, - center + QPointF{cube[5].x, cube[5].y}, - center + QPointF{cube[6].x, cube[6].y}, - center + QPointF{cube[7].x, cube[7].y}, + center + QPointF{cube[4][0], cube[4][1]}, + center + QPointF{cube[5][0], cube[5][1]}, + center + QPointF{cube[6][0], cube[6][1]}, + center + QPointF{cube[7][0], cube[7][1]}, }; DrawPolygon(p, front_face); DrawPolygon(p, back_face); - p.drawLine(center + QPointF{cube[0].x, cube[0].y}, center + QPointF{cube[4].x, cube[4].y}); - p.drawLine(center + QPointF{cube[1].x, cube[1].y}, center + QPointF{cube[5].x, cube[5].y}); - p.drawLine(center + QPointF{cube[2].x, cube[2].y}, center + QPointF{cube[6].x, cube[6].y}); - p.drawLine(center + QPointF{cube[3].x, cube[3].y}, center + QPointF{cube[7].x, cube[7].y}); + p.drawLine(center + QPointF{cube[0][0], cube[0][1]}, center + QPointF{cube[4][0], cube[4][1]}); + p.drawLine(center + QPointF{cube[1][0], cube[1][1]}, center + QPointF{cube[5][0], cube[5][1]}); + p.drawLine(center + QPointF{cube[2][0], cube[2][1]}, center + QPointF{cube[6][0], cube[6][1]}); + p.drawLine(center + QPointF{cube[3][0], cube[3][1]}, center + QPointF{cube[7][0], cube[7][1]}); } template diff --git a/src/yuzu/configuration/configure_input_player_widget.h b/src/yuzu/configuration/configure_input_player_widget.h index d7ec943240..d439c718fb 100644 --- a/src/yuzu/configuration/configure_input_player_widget.h +++ b/src/yuzu/configuration/configure_input_player_widget.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -198,7 +198,7 @@ private: void DrawArrow(QPainter& p, QPointF center, Direction direction, float size); // Draw motion functions - void Draw3dCube(QPainter& p, QPointF center, const Common::Vec3f& euler, float size); + void Draw3dCube(QPainter& p, QPointF center, const Common::Vec& euler, float size); // Draw primitive types template diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp index d1bcac12aa..2083315c03 100644 --- a/src/yuzu/configuration/configure_motion_touch.cpp +++ b/src/yuzu/configuration/configure_motion_touch.cpp @@ -94,11 +94,10 @@ void ConfigureMotionTouch::SetConfiguration() { const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue()); touch_from_button_maps = Settings::values.touch_from_button_maps; - for (const auto& touch_map : touch_from_button_maps) { + for (const auto& touch_map : touch_from_button_maps) ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name)); - } - ui->touch_from_button_map->setCurrentIndex( - Settings::values.touch_from_button_map_index.GetValue()); + if (auto const index = Settings::values.touch_from_button_map_index.GetValue(); int(index) < ui->touch_from_button_map->count()) + ui->touch_from_button_map->setCurrentIndex(index); min_x = touch_param.Get("min_x", 100); min_y = touch_param.Get("min_y", 50); diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index dcf40d678f..6f530eb0ab 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -30,6 +30,8 @@ #include "core/loader/loader.h" #include "frontend_common/config.h" #include "qt_common/config/uisettings.h" +#include "qt_common/qt_common.h" +#include "qt_common/util/vk.h" #include "ui_configure_per_game.h" #include "yuzu/configuration/configuration_shared.h" #include "yuzu/configuration/configure_applets.h" @@ -44,7 +46,6 @@ #include "yuzu/configuration/configure_per_game_addons.h" #include "yuzu/configuration/configure_system.h" #include "yuzu/util/util.h" -#include "yuzu/vk_device_info.h" ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name, std::vector& vk_device_records, @@ -205,6 +206,6 @@ void ConfigurePerGame::LoadConfiguration() { ui->display_format->setText( QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))); - const auto valueText = ReadableByteSize(file->GetSize()); + const auto valueText = QtCommon::ReadableByteSize(file->GetSize()); ui->display_size->setText(valueText); } diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h index ca973db8b0..bc6561235e 100644 --- a/src/yuzu/configuration/configure_per_game.h +++ b/src/yuzu/configuration/configure_per_game.h @@ -18,7 +18,7 @@ #include "frontend_common/config.h" #include "qt_common/config/qt_config.h" #include "qt_common/config/shared_translation.h" -#include "vk_device_info.h" +#include "qt_common/util/vk.h" #include "yuzu/configuration/configuration_shared.h" namespace Core { diff --git a/src/yuzu/configuration/configure_post_processing.cpp b/src/yuzu/configuration/configure_post_processing.cpp new file mode 100644 index 0000000000..659c3618f2 --- /dev/null +++ b/src/yuzu/configuration/configure_post_processing.cpp @@ -0,0 +1,517 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/settings.h" +#include "video_core/post_processing/fx_chain.h" +#include "video_core/post_processing/fx_effect.h" +#include "video_core/post_processing/fx_preset.h" +#include "yuzu/configuration/configure_post_processing.h" + +namespace { + +std::array CurrentValue(int index, const VideoCore::FxUniformDesc& uniform) { + auto& chain = VideoCore::FxChain::Instance(); + if (chain.HasValue(static_cast(index), uniform.name)) { + return chain.GetValue(static_cast(index), uniform.name); + } + return uniform.default_value; +} + +int SliderSteps(const VideoCore::FxUniformDesc& uniform) { + const float span = uniform.ui_max - uniform.ui_min; + const int steps = static_cast(std::lround(span / uniform.ui_step)); + if (steps < 1) { + return 1; + } + return steps; +} + +QString FormatValue(const VideoCore::FxUniformDesc& uniform, float value) { + if (uniform.kind == VideoCore::FxUniformKind::Floating) { + return QString::number(value, 'f', 3); + } + return QString::number(static_cast(std::lround(value))); +} + +QString SlotLabel(const VideoCore::FxEffectDesc& effect, const std::string& technique) { + const QString name = QString::fromStdString(effect.label); + if (effect.techniques.size() == 1) { + return name; + } + return name + QStringLiteral(" · ") + QString::fromStdString(technique); +} + +} // Anonymous namespace + +ConfigurePostProcessing::ConfigurePostProcessing(QWidget* parent) : QDialog(parent) { + setWindowTitle(tr("Post-Processing Effects")); + setMinimumWidth(560); + setMinimumHeight(460); + + auto* root = new QVBoxLayout(this); + + auto* description = + new QLabel(tr("Changes apply immediately while a game is running."), this); + description->setWordWrap(true); + root->addWidget(description); + + enabled_box = new QCheckBox(tr("Enable post-processing"), this); + enabled_box->setChecked(Settings::values.post_shader_enabled.GetValue()); + connect(enabled_box, &QCheckBox::toggled, this, [](bool checked) { + Settings::values.post_shader_enabled.SetValue(checked); + }); + root->addWidget(enabled_box); + + auto* preset_row = new QHBoxLayout(); + preset_row->addWidget(new QLabel(tr("Preset:"), this)); + + preset_combo = new QComboBox(this); + preset_row->addWidget(preset_combo, 1); + + auto* apply_button = new QPushButton(tr("Apply"), this); + connect(apply_button, &QPushButton::clicked, this, [this]() { + const QString name = preset_combo->currentData().toString(); + if (name.isEmpty()) { + return; + } + VideoCore::ApplyFxPreset(name.toStdString()); + ApplyStructuralChange(); + }); + preset_row->addWidget(apply_button); + + auto* reset_button = new QPushButton(tr("Reset Values"), this); + reset_button->setToolTip(tr("Return every effect to the values the preset ships with.")); + connect(reset_button, &QPushButton::clicked, this, [this]() { + const std::string active = VideoCore::GetActiveFxPreset(); + if (active.empty()) { + return; + } + VideoCore::ApplyFxPreset(active); + ApplyStructuralChange(); + }); + preset_row->addWidget(reset_button); + + auto* save_button = new QPushButton(tr("Save As..."), this); + connect(save_button, &QPushButton::clicked, this, [this]() { + bool accepted = false; + const QString name = + QInputDialog::getText(this, tr("Save Preset"), tr("Preset name:"), QLineEdit::Normal, + QString(), &accepted); + if (!accepted || name.trimmed().isEmpty()) { + return; + } + VideoCore::SaveFxPreset(name.trimmed().toStdString(), std::string()); + PopulatePresetCombo(); + RefreshPresetStatus(); + }); + preset_row->addWidget(save_button); + + auto* clear_button = new QPushButton(tr("Clear"), this); + clear_button->setToolTip(tr("Remove every effect in use and deselect the preset.")); + connect(clear_button, &QPushButton::clicked, this, [this]() { + VideoCore::FxChain::Instance().Clear(); + VideoCore::SetActiveFxPreset(std::string()); + preset_combo->setCurrentIndex(0); + ApplyStructuralChange(); + }); + preset_row->addWidget(clear_button); + + root->addLayout(preset_row); + + preset_status = new QLabel(this); + preset_status->setWordWrap(true); + root->addWidget(preset_status); + + auto* scroll = new QScrollArea(this); + scroll->setWidgetResizable(true); + slots_container = new QWidget(scroll); + slots_layout = new QVBoxLayout(slots_container); + slots_layout->setAlignment(Qt::AlignTop); + scroll->setWidget(slots_container); + root->addWidget(scroll, 1); + + auto* actions = new QHBoxLayout(); + + auto* add_button = new QPushButton(tr("Add Effect"), this); + auto* add_menu = new QMenu(add_button); + for (const auto& effect : VideoCore::GetFxCatalog()) { + if (!effect.Valid()) { + continue; + } + for (const auto& technique : effect.techniques) { + QAction* action = add_menu->addAction(SlotLabel(effect, technique)); + action->setToolTip(QString::fromStdString(effect.description)); + const std::string file = effect.file; + const std::string name = technique; + connect(action, &QAction::triggered, this, [this, file, name]() { + VideoCore::FxChain::Instance().Append(file, name); + ApplyStructuralChange(); + }); + } + } + add_button->setMenu(add_menu); + actions->addWidget(add_button); + + actions->addStretch(); + root->addLayout(actions); + + auto* buttons = new QDialogButtonBox(QDialogButtonBox::Close, this); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::close); + root->addWidget(buttons); + + VideoCore::ReloadFxCatalog(); + VideoCore::ReloadFxPresetCatalog(); + VideoCore::FxChain::Instance().LoadFromSettings(); + VideoCore::FxChain::Instance().DropUnknownEntries(); + PopulatePresetCombo(); + RebuildRows(); + RefreshPresetStatus(); +} + +ConfigurePostProcessing::~ConfigurePostProcessing() = default; + +void ConfigurePostProcessing::ApplyStructuralChange() { + VideoCore::FxChain::Instance().StoreToSettings(); + RebuildRows(); + RefreshPresetStatus(); +} + +void ConfigurePostProcessing::PopulatePresetCombo() { + const QString previous = preset_combo->currentData().toString(); + preset_combo->clear(); + preset_combo->addItem(tr("None"), QString()); + + for (const auto& preset : VideoCore::GetFxPresetCatalog()) { + const QString name = QString::fromStdString(preset.name); + preset_combo->addItem(name, name); + preset_combo->setItemData(preset_combo->count() - 1, + QString::fromStdString(preset.description), Qt::ToolTipRole); + } + + const int restored = preset_combo->findData(previous); + if (!previous.isEmpty() && restored >= 0) { + preset_combo->setCurrentIndex(restored); + return; + } + + const int active = preset_combo->findData(QString::fromStdString(VideoCore::GetActiveFxPreset())); + if (active >= 0) { + preset_combo->setCurrentIndex(active); + return; + } + + preset_combo->setCurrentIndex(0); +} + +void ConfigurePostProcessing::RefreshPresetStatus() { + const std::string active = VideoCore::GetActiveFxPreset(); + if (active.empty()) { + if (VideoCore::FxChain::Instance().Size() == 0) { + preset_status->setText( + tr("No effects in use. Pick a preset and press Apply, or add effects one " + "by one.")); + return; + } + preset_status->setText(tr("Custom chain. Pick a preset above and press Apply to replace it.")); + return; + } + + if (VideoCore::IsActiveFxPresetModified()) { + preset_status->setText( + tr("Active preset: %1 (modified). Press Reset Values to go back to how it ships.") + .arg(QString::fromStdString(active))); + return; + } + + preset_status->setText(tr("Active preset: %1").arg(QString::fromStdString(active))); +} + +void ConfigurePostProcessing::PopulateEffectCombo(QComboBox* combo, + const VideoCore::FxChainEntry& entry) const { + combo->clear(); + int selected = -1; + + for (const auto& effect : VideoCore::GetFxCatalog()) { + if (!effect.Valid()) { + continue; + } + for (const auto& technique : effect.techniques) { + const QString key = QString::fromStdString(effect.file + "|" + technique); + combo->addItem(SlotLabel(effect, technique), key); + combo->setItemData(combo->count() - 1, QString::fromStdString(effect.description), + Qt::ToolTipRole); + if (effect.file == entry.file && technique == entry.technique) { + selected = combo->count() - 1; + } + } + } + + if (selected >= 0) { + combo->setCurrentIndex(selected); + } +} + +void ConfigurePostProcessing::BuildUniformWidget(QWidget* parent, QVBoxLayout* layout, int index, + const VideoCore::FxUniformDesc& uniform) { + const auto value = CurrentValue(index, uniform); + const QString label = QString::fromStdString(uniform.label); + + if (uniform.ui_type == VideoCore::FxUiType::CheckBox) { + auto* box = new QCheckBox(label, parent); + box->setChecked(value[0] != 0.0f); + if (!uniform.tooltip.empty()) { + box->setToolTip(QString::fromStdString(uniform.tooltip)); + } + const std::string name = uniform.name; + connect(box, &QCheckBox::toggled, this, [index, name](bool checked) { + std::array next{}; + if (checked) { + next[0] = 1.0f; + } + VideoCore::FxChain::Instance().SetValue(static_cast(index), name, next); + VideoCore::FxChain::Instance().StoreToSettings(); + }); + layout->addWidget(box); + return; + } + + if (uniform.ui_type == VideoCore::FxUiType::Combo || + uniform.ui_type == VideoCore::FxUiType::Radio) { + auto* row = new QHBoxLayout(); + row->addWidget(new QLabel(label, parent)); + auto* combo = new QComboBox(parent); + for (size_t i = 0; i < uniform.items.size(); ++i) { + combo->addItem(QString::fromStdString(uniform.items[i]), static_cast(i)); + } + if (combo->count() == 0) { + combo->addItem(tr("Enabled"), 1); + combo->addItem(tr("Disabled"), 0); + } + const int current = static_cast(std::lround(value[0])); + if (current >= 0 && current < combo->count()) { + combo->setCurrentIndex(current); + } + if (!uniform.tooltip.empty()) { + combo->setToolTip(QString::fromStdString(uniform.tooltip)); + } + const std::string name = uniform.name; + connect(combo, &QComboBox::currentIndexChanged, this, [index, name](int selected) { + std::array next{}; + next[0] = static_cast(selected); + VideoCore::FxChain::Instance().SetValue(static_cast(index), name, next); + VideoCore::FxChain::Instance().StoreToSettings(); + }); + row->addWidget(combo, 1); + layout->addLayout(row); + return; + } + + auto* grid = new QGridLayout(); + for (unsigned component = 0; component < uniform.components; ++component) { + QString component_label = label; + if (uniform.components > 1) { + component_label = label + QStringLiteral(" [%1]").arg(component); + } + + auto* name_label = new QLabel(component_label, parent); + auto* value_label = new QLabel(parent); + value_label->setMinimumWidth(64); + value_label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + value_label->setText(FormatValue(uniform, value[component])); + + auto* slider = new QSlider(Qt::Horizontal, parent); + slider->setMinimum(0); + slider->setMaximum(SliderSteps(uniform)); + slider->setValue( + static_cast(std::lround((value[component] - uniform.ui_min) / uniform.ui_step))); + if (!uniform.tooltip.empty()) { + slider->setToolTip(QString::fromStdString(uniform.tooltip)); + } + + const std::string name = uniform.name; + const auto desc = uniform; + connect(slider, &QSlider::valueChanged, this, + [index, name, desc, component, value_label](int steps) { + auto next = CurrentValue(index, desc); + next[component] = desc.ui_min + static_cast(steps) * desc.ui_step; + VideoCore::FxChain::Instance().SetValue(static_cast(index), name, next); + VideoCore::FxChain::Instance().StoreToSettings(); + value_label->setText(FormatValue(desc, next[component])); + }); + + grid->addWidget(name_label, static_cast(component), 0); + grid->addWidget(slider, static_cast(component), 1); + grid->addWidget(value_label, static_cast(component), 2); + } + layout->addLayout(grid); +} + +QWidget* ConfigurePostProcessing::BuildSlot(int index, const VideoCore::FxChainEntry& entry) { + auto* group = new QFrame(slots_container); + group->setObjectName(QStringLiteral("fxSlot")); + group->setStyleSheet(QStringLiteral( + "QFrame#fxSlot { background-color: palette(alternate-base);" + " border: 1px solid palette(mid); border-radius: 6px; }")); + auto* layout = new QVBoxLayout(group); + + auto* header = new QHBoxLayout(); + + auto* toggle = new QToolButton(group); + toggle->setArrowType(Qt::RightArrow); + toggle->setAutoRaise(true); + toggle->setCheckable(true); + header->addWidget(toggle); + + auto* combo = new QComboBox(group); + PopulateEffectCombo(combo, entry); + connect(combo, &QComboBox::currentIndexChanged, this, [this, index, combo](int) { + const QString key = combo->currentData().toString(); + const qsizetype separator = key.indexOf(QLatin1Char('|')); + if (separator < 0) { + return; + } + VideoCore::FxChain::Instance().Replace(static_cast(index), + key.left(separator).toStdString(), + key.mid(separator + 1).toStdString()); + ApplyStructuralChange(); + }); + header->addWidget(combo, 1); + + auto* up_button = new QToolButton(group); + up_button->setText(QStringLiteral("▲")); + up_button->setEnabled(index > 0); + connect(up_button, &QToolButton::clicked, this, [this, index]() { + VideoCore::FxChain::Instance().Move(static_cast(index), -1); + ApplyStructuralChange(); + }); + header->addWidget(up_button); + + auto* down_button = new QToolButton(group); + down_button->setText(QStringLiteral("▼")); + down_button->setEnabled(static_cast(index) + 1 < VideoCore::FxChain::Instance().Size()); + connect(down_button, &QToolButton::clicked, this, [this, index]() { + VideoCore::FxChain::Instance().Move(static_cast(index), 1); + ApplyStructuralChange(); + }); + header->addWidget(down_button); + + auto* reset_button = new QToolButton(group); + reset_button->setText(QStringLiteral("⟲")); + reset_button->setToolTip(tr("Reset to defaults")); + connect(reset_button, &QToolButton::clicked, this, [this, index]() { + VideoCore::FxChain::Instance().ResetValues(static_cast(index)); + ApplyStructuralChange(); + }); + header->addWidget(reset_button); + + auto* remove_button = new QToolButton(group); + remove_button->setText(QStringLiteral("✕")); + connect(remove_button, &QToolButton::clicked, this, [this, index]() { + VideoCore::FxChain::Instance().Remove(static_cast(index)); + ApplyStructuralChange(); + }); + header->addWidget(remove_button); + + layout->addLayout(header); + + auto* body = new QWidget(group); + auto* body_layout = new QVBoxLayout(body); + body_layout->setContentsMargins(0, 0, 0, 0); + body->setVisible(false); + layout->addWidget(body); + + connect(toggle, &QToolButton::toggled, this, [toggle, body](bool open) { + body->setVisible(open); + if (open) { + toggle->setArrowType(Qt::DownArrow); + } else { + toggle->setArrowType(Qt::RightArrow); + } + }); + + const VideoCore::FxEffectDesc* effect = VideoCore::FindFxEffect(entry.file); + if (effect == nullptr) { + auto* missing = + new QLabel(tr("Effect '%1' was not found.").arg(QString::fromStdString(entry.file)), + group); + missing->setWordWrap(true); + body_layout->addWidget(missing); + return group; + } + + if (!effect->error.empty()) { + auto* failed = new QLabel( + tr("Effect failed to compile:\n%1").arg(QString::fromStdString(effect->error)), group); + failed->setWordWrap(true); + body_layout->addWidget(failed); + return group; + } + + std::string current_category; + for (const auto& uniform : effect->uniforms) { + if (uniform.category != current_category) { + current_category = uniform.category; + if (!current_category.empty()) { + auto* category = new QLabel(QString::fromStdString(current_category), group); + category->setStyleSheet(QStringLiteral("font-weight: bold;")); + body_layout->addWidget(category); + } + } + BuildUniformWidget(body, body_layout, index, uniform); + } + + return group; +} + +void ConfigurePostProcessing::RebuildRows() { + QLayoutItem* item = nullptr; + while ((item = slots_layout->takeAt(0)) != nullptr) { + if (item->widget() != nullptr) { + item->widget()->deleteLater(); + } + delete item; + } + + bool has_usable = false; + for (const auto& effect : VideoCore::GetFxCatalog()) { + if (effect.Valid()) { + has_usable = true; + break; + } + } + + if (!has_usable) { + auto* empty = new QLabel( + tr("No usable ReShade FX effects were found. Place .fx files in the post_shaders " + "folder."), + slots_container); + empty->setWordWrap(true); + slots_layout->addWidget(empty); + return; + } + + const auto entries = VideoCore::FxChain::Instance().Entries(); + for (size_t i = 0; i < entries.size(); ++i) { + slots_layout->addWidget(BuildSlot(static_cast(i), entries[i])); + } +} diff --git a/src/yuzu/configuration/configure_post_processing.h b/src/yuzu/configuration/configure_post_processing.h new file mode 100644 index 0000000000..5873bbaa01 --- /dev/null +++ b/src/yuzu/configuration/configure_post_processing.h @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +class QCheckBox; +class QComboBox; +class QLabel; +class QVBoxLayout; +class QWidget; + +namespace VideoCore { +struct FxChainEntry; +struct FxEffectDesc; +struct FxUniformDesc; +} + +class ConfigurePostProcessing : public QDialog { + Q_OBJECT + +public: + explicit ConfigurePostProcessing(QWidget* parent = nullptr); + ~ConfigurePostProcessing() override; + +private: + void RebuildRows(); + QWidget* BuildSlot(int index, const VideoCore::FxChainEntry& entry); + void BuildUniformWidget(QWidget* parent, QVBoxLayout* layout, int index, + const VideoCore::FxUniformDesc& uniform); + void PopulateEffectCombo(QComboBox* combo, const VideoCore::FxChainEntry& entry) const; + void ApplyStructuralChange(); + void PopulatePresetCombo(); + void RefreshPresetStatus(); + + QVBoxLayout* slots_layout{}; + QWidget* slots_container{}; + QComboBox* preset_combo{}; + QLabel* preset_status{}; + QCheckBox* enabled_box{}; +}; diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp index 4c842bd069..d722eba07b 100644 --- a/src/yuzu/configuration/configure_ringcon.cpp +++ b/src/yuzu/configuration/configure_ringcon.cpp @@ -378,6 +378,7 @@ void ConfigureRingController::mousePressEvent(QMouseEvent* event) { const auto button = GRenderWindow::QtButtonToMouseButton(event->button()); input_subsystem->GetMouse()->PressButton(0, 0, button); + input_subsystem->GetMouse()->NotifyChanged(); } void ConfigureRingController::keyPressEvent(QKeyEvent* event) { diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index af8d9fecce..3f2b793d86 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp @@ -109,7 +109,6 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) // Force game list reload if any of the relevant settings are changed. connect(ui->show_add_ons, &QCheckBox::STATE_CHANGED, this, &ConfigureUi::RequestGameListUpdate); - connect(ui->show_compat, &QCheckBox::STATE_CHANGED, this, &ConfigureUi::RequestGameListUpdate); connect(ui->show_size, &QCheckBox::STATE_CHANGED, this, &ConfigureUi::RequestGameListUpdate); connect(ui->show_types, &QCheckBox::STATE_CHANGED, this, &ConfigureUi::RequestGameListUpdate); connect(ui->show_play_time, &QCheckBox::STATE_CHANGED, this, @@ -153,7 +152,6 @@ void ConfigureUi::ApplyConfiguration() { UISettings::values.theme = ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString().toStdString(); UISettings::values.show_add_ons = ui->show_add_ons->isChecked(); - UISettings::values.show_compat = ui->show_compat->isChecked(); UISettings::values.show_size = ui->show_size->isChecked(); UISettings::values.show_types = ui->show_types->isChecked(); UISettings::values.show_play_time = ui->show_play_time->isChecked(); @@ -182,7 +180,6 @@ void ConfigureUi::SetConfiguration() { ui->language_combobox->setCurrentIndex(ui->language_combobox->findData( QString::fromStdString(UISettings::values.language.GetValue()))); ui->show_add_ons->setChecked(UISettings::values.show_add_ons.GetValue()); - ui->show_compat->setChecked(UISettings::values.show_compat.GetValue()); ui->show_size->setChecked(UISettings::values.show_size.GetValue()); ui->show_types->setChecked(UISettings::values.show_types.GetValue()); ui->show_play_time->setChecked(UISettings::values.show_play_time.GetValue()); diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui index 123068c9e2..452a614b78 100644 --- a/src/yuzu/configuration/configure_ui.ui +++ b/src/yuzu/configuration/configure_ui.ui @@ -76,13 +76,6 @@ - - - - Show Compatibility List - - - diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h index 326e7f9a1d..4ee7a266c4 100644 --- a/src/yuzu/configuration/input_profiles.h +++ b/src/yuzu/configuration/input_profiles.h @@ -7,7 +7,7 @@ #pragma once #include -#include +#include "common/container/unordered_map.h" #include "qt_common/config/qt_config.h" @@ -35,5 +35,5 @@ public: private: bool ProfileExistsInMap(const std::string& profile_name) const; - ankerl::unordered_dense::map> map_profiles; + ::Common::unordered_map> map_profiles; }; diff --git a/src/yuzu/configuration/shared_widget.cpp b/src/yuzu/configuration/shared_widget.cpp index ce11763f91..5e8e04be58 100644 --- a/src/yuzu/configuration/shared_widget.cpp +++ b/src/yuzu/configuration/shared_widget.cpp @@ -140,7 +140,12 @@ QWidget* Widget::CreateCombobox(std::function& serializer, if (combobox_enumerations.contains(type)) { enumeration = &combobox_enumerations.at(type); for (const auto& [id, name] : *enumeration) { - combobox->addItem(name); + if (type == Settings::EnumMetadata::Index() && + id == static_cast(Settings::AppletMode::Disabled) && + setting.Id() != Settings::values.controller_applet_mode.Id()) { + continue; + } + combobox->addItem(name, id); } } else { return combobox; @@ -158,9 +163,8 @@ QWidget* Widget::CreateCombobox(std::function& serializer, const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0); combobox->setCurrentIndex(find_index(setting_value)); - serializer = [this, enumeration]() { - int current = combobox->currentIndex(); - return std::to_string(enumeration->at(current).first); + serializer = [this]() { + return std::to_string(combobox->currentData().toUInt()); }; restore_func = [this, find_index]() { @@ -772,10 +776,19 @@ Builder::Builder(QWidget* parent_, bool runtime_lock_) Builder::~Builder() = default; +static bool IsAndroidOnly(const Settings::BasicSetting& setting) { + const std::string& label = setting.GetLabel(); + return label.starts_with("frame_gen") || label == "emulate_bgr565"; +} + Widget* Builder::BuildWidget(Settings::BasicSetting* setting, std::vector>& apply_funcs, RequestType request, bool managed, float multiplier, Settings::BasicSetting* other_setting, const QString& suffix) const { + if (IsAndroidOnly(*setting)) { + return nullptr; + } + if (!Settings::IsConfiguringGlobal() && !setting->Switchable()) { return nullptr; } diff --git a/src/yuzu/data_dialog.cpp b/src/yuzu/data_dialog.cpp index 8d50843eb3..ffdedcf1c2 100644 --- a/src/yuzu/data_dialog.cpp +++ b/src/yuzu/data_dialog.cpp @@ -63,27 +63,31 @@ DataWidget::DataWidget(FrontendCommon::DataManager::DataDir data_dir, void DataWidget::clear() { std::optional user_id = selectProfile(); - if (!user_id) return; + if (!user_id) + return; QtCommon::Content::ClearDataDir(m_dir, user_id.value()); scan(); } void DataWidget::open() { std::optional user_id = selectProfile(); - if (!user_id) return; - QDesktopServices::openUrl(QUrl::fromLocalFile( - QString::fromStdString(FrontendCommon::DataManager::GetDataDirString(m_dir, user_id.value())))); + if (!user_id) + return; + QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString( + FrontendCommon::DataManager::GetDataDirString(m_dir, user_id.value())))); } void DataWidget::upload() { std::optional user_id = selectProfile(); - if (!user_id) return; + if (!user_id) + return; QtCommon::Content::ExportDataDir(m_dir, user_id.value(), m_exportName); } void DataWidget::download() { std::optional user_id = selectProfile(); - if (!user_id) return; + if (!user_id) + return; QtCommon::Content::ImportDataDir(m_dir, user_id.value(), std::bind(&DataWidget::scan, this)); } @@ -107,7 +111,8 @@ std::optional DataWidget::selectProfile() { std::string user_id{}; if (m_dir == FrontendCommon::DataManager::DataDir::Saves) { user_id = GetProfileIDString(); - if (user_id.empty()) return std::nullopt; + if (user_id.empty()) + return std::nullopt; } return user_id; diff --git a/src/yuzu/data_dialog.h b/src/yuzu/data_dialog.h index 2915f00848..b780debdff 100644 --- a/src/yuzu/data_dialog.h +++ b/src/yuzu/data_dialog.h @@ -1,8 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef DATA_DIALOG_H -#define DATA_DIALOG_H +#pragma once #include #include "frontend_common/data_manager.h" @@ -47,5 +46,3 @@ private: std::optional selectProfile(); }; - -#endif // DATA_DIALOG_H diff --git a/src/yuzu/debugger/console.cpp b/src/yuzu/debugger/console.cpp index 8965c7a08f..3506f1edf3 100644 --- a/src/yuzu/debugger/console.cpp +++ b/src/yuzu/debugger/console.cpp @@ -11,7 +11,6 @@ #endif #include "common/logging.h" -#include "yuzu/debugger/console.h" #include "qt_common/config/uisettings.h" #include "yuzu/debugger/console.h" diff --git a/src/yuzu/game/carousel.cpp b/src/yuzu/game/carousel.cpp new file mode 100644 index 0000000000..ac9c9947b5 --- /dev/null +++ b/src/yuzu/game/carousel.cpp @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "qt_common/config/uisettings.h" +#include "qt_common/game_list/model.h" +#include "yuzu/game/common.h" +#include "yuzu/game/game_card.h" +#include "yuzu/game/carousel.h" + +GameCarousel::GameCarousel(QWidget* parent) : QListView{parent} { + m_gameCard = new GameCard(this); + setItemDelegate(m_gameCard); + + setViewMode(QListView::IconMode); + setMovement(QListView::Static); + setUniformItemSizes(true); + setSelectionMode(QAbstractItemView::SingleSelection); + + setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + setEditTriggers(QAbstractItemView::NoEditTriggers); + setContextMenuPolicy(Qt::CustomContextMenu); + + setSpacing(10); + setWordWrap(true); + setTextElideMode(Qt::ElideRight); + setFlow(QListView::LeftToRight); + setWrapping(false); +} + +void GameCarousel::SetModel(GameListModel* model) { + QListView::setModel(model); + UpdateIconSize(); +} + +void GameCarousel::ApplyFilter(const QString& edit_filter_text, GameListModel* model) { + int row_count = model->rowCount(); + + for (int i = 0; i < row_count; ++i) { + QStandardItem* item = model->item(i, 0); + if (!item) + continue; + + if (Yuzu::FilterMatches(edit_filter_text, item)) { + setRowHidden(i, false); + } else { + setRowHidden(i, true); + } + } +} + +void GameCarousel::UpdateIconSize() { + const u32 icon_size = UISettings::values.game_icon_size.GetValue(); + + int heightMargin = 0; + int widthMargin = 80; + + // TODO(crueter): get rid of this nonsense + if (UISettings::values.show_game_name) { + switch (icon_size) { + case 128: + heightMargin = 65; + break; + case 0: + widthMargin = 120; + heightMargin = 120; + break; + case 64: + heightMargin = 77; + break; + case 32: + case 256: + heightMargin = 81; + break; + } + } else { + widthMargin = 24; + heightMargin = 24; + } + + const int min_item_width = icon_size + widthMargin; + const int min_item_height = icon_size + heightMargin; + const int grid_height = std::max(min_item_height, viewport()->height()); + + QSize content_size(min_item_width, min_item_height); + QSize grid_size(min_item_width, grid_height); + if (gridSize() != grid_size) { + setUpdatesEnabled(false); + + setGridSize(grid_size); + m_gameCard->setSize(grid_size, content_size, 0, 0); + + setUpdatesEnabled(true); + } +} + +QModelIndex GameCarousel::indexAt(const QPoint& point) const { + QModelIndex index = QListView::indexAt(point); + if (!index.isValid()) + return {}; + if (m_gameCard && !m_gameCard->hitTest(point, index, this, visualRect(index))) + return {}; + return index; +} diff --git a/src/yuzu/game/carousel.h b/src/yuzu/game/carousel.h new file mode 100644 index 0000000000..c03884c8c2 --- /dev/null +++ b/src/yuzu/game/carousel.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +class GameCard; +class GameListModel; +class QResizeEvent; + +class GameCarousel : public QListView { + Q_OBJECT + +public: + explicit GameCarousel(QWidget* parent = nullptr); + + void SetModel(GameListModel* model); + void ApplyFilter(const QString& edit_filter_text, GameListModel* model); + void UpdateIconSize(); + + QModelIndex indexAt(const QPoint& point) const override; + +private: + GameCard* m_gameCard = nullptr; +}; diff --git a/src/yuzu/game/common.h b/src/yuzu/game/common.h new file mode 100644 index 0000000000..063733e8d8 --- /dev/null +++ b/src/yuzu/game/common.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +#include "qt_common/game_list/game_list_p.h" + +namespace Yuzu { + +inline bool ContainsAllWords(const QString& haystack, const QString& userinput) { + const QStringList userinput_split = userinput.split(QLatin1Char{' '}, Qt::SkipEmptyParts); + return std::all_of(userinput_split.begin(), userinput_split.end(), [&haystack](const QString& s) { + return haystack.contains(s); + }); +} + +inline bool FilterMatches(const QString& filter, const QStandardItem* item) { + if (filter.isEmpty()) + return true; + + auto const sluggify = [](const QString& s) { + QString o(s.normalized(QString::NormalizationForm_D).toLower()); + return o.replace(QRegularExpression(QStringLiteral("[^a-z\\s]")), QStringLiteral("")); + }; + + const auto norm_filter = sluggify(filter); + const auto program_id = item->data(GameListItemPath::ProgramIdRole).toULongLong(); + const QString file_path = sluggify(item->data(GameListItemPath::FullPathRole).toString()); + const QString file_title = sluggify(item->data(GameListItemPath::TitleRole).toString()); + const QString file_program_id = QStringLiteral("%1").arg(program_id, 16, 16, QLatin1Char{'0'}); + const QString file_name = sluggify(file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + file_title); + return (ContainsAllWords(file_name, norm_filter) + || ContainsAllWords(file_title, norm_filter)) + || (file_program_id.size() == 16 && file_program_id.contains(norm_filter)); +} + +} // namespace Yuzu diff --git a/src/yuzu/game/game_card.cpp b/src/yuzu/game/game_card.cpp index 05167fe455..12ad482c88 100644 --- a/src/yuzu/game/game_card.cpp +++ b/src/yuzu/game/game_card.cpp @@ -18,33 +18,16 @@ void GameCard::paint(QPainter* painter, const QStyleOptionViewItem& option, painter->save(); painter->setRenderHint(QPainter::Antialiasing); - // Padding, dimensions, alignment... - const int column = index.row() % m_columns; - const int cell_width = option.rect.width(); - const int fixed_card_width = cell_width - m_padding; - const int margins = 8; + constexpr int cardCornerRadius = 10; - // The gist of it is that this anchors the left and right sides to the edges, - // while maintaining an even gap between each card. - // I just smashed random keys into my keyboard until something worked. - // Don't even bother trying to figure out what the hell this is doing. - const auto total_row_width = m_columns * cell_width; - const auto total_gap_space = total_row_width - (margins * 2) - (m_columns * fixed_card_width); - const auto gap = (m_columns > 1) ? (total_gap_space / (m_columns - 1)) : 0; + const QRect cardRect = getCardRect(option, index); - const auto relative_x = margins + (column * (fixed_card_width + gap)); - const auto x_pos = option.rect.left() - (column * cell_width) + static_cast(relative_x); - - // also, add some additional padding here to prevent card overlap - QRect cardRect(x_pos + 4, option.rect.top() + 4, fixed_card_width - 8, option.rect.height() - margins); - - // colors QPalette palette = option.palette; QColor backgroundColor = palette.window().color(); QColor borderColor = palette.dark().color(); QColor textColor = palette.text().color(); - // if it's selected add a blue background + // highlight on select or hover if (option.state & QStyle::State_Selected) { backgroundColor = palette.highlight().color(); borderColor = palette.highlight().color().lighter(150); @@ -53,63 +36,45 @@ void GameCard::paint(QPainter* painter, const QStyleOptionViewItem& option, backgroundColor = backgroundColor.lighter(120); } - // bg painter->setBrush(backgroundColor); painter->setPen(QPen(borderColor, 1)); - painter->drawRoundedRect(cardRect, 10, 10); + painter->drawRoundedRect(cardRect, cardCornerRadius, cardCornerRadius); - // icon - int _iconsize = UISettings::values.game_icon_size.GetValue(); - QSize iconSize(_iconsize, _iconsize); - QPixmap iconPixmap = index.data(Qt::DecorationRole).value(); + const u32 icon_size = UISettings::values.game_icon_size.GetValue(); + QPixmap icon_pixmap = index.data(Qt::DecorationRole).value(); QRect iconRect; - if (!iconPixmap.isNull()) { - QSize scaledSize = iconPixmap.size(); - scaledSize.scale(iconSize, Qt::KeepAspectRatio); + if (!icon_pixmap.isNull()) { + QSize scaled = icon_pixmap.size(); + scaled.scale(icon_size, icon_size, Qt::KeepAspectRatio); - int x = cardRect.left() + (cardRect.width() - scaledSize.width()) / 2; - int y = cardRect.top() + margins; - - iconRect = QRect(x, y, scaledSize.width(), scaledSize.height()); + iconRect = {cardRect.left() + (cardRect.width() - scaled.width()) / 2, + cardRect.top() + cardMargin - 1, scaled.width(), scaled.height()}; painter->setRenderHint(QPainter::SmoothPixmapTransform, true); - // Put this in a separate thing on the painter stack to prevent clipping the text. painter->save(); - - // round image edges - QPainterPath path; - path.addRoundedRect(iconRect, 10, 10); - painter->setClipPath(path); - - painter->drawPixmap(iconRect, iconPixmap); - + QPainterPath clip_path; + clip_path.addRoundedRect(iconRect, cardCornerRadius, cardCornerRadius); + painter->setClipPath(clip_path); + painter->drawPixmap(iconRect, icon_pixmap); painter->restore(); } else { - // if there is no icon just draw a blank rect - iconRect = QRect(cardRect.left() + margins, cardRect.top() + margins, _iconsize, _iconsize); + iconRect = {cardRect.left() + cardMargin, cardRect.top() + cardMargin, + static_cast(icon_size), static_cast(icon_size)}; } if (UISettings::values.show_game_name.GetValue()) { - // padding + text QRect textRect = cardRect; - textRect.setTop(iconRect.bottom() + margins); - textRect.adjust(margins, 0, -margins, -margins); + textRect.setTop(iconRect.bottom() + cardMargin); + textRect.adjust(cardMargin, 0, -cardMargin, -cardMargin); - // We are already crammed on space, ignore the row 2 - QString title = index.data(Qt::DisplayRole).toString(); - title = title.split(QLatin1Char('\n')).first(); + QString title = index.data(Qt::DisplayRole).toString().split(QLatin1Char('\n')).first(); - // now draw text painter->setPen(textColor); QFont font = option.font; font.setBold(true); - - // TODO(crueter): fix this abysmal scaling - font.setPixelSize(1.5 + std::max(10.0, std::sqrt(_iconsize))); - - // TODO(crueter): elide mode + font.setPixelSize(std::max(11.0, std::sqrt(static_cast(icon_size)))); painter->setFont(font); painter->drawText(textRect, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, title); @@ -118,12 +83,62 @@ void GameCard::paint(QPainter* painter, const QStyleOptionViewItem& option, painter->restore(); } +QRect GameCard::getCardRect(const QStyleOptionViewItem& option, const QModelIndex& index) const { + const int cell_width = option.rect.width(); + + const int card_width = cell_width - m_padding; + + int card_left, card_top, card_height; + + if (m_columns >= 1) { + // grid mode + + // center everything in-line, such that the leftmost and rightmost cards + // have ~ equal padding to the edge of the viewport + // spacing between each card is larger, but equal to each other + const int column = index.row() % m_columns; + + const int row_width = m_columns * cell_width; + const int total_gap = row_width - cardMargin * 2 - m_columns * card_width; + const int gap = (m_columns > 1) ? (total_gap / (m_columns - 1)) : 0; + card_left = + option.rect.left() - column * cell_width + cardMargin + column * (card_width + gap) + 4; + + // fill cell vertically + card_top = option.rect.top() + cardMargin; + card_height = option.rect.height() - cardMargin - 1; + } else { + // carousel mode + card_left = option.rect.left() + cardMargin + 4; + + // the delegate itself takes up the full height, but the card itself + // gets centered + const int content_height = m_contentSize.height() - cardMargin; + const int cell_height = option.rect.height(); + + card_height = std::min(content_height, cell_height - cardMargin * 2) - 1; + card_top = option.rect.top() + (cell_height - card_height) / 2; + } + + return QRect(card_left, card_top, card_width - cardMargin, card_height); +} + +bool GameCard::hitTest(const QPoint& point, const QModelIndex& index, const QWidget* widget, + const QRect& cellRect) const { + QStyleOptionViewItem option; + option.initFrom(widget); + option.rect = cellRect; + return getCardRect(option, index).contains(point); +} + QSize GameCard::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { return m_size; } -void GameCard::setSize(const QSize& newSize, const int padding, const int columns) { +void GameCard::setSize(const QSize& newSize, const QSize& contentSize, const int padding, + const int columns) { m_size = newSize; + m_contentSize = contentSize; m_padding = padding; m_columns = columns; } diff --git a/src/yuzu/game/game_card.h b/src/yuzu/game/game_card.h index f2a3edfddf..1d522c0ed8 100644 --- a/src/yuzu/game/game_card.h +++ b/src/yuzu/game/game_card.h @@ -17,11 +17,19 @@ public: void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + QRect getCardRect(const QStyleOptionViewItem &option, const QModelIndex &index) const; + + bool hitTest(const QPoint& point, const QModelIndex& index, + const QWidget* widget, const QRect& cellRect) const; + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; - void setSize(const QSize& newSize, const int padding, const int columns); + void setSize(const QSize& newSize, const QSize& contentSize, const int padding, const int columns); private: + static constexpr int cardMargin = 8; + QSize m_size; + QSize m_contentSize; int m_padding; int m_columns; }; diff --git a/src/yuzu/game/game_grid.cpp b/src/yuzu/game/game_grid.cpp new file mode 100644 index 0000000000..c5f77cf897 --- /dev/null +++ b/src/yuzu/game/game_grid.cpp @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include "qt_common/config/uisettings.h" +#include "qt_common/game_list/model.h" +#include "yuzu/game/common.h" +#include "yuzu/game/game_card.h" +#include "yuzu/game/game_grid.h" + +GameGrid::GameGrid(QWidget* parent) : QListView{parent} { + m_gameCard = new GameCard(this); + setItemDelegate(m_gameCard); + + setViewMode(QListView::ListMode); + setResizeMode(QListView::Fixed); + setUniformItemSizes(true); + setSelectionMode(QAbstractItemView::SingleSelection); + setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); + + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + setEditTriggers(QAbstractItemView::NoEditTriggers); + setContextMenuPolicy(Qt::CustomContextMenu); + + setSpacing(10); + setWordWrap(true); + setTextElideMode(Qt::ElideRight); + setFlow(QListView::LeftToRight); + setWrapping(true); +} + +void GameGrid::SetModel(GameListModel* model) { + QListView::setModel(model); + UpdateIconSize(); +} + +void GameGrid::ApplyFilter(const QString& edit_filter_text, GameListModel* model) { + int row_count = model->rowCount(); + + for (int i = 0; i < row_count; ++i) { + QStandardItem* item = model->item(i, 0); + if (!item) + continue; + + if (Yuzu::FilterMatches(edit_filter_text, item)) { + setRowHidden(i, false); + } else { + setRowHidden(i, true); + } + } +} + +void GameGrid::UpdateIconSize() { + const u32 icon_size = UISettings::values.game_icon_size.GetValue(); + + int heightMargin = 0; + int widthMargin = 80; + + if (UISettings::values.show_game_name) { + switch (icon_size) { + case 128: + heightMargin = 65; + break; + case 0: + widthMargin = 120; + heightMargin = 120; + break; + case 64: + heightMargin = 77; + break; + case 32: + case 256: + heightMargin = 81; + break; + } + } else { + widthMargin = 24; + heightMargin = 24; + } + + const int view_width = viewport()->width(); + + const double spacing = 0.01; + const int min_item_width = icon_size + widthMargin; + + int columns = std::max(1, (view_width - 16) / min_item_width); + int stretched_width = ((view_width) - (spacing * (columns - 1))) / columns; + + QSize grid_size(stretched_width, icon_size + heightMargin); + if (gridSize() != grid_size) { + setUpdatesEnabled(false); + + setGridSize(grid_size); + m_gameCard->setSize(grid_size, grid_size, stretched_width - min_item_width, columns); + + setUpdatesEnabled(true); + } +} + +QModelIndex GameGrid::indexAt(const QPoint& point) const { + QModelIndex index = QListView::indexAt(point); + if (!index.isValid()) + return {}; + if (m_gameCard && !m_gameCard->hitTest(point, index, this, visualRect(index))) + return {}; + return index; +} diff --git a/src/yuzu/game/game_grid.h b/src/yuzu/game/game_grid.h new file mode 100644 index 0000000000..3b5d6da1d4 --- /dev/null +++ b/src/yuzu/game/game_grid.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +class GameCard; +class GameListModel; + +class GameGrid : public QListView { + Q_OBJECT + +public: + explicit GameGrid(QWidget* parent = nullptr); + + void SetModel(GameListModel* model); + void ApplyFilter(const QString& edit_filter_text, GameListModel* model); + void UpdateIconSize(); + + QModelIndex indexAt(const QPoint& point) const override; + +private: + GameCard* m_gameCard = nullptr; +}; diff --git a/src/yuzu/game/game_list.cpp b/src/yuzu/game/game_list.cpp index 87b5f87474..6b89e59253 100644 --- a/src/yuzu/game/game_list.cpp +++ b/src/yuzu/game/game_list.cpp @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2026 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 + #include #include #include @@ -15,431 +18,61 @@ #include #include #include -#include #include #include -#include -#include +#include + #include "common/common_types.h" -#include "common/logging.h" -#include "common/settings.h" #include "core/core.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" -#include "game/game_card.h" #include "qt_common/config/uisettings.h" +#include "qt_common/game_list/game_list_p.h" +#include "qt_common/game_list/model.h" #include "qt_common/qt_common.h" #include "qt_common/util/game.h" -#include "yuzu/compatibility_list.h" +#include "yuzu/game/carousel.h" +#include "yuzu/game/game_grid.h" #include "yuzu/game/game_list.h" -#include "yuzu/game/game_list_p.h" -#include "yuzu/game/game_list_worker.h" +#include "yuzu/game/game_tree.h" +#include "yuzu/game/search_field.h" #include "yuzu/main_window.h" #include "yuzu/util/controller_navigation.h" -GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist_, QObject* parent) - : QObject(parent), gamelist{gamelist_} {} - -// EventFilter in order to process systemkeys while editing the searchfield -bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* event) { - // If it isn't a KeyRelease event then continue with standard event processing - if (event->type() != QEvent::KeyRelease) - return QObject::eventFilter(obj, event); - - QKeyEvent* keyEvent = static_cast(event); - QString edit_filter_text = gamelist->search_field->edit_filter->text().toLower(); - - // If the searchfield's text hasn't changed special function keys get checked - // If no function key changes the searchfield's text the filter doesn't need to get reloaded - if (edit_filter_text == edit_filter_text_old) { - switch (keyEvent->key()) { - // Escape: Resets the searchfield - case Qt::Key_Escape: { - if (edit_filter_text_old.isEmpty()) { - return QObject::eventFilter(obj, event); - } else { - gamelist->search_field->edit_filter->clear(); - edit_filter_text.clear(); - } - break; - } - // Return and Enter - // If the enter key gets pressed first checks how many and which entry is visible - // If there is only one result launch this game - case Qt::Key_Return: - case Qt::Key_Enter: { - if (gamelist->search_field->visible == 1) { - const QString file_path = gamelist->GetLastFilterResultItem(); - - // To avoid loading error dialog loops while confirming them using enter - // Also users usually want to run a different game after closing one - gamelist->search_field->edit_filter->clear(); - edit_filter_text.clear(); - emit gamelist->GameChosen(file_path); - } else { - return QObject::eventFilter(obj, event); - } - break; - } - default: - return QObject::eventFilter(obj, event); - } - } - edit_filter_text_old = edit_filter_text; - return QObject::eventFilter(obj, event); -} - -void GameListSearchField::setFilterResult(int visible_, int total_) { - visible = visible_; - total = total_; - - label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible)); -} - -QString GameListSearchField::filterText() const { - return edit_filter->text(); -} - -QString GameList::GetLastFilterResultItem() const { - QString file_path; - - for (int i = 1; i < item_model->rowCount() - 1; ++i) { - const QStandardItem* folder = item_model->item(i, 0); - const QModelIndex folder_index = folder->index(); - const int children_count = folder->rowCount(); - - for (int j = 0; j < children_count; ++j) { - if (tree_view->isRowHidden(j, folder_index)) { - continue; - } - - const QStandardItem* child = folder->child(j, 0); - file_path = child->data(GameListItemPath::FullPathRole).toString(); - } - } - - return file_path; -} - -void GameListSearchField::clear() { - edit_filter->clear(); -} - -void GameListSearchField::setFocus() { - if (edit_filter->isVisible()) { - edit_filter->setFocus(); - } -} - -GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} { - auto* const key_release_eater = new KeyReleaseEater(parent, this); - layout_filter = new QHBoxLayout; - layout_filter->setContentsMargins(8, 8, 8, 8); - label_filter = new QLabel; - edit_filter = new QLineEdit; - edit_filter->clear(); - edit_filter->installEventFilter(key_release_eater); - edit_filter->setClearButtonEnabled(true); - connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::OnTextChanged); - label_filter_result = new QLabel; - button_filter_close = new QToolButton(this); - button_filter_close->setText(QStringLiteral("X")); - button_filter_close->setCursor(Qt::ArrowCursor); - button_filter_close->setStyleSheet( - QStringLiteral("QToolButton{ border: none; padding: 0px; color: " - "#000000; font-weight: bold; background: #F0F0F0; }" - "QToolButton:hover{ border: none; padding: 0px; color: " - "#EEEEEE; font-weight: bold; background: #E81123}")); - connect(button_filter_close, &QToolButton::clicked, parent, &GameList::OnFilterCloseClicked); - layout_filter->setSpacing(10); - layout_filter->addWidget(label_filter); - layout_filter->addWidget(edit_filter); - layout_filter->addWidget(label_filter_result); - layout_filter->addWidget(button_filter_close); - setLayout(layout_filter); - RetranslateUI(); -} - -/** - * Checks if all words separated by spaces are contained in another string - * This offers a word order insensitive search function - * - * @param haystack String that gets checked if it contains all words of the userinput string - * @param userinput String containing all words getting checked - * @return true if the haystack contains all words of userinput - */ -static bool ContainsAllWords(const QString& haystack, const QString& userinput) { - const QStringList userinput_split = userinput.split(QLatin1Char{' '}, Qt::SkipEmptyParts); - - return std::all_of(userinput_split.begin(), userinput_split.end(), - [&haystack](const QString& s) { return haystack.contains(s); }); -} - -// Syncs the expanded state of Game Directories with settings to persist across sessions -void GameList::OnItemExpanded(const QModelIndex& item) { - const auto type = item.data(GameListItem::TypeRole).value(); - const bool is_dir = type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir || - type == GameListItemType::UserNandDir || - type == GameListItemType::SysNandDir; - const bool is_fave = type == GameListItemType::Favorites; - if (!is_dir && !is_fave) { - return; - } - const bool is_expanded = tree_view->isExpanded(item); - if (is_fave) { - UISettings::values.favorites_expanded = is_expanded; - return; - } - const int item_dir_index = item.data(GameListDir::GameDirRole).toInt(); - UISettings::values.game_dirs[item_dir_index].expanded = is_expanded; -} - -// Event in order to filter the gamelist after editing the searchfield -void GameList::OnTextChanged(const QString& new_text) { - QString edit_filter_text = new_text.toLower(); - QStandardItem* folder; - int children_total = 0; - int result_count = 0; - - auto hide = [this](int row, bool hidden, QModelIndex index = QModelIndex()) { - if (m_isTreeMode) { - tree_view->setRowHidden(row, index, hidden); - } else { - list_view->setRowHidden(row, hidden); - } - }; - - // If the searchfield is empty every item is visible - // Otherwise the filter gets applied - - // TODO(crueter) dedupe - if (!m_isTreeMode) { - int row_count = item_model->rowCount(); - - for (int i = 0; i < row_count; ++i) { - QStandardItem* item = item_model->item(i, 0); - if (!item) - continue; - - children_total++; - - const QString file_path = - item->data(GameListItemPath::FullPathRole).toString().toLower(); - const QString file_title = item->data(GameListItemPath::TitleRole).toString().toLower(); - const QString file_name = file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + - QLatin1Char{' '} + file_title; - - if (edit_filter_text.isEmpty() || ContainsAllWords(file_name, edit_filter_text)) { - hide(i, false); - result_count++; - } else { - hide(i, true); - } - } - search_field->setFilterResult(result_count, children_total); - } else if (edit_filter_text.isEmpty()) { - hide(0, UISettings::values.favorited_ids.size() == 0, - item_model->invisibleRootItem()->index()); - for (int i = 1; i < item_model->rowCount() - 1; ++i) { - folder = item_model->item(i, 0); - const QModelIndex folder_index = folder->index(); - const int children_count = folder->rowCount(); - for (int j = 0; j < children_count; ++j) { - ++children_total; - hide(j, false, folder_index); - } - } - search_field->setFilterResult(children_total, children_total); - } else { - hide(0, true, item_model->invisibleRootItem()->index()); - for (int i = 1; i < item_model->rowCount() - 1; ++i) { - folder = item_model->item(i, 0); - const QModelIndex folder_index = folder->index(); - const int children_count = folder->rowCount(); - for (int j = 0; j < children_count; ++j) { - ++children_total; - - const QStandardItem* child = folder->child(j, 0); - - const auto program_id = child->data(GameListItemPath::ProgramIdRole).toULongLong(); - - const QString file_path = - child->data(GameListItemPath::FullPathRole).toString().toLower(); - const QString file_title = - child->data(GameListItemPath::TitleRole).toString().toLower(); - const QString file_program_id = - QStringLiteral("%1").arg(program_id, 16, 16, QLatin1Char{'0'}); - - // Only items which filename in combination with its title contains all words - // that are in the searchfield will be visible in the gamelist - // The search is case insensitive because of toLower() - // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent - // multiple conversions of edit_filter_text for each game in the gamelist - const QString file_name = - file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + - file_title; - if (ContainsAllWords(file_name, edit_filter_text) || - (file_program_id.size() == 16 && file_program_id.contains(edit_filter_text))) { - hide(j, false, folder_index); - ++result_count; - } else { - hide(j, true, folder_index); - } - } - } - search_field->setFilterResult(result_count, children_total); - } -} - -void GameList::OnUpdateThemedIcons() { - for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) { - QStandardItem* child = item_model->invisibleRootItem()->child(i); - - const int icon_size = UISettings::values.folder_icon_size.GetValue(); - - switch (child->data(GameListItem::TypeRole).value()) { - case GameListItemType::SdmcDir: - child->setData( - QIcon::fromTheme(QStringLiteral("sd_card")) - .pixmap(icon_size) - .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); - break; - case GameListItemType::UserNandDir: - child->setData( - QIcon::fromTheme(QStringLiteral("chip")) - .pixmap(icon_size) - .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); - break; - case GameListItemType::SysNandDir: - child->setData( - QIcon::fromTheme(QStringLiteral("chip")) - .pixmap(icon_size) - .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); - break; - case GameListItemType::CustomDir: { - const UISettings::GameDir& game_dir = - UISettings::values.game_dirs[child->data(GameListDir::GameDirRole).toInt()]; - const QString icon_name = QFileInfo::exists(QString::fromStdString(game_dir.path)) - ? QStringLiteral("folder") - : QStringLiteral("bad_folder"); - child->setData( - QIcon::fromTheme(icon_name).pixmap(icon_size).scaled( - icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); - break; - } - case GameListItemType::AddDir: - child->setData( - QIcon::fromTheme(QStringLiteral("list-add")) - .pixmap(icon_size) - .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); - break; - case GameListItemType::Favorites: - child->setData( - QIcon::fromTheme(QStringLiteral("star")) - .pixmap(icon_size) - .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), - Qt::DecorationRole); - break; - default: - break; - } - } -} - -void GameList::OnFilterCloseClicked() { - main_window->filterBarSetChecked(false); -} - GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_, PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_, MainWindow* parent) : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, play_time_manager{play_time_manager_}, system{system_} { - watcher = new QFileSystemWatcher(this); - connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); - - external_watcher = new QFileSystemWatcher(this); - ResetExternalWatcher(); - connect(external_watcher, &QFileSystemWatcher::directoryChanged, this, - &GameList::RefreshExternalContent); this->main_window = parent; layout = new QVBoxLayout; - tree_view = new QTreeView(this); - list_view = new QListView(this); - m_gameCard = new GameCard(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + setLayout(layout); - list_view->setItemDelegate(m_gameCard); + item_model = new GameListModel(vfs, provider, play_time_manager, system, this); + + SetupViews(); + + search_field = new GameListSearchField(this); + layout->addWidget(search_field); controller_navigation = new ControllerNavigation(system.HIDCore(), this); - search_field = new GameListSearchField(this); - item_model = new QStandardItemModel(tree_view); - tree_view->setModel(item_model); - list_view->setModel(item_model); SetupScrollAnimation(); - // tree - tree_view->setAlternatingRowColors(true); - tree_view->setSelectionMode(QHeaderView::SingleSelection); - tree_view->setSelectionBehavior(QHeaderView::SelectRows); - tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); - tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); - tree_view->setSortingEnabled(true); - tree_view->setEditTriggers(QHeaderView::NoEditTriggers); - tree_view->setContextMenuPolicy(Qt::CustomContextMenu); - tree_view->setAttribute(Qt::WA_AcceptTouchEvents, true); - tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }")); - - // list view setup - list_view->setViewMode(QListView::ListMode); - list_view->setResizeMode(QListView::Fixed); - list_view->setUniformItemSizes(true); - list_view->setSelectionMode(QAbstractItemView::SingleSelection); - list_view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); - list_view->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); - - // Forcefully disable scroll bar, prevents thing where game list items - // will start clamping prematurely. - list_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - - list_view->setEditTriggers(QAbstractItemView::NoEditTriggers); - list_view->setContextMenuPolicy(Qt::CustomContextMenu); - list_view->setGridSize(QSize(140, 160)); - m_gameCard->setSize(list_view->gridSize(), 0, 4); - - list_view->setSpacing(10); - list_view->setWordWrap(true); - list_view->setTextElideMode(Qt::ElideRight); - list_view->setFlow(QListView::LeftToRight); - list_view->setWrapping(true); - - item_model->insertColumns(0, COLUMN_COUNT); - RetranslateUI(); - - tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons); - tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); - tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); - item_model->setSortRole(GameListItemPath::SortRole); - - connect(main_window, &MainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons); - connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); - connect(list_view, &QListView::activated, this, &GameList::ValidateEntry); - connect(list_view, &QListView::customContextMenuRequested, this, &GameList::PopupContextMenu); + connect(grid_view, &QListView::activated, this, &GameList::ValidateEntry); + connect(grid_view, &QListView::customContextMenuRequested, this, &GameList::PopupContextMenu); + + connect(carousel_view, &QListView::activated, this, &GameList::ValidateEntry); + connect(carousel_view, &QListView::customContextMenuRequested, this, &GameList::PopupContextMenu); - connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded); - connect(tree_view, &QTreeView::collapsed, this, &GameList::OnItemExpanded); connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent, this, [this](Qt::Key key) { - // Avoid pressing buttons while playing if (system.IsPoweredOn()) { return; } @@ -447,64 +80,144 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid return; } QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); - QCoreApplication::postEvent(m_currentView, event); }); - // We must register all custom types with the Qt Automoc system so that we are able to use - // it with signals/slots. In this case, QList falls under the umbrella of custom types. + connect(item_model, &GameListModel::PopulatingCompleted, this, + &GameList::OnPopulatingCompleted); + + connect(item_model, &GameListModel::ShowList, this, &GameList::ShowList); + connect(item_model, &GameListModel::SaveConfig, this, &GameList::SaveConfig); + connect(item_model, &GameListModel::PopulatingStarted, this, &GameList::OnPopulate); + + // TODO: impl on grid/carousel + connect(tree_view, &GameTree::FilterResultReady, search_field, + [this](int visible, int total) { search_field->setFilterResult(visible, total); }); + qRegisterMetaType>("QList"); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); - - layout->addWidget(tree_view); - layout->addWidget(list_view); - layout->addWidget(search_field); - setLayout(layout); - ResetViewMode(); } +GameList::~GameList() { + UnloadController(); +} + +void GameList::SetupViews() { + tree_view = new GameTree(this); + grid_view = new GameGrid(this); + carousel_view = new GameCarousel(this); + + tree_view->SetModel(item_model); + grid_view->SetModel(item_model); + carousel_view->SetModel(item_model); +} + +QString GameList::GetLastFilterResultItem() const { + return tree_view->GetLastFilterResultItem(); +} + +void GameList::ClearFilter() { + search_field->clear(); +} + +void GameList::SetFilterFocus() { + if (item_model->rowCount() > 0) { + search_field->setFocus(); + } +} + +void GameList::SetFilterVisible(bool visibility) { + search_field->setVisible(visibility); +} + +bool GameList::IsEmpty() const { + return item_model->IsEmpty(); +} + +void GameList::OnPopulate() { + m_currentView->setEnabled(false); + + switch (game_list_mode) { + case Settings::GameListMode::TreeView: + tree_view->UpdateColumnVisibility(item_model); + break; + case Settings::GameListMode::GridView: + grid_view->UpdateIconSize(); + break; + case Settings::GameListMode::CarouselView: + carousel_view->UpdateIconSize(); + break; + } +} + +void GameList::PopulateAsync(QVector& game_dirs) { + item_model->PopulateAsync(game_dirs); +} + +void GameList::SaveInterfaceLayout() { + tree_view->SaveInterfaceLayout(); +} + +void GameList::LoadInterfaceLayout() { + tree_view->LoadInterfaceLayout(); +} + +QStandardItemModel* GameList::GetModel() const { + return item_model; +} + void GameList::UnloadController() { controller_navigation->UnloadController(); } -bool GameList::IsTreeMode() { - return m_isTreeMode; -} - void GameList::ResetViewMode() { - auto& setting = UISettings::values.game_list_mode; + const auto mode = UISettings::values.game_list_mode.GetValue(); + game_list_mode = mode; + + if (m_currentView) + layout->removeWidget(m_currentView); + bool newTreeMode = false; - switch (setting.GetValue()) { + switch (mode) { case Settings::GameListMode::TreeView: m_currentView = tree_view; newTreeMode = true; - tree_view->setVisible(true); - list_view->setVisible(false); break; case Settings::GameListMode::GridView: - m_currentView = list_view; + m_currentView = grid_view; + newTreeMode = false; + + break; + case Settings::GameListMode::CarouselView: + m_currentView = carousel_view; newTreeMode = false; - list_view->setVisible(true); - tree_view->setVisible(false); break; default: - break; + UNREACHABLE(); } + tree_view->setVisible(false); + grid_view->setVisible(false); + carousel_view->setVisible(false); + + tree_view->setEnabled(false); + grid_view->setEnabled(false); + carousel_view->setEnabled(false); + + m_currentView->setVisible(true); + m_currentView->setEnabled(true); + layout->insertWidget(0, m_currentView); + auto view = m_currentView->viewport(); view->installEventFilter(this); - // touch gestures view->grabGesture(Qt::SwipeGesture); view->grabGesture(Qt::PanGesture); - // TODO: touch? QScroller::grabGesture(view, QScroller::LeftMouseButtonGesture); auto scroller = QScroller::scroller(view); @@ -517,50 +230,133 @@ void GameList::ResetViewMode() { if (m_isTreeMode != newTreeMode) { m_isTreeMode = newTreeMode; - - RefreshGameDirectory(); + item_model->SetFlat(!m_isTreeMode); + if (!UISettings::values.game_dirs.empty()) { + item_model->PopulateAsync(UISettings::values.game_dirs); + } } } -GameList::~GameList() { - UnloadController(); +void GameList::OnTextChanged(const QString& new_text) { + const QString edit_filter_text = new_text.toLower(); + + if (m_isTreeMode) { + tree_view->ApplyFilter(edit_filter_text, item_model); + } else { + grid_view->ApplyFilter(edit_filter_text, item_model); + } } -void GameList::SetFilterFocus() { - if (tree_view->model()->rowCount() > 0) { +void GameList::OnFilterCloseClicked() { + main_window->filterBarSetChecked(false); +} + +void GameList::OnPopulatingCompleted(const QStringList& watch_list) { + emit ShowList(!item_model->IsEmpty()); + + // favorites row + if (m_isTreeMode) { + tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), + UISettings::values.favorited_ids.size() == 0); + tree_view->setExpanded(item_model->invisibleRootItem()->child(0)->index(), + UISettings::values.favorites_expanded.GetValue()); + } + + // restore collapsed/expanded flags + if (m_isTreeMode) { + const auto* root = item_model->invisibleRootItem(); + for (int i = 1; i < root->rowCount() - 1; ++i) { + const auto* dir_item = root->child(i); + const auto type = dir_item->data(GameListItem::TypeRole).value(); + if (type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir || + type == GameListItemType::UserNandDir || type == GameListItemType::SysNandDir) { + const int dir_index = dir_item->data(GameListDir::GameDirRole).toInt(); + if (dir_index >= 0 && dir_index < UISettings::values.game_dirs.size()) { + tree_view->setExpanded(dir_item->index(), + UISettings::values.game_dirs[dir_index].expanded); + } + } + } + } + + // Watcher updates + auto* watcher = item_model->GetWatcher(); + auto current_watch_list = watcher->directories(); + + constexpr qsizetype LIMIT_WATCH_DIRECTORIES = 5000; + constexpr int SLICE_SIZE = 25; + + QStringList to_remove, to_add; + + const auto slice = [&](const QStringList& list, std::function callback) { + const int len = (std::min)(list.size(), LIMIT_WATCH_DIRECTORIES); + for (int i = 0; i < len; i += SLICE_SIZE) { + auto chunk = list.mid(i, SLICE_SIZE); + if (!chunk.isEmpty()) { + callback(chunk); + } + QCoreApplication::processEvents(); + } + }; + + // remove any paths not in the new watch list + for (const auto& path : std::as_const(current_watch_list)) { + if (!watch_list.contains(path)) { + to_remove.emplaceBack(path); + } + } + + slice(to_remove, [watcher](const QStringList& chunk) { watcher->removePaths(chunk); }); + + // add any paths not in the old watch list + for (const auto& path : std::as_const(watch_list)) { + if (!current_watch_list.contains(path)) { + to_add.emplaceBack(path); + } + } + + slice(to_add, [watcher](const QStringList& chunk) { watcher->addPaths(chunk); }); + + m_currentView->setEnabled(true); + + int children_total = 0; + for (int i = 1; i < item_model->rowCount() - 1; ++i) { + children_total += item_model->item(i, 0)->rowCount(); + } + + search_field->setFilterResult(children_total, children_total); + if (children_total > 0) { search_field->setFocus(); } + + // TODO: carousel/grid impl. + item_model->sort(tree_view->header()->sortIndicatorSection(), + tree_view->header()->sortIndicatorOrder()); + + emit PopulatingCompleted(); } -void GameList::SetFilterVisible(bool visibility) { - search_field->setVisible(visibility); +void GameList::RefreshGameDirectory() { + item_model->RefreshGameDirectory(); } -void GameList::ClearFilter() { - search_field->clear(); +void GameList::RefreshExternalContent() { + item_model->RefreshExternalContent(); } -void GameList::WorkerEvent() { - current_worker->ProcessEvents(this); -} - -void GameList::AddDirEntry(GameListDir* entry_items) { - if (m_isTreeMode) { - item_model->invisibleRootItem()->appendRow(entry_items); - tree_view->setExpanded( - entry_items->index(), - UISettings::values.game_dirs[entry_items->data(GameListDir::GameDirRole).toInt()] - .expanded); +void GameList::UpdateIconSizes() { + switch (game_list_mode) { + case Settings::GameListMode::GridView: + grid_view->UpdateIconSize(); + break; + case Settings::GameListMode::CarouselView: + carousel_view->UpdateIconSize(); + break; + case Settings::GameListMode::TreeView: + break; } } -void GameList::AddEntry(const QList& entry_items, GameListDir* parent) { - if (!m_isTreeMode) - item_model->invisibleRootItem()->appendRow(entry_items); - else - parent->appendRow(entry_items); -} - void GameList::ValidateEntry(const QModelIndex& item) { const auto selected = item.sibling(item.row(), 0); @@ -584,7 +380,6 @@ void GameList::ValidateEntry(const QModelIndex& item) { const auto title_id = selected.data(GameListItemPath::ProgramIdRole).toULongLong(); - // Users usually want to run a different game after closing one search_field->clear(); emit GameChosen(file_path, title_id); break; @@ -597,82 +392,19 @@ void GameList::ValidateEntry(const QModelIndex& item) { } } -bool GameList::IsEmpty() const { - for (int i = 0; i < item_model->rowCount(); i++) { - const QStandardItem* child = item_model->invisibleRootItem()->child(i); - const auto type = static_cast(child->type()); +void GameList::ToggleFavorite(u64 program_id) { + item_model->ToggleFavorite(program_id); - if (!child->hasChildren() && - (type == GameListItemType::SdmcDir || type == GameListItemType::UserNandDir || - type == GameListItemType::SysNandDir)) { - item_model->invisibleRootItem()->removeRow(child->row()); - i--; - } - } - - return !item_model->invisibleRootItem()->hasChildren(); -} - -void GameList::DonePopulating(const QStringList& watch_list) { - emit ShowList(!IsEmpty()); - - // Add favorites row - if (m_isTreeMode) { - item_model->invisibleRootItem()->appendRow(new GameListAddDir()); - - item_model->invisibleRootItem()->insertRow(0, new GameListFavorites()); + if (UISettings::values.favorited_ids.contains(program_id)) { tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), - UISettings::values.favorited_ids.size() == 0); - tree_view->setExpanded(item_model->invisibleRootItem()->child(0)->index(), - UISettings::values.favorites_expanded.GetValue()); - for (const auto id : std::as_const(UISettings::values.favorited_ids)) { - AddFavorite(id); + !search_field->filterText().isEmpty()); + item_model->sort(tree_view->header()->sortIndicatorSection(), + tree_view->header()->sortIndicatorOrder()); + } else { + if (UISettings::values.favorited_ids.size() == 0) { + tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), true); } } - - // Clear out the old directories to watch for changes and add the new ones - auto watch_dirs = watcher->directories(); - if (!watch_dirs.isEmpty()) { - watcher->removePaths(watch_dirs); - } - // Workaround: Add the watch paths in chunks to allow the gui to refresh - // This prevents the UI from stalling when a large number of watch paths are added - // Also artificially caps the watcher to a certain number of directories - constexpr int LIMIT_WATCH_DIRECTORIES = 5000; - constexpr int SLICE_SIZE = 25; - int len = (std::min)(static_cast(watch_list.size()), LIMIT_WATCH_DIRECTORIES); - - // Block signals to prevent the watcher from triggering a refresh while we are adding paths. - // This fixes a refresh loop on macOS. -#ifdef __APPLE__ - const bool old_signals_blocked = watcher->blockSignals(true); -#endif - - for (int i = 0; i < len; i += SLICE_SIZE) { - auto chunk = watch_list.mid(i, SLICE_SIZE); - if (!chunk.isEmpty()) { - watcher->addPaths(chunk); - } - QCoreApplication::processEvents(); - } - -#ifdef __APPLE__ - watcher->blockSignals(old_signals_blocked); -#endif - m_currentView->setEnabled(true); - - int children_total = 0; - for (int i = 1; i < item_model->rowCount() - 1; ++i) { - children_total += item_model->item(i, 0)->rowCount(); - } - search_field->setFilterResult(children_total, children_total); - if (children_total > 0) { - search_field->setFocus(); - } - item_model->sort(tree_view->header()->sortIndicatorSection(), - tree_view->header()->sortIndicatorOrder()); - - emit PopulatingCompleted(); } void GameList::PopupContextMenu(const QPoint& menu_location) { @@ -746,8 +478,6 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC")); QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity")); QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); - QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); -// TODO: Implement shortcut creation for macOS #if !defined(__APPLE__) QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut")); QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop")); @@ -769,8 +499,6 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri remove_vk_shader_cache->setVisible(program_id != 0); remove_shader_cache->setVisible(program_id != 0); remove_all_content->setVisible(program_id != 0); - auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); - navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0); connect(favorite, &QAction::triggered, this, [this, program_id]() { ToggleFavorite(program_id); }); @@ -830,10 +558,6 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri [this, path]() { emit VerifyIntegrityRequested(path); }); connect(copy_tid, &QAction::triggered, this, [this, program_id]() { emit CopyTIDRequested(program_id); }); - connect(navigate_to_gamedb_entry, &QAction::triggered, this, [this, program_id]() { - emit NavigateToGamedbEntryRequested(program_id, compatibility_list); - }); -// TODO: Implement shortcut creation for macOS #if !defined(__APPLE__) connect(create_desktop_shortcut, &QAction::triggered, this, [this, program_id, path]() { emit CreateShortcut(program_id, path, QtCommon::Game::ShortcutTarget::Desktop); @@ -885,32 +609,26 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) { connect(move_up, &QAction::triggered, this, [this, selected, row, game_dir_index] { const int other_index = selected.sibling(row - 1, 0).data(GameListDir::GameDirRole).toInt(); - // swap the items in the settings std::swap(UISettings::values.game_dirs[game_dir_index], UISettings::values.game_dirs[other_index]); - // swap the indexes held by the QVariants item_model->setData(selected, QVariant(other_index), GameListDir::GameDirRole); item_model->setData(selected.sibling(row - 1, 0), QVariant(game_dir_index), GameListDir::GameDirRole); - // move the treeview items - QList item = item_model->takeRow(row); - item_model->invisibleRootItem()->insertRow(row - 1, item); + QList items = item_model->takeRow(row); + item_model->invisibleRootItem()->insertRow(row - 1, items); tree_view->setExpanded(selected.sibling(row - 1, 0), UISettings::values.game_dirs[other_index].expanded); }); connect(move_down, &QAction::triggered, this, [this, selected, row, game_dir_index] { const int other_index = selected.sibling(row + 1, 0).data(GameListDir::GameDirRole).toInt(); - // swap the items in the settings std::swap(UISettings::values.game_dirs[game_dir_index], UISettings::values.game_dirs[other_index]); - // swap the indexes held by the QVariants item_model->setData(selected, QVariant(other_index), GameListDir::GameDirRole); item_model->setData(selected.sibling(row + 1, 0), QVariant(game_dir_index), GameListDir::GameDirRole); - // move the treeview items - const QList item = item_model->takeRow(row); - item_model->invisibleRootItem()->insertRow(row + 1, item); + const QList items = item_model->takeRow(row); + item_model->invisibleRootItem()->insertRow(row + 1, items); tree_view->setExpanded(selected.sibling(row + 1, 0), UISettings::values.game_dirs[other_index].expanded); }); @@ -925,58 +643,13 @@ void GameList::AddFavoritesPopup(QMenu& context_menu) { QAction* clear = context_menu.addAction(tr("Clear")); connect(clear, &QAction::triggered, this, [this] { - for (const auto id : std::as_const(UISettings::values.favorited_ids)) { - RemoveFavorite(id); - } UISettings::values.favorited_ids.clear(); + item_model->invisibleRootItem()->child(0)->removeRows( + 0, item_model->invisibleRootItem()->child(0)->rowCount()); tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), true); }); } -void GameList::LoadCompatibilityList() { - QFile compat_list{QStringLiteral(":compatibility_list/compatibility_list.json")}; - - if (!compat_list.open(QFile::ReadOnly | QFile::Text)) { - LOG_ERROR(Frontend, "Unable to open game compatibility list"); - return; - } - - if (compat_list.size() == 0) { - LOG_WARNING(Frontend, "Game compatibility list is empty"); - return; - } - - const QByteArray content = compat_list.readAll(); - if (content.isEmpty()) { - LOG_ERROR(Frontend, "Unable to completely read game compatibility list"); - return; - } - - const QJsonDocument json = QJsonDocument::fromJson(content); - const QJsonArray arr = json.array(); - - for (const QJsonValue& value : arr) { - const QJsonObject game = value.toObject(); - const QString compatibility_key = QStringLiteral("compatibility"); - - if (!game.contains(compatibility_key) || !game[compatibility_key].isDouble()) { - continue; - } - - const int compatibility = game[compatibility_key].toInt(); - const QString directory = game[QStringLiteral("directory")].toString(); - const QJsonArray ids = game[QStringLiteral("releases")].toArray(); - - for (const QJsonValue& id_ref : ids) { - const QJsonObject id_object = id_ref.toObject(); - const QString id = id_object[QStringLiteral("id")].toString(); - - compatibility_list.emplace(id.toUpper().toStdString(), - std::make_pair(QString::number(compatibility), directory)); - } - } -} - void GameList::changeEvent(QEvent* event) { if (event->type() == QEvent::LanguageChange) { RetranslateUI(); @@ -986,258 +659,11 @@ void GameList::changeEvent(QEvent* event) { } void GameList::RetranslateUI() { - item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name")); - item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility")); - item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); - item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); - item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); - item_model->setHeaderData(COLUMN_PLAY_TIME, Qt::Horizontal, tr("Play time")); -} - -void GameListSearchField::changeEvent(QEvent* event) { - if (event->type() == QEvent::LanguageChange) { - RetranslateUI(); - } - - QWidget::changeEvent(event); -} - -void GameListSearchField::RetranslateUI() { - label_filter->setText(tr("Filter:")); - edit_filter->setPlaceholderText(tr("Enter pattern to filter")); -} - -QStandardItemModel* GameList::GetModel() const { - return item_model; -} - -void GameList::UpdateIconSize() { - // Update sizes and stuff for the list view - const u32 icon_size = UISettings::values.game_icon_size.GetValue(); - - int heightMargin = 0; - int widthMargin = 80; - - if (UISettings::values.show_game_name) { - // the scaling on the card is kinda abysmal. - // TODO(crueter): refactor - switch (icon_size) { - case 128: - heightMargin = 65; - break; - case 0: - widthMargin = 120; - heightMargin = 120; - break; - case 64: - heightMargin = 77; - break; - case 32: - case 256: - heightMargin = 81; - break; - } - } else { - widthMargin = 24; - heightMargin = 24; - } - - // "auto" resize // - const int view_width = list_view->viewport()->width(); - - // Tiny space padding to prevent the list view from forcing its own resize operation. - const double spacing = 0.01; - const int min_item_width = icon_size + widthMargin; - - // And now stretch it a bit to fill out remaining space. - // Not perfect but works well enough for now - int columns = std::max(1, (view_width - 16) / min_item_width); - int stretched_width = ((view_width) - (spacing * (columns - 1))) / columns; - - // only updates things if grid size is changed - QSize grid_size(stretched_width, icon_size + heightMargin); - if (list_view->gridSize() != grid_size) { - list_view->setUpdatesEnabled(false); - - list_view->setGridSize(grid_size); - m_gameCard->setSize(grid_size, stretched_width - min_item_width, columns); - - list_view->setUpdatesEnabled(true); - } -} - -void GameList::PopulateAsync(QVector& game_dirs) { - m_currentView->setEnabled(false); - - // Update the columns in case UISettings has changed - tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons); - tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); - tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types); - tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); - tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); - - if (!m_isTreeMode) - UpdateIconSize(); - - // Cancel any existing worker. - current_worker.reset(); - - // Delete any rows that might already exist if we're repopulating - item_model->removeRows(0, item_model->rowCount()); - search_field->clear(); - - current_worker = std::make_unique(vfs, provider, game_dirs, compatibility_list, - play_time_manager, system); - - // Get events from the worker as data becomes available - connect(current_worker.get(), &GameListWorker::DataAvailable, this, &GameList::WorkerEvent, - Qt::QueuedConnection); - - QThreadPool::globalInstance()->start(current_worker.get()); -} - -void GameList::SaveInterfaceLayout() { - UISettings::values.gamelist_header_state = tree_view->header()->saveState(); -} - -void GameList::LoadInterfaceLayout() { - auto* header = tree_view->header(); - - if (header->restoreState(UISettings::values.gamelist_header_state)) - return; - - // We are using the name column to display icons and titles - // so make it as large as possible as default. - - // TODO(crueter): width() is not initialized yet, so use a sane default value - header->resizeSection(COLUMN_NAME, 840); -} - -const QStringList GameList::supported_file_extensions = { - QStringLiteral("nso"), QStringLiteral("nro"), QStringLiteral("nca"), - QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")}; - -void GameList::RefreshGameDirectory() { - // Reset the externals watcher whenever the game list is reloaded, - // primarily ensures that new titles and external dirs are caught. - ResetExternalWatcher(); - - if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) { - LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); - QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs); - PopulateAsync(UISettings::values.game_dirs); - } -} - -void GameList::RefreshExternalContent() { - // TODO: Explore the possibility of only resetting the metadata cache for that specific game. - if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) { - LOG_INFO(Frontend, "External content directory changed. Clearing metadata cache."); - QtCommon::Game::ResetMetadata(false); - QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs); - PopulateAsync(UISettings::values.game_dirs); - } -} - -void GameList::ResetExternalWatcher() { - auto watch_dirs = external_watcher->directories(); - if (!watch_dirs.isEmpty()) { - external_watcher->removePaths(watch_dirs); - } - - for (const std::string& dir : Settings::values.external_content_dirs) { - external_watcher->addPath(QString::fromStdString(dir)); - } -} - -void GameList::ToggleFavorite(u64 program_id) { - if (!UISettings::values.favorited_ids.contains(program_id)) { - tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), - !search_field->filterText().isEmpty()); - UISettings::values.favorited_ids.append(program_id); - AddFavorite(program_id); - item_model->sort(tree_view->header()->sortIndicatorSection(), - tree_view->header()->sortIndicatorOrder()); - } else { - UISettings::values.favorited_ids.removeOne(program_id); - RemoveFavorite(program_id); - if (UISettings::values.favorited_ids.size() == 0) { - tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), true); - } - } - emit SaveConfig(); -} - -void GameList::AddFavorite(u64 program_id) { - auto* favorites_row = item_model->item(0); - - for (int i = 1; i < item_model->rowCount() - 1; i++) { - const auto* folder = item_model->item(i); - for (int j = 0; j < folder->rowCount(); j++) { - if (folder->child(j)->data(GameListItemPath::ProgramIdRole).toULongLong() == - program_id) { - QList list; - for (int k = 0; k < COLUMN_COUNT; k++) { - list.append(folder->child(j, k)->clone()); - } - list[0]->setData(folder->child(j)->data(GameListItem::SortRole), - GameListItem::SortRole); - list[0]->setText(folder->child(j)->data(Qt::DisplayRole).toString()); - - favorites_row->appendRow(list); - return; - } - } - } -} - -void GameList::RemoveFavorite(u64 program_id) { - auto* favorites_row = item_model->item(0); - - for (int i = 0; i < favorites_row->rowCount(); i++) { - const auto* game = favorites_row->child(i); - if (game->data(GameListItemPath::ProgramIdRole).toULongLong() == program_id) { - favorites_row->removeRow(i); - return; - } - } -} - -GameListPlaceholder::GameListPlaceholder(MainWindow* parent) : QWidget{parent} { - connect(parent, &MainWindow::UpdateThemedIcons, this, - &GameListPlaceholder::onUpdateThemedIcons); - - layout = new QVBoxLayout; - image = new QLabel; - text = new QLabel; - layout->setAlignment(Qt::AlignCenter); - image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); - - RetranslateUI(); - QFont font = text->font(); - font.setPointSize(20); - text->setFont(font); - text->setAlignment(Qt::AlignHCenter); - image->setAlignment(Qt::AlignHCenter); - - layout->addWidget(image); - layout->addWidget(text); - setLayout(layout); -} - -GameListPlaceholder::~GameListPlaceholder() = default; - -void GameListPlaceholder::onUpdateThemedIcons() { - image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); -} - -void GameListPlaceholder::mouseDoubleClickEvent(QMouseEvent* event) { - emit GameListPlaceholder::AddDirectory(); + item_model->RetranslateUI(); } void GameList::SetupScrollAnimation() { auto setup = [this](QVariantAnimation* anim, QScrollBar* bar) { - // animation handles moving the bar instead of Qt's built in crap anim->setEasingCurve(QEasingCurve::OutCubic); anim->setDuration(200); connect(anim, &QVariantAnimation::valueChanged, this, @@ -1250,8 +676,8 @@ void GameList::SetupScrollAnimation() { setup(vertical_scroll, tree_view->verticalScrollBar()); setup(horizontal_scroll, tree_view->horizontalScrollBar()); - setup(vertical_scroll, list_view->verticalScrollBar()); - setup(horizontal_scroll, list_view->horizontalScrollBar()); + setup(vertical_scroll, grid_view->verticalScrollBar()); + setup(horizontal_scroll, grid_view->horizontalScrollBar()); } bool GameList::eventFilter(QObject* obj, QEvent* event) { @@ -1263,13 +689,11 @@ bool GameList::eventFilter(QObject* obj, QEvent* event) { int deltaX = wheelEvent->angleDelta().x(); int deltaY = wheelEvent->angleDelta().y(); - // if shift is held do a horizontal scroll if (horizontal && deltaY != 0 && deltaX == 0) { deltaX = deltaY; deltaY = 0; } - // TODO(crueter): dedup this if (deltaY != 0) { if (vertical_scroll->state() == QAbstractAnimation::Stopped) vertical_scroll_target = m_currentView->verticalScrollBar()->value(); @@ -1304,7 +728,6 @@ bool GameList::eventFilter(QObject* obj, QEvent* event) { if (obj == m_currentView->viewport() && event->type() == QEvent::MouseButtonPress) { QMouseEvent* mouseEvent = static_cast(event); - // if the user clicks outside of the list, deselect the current item. QModelIndex index = m_currentView->indexAt(mouseEvent->pos()); if (!index.isValid()) { m_currentView->selectionModel()->clearSelection(); @@ -1312,14 +735,44 @@ bool GameList::eventFilter(QObject* obj, QEvent* event) { } } - if (obj == list_view->viewport() && event->type() == QEvent::Resize) { - UpdateIconSize(); + if (obj == grid_view->viewport() && event->type() == QEvent::Resize) { + grid_view->UpdateIconSize(); + return true; + } + + if (obj == carousel_view->viewport() && event->type() == QEvent::Resize) { + carousel_view->UpdateIconSize(); return true; } return QWidget::eventFilter(obj, event); } +GameListPlaceholder::GameListPlaceholder(MainWindow* parent) : QWidget{parent} { + layout = new QVBoxLayout; + image = new QLabel; + text = new QLabel; + layout->setAlignment(Qt::AlignCenter); + image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200)); + + RetranslateUI(); + QFont font = text->font(); + font.setPointSize(20); + text->setFont(font); + text->setAlignment(Qt::AlignHCenter); + image->setAlignment(Qt::AlignHCenter); + + layout->addWidget(image); + layout->addWidget(text); + setLayout(layout); +} + +GameListPlaceholder::~GameListPlaceholder() = default; + +void GameListPlaceholder::mouseDoubleClickEvent(QMouseEvent* event) { + emit GameListPlaceholder::AddDirectory(); +} + void GameListPlaceholder::changeEvent(QEvent* event) { if (event->type() == QEvent::LanguageChange) { RetranslateUI(); diff --git a/src/yuzu/game/game_list.h b/src/yuzu/game/game_list.h index 42efcbdc3b..1540dd9cf0 100644 --- a/src/yuzu/game/game_list.h +++ b/src/yuzu/game/game_list.h @@ -6,43 +6,44 @@ #pragma once -#include #include #include #include #include #include #include -#include #include #include #include -#include #include "common/common_types.h" #include "core/core.h" #include "frontend_common/play_time_manager.h" #include "qt_common/config/uisettings.h" +#include "qt_common/game_list/model.h" #include "qt_common/util/game.h" -#include "yuzu/compatibility_list.h" class QVariantAnimation; -class QListView; - class GameCard; -namespace Core { -class System; -} - +class GameListModel; +class GameTree; +class GameGrid; +class GameListSearchField; class ControllerNavigation; class GameListWorker; -class GameListSearchField; class GameListDir; class MainWindow; enum class AmLaunchType; enum class StartGameType; +class GameCarousel; + +class QAbstractItemView; +namespace Core { +class System; +} + namespace FileSys { class ManualContentProvider; class VfsFilesystem; @@ -62,16 +63,6 @@ class GameList : public QWidget { Q_OBJECT public: - enum { - COLUMN_NAME, - COLUMN_FILE_TYPE, - COLUMN_SIZE, - COLUMN_PLAY_TIME, - COLUMN_ADD_ONS, - COLUMN_COMPATIBILITY, - COLUMN_COUNT, // Number of columns - }; - explicit GameList(std::shared_ptr vfs_, FileSys::ManualContentProvider* provider_, PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_, @@ -84,7 +75,6 @@ public: void SetFilterVisible(bool visibility); bool IsEmpty() const; - void LoadCompatibilityList(); void PopulateAsync(QVector& game_dirs); void SaveInterfaceLayout(); @@ -95,15 +85,13 @@ public: /// Disables events from the emulated controller void UnloadController(); - static const QStringList supported_file_extensions; - - bool IsTreeMode(); void ResetViewMode(); public slots: void RefreshGameDirectory(); void RefreshExternalContent(); - void ResetExternalWatcher(); + void UpdateIconSizes(); + void OnPopulate(); signals: void BootGame(const QString& game_path, StartGameType type); @@ -121,8 +109,6 @@ signals: void CopyTIDRequested(u64 program_id); void CreateShortcut(u64 program_id, const std::string& game_path, const QtCommon::Game::ShortcutTarget target); - void NavigateToGamedbEntryRequested(u64 program_id, - const CompatibilityList& compatibility_list); void OpenPerGameGeneralRequested(const std::string& file); void LinkToRyujinxRequested(const u64& program_id); void OpenDirectory(const QString& directory); @@ -132,27 +118,19 @@ signals: void SaveConfig(); private slots: - void OnItemExpanded(const QModelIndex& item); void OnTextChanged(const QString& new_text); void OnFilterCloseClicked(); - void OnUpdateThemedIcons(); - - void UpdateIconSize(); + void OnPopulatingCompleted(const QStringList& watch_list); private: - friend class GameListWorker; - void WorkerEvent(); + void SetupViews(); + void SetupScrollAnimation(); + bool eventFilter(QObject* obj, QEvent* event) override; + void changeEvent(QEvent*) override; - void AddDirEntry(GameListDir* entry_items); - void AddEntry(const QList& entry_items, GameListDir* parent); - void DonePopulating(const QStringList& watch_list); - -private: void ValidateEntry(const QModelIndex& item); void ToggleFavorite(u64 program_id); - void AddFavorite(u64 program_id); - void RemoveFavorite(u64 program_id); void PopupContextMenu(const QPoint& menu_location); void AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path); @@ -160,41 +138,34 @@ private: void AddPermDirPopup(QMenu& context_menu, QModelIndex selected); void AddFavoritesPopup(QMenu& context_menu); - void changeEvent(QEvent*) override; void RetranslateUI(); + friend class GameListSearchField; + std::shared_ptr vfs; FileSys::ManualContentProvider* provider; - GameListSearchField* search_field; + GameListSearchField* search_field = nullptr; MainWindow* main_window = nullptr; QVBoxLayout* layout = nullptr; - QTreeView* tree_view = nullptr; - QListView* list_view = nullptr; - GameCard* m_gameCard = nullptr; + GameTree* tree_view = nullptr; + GameGrid* grid_view = nullptr; + GameCarousel* carousel_view = nullptr; + GameListModel* item_model = nullptr; + Settings::GameListMode game_list_mode; - QStandardItemModel* item_model = nullptr; - std::unique_ptr current_worker; - QFileSystemWatcher* watcher = nullptr; - QFileSystemWatcher* external_watcher = nullptr; ControllerNavigation* controller_navigation = nullptr; - CompatibilityList compatibility_list; QVariantAnimation* vertical_scroll = nullptr; QVariantAnimation* horizontal_scroll = nullptr; int vertical_scroll_target = 0; int horizontal_scroll_target = 0; - void SetupScrollAnimation(); - bool eventFilter(QObject* obj, QEvent* event) override; - - friend class GameListSearchField; - const PlayTime::PlayTimeManager& play_time_manager; Core::System& system; bool m_isTreeMode = true; - QAbstractItemView* m_currentView = tree_view; + QAbstractItemView* m_currentView = nullptr; }; class GameListPlaceholder : public QWidget { @@ -206,9 +177,6 @@ public: signals: void AddDirectory(); -private slots: - void onUpdateThemedIcons(); - protected: void mouseDoubleClickEvent(QMouseEvent* event) override; diff --git a/src/yuzu/game/game_tree.cpp b/src/yuzu/game/game_tree.cpp new file mode 100644 index 0000000000..d62141d8da --- /dev/null +++ b/src/yuzu/game/game_tree.cpp @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include + +#include "qt_common/config/uisettings.h" +#include "qt_common/game_list/game_list_p.h" +#include "qt_common/game_list/model.h" +#include "yuzu/game/common.h" +#include "yuzu/game/game_tree.h" + +GameTree::GameTree(QWidget* parent) : QTreeView{parent} { + setAlternatingRowColors(true); + setSelectionMode(QHeaderView::SingleSelection); + setSelectionBehavior(QHeaderView::SelectRows); + setVerticalScrollMode(QHeaderView::ScrollPerPixel); + setHorizontalScrollMode(QHeaderView::ScrollPerPixel); + setSortingEnabled(true); + setEditTriggers(QHeaderView::NoEditTriggers); + setContextMenuPolicy(Qt::CustomContextMenu); + setAttribute(Qt::WA_AcceptTouchEvents, true); + setStyleSheet(QStringLiteral("QTreeView{ border: none; }")); + + connect(this, &QTreeView::expanded, this, &GameTree::OnItemExpanded); + connect(this, &QTreeView::collapsed, this, &GameTree::OnItemExpanded); +} + +void GameTree::SetModel(GameListModel* model) { + QTreeView::setModel(model); + LoadInterfaceLayout(); + UpdateColumnVisibility(model); +} + +void GameTree::OnItemExpanded(const QModelIndex& item) { + const auto type = item.data(GameListItem::TypeRole).value(); + const bool is_dir = type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir || + type == GameListItemType::UserNandDir || + type == GameListItemType::SysNandDir; + const bool is_fave = type == GameListItemType::Favorites; + if (!is_dir && !is_fave) { + return; + } + const bool is_expanded = isExpanded(item); + if (is_fave) { + UISettings::values.favorites_expanded = is_expanded; + return; + } + const int item_dir_index = item.data(GameListDir::GameDirRole).toInt(); + UISettings::values.game_dirs[item_dir_index].expanded = is_expanded; +} + +void GameTree::SaveInterfaceLayout() { + UISettings::values.gamelist_header_state = header()->saveState(); +} + +void GameTree::LoadInterfaceLayout() { + auto* hdr = header(); + + if (hdr->restoreState(UISettings::values.gamelist_header_state)) + return; + + hdr->resizeSection(GameListModel::COLUMN_NAME, 840); +} + +void GameTree::UpdateColumnVisibility(GameListModel* model) { + Q_UNUSED(model) + setColumnHidden(GameListModel::COLUMN_ADD_ONS, !UISettings::values.show_add_ons); + setColumnHidden(GameListModel::COLUMN_FILE_TYPE, !UISettings::values.show_types); + setColumnHidden(GameListModel::COLUMN_SIZE, !UISettings::values.show_size); + setColumnHidden(GameListModel::COLUMN_PLAY_TIME, !UISettings::values.show_play_time); +} + +QString GameTree::GetLastFilterResultItem() const { + QString file_path; + + auto* model = qobject_cast(QTreeView::model()); + if (!model) + return {}; + + for (int i = 1; i < model->rowCount() - 1; ++i) { + const QStandardItem* folder = model->item(i, 0); + const QModelIndex folder_index = folder->index(); + const int children_count = folder->rowCount(); + + for (int j = 0; j < children_count; ++j) { + if (isRowHidden(j, folder_index)) { + continue; + } + + const QStandardItem* child = folder->child(j, 0); + file_path = child->data(GameListItemPath::FullPathRole).toString(); + } + } + + return file_path; +} + +int GameTree::FilterClosedResultCount(GameListModel* model) { + int children_total = 0; + + auto hide_favorites_row = UISettings::values.favorited_ids.size() == 0; + setRowHidden(0, model->invisibleRootItem()->index(), hide_favorites_row); + + for (int i = 1; i < model->rowCount() - 1; ++i) { + auto* folder = model->item(i, 0); + const QModelIndex folder_index = folder->index(); + const int children_count = folder->rowCount(); + for (int j = 0; j < children_count; ++j) { + ++children_total; + setRowHidden(j, folder_index, false); + } + } + + return children_total; +} + +void GameTree::ApplyFilter(const QString& edit_filter_text, GameListModel* model) { + int children_total = 0; + int result_count = 0; + + if (edit_filter_text.isEmpty()) { + children_total = FilterClosedResultCount(model); + emit FilterResultReady(children_total, children_total); + return; + } + + setRowHidden(0, model->invisibleRootItem()->index(), true); + + for (int i = 1; i < model->rowCount() - 1; ++i) { + auto* folder = model->item(i, 0); + const QModelIndex folder_index = folder->index(); + const int children_count = folder->rowCount(); + + for (int j = 0; j < children_count; ++j) { + ++children_total; + + const QStandardItem* child = folder->child(j, 0); + + if (Yuzu::FilterMatches(edit_filter_text, child)) { + setRowHidden(j, folder_index, false); + ++result_count; + } else { + setRowHidden(j, folder_index, true); + } + } + } + + emit FilterResultReady(result_count, children_total); +} diff --git a/src/yuzu/game/game_tree.h b/src/yuzu/game/game_tree.h new file mode 100644 index 0000000000..e4417803aa --- /dev/null +++ b/src/yuzu/game/game_tree.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +class GameListModel; + +class GameTree : public QTreeView { + Q_OBJECT + +public: + explicit GameTree(QWidget* parent = nullptr); + + void SetModel(GameListModel* model); + + QString GetLastFilterResultItem() const; + int FilterClosedResultCount(GameListModel* model); + void ApplyFilter(const QString& edit_filter_text, GameListModel* model); + + void SaveInterfaceLayout(); + void LoadInterfaceLayout(); + + void UpdateColumnVisibility(GameListModel* model); + +signals: + void ItemExpandedChanged(const QModelIndex& item); + void FilterResultReady(int visible, int total); + +private slots: + void OnItemExpanded(const QModelIndex& item); +}; diff --git a/src/yuzu/game/search_field.cpp b/src/yuzu/game/search_field.cpp new file mode 100644 index 0000000000..c2d4893742 --- /dev/null +++ b/src/yuzu/game/search_field.cpp @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "game/game_list.h" +#include "game/search_field.h" + +#include +#include + +// TODO: Remove GameList dependence? +GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist_, QObject* parent) + : QObject(parent), gamelist{gamelist_} {} + +// EventFilter in order to process systemkeys while editing the searchfield +bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* event) { + // If it isn't a KeyRelease event then continue with standard event processing + if (event->type() != QEvent::KeyRelease) + return QObject::eventFilter(obj, event); + + QKeyEvent* keyEvent = static_cast(event); + QString edit_filter_text = gamelist->search_field->edit_filter->text().toLower(); + + // If the searchfield's text hasn't changed special function keys get checked + // If no function key changes the searchfield's text the filter doesn't need to get reloaded + if (edit_filter_text == edit_filter_text_old) { + switch (keyEvent->key()) { + // Escape: Resets the searchfield + case Qt::Key_Escape: { + if (edit_filter_text_old.isEmpty()) { + return QObject::eventFilter(obj, event); + } else { + gamelist->search_field->edit_filter->clear(); + edit_filter_text.clear(); + } + break; + } + // Return and Enter + // If the enter key gets pressed first checks how many and which entry is visible + // If there is only one result launch this game + case Qt::Key_Return: + case Qt::Key_Enter: { + if (gamelist->search_field->visible == 1) { + const QString file_path = gamelist->GetLastFilterResultItem(); + + // To avoid loading error dialog loops while confirming them using enter + // Also users usually want to run a different game after closing one + gamelist->search_field->edit_filter->clear(); + edit_filter_text.clear(); + emit gamelist->GameChosen(file_path); + } else { + return QObject::eventFilter(obj, event); + } + break; + } + default: + return QObject::eventFilter(obj, event); + } + } + edit_filter_text_old = edit_filter_text; + return QObject::eventFilter(obj, event); +} + +void GameListSearchField::setFilterResult(int visible_, int total_) { + visible = visible_; + total = total_; + + label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible)); +} + +QString GameListSearchField::filterText() const { + return edit_filter->text(); +} + +void GameListSearchField::clear() { + edit_filter->clear(); +} + +void GameListSearchField::setFocus() { + if (edit_filter->isVisible()) { + edit_filter->setFocus(); + } +} + +GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} { + auto* const key_release_eater = new KeyReleaseEater(parent, this); + layout_filter = new QHBoxLayout; + layout_filter->setContentsMargins(8, 8, 8, 8); + label_filter = new QLabel; + edit_filter = new QLineEdit; + edit_filter->clear(); + edit_filter->installEventFilter(key_release_eater); + edit_filter->setClearButtonEnabled(true); + connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::OnTextChanged); + label_filter_result = new QLabel; + button_filter_close = new QToolButton(this); + button_filter_close->setText(QStringLiteral("X")); + button_filter_close->setCursor(Qt::ArrowCursor); + button_filter_close->setStyleSheet( + QStringLiteral("QToolButton{ border: none; padding: 0px; color: " + "#000000; font-weight: bold; background: #F0F0F0; }" + "QToolButton:hover{ border: none; padding: 0px; color: " + "#EEEEEE; font-weight: bold; background: #E81123}")); + connect(button_filter_close, &QToolButton::clicked, parent, &GameList::OnFilterCloseClicked); + layout_filter->setSpacing(10); + layout_filter->addWidget(label_filter); + layout_filter->addWidget(edit_filter); + layout_filter->addWidget(label_filter_result); + layout_filter->addWidget(button_filter_close); + setLayout(layout_filter); + RetranslateUI(); +} + +void GameListSearchField::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + } + + QWidget::changeEvent(event); +} + +void GameListSearchField::RetranslateUI() { + label_filter->setText(tr("Filter:")); + edit_filter->setPlaceholderText(tr("Enter pattern to filter")); +} diff --git a/src/yuzu/game/search_field.h b/src/yuzu/game/search_field.h new file mode 100644 index 0000000000..18f97300f7 --- /dev/null +++ b/src/yuzu/game/search_field.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +class GameList; +class QHBoxLayout; +class QTreeView; +class QLabel; +class QLineEdit; +class QToolButton; + +class GameListSearchField : public QWidget { + Q_OBJECT + +public: + explicit GameListSearchField(GameList* parent = nullptr); + + QString filterText() const; + void setFilterResult(int visible_, int total_); + + void clear(); + void setFocus(); + +private: + void changeEvent(QEvent*) override; + void RetranslateUI(); + + class KeyReleaseEater : public QObject { + public: + explicit KeyReleaseEater(GameList* gamelist_, QObject* parent = nullptr); + + private: + GameList* gamelist = nullptr; + QString edit_filter_text_old; + + protected: + // EventFilter in order to process systemkeys while editing the searchfield + bool eventFilter(QObject* obj, QEvent* event) override; + }; + int visible; + int total; + + QHBoxLayout* layout_filter = nullptr; + QTreeView* tree_view = nullptr; + QLabel* label_filter = nullptr; + QLineEdit* edit_filter = nullptr; + QLabel* label_filter_result = nullptr; + QToolButton* button_filter_close = nullptr; +}; diff --git a/src/yuzu/loading_screen.cpp b/src/yuzu/loading_screen.cpp index d67667a91d..495e5447ee 100644 --- a/src/yuzu/loading_screen.cpp +++ b/src/yuzu/loading_screen.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include "common/container/unordered_map.h" #include "core/frontend/framebuffer_layout.h" #include "core/loader/loader.h" #include "ui_loading_screen.h" diff --git a/src/yuzu/loading_screen.h b/src/yuzu/loading_screen.h index a39d5f6605..2c71d85e55 100644 --- a/src/yuzu/loading_screen.h +++ b/src/yuzu/loading_screen.h @@ -80,8 +80,8 @@ private: std::unique_ptr fadeout_animation; // Definitions for the differences in text and styling for each stage - ankerl::unordered_dense::map progressbar_style; - ankerl::unordered_dense::map stage_translations; + ::Common::unordered_map progressbar_style; + ::Common::unordered_map stage_translations; // newly generated shaders are added to the end of the file, so when loading and compiling // shaders, it will start quickly but end slow if new shaders were added since previous launch. diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index f8528d9671..e34f8f8d94 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -8,13 +8,20 @@ #include #include "dedicated_room/yuzu_room.h" #endif - -#include - #ifdef __unix__ #include "qt_common/gui_settings.h" #endif +#ifndef _WIN32 +#include +#endif + +#if defined(__APPLE__) +#include +#include +#include +#endif + #include "main_window.h" #ifdef _WIN32 @@ -100,16 +107,41 @@ int main(int argc, char* argv[]) { Breakpad::InstallCrashHandler(); #endif - Common::DetachedTasks detached_tasks; - // Init settings params QCoreApplication::setOrganizationName(QStringLiteral("eden")); QCoreApplication::setApplicationName(QStringLiteral("eden")); + // Increases the maximum open file limit. + // TODO: This should be common to all frontends. #ifdef _WIN32 - // Increases the maximum open file limit to 8192 + // MSVCRT limits this to 2048 for some inexplicable (and likely arcane) reason, + // so we have to account for that as well. +#ifdef __MSVCRT__ + _setmaxstdio(2048); +#else _setmaxstdio(8192); -#elif defined(__APPLE__) +#endif // __MSVCRT__ +#elif defined(__unix__) || defined(__APPLE__) + // Set the max open file limit to 8192, or the hard limit. + // Most sane systems should not hit the hard limit here. + struct rlimit rl; + if (getrlimit(RLIMIT_NOFILE, &rl) == 0) { + rl.rlim_cur = std::min(8192, rl.rlim_max); + setrlimit(RLIMIT_NOFILE, &rl); + } +#endif // _WIN32 + +#if defined(__APPLE__) + // Convert the relative path to an absolute path before the chdir + char resolved[PATH_MAX]; + for (int i = 1; i < argc; ++i) { + if (strcmp(argv[i], "-u") == 0 || strcmp(argv[i], "-input-profile") == 0) { + ++i; + } else if (argv[i][0] != '-' && argv[i][0] != '/' && realpath(argv[i], resolved)) { + argv[i] = strdup(resolved); + } + } + // 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 @@ -172,10 +204,6 @@ int main(int argc, char* argv[]) { // After settings have been loaded by GMainWindow, apply the filter main_window.show(); - app.connect(&app, &QGuiApplication::applicationStateChanged, &main_window, - &MainWindow::OnAppFocusStateChanged); - - int result = app.exec(); - detached_tasks.WaitForAllTasks(); - return result; + app.connect(&app, &QGuiApplication::applicationStateChanged, &main_window, &MainWindow::OnAppFocusStateChanged); + return app.exec(); } diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 59aab0ef93..92b725fa11 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -45,7 +45,7 @@ 0 0 1280 - 22 + 23 @@ -111,6 +111,7 @@ + @@ -147,6 +148,7 @@ + @@ -232,8 +234,7 @@ - - + @@ -401,30 +402,14 @@ Load/Remove &Amiibo... - - - false - - - &Report Compatibility - - - false - - Open &Mods Page - + - Open &Quickstart Guide - - - - - &FAQ + &User Handbook @@ -620,6 +605,19 @@ Show &Performance Overlay + + + Post-Processing &Shaders... + + + + + true + + + &Carousel View + + diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index 7549325567..ad76e5b2db 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -1,18 +1,25 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -// Qt on macOS doesn't define VMA shit -#include -#include "common/settings.h" -#include "common/settings_enums.h" -#include "frontend_common/settings_generator.h" -#include "qt_common/qt_string_lookup.h" -#include "render/performance_overlay.h" -#include "updater/update_dialog.h" +// Static Qt on macOS doesn't use Vulkan +// Other platforms do, and thus have conflicting VulkanMemoryAllocator symbols +#include "core/launch_params.h" #if defined(QT_STATICPLUGIN) && !defined(__APPLE__) #undef VMA_IMPLEMENTATION #endif +#include +#include "common/fs/path_util.h" +#include "common/settings.h" +#include "common/settings_enums.h" +#include "frontend_common/settings_generator.h" +#include "render/performance_overlay.h" +#ifdef HAS_RESHADE +#include "configuration/configure_post_processing.h" +#include "video_core/post_processing/fx_preset.h" +#endif +#include "updater/update_dialog.h" + #include "common/fs/ryujinx_compat.h" #include "main_window.h" #include "network/network.h" @@ -30,10 +37,10 @@ #include "bootmanager.h" #include "loading_screen.h" +#include "qt_common/util/vk.h" #include "ryujinx_dialog.h" #include "set_play_time_dialog.h" #include "util/util.h" -#include "vk_device_info.h" #include "yuzu/game/game_list.h" #include "applets/qt_amiibo_settings.h" @@ -85,6 +92,7 @@ #include "qt_common/abstract/frontend.h" #include "qt_common/qt_common.h" +#include "qt_common/qt_string_lookup.h" #include "qt_common/util/content.h" #include "qt_common/util/fs.h" @@ -92,6 +100,8 @@ #include "qt_common/util/mod.h" #include "qt_common/util/path.h" +#include "qt_common/render/emu_thread.h" + // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows // defines. static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper(const std::string& path, @@ -120,7 +130,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "common/string_util.h" #ifdef ARCHITECTURE_x86_64 -#include "common/x64/cpu_detect.h" +#include "common/cpu_features.h" #endif // Core // @@ -134,6 +144,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/hle/service/am/frontend/applet_web_browser_types.h" #include "core/file_sys/card_image.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/romfs.h" #include "core/file_sys/savedata_factory.h" @@ -153,7 +164,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "video_core/renderer_base.h" #include "video_core/shader_notify.h" -#include +#include #include @@ -314,73 +325,6 @@ enum class CalloutFlag : uint32_t { const int MainWindow::max_recent_files_item; -static void RemoveCachedContents() { - const auto cache_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir); - const auto offline_fonts = cache_dir / "fonts"; - const auto offline_manual = cache_dir / "offline_web_applet_manual"; - const auto offline_legal_information = cache_dir / "offline_web_applet_legal_information"; - const auto offline_system_data = cache_dir / "offline_web_applet_system_data"; - - Common::FS::RemoveDirRecursively(offline_fonts); - Common::FS::RemoveDirRecursively(offline_manual); - Common::FS::RemoveDirRecursively(offline_legal_information); - Common::FS::RemoveDirRecursively(offline_system_data); -} - -static void LogRuntimes() { -#ifdef _MSC_VER - // It is possible that the name of the dll will change. - // vcruntime140.dll is for 2015 and onwards - static constexpr char runtime_dll_name[] = "vcruntime140.dll"; - UINT sz = GetFileVersionInfoSizeA(runtime_dll_name, nullptr); - bool runtime_version_inspection_worked = false; - if (sz > 0) { - std::vector buf(sz); - if (GetFileVersionInfoA(runtime_dll_name, 0, sz, buf.data())) { - VS_FIXEDFILEINFO* pvi; - sz = sizeof(VS_FIXEDFILEINFO); - if (VerQueryValueA(buf.data(), "\\", reinterpret_cast(&pvi), &sz)) { - if (pvi->dwSignature == VS_FFI_SIGNATURE) { - runtime_version_inspection_worked = true; - LOG_INFO(Frontend, "MSVC Compiler: {} Runtime: {}.{}.{}.{}", _MSC_VER, - pvi->dwProductVersionMS >> 16, pvi->dwProductVersionMS & 0xFFFF, - pvi->dwProductVersionLS >> 16, pvi->dwProductVersionLS & 0xFFFF); - } - } - } - } - if (!runtime_version_inspection_worked) { - LOG_INFO(Frontend, "Unable to inspect {}", runtime_dll_name); - } -#endif - LOG_INFO(Frontend, "Qt Compile: {} Runtime: {}", QT_VERSION_STR, qVersion()); -} - -static QString PrettyProductName() { -#ifdef _WIN32 - // After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2 - // With that notation change they changed the registry key used to denote the current version - QSettings windows_registry( - QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), - QSettings::NativeFormat); - const QString release_id = windows_registry.value(QStringLiteral("ReleaseId")).toString(); - if (release_id == QStringLiteral("2009")) { - const u32 current_build = windows_registry.value(QStringLiteral("CurrentBuild")).toUInt(); - const QString display_version = - windows_registry.value(QStringLiteral("DisplayVersion")).toString(); - const u32 ubr = windows_registry.value(QStringLiteral("UBR")).toUInt(); - u32 version = 10; - if (current_build >= 22000) { - version = 11; - } - return QStringLiteral("Windows %1 Version %2 (Build %3.%4)") - .arg(QString::number(version), display_version, QString::number(current_build), - QString::number(ubr)); - } -#endif - return QSysInfo::prettyProductName(); -} - namespace { constexpr std::array, 5> default_game_icon_sizes{ @@ -420,6 +364,10 @@ MainWindow::MainWindow(bool has_broken_vulkan) Common::FS::CreateEdenPaths(); this->config = std::make_unique(); + Common::Log::Filter filter; + filter.ParseFilterString(Settings::values.log_filter.GetValue()); + Common::Log::SetGlobalFilter(filter); + if (user_data_migrator.migrated) { // Sort-of hack whereby we only move the old dir if it's a subfolder of the user dir @@ -446,11 +394,6 @@ MainWindow::MainWindow(bool has_broken_vulkan) UISettings::RestoreWindowState(config); - QtCommon::system->Initialize(); - - Common::Log::Initialize(); - Common::Log::Start(); - LoadTranslation(); FrontendCommon::GenerateSettings(); @@ -471,10 +414,6 @@ MainWindow::MainWindow(bool has_broken_vulkan) play_time_manager = std::make_unique(); - Network::Init(); - - QtCommon::Meta::RegisterMetaTypes(); - InitializeWidgets(); InitializeDebugWidgets(); InitializeRecentFileMenuActions(); @@ -486,53 +425,9 @@ MainWindow::MainWindow(bool has_broken_vulkan) ConnectMenuEvents(); ConnectWidgetEvents(); - QtCommon::system->HIDCore().ReloadInputDevices(); + QtCommon::SetupHID(); controller_dialog->refreshConfiguration(); - const auto branch_name = std::string(Common::g_scm_branch); - const auto description = std::string(Common::g_scm_desc); - const auto build_id = std::string(Common::g_build_id); - - const auto yuzu_build = fmt::format("Eden Development Build | {}-{}", branch_name, description); - const auto override_build = - fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id); - const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build; - const auto processor_count = std::thread::hardware_concurrency(); - - LOG_INFO(Frontend, "Eden Version: {}", yuzu_build_version); - LogRuntimes(); -#ifdef ARCHITECTURE_x86_64 - const auto& caps = Common::GetCPUCaps(); - std::string cpu_string = caps.cpu_string; - if (caps.avx || caps.avx2 || caps.avx512f) { - cpu_string += " | AVX"; - if (caps.avx512f) { - cpu_string += "512"; - } else if (caps.avx2) { - cpu_string += '2'; - } - if (caps.fma || caps.fma4) { - cpu_string += " | FMA"; - } - } - LOG_INFO(Frontend, "Host CPU: {}", cpu_string); - if (std::optional processor_core = Common::GetProcessorCount()) { - LOG_INFO(Frontend, "Host CPU Cores: {}", *processor_core); - } -#endif - LOG_INFO(Frontend, "Host CPU Threads: {}", processor_count); - LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString()); - LOG_INFO(Frontend, "Host RAM: {:.2f} GiB", - Common::GetMemInfo().TotalPhysicalMemory / f64{1_GiB}); - LOG_INFO(Frontend, "Host Swap: {:.2f} GiB", Common::GetMemInfo().TotalSwapMemory / f64{1_GiB}); -#ifdef _WIN32 - LOG_INFO(Frontend, "Host Timer Resolution: {:.4f} ms", - std::chrono::duration_cast>( - Common::Windows::SetCurrentTimerResolutionToMaximum()) - .count()); - QtCommon::system->CoreTiming().SetTimerResolutionNs( - Common::Windows::GetCurrentTimerResolution()); -#endif UpdateWindowTitle(); show(); @@ -547,13 +442,8 @@ MainWindow::MainWindow(bool has_broken_vulkan) } #endif - QtCommon::system->SetContentProvider(std::make_unique()); - QtCommon::system->RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual, - QtCommon::provider.get()); - QtCommon::system->GetFileSystemController().CreateFactories(*QtCommon::vfs); - - // Remove cached contents generated during the previous session - RemoveCachedContents(); + // Setup content providers. + QtCommon::SetupContentProviders(); // Gen keys if necessary OnCheckFirmwareDecryption(); @@ -568,7 +458,6 @@ MainWindow::MainWindow(bool has_broken_vulkan) if (Settings::values.use_dev_keys.GetValue()) { Core::Crypto::KeyManager::Instance().ReloadKeys(); } - game_list->LoadCompatibilityList(); game_list->PopulateAsync(UISettings::values.game_dirs); // Set up game list mode checkboxes. @@ -627,77 +516,34 @@ MainWindow::MainWindow(bool has_broken_vulkan) // to prevent the UI from blowing up. UpdateUITheme(); - QStringList args = QApplication::arguments(); + if (QStringList args = QApplication::arguments(); args.size() >= 2) { + QList qba{}; + for (auto const& e : args) + qba.push_back(e.toUtf8()); + std::vector args_cstr{}; + for (auto& e : qba) + args_cstr.push_back(e.data()); - if (args.size() < 2) { - return; - } + auto const lp = Core::ParseLaunchParams(*QtCommon::system, args_cstr.size(), args_cstr.data(), nullptr); - QString game_path; - bool should_launch_qlaunch = false; - bool should_launch_setup = false; - bool has_gamepath = false; - bool is_fullscreen = false; - - // Preserves drag/drop functionality - for (int i = 1; i < args.size(); ++i) { - if (args[i] == QStringLiteral("-f")) { - // Launch game in fullscreen mode - is_fullscreen = true; - } else if (args[i] == QStringLiteral("-u") && i < args.size() - 1) { - // Launch game with a specific user - int user_arg_idx = ++i; - bool argument_ok; - std::size_t selected_user = args[user_arg_idx].toUInt(&argument_ok); - if (!argument_ok) { - // try to look it up by username, only finds the first username that matches. - std::string const user_arg_str = args[user_arg_idx].toStdString(); - auto const user_idx = - QtCommon::system->GetProfileManager().GetUserIndex(user_arg_str); - if (user_idx != std::nullopt) { - selected_user = user_idx.value(); - } else { - LOG_ERROR(Frontend, "Invalid user argument '{}'", user_arg_str); - continue; - } - } - if (QtCommon::system->GetProfileManager().UserExistsIndex(selected_user)) { - Settings::values.current_user = s32(selected_user); - user_flag_cmd_line = true; - } else { - LOG_ERROR(Frontend, "Selected user {} doesn't exist", selected_user); - } - } else if (args[i] == QStringLiteral("-g") && i < args.size() - 1) { - // Launch game at path - game_path = args[++i]; - has_gamepath = true; - } else if (args[i] == QStringLiteral("-input-profile") && i < args.size() - 1) { - auto& players = Settings::values.players.GetValue(); - players[0].profile_name = args[++i].toStdString(); - } else if (args[i] == QStringLiteral("-qlaunch")) { - should_launch_qlaunch = true; - } else if (args[i] == QStringLiteral("-setup")) { - should_launch_setup = true; - } else { - game_path = args[i]; - has_gamepath = true; + // Override fullscreen setting if gamepath or argument is provided + if (!lp.filepath.empty() || lp.fullscreen) { + ui->action_Fullscreen->setChecked(lp.fullscreen); } - } - // Override fullscreen setting if gamepath or argument is provided - if (has_gamepath || is_fullscreen) { - ui->action_Fullscreen->setChecked(is_fullscreen); - } - - if (should_launch_setup) { - LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Starter), std::nullopt); - } else { - if (!game_path.isEmpty()) { - BootGame(game_path, ApplicationAppletParameters()); - } else { - if (should_launch_qlaunch) { - LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::QLaunch), std::nullopt); - } + if (!lp.filepath.empty()) { + BootGame(QString::fromStdString(lp.filepath), ApplicationAppletParameters()); + } else if (lp.launch_setup) { + LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Starter), std::nullopt); + } else if (lp.launch_qlaunch) { + LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::QLaunch), std::nullopt); + } else if (lp.launch_hlaunch) { + std::filesystem::path const sd_dir = + Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir); + auto const hbl_path = (sd_dir / "atmosphere" / "hbl.nsp").string(); + BootGame( + QString::fromStdString(hbl_path), + LibraryAppletParameters(0x010000000000100Dull, Service::AM::AppletId::QLaunch)); } } } @@ -1070,10 +916,7 @@ void MainWindow::WebBrowserRequestExit() { } void MainWindow::InitializeWidgets() { -#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING - ui->action_Report_Compatibility->setVisible(true); -#endif - render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *QtCommon::system); + render_window = new GRenderWindow(this, input_subsystem); render_window->hide(); game_list = new GameList(QtCommon::vfs, QtCommon::provider.get(), *play_time_manager, @@ -1198,16 +1041,50 @@ void MainWindow::InitializeWidgets() { statusBar()->insertPermanentWidget(0, volume_button); +#ifdef HAS_RESHADE + post_shader_status_button = new QPushButton(); + post_shader_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); + post_shader_status_button->setFocusPolicy(Qt::NoFocus); + post_shader_status_button->setCheckable(true); + connect(post_shader_status_button, &QPushButton::clicked, this, [this] { + const bool enabled = Settings::values.post_shader_enabled.GetValue(); + Settings::values.post_shader_enabled.SetValue(!enabled); + UpdatePostShaderText(); + }); + UpdatePostShaderText(); + post_shader_status_button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(post_shader_status_button, &QPushButton::customContextMenuRequested, + [this](const QPoint& menu_location) { + QMenu context_menu; + + for (auto const& preset : VideoCore::GetFxPresetCatalog()) { + context_menu.addAction(QString::fromStdString(preset.name), + [this, name = preset.name] { + VideoCore::ApplyFxPreset(name); + Settings::values.post_shader_enabled.SetValue(true); + UpdatePostShaderText(); + }); + } + + context_menu.addSeparator(); + context_menu.addAction(tr("Configure Effects..."), this, + &MainWindow::OnPostProcessingShaders); + + context_menu.exec(post_shader_status_button->mapToGlobal(menu_location)); + post_shader_status_button->repaint(); + }); + statusBar()->insertPermanentWidget(0, post_shader_status_button); +#endif + // setup AA button aa_status_button = new QPushButton(); aa_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); aa_status_button->setFocusPolicy(Qt::NoFocus); connect(aa_status_button, &QPushButton::clicked, [&] { auto aa_mode = Settings::values.anti_aliasing.GetValue(); - aa_mode = static_cast(static_cast(aa_mode) + 1); - if (aa_mode == Settings::AntiAliasing::MaxEnum) { - aa_mode = Settings::AntiAliasing::None; - } + aa_mode = Settings::AntiAliasing(u32(aa_mode) + 1); + if (u32(aa_mode) > u32(Settings::EnumMetadata::GetLast())) + aa_mode = Settings::EnumMetadata::GetFirst(); Settings::values.anti_aliasing.SetValue(aa_mode); aa_status_button->setChecked(true); UpdateAAText(); @@ -1533,11 +1410,12 @@ void MainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) { return; } if (UISettings::values.pause_when_in_background) { - if (emu_thread->IsRunning() && + if (QtCommon::emu_thread->IsRunning() && (state & (Qt::ApplicationHidden | Qt::ApplicationInactive))) { auto_paused = true; OnPauseGame(); - } else if (!emu_thread->IsRunning() && auto_paused && (state & Qt::ApplicationActive)) { + } else if (!QtCommon::emu_thread->IsRunning() && auto_paused && + (state & Qt::ApplicationActive)) { auto_paused = false; OnStartGame(); } @@ -1572,8 +1450,6 @@ void MainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::VerifyIntegrityRequested, this, &MainWindow::OnGameListVerifyIntegrity); connect(game_list, &GameList::CopyTIDRequested, this, &MainWindow::OnGameListCopyTID); - connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, - &MainWindow::OnGameListNavigateToGamedbEntry); connect(game_list, &GameList::CreateShortcut, this, &MainWindow::OnGameListCreateShortcut); connect(game_list, &GameList::AddDirectory, this, &MainWindow::OnGameListAddDirectory); connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this, @@ -1589,11 +1465,6 @@ void MainWindow::ConnectWidgetEvents() { connect(this, &MainWindow::UpdateInstallProgress, this, &MainWindow::IncrementInstallProgress); - connect(this, &MainWindow::EmulationStarting, render_window, - &GRenderWindow::OnEmulationStarting); - connect(this, &MainWindow::EmulationStopping, render_window, - &GRenderWindow::OnEmulationStopping); - // Software Keyboard Applet connect(this, &MainWindow::EmulationStarting, this, &MainWindow::SoftwareKeyboardExit); connect(this, &MainWindow::EmulationStopping, this, &MainWindow::SoftwareKeyboardExit); @@ -1623,10 +1494,8 @@ void MainWindow::ConnectMenuEvents() { // Emulation connect_menu(ui->action_Pause, &MainWindow::OnPauseContinueGame); connect_menu(ui->action_Stop, &MainWindow::OnStopGame); - connect_menu(ui->action_Report_Compatibility, &MainWindow::OnMenuReportCompatibility); connect_menu(ui->action_Open_Mods_Page, &MainWindow::OnOpenModsPage); - connect_menu(ui->action_Open_Quickstart_Guide, &MainWindow::OnOpenQuickstartGuide); - connect_menu(ui->action_Open_FAQ, &MainWindow::OnOpenFAQ); + connect_menu(ui->action_Open_UserHandbook, &MainWindow::OnOpenUserHandbook); connect_menu(ui->action_Restart, &MainWindow::OnRestartGame); connect_menu(ui->action_Configure, &MainWindow::OnConfigure); connect_menu(ui->action_Configure_Current_Game, &MainWindow::OnConfigurePerGame); @@ -1637,6 +1506,11 @@ void MainWindow::ConnectMenuEvents() { connect_menu(ui->action_Show_Filter_Bar, &MainWindow::OnToggleFilterBar); connect_menu(ui->action_Show_Status_Bar, &MainWindow::OnToggleStatusBar); connect_menu(ui->action_Show_Performance_Overlay, &MainWindow::OnTogglePerfOverlay); +#ifdef HAS_RESHADE + connect_menu(ui->action_Post_Processing_Shaders, &MainWindow::OnPostProcessingShaders); +#else + ui->action_Post_Processing_Shaders->setVisible(false); +#endif connect_menu(ui->action_Reset_Window_Size_720, &MainWindow::ResetWindowSize720); connect_menu(ui->action_Reset_Window_Size_900, &MainWindow::ResetWindowSize900); @@ -1647,6 +1521,7 @@ void MainWindow::ConnectMenuEvents() { connect_menu(ui->action_Grid_View, &MainWindow::SetGridView); connect_menu(ui->action_Tree_View, &MainWindow::SetTreeView); + connect_menu(ui->action_Carousel_View, &MainWindow::SetCarouselView); game_size_actions = new QActionGroup(this); game_size_actions->setExclusive(true); @@ -1666,6 +1541,8 @@ void MainWindow::ConnectMenuEvents() { if (checked) { UISettings::values.game_icon_size.SetValue(size); CheckIconSize(); + + game_list->UpdateIconSizes(); game_list->RefreshGameDirectory(); } }); @@ -1747,14 +1624,13 @@ void MainWindow::ConnectMenuEvents() { } void MainWindow::UpdateMenuState() { - const bool is_paused = emu_thread == nullptr || !emu_thread->IsRunning(); + const bool is_paused = QtCommon::emu_thread == nullptr || !QtCommon::emu_thread->IsRunning(); const bool is_firmware_available = CheckFirmwarePresence(); const std::array running_actions{ ui->action_Stop, ui->action_Restart, ui->action_Configure_Current_Game, - ui->action_Report_Compatibility, ui->action_Load_Amiibo, ui->action_Pause, }; @@ -1815,17 +1691,16 @@ void MainWindow::SetupPrepareForSleep() { } void MainWindow::OnPrepareForSleep(bool prepare_sleep) { - if (emu_thread == nullptr) { + if (QtCommon::emu_thread == nullptr) return; - } if (prepare_sleep) { - if (emu_thread->IsRunning()) { + if (QtCommon::emu_thread->IsRunning()) { auto_paused = true; OnPauseGame(); } } else { - if (!emu_thread->IsRunning() && auto_paused) { + if (!QtCommon::emu_thread->IsRunning() && auto_paused) { auto_paused = false; OnStartGame(); } @@ -1898,23 +1773,20 @@ void MainWindow::AllowOSSleep() { bool MainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletParameters params) { // Shutdown previous session if the emu thread is still active... - if (emu_thread != nullptr) { + if (QtCommon::emu_thread != nullptr) ShutdownGame(); - } - if (!render_window->InitRenderTarget()) { + if (!render_window->InitRenderTarget()) return false; - } QtCommon::system->SetFilesystem(QtCommon::vfs); - if (params.launch_type == Service::AM::LaunchType::FrontendInitiated) { + if (params.launch_type == Service::AM::LaunchType::FrontendInitiated) QtCommon::system->GetUserChannel().clear(); - } QtCommon::system->SetFrontendAppletSet({ std::make_unique(*this), // Amiibo Settings - (UISettings::values.controller_applet_disabled.GetValue() == true) + (Settings::values.controller_applet_mode.GetValue() == Settings::AppletMode::Disabled) ? nullptr : std::make_unique(*this), // Controller Selector std::make_unique(*this), // Error Display @@ -1964,12 +1836,10 @@ bool MainWindow::LoadROM(const QString& filename, Service::AM::FrontendAppletPar case Core::SystemResultStatus::ErrorVideoCore: QMessageBox::critical( this, tr("An error occurred initializing the video core."), - tr("Eden has encountered an error while running the video core. " - "This is usually caused by outdated GPU drivers, including integrated ones. " - "Please see the log for more details. " - "For more information on accessing the log, please see the following page: " - "" - "How to Upload the Log File. ")); + tr("This is usually caused by outdated GPU drivers. " + "Please see the log for more details. See: " + "" + "How to access log files.")); break; default: if (result > Core::SystemResultStatus::ErrorLoader) { @@ -2017,36 +1887,6 @@ bool MainWindow::SelectAndSetCurrentUser( return true; } -void MainWindow::ConfigureFilesystemProvider(const std::string& filepath) { - // Ensure all NCAs are registered before launching the game - const auto file = QtCommon::vfs->OpenFile(filepath, FileSys::OpenMode::Read); - if (!file) { - return; - } - - if (QtCommon::provider->AddEntriesFromContainer(file)) { - return; - } - - auto loader = Loader::GetLoader(*QtCommon::system, file); - if (!loader) { - return; - } - - const auto file_type = loader->GetFileType(); - if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { - return; - } - - u64 program_id = 0; - const auto res2 = loader->ReadProgramId(program_id); - if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { - QtCommon::provider->AddEntry(FileSys::TitleType::Application, - FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), - program_id, file); - } -} - void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletParameters params, StartGameType type) { LOG_INFO(Frontend, "Eden starting..."); @@ -2065,7 +1905,7 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa last_filename_booted = filename; - ConfigureFilesystemProvider(filename.toStdString()); + QtCommon::Content::configureFilesystemProvider(filename.toStdString()); const auto v_file = Core::GetGameFileFromPath(QtCommon::vfs, filename.toUtf8().constData()); const auto loader = Loader::GetLoader(*QtCommon::system, v_file, params.program_id, params.program_index); @@ -2110,25 +1950,50 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa game_list->setDisabled(true); // Create and start the emulation thread - emu_thread = std::make_unique(*QtCommon::system); - emit EmulationStarting(emu_thread.get()); - emu_thread->start(); + QtCommon::emu_thread = std::make_unique(); + emit EmulationStarting(); + QtCommon::emu_thread->start(); // Register an ExecuteProgram callback such that Core can execute a sub-program QtCommon::system->RegisterExecuteProgramCallback( [this](std::size_t program_index_) { render_window->ExecuteProgram(program_index_); }); QtCommon::system->RegisterExitCallback([this] { - emu_thread->ForceStop(); + QtCommon::emu_thread->ForceStop(); render_window->Exit(); }); + QtCommon::system->RegisterApplicationChangedCallback([this](u64 changed_program_id) { + if (QtCommon::emu_thread) + QtCommon::emu_thread->RequestDiskShaderCacheReload(changed_program_id); + + QMetaObject::invokeMethod( + this, [this, changed_program_id] { this->OnApplicationChanged(changed_program_id); }, + Qt::QueuedConnection); + }); + connect(render_window, &GRenderWindow::Closed, this, &MainWindow::OnStopGame); connect(render_window, &GRenderWindow::MouseActivity, this, &MainWindow::OnMouseActivity); - connect(emu_thread.get(), &EmuThread::LoadProgress, loading_screen, + connect(QtCommon::emu_thread.get(), &EmuThread::LoadProgress, loading_screen, &LoadingScreen::OnLoadProgress, Qt::QueuedConnection); + connect( + QtCommon::emu_thread.get(), &EmuThread::ShaderCacheReloadStarted, this, + [this] { + loading_screen->Prepare(QtCommon::system->GetAppLoader()); + loading_screen->show(); + render_window->hide(); + }, + Qt::QueuedConnection); + + connect( + QtCommon::emu_thread.get(), &EmuThread::ShaderCacheReloadFinished, this, + [this] { + loading_screen->OnLoadComplete(); + }, + Qt::QueuedConnection); + // Update the GUI UpdateStatusButtons(); if (ui->action_Single_Window_Mode->isChecked()) { @@ -2221,9 +2086,10 @@ bool MainWindow::OnShutdownBegin() { discord_rpc->Pause(); RequestGameExit(); - emu_thread->disconnect(); - emu_thread->SetRunning(true); + QtCommon::emu_thread->disconnect(); + QtCommon::emu_thread->SetRunning(true); + connect(QtCommon::emu_thread.get(), &QThread::finished, this, &MainWindow::OnEmulationStopped); emit EmulationStopping(); int shutdown_time = 1000; @@ -2237,7 +2103,6 @@ bool MainWindow::OnShutdownBegin() { shutdown_timer.setSingleShot(true); shutdown_timer.start(shutdown_time); connect(&shutdown_timer, &QTimer::timeout, this, &MainWindow::OnEmulationStopTimeExpired); - connect(emu_thread.get(), &QThread::finished, this, &MainWindow::OnEmulationStopped); // Disable everything to prevent anything from being triggered here ui->action_Pause->setEnabled(false); @@ -2255,17 +2120,17 @@ void MainWindow::OnShutdownBeginDialog() { } void MainWindow::OnEmulationStopTimeExpired() { - if (emu_thread) { - emu_thread->ForceStop(); + if (QtCommon::emu_thread) { + QtCommon::emu_thread->ForceStop(); } } void MainWindow::OnEmulationStopped() { shutdown_timer.stop(); - if (emu_thread) { - emu_thread->disconnect(); - emu_thread->wait(); - emu_thread.reset(); + if (QtCommon::emu_thread) { + QtCommon::emu_thread->disconnect(); + QtCommon::emu_thread->wait(); + QtCommon::emu_thread.reset(); } if (shutdown_dialog) { @@ -2765,21 +2630,6 @@ void MainWindow::OnGameListCopyTID(u64 program_id) { clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id))); } -void MainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, - const CompatibilityList& compatibility_list) { - const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); - - QString directory; - if (it != compatibility_list.end()) { - directory = it->second.second; - } - - QDesktopServices::openUrl(QUrl( - QStringLiteral( - "https://www.emuready.com/listings?emulatorIds=43bfc023-ec22-422d-8324-048a8ec9f28f") + - directory)); -} - void MainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path, const QtCommon::Game::ShortcutTarget target) { // Create shortcu @@ -2906,7 +2756,7 @@ void MainWindow::OnMenuLoadFile() { is_load_file_select_active = true; const QString extensions = QStringLiteral("*.") - .append(GameList::supported_file_extensions.join(QStringLiteral(" *."))) + .append(QtCommon::supported_file_extensions.join(QStringLiteral(" *."))) .append(QStringLiteral(" main")); const QString file_filter = tr("Switch Executable (%1);;All Files (*.*)", "%1 is an identifier for the Switch executable file extensions.") @@ -2947,9 +2797,9 @@ void MainWindow::IncrementInstallProgress() { void MainWindow::OnMenuInstallToNAND() { const QString file_filter = - tr("Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive " - "(*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge " - "Image (*.xci)"); + tr("Installable Switch File (*.nca *.nsp *.nsz *.xci *.xcz);;Nintendo Content Archive " + "(*.nca);;Nintendo Submission Package (*.nsp *.nsz);;NX Cartridge " + "Image (*.xci *.xcz)"); QStringList filenames = QFileDialog::getOpenFileNames( this, tr("Install Files"), QString::fromStdString(UISettings::values.roms_path), @@ -3006,7 +2856,10 @@ void MainWindow::OnMenuInstallToNAND() { QFuture future; ContentManager::InstallResult result; - if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive)) { + if (file.endsWith(QStringLiteral("nsp"), Qt::CaseInsensitive) || + file.endsWith(QStringLiteral("nsz"), Qt::CaseInsensitive) || + file.endsWith(QStringLiteral("xci"), Qt::CaseInsensitive) || + file.endsWith(QStringLiteral("xcz"), Qt::CaseInsensitive)) { const auto progress_callback = [this](size_t size, size_t progress) { emit UpdateInstallProgress(); if (install_progress->wasCanceled()) { @@ -3142,7 +2995,7 @@ void MainWindow::OnMenuRecentFile() { void MainWindow::OnStartGame() { PreventOSSleep(); - emu_thread->SetRunning(true); + QtCommon::emu_thread->SetRunning(true); UpdateMenuState(); OnTasStateChanged(); @@ -3168,7 +3021,7 @@ void MainWindow::OnRestartGame() { } void MainWindow::OnPauseGame() { - emu_thread->SetRunning(false); + QtCommon::emu_thread->SetRunning(false); play_time_manager->Stop(); UpdateMenuState(); AllowOSSleep(); @@ -3177,7 +3030,7 @@ void MainWindow::OnPauseGame() { void MainWindow::OnPauseContinueGame() { if (emulation_running) { - if (emu_thread->IsRunning()) { + if (QtCommon::emu_thread->IsRunning()) { OnPauseGame(); } else { OnStartGame(); @@ -3232,12 +3085,31 @@ void MainWindow::OnLoadComplete() { } void MainWindow::OnExecuteProgram(std::size_t program_index) { + const u64 previous_program_id = QtCommon::system->GetApplicationProcessProgramID(); + + const auto current_path = + QString::fromStdString(QtCommon::system->GetCurrentApplicationFilePath()); + + LOG_INFO(Frontend, "ExecuteProgram requested, program_index={} previous_program_id={:016X}", + program_index, previous_program_id); + ShutdownGame(); auto params = ApplicationAppletParameters(); params.program_index = static_cast(program_index); params.launch_type = Service::AM::LaunchType::ApplicationInitiated; - BootGame(last_filename_booted, params); + + if (previous_program_id > static_cast(Service::AM::AppletProgramId::MaxProgramId)) { + params.previous_program_index = + static_cast(previous_program_id - FileSys::GetBaseTitleID(previous_program_id)); + params.program_id = previous_program_id; + } + + const auto filename = current_path.isEmpty() ? last_filename_booted : current_path; + + LOG_DEBUG(Frontend, "ExecuteProgram booting from path: {}", filename.toStdString()); + + BootGame(filename, params); } void MainWindow::OnExit() { @@ -3273,8 +3145,8 @@ void MainWindow::OnMenuReportCompatibility() { tr("Compatibility list reporting is currently disabled. Check back later!")); // #if defined(ARCHITECTURE_x86_64) && !defined(__APPLE__) - // const auto& caps = Common::GetCPUCaps(); - // const bool has_fma = caps.fma || caps.fma4; + // const auto& caps = g_cpu_caps; + // const bool has_fma = caps.fma; // const auto processor_count = std::thread::hardware_concurrency(); // const bool has_4threads = processor_count == 0 || processor_count >= 4; // const bool has_8gb_ram = Common::GetMemInfo().TotalPhysicalMemory >= 8_GiB; @@ -3321,12 +3193,8 @@ void MainWindow::OnOpenModsPage() { OpenURL(QUrl(QStringLiteral("https://github.com/eden-emulator/yuzu-mod-archive"))); } -void MainWindow::OnOpenQuickstartGuide() { - OpenURL(QUrl(QStringLiteral("https://yuzu-mirror.github.io/help/quickstart/"))); -} - -void MainWindow::OnOpenFAQ() { - OpenURL(QUrl(QStringLiteral("https://yuzu-mirror.github.io/help"))); +void MainWindow::OnOpenUserHandbook() { + OpenURL(QUrl(QStringLiteral("https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/README.md"))); } void MainWindow::ToggleFullscreen() { @@ -3467,9 +3335,10 @@ void MainWindow::ResetWindowSize1080() { void MainWindow::SetGameListMode(Settings::GameListMode mode) { ui->action_Grid_View->setChecked(mode == Settings::GameListMode::GridView); ui->action_Tree_View->setChecked(mode == Settings::GameListMode::TreeView); + ui->action_Carousel_View->setChecked(mode == Settings::GameListMode::CarouselView); UISettings::values.game_list_mode = mode; - ui->action_Show_Game_Name->setEnabled(mode == Settings::GameListMode::GridView); + ui->action_Show_Game_Name->setEnabled(mode != Settings::GameListMode::TreeView); CheckIconSize(); game_list->ResetViewMode(); @@ -3483,11 +3352,15 @@ void MainWindow::SetTreeView() { SetGameListMode(Settings::GameListMode::TreeView); } +void MainWindow::SetCarouselView() { + SetGameListMode(Settings::GameListMode::CarouselView); +} + void MainWindow::CheckIconSize() { - // When in grid view mode, with text off - // there is no point in having icons turned off.. + // When in grid/carousel view mode, with text off + // there is no point in having icons turned off auto actions = game_size_actions->actions(); - if (UISettings::values.game_list_mode.GetValue() == Settings::GameListMode::GridView && + if (UISettings::values.game_list_mode.GetValue() != Settings::GameListMode::TreeView && !UISettings::values.show_game_name.GetValue()) { u32 newSize = UISettings::values.game_icon_size.GetValue(); if (newSize == 0) { @@ -3518,6 +3391,7 @@ void MainWindow::ToggleShowGameName() { CheckIconSize(); + game_list->UpdateIconSizes(); game_list->RefreshGameDirectory(); } @@ -3728,9 +3602,6 @@ void MainWindow::OnToggleDockedMode() { void MainWindow::OnToggleGpuAccuracy() { switch (Settings::values.gpu_accuracy.GetValue()) { case Settings::GpuAccuracy::Low: - Settings::values.gpu_accuracy.SetValue(Settings::GpuAccuracy::Medium); - break; - case Settings::GpuAccuracy::Medium: Settings::values.gpu_accuracy.SetValue(Settings::GpuAccuracy::High); break; case Settings::GpuAccuracy::High: @@ -3777,10 +3648,9 @@ void MainWindow::OnIncreaseVolume() { void MainWindow::OnToggleAdaptingFilter() { auto filter = Settings::values.scaling_filter.GetValue(); - filter = static_cast(static_cast(filter) + 1); - if (filter == Settings::ScalingFilter::MaxEnum) { - filter = Settings::ScalingFilter::NearestNeighbor; - } + filter = Settings::ScalingFilter(u32(filter) + 1); + if (u32(filter) > u32(Settings::EnumMetadata::GetLast())) + filter = Settings::EnumMetadata::GetFirst(); Settings::values.scaling_filter.SetValue(filter); filter_status_button->setChecked(true); UpdateFilterText(); @@ -3794,12 +3664,12 @@ void MainWindow::OnToggleGraphicsAPI() { api = Settings::RendererBackend::OpenGL_GLSL; break; case Settings::RendererBackend::OpenGL_GLSL: - api = Settings::RendererBackend::OpenGL_GLSL; - break; - case Settings::RendererBackend::OpenGL_SPIRV: api = Settings::RendererBackend::OpenGL_GLASM; break; case Settings::RendererBackend::OpenGL_GLASM: + api = Settings::RendererBackend::OpenGL_SPIRV; + break; + case Settings::RendererBackend::OpenGL_SPIRV: api = Settings::RendererBackend::Null; break; #else @@ -3857,12 +3727,9 @@ void MainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file_ } void MainWindow::OnLoadAmiibo() { - if (emu_thread == nullptr || !emu_thread->IsRunning()) { + if (QtCommon::emu_thread == nullptr || !QtCommon::emu_thread->IsRunning() || + is_amiibo_file_select_active) return; - } - if (is_amiibo_file_select_active) { - return; - } auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); @@ -3966,79 +3833,21 @@ void MainWindow::OnVerifyInstalledContents() { QtCommon::Content::VerifyInstalledContents(); } -void MainWindow::InstallFirmware(const QString& location, bool recursive) { - QtCommon::Content::InstallFirmware(location, recursive); +void MainWindow::OnInstallFirmware() { + QtCommon::Content::InstallFirmware(); OnCheckFirmwareDecryption(); } -void MainWindow::OnInstallFirmware() { - // Don't do this while emulation is running, that'd probably be a bad idea. - if (emu_thread != nullptr && emu_thread->IsRunning()) { - return; - } - - // Check for installed keys, error out, suggest restart? - if (!ContentManager::AreKeysPresent()) { - QMessageBox::information( - this, tr("Keys not installed"), - tr("Install decryption keys and restart Eden before attempting to install firmware.")); - return; - } - - const QString firmware_source_location = QFileDialog::getExistingDirectory( - this, tr("Select Dumped Firmware Source Location"), {}, QFileDialog::ShowDirsOnly); - if (firmware_source_location.isEmpty()) { - return; - } - - InstallFirmware(firmware_source_location); -} - void MainWindow::OnInstallFirmwareFromZIP() { - // Don't do this while emulation is running, that'd probably be a bad idea. - if (emu_thread != nullptr && emu_thread->IsRunning()) { - return; - } - - // Check for installed keys, error out, suggest restart? - if (!ContentManager::AreKeysPresent()) { - QMessageBox::information( - this, tr("Keys not installed"), - tr("Install decryption keys and restart Eden before attempting to install firmware.")); - return; - } - - const QString firmware_zip_location = QFileDialog::getOpenFileName( - this, tr("Select Dumped Firmware ZIP"), {}, tr("Zipped Archives (*.zip)")); - if (firmware_zip_location.isEmpty()) { - return; - } - - const QString qCacheDir = QtCommon::Content::UnzipFirmwareToTmp(firmware_zip_location); - - // In this case, it has to be done recursively, since sometimes people - // will pack it into a subdirectory after dumping - if (!qCacheDir.isEmpty()) { - InstallFirmware(qCacheDir, true); - std::error_code ec; - std::filesystem::remove_all(std::filesystem::temp_directory_path() / "eden" / "firmware", - ec); - - if (ec) { - QMessageBox::warning(this, tr("Firmware cleanup failed"), - tr("Failed to clean up extracted firmware cache.\n" - "Check write permissions in the system temp directory and try " - "again.\nOS reported error: %1") - .arg(QString::fromStdString(ec.message()))); - } - } + QtCommon::Content::InstallFirmwareZip(); + OnCheckFirmwareDecryption(); } +// TODO(crueter): QtCommon this: game list populate can be a signal? void MainWindow::OnInstallDecryptionKeys() { // Don't do this while emulation is running. - if (emu_thread != nullptr && emu_thread->IsRunning()) { + if (QtCommon::emu_thread != nullptr && QtCommon::emu_thread->IsRunning()) return; - } QtCommon::Content::InstallKeys(); @@ -4066,11 +3875,10 @@ void MainWindow::OnDataDialog() { void MainWindow::OnToggleFilterBar() { game_list->SetFilterVisible(ui->action_Show_Filter_Bar->isChecked()); - if (ui->action_Show_Filter_Bar->isChecked()) { + if (ui->action_Show_Filter_Bar->isChecked()) game_list->SetFilterFocus(); - } else { + else game_list->ClearFilter(); - } } void MainWindow::OnToggleStatusBar() { @@ -4078,15 +3886,32 @@ void MainWindow::OnToggleStatusBar() { } void MainWindow::OnTogglePerfOverlay() { - if (perf_overlay) { + if (perf_overlay) perf_overlay->setVisible(ui->action_Show_Performance_Overlay->isChecked()); - } } +#ifdef HAS_RESHADE +void MainWindow::OnPostProcessingShaders() { + if (post_processing_dialog == nullptr) { + post_processing_dialog = new ConfigurePostProcessing(this); + connect(post_processing_dialog, &QDialog::finished, post_processing_dialog, [this]() { + post_processing_dialog->deleteLater(); + post_processing_dialog = nullptr; + UpdatePostShaderText(); + }); + } + + post_processing_dialog->show(); + post_processing_dialog->raise(); + post_processing_dialog->activateWindow(); +} +#endif + void MainWindow::OnGameListRefresh() { // Resets metadata cache and reloads QtCommon::Game::ResetMetadata(false); game_list->RefreshGameDirectory(); + game_list->RefreshExternalContent(); SetFirmwareVersion(); } @@ -4185,9 +4010,8 @@ void MainWindow::OnCreateHomeMenuApplicationMenuShortcut() { } void MainWindow::OnCaptureScreenshot() { - if (emu_thread == nullptr || !emu_thread->IsRunning()) { + if (QtCommon::emu_thread == nullptr || !QtCommon::emu_thread->IsRunning()) return; - } const u64 title_id = QtCommon::system->GetApplicationProcessProgramID(); const auto screenshot_path = @@ -4199,9 +4023,8 @@ void MainWindow::OnCaptureScreenshot() { .arg(title_id, 16, 16, QLatin1Char{'0'}) .arg(date); - if (!Common::FS::CreateDir(screenshot_path.toStdString())) { + if (!Common::FS::CreateDir(screenshot_path.toStdString())) return; - } #ifdef _WIN32 if (UISettings::values.enable_screenshot_save_as) { @@ -4228,6 +4051,41 @@ void MainWindow::OnEmulatorUpdateAvailable() { } #endif +void MainWindow::OnApplicationChanged(u64 program_id) { + if (!emulation_running || program_id == 0) + return; + + std::string title_name; + std::string title_version; + + const FileSys::PatchManager pm(program_id, QtCommon::system->GetFileSystemController(), + QtCommon::system->GetContentProvider()); + if (const auto metadata = pm.GetControlMetadata(); metadata.first != nullptr) { + title_version = metadata.first->GetVersionString(); + title_name = metadata.first->GetApplicationName(); + } + if (title_name.empty()) { + title_name = fmt::format("{:016X}", program_id); + } + + if (const auto* process = QtCommon::system->Kernel().ApplicationProcess(); process != nullptr) { + const auto instruction_set_suffix = process->Is64Bit() ? tr("(64-bit)") : tr("(32-bit)"); + title_name = + tr("%1 %2", "%1 is the title name. %2 indicates if the title is 64-bit or 32-bit") + .arg(QString::fromStdString(title_name), instruction_set_suffix) + .toStdString(); + } + + LOG_INFO(Frontend, "Now running: {:016X} | {} | {}", program_id, title_name, title_version); + + UpdateWindowTitle(title_name, title_version, + QtCommon::system->GPU().Renderer().GetDeviceVendor()); + + // Switch to record playtime for the running program + if (play_time_manager) + play_time_manager->SetProgramId(program_id); +} + void MainWindow::UpdateWindowTitle(std::string_view title_name, std::string_view title_version, std::string_view gpu_vendor) { static const std::string build_id = std::string{Common::g_build_id}; @@ -4307,16 +4165,15 @@ void MainWindow::OnTasStateChanged() { } void MainWindow::UpdateStatusBar() { - if (emu_thread == nullptr || !QtCommon::system->IsPoweredOn()) { + if (QtCommon::emu_thread == nullptr || !QtCommon::system->IsPoweredOn()) { status_bar_update_timer.stop(); return; } - if (Settings::values.tas_enable) { + if (Settings::values.tas_enable) tas_label->setText(GetTasStateDescription()); - } else { + else tas_label->clear(); - } auto results = QtCommon::system->GetAndResetPerfStats(); auto& shader_notify = QtCommon::system->GPU().ShaderNotify(); @@ -4384,8 +4241,7 @@ void MainWindow::UpdateAPIText() { void MainWindow::UpdateFilterText() { const auto filter = Settings::values.scaling_filter.GetValue(); const auto filter_text = ConfigurationShared::scaling_filter_texts_map.find(filter)->second; - filter_status_button->setText(filter == Settings::ScalingFilter::Fsr ? tr("FSR") - : filter_text.toUpper()); + filter_status_button->setText(filter_text.toUpper()); } void MainWindow::UpdateAAText() { @@ -4396,6 +4252,26 @@ void MainWindow::UpdateAAText() { : aa_text.toUpper()); } +#ifdef HAS_RESHADE +void MainWindow::UpdatePostShaderText() { + const bool enabled = Settings::values.post_shader_enabled.GetValue(); + post_shader_status_button->setChecked(enabled); + + if (!enabled) { + post_shader_status_button->setText(tr("NO FX")); + return; + } + + const std::string preset = VideoCore::GetActiveFxPreset(); + if (preset.empty()) { + post_shader_status_button->setText(tr("FX")); + return; + } + + post_shader_status_button->setText(QString::fromStdString(preset).toUpper()); +} +#endif + void MainWindow::UpdateVolumeUI() { const auto volume_value = static_cast(Settings::values.volume.GetValue()); volume_slider->setValue(volume_value); @@ -4438,14 +4314,13 @@ void MainWindow::UpdateUISettings() { } void MainWindow::UpdateInputDrivers() { - if (!input_subsystem) { + if (!input_subsystem) return; - } input_subsystem->PumpEvents(); } void MainWindow::HideMouseCursor() { - if (emu_thread == nullptr && UISettings::values.hide_mouse) { + if (QtCommon::emu_thread == nullptr && UISettings::values.hide_mouse) { mouse_hide_timer.stop(); ShowMouseCursor(); return; @@ -4455,9 +4330,8 @@ void MainWindow::HideMouseCursor() { void MainWindow::ShowMouseCursor() { render_window->unsetCursor(); - if (emu_thread != nullptr && UISettings::values.hide_mouse) { + if (QtCommon::emu_thread != nullptr && UISettings::values.hide_mouse) mouse_hide_timer.start(); - } } void MainWindow::OnMouseActivity() { @@ -4637,14 +4511,14 @@ bool MainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed } bool MainWindow::ConfirmClose() { - if (emu_thread == nullptr || - UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Never) { + if (QtCommon::emu_thread == nullptr || + UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Never) return true; - } + if (!QtCommon::system->GetExitLocked() && - UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Based_On_Game) { + UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Based_On_Game) return true; - } + const auto text = tr("Are you sure you want to close Eden?"); return question(this, tr("Eden"), text); } @@ -4665,9 +4539,8 @@ void MainWindow::closeEvent(QCloseEvent* event) { game_list->UnloadController(); // Shutdown session if the emu thread is active... - if (emu_thread != nullptr) { + if (QtCommon::emu_thread != nullptr) ShutdownGame(); - } render_window->close(); multiplayer_state->Close(); @@ -4730,7 +4603,7 @@ void MainWindow::dragMoveEvent(QDragMoveEvent* event) { } bool MainWindow::ConfirmChangeGame() { - if (emu_thread == nullptr) + if (QtCommon::emu_thread == nullptr) return true; // Use custom question to link controller navigation @@ -4741,9 +4614,9 @@ bool MainWindow::ConfirmChangeGame() { } bool MainWindow::ConfirmForceLockedExit() { - if (emu_thread == nullptr) { + if (QtCommon::emu_thread == nullptr) return true; - } + const auto text = tr("The currently running application has requested Eden to not exit.\n\n" "Would you like to bypass this and exit anyway?"); @@ -4751,9 +4624,8 @@ bool MainWindow::ConfirmForceLockedExit() { } void MainWindow::RequestGameExit() { - if (!QtCommon::system->IsPoweredOn()) { + if (!QtCommon::system->IsPoweredOn()) return; - } QtCommon::system->SetExitRequested(true); QtCommon::system->GetAppletManager().RequestExit(); @@ -4766,32 +4638,29 @@ void MainWindow::filterBarSetChecked(bool state) { static void AdjustLinkColor() { QPalette new_pal(qApp->palette()); - if (UISettings::IsDarkTheme()) { + + if (UISettings::IsDarkTheme()) new_pal.setColor(QPalette::Link, QColor(0, 190, 255, 255)); - } else { + else new_pal.setColor(QPalette::Link, QColor(0, 140, 200, 255)); - } - if (qApp->palette().color(QPalette::Link) != new_pal.color(QPalette::Link)) { + + if (qApp->palette().color(QPalette::Link) != new_pal.color(QPalette::Link)) qApp->setPalette(new_pal); - } } void MainWindow::UpdateUITheme() { - const QString default_theme = QString::fromUtf8( - UISettings::themes[static_cast(UISettings::default_theme)].second); + const QString default_theme = QString::fromUtf8(UISettings::themes[size_t(UISettings::default_theme)].second); QString current_theme = QString::fromStdString(UISettings::values.theme); - if (current_theme.isEmpty()) { + if (current_theme.isEmpty()) current_theme = default_theme; - } #ifdef _WIN32 QIcon::setThemeName(current_theme); AdjustLinkColor(); #else if (current_theme == QStringLiteral("default") || current_theme == QStringLiteral("colorful")) { - QIcon::setThemeName(current_theme == QStringLiteral("colorful") ? current_theme - : startup_icon_theme); + QIcon::setThemeName(current_theme == QStringLiteral("colorful") ? current_theme : startup_icon_theme); QIcon::setThemeSearchPaths(QStringList(default_theme_paths)); if (isDarkMode()) { current_theme = QStringLiteral("default_dark"); @@ -4844,17 +4713,15 @@ void MainWindow::LoadTranslation() { QStringLiteral(":/languages/")); } - if (loaded) { + if (loaded) qApp->installTranslator(&translator); - } else { + else UISettings::values.language = std::string("en"); - } } void MainWindow::OnLanguageChanged(const QString& locale) { - if (UISettings::values.language.GetValue() != std::string("en")) { + if (UISettings::values.language.GetValue() != std::string("en")) qApp->removeTranslator(&translator); - } QList actions = game_size_actions->actions(); for (size_t i = 0; i < default_game_icon_sizes.size(); i++) { @@ -4870,11 +4737,10 @@ void MainWindow::OnLanguageChanged(const QString& locale) { void MainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { #ifdef USE_DISCORD_PRESENCE - if (state) { + if (state) discord_rpc = std::make_unique(*QtCommon::system); - } else { + else discord_rpc = std::make_unique(); - } #else discord_rpc = std::make_unique(); #endif @@ -4957,6 +4823,6 @@ void VolumeButton::ResetMultiplier() { #endif #if !defined(QT_STATICPLUGIN) || defined(__APPLE__) -#define VMA_IMPLEMENTATION +#define VMA_IMPLEMENTATION 1 #include "video_core/vulkan_common/vma.h" #endif diff --git a/src/yuzu/main_window.h b/src/yuzu/main_window.h index b676c0534a..8aedeec721 100644 --- a/src/yuzu/main_window.h +++ b/src/yuzu/main_window.h @@ -20,11 +20,10 @@ #include "common/common_types.h" #include "common/settings_enums.h" #include "frontend_common/content_manager.h" -#include "frontend_common/update_checker.h" #include "input_common/drivers/tas_input.h" #include "qt_common/config/qt_config.h" +#include "qt_common/qt_common.h" #include "qt_common/util/game.h" -#include "yuzu/compatibility_list.h" #include "yuzu/hotkeys.h" #include "yuzu/user_data_migration.h" @@ -38,6 +37,7 @@ #ifdef ENABLE_UPDATE_CHECKER #include #include +#include "common/net/net.h" #endif class QtConfig; @@ -56,6 +56,9 @@ class QSlider; class QHBoxLayout; class WaitTreeWidget; class PerformanceOverlay; +#ifdef HAS_RESHADE +class ConfigurePostProcessing; +#endif enum class GameListOpenTarget; enum class DumpRomFSTarget; class GameListPlaceholder; @@ -66,11 +69,6 @@ class QtProfileSelectionDialog; class QtSoftwareKeyboardDialog; class QtNXWebEngineView; -enum class StartGameType { - Normal, // Can use custom configuration - Global, // Only uses global configuration -}; - namespace VideoCore { class ShaderNotify; } @@ -184,11 +182,8 @@ signals: * Signal that is emitted when a new EmuThread has been created and an emulation session is * about to start. At this time, the core system emulation has been initialized, and all * emulation handles and memory should be valid. - * - * @param emu_thread Pointer to the newly created EmuThread (to be used by widgets that need to - * access/change emulation state). */ - void EmulationStarting(EmuThread* emu_thread); + void EmulationStarting(); /** * Signal that is emitted when emulation is about to stop. At this time, the EmuThread and core @@ -344,8 +339,7 @@ private slots: void OnPrepareForSleep(bool prepare_sleep); void OnMenuReportCompatibility(); void OnOpenModsPage(); - void OnOpenQuickstartGuide(); - void OnOpenFAQ(); + void OnOpenUserHandbook(); /// Called whenever a user selects a game in the game list widget. void OnGameListLoadFile(QString game_path, u64 program_id); @@ -359,8 +353,6 @@ private slots: void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); void OnGameListVerifyIntegrity(const std::string& game_path); void OnGameListCopyTID(u64 program_id); - void OnGameListNavigateToGamedbEntry(u64 program_id, - const CompatibilityList& compatibility_list); void OnGameListCreateShortcut(u64 program_id, const std::string& game_path, const QtCommon::Game::ShortcutTarget target); void OnGameListOpenDirectory(const QString& directory); @@ -402,6 +394,9 @@ private slots: void OnToggleFilterBar(); void OnToggleStatusBar(); void OnTogglePerfOverlay(); +#ifdef HAS_RESHADE + void OnPostProcessingShaders(); +#endif void OnGameListRefresh(); void InitializeHotkeys(); void ToggleFullscreen(); @@ -417,6 +412,7 @@ private slots: void SetGameListMode(Settings::GameListMode mode); void SetGridView(); void SetTreeView(); + void SetCarouselView(); void CheckIconSize(); void ToggleShowGameName(); @@ -447,10 +443,12 @@ private: ContentManager::InstallResult InstallNCA(const QString& filename); void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {}, std::string_view gpu_vendor = {}); + void OnApplicationChanged(u64 program_id); void UpdateDockedButton(); void UpdateAPIText(); void UpdateFilterText(); void UpdateAAText(); + void UpdatePostShaderText(); void UpdateVolumeUI(); void UpdateStatusBar(); void UpdateGPUAccuracyButton(); @@ -465,7 +463,6 @@ private: bool CheckFirmwarePresence(); void SetFirmwareVersion(); void SetFPSSuffix(); - void ConfigureFilesystemProvider(const std::string& filepath); /** * Open (or not) the right confirm dialog based on current setting and game exit lock * @returns true if the player confirmed or the settings do no require it @@ -505,6 +502,9 @@ private: QTimer shutdown_timer; OverlayDialog* shutdown_dialog{}; PerformanceOverlay* perf_overlay = nullptr; +#ifdef HAS_RESHADE + ConfigurePostProcessing* post_processing_dialog = nullptr; +#endif GameListPlaceholder* game_list_placeholder = nullptr; @@ -525,6 +525,7 @@ private: QPushButton* dock_status_button = nullptr; QPushButton* filter_status_button = nullptr; QPushButton* aa_status_button = nullptr; + QPushButton* post_shader_status_button = nullptr; VolumeButton* volume_button = nullptr; QWidget* volume_popup = nullptr; QSlider* volume_slider = nullptr; @@ -538,7 +539,6 @@ private: // Whether emulation is currently running in yuzu. bool emulation_running = false; - std::unique_ptr emu_thread; // The path to the game currently running QString current_game_path; // Whether a user was set on the command line (skips UserSelector if it's forced to show up) @@ -599,8 +599,6 @@ private: const std::string& game_title, QtCommon::Game::ShortcutTarget target, std::string arguments, const bool needs_title); - void InstallFirmware(const QString& location, bool recursive = false); - protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; diff --git a/src/yuzu/migration_dialog.h b/src/yuzu/migration_dialog.h index a2a2c80358..1cd0f75760 100644 --- a/src/yuzu/migration_dialog.h +++ b/src/yuzu/migration_dialog.h @@ -1,8 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef MIGRATION_DIALOG_H -#define MIGRATION_DIALOG_H +#pragma once #include #include @@ -29,5 +28,3 @@ private: QAbstractButton* m_clickedButton; }; - -#endif // MIGRATION_DIALOG_H diff --git a/src/yuzu/migration_worker.cpp b/src/yuzu/migration_worker.cpp index ba5e50e187..214356772f 100644 --- a/src/yuzu/migration_worker.cpp +++ b/src/yuzu/migration_worker.cpp @@ -38,7 +38,7 @@ void MigrationWorker::process() { try { fs::remove_all(eden_dir); } catch (fs::filesystem_error& _) { - // ignore because linux does stupid crap sometimes + // directory may not exist at this point, that's fine } switch (strategy) { diff --git a/src/yuzu/migration_worker.h b/src/yuzu/migration_worker.h index 42abedb9c8..0a8a5b8275 100644 --- a/src/yuzu/migration_worker.h +++ b/src/yuzu/migration_worker.h @@ -1,8 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef MIGRATION_WORKER_H -#define MIGRATION_WORKER_H +#pragma once #include #include "common/fs/path_util.h" @@ -73,5 +72,3 @@ private: MigrationStrategy strategy; QString success_text = tr("Data was migrated successfully."); }; - -#endif // MIGRATION_WORKER_H diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp index e274aaf62d..c28ae31bee 100644 --- a/src/yuzu/multiplayer/chat_room.cpp +++ b/src/yuzu/multiplayer/chat_room.cpp @@ -20,7 +20,7 @@ #include "common/logging.h" #include "network/announce_multiplayer_session.h" #include "ui_chat_room.h" -#include "yuzu/game/game_list_p.h" +#include "qt_common/game_list/game_list_p.h" #include "yuzu/multiplayer/chat_room.h" #include "yuzu/multiplayer/message.h" #ifdef ENABLE_WEB_SERVICE diff --git a/src/yuzu/multiplayer/chat_room.h b/src/yuzu/multiplayer/chat_room.h index 44ec8952d1..1ee87758d3 100644 --- a/src/yuzu/multiplayer/chat_room.h +++ b/src/yuzu/multiplayer/chat_room.h @@ -10,7 +10,8 @@ #include #include #include -#include +#include "common/container/unordered_map.h" +#include "common/container/unordered_set.h" #include "network/network.h" namespace Ui { @@ -65,8 +66,8 @@ private: bool has_mod_perms = false; QStandardItemModel* player_list; std::unique_ptr ui; - ankerl::unordered_dense::set block_list; - ankerl::unordered_dense::map icon_cache; + ::Common::unordered_set block_list; + ::Common::unordered_map icon_cache; }; Q_DECLARE_METATYPE(Network::ChatEntry); diff --git a/src/yuzu/multiplayer/client_room.cpp b/src/yuzu/multiplayer/client_room.cpp index 391ca56c05..31e5e20e52 100644 --- a/src/yuzu/multiplayer/client_room.cpp +++ b/src/yuzu/multiplayer/client_room.cpp @@ -14,7 +14,7 @@ #include "common/logging.h" #include "network/announce_multiplayer_session.h" #include "ui_client_room.h" -#include "yuzu/game/game_list_p.h" +#include "qt_common/game_list/game_list_p.h" #include "yuzu/multiplayer/client_room.h" #include "yuzu/multiplayer/message.h" #include "yuzu/multiplayer/moderation_dialog.h" diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp index 2a5530e02c..ac6017e71f 100644 --- a/src/yuzu/multiplayer/host_room.cpp +++ b/src/yuzu/multiplayer/host_room.cpp @@ -20,7 +20,7 @@ #include "network/announce_multiplayer_session.h" #include "qt_common/config/uisettings.h" #include "ui_host_room.h" -#include "yuzu/game/game_list_p.h" +#include "qt_common/game_list/game_list_p.h" #include "yuzu/main_window.h" #include "yuzu/multiplayer/host_room.h" #include "yuzu/multiplayer/message.h" diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp index 8e9ff6563f..d9e752896a 100644 --- a/src/yuzu/multiplayer/lobby.cpp +++ b/src/yuzu/multiplayer/lobby.cpp @@ -15,7 +15,7 @@ #include "network/network.h" #include "qt_common/config/uisettings.h" #include "ui_lobby.h" -#include "yuzu/game/game_list_p.h" +#include "qt_common/game_list/game_list_p.h" #include "yuzu/main_window.h" #include "yuzu/multiplayer/client_room.h" #include "yuzu/multiplayer/lobby.h" diff --git a/src/yuzu/ryujinx_dialog.h b/src/yuzu/ryujinx_dialog.h index c067499cc1..e668cf5ca0 100644 --- a/src/yuzu/ryujinx_dialog.h +++ b/src/yuzu/ryujinx_dialog.h @@ -1,8 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later -#ifndef RYUJINX_DIALOG_H -#define RYUJINX_DIALOG_H +#pragma once #include #include @@ -28,5 +27,3 @@ private: std::filesystem::path m_eden; std::filesystem::path m_ryu; }; - -#endif // RYUJINX_DIALOG_H diff --git a/src/yuzu/updater/update_dialog.cpp b/src/yuzu/updater/update_dialog.cpp index c2c0556e9c..f40fced357 100644 --- a/src/yuzu/updater/update_dialog.cpp +++ b/src/yuzu/updater/update_dialog.cpp @@ -46,6 +46,7 @@ UpdateDialog::UpdateDialog(const Common::Net::Release& release, QWidget* parent) QDesktopServices::openUrl(QUrl{QString::fromStdString(release.html_url)}); }); } else if (assets.size() == 1) { + ui->groupBox->setHidden(true); m_asset = assets[0]; connect(this, &QDialog::accepted, this, &UpdateDialog::Download); @@ -73,6 +74,7 @@ UpdateDialog::~UpdateDialog() { delete ui; } +// TODO: migrate to a net.cpp wrapper void UpdateDialog::Download() { const auto filename = QtCommon::Frontend::GetSaveFileName( tr("New Version Location"), @@ -160,7 +162,7 @@ void UpdateDialog::Download() { QString::number(response.status))); return; } - if (!response.headers.contains("content-type")) { + if (!response.has_header("content-type")) { LOG_ERROR(Frontend, "GET to {}{} returned no content", m_asset.url, m_asset.path); return; } diff --git a/src/yuzu/updater/update_dialog.h b/src/yuzu/updater/update_dialog.h index 692481b710..e821ef660d 100644 --- a/src/yuzu/updater/update_dialog.h +++ b/src/yuzu/updater/update_dialog.h @@ -15,7 +15,7 @@ class UpdateDialog : public QDialog { Q_OBJECT public: - explicit UpdateDialog(const Common::Net::Release &release, QWidget* parent = nullptr); + explicit UpdateDialog(const Common::Net::Release& release, QWidget* parent = nullptr); ~UpdateDialog(); private slots: @@ -23,6 +23,6 @@ private slots: private: Ui::UpdateDialog* ui; - QList m_buttons; + QList m_buttons; Common::Net::Asset m_asset; }; diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp index a3933d9b63..eb6583b358 100644 --- a/src/yuzu/util/util.cpp +++ b/src/yuzu/util/util.cpp @@ -4,14 +4,11 @@ // SPDX-FileCopyrightText: 2015 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include #include #include "applets/qt_profile_select.h" -#include "common/logging.h" #include "core/frontend/applets/profile_select.h" #include "core/hle/service/acc/profile_manager.h" -#include "frontend_common/data_manager.h" #include "qt_common/qt_common.h" #include "yuzu/util/util.h" @@ -28,126 +25,6 @@ QFont GetMonospaceFont() { return font; } -QString ReadableByteSize(qulonglong size) { - return QString::fromStdString(FrontendCommon::DataManager::ReadableBytesSize(size)); -} - -QPixmap CreateCirclePixmapFromColor(const QColor& color) { - QPixmap circle_pixmap(16, 16); - circle_pixmap.fill(Qt::transparent); - QPainter painter(&circle_pixmap); - painter.setRenderHint(QPainter::Antialiasing); - painter.setPen(color); - painter.setBrush(color); - painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0); - return circle_pixmap; -} - -bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image) { -#if defined(WIN32) -#pragma pack(push, 2) - struct IconDir { - WORD id_reserved; - WORD id_type; - WORD id_count; - }; - - struct IconDirEntry { - BYTE width; - BYTE height; - BYTE color_count; - BYTE reserved; - WORD planes; - WORD bit_count; - DWORD bytes_in_res; - DWORD image_offset; - }; -#pragma pack(pop) - - const QImage source_image = image.convertToFormat(QImage::Format_RGB32); - constexpr std::array scale_sizes{256, 128, 64, 48, 32, 24, 16}; - constexpr int bytes_per_pixel = 4; - - const IconDir icon_dir{ - .id_reserved = 0, - .id_type = 1, - .id_count = static_cast(scale_sizes.size()), - }; - - Common::FS::IOFile icon_file(icon_path.string(), Common::FS::FileAccessMode::Write, - Common::FS::FileType::BinaryFile); - if (!icon_file.IsOpen()) { - return false; - } - - if (!icon_file.Write(icon_dir)) { - return false; - } - - std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size()); - for (std::size_t i = 0; i < scale_sizes.size(); i++) { - const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel; - const IconDirEntry icon_entry{ - .width = static_cast(scale_sizes[i]), - .height = static_cast(scale_sizes[i]), - .color_count = 0, - .reserved = 0, - .planes = 1, - .bit_count = bytes_per_pixel * 8, - .bytes_in_res = static_cast(sizeof(BITMAPINFOHEADER) + image_size), - .image_offset = static_cast(image_offset), - }; - image_offset += icon_entry.bytes_in_res; - if (!icon_file.Write(icon_entry)) { - return false; - } - } - - for (std::size_t i = 0; i < scale_sizes.size(); i++) { - const QImage scaled_image = source_image.scaled( - scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - const BITMAPINFOHEADER info_header{ - .biSize = sizeof(BITMAPINFOHEADER), - .biWidth = scaled_image.width(), - .biHeight = scaled_image.height() * 2, - .biPlanes = 1, - .biBitCount = bytes_per_pixel * 8, - .biCompression = BI_RGB, - .biSizeImage{}, - .biXPelsPerMeter{}, - .biYPelsPerMeter{}, - .biClrUsed{}, - .biClrImportant{}, - }; - - if (!icon_file.Write(info_header)) { - return false; - } - - for (int y = 0; y < scaled_image.height(); y++) { - const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y); - std::vector line_data(scaled_image.width() * bytes_per_pixel); - std::memcpy(line_data.data(), line, line_data.size()); - if (!icon_file.Write(line_data)) { - return false; - } - } - } - icon_file.Close(); - - return true; -#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"); - } else { - LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string()); - } - return true; -#else - return false; -#endif -} const std::optional GetProfileID() { // if there's only a single profile, the user probably wants to use that... right? const auto& profiles = QtCommon::system->GetProfileManager().FindExistingProfileUUIDs(); diff --git a/src/yuzu/util/util.h b/src/yuzu/util/util.h index 7b482aa11d..2e4e325e22 100644 --- a/src/yuzu/util/util.h +++ b/src/yuzu/util/util.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2015 Citra Emulator Project @@ -6,7 +6,6 @@ #pragma once -#include #include #include #include "common/uuid.h" @@ -14,24 +13,6 @@ /// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc. [[nodiscard]] QFont GetMonospaceFont(); -/// Convert a size in bytes into a readable format (KiB, MiB, etc.) -[[nodiscard]] QString ReadableByteSize(qulonglong size); - -/** - * Creates a circle pixmap from a specified color - * @param color The color the pixmap shall have - * @return QPixmap circle pixmap - */ -[[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color); - -/** - * Saves a windows icon to a file - * @param path The icons path - * @param image The image to save - * @return bool If the operation succeeded - */ -[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image); - /** * Prompt the user for a profile ID. If there is only one valid profile, returns that profile. * @return The selected profile, or an std::nullopt if none were selected diff --git a/src/yuzu/vk_device_info.h b/src/yuzu/vk_device_info.h deleted file mode 100644 index bda8262f4e..0000000000 --- a/src/yuzu/vk_device_info.h +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#include -#include -#include "common/common_types.h" -#include "vulkan/vulkan_core.h" - -class QWindow; - -namespace Settings { -enum class VSyncMode : u32; -} -// #include "common/settings.h" - -namespace VkDeviceInfo { -// Short class to record Vulkan driver information for configuration purposes -class Record { -public: - explicit Record(std::string_view name, const std::vector& vsync_modes, - bool has_broken_compute); - ~Record(); - - const std::string name; - const std::vector vsync_support; - const bool has_broken_compute; -}; - -void PopulateRecords(std::vector& records, QWindow* window); -} // namespace VkDeviceInfo diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt index 35df53f381..17a1545167 100644 --- a/src/yuzu_cmd/CMakeLists.txt +++ b/src/yuzu_cmd/CMakeLists.txt @@ -17,20 +17,20 @@ endfunction() if (ENABLE_OPENGL) list(APPEND OPENGL_SOURCES - emu_window/emu_window_sdl2_gl.cpp - emu_window/emu_window_sdl2_gl.h + emu_window/emu_window_sdl3_gl.cpp + emu_window/emu_window_sdl3_gl.h ) else() set(OPENGL_SOURCES "") endif() add_executable(yuzu-cmd - emu_window/emu_window_sdl2.cpp - emu_window/emu_window_sdl2.h - emu_window/emu_window_sdl2_null.cpp - emu_window/emu_window_sdl2_null.h - emu_window/emu_window_sdl2_vk.cpp - emu_window/emu_window_sdl2_vk.h + emu_window/emu_window_sdl3.cpp + emu_window/emu_window_sdl3.h + emu_window/emu_window_sdl3_null.cpp + emu_window/emu_window_sdl3_null.h + emu_window/emu_window_sdl3_vk.cpp + emu_window/emu_window_sdl3_vk.h sdl_config.cpp sdl_config.h yuzu.cpp @@ -42,15 +42,12 @@ target_link_libraries(yuzu-cmd PRIVATE common core input_common frontend_common if (ENABLE_OPENGL) target_link_libraries(yuzu-cmd PRIVATE glad) endif() -if (MSVC) - target_link_libraries(yuzu-cmd PRIVATE getopt) -endif() target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) create_resource("../../dist/eden.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon") target_include_directories(yuzu-cmd PRIVATE ${RESOURCES_DIR}) -target_link_libraries(yuzu-cmd PRIVATE SDL2::SDL2) +target_link_libraries(yuzu-cmd PRIVATE SDL3::SDL3) if(UNIX AND NOT APPLE) install(TARGETS yuzu-cmd) diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp deleted file mode 100644 index c54e5b4fda..0000000000 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 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 - -#include -#include -#include - -#include - -#include "common/logging.h" -#include "common/scm_rev.h" -#include "video_core/renderer_vulkan/renderer_vulkan.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h" - -#include -#include - -EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem_, - Core::System& system_, bool fullscreen) - : EmuWindow_SDL2{input_subsystem_, system_} { - const std::string window_title = fmt::format("Eden {} | {}-{} (Vulkan)", - Common::g_build_name, - Common::g_scm_branch, - Common::g_scm_desc); - render_window = - SDL_CreateWindow(window_title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height, - SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); - - SDL_SysWMinfo wm; - SDL_VERSION(&wm.version); - if (SDL_GetWindowWMInfo(render_window, &wm) == SDL_FALSE) { - LOG_CRITICAL(Frontend, "Failed to get information from the window manager: {}", - SDL_GetError()); - std::exit(EXIT_FAILURE); - } - - SetWindowIcon(); - - if (fullscreen) { - Fullscreen(); - ShowCursor(false); - } - - switch (wm.subsystem) { -#ifdef SDL_VIDEO_DRIVER_WINDOWS - case SDL_SYSWM_TYPE::SDL_SYSWM_WINDOWS: - window_info.type = Core::Frontend::WindowSystemType::Windows; - window_info.render_surface = reinterpret_cast(wm.info.win.window); - break; -#endif -#ifdef SDL_VIDEO_DRIVER_X11 - case SDL_SYSWM_TYPE::SDL_SYSWM_X11: - window_info.type = Core::Frontend::WindowSystemType::X11; - window_info.display_connection = wm.info.x11.display; - window_info.render_surface = reinterpret_cast(wm.info.x11.window); - break; -#endif -#ifdef SDL_VIDEO_DRIVER_WAYLAND - case SDL_SYSWM_TYPE::SDL_SYSWM_WAYLAND: - window_info.type = Core::Frontend::WindowSystemType::Wayland; - window_info.display_connection = wm.info.wl.display; - window_info.render_surface = wm.info.wl.surface; - break; -#endif -#ifdef SDL_VIDEO_DRIVER_COCOA - case SDL_SYSWM_TYPE::SDL_SYSWM_COCOA: - window_info.type = Core::Frontend::WindowSystemType::Cocoa; - window_info.render_surface = SDL_Metal_CreateView(render_window); - break; -#endif -#ifdef SDL_VIDEO_DRIVER_ANDROID - case SDL_SYSWM_TYPE::SDL_SYSWM_ANDROID: - window_info.type = Core::Frontend::WindowSystemType::Android; - window_info.render_surface = reinterpret_cast(wm.info.android.window); - break; -#endif - default: - LOG_CRITICAL(Frontend, "Window manager subsystem {} not implemented", wm.subsystem); - std::exit(EXIT_FAILURE); - break; - } - - OnResize(); - OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); - SDL_PumpEvents(); - LOG_INFO(Frontend, "Eden Version: {} | {}-{} (Vulkan)", Common::g_build_name, - Common::g_scm_branch, Common::g_scm_desc); -} - -EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() = default; - -std::unique_ptr EmuWindow_SDL2_VK::CreateSharedContext() const { - return std::make_unique(); -} diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp similarity index 50% rename from src/yuzu_cmd/emu_window/emu_window_sdl2.cpp rename to src/yuzu_cmd/emu_window/emu_window_sdl3.cpp index c00734216e..950e001f81 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp @@ -1,9 +1,14 @@ // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#include +#include +#ifdef __EMSCRIPTEN__ +#include +#endif #include "common/logging.h" #include "common/scm_rev.h" @@ -15,26 +20,39 @@ #include "input_common/drivers/mouse.h" #include "input_common/drivers/touch_screen.h" #include "input_common/main.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3.h" #include "yuzu_cmd/yuzu_icon.h" -EmuWindow_SDL2::EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_) +EmuWindow_SDL3::EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_) : input_subsystem{input_subsystem_}, system{system_} { input_subsystem->Initialize(); - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) { - LOG_CRITICAL(Frontend, "Failed to initialize SDL2: {}, Exiting...", SDL_GetError()); + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) { + LOG_CRITICAL(Frontend, "Failed to initialize SDL3: {}, Exiting...", SDL_GetError()); exit(1); } - SDL_SetMainReady(); + titlebar_timer = SDL_AddTimer(2000, [](void *userdata, SDL_TimerID, Uint32) -> Uint32 { + auto* this_ = (EmuWindow_SDL3*)userdata; + auto const results = this_->system.GetAndResetPerfStats(); + auto const title = fmt::format("{} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc, results.average_game_fps, results.emulation_speed * 100.0f); + SDL_SetWindowTitle(this_->render_window, title.c_str()); + return 2000; + }, this); + mouse_timer = SDL_AddTimer(100, [](void *userdata, SDL_TimerID, Uint32) -> Uint32 { + auto* this_ = (EmuWindow_SDL3*)userdata; + this_->input_subsystem->GetMouse()->NotifyChanged(); + return 100; + }, this); } -EmuWindow_SDL2::~EmuWindow_SDL2() { +EmuWindow_SDL3::~EmuWindow_SDL3() { + SDL_RemoveTimer(titlebar_timer); + SDL_RemoveTimer(mouse_timer); system.HIDCore().UnloadInputDevices(); input_subsystem->Shutdown(); SDL_Quit(); } -InputCommon::MouseButton EmuWindow_SDL2::SDLButtonToMouseButton(u32 button) const { +InputCommon::MouseButton EmuWindow_SDL3::SDLButtonToMouseButton(u32 button) const { switch (button) { case SDL_BUTTON_LEFT: return InputCommon::MouseButton::Left; @@ -52,7 +70,7 @@ InputCommon::MouseButton EmuWindow_SDL2::SDLButtonToMouseButton(u32 button) cons } /// @brief Translates pixel position to float position -EmuWindow_SDL2::FloatPairNonHFA EmuWindow_SDL2::MouseToTouchPos(s32 touch_x, s32 touch_y) const { +EmuWindow_SDL3::FloatPairNonHFA EmuWindow_SDL3::MouseToTouchPos(s32 touch_x, s32 touch_y) const { int w = 0, h = 0; SDL_GetWindowSize(render_window, &w, &h); const float fx = float(touch_x) / w; @@ -64,9 +82,9 @@ EmuWindow_SDL2::FloatPairNonHFA EmuWindow_SDL2::MouseToTouchPos(s32 touch_x, s32 }; } -void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { +void EmuWindow_SDL3::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { const auto mouse_button = SDLButtonToMouseButton(button); - if (state == SDL_PRESSED) { + if (state != 0) { auto const [touch_x, touch_y, _] = MouseToTouchPos(x, y); input_subsystem->GetMouse()->PressButton(x, y, mouse_button); input_subsystem->GetMouse()->PressMouseButton(mouse_button); @@ -74,66 +92,73 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { } else { input_subsystem->GetMouse()->ReleaseButton(mouse_button); } + input_subsystem->GetMouse()->NotifyChanged(); } -void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { +void EmuWindow_SDL3::OnMouseMotion(s32 x, s32 y) { auto const [touch_x, touch_y, _] = MouseToTouchPos(x, y); input_subsystem->GetMouse()->Move(x, y, 0, 0); input_subsystem->GetMouse()->MouseMove(touch_x, touch_y); input_subsystem->GetMouse()->TouchMove(touch_x, touch_y); + input_subsystem->GetMouse()->NotifyChanged(); } -void EmuWindow_SDL2::OnFingerDown(float x, float y, std::size_t id) { +void EmuWindow_SDL3::OnFingerDown(float x, float y, std::size_t id) { input_subsystem->GetTouchScreen()->TouchPressed(x, y, id); } -void EmuWindow_SDL2::OnFingerMotion(float x, float y, std::size_t id) { +void EmuWindow_SDL3::OnFingerMotion(float x, float y, std::size_t id) { input_subsystem->GetTouchScreen()->TouchMoved(x, y, id); } -void EmuWindow_SDL2::OnFingerUp() { +void EmuWindow_SDL3::OnFingerUp() { input_subsystem->GetTouchScreen()->ReleaseAllTouch(); } -void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) { - if (state == SDL_PRESSED) { +void EmuWindow_SDL3::OnKeyEvent(int key, u8 state) { + if (state != 0) { input_subsystem->GetKeyboard()->PressKey(static_cast(key)); - } else if (state == SDL_RELEASED) { + } else { input_subsystem->GetKeyboard()->ReleaseKey(static_cast(key)); } } -bool EmuWindow_SDL2::IsOpen() const { +bool EmuWindow_SDL3::IsOpen() const { return is_open; } -bool EmuWindow_SDL2::IsShown() const { +bool EmuWindow_SDL3::IsShown() const { return is_shown; } -void EmuWindow_SDL2::OnResize() { +void EmuWindow_SDL3::OnResize() { int width, height; - SDL_GL_GetDrawableSize(render_window, &width, &height); + SDL_GetWindowSizeInPixels(render_window, &width, &height); UpdateCurrentFramebufferLayout(width, height); } -void EmuWindow_SDL2::ShowCursor(bool show_cursor) { - SDL_ShowCursor(show_cursor ? SDL_ENABLE : SDL_DISABLE); +void EmuWindow_SDL3::ShowCursor(bool show_cursor) { + if (show_cursor) { + SDL_ShowCursor(); + } else { + SDL_HideCursor(); + } } -void EmuWindow_SDL2::Fullscreen() { +void EmuWindow_SDL3::Fullscreen() { SDL_DisplayMode display_mode; switch (Settings::values.fullscreen_mode.GetValue()) { case Settings::FullscreenMode::Exclusive: - // Set window size to render size before entering fullscreen -- SDL2 does not resize window - // to display dimensions automatically in this mode. - if (SDL_GetDesktopDisplayMode(0, &display_mode) == 0) { + // Set window size to render size before entering fullscreen in exclusive mode. + if (const SDL_DisplayMode* display_mode_ptr = SDL_GetDesktopDisplayMode(SDL_GetDisplayForWindow(render_window))) { + display_mode = *display_mode_ptr; SDL_SetWindowSize(render_window, display_mode.w, display_mode.h); + SDL_SetWindowFullscreenMode(render_window, &display_mode); } else { LOG_ERROR(Frontend, "SDL_GetDesktopDisplayMode failed: {}", SDL_GetError()); } - if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN) == 0) { + if (SDL_SetWindowFullscreen(render_window, true)) { return; } @@ -141,7 +166,8 @@ void EmuWindow_SDL2::Fullscreen() { LOG_INFO(Frontend, "Attempting to use borderless fullscreen..."); [[fallthrough]]; case Settings::FullscreenMode::Borderless: - if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN_DESKTOP) == 0) { + SDL_SetWindowFullscreenMode(render_window, nullptr); + if (SDL_SetWindowFullscreen(render_window, true)) { return; } @@ -156,107 +182,76 @@ void EmuWindow_SDL2::Fullscreen() { } } -void EmuWindow_SDL2::WaitEvent() { - // Called on main thread - SDL_Event event; - - if (!SDL_WaitEvent(&event)) { - const char* error = SDL_GetError(); - if (!error || strcmp(error, "") == 0) { - // https://github.com/libsdl-org/SDL/issues/5780 - // Sometimes SDL will return without actually having hit an error condition; - // just ignore it in this case. - return; - } - - LOG_CRITICAL(Frontend, "SDL_WaitEvent failed: {}", error); - exit(1); - } - +void EmuWindow_SDL3::OnEvent(SDL_Event& event) { + // Notice how we skip the "update title" aspect on most events + // this is because some WMs do NOT like changing titles while resizing + // so let's just... not do that, thanks :) + // Afterall we don't really expect the user to pay attention to the titlebar + // while they're moving a lot of shit around... switch (event.type) { - case SDL_WINDOWEVENT: - switch (event.window.event) { - case SDL_WINDOWEVENT_SIZE_CHANGED: - case SDL_WINDOWEVENT_RESIZED: - case SDL_WINDOWEVENT_MAXIMIZED: - case SDL_WINDOWEVENT_RESTORED: - OnResize(); - break; - case SDL_WINDOWEVENT_MINIMIZED: - case SDL_WINDOWEVENT_EXPOSED: - is_shown = event.window.event == SDL_WINDOWEVENT_EXPOSED; - OnResize(); - break; - case SDL_WINDOWEVENT_CLOSE: - is_open = false; - break; - } - break; - case SDL_KEYDOWN: - case SDL_KEYUP: - OnKeyEvent(static_cast(event.key.keysym.scancode), event.key.state); - break; - case SDL_MOUSEMOTION: + case SDL_EVENT_WINDOW_RESIZED: + case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: + case SDL_EVENT_WINDOW_MAXIMIZED: + case SDL_EVENT_WINDOW_RESTORED: + return OnResize(); + case SDL_EVENT_WINDOW_MINIMIZED: + is_shown = false; + return OnResize(); + case SDL_EVENT_WINDOW_EXPOSED: + is_shown = true; + return OnResize(); + case SDL_EVENT_WINDOW_CLOSE_REQUESTED: + is_open = false; + return; + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + return OnKeyEvent(int(event.key.scancode), event.key.down ? 1 : 0); + case SDL_EVENT_MOUSE_MOTION: // ignore if it came from touch if (event.button.which != SDL_TOUCH_MOUSEID) OnMouseMotion(event.motion.x, event.motion.y); - break; - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: + return; + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: // ignore if it came from touch - if (event.button.which != SDL_TOUCH_MOUSEID) { - OnMouseButton(event.button.button, event.button.state, event.button.x, event.button.y); - } - break; - case SDL_FINGERDOWN: - OnFingerDown(event.tfinger.x, event.tfinger.y, - static_cast(event.tfinger.touchId)); - break; - case SDL_FINGERMOTION: - OnFingerMotion(event.tfinger.x, event.tfinger.y, - static_cast(event.tfinger.touchId)); - break; - case SDL_FINGERUP: - OnFingerUp(); - break; - case SDL_QUIT: + if (event.button.which != SDL_TOUCH_MOUSEID) + OnMouseButton(event.button.button, event.button.down ? 1 : 0, s32(event.button.x), s32(event.button.y)); + return; + case SDL_EVENT_FINGER_DOWN: + return OnFingerDown(event.tfinger.x, event.tfinger.y, std::size_t(event.tfinger.touchID)); + case SDL_EVENT_FINGER_MOTION: + return OnFingerMotion(event.tfinger.x, event.tfinger.y, std::size_t(event.tfinger.touchID)); + case SDL_EVENT_FINGER_UP: + return OnFingerUp(); + case SDL_EVENT_QUIT: is_open = false; - break; + return; default: break; } - - const u32 current_time = SDL_GetTicks(); - if (current_time > last_time + 2000) { - const auto results = system.GetAndResetPerfStats(); - const auto title = fmt::format("{} | {}-{} | FPS: {:.0f} ({:.0f}%)", - Common::g_build_fullname, - Common::g_scm_branch, - Common::g_scm_desc, - results.average_game_fps, - results.emulation_speed * 100.0); - SDL_SetWindowTitle(render_window, title.c_str()); - last_time = current_time; - } } // Credits to Samantas5855 and others for this function. -void EmuWindow_SDL2::SetWindowIcon() { - SDL_RWops* const yuzu_icon_stream = SDL_RWFromConstMem((void*)yuzu_icon, yuzu_icon_size); +void EmuWindow_SDL3::SetWindowIcon() { +#if defined(__EMSCRIPTEN__) || defined(__wasi__) + // Icons do not work yet +#else + SDL_IOStream* const yuzu_icon_stream = SDL_IOFromConstMem((void*)yuzu_icon, yuzu_icon_size); if (yuzu_icon_stream == nullptr) { LOG_WARNING(Frontend, "Failed to create Eden icon stream."); return; } - SDL_Surface* const window_icon = SDL_LoadBMP_RW(yuzu_icon_stream, 1); + SDL_Surface* const window_icon = SDL_LoadBMP_IO(yuzu_icon_stream, true); if (window_icon == nullptr) { LOG_WARNING(Frontend, "Failed to read BMP from stream."); return; } // The icon is attached to the window pointer SDL_SetWindowIcon(render_window, window_icon); - SDL_FreeSurface(window_icon); + SDL_DestroySurface(window_icon); +#endif } -void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair minimal_size) { +void EmuWindow_SDL3::OnMinimalClientAreaChangeRequest(std::pair minimal_size) { SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second); } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl3.h similarity index 85% rename from src/yuzu_cmd/emu_window/emu_window_sdl2.h rename to src/yuzu_cmd/emu_window/emu_window_sdl3.h index 8aec1efda0..ffabaeb143 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3.h @@ -1,5 +1,6 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -8,11 +9,12 @@ #include #include +#include +#include + #include "core/frontend/emu_window.h" #include "core/frontend/graphics_context.h" -struct SDL_Window; - namespace Core { class System; } @@ -22,10 +24,10 @@ class InputSubsystem; enum class MouseButton; } // namespace InputCommon -class EmuWindow_SDL2 : public Core::Frontend::EmuWindow { +class EmuWindow_SDL3 : public Core::Frontend::EmuWindow { public: - explicit EmuWindow_SDL2(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_); - ~EmuWindow_SDL2(); + explicit EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_); + ~EmuWindow_SDL3(); /// Whether the window is still open, and a close request hasn't yet been sent bool IsOpen() const; @@ -34,7 +36,7 @@ public: bool IsShown() const override; /// Wait for the next event on the main thread. - void WaitEvent(); + void OnEvent(SDL_Event& event); // Sets the window icon from yuzu.bmp void SetWindowIcon(); @@ -79,18 +81,21 @@ protected: /// Called when a configuration change affects the minimal size of the window void OnMinimalClientAreaChangeRequest(std::pair minimal_size) override; + /// Periodic changer of titlebar (independent of event loop) + SDL_TimerID titlebar_timer; + + // Mouse resetter once it + SDL_TimerID mouse_timer; + /// Is the window still open? bool is_open = true; /// Is the window being shown? bool is_shown = true; - /// Internal SDL2 render window + /// Internal SDL3 render window SDL_Window* render_window{}; - /// Keeps track of how often to update the title bar during gameplay - u32 last_time = 0; - /// Input subsystem to use with this window. InputCommon::InputSubsystem* input_subsystem; diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl3_gl.cpp similarity index 79% rename from src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp rename to src/yuzu_cmd/emu_window/emu_window_sdl3_gl.cpp index 448c902131..e86858a232 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3_gl.cpp @@ -9,7 +9,7 @@ #include #define SDL_MAIN_HANDLED -#include +#include #include #include @@ -20,7 +20,13 @@ #include "core/core.h" #include "input_common/main.h" #include "video_core/renderer_base.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3_gl.h" + +namespace { +void* SDLGLGetProcAddress(const char* proc_name) { + return reinterpret_cast(SDL_GL_GetProcAddress(proc_name)); +} +} // Anonymous namespace class SDLGLContext : public Core::Frontend::GraphicsContext { public: @@ -30,7 +36,7 @@ public: ~SDLGLContext() { DoneCurrent(); - SDL_GL_DeleteContext(context); + SDL_GL_DestroyContext(context); } void SwapBuffers() override { @@ -58,7 +64,7 @@ private: bool is_current = false; }; -bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() { +bool EmuWindow_SDL3_GL::SupportsRequiredGLExtensions() { std::vector unsupported_ext{}; #ifdef HAS_OPENGL // Extensions required to support some texture formats. @@ -72,9 +78,9 @@ bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() { return unsupported_ext.empty(); } -EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem_, +EmuWindow_SDL3_GL::EmuWindow_SDL3_GL(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_, bool fullscreen) - : EmuWindow_SDL2{input_subsystem_, system_} { + : EmuWindow_SDL3{input_subsystem_, system_} { SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); @@ -92,14 +98,13 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste std::string window_title = fmt::format("{} | {}-{}", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc); render_window = - SDL_CreateWindow(window_title.c_str(), - SDL_WINDOWPOS_UNDEFINED, // x position - SDL_WINDOWPOS_UNDEFINED, // y position - Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height, - SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + SDL_CreateWindow(window_title.c_str(), Layout::ScreenUndocked::Width, + Layout::ScreenUndocked::Height, + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | + SDL_WINDOW_HIGH_PIXEL_DENSITY); if (render_window == nullptr) { - LOG_CRITICAL(Frontend, "Failed to create SDL2 window! {}", SDL_GetError()); + LOG_CRITICAL(Frontend, "Failed to create SDL3 window! {}", SDL_GetError()); exit(1); } @@ -116,15 +121,15 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste core_context = CreateSharedContext(); if (window_context == nullptr) { - LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: {}", SDL_GetError()); + LOG_CRITICAL(Frontend, "Failed to create SDL3 GL context: {}", SDL_GetError()); exit(1); } if (core_context == nullptr) { - LOG_CRITICAL(Frontend, "Failed to create shared SDL2 GL context: {}", SDL_GetError()); + LOG_CRITICAL(Frontend, "Failed to create shared SDL3 GL context: {}", SDL_GetError()); exit(1); } - if (!gladLoadGLLoader(static_cast(SDL_GL_GetProcAddress))) { + if (!gladLoadGLLoader(static_cast(SDLGLGetProcAddress))) { LOG_CRITICAL(Frontend, "Failed to initialize GL functions! {}", SDL_GetError()); exit(1); } @@ -142,11 +147,11 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste Settings::LogSettings(); } -EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() { +EmuWindow_SDL3_GL::~EmuWindow_SDL3_GL() { core_context.reset(); - SDL_GL_DeleteContext(window_context); + SDL_GL_DestroyContext(window_context); } -std::unique_ptr EmuWindow_SDL2_GL::CreateSharedContext() const { +std::unique_ptr EmuWindow_SDL3_GL::CreateSharedContext() const { return std::make_unique(render_window); } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl3_gl.h similarity index 70% rename from src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h rename to src/yuzu_cmd/emu_window/emu_window_sdl3_gl.h index 39346e7047..73a7d3d482 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3_gl.h @@ -1,11 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 #pragma once #include +#include #include "core/frontend/emu_window.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3.h" namespace Core { class System; @@ -15,11 +19,11 @@ namespace InputCommon { class InputSubsystem; } -class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 { +class EmuWindow_SDL3_GL final : public EmuWindow_SDL3 { public: - explicit EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_, + explicit EmuWindow_SDL3_GL(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_, bool fullscreen); - ~EmuWindow_SDL2_GL(); + ~EmuWindow_SDL3_GL(); std::unique_ptr CreateSharedContext() const override; @@ -27,8 +31,6 @@ private: /// Whether the GPU and driver supports the OpenGL extension required bool SupportsRequiredGLExtensions(); - using SDL_GLContext = void*; - /// The OpenGL context associated with the window SDL_GLContext window_context; diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_null.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl3_null.cpp similarity index 67% rename from src/yuzu_cmd/emu_window/emu_window_sdl2_null.cpp rename to src/yuzu_cmd/emu_window/emu_window_sdl3_null.cpp index 4947080313..f5e4e19a30 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_null.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3_null.cpp @@ -13,25 +13,25 @@ #include "common/logging.h" #include "common/scm_rev.h" #include "video_core/renderer_null/renderer_null.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2_null.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3_null.h" -#ifdef YUZU_USE_EXTERNAL_SDL2 +#ifdef YUZU_USE_EXTERNAL_SDL3 // Include this before SDL.h to prevent the external from including a dummy #define USING_GENERATED_CONFIG_H -#include +#include #endif -#include +#include -EmuWindow_SDL2_Null::EmuWindow_SDL2_Null(InputCommon::InputSubsystem* input_subsystem_, +EmuWindow_SDL3_Null::EmuWindow_SDL3_Null(InputCommon::InputSubsystem* input_subsystem_, Core::System& system_, bool fullscreen) - : EmuWindow_SDL2{input_subsystem_, system_} { + : EmuWindow_SDL3{input_subsystem_, system_} { const std::string window_title = fmt::format("Eden {} | {}-{} (Vulkan)", Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc); render_window = - SDL_CreateWindow(window_title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height, - SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + SDL_CreateWindow(window_title.c_str(), Layout::ScreenUndocked::Width, + Layout::ScreenUndocked::Height, + SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY); SetWindowIcon(); @@ -47,8 +47,8 @@ EmuWindow_SDL2_Null::EmuWindow_SDL2_Null(InputCommon::InputSubsystem* input_subs Common::g_scm_branch, Common::g_scm_desc); } -EmuWindow_SDL2_Null::~EmuWindow_SDL2_Null() = default; +EmuWindow_SDL3_Null::~EmuWindow_SDL3_Null() = default; -std::unique_ptr EmuWindow_SDL2_Null::CreateSharedContext() const { +std::unique_ptr EmuWindow_SDL3_Null::CreateSharedContext() const { return std::make_unique(); } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_null.h b/src/yuzu_cmd/emu_window/emu_window_sdl3_null.h similarity index 58% rename from src/yuzu_cmd/emu_window/emu_window_sdl2_null.h rename to src/yuzu_cmd/emu_window/emu_window_sdl3_null.h index 35aee286df..7c046e72b8 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_null.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3_null.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -6,7 +9,7 @@ #include #include "core/frontend/emu_window.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3.h" namespace Core { class System; @@ -16,11 +19,11 @@ namespace InputCommon { class InputSubsystem; } -class EmuWindow_SDL2_Null final : public EmuWindow_SDL2 { +class EmuWindow_SDL3_Null final : public EmuWindow_SDL3 { public: - explicit EmuWindow_SDL2_Null(InputCommon::InputSubsystem* input_subsystem_, + explicit EmuWindow_SDL3_Null(InputCommon::InputSubsystem* input_subsystem_, Core::System& system, bool fullscreen); - ~EmuWindow_SDL2_Null() override; + ~EmuWindow_SDL3_Null() override; std::unique_ptr CreateSharedContext() const override; }; diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl3_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl3_vk.cpp new file mode 100644 index 0000000000..9019cbbba0 --- /dev/null +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3_vk.cpp @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 + +#include +#include +#include +#include + +#include + +#include "common/logging.h" +#include "common/scm_rev.h" +#include "video_core/renderer_vulkan/renderer_vulkan.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3_vk.h" + +#include +#include + +EmuWindow_SDL3_VK::EmuWindow_SDL3_VK(InputCommon::InputSubsystem* input_subsystem_, + Core::System& system_, bool fullscreen) + : EmuWindow_SDL3{input_subsystem_, system_} { + const std::string window_title = fmt::format("Eden {} | {}-{} (Vulkan)", + Common::g_build_name, + Common::g_scm_branch, + Common::g_scm_desc); + render_window = + SDL_CreateWindow(window_title.c_str(), Layout::ScreenUndocked::Width, + Layout::ScreenUndocked::Height, + SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY); + + const SDL_PropertiesID window_props = SDL_GetWindowProperties(render_window); + + SetWindowIcon(); + + if (fullscreen) { + Fullscreen(); + ShowCursor(false); + } + + if (void* hwnd = + SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr)) { + window_info.type = Core::Frontend::WindowSystemType::Windows; + window_info.render_surface = hwnd; + } else if (void* wl_display = + SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, + nullptr); + wl_display != nullptr) { + void* wl_surface = + SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr); + if (wl_surface == nullptr) { + LOG_CRITICAL(Frontend, "Wayland surface is unavailable"); + std::exit(EXIT_FAILURE); + } + window_info.type = Core::Frontend::WindowSystemType::Wayland; + window_info.display_connection = wl_display; + window_info.render_surface = wl_surface; + } else if (void* x11_display = + SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, + nullptr); + x11_display != nullptr) { + const auto x11_window = + SDL_GetNumberProperty(window_props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); + if (x11_window == 0) { + LOG_CRITICAL(Frontend, "X11 window handle is unavailable"); + std::exit(EXIT_FAILURE); + } + window_info.type = Core::Frontend::WindowSystemType::X11; + window_info.display_connection = x11_display; + window_info.render_surface = reinterpret_cast(static_cast(x11_window)); + } else if (SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, + nullptr) != nullptr) { + window_info.type = Core::Frontend::WindowSystemType::Cocoa; + window_info.render_surface = SDL_Metal_CreateView(render_window); + } else if (void* android_window = + SDL_GetPointerProperty(window_props, SDL_PROP_WINDOW_ANDROID_WINDOW_POINTER, + nullptr); + android_window != nullptr) { + window_info.type = Core::Frontend::WindowSystemType::Android; + window_info.render_surface = android_window; + } else { + LOG_CRITICAL(Frontend, "Unable to determine native window backend from SDL properties"); + std::exit(EXIT_FAILURE); + } + + OnResize(); + OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); + SDL_PumpEvents(); + LOG_INFO(Frontend, "Eden Version: {} | {}-{} (Vulkan)", Common::g_build_name, + Common::g_scm_branch, Common::g_scm_desc); +} + +EmuWindow_SDL3_VK::~EmuWindow_SDL3_VK() = default; + +std::unique_ptr EmuWindow_SDL3_VK::CreateSharedContext() const { + return std::make_unique(); +} diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h b/src/yuzu_cmd/emu_window/emu_window_sdl3_vk.h similarity index 59% rename from src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h rename to src/yuzu_cmd/emu_window/emu_window_sdl3_vk.h index 9467d164a4..5eb2626dd7 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3_vk.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 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 @@ -6,7 +9,7 @@ #include #include "core/frontend/emu_window.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3.h" namespace Core { class System; @@ -16,11 +19,11 @@ namespace InputCommon { class InputSubsystem; } -class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 { +class EmuWindow_SDL3_VK final : public EmuWindow_SDL3 { public: - explicit EmuWindow_SDL2_VK(InputCommon::InputSubsystem* input_subsystem_, Core::System& system, + explicit EmuWindow_SDL3_VK(InputCommon::InputSubsystem* input_subsystem_, Core::System& system, bool fullscreen); - ~EmuWindow_SDL2_VK() override; + ~EmuWindow_SDL3_VK() override; std::unique_ptr CreateSharedContext() const override; }; diff --git a/src/yuzu_cmd/sdl_config.cpp b/src/yuzu_cmd/sdl_config.cpp index 136043dc9e..b06a5a698c 100644 --- a/src/yuzu_cmd/sdl_config.cpp +++ b/src/yuzu_cmd/sdl_config.cpp @@ -6,7 +6,7 @@ // SDL will break our main function in yuzu-cmd if we don't define this before adding SDL.h #define SDL_MAIN_HANDLED -#include +#include #include "common/logging.h" #include "input_common/main.h" diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 54cc832bf2..473a5b1014 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -6,12 +6,19 @@ #include #include +#include #include #include +#include "common/settings_enums.h" +#include "core/launch_params.h" +#ifdef __EMSCRIPTEN__ +#include +#endif +#define SDL_MAIN_USE_CALLBACKS 1 +#include #include -#include "common/detached_tasks.h" #include "common/logging.h" #include "common/scm_rev.h" #include "common/settings.h" @@ -29,29 +36,23 @@ #include "input_common/main.h" #include "network/network.h" #include "sdl_config.h" +#include "video_core/gpu.h" +#include "video_core/rasterizer_interface.h" #include "video_core/renderer_base.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3.h" #ifdef HAS_OPENGL -#include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3_gl.h" #endif -#include "yuzu_cmd/emu_window/emu_window_sdl2_null.h" -#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3_null.h" +#include "yuzu_cmd/emu_window/emu_window_sdl3_vk.h" #ifdef _WIN32 // windows.h needs to be included before shellapi.h #include - #include - #include "common/windows/timer_resolution.h" #endif -#undef _UNICODE -#include -#ifndef _MSC_VER -#include -#endif - #ifdef _WIN32 extern "C" { // tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable @@ -61,25 +62,6 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } #endif -static void PrintHelp(const char* argv0) { - std::cout << "Usage: " << argv0 - << " [options] \n" - "-c, --config Load the specified configuration file\n" - "-f, --fullscreen Start in fullscreen mode\n" - "-g, --game File path of the game to load\n" - "-h, --help Display this help and exit\n" - "-m, --multiplayer=nick:password@address:port" - " Nickname, password, address and port for multiplayer\n" - "-p, --program Pass following string as arguments to executable\n" - "-u, --user Select a specific user profile from 0 to 7\n" - "-d, --debug Run the GDB stub on a port from 1 to 65535\n" - "-v, --version Output version information and exit\n"; -} - -static void PrintVersion() { - std::cout << "Eden " << Common::g_scm_branch << " " << Common::g_scm_desc << std::endl; -} - static void OnStateChanged(const Network::RoomMember::State& state) { switch (state) { case Network::RoomMember::State::Idle: @@ -175,8 +157,14 @@ static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) { std::cout << std::endl << "* " << message << std::endl << std::endl; } -/// Application entry point -int main(int argc, char** argv) { +struct SdlState { + Core::System system{}; + std::unique_ptr emu_window; +}; + +extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) { + SdlState* state = new SdlState(); + #ifdef _WIN32 if (AttachConsole(ATTACH_PARENT_PROCESS)) { freopen("CONOUT$", "wb", stdout); @@ -187,273 +175,167 @@ int main(int argc, char** argv) { Common::Log::Initialize(); Common::Log::SetColorConsoleBackendEnabled(true); Common::Log::Start(); - Common::DetachedTasks detached_tasks; int option_index = 0; #ifdef _WIN32 int argc_w; auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w); - if (argv_w == nullptr) { LOG_CRITICAL(Frontend, "Failed to get command line arguments"); - return -1; + return SDL_APP_FAILURE; } -#endif - std::string filepath; - std::optional config_path{}; - std::string program_args; - std::optional selected_user{}; - std::optional override_gdb_port{}; - bool use_multiplayer = false; - bool fullscreen = false; - std::string nickname{}; - std::string password{}; - std::string address{}; - std::string input_profile{}; - u16 port = Network::DefaultRoomPort; - - static struct option long_options[] = { - // clang-format off - {"debug", no_argument, 0, 'd'}, - {"config", required_argument, 0, 'c'}, - {"fullscreen", no_argument, 0, 'f'}, - {"help", no_argument, 0, 'h'}, - {"game", required_argument, 0, 'g'}, - {"multiplayer", required_argument, 0, 'm'}, - {"program", optional_argument, 0, 'p'}, - {"user", required_argument, 0, 'u'}, - {"version", no_argument, 0, 'v'}, - {"input-profile", no_argument, 0, 'i'}, - {0, 0, 0, 0}, - // clang-format on - }; - - while (optind < argc) { - int arg = getopt_long(argc, argv, "g:fhvcip::c:u:d:", long_options, &option_index); - if (arg != -1) { - switch (char(arg)) { - case 'd': - override_gdb_port = uint16_t(atoi(optarg)); - break; - case 'c': - config_path = optarg; - break; - case 'f': - fullscreen = true; - LOG_INFO(Frontend, "Starting in fullscreen mode..."); - break; - case 'h': - PrintHelp(argv[0]); - return 0; - case 'g': - filepath = std::string(optarg); - break; - case 'i': { - input_profile = std::string(optarg); - break; - } - case 'm': { - use_multiplayer = true; - const std::string str_arg(optarg); - // regex to check if the format is nickname:password@ip:port - // with optional :password - const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$"); - if (!std::regex_match(str_arg, re)) { - std::cout << "Wrong format for option --multiplayer\n"; - PrintHelp(argv[0]); - return 0; - } - - std::smatch match; - std::regex_search(str_arg, match, re); - ASSERT(match.size() == 5); - nickname = match[1]; - password = match[2]; - address = match[3]; - if (!match[4].str().empty()) { - port = static_cast(std::strtoul(match[4].str().c_str(), nullptr, 0)); - } - std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$"); - if (!std::regex_match(nickname, nickname_re)) { - std::cout - << "Nickname is not valid. Must be 4 to 20 alphanumeric characters.\n"; - return 0; - } - if (address.empty()) { - std::cout << "Address to room must not be empty.\n"; - return 0; - } - break; - } - case 'p': - program_args = argv[optind]; - ++optind; - break; - case 'u': - selected_user = atoi(optarg); - break; - case 'v': - PrintVersion(); - return 0; - } - } else { -#ifdef _WIN32 - filepath = Common::UTF16ToUTF8(argv_w[optind]); #else - filepath = argv[optind]; + wchar_t **argv_w = nullptr; #endif - optind++; - } - } - - SdlConfig config{config_path}; - - // apply the log_filter setting - // the logger was initialized before and doesn't pick up the filter on its own - Common::Log::Filter filter; - filter.ParseFilterString(Settings::values.log_filter.GetValue()); - Common::Log::SetGlobalFilter(filter); - - if (!program_args.empty()) { - Settings::values.program_args = program_args; - } - - if (!input_profile.empty()) { - auto& players = Settings::values.players.GetValue(); - players[0].profile_name = input_profile; - } - - if (selected_user.has_value()) { - Settings::values.current_user = std::clamp(*selected_user, 0, 7); - } - - if (override_gdb_port.has_value()) { - Settings::values.use_gdbstub = true; - Settings::values.gdbstub_port = *override_gdb_port; - } - + Core::LaunchParams lp = Core::ParseLaunchParams(state->system, argc, argv, argv_w); #ifdef _WIN32 LocalFree(argv_w); #endif + SdlConfig config{lp.config_path}; - if (filepath.empty()) { + Core::ApplyLaunchParams(lp); + + if (lp.filepath.empty()) { LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified"); - return -1; + return SDL_APP_FAILURE; } - Core::System system{}; - system.Initialize(); + state->system.Initialize(); InputCommon::InputSubsystem input_subsystem{}; // Apply the command line arguments - system.ApplySettings(); + state->system.ApplySettings(); - std::unique_ptr emu_window; switch (Settings::values.renderer_backend.GetValue()) { #ifdef HAS_OPENGL case Settings::RendererBackend::OpenGL_GLSL: case Settings::RendererBackend::OpenGL_GLASM: case Settings::RendererBackend::OpenGL_SPIRV: - emu_window = std::make_unique(&input_subsystem, system, fullscreen); + state->emu_window = std::make_unique(&input_subsystem, state->system, lp.fullscreen); break; #endif case Settings::RendererBackend::Vulkan: - emu_window = std::make_unique(&input_subsystem, system, fullscreen); + state->emu_window = std::make_unique(&input_subsystem, state->system, lp.fullscreen); break; case Settings::RendererBackend::Null: - emu_window = std::make_unique(&input_subsystem, system, fullscreen); + state->emu_window = std::make_unique(&input_subsystem, state->system, lp.fullscreen); break; default: LOG_CRITICAL(Frontend, "Invalid renderer backend"); - return -1; + return SDL_APP_FAILURE; } #ifdef _WIN32 Common::Windows::SetCurrentTimerResolutionToMaximum(); - system.CoreTiming().SetTimerResolutionNs(Common::Windows::GetCurrentTimerResolution()); + state->system.CoreTiming().SetTimerResolutionNs(Common::Windows::GetCurrentTimerResolution()); #endif - system.SetContentProvider(std::make_unique()); - system.SetFilesystem(std::make_shared()); - system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); - system.GetUserChannel().clear(); + state->system.SetContentProvider(std::make_unique()); + state->system.SetFilesystem(std::make_shared()); + state->system.GetFileSystemController().CreateFactories(*state->system.GetFilesystem()); + state->system.GetUserChannel().clear(); Service::AM::FrontendAppletParameters load_parameters{ .applet_id = Service::AM::AppletId::Application, }; - const Core::SystemResultStatus load_result{system.Load(*emu_window, filepath, load_parameters)}; - + const Core::SystemResultStatus load_result = state->system.Load(*state->emu_window, lp.filepath, load_parameters); switch (load_result) { - case Core::SystemResultStatus::ErrorGetLoader: - LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath); - return -1; - case Core::SystemResultStatus::ErrorLoader: - LOG_CRITICAL(Frontend, "Failed to load ROM!"); - return -1; - case Core::SystemResultStatus::ErrorNotInitialized: - LOG_CRITICAL(Frontend, "CPUCore not initialized"); - return -1; - case Core::SystemResultStatus::ErrorVideoCore: - LOG_CRITICAL(Frontend, "Failed to initialize VideoCore!"); - return -1; case Core::SystemResultStatus::Success: break; // Expected case + case Core::SystemResultStatus::ErrorGetLoader: + LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", lp.filepath); + return SDL_APP_FAILURE; + case Core::SystemResultStatus::ErrorLoader: + LOG_CRITICAL(Frontend, "Failed to load ROM!"); + return SDL_APP_FAILURE; + case Core::SystemResultStatus::ErrorNotInitialized: + LOG_CRITICAL(Frontend, "CPUCore not initialized"); + return SDL_APP_FAILURE; + case Core::SystemResultStatus::ErrorVideoCore: + LOG_CRITICAL(Frontend, "Failed to initialize VideoCore!"); + return SDL_APP_FAILURE; default: - if (static_cast(load_result) > - static_cast(Core::SystemResultStatus::ErrorLoader)) { - const u16 loader_id = static_cast(Core::SystemResultStatus::ErrorLoader); - const u16 error_id = static_cast(load_result) - loader_id; - LOG_CRITICAL(Frontend, - "While attempting to load the ROM requested, an error occurred. Please " - "refer to the Eden wiki for more information or the Eden discord for " - "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}", - loader_id, error_id, static_cast(error_id)); - } - break; + const u16 loader_id = u16(Core::SystemResultStatus::ErrorLoader); + const u16 error_id = u16(load_result) - loader_id; + LOG_CRITICAL(Frontend, + "While attempting to load the ROM requested, an error occurred. Please " + "refer to the Eden wiki for more information or the Eden discord for " + "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}", + loader_id, error_id, Loader::ResultStatus(error_id)); + return SDL_APP_FAILURE; } - if (use_multiplayer) { + if (lp.use_multiplayer) { if (auto member = Network::GetRoomMember().lock()) { member->BindOnChatMessageReceived(OnMessageReceived); member->BindOnStatusMessageReceived(OnStatusMessageReceived); member->BindOnStateChanged(OnStateChanged); member->BindOnError(OnNetworkError); - LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port, - nickname); - member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredIP, password); + LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", lp.address, lp.port, lp.nickname); + member->Join(lp.nickname, lp.address.c_str(), lp.port, 0, Network::NoPreferredIP, lp.password); } else { LOG_ERROR(Network, "Could not access RoomMember"); - return 0; + return SDL_APP_FAILURE; } } // Core is loaded, start the GPU (makes the GPU contexts current to this thread) - system.GPU().Start(); - system.GetCpuManager().OnGpuReady(); + state->system.GPU().Start(); + state->system.GetCpuManager().OnGpuReady(); if (Settings::values.use_disk_shader_cache.GetValue()) { - system.Renderer().ReadRasterizer()->LoadDiskResources( - system.GetApplicationProcessProgramID(), std::stop_token{}, + state->system.Renderer().ReadRasterizer()->LoadDiskResources( + state->system.GetApplicationProcessProgramID(), std::stop_token{}, [](VideoCore::LoadCallbackStage, size_t value, size_t total) {}); } - system.RegisterExitCallback([&] { - // Just exit right away. - exit(0); + // don't do anything, SDL3 already exists for us :D + state->system.RegisterExitCallback([] {}); + + // QLaunch launched applications (should) now reload shader cache. + static std::mutex shader_cache_reload_mutex; + state->system.RegisterApplicationChangedCallback([&state](u64 changed_program_id) { + if (!Settings::values.use_disk_shader_cache.GetValue()) { + return; + } + + std::scoped_lock lk{shader_cache_reload_mutex}; + + LOG_INFO(Frontend, "Reloading disk shader cache for {:016X}", changed_program_id); + + state->system.Pause(); + state->system.GPU().WaitForIdle(); + state->system.GPU().ObtainContext(); + + state->system.Renderer().ReadRasterizer()->LoadDiskResources( + changed_program_id, std::stop_token{}, + [](VideoCore::LoadCallbackStage, size_t, size_t) {}); + + state->system.GPU().ReleaseContext(); + state->system.Run(); }); - void(system.Run()); - if (system.DebuggerEnabled()) { - system.InitializeDebugger(); - } - while (emu_window->IsOpen()) { - emu_window->WaitEvent(); - } - system.DetachDebugger(); - void(system.Pause()); - system.ShutdownMainProcess(); - detached_tasks.WaitForAllTasks(); - return 0; + + void(state->system.Run()); + if (state->system.DebuggerEnabled()) + state->system.InitializeDebugger(); + return SDL_APP_SUCCESS; +} +extern "C" SDL_AppResult SDL_AppIterate(void *appstate) { + SdlState *state = (SdlState *)appstate; + return state->emu_window->IsOpen() ? SDL_APP_CONTINUE : SDL_APP_SUCCESS; +} +extern "C" SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { + SdlState *state = (SdlState *)appstate; + state->emu_window->OnEvent(*event); + return SDL_APP_SUCCESS; +} +extern "C" void SDL_AppQuit(void *appstate, SDL_AppResult result) { + SdlState *state = (SdlState *)appstate; + state->system.DetachDebugger(); + void(state->system.Pause()); + state->system.ShutdownMainProcess(); + delete state; } #define VMA_IMPLEMENTATION diff --git a/tools/README.md b/tools/README.md index 767892e7ac..cde6a21464 100644 --- a/tools/README.md +++ b/tools/README.md @@ -1,12 +1,18 @@ # Tools -Tools for Eden and other subprojects. +Tools for Eden and other subprojects. When adding new scripts please use `#!/bin/sh -e` or `#!/usr/bin/env ` (for `.py`, `.rb`, or `.perl`). Keep scripts POSIX compliant (i.e not require hard `bash` to run, just plain old `sh`). ## Third-Party - [CPMUtil Scripts](./cpm) -## Eden +## Binaries + +- `maxwell-spirv`: Converts Maxwell shaders (dumped from `.ash` files) into SPIR-V code (emitted into STDOUT). +- `maxwell-disas`: Dumb raw Maxwell disassembler. +- `maxwell-ir`: Dump generated IR of Maxwell shaders. + +## Scripts - `generate_converters.py`: Generates converters for given formats of textures (C++ helper). - `svc_generator.py`: Generates the files `src/core/hle/kernel/svc.cpp` and `src/core/hle/kernel/svc.h` based off prototypes. @@ -22,9 +28,12 @@ Tools for Eden and other subprojects. - `clang-format.sh`: Runs `clang-format` on the entire codebase. * Requires: clang - `find-unused-strings.sh`: Find any unused strings in the Android app (XML -> Kotlin). +- `cpp-lint.sh`: Homemade dumb C++ linter. +- `fuzzsettings.cpp`: Fuzz settings files. ## Android -It's recommended to run these scritps after almost any Android change, as they are relatively fast and important both for APK bloat and CI. + +It's recommended to run these scripts after almost any Android change, as they are relatively fast and important both for APK bloat and CI. - `unused-strings.sh`: Finds unused strings in `strings.xml` files. - `stale-translations.sh`: Finds translated strings that aren't present in the source `strings.xml` file. diff --git a/tools/clang-format.sh b/tools/clang-format.sh index e2857d9723..ca5e6fce39 100755 --- a/tools/clang-format.sh +++ b/tools/clang-format.sh @@ -1,4 +1,4 @@ -#! /bin/sh +#!/bin/sh -e # SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/tools/cpm/cmake/README.md b/tools/cpm/cmake/README.md new file mode 100755 index 0000000000..2dd6dfcbe6 --- /dev/null +++ b/tools/cpm/cmake/README.md @@ -0,0 +1,3 @@ +# CPMUtil CMake Test Scripts + +Testing out some CMake scripting functionality. diff --git a/tools/cpm/cmake/ScriptUtils.cmake b/tools/cpm/cmake/ScriptUtils.cmake new file mode 100755 index 0000000000..183deae14d --- /dev/null +++ b/tools/cpm/cmake/ScriptUtils.cmake @@ -0,0 +1,383 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +# TODO: Account for CPMConfig.cmake +list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}) +list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/CMakeModules) +set(CPMUTIL_ROOT ${CMAKE_SOURCE_DIR}) + +include(CPMUtil) + +# Parse the JSON object for a given key. +macro(parse_key key) + get_json_object(${key}) + set(JSON_NAME ${key}) + parse_object(${object}) +endmacro() + + +# Analogous to GNU mktemp, with fallbacks +function(mktempdir out) + # shell out to system mktemp if available + cpm_find_program(MKTEMP_EXECUTABLE mktemp) + if (MKTEMP_EXECUTABLE) + execute_process(COMMAND mktemp -d + OUTPUT_VARIABLE dir + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE ret) + + if (ret EQUAL 0) + set(${out} "${dir}" PARENT_SCOPE) + return() + endif() + endif() + + string(RANDOM LENGTH 10 rand_str) + set(tmp_str "tmp.${rand_str}") + + # create something in /tmp if it exists + if(EXISTS "/tmp" AND IS_DIRECTORY "/tmp") + set(dir "/tmp/${tmp_str}") + file(MAKE_DIRECTORY "${dir}" RESULT res) + if (res EQUAL 0) + set(${out} "${dir}" PARENT_SCOPE) + return() + endif() + endif() + + # tmpdir does not exist, extremely legacy mode + set(dir "${CMAKE_CURRENT_LIST_DIR}/.tmp/${tmp_str}") + file(MAKE_DIRECTORY "${dir}" RESULT res) + if (res EQUAL 0) + set(${out} "${dir}" PARENT_SCOPE) + return() + endif() + + fatal("Fatal: Could not create temporary directory. " + "Check write permissions to the current directory") +endfunction() + +# Get a package's effective URL, for an already parsed object +function(get_package_url_object out) + if (${url}) + set(${out} "${url}") + else() + get_package_url(URL_OUT "${out}" + GIT_HOST "${git_host}" + REPO "${repo}" + VERSION "${version}" + ARTIFACT "${artifact}" + PACKAGE "${package}") + endif() + + return(PROPAGATE ${out}) +endfunction() + +# Fetch a package from an already-parsed object. +function(fetch_package_object) + set(optionArgs FORCE) + cmake_parse_arguments(ARG "${optionArgs}" "" "" ${ARGN}) + + if (${url}) + set(pkg_url "${url}") + else() + get_package_url(URL_OUT pkg_url + GIT_HOST "${git_host}" + REPO "${repo}" + VERSION "${version}" + ARTIFACT "${artifact}" + PACKAGE "${package}") + endif() + + if (DEFINED CACHE_PATH_OVERRIDE) + set(cache_path ${CACHE_PATH_OVERRIDE}) + else() + get_cache_path(${package} ${version} cache_path) + endif() + + set(fetch_args + URL "${pkg_url}" + HASH "${hash}" + PATH "${cache_path}" + PATCHES ${patches}) + + if(ARG_FORCE) + list(APPEND fetch_args FORCE) + endif() + + fetch_package(${fetch_args}) +endfunction() + +# Format the cpmfile. Requires one of: jq, python, perl +# If you don't have any of those, sorry not sorry. +# Maybe I should make a shell-based alternative. +function(format_cpmfile) + # jq is the preferred formatter since it's the fastest + cpm_find_program(JQ_EXECUTABLE jq) + if (JQ_EXECUTABLE) + set(command ${JQ_EXECUTABLE} --indent 4 -S .) + else() + # Python is simple and works + find_package(Python 3.5 COMPONENTS Interpreter QUIET) + if (Python_FOUND) + set(command ${Python_EXECUTABLE} -m json.tool + --indent 4 --sort-keys) + else() + # json_pp (part of perl) also works well + cpm_find_program(JSONPP_EXECUTABLE json_pp) + if (JSONPP_EXECUTABLE) + set(json_opts "indent" "indent_length=4" "canonical" + "space_after=1" "space_before=0") + string(JOIN "," json_opts_str ${json_opts}) + + set(command ${JSONPP_EXECUTABLE} -f json -t json -json_opt + "${json_opts_str}") + else() + fatal("Fatal: could not find one of jq, Python, or perl" + "(json_pp). Install one of these packages to use" + "CPMUtil's tooling. If they ARE installed, your" + "CMake installation is broken.") + endif() + endif() + endif() + + get_cpmfile_path(file) + mktempdir(TMP) + set(tmp_file ${TMP}/cpmfile.json) + + execute_process(COMMAND ${command} + INPUT_FILE ${file} + OUTPUT_FILE ${tmp_file}) + + # TODO: error handling, mv, cp? + file(COPY_FILE ${tmp_file} ${file}) + + file(REMOVE_RECURSE ${TMP}) +endfunction() + +# Computes expected SHA512 hash of a package +function(get_package_hash url out) + mktempdir(TMP) + + get_filename_component(filename ${url} NAME) + set(file ${TMP}/${filename}) + + cpm_download("${url}" "${file}") + file(SHA512 ${file} ${out}) + + file(REMOVE_RECURSE ${TMP}) + return(PROPAGATE ${out}) +endfunction() + +# Download and put the content into a variable. +function(cpm_download_var url out) + mktempdir(TMP) + + set(file ${TMP}/tmp) + cpm_download("${url}" "${file}") + file(READ ${file} ${out}) + + file(REMOVE_RECURSE ${TMP}) + return(PROPAGATE ${out}) +endfunction() + +# Check if a URL request succeeds without actually saving anything +function(cpm_url_exists url out) + foreach(i RANGE 5) + file(DOWNLOAD "${url}" STATUS ret LOG log TIMEOUT 10) + list(GET ret 0 code) + + if (code EQUAL 0) + set(${out} TRUE) + break() + else() + if (log MATCHES "HTTP/[0-9.]+ (429|403)") + sleep(5) + continue() + endif() + + set(${out} FALSE) + break() + endif() + endforeach() + + return(PROPAGATE ${out}) +endfunction() + +# Get latest tag for a package. +# Requires an already-parsed object +function(get_latest_tag out) + # TODO: Ci packages + if (NOT repo OR ci) + set(${out} null) + return(PROPAGATE ${out}) + endif() + + # first determine if this is a tag or not + if (git_host STREQUAL github.com) + set(api "https://api.github.com/repos/${repo}") + set(check_endpoint "/git/refs/tags") + else() + set(api "https://${git_host}/api/v1/repos/${repo}") + set(check_endpoint "/tags") + endif() + + # artifacts must only check releases + if ("${artifact}" STREQUAL "") + set(json_key name) + set(endpoint "/tags") + else() + set(json_key tag_name) + set(endpoint "/releases") + set(check_endpoint "/releases/tags") + endif() + + cpm_url_exists("${api}${check_endpoint}/${version}" is_tag) + + if (NOT is_tag) + set(${out} null) + return(PROPAGATE ${out}) + endif() + + # strip out prefixes e.g. boost-, openssl-, v + # NOTE: subrels e.g. -1 at the end may cause issues. + string(REGEX REPLACE "[^0-9.-]" "" t_numeric_version "${version}") + string(REGEX REPLACE "-$|^-" "" t_numeric_version "${numeric_version}") + + # now api req + cpm_download_var("${api}${endpoint}" tags) + + string(JSON len LENGTH ${tags}) + math(EXPR last_index "${len} - 1") + + set(greatest_version_numeric ${t_numeric_version}) + set(greatest_version ${version}) + + foreach(i RANGE ${last_index}) + string(JSON tag_obj GET "${tags}" ${i}) + string(JSON tag_name GET "${tag_obj}" ${json_key}) + + # same as above + string(REGEX REPLACE "[^0-9.-]" "" numeric_tag "${tag_name}") + string(REGEX REPLACE "-$|^-" "" numeric_tag "${numeric_tag}") + + if (numeric_tag VERSION_GREATER_EQUAL greatest_version_numeric) + set(greatest_version_numeric ${numeric_tag}) + set(greatest_version ${tag_name}) + endif() + endforeach() + + # numeric version replacement + if (numeric_version) + set(${out} ${greatest_version_numeric}) + else() + set(${out} ${greatest_version}) + endif() + + return(PROPAGATE ${out}) +endfunction() + +# TODO(crueter): Combine these + +# Update hash and version of a package +# Outputs the updated object +function(modify_package object version hash out) + string(JSON new_object SET "${object}" hash "\"${hash}\"") + string(JSON new_object SET "${new_object}" version "\"${version}\"") + + set(${out} "${new_object}") + return(PROPAGATE ${out}) +endfunction() + +# Update hash and numeric_version of a package +# Outputs the updated object +function(modify_package_numeric object version hash out) + string(JSON new_object SET "${object}" hash "\"${hash}\"") + string(JSON new_object SET "${new_object}" numeric_version "\"${version}\"") + + set(${out} "${new_object}") + return(PROPAGATE ${out}) +endfunction() + + +function(get_cpmfile_keys out) + get_cpmfile_content(object) + + string(JSON len LENGTH ${object}) + + math(EXPR last_index "${len} - 1") + foreach(i RANGE ${last_index}) + string(JSON key MEMBER ${object} ${i}) + list(APPEND ${out} ${key}) + endforeach() + + return(PROPAGATE ${out}) +endfunction() + +function(parse_script_args out) + if(PRINT_USAGE) + usage() + cmake_language(EXIT 0) + endif() + + if(NOT CMAKE_SCRIPT_MODE_FILE OR NOT CMAKE_ARGC) + set(${out} "" PARENT_SCOPE) + return() + endif() + + set(found_script FALSE) + set(idx 0) + + get_filename_component(script_name "${CMAKE_SCRIPT_MODE_FILE}" NAME) + + while(idx LESS CMAKE_ARGC) + if(found_script) + set(arg "${CMAKE_ARGV${idx}}") + if(NOT arg STREQUAL "--") + list(APPEND positional_args ${arg}) + endif() + elseif(CMAKE_ARGV${idx} STREQUAL CMAKE_SCRIPT_MODE_FILE) + set(found_script TRUE) + else() + get_filename_component(arg_name "${CMAKE_ARGV${idx}}" NAME) + if(arg_name STREQUAL script_name) + set(found_script TRUE) + endif() + endif() + math(EXPR idx "${idx} + 1") + endwhile() + + if(ALL_PACKAGES AND NOT NO_ALL) + get_cpmfile_keys(all_keys) + if(NO_CI) + set(filtered_keys) + foreach(key ${all_keys}) + parse_key(${key}) + if(NOT ci) + list(APPEND filtered_keys ${key}) + endif() + endforeach() + set(positional_args ${filtered_keys}) + else() + set(positional_args ${all_keys}) + endif() + endif() + + set(${out} ${positional_args} PARENT_SCOPE) +endfunction() + +# Convert a CMake list to a JSON array +function(list_to_array list out) + set(${out} "[]") + set(idx 0) + foreach(elem ${list}) + string(JSON ${out} SET ${${out}} "${idx}" "\"${elem}\"") + math(EXPR idx "${idx} + 1") + endforeach() + + return(PROPAGATE ${out}) +endfunction() \ No newline at end of file diff --git a/tools/cpm/cmake/format.cmake b/tools/cpm/cmake/format.cmake new file mode 100755 index 0000000000..7eff47655f --- /dev/null +++ b/tools/cpm/cmake/format.cmake @@ -0,0 +1,11 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/ScriptUtils.cmake) + +# TODO: Run some sanity checks e.g. patches exist, etc. +format_cpmfile() diff --git a/tools/cpm/cmake/ls.cmake b/tools/cpm/cmake/ls.cmake new file mode 100755 index 0000000000..41a1d1d7af --- /dev/null +++ b/tools/cpm/cmake/ls.cmake @@ -0,0 +1,24 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/ScriptUtils.cmake) + +set(NO_ALL TRUE) + +function(usage) + echo([=[ +Usage: cpmutil.sh ls + +List all packages in the cpmfile. +]=]) +endfunction() + +get_cpmfile_keys(keys) + +foreach(key ${keys}) + echo("${key}") +endforeach() diff --git a/tools/cpm/cmake/package/add.cmake b/tools/cpm/cmake/package/add.cmake new file mode 100644 index 0000000000..02c4e1d900 --- /dev/null +++ b/tools/cpm/cmake/package/add.cmake @@ -0,0 +1,104 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: add.cmake [OPTIONS...] + +Internal use only. +]=]) +endfunction() + +if (NOT DEFINED KEY) + fatal("KEY is required") +endif() + +if (NOT DEFINED REPO) + fatal("REPO is required") +endif() + +if (NOT DEFINED VERSION) + fatal("VERSION is required") +endif() + + +if (NOT DEFINED CI) + set(CI FALSE) +endif() + +# construct json +set(object "{}") + +macro(add key val) + set(${key} ${val}) + string(JSON object SET ${object} ${key} "\"${val}\"") +endmacro() + +add(repo ${REPO}) +add(version ${VERSION}) + +if (DEFINED GIT_HOST AND NOT "${GIT_HOST}" STREQUAL github.com) + add(git_host ${GIT_HOST}) +else() + set(git_host github.com) +endif() + +if (DEFINED PACKAGE) + add(package ${PACKAGE}) +else() + set(package ${KEY}) +endif() + +if (DEFINED FIND_ARGS) + add(find_args "${FIND_ARGS}") +endif() + +if (DEFINED MIN_VERSION) + add(min_version ${MIN_VERSION}) +endif() + +if (DEFINED ARTIFACT) + add(artifact ${ARTIFACT}) +endif() + +if (CI) + add(ci true) + + if (DEFINED DISABLED_PLATFORMS) + list_to_array(${DISABLED_PLATFORMS} json_disabled) + string(JSON object SET "${object}" disabled_platforms "${json_disabled}") + endif() +else() + if (DEFINED OPTIONS) + list_to_array(${OPTIONS} json_options) + string(JSON object SET "${object}" options "${json_options}") + endif() + + # get hash + get_package_url_object(pkg_url) + get_package_hash("${pkg_url}" pkg_hash) + + add(hash ${pkg_hash}) + +endif() + +echo("\"${KEY}\": ${object}") + +# now write +get_cpmfile_content(cpmfile) + +# update cached cpmfile content +string(JSON cpmfile SET "${cpmfile}" "${KEY}" "${object}") + +# write cached cpmfile +get_cpmfile_path(file) +file(WRITE ${file} "${cpmfile}") +format_cpmfile() + +echo("-- Added ${KEY}") diff --git a/tools/cpm/cmake/package/dir.cmake b/tools/cpm/cmake/package/dir.cmake new file mode 100755 index 0000000000..bbbf523b31 --- /dev/null +++ b/tools/cpm/cmake/package/dir.cmake @@ -0,0 +1,33 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package dir [-a|--all] [PACKAGE]... + +Get the local directory for the specified packages. + +Options: + -a, --all Operate on all packages in this project. +]=]) +endfunction() + +parse_script_args(args) + +# TODO: CI packages. + +foreach(key ${args}) + parse_key(${key}) + + # Guh. + get_cache_path(${package} ${version} cache_path) + + cmake_path(ABSOLUTE_PATH cache_path NORMALIZE OUTPUT_VARIABLE abs_path) + echo("${key}: ${abs_path}") +endforeach() \ No newline at end of file diff --git a/tools/cpm/cmake/package/fetch.cmake b/tools/cpm/cmake/package/fetch.cmake new file mode 100755 index 0000000000..f2b678de01 --- /dev/null +++ b/tools/cpm/cmake/package/fetch.cmake @@ -0,0 +1,34 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package fetch [-a|--all] [PACKAGE]... + +Fetch the specified package or packages from their defined download locations. +If the package is already cached, it will not be re-fetched. + +Options: + -a, --all Operate on all packages in this project. +]=]) +endfunction() + +set(NO_CI TRUE) +parse_script_args(args) + +foreach(key ${args}) + if (ci) + continue() + endif() + + parse_key(${key}) + echo("-- ${key}") + + fetch_package_object() +endforeach() diff --git a/tools/cpm/cmake/package/hash.cmake b/tools/cpm/cmake/package/hash.cmake new file mode 100755 index 0000000000..6be23c9882 --- /dev/null +++ b/tools/cpm/cmake/package/hash.cmake @@ -0,0 +1,59 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package hash [-a|--all] [PACKAGE]... + +Check the hash of a specific package or packages. +If a hash mismatch occurs, this script will update the package's hash. + +Options: + -a, --all Operate on all packages in this project. + +Note that this procedure will usually take a long time +depending on the number and size of dependencies. +]=]) +endfunction() + +set(NO_CI TRUE) +parse_script_args(args) + +get_cpmfile_content(cpmfile) +foreach(key ${args}) + if (ci) + continue() + endif() + + parse_key("${key}") + echo("-- ${key}") + + get_package_url_object(pkg_url) + get_package_hash("${pkg_url}" pkg_hash) + + if (pkg_hash STREQUAL hash) + echo("Hashes match") + else() + echo_error("Hash mismatch") + echo_error("Expected: ${hash}") + echo_error("Got: ${pkg_hash}") + + modify_package("${object}" "${version}" "${pkg_hash}" new_object) + + # update cached cpmfile content + string(JSON cpmfile SET "${cpmfile}" "${key}" "${new_object}") + + echo("Corrected hash for ${key}") + endif() +endforeach() + +# write cached cpmfile +get_cpmfile_path(file) +file(WRITE ${file} "${cpmfile}") +format_cpmfile() diff --git a/tools/cpm/cmake/package/patch.cmake b/tools/cpm/cmake/package/patch.cmake new file mode 100644 index 0000000000..3844921827 --- /dev/null +++ b/tools/cpm/cmake/package/patch.cmake @@ -0,0 +1,155 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package patch [PACKAGE] + +Create an in-tree patch for the specified package. +]=]) +endfunction() + +set(NO_ALL TRUE) + +# arg parsing +parse_script_args(args) + +list(LENGTH args arg_len) +if(arg_len GREATER 0) + list(GET args 0 KEY) +endif() + +if (NOT KEY) + fatal("You must provide a key") +endif() + +if (NOT DEFINED DESCRIPTION) + fatal("No description provided") +endif() + +parse_key(${KEY}) +get_cache_path(${package} ${version} local_cache) + +if (NOT EXISTS ${local_cache}) + fatal("${package} is not fetched locally") +endif() + +# get last patch number + 1 +list(LENGTH patches patches_len) +math(EXPR last_index "${patches_len} - 1") +list(GET patches ${last_index} patch) +string(REGEX MATCH "[0-9][0-9][0-9][0-9]" number "${patch}") +math(EXPR new_patchnum "${number} + 1") + +# now pad it for filename usage +set(padded "0000${new_patchnum}") +string(LENGTH ${padded} total_len) +math(EXPR start_index "${total_len} - 4") +string(SUBSTRING ${padded} ${start_index} 4 padded_patch_number) + +# this requires Git +find_package(Git REQUIRED) + +# fetch temporary package +mktempdir(TMP) + +set(CACHE_PATH_OVERRIDE ${TMP}/local) +fetch_package_object() + +# make patch dir +set(patch_dir "${CPMUTIL_PATCH_DIR}/${KEY}") +file(MAKE_DIRECTORY "${patch_dir}") + +# git stuff +macro(git_cmd out out_status) + echo("${GIT_EXECUTABLE} ${ARGN}") + # TODO: error handling + execute_process(COMMAND ${GIT_EXECUTABLE} ${ARGN} + WORKING_DIRECTORY ${CACHE_PATH_OVERRIDE} + OUTPUT_VARIABLE ${out} + RESULT_VARIABLE ${out_status}) +endmacro() + +# initialize +git_cmd(_ _ init) +git_cmd(_ _ add -A) +git_cmd(_ _ commit -m init) +git_cmd(_ _ "--work-tree=${local_cache}" add -A) + +# check for diffs +git_cmd(_ diff_status diff --cached --quiet) +git_cmd(out _ diff) + +if (diff_status EQUAL 0) + echo(${out}) + echo_error("No differences found between local copy and source") + file(REMOVE_RECURSE ${TMP}) + cmake_language(EXIT 1) +endif() + +# prompt for patch description +git_cmd(_ commit_status commit -m "${DESCRIPTION}") + +# format patch +git_cmd(patch_content _ format-patch -1 HEAD --stdout) + +# now get patch name... + +# strip out existing numeric prefix +string(REGEX REPLACE "^[0-9][0-9][0-9][0-9]-" "" name_part "${DESCRIPTION}") +# spaces to dashes +string(REPLACE " " "-" name_part "${name_part}") +# strip out non-alphanumeric or dash characters +string(REGEX REPLACE "[^a-zA-Z0-9-]" "" name_part "${name_part}") +# collapse consecutive dashes +string(REGEX REPLACE "-+" "-" name_part "${name_part}") +# strip leading/trailing dashes +string(REGEX REPLACE "^-|-$" "" name_part "${name_part}") + +if (NOT name_part) + set(name_part "patch") + echo_error("Warning: could not determine patch name") +endif() + +# Truncate to 49 chars (60 - num prefix - `.patch` suffix) +string(SUBSTRING "${name_part}" 0 49 name_part) + +# And remove any trailing dashes +string(REGEX REPLACE "-$" "" name_part "${name_part}") + +# now construct and save patch +set(patch_name "${padded_patch_number}-${name_part}.patch") + +file(WRITE "${patch_dir}/${patch_name}" "${patch_content}") + +echo("-- Patch created at ${patch_dir}/${patch_name}") +file(REMOVE_RECURSE ${TMP}) + +# Now add to cpmfile. +get_cpmfile_content(content) + +# Build new patches JSON array +set(new_patches "[") +if(patches) + string(JSON existing GET "${content}" "${KEY}" "patches") + string(JSON len LENGTH "${existing}") + math(EXPR range_end "${len} - 1") + foreach(idx RANGE ${range_end}) + string(JSON val GET "${existing}" ${idx}) + string(APPEND new_patches "\"${val}\",") + endforeach() +endif() +string(APPEND new_patches "\"${patch_name}\"]") + +# Update the cpmfile +string(JSON content SET "${content}" "${KEY}" patches "${new_patches}") +get_cpmfile_path(file_path) +file(WRITE "${file_path}" "${content}") +format_cpmfile() + diff --git a/tools/cpm/cmake/package/reset.cmake b/tools/cpm/cmake/package/reset.cmake new file mode 100755 index 0000000000..2b210e4f64 --- /dev/null +++ b/tools/cpm/cmake/package/reset.cmake @@ -0,0 +1,34 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package reset [-a|--all] [PACKAGE]... + +Reset a locally fetched package to its original state. +This is most useful for dropping any changes you've made. + +Options: + -a, --all Operate on all packages in this project. +]=]) +endfunction() + +set(NO_CI TRUE) +parse_script_args(args) + +foreach(key ${args}) + if (ci) + continue() + endif() + + parse_key(${key}) + echo("-- ${key}") + + fetch_package_object(FORCE) +endforeach() diff --git a/tools/cpm/cmake/package/rm.cmake b/tools/cpm/cmake/package/rm.cmake new file mode 100755 index 0000000000..2547758920 --- /dev/null +++ b/tools/cpm/cmake/package/rm.cmake @@ -0,0 +1,31 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package rm [PACKAGE]... + +Delete a package or packages' cpmfile definition. +]=]) +endfunction() + +set(NO_ALL TRUE) +parse_script_args(args) + +get_cpmfile_content(object) + +# Remove key +foreach(key ${args}) + string(JSON object REMOVE "${object}" ${key}) +endforeach() + +# write +get_cpmfile_path(file) +file(WRITE ${file} "${object}") +format_cpmfile() \ No newline at end of file diff --git a/tools/cpm/cmake/package/update.cmake b/tools/cpm/cmake/package/update.cmake new file mode 100644 index 0000000000..82ea70340a --- /dev/null +++ b/tools/cpm/cmake/package/update.cmake @@ -0,0 +1,97 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package update [-a|--all] [-c|--commit] [PACKAGE]... + +Check for updates for a package or packages. + +Options: + -a, --all Operate on all packages in this project. + -c, --commit Automatically generate a commit message +]=]) +endfunction() + +set(NO_CI TRUE) + +parse_script_args(args) + +get_cpmfile_content(cpmfile) +set(update_log "") +set(changed FALSE) + +foreach(key ${args}) + parse_key(${key}) + + get_latest_tag(tag) + + if (${tag} STREQUAL null) + continue() + elseif(NOT ${tag} STREQUAL ${version}) + if (numeric_version) + set(old_version ${numeric_version}) + echo("${key}: ${old_version} -> ${tag}") + set(numeric_version ${tag}) + get_json_element("${object}" version version) + else() + set(old_version ${version}) + echo("${key}: ${old_version} -> ${tag}") + set(version ${tag}) + endif() + + get_json_element("${object}" artifact artifact) + + # Redo version replacements + process_version_replacements() + + get_package_url_object(pkg_url) + get_package_hash("${pkg_url}" pkg_hash) + + if (numeric_version) + modify_package_numeric("${object}" + "${tag}" "${pkg_hash}" new_object) + else() + modify_package("${object}" "${tag}" "${pkg_hash}" new_object) + endif() + + # update cached cpmfile content + string(JSON cpmfile SET "${cpmfile}" "${key}" "${new_object}") + + # used for commit + string(APPEND update_log "* ${key}: ${old_version} -> ${tag}\n") + set(changed TRUE) + else() + echo("${key}: Up to date") + endif() +endforeach() + +get_cpmfile_path(file) +file(WRITE ${file} "${cpmfile}") +format_cpmfile() + +if(MAKE_COMMIT AND changed) + mktempdir(TMP) + + find_package(Git QUIET) + + if (NOT Git_FOUND) + fatal("Git is required to be installed for --commit," + "but it could not be found.") + endif() + + set(msg_file ${TMP}/commit_msg.txt) + file(WRITE ${msg_file} "Update dependencies\n\n${update_log}") + execute_process(COMMAND ${GIT_EXECUTABLE} add cpmfile.json + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + execute_process(COMMAND ${GIT_EXECUTABLE} commit -F ${msg_file} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + + file(REMOVE_RECURSE ${TMP}) +endif() diff --git a/tools/cpm/cmake/package/url.cmake b/tools/cpm/cmake/package/url.cmake new file mode 100755 index 0000000000..cfc1d41037 --- /dev/null +++ b/tools/cpm/cmake/package/url.cmake @@ -0,0 +1,28 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package url [-a|--all] [PACKAGE]... + +Get the download URL for the specified packages. + +Options: + -a, --all Operate on all packages in this project. +]=]) +endfunction() + +parse_script_args(args) + +foreach(key ${args}) + parse_key(${key}) + + get_package_url_object(pkg_url) + echo("${key}: ${pkg_url}") +endforeach() \ No newline at end of file diff --git a/tools/cpm/cmake/package/version.cmake b/tools/cpm/cmake/package/version.cmake new file mode 100755 index 0000000000..0f31c1d558 --- /dev/null +++ b/tools/cpm/cmake/package/version.cmake @@ -0,0 +1,75 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package version [PACKAGE] [VERSION] + +Update a package's version. If the package uses a sha, you must provide a sha, +and if the package uses a tag, you must provide the fully qualified tag. +]=]) +endfunction() + +set(NO_ALL TRUE) +# arg parsing +parse_script_args(args) + +list(LENGTH args arg_len) +if(arg_len GREATER 0) + list(GET args 0 KEY) +endif() + +if(arg_len GREATER 1) + list(GET args 1 NEW_VERSION) +endif() + +# checks +if (NOT KEY) + fatal("You must provide a key") +endif() + +if (NOT NEW_VERSION) + fatal("You must provide a version") +endif() + +# action +get_cpmfile_content(cpmfile) +parse_key("${KEY}") + +if (numeric_version) + set(numeric_version ${NEW_VERSION}) + get_json_element("${object}" version version) +else() + set(version ${NEW_VERSION}) +endif() + +get_json_element("${object}" artifact artifact) + +# Redo version replacements +process_version_replacements() + +get_package_url_object(pkg_url) +get_package_hash("${pkg_url}" pkg_hash) + +if (numeric_version) + modify_package_numeric("${object}" + "${NEW_VERSION}" "${pkg_hash}" new_object) +else() + modify_package("${object}" "${NEW_VERSION}" "${pkg_hash}" new_object) +endif() + +# update cached cpmfile content +string(JSON cpmfile SET "${cpmfile}" "${KEY}" "${new_object}") + +# write cached cpmfile +get_cpmfile_path(file) +file(WRITE ${file} "${cpmfile}") +format_cpmfile() + +echo("-- * Updated") diff --git a/tools/cpm/cmake/package/which.cmake b/tools/cpm/cmake/package/which.cmake new file mode 100755 index 0000000000..46c5dcc021 --- /dev/null +++ b/tools/cpm/cmake/package/which.cmake @@ -0,0 +1,36 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/../ScriptUtils.cmake) + +function(usage) + echo([=[ +Usage: cpmutil.sh package which [PACKAGE]... + +Check if a package or packages are defined in the cpmfile. +]=]) +endfunction() + +set(NO_ALL TRUE) +parse_script_args(args) +set(exit 0) + +get_cpmfile_content(object) + +foreach(key ${args}) + # Check if a key exists + string(JSON member ERROR_VARIABLE err GET "${object}" ${key}) + + if (NOT err) + echo("${key}") + else() + echo_error("${key} not defined in cpmfile") + set(exit 1) + endif() +endforeach() + +cmake_language(EXIT ${exit}) \ No newline at end of file diff --git a/tools/cpm/cmake/update.cmake b/tools/cpm/cmake/update.cmake new file mode 100755 index 0000000000..79c1fa1d00 --- /dev/null +++ b/tools/cpm/cmake/update.cmake @@ -0,0 +1,33 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +include(${CMAKE_CURRENT_LIST_DIR}/ScriptUtils.cmake) + +# Update CPMUtil and its tooling/etc +set(pwd ${CMAKE_SOURCE_DIR}) + +set(host "https://git.crueter.xyz") +set(repo "CMake/CPMUtil") +set(release "releases/download/continuous") +set(filename CPMUtil.tar.zst) + +mktempdir(TMP) + +# Download tarball +set(url "${host}/${repo}/${release}/${filename}") +set(file ${TMP}/${filename}) + +cpm_download(${url} ${file}) + +# Extract to current working directory +file(ARCHIVE_EXTRACT + INPUT ${file} + DESTINATION ${pwd}) + +# done :) +echo("Updated CPMUtil") +file(REMOVE_RECURSE ${TMP}) \ No newline at end of file diff --git a/tools/cpm/common.sh b/tools/cpm/common.sh deleted file mode 100755 index 97a2fed003..0000000000 --- a/tools/cpm/common.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2026 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -################################## -# CHANGE THESE FOR YOUR PROJECT! # -################################## - -# TODO: cache cpmfile defs - -must_install() { - for cmd in "$@"; do - command -v "$cmd" >/dev/null 2>&1 || { echo "-- $cmd must be installed" && exit 1; } - done -} - -must_install jq find mktemp tar 7z unzip sha512sum git patch curl xargs - -# How many levels to go (3 is 2 subdirs max) -MAXDEPTH=3 - -# For your project you'll want to change this to define what dirs you have cpmfiles in -# Remember to account for the MAXDEPTH variable! -# Adding ./ before each will help to remove duplicates -CPMFILES=$(find . -maxdepth "$MAXDEPTH" -name cpmfile.json | sort | uniq) - -# shellcheck disable=SC2016 -PACKAGES=$(echo "$CPMFILES" | xargs jq -s 'reduce .[] as $item ({}; . * $item)') - -LIBS=$(echo "$PACKAGES" | jq -j 'keys_unsorted | join(" ")') - -export PACKAGES -export CPMFILES -export LIBS -export DIRS -export MAXDEPTH diff --git a/tools/cpm/format.sh b/tools/cpm/format.sh deleted file mode 100755 index df65f5a000..0000000000 --- a/tools/cpm/format.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2025 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -for file in $CPMFILES; do - jq --indent 4 <"$file" >"$file".new - mv "$file".new "$file" -done diff --git a/tools/cpm/migrate.sh b/tools/cpm/migrate.sh index b24bbfbbbf..fd4d6880db 100755 --- a/tools/cpm/migrate.sh +++ b/tools/cpm/migrate.sh @@ -6,14 +6,22 @@ SUBMODULES="$(git submodule status --recursive | cut -c2-)" : "${SUBMODULES:?No submodules defined!}" -tmp=$(mktemp) -printf '{}' >"$tmp" - IFS=" " for i in $SUBMODULES; do - sha=$(echo "$i" | cut -d" " -f1 | cut -c1-10) - ver=$(echo "$i" | cut -d" " -f3 | tr -d '()') + commit=$(echo "$i" -d" " -f1 | cut -c1-10) + short_commit=$(echo "$i" -d" " -f1 | cut -c1-7) + + ref=$(echo "$i" | cut -d" " -f3 | tr -d '()') + + case "$ref" in + # ref == commit, use commit versioning + "$short_commit") version="$commit" ;; + # ref is a branch, use commit versioning + heads/*) version="$commit" ;; + # ref is (probably) a tag, use tag versioning + *) version="$ref" ;; + esac path=$(echo "$i" | cut -d" " -f2) name=$(echo "$path" | awk -F/ '{print $NF}') @@ -21,26 +29,9 @@ for i in $SUBMODULES; do remote=$(git -C "$path" remote get-url origin) host=$(echo "$remote" | cut -d"/" -f3) - [ "$host" != github.com ] || host= repo=$(echo "$remote" | cut -d"/" -f4-5 | cut -d'.' -f1) - entry=$(jq -n --arg name "$name" \ - --arg sha "$sha" \ - --arg ver "$ver" \ - --arg repo "$repo" \ - --arg host "$host" \ - '{ - ($name): { - sha: $sha, - git_version: $ver, - repo: $repo - } + (if $host != "" then {git_host: $host} else {} end) - }') - - jq --argjson new "$entry" '. + $new' "$tmp" >"${tmp}.new" - mv "$tmp.new" "$tmp" + cmake -DKEY="$name" -DVERSION="$version" -DREPO="$repo" -DGIT_HOST="$host" \ + -P "$CMAKE/package/add.cmake" done - -jq '.' "$tmp" >cpmfile.json -rm -f "$tmp" diff --git a/tools/cpm/package.sh b/tools/cpm/package.sh index 474a2f351e..8246222d50 100755 --- a/tools/cpm/package.sh +++ b/tools/cpm/package.sh @@ -12,14 +12,17 @@ Usage: cpmutil.sh package [command] Operate on a package or packages. Commands: - hash Verify the hash of a package, and update it if needed - update Check for updates for a package - fetch Fetch a package and place it in the cache - add Add a new package - rm Remove a package - version Change the version of a package - which Find which cpmfile a package is defined in - download Get the download URL for a package + hash Verify the hash of a package, and update it if needed + update Check for updates for a package + fetch Fetch a package and place it in the cache + add Add a new package + rm Remove a package + version Change the version of a package + which Check if a package is defined + url Get the download URL for a package + dir Get the local directory for a package + reset Reset a fetched package to its original state + patch Add a patch to a package in the cpmfile EOF @@ -27,11 +30,48 @@ EOF } SCRIPTS=$(CDPATH='' cd -- "$(dirname -- "$0")/package" && pwd) -export SCRIPTS +CMAKE="$CMAKE/package" + +export SCRIPTS CMAKE + +filter_args() { + print_usage=false + all_flag=false + commit_flag=false + filtered="" + + for arg in "$@"; do + case "$arg" in + -h | --help) print_usage=true ;; + -a | --all) all_flag=true ;; + -c | --commit) commit_flag=true ;; + -ac | -ca) + commit_flag=true + all_flag=true + ;; + *) filtered="$filtered $arg" ;; + esac + done +} while :; do case "$1" in - hash | update | fetch | add | rm | version | which | download) + dir | fetch | hash | reset | rm | url | version | which | update) + cmd="$1" + shift + + filter_args "$@" + cmake_defines="" + + if $print_usage; then cmake_defines="$cmake_defines -DPRINT_USAGE=TRUE"; fi + if $all_flag; then cmake_defines="$cmake_defines -DALL_PACKAGES=TRUE"; fi + if $commit_flag; then cmake_defines="$cmake_defines -DMAKE_COMMIT=TRUE"; fi + + # shellcheck disable=SC2086 + cmake $cmake_defines -P "$CMAKE/$cmd.cmake" -- $filtered + break + ;; + add | patch) cmd="$1" shift "$SCRIPTS/$cmd".sh "$@" diff --git a/tools/cpm/package/add.sh b/tools/cpm/package/add.sh index f8071ee1bd..0129613931 100755 --- a/tools/cpm/package/add.sh +++ b/tools/cpm/package/add.sh @@ -7,31 +7,20 @@ RETURN=0 usage() { cat < Use the specified cpmfile instead of the root cpmfile - Note that you are still responsible for integrating this into your CMake. EOF - exit $RETURN + exit "$RETURN" } die() { echo "-- $*" >&2 - exit 1 -} - -_cpmfile() { - [ -n "$1" ] || die "You must specify a valid cpmfile." - CPMFILE="$1" + RETURN=1 usage } while :; do @@ -44,21 +33,11 @@ while :; do opt=$(echo "$opt" | cut -c2-) case "$char" in - t) TAG=1 ;; - c) - _cpmfile "$2" - shift - ;; h) usage ;; *) die "Invalid option -$char" ;; esac done ;; - --tag) TAG=1 ;; - --cpmfile) - _cpmfile "$2" - shift - ;; --help) usage ;; "$0") break ;; "") break ;; @@ -68,12 +47,155 @@ while :; do shift done -: "${CPMFILE:=$PWD/cpmfile.json}" - [ -n "$PKG" ] || die "You must specify a package name." -export PKG -export CPMFILE -export TAG +# This reads a single-line input from the user and also gives them +# help if needed. +# $1: The prompt itself, without any trailing spaces or whatever +# $2: The help text that gets shown when the user types a question mark +# $3: This is set to "required" if it's necessary, +# otherwise it can continue without input. +# Stores its output in the "reply" variable +read_single() { + while :; do + printf -- "-- %s" "$1" + [ -z "$2" ] || printf " (? for help, %s)" "$3" + printf ": " + if ! IFS= read -r reply; then + echo + [ "$3" = "required" ] && continue || reply="" + fi + case "$reply" in + "?") echo "$2" ;; + "") [ "$3" = "required" ] && continue || return 0 ;; + *) return 0 ;; + esac + done +} -"$SCRIPTS"/util/interactive.sh +# read_single, but optional +optional() { + read_single "$1" "$2" "optional" +} + +# a +required() { + read_single "$1" "$2" "required" +} + +# Basically the same as the single line function except multiline, +# also it's never "required" so we don't need that handling. +multi() { + echo "-- $1" + if [ -n "$2" ]; then + echo "-- (? on first line for help, Ctrl-D to finish)" + else + echo "-- (Ctrl-D to finish)" + fi + while :; do + reply=$(cat) + if [ "$(echo "$reply" | head -n 1)" = "?" ] && [ -n "$2" ]; then + echo "$2" + continue + fi + + # removes trailing EOF and empty lines + reply=$(printf '%s\n' "$reply" | + sed 's/\x04$//' | + sed '/^[[:space:]]*$/d') + + break + done +} + +# the actual inputs :) + +required "Package repository (owner/repo)" \ + "The remote repository this is stored on. +You shouldn't include the host, just owner/repo is enough." + +REPO="$reply" + +required "Version of the bundled package" \ + "The tag or commit hash of the bundled package." + +VERSION="$reply" + +optional "Package name for find_package" \ + "When searching for system packages, this argument will be passed to find_package. +For example, using \"Boost\" here will result in CPMUtil internally calling find_package(Boost). +If unset, defaults to the JSON key." + +PACKAGE="$reply" + +optional "Minimum required version" \ + "The minimum required version for this package if it's pulled in by the system." + +MIN_VERSION="$reply" + +optional "Additional find_package arguments, space-separated" \ + "Extra arguments passed to find_package(), (e.g. CONFIG)" + +FIND_ARGS="$reply" + +optional "Git host (default: github.com)" \ + "The hostname of the Git server, if not GitHub (e.g. codeberg.org, git.crueter.xyz)" + +GIT_HOST="$reply" + +optional "Is this a CI package? [y/N]" \ + "Yes if the package is a prebuilt binary distribution (e.g. crueter-ci), +no if the package is built from source if it's bundled." + +case "$reply" in +[Yy]*) CI=true ;; +*) CI=false ;; +esac + +if [ "$CI" = "false" ]; then + optional "Name of the release artifact to download, if applicable. +-- %VERSION% is replaced by the version ($VERSION)" \ + "Download the specified artifact from the release with the previously specified tag." + + ARTIFACT="$reply" + + multi "Fixed options, one per line (e.g. OPUS_BUILD_TESTING OFF)" \ + "Fixed options passed to the project's CMakeLists.txt. Variadic options +should be set in CMake with AddJsonPackage's OPTIONS parameter." + + OPTIONS="$reply" +else + required "Name of the CI artifact" \ + "CI artifacts are stored as --.tar.zst. This option controls the name." + + ARTIFACT="$reply" + + multi "Platforms without a package (one per line)" \ + "Valid platforms: +windows-amd64 windows-arm64 +mingw-amd64 mingw-arm64 +android-aarch64 android-x86_64 +linux-amd64 linux-aarch64 +macos-universal ios-aarch64" + + DISABLED_PLATFORMS="$reply" +fi + +# invoke add.cmake for validation +set -- "$@" \ + -DKEY="$PKG" \ + -DREPO="$REPO" \ + -DVERSION="$VERSION" \ + -DCI="$CI" + +[ -z "$PACKAGE" ] || set -- "$@" -DPACKAGE="$PACKAGE" +[ -z "$GIT_HOST" ] || set -- "$@" -DGIT_HOST="$GIT_HOST" +[ -z "$MIN_VERSION" ] || set -- "$@" -DMIN_VERSION="$MIN_VERSION" +[ -z "$FIND_ARGS" ] || set -- "$@" -DFIND_ARGS="$FIND_ARGS" +[ -z "$OPTIONS" ] || set -- "$@" -DOPTIONS="$(echo "$OPTIONS" | tr '\n' ';')" +[ -z "$ARTIFACT" ] || set -- "$@" -DARTIFACT="$ARTIFACT" +[ -z "$DISABLED_PLATFORMS" ] || set -- "$@" -DDISABLED_PLATFORMS="$(echo "$DISABLED_PLATFORMS" | tr '\n' ';')" + +cmake "$@" -P "$CMAKE"/add.cmake + +echo "Added package $PKG to cpmfile.json. Include it in your project with AddJsonPackage($PKG)" diff --git a/tools/cpm/package/download.sh b/tools/cpm/package/download.sh deleted file mode 100755 index 2a6125e74d..0000000000 --- a/tools/cpm/package/download.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2026 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -usage() { - cat </dev/null - ;; - *.tar*) - tar xf "$OUTFILE" >/dev/null - ;; - *.zip) - unzip "$OUTFILE" >/dev/null - ;; - esac - - # basically if only one real item exists at the top we just move everything from there - # since github and some vendors hate me - DIRS=$(find . -maxdepth 1 -type d -o -type f) - - # thanks gnu - if [ "$(echo "$DIRS" | wc -l)" -eq 2 ]; then - SUBDIR=$(find . -maxdepth 1 -type d -not -name ".") - mv "$SUBDIR"/* "$OUTDIR" - mv "$SUBDIR"/.* "$OUTDIR" 2>/dev/null || true - rmdir "$SUBDIR" - else - mv ./* "$OUTDIR" - mv ./.* "$OUTDIR" 2>/dev/null || true - fi - - cd "$OUTDIR" - - if echo "$JSON" | grep -e "patches" >/dev/null; then - PATCHES=$(echo "$JSON" | jq -r '.patches | join(" ")') - for patch in $PATCHES; do - patch --binary -p1 <"$ROOTDIR/.patch/$PACKAGE/$patch" - done - fi - - cd "$PREVDIR" -} - -ci_package() { - [ "$REPO" != null ] || { echo "-- ! No repo defined" && return; } - - echo "-- CI package $PACKAGE_NAME" - - for platform in \ - windows-amd64 windows-arm64 \ - mingw-amd64 mingw-arm64 \ - android-aarch64 android-x86_64 \ - solaris-amd64 freebsd-amd64 openbsd-amd64 \ - linux-amd64 linux-aarch64 \ - macos-universal; do - echo "-- * platform $platform" - - case $DISABLED in - *"$platform"*) - echo "-- * -- disabled" - continue - ;; - esac - - FILENAME="${NAME}-${platform}-${VERSION}.${EXT}" - DOWNLOAD="https://$GIT_HOST/${REPO}/releases/download/v${VERSION}/${FILENAME}" - KEY="$platform-$VERSION" - - LOWER_PACKAGE=$(echo "$PACKAGE_NAME" | tr '[:upper:]' '[:lower:]') - OUTDIR="${CPM_SOURCE_CACHE}/${LOWER_PACKAGE}/${KEY}" - [ -d "$OUTDIR" ] && continue - - HASH_ALGO=$(echo "$JSON" | jq -r ".hash_algo") - [ "$HASH_ALGO" != null ] || HASH_ALGO=sha512 - - HASH_SUFFIX="${HASH_ALGO}sum" - HASH_URL="${DOWNLOAD}.${HASH_SUFFIX}" - - HASH=$(curl "$HASH_URL" -sS -q -L -o -) - - download_package - done -} - -usage() { - cat <&2 + echo "-- Patch cancelled" >&2 + rm -rf "$TMP" + exit 1 +fi + +cmake -DDESCRIPTION="$description" -P "$CMAKE"/patch.cmake "$@" diff --git a/tools/cpm/package/rm.sh b/tools/cpm/package/rm.sh deleted file mode 100755 index 8b1ab94295..0000000000 --- a/tools/cpm/package/rm.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2026 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -RETURN=0 - -usage() { - cat <"$JSON".tmp - mv "$JSON".tmp "$JSON" - echo "-- Removed $pkg from $JSON" || : -done diff --git a/tools/cpm/package/update.sh b/tools/cpm/package/update.sh deleted file mode 100755 index 92f4f423a9..0000000000 --- a/tools/cpm/package/update.sh +++ /dev/null @@ -1,160 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2026 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -filter_out() { - TAGS=$(echo "$TAGS" | jq "[.[] | select(.name | test(\"$1\"; \"i\") | not)]") -} - -filter_in() { - TAGS=$(echo "$TAGS" | jq "[.[] | select(.name | test(\"$1\"; \"i\"))]") -} - -usage() { - cat <--.tar.zst. This option controls the version." - - VERSION="$reply" - - required "Name of the CI artifact" \ - "CI artifacts are stored as --.tar.zst. This option controls the name." - - ARTIFACT="$reply" - - multi "Platforms without a package (one per line)" \ - "Valid platforms: -windows-amd64 windows-arm64 -mingw-amd64 mingw-arm64 -android-aarch64 android-x86_64 -solaris-amd64 freebsd-amd64 openbsd-amd64 -linux-amd64 linux-aarch64 -macos-universal" - - DISABLED_PLATFORMS="$reply" -fi - -# now time to construct the actual json -jq_input='{repo: "'"$REPO"'"}' - -# common trivial fields -[ -z "$PACKAGE" ] || jq_input="$jq_input + {package: \"$PACKAGE\"}" -[ -z "$MIN_VERSION" ] || jq_input="$jq_input + {min_version: \"$MIN_VERSION\"}" -[ -z "$FIND_ARGS" ] || jq_input="$jq_input + {find_args: \"$FIND_ARGS\"}" - -if [ "$CI" = "true" ]; then - jq_input="$jq_input + { - ci: true, - version: \"$VERSION\", - artifact: \"$ARTIFACT\" - }" - - # disabled platforms - if [ -n "$DISABLED_PLATFORMS" ] && [ -n "$(printf '%s' "$DISABLED_PLATFORMS" | tr -d ' \t\n\r')" ]; then - disabled_json=$(printf '%s\n' "$DISABLED_PLATFORMS" | jq -R . | jq -s .) - jq_input="$jq_input + {disabled_platforms: $disabled_json}" - fi -else - [ -z "$MIN_VERSION" ] || jq_input="$jq_input + {version: \"$MIN_VERSION\"}" - jq_input="$jq_input + {hash: \"\"}" - - # options - if [ -n "$OPTIONS" ] && [ -n "$(printf '%s' "$OPTIONS" | tr -d ' \t\n\r')" ]; then - options_json=$(printf '%s\n' "$OPTIONS" | jq -R . | jq -s .) - jq_input="$jq_input + {options: $options_json}" - fi - - # Git host - if [ -n "$GIT_HOST" ] && [ "$GIT_HOST" != "github.com" ]; then - jq_input="$jq_input + {git_host: \"$GIT_HOST\"}" - fi - - # versioning stuff - if [ -n "$GIT_VERSION" ]; then - jq_input="$jq_input + {git_version: \"$GIT_VERSION\"}" - [ -z "$TAGNAME" ] || jq_input="$jq_input + {tag: \"$TAGNAME\"}" - [ -z "$ARTIFACT" ] || jq_input="$jq_input + {artifact: \"$ARTIFACT\"}" - else - jq_input="$jq_input + {sha: \"$SHA\"}" - fi -fi - -new_json=$(jq -n "$jq_input") - -jq --arg key "$PKG" --argjson new "$new_json" \ - '.[$key] = $new' "$CPMFILE" --indent 4 >"${CPMFILE}.tmp" && - mv "${CPMFILE}.tmp" "$CPMFILE" - -# now correct the hash -if [ "$CI" != true ]; then - # shellcheck disable=SC1091 - . "$ROOTDIR"/common.sh - QUIET=true UPDATE=true "$SCRIPTS"/util/fix-hash.sh "$PKG" -fi - -echo "Added package $PKG to $CPMFILE. Include it in your project with AddJsonPackage($PKG)" diff --git a/tools/cpm/package/util/replace.sh b/tools/cpm/package/util/replace.sh deleted file mode 100755 index 4ec65384da..0000000000 --- a/tools/cpm/package/util/replace.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2025 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -# Replace a specified package with a modified json. - -FILE=$(echo "$CPMFILES" | xargs grep -l "\"$1\"") - -jq --indent 4 --argjson repl "$2" ".\"$1\" *= \$repl" "$FILE" >"$FILE".new -mv "$FILE".new "$FILE" - -echo "-- * -- Updated $FILE" diff --git a/tools/cpm/package/util/url-hash.sh b/tools/cpm/package/util/url-hash.sh deleted file mode 100755 index eae784b53d..0000000000 --- a/tools/cpm/package/util/url-hash.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -# SPDX-FileCopyrightText: Copyright 2025 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -SUM=$(curl -Ls "$1" -o - | sha512sum) -echo "$SUM" | cut -d " " -f1 diff --git a/tools/cpm/package/vars.sh b/tools/cpm/package/vars.sh deleted file mode 100755 index 07f4883ddf..0000000000 --- a/tools/cpm/package/vars.sh +++ /dev/null @@ -1,176 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2026 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -# shellcheck disable=SC1091 - -value() { - echo "$JSON" | jq -r ".$1" -} - -[ -n "$PACKAGE" ] || { echo "Package was not specified" && exit 0; } - -# shellcheck disable=SC2153 -JSON=$(echo "$PACKAGES" | jq -r ".\"$PACKAGE\" | select( . != null )") - -if [ -z "$JSON" ]; then - echo "!! No cpmfile definition for $PACKAGE" >&2 - exit 1 -fi - -# unset stuff -export PACKAGE_NAME="null" -export REPO="null" -export CI="null" -export GIT_HOST="null" -export EXT="null" -export NAME="null" -export DISABLED="null" -export TAG="null" -export ARTIFACT="null" -export SHA="null" -export VERSION="null" -export GIT_VERSION="null" -export DOWNLOAD="null" -export URL="null" -export KEY="null" -export HASH="null" -export ORIGINAL_TAG="null" -export HAS_REPLACE="null" -export VERSION_REPLACE="null" -export HASH_URL="null" -export HASH_SUFFIX="null" -export HASH_ALGO="null" - -######## -# Meta # -######## - -REPO=$(value "repo") -CI=$(value "ci") - -PACKAGE_NAME=$(value "package") -[ "$PACKAGE_NAME" != null ] || PACKAGE_NAME="$PACKAGE" - -GIT_HOST=$(value "git_host") -[ "$GIT_HOST" != null ] || GIT_HOST=github.com - -export PACKAGE_NAME -export REPO -export CI -export GIT_HOST - -###################### -# CI Package Parsing # -###################### - -VERSION=$(value "version") - -if [ "$CI" = "true" ]; then - EXT=$(value "extension") - [ "$EXT" != null ] || EXT="tar.zst" - - NAME=$(value "name") - DISABLED=$(echo "$JSON" | jq -j '.disabled_platforms') - - [ "$NAME" != null ] || NAME="$PACKAGE_NAME" - - export EXT - export NAME - export DISABLED - export VERSION - - return 0 -fi - -############## -# Versioning # -############## - -TAG=$(value "tag") -ARTIFACT=$(value "artifact") -SHA=$(value "sha") -GIT_VERSION=$(value "git_version") - -[ "$GIT_VERSION" != null ] || GIT_VERSION="$VERSION" - -if [ "$GIT_VERSION" != null ]; then - VERSION_REPLACE="$GIT_VERSION" -else - VERSION_REPLACE="$VERSION" -fi - -if echo "$TAG" | grep -e "%VERSION%" >/dev/null; then - HAS_REPLACE=true -else - HAS_REPLACE=false -fi - -ORIGINAL_TAG="$TAG" - -TAG=$(echo "$TAG" | sed "s/%VERSION%/$VERSION_REPLACE/g") -ARTIFACT=$(echo "$ARTIFACT" | sed "s/%VERSION%/$VERSION_REPLACE/g") -ARTIFACT=$(echo "$ARTIFACT" | sed "s/%TAG%/$TAG/g") - -export TAG -export ARTIFACT -export SHA -export VERSION -export GIT_VERSION -export ORIGINAL_TAG -export HAS_REPLACE -export VERSION_REPLACE - -############### -# URL Parsing # -############### - -URL=$(value "url") -BRANCH=$(value "branch") - -export BRANCH -export URL - -. "$SCRIPTS"/vars/url.sh - -export DOWNLOAD - -############### -# Key Parsing # -############### - -KEY=$(value "key") - -. "$SCRIPTS"/vars/key.sh - -export KEY - -################ -# Hash Parsing # -################ - -HASH_ALGO=$(value "hash_algo") -[ "$HASH_ALGO" != null ] || HASH_ALGO=sha512 - -HASH=$(value "hash") - -if [ "$HASH" = null ]; then - HASH_SUFFIX="${HASH_ALGO}sum" - HASH_URL=$(value "hash_url") - - if [ "$HASH_URL" = null ]; then - HASH_URL="${DOWNLOAD}.${HASH_SUFFIX}" - fi - - HASH=$(curl "$HASH_URL" -Ss -L -o -) -else - HASH_URL=null - HASH_SUFFIX=null -fi - -export HASH_URL -export HASH_SUFFIX -export HASH -export HASH_ALGO -export JSON diff --git a/tools/cpm/package/vars/key.sh b/tools/cpm/package/vars/key.sh deleted file mode 100755 index e6d68c2abb..0000000000 --- a/tools/cpm/package/vars/key.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2025 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -if [ "$KEY" = null ]; then - if [ "$SHA" != null ]; then - KEY=$(echo "$SHA" | cut -c1-4) - 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_NAME. Must define one of: key, sha, tag, version, git_version" - exit 1 - fi -fi - -export KEY diff --git a/tools/cpm/package/vars/url.sh b/tools/cpm/package/vars/url.sh deleted file mode 100755 index 205d3a06ef..0000000000 --- a/tools/cpm/package/vars/url.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh - -# SPDX-FileCopyrightText: Copyright 2025 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -# Required vars: URL, GIT_HOST, REPO, TAG, ARTIFACT, BRANCH, SHA -if [ "$URL" != "null" ]; then - DOWNLOAD="$URL" -elif [ "$REPO" != "null" ]; then - GIT_URL="https://$GIT_HOST/$REPO" - - if [ "$TAG" != "null" ]; then - if [ "$ARTIFACT" != "null" ]; then - DOWNLOAD="${GIT_URL}/releases/download/${TAG}/${ARTIFACT}" - else - DOWNLOAD="${GIT_URL}/archive/refs/tags/${TAG}.tar.gz" - fi - elif [ "$SHA" != "null" ]; then - DOWNLOAD="${GIT_URL}/archive/${SHA}.tar.gz" - else - if [ "$BRANCH" = null ]; then - BRANCH=master - fi - - DOWNLOAD="${GIT_URL}/archive/refs/heads/${BRANCH}.tar.gz" - fi -else - echo "!! No repo or URL defined for $PACKAGE_NAME" - exit 1 -fi - -export DOWNLOAD diff --git a/tools/cpm/package/version.sh b/tools/cpm/package/version.sh deleted file mode 100755 index fef78124a0..0000000000 --- a/tools/cpm/package/version.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/sh -e - -# SPDX-FileCopyrightText: Copyright 2026 crueter -# SPDX-License-Identifier: LGPL-3.0-or-later - -# shellcheck disable=SC1091 - -usage() { - cat <&2 && exit 1; } - -echo "$JSON" diff --git a/tools/cpmutil.sh b/tools/cpmutil.sh index cec758a726..b1edbbde4b 100755 --- a/tools/cpmutil.sh +++ b/tools/cpmutil.sh @@ -7,8 +7,7 @@ ROOTDIR=$(CDPATH='' cd -- "$(dirname -- "$0")/cpm" && pwd) SCRIPTS="$ROOTDIR" - -. "$SCRIPTS"/common.sh +CMAKE="$ROOTDIR/cmake" RETURN=0 @@ -25,36 +24,56 @@ General command-line utility for CPMUtil operations. Commands: package Run operations on a package or packages - format Format all cpmfiles + format Format cpmfile + ls List all packages in the cpmfile update Update CPMUtil and its tooling - ls List all cpmfiles migrate Convert submodules to a basic cpmfile Package commands: - hash Verify the hash of a package, and update it if needed - update Check for updates for a package - fetch Fetch a package and place it in the cache - add Add a new package - rm Remove a package - version Change the version of a package - which Find which cpmfile a package is defined in - download Get the download URL for a package + hash Verify the hash of a package, and update it if needed + update Check for updates for a package + fetch Fetch a package and place it in the cache + add Add a new package + rm Remove a package + version Change the version of a package + which Check if a package is defined + url Get the download URL for a package + dir Get the local directory for a package + reset Reset a fetched package to its original state + patch Create an in-tree patch based on local modifications EOF exit $RETURN } -export ROOTDIR +export ROOTDIR CMAKE SCRIPTS while :; do case "$1" in - ls) - echo "$CPMFILES" | tr ' ' '\n' + format | update) + cmake -P "$CMAKE/$1".cmake break ;; - format | update | migrate) - "$SCRIPTS/$1".sh + ls) + shift + print_usage=false + filtered="" + for arg in "$@"; do + case "$arg" in + -h | --help) print_usage=true ;; + -n) ;; + *) filtered="$filtered $arg" ;; + esac + done + cmake_defines="" + $print_usage && cmake_defines="-DPRINT_USAGE=TRUE" + # shellcheck disable=SC2086 + cmake $cmake_defines -P "$CMAKE/ls.cmake" -- $filtered + break + ;; + migrate) + "$SCRIPTS"/migrate.sh break ;; package) diff --git a/tools/cpp-lint.sh b/tools/cpp-lint.sh new file mode 100755 index 0000000000..4c5a6e10dc --- /dev/null +++ b/tools/cpp-lint.sh @@ -0,0 +1,55 @@ +#!/bin/sh -ex + +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +# tools/../ +ROOTDIR=$(CDPATH='' cd -- "$(dirname -- "$0")/../" && pwd) +BUILD_DIR="$ROOTDIR"/build +SRC_DIR="$ROOTDIR"/src + +die() { + echo "-- $*" >&2 + exit 1 +} + +usage() { + cat <td_name), stack(100)] = count(); -} -profile-".$sampling_hz." /pid == ".$sampling_pid." && arg1/ { - @[stringof(curthread->td_name), ustack(100)] = count(); -} -tick-".$sampling_time."s { - exit(0); -}"; +profile-".$sampling_hz." /pid == ".$sampling_pid." && arg0/ { @[stringof(curthread->td_name), stack(100)] = count(); } +profile-".$sampling_hz." /pid == ".$sampling_pid." && arg1/ { @[stringof(curthread->td_name), ustack(100)] = count(); } +tick-".$sampling_time."s { exit(0); }"; } elsif ($sampling_type eq 3) { + # trace I/O requests return " -io::start /pid == ".$sampling_pid."/ { - @[ustack(100)] = count(); -} -tick-".$sampling_time."s { - exit(0); -}"; +io::start /pid == ".$sampling_pid."/ { @[ustack(100)] = count(); } +tick-".$sampling_time."s { exit(0); }"; } else { die "idk"; } @@ -111,11 +98,17 @@ sub dtrace_generate { foreach my $i (0 .. $#ARGV) { if ($ARGV[$i] eq '-h') { - print "Usage: $0\n"; - printf "%-20s%s\n", "-p", "Prompt for parameters"; - printf "%-20s%s\n", "-g", "Generate dtrace output"; - printf "%-20s%s\n", "-s", "Continously generate output until Ctrl^C"; - printf "%-20s%s\n", "-", "Select dtrace type"; + print " +Usage: $0\n +-p Prompt for parameters\n +-g Generate dtrace output\n +-s Continously generate output until Ctrl^C\n +- Select dtrace type\n + 0: Profile kernel + user stacks (default)\n + 1: Trace syscall entries\n + 2: Profile kernel + user stacks with thread names\n + 3: Trace I/O requests\n +"; } elsif ($ARGV[$i] eq '-g') { dtrace_generate; } elsif ($ARGV[$i] eq '-s') { diff --git a/tools/fuzzsettings.cpp b/tools/fuzzsettings.cpp new file mode 100644 index 0000000000..e9685ce9f4 --- /dev/null +++ b/tools/fuzzsettings.cpp @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + std::srand(unsigned(std::time(nullptr))); + + FILE *fp = std::fopen(argv[1], "rt"); + if (fp) { + char line[BUFSIZ]; + while (std::fgets(line, sizeof(line), fp)) { + if (line[0] == '[') { + std::printf("%s", line); + } else if (std::isspace(line[0])) { + std::printf("%s", line); + } else { + char *p = std::strchr(line, '='); + if (std::strstr(line, "\\default") == nullptr) { + // not default + *p = '\0'; + std::string new_line{line}; + std::string value{p + 1}; + if (value == "true" || value == "false") { + new_line += std::string{} + "=TreufLAlse857874FJJakshjryiu475" + '\n'; + } else if (std::isdigit(value[0])) { + if (new_line == "size" + || std::strstr(new_line.c_str(), "entries\\size") != nullptr + || std::strstr(new_line.c_str(), "\\size")) { + new_line += "=-1\n"; + } else { + new_line += '=' + std::to_string(int(std::rand())) + '\n'; + } + } else { + std::string_view const cset{"03832///1/1/.1/1./1./1./1.1/.1194573290uwmgjouidyhiomHMNIODASJK,POF MSHDVLJPOIuksdtpsunmghns"}; + std::string rst{"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}; + for (size_t i = 0; i < rst.size(); ++i) + rst[i] = cset[std::rand() % cset.size()]; + + //new_line += "=\"" + rst + "\""; + new_line += "=" + value; + } + std::printf("%s", new_line.c_str()); + } else { + // yes default + *p = '\0'; + std::string new_line{line}; + std::string value{p + 1}; + new_line += "=false\n"; + std::printf("%s", new_line.c_str()); + } + } + } + std::fclose(fp); + } + return 0; +} diff --git a/tools/fuzzsettings.sh b/tools/fuzzsettings.sh new file mode 100755 index 0000000000..7cf613dc3d --- /dev/null +++ b/tools/fuzzsettings.sh @@ -0,0 +1,12 @@ +#!/bin/sh -ex + +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +ROOTDIR=$(CDPATH='' cd -- "$(dirname -- "$0")/" && pwd) + +touch "$2" + +c++ "$ROOTDIR/fuzzsettings.cpp" -o fuzzsettings +./fuzzsettings "$1" >"$2" +rm fuzzsettings diff --git a/tools/gendynarm.cpp b/tools/gendynarm.cpp index 3d1588e7e8..3e28363a10 100644 --- a/tools/gendynarm.cpp +++ b/tools/gendynarm.cpp @@ -17,12 +17,6 @@ namespace mcl { template constexpr std::size_t bitsizeof = CHAR_BIT * sizeof(T); } -namespace mcl::bit { -template -inline size_t count_ones(T x) { - return std::bitset>(x).count(); -} -} template inline consteval std::array StringToArray(const char (&str)[N + 1]) { std::array result{}; @@ -380,7 +374,7 @@ INST(arm_SRS, "SRS", "1111100--1-0110100000101000----- }; // If a matcher has more bits in its mask it is more specific, so it should come first. std::stable_sort(list.begin(), list.end(), [](const auto& matcher1, const auto& matcher2) { - return mcl::bit::count_ones(matcher1.second) > mcl::bit::count_ones(matcher2.second); + return std::popcount(matcher1.second) > std::popcount(matcher2.second); }); for (auto const& e : list) printf("%s\n", e.inst_final); @@ -587,7 +581,7 @@ INST(v8_VLD_single, "VLD{1-4} (single)", "111101001D10nnnnddddzzN }); // If a matcher has more bits in its mask it is more specific, so it should come first. std::stable_sort(sort_begin, sort_end, [](const auto& a, const auto& b) { - return mcl::bit::count_ones(a.second) > mcl::bit::count_ones(b.second); + return std::popcount(a.second) > std::popcount(b.second); }); for (auto const& e : table) printf("%s\n", e.inst_final); @@ -1628,7 +1622,7 @@ INST(FNMSUB_float, "FNMSUB", "00011 // If a matcher has more bits in its mask it is more specific, so it should come first. std::stable_sort(list.begin(), list.end(), [](const auto& a, const auto& b) { // If a matcher has more bits in its mask it is more specific, so it should come first. - return mcl::bit::count_ones(a.second) > mcl::bit::count_ones(b.second); + return std::popcount(a.second) > std::popcount(b.second); }); // Exceptions to the above rule of thumb. std::stable_partition(list.begin(), list.end(), [&](const auto& e) { diff --git a/tools/lanczos-gen.pl b/tools/lanczos-gen.pl old mode 100644 new mode 100755 index 2e26c8c6b9..3eab5cdb10 --- a/tools/lanczos-gen.pl +++ b/tools/lanczos-gen.pl @@ -1,5 +1,5 @@ -#!/usr/bin/perl -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +#!/usr/bin/env perl +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later use strict; use warnings; diff --git a/tools/llvmpipe-run.sh b/tools/llvmpipe-run.sh index c3a5a85d41..f09fe87ef4 100755 --- a/tools/llvmpipe-run.sh +++ b/tools/llvmpipe-run.sh @@ -1,5 +1,6 @@ -#!/bin/sh -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +#!/bin/sh -e + +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later # This script basically allows you to "dirtily" use llvmpipe in any configuration diff --git a/tools/maxwell-disas/CMakeLists.txt b/tools/maxwell-disas/CMakeLists.txt new file mode 100644 index 0000000000..2595d45a35 --- /dev/null +++ b/tools/maxwell-disas/CMakeLists.txt @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later +add_executable(maxwell-disas + main.cpp +) +target_link_libraries(maxwell-disas PRIVATE common shader_recompiler Threads::Threads) +target_include_directories(maxwell-disas PRIVATE ${CMAKE_SOURCE_DIR}/src) +if(UNIX AND NOT APPLE) + install(TARGETS maxwell-disas) +endif() +create_target_directory_groups(maxwell-disas) diff --git a/tools/maxwell-disas/file_environment.h b/tools/maxwell-disas/file_environment.h new file mode 100644 index 0000000000..cf190806e4 --- /dev/null +++ b/tools/maxwell-disas/file_environment.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include "shader_recompiler/environment.h" + +class FileEnvironment final : public Shader::Environment { +public: + FileEnvironment() = default; + ~FileEnvironment() override = default; + FileEnvironment& operator=(FileEnvironment&&) noexcept = default; + FileEnvironment(FileEnvironment&&) noexcept = default; + FileEnvironment& operator=(const FileEnvironment&) = delete; + FileEnvironment(const FileEnvironment&) = delete; + void Deserialize(std::ifstream& file) {} + [[nodiscard]] u64 ReadInstruction(u32 address) override { + if (address < read_lowest || address > read_highest) { + std::printf("cant read %08x\n", address); + std::abort(); + } + return code[(address - read_lowest) / sizeof(u64)]; + } + [[nodiscard]] u32 ReadCbufValue(u32 cbuf_index, u32 cbuf_offset) override { return 0; } + [[nodiscard]] Shader::TextureType ReadTextureType(u32 handle) override { + auto const it{texture_types.find(handle)}; + return it->second; + } + [[nodiscard]] Shader::TexturePixelFormat ReadTexturePixelFormat(u32 handle) override { + auto const it{texture_pixel_formats.find(handle)}; + return it->second; + } + [[nodiscard]] bool IsTexturePixelFormatInteger(u32 handle) override { return true; } + [[nodiscard]] u32 ReadViewportTransformState() override { return viewport_transform_state; } + [[nodiscard]] u32 LocalMemorySize() const override { return local_memory_size; } + [[nodiscard]] u32 SharedMemorySize() const override { return shared_memory_size; } + [[nodiscard]] u32 TextureBoundBuffer() const override { return texture_bound; } + [[nodiscard]] std::array WorkgroupSize() const override { return workgroup_size; } + [[nodiscard]] std::optional GetReplaceConstBuffer(u32 bank, u32 offset) override { + auto const it = cbuf_replacements.find((u64(bank) << 32) | u64(offset)); + return it != cbuf_replacements.end() ? std::optional{it->second} : std::nullopt; + } + [[nodiscard]] bool HasHLEMacroState() const override { return cbuf_replacements.size() != 0; } + void Dump(u64 pipeline_hash, u64 shader_hash) override {} + std::vector code; + std::unordered_map texture_types; + std::unordered_map texture_pixel_formats; + std::unordered_map cbuf_values; + std::unordered_map cbuf_replacements; + std::array workgroup_size{}; + u32 local_memory_size{}; + u32 shared_memory_size{}; + u32 texture_bound{}; + u32 read_lowest{}; + u32 read_highest{}; + u32 initial_offset{}; + u32 viewport_transform_state = 1; +}; diff --git a/tools/maxwell-disas/generated.cpp b/tools/maxwell-disas/generated.cpp new file mode 100644 index 0000000000..687209558d --- /dev/null +++ b/tools/maxwell-disas/generated.cpp @@ -0,0 +1,641 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +namespace Shader::Maxwell { +std::string DissasemblyFormat(uint64_t inst) { + std::string s{}; + if(((inst>>48)&0xfff8ULL)==0x5c58ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c58ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FSWZADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c68ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FMUL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c68ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FMUL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c90ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"RRO "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c90ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"RRO "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x5080ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"MUFU "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c88ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FCHK "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c88ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FCHK "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c70ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c70ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DMUL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DMUL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c50ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c50ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c38ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IMUL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c38ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IMUL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c10ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c10ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c18ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISCADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c18ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISCADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c20ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c20ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BFE "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BFE "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5bf0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BFI "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4bf0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BFI "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x53f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BFI "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c28ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c28ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c48ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c48ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5bf8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SHF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5cf8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SHF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LOP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LOP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5be0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LOP3 "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5be0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LOP3 "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c30ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FLO "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c30ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FLO "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c08ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"POPC "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c08ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"POPC "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x50f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5ca8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5ca8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5ca8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5ca8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5ca8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5ca8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5ca8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5ca8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4ca8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x4ca8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x4ca8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x4ca8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x4ca8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x4ca8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x4ca8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x4ca8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x5cb0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5cb0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5cb0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5cb0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4cb0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x4cb0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x4cb0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x4cb0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x5cb8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"I2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5cb8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"I2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5cb8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"I2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5cb8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"I2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4cb8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"I2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x4cb8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"I2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x4cb8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"I2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x4cb8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"I2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x5ce0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"I2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5ce0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"I2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4ce0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"I2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x4ce0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"I2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x5c98ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"MOV "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4c98ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"MOV "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x5ca0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SEL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4ca0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SEL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xef10ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SHFL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xef10ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SHFL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xef10ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SHFL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xef10ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SHFL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5ce8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"P2R "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4ce8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"P2R "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5cf0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"R2P "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4cf0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"R2P "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5098ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x5098ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x50a0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CSETP "; + if(((inst>>48)&0xfff8ULL)==0x50a0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CSETP "; + if(((inst>>48)&0xfff8ULL)==0x5088ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"PSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x5088ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"PSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x5090ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"PSETP "; + if(((inst>>48)&0xfff8ULL)==0x5090ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"PSETP "; + if(((inst>>48)&0xfff8ULL)==0xeea0ULL)return s+"STP "; + if(((inst>>48)&0xfff8ULL)==0xdf58ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TMML "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xdf58ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TMML "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xdf60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TMML "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xdf60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TMML "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xdf48ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TXQ "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xdf48ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TXQ "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xdf48ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TXQ "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xdf48ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TXQ "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xdf50ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TXQ "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xdf50ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TXQ "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xdf50ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TXQ "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xdf50ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TXQ "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xdf40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TXA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xdf40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TXA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DEPBAR "; + if(((inst>>48)&0xfff8ULL)==0xf0f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DEPBAR "; + if(((inst>>48)&0xfff8ULL)==0xf0f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DEPBAR "; + if(((inst>>48)&0xfff8ULL)==0xefa0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"AL2P "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xefa0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"AL2P "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xefd8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ALD "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xefd8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ALD "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xefd8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ALD "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xefd8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ALD "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xeff0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"AST "; + if(((inst>>48)&0xfff8ULL)==0xeff0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"AST "; + if(((inst>>48)&0xfff8ULL)==0xeff0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"AST "; + if(((inst>>48)&0xfff8ULL)==0xeff0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"AST "; + if(((inst>>48)&0xfff8ULL)==0xfbe0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"OUT "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xebe0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"OUT "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xefe8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"PIXLD "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xefe8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"PIXLD "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xefe8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"PIXLD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xef90ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LDC "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xef90ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LDC "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xef90ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LDC "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xef90ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LDC "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xeed0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LDG "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xeed0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LDG "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xeec8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LDG "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xeec8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LDG "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xef40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LDL "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xef40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LDL "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xef48ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LDS "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x5bd0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LEA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x4bd0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LEA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x5bd8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LEA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xeed8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"STG "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xeed8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"STG "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xef50ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"STL "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xef50ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"STL "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xef58ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"STS "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xef58ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"STS "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xebf8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"RED "; + if(((inst>>48)&0xfff8ULL)==0xebf8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"RED "; + if(((inst>>48)&0xfff8ULL)==0xef80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CCTLL "; + if(((inst>>48)&0xfff8ULL)==0xef80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CCTLL "; + if(((inst>>48)&0xfff8ULL)==0xef80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CCTLL "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xef80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CCTLL "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xebe8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CCTLT "; + if(((inst>>48)&0xfff8ULL)==0xebf0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CCTLT "; + if(((inst>>48)&0xfff8ULL)==0xebf0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CCTLT "; + if(((inst>>48)&0xfff8ULL)==0xef98ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"MEMBAR "; + if(((inst>>48)&0xfff8ULL)==0xeb10ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SULD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xeb18ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SULD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xeb00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SULD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xeb08ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SULD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xeb30ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SUST "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xeb38ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SUST "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xeb20ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SUST "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xeb28ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SUST "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xeb50ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SURED "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xeb58ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SURED "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xeb40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SURED "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xeb48ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SURED "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xea70ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SUATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xea60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SUATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xea68ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SUATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xead0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SUATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xeac0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SUATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xeac8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SUATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0f8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SYNC "; + if(((inst>>48)&0xfff8ULL)==0x50b0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"NOP "; + if(((inst>>48)&0xfff8ULL)==0x50b0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"NOP "; + if(((inst>>48)&0xfff8ULL)==0xf0c8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"S2R "+"R"+std::to_string((inst&0xff)>>0x0)+" "+std::string(SpecialRegGetName((inst&0xfffffff)>>0x14)); + if(((inst>>48)&0xfff8ULL)==0x50c8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CS2R "+"R"+std::to_string((inst&0xff)>>0x0)+" "+std::string(SpecialRegGetName((inst&0xfffffff)>>0x14)); + if(((inst>>48)&0xfff8ULL)==0xf0b8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"B2R "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0b8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"B2R "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0b8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"B2R "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0c0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"R2B "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x50d0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LEPC "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BAR "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BAR "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BAR "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BAR "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BAR "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BAR "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BAR "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BAR "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BAR "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BAR "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BAR "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BAR "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BAR "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BAR "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BAR "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BAR "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0xf0a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BAR "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff8ULL)==0x50e0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VOTE "; + if(((inst>>48)&0xfff8ULL)==0x50d8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VOTE "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0xefd0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISBERD "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff8ULL)==0x0ULL)return s+"HFMA2 "; + if(((inst>>48)&0xfff8ULL)==0x0ULL)return s+"HSET2 "; + if(((inst>>48)&0xfff8ULL)==0x0ULL)return s+"HSET2 "; + if(((inst>>48)&0xfff8ULL)==0x0ULL)return s+"HADD2 "; + if(((inst>>48)&0xfff8ULL)==0x0ULL)return s+"HMUL2 "; + if(((inst>>48)&0xfff8ULL)==0x5d20ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"HSETP2 "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff8ULL)==0x5d20ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"HSETP2 "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfef8ULL)==0x3858ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x5ba0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FCMP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x4ba0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FCMP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x53a0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FCMP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x3868ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FMUL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x3860ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x5bb0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x5bb0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x4bb0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x4bb0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x3890ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"RRO "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x3888ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FCHK "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x5b70ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DFMA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x4b70ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DFMA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x5370ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DFMA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x3870ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x3880ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DMUL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x3850ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x5b80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x5b80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x4b80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x4b80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x3838ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IMUL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x3810ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x5cc0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IADD3 "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x4cc0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IADD3 "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x3818ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISCADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x3820ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x3800ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BFE "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x36f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BFI "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x3828ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x3848ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x36f8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SHF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x38f8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SHF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x3840ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LOP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x3830ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FLO "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff0ULL)==0x5b50ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x5b50ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x4b50ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x4b50ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x5b50ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x5b50ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x4b50ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x4b50ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x5b60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x5b60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x4b60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x4b60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x5b60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x5b60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x4b60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x4b60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x5b40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ICMP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x4b40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ICMP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x5340ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ICMP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x5b40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ICMP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x4b40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ICMP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x5340ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ICMP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x3808ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"POPC "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38a8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38b0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38b0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38b0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38b0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"F2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38b8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"I2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38b8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"I2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38b8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"I2F "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38e0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"I2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38e0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"I2I "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x3898ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"MOV "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff0ULL)==0x100ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"MOV32I "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef8ULL)==0x38a0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SEL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x5bc0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"PRMT "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0x4bc0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"PRMT "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0x53c0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"PRMT "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x38e8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"P2R "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x38e8ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"P2R "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x38f0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"R2P "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0xf6e0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"OUT "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef8ULL)==0x36d0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LEA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0xeef0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff0ULL)==0xeef0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff0ULL)==0xeef0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0xeef0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0xeef0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0xeef0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0xee60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOM "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0xee70ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff0ULL)==0xee70ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0xee70ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfff0ULL)==0xe240ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BRA "; + if(((inst>>48)&0xfff0ULL)==0xe240ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BRA "; + if(((inst>>48)&0xfff0ULL)==0xe250ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BRX "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0xe250ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BRX "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0xe210ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"JMP "; + if(((inst>>48)&0xfff0ULL)==0xe210ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"JMP "; + if(((inst>>48)&0xfff0ULL)==0xe200ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"JMX "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0xe200ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"JMX "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0xe260ULL)return s+"CAL "; + if(((inst>>48)&0xfff0ULL)==0xe260ULL)return s+"CAL "; + if(((inst>>48)&0xfff0ULL)==0xe270ULL)return s+"PRET "; + if(((inst>>48)&0xfff0ULL)==0xe270ULL)return s+"PRET "; + if(((inst>>48)&0xfff0ULL)==0xe220ULL)return s+"JCAL "; + if(((inst>>48)&0xfff0ULL)==0xe220ULL)return s+"JCAL "; + if(((inst>>48)&0xfff0ULL)==0xe290ULL)return s+"SSY "; + if(((inst>>48)&0xfff0ULL)==0xe290ULL)return s+"SSY "; + if(((inst>>48)&0xfff0ULL)==0xe280ULL)return s+"PLONGJMP "; + if(((inst>>48)&0xfff0ULL)==0xe280ULL)return s+"PLONGJMP "; + if(((inst>>48)&0xfff0ULL)==0xe2a0ULL)return s+"PBK "; + if(((inst>>48)&0xfff0ULL)==0xe2a0ULL)return s+"PBK "; + if(((inst>>48)&0xfff0ULL)==0xe2b0ULL)return s+"PCNT "; + if(((inst>>48)&0xfff0ULL)==0xe2b0ULL)return s+"PCNT "; + if(((inst>>48)&0xfff0ULL)==0xe320ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"RET "; + if(((inst>>48)&0xfff0ULL)==0xe310ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LONGJMP "; + if(((inst>>48)&0xfff0ULL)==0xe330ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"KIL "; + if(((inst>>48)&0xfff0ULL)==0xe340ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"BRK "; + if(((inst>>48)&0xfff0ULL)==0xe350ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CONT "; + if(((inst>>48)&0xfff0ULL)==0xe300ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"EXIT "; + if(((inst>>48)&0xfff0ULL)==0xe230ULL)return s+"PEXIT "; + if(((inst>>48)&0xfff0ULL)==0xe370ULL)return s+"SAM "; + if(((inst>>48)&0xfff0ULL)==0xe380ULL)return s+"RAM "; + if(((inst>>48)&0xfff0ULL)==0xe3a0ULL)return s+"BPT "; + if(((inst>>48)&0xfff0ULL)==0xe360ULL)return s+"RTT "; + if(((inst>>48)&0xfff0ULL)==0xe390ULL)return s+"IDE "; + if(((inst>>48)&0xfff0ULL)==0xe390ULL)return s+"IDE "; + if(((inst>>48)&0xfff0ULL)==0xe2e0ULL)return s+"SETCRSPTR "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0xe2c0ULL)return s+"GETCRSPTR "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfff0ULL)==0xe2f0ULL)return s+"SETLMEMBASE "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfff0ULL)==0xe2d0ULL)return s+"GETLMEMBASE "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xfef0ULL)==0x36a0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FCMP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef0ULL)==0x36b0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef0ULL)==0x36b0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef0ULL)==0x3670ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DFMA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef0ULL)==0x3680ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef0ULL)==0x3680ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DSETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef0ULL)==0x38c0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IADD3 "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef0ULL)==0x3650ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef0ULL)==0x3650ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef0ULL)==0x3650ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef0ULL)==0x3650ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef0ULL)==0x3660ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef0ULL)==0x3660ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef0ULL)==0x3660ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef0ULL)==0x3660ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISETP "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef0ULL)==0x3640ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ICMP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef0ULL)==0x3640ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ICMP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfef0ULL)==0x36c0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"PRMT "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xffe0ULL)==0xee40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOMS "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xffe0ULL)==0xee40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOMS "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xffe0ULL)==0xee40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOMS "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffe0ULL)==0xee40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOMS "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffe0ULL)==0xee40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOMS "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffe0ULL)==0xee40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOMS "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffe0ULL)==0xef60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CCTL "; + if(((inst>>48)&0xffe0ULL)==0xef60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CCTL "; + if(((inst>>48)&0xffe0ULL)==0xef60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CCTL "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xffe0ULL)==0xef60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CCTL "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xffe0ULL)==0xef60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CCTL "; + if(((inst>>48)&0xffe0ULL)==0xef60ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"CCTL "; + if(((inst>>48)&0xffc0ULL)==0x5b00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"XMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffc0ULL)==0xde80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TEX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffc0ULL)==0xde80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TEX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffc0ULL)==0xdf00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TLD4S "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffc0ULL)==0xdf80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TLD4S "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffc0ULL)==0xdec0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TLD4 "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffc0ULL)==0xdec0ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TLD4 "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffc0ULL)==0xde00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TXD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffc0ULL)==0xde00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TXD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffc0ULL)==0xde40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TXD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffc0ULL)==0xde40ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TXD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffc0ULL)==0xee00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOMS "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xffc0ULL)==0xee00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOMS "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xffc0ULL)==0xee00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOMS "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffc0ULL)==0xee00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOMS "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffc0ULL)==0xee00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOMS "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffc0ULL)==0xee00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOMS "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffc0ULL)==0xeb80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SUATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffc0ULL)==0xea00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SUATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xffc0ULL)==0xea80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"SUATOM "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff80ULL)==0x5980ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FFMA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff80ULL)==0x4980ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FFMA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff80ULL)==0x5180ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FFMA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff80ULL)==0x5900ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff80ULL)==0x5900ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff80ULL)==0x4900ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff80ULL)==0x4900ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff80ULL)==0x5a00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff80ULL)==0x4a00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff80ULL)==0x5200ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff80ULL)==0x5a80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IMADSP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff80ULL)==0x4a80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IMADSP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff80ULL)==0x5280ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IMADSP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfec0ULL)==0x3600ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"XMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfec0ULL)==0x3600ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"XMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff80ULL)==0x5100ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"XMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff80ULL)==0x5000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF4 "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff80ULL)==0x5000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF4 "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff80ULL)==0x5000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF4 "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe80ULL)==0x3280ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FFMA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x1e00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FMUL32I "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5800ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5800ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x4800ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x4800ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0xe000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IPA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0xe000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IPA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0xe000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IPA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0xe000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IPA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe80ULL)==0x3200ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe80ULL)==0x3200ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"DSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe80ULL)==0x3400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe80ULL)==0x3480ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IMADSP "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x1f00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IMUL32I "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5f00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5f00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5f00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5f00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5f00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5f00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5f00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5f00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5f00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5f00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5f00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5f00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5f00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5f00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5f00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VABSDIFF "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5700ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5700ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5700ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5700ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5700ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5700ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5700ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5700ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5700ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5700ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5700ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5700ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5700ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5700ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5700ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHL "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5600ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5600ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5600ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5600ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5600ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5600ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5600ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5600ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5600ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0x5600ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5600ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5600ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5600ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5600ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0x5600ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSHR "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xff00ULL)==0xdc00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TLD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0xdd00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TLD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0xed00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOM "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0xed00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOM "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0xec00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOMS "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xff00ULL)==0xec00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ATOMS "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe80ULL)==0x80ULL)return s+"HSET2 "; + if(((inst>>48)&0xfe80ULL)==0x80ULL)return s+"HSET2 "; + if(((inst>>48)&0xfe80ULL)==0x80ULL)return s+"HSET2 "; + if(((inst>>48)&0xfe80ULL)==0x80ULL)return s+"HSET2 "; + if(((inst>>48)&0xfe80ULL)==0x80ULL)return s+"HADD2 "; + if(((inst>>48)&0xfe80ULL)==0x80ULL)return s+"HADD2 "; + if(((inst>>48)&0xfe80ULL)==0x80ULL)return s+"HMUL2 "; + if(((inst>>48)&0xfe80ULL)==0x80ULL)return s+"HMUL2 "; + if(((inst>>48)&0xfe80ULL)==0x7e00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"HSETP2 "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe80ULL)==0x7e00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"HSETP2 "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe80ULL)==0x7e80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"HSETP2 "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe80ULL)==0x7e80ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"HSETP2 "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x3000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x3000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x1c00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IADD32I "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x200ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LOP3 "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x4e00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"XMAD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x3a00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x3a00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x3a00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x3a00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x3a00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x3a00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x3a00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x3a00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x3a00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x3a00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x3a00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x3a00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x3a00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x3a00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x3a00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VMNMX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x4000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x4000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x4000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x4000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x4000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x4000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x4000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x4000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x4000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x4000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x4000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x4000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x4000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x4000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x4000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VSET "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0xd800ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TEXS "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0xd800ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TEXS "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0xd000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TEXS "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0xd000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TEXS "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0xda00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TLDS "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0xda00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TLDS "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0xd200ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TLDS "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0xd200ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TLDS "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfe00ULL)==0x0ULL)return s+"HMUL2_32I "; + if(((inst>>48)&0xfe00ULL)==0x0ULL)return s+"HADD2_32I "; + if(((inst>>48)&0xfe00ULL)==0x2800ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"HFMA2_32I "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x2800ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"HFMA2_32I "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfc00ULL)==0xc00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FFMA32I "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfc00ULL)==0xc00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FFMA32I "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfc00ULL)==0x800ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"FADD32I "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfc00ULL)==0x1000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"IMAD32I "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfc00ULL)==0x1400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ISCADD32I "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfc00ULL)==0x400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LOP32I "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfc00ULL)==0x400ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LOP3 "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfe00ULL)==0x200ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LOP3 "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfc00ULL)==0x3c00ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LOP3 "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfc00ULL)==0x2000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfc00ULL)==0x2000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfc00ULL)==0x2000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfc00ULL)==0x2000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfc00ULL)==0x2000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfc00ULL)==0x2000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfc00ULL)==0x2000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfc00ULL)==0x2000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfc00ULL)==0x2000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xfc00ULL)==0x2000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfc00ULL)==0x2000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfc00ULL)==0x2000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfc00ULL)==0x2000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfc00ULL)==0x2000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfc00ULL)==0x2000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"VADD "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xfc00ULL)==0x1800ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LEA "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xf880ULL)==0x0ULL)return s+"HFMA2 "; + if(((inst>>48)&0xf880ULL)==0x0ULL)return s+"HFMA2 "; + if(((inst>>48)&0xf880ULL)==0x6080ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"HFMA2 "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "; + if(((inst>>48)&0xf800ULL)==0xc000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TEX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xf800ULL)==0xc000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TEX "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xf800ULL)==0xc800ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TLD4 "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xf800ULL)==0xc800ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"TLD4 "+"R"+std::to_string((inst&0xff)>>0x0)+" "+"R"+std::to_string((inst&0xffff)>>0x8)+" "+"R"+std::to_string((inst&0xfffffff)>>0x14)+" "; + if(((inst>>48)&0xe000ULL)==0x8000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LD "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xe000ULL)==0x8000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"LD "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xe000ULL)==0xa000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ST "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + if(((inst>>48)&0xe000ULL)==0xa000ULL)return s+(((inst&0x7ffff)>>0x10)?"":"!")+"ST "+"R"+std::to_string((inst&0xff)>>0x0)+" "; + return "?";} +} diff --git a/tools/maxwell-disas/main.cpp b/tools/maxwell-disas/main.cpp new file mode 100644 index 0000000000..d389ee4b8a --- /dev/null +++ b/tools/maxwell-disas/main.cpp @@ -0,0 +1,221 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "common/assert.h" +#include +#include +#include + +enum class Opcode { +#define INST(name, cute, encode) name, +#include "shader_recompiler/frontend/maxwell/maxwell.inc" +#undef INST +}; + +consteval std::pair MaskValueFromEncoding(const char data[20]) noexcept { + u64 mask = 0, value = 0, bit = u64(1) << 63; + for (int i = 0; i < 20; ++i) + switch (data[i]) { + case '0': + mask |= bit; + bit >>= 1; + break; + case '1': + mask |= bit; + value |= bit; + bit >>= 1; + break; + case '-': + bit >>= 1; + break; + default: + break; + } + return { mask, value }; +} + +Opcode Decode(u64 insn) { +#define INST(name, cute, encode) \ + if (auto const p = MaskValueFromEncoding(encode); (insn & p.first) == p.second) \ + return Opcode::name; +#include "shader_recompiler/frontend/maxwell/maxwell.inc" +#undef INST + ASSERT_MSG(false, "Invalid insn {:#016x}", insn); + return Opcode::NOP; +} + +const char* NameOf(Opcode opcode) { + constexpr const char* NAME_TABLE[] = { +#define INST(name, cute, encode) cute, +#include "shader_recompiler/frontend/maxwell/maxwell.inc" +#undef INST + }; + ASSERT_MSG(size_t(opcode) < sizeof(NAME_TABLE) / sizeof(NAME_TABLE[0]), "Invalid opcode with raw value {}", int(opcode)); + return NAME_TABLE[size_t(opcode)]; +} + +namespace Shader::Maxwell { +std::string_view SpecialRegGetName(size_t i) { + switch (i) { + case 0: return "SR_LANEID"; + case 1: return "SR_CLOCK"; + case 2: return "SR_VIRTCFG"; + case 3: return "SR_VIRTID"; + case 4: return "SR_PM0"; + case 5: return "SR_PM1"; + case 6: return "SR_PM2"; + case 7: return "SR_PM3"; + case 8: return "SR_PM4"; + case 9: return "SR_PM5"; + case 10: return "SR_PM6"; + case 11: return "SR_PM7"; + case 12: return "SR_?"; + case 13: return "SR_?"; + case 14: return "SR_?"; + case 15: return "SR_ORDERING_TICKET"; + case 16: return "SR_PRIM_TYPE"; + case 17: return "SR_INVOCATION_ID"; + case 18: return "SR_Y_DIRECTION"; + case 19: return "SR_THREAD_KILL"; + case 20: return "SM_SHADER_TYPE"; + case 21: return "SR_DIRECTCBEWRITEADDRESSLOW"; + case 22: return "SR_DIRECTCBEWRITEADDRESSHIGH"; + case 23: return "SR_DIRECTCBEWRITEENABLED"; + case 24: return "SR_MACHINE_ID_0"; + case 25: return "SR_MACHINE_ID_1"; + case 26: return "SR_MACHINE_ID_2"; + case 27: return "SR_MACHINE_ID_3"; + case 28: return "SR_AFFINITY"; + case 29: return "SR_INVOCATION_INFO"; + case 30: return "SR_WSCALEFACTOR_XY"; + case 31: return "SR_WSCALEFACTOR_Z"; + case 32: return "SR_TID"; + case 33: return "SR_TID_X"; + case 34: return "SR_TID_Y"; + case 35: return "SR_TID_Z"; + case 36: return "SR_CTA_PARAM"; + case 37: return "SR_CTAID_X"; + case 38: return "SR_CTAID_Y"; + case 39: return "SR_CTAID_Z"; + case 40: return "SR_NTID"; + case 41: return "SR_CirQueueIncrMinusOne"; + case 42: return "SR_NLATC"; + case 43: return "SR_?"; + case 44: return "SR_SM_SPA_VERSION"; + case 45: return "SR_MULTIPASSSHADERINFO"; + case 46: return "SR_LWINHI"; + case 47: return "SR_SWINHI"; + case 48: return "SR_SWINLO"; + case 49: return "SR_SWINSZ"; + case 50: return "SR_SMEMSZ"; + case 51: return "SR_SMEMBANKS"; + case 52: return "SR_LWINLO"; + case 53: return "SR_LWINSZ"; + case 54: return "SR_LMEMLOSZ"; + case 55: return "SR_LMEMHIOFF"; + case 56: return "SR_EQMASK"; + case 57: return "SR_LTMASK"; + case 58: return "SR_LEMASK"; + case 59: return "SR_GTMASK"; + case 60: return "SR_GEMASK"; + case 61: return "SR_REGALLOC"; + case 62: return "SR_BARRIERALLOC"; + case 63: return "SR_?"; + case 64: return "SR_GLOBALERRORSTATUS"; + case 65: return "SR_?"; + case 66: return "SR_WARPERRORSTATUS"; + case 67: return "SR_WARPERRORSTATUSCLEAR"; + case 68: return "SR_?"; + case 69: return "SR_?"; + case 70: return "SR_?"; + case 71: return "SR_?"; + case 72: return "SR_PM_HI0"; + case 73: return "SR_PM_HI1"; + case 74: return "SR_PM_HI2"; + case 75: return "SR_PM_HI3"; + case 76: return "SR_PM_HI4"; + case 77: return "SR_PM_HI5"; + case 78: return "SR_PM_HI6"; + case 79: return "SR_PM_HI7"; + case 80: return "SR_CLOCKLO"; + case 81: return "SR_CLOCKHI"; + case 82: return "SR_GLOBALTIMERLO"; + case 83: return "SR_GLOBALTIMERHI"; + case 84: return "SR_?"; + case 85: return "SR_?"; + case 86: return "SR_?"; + case 87: return "SR_?"; + case 88: return "SR_?"; + case 89: return "SR_?"; + case 90: return "SR_?"; + case 91: return "SR_?"; + case 92: return "SR_?"; + case 93: return "SR_?"; + case 94: return "SR_?"; + case 95: return "SR_?"; + case 96: return "SR_HWTASKID"; + case 97: return "SR_CIRCULARQUEUEENTRYINDEX"; + case 98: return "SR_CIRCULARQUEUEENTRYADDRESSLOW"; + case 99: return "SR_CIRCULARQUEUEENTRYADDRESSHIGH"; + default: return "SR_??"; } +} +} +#include "generated.cpp" + +int DisasReferenceImpl(int argc, char *argv[]) { + std::vector code; + FILE *fp = fopen(argv[1], "rb"); + if (fp != NULL) { + struct stat st; + fstat(fileno(fp), &st); + auto const words = (size_t(st.st_size) / sizeof(uint64_t)); + code.resize(words + 1); + fread(code.data(), sizeof(uint64_t), words, fp); + fclose(fp); + } + for (size_t i = 0; i < code.size(); ++i) { + printf("%016lx\t%-40s\n", code[i] + , Shader::Maxwell::DissasemblyFormat(code[i]).data() + ); + } + return EXIT_SUCCESS; +} + +int DisasShaderRecompilerImpl(int argc, char *argv[]) { + std::vector code; + FILE *fp = fopen(argv[1], "rb"); + if (fp != NULL) { + struct stat st; + fstat(fileno(fp), &st); + auto const words = (size_t(st.st_size) / sizeof(u64)); + code.resize(words + 1); + fread(code.data(), sizeof(u64), words, fp); + fclose(fp); + } + + for (size_t i = 0; i < code.size(); ++i) { + auto const opcode = Decode(code[i]); + printf("%016lx\t%s\n", code[i], NameOf(opcode)); + } + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) { + if (argc < 2) { + printf( + "usage: %s [input file] [-n/i]\n" + "Specify -n to use a disassembler that is NOT tied to the shader recompiler\n" + "aka. a reference disassembler\n" + , argv[0]); + return EXIT_FAILURE; + } + + //DumpProgram + + if (argc >= 3) { + if (::strcmp(argv[2], "-n") == 0 || ::strcmp(argv[2], "--new") == 0) { + return DisasReferenceImpl(argc, argv); + } + } + return DisasShaderRecompilerImpl(argc, argv); +} diff --git a/tools/maxwell-ir/CMakeLists.txt b/tools/maxwell-ir/CMakeLists.txt new file mode 100644 index 0000000000..45441dcae1 --- /dev/null +++ b/tools/maxwell-ir/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later +add_executable(maxwell-ir main.cpp) +target_link_libraries(maxwell-ir PRIVATE common shader_recompiler Threads::Threads) +target_include_directories(maxwell-ir PRIVATE ${CMAKE_SOURCE_DIR}/src) +if(UNIX AND NOT APPLE) + install(TARGETS maxwell-ir) +endif() +create_target_directory_groups(maxwell-ir) diff --git a/tools/maxwell-ir/main.cpp b/tools/maxwell-ir/main.cpp new file mode 100644 index 0000000000..645816878d --- /dev/null +++ b/tools/maxwell-ir/main.cpp @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include + +#include "shader_recompiler/frontend/ir/basic_block.h" +#include "shader_recompiler/frontend/maxwell/control_flow.h" +#include "shader_recompiler/frontend/maxwell/translate_program.h" +#include "shader_recompiler/host_translate_info.h" +#include "shader_recompiler/object_pool.h" +#include "../maxwell-disas/file_environment.h" + +int IrShaderRecompilerImpl(int argc, char *argv[]) { + size_t cfg_offset = 0; + + Shader::ObjectPool inst_pool; + Shader::ObjectPool block_pool; + Shader::ObjectPool cfg_blocks; + FileEnvironment env; + + FILE *fp = fopen(argv[1], "rb"); + if (fp != NULL) { + struct stat st; + fstat(fileno(fp), &st); + auto const words = (st.st_size / sizeof(u64)); + env.code.resize(words + 1); + fread(env.code.data(), sizeof(u64), words, fp); + fclose(fp); + } + + env.read_highest = env.read_lowest + env.code.size() * sizeof(u64); + + Shader::Maxwell::Flow::CFG cfg(env, cfg_blocks, cfg_offset); + + Shader::HostTranslateInfo host_info; + host_info.support_float64 = true; + host_info.support_float16 = true; + host_info.support_int64 = true; + host_info.needs_demote_reorder = true; + host_info.support_snorm_render_buffer = true; + host_info.support_viewport_index_layer = true; + host_info.support_geometry_shader_passthrough = true; + host_info.support_conditional_barrier = true; + host_info.min_ssbo_alignment = 0; + host_info.ApplyDescriptorLimitPolicy(); + auto program = Shader::Maxwell::TranslateProgram(inst_pool, block_pool, env, cfg, host_info); + auto const dumped_ir = Shader::IR::DumpProgram(program); + std::printf("%s\n", dumped_ir.c_str()); + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) { + if (argc < 2) { + printf("usage: %s [input file]\n", argv[0]); + return EXIT_FAILURE; + } + return IrShaderRecompilerImpl(argc, argv); +} diff --git a/tools/maxwell-spirv/CMakeLists.txt b/tools/maxwell-spirv/CMakeLists.txt new file mode 100644 index 0000000000..819373712e --- /dev/null +++ b/tools/maxwell-spirv/CMakeLists.txt @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later +add_executable(maxwell-spirv + main.cpp + spirv_recompiler_impl.cpp + spirv_reference_impl.cpp +) +target_link_libraries(maxwell-spirv PRIVATE common shader_recompiler Threads::Threads) +target_include_directories(maxwell-spirv PRIVATE ${CMAKE_SOURCE_DIR}/src) +if(UNIX AND NOT APPLE) + install(TARGETS maxwell-spirv) +endif() +create_target_directory_groups(maxwell-spirv) diff --git a/tools/maxwell-spirv/main.cpp b/tools/maxwell-spirv/main.cpp new file mode 100644 index 0000000000..f9245d436e --- /dev/null +++ b/tools/maxwell-spirv/main.cpp @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include + +int SpirvReferenceImpl(int argc, char *argv[]); +int SpirvShaderRecompilerImpl(int argc, char *argv[]); + +int main(int argc, char *argv[]) { + if (argc < 2) { + printf("usage: %s [input file]\n" + "Specify -n to use a recompiler that is NOT tied to the shader recompiler\n" + "aka. a reference recompiler\n" + "RAW SPIRV CODE WILL BE SENT TO STDOUT!\n", argv[0]); + return EXIT_FAILURE; + } + if (argc >= 3) { + if (::strcmp(argv[2], "-n") == 0 + || ::strcmp(argv[2], "--new") == 0) { + return SpirvReferenceImpl(argc, argv); + } + } + return SpirvShaderRecompilerImpl(argc, argv); +} diff --git a/tools/maxwell-spirv/spirv_recompiler_impl.cpp b/tools/maxwell-spirv/spirv_recompiler_impl.cpp new file mode 100644 index 0000000000..9dae69df91 --- /dev/null +++ b/tools/maxwell-spirv/spirv_recompiler_impl.cpp @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include "shader_recompiler/backend/spirv/emit_spirv.h" +#include "shader_recompiler/environment.h" +#include "shader_recompiler/frontend/maxwell/control_flow.h" +#include "shader_recompiler/frontend/maxwell/translate_program.h" +#include "shader_recompiler/host_translate_info.h" +#include "shader_recompiler/object_pool.h" +#include "shader_recompiler/profile.h" +#include "shader_recompiler/runtime_info.h" + +#include "../maxwell-disas/file_environment.h" + +int SpirvShaderRecompilerImpl(int argc, char *argv[]) { + if (argc != 2) { + printf("usage: %s [input file] [-n]\n" + "RAW SPIRV CODE WILL BE SENT TO STDOUT!\n", argv[0]); + return EXIT_FAILURE; + } + + size_t cfg_offset = 0; + + Shader::ObjectPool inst_pool; + Shader::ObjectPool block_pool; + Shader::ObjectPool cfg_blocks; + FileEnvironment env; + + FILE *fp = fopen(argv[1], "rb"); + if (fp != NULL) { + struct stat st; + fstat(fileno(fp), &st); + auto const words = (st.st_size / sizeof(u64)); + env.code.resize(words + 1); + fread(env.code.data(), sizeof(u64), words, fp); + fclose(fp); + } + + env.read_highest = env.read_lowest + env.code.size() * sizeof(u64); + + Shader::Maxwell::Flow::CFG cfg(env, cfg_blocks, cfg_offset); + + Shader::HostTranslateInfo host_info; + host_info.support_float64 = true; + host_info.support_float16 = true; + host_info.support_int64 = true; + host_info.needs_demote_reorder = true; + host_info.support_snorm_render_buffer = true; + host_info.support_viewport_index_layer = true; + host_info.support_geometry_shader_passthrough = true; + host_info.support_conditional_barrier = true; + host_info.min_ssbo_alignment = 0; + host_info.ApplyDescriptorLimitPolicy(); + auto program = Shader::Maxwell::TranslateProgram(inst_pool, block_pool, env, cfg, host_info); + + // IR::Program TranslateProgram(ObjectPool& inst_pool, ObjectPool& block_pool, + // Environment& env, Flow::CFG& cfg, const HostTranslateInfo& host_info) + // std::vector EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info, + // IR::Program& program, Bindings& bindings, bool optimize) + Shader::Profile profile{}; + Shader::RuntimeInfo runtime_info; + auto const spirv_pgm = Shader::Backend::SPIRV::EmitSPIRV(profile, program, true); + fwrite(spirv_pgm.data(), sizeof(u64), spirv_pgm.size(), stdout); + + return EXIT_SUCCESS; +} diff --git a/tools/maxwell-spirv/spirv_reference_impl.cpp b/tools/maxwell-spirv/spirv_reference_impl.cpp new file mode 100644 index 0000000000..db4d0994e4 --- /dev/null +++ b/tools/maxwell-spirv/spirv_reference_impl.cpp @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "common/bit_field.h" +#include "common/common_types.h" +#include "spirv/unified1/spirv.hpp11" +#include +#include +#include +#include + +int ReferenceImpl(int argc, char *argv[]) { + //todo + return EXIT_SUCCESS; +} diff --git a/tools/optimize-assets.sh b/tools/optimize-assets.sh index 70fa2375a5..a9b2888648 100755 --- a/tools/optimize-assets.sh +++ b/tools/optimize-assets.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later which optipng || exit diff --git a/tools/stale-translations.sh b/tools/stale-translations.sh index 3a9362a252..63ea87b19a 100755 --- a/tools/stale-translations.sh +++ b/tools/stale-translations.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later ANDROID=src/android/app/src/main diff --git a/tools/svc_generator.py b/tools/svc_generator.py index d00fe0ba63..9a99c3ba51 100755 --- a/tools/svc_generator.py +++ b/tools/svc_generator.py @@ -1,5 +1,5 @@ -#!/usr/bin/python3 -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright 2026 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 @@ -212,7 +212,7 @@ def emit_size_check(): if type != "void": lines.append(f"static_assert(sizeof({type}) == {size});") - return "\n".join(lines) + return f"\n{lines}" # Replaces a type with an arch-specific one, if it exists. @@ -423,7 +423,7 @@ def emit_lines(lines, indent=' '): output_lines.append(line) first = False - return "\n".join(output_lines) + return f"\n{output_lines}" # Emit a C++ function to wrap a guest SVC. @@ -601,7 +601,7 @@ def emit_call(bitness, names, suffix): lines.append(f"{indent}}}") lines.append("}") - return "\n".join(lines) + return f"\n{lines}" def build_fn_declaration(return_type, name, arguments): @@ -623,7 +623,7 @@ def build_enum_declarations(): lines.append(f"{indent}{name} = {hex(imm)},") lines.append("};") - return "\n".join(lines) + return f"\n{lines}" def main(): @@ -665,11 +665,11 @@ def main(): with open("src/core/hle/kernel/svc.h", "w") as f: f.write(COPYRIGHT) f.write(PROLOGUE_H) - f.write("\n".join(svc_fw_declarations)) + f.write(f"\n{svc_fw_declarations}") f.write("\n\n") - f.write("\n".join(arch_fw_declarations[BIT_32])) + f.write(f"\n{arch_fw_declarations[BIT_32]}") f.write("\n\n") - f.write("\n".join(arch_fw_declarations[BIT_64])) + f.write(f"\n{arch_fw_declarations[BIT_64]}") f.write("\n\n") f.write(enum_decls) f.write(EPILOGUE_H) @@ -679,7 +679,7 @@ def main(): f.write(PROLOGUE_CPP) f.write(emit_size_check()) f.write("\n\n") - f.write("\n\n".join(wrapper_fns)) + f.write(f"\n\n{wrapper_fns}") f.write("\n\n") f.write(call_32) f.write("\n\n") diff --git a/tools/translations/qt-source.sh b/tools/translations/qt-source.sh index 89c881d8c0..707ec2783c 100755 --- a/tools/translations/qt-source.sh +++ b/tools/translations/qt-source.sh @@ -1,6 +1,6 @@ -#!/bin/sh +#!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later SOURCES=$(find src/yuzu src/qt_common -type f \( -name "*.ui" -o -name "*.cpp" -o -name "*.h" -o -name "*.plist" \)) diff --git a/tools/translations/update-translations.sh b/tools/translations/update-translations.sh index 8bcd79142f..6ed740140d 100755 --- a/tools/translations/update-translations.sh +++ b/tools/translations/update-translations.sh @@ -1,6 +1,6 @@ -#!/bin/sh +#!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later command -v tx-cli && COMMAND=tx-cli diff --git a/tools/unused-strings.sh b/tools/unused-strings.sh index 1165a203aa..10f6a63565 100755 --- a/tools/unused-strings.sh +++ b/tools/unused-strings.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later ANDROID=src/android/app/src/main diff --git a/tools/windows/install-vulkan-sdk.sh b/tools/windows/install-vulkan-sdk.sh index 0f136748cf..2151363793 100644 --- a/tools/windows/install-vulkan-sdk.sh +++ b/tools/windows/install-vulkan-sdk.sh @@ -1,4 +1,4 @@ -#!/usr/bin/sh +#!/bin/sh -e # SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later