diff --git a/.ci/scripts/common/post-upload.sh b/.ci/scripts/common/post-upload.sh index e46ee0abb..99e79fcb6 100644 --- a/.ci/scripts/common/post-upload.sh +++ b/.ci/scripts/common/post-upload.sh @@ -15,5 +15,5 @@ mv "${REV_NAME}-source.tar.xz" $RELEASE_NAME 7z a "$REV_NAME.7z" $RELEASE_NAME # move the compiled archive into the artifacts directory to be uploaded by travis releases -mv "$ARCHIVE_NAME" artifacts/ -mv "$REV_NAME.7z" artifacts/ +mv "$ARCHIVE_NAME" "${ARTIFACTS_DIR}/" +mv "$REV_NAME.7z" "${ARTIFACTS_DIR}/" diff --git a/.ci/scripts/common/pre-upload.sh b/.ci/scripts/common/pre-upload.sh index 3c2fc79a2..a49e3fff3 100644 --- a/.ci/scripts/common/pre-upload.sh +++ b/.ci/scripts/common/pre-upload.sh @@ -2,5 +2,6 @@ GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`" GITREV="`git show -s --format='%h'`" +ARTIFACTS_DIR="artifacts" -mkdir -p artifacts +mkdir -p "${ARTIFACTS_DIR}/" diff --git a/.ci/scripts/linux/docker.sh b/.ci/scripts/linux/docker.sh index 277775ef6..30391f6ad 100755 --- a/.ci/scripts/linux/docker.sh +++ b/.ci/scripts/linux/docker.sh @@ -1,14 +1,54 @@ #!/bin/bash -ex +# Exit on error, rather than continuing with the rest of the script. +set -e + cd /yuzu ccache -s mkdir build || true && cd build -cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON +cmake .. -DDISPLAY_VERSION=$1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DCMAKE_INSTALL_PREFIX="/usr" -ninja +make -j$(nproc) ccache -s ctest -VV -C Release + +make install DESTDIR=AppDir +rm -vf AppDir/usr/bin/yuzu-cmd AppDir/usr/bin/yuzu-tester + +# Download tools needed to build an AppImage +wget -nc https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage +wget -nc https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage +wget -nc https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage +wget -nc https://github.com/darealshinji/AppImageKit-checkrt/releases/download/continuous/AppRun-patched-x86_64 +wget -nc https://github.com/darealshinji/AppImageKit-checkrt/releases/download/continuous/exec-x86_64.so +# Set executable bit +chmod 755 \ + appimagetool-x86_64.AppImage \ + AppRun-patched-x86_64 \ + exec-x86_64.so \ + linuxdeploy-x86_64.AppImage \ + linuxdeploy-plugin-qt-x86_64.AppImage + +# Workaround for https://github.com/AppImage/AppImageKit/issues/828 +export APPIMAGE_EXTRACT_AND_RUN=1 + +mkdir -p AppDir/usr/optional +mkdir -p AppDir/usr/optional/libstdc++ +mkdir -p AppDir/usr/optional/libgcc_s + +# Deploy yuzu's needed dependencies +./linuxdeploy-x86_64.AppImage --appdir AppDir --plugin qt + +# Workaround for building yuzu with GCC 10 but also trying to distribute it to Ubuntu 18.04 et al. +# See https://github.com/darealshinji/AppImageKit-checkrt +cp exec-x86_64.so AppDir/usr/optional/exec.so +cp AppRun-patched-x86_64 AppDir/AppRun +cp --dereference /usr/lib/x86_64-linux-gnu/libstdc++.so.6 AppDir/usr/optional/libstdc++/libstdc++.so.6 +cp --dereference /lib/x86_64-linux-gnu/libgcc_s.so.1 AppDir/usr/optional/libgcc_s/libgcc_s.so.1 + +# Build the AppImage +./appimagetool-x86_64.AppImage AppDir diff --git a/.ci/scripts/linux/upload.sh b/.ci/scripts/linux/upload.sh index fe4e6b2ac..7175e4cb5 100644 --- a/.ci/scripts/linux/upload.sh +++ b/.ci/scripts/linux/upload.sh @@ -2,6 +2,8 @@ . .ci/scripts/common/pre-upload.sh +APPIMAGE_NAME="yuzu-x86_64.AppImage" +NEW_APPIMAGE_NAME="yuzu-${GITDATE}-${GITREV}-x86_64.AppImage" REV_NAME="yuzu-linux-${GITDATE}-${GITREV}" ARCHIVE_NAME="${REV_NAME}.tar.xz" COMPRESSION_FLAGS="-cJvf" @@ -17,4 +19,7 @@ mkdir "$DIR_NAME" cp build/bin/yuzu-cmd "$DIR_NAME" cp build/bin/yuzu "$DIR_NAME" +# Copy the AppImage to the artifacts directory and avoid compressing it +cp "build/${APPIMAGE_NAME}" "${ARTIFACTS_DIR}/${NEW_APPIMAGE_NAME}" + . .ci/scripts/common/post-upload.sh diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh index adfd636fa..2bc9f36ab 100755 --- a/.ci/scripts/windows/docker.sh +++ b/.ci/scripts/windows/docker.sh @@ -5,7 +5,7 @@ cd /yuzu ccache -s mkdir build || true && cd build -cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON +cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON ninja ccache -s diff --git a/.ci/templates/build-msvc.yml b/.ci/templates/build-msvc.yml index d85a949aa..721179550 100644 --- a/.ci/templates/build-msvc.yml +++ b/.ci/templates/build-msvc.yml @@ -4,9 +4,11 @@ parameters: version: '' steps: +- script: choco install vulkan-sdk + displayName: 'Install vulkan-sdk' - script: python -m pip install --upgrade pip conan displayName: 'Install conan' -- script: mkdir build && cd build && cmake -G "Visual Studio 16 2019" -A x64 --config Release -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON -DDISPLAY_VERSION=${{ parameters['version'] }} .. && cd .. +- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 16 2019" -A x64 --config Release -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} .. && cd .. displayName: 'Configure CMake' - task: MSBuild@1 displayName: 'Build' diff --git a/.ci/yuzu-patreon-step2.yml b/.ci/yuzu-patreon-step2.yml index 41eccd973..3f338e2a0 100644 --- a/.ci/yuzu-patreon-step2.yml +++ b/.ci/yuzu-patreon-step2.yml @@ -9,6 +9,7 @@ stages: displayName: 'build' jobs: - job: build + timeoutInMinutes: 120 displayName: 'windows-msvc' pool: vmImage: windows-2019 diff --git a/.gitmodules b/.gitmodules index 9d9356151..41022615b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,15 +1,12 @@ [submodule "inih"] path = externals/inih/inih - url = https://github.com/svn2github/inih + url = https://github.com/benhoyt/inih.git [submodule "cubeb"] path = externals/cubeb url = https://github.com/kinetiknz/cubeb.git [submodule "dynarmic"] path = externals/dynarmic url = https://github.com/MerryMage/dynarmic.git -[submodule "unicorn"] - path = externals/unicorn - url = https://github.com/yuzu-emu/unicorn [submodule "soundtouch"] path = externals/soundtouch url = https://github.com/citra-emu/ext-soundtouch.git diff --git a/.travis/linux-mingw/docker.sh b/.travis/linux-mingw/docker.sh index 28033acfb..80d7dfe9b 100755 --- a/.travis/linux-mingw/docker.sh +++ b/.travis/linux-mingw/docker.sh @@ -4,16 +4,8 @@ cd /yuzu # override Travis CI unreasonable ccache size echo 'max_size = 3.0G' > "$HOME/.ccache/ccache.conf" -# Dirty hack to trick unicorn makefile into believing we are in a MINGW system -mv /bin/uname /bin/uname1 && echo -e '#!/bin/sh\necho MINGW64' >> /bin/uname -chmod +x /bin/uname - -# Dirty hack to trick unicorn makefile into believing we have cmd -echo '' >> /bin/cmd -chmod +x /bin/cmd - mkdir build && cd build -cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release +cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release ninja # Clean up the dirty hacks diff --git a/.travis/linux/build.sh b/.travis/linux/build.sh index 3929f97fc..0c7fb8c9d 100755 --- a/.travis/linux/build.sh +++ b/.travis/linux/build.sh @@ -1,4 +1,4 @@ #!/bin/bash -ex mkdir -p "$HOME/.ccache" -docker run -e ENABLE_COMPATIBILITY_REPORTING --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.travis/linux/docker.sh +docker run -e ENABLE_COMPATIBILITY_REPORTING --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/home/yuzu/.ccache yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.travis/linux/docker.sh diff --git a/.travis/linux/docker.sh b/.travis/linux/docker.sh index 3a9970384..166fb6d4c 100755 --- a/.travis/linux/docker.sh +++ b/.travis/linux/docker.sh @@ -3,7 +3,7 @@ cd /yuzu mkdir build && cd build -cmake .. -G Ninja -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON +cmake .. -G Ninja -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON ninja ccache -s diff --git a/.travis/macos/build.sh b/.travis/macos/build.sh index 0abd1a93a..db1c7cae7 100755 --- a/.travis/macos/build.sh +++ b/.travis/macos/build.sh @@ -4,13 +4,12 @@ set -o pipefail export MACOSX_DEPLOYMENT_TARGET=10.14 export Qt5_DIR=$(brew --prefix)/opt/qt5 -export UNICORNDIR=$(pwd)/externals/unicorn export PATH="/usr/local/opt/ccache/libexec:$PATH" # TODO: Build using ninja instead of make mkdir build && cd build cmake --version -cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON +cmake .. -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON make -j4 ccache -s diff --git a/CMakeLists.txt b/CMakeLists.txt index 45bd03a65..aaf3a90cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,18 +18,18 @@ CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" ON "EN option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON) -option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON) - option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF) option(YUZU_ENABLE_BOXCAT "Enable the Boxcat service, a yuzu high-level implementation of BCAT" ON) option(ENABLE_CUBEB "Enables the cubeb audio backend" ON) -option(ENABLE_VULKAN "Enables Vulkan backend" ON) - option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF) +if (NOT ENABLE_WEB_SERVICE) + set(YUZU_ENABLE_BOXCAT OFF) +endif() + # Default to a Release build get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if (NOT IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE) @@ -115,6 +115,9 @@ if (NOT DEFINED ARCHITECTURE) endif() message(STATUS "Target architecture: ${ARCHITECTURE}") +if (UNIX) + add_definitions(-DYUZU_UNIX=1) +endif() # Configure C++ standard # =========================== @@ -159,15 +162,14 @@ macro(yuzu_find_packages) # Capitalization matters here. We need the naming to match the generated paths from Conan set(REQUIRED_LIBS # Cmake Pkg Prefix Version Conan Pkg - "Boost 1.73 boost/1.73.0" "Catch2 2.13 catch2/2.13.0" - "fmt 7.0 fmt/7.0.3" + "fmt 7.1 fmt/7.1.2" # can't use until https://github.com/bincrafters/community/issues/1173 #"libzip 1.5 libzip/1.5.2@bincrafters/stable" "lz4 1.8 lz4/1.9.2" "nlohmann_json 3.8 nlohmann_json/3.8.0" "ZLIB 1.2 zlib/1.2.11" - "zstd 1.4 zstd/1.4.5" + "zstd 1.4 zstd/1.4.8" ) foreach(PACKAGE ${REQUIRED_LIBS}) @@ -194,6 +196,22 @@ macro(yuzu_find_packages) unset(FN_FORCE_REQUIRED) endmacro() +find_package(Boost 1.73.0 COMPONENTS context headers QUIET) +if (Boost_FOUND) + set(Boost_LIBRARIES Boost::boost) + # Conditionally add Boost::context only if the active version of the Conan or system Boost package provides it + # The old version is missing Boost::context, so we want to avoid adding in that case + # The new version requires adding Boost::context to prevent linking issues + # + # This one is used by Conan on subsequent CMake configures, not the first configure. + if (TARGET Boost::context) + list(APPEND Boost_LIBRARIES Boost::context) + endif() +else() + message(STATUS "Boost 1.73.0 or newer not found, falling back to Conan") + list(APPEND CONAN_REQUIRED_LIBS "boost/1.73.0") +endif() + # Attempt to locate any packages that are required and report the missing ones in CONAN_REQUIRED_LIBS yuzu_find_packages() @@ -225,7 +243,7 @@ if(ENABLE_QT) if (YUZU_USE_QT_WEB_ENGINE) find_package(Qt5 COMPONENTS WebEngineCore WebEngineWidgets) endif() - + if (ENABLE_QT_TRANSLATION) find_package(Qt5 REQUIRED COMPONENTS LinguistTools ${QT_PREFIX_HINT}) endif() @@ -263,6 +281,7 @@ if (CONAN_REQUIRED_LIBS) libzip:with_openssl=False libzip:enable_windows_crypto=False ) + conan_check(VERSION 1.24.0 REQUIRED) # Add the bincrafters remote conan_add_remote(NAME bincrafters @@ -297,6 +316,17 @@ if (CONAN_REQUIRED_LIBS) # this time with required, so we bail if its not found. yuzu_find_packages(FORCE_REQUIRED) + if (NOT Boost_FOUND) + find_package(Boost 1.73.0 REQUIRED COMPONENTS context headers) + set(Boost_LIBRARIES Boost::boost) + # Conditionally add Boost::context only if the active version of the Conan Boost package provides it + # The old version is missing Boost::context, so we want to avoid adding in that case + # The new version requires adding Boost::context to prevent linking issues + if (TARGET Boost::context) + list(APPEND Boost_LIBRARIES Boost::context) + endif() + endif() + # Due to issues with variable scopes in functions, we need to also find_package(qt5) outside of the function if(ENABLE_QT) list(APPEND CMAKE_MODULE_PATH "${CONAN_QT_ROOT_RELEASE}") @@ -354,85 +384,23 @@ if (NOT LIBUSB_FOUND) set(LIBUSB_LIBRARIES usb) endif() +# Use system installed ffmpeg. +if (NOT MSVC) + find_package(FFmpeg REQUIRED) +else() + set(FFMPEG_EXT_NAME "ffmpeg-4.2.1") + set(FFMPEG_PATH "${CMAKE_BINARY_DIR}/externals/${FFMPEG_EXT_NAME}") + download_bundled_external("ffmpeg/" ${FFMPEG_EXT_NAME} "") + set(FFMPEG_FOUND YES) + set(FFMPEG_INCLUDE_DIR "${FFMPEG_PATH}/include" CACHE PATH "Path to FFmpeg headers" FORCE) + set(FFMPEG_LIBRARY_DIR "${FFMPEG_PATH}/bin" CACHE PATH "Path to FFmpeg library" FORCE) + set(FFMPEG_DLL_DIR "${FFMPEG_PATH}/bin" CACHE PATH "Path to FFmpeg dll's" FORCE) +endif() + # Prefer the -pthread flag on Linux. set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) -# If unicorn isn't found, msvc -> download bundled unicorn; everyone else -> build external -if (YUZU_USE_BUNDLED_UNICORN) - if (MSVC) - message(STATUS "unicorn not found, falling back to bundled") - # Detect toolchain and platform - if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64) - set(UNICORN_VER "unicorn-yuzu") - else() - message(FATAL_ERROR "No bundled Unicorn binaries for your toolchain. Disable YUZU_USE_BUNDLED_UNICORN and provide your own.") - endif() - - if (DEFINED UNICORN_VER) - download_bundled_external("unicorn/" ${UNICORN_VER} UNICORN_PREFIX) - endif() - - if (DEFINED UNICORN_VER) - download_bundled_external("unicorn/" ${UNICORN_VER} UNICORN_PREFIX) - endif() - - set(UNICORN_FOUND YES) - set(LIBUNICORN_INCLUDE_DIR "${UNICORN_PREFIX}/include" CACHE PATH "Path to Unicorn headers" FORCE) - set(LIBUNICORN_LIBRARY "${UNICORN_PREFIX}/lib/x64/unicorn_dynload.lib" CACHE PATH "Path to Unicorn library" FORCE) - set(UNICORN_DLL_DIR "${UNICORN_PREFIX}/lib/x64/" CACHE PATH "Path to unicorn.dll" FORCE) - else() - message(STATUS "unicorn not found, falling back to externals") - if (MINGW) - set(UNICORN_LIB_NAME "unicorn.a") - else() - set(UNICORN_LIB_NAME "libunicorn.a") - endif() - - set(UNICORN_FOUND YES) - set(UNICORN_PREFIX ${PROJECT_SOURCE_DIR}/externals/unicorn) - set(LIBUNICORN_LIBRARY "${UNICORN_PREFIX}/${UNICORN_LIB_NAME}" CACHE PATH "Path to Unicorn library" FORCE) - set(LIBUNICORN_INCLUDE_DIR "${UNICORN_PREFIX}/include" CACHE PATH "Path to Unicorn headers" FORCE) - set(UNICORN_DLL_DIR "${UNICORN_PREFIX}/" CACHE PATH "Path to unicorn dynamic library" FORCE) - - find_package(PythonInterp 2.7 REQUIRED) - - if (MINGW) - # Intentionally call the unicorn makefile directly instead of using make.sh so that we can override the - # UNAME_S makefile variable to MINGW. This way we don't have to hack at the uname binary to build - # Additionally, overriding DO_WINDOWS_EXPORT prevents unicorn from patching the static unicorn.a by using msvc and cmd, - # which are both things we don't have in a mingw cross compiling environment. - add_custom_command(OUTPUT ${LIBUNICORN_LIBRARY} - COMMAND ${CMAKE_COMMAND} -E env UNICORN_ARCHS="aarch64" PYTHON="${PYTHON_EXECUTABLE}" CC=x86_64-w64-mingw32-gcc AR=x86_64-w64-mingw32-gcc-ar RANLIB=x86_64-w64-mingw32-gcc-ranlib make UNAME_S=MINGW DO_WINDOWS_EXPORT=0 - WORKING_DIRECTORY ${UNICORN_PREFIX} - ) - else() - add_custom_command(OUTPUT ${LIBUNICORN_LIBRARY} - COMMAND ${CMAKE_COMMAND} -E env UNICORN_ARCHS="aarch64" PYTHON="${PYTHON_EXECUTABLE}" /bin/sh make.sh macos-universal-no - WORKING_DIRECTORY ${UNICORN_PREFIX} - ) - endif() - - # ALL makes this custom target build every time - # but it won't actually build if LIBUNICORN_LIBRARY is up to date - add_custom_target(unicorn-build ALL - DEPENDS ${LIBUNICORN_LIBRARY} - ) - unset(UNICORN_LIB_NAME) - endif() -else() - find_package(Unicorn REQUIRED) -endif() - -if (UNICORN_FOUND) - add_library(unicorn INTERFACE) - add_dependencies(unicorn unicorn-build) - target_link_libraries(unicorn INTERFACE "${LIBUNICORN_LIBRARY}") - target_include_directories(unicorn INTERFACE "${LIBUNICORN_INCLUDE_DIR}") -else() - message(FATAL_ERROR "Could not find or build unicorn which is required.") -endif() - # Platform-specific library requirements # ====================================== diff --git a/CMakeModules/CopyYuzuFFmpegDeps.cmake b/CMakeModules/CopyYuzuFFmpegDeps.cmake new file mode 100644 index 000000000..cca1eeeab --- /dev/null +++ b/CMakeModules/CopyYuzuFFmpegDeps.cmake @@ -0,0 +1,10 @@ +function(copy_yuzu_FFmpeg_deps target_dir) + include(WindowsCopyFiles) + set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$/") + windows_copy_files(${target_dir} ${FFMPEG_DLL_DIR} ${DLL_DEST} + avcodec-58.dll + avutil-56.dll + swresample-3.dll + swscale-5.dll + ) +endfunction(copy_yuzu_FFmpeg_deps) diff --git a/CMakeModules/CopyYuzuUnicornDeps.cmake b/CMakeModules/CopyYuzuUnicornDeps.cmake deleted file mode 100644 index 7af0ef023..000000000 --- a/CMakeModules/CopyYuzuUnicornDeps.cmake +++ /dev/null @@ -1,9 +0,0 @@ -function(copy_yuzu_unicorn_deps target_dir) - include(WindowsCopyFiles) - set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$/") - windows_copy_files(${target_dir} ${UNICORN_DLL_DIR} ${DLL_DEST} - libgcc_s_seh-1.dll - libwinpthread-1.dll - unicorn.dll - ) -endfunction(copy_yuzu_unicorn_deps) diff --git a/README.md b/README.md index 981c8ef24..fbf62eb7c 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,6 @@ If you want to contribute to the user interface translation, please check out th * __Windows__: [Windows Build](https://github.com/yuzu-emu/yuzu/wiki/Building-For-Windows) * __Linux__: [Linux Build](https://github.com/yuzu-emu/yuzu/wiki/Building-For-Linux) -* __macOS__: [macOS Build](https://github.com/yuzu-emu/yuzu/wiki/Building-for-macOS) ### Support diff --git a/dist/languages/de.ts b/dist/languages/de.ts new file mode 100644 index 000000000..b93c9f51c --- /dev/null +++ b/dist/languages/de.ts @@ -0,0 +1,4749 @@ + + + AboutDialog + + + About yuzu + Über yuzu + + + + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + + + + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + + + + <html><head/><body><p>%1 | %2-%3 (%4)</p></body></html> + <html><head/><body><p>%1 | %2-%3 (%4)</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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu ist ein experimenteller, quelloffener Emulator für Nintendo Switch, lizensiert unter GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Diese Software sollte nicht dazu verwendet werden, Spiele zu spielen, die du nicht legal erhalten hast.</span></p></body></html> + + + + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Webseite</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Quellcode</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Mitwirkende</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/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. yuzu is not affiliated with Nintendo in any way.</span></p></body></html> + <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; ist ein Warenzeichen von Nintendo. yuzu ist in keiner Weise mit Nintendo verbunden.</span></p></body></html> + + + + CalibrationConfigurationDialog + + + Communicating with the server... + Verbindung mit dem Server wird hergestellt... + + + + Cancel + Abbrechen + + + + Touch the top left corner <br>of your touchpad. + Tippe auf die obere linke Ecke <br>deines Touchpads. + + + + Now touch the bottom right corner <br>of your touchpad. + Tippe auf die untere rechte Ecke <br>deines Touchpads. + + + + Configuration completed! + Konfiguration abgeschlossen! + + + + OK + OK + + + + 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://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu 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 yuzu 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 yuzu account</li></ul></body></html> + <html><head/><body><p><span style=" font-size:10pt;">Solltest du einen Bericht zur </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu-Kompatibilitätsliste</span></a><span style=" font-size:10pt;"> beitragen wollen, werden die folgenden Informationen gesammelt und auf der yuzu-Webseite dargestellt:</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-Informationen (CPU / GPU / Betriebssystem)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Welche yuzu-Version du benutzt</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Den verbundenen yuzu-Account</li></ul></body></html> + + + + Perfect + Perfekt + + + + <html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html> + <html><head/><body><p>Das Spiel funktioniert einwandfrei und ohne Audio- oder Grafikfehler.</p></body></html> + + + + Great + Gut + + + + <html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html> + <html><head/><body><p>Das Spiel funktioniert mit kleineren Grafik- oder Audiofehlern und ist von Anfang bis Ende spielbar. Eventuell sind einige Workarounds erforderlich.</p></body></html> + + + + Okay + Okay + + + + <html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html> + <html><head/><body><p>Das Spiel funktioniert mit größeren Grafik- oder Audiofehlern, lässt sich aber mit Workarounds bis zum Ende durchspielen. </p></body></html> + + + + Bad + Schlecht + + + + <html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html> + <html><head/><body><p>Spiel funktioniert zwar, aber nur mit starken Grafik- oder Audiofehlern. Manche Bereiche des Spiels können selbst mit Workarounds nicht abgeschlossen werden.</p></body></html> + + + + Intro/Menu + Intro/Menü + + + + <html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html> + <html><head/><body><p>Das Spiel ist wegen schwerwiegendsten Grafik- oder Audiofehlern unspielbar. Das Spiel lässt sich lediglich starten.</p></body></html> + + + + Won't Boot + Startet nicht + + + + <html><head/><body><p>The game crashes when attempting to startup.</p></body></html> + <html><head/><body><p>Das Spiel stürzt beim Versuch zu starten ab.</p></body></html> + + + + <html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html> + <html><head/><body><p>Unabhängig von Geschwindigkeit oder Performance, wie gut läuft das Spiel von Start bis Ende in dieser Version von yuzu?</p></body></html> + + + + Thank you for your submission! + Vielen Dank für deinen Beitrag! + + + + Submitting + Absenden + + + + Communication error + Kommunikationsfehler + + + + An error occured while sending the Testcase + Beim Senden des Berichtes ist ein Fehler aufgetreten. + + + + Next + Weiter + + + + ConfigureAudio + + + Audio + Audio + + + + Output Engine: + Ausgabe-Engine: + + + + This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency. + Dieser Nachbearbeitungseffekt passt die Audiogeschwindigkeit an die Emulationsgeschwindigkeit an und verhindert Audioaussetzer. Dies erhöht jedoch die Audioverzögerung. + + + + Enable audio stretching + Audiodehnung aktivieren + + + + Audio Device: + Audiogerät: + + + + Use global volume + Globale Lautstärke verwenden + + + + Set volume: + Lautstärke: + + + + Volume: + Lautstärke: + + + + 0 % + 0 % + + + + %1% + Volume percentage (e.g. 50%) + %1% + + + + ConfigureCpu + + + Form + Form + + + + General + Allgemeines + + + + Accuracy: + Genauigkeit der Emulation: + + + + Accurate + Akkurat + + + + Unsafe + Unsicher + + + + Enable Debug Mode + Debug-Modus aktivieren + + + + We recommend setting accuracy to "Accurate". + Wir empfehlen, dies auf "Akkurat" zu stellen. + + + + Unsafe CPU Optimization Settings + Unsichere CPU-Optimierungen + + + + These settings reduce accuracy for speed. + Diese Optionen reduzieren die Genauigkeit, können jedoch die Geschwindigkeit erhöhen. + + + + Unfuse FMA (improve performance on CPUs without FMA) + Unfuse FMA (erhöht Leistung auf CPUs ohne FMA) + + + + + <div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div> + + + <div>Diese Option steigert die Geschwindigkeit, indem die Genauigkeit von sog. "fused-multiply-add" Anweisungen auf CPUs ohne native FMA-Unterstützung reduziert wird.</div> + + + + + Faster FRSQRTE and FRECPE + Schnelleres FRSQRTE und FRECPE + + + + + <div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div> + + + <div>Diese Option steigert die Geschwindigkeit von einigen "floating point"-Anweisungen, indem weniger akkurate Schätzungen der Werte verwendet werden.</div> + + + + + CPU settings are available only when game is not running. + Die CPU-Einstellungen sind nur verfügbar, wenn kein Spiel aktiv ist. + + + + Setting CPU to Debug Mode + Debug-Modus für CPU + + + + CPU Debug Mode is only intended for developer use. Are you sure you want to enable this? + Der Debug-Modus ist nur für Entwickler gedacht. Bist du dir sicher? + + + + ConfigureCpuDebug + + + Form + Form + + + + Toggle CPU Optimizations + CPU-Optimierungen ändern + + + + + <div> + <b>For debugging only.</b> + <br> + If you're not sure what these do, keep all of these enabled. + <br> + These settings only take effect when CPU Accuracy is "Debug Mode". + </div> + + + <div> + <b>Dies ist nur zur Fehlerbehebung gedacht .</b> + <br> + Falls du dir nicht sicher bist, was hier passiert, solltest du keine dieser Optionen ändern. + <br> + Diese Optionen treten nur dann in Kraft, wenn auch der CPU Debug-Modus aktiviert ist. + </div> + + + + + Enable inline page tables + Enable inline page tables + + + + + <div style="white-space: nowrap">This optimization speeds up memory accesses by the guest program.</div> + <div style="white-space: nowrap">Enabling it inlines accesses to PageTable::pointers into emitted code.</div> + <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">This optimization speeds up memory accesses by the guest program.</div> + <div style="white-space: nowrap">Enabling it inlines accesses to PageTable::pointers into emitted code.</div> + <div style="white-space: nowrap">Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.</div> + + + + + Enable block linking + Enable block linking + + + + + <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> + + + + + Enable return stack buffer + Return stack buffer aktivieren + + + + + <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> + + + + + Enable fast dispatcher + Enable fast dispatcher + + + + + <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> + + + + + Enable context elimination + Enable context elimination + + + + + <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> + + + + + Enable constant propagation + Enable constant propagation + + + + + <div>Enables IR optimizations that involve constant propagation.</div> + + + <div>Enables IR optimizations that involve constant propagation.</div> + + + + + Enable miscellaneous optimizations + Enable miscellaneous optimizations + + + + + <div>Enables miscellaneous IR optimizations.</div> + + + <div>Enables miscellaneous IR optimizations.</div> + + + + + Enable misalignment check reduction + Enable misalignment check reduction + + + + + <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">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> + + + + + CPU settings are available only when game is not running. + Die CPU-Einstellungen sind nur verfügbar, wenn kein Spiel aktiv ist. + + + + ConfigureDebug + + + Form + Form + + + + GDB + GDB + + + + Enable GDB Stub + GDB-Stub aktivieren + + + + Port: + Port: + + + + Logging + Logging + + + + Global Log Filter + Globaler Log-Filter + + + + Show Log Console (Windows Only) + Log-Konsole öffnen (nur Windows) + + + + Open Log Location + Log-Verzeichnis öffnen + + + + Homebrew + Homebrew + + + + Arguments String + String-Argumente + + + + Graphics + Grafik + + + + When checked, the graphics API enters in a slower debugging mode + Wenn aktiviert, wird die Grafik-API in einem langsamen Debug-Modus gestartet. + + + + Enable Graphics Debugging + Grafik-Debugging aktivieren + + + + When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower + Diese Option deaktiviert den Macro-JIT-Compiler. Dies wird die Geschwindigkeit verringern. + + + + Disable Macro JIT + Macro-JIT deaktivieren + + + + Dump + Speichern + + + + Enable Verbose Reporting Services + Ausführliche Service-Fehlerberichte aktivieren + + + + This will be reset automatically when yuzu closes. + Dies wird automatisch beim Schließen von yuzu zurückgesetzt. + + + + Advanced + Erweitert + + + + Kiosk (Quest) Mode + Kiosk(Quest)-Modus + + + + ConfigureDebugController + + + Configure Debug Controller + Debug-Controller einrichten + + + + Clear + Löschen + + + + Defaults + Standardwerte + + + + ConfigureDialog + + + yuzu Configuration + yuzu-Konfiguration + + + + + + + General + Allgemein + + + + + UI + Benutzeroberfläche + + + + Game List + Spieleliste + + + + + + + System + System + + + + + + Profiles + Nutzer + + + + + + Filesystem + Dateisystem + + + + + + + Controls + Steuerung + + + + + + Hotkeys + Hotkeys + + + + + + + CPU + CPU + + + + + + + + + Debug + Debug + + + + + + + Graphics + Grafik + + + + + Advanced + Erweitert + + + + GraphicsAdvanced + GraphicsAdvanced + + + + + + + Audio + Audio + + + + + + Web + Web + + + + + + Services + Services + + + + ConfigureFilesystem + + + Form + Form + + + + Storage Directories + Speicherverzeichnisse + + + + NAND + NAND + + + + + + + + + ... + ... + + + + SD Card + SD-Karte + + + + Gamecard + Gamecard + + + + Path + Pfad + + + + Inserted + Eingelegt + + + + Current Game + Aktuelles Spiel + + + + Patch Manager + Patchmanager + + + + Dump Decompressed NSOs + Dekomprimierte NSOs dumpen + + + + Dump ExeFS + ExeFS dumpen + + + + Mod Load Root + Mod-Ladeverzeichnis + + + + Dump Root + Root dumpen + + + + Caching + Caching + + + + Cache Directory + Cache-Ordner + + + + Cache Game List Metadata + Metadaten der Spieleliste cachen + + + + + + + Reset Metadata Cache + Metadaten-Cache zurücksetzen + + + + Select Emulated NAND Directory... + Emulierten NAND-Ordner auswählen... + + + + Select Emulated SD Directory... + Emulierten SD-Ordner auswählen... + + + + Select Gamecard Path... + Gamecard-Pfad auswählen... + + + + Select Dump Directory... + Dump-Verzeichnis auswählen... + + + + Select Mod Load Directory... + Mod-Ladeverzeichnis auswählen... + + + + Select Cache Directory... + Cache-Ordner auswählen... + + + + 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. Er könnte in Gebrauch oder nicht vorhanden sein. + + + + ConfigureGeneral + + + Form + Form + + + + General + Allgemein + + + + Limit Speed Percent + Geschwindigkeit auf % festlegen + + + + % + % + + + + Multicore CPU Emulation + Multicore-CPU-Emulation + + + + Confirm exit while emulation is running + Schließen des Emulators bestätigen, falls ein Spiel läuft + + + + Prompt for user on game boot + Beim Spielstart nach Nutzer fragen + + + + Pause emulation when in background + Emulation im Hintergrund pausieren + + + + Hide mouse on inactivity + Mauszeiger verstecken + + + + ConfigureGraphics + + + Form + Form + + + + API Settings + API-Einstellungen + + + + API: + API: + + + + Device: + Gerät: + + + + Graphics Settings + Grafik-Einstellungen + + + + Use disk shader cache + Nutze Festplatten-Shader-Cache + + + + Use asynchronous GPU emulation + Asynchrone GPU-Emulation verwenden + + + + Aspect Ratio: + Seitenverhältnis: + + + + Default (16:9) + Standard (16:9) + + + + Force 4:3 + 4:3 erzwingen + + + + Force 21:9 + 21:9 erzwingen + + + + Stretch to Window + Auf Fenster anpassen + + + + + Use global background color + Globale Hintergrundfarbe verwenden + + + + Set background color: + Hintergrundfarbe: + + + + Background Color: + Hintergrundfarbe: + + + + OpenGL Graphics Device + OpenGL GPU + + + + ConfigureGraphicsAdvanced + + + Form + Form + + + + Advanced Graphics Settings + Erweiterte Grafik-Einstellungen + + + + Accuracy Level: + Genauigkeit der Emulation: + + + + VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference. + VSync verhindert Screen-Tearing, aber manche Grafikkarten haben eine schlechtere Leistung, wenn es aktiviert ist. Wenn du keinen Unterschied merkst, lasse es aktiviert. + + + + Use VSync (OpenGL only) + VSync nutzen (Nur OpenGL) + + + + Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental. + Aktivieren dieser Einstellung reduziert Stottern durch Shader. Aktiviert OpenGL Assembly-Shader auf unterstützten Nvidia-Karten (NV_gpu_program5 wird benötigt). Dieses Feature ist experimentell. + + + + Use assembly shaders (experimental, Nvidia OpenGL only) + Nutze Assembly-Shader (experimentell, nur Nvidia OpenGL) + + + + Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental. + Nutze asynchrone Shader-Kompilierung. Dies kann Stottern durch Shader reduzieren. Dieses Feature ist experimentell. + + + + Use asynchronous shader building (experimental) + Nutze asynchrone Shader-Kompilierung (experimentell) + + + + Use Fast GPU Time + Nutze schnelle GPU-Zeit + + + + Anisotropic Filtering: + Anisotrope Filterung: + + + + Default + Standard + + + + 2x + 2x + + + + 4x + 4x + + + + 8x + 8x + + + + 16x + 16x + + + + ConfigureHotkeys + + + Hotkey Settings + Hotkey-Einstellungen + + + + Double-click on a binding to change it. + Doppelklicke auf eine Tastensequenz, um sie zu ändern. + + + + Clear All + Alle löschen + + + + Restore Defaults + Standardwerte wiederherstellen + + + + Action + Aktion + + + + Hotkey + Hotkey + + + + Context + Kontext + + + + + Conflicting Key Sequence + Tastensequenz bereits belegt + + + + The entered key sequence is already assigned to: %1 + Die eingegebene Sequenz ist bereits vergeben an: %1 + + + + Restore Default + Standardwerte wiederherstellen + + + + Clear + Löschen + + + + The default key sequence is already assigned to: %1 + Die Standard-Sequenz ist bereits vergeben an: %1 + + + + ConfigureInput + + + ConfigureInput + ConfigureInput + + + + + Player 1 + Spieler 1 + + + + + Player 2 + Spieler 2 + + + + + Player 3 + Spieler 3 + + + + + Player 4 + Spieler 4 + + + + + Player 5 + Spieler 5 + + + + + Player 6 + Spieler 6 + + + + + Player 7 + Spieler 7 + + + + + Player 8 + Spieler 8 + + + + + Advanced + Erweitert + + + + Console Mode + Konsolenmodus + + + + Docked + Im Dock + + + + Undocked + Handheld + + + + Vibration + Vibration + + + + % + % + + + + Motion + Bewegung + + + + Configure + Konfigurieren + + + + Controllers + Controller + + + + 1 + 1 + + + + 2 + 2 + + + + 3 + 3 + + + + 4 + 4 + + + + 5 + 5 + + + + 6 + 6 + + + + 7 + 7 + + + + 8 + 8 + + + + Connected + Verbunden + + + + Defaults + Standardwerte + + + + Clear + Löschen + + + + ConfigureInputAdvanced + + + Configure Input + Eingabe einrichten + + + + Joycon Colors + Joyconfarben + + + + Player 1 + Spieler 1 + + + + + + + + + + + L Body + Links + + + + + + + + + + + L Button + L-Taste + + + + + + + + + + + R Body + Rechts + + + + + + + + + + + R Button + R-Taste + + + + Player 2 + Spieler 2 + + + + Player 3 + Spieler 3 + + + + Player 4 + Spieler 4 + + + + Player 5 + Spieler 5 + + + + Player 6 + Spieler 6 + + + + Player 7 + Spieler 7 + + + + Player 8 + Spieler 8 + + + + Other + Weiteres + + + + Keyboard + Tastatur + + + + + Advanced + Erweitert + + + + Touchscreen + Touchscreen + + + + Mouse + Maus + + + + Motion / Touch + Bewegung / Touch + + + + + Configure + Konfigurieren + + + + Debug Controller + Debug Controller + + + + ConfigureInputPlayer + + + Configure Input + Eingabe einrichten + + + + Connect Controller + Controller verbinden + + + + + + Pro Controller + Pro Controller + + + + + Dual Joycons + Zwei Joycons + + + + + Left Joycon + Linker Joycon + + + + + Right Joycon + Rechter Joycon + + + + + Handheld + Handheld + + + + Input Device + Eingabegerät + + + + Any + Alle + + + + Keyboard/Mouse + Tastatur/Maus + + + + Profile + Profil + + + + Save + Speichern + + + + New + Neu + + + + Delete + Löschen + + + + Left Stick + Linker Analogstick + + + + + + + + + Up + Hoch + + + + + + + + + Left + Links + + + + + + + + + Right + Rechts + + + + + + + + + Down + Runter + + + + + + + Pressed + Gedrückt + + + + + + + Modifier + Modifikator + + + + + Range + Radius + + + + + % + % + + + + + Deadzone: 0% + Deadzone: 0% + + + + + Modifier Range: 0% + Modifikator-Radius: 0% + + + + D-Pad + Steuerkreuz + + + + + L + L + + + + + ZL + ZL + + + + + Minus + Minus + + + + + Capture + Screenshot + + + + + Plus + Plus + + + + + Home + Home + + + + + R + R + + + + + ZR + ZR + + + + + SL + SL + + + + + SR + SR + + + + Face Buttons + Tasten + + + + + X + X + + + + + Y + Y + + + + + A + A + + + + + B + B + + + + Right Stick + Rechter Analogstick + + + + + Deadzone: %1% + Deadzone: %1% + + + + + Modifier Range: %1% + Modifikator-Radius: %1% + + + + [waiting] + [wartet] + + + + ConfigureMotionTouch + + + Configure Motion / Touch + Bewegung / Touch einrichten + + + + Motion + Bewegung + + + + Motion Provider: + Quelle für Bewegung: + + + + Sensitivity: + Empfindlichkeit: + + + + Touch + Touch + + + + Touch Provider: + Quelle für Touch: + + + + Calibration: + Kalibrierung: + + + + (100, 50) - (1800, 850) + (100, 50) - (1800, 850) + + + + + + Configure + Einrichtung + + + + Use button mapping: + Tastenbelegung nutzen: + + + + CemuhookUDP Config + CemuhookUDP Konfiguration + + + + You may use any Cemuhook compatible UDP input source to provide motion and touch input. + Du kannst alle Cemuhook-kompatiblen UDP-Eingabequellen für Bewegung und Touch verwenden. + + + + Server: + Server: + + + + Port: + Port: + + + + Pad: + Pad: + + + + Pad 1 + Pad 1 + + + + Pad 2 + Pad 2 + + + + Pad 3 + Pad 3 + + + + Pad 4 + Pad 4 + + + + Learn More + Mehr erfahren + + + + + Test + Testen + + + + Mouse (Right Click) + Maus (Rechtsklick) + + + + + CemuhookUDP + CemuhookUDP + + + + Emulator Window + Emulator-Fenster + + + + <a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Learn More</span></a> + <a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Mehr erfahren</span></a> + + + + 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. + + + + Citra + Citra + + + + 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. + + + + ConfigureMouseAdvanced + + + Configure Mouse + Maus einrichten + + + + Mouse Buttons + Maustasten + + + + Forward: + Vorwärts: + + + + Back: + Rückwärts: + + + + Left: + Links: + + + + Middle: + Mitte: + + + + Right: + Rechts: + + + + + Clear + Löschen + + + + Defaults + Standardwerte + + + + [not set] + [nicht belegt] + + + + Restore Default + Standardwert wiederherstellen + + + + [press key] + [Taste drücken] + + + + ConfigurePerGame + + + Dialog + Dialog + + + + Info + Info + + + + Name + Name + + + + Title ID + Titel ID + + + + Filename + Dateiname + + + + Format + Format + + + + Version + Version + + + + Size + Größe + + + + Developer + Entwickler + + + + Add-Ons + Add-Ons + + + + General + Allgemeines + + + + System + System + + + + Graphics + Grafik + + + + Adv. Graphics + Erw. Grafik + + + + Audio + Audio + + + + Properties + Einstellungen + + + + Use global configuration (%1) + Globale Konfiguration verwenden (%1) + + + + ConfigurePerGameAddons + + + Form + Form + + + + Patch Name + Patchname + + + + Version + Version + + + + ConfigureProfileManager + + + Form + Form + + + + Profile Manager + Nutzerverwaltung + + + + Current User + Aktueller Nutzer + + + + Username + Nutzername + + + + Set Image + Bild wählen + + + + Add + Hinzufügen + + + + Rename + Umbenennen + + + + Remove + Entfernen + + + + Profile management is available only when game is not running. + Die Nutzerverwaltung ist nur verfügbar, wenn kein Spiel aktiv ist. + + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Enter Username + Nutzername eingeben + + + + Users + Nutzer + + + + Enter a username for the new user: + Gib einen Benutzernamen für den neuen Benutzer ein: + + + + Enter a new username: + Gib einen neuen Nutzernamen ein: + + + + Confirm Delete + Löschen bestätigen + + + + You are about to delete user with name "%1". Are you sure? + Du bist dabei, den Nutzer "%1" zu löschen. Bist du dir sicher? + + + + Select User Image + Profilbild wählen + + + + JPEG Images (*.jpg *.jpeg) + JPEG Bilddateien (*.jpg *.jpeg) + + + + Error deleting image + Fehler beim Löschen des Bildes + + + + Error occurred attempting to overwrite previous image at: %1. + Fehler beim Überschreiben des vorherigen Bildes bei: %1 + + + + Error deleting file + Fehler beim Löschen der Datei + + + + Unable to delete existing file: %1. + Konnte die bestehende Datei "%1" nicht löschen. + + + + Error creating user image directory + Fehler beim Erstellen des Ordners für die Profilbilder + + + + Unable to create directory %1 for storing user images. + Konnte Ordner "%1" nicht erstellen, um Profilbilder zu speichern. + + + + Error copying user image + Fehler beim Kopieren des Profilbildes + + + + Unable to copy image from %1 to %2 + Das Bild konnte nicht von "%1" nach "%2" kopiert werden + + + + ConfigureService + + + Form + Form + + + + BCAT + BCAT + + + + BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content. + BCAT ist Nintendos Methode, Daten an Spiele zu senden, um die Community zu involvieren und zusätzliche Inhalte freizuschalten. + + + + BCAT Backend + BCAT Backend + + + + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Learn more about BCAT, Boxcat, and Current Events</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Erfahre mehr über BCAT, Boxcat, und aktuelle Events</span></a></p></body></html> + + + + The boxcat service is offline or you are not connected to the internet. + Der Boxcat Service ist offline oder du bist nicht mit dem Internet verbunden. + + + + There was an error while processing the boxcat event data. Contact the yuzu developers. + Bei der Verarbeitung der Boxcat-Eventdaten ist ein Fehler aufgetreten. Kontaktiere die yuzu-Entwickler. + + + + The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu. + Die Version von yuzu, die du verwendest, ist entweder zu neu oder zu alt für den Server. Versuche auf die neueste offizielle Version von yuzu zu updaten. + + + + + There are currently no events on boxcat. + Es gibt zurzeit keine Events auf Boxcat. + + + + + Current Boxcat Events + Momentane Boxcat-Events + + + + Yuzu is retrieving the latest boxcat status... + Yuzu lädt den neuesten Boxcat-Status... + + + + ConfigureSystem + + + Form + Form + + + + System Settings + Systemeinstellungen + + + + Region: + Region: + + + + Auto + Auto + + + + Default + Standard + + + + 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 + + + + + Japan + Japan + + + + 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 + + + + USA + USA + + + + Europe + Europa + + + + Australia + Australien + + + + China + China + + + + Korea + Korea + + + + Taiwan + Taiwan + + + + Time Zone: + Zeitzone: + + + + Note: this can be overridden when region setting is auto-select + Anmerkung: Diese Einstellung kann überschrieben werden, falls deine Region auf "auto-select" eingestellt ist. + + + + Japanese (日本語) + Japanisch (日本語) + + + + English + 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 (正體中文) + + + + Custom RTC + Benutzerdefinierte Echtzeituhr + + + + Language + Sprache + + + + RNG Seed + RNG Seed + + + + Mono + Mono + + + + Stereo + Stereo + + + + Surround + Surround + + + + Console ID: + Konsolen ID: + + + + Sound output mode + Soundausgabe + + + + d MMM yyyy h:mm:ss AP + d MMM yyyy h:mm:ss AP + + + + Regenerate + Neu generieren + + + + System settings are available only when game is not running. + Die Systemeinstellungen sind nur verfügbar, wenn kein Spiel aktiv ist. + + + + This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue? + Dieser Vorgang wird deine momentane "virtuelle Switch" mit einer Neuen ersetzen. Deine momentane "virtuelle Switch" wird nicht wiederherstellbar sein. Dies könnte einige unerwartete Effekte in manchen Spielen mit sich bringen. Zudem könnte der Prozess fehlschlagen, wenn zu alte Daten verwendet werden. Möchtest du den Vorgang fortsetzen? + + + + Warning + Warnung + + + + Console ID: 0x%1 + Konsolen ID: 0x%1 + + + + ConfigureTouchFromButton + + + Configure Touchscreen Mappings + Touchscreen-Belegung einrichten + + + + Mapping: + Belegung: + + + + New + Neu + + + + Delete + Löschen + + + + Rename + Umbenennen + + + + 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. + + + + Delete Point + Punkt löschen + + + + Button + Knopf + + + + X + X axis + X + + + + Y + Y axis + Y + + + + New Profile + Neues Profil + + + + Enter the name for the new profile. + Neuen Namen für das Profil eingeben. + + + + Delete Profile + Profil löschen + + + + Delete profile %1? + Profil "%1" löschen? + + + + Rename Profile + Profil umbenennen + + + + New name: + Neuer Name: + + + + [press key] + [Knopf drücken] + + + + ConfigureTouchscreenAdvanced + + + Configure Touchscreen + Touchscreen einrichten: + + + + Warning: The settings in this page affect the inner workings of yuzu'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. + Achtung: Die Einstellungen auf dieser Seite haben Einfluss auf yuzus Touchscreen-Emulation. Sie zu ändern könnte zu unerwünschtem Verhalten, wie zu einem Ausfall des Touchscreens, führen. Du solltest sie nur verändern, falls du weißt, was du tust. + + + + Touch Parameters + Berührungsparameter + + + + Touch Diameter Y + Berührungsdurchmesser Y + + + + Finger + Finger + + + + Touch Diameter X + Berührungsdurchmesser X + + + + Rotational Angle + Drehwinkel + + + + Restore Defaults + Standardwerte wiederherstellen + + + + ConfigureUi + + + Form + Form + + + + General + Allgemein + + + + Note: Changing language will apply your configuration. + Anmerkung: Das Ändern der Sprache wird deine Konfiguration speichern. + + + + Interface language: + Sprache der Benutzeroberfläche: + + + + Theme: + Theme: + + + + Game List + Spieleliste + + + + Show Add-Ons Column + Add-On Spalte anzeigen + + + + Icon Size: + Icongröße: + + + + Row 1 Text: + Zeile 1 Text: + + + + Row 2 Text: + Zeile 2 Text: + + + + Screenshots + Screenshots + + + + Ask Where To Save Screenshots (Windows Only) + Frage nach, wo Screenshots gespeichert werden sollen (Nur Windows) + + + + Screenshots Path: + Screenshotpfad + + + + ... + ... + + + + Select Screenshots Path... + Screenshotpfad auswählen... + + + + <System> + <System> + + + + English + Englisch + + + + ConfigureWeb + + + Form + Form + + + + yuzu Web Service + yuzu Web Service + + + + By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information. + Mit dem Bereitstellen deines Benutzernamens und Tokens erlaubst du yuzu, zusätzliche Nutzungsdaten zu sammeln. Diese könnten auch Informationen beinhalten, die dich identifizieren könnten. + + + + + Verify + Überprüfen + + + + Sign up + Registrieren + + + + Token: + Token: + + + + Username: + Nutzername: + + + + What is my token? + Was ist mein Token? + + + + Telemetry + Telemetrie + + + + Share anonymous usage data with the yuzu team + Teile anonyme Nutzungsdaten mit dem yuzu-Team + + + + Learn more + Mehr erfahren + + + + Telemetry ID: + Telemetrie-ID: + + + + Regenerate + Neu generieren + + + + Discord Presence + Discord-Präsenz + + + + Show Current Game in your Discord Status + Zeig dein momentanes Spiel in deinem Discord-Status + + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Learn more</span></a> + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Mehr erfahren</span></a> + + + + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Sign up</span></a> + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Registrieren</span></a> + + + + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">What is my token?</span></a> + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">Was ist mein Token?</span></a> + + + + + Telemetry ID: 0x%1 + Telemetrie-ID: 0x%1 + + + + + Unspecified + Nicht spezifiziert + + + + Token not verified + Token nicht verifiziert + + + + Token was not verified. The change to your token has not been saved. + Token wurde nicht verfiziert. Die Änderungen an deinem Token wurden nicht gespeichert. + + + + Verifying... + Verifizieren... + + + + Verification failed + Verifizierung fehlgeschlagen + + + + Verification failed. Check that you have entered your token correctly, and that your internet connection is working. + Verifizierung fehlgeschlagen. Prüfe ob dein Nutzername und Token richtig eingegeben wurden und ob deine Internetverbindung korrekt funktioniert. + + + + GMainWindow + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous data is collected</a> to help improve yuzu. <br/><br/>Would you like to share your usage data with us? + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonyme Daten werden gesammelt,</a> um yuzu zu verbessern.<br/><br/>Möchstest du deine Nutzungsdaten mit uns teilen? + + + + Telemetry + Telemetrie + + + + Text Check Failed + Textprüfung fehlgeschlagen + + + + Loading Web Applet... + Lade Web-Applet... + + + + Exit Web Applet + Web-Applet verlassen + + + + Exit + Verlassen + + + + To exit the web application, use the game provided controls to select exit, select the 'Exit Web Applet' option in the menu bar, or press the 'Enter' key. + Um das Web-Applet zu verlassen, verwende die im Spiel enthaltenen Steuerelemente, wähle die die Option 'Web-Applet beenden' in der Menüleiste oder drücke die 'Enter'-Taste. + + + + Web Applet + Web-Applet + + + + This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested. + Diese Version von yuzu wurde ohne QtWebEngine-Unterstützung kompiliert, was bedeutet, dass yuzu das angeforderte Spielhandbuch oder die Webseite nicht richtig anzeigen kann. + + + + The amount of shaders currently being built + Wie viele Shader im Moment kompiliert werden + + + + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. + Derzeitige Emulations-Geschwindigkeit. Werte höher oder niedriger als 100% zeigen, dass die Emulation scheller 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 angezeigt werden 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. + Zeit, die gebraucht wurde, um einen Switch-Frame zu emulieren, ohne Framelimit oder V-Sync. Für eine Emulation bei voller Geschwindigkeit sollte dieser Wert bei höchstens 16.67ms liegen. + + + + DOCK + DOCK + + + + ASYNC + ASYNC + + + + MULTICORE + MEHRKERN + + + + VULKAN + VULKAN + + + + OPENGL + OPENGL + + + + Clear Recent Files + Zuletzt geladene Dateien leeren + + + + 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><br>For an explanation of the various Switch formats yuzu supports, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our wiki</a>. This message will not be shown again. + Du nutzt eine entpackte ROM-Ordnerstruktur für dieses Spiel, welches ein veraltetes Format ist und von anderen Formaten wie NCA, NAX, XCI oder NSP überholt wurde. Entpackte ROM-Ordner unterstützen keine Icons, Metadaten oder Updates.<br><br><a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>Unser Wiki</a> enthält eine Erklärung der verschiedenen Formate, die yuzu unterstützt. Diese Nachricht wird nicht noch einmal angezeigt. + + + + + Error while loading ROM! + ROM konnte nicht geladen werden! + + + + The ROM format is not supported. + ROM-Format wird nicht unterstützt. + + + + An error occurred initializing the video core. + Beim Initialisieren des Video-Kerns ist ein Fehler aufgetreten. + + + + yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.Ensure that you have the latest graphics drivers for your GPU. + Beim Laden des Video-Kerns trat ein Fehler auf. Bitte prüfe die Log-Dateien auf mögliche Fehlermeldungen. Weitere Informationen: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Wie kann ich eine Log-Datei hochladen?</a>. Stelle sicher, dass die aktuellsten Grafiktreiber für deine Grafikkarte installiert sind. + + + + Error while loading ROM! + Fehler beim Laden des ROMs! + + + + An unknown error occurred. Please see the log for more details. + Ein unbekannter Fehler ist aufgetreten. Bitte prüfe die Log-Dateien auf mögliche Fehlermeldungen. + + + + Start + Start + + + + Save Data + Speicherdaten + + + + Mod Data + Mod-Daten + + + + Error Opening %1 Folder + Konnte Verzeichnis %1 nicht öffnen + + + + + Folder does not exist! + Verzeichnis existiert nicht! + + + + Error Opening Transferable Shader Cache + Fehler beim Öffnen des transferierbaren Shader-Caches + + + + + A shader cache for this title does not exist. + Es existiert kein Shader-Cache für diesen Titel. + + + + Contents + Inhalte + + + + Update + Update + + + + DLC + DLC + + + + Remove Entry + Eintrag entfernen + + + + Remove Installed Game %1? + Installiertes Spiel %1 entfernen? + + + + + + + + Successfully Removed + Erfolgreich entfernt + + + + Successfully removed the installed base game. + Das Spiel wurde entfernt. + + + + + + Error Removing %1 + Fehler beim Entfernen von %1 + + + + The base game is not installed in the NAND and cannot be removed. + Das Spiel ist nicht im NAND installiert und kann somit nicht entfernt werden. + + + + Successfully removed the installed update. + Das Update wurde entfernt. + + + + There is no update installed for this title. + Es ist kein Update für diesen Titel installiert. + + + + There are no DLC installed for this title. + Es sind keine DLC für diesen Titel installiert. + + + + Successfully removed %1 installed DLC. + %1 DLC entfernt. + + + + Delete Transferable Shader Cache? + Transferierbaren Shader-Cache entfernen? + + + + Remove Custom Game Configuration? + Spiel-Einstellungen entfernen? + + + + Remove File + Datei entfernen + + + + + Error Removing Transferable Shader Cache + Fehler beim Entfernen + + + + Successfully removed the transferable shader cache. + Der transferierbare Shader-Cache wurde entfernt. + + + + Failed to remove the transferable shader cache. + Konnte den transferierbaren Shader-Cache nicht entfernen. + + + + + Error Removing Custom Configuration + Fehler beim Entfernen + + + + A custom configuration for this title does not exist. + Es existieren keine Spiel-Einstellungen für dieses Spiel. + + + + Successfully removed the custom game configuration. + Die Spiel-Einstellungen wurden entfernt. + + + + Failed to remove the custom game configuration. + Die Spiel-Einstellungen konnten nicht entfernt werden. + + + + RomFS Extraction Failed! + RomFS-Extraktion fehlgeschlagen! + + + + There was an error copying the RomFS files or the user cancelled the operation. + Das RomFS konnte wegen eines Fehlers oder Abbruchs nicht kopiert werden. + + + + Full + Komplett + + + + Skeleton + Nur Ordnerstruktur + + + + Select RomFS Dump Mode + RomFS Extraktions-Modus 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, wie das RomFS gespeichert werden soll.<br>"Full" wird alle Dateien des Spiels extrahieren, während <br>"Skeleton" nur die Ordnerstruktur erstellt. + + + + Extracting RomFS... + RomFS wird extrahiert... + + + + + Cancel + Abbrechen + + + + RomFS Extraction Succeeded! + RomFS wurde extrahiert! + + + + The operation completed successfully. + Der Vorgang wurde erfolgreich abgeschlossen. + + + + Error Opening %1 + Fehler beim Öffnen von %1 + + + + Select Directory + Verzeichnis auswählen + + + + Properties + Einstellungen + + + + The game properties could not be loaded. + Spiel-Einstellungen konnten nicht geladen werden. + + + + Switch Executable (%1);;All Files (*.*) + %1 is an identifier for the Switch executable file extensions. + Switch-Programme (%1);;Alle Dateien (*.*) + + + + Load File + Datei laden + + + + Open Extracted ROM Directory + Öffne das extrahierte ROM-Verzeichnis + + + + Invalid Directory Selected + Ungültiges Verzeichnis ausgewählt + + + + The directory you have selected does not contain a 'main' file. + Das Verzeichnis, das du ausgewählt hast, enthält keine 'main'-Datei. + + + + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) + Installierbares Switch-Programm (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submissions Package (*.nsp);;NX Cartridge Image (*.xci) + + + + Install Files + Dateien installieren + + + + Installing file "%1"... + Datei "%1" wird installiert... + + + + Install Results + NAND-Installation + + + + System Application + Systemanwendung + + + + System Archive + Systemarchiv + + + + System Application Update + Systemanwendungsupdate + + + + Firmware Package (Type A) + Firmware-Paket (Typ A) + + + + Firmware Package (Type B) + Firmware-Paket (Typ B) + + + + Game + Spiel + + + + Game Update + Spiel-Update + + + + Game DLC + Spiel-DLC + + + + Delta Title + Delta-Titel + + + + Select NCA Install Type... + Wähle den NCA-Installationstyp aus... + + + + 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, als was diese NCA installiert werden soll: +(In den meisten Fällen sollte die Standardeinstellung 'Spiel' ausreichen.) + + + + Failed to Install + Installation fehlgeschlagen + + + + The title type you selected for the NCA is invalid. + Der Titel-Typ, den du für diese NCA ausgewählt hast, ist ungültig. + + + + File not found + Datei nicht gefunden + + + + File "%1" not found + Datei "%1" nicht gefunden + + + + + Continue + Fortsetzen + + + + Error Display + Fehleranzeige + + + + Missing yuzu Account + Fehlender yuzu-Account + + + + In order to submit a game compatibility test case, you must link your yuzu account.<br><br/>To link your yuzu account, go to Emulation &gt; Configuration &gt; Web. + Um einen Kompatibilitätsbericht abzuschicken, musst du einen yuzu-Account mit yuzu verbinden.<br><br/>Um einen yuzu-Account zu verbinden, prüfe die Einstellungen unter Emulation &gt; Konfiguration &gt; Web. + + + + Error opening URL + Fehler beim Öffnen der URL + + + + Unable to open the URL "%1". + URL "%1" kann nicht geöffnet werden. + + + + Amiibo File (%1);; All Files (*.*) + Amiibo-Datei (%1);; Alle Dateien (*.*) + + + + Load Amiibo + Amiibo laden + + + + Error opening Amiibo data file + Fehler beim Öffnen der Amiibo Datei + + + + Unable to open Amiibo file "%1" for reading. + Die Amiibo Datei "%1" konnte nicht zum Lesen geöffnet werden. + + + + Error reading Amiibo data file + Fehler beim Lesen der Amiibo-Daten + + + + Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes. + Amiibo-Daten können nicht vollständig gelesen werden. Es wurde erwartet, dass %1 Bytes gelesen werden, es konnten aber nur %2 Bytes gelesen werden. + + + + Error loading Amiibo data + Fehler beim Laden der Amiibo-Daten + + + + Unable to load Amiibo data. + Amiibo-Daten konnten nicht geladen werden. + + + + Capture Screenshot + Screenshot aufnehmen + + + + PNG Image (*.png) + PNG Bild (*.png) + + + + Speed: %1% / %2% + Geschwindigkeit: %1% / %2% + + + + Speed: %1% + Geschwindigkeit: %1% + + + + Game: %1 FPS + Spiel: %1 FPS + + + + Frame: %1 ms + Frame: %1 ms + + + + The game you are trying to load requires additional files from your Switch to be dumped before playing.<br/><br/>For more information on dumping these files, please see the following wiki page: <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + Das Spiel, dass du versuchst zu spielen, benötigt bestimmte Dateien von deiner Switch-Konsole.<br/><br/>Um Informationen darüber zu erhalten, wie du diese Dateien von deiner Switch extrahieren kannst, prüfe bitte die folgenden Wiki-Seiten: <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>System-Archive und Shared Fonts von einer Switch-Konsole extrahieren</a>.<br/><br/>Willst du zur Spiele-Liste zurückkehren und die Emulation beenden? Das Fortsetzen der Emulation könnte zu Spielfehlern, Abstürzen, beschädigten Speicherdaten und anderen Fehlern führen. + + + + yuzu was unable to locate a Switch system archive. %1 + yuzu konnte ein Switch Systemarchiv nicht finden. %1 + + + + yuzu was unable to locate a Switch system archive: %1. %2 + yuzu konnte ein Switch Systemarchiv nicht finden: %1. %2 + + + + System Archive Not Found + Systemarchiv nicht gefunden + + + + System Archive Missing + Systemarchiv fehlt + + + + yuzu was unable to locate the Switch shared fonts. %1 + yuzu konnte die Switch Shared Fonts nicht finden. %1 + + + + Shared Fonts Not Found + Shared Fonts nicht gefunden + + + + Shared Font Missing + Shared Font fehlt + + + + Fatal Error + Schwerwiegender Fehler + + + + yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + Ein schwerwiegender Fehler ist aufgetreten, bitte prüfe die Log-Dateien auf mögliche Fehlermeldungen. Weitere Informationen: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Wie kann ich eine Log-Datei hochladen</a>.<br/><br/>Willst du zur Spiele-Liste zurückkehren und die Emulation beenden? Das Fortsetzen der Emulation könnte zu Spielfehlern, Abstürzen, beschädigten Speicherdaten und anderen Fehlern führen. + + + + Fatal Error encountered + Fataler Fehler aufgetreten + + + + Confirm Key Rederivation + Schlüsselableitung bestätigen + + + + You are about to force rederive all of your keys. +If you do not know what this means or what you are doing, +this is a potentially destructive action. +Please make sure this is what you want +and optionally make backups. + +This will delete your autogenerated key files and re-run the key derivation module. + Du bist im Begriff, alle Schlüssel neu abzuleiten. Falls du nicht weißt, was das heißt oder was du hier tust, könnte dieser Prozess möglicherweise destruktiv sein. Bitte stelle sicher, dass du wirklich fortfahren willst und optional Sicherungen deiner Daten machst. + +Dieser Prozess wird die generierten Schlüsseldateien löschen und die Schlüsselableitung neu starten. + + + + Missing fuses + Fuses fehlen + + + + - Missing BOOT0 + - BOOT0 fehlt + + + + - Missing BCPKG2-1-Normal-Main + - BCPKG2-1-Normal-Main fehlt + + + + - Missing PRODINFO + - PRODINFO fehlt + + + + Derivation Components Missing + Derivationskomponenten fehlen + + + + Components are missing that may hinder key derivation from completing. <br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu quickstart guide</a> to get all your keys and games.<br><br><small>(%1)</small> + Einige Komponenten, die yuzu benötigt, um Schlüssel zu generieren, wurden nicht gefunden. <br>Bitte folge <a href='https://yuzu-emu.org/help/quickstart/'>dem yuzu Schnellstart-Guide</a> um alle deine Schlüssel und Spiele zu übertragen.<br><br><small>(%1)</small> + + + + Deriving keys... +This may take up to a minute depending +on your system's performance. + Schlüssel werden abgeleitet... +Dies könnte, je nach Leistung deines Systems, bis zu einer Minute dauern. + + + + Deriving Keys + Schlüsselableitung + + + + Select RomFS Dump Target + RomFS wählen + + + + Please select which RomFS you would like to dump. + Wähle, welches RomFS du speichern möchtest. + + + + + + yuzu + yuzu + + + + Are you sure you want to close yuzu? + Bist du sicher, dass du yuzu beenden willst? + + + + Are you sure you want to stop the emulation? Any unsaved progress will be lost. + Bist du sicher, dass du die Emulation stoppen willst? Jeder nicht gespeicherte Fortschritt geht verloren. + + + + The currently running application has requested yuzu to not exit. + +Would you like to bypass this and exit anyway? + Die derzeit laufende Anwendung hat yuzu aufgefordert, sich nicht zu beenden. + +Möchtest du dies umgehen und sie trotzdem beenden? + + + + GRenderWindow + + + OpenGL not available! + OpenGL nicht verfügbar! + + + + yuzu has not been compiled with OpenGL support. + yuzu wurde nicht mit OpenGL-Unterstützung kompiliert. + + + + Vulkan not available! + Vulkan nicht verfügbar! + + + + yuzu has not been compiled with Vulkan support. + yuzu wurde nicht mit Vulkan-Unterstützung kompiliert. + + + + Error while initializing OpenGL 4.3! + Fehler beim Initialisieren von OpenGL 4.3! + + + + Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver. + Deine Grafikkarte unterstützt kein OpenGL 4.3, oder du hast nicht den neusten Treiber installiert. + + + + Error while initializing OpenGL! + Fehler beim Initialisieren von OpenGL! + + + + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>Unsupported extensions:<br> + Deine GPU unterstützt anscheinend nicht eine oder mehrere von yuzu benötigte OpenGL-Erweiterungen. Bitte stelle sicher, dass du den neusten Grafiktreiber für deine GPU installiert hast.<br><br>Nicht unterstützte Erweiterungen:<br> + + + + GameList + + + + Name + Name + + + + + Compatibility + Kompatibilität + + + + + Add-ons + Add-ons + + + + + + + File type + Dateityp + + + + + + + Size + Größe + + + + Open Save Data Location + Spielstand-Verzeichnis öffnen + + + + Open Mod Data Location + Mod-Verzeichnis öffnen + + + + Open Transferable Shader Cache + Transferierbaren Shader-Cache öffnen + + + + Remove + Entfernen + + + + Remove Installed Update + Installiertes Update entfernen + + + + Remove All Installed DLC + Alle installierten DLCs entfernen + + + + Remove Shader Cache + Shader-Cache entfernen + + + + Remove Custom Configuration + Spiel-Einstellungen entfernen + + + + Remove All Installed Contents + Alle installierten Inhalte entfernen + + + + Dump RomFS + RomFS speichern + + + + Copy Title ID to Clipboard + Title-ID in die Zwischenablage kopieren + + + + Navigate to GameDB entry + GameDB-Eintrag öffnen + + + + Properties + Eigenschaften + + + + Scan Subfolders + Unterordner scannen + + + + Remove Game Directory + Spieleverzeichnis entfernen + + + + ▲ Move Up + ▲ Nach Oben + + + + ▼ Move Down + ▼ Nach Unten + + + + Open Directory Location + Verzeichnis öffnen + + + + GameListItemCompat + + + Perfect + Perfekt + + + + Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without +any workarounds needed. + Das Spiel funktioniert fehlerfrei, ohne Audio- oder Grafikfehler, und sämtliche getestete Funktionalität funktioniert wie vorhergesehen ohne Workarounds. + + + + Great + Gut + + + + Game functions with minor graphical or audio glitches and is playable from start to finish. May require some +workarounds. + Das Spiel funktioniert mit kleineren Audio- oder Grafikfehlern und lässt sich bis zum Ende durchspielen. +Eventuell sind einige Workarounds notwendig. + + + + Okay + Okay + + + + Game functions with major graphical or audio glitches, but game is playable from start to finish with +workarounds. + Das Spiel funktioniert mit größern Audio- oder Grafikfehlern, +lässt sich aber mit Workarounds bis zum Ende durchspielen. + + + + Bad + Schlecht + + + + Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches +even with workarounds. + Spiel funktioniert zwar, aber nur mit starken Audio- oder Grafikfehlern. Manche Bereiche des Spiels können selbst mit Workarounds nicht abgeschlossen werden. + + + + Intro/Menu + Intro/Menü + + + + Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start +Screen. + Das Spiel ist wegen schwerwiegenden Audio- oder Grafikfehlern unspielbar. Das Spiel lässt sich lediglich starten. + + + + 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. + + + + GameListPlaceholder + + + Double-click to add a new folder to the game list + Doppelklicke, um einen neuen Ordner zur Spieleliste hinzuzufügen. + + + + GameListSearchField + + + Filter: + Filter: + + + + Enter pattern to filter + Wörter zum Filtern eingeben + + + + InstallDialog + + + Please confirm these are the files you wish to install. + Bitte bestätige, dass du diese Dateien installieren willst. + + + + Installing an Update or DLC will overwrite the previously installed one. + Wenn du ein Update oder DLC installierst, wirst du die vorher installierten überschreiben. + + + + Install + Installieren + + + + Install Files to NAND + Dateien im NAND installieren + + + + LoadingScreen + + + Loading Shaders 387 / 1628 + Shader 387 / 1628 wird geladen... + + + + Loading Shaders %v out of %m + Shader %v / %m wird geladen... + + + + Estimated Time 5m 4s + Geschätzte Zeit: 5m 4s + + + + Loading... + Lädt... + + + + Loading Shaders %1 / %2 + Shader %1 / %2 wird geladen... + + + + Launching... + Starten... + + + + Estimated Time %1 + Geschätzte Zeit: %1 + + + + MainWindow + + + yuzu + yuzu + + + + &File + &Datei + + + + Recent Files + Letzte Dateien + + + + &Emulation + &Emulation + + + + &View + &Anzeige + + + + Debugging + Debugging + + + + Tools + Werkzeuge + + + + &Help + &Hilfe + + + + Install Files to NAND... + Dateien im NAND installieren... + + + + Load File... + Datei laden... + + + + Load Folder... + Verzeichnis laden... + + + + E&xit + S&chließen + + + + &Start + &Start + + + + &Pause + &Pause + + + + &Stop + &Stop + + + + Reinitialize keys... + Schlüssel neu initialisieren... + + + + About yuzu + Über yuzu + + + + Single Window Mode + Einzelfenster-Modus + + + + Configure... + Einstellungen... + + + + Display Dock Widget Headers + Dock-Widget-Header anzeigen + + + + Show Filter Bar + Filterleiste anzeigen + + + + Show Status Bar + Statusleiste anzeigen + + + + Reset Window Size + Fenstergröße zurücksetzen + + + + Fullscreen + Vollbild + + + + Restart + Neustart + + + + Load Amiibo... + Amiibo laden... + + + + Report Compatibility + Kompatibilität berichten + + + + Open Mods Page + Mods-Seite öffnen + + + + Open Quickstart Guide + Schnellstart-Anleitung öffnen + + + + FAQ + FAQ + + + + Open yuzu Folder + yuzu-Verzeichnis öffnen + + + + Capture Screenshot + Screenshot aufnehmen + + + + Configure Current Game.. + Spiel-Einstellungen ändern... + + + + MicroProfileDialog + + + MicroProfile + MicroProfile + + + + QObject + + + Installed SD Titles + Installierte SD-Titel + + + + Installed NAND Titles + Installierte NAND-Titel + + + + System Titles + Systemtitel + + + + Add New Game Directory + Neues Spieleverzeichnis hinzufügen + + + + + + Shift + Shift + + + + + + Ctrl + Strg + + + + + + Alt + Alt + + + + + + + [not set] + [nicht gesetzt] + + + + + + Hat %1 %2 + Hat %1 %2 + + + + + + Axis %1%2 + Achse %1%2 + + + + + + Button %1 + Taste %1 + + + + + + + [unknown] + [unbekannt] + + + + + Click 0 + Klick 0 + + + + + Click 1 + Klick 1 + + + + + Click 2 + Klick 2 + + + + + Click 3 + Klick 3 + + + + + Click 4 + Klick 4 + + + + GC Axis %1%2 + GC Achse %1%2 + + + + GC Button %1 + GC Knopf %1 + + + + + [unused] + [unbenutzt] + + + + + Axis %1 + Achse %1 + + + + + GC Axis %1 + GC Achse %1 + + + + QtErrorDisplay + + + An error has occured. +Please try again or contact the developer of the software. + +Error Code: %1-%2 (0x%3) + Ein Fehler ist aufgetreten. +Bitte versuche es noch einmal oder kontaktiere den Entwickler der Software. + +Fehlercode: %1-%2 (0x%3) + + + + An error occured on %1 at %2. +Please try again or contact the developer of the software. + +Error Code: %3-%4 (0x%5) + Ein Fehler ist in %1 bei %2 aufgetreten. +Bitte versuche es noch einmal oder kontaktiere den Entwickler der Software. + +Fehlercode: %3-%4 (0x%5) + + + + An error has occured. +Error Code: %1-%2 (0x%3) + +%4 + +%5 + Ein Fehler ist aufgetreten. +Fehlercode: %1-%2 (0x%3) + +%4 + +%5 + + + + QtProfileSelectionDialog + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Select a user: + Wähle einen Benutzer aus: + + + + Users + Nutzer + + + + Profile Selector + Profilauswahl + + + + QtSoftwareKeyboardDialog + + + Enter text: + Text eingeben: + + + + Software Keyboard + Software-Tastatur + + + + SequenceDialog + + + Enter a hotkey + Hotkey eingeben + + + + WaitTreeCallstack + + + Call stack + Stack aufrufen + + + + WaitTreeMutexInfo + + + waiting for mutex 0x%1 + Warten auf Mutex 0x%1 + + + + has waiters: %1 + has waiters: %1 + + + + owner handle: 0x%1 + owner handle: 0x%1 + + + + WaitTreeObjectList + + + waiting for all objects + Warten auf alle Objekte + + + + waiting for one of the following objects + Warten auf eines der folgenden Objekte + + + + WaitTreeSynchronizationObject + + + [%1]%2 %3 + [%1]%2 %3 + + + + waited by no thread + von keinem Thread pausiert + + + + WaitTreeThread + + + + running + läuft + + + + ready + bereit + + + + + paused + pausiert + + + + waiting for HLE return + warten auf HLE Rückgabe + + + + sleeping + schläft + + + + waiting for IPC reply + Warten auf IPC-Antwort + + + + waiting for objects + Warten auf Objekte + + + + waiting for mutex + Warten auf Mutex + + + + waiting for condition variable + wartet auf condition variable + + + + waiting for address arbiter + Warten auf den Adressarbiter + + + + dormant + ruhend + + + + dead + tot + + + + PC = 0x%1 LR = 0x%2 + PC = 0x%1 LR = 0x%2 + + + + ideal + ideal + + + + core %1 + Kern %1 + + + + Unknown processor %1 + Unbekannter Prozessor %1 + + + + processor = %1 + Prozessor = %1 + + + + ideal core = %1 + ideal core = %1 + + + + affinity mask = %1 + Affinitätsmaske = %1 + + + + thread id = %1 + Thread-ID = %1 + + + + priority = %1(current) / %2(normal) + Priorität = %1(aktuell) / %2(normal) + + + + last running ticks = %1 + Letzte laufende Ticks = %1 + + + + not waiting for mutex + nicht auf Mutex warten + + + + WaitTreeThreadList + + + waited by thread + gewartet von Thread + + + + WaitTreeWidget + + + Wait Tree + Wait Tree + + + \ No newline at end of file diff --git a/dist/languages/es.ts b/dist/languages/es.ts new file mode 100644 index 000000000..437817561 --- /dev/null +++ b/dist/languages/es.ts @@ -0,0 +1,4757 @@ + + + AboutDialog + + + About yuzu + Acerca de yuzu + + + + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + + + + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + + + + <html><head/><body><p>%1 | %2-%3 (%4)</p></body></html> + <html><head/><body><p>%1 | %2-%3 (%4)</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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu es un emulador experimental de código abierto de Nintendo Switch licenciado bajo GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">Este software no debe ser utilizado para jugar juegos que no hayas obtenido legalmente.</span></p></body></html> + + + + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Sitio web</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Código fuente</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contribuidor</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">Licencia</span></a></p></body></html> + + + + <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.</span></p></body></html> + <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; es una marca registrada de Nintendo. yuzu no esta afiliado con Nintendo de ninguna manera.</span></p></body></html> + + + + CalibrationConfigurationDialog + + + Communicating with the server... + Comunicando con el servidor... + + + + Cancel + Cancelar + + + + Touch the top left corner <br>of your touchpad. + Toque la esquina superior izquierda<br>de su trackpad. + + + + Now touch the bottom right corner <br>of your touchpad. + Ahora toque la esquina inferior derecha <br>de su trackpad. + + + + Configuration completed! + ¡Configuración completa! + + + + OK + OK + + + + CompatDB + + + Report Compatibility + Informar de compatibilidad + + + + + Report Game Compatibility + Informar de compatibilidad del juego + + + + <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu 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 yuzu 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 yuzu account</li></ul></body></html> + <html><head/><body><p><span style=" font-size:10pt;">SI decides presentar una prueba a la </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">Lista de Compatibilidad de yuzu</span></a><span style=" font-size:10pt;">, La siguiente información será obtenida y mostrada en el sitio web:</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;">Informacion de 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é versión de yuzu estas utilizando</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 yuzu conectada</li></ul></body></html> + + + + Perfect + Perfecto + + + + <html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html> + <html><head/><body><p>El juego funciona a la perfección sin ningún problema gráfico o de audio.</p></body></html> + + + + Great + Excelente + + + + <html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html> + <html><head/><body><p>El juego funciona con fallos gráficos o de audio menores y es jugable desde el principio hasta el final. Puede requerir de soluciones temporales.</p></body></html> + + + + Okay + Bien + + + + <html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html> + <html><head/><body><p>El juego funciona con importantes fallos gráficos o de audio, pero es jugable desde el principio hasta el final con soluciones temporales.</p></body></html> + + + + Bad + Mal + + + + <html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html> + <html><head/><body><p>El juego funciona, pero tiene errores gráficos o de audio. Imposible progresar en ciertas áreas debido a errores incluso con soluciones temporales.</p></body></html> + + + + Intro/Menu + Intro/Menú + + + + <html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html> + <html><head/><body><p>No es posible jugar a este juego debido a importantes errores gráficos o de audio. Es imposible avanzar mas allá de la pantalla de inicio.</p></body></html> + + + + Won't Boot + No inicia + + + + <html><head/><body><p>The game crashes when attempting to startup.</p></body></html> + <html><head/><body><p>El juego se bloquea al intentar iniciar.</p></body></html> + + + + <html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html> + <html><head/><body><p>Independientemente de la velocidad o del rendimiento, ¿cómo definiría su experiencia con el juego de principio a fin en esta versión de yuzu?</p></body></html> + + + + Thank you for your submission! + Gracias por su contribución. + + + + Submitting + Enviando + + + + Communication error + Error de comunicación. + + + + An error occured while sending the Testcase + Ha ocurrido un error mientras se mandaba el caso de prueba + + + + Next + Siguiente + + + + ConfigureAudio + + + Audio + Audio + + + + Output Engine: + Motor de Salida: + + + + This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency. + Este efecto de post-procesado ajusta la velocidad del audio para igualarla a la velocidad de emulación y así prevenir parones en el audio. No obstante, esto aumenta la latencia del audio. + + + + Enable audio stretching + Activar extensión del audio. + + + + Audio Device: + Dispositivo de Audio: + + + + Use global volume + Usar el volumen global + + + + Set volume: + Ajustar volumen: + + + + Volume: + Volumen: + + + + 0 % + 0 % + + + + %1% + Volume percentage (e.g. 50%) + %1% + + + + ConfigureCpu + + + Form + Formulario + + + + General + General + + + + Accuracy: + Precisión: + + + + Accurate + Preciso + + + + Unsafe + Impreciso + + + + Enable Debug Mode + Activar el Modo de Depuración + + + + We recommend setting accuracy to "Accurate". + Recomendamos ajustar la precisión a "Preciso". + + + + Unsafe CPU Optimization Settings + Ajustes del Modo Impreciso de Optimización de la CPU + + + + These settings reduce accuracy for speed. + Estos ajustes reducen la precisión para mejorar el rendimiento. + + + + Unfuse FMA (improve performance on CPUs without FMA) + Unfuse FMA (mejorar el rendimiendo en las CPU sin FMA) + + + + + <div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div> + + + <div>Esta opción mejora el rendimiento al reducir la precisión de las instrucciónes fused-multiply-add en las CPU sin soporte nativo de FMA.</div> + + + + + Faster FRSQRTE and FRECPE + Más rápido FRSQRTE y FRECPE + + + + + <div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div> + + + <div>Esta opción mejora el rendimiento de algunas funciones aproximadas de punto flotante al utilizar aproximaciones nativas menos precisas.</div> + + + + + CPU settings are available only when game is not running. + Los ajustes de la CPU sólo se encuentran disponibles cuando no se está ejecutando ningún juego. + + + + Setting CPU to Debug Mode + Ajustando la CPU al Modo de Depuración + + + + CPU Debug Mode is only intended for developer use. Are you sure you want to enable this? + El Modo de Depuración de la CPU sólo está destinado al uso de los desarrolladores. ¿Estás seguro de que quieres activar esto? + + + + ConfigureCpuDebug + + + Form + Formulario + + + + Toggle CPU Optimizations + Cambiar las optimizaciones de la CPU + + + + + <div> + <b>For debugging only.</b> + <br> + If you're not sure what these do, keep all of these enabled. + <br> + These settings only take effect when CPU Accuracy is "Debug Mode". + </div> + + + <div> + <b>Sólo para la depuración.</b> + <br> + Si no está seguro de lo que hacen estas opciónes, manténgalos todos activados. + <br> + Estos ajustes sólo toman efecto cuando la precisión de la CPU esta en "Modo de Depuración". + </div> + + + + + Enable inline page tables + Activar las tablas de páginas inline. + + + + + <div style="white-space: nowrap">This optimization speeds up memory accesses by the guest program.</div> + <div style="white-space: nowrap">Enabling it inlines accesses to PageTable::pointers into emitted code.</div> + <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">Esta optimización acelera los accesos a la memoria del programa del invitado.</div> + <div style="white-space: nowrap">Activando las inlines accede a PageTable::pointers en código emitido.</div> + <div style="white-space: nowrap">Desactivando esto obliga a que todos los accesos a la memoria pasen por las funciones Memory::Read/Memory::Write.</div> + + + + + Enable block linking + Activar el enlace de bloques + + + + + <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>Esta optimización evita las búsquedas del despachador permitiendo que los bloques básicos emitidos salten directamente a otros bloques básicos si el PC de destino es estático.</div> + + + + + Enable return stack buffer + Activar el buffer de la pila de retorno + + + + + <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>Esta optimización evita las búsquedas de los despachadores al rastrear las direcciones potenciales de retorno de las instrucciones de BL. Esto se aproxima a lo que sucede con un buffer de la pila de retorno en una CPU real.</div> + + + + + Enable fast dispatcher + Activar el despachador rápido + + + + + <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>Habilitar un sistema de despacho de dos niveles. Un despachador más rápido escrito en ensamblado tiene un pequeño caché MRU de destinos de salto se utiliza primero. Si eso falla, el despacho vuelve al despacho más lento de C++.</div> + + + + + Enable context elimination + Activar la eliminación del contexto + + + + + <div>Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.</div> + + + <div>Esto habilita una optimización de IR que reduce los accesos innecesarios a la estructura del contexto de la CPU.</div> + + + + + Enable constant propagation + Activar la propagación constante + + + + + <div>Enables IR optimizations that involve constant propagation.</div> + + + <div>Esto habilita optimizaciones de IR que implican una propagación constante.</div> + + + + + Enable miscellaneous optimizations + Activar optimizaciones misceláneas + + + + + <div>Enables miscellaneous IR optimizations.</div> + + + <div>Esto habilita optimizaciónes misceláneas IR.</div> + + + + + Enable misalignment check reduction + Activar la reducción del control de desalineación + + + + + <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">Cuando está activada, una desalineación sólo se activa cuando un acceso cruza el límite de una página.</div> + <div style="white-space: nowrap">Cuando está desactivado, se desencadena una desalineación en todos los accesos desalineados.</div> + + + + + CPU settings are available only when game is not running. + Los ajustes de la CPU sólo se encuentran disponibles cuando no se está ejecutando ningún juego. + + + + ConfigureDebug + + + Form + Formulario + + + + GDB + GDB + + + + Enable GDB Stub + Activar GDB Stub + + + + Port: + Puerto: + + + + Logging + Registro + + + + Global Log Filter + Filtro de Registro Global + + + + Show Log Console (Windows Only) + Mostrar Consola del Registro (Sólo en Windows) + + + + Open Log Location + Abrir Ubicación del Registro + + + + Homebrew + Homebrew + + + + Arguments String + Cadena de Argumentos + + + + Graphics + Gráficos + + + + When checked, the graphics API enters in a slower debugging mode + Cuando está marcado, la API de gráficos entra en un modo de depuración más lento + + + + Enable Graphics Debugging + Activar la Depuración de Gráficos + + + + When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower + Cuando está marcado, se desactiva el compilador de macro Just In Time. Activando esto hace que los juegos se ejecuten más lento + + + + Disable Macro JIT + Desactivar Macro JIT + + + + Dump + Volcar + + + + Enable Verbose Reporting Services + Habilitar Servicios de Reporte Detallados + + + + This will be reset automatically when yuzu closes. + Estas opciones se restablecerán automáticamente cuando yuzu se cierre. + + + + Advanced + Avanzado + + + + Kiosk (Quest) Mode + Modo Quiosco (Quest) + + + + ConfigureDebugController + + + Configure Debug Controller + Configurar el Control de Depuración + + + + Clear + Eliminar + + + + Defaults + Valores Predeterminados + + + + ConfigureDialog + + + yuzu Configuration + Configuración de yuzu. + + + + + + + General + General + + + + + UI + IU + + + + Game List + Lista de Juegos + + + + + + + System + Sistema + + + + + + Profiles + Perfiles + + + + + + Filesystem + Sistema de Archivos + + + + + + + Controls + Controles + + + + + + Hotkeys + Teclas de Acceso Rápido + + + + + + + CPU + CPU + + + + + + + + + Debug + Depuración + + + + + + + Graphics + Gráficos + + + + + Advanced + Avanzado + + + + GraphicsAdvanced + GráficosAvanzados + + + + + + + Audio + Audio + + + + + + Web + Web + + + + + + Services + Servicios + + + + ConfigureFilesystem + + + Form + Formulario + + + + Storage Directories + Directorios de Almacenamiento + + + + NAND + NAND + + + + + + + + + ... + ... + + + + SD Card + Tarjeta SD + + + + Gamecard + Cartucho de Juegos + + + + Path + Ruta + + + + Inserted + Insertado + + + + Current Game + Juego Actual + + + + Patch Manager + Administrador de Parches + + + + Dump Decompressed NSOs + Volcar NSOs Descomprimidos + + + + Dump ExeFS + Volcar Partición ExeFS + + + + Mod Load Root + Carpeta raíz de carga de Mods + + + + Dump Root + Carpeta raíz de Volcado + + + + Caching + Cargando Caché + + + + Cache Directory + Directorio de Caché + + + + Cache Game List Metadata + Metadatos de Lista de Juegos en Caché + + + + + + + Reset Metadata Cache + Reiniciar Caché de Metadatos + + + + Select Emulated NAND Directory... + Seleccione el directorio de NAND Emulado... + + + + Select Emulated SD Directory... + Seleccione el directorio de SD Emulado... + + + + Select Gamecard Path... + Seleccione la ruta del Cartucho... + + + + Select Dump Directory... + Seleccione Directorio para Volcar... + + + + Select Mod Load Directory... + Seleccione el directorio de carga de Mod... + + + + Select Cache Directory... + Seleccione el Directorio para Caché... + + + + The metadata cache is already empty. + El caché de metadatos ya está vacío. + + + + The operation completed successfully. + La operación se completó con éxito. + + + + The metadata cache couldn't be deleted. It might be in use or non-existent. + El caché de metadatos no se pudo eliminar. Puede que se encuentre en uso actualmente o ya haya sido eliminado. + + + + ConfigureGeneral + + + Form + Formulario + + + + General + General + + + + Limit Speed Percent + Limitar el Porcentaje de Velocidad + + + + % + % + + + + Multicore CPU Emulation + Emulación de CPU multinúcleo + + + + Confirm exit while emulation is running + Confirmar salida mientras se ejecuta la emulación + + + + Prompt for user on game boot + Mostrar usuario actual al abrir el juego + + + + Pause emulation when in background + Pausar emulación cuando la ventana esté en segundo plano + + + + Hide mouse on inactivity + Ocultar el cursor del ratón cuando no está activo. + + + + ConfigureGraphics + + + Form + Formulario + + + + API Settings + Ajustes de la API + + + + API: + API: + + + + Device: + Dispositivo: + + + + Graphics Settings + Ajustes de los Gráficos + + + + Use disk shader cache + Usar caché de shaders en disco + + + + Use asynchronous GPU emulation + Usar emulación asíncrona de GPU + + + + Aspect Ratio: + Relación de Aspecto: + + + + Default (16:9) + Valor Predeterminado (16:9) + + + + Force 4:3 + Forzar a 4:3 + + + + Force 21:9 + Forzar a 21:9 + + + + Stretch to Window + Estirar a la Ventana + + + + + Use global background color + Usar el color de fondo global + + + + Set background color: + Establecer el color de fondo: + + + + Background Color: + Color de Fondo: + + + + OpenGL Graphics Device + Dispositivo Gráfico OpenGL: + + + + ConfigureGraphicsAdvanced + + + Form + Formulario + + + + Advanced Graphics Settings + Ajustes de los Gráficos Avanzados + + + + Accuracy Level: + Nivel de Precisión: + + + + VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference. + VSync evita que la pantalla se desgarre, pero algunas tarjetas gráficas tienen un rendimiento menor con VSync activado. Mantenlo activado si no notas una diferencia de rendimiento. + + + + Use VSync (OpenGL only) + Usar VSync (sólo en OpenGL) + + + + Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental. + Activando esto reduce el tartamudeo causado por la compilación de shaders. Activa shaders de ensamblaje OpenGL en los dispositivos de Nvidia soportados (se requiere NV_gpu_program5). Esta función es experimental. + + + + Use assembly shaders (experimental, Nvidia OpenGL only) + Usar shaders de ensamblaje (experimental, sólo Nvidia OpenGL) + + + + Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental. + Activa la compilación de shaders en modo asíncrono, cuál podría reducir el tartamudeo de los shaders. Esta función es experimental. + + + + Use asynchronous shader building (experimental) + Usar la construcción de shaders asíncronos (experimental) + + + + Use Fast GPU Time + Usar Tiempo Rápido en la GPU + + + + Anisotropic Filtering: + Filtrado Anisotrópico: + + + + Default + Valor Predeterminado + + + + 2x + 2x + + + + 4x + 4x + + + + 8x + 8x + + + + 16x + 16x + + + + ConfigureHotkeys + + + Hotkey Settings + Configuración de Teclas de Acceso Rápido + + + + Double-click on a binding to change it. + Haz doble-clic para cambiar la asignación de teclas. + + + + Clear All + Eliminar Todo + + + + Restore Defaults + Restaurar Valores Predeterminados + + + + Action + Acción + + + + Hotkey + Tecla de Acceso Rápido + + + + Context + Contexto + + + + + Conflicting Key Sequence + Secuencia de Teclas en Conflicto + + + + The entered key sequence is already assigned to: %1 + La secuencia de teclas introducida ya ha sido asignada a: %1 + + + + Restore Default + Restaurar Valor Predeterminado + + + + Clear + Eliminar + + + + The default key sequence is already assigned to: %1 + La secuencia de teclas predeterminada ya ha sido asignada a: %1 + + + + ConfigureInput + + + ConfigureInput + ConfigurarEntrada + + + + + Player 1 + Jugador 1 + + + + + Player 2 + Jugador 2 + + + + + Player 3 + Jugador 3 + + + + + Player 4 + Jugador 4 + + + + + Player 5 + Jugador 5 + + + + + Player 6 + Jugador 6 + + + + + Player 7 + Jugador 7 + + + + + Player 8 + Jugador 8 + + + + + Advanced + Avanzado + + + + Console Mode + Modo de la Consola + + + + Docked + Estacionado + + + + Undocked + Portátil + + + + Vibration + Vibración + + + + % + % + + + + Motion + Movimiento + + + + Configure + Configurar + + + + Controllers + Controladores + + + + 1 + 1 + + + + 2 + 2 + + + + 3 + 3 + + + + 4 + 4 + + + + 5 + 5 + + + + 6 + 6 + + + + 7 + 7 + + + + 8 + 8 + + + + Connected + Conectado + + + + Defaults + Valores Predeterminados + + + + Clear + Eliminar + + + + ConfigureInputAdvanced + + + Configure Input + Configurar Controles + + + + Joycon Colors + Colores de Joycon + + + + Player 1 + Jugador 1 + + + + + + + + + + + L Body + Lado L + + + + + + + + + + + L Button + Botón L + + + + + + + + + + + R Body + Lado R + + + + + + + + + + + R Button + Botón R + + + + Player 2 + Jugador 2 + + + + Player 3 + Jugador 3 + + + + Player 4 + Jugador 4 + + + + Player 5 + Jugador 5 + + + + Player 6 + Jugador 6 + + + + Player 7 + Jugador 7 + + + + Player 8 + Jugador 8 + + + + Other + Otro + + + + Keyboard + Teclado + + + + + Advanced + Avanzado + + + + Touchscreen + Pantalla Táctil + + + + Mouse + Ratón + + + + Motion / Touch + Movimiento / Táctil + + + + + Configure + Configurar + + + + Debug Controller + Control de Depuración + + + + ConfigureInputPlayer + + + Configure Input + Configurar Controles + + + + Connect Controller + Conectar el Controlador + + + + + + Pro Controller + Pro Controller + + + + + Dual Joycons + Doble Joycons + + + + + Left Joycon + Joycon Izquierdo + + + + + Right Joycon + Joycon Derecho + + + + + Handheld + Portátil + + + + Input Device + Dispositivo de Entrada + + + + Any + Cualquier + + + + Keyboard/Mouse + Teclado/Ratón + + + + Profile + Perfil + + + + Save + Guardar + + + + New + Crear + + + + Delete + Borrar + + + + Left Stick + Palanca Izquierda + + + + + + + + + Up + Arriba + + + + + + + + + Left + Izquierda + + + + + + + + + Right + Derecha + + + + + + + + + Down + Abajo + + + + + + + Pressed + Presionado + + + + + + + Modifier + Modificador + + + + + Range + Rango + + + + + % + % + + + + + Deadzone: 0% + Zona Muerta: 0% + + + + + Modifier Range: 0% + Rango del Modificador: 0% + + + + D-Pad + Cruceta + + + + + L + L + + + + + ZL + ZL + + + + + Minus + Menos + + + + + Capture + Captura + + + + + Plus + Más + + + + + Home + Inicio + + + + + R + R + + + + + ZR + ZR + + + + + SL + SL + + + + + SR + SR + + + + Face Buttons + Botones Frontales + + + + + X + X + + + + + Y + Y + + + + + A + A + + + + + B + B + + + + Right Stick + Palanca Derecha + + + + + Deadzone: %1% + Zona Muerta: %1% + + + + + Modifier Range: %1% + Rango del Modificador: %1% + + + + [waiting] + [esperando] + + + + ConfigureMotionTouch + + + Configure Motion / Touch + Configurar: Movimiento / Táctil + + + + Motion + Movimiento + + + + Motion Provider: + Proveedor de Movimiento: + + + + Sensitivity: + Sensibilidad: + + + + Touch + Táctil + + + + Touch Provider: + Proveedor de Táctil: + + + + Calibration: + Calibración: + + + + (100, 50) - (1800, 850) + (100, 50) - (1800, 850) + + + + + + Configure + Configurar + + + + Use button mapping: + Usar el mapeo de botones: + + + + CemuhookUDP Config + Configuración CemuhookUDP + + + + You may use any Cemuhook compatible UDP input source to provide motion and touch input. + Puede utilizar cualquier fuente de entrada UDP compatible con Cemuhook para proporcionar una entrada de movimiento y de tacto. + + + + Server: + Servidor: + + + + Port: + Puerto: + + + + Pad: + Pad: + + + + Pad 1 + Pad 1 + + + + Pad 2 + Pad 2 + + + + Pad 3 + Pad 3 + + + + Pad 4 + Pad 4 + + + + Learn More + Más Información + + + + + Test + Probar + + + + Mouse (Right Click) + Ratón (Clic Derecho) + + + + + CemuhookUDP + CemuhookUDP + + + + Emulator Window + Ventana del Emulador + + + + <a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Learn More</span></a> + <a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Más Información</span></a> + + + + Testing + Probando + + + + Configuring + Configurando + + + + Test Successful + Prueba Existosa + + + + Successfully received data from the server. + Se recibió 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 pudo recibir datos válidos del servidor.<br>Por favor, verifique que el servidor esté configurado correctamente y que la dirección y el puerto sean correctos. + + + + Citra + Citra + + + + 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, espere a que termine el proceso. + + + + ConfigureMouseAdvanced + + + Configure Mouse + Configurar el Ratón + + + + Mouse Buttons + Botones del Ratón + + + + Forward: + Adelante: + + + + Back: + Atrás: + + + + Left: + Izquierda: + + + + Middle: + Medio: + + + + Right: + Derecha: + + + + + Clear + Borrar + + + + Defaults + Valores Predeterminados + + + + [not set] + [no establecido] + + + + Restore Default + Restaurar Valor Predeterminado + + + + [press key] + [pulsa un botón] + + + + ConfigurePerGame + + + Dialog + Diálogo + + + + Info + Información + + + + Name + Nombre + + + + Title ID + ID del Título + + + + Filename + Nombre del Archivo + + + + Format + Formato + + + + Version + Versión + + + + Size + Tamaño + + + + Developer + Desarrollador + + + + Add-Ons + Complementos + + + + General + General + + + + System + Sistema + + + + Graphics + Gráficos + + + + Adv. Graphics + Gráficos Avanzados + + + + Audio + Audio + + + + Properties + Propiedades + + + + Use global configuration (%1) + Usar la configuración global (%1) + + + + ConfigurePerGameAddons + + + Form + Formulario + + + + Patch Name + Nombre del Parche + + + + Version + Versión + + + + ConfigureProfileManager + + + Form + Formulario + + + + Profile Manager + Administrador de Perfiles + + + + Current User + Usuario Actual + + + + Username + Nombre de Usuario + + + + Set Image + Seleccionar Imagen + + + + Add + Añadir + + + + Rename + Renombrar + + + + Remove + Eliminar + + + + Profile management is available only when game is not running. + El sistema de perfiles solo se encuentra disponible cuando no se está ejecutando ningún juego. + + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Enter Username + Introduzca el Nombre de Usuario + + + + Users + Usuarios + + + + Enter a username for the new user: + Introduzca un nombre para el nuevo usuario: + + + + Enter a new username: + Introduzca un nuevo nombre de usuario: + + + + Confirm Delete + Confirmar Eliminanación + + + + You are about to delete user with name "%1". Are you sure? + Estás a punto de eliminar al usuario "%1" ¿Estás seguro? + + + + Select User Image + Seleccione una Imagen de Usuario + + + + JPEG Images (*.jpg *.jpeg) + Imagenes JPEG (*.jpg *.jpeg) + + + + Error deleting image + Error al eliminar la imagen + + + + Error occurred attempting to overwrite previous image at: %1. + Ocurrió un error al intentar sobrescribir la imagen anterior en: %1. + + + + Error deleting file + Error eliminando archivo + + + + Unable to delete existing file: %1. + No se pudo eliminar el archivo existente: %1. + + + + Error creating user image directory + Error al crear el directorio de imagen del usuario + + + + Unable to create directory %1 for storing user images. + No se puede crear el directorio % 1 para almacenar imágenes de usuario. + + + + Error copying user image + Error al copiar la imagen del usuario. + + + + Unable to copy image from %1 to %2 + No se puede copiar la imagen de %1 a %2 + + + + ConfigureService + + + Form + Formulario + + + + BCAT + BCAT + + + + BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content. + BCAT es la forma con la que Nintendo envía datos de sus juegos para así interconectar su comunidad y desbloquear contenido adicional. + + + + BCAT Backend + Backend de BCAT + + + + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Learn more about BCAT, Boxcat, and Current Events</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat">Saber más sobre BCAT, Boxcat, y sus eventos actuales.<span style=" text-decoration: underline; color:#0000ff;"></a></p></body></html> + + + + The boxcat service is offline or you are not connected to the internet. + El servicio boxcat se encuentra fuera de linea o usted no está conectado a internet. + + + + There was an error while processing the boxcat event data. Contact the yuzu developers. + Hubo un error al intentar procesar los datos del evento de boxcat. Por favor, contacte con los desarrolladores de yuzu. + + + + The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu. + La versión de yuzu que está utilizando actualmente es demasiado nueva o demasiado antigua para este servidor. Intente actualizar a la última versión oficial de yuzu. + + + + + There are currently no events on boxcat. + No hay ningún evento de boxcat en estos momentos. + + + + + Current Boxcat Events + Eventos de Boxcat Actuales + + + + Yuzu is retrieving the latest boxcat status... + Yuzu está verificando el estado actual de boxcat... + + + + ConfigureSystem + + + Form + Formulario + + + + System Settings + Ajustes del sistema + + + + Region: + Región: + + + + Auto + Auto + + + + Default + Valor Predeterminado + + + + 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 + Iran + + + + Israel + Israel + + + + Jamaica + Jamaica + + + + + Japan + Japón + + + + 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 + Zulu + + + + USA + EEUU + + + + Europe + Europa + + + + Australia + Australia + + + + China + China + + + + Korea + Corea + + + + Taiwan + Taiwan + + + + Time Zone: + Zona Horaria + + + + Note: this can be overridden when region setting is auto-select + Nota: esto puede ser ignorado cuando la opción de región está en "autoseleccionar" + + + + Japanese (日本語) + Japonés (日本語) + + + + English + Inglés (english) + + + + 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 (正體中文) + + + + Custom RTC + RTC Personalizado + + + + Language + Idioma + + + + RNG Seed + Semilla de GNA + + + + Mono + Mono + + + + Stereo + Estereo + + + + Surround + Envolvente + + + + Console ID: + ID de la Consola: + + + + Sound output mode + Modo de salida del sonido: + + + + d MMM yyyy h:mm:ss AP + d MMM aaaa h:mm:ss AP + + + + Regenerate + Regenerar + + + + System settings are available only when game is not running. + Los ajustes del sistema solo se encuentran disponibles cuando no se está ejecutando ningún juego. + + + + This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue? + Esto reemplazará tu Switch virtual con una nueva. Tu Switch virtual actual no será recuperable. Esto podría causar efectos inesperados en determinados juegos. Si usas un archivo de configuración obsoleto, esto podría fallar. ¿Continuar? + + + + Warning + Advertencia + + + + Console ID: 0x%1 + ID de Consola: 0x%1 + + + + ConfigureTouchFromButton + + + Configure Touchscreen Mappings + Configurar las Asignaciones de la Pantalla Táctil + + + + Mapping: + Asignaciones: + + + + New + Crear + + + + Delete + Borrar + + + + Rename + Renombrar + + + + 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. + Haz clic en el área inferior para añadir un punto, y luego presiona un botón para unir. +Arrastre los puntos para cambiar de posición, o haga doble clic en las celdas de la tabla para editar los valores. + + + + Delete Point + Borrar Punto + + + + Button + Botón + + + + X + X axis + X + + + + Y + Y axis + Y + + + + New Profile + Crear Perfíl + + + + Enter the name for the new profile. + Introduzca un nombre para el nuevo perfíl: + + + + Delete Profile + Borrar Perfíl + + + + Delete profile %1? + Borrar Perfíl %1? + + + + Rename Profile + Renombrar Perfíl + + + + New name: + Nuevo nombre: + + + + [press key] + [presionar tecla] + + + + ConfigureTouchscreenAdvanced + + + Configure Touchscreen + Configurar pantalla táctil + + + + Warning: The settings in this page affect the inner workings of yuzu'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. + Advertencia: Los ajustes en esta página afectarán al funcionamiento de la pantalla táctil emulada de yuzu. Cambiarlas podría resultar en un comportamiento inapropiado, como que la pantalla táctil deje de funcionar parcialmente. Se recomienda usar esta pestaña sólo si sabes lo que estás haciendo. + + + + Touch Parameters + Parámetros Táctil + + + + Touch Diameter Y + Diámetro Táctil Y + + + + Finger + Dedo + + + + Touch Diameter X + Diámetro Táctil X + + + + Rotational Angle + Ángulo Rotacional + + + + Restore Defaults + Recuperar ajustes predeterminados + + + + ConfigureUi + + + Form + Forma + + + + General + General + + + + Note: Changing language will apply your configuration. + Nota: Cambiar el idioma guardará tu configuración actual. + + + + Interface language: + Lenguage de la Interfaz: + + + + Theme: + Tema: + + + + Game List + Lista de Juegos + + + + Show Add-Ons Column + Mostrar Columna de Accesorios + + + + Icon Size: + Tamaño de los Iconos: + + + + Row 1 Text: + Texto de la Fila 1: + + + + Row 2 Text: + Texto de la Fila 2: + + + + Screenshots + Capturas de Pantalla + + + + Ask Where To Save Screenshots (Windows Only) + Pregunte Dónde Guardar las Capturas de Pantalla (sólo para Windows) + + + + Screenshots Path: + Trayectoria de las Capturas de Pantalla: + + + + ... + ... + + + + Select Screenshots Path... + Selecciona la Trayectoria de las Capturas de Pantalla: + + + + <System> + <System> + + + + English + Inglés + + + + ConfigureWeb + + + Form + Formulario + + + + yuzu Web Service + Servicio Web de yuzu + + + + By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information. + Al proporcionar su nombre de usuario y token, das tu consentimiento a que yuzu recopile datos de uso adicionales, que pueden incluir información de identificación del usuario. + + + + + Verify + Verificar + + + + Sign up + Registrarse + + + + Token: + Token: + + + + Username: + Nombre de usuario: + + + + What is my token? + ¿Cuál es mi token? + + + + Telemetry + Telemetría + + + + Share anonymous usage data with the yuzu team + Compartir datos de uso anónimos con el equipo de yuzu + + + + Learn more + Saber más + + + + Telemetry ID: + ID de Telemetría: + + + + Regenerate + Regenerar + + + + Discord Presence + Presencia de Discord + + + + Show Current Game in your Discord Status + Mostrar el juego actual en tu estado de Discord + + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Learn more</span></a> + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Saber más</span></a> + + + + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Sign up</span></a> + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Regístrate</span></a> + + + + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">What is my token?</span></a> + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">¿Cuál es mi token?</span></a> + + + + + Telemetry ID: 0x%1 + ID de Telemetría: 0x%1 + + + + + Unspecified + Sin especificar + + + + Token not verified + No se pudo verificar el token + + + + Token was not verified. The change to your token has not been saved. + El token pudo ser verificado. El cambio realizado a su token no se ha guardado. + + + + Verifying... + Verificando... + + + + Verification failed + Verificación fallida + + + + Verification failed. Check that you have entered your token correctly, and that your internet connection is working. + Verificación fallida. Compruebe que ha ingresado el token correctamente, y que esté funcionando su conexión a internet. + + + + GMainWindow + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous data is collected</a> to help improve yuzu. <br/><br/>Would you like to share your usage data with us? + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Se recogen datos anónimos</a> para ayudar a mejorar yuzu. <br/><br/>¿Quieres compartir tus datos de uso con nosotros? + + + + Telemetry + Telemetría + + + + Text Check Failed + Comprobación de Texto Fallida + + + + Loading Web Applet... + Cargando Web Applet... + + + + Exit Web Applet + Cerrar Web Applet + + + + Exit + Salir + + + + To exit the web application, use the game provided controls to select exit, select the 'Exit Web Applet' option in the menu bar, or press the 'Enter' key. + Para salir de la aplicación web, use los controles provistos por el juego y seleccione la opción "Cerrar Web Applet" en la barra del menú, o presione la tecla "Enter". + + + + Web Applet + Web Applet + + + + This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested. + Esta versión de yuzu fue creada sin soporte para QtWebEngine, lo que significa que yuzu no puede mostrar correctamente el manual del juego o la página solicitada. + + + + The amount of shaders currently being built + La cantidad de shaders que se están construyendo actualmente + + + + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. + La velocidad de emulación actual. Los valores superiores o inferiores al 100% indican que la emulación se está ejecutando más rápido o más lento 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. + Cuántos fotogramas por segundo está mostrando el juego actualmente. Esto variará de un juego a otro y de una escena a otra. + + + + 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 que lleva emular un fotograma de la Switch, sin tener en cuenta la limitación de fotogramas o sincronización vertical. Para una emulación óptima, este valor debería ser como máximo de 16.67 ms. + + + + DOCK + ESTACIONADO + + + + ASYNC + ASINC + + + + MULTICORE + MULTINÚCLEO + + + + VULKAN + VULKAN + + + + OPENGL + OPENGL + + + + Clear Recent Files + Limpiar Archivos Recientes + + + + Warning Outdated Game Format + Advertencia Formato de 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><br>For an explanation of the various Switch formats yuzu supports, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our wiki</a>. This message will not be shown again. + Está utilizando el formato de directorio de ROM deconstruido para este juego, que es un formato desactualizado que ha sido reemplazado por otros, como NCA, NAX, XCI o NSP. Los directorios de ROM deconstruidos carecen de íconos, metadatos y soporte de actualización.<br><br>Para obtener una explicación de los diversos formatos de Switch que soporta yuzu,<a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>echa un vistazo a nuestra wiki</a>. Este mensaje no se volverá a mostrar. + + + + + Error while loading ROM! + ¡Error al cargar la ROM! + + + + The ROM format is not supported. + El formato de la ROM no es compatible. + + + + An error occurred initializing the video core. + Se produjo un error al inicializar el núcleo de video. + + + + yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.Ensure that you have the latest graphics drivers for your GPU. + yuzu ha encontrado un error al ejecutar el núcleo de video, consulte el registro para obtener más detalles. Para obtener más información sobre cómo acceder al registro, consulte la siguiente página: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Cómo cargar el archivo de registro.</a> Asegúrese de tener los últimos controladores de gráficos para su GPU. + + + + Error while loading ROM! + ¡Error al cargar la ROM! + + + + An unknown error occurred. Please see the log for more details. + Error desconocido. Por favor, consulte el registro para ver más detalles. + + + + Start + Iniciar + + + + Save Data + Datos de Juegos Guardados + + + + Mod Data + Datos de Mods + + + + Error Opening %1 Folder + Error al abrir la carpeta %1 + + + + + Folder does not exist! + ¡La carpeta no existe! + + + + Error Opening Transferable Shader Cache + Error al Abrir el Caché Transferible de Shaders + + + + + A shader cache for this title does not exist. + No existe un Caché de shaders para este título. + + + + Contents + Contenidos + + + + Update + Actualización + + + + DLC + DLC + + + + Remove Entry + Eliminar el Título + + + + Remove Installed Game %1? + Eliminar el Juego Instalado %1? + + + + + + + + Successfully Removed + Se Eliminó con Éxito + + + + Successfully removed the installed base game. + Se eliminó con éxito el juego de base instalado. + + + + + + Error Removing %1 + Error en la eliminación de %1 + + + + The base game is not installed in the NAND and cannot be removed. + El juego base no está instalado en el NAND y no se puede eliminar. + + + + Successfully removed the installed update. + Se eliminó con éxito la actualización instalada. + + + + There is no update installed for this title. + No hay ninguna actualización instalada para este título. + + + + There are no DLC installed for this title. + No hay ningún DLC instalado para este título. + + + + Successfully removed %1 installed DLC. + Se eliminó con éxito %1 DLC instalado(s). + + + + Delete Transferable Shader Cache? + ¿Desea eliminar el Caché Transferible de Shaders? + + + + Remove Custom Game Configuration? + ¿Desea Eliminar la Configuración Personalizada del Juego? + + + + Remove File + Eliminar Archivo + + + + + Error Removing Transferable Shader Cache + Error al Eliminar el Caché Transferible de Shaders + + + + Successfully removed the transferable shader cache. + El Caché Transferible de Shaders fue eliminado con éxito. + + + + Failed to remove the transferable shader cache. + No se ha podido eliminar el caché de shaders transferibles. + + + + + Error Removing Custom Configuration + Error al Eliminar la Configuración Personalizada del Juego + + + + A custom configuration for this title does not exist. + No existe una configuración personalizada para este título. + + + + Successfully removed the custom game configuration. + Se eliminó con éxito la configuración personalizada del juego. + + + + Failed to remove the custom game configuration. + No se ha podido eliminar la configuración personalizada del juego. + + + + RomFS Extraction Failed! + ¡La extracción de RomFS falló! + + + + There was an error copying the RomFS files or the user cancelled the operation. + Se produjo un error al copiar los archivos RomFS o el usuario canceló la operación. + + + + Full + Completo + + + + Skeleton + Base + + + + Select RomFS Dump Mode + Elegir modo para volcar el 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. + Seleccione la forma en que desea volcar el RomFS. <br>Copiará todos los archivos en el nuevo directorio <br> mientras que el esqueleto solo creará la estructura del directorio. + + + + Extracting RomFS... + Extrayendo RomFS... + + + + + Cancel + Cancelar + + + + RomFS Extraction Succeeded! + ¡La extracción RomFS tuvo éxito! + + + + The operation completed successfully. + La operación se completó con éxito. + + + + Error Opening %1 + Error al intentar 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 el directorio de la ROM extraída + + + + Invalid Directory Selected + Directorio no válido seleccionado + + + + The directory you have selected does not contain a 'main' file. + El directorio que ha seleccionado no contiene ningún 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 Contenidos Nintendo (*.nca);;Paquete de Envío Nintendo (*.nsp);;Imagen de Cartucho NX (*.xci) + + + + Install Files + Instalar Archivos + + + + Installing file "%1"... + Instalando el archivo "%1"... + + + + Install Results + Instalar Resultados + + + + 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 + DLC del Juego + + + + Delta Title + Titulo 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 como: +(En la mayoría de los casos, el 'Juego' predeterminado está bien). + + + + Failed to Install + Fallo en la instalación + + + + The title type you selected for the NCA is invalid. + El tipo de título que seleccionó para el NCA no es válido. + + + + File not found + Archivo no encontrado + + + + File "%1" not found + Archivo "%1" no encontrado + + + + + Continue + Continuar + + + + Error Display + Mostrar Error + + + + Missing yuzu Account + Falta la cuenta de Yuzu + + + + In order to submit a game compatibility test case, you must link your yuzu account.<br><br/>To link your yuzu account, go to Emulation &gt; Configuration &gt; Web. + Para enviar un caso de prueba de compatibilidad de juegos, debe vincular su cuenta de yuzu.<br><br/> Para vincular su cuenta yuzu, vaya a Emulación & gt; Configuración & gt; Web. + + + + Error opening URL + Error al abrir la URL + + + + Unable to open the URL "%1". + No se puede abrir la URL "%1". + + + + Amiibo File (%1);; All Files (*.*) + Archivo Amiibo (%1);; Todos los archivos (*.*) + + + + Load Amiibo + Cargar amiibo + + + + Error opening Amiibo data file + Error al abrir el archivo de datos de Amiibo + + + + Unable to open Amiibo file "%1" for reading. + No se puede abrir el archivo de Amiibo "%1" para leer. + + + + Error reading Amiibo data file + Error al leer el archivo de datos de Amiibo + + + + Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes. + No se pueden leer completamente los datos de Amiibo. Se esperaban leer %1 bytes, pero solo se pudo leer %2 bytes. + + + + Error loading Amiibo data + Error al cargar los datos de Amiibo + + + + Unable to load Amiibo data. + No se pueden cargar los datos de Amiibo. + + + + Capture Screenshot + Captura de Pantalla + + + + PNG Image (*.png) + Imagen PNG (*.png) + + + + Speed: %1% / %2% + Velocidad: %1% / %2% + + + + Speed: %1% + Velocidad: %1% + + + + Game: %1 FPS + Juego: %1 FPS + + + + Frame: %1 ms + Fotogramas: %1 ms + + + + The game you are trying to load requires additional files from your Switch to be dumped before playing.<br/><br/>For more information on dumping these files, please see the following wiki page: <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + El juego que estás intentando cargar requiere de archivos adicionales de su Switch antes de poder jugar. <br/><br/>Para obtener más información sobre cómo obtener estos archivos, ve a la siguiente página de la wiki: <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Volcar archivos del sistema y las fuentes compartidas desde una Consola Switch. </a>.<br/><br/>¿Quieres volver a la lista de juegos? Continuar con la emulación puede provocar fallos, datos de guardado dañados u otros errores. + + + + yuzu was unable to locate a Switch system archive. %1 + yuzu no pudo localizar el archivo de sistema de la Switch. %1 + + + + yuzu was unable to locate a Switch system archive: %1. %2 + yuzu no pudo localizar un archivo de sistema de la Switch: %1. %2 + + + + System Archive Not Found + Archivo del Sistema No Encontrado + + + + System Archive Missing + Falta Archivo del Sistema + + + + yuzu was unable to locate the Switch shared fonts. %1 + yuzu no pudo encontrar las Fuentes Compartidas de la Switch. %1 + + + + Shared Fonts Not Found + Fuentes compartidas no encontradas + + + + Shared Font Missing + Faltan las Fuentes Compartidas + + + + Fatal Error + Error Fatal + + + + yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + yuzu ha encontrado un error fatal, consulte el registro para obtener más detalles. Para obtener más información sobre cómo acceder al registro, consulte la siguiente página: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>¿Cómo cargar el archivo de registro?</a>.<br/><br/> La emulación continua puede provocar fallos, datos de guardado dañados u otros errores. + + + + Fatal Error encountered + Error Fatal Encontrado + + + + Confirm Key Rederivation + Confirma la Clave de Rederivación + + + + You are about to force rederive all of your keys. +If you do not know what this means or what you are doing, +this is a potentially destructive action. +Please make sure this is what you want +and optionally make backups. + +This will delete your autogenerated key files and re-run the key derivation module. + Estás a punto de forzar todas tus claves. +Si no sabes qué es esto, +es una acción potencialmente destructiva. +Por favor, asegúrese de que esto +es lo que desea hacer si es necesario. + + Esto eliminará los archivos de las claves generados automáticamente y volverá a ejecutar el módulo de derivación de claves. + + + + Missing fuses + Falta fuses + + + + - Missing BOOT0 + - Falta BOOT0 + + + + - Missing BCPKG2-1-Normal-Main + - Falta BCPKG2-1-Normal-Main + + + + - Missing PRODINFO + - Falta PRODINFO + + + + Derivation Components Missing + Faltan Componentes de Derivación + + + + Components are missing that may hinder key derivation from completing. <br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu quickstart guide</a> to get all your keys and games.<br><br><small>(%1)</small> + Faltan componentes que pueden impedir que la derivación de la clave se complete. <br>Por favor siga <a href='https://yuzu-emu.org/help/quickstart/'>la guía de inicio rápido de yuzu</a> para conseguir todas tus llaves y juegos.<br><br><small>(%1)</small> + + + + Deriving keys... +This may take up to a minute depending +on your system's performance. + Derivando claves... +Esto puede llevar unos minutos dependiendo +del rendimiento de su sistema. + + + + Deriving Keys + Obtención de claves + + + + Select RomFS Dump Target + Selecciona el destinatario para volcar el RomFS + + + + Please select which RomFS you would like to dump. + Por favor, seleccione los RomFS que desea volcar. + + + + + + yuzu + yuzu + + + + Are you sure you want to close yuzu? + ¿Estás seguro de que quieres cerrar yuzu? + + + + Are you sure you want to stop the emulation? Any unsaved progress will be lost. + ¿Estás seguro de que quieres detener la emulación? Cualquier progreso no guardado se perderá. + + + + The currently running application has requested yuzu to not exit. + +Would you like to bypass this and exit anyway? + La aplicación que se está ejecutando actualmente ha solicitado que yuzu no sea cerrado. + +¿Desea cerrarlo de todas formas? + + + + GRenderWindow + + + OpenGL not available! + OpenGL no está disponible! + + + + yuzu has not been compiled with OpenGL support. + yuzu no ha sido compilado con soporte de OpenGL. + + + + Vulkan not available! + Vulkan no está disponible! + + + + yuzu has not been compiled with Vulkan support. + yuzu no ha sido compilado con soporte de Vulkan. + + + + Error while initializing OpenGL 4.3! + ¡Error al inicializar OpenGL 4.3! + + + + Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver. + Tu GPU no soporta OpenGL 4.3 o no tienes instalado el último controlador de la tarjeta gráfica. + + + + Error while initializing OpenGL! + ¡Error al inicializar OpenGL! + + + + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>Unsupported extensions:<br> + Es posible que su GPU no soporta una o más extensiones OpenGL requeridas. Por favor, asegúrese de tener el último controlador de la tarjeta gráfica<br><br>Extensiones no soportadas:<br> + + + + GameList + + + + Name + Nombre + + + + + Compatibility + Compatibilidad + + + + + Add-ons + Complementos + + + + + + + File type + Tipo de Archivo + + + + + + + Size + Tamaño + + + + Open Save Data Location + Abrir ubicación de los archivos de guardado + + + + Open Mod Data Location + Abrir ubicación de Mods + + + + Open Transferable Shader Cache + Abrir Caché Transferible de Shaders + + + + Remove + Eliminar + + + + Remove Installed Update + Eliminar la Actualización Instalada + + + + Remove All Installed DLC + Eliminar todos los DLC instalados + + + + Remove Shader Cache + Eliminar el Caché de Shaders + + + + Remove Custom Configuration + Eliminar Configuración Personalizada + + + + Remove All Installed Contents + Eliminar Todos los Contenidos Instalados + + + + Dump RomFS + Volcar RomFS + + + + Copy Title ID to Clipboard + Copiar ID de título al portapapeles + + + + Navigate to GameDB entry + Navegar a la entrada de BD del juego + + + + Properties + Propiedades + + + + 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 + + + + GameListItemCompat + + + Perfect + Perfecto + + + + Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without +any workarounds needed. + El juego funciona a la perfección sin fallos de audio o gráficos, todas las funciones probadas funcionan según lo previsto +sin ninguna solución necesaria. + + + + Great + Excelente + + + + Game functions with minor graphical or audio glitches and is playable from start to finish. May require some +workarounds. + El juego funciona con fallos gráficos o de audio menores y se puede jugar de principio a fin. Puede requerir de +soluciones temporales. + + + + Okay + Bien + + + + Game functions with major graphical or audio glitches, but game is playable from start to finish with +workarounds. + El juego funciona con importantes fallos gráficos o de audio, pero el juego se puede jugar de principio a fin con +soluciones temporales. + + + + Bad + Mal + + + + Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches +even with workarounds. + El juego funciona, pero con importantes fallos gráficos o de audio. Es imposible avanzar en zonas específicas +incluso con soluciones temporales. + + + + Intro/Menu + Intro/Menú + + + + Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start +Screen. + No es posible jugar a este juego debido a importantes errores gráficos o de audio. Es imposible avanzar mas allá de la pantalla +de inicio. + + + + Won't Boot + No inicia + + + + The game crashes when attempting to startup. + El juego se bloquea al intentar iniciar. + + + + Not Tested + Sin probar + + + + The game has not yet been tested. + El juego todavía no ha sido probado. + + + + GameListPlaceholder + + + Double-click to add a new folder to the game list + Haga doble clic para agregar un nuevo directorio a la lista de juegos. + + + + GameListSearchField + + + Filter: + Filtrar: + + + + Enter pattern to filter + Introduce patrón para filtrar + + + + InstallDialog + + + Please confirm these are the files you wish to install. + Por favor, confirme que estos son los archivos que desea instalar. + + + + Installing an Update or DLC will overwrite the previously installed one. + Instalando una Actualización o DLC reemplazará la que está instalada anteriormente. + + + + Install + Instalar + + + + Install Files to NAND + Instalar archivos al NAND... + + + + LoadingScreen + + + Loading Shaders 387 / 1628 + Cargando Shaders 387 / 1628 + + + + Loading Shaders %v out of %m + Cargando Shaders %v de %m + + + + Estimated Time 5m 4s + Tiempo Estimado 5m 4s + + + + Loading... + Cargando... + + + + Loading Shaders %1 / %2 + Cargando Shaders %1 / %2 + + + + Launching... + Iniciando... + + + + Estimated Time %1 + Tiempo estimado %1 + + + + MainWindow + + + yuzu + yuzu + + + + &File + &Archivo + + + + Recent Files + Archivos recientes + + + + &Emulation + &Emulación + + + + &View + &Ver + + + + Debugging + Depuración + + + + Tools + Herramientas + + + + &Help + &Ayuda + + + + Install Files to NAND... + Instalar archivos al NAND... + + + + Load File... + Cargar archivo... + + + + Load Folder... + Cargar carpeta... + + + + E&xit + S&alir + + + + &Start + &Iniciar + + + + &Pause + &Pausar + + + + &Stop + &Detener + + + + Reinitialize keys... + Reiniciar claves... + + + + About yuzu + Acerca de yuzu + + + + Single Window Mode + Modo Ventana Única + + + + Configure... + Configurar... + + + + Display Dock Widget Headers + Mostrar los Encabezados del Widget + + + + Show Filter Bar + Mostrar Barra de Filtro + + + + Show Status Bar + Mostrar Barra de Estado + + + + Reset Window Size + Reiniciar el Tamaño de la Ventana + + + + Fullscreen + Pantalla completa + + + + Restart + Reiniciar + + + + Load Amiibo... + Cargar Amiibo... + + + + Report Compatibility + Informar de compatibilidad + + + + Open Mods Page + Abrir la Página de Mods + + + + Open Quickstart Guide + Abrir la Guía de Inicio Rápido + + + + FAQ + Preguntas Más Frecuentes + + + + Open yuzu Folder + Abrir la carpeta yuzu + + + + Capture Screenshot + Captura de Pantalla + + + + Configure Current Game.. + Configurar el Juego Actual... + + + + MicroProfileDialog + + + MicroProfile + MicroPerfil + + + + QObject + + + Installed SD Titles + Títulos instalados en la SD + + + + Installed NAND Titles + Títulos instalados en la NAND + + + + System Titles + Títulos del Sistema + + + + Add New Game Directory + Agregar un Nuevo Directorio de Juegos + + + + + + Shift + Shift + + + + + + Ctrl + Ctrl + + + + + + Alt + Alt + + + + + + + [not set] + [no establecido] + + + + + + Hat %1 %2 + Rotación %1 %2 + + + + + + Axis %1%2 + Eje %1%2 + + + + + + Button %1 + Botón %1 + + + + + + + [unknown] + [desconocido] + + + + + Click 0 + Clic 0 + + + + + Click 1 + Clic 1 + + + + + Click 2 + Clic 2 + + + + + Click 3 + Clic 3 + + + + + Click 4 + Clic 4 + + + + GC Axis %1%2 + Eje GC %1%2 + + + + GC Button %1 + Botón GC %1 + + + + + [unused] + [no usado] + + + + + Axis %1 + Eje %1 + + + + + GC Axis %1 + Eje GC %1 + + + + QtErrorDisplay + + + An error has occured. +Please try again or contact the developer of the software. + +Error Code: %1-%2 (0x%3) + Ha ocurrido un error. +Inténtelo nuevamente o contacte con el desarrollador del software. + +Código del error: %1-%2 (0x%3) + + + + An error occured on %1 at %2. +Please try again or contact the developer of the software. + +Error Code: %3-%4 (0x%5) + Ha ocurrido un error en el %1 a las %2. +Inténtelo nuevamente o contacte con el desarrollador del software. + +Código del error: %3-%4 (0x%5) + + + + An error has occured. +Error Code: %1-%2 (0x%3) + +%4 + +%5 + Ha ocurrido un error. +Códio del error: %1-%2 (0x%3) + +%4 + +%5 + + + + QtProfileSelectionDialog + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Select a user: + Seleccione un usuario: + + + + Users + Usuarios + + + + Profile Selector + Selector de perfil + + + + QtSoftwareKeyboardDialog + + + Enter text: + Introducir texto: + + + + Software Keyboard + Software del Teclado + + + + SequenceDialog + + + Enter a hotkey + Ingrese combinación de teclas de acceso rápido + + + + WaitTreeCallstack + + + Call stack + Pila de Llamadas + + + + WaitTreeMutexInfo + + + waiting for mutex 0x%1 + esperando por mutex 0x%1 + + + + has waiters: %1 + tiene receptores: %1 + + + + owner handle: 0x%1 + manejo del propietario: 0x%1 + + + + WaitTreeObjectList + + + waiting for all objects + esperando todos los objetos + + + + waiting for one of the following objects + esperando uno de los siguientes objetos + + + + WaitTreeSynchronizationObject + + + [%1]%2 %3 + [%1]%2 %3 + + + + waited by no thread + esperado por ningún hilo + + + + WaitTreeThread + + + + running + corriendo + + + + ready + listo + + + + + paused + en pausa + + + + waiting for HLE return + esperando para el retorno HLE + + + + sleeping + dormido + + + + waiting for IPC reply + esperando para respuesta IPC + + + + waiting for objects + esperando objetos + + + + waiting for mutex + esperando por mutex + + + + waiting for condition variable + esperando variable condicional + + + + waiting for address arbiter + esperando al árbitro de dirección + + + + dormant + inactivo + + + + dead + muerto + + + + PC = 0x%1 LR = 0x%2 + PC = 0x%1 LR = 0x%2 + + + + ideal + ideal + + + + core %1 + núcleo %1 + + + + Unknown processor %1 + Procesador desconocido %1 + + + + processor = %1 + procesador = %1 + + + + ideal core = %1 + núcleo ideal = %1 + + + + affinity mask = %1 + máscara de afinidad = %1 + + + + thread id = %1 + id de hilo = %1 + + + + priority = %1(current) / %2(normal) + prioridad = %1(presente) / %2(normal) + + + + last running ticks = %1 + Últimos ticks consecutivos = %1 + + + + not waiting for mutex + no esperando por mutex + + + + WaitTreeThreadList + + + waited by thread + esperado por hilo + + + + WaitTreeWidget + + + Wait Tree + Árbol de Espera + + + \ No newline at end of file diff --git a/dist/languages/fr.ts b/dist/languages/fr.ts new file mode 100644 index 000000000..83e678f93 --- /dev/null +++ b/dist/languages/fr.ts @@ -0,0 +1,4732 @@ + + + AboutDialog + + + About yuzu + À propos de yuzu + + + + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + + + + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + <html><head/><body><span style=" font-size:28pt;">yuzu</span><p></body></html> + + + + <html><head/><body><p>%1 | %2-%3 (%4)</p></body></html> + <html><head/><body><p>%1 | %2-%3 (%4)</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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">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" /><style type="text/css"> +p, li { white-space: pre-wrap; }</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu est un émulateur expérimental open-source pour la Nintendo Switch licencié sous GPLv2.0. </span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">Ce logiciel ne doit pas être utilisé pour jouer à des jeux que vous n'avez pas obtenu légalement.</span></p></body> +</html> + + + + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Site Web</span></a>|<a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Code Source</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributeurs</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">Licence</span></a></p></body></html> + + + + <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.</span></p></body></html> + <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; est une marque déposée de Nintendo. yuzu n'est en aucun cas affilié à Nintendo.</span></p></body></html> + + + + CalibrationConfigurationDialog + + + Communicating with the server... + Communications avec le serveur... + + + + Cancel + Annuler + + + + Touch the top left corner <br>of your touchpad. + Touchez le coin supérieur<br>gauche de votre pavé tactile. + + + + Now touch the bottom right corner <br>of your touchpad. + Touchez le coin supérieur gauche<br> de votre pavé tactile. + + + + Configuration completed! + Configuration terminée! + + + + OK + OK + + + + 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://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu 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 yuzu 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 yuzu account</li></ul></body></html> + <html><head/><body><p><span style=" font-size:10pt;">Si vous choisissez à soumettre un test d'essai à la liste de compatibilité yuzu<span style=" font-size:10pt; text-decoration: underline; color:#0000ff;"><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt;">, Les informations suivantes seront collectées et publié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 Système (Processeur / Carte Graphique / 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;">La version de yuzu que vous employez</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Le compte yuzu sous lequel vous êtes connecté</li></ul></body></html> + + + + Perfect + Parfait + + + + <html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html> + <html><head/><body><p>Le jeu fonctionne parfaitement sans problèmes audio-visuels.</p></body></html> + + + + Great + Super + + + + <html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html> + <html><head/><body><p>Le jeu fonctionne avec des problèmes audio-visuels mineurs et est jouable du début jusqu'à la fin. Peut nécessiter des patchs temporaires. </p></body></html> + + + + Okay + Correct + + + + <html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html> + <html><head/><body><p>Le jeu fonctionne avec des problèmes audio ou graphiques majeurs, mais est jouable du début à la fin avec des modifications.</p></body></html> + + + + Bad + Mauvais + + + + <html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html> + <html><head/><body><p>Le jeu fonctionne, mais avec des problèmes graphiques ou audio majeurs. Impossible de progresser à certains endroits spécifiques à cause de ces problèmes même en utilisant des modifications. </p></body></html> + + + + Intro/Menu + Intro/Menu + + + + <html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html> + <html><head/><body><p>Le jeu est complètement injouable à cause de problèmes graphique ou audio majeur. Impossible de progresser plus loin que le menu de départ.</p></body></html> + + + + Won't Boot + Ne se lance pas + + + + <html><head/><body><p>The game crashes when attempting to startup.</p></body></html> + <html><head/><body><p>Le jeu crash au lancement. .</p></body></html> + + + + <html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html> + <html><head/><body><p>Sans prendre en compte la vitesse ou les performances, À quel point ce jeu est-il bien émulée du début à la fin sur cette version de yuzu ?</p></body></html> + + + + Thank you for your submission! + Merci de votre Suggestion ! + + + + Submitting + Soumission en cours + + + + Communication error + Erreur de communication + + + + An error occured while sending the Testcase + Une erreur est survenue en envoyant l'erreur + + + + Next + Suivant + + + + ConfigureAudio + + + Audio + Audio + + + + Output Engine: + Moteur de Sortie : + + + + This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency. + Il s'agit d'un effet de post-traitement qui ajuste la vitesse de l'audio sur celle de l'émulation afin de prévenir les lags du son. Ceci augmente toutefois la latence du son. + + + + Enable audio stretching + Activer l'étirement du son + + + + Audio Device: + Appareil audio : + + + + Use global volume + Utiliser le volume global + + + + Set volume: + Régler le volume: + + + + Volume: + Volume : + + + + 0 % + 0 % + + + + %1% + Volume percentage (e.g. 50%) + %1% + + + + ConfigureCpu + + + Form + Forme + + + + General + Général + + + + Accuracy: + Précision: + + + + Accurate + Précis + + + + Unsafe + Peu sûr + + + + Enable Debug Mode + Activer le mode de débogage + + + + We recommend setting accuracy to "Accurate". + Nous recommandons de régler la précision sur "Précis". + + + + Unsafe CPU Optimization Settings + Paramètres d'optimisation du CPU non sûrs + + + + These settings reduce accuracy for speed. + Ces réglages réduisent la précision pour la vitesse. + + + + Unfuse FMA (improve performance on CPUs without FMA) + Non-utilisation du FMA (améliorer les performances des CPU sans FMA) + + + + + <div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div> + + +Cette option améliore la vitesse en réduisant la précision des instructions fusionnées-multiple-ajoutées sur les CPU sans support FMA natif. + + + + Faster FRSQRTE and FRECPE + FRSQRTE et FRECPE plus rapides + + + + + <div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div> + + +Cette option améliore la vitesse de certaines fonctions approximatives en virgule flottante en utilisant des approximations natives moins précises. + + + + CPU settings are available only when game is not running. + Les paramètres du CPU ne sont disponibles que lorsque le jeu n'est pas en marche. + + + + Setting CPU to Debug Mode + Mise en mode de débogage du CPU + + + + CPU Debug Mode is only intended for developer use. Are you sure you want to enable this? + Le mode de débogage du CPU est uniquement destiné à l'usage des développeurs. Êtes-vous sûr de vouloir l'activer? + + + + ConfigureCpuDebug + + + Form + Forme + + + + Toggle CPU Optimizations + Activer les optimisations du CPU + + + + + <div> + <b>For debugging only.</b> + <br> + If you're not sure what these do, keep all of these enabled. + <br> + These settings only take effect when CPU Accuracy is "Debug Mode". + </div> + + +<div> +<b>Pour le débogage uniquement.</b> +<br> +Si vous n'êtes pas sûr de ce qu'elles font, laissez-les toutes activées. +<br> +Ces réglages ne prennent effet que lorsque la précision du CPU est en "mode débogage". +</div> + + + + Enable inline page tables + + + + + + <div style="white-space: nowrap">This optimization speeds up memory accesses by the guest program.</div> + <div style="white-space: nowrap">Enabling it inlines accesses to PageTable::pointers into emitted code.</div> + <div style="white-space: nowrap">Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.</div> + + + + + + Enable block linking + + + + + + <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> + + + + + + Enable return stack buffer + + + + + + <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> + + + + + + Enable fast dispatcher + + + + + + <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> + + + + + + Enable context elimination + + + + + + <div>Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.</div> + + + + + + Enable constant propagation + + + + + + <div>Enables IR optimizations that involve constant propagation.</div> + + + + + + Enable miscellaneous optimizations + + + + + + <div>Enables miscellaneous IR optimizations.</div> + + + + + + Enable misalignment check reduction + + + + + + <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> + + + + + + CPU settings are available only when game is not running. + + + + + ConfigureDebug + + + Form + Forme + + + + GDB + GDB + + + + Enable GDB Stub + Activer le "stub" de GDB + + + + Port: + Port : + + + + Logging + S'enregistrer + + + + Global Log Filter + Filtre de log global + + + + Show Log Console (Windows Only) + Montrer le journal de logs (Uniquement sur Windows) + + + + Open Log Location + Ouvrir l'emplacement du journal de logs + + + + Homebrew + Homebrew + + + + Arguments String + Chaîne d'arguments + + + + Graphics + + + + + When checked, the graphics API enters in a slower debugging mode + + + + + Enable Graphics Debugging + + + + + When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower + + + + + Disable Macro JIT + + + + + Dump + + + + + Enable Verbose Reporting Services + Activer les services de rapport verbeux + + + + This will be reset automatically when yuzu closes. + Ceci sera automatiquement mis à zéro quand yuzu se fermera + + + + Advanced + Avancé + + + + Kiosk (Quest) Mode + Mode Kiosk (Quest) + + + + ConfigureDebugController + + + Configure Debug Controller + + + + + Clear + + + + + Defaults + + + + + ConfigureDialog + + + yuzu Configuration + Configuration de yuzu + + + + + + + General + Général + + + + + UI + UI + + + + Game List + Liste des jeux + + + + + + + System + Système + + + + + + Profiles + Profils + + + + + + Filesystem + Système de fichier + + + + + + + Controls + Contrôles + + + + + + Hotkeys + Raccourcis clavier + + + + + + + CPU + + + + + + + + + + Debug + Débogage + + + + + + + Graphics + Vidéo + + + + + Advanced + + + + + GraphicsAdvanced + + + + + + + + Audio + Son + + + + + + Web + Web + + + + + + Services + Services + + + + ConfigureFilesystem + + + Form + Forme + + + + Storage Directories + Répertoire de stockage + + + + NAND + NAND + + + + + + + + + ... + ... + + + + SD Card + Carte SD + + + + Gamecard + Cartouche de jeu + + + + Path + Chemin + + + + Inserted + Inséré + + + + Current Game + Jeu en cours + + + + Patch Manager + Gestionnaire de correctif + + + + Dump Decompressed NSOs + Extraire les fichiers NSOs décompressés + + + + Dump ExeFS + Extraire l'ExeFS + + + + Mod Load Root + Racine de chargement de mod + + + + Dump Root + Extraire la racine + + + + Caching + Mise en cache + + + + Cache Directory + Répertoire du cache + + + + Cache Game List Metadata + Mettre en cache la métadonnée de la liste des jeux + + + + + + + Reset Metadata Cache + Mettre à zéro le cache des métadonnées + + + + Select Emulated NAND Directory... + Sélectionner le répertoire NAND émulé... + + + + Select Emulated SD Directory... + Sélectionner le répertoire SD émulé... + + + + Select Gamecard Path... + Sélectionner le chemin de la cartouche de jeu... + + + + Select Dump Directory... + Sélectionner le répertoire d'extraction... + + + + Select Mod Load Directory... + Sélectionner le répertoire de chargement de mod... + + + + Select Cache Directory... + Sélectionner le répertoire du cache... + + + + The metadata cache is already empty. + Le cache des métadonnées est déjà vide. + + + + The operation completed successfully. + L'opération s'est terminée avec succès. + + + + The metadata cache couldn't be deleted. It might be in use or non-existent. + Le cache des métadonnées n'a pas pu être supprimé. Il pourrait être utilisé ou non-existant. + + + + ConfigureGeneral + + + Form + Forme + + + + General + Général + + + + Limit Speed Percent + Limiter la vitesse en pourcentages + + + + % + % + + + + Multicore CPU Emulation + + + + + Confirm exit while emulation is running + Confirmer la sortie pendent l'émulation en cours + + + + Prompt for user on game boot + Demander l'utilisateur au lancement d'un jeu + + + + Pause emulation when in background + Mettre en pause l’émulation lorsque mis en arrière plan + + + + Hide mouse on inactivity + + + + + ConfigureGraphics + + + Form + Forme + + + + API Settings + + + + + API: + + + + + Device: + + + + + Graphics Settings + + + + + Use disk shader cache + Utiliser les shader cache de disque + + + + Use asynchronous GPU emulation + Utiliser l'émulation GPU asynchrone + + + + Aspect Ratio: + + + + + Default (16:9) + + + + + Force 4:3 + + + + + Force 21:9 + + + + + Stretch to Window + + + + + + Use global background color + + + + + Set background color: + + + + + Background Color: + Couleur de L’arrière plan : + + + + OpenGL Graphics Device + + + + + ConfigureGraphicsAdvanced + + + Form + + + + + Advanced Graphics Settings + + + + + Accuracy Level: + + + + + VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference. + + + + + Use VSync (OpenGL only) + + + + + Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental. + + + + + Use assembly shaders (experimental, Nvidia OpenGL only) + + + + + Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental. + + + + + Use asynchronous shader building (experimental) + + + + + Use Fast GPU Time + + + + + Anisotropic Filtering: + + + + + Default + + + + + 2x + + + + + 4x + + + + + 8x + + + + + 16x + + + + + ConfigureHotkeys + + + Hotkey Settings + Paramètres de raccourcis + + + + Double-click on a binding to change it. + Double-cliquez sur une liaison pour la changer. + + + + Clear All + + + + + Restore Defaults + + + + + Action + Action + + + + Hotkey + Raccourci clavier + + + + Context + Contexte + + + + + Conflicting Key Sequence + Séquence de touches conflictuelle + + + + The entered key sequence is already assigned to: %1 + + + + + Restore Default + + + + + Clear + + + + + The default key sequence is already assigned to: %1 + + + + + ConfigureInput + + + ConfigureInput + + + + + + Player 1 + Joueur 1 + + + + + Player 2 + Joueur 2 + + + + + Player 3 + Joueur 3 + + + + + Player 4 + Joueur 4 + + + + + Player 5 + Joueur 5 + + + + + Player 6 + Joueur 6 + + + + + Player 7 + Joueur 7 + + + + + Player 8 + Joueur 8 + + + + + Advanced + Avancé + + + + Console Mode + + + + + Docked + + + + + Undocked + + + + + Vibration + + + + + % + + + + + Motion + + + + + Configure + Configurer + + + + Controllers + + + + + 1 + + + + + 2 + + + + + 3 + + + + + 4 + + + + + 5 + + + + + 6 + + + + + 7 + + + + + 8 + + + + + Connected + + + + + Defaults + + + + + Clear + + + + + ConfigureInputAdvanced + + + Configure Input + + + + + Joycon Colors + + + + + Player 1 + + + + + + + + + + + + L Body + + + + + + + + + + + + L Button + + + + + + + + + + + + R Body + + + + + + + + + + + + R Button + + + + + Player 2 + + + + + Player 3 + + + + + Player 4 + + + + + Player 5 + + + + + Player 6 + + + + + Player 7 + + + + + Player 8 + + + + + Other + + + + + Keyboard + + + + + + Advanced + + + + + Touchscreen + + + + + Mouse + + + + + Motion / Touch + + + + + + Configure + + + + + Debug Controller + + + + + ConfigureInputPlayer + + + Configure Input + Configurer les touches + + + + Connect Controller + + + + + + + Pro Controller + + + + + + Dual Joycons + + + + + + Left Joycon + + + + + + Right Joycon + + + + + + Handheld + + + + + Input Device + + + + + Any + + + + + Keyboard/Mouse + + + + + Profile + + + + + Save + + + + + New + + + + + Delete + + + + + Left Stick + Stick Gauche + + + + + + + + + Up + + + + + + + + + + Left + + + + + + + + + + Right + + + + + + + + + + Down + + + + + + + + Pressed + + + + + + + + Modifier + + + + + + Range + + + + + + % + + + + + + Deadzone: 0% + + + + + + Modifier Range: 0% + + + + + D-Pad + + + + + + L + + + + + + ZL + + + + + + Minus + + + + + + Capture + + + + + + Plus + + + + + + Home + + + + + + R + + + + + + ZR + + + + + + SL + + + + + + SR + + + + + Face Buttons + Boutons + + + + + X + + + + + + Y + + + + + + A + + + + + + B + + + + + Right Stick + Stick Droit + + + + + Deadzone: %1% + + + + + + Modifier Range: %1% + + + + + [waiting] + + + + + ConfigureMotionTouch + + + Configure Motion / Touch + + + + + Motion + + + + + Motion Provider: + + + + + Sensitivity: + + + + + Touch + + + + + Touch Provider: + + + + + Calibration: + + + + + (100, 50) - (1800, 850) + + + + + + + Configure + + + + + Use button mapping: + + + + + CemuhookUDP Config + + + + + You may use any Cemuhook compatible UDP input source to provide motion and touch input. + + + + + Server: + + + + + Port: + + + + + Pad: + + + + + Pad 1 + + + + + Pad 2 + + + + + Pad 3 + + + + + Pad 4 + + + + + Learn More + + + + + + Test + + + + + Mouse (Right Click) + + + + + + CemuhookUDP + + + + + Emulator Window + + + + + <a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Learn More</span></a> + + + + + 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. + + + + + Citra + + + + + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. + + + + + ConfigureMouseAdvanced + + + Configure Mouse + Configuration de la souris + + + + Mouse Buttons + Boutons de la souris + + + + Forward: + Avant : + + + + Back: + Arrière : + + + + Left: + Gauche : + + + + Middle: + Milieu : + + + + Right: + Droite : + + + + + Clear + Effacer + + + + Defaults + + + + + [not set] + [pas défini] + + + + Restore Default + Réinitialiser + + + + [press key] + [appuyez sur une touche] + + + + ConfigurePerGame + + + Dialog + + + + + Info + + + + + Name + + + + + Title ID + + + + + Filename + + + + + Format + + + + + Version + + + + + Size + + + + + Developer + + + + + Add-Ons + + + + + General + + + + + System + + + + + Graphics + + + + + Adv. Graphics + + + + + Audio + + + + + Properties + + + + + Use global configuration (%1) + + + + + ConfigurePerGameAddons + + + Form + + + + + Patch Name + + + + + Version + + + + + ConfigureProfileManager + + + Form + Forme + + + + Profile Manager + Gestionnaire de profil + + + + Current User + Utilisateur actuel + + + + Username + Nom d'utilisateur + + + + Set Image + Mettre une image + + + + Add + Ajouter + + + + Rename + Renommer + + + + Remove + Supprimer + + + + Profile management is available only when game is not running. + La gestion de profil est accessible uniquement lorsque aucun jeu n'est en cours. + + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Enter Username + Entrez un nom d'utilisateur + + + + Users + Utilisateurs + + + + Enter a username for the new user: + Entrez un nom d'utilisateur pour le nouvel utilisateur : + + + + Enter a new username: + Entrez un nouveau nom d'utilisateur : + + + + Confirm Delete + Confirmez la suppression + + + + You are about to delete user with name "%1". Are you sure? + Vous êtes sur le point de supprimer l'utilisateur avec le nom "%1". Êtes vous sûr? + + + + Select User Image + Sélectionner l'image de l'utilisateur + + + + JPEG Images (*.jpg *.jpeg) + Images JPEG (*.jpg *.jpeg) + + + + Error deleting image + Erreur dans la suppression de l'image + + + + Error occurred attempting to overwrite previous image at: %1. + Une erreur est survenue en essayant de changer l'image précédente à : %1. + + + + Error deleting file + Erreur dans la suppression du fichier + + + + Unable to delete existing file: %1. + Impossible de supprimer le fichier existant : %1. + + + + Error creating user image directory + Erreur dans la création du répertoire d'image de l'utilisateur + + + + Unable to create directory %1 for storing user images. + Impossible de créer le répertoire %1 pour stocker les images de l'utilisateur. + + + + Error copying user image + Erreur dans la copie de l'image de l'utilisateur + + + + Unable to copy image from %1 to %2 + Impossible de copier l'image de %1 à %2 + + + + ConfigureService + + + Form + Forme + + + + BCAT + BCAT + + + + BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content. + Le BCAT et le chemin que Nintendo utilise pour envoyer des informations au jeux pour encourager sa communauté à débloquer du contenu additionel. + + + + BCAT Backend + BCAT Back-end + + + + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Learn more about BCAT, Boxcat, and Current Events</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Apprenez en plus sur le BCAT, le Boxcat, et les événements courants</span></a></p></body></html> + + + + The boxcat service is offline or you are not connected to the internet. + Le service boxcat est hors-ligne ou vous n'êtes pas connectés à internet. + + + + There was an error while processing the boxcat event data. Contact the yuzu developers. + Une erreur est survenu lors du traitement des données boxcat. Contactez les développeurs de yuzu. + + + + The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu. + La version de yuzu que vous utilisez est trop vieille ou trop nouvelle pour le serveur. Essayez la dernière mise à jour officiel de yuzu. + + + + + There are currently no events on boxcat. + Il n'y a pour le moment aucun événements sur boxcat. + + + + + Current Boxcat Events + + + + + Yuzu is retrieving the latest boxcat status... + Yuzu récupere le statut de boxcat le plus récent... + + + + ConfigureSystem + + + Form + Forme + + + + System Settings + Paramètres Système + + + + Region: + + + + + Auto + + + + + Default + + + + + CET + + + + + CST6CDT + + + + + Cuba + + + + + EET + + + + + Egypt + + + + + Eire + + + + + EST + + + + + EST5EDT + + + + + GB + + + + + GB-Eire + + + + + GMT + + + + + GMT+0 + + + + + GMT-0 + + + + + GMT0 + + + + + Greenwich + + + + + Hongkong + + + + + HST + + + + + Iceland + + + + + Iran + + + + + Israel + + + + + Jamaica + + + + + + Japan + + + + + Kwajalein + + + + + Libya + + + + + MET + + + + + MST + + + + + MST7MDT + + + + + Navajo + + + + + NZ + + + + + NZ-CHAT + + + + + Poland + + + + + Portugal + + + + + PRC + + + + + PST8PDT + + + + + ROC + + + + + ROK + + + + + Singapore + + + + + Turkey + + + + + UCT + + + + + Universal + + + + + UTC + + + + + W-SU + + + + + WET + + + + + Zulu + + + + + USA + + + + + Europe + + + + + Australia + + + + + China + + + + + Korea + + + + + Taiwan + + + + + Time Zone: + + + + + Note: this can be overridden when region setting is auto-select + Note : ceci peut être remplacé quand le paramètre de région est réglé sur automatique + + + + Japanese (日本語) + Japonais (日本語) + + + + English + Anglais + + + + 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 (正體中文) + + + + Custom RTC + RTC Customisé + + + + Language + Langue + + + + RNG Seed + Seed RNG + + + + Mono + Mono + + + + Stereo + Stéréo + + + + Surround + Surround + + + + Console ID: + ID de la Console : + + + + Sound output mode + Mode de sortie Audio + + + + d MMM yyyy h:mm:ss AP + d MMM yyyy h:mm:ss AP + + + + Regenerate + Regénérer + + + + System settings are available only when game is not running. + Les paramètres systèmes ne sont accessibles que lorsque le jeu n'est pas en cours. + + + + This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue? + Ceci remplacera la Switch virtuelle actuelle par une nouvelle. La Switch actuelle ne sera plus récupérable. cela peut entrainer des effets non désirés pendant le jeu. Ceci peut échouer si une configuration de sauvegarde périmée est utilisée. Continuer ? + + + + Warning + Avertissement + + + + Console ID: 0x%1 + ID de la Console : 0x%1 + + + + ConfigureTouchFromButton + + + Configure Touchscreen Mappings + + + + + Mapping: + + + + + New + + + + + Delete + + + + + 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 + + + + + Button + + + + + X + X axis + + + + + Y + Y axis + + + + + New Profile + + + + + Enter the name for the new profile. + + + + + Delete Profile + + + + + Delete profile %1? + + + + + Rename Profile + + + + + New name: + + + + + [press key] + + + + + ConfigureTouchscreenAdvanced + + + Configure Touchscreen + Configurer l'Écran Tactile + + + + Warning: The settings in this page affect the inner workings of yuzu'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. + Avertissement : les paramètres de cette page affecte la fonctionnalité intrinsèque de l'écran tactile émulée de yuzu. Toute modification pourrait aboutir à un comportement non désiré, comme par exemple : l'écran tactile qui ne fonctionne plus, de manières partielle ou totale. N'utilisez cette page que si ne vous ne savez ce que vos faites. + + + + Touch Parameters + Paramètres Tactiles + + + + Touch Diameter Y + Diamètres Tactiles Y + + + + Finger + Doigt + + + + Touch Diameter X + Diamètres Tactiles X + + + + Rotational Angle + Angle de Rotation + + + + Restore Defaults + Restaurer + + + + ConfigureUi + + + Form + Forme + + + + General + Général + + + + Note: Changing language will apply your configuration. + Note : Changer la langue appliquera votre configuration. + + + + Interface language: + Language de l'interface : + + + + Theme: + Thème : + + + + Game List + Liste de jeux + + + + Show Add-Ons Column + Afficher la colonne Des Add-Ons + + + + Icon Size: + Grosseur de l'icône + + + + Row 1 Text: + Texte rangée 1 : + + + + Row 2 Text: + Texte rangée 2 : + + + + Screenshots + + + + + Ask Where To Save Screenshots (Windows Only) + + + + + Screenshots Path: + + + + + ... + + + + + Select Screenshots Path... + + + + + <System> + <System> + + + + English + Anglais + + + + ConfigureWeb + + + Form + Forme + + + + yuzu Web Service + Service Web yuzu + + + + By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information. + En fournissant votre surnom et token, vous acceptez de permettre à yuzu de collecter des données d'utilisation supplémentaires, qui peuvent contenir des informations d'identification de l'utilisateur. + + + + + Verify + Vérifier + + + + Sign up + Se connecter + + + + Token: + Token : + + + + Username: + Pseudonyme : + + + + What is my token? + Qu'est ce que mon token ? + + + + Telemetry + Télémétrie + + + + Share anonymous usage data with the yuzu team + Partager les données d'utilisation anonymes avec l'équipe yuzu + + + + Learn more + En savoir plus + + + + Telemetry ID: + ID Télémétrie : + + + + Regenerate + Regénérer + + + + Discord Presence + Statut Discord + + + + Show Current Game in your Discord Status + Afficher le jeu en cours dans le Statut Discord + + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Learn more</span></a> + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">En savoir plus</span></a> + + + + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Sign up</span></a> + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Se connecter</span></a> + + + + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">What is my token?</span></a> + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">Quel est mon token ?</span></a> + + + + + Telemetry ID: 0x%1 + ID Télémétrie : 0x%1 + + + + + Unspecified + Non-spécifié + + + + Token not verified + Token non vérifié + + + + Token was not verified. The change to your token has not been saved. + Le token n'a pas été vérifié. Le changement à votre token n'a pas été enregistré. + + + + Verifying... + Vérification... + + + + Verification failed + Échec de la vérification + + + + Verification failed. Check that you have entered your token correctly, and that your internet connection is working. + Échec de la vérification. Vérifiez si vous avez correctement entrez votre token, et que votre connection internet fonctionne. + + + + GMainWindow + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous data is collected</a> to help improve yuzu. <br/><br/>Would you like to share your usage data with us? + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Des données anonymes sont collectées</a> pour aider à améliorer yuzu. <br/><br/>Voulez-vous partager vos données d'utilisations avec nous ? + + + + Telemetry + Télémétrie + + + + Text Check Failed + Échec de la vérification du texte + + + + Loading Web Applet... + Chargement du Web Applet... + + + + Exit Web Applet + Quitter le Web Applet + + + + Exit + Quitter + + + + To exit the web application, use the game provided controls to select exit, select the 'Exit Web Applet' option in the menu bar, or press the 'Enter' key. + Pour quitter l'application web, utilisez les contrôles réserver par le jeu pour sélectionner exit, sélectionner l'option 'Quitter le Web Applet' dans la barre menu, ou appuyez sur la touche 'Entrer'. + + + + Web Applet + Web Applet + + + + This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested. + Cette version de yuzu à été construit sans le support de QtWebEngine, ce qui veut dire que yuzu ne peut pas correctement afficher le manuel du jeu ou la page web demandée. + + + + The amount of shaders currently being built + + + + + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. + Valeur actuelle de la vitesse de l'émulation. Des valeurs plus hautes ou plus basses que 100% indique que l'émulation fonctionne plus vite ou plus lentement qu'une véritable Switch. + + + + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. + Combien d'image par seconde le jeu est en train d'afficher. Ceci vas varier de jeu en jeu et de scènes en scènes. + + + + 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. + Temps pris pour émuler une image par seconde de la switch, sans compter le limiteur d'image par seconde ou la synchronisation verticale. Pour une émulation à pleine vitesse, ceci devrait être au maximum à 16.67 ms. + + + + DOCK + + + + + ASYNC + + + + + MULTICORE + + + + + VULKAN + + + + + OPENGL + + + + + Clear Recent Files + Effacer les Fichiers Récents + + + + Warning Outdated Game Format + Avertissement : Le Format de jeu est dépassé + + + + 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><br>For an explanation of the various Switch formats yuzu supports, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our wiki</a>. This message will not be shown again. + Vous utilisez un format de ROM déconstruite pour ce jeu, qui est donc un format dépassé qui à été remplacer par d'autre. Par exemple les formats NCA, NAX, XCI, ou NSP. Les destinations de ROM déconstruites manque des icônes, des métadonnée et du support de mise à jour.<br><br>Pour une explication des divers formats Switch que yuzu supporte, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>Regardez dans le wiki</a>. Ce message ne sera pas montré une autre fois. + + + + + Error while loading ROM! + Erreur lors du chargement de la ROM ! + + + + The ROM format is not supported. + Le format de la ROM n'est pas supporté. + + + + An error occurred initializing the video core. + Une erreur s'est produite lors de l'initialisation du noyau dédié à la vidéo. + + + + yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.Ensure that you have the latest graphics drivers for your GPU. + yuzu a rencontré une erreur fatale, veuillez consulter les logs pour plus de détails. Pour plus d'informations sur l'accès aux logs, veuillez consulter la page suivante : <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'> Comment télécharger le fichier des logs </a>.<br/><br/>Voulez-vous quitter la liste des jeux ? Une émulation continue peut entraîner des crashs, la corruption de données de sauvegarde ou d’autres bugs. + + + + Error while loading ROM! + + + + + An unknown error occurred. Please see the log for more details. + Une erreur inconnue est survenue. Veuillez consulter le journal des logs pour plus de détails. + + + + Start + Démarrer + + + + Save Data + Enregistrer les données + + + + Mod Data + Donnés du Mod + + + + Error Opening %1 Folder + Erreur dans l'ouverture du dossier %1. + + + + + Folder does not exist! + Le dossier n'existe pas ! + + + + Error Opening Transferable Shader Cache + Erreur lors de l'ouverture des Shader Cache Transferable + + + + + A shader cache for this title does not exist. + Un shader cache pour ce titre n'existe pas. + + + + Contents + + + + + Update + + + + + DLC + + + + + Remove Entry + + + + + Remove Installed Game %1? + + + + + + + + + Successfully Removed + + + + + Successfully removed the installed base game. + + + + + + + Error Removing %1 + + + + + The base game is not installed in the NAND and cannot be removed. + + + + + Successfully removed the installed update. + + + + + There is no update installed for this title. + + + + + There are no DLC installed for this title. + + + + + Successfully removed %1 installed DLC. + + + + + Delete Transferable Shader Cache? + + + + + Remove Custom Game Configuration? + + + + + Remove File + + + + + + Error Removing Transferable Shader Cache + + + + + Successfully removed the transferable shader cache. + + + + + Failed to remove the transferable shader cache. + + + + + + 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. + + + + + RomFS Extraction Failed! + L'extraction de la RomFS a échoué ! + + + + There was an error copying the RomFS files or the user cancelled the operation. + Une erreur s'est produite lors de la copie des fichiers RomFS ou l'utilisateur a annulé l'opération. + + + + Full + Plein + + + + Skeleton + Squelette + + + + Select RomFS Dump Mode + Sélectionnez le mode d'extraction de la 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. + Veuillez sélectionner la manière dont vous souhaitez que le fichier RomFS soit extrait.<br>Full copiera tous les fichiers dans le nouveau répertoire, tandis que<br>skeleton créera uniquement la structure de répertoires. + + + + Extracting RomFS... + Extraction de la RomFS ... + + + + + Cancel + Annuler + + + + RomFS Extraction Succeeded! + Extraction de la RomFS réussi ! + + + + The operation completed successfully. + L'opération s'est déroulée avec succès. + + + + Error Opening %1 + Erreur lors de l'ouverture %1 + + + + Select Directory + Sélectionner un répertoire + + + + Properties + Propriétés + + + + The game properties could not be loaded. + Les propriétés du jeu n'ont pas pu être chargées. + + + + Switch Executable (%1);;All Files (*.*) + %1 is an identifier for the Switch executable file extensions. + Exécutable Switch (%1);;Tous les fichiers (*.*) + + + + Load File + Charger un fichier + + + + Open Extracted ROM Directory + Ouvrir le dossier des ROM extraites + + + + Invalid Directory Selected + Destination sélectionnée invalide + + + + The directory you have selected does not contain a 'main' file. + Le répertoire que vous avez sélectionné ne contient pas de fichier "main". + + + + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) + + + + + Install Files + + + + + Installing file "%1"... + Installation du fichier "%1" ... + + + + Install Results + + + + + System Application + Application Système + + + + System Archive + Archive Système + + + + System Application Update + Mise à jour de l'application système + + + + Firmware Package (Type A) + Paquet micrologiciel (Type A) + + + + Firmware Package (Type B) + Paquet micrologiciel (Type B) + + + + Game + Jeu + + + + Game Update + Mise à jour de jeu + + + + Game DLC + DLC de jeu + + + + Delta Title + Titre Delta + + + + Select NCA Install Type... + Sélectionner le type d'installation du NCA... + + + + Please select the type of title you would like to install this NCA as: +(In most instances, the default 'Game' is fine.) + Veuillez sélectionner le type de titre auquel vous voulez installer ce NCA : +(Dans la plupart des cas, le titre par défaut : 'Jeu' est correct.) + + + + Failed to Install + Échec de l'installation + + + + The title type you selected for the NCA is invalid. + Le type de titre que vous avez sélectionné pour le NCA n'est pas valide. + + + + File not found + Fichier non trouvé + + + + File "%1" not found + Fichier "%1" non trouvé + + + + + Continue + Continuer + + + + Error Display + Erreur d'affichage + + + + Missing yuzu Account + Compte yuzu manquant + + + + In order to submit a game compatibility test case, you must link your yuzu account.<br><br/>To link your yuzu account, go to Emulation &gt; Configuration &gt; Web. + Pour soumettre un test de compatibilité pour un jeu, vous devez lier votre compte yuzu.<br><br/>Pour lier votre compte yuzu, aller à Emulation &gt; Configuration&gt; Web. + + + + Error opening URL + + + + + Unable to open the URL "%1". + + + + + Amiibo File (%1);; All Files (*.*) + Fichier Amiibo (%1);; Tous les fichiers (*.*) + + + + Load Amiibo + Charger un Amiibo + + + + Error opening Amiibo data file + Erreur lors de l'ouverture du fichier de données Amiibo + + + + Unable to open Amiibo file "%1" for reading. + Impossible d'ouvrir le fichier Amiibo "%1" à lire. + + + + Error reading Amiibo data file + Erreur lors de la lecture du fichier de données Amiibo + + + + Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes. + Impossible de lire entièrement les données Amiibo. On s'attend à lire %1 octets, mais il n'a pu lire que %2 octets + + + + Error loading Amiibo data + Erreur lors du chargement des données Amiibo + + + + Unable to load Amiibo data. + Impossible de charger les données Amiibo. + + + + Capture Screenshot + Capture d'écran + + + + PNG Image (*.png) + Image PNG (*.png) + + + + Speed: %1% / %2% + Vitesse : %1% / %2% + + + + Speed: %1% + Vitesse : %1% + + + + Game: %1 FPS + Jeu : %1 FPS + + + + Frame: %1 ms + Frame : %1 ms + + + + The game you are trying to load requires additional files from your Switch to be dumped before playing.<br/><br/>For more information on dumping these files, please see the following wiki page: <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + Le jeu que vous essayez de charger a besoin de fichiers additionnels que vous devez extraire depuis votre Switch avant de jouer.<br/><br/>Pour plus d'information sur l'extraction de ces fichiers, veuillez consulter la page du wiki suivante : <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Extraction des archives système et des Shared Fonts depuis la Switch</a>.<br/><br/>Voulez-vous quitter la liste des jeux ? Une émulation continue peut entraîner des crashs, la corruption de données de sauvegarde ou d’autres bugs. + + + + yuzu was unable to locate a Switch system archive. %1 + yuzu n'a pas été capable de localiser un système d'archive Switch. %1 + + + + yuzu was unable to locate a Switch system archive: %1. %2 + yuzu n'a pas été capable de localiser un système d'archive Switch. %1. %2 + + + + System Archive Not Found + Archive système introuvable + + + + System Archive Missing + Archive Système Manquante + + + + yuzu was unable to locate the Switch shared fonts. %1 + Yuzu n'a pas été capable de localiser les polices partagées de la Switch. %1 + + + + Shared Fonts Not Found + Les polices partagées non pas été trouvées + + + + Shared Font Missing + Polices Partagée Manquante + + + + Fatal Error + Erreur fatale + + + + yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + yuzu a rencontré une erreur fatale, veuillez consulter les logs pour plus de détails. Pour plus d'informations sur l'accès aux logs, veuillez consulter la page suivante : <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'> Comment télécharger le fichier des logs </a>.<br/><br/>Voulez-vous quitter la liste des jeux ? Une émulation continue peut entraîner des crashs, la corruption de données de sauvegarde ou d’autres bugs. + + + + Fatal Error encountered + Erreur Fatale rencontrée + + + + Confirm Key Rederivation + Confirmer la réinstallation de la clé + + + + You are about to force rederive all of your keys. +If you do not know what this means or what you are doing, +this is a potentially destructive action. +Please make sure this is what you want +and optionally make backups. + +This will delete your autogenerated key files and re-run the key derivation module. + Vous êtes sur le point de réinstaller toutes vos clés. +Si vous ne savez pas ce que cela veut dire et ce que vous faites, +cela peut être une action potentiellement destructrice. +S'il vous plaît assurez-vous que c'est bien ce que vous voulez +et éventuellement faites des sauvegardes. + +Cela supprimera vos fichiers de clé générés automatiquement et ré exécutera le module d'installation de clé. + + + + Missing fuses + + + + + - Missing BOOT0 + + + + + - Missing BCPKG2-1-Normal-Main + + + + + - Missing PRODINFO + + + + + Derivation Components Missing + + + + + Components are missing that may hinder key derivation from completing. <br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu quickstart guide</a> to get all your keys and games.<br><br><small>(%1)</small> + + + + + Deriving keys... +This may take up to a minute depending +on your system's performance. + Installation des clés... +Cela peut prendre jusqu'à une minute en fonction +des performances de votre système. + + + + Deriving Keys + Installation des clés + + + + Select RomFS Dump Target + Sélectionner la cible d'extraction du RomFS + + + + Please select which RomFS you would like to dump. + Veuillez sélectionner quel RomFS vous voulez extraire. + + + + + + yuzu + yuzu + + + + Are you sure you want to close yuzu? + Êtes vous sûr de vouloir fermer yuzu ? + + + + Are you sure you want to stop the emulation? Any unsaved progress will be lost. + Êtes-vous sûr d'arrêter l'émulation ? Tout progrès non enregistré sera perdu. + + + + The currently running application has requested yuzu to not exit. + +Would you like to bypass this and exit anyway? + L'application en cours a demandé à yuzu de ne pas quitter. + +Voulez-vous ignorer ceci and quitter quand même ? + + + + GRenderWindow + + + OpenGL not available! + + + + + yuzu has not been compiled with OpenGL support. + + + + + Vulkan not available! + + + + + yuzu has not been compiled with Vulkan support. + + + + + Error while initializing OpenGL 4.3! + + + + + Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver. + + + + + Error while initializing OpenGL! + + + + + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>Unsupported extensions:<br> + + + + + GameList + + + + Name + Nom + + + + + Compatibility + Compatibilité + + + + + Add-ons + Extensions + + + + + + + File type + Type de fichier + + + + + + + Size + Taille + + + + 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 Shader Cache + Ouvrir le cache de shaders transférable + + + + Remove + + + + + Remove Installed Update + + + + + Remove All Installed DLC + + + + + Remove Shader Cache + + + + + Remove Custom Configuration + + + + + Remove All Installed Contents + + + + + Dump RomFS + Extraire la RomFS + + + + Copy Title ID to Clipboard + Copier l'ID du titre dans le Presse-papiers + + + + Navigate to GameDB entry + Accédez à l'entrée GameDB + + + + Properties + Propriétés + + + + Scan Subfolders + Scanner les sous-dossiers + + + + Remove Game Directory + Supprimer le répertoire du jeu + + + + ▲ Move Up + + + + + ▼ Move Down + + + + + Open Directory Location + Ouvrir l'emplacement du répertoire + + + + GameListItemCompat + + + Perfect + Parfait + + + + Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without +any workarounds needed. + Le jeu fonctionne parfaitement, de manière fluide sans aucun bug audio ou graphique, toutes les fonctionnalités testées fonctionnent comme prévu sans +aucune modification nécessaire. + + + + Great + Bon + + + + Game functions with minor graphical or audio glitches and is playable from start to finish. May require some +workarounds. + Le jeu fonctionne correctement avec des bugs audio ou graphiques mineurs et est jouable du début à la fin. Nécessite peut être des +modifications + + + + Okay + Ok + + + + Game functions with major graphical or audio glitches, but game is playable from start to finish with +workarounds. + Le jeu fonctionne avec des bugs audio ou graphiques majeurs, mais il est jouable du début à la fin avec des modifications. + + + + Bad + Mauvais + + + + Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches +even with workarounds. + Le jeu fonctionne mais avec des bugs audio et graphiques majeurs. Impossible de progresser dans certaines zones à causes des bugs +même avec des modifications. + + + + Intro/Menu + Intro/Menu + + + + Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start +Screen. + Le jeu est complètement injouable à cause de bugs audio et graphiques. Impossible de progresser plus loin que l'écran 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é. + + + + GameListPlaceholder + + + Double-click to add a new folder to the game list + Double-cliquez pour ajouter un nouveau dossier à la liste de jeux + + + + GameListSearchField + + + Filter: + Filtre : + + + + Enter pattern to filter + Entrez un motif à filtrer + + + + InstallDialog + + + Please confirm these are the files you wish to install. + + + + + Installing an Update or DLC will overwrite the previously installed one. + + + + + Install + + + + + Install Files to NAND + + + + + LoadingScreen + + + Loading Shaders 387 / 1628 + Chargement des shaders 387 / 1628 + + + + Loading Shaders %v out of %m + Chargement des shaders %v sur %m + + + + Estimated Time 5m 4s + Temps Estimé 5m 4s + + + + Loading... + Chargement... + + + + Loading Shaders %1 / %2 + Chargement des shaders %1 / %2 + + + + Launching... + Lancement... + + + + Estimated Time %1 + Temps Estimé %1 + + + + MainWindow + + + yuzu + yuzu + + + + &File + &Fichier + + + + Recent Files + Fichiers récents + + + + &Emulation + &Émulation + + + + &View + &Vue + + + + Debugging + Débogage + + + + Tools + Outils + + + + &Help + &Aide + + + + Install Files to NAND... + + + + + Load File... + Charger un fichier ... + + + + Load Folder... + Charger un dossier ... + + + + E&xit + Q&uitter + + + + &Start + &Démarrer + + + + &Pause + &Pause + + + + &Stop + &Arrêter + + + + Reinitialize keys... + Réinitialiser les clés ... + + + + About yuzu + À propos de yuzu + + + + Single Window Mode + Mode fenêtre unique + + + + Configure... + Configurer ... + + + + Display Dock Widget Headers + Afficher les "Dock Widget Headers" + + + + Show Filter Bar + Afficher la barre de filtre + + + + Show Status Bar + Afficher la barre d'état + + + + Reset Window Size + + + + + Fullscreen + Plein écran + + + + Restart + Redémarrer + + + + Load Amiibo... + Charger un Amiibo + + + + Report Compatibility + Signaler la compatibilité + + + + Open Mods Page + + + + + Open Quickstart Guide + + + + + FAQ + + + + + Open yuzu Folder + Ouvrir le dossier de yuzu + + + + Capture Screenshot + Prendre une capture d'ecran + + + + Configure Current Game.. + + + + + MicroProfileDialog + + + MicroProfile + MicroProfil + + + + QObject + + + 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 + + + + + + Shift + Maj + + + + + + Ctrl + Ctrl + + + + + + Alt + Alt + + + + + + + [not set] + [non défini] + + + + + + Hat %1 %2 + Chapeau %1 %2 + + + + + + Axis %1%2 + Axe %1%2 + + + + + + Button %1 + Bouton %1 + + + + + + + [unknown] + [inconnu] + + + + + Click 0 + + + + + + Click 1 + + + + + + Click 2 + + + + + + Click 3 + + + + + + Click 4 + + + + + GC Axis %1%2 + + + + + GC Button %1 + + + + + + [unused] + [inutilisé] + + + + + Axis %1 + Axe %1 + + + + + GC Axis %1 + + + + + QtErrorDisplay + + + An error has occured. +Please try again or contact the developer of the software. + +Error Code: %1-%2 (0x%3) + Une erreur s'est produite. +Veuillez essayer à nouveau ou contactez le développeur du logiciel. + +Code d'Erreur: %1-%2 (0x%3) + + + + An error occured on %1 at %2. +Please try again or contact the developer of the software. + +Error Code: %3-%4 (0x%5) + Une erreur s'est produite le %1 à %2. +Veuillez essayer à nouveau ou contactez le développeur du logiciel. + +Code d'Erreur: %3-%4 (0x%5) + + + + An error has occured. +Error Code: %1-%2 (0x%3) + +%4 + +%5 + Une erreur s'est produite. +Code d'Erreur: %1-%2 (0x%3) + +%4 + +%5 + + + + QtProfileSelectionDialog + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Select a user: + Choisir un utilisateur : + + + + Users + Utilisateurs + + + + Profile Selector + Sélecteur de profil + + + + QtSoftwareKeyboardDialog + + + Enter text: + Entrer le texte : + + + + Software Keyboard + Clavier virtuel + + + + SequenceDialog + + + Enter a hotkey + Entrez une hotkey + + + + WaitTreeCallstack + + + Call stack + Pile d'exécution + + + + WaitTreeMutexInfo + + + waiting for mutex 0x%1 + en attente du "mutex" 0x%1 + + + + has waiters: %1 + En attente : %1 + + + + owner handle: 0x%1 + propriétaire de la manche : 0x%1 + + + + WaitTreeObjectList + + + waiting for all objects + en attente de tous les objets + + + + waiting for one of the following objects + en attente d'un des objets suivants + + + + WaitTreeSynchronizationObject + + + [%1]%2 %3 + + + + + waited by no thread + + + + + WaitTreeThread + + + + running + en cours + + + + ready + prêt + + + + + paused + en pause + + + + waiting for HLE return + en attente du retour de HLE + + + + sleeping + en veille + + + + waiting for IPC reply + en attente de réponse IPC + + + + waiting for objects + En attente d'objets + + + + waiting for mutex + En attente du "mutex" + + + + waiting for condition variable + en attente de la variable conditionnelle + + + + waiting for address arbiter + En attente de l'adresse arbitre + + + + dormant + dormant + + + + dead + Mort + + + + PC = 0x%1 LR = 0x%2 + PC = 0x%1 LR = 0x%2 + + + + ideal + idéal + + + + core %1 + cœur %1 + + + + Unknown processor %1 + Processeur inconnu %1 + + + + processor = %1 + Processeur = %1 + + + + ideal core = %1 + Cœur idéal = %1 + + + + affinity mask = %1 + masque d'affinité = %1 + + + + thread id = %1 + id du fil = %1 + + + + priority = %1(current) / %2(normal) + priorité = %1(courant) / %2(normal) + + + + last running ticks = %1 + dernier tick en cours = %1 + + + + not waiting for mutex + en attente du "mutex" + + + + WaitTreeThreadList + + + waited by thread + attendu par un fil + + + + WaitTreeWidget + + + Wait Tree + Arbre d'attente + + + \ No newline at end of file diff --git a/dist/languages/it.ts b/dist/languages/it.ts new file mode 100644 index 000000000..09c832fd3 --- /dev/null +++ b/dist/languages/it.ts @@ -0,0 +1,4724 @@ + + + AboutDialog + + + About yuzu + Riguardo yuzu + + + + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + + + + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + + + + <html><head/><body><p>%1 | %2-%3 (%4)</p></body></html> + <html><head/><body><p>%1 | %2-%3 (%4)</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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu è un emulatore open source sperimentale per Nintendo Switch sotto licenza GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">Questo software non dovrebbe essere utilizzato per giocare a giochi che non hai ottenuto legalmente</span></p></body></html> + + + + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Sito</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Codice Sorgente</span></a> I <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributori</span></a> I <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">Licenza</span></a></p></body></html> + + + + <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.</span></p></body></html> + <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; è un marchio registrato di Nintendo. yuzu non è affiliato a Nintendo in alcun modo.</span></p></body></html> + + + + CalibrationConfigurationDialog + + + Communicating with the server... + In comunicazione con il server... + + + + Cancel + Annulla + + + + Touch the top left corner <br>of your touchpad. + + + + + Now touch the bottom right corner <br>of your touchpad. + + + + + Configuration completed! + Configurazione completata! + + + + OK + OK + + + + CompatDB + + + Report Compatibility + Segnala Compatibilità + + + + + Report Game Compatibility + Segnala la Compatibilità di un Gioco + + + + <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu 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 yuzu 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 yuzu account</li></ul></body></html> + <html><head/><body><p><span style=" font-size:10pt;">Se dovessi scegliere di inviare un rapporto alla </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">Lista Compatibilità di yuzu</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;">Quale versione di yuzu stai utilizzando</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">L'account di yuzu connesso</li></ul></body></html> + + + + Perfect + Perfetto + + + + <html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html> + <html><head/><body><p>Il gioco funziona impeccabilmente senza errori di audio o grafici.</p></body></html> + + + + Great + Buono + + + + <html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html> + <html><head/><body><p>Il gioco funziona con qualche errore grafico minore o glitch nell'audio ed è giocabile dall'inizio alla fine. Potrebbe richiedere qualche metodo alternativo.</p></body></html> + + + + Okay + Ok + + + + <html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html> + <html><head/><body><p>Il gioco funziona e ha errori grafici gravi o glitch nell'audio ma è possibile giocare dall'inizio alla fine con dei metodi alternativi.</p></body></html> + + + + Bad + Scadente + + + + <html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html> + <html><head/><body><p>Il gioco presenta alcuni errori audio o video. E' impossibile proseguire in alcune aree a causa della presenza di glitch persino utilizzando metodi alternativi.</p></body></html> + + + + Intro/Menu + Intro/Menu + + + + <html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html> + <html><head/><body><p>Questo gioco è completamente ingiocabile a causa di gravi errori audio o video. E' impossibile proseguire oltre la schermata iniziale.</p></body></html> + + + + Won't Boot + Non si Avvia + + + + <html><head/><body><p>The game crashes when attempting to startup.</p></body></html> + <html><head/><body><p>Il gioco si blocca quando viene avviato.</p></body></html> + + + + <html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html> + <html><head/><body><p>Indipendentemente da velocità o prestazioni, come ti è sembrato giocare questo gioco dall'inizio alla fine su questa versione di yuzu?</p></body></html> + + + + Thank you for your submission! + Grazie per la tua segnalazione! + + + + Submitting + Inviando + + + + Communication error + Errore di comunicazione + + + + An error occured while sending the Testcase + E' stato riscontrato un errore mandando il Testcase + + + + Next + Prossimo + + + + ConfigureAudio + + + Audio + Audio + + + + Output Engine: + Motore dell'Output: + + + + This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency. + L'effetto post-processing regola la velocità dell'audio per fare in modo che sia uguale alla velocità del gioco e prevenire problemi di audio. Questo tuttavia aumenta la latenza dell'audio. + + + + Enable audio stretching + Abilita allungamento dell'audio + + + + Audio Device: + Dispositivo Audio: + + + + Use global volume + Usa volume globale + + + + Set volume: + Imposta volume: + + + + Volume: + Volume: + + + + 0 % + 0 % + + + + %1% + Volume percentage (e.g. 50%) + %1% + + + + ConfigureCpu + + + Form + + + + + General + Generale + + + + Accuracy: + + + + + Accurate + + + + + Unsafe + Non sicuro + + + + Enable Debug Mode + Abilita Modalità Debug + + + + We recommend setting accuracy to "Accurate". + + + + + Unsafe CPU Optimization Settings + Impostazioni Ottimizzazione CPU non sicura + + + + These settings reduce accuracy for speed. + + + + + Unfuse FMA (improve performance on CPUs without FMA) + + + + + + <div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div> + + + + + + Faster FRSQRTE and FRECPE + + + + + + <div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div> + + + + + + CPU settings are available only when game is not running. + + + + + Setting CPU to Debug Mode + + + + + CPU Debug Mode is only intended for developer use. Are you sure you want to enable this? + + + + + ConfigureCpuDebug + + + Form + + + + + Toggle CPU Optimizations + + + + + + <div> + <b>For debugging only.</b> + <br> + If you're not sure what these do, keep all of these enabled. + <br> + These settings only take effect when CPU Accuracy is "Debug Mode". + </div> + + + + + + Enable inline page tables + + + + + + <div style="white-space: nowrap">This optimization speeds up memory accesses by the guest program.</div> + <div style="white-space: nowrap">Enabling it inlines accesses to PageTable::pointers into emitted code.</div> + <div style="white-space: nowrap">Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.</div> + + + + + + Enable block linking + + + + + + <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> + + + + + + Enable return stack buffer + + + + + + <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> + + + + + + Enable fast dispatcher + + + + + + <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> + + + + + + Enable context elimination + + + + + + <div>Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.</div> + + + + + + Enable constant propagation + + + + + + <div>Enables IR optimizations that involve constant propagation.</div> + + + + + + Enable miscellaneous optimizations + + + + + + <div>Enables miscellaneous IR optimizations.</div> + + + + + + Enable misalignment check reduction + + + + + + <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> + + + + + + CPU settings are available only when game is not running. + + + + + ConfigureDebug + + + Form + Form + + + + GDB + GDB + + + + Enable GDB Stub + Abilita GDB Stub + + + + Port: + Porta: + + + + Logging + Logging + + + + Global Log Filter + Filtro Log Globale + + + + Show Log Console (Windows Only) + Mostra Console Log (Solo per Windows) + + + + Open Log Location + Apri Cartella Log + + + + Homebrew + Homebrew + + + + Arguments String + Stringa degli Argomenti + + + + Graphics + Grafica + + + + When checked, the graphics API enters in a slower debugging mode + + + + + Enable Graphics Debugging + + + + + When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower + + + + + Disable Macro JIT + + + + + Dump + + + + + Enable Verbose Reporting Services + Abilita Servizi di Segnalazione Dettagliata + + + + This will be reset automatically when yuzu closes. + Verrà ripristinato automaticamente dopo la chiusura di yuzu. + + + + Advanced + Avanzate + + + + Kiosk (Quest) Mode + Modalità Kiosk (Quest) + + + + ConfigureDebugController + + + Configure Debug Controller + + + + + Clear + + + + + Defaults + + + + + ConfigureDialog + + + yuzu Configuration + Configurazione di yuzu + + + + + + + General + Generale + + + + + UI + Interfaccia + + + + Game List + Lista Giochi + + + + + + + System + Sistema + + + + + + Profiles + Profili + + + + + + Filesystem + Filesystem + + + + + + + Controls + Comandi + + + + + + Hotkeys + Hotkey + + + + + + + CPU + + + + + + + + + + Debug + Debug + + + + + + + Graphics + Grafica + + + + + Advanced + + + + + GraphicsAdvanced + + + + + + + + Audio + Audio + + + + + + Web + Web + + + + + + Services + Servizi + + + + ConfigureFilesystem + + + Form + Form + + + + Storage Directories + Cartelle di Archiviazione + + + + NAND + NAND + + + + + + + + + ... + ... + + + + SD Card + Scheda SD + + + + Gamecard + Cartuccia di gioco + + + + Path + Percorso + + + + Inserted + Inserita + + + + Current Game + Gioco In Uso + + + + Patch Manager + Gestione Patch + + + + Dump Decompressed NSOs + Estrai NSO Decompressi + + + + Dump ExeFS + Estrai ExeFS + + + + Mod Load Root + Cartella Caricamento Mod + + + + Dump Root + Cartella di Estrazione + + + + Caching + Caching + + + + Cache Directory + Cartella Cache + + + + Cache Game List Metadata + Crea Cache di Metadati Lista Giochi + + + + + + + Reset Metadata Cache + Elimina Cache dei Metadati + + + + Select Emulated NAND Directory... + Seleziona Cartella NAND Emulata + + + + Select Emulated SD Directory... + Seleziona Cartella Scheda SD Emulata + + + + Select Gamecard Path... + Seleziona Percorso Cartuccia di Gioco + + + + Select Dump Directory... + Seleziona Cartella di Estrazione + + + + Select Mod Load Directory... + Seleziona Cartella per il Caricamento delle Mod + + + + Select Cache Directory... + Seleziona Cartella della Cache + + + + The metadata cache is already empty. + La cache dei metadati è già vuota. + + + + The operation completed successfully. + L'operazione è stata completata con successo. + + + + The metadata cache couldn't be deleted. It might be in use or non-existent. + Impossibile eliminare cache dei metadati. Potrebbe essere in uso o inesistente. + + + + ConfigureGeneral + + + Form + Form + + + + General + Generale + + + + Limit Speed Percent + Percentuale Limite Velocità + + + + % + % + + + + Multicore CPU Emulation + + + + + Confirm exit while emulation is running + Richiedi conferma di uscire mentre l'emulazione è in corso + + + + Prompt for user on game boot + Richiedi utente all'avvio di un gioco + + + + Pause emulation when in background + Pausa emulazione quando in background + + + + Hide mouse on inactivity + + + + + ConfigureGraphics + + + Form + Form + + + + API Settings + Impostazioni API + + + + API: + API: + + + + Device: + Dispositivo: + + + + Graphics Settings + Impostazioni grafiche + + + + Use disk shader cache + Usa cache shader su disco + + + + Use asynchronous GPU emulation + Usa emulazione GPU asincrona + + + + Aspect Ratio: + + + + + Default (16:9) + Default (16:9) + + + + Force 4:3 + + + + + Force 21:9 + + + + + Stretch to Window + + + + + + Use global background color + Usa il colore di sfondo globale + + + + Set background color: + Imposta colore dello sfondo: + + + + Background Color: + Colore Sfondo: + + + + OpenGL Graphics Device + + + + + ConfigureGraphicsAdvanced + + + Form + + + + + Advanced Graphics Settings + + + + + Accuracy Level: + + + + + VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference. + + + + + Use VSync (OpenGL only) + + + + + Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental. + + + + + Use assembly shaders (experimental, Nvidia OpenGL only) + + + + + Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental. + + + + + Use asynchronous shader building (experimental) + + + + + Use Fast GPU Time + + + + + Anisotropic Filtering: + + + + + Default + + + + + 2x + 2x + + + + 4x + 4x + + + + 8x + 8x + + + + 16x + 16x + + + + ConfigureHotkeys + + + Hotkey Settings + Impostazioni Hotkey + + + + Double-click on a binding to change it. + Clicca due volte su un collegamento per cambiarlo. + + + + Clear All + + + + + Restore Defaults + + + + + Action + Azione + + + + Hotkey + Hotkey + + + + Context + Contesto + + + + + Conflicting Key Sequence + Conflitto nella Sequenza di Tasti + + + + The entered key sequence is already assigned to: %1 + + + + + Restore Default + + + + + Clear + + + + + The default key sequence is already assigned to: %1 + + + + + ConfigureInput + + + ConfigureInput + + + + + + Player 1 + Giocatore 1 + + + + + Player 2 + Giocatore 2 + + + + + Player 3 + Giocatore 3 + + + + + Player 4 + Giocatore 4 + + + + + Player 5 + Giocatore 5 + + + + + Player 6 + Giocatore 6 + + + + + Player 7 + Giocatore 7 + + + + + Player 8 + Giocatore 8 + + + + + Advanced + Avanzate + + + + Console Mode + + + + + Docked + + + + + Undocked + + + + + Vibration + Vibrazione + + + + % + % + + + + Motion + + + + + Configure + Configura + + + + Controllers + + + + + 1 + 1 + + + + 2 + 2 + + + + 3 + 3 + + + + 4 + 4 + + + + 5 + 5 + + + + 6 + 6 + + + + 7 + 7 + + + + 8 + 8 + + + + Connected + Connesso + + + + Defaults + Predefiniti + + + + Clear + + + + + ConfigureInputAdvanced + + + Configure Input + + + + + Joycon Colors + + + + + Player 1 + Giocatore 1 + + + + + + + + + + + L Body + + + + + + + + + + + + L Button + + + + + + + + + + + + R Body + + + + + + + + + + + + R Button + + + + + Player 2 + Giocatore 2 + + + + Player 3 + Giocatore 3 + + + + Player 4 + Giocatore 4 + + + + Player 5 + Giocatore 5 + + + + Player 6 + Giocatore 6 + + + + Player 7 + Giocatore 7 + + + + Player 8 + Giocatore 8 + + + + Other + Altro + + + + Keyboard + Tastiera + + + + + Advanced + Avanzate + + + + Touchscreen + Touchscreen + + + + Mouse + Mouse + + + + Motion / Touch + + + + + + Configure + Configura + + + + Debug Controller + Debug Controller + + + + ConfigureInputPlayer + + + Configure Input + Configura Input + + + + Connect Controller + + + + + + + Pro Controller + + + + + + Dual Joycons + + + + + + Left Joycon + + + + + + Right Joycon + + + + + + Handheld + + + + + Input Device + + + + + Any + + + + + Keyboard/Mouse + + + + + Profile + + + + + Save + Salva + + + + New + Nuovo + + + + Delete + Elimina + + + + Left Stick + Stick Sinistro + + + + + + + + + Up + Su + + + + + + + + + Left + Sinistra + + + + + + + + + Right + Destra + + + + + + + + + Down + Giù + + + + + + + Pressed + Premuto + + + + + + + Modifier + Modificatore + + + + + Range + + + + + + % + % + + + + + Deadzone: 0% + + + + + + Modifier Range: 0% + + + + + D-Pad + + + + + + L + L + + + + + ZL + ZL + + + + + Minus + Meno + + + + + Capture + Cattura + + + + + Plus + Più + + + + + Home + Home + + + + + R + R + + + + + ZR + ZR + + + + + SL + SL + + + + + SR + SR + + + + Face Buttons + Pulsanti Frontali + + + + + X + X + + + + + Y + Y + + + + + A + A + + + + + B + B + + + + Right Stick + Stick Destro + + + + + Deadzone: %1% + + + + + + Modifier Range: %1% + + + + + [waiting] + [in attesa] + + + + ConfigureMotionTouch + + + Configure Motion / Touch + + + + + Motion + + + + + Motion Provider: + + + + + Sensitivity: + Sensibilità: + + + + Touch + Touch + + + + Touch Provider: + + + + + Calibration: + Calibrazione: + + + + (100, 50) - (1800, 850) + (100, 50) - (1800, 850) + + + + + + Configure + Configura + + + + Use button mapping: + + + + + CemuhookUDP Config + + + + + You may use any Cemuhook compatible UDP input source to provide motion and touch input. + + + + + Server: + + + + + Port: + + + + + Pad: + + + + + Pad 1 + + + + + Pad 2 + + + + + Pad 3 + + + + + Pad 4 + + + + + Learn More + + + + + + Test + + + + + Mouse (Right Click) + + + + + + CemuhookUDP + + + + + Emulator Window + + + + + <a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Learn More</span></a> + + + + + 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. + + + + + Citra + + + + + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. + + + + + ConfigureMouseAdvanced + + + Configure Mouse + Configura Mouse + + + + Mouse Buttons + Pulsanti Mouse + + + + Forward: + Avanti: + + + + Back: + Indietro: + + + + Left: + Sinistro: + + + + Middle: + Centrale: + + + + Right: + Destro: + + + + + Clear + Cancella + + + + Defaults + + + + + [not set] + [non impostato] + + + + Restore Default + Ripristina Predefinito + + + + [press key] + [premi un pulsante] + + + + ConfigurePerGame + + + Dialog + + + + + Info + + + + + Name + + + + + Title ID + + + + + Filename + + + + + Format + + + + + Version + + + + + Size + + + + + Developer + + + + + Add-Ons + + + + + General + + + + + System + + + + + Graphics + + + + + Adv. Graphics + + + + + Audio + + + + + Properties + + + + + Use global configuration (%1) + + + + + ConfigurePerGameAddons + + + Form + + + + + Patch Name + + + + + Version + + + + + ConfigureProfileManager + + + Form + Form + + + + Profile Manager + Gestione Profili + + + + Current User + Utente in Uso + + + + Username + Nome Utente + + + + Set Image + Imposta Immagine + + + + Add + Aggiungi + + + + Rename + Rinomina + + + + Remove + Rimuovi + + + + Profile management is available only when game is not running. + La gestione dei profili è disponibile solamente quando nessun gioco è in esecuzione. + + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + &1 +%2 + + + + Enter Username + Inserisci Nome Utente + + + + Users + Utenti + + + + Enter a username for the new user: + Inserisci un nome utente per il nuovo utente: + + + + Enter a new username: + Inserisci un nuovo nome utente: + + + + Confirm Delete + Conferma Eliminazione + + + + You are about to delete user with name "%1". Are you sure? + Stai per cancellare l'utente chiamato "%1". Sei sicuro? + + + + Select User Image + Seleziona Immagine Utente + + + + JPEG Images (*.jpg *.jpeg) + Immagini JPEG (*.jpg *.jpeg) + + + + Error deleting image + Impossibile eliminare l'immagine + + + + Error occurred attempting to overwrite previous image at: %1. + Impossibile sovrascrivere l'immagine precedente in: %1. + + + + Error deleting file + Impossibile eliminare il file + + + + Unable to delete existing file: %1. + Impossibile eliminare il file già esistente: %1. + + + + Error creating user image directory + Errore nella creazione della cartella delle immagini dell'utente + + + + Unable to create directory %1 for storing user images. + Impossibile creare la cartella %1 per archiviare le immagini dell'utente. + + + + Error copying user image + Impossibile copiare l'immagine utente + + + + Unable to copy image from %1 to %2 + Impossibile copiare l'immagine da %1 a %2 + + + + ConfigureService + + + Form + Form + + + + BCAT + BCAT + + + + BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content. + Nintendo usa BCAT per inviare dati ai giochi per coinvolgere la comunità e sbloccare contenuti aggiuntivi. + + + + BCAT Backend + Backend BCAT + + + + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Learn more about BCAT, Boxcat, and Current Events</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Scopri di più riguardo BCAT, Boxcat, ed Eventi in Corso</span></a></p></body></html> + + + + The boxcat service is offline or you are not connected to the internet. + Il servizio boxcat è offline o potresti non essere connesso a internet. + + + + There was an error while processing the boxcat event data. Contact the yuzu developers. + E' stato riscontrato un errore nell'elaborazione dei dati degli eventi di boxcat. Contatta gli sviluppatori di yuzu. + + + + The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu. + La versione di yuzu che stai usando è troppo vecchia o troppo nuova per questo server. Prova ad aggiornare all'ultima release ufficiale di yuzu. + + + + + There are currently no events on boxcat. + Non ci sono eventi in corso su boxcat. + + + + + Current Boxcat Events + + + + + Yuzu is retrieving the latest boxcat status... + yuzu sta recuperando lo stato attuale di boxcat... + + + + ConfigureSystem + + + Form + Form + + + + System Settings + Impostazioni di Sistema + + + + Region: + + + + + Auto + + + + + Default + + + + + CET + + + + + CST6CDT + + + + + Cuba + + + + + EET + + + + + Egypt + + + + + Eire + + + + + EST + + + + + EST5EDT + + + + + GB + + + + + GB-Eire + + + + + GMT + + + + + GMT+0 + + + + + GMT-0 + + + + + GMT0 + + + + + Greenwich + + + + + Hongkong + + + + + HST + + + + + Iceland + + + + + Iran + + + + + Israel + + + + + Jamaica + + + + + + Japan + + + + + Kwajalein + + + + + Libya + + + + + MET + + + + + MST + + + + + MST7MDT + + + + + Navajo + + + + + NZ + + + + + NZ-CHAT + + + + + Poland + + + + + Portugal + + + + + PRC + + + + + PST8PDT + + + + + ROC + + + + + ROK + + + + + Singapore + + + + + Turkey + + + + + UCT + + + + + Universal + + + + + UTC + UTC + + + + W-SU + W-SU + + + + WET + WET + + + + Zulu + Zulu + + + + USA + USA + + + + Europe + Europa + + + + Australia + Australia + + + + China + Cina + + + + Korea + Corea + + + + Taiwan + Taiwan + + + + Time Zone: + + + + + Note: this can be overridden when region setting is auto-select + Nota: questo può essere ignorato quando le impostazioni della regione sono su auto-select + + + + Japanese (日本語) + Giapponese (日本語) + + + + English + Inglese (English) + + + + 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 (正體中文) + + + + Custom RTC + RTC Personalizzato + + + + Language + Lingua + + + + RNG Seed + Seed RNG + + + + Mono + Mono + + + + Stereo + Stereo + + + + Surround + Surround + + + + Console ID: + ID Console: + + + + Sound output mode + Modalità di output del sonoro + + + + d MMM yyyy h:mm:ss AP + d MMM yyyy h:mm:ss AP + + + + Regenerate + Rigenera + + + + System settings are available only when game is not running. + Le impostazioni di sistema sono disponibili solamente quando nessun gioco è in esecuzione. + + + + This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue? + Questo rimpiazzerà la tua Switch virtuale con una nuova. La tua Switch virtuale non sarà recuperabile. Questo potrebbe avere effetti indesiderati nei giochi. Questo potrebbe fallire, se usi un salvataggio non aggiornato. Desideri continuare? + + + + Warning + Attenzione + + + + Console ID: 0x%1 + ID Console: 0x%1 + + + + ConfigureTouchFromButton + + + Configure Touchscreen Mappings + + + + + Mapping: + + + + + New + Nuovo + + + + Delete + Elimina + + + + Rename + Rinomina + + + + 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 + Elimina Punto + + + + Button + Pulsante + + + + X + X axis + X + + + + Y + Y axis + Y + + + + New Profile + Nuovo Profilo + + + + Enter the name for the new profile. + Inserisci il nome per il nuovo profilo: + + + + Delete Profile + Elimina Profilo + + + + Delete profile %1? + Elimina profilo %1? + + + + Rename Profile + Rinomina Profilo + + + + New name: + Nuovo nome: + + + + [press key] + [premi pulsante] + + + + ConfigureTouchscreenAdvanced + + + Configure Touchscreen + Configura Touchscreen + + + + Warning: The settings in this page affect the inner workings of yuzu'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. + Attenzione: Le impostazioni in questa pagina influenzano il funzionamento interno del touchscreen emulato di yuzu. Cambiarle potrebbe risultare in effetti indesiderati, ad esempio il touchscreen potrebbe non funzionare parzialmente o totalmente. Utilizza questa pagina solo se sei consapevole di ciò che stai facendo. + + + + Touch Parameters + Parametri Touch + + + + Touch Diameter Y + Diametro Touch Y + + + + Finger + Dito + + + + Touch Diameter X + Diametro Touch X + + + + Rotational Angle + Angolo di Rotazione + + + + Restore Defaults + Ripristina Predefiniti + + + + ConfigureUi + + + Form + Form + + + + General + Generale + + + + Note: Changing language will apply your configuration. + Nota: Cambiare lingua applicherà i cambiamenti fatti alla configurazione. + + + + Interface language: + Lingua Interfaccia: + + + + Theme: + Tema: + + + + Game List + Lista Giochi + + + + Show Add-Ons Column + Mostra Colonna Add-On + + + + Icon Size: + Dimensione Icona: + + + + Row 1 Text: + Testo Riga 1: + + + + Row 2 Text: + Testo Riga 2: + + + + Screenshots + Screenshots + + + + Ask Where To Save Screenshots (Windows Only) + + + + + Screenshots Path: + + + + + ... + + + + + Select Screenshots Path... + + + + + <System> + <System> + + + + English + Inglese + + + + ConfigureWeb + + + Form + Form + + + + yuzu Web Service + Servizio Web di yuzu + + + + By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information. + Fornendo i tuoi nome utente e token, permetti a yuzu di raccogliere dati di utilizzo, che potrebbero includere informazioni di identificazione utente. + + + + + Verify + Verifica + + + + Sign up + Registrati + + + + Token: + Token: + + + + Username: + Nome utente: + + + + What is my token? + Qual è il mio token? + + + + Telemetry + Telemetria + + + + Share anonymous usage data with the yuzu team + Condividi dati sull'utilizzo anonimamente con il team di yuzu + + + + Learn more + Per saperne di più + + + + Telemetry ID: + ID Telemetria: + + + + Regenerate + Rigenera + + + + Discord Presence + Discord Presence + + + + Show Current Game in your Discord Status + Mostra il Gioco Attuale nel tuo Stato di Discord + + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Learn more</span></a> + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Per saperne di più</span></a> + + + + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Sign up</span></a> + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Registrati</span></a> + + + + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">What is my token?</span></a> + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">Qual è il mio token?</span></a> + + + + + Telemetry ID: 0x%1 + ID Telemetria: 0x%1 + + + + + Unspecified + Non specificato + + + + Token not verified + Token non verificato + + + + Token was not verified. The change to your token has not been saved. + Nome utente e token non verificati. I cambiamenti al tuo nome utente e/o token non sono stati salvati. + + + + Verifying... + Verifica in corso... + + + + Verification failed + Verifica fallita + + + + Verification failed. Check that you have entered your token correctly, and that your internet connection is working. + Verifica fallita. Controlla di aver inserito correttamente il tuo username e token, e che la tua connessione ad internet sia funzionante. + + + + GMainWindow + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous data is collected</a> to help improve yuzu. <br/><br/>Would you like to share your usage data with us? + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Vengono raccolti dati anonimi</a> per aiutarci a migliorare yuzu. <br/><br/>Desideri condividere i tuoi dati di utilizzo con noi? + + + + Telemetry + Telemetria + + + + Text Check Failed + Verificazione Testo Fallita + + + + Loading Web Applet... + Caricamento Web Applet... + + + + Exit Web Applet + Esci dal Web Applet + + + + Exit + Esci + + + + To exit the web application, use the game provided controls to select exit, select the 'Exit Web Applet' option in the menu bar, or press the 'Enter' key. + Per uscire dall'applicazione web, usa i pulsanti provvisti dal gioco per uscire, seleziona l'opzione 'Esci dal Web Applet' nel menu in alto, o premi il tasto 'Invio'. + + + + Web Applet + Web Applet + + + + This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested. + Questa versione di yuzu è stata compilata senza supporto a QtWebEngine, quindi yuzu non potrà aprire il manuale di gioco o delle pagine web. + + + + The amount of shaders currently being built + + + + + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. + Velocità corrente 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. + Quanti frame al secondo il gioco mostra attualmente. Questo varia da gioco a gioco e da situazione a 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 utilizzato per emulare un frame della Switch, non contando i limiti ai frame o il v-sync. +Per un'emulazione alla massima velocità, il valore dev'essere al massimo 16.67 ms. + + + + DOCK + + + + + ASYNC + + + + + MULTICORE + + + + + VULKAN + + + + + OPENGL + + + + + Clear Recent Files + Elimina File Recenti + + + + Warning Outdated Game Format + Avviso Formato di 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><br>For an explanation of the various Switch formats yuzu supports, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our wiki</a>. This message will not be shown again. + Stai usando una cartella con dentro una ROM decostruita come formato per avviare questo gioco, è un formato obsoleto ed è stato sostituito da altri come NCA, NAX, XCI o NSP. Le ROM decostruite non hanno icone, metadata e non supportano gli aggiornamenti. <br><br>Per una spiegazione sui vari formati di Switch che yuzu supporta, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>controlla la nostra wiki</a>. Questo messaggio non verrà più mostrato. + + + + + 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. + E' stato riscontrato un errore nell'inizializzazione del core video. + + + + yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.Ensure that you have the latest graphics drivers for your GPU. + yuzu ha riscontrato un errore nell'esecuzione del video core, visualizza il log per maggiori dettagli. Per maggiori informazioni su come accedere al log, visualizza la seguente pagina: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Come Caricare Il File Log</a>. Assicurati di avere gli ultimi driver della tua GPU. + + + + Error while loading ROM! + + + + + An unknown error occurred. Please see the log for more details. + E' stato riscontrato un errore sconosciuto. Visualizza il log per maggiori dettagli. + + + + Start + Avvia + + + + Save Data + Dati di Salvataggio + + + + Mod Data + Dati delle Mod + + + + Error Opening %1 Folder + Errore nell'Apertura della Cartella %1 + + + + + Folder does not exist! + La cartella non esiste! + + + + Error Opening Transferable Shader Cache + Errore nell'Apertura della Cache Shader Trasferibile + + + + + A shader cache for this title does not exist. + Una cache di shader per questo titolo non esiste. + + + + Contents + Contenuti + + + + Update + Aggiorna + + + + DLC + DLC + + + + Remove Entry + + + + + Remove Installed Game %1? + + + + + + + + + Successfully Removed + + + + + Successfully removed the installed base game. + + + + + + + Error Removing %1 + + + + + The base game is not installed in the NAND and cannot be removed. + + + + + Successfully removed the installed update. + + + + + There is no update installed for this title. + + + + + There are no DLC installed for this title. + + + + + Successfully removed %1 installed DLC. + + + + + Delete Transferable Shader Cache? + + + + + Remove Custom Game Configuration? + + + + + Remove File + + + + + + Error Removing Transferable Shader Cache + + + + + Successfully removed the transferable shader cache. + + + + + Failed to remove the transferable shader cache. + + + + + + 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. + + + + + RomFS Extraction Failed! + Estrazione RomFS Fallita! + + + + There was an error copying the RomFS files or the user cancelled the operation. + C'è stato un errore nella copia dei file del RomFS o l'operazione è stata annullata dall'utente. + + + + Full + Completa + + + + Skeleton + Scheletro + + + + Select RomFS Dump Mode + Seleziona Modalità Estrazione 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>Completo copierà tutti i file in una nuova cartella mentre<br>scheletro creerà solamente le cartelle e le sottocartelle. + + + + 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 + Errore nell'Apertura di %1 + + + + Select Directory + Seleziona Cartella + + + + Properties + Proprietà + + + + The game properties could not be loaded. + Le proprietà del gioco non sono potute essere caricate. + + + + 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 Cartella 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) + + + + + Install Files + + + + + Installing file "%1"... + Installazione del file "%1"... + + + + Install Results + + + + + System Application + Applicazione di Sistema + + + + System Archive + Archivio di Sistema + + + + System Application Update + Aggiornamento 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 Gioco + + + + 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 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 l'NCA non è valido. + + + + File not found + File non trovato + + + + File "%1" not found + File "%1" non trovato + + + + + Continue + Continua + + + + Error Display + Messaggio di Errore + + + + Missing yuzu Account + Account di yuzu non trovato + + + + In order to submit a game compatibility test case, you must link your yuzu account.<br><br/>To link your yuzu account, go to Emulation &gt; Configuration &gt; Web. + Per segnalare la compatibilità di un gioco, devi collegare il tuo account yuzu. <br><br/>Per collegare il tuo account yuzu, vai su Emulazione &gt; +Configurazione &gt; Web. + + + + Error opening URL + + + + + Unable to open the URL "%1". + + + + + Amiibo File (%1);; All Files (*.*) + File Amiibo (%1);; Tutti I File (*.*) + + + + Load Amiibo + Carica Amiibo + + + + Error opening Amiibo data file + Errore nell'apertura del file dati Amiibo + + + + Unable to open Amiibo file "%1" for reading. + Impossibile aprire e leggere il file Amiibo "%1". + + + + Error reading Amiibo data file + Errore nella lettura dei dati del file Amiibo + + + + Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes. + Impossibile leggere tutti i dati dell'Amiibo. E' stato possibile leggere solamente %2 byte di %1. + + + + Error loading Amiibo data + Errore nel caricamento dei dati dell'Amiibo. + + + + Unable to load Amiibo data. + Impossibile caricare i dati dell'Amiibo. + + + + Capture Screenshot + Cattura Screenshot + + + + PNG Image (*.png) + Immagine PNG (*.png) + + + + Speed: %1% / %2% + Velocità: %1% / %2% + + + + Speed: %1% + Velocità: %1% + + + + Game: %1 FPS + Gioco: %1 FPS + + + + Frame: %1 ms + Frame: %1 ms + + + + The game you are trying to load requires additional files from your Switch to be dumped before playing.<br/><br/>For more information on dumping these files, please see the following wiki page: <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + Il gioco che stai provando a caricare richiede ulteriori file che devono essere estratti dalla tua Switch prima di poter giocare. <br/><br/>Per maggiori informazioni sull'estrazione di questi file, visualizza la seguente pagina della wiki: <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Estrazione di Archivi di Sistema e Font Condivisi da una Console Switch</a>.<br/><br/>Vuoi uscire e tornare alla lista dei giochi? Continuare l'emulazione potrebbe risultare in crash, salvataggi corrotti o altri bug. + + + + yuzu was unable to locate a Switch system archive. %1 + yuzu non ha potuto individuare un archivio di sistema della Switch. %1 + + + + yuzu was unable to locate a Switch system archive: %1. %2 + yuzu non ha potuto individuare un archivio di sistema della Switch: %1. %2 + + + + System Archive Not Found + Archivio di Sistema Non Trovato + + + + System Archive Missing + Archivio di Sistema Mancante + + + + yuzu was unable to locate the Switch shared fonts. %1 + yuzu non ha potuto individuare i font condivisi della Switch. %1 + + + + Shared Fonts Not Found + Font Condivisi Non Trovati + + + + Shared Font Missing + Font Condivisi Mancanti + + + + Fatal Error + Errore Fatale + + + + yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + yuzu ha riscontrato un errore fatale, visualizza il log per maggiori dettagli. Per maggiori informazioni su come accedere al log, visualizza la seguente pagina: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Come Caricare Il File Log</a>.<br/><br/>Vuoi uscire e tornare alla lista dei giochi? Continuare l'emulazione potrebbe risultare in crash, salvataggi corrotti o altri bug. + + + + Fatal Error encountered + Errore Fatale riscontrato + + + + Confirm Key Rederivation + Conferma Riderivazione Chiave + + + + You are about to force rederive all of your keys. +If you do not know what this means or what you are doing, +this is a potentially destructive action. +Please make sure this is what you want +and optionally make backups. + +This will delete your autogenerated key files and re-run the key derivation module. + Stai per forzare la ri-derivazione di tutte le tue chiavi. +Se non sai cosa significa o cosa stai per fare, +questa azione potrebbe causare gravi problemi. +Assicurati di volerlo fare +e facoltativamente fai dei backup. + +Questo eliminerà i tuoi file di chiavi autogenerati e ri-avvierà il processo di derivazione delle chiavi. + + + + Missing fuses + + + + + - Missing BOOT0 + + + + + - Missing BCPKG2-1-Normal-Main + + + + + - Missing PRODINFO + + + + + Derivation Components Missing + + + + + Components are missing that may hinder key derivation from completing. <br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu quickstart guide</a> to get all your keys and games.<br><br><small>(%1)</small> + + + + + Deriving keys... +This may take up to a minute depending +on your system's performance. + Derivazione chiavi... +Questa operazione potrebbe durare fino a un minuto in +base alle prestazioni del tuo sistema. + + + + Deriving Keys + Derivazione Chiavi + + + + Select RomFS Dump Target + Seleziona Target dell'Estrazione del RomFS + + + + Please select which RomFS you would like to dump. + Seleziona quale RomFS vorresti estrarre. + + + + + + yuzu + yuzu + + + + Are you sure you want to close yuzu? + Sei sicuro di voler chiudere yuzu? + + + + Are you sure you want to stop the emulation? Any unsaved progress will be lost. + Sei sicuro di voler fermare l'emulazione? Tutti i progressi non salvati verranno perduti. + + + + The currently running application has requested yuzu to not exit. + +Would you like to bypass this and exit anyway? + L'applicazione in uso ha richiesto a yuzu di non uscire. + +Desideri uscire comunque? + + + + GRenderWindow + + + OpenGL not available! + + + + + yuzu has not been compiled with OpenGL support. + + + + + Vulkan not available! + + + + + yuzu has not been compiled with Vulkan support. + + + + + Error while initializing OpenGL 4.3! + + + + + Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver. + + + + + Error while initializing OpenGL! + + + + + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>Unsupported extensions:<br> + + + + + GameList + + + + Name + Nome + + + + + Compatibility + Compatibilità + + + + + Add-ons + Add-on + + + + + + + File type + Tipo di file + + + + + + + Size + Dimensione + + + + Open Save Data Location + Apri Cartella Dati di Salvataggio + + + + Open Mod Data Location + Apri Cartella Mod + + + + Open Transferable Shader Cache + Apri Cache Shader Trasferibile + + + + Remove + + + + + Remove Installed Update + + + + + Remove All Installed DLC + + + + + Remove Shader Cache + + + + + Remove Custom Configuration + + + + + Remove All Installed Contents + + + + + Dump RomFS + Estrai RomFS + + + + Copy Title ID to Clipboard + Copia il Title ID negli Appunti + + + + Navigate to GameDB entry + Vai alla pagina di GameDB + + + + Properties + Proprietà + + + + Scan Subfolders + Scansiona Sottocartelle + + + + Remove Game Directory + Rimuovi Cartella Giochi + + + + ▲ Move Up + + + + + ▼ Move Down + + + + + Open Directory Location + Apri Cartella + + + + GameListItemCompat + + + Perfect + Perfetto + + + + Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without +any workarounds needed. + Il gioco funziona perfettamente senza alcuni glitch audio o video, tutte le funzionalità testate funzionano come dovrebbero senza alcun metodo alternativo necessario. + + + + Great + Buono + + + + Game functions with minor graphical or audio glitches and is playable from start to finish. May require some +workarounds. + Il gioco presenta qualche errore grafico minore o glitch nell'audio ed è giocabile dall'inizio alla fine. Potrebbe richiedere dei metodi alternativi. + + + + Okay + Ok + + + + Game functions with major graphical or audio glitches, but game is playable from start to finish with +workarounds. + l gioco presenta alcuni errori gravi audio o video. E' impossibile proseguire in alcune aree a causa della presenza di glitch persino utilizzando metodi alternativi. + + + + Bad + Scadente + + + + Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches +even with workarounds. + Il gioco funziona, ma presenta alcuni errori gravi audio o video. È impossibile proseguire in alcune aree a causa della presenza di glitch +persino utilizzando metodi alternativi. + + + + Intro/Menu + Intro/Menu + + + + Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start +Screen. + Il gioco è completamente ingiocabile a causa di errori gravi audio o video. È 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. + + + + GameListPlaceholder + + + Double-click to add a new folder to the game list + Clicca due volte per aggiungere una nuova cartella alla lista dei giochi + + + + GameListSearchField + + + Filter: + Filtro: + + + + Enter pattern to filter + Inserisci pattern per filtrare + + + + InstallDialog + + + Please confirm these are the files you wish to install. + + + + + Installing an Update or DLC will overwrite the previously installed one. + + + + + Install + + + + + Install Files to NAND + + + + + LoadingScreen + + + Loading Shaders 387 / 1628 + 387 / 1628 Shader Caricate + + + + Loading Shaders %v out of %m + Caricamento di %v su %m Shader + + + + Estimated Time 5m 4s + Tempo Stimato 5m 4s + + + + Loading... + Caricamento... + + + + Loading Shaders %1 / %2 + %1 / %2 Shader Caricate + + + + Launching... + Avvio in corso... + + + + Estimated Time %1 + Tempo Stimato %1 + + + + MainWindow + + + yuzu + yuzu + + + + &File + &File + + + + Recent Files + File Recenti + + + + &Emulation + &Emulazione + + + + &View + &Visualizza + + + + Debugging + Debugging + + + + Tools + Strumenti + + + + &Help + &Aiuto + + + + Install Files to NAND... + + + + + Load File... + Carica File... + + + + Load Folder... + Carica Cartella... + + + + E&xit + &Esci + + + + &Start + &Avvia + + + + &Pause + &Pausa + + + + &Stop + &Stop + + + + Reinitialize keys... + Ri-inizializzando le chiavi... + + + + About yuzu + Riguardo yuzu + + + + Single Window Mode + Modalità Finestra Singola + + + + Configure... + Configura... + + + + Display Dock Widget Headers + Visualizza le Intestazioni del Dock dei Widget + + + + Show Filter Bar + Mostra Barra Filtri + + + + Show Status Bar + Mostra Barra Stato + + + + Reset Window Size + + + + + Fullscreen + Schermo Intero + + + + Restart + Riavvia + + + + Load Amiibo... + Carica Amiibo... + + + + Report Compatibility + Segnala Compatibilità + + + + Open Mods Page + + + + + Open Quickstart Guide + + + + + FAQ + + + + + Open yuzu Folder + Apri Cartella yuzu + + + + Capture Screenshot + Cattura Screenshot + + + + Configure Current Game.. + + + + + MicroProfileDialog + + + MicroProfile + MicroProfile + + + + QObject + + + Installed SD Titles + Titoli SD Installati + + + + Installed NAND Titles + Titoli NAND Installati + + + + System Titles + Titoli di Sistema + + + + Add New Game Directory + Aggiungi Nuova Cartella Giochi + + + + + + Shift + Shift + + + + + + Ctrl + Ctrl + + + + + + Alt + Alt + + + + + + + [not set] + [non impostato] + + + + + + Hat %1 %2 + Hat %1 %2 + + + + + + Axis %1%2 + Asse %1%2 + + + + + + Button %1 + Pulsante %1 + + + + + + + [unknown] + [sconosciuto] + + + + + Click 0 + Clicca 0 + + + + + Click 1 + Clicca 1 + + + + + Click 2 + Clicca 2 + + + + + Click 3 + Clicca 3 + + + + + Click 4 + Clicca 4 + + + + GC Axis %1%2 + Assi GC %1%2 + + + + GC Button %1 + Pulsanti GC %1 + + + + + [unused] + [inutilizzato] + + + + + Axis %1 + Asse %1 + + + + + GC Axis %1 + Assi GC %1 + + + + QtErrorDisplay + + + An error has occured. +Please try again or contact the developer of the software. + +Error Code: %1-%2 (0x%3) + E' stato riscontrato un errore. +Riprova o contatta gli sviluppatori del software. + +Codice Errore: %1-%2 (0x%3) + + + + An error occured on %1 at %2. +Please try again or contact the developer of the software. + +Error Code: %3-%4 (0x%5) + E' stato riscontrato un errore su %1 a %2. +Riprova o contatta gli sviluppatori del software. + +Codice Errore: %3-%4 (0x%5) + + + + An error has occured. +Error Code: %1-%2 (0x%3) + +%4 + +%5 + E' stato riscontrato un errore. +Codice Errore: %1-%2 (0x%3) + +%4 + +%5 + + + + QtProfileSelectionDialog + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Select a user: + Seleziona un utente: + + + + Users + Utenti + + + + Profile Selector + Selettore Profili + + + + QtSoftwareKeyboardDialog + + + Enter text: + Inserisci testo: + + + + Software Keyboard + Tastiera Software + + + + SequenceDialog + + + Enter a hotkey + Inserisci una hotkey + + + + WaitTreeCallstack + + + Call stack + Stack chiamata + + + + WaitTreeMutexInfo + + + waiting for mutex 0x%1 + attende il mutex 0x%1 + + + + has waiters: %1 + ha dei waiter: %1 + + + + owner handle: 0x%1 + proprietario handle: 0x%1 + + + + WaitTreeObjectList + + + waiting for all objects + attende tutti gli oggetti + + + + waiting for one of the following objects + attende uno dei seguenti oggetti + + + + WaitTreeSynchronizationObject + + + [%1]%2 %3 + [%1]%2 %3 + + + + waited by no thread + atteso da nessun thread + + + + WaitTreeThread + + + + running + in funzione + + + + ready + pronto + + + + + paused + in pausa + + + + waiting for HLE return + attende ritorno dell'HLE + + + + sleeping + dormendo + + + + waiting for IPC reply + attende una risposta dell'IPC + + + + waiting for objects + attende degli oggetti + + + + waiting for mutex + attende un mutex + + + + waiting for condition variable + aspettando la condition variable + + + + waiting for address arbiter + attende un indirizzo arbitro + + + + dormant + dormiente + + + + dead + morto + + + + PC = 0x%1 LR = 0x%2 + PC = 0x%1 LR = 0x%2 + + + + ideal + ideale + + + + core %1 + core %1 + + + + Unknown processor %1 + Processore sconosciuto %1 + + + + processor = %1 + processore = %1 + + + + ideal core = %1 + core ideale = %1 + + + + affinity mask = %1 + affinity mask = %1 + + + + thread id = %1 + id thread = %1 + + + + priority = %1(current) / %2(normal) + priorità = %1(corrente) / %2(normale) + + + + last running ticks = %1 + last running ticks = %1 + + + + not waiting for mutex + non attende un mutex + + + + WaitTreeThreadList + + + waited by thread + atteso dal thread + + + + WaitTreeWidget + + + Wait Tree + Wait Tree + + + \ No newline at end of file diff --git a/dist/languages/ja_JP.ts b/dist/languages/ja_JP.ts new file mode 100644 index 000000000..4c3870603 --- /dev/null +++ b/dist/languages/ja_JP.ts @@ -0,0 +1,4752 @@ + + + AboutDialog + + + About yuzu + yuzuについて + + + + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + + + + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + + + + <html><head/><body><p>%1 | %2-%3 (%4)</p></body></html> + <html><head/><body><p>%1 | %2-%3 (%4)</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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzuはGPLv2.0の下で提供されている任天堂Switchの実験的なオープンソースエミュレータです。</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">このソフトウェアは違法に入手したゲームを遊ぶために使用されるべきではありません。</span></p></body></html> + + + + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">ウェブサイト</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">ソースコード</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">貢献者</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/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. yuzu is not affiliated with Nintendo in any way.</span></p></body></html> + <html><head/><body><p><span style=" font-size:7pt;">&quot;任天堂 Switch&quot;は任天堂の登録商標です。 yuzuは任天堂と提携しているわけではありません。</span></p></body></html> + + + + CalibrationConfigurationDialog + + + Communicating with the server... + サーバと通信中... + + + + Cancel + キャンセル + + + + Touch the top left corner <br>of your touchpad. + タッチパッドの左上を<br>タッチして下さい。 + + + + Now touch the bottom right corner <br>of your touchpad. + 次にタッチパッドの右下を<br>タッチして下さい。 + + + + Configuration completed! + 設定が完了しました。 + + + + OK + OK + + + + 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://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu 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 yuzu 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 yuzu account</li></ul></body></html> + <html><head/><body><p><span style=" font-size:10pt;">テストケースを</span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu互換性リスト</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/OS)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">実行中のyuzuバージョン</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">接続中のyuzuアカウント</li></ul></body></html> + + + + Perfect + パーフェクト + + + + <html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html> + <html><head/><body><p>ゲームはオーディオやグラフィックの不具合なしに完全に動作します。</p></body></html> + + + + Great + グレート + + + + <html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html> + <html><head/><body><p>ゲームの動作にはグラフィック、またはオーディオの軽微な不具合がありますが、最初から最後までプレイ可能です。いくつかの回避策が必要な場合があります。</p></body></html> + + + + Okay + OK + + + + <html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html> + <html><head/><body><p>ゲームの動作にはグラフィック、またはオーディオの重大な不具合がありますが、回避策を使うことで最初から最後までプレイ可能です。</p></body></html> + + + + Bad + NG + + + + <html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html> + <html><head/><body><p>ゲームは動作しますが、グラフィック、またはオーディオに重大な不具合があります。回避策を使用しても不具合が原因で特定の場所から進めなくなります。</p></body></html> + + + + Intro/Menu + イントロ/メニューまで + + + + <html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html> + <html><head/><body><p>グラフィック、またはオーディオの重大な不具合のため、ゲームはプレイ不可能です。スタート画面から先に進めることは出来ません。</p></body></html> + + + + Won't Boot + 起動せず + + + + <html><head/><body><p>The game crashes when attempting to startup.</p></body></html> + <html><head/><body><p>ゲームは起動時にクラッシュします。</p></body></html> + + + + <html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html> + <html><head/><body><p>速度やパフォーマンスに関係なく、このゲームはこのバージョンのyuzuで最初から最後までどの程度うまく実行できていますか?</p></body></html> + + + + Thank you for your submission! + ご協力ありがとうございます! + + + + Submitting + 送信中 + + + + Communication error + 通信エラー + + + + An error occured while sending the Testcase + テストケースを送信中にエラーが発生しました + + + + Next + + + + + ConfigureAudio + + + Audio + オーディオ + + + + Output Engine: + 出力エンジン: + + + + This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency. + 後処理エフェクトは、エミュレーション速度と一致するようにオーディオ速度を調整し、オーディオの乱れを防ぐのに役立ちます。ただしオーディオの遅延が長くなります。 + + + + Enable audio stretching + オーディオストレッチの有効化 + + + + Audio Device: + オーディオデバイス: + + + + Use global volume + 共通の音量設定を使用 + + + + Set volume: + 音量設定: + + + + Volume: + 音量: + + + + 0 % + 0 % + + + + %1% + Volume percentage (e.g. 50%) + %1% + + + + ConfigureCpu + + + Form + フォーム + + + + General + 全般 + + + + Accuracy: + 正確度: + + + + Accurate + 正確 + + + + Unsafe + 不安定 + + + + Enable Debug Mode + デバッグモードを使用 + + + + We recommend setting accuracy to "Accurate". + 正確度は”正確”に設定することを推奨します。 + + + + Unsafe CPU Optimization Settings + CPU最適化設定(不安定) + + + + These settings reduce accuracy for speed. + これらの設定は高速化のために正確性を犠牲にします。 + + + + Unfuse FMA (improve performance on CPUs without FMA) + FMAの統合を解除(FMAを持たないCPUにおいて速度を改善する) + + + + + <div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div> + + + <div>このオプションは、ネイティブFMAのサポートを持たないCPUで融合積和命令の精度を下げることで速度を改善させます。</div> + + + + + Faster FRSQRTE and FRECPE + 高速FRSQRTEとFRECPE + + + + + <div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div> + + + <div>このオプションは、精度の低いネイティブ近似を使用することにより、一部の近似浮動小数点関数の速度を改善させます。</div> + + + + + CPU settings are available only when game is not running. + CPU設定は、ゲームが実行されていない場合にのみ使用できます。 + + + + Setting CPU to Debug Mode + CPUをデバッグモードに設定 + + + + CPU Debug Mode is only intended for developer use. Are you sure you want to enable this? + CPUデバッグモードは、開発者による使用のみを目的としています。有効にしてもよろしいですか? + + + + ConfigureCpuDebug + + + Form + フォーム + + + + Toggle CPU Optimizations + CPUの最適化を切り替え + + + + + <div> + <b>For debugging only.</b> + <br> + If you're not sure what these do, keep all of these enabled. + <br> + These settings only take effect when CPU Accuracy is "Debug Mode". + </div> + + + <div> + <b>デバッグ用</b> + <br> + これらの機能が何を意味するのか分からない場合は、すべて有効にしておいてください。 + <br> + これらの設定は、CPU精度が”デバッグモード”の場合にのみ有効になります。 + </div> + + + + + Enable inline page tables + インラインページテーブルの有効化 + + + + + <div style="white-space: nowrap">This optimization speeds up memory accesses by the guest program.</div> + <div style="white-space: nowrap">Enabling it inlines accesses to PageTable::pointers into emitted code.</div> + <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">これを有効にすると、PageTable :: pointersへのアクセスが発行されたコードにインライン化されます。</div> + <div style="white-space: nowrap">これを無効にすると、すべてのメモリアクセスが強制的にMemory :: Read / Memory :: Write関数を経由します。</div> + + + + + Enable block linking + ブロックリンクの有効化 + + + + + <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>この最適化では、宛先PCが静的な場合、発行された基本ブロックが他の基本ブロックに直接ジャンプできるようにすることで、ディスパッチャーのルックアップを回避します。</div> + + + + + Enable return stack buffer + リターンスタックバッファの有効化 + + + + + <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>この最適化は、BL命令の潜在的な戻りアドレスを追跡することにより、ディスパッチャーの検索を回避します。これは、実際のCPUのリターンスタックバッファで何が起こるかを概算します。</div> + + + + + Enable fast dispatcher + 高速ディスパッチャの有効化 + + + + + <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> + + + + + Enable context elimination + コンテキスト消去の有効化 + + + + + <div>Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.</div> + + + <div>CPUコンテキスト構造への不要なアクセスを削減するIR最適化を有効にします。</div> + + + + + Enable constant propagation + 定数伝播の有効化 + + + + + <div>Enables IR optimizations that involve constant propagation.</div> + + + <div>一定の伝播を伴うIR最適化を有効にします。</div> + + + + + Enable miscellaneous optimizations + その他の最適化を有効化 + + + + + <div>Enables miscellaneous IR optimizations.</div> + + + <div>その他のIR最適化を有効にします。</div> + + + + + Enable misalignment check reduction + ミスアライメントチェックの削減を有効化 + + + + + <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">有効にすると、アクセスがページの境界を越えたときにのみ、ミスアライメントがトリガーされます。</div> + <div style="white-space: nowrap">無効にすると、ミスアライメントされたすべてのアクセスでミスアライメントがトリガーされます。</div> + + + + + CPU settings are available only when game is not running. + CPU設定は、ゲームが実行されていない場合にのみ使用できます。 + + + + ConfigureDebug + + + Form + フォーム + + + + GDB + GDB + + + + Enable GDB Stub + GDBスタブの有効化 + + + + Port: + ポート: + + + + Logging + ログの記録 + + + + Global Log Filter + グローバルログフィルター + + + + Show Log Console (Windows Only) + ログコンソールの表示(Windowsのみ) + + + + Open Log Location + ログ出力フォルダを開く + + + + Homebrew + 自作 + + + + Arguments String + 引数文字列 + + + + Graphics + グラフィック + + + + When checked, the graphics API enters in a slower debugging mode + オンにすると、グラフィックAPIはより遅いデバッグモードになります。 + + + + Enable Graphics Debugging + グラフィックデバッグの有効化 + + + + When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower + オンにすると、マクロJust In Timeコンパイラが無効になります。有効にすると、ゲームの実行が遅くなります。 + + + + Disable Macro JIT + マクロJITの無効化 + + + + Dump + ダンプ + + + + Enable Verbose Reporting Services + 詳細なレポートサービスを有効にする + + + + This will be reset automatically when yuzu closes. + yuzuを終了したときに自動的にリセットされます。 + + + + Advanced + 拡張 + + + + Kiosk (Quest) Mode + キオスク (クエスト) モード + + + + ConfigureDebugController + + + Configure Debug Controller + デバッグコントローラ設定 + + + + Clear + 消去 + + + + Defaults + デフォルト + + + + ConfigureDialog + + + yuzu Configuration + yuzuの設定 + + + + + + + General + 全般 + + + + + UI + UI + + + + Game List + ゲームリスト + + + + + + + System + システム + + + + + + Profiles + プロファイル + + + + + + Filesystem + ファイルシステム + + + + + + + Controls + コントロール + + + + + + Hotkeys + ホットキー + + + + + + + CPU + CPU + + + + + + + + + Debug + デバッグ + + + + + + + Graphics + グラフィック + + + + + Advanced + 拡張 + + + + GraphicsAdvanced + 拡張グラフィック + + + + + + + Audio + オーディオ + + + + + + Web + Web + + + + + + Services + サービス + + + + ConfigureFilesystem + + + Form + フォーム + + + + Storage Directories + ストレージディレクトリ + + + + NAND + NAND + + + + + + + + + ... + ... + + + + SD Card + SDカード + + + + Gamecard + ゲームカード + + + + Path + パス + + + + Inserted + 挿入する + + + + Current Game + 現在のゲーム + + + + Patch Manager + パッチマネージャ + + + + Dump Decompressed NSOs + 解凍されたNSOをダンプ + + + + Dump ExeFS + ExeFSをダンプ + + + + Mod Load Root + Modロードディレクトリ + + + + Dump Root + ダンプディレクトリ + + + + Caching + キャッシュ + + + + Cache Directory + キャッシュディレクトリ + + + + Cache Game List Metadata + ゲームリストのメタデータをキャッシュする + + + + + + + Reset Metadata Cache + メタデータのキャッシュをクリアする + + + + Select Emulated NAND Directory... + NANDディレクトリを選択... + + + + Select Emulated SD Directory... + SDディレクトリを選択... + + + + Select Gamecard Path... + ゲームカードのパスを選択... + + + + Select Dump Directory... + ダンプディレクトリを選択... + + + + Select Mod Load Directory... + Modロードディレクトリを選択... + + + + Select Cache Directory... + キャッシュディレクトリを選択... + + + + 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. + メタデータのキャッシュを削除できませんでした。使用中、または存在していない可能性があります。 + + + + ConfigureGeneral + + + Form + フォーム + + + + General + 全般 + + + + Limit Speed Percent + 速度パーセントの制限 + + + + % + % + + + + Multicore CPU Emulation + マルチコアCPUエミュレーション + + + + Confirm exit while emulation is running + エミュレーション実行中の終了確認 + + + + Prompt for user on game boot + ゲーム起動時に確認を表示 + + + + Pause emulation when in background + バックグラウンド時にエミュレーションを停止 + + + + Hide mouse on inactivity + 未使用時にマウスを非表示 + + + + ConfigureGraphics + + + Form + フォーム + + + + API Settings + API設定 + + + + API: + API: + + + + Device: + デバイス: + + + + Graphics Settings + グラフィック設定 + + + + Use disk shader cache + ディスクシェーダキャッシュを使用する + + + + Use asynchronous GPU emulation + 非同期GPUエミュレーションを使用する + + + + Aspect Ratio: + アスペクト比: + + + + Default (16:9) + デフォルト (16:9) + + + + Force 4:3 + 4:3を強制 + + + + Force 21:9 + 21:9を強制 + + + + Stretch to Window + ウィンドウに合わせる + + + + + Use global background color + 共通の背景色を使用 + + + + Set background color: + 背景色の設定: + + + + Background Color: + 背景色: + + + + OpenGL Graphics Device + OpenGLグラフィックデバイス + + + + ConfigureGraphicsAdvanced + + + Form + フォーム + + + + Advanced Graphics Settings + 拡張グラフィック設定 + + + + Accuracy Level: + 正確性レベル: + + + + VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference. + VSyncは画面のティアリングを防ぎますが、一部のグラフィックカードはVSyncが有効になっているとパフォーマンスが低下します。パフォーマンスの違いに気づかない場合は、有効にしてください。 + + + + Use VSync (OpenGL only) + VSyncを使用(OpenGLのみ) + + + + Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental. + これを有効にすると、シェーダースタッターが減少します。サポートされているNvidiaデバイスでOpenGLアセンブリシェーダーを有効にします(NV_gpu_program5が必要です)。この機能は実験的なものです。 + + + + Use assembly shaders (experimental, Nvidia OpenGL only) + アセンブリシェーダーを使用します(実験的、Nvidia OpenGLのみ)。 + + + + Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental. + 非同期シェーダーコンパイルを有効にします。これにより、シェーダースタッターが減少する場合があります。この機能は実験的なものです。 + + + + Use asynchronous shader building (experimental) + 非同期シェーダー構築を使用します(実験的)。 + + + + Use Fast GPU Time + 高速CPU時間を使用 + + + + Anisotropic Filtering: + 異方性フィルタリング: + + + + Default + デフォルト + + + + 2x + 2x + + + + 4x + 4x + + + + 8x + 8x + + + + 16x + 16x + + + + ConfigureHotkeys + + + Hotkey Settings + ホットキー設定 + + + + Double-click on a binding to change it. + 変更するには項目をダブルクリックしてください。 + + + + Clear All + すべて消去 + + + + Restore Defaults + デフォルトに戻す + + + + Action + アクション + + + + Hotkey + ホットキー + + + + Context + コンテキスト + + + + + Conflicting Key Sequence + キー設定の衝突 + + + + The entered key sequence is already assigned to: %1 + 入力されたキーシーケンスは既に割り当てられています:%1 + + + + Restore Default + デフォルトに戻す + + + + Clear + 消去 + + + + The default key sequence is already assigned to: %1 + デフォルトのキーシーケンスは既に割り当てられています:%1 + + + + ConfigureInput + + + ConfigureInput + 入力設定 + + + + + Player 1 + プレイヤー1 + + + + + Player 2 + プレイヤー2 + + + + + Player 3 + プレイヤー3 + + + + + Player 4 + プレイヤー4 + + + + + Player 5 + プレイヤー5 + + + + + Player 6 + プレイヤー6 + + + + + Player 7 + プレイヤー7 + + + + + Player 8 + プレイヤー8 + + + + + Advanced + 拡張 + + + + Console Mode + コンソールモード + + + + Docked + 接続 + + + + Undocked + 接続解除 + + + + Vibration + バイブレーション + + + + % + % + + + + Motion + モーション + + + + Configure + 設定 + + + + Controllers + コントローラ + + + + 1 + 1 + + + + 2 + 2 + + + + 3 + 3 + + + + 4 + 4 + + + + 5 + 5 + + + + 6 + 6 + + + + 7 + 7 + + + + 8 + 8 + + + + Connected + 接続 + + + + Defaults + デフォルト + + + + Clear + 消去 + + + + ConfigureInputAdvanced + + + Configure Input + 入力設定 + + + + Joycon Colors + Joyconカラー + + + + Player 1 + プレイヤー1 + + + + + + + + + + + L Body + L本体 + + + + + + + + + + + L Button + Lボタン + + + + + + + + + + + R Body + R本体 + + + + + + + + + + + R Button + Rボタン + + + + Player 2 + プレイヤー2 + + + + Player 3 + プレイヤー3 + + + + Player 4 + プレイヤー4 + + + + Player 5 + プレイヤー5 + + + + Player 6 + プレイヤー6 + + + + Player 7 + プレイヤー7 + + + + Player 8 + プレイヤー8 + + + + Other + その他 + + + + Keyboard + キーボード + + + + + Advanced + 拡張 + + + + Touchscreen + タッチスクリーン + + + + Mouse + マウス + + + + Motion / Touch + モーション / タッチ + + + + + Configure + 設定 + + + + Debug Controller + コントローラのデバッグ + + + + ConfigureInputPlayer + + + Configure Input + 入力設定 + + + + Connect Controller + コントローラの接続 + + + + + + Pro Controller + プロコントローラ + + + + + Dual Joycons + デュアルジョイコン + + + + + Left Joycon + ジョイコン左 + + + + + Right Joycon + ジョイコン右 + + + + + Handheld + 携帯 + + + + Input Device + 入力デバイス + + + + Any + すべて + + + + Keyboard/Mouse + キーボード / マウス + + + + Profile + プロファイル + + + + Save + セーブ + + + + New + 新規 + + + + Delete + 削除 + + + + Left Stick + 左スティック + + + + + + + + + Up + + + + + + + + + + Left + + + + + + + + + + Right + + + + + + + + + + Down + + + + + + + + Pressed + 押下 + + + + + + + Modifier + 変更 + + + + + Range + 範囲 + + + + + % + % + + + + + Deadzone: 0% + デッドゾーン:0% + + + + + Modifier Range: 0% + 変更範囲:0% + + + + D-Pad + Dパッド + + + + + L + L + + + + + ZL + ZL + + + + + Minus + マイナス + + + + + Capture + キャプチャ + + + + + Plus + プラス + + + + + Home + ホーム + + + + + R + R + + + + + ZR + ZR + + + + + SL + SL + + + + + SR + SR + + + + Face Buttons + ボタン + + + + + X + X + + + + + Y + Y + + + + + A + A + + + + + B + B + + + + Right Stick + 右スティック + + + + + Deadzone: %1% + デッドゾーン:%1% + + + + + Modifier Range: %1% + 変更範囲:%1% + + + + [waiting] + [待機中] + + + + ConfigureMotionTouch + + + Configure Motion / Touch + モーション / タッチ設定 + + + + Motion + モーション + + + + Motion Provider: + モーションプロバイダ: + + + + Sensitivity: + 感度: + + + + Touch + タッチ + + + + Touch Provider: + タッチプロバイダ: + + + + Calibration: + キャリブレーション: + + + + (100, 50) - (1800, 850) + (100, 50) - (1800, 850) + + + + + + Configure + 設定 + + + + Use button mapping: + ボタンマッピングの使用: + + + + CemuhookUDP Config + CemuhookUDP設定 + + + + You may use any Cemuhook compatible UDP input source to provide motion and touch input. + モーションとタッチ入力を提供するために、Cemuhook互換のUDP入力ソースを使用します。 + + + + Server: + サーバー: + + + + Port: + ポート: + + + + Pad: + パッド: + + + + Pad 1 + パッド1 + + + + Pad 2 + パッド2 + + + + Pad 3 + パッド3 + + + + Pad 4 + パッド4 + + + + Learn More + 詳細情報 + + + + + Test + テスト + + + + Mouse (Right Click) + マウス(右クリック) + + + + + CemuhookUDP + CemuhookUDP + + + + Emulator Window + エミュレータウィンドウ + + + + <a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Learn More</span></a> + <a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">詳細情報</span></a> + + + + 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>サーバーが正しくセットアップされ、アドレスとポートが正しいことを確認してください。 + + + + Citra + Citra + + + + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. + UDPテスト、またはキャリブレーション設定が実行中です。<br>完了するまでお待ちください。 + + + + ConfigureMouseAdvanced + + + Configure Mouse + マウス設定 + + + + Mouse Buttons + マウスボタン + + + + Forward: + 進む: + + + + Back: + 戻る: + + + + Left: + 左: + + + + Middle: + 中央: + + + + Right: + 右: + + + + + Clear + 消去 + + + + Defaults + デフォルト + + + + [not set] + [未設定] + + + + Restore Default + デフォルトに戻す + + + + [press key] + [キーを押す] + + + + ConfigurePerGame + + + Dialog + ダイアログ + + + + Info + 情報 + + + + Name + 名称 + + + + Title ID + タイトルID + + + + Filename + ファイル名 + + + + Format + フォーマット + + + + Version + バージョン + + + + Size + サイズ + + + + Developer + 開発者 + + + + Add-Ons + アドオン + + + + General + 全般 + + + + System + システム + + + + Graphics + グラフィック + + + + Adv. Graphics + 拡張グラフィック + + + + Audio + オーディオ + + + + Properties + プロパティ + + + + Use global configuration (%1) + 共通設定を使用(%1) + + + + ConfigurePerGameAddons + + + Form + フォーム + + + + Patch Name + パッチ名称 + + + + Version + バージョン + + + + ConfigureProfileManager + + + Form + フォーム + + + + Profile Manager + プロファイルマネージャ + + + + Current User + 現在のユーザー + + + + Username + ユーザー名 + + + + Set Image + 画像を設定 + + + + Add + 追加 + + + + Rename + リネーム + + + + Remove + 削除 + + + + Profile management is available only when game is not running. + プロファイル管理はゲームが実行されていない場合のみ可能です。 + + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Enter Username + ユーザー名を入力 + + + + Users + ユーザー + + + + Enter a username for the new user: + 新しいユーザーのユーザー名を入力: + + + + Enter a new username: + 新しいユーザー名を入力: + + + + Confirm Delete + 削除確認 + + + + You are about to delete user with name "%1". Are you sure? + ユーザー名”%1”を削除しようとしています。間違いありませんか? + + + + Select User Image + ユーザー画像を選択 + + + + JPEG Images (*.jpg *.jpeg) + JPEG画像 (*.jpg *.jpeg) + + + + Error deleting image + 画像削除エラー + + + + Error occurred attempting to overwrite previous image at: %1. + 既存画像の上書き時にエラーが発生しました: %1 + + + + Error deleting file + ファイル削除エラー + + + + Unable to delete existing file: %1. + ファイルを削除できませんでした: %1 + + + + Error creating user image directory + ユーザー画像ディレクトリ作成失敗 + + + + Unable to create directory %1 for storing user images. + ユーザー画像保存ディレクトリ”%1”を作成できませんでした。 + + + + Error copying user image + ユーザー画像コピーエラー + + + + Unable to copy image from %1 to %2 + 画像を”%1”から”%2”へコピー出来ませんでした。 + + + + ConfigureService + + + Form + フォーム + + + + BCAT + BCAT + + + + BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content. + BCATは任天堂のゲームへのデータ送信方法であり、コミュニティに参加して追加コンテンツのロックを解除します。 + + + + BCAT Backend + BCATバックエンド + + + + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Learn more about BCAT, Boxcat, and Current Events</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">BCAT, Boxcat, 現在のイベントについての詳細情報</span></a></p></body></html> + + + + The boxcat service is offline or you are not connected to the internet. + Boxcatサービスはオフラインまたはインターネットに接続されていません。 + + + + There was an error while processing the boxcat event data. Contact the yuzu developers. + Boxcatイベントデータを処理中にエラー発生しました。yuzu開発者に連絡してください。 + + + + The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu. + 使用中のyuzuのバージョンは、サーバと比較して新しすぎまたは古すぎます。最新の公式リリースに更新してください。 + + + + + There are currently no events on boxcat. + Boxcatには現在イベントがありません。 + + + + + Current Boxcat Events + 現在のBoxcatイベント + + + + Yuzu is retrieving the latest boxcat status... + yuzuが最新のBoxcat状況を取得中です... + + + + ConfigureSystem + + + Form + フォーム + + + + System Settings + システム設定 + + + + Region: + リージョン: + + + + Auto + 自動 + + + + Default + デフォルト + + + + 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 + ジャマイカ + + + + + Japan + 日本 + + + + 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 + ズールー + + + + USA + USA + + + + Europe + ヨーロッパ + + + + Australia + オーストラリア + + + + China + 中国 + + + + Korea + 韓国 + + + + Taiwan + 台湾 + + + + Time Zone: + タイムゾーン: + + + + Note: this can be overridden when region setting is auto-select + 注意:地域設定が自動選択の場合、設定が上書きされる可能性があります。 + + + + Japanese (日本語) + 日本(日本語) + + + + English + 英語 + + + + French (français) + フランス(フランス語) + + + + German (Deutsch) + ドイツ(ドイツ語) + + + + Italian (italiano) + イタリア(イタリア語) + + + + Spanish (español) + スペイン(スペイン語) + + + + Chinese + 中国 + + + + Korean (한국어) + 韓国(韓国語) + + + + Dutch (Nederlands) + オランダ(オランダ語) + + + + Portuguese (português) + ポルトガル(ポルトガル語) + + + + Russian (Русский) + ロシア(ロシア語) + + + + Taiwanese + 台湾 + + + + British English + イギリス + + + + Canadian French + カナダ・フランス語 + + + + Latin American Spanish + ラテンアメリカ・スペイン語 + + + + Simplified Chinese + 簡体字中国語 + + + + Traditional Chinese (正體中文) + 繁体字中国語(正體中文) + + + + Custom RTC + カスタムRTC + + + + Language + 言語 + + + + RNG Seed + RNG速度 + + + + Mono + モノラル + + + + Stereo + ステレオ + + + + Surround + サラウンド + + + + Console ID: + コンソールID: + + + + Sound output mode + サウンド出力モード + + + + d MMM yyyy h:mm:ss AP + d MMM yyyy h:mm:ss AP + + + + Regenerate + 再作成 + + + + System settings are available only when game is not running. + システム設定はゲームが実行されていない場合のみ可能です。 + + + + This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue? + 現在の仮想スイッチが新しいものに置換されます。現在の仮想スイッチは復旧できません。ゲームに予期せぬ影響を与える可能性があります。古い設定などを使うと失敗するかもしれません。続行しますか? + + + + Warning + 警告 + + + + Console ID: 0x%1 + コンソールID: 0x%1 + + + + ConfigureTouchFromButton + + + Configure Touchscreen Mappings + タッチスクリーンマッピング設定 + + + + Mapping: + マッピング: + + + + New + 新規 + + + + Delete + 削除 + + + + 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 + ポイント削除 + + + + Button + ボタン + + + + X + X axis + X + + + + Y + Y axis + Y + + + + New Profile + 新規プロファイル + + + + Enter the name for the new profile. + 新規プロファイルの名称を入力 + + + + Delete Profile + プロファイル削除 + + + + Delete profile %1? + プロファイル%1を削除しますか? + + + + Rename Profile + プロファイルリネーム + + + + New name: + 新規名称: + + + + [press key] + [キーを押す] + + + + ConfigureTouchscreenAdvanced + + + Configure Touchscreen + タッチスクリーン設定 + + + + Warning: The settings in this page affect the inner workings of yuzu'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. + 警告:このページの設定は、yuzuのタッチスクリーンエミュレートの内部動作に影響します。これらを変更すると、タッチスクリーンが部分的に機能しなくなったりするなど、望ましくない動作が生じる可能性があります。それらを理解した上で、このページを使用してください。 + + + + Touch Parameters + タッチパラメータ + + + + Touch Diameter Y + タッチ直径Y + + + + Finger + + + + + Touch Diameter X + タッチ直径X + + + + Rotational Angle + 回転角 + + + + Restore Defaults + デフォルトに戻す + + + + ConfigureUi + + + Form + フォーム + + + + General + 全般 + + + + Note: Changing language will apply your configuration. + 注意:言語を変更した時点で、設定が適用されます。 + + + + Interface language: + UI言語: + + + + Theme: + テーマ: + + + + Game List + ゲームリスト + + + + Show Add-Ons Column + アドオン列を表示 + + + + Icon Size: + アイコンサイズ: + + + + Row 1 Text: + 1行目: + + + + Row 2 Text: + 2行目: + + + + Screenshots + スクリーンショット + + + + Ask Where To Save Screenshots (Windows Only) + スクリーンショットの保存場所を確認する(Windowsのみ) + + + + Screenshots Path: + スクリーンショットパス: + + + + ... + ... + + + + Select Screenshots Path... + スクリーンショットパスを選択... + + + + <System> + <System> + + + + English + 英語 + + + + ConfigureWeb + + + Form + フォーム + + + + yuzu Web Service + yuzu Webサービス + + + + By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information. + ユーザー名とトークンを入力すると、yuzuがユーザー識別情報を含む可能性がある追加の統計情報データを収集することを許可することに同意したと見なされます。 + + + + + Verify + 検証 + + + + Sign up + サインアップ + + + + Token: + トークン: + + + + Username: + ユーザー名: + + + + What is my token? + 私のトークンはなんですか? + + + + Telemetry + テレメトリ + + + + Share anonymous usage data with the yuzu team + 匿名統計情報データをyuzuチームと共有 + + + + Learn more + 詳細情報 + + + + Telemetry ID: + テレメトリID: + + + + Regenerate + 再作成 + + + + Discord Presence + Discordプレゼンス + + + + Show Current Game in your Discord Status + Discordステータスに実行中のゲームを表示 + + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Learn more</span></a> + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">詳細情報</span></a> + + + + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Sign up</span></a> + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">サインアップ</span></a> + + + + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">What is my token?</span></a> + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">私のトークンはなんですか?</span></a> + + + + + Telemetry ID: 0x%1 + テレメトリID:0x%1 + + + + + Unspecified + 未設定 + + + + Token not verified + トークンは検証されていません + + + + Token was not verified. The change to your token has not been saved. + トークンは検証されていません。トークンの変更はまだ保存されていません。 + + + + Verifying... + 検証中... + + + + Verification failed + 検証失敗 + + + + Verification failed. Check that you have entered your token correctly, and that your internet connection is working. + 検証に失敗しました。トークンを正しく入力したこと、およびインターネット接続が機能していることを確認してください。 + + + + GMainWindow + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous data is collected</a> to help improve yuzu. <br/><br/>Would you like to share your usage data with us? + yuzuを改善するための<a href='https://yuzu-emu.org/help/feature/telemetry/'>匿名データが収集されました</a>。<br/><br/>統計情報データを共有しますか? + + + + Telemetry + テレメトリ + + + + Text Check Failed + テキストチェック失敗 + + + + Loading Web Applet... + Webアプレットをロード中... + + + + Exit Web Applet + Webアプレットを終了する + + + + Exit + 終了 + + + + To exit the web application, use the game provided controls to select exit, select the 'Exit Web Applet' option in the menu bar, or press the 'Enter' key. + Webアプリケーションを終了するには、ゲームが提供する"終了"を選択するか、メニューの"Webアプレットを終了する"を選択するか、"Enter"キーを押してください。 + + + + Web Applet + Webアプレット + + + + This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested. + 使用中のyuzuはQtWebEngineをサポートしていないため、ゲームの説明書やWebページを正しく表示できません。 + + + + The amount of shaders currently being built + ビルド中のシェーダー数 + + + + 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. + ゲームが現在表示している1秒あたりのフレーム数。これはゲームごと、シーンごとに異なります。 + + + + 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フレームをエミュレートするのにかかる時間で、フレームリミットやV-Syncは含まれません。フルスピードエミュレーションの場合、最大で16.67ミリ秒になります。 + + + + DOCK + DOCK + + + + ASYNC + ASYNC + + + + MULTICORE + MULTICORE + + + + VULKAN + VULKAN + + + + OPENGL + OPENGL + + + + Clear Recent Files + 最近使用したファイルを消去 + + + + 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><br>For an explanation of the various Switch formats yuzu supports, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our wiki</a>. This message will not be shown again. + このゲームでは、分解されたROMディレクトリフォーマットを使用しています。これは、NCA、NAX、XCI、またはNSPなどに取って代わられた古いフォーマットです。分解されたROMディレクトリには、アイコン、メタデータ、およびアップデートサポートがありません。<br><br>yuzuがサポートするSwitchフォーマットの説明については、<a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>wikiをチェックしてください</a>。このメッセージは二度と表示されません。 + + + + + Error while loading ROM! + ROMロード中にエラーが発生しました! + + + + The ROM format is not supported. + このROMフォーマットはサポートされていません。 + + + + An error occurred initializing the video core. + ビデオコア初期化中にエラーが発生しました。 + + + + yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.Ensure that you have the latest graphics drivers for your GPU. + ビデオコアの実行中にyuzuでエラーが発生しました。詳細については、ログを参照してください。ログへのアクセスの詳細については、次のページを参照してください:<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>ログファイルをアップロードする方法</a>。最新のグラフィックドライバを使用していることを確認して下さい。 + + + + Error while loading ROM! + ROMロードエラー! + + + + An unknown error occurred. Please see the log for more details. + 不明なエラーが発生しました。詳細はログを確認して下さい。 + + + + Start + 開始 + + + + Save Data + データのセーブ + + + + Mod Data + Modデータ + + + + Error Opening %1 Folder + ”%1”フォルダを開けませんでした + + + + + Folder does not exist! + フォルダが存在しません! + + + + Error Opening Transferable Shader Cache + シェーダキャッシュを開けませんでした + + + + + A shader cache for this title does not exist. + このタイトル用のシェーダキャッシュは存在しません。 + + + + Contents + コンテンツ + + + + Update + アップデート + + + + DLC + DLC + + + + Remove Entry + エントリ削除 + + + + Remove Installed Game %1? + インストールされているゲーム%1を削除しますか? + + + + + + + + Successfully Removed + 削除成功 + + + + Successfully removed the installed base game. + インストールされたゲームを正常に削除しました。 + + + + + + Error Removing %1 + %1削除エラー + + + + 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 DLC installed for this title. + このタイトルにはDLCがインストールされていません。 + + + + Successfully removed %1 installed DLC. + %1にインストールされたDLCを正常に削除しました。 + + + + Delete Transferable Shader Cache? + 転送可能なシェーダーキャッシュを削除しますか? + + + + Remove Custom Game Configuration? + カスタムゲーム設定を削除しますか? + + + + Remove File + ファイル削除 + + + + + Error Removing Transferable Shader Cache + 転送可能なシェーダーキャッシュの削除エラー + + + + Successfully removed the transferable shader cache. + 転送可能なシェーダーキャッシュが正常に削除されました。 + + + + Failed to remove the transferable shader cache. + 転送可能なシェーダーキャッシュを削除できませんでした。 + + + + + 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. + カスタムゲーム設定の削除に失敗しました。 + + + + 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>”スケルトン”はディレクトリ構造を作成するだけです。 + + + + 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) + インストール可能なスイッチファイル (*.nca *.nsp *.xci);;任天堂コンテンツアーカイブ (*.nca);;任天堂サブミッションパッケージ (*.nsp);;NXカートリッジイメージ (*.xci) + + + + Install Files + ファイルのインストール + + + + Installing file "%1"... + "%1"ファイルをインストールしています・・・ + + + + Install Results + インストール結果 + + + + System Application + システムアプリケーション + + + + System Archive + システムアーカイブ + + + + System Application Update + システムアプリケーションアップデート + + + + Firmware Package (Type A) + ファームウェアパッケージ(Type A) + + + + Firmware Package (Type B) + ファームウェアパッケージ(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”が存在しません + + + + + Continue + 続行 + + + + Error Display + 表示エラー + + + + Missing yuzu Account + yuzuアカウントが存在しません + + + + In order to submit a game compatibility test case, you must link your yuzu account.<br><br/>To link your yuzu account, go to Emulation &gt; Configuration &gt; Web. + ゲームの互換性テストケースを送信するには、yuzuアカウントをリンクする必要があります。<br><br/>yuzuアカウントをリンクするには、エミュレーション > 設定 > Web から行います。 + + + + Error opening URL + URLオープンエラー + + + + Unable to open the URL "%1". + URL"%1"を開けません。 + + + + Amiibo File (%1);; All Files (*.*) + amiiboファイル (%1);;すべてのファイル (*.*) + + + + Load Amiibo + amiiboのロード + + + + Error opening Amiibo data file + amiiboデータファイルを開けませんでした + + + + Unable to open Amiibo file "%1" for reading. + amiiboデータファイル”%1”を読み込めませんでした。 + + + + Error reading Amiibo data file + amiiboデータファイルを読み込み中にエラーが発生した + + + + Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes. + amiiboデータを完全には読み取ることができませんでした。%1バイトを読み込もうとしましたが、%2バイトしか読み取れませんでした。 + + + + Error loading Amiibo data + amiiboデータ読み込み中にエラーが発生しました + + + + Unable to load Amiibo data. + amiiboデータをロードできませんでした。 + + + + Capture Screenshot + スクリーンショットのキャプチャ + + + + PNG Image (*.png) + PNG画像 (*.png) + + + + Speed: %1% / %2% + 速度:%1% / %2% + + + + Speed: %1% + 速度:%1% + + + + Game: %1 FPS + ゲーム:%1 FPS + + + + Frame: %1 ms + フレーム:%1 ms + + + + The game you are trying to load requires additional files from your Switch to be dumped before playing.<br/><br/>For more information on dumping these files, please see the following wiki page: <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + ロードしようとしているゲームはプレイする前にスイッチからダンプされた追加のファイルを必要とします。<br/><br/>これらのファイルのダンプの詳細については、次のWikiページを参照してください:<a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>スイッチコンソールからのシステムアーカイブと共有フォントをダンプする</a>。<br/><br/>ゲームリストに戻りますか?エミュレーションを続けると、クラッシュ、保存データの破損、またはその他のバグが発生する可能性があります。 + + + + yuzu was unable to locate a Switch system archive. %1 + yuzuはSwitchのシステムアーカイブ "%1" を見つけられませんでした。 + + + + yuzu was unable to locate a Switch system archive: %1. %2 + yuzuはSwitchのシステムアーカイブ "%1" "%2" を見つけられませんでした。 + + + + System Archive Not Found + システムアーカイブが見つかりません + + + + System Archive Missing + システムアーカイブが見つかりません + + + + yuzu was unable to locate the Switch shared fonts. %1 + yuzuはSwitchの共有フォント "%1" を見つけられませんでした。 + + + + Shared Fonts Not Found + 共有フォントが存在しません + + + + Shared Font Missing + 共有フォントが存在しません + + + + Fatal Error + 致命的なエラー + + + + yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + yuzuが致命的なエラーを検出しました。詳細については、ログを参照してください。ログへのアクセスの詳細については、次のページを参照してください。<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>ログファイルをアップロードする方法</a>。<br/><br/>ゲームリストに戻りますか?エミュレーションを続けると、クラッシュ、保存データの破損、またはその他のバグが発生する可能性があります。 + + + + Fatal Error encountered + 致命的なエラー発生 + + + + Confirm Key Rederivation + キーの再取得確認 + + + + You are about to force rederive all of your keys. +If you do not know what this means or what you are doing, +this is a potentially destructive action. +Please make sure this is what you want +and optionally make backups. + +This will delete your autogenerated key files and re-run the key derivation module. + すべてのキーを再作成しようとしています。 +これが何を意味するのか分からない場合、または何をしようとしているのか分からない場合、 +これは破壊的な可能性がある実行です。 +これがあなたが望むものであることを確認し、 +そしてオプションでバックアップを作成します。 + +これにより、自動生成されたキーファイルが削除され、キー導出モジュールが再実行されます。 + + + + Missing fuses + ヒューズがありません + + + + - Missing BOOT0 + - BOOT0がありません + + + + - Missing BCPKG2-1-Normal-Main + - BCPKG2-1-Normal-Mainがありません + + + + - Missing PRODINFO + - PRODINFOがありません + + + + Derivation Components Missing + 派生コンポーネントがありません + + + + Components are missing that may hinder key derivation from completing. <br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu quickstart guide</a> to get all your keys and games.<br><br><small>(%1)</small> + コンポーネントが見つからず、キーの導出が完了しない可能性があります。<br>ゲームとキーを取得するために、<a href='https://yuzu-emu.org/help/quickstart/'>yuzuクイックスタートガイド</a>の手順に従って下さい。<br><br><small>(%1)</small> + + + + Deriving keys... +This may take up to a minute depending +on your system's performance. + キーを作成中... +システムのパフォーマンスによっては +1分以上かかります。 + + + + Deriving Keys + 派生キー + + + + Select RomFS Dump Target + RomFSダンプターゲットの選択 + + + + Please select which RomFS you would like to dump. + ダンプしたいRomFSを選択して下さい。 + + + + + + yuzu + yuzu + + + + Are you sure you want to close yuzu? + yuzuを終了してもよろしいですか? + + + + Are you sure you want to stop the emulation? Any unsaved progress will be lost. + エミュレーションを停止してもよろしいですか?セーブされていない進行状況は失われます。 + + + + The currently running application has requested yuzu to not exit. + +Would you like to bypass this and exit anyway? + 現在動作中のアプリケーションはyuzuに終了しないよう要求しています。 + +無視してとにかく終了しますか? + + + + GRenderWindow + + + OpenGL not available! + OpenGLは使用できません! + + + + yuzu has not been compiled with OpenGL support. + yuzuはOpenGLサポート付きでコンパイルされていません。 + + + + Vulkan not available! + Vulkanは使用できません! + + + + yuzu has not been compiled with Vulkan support. + yuzuはVulkanサポート付きでコンパイルされていません。 + + + + Error while initializing OpenGL 4.3! + OpenGL4.3初期化エラー + + + + Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver. + GPUがOpenGL 4.3をサポートしていないか、最新のグラフィックスドライバーではありません。 + + + + Error while initializing OpenGL! + OpenGL初期化エラー + + + + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>Unsupported extensions:<br> + GPUが1つ以上の必要なOpenGL拡張機能をサポートしていない可能性があります。最新のグラフィックドライバを使用していることを確認してください。<br><br>サポートされていない拡張機能:<br> + + + + GameList + + + + Name + 名称 + + + + + Compatibility + 互換性 + + + + + Add-ons + アドオン + + + + + + + File type + ファイル種別 + + + + + + + Size + サイズ + + + + Open Save Data Location + セーブデータディレクトリを開く + + + + Open Mod Data Location + Modデータディレクトリを開く + + + + Open Transferable Shader Cache + シェーダキャッシュを開く + + + + Remove + 削除 + + + + Remove Installed Update + インストールされているアップデートを削除 + + + + Remove All Installed DLC + 全てのインストールされているDLCを削除 + + + + Remove Shader Cache + シェーダーキャッシュを削除 + + + + Remove Custom Configuration + カスタム設定を削除 + + + + Remove All Installed Contents + 全てのインストールされているコンテンツを削除 + + + + Dump RomFS + RomFSをダンプ + + + + Copy Title ID to Clipboard + タイトルIDをクリップボードへコピー + + + + Navigate to GameDB entry + GameDBエントリを表示 + + + + Properties + プロパティ + + + + Scan Subfolders + サブフォルダをスキャンする + + + + Remove Game Directory + ゲームディレクトリを削除する + + + + ▲ Move Up + ▲ 上へ移動 + + + + ▼ Move Down + ▼ 下へ移動 + + + + Open Directory Location + ディレクトリの場所を開く + + + + GameListItemCompat + + + Perfect + パーフェクト + + + + Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without +any workarounds needed. + ゲームはオーディオ、またはグラフィックの不具合なしで完璧に動作し、回避策なしで期待通りに動作します。 + + + + Great + グレート + + + + Game functions with minor graphical or audio glitches and is playable from start to finish. May require some +workarounds. + ゲームの動作にはグラフィック、またはオーディオの軽微な不具合がありますが、最初から最後までプレイ可能です。いくつかの回避策が必要な場合があります。 + + + + Okay + OK + + + + Game functions with major graphical or audio glitches, but game is playable from start to finish with +workarounds. + ゲームの動作にはグラフィック、またはオーディオの重大な不具合がありますが、回避策を使うことで最初から最後までプレイ可能です。 + + + + Bad + NG + + + + Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches +even with workarounds. + ゲームは動作しますが、グラフィック、またはオーディオに重大な不具合があります。回避策を使用しても不具合が原因で特定の場所から進めなくなります。 + + + + Intro/Menu + イントロ/メニュー + + + + Game is completely unplayable due to major graphical or audio glitches. 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. + ゲームはまだテストされていません。 + + + + GameListPlaceholder + + + Double-click to add a new folder to the game list + 新しいゲームリストフォルダを追加するにはダブルクリックしてください。 + + + + GameListSearchField + + + Filter: + フィルター: + + + + Enter pattern to filter + フィルターパターンを入力 + + + + InstallDialog + + + Please confirm these are the files you wish to install. + これらがインストールするファイルであることを確認してください。 + + + + Installing an Update or DLC will overwrite the previously installed one. + アップデート、またはDLCをインストールすると、以前にインストールしたものが上書きされます。 + + + + Install + インストール + + + + Install Files to NAND + ファイルをNANDへインストール + + + + LoadingScreen + + + Loading Shaders 387 / 1628 + シェーダをロード中 387 / 1628 + + + + Loading Shaders %v out of %m + シェーダをロード中 %v / %m + + + + Estimated Time 5m 4s + 予想時間 5分4秒 + + + + Loading... + ロード中... + + + + Loading Shaders %1 / %2 + シェーダをロード中 %1 / %2 + + + + Launching... + 起動中... + + + + Estimated Time %1 + 予想時間 %1 + + + + MainWindow + + + yuzu + yuzu + + + + &File + ファイル(&F) + + + + Recent Files + 最近使用したファイル + + + + &Emulation + エミュレーション(&E) + + + + &View + 表示(&V) + + + + Debugging + デバッグ + + + + Tools + ツール + + + + &Help + ヘルプ(&H) + + + + Install Files to NAND... + ファイルをNANDへインストール... + + + + Load File... + ファイルをロード... + + + + Load Folder... + フォルダをロード... + + + + E&xit + 終了(&E) + + + + &Start + 実行(&S) + + + + &Pause + 中断(&P) + + + + &Stop + 停止(&S) + + + + Reinitialize keys... + キーの再初期化... + + + + About yuzu + yuzuについて + + + + Single Window Mode + 単一ウィンドウモード + + + + Configure... + 設定... + + + + Display Dock Widget Headers + ドックウィジェットヘッダーの表示 + + + + Show Filter Bar + フィルターバーの表示 + + + + Show Status Bar + ステータスバーの表示 + + + + Reset Window Size + ウィンドウサイズをリセット + + + + Fullscreen + フルスクリーン + + + + Restart + 再起動 + + + + Load Amiibo... + amiiboをロード... + + + + Report Compatibility + 互換性を報告 + + + + Open Mods Page + Modページを開く + + + + Open Quickstart Guide + クイックスタートガイドを開く + + + + FAQ + FAQ + + + + Open yuzu Folder + yuzuディレクトリを開く + + + + Capture Screenshot + スクリーンショットをキャプチャ + + + + Configure Current Game.. + 現在のゲームの設定... + + + + MicroProfileDialog + + + MicroProfile + マイクロプロファイル + + + + QObject + + + Installed SD Titles + インストール済みSDタイトル + + + + Installed NAND Titles + インストール済みNANDタイトル + + + + System Titles + システムタイトル + + + + Add New Game Directory + 新しいゲームディレクトリを追加する + + + + + + Shift + Shift + + + + + + Ctrl + Ctrl + + + + + + Alt + Alt + + + + + + + [not set] + [未設定] + + + + + + Hat %1 %2 + 十字キー %1 %2 + + + + + + Axis %1%2 + 軸 %1%2 + + + + + + Button %1 + ボタン %1 + + + + + + + [unknown] + [不明] + + + + + Click 0 + クリック0 + + + + + Click 1 + クリック1 + + + + + Click 2 + クリック2 + + + + + Click 3 + クリック3 + + + + + Click 4 + クリック4 + + + + GC Axis %1%2 + GC Axis %1%2 + + + + GC Button %1 + GC Button %1 + + + + + [unused] + [未使用] + + + + + Axis %1 + 軸 %1 + + + + + GC Axis %1 + GC Axis %1 + + + + QtErrorDisplay + + + An error has occured. +Please try again or contact the developer of the software. + +Error Code: %1-%2 (0x%3) + エラーが発生しました。 +もう一度試す、またはソフト開発者に連絡してください。 + +エラーコード: %1-%2 (0x%3) + + + + An error occured on %1 at %2. +Please try again or contact the developer of the software. + +Error Code: %3-%4 (0x%5) + %2 の %1 でエラーが発生しました。 +もう一度試す、またはソフト開発者に連絡してください。 + +エラーコード: %3-%4 (0x%5) + + + + An error has occured. +Error Code: %1-%2 (0x%3) + +%4 + +%5 + エラーが発生しました。 +エラーコード: %1-%2 (0x%3) + +%4 + +%5 + + + + QtProfileSelectionDialog + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Select a user: + ユーザー選択: + + + + Users + ユーザー + + + + Profile Selector + プロファイル選択 + + + + QtSoftwareKeyboardDialog + + + Enter text: + テキストを入力: + + + + Software Keyboard + ソフトウェアキーボード + + + + SequenceDialog + + + Enter a hotkey + ホットキーを入力 + + + + WaitTreeCallstack + + + Call stack + コールスタック + + + + WaitTreeMutexInfo + + + waiting for mutex 0x%1 + ミューテックス 0x%1 待ち + + + + has waiters: %1 + 待機:%1 + + + + owner handle: 0x%1 + オーナーハンドル: 0x%1 + + + + WaitTreeObjectList + + + waiting for all objects + 全オブジェクト待ち + + + + waiting for one of the following objects + 以下のオブジェクトのうちの一つを待機中 + + + + WaitTreeSynchronizationObject + + + [%1]%2 %3 + [%1]%2 %3 + + + + waited by no thread + スレッドなしで待機 + + + + WaitTreeThread + + + + running + 実行中 + + + + ready + 準備完了 + + + + + paused + 中断 + + + + waiting for HLE return + HLEリターン待ち + + + + sleeping + 休止中 + + + + waiting for IPC reply + IPC返答待ち + + + + waiting for objects + オブジェクト待ち + + + + waiting for mutex + ミューテックス待ち + + + + waiting for condition variable + 条件変数待ち + + + + waiting for address arbiter + アドレス決定待ち + + + + dormant + 休止 + + + + dead + 死亡 + + + + PC = 0x%1 LR = 0x%2 + PC = 0x%1 LR = 0x%2 + + + + ideal + 理想的 + + + + core %1 + コア %1 + + + + Unknown processor %1 + 不明なプロセッサ %1 + + + + processor = %1 + プロセッサ = %1 + + + + ideal core = %1 + イデアルコア = %1 + + + + affinity mask = %1 + アフィニティマスク = %1 + + + + thread id = %1 + スレッドID = %1 + + + + priority = %1(current) / %2(normal) + 優先度 = %1(現在) / %2(通常) + + + + last running ticks = %1 + 最終実行刻み = %1 + + + + not waiting for mutex + ミューテックス待ちではない + + + + WaitTreeThreadList + + + waited by thread + スレッドによる待機 + + + + WaitTreeWidget + + + Wait Tree + 待ちツリー + + + \ No newline at end of file diff --git a/dist/languages/nl.ts b/dist/languages/nl.ts new file mode 100644 index 000000000..0f5178458 --- /dev/null +++ b/dist/languages/nl.ts @@ -0,0 +1,4719 @@ + + + AboutDialog + + + About yuzu + Over yuzu + + + + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + + + + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + + + + <html><head/><body><p>%1 | %2-%3 (%4)</p></body></html> + <html><head/><body><p>%1 | %2-%3 (%4)</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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu is een experimentele open-bron emulator voor de Nintendo Switch, in licentie gegeven onder GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">De software zou niet gebruikt moeten worden om games te spelen die je niet legaal verkregen hebt.</span></p></body></html> + + + + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Broncode</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Bijdragers</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">Licentie</span></a></p></body></html> + + + + <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.</span></p></body></html> + <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; is een handelsmerk van Nintendo. yuzu is op geen enkele manier met Nintendo verbonden.</span></p></body></html> + + + + CalibrationConfigurationDialog + + + Communicating with the server... + Communiceren met de server... + + + + Cancel + Annuleren + + + + Touch the top left corner <br>of your touchpad. + + + + + Now touch the bottom right corner <br>of your touchpad. + + + + + Configuration completed! + + + + + OK + + + + + CompatDB + + + Report Compatibility + Rapporteer Compatibiliteit + + + + + Report Game Compatibility + Rapporteer Game Compatibiliteit + + + + <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu 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 yuzu 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 yuzu account</li></ul></body></html> + <html><head/><body><p><span style=" font-size:10pt;">Als je kiest een test case op te sturen naar de </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu compatibiliteitslijst</span></a><span style=" font-size:10pt;">, zal de volgende informatie worden verzameld en getoond op de 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 Informatie (CPU / GPU / Besturingssysteem)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Welke versie van yuzu je draait</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Het verbonden yuzu account</li></ul></body></html> + + + + Perfect + Perfect + + + + <html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html> + <html><head/><body><p>De game werkt foutloos, zonder geluid of grafische problemen.</p></body></html> + + + + Great + Goed + + + + <html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html> + <html><head/><body><p>Game werkt met kleine grafische of geluid problemen en is speelbaar van start tot eind. Kan enkele tijdelijke oplossingen nodig hebben.</p></body></html> + + + + Okay + OK + + + + <html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html> + <html><head/><body><p>Game werkt met grote grafische of geluid problemen, maar is speelbaar van start tot eind met tijdelijke oplossingen.</p></body></html> + + + + Bad + Slecht + + + + <html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html> + <html><head/><body><p>Game werkt, maar met grote grafische of geluid problemen. Specifieke gebieden kunnen niet worden uitgespeeld vanwege problemen, zelfs met tijdelijke oplossingen. </p></body></html> + + + + Intro/Menu + Intro/Menu + + + + <html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html> + <html><head/><body><p>Game is compleet onspeelbaar dankzij grote grafische of geluid problemen. Onmogelijk voorbij het startscherm te komen.</p></body></html> + + + + Won't Boot + Start Niet + + + + <html><head/><body><p>The game crashes when attempting to startup.</p></body></html> + <html><head/><body><p>De game crasht wanneer je het probeert op te starten.</p></body></html> + + + + <html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html> + <html><head/><body><p>Onafhankelijk van snelheid of prestaties, Hoe goed speelt de game van start tot eind op deze versie van yuzu?</p></body></html> + + + + Thank you for your submission! + Bedankt voor je inzending! + + + + Submitting + Inzenden + + + + Communication error + Communicatiefout + + + + An error occured while sending the Testcase + Er was een fout tijdens het insturen van de Testcase. + + + + Next + Volgende + + + + ConfigureAudio + + + Audio + Geluid + + + + Output Engine: + Output Engine: + + + + This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency. + Dit nabewerkings effect past de snelheid van het geluid zodanig aan dat de snelheid van de emulatie en van het geluid op elkaar afgestemd zijn. Dit zorgt er wel voor dat de latency van het geluid hoger word. + + + + Enable audio stretching + Geluid Rekking Aanzetten + + + + Audio Device: + Geluid Apparaat: + + + + Use global volume + + + + + Set volume: + + + + + Volume: + Volume: + + + + 0 % + 0 % + + + + %1% + Volume percentage (e.g. 50%) + %1% + + + + ConfigureCpu + + + Form + + + + + General + + + + + Accuracy: + + + + + Accurate + + + + + Unsafe + + + + + Enable Debug Mode + + + + + We recommend setting accuracy to "Accurate". + + + + + Unsafe CPU Optimization Settings + + + + + These settings reduce accuracy for speed. + + + + + Unfuse FMA (improve performance on CPUs without FMA) + + + + + + <div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div> + + + + + + Faster FRSQRTE and FRECPE + + + + + + <div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div> + + + + + + CPU settings are available only when game is not running. + + + + + Setting CPU to Debug Mode + + + + + CPU Debug Mode is only intended for developer use. Are you sure you want to enable this? + + + + + ConfigureCpuDebug + + + Form + + + + + Toggle CPU Optimizations + + + + + + <div> + <b>For debugging only.</b> + <br> + If you're not sure what these do, keep all of these enabled. + <br> + These settings only take effect when CPU Accuracy is "Debug Mode". + </div> + + + + + + Enable inline page tables + + + + + + <div style="white-space: nowrap">This optimization speeds up memory accesses by the guest program.</div> + <div style="white-space: nowrap">Enabling it inlines accesses to PageTable::pointers into emitted code.</div> + <div style="white-space: nowrap">Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.</div> + + + + + + Enable block linking + + + + + + <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> + + + + + + Enable return stack buffer + + + + + + <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> + + + + + + Enable fast dispatcher + + + + + + <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> + + + + + + Enable context elimination + + + + + + <div>Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.</div> + + + + + + Enable constant propagation + + + + + + <div>Enables IR optimizations that involve constant propagation.</div> + + + + + + Enable miscellaneous optimizations + + + + + + <div>Enables miscellaneous IR optimizations.</div> + + + + + + Enable misalignment check reduction + + + + + + <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> + + + + + + CPU settings are available only when game is not running. + + + + + ConfigureDebug + + + Form + Vorm + + + + GDB + GDB + + + + Enable GDB Stub + GDB Stub Aanzetten + + + + Port: + Poort: + + + + Logging + Loggen + + + + Global Log Filter + Globale Log Filter + + + + Show Log Console (Windows Only) + Laat Log Venster Zien (Alleen voor Windows) + + + + Open Log Location + Open Log Locatie + + + + Homebrew + Homebrew + + + + Arguments String + Argumenten Rij + + + + Graphics + + + + + When checked, the graphics API enters in a slower debugging mode + + + + + Enable Graphics Debugging + + + + + When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower + + + + + Disable Macro JIT + + + + + Dump + + + + + Enable Verbose Reporting Services + Gebruik Uitgebreide Rapporteer Services + + + + This will be reset automatically when yuzu closes. + Deze optie hersteld wanneer je yuzu afsluit. + + + + Advanced + Geavanceerd + + + + Kiosk (Quest) Mode + Kiosk (Quest) Modus + + + + ConfigureDebugController + + + Configure Debug Controller + + + + + Clear + + + + + Defaults + + + + + ConfigureDialog + + + yuzu Configuration + yuzu Configuratie + + + + + + + General + Algemeen + + + + + UI + UI + + + + Game List + Game Lijst + + + + + + + System + Systeem + + + + + + Profiles + Profielen + + + + + + Filesystem + Bestandssysteem + + + + + + + Controls + Bediening + + + + + + Hotkeys + Sneltoetsen + + + + + + + CPU + + + + + + + + + + Debug + Debug + + + + + + + Graphics + Grafisch + + + + + Advanced + + + + + GraphicsAdvanced + + + + + + + + Audio + Geluid + + + + + + Web + Web + + + + + + Services + Services + + + + ConfigureFilesystem + + + Form + Vorm + + + + Storage Directories + Opslag Folders + + + + NAND + NAND + + + + + + + + + ... + ... + + + + SD Card + SD Kaart + + + + Gamecard + Gamekaart + + + + Path + Pad + + + + Inserted + Ingevoerd + + + + Current Game + Huidig Spel + + + + Patch Manager + Patch Beheer + + + + Dump Decompressed NSOs + Dump Uitgepakte NSO's + + + + Dump ExeFS + Dump ExeFS + + + + Mod Load Root + Mod Laad Root + + + + Dump Root + Dump Root + + + + Caching + Caching + + + + Cache Directory + Cache Folder + + + + Cache Game List Metadata + Cache Spel Lijst Metadata + + + + + + + Reset Metadata Cache + Herstel Metadata Cache + + + + Select Emulated NAND Directory... + Selecteer Geëmuleerde NAND Folder + + + + Select Emulated SD Directory... + Selecteer Geëmuleerde SD Folder + + + + Select Gamecard Path... + Selecteer Gamekaart Pad + + + + Select Dump Directory... + Selecteer Dump Folder + + + + Select Mod Load Directory... + Selecteer Mod Laad Folder + + + + Select Cache Directory... + Selecteer Cache Folder + + + + The metadata cache is already empty. + De metadata cache is al leeg. + + + + The operation completed successfully. + De operatie is succesvol voltooid. + + + + The metadata cache couldn't be deleted. It might be in use or non-existent. + De metadata cache kon niet worden verwijderd. Het wordt mogelijk gebruikt of bestaat niet. + + + + ConfigureGeneral + + + Form + Vorm + + + + General + Algemeen + + + + Limit Speed Percent + Limiteer Snelheid Percentage + + + + % + % + + + + Multicore CPU Emulation + + + + + Confirm exit while emulation is running + Bevestig sluiten terwijl emulatie nog bezig is + + + + Prompt for user on game boot + Vraag voor gebruiker bij het opstartten van het spel. + + + + Pause emulation when in background + Pauzeer Emulatie wanneer yuzu op de achtergrond openstaat + + + + Hide mouse on inactivity + + + + + ConfigureGraphics + + + Form + Vorm + + + + API Settings + + + + + API: + + + + + Device: + + + + + Graphics Settings + + + + + Use disk shader cache + Gebruik schijf shader cache + + + + Use asynchronous GPU emulation + Gebruik asynchroon GPU emulatie + + + + Aspect Ratio: + + + + + Default (16:9) + + + + + Force 4:3 + + + + + Force 21:9 + + + + + Stretch to Window + + + + + + Use global background color + + + + + Set background color: + + + + + Background Color: + Achtergrondkleur: + + + + OpenGL Graphics Device + + + + + ConfigureGraphicsAdvanced + + + Form + + + + + Advanced Graphics Settings + + + + + Accuracy Level: + + + + + VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference. + + + + + Use VSync (OpenGL only) + + + + + Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental. + + + + + Use assembly shaders (experimental, Nvidia OpenGL only) + + + + + Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental. + + + + + Use asynchronous shader building (experimental) + + + + + Use Fast GPU Time + + + + + Anisotropic Filtering: + + + + + Default + + + + + 2x + + + + + 4x + + + + + 8x + + + + + 16x + + + + + ConfigureHotkeys + + + Hotkey Settings + Sneltoets Instellingen + + + + Double-click on a binding to change it. + Dubbel-klik op een binding om het te veranderen. + + + + Clear All + + + + + Restore Defaults + + + + + Action + Actie + + + + Hotkey + Sneltoets + + + + Context + Context + + + + + Conflicting Key Sequence + Ongeldige Toets Volgorde + + + + The entered key sequence is already assigned to: %1 + + + + + Restore Default + + + + + Clear + + + + + The default key sequence is already assigned to: %1 + + + + + ConfigureInput + + + ConfigureInput + + + + + + Player 1 + Speler 1 + + + + + Player 2 + Speler 2 + + + + + Player 3 + Speler 3 + + + + + Player 4 + Speler 4 + + + + + Player 5 + Speler 5 + + + + + Player 6 + Speler 6 + + + + + Player 7 + Speler 7 + + + + + Player 8 + Speler 8 + + + + + Advanced + Geavanceerd + + + + Console Mode + + + + + Docked + + + + + Undocked + + + + + Vibration + + + + + % + + + + + Motion + + + + + Configure + Configureer + + + + Controllers + + + + + 1 + + + + + 2 + + + + + 3 + + + + + 4 + + + + + 5 + + + + + 6 + + + + + 7 + + + + + 8 + + + + + Connected + + + + + Defaults + + + + + Clear + + + + + ConfigureInputAdvanced + + + Configure Input + + + + + Joycon Colors + + + + + Player 1 + + + + + + + + + + + + L Body + + + + + + + + + + + + L Button + + + + + + + + + + + + R Body + + + + + + + + + + + + R Button + + + + + Player 2 + + + + + Player 3 + + + + + Player 4 + + + + + Player 5 + + + + + Player 6 + + + + + Player 7 + + + + + Player 8 + + + + + Other + + + + + Keyboard + + + + + + Advanced + + + + + Touchscreen + + + + + Mouse + + + + + Motion / Touch + + + + + + Configure + + + + + Debug Controller + + + + + ConfigureInputPlayer + + + Configure Input + Configureer Invoer + + + + Connect Controller + + + + + + + Pro Controller + + + + + + Dual Joycons + + + + + + Left Joycon + + + + + + Right Joycon + + + + + + Handheld + + + + + Input Device + + + + + Any + + + + + Keyboard/Mouse + + + + + Profile + + + + + Save + + + + + New + + + + + Delete + + + + + Left Stick + Linker Stick + + + + + + + + + Up + + + + + + + + + + Left + + + + + + + + + + Right + + + + + + + + + + Down + + + + + + + + Pressed + + + + + + + + Modifier + + + + + + Range + + + + + + % + + + + + + Deadzone: 0% + + + + + + Modifier Range: 0% + + + + + D-Pad + + + + + + L + + + + + + ZL + + + + + + Minus + + + + + + Capture + + + + + + Plus + + + + + + Home + + + + + + R + + + + + + ZR + + + + + + SL + + + + + + SR + + + + + Face Buttons + Gezicht Knoppen + + + + + X + + + + + + Y + + + + + + A + + + + + + B + + + + + Right Stick + Rechter Stick + + + + + Deadzone: %1% + + + + + + Modifier Range: %1% + + + + + [waiting] + + + + + ConfigureMotionTouch + + + Configure Motion / Touch + + + + + Motion + + + + + Motion Provider: + + + + + Sensitivity: + + + + + Touch + + + + + Touch Provider: + + + + + Calibration: + + + + + (100, 50) - (1800, 850) + + + + + + + Configure + + + + + Use button mapping: + + + + + CemuhookUDP Config + + + + + You may use any Cemuhook compatible UDP input source to provide motion and touch input. + + + + + Server: + + + + + Port: + + + + + Pad: + + + + + Pad 1 + + + + + Pad 2 + + + + + Pad 3 + + + + + Pad 4 + + + + + Learn More + + + + + + Test + + + + + Mouse (Right Click) + + + + + + CemuhookUDP + + + + + Emulator Window + + + + + <a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Learn More</span></a> + + + + + 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. + + + + + Citra + + + + + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. + + + + + ConfigureMouseAdvanced + + + Configure Mouse + Configureer Muis + + + + Mouse Buttons + Muisknoppen + + + + Forward: + Voorwaarts: + + + + Back: + Terug: + + + + Left: + Links: + + + + Middle: + Middel: + + + + Right: + Rechts: + + + + + Clear + Herstellen + + + + Defaults + + + + + [not set] + [niet ingesteld] + + + + Restore Default + Herstel Standaardwaarde + + + + [press key] + [druk op knop] + + + + ConfigurePerGame + + + Dialog + + + + + Info + + + + + Name + + + + + Title ID + + + + + Filename + + + + + Format + + + + + Version + + + + + Size + + + + + Developer + + + + + Add-Ons + + + + + General + + + + + System + + + + + Graphics + + + + + Adv. Graphics + + + + + Audio + + + + + Properties + + + + + Use global configuration (%1) + + + + + ConfigurePerGameAddons + + + Form + + + + + Patch Name + + + + + Version + + + + + ConfigureProfileManager + + + Form + Vorm + + + + Profile Manager + Profiel Beheer + + + + Current User + Huidige Gebruiker + + + + Username + Gebruikersnaam + + + + Set Image + Selecteer Afbeelding + + + + Add + Voeg Toe + + + + Rename + Hernoem + + + + Remove + Verwijder + + + + Profile management is available only when game is not running. + Profiel beheer is alleen beschikbaar wanneer het spel niet bezig is. + + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Enter Username + Voer een Gebruikersnaam in + + + + Users + Gebruikers + + + + Enter a username for the new user: + Voer een gebruikersnaam in voor de nieuwe gebruiker: + + + + Enter a new username: + Voer nieuwe gebruikersnaam in: + + + + Confirm Delete + Bevestig Verwijdering + + + + You are about to delete user with name "%1". Are you sure? + Je staat op het punt een gebruiker met de naam "%1" te verwijderen. Weet je het zeker? + + + + Select User Image + Selecteer gebruiker's foto + + + + JPEG Images (*.jpg *.jpeg) + JPEG foto's (*.jpg *.jpeg) + + + + Error deleting image + Fout tijdens verwijderen afbeelding + + + + Error occurred attempting to overwrite previous image at: %1. + Er is een fout opgetreden bij het overschrijven van de vorige afbeelding in: %1. + + + + Error deleting file + Fout tijdens verwijderen bestand + + + + Unable to delete existing file: %1. + Kan bestaand bestand niet verwijderen: %1. + + + + Error creating user image directory + Fout tijdens het maken van de map met afbeeldingen van de gebruiker + + + + Unable to create directory %1 for storing user images. + Fout tijdens het maken van map %1 om gebruikersafbeeldingen in te bewaren. + + + + Error copying user image + Fout tijdens het kopiëren van de gebruiker afbeelding + + + + Unable to copy image from %1 to %2 + Kan afbeelding niet kopiëren van %1 naar %2 + + + + ConfigureService + + + Form + Vorm + + + + BCAT + BCAT + + + + BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content. + BCAT is Nintendo's manier om informatie naar games te versturen om de gemeenschap te stimuleren of om nieuwe inhoud te ontgrendelen + + + + BCAT Backend + BCAT Backend + + + + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Learn more about BCAT, Boxcat, and Current Events</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Leer meer over BCAT, Boxcat, en actuele Evenementen</span></a></p></body></html> + + + + The boxcat service is offline or you are not connected to the internet. + De boxcat service is offline of je bent niet verbonden met het internet. + + + + There was an error while processing the boxcat event data. Contact the yuzu developers. + Er was een fout tijdens het processen van de boxcat evenement data. Contacteer de yuzu ontwikkelaars. + + + + The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu. + De versie van yuzu die je gebruikt is te nieuw of te oud voor de server. Probeer te updaten naar de nieuwste officiële release van yuzu. + + + + + There are currently no events on boxcat. + Er zijn momenteel geen evenementen op boxcat. + + + + + Current Boxcat Events + + + + + Yuzu is retrieving the latest boxcat status... + Yuzu haalt de laatste boxcat-status op... + + + + ConfigureSystem + + + Form + Vorm + + + + System Settings + Systeeminstellingen + + + + Region: + + + + + Auto + + + + + Default + + + + + CET + + + + + CST6CDT + + + + + Cuba + + + + + EET + + + + + Egypt + + + + + Eire + + + + + EST + + + + + EST5EDT + + + + + GB + + + + + GB-Eire + + + + + GMT + + + + + GMT+0 + + + + + GMT-0 + + + + + GMT0 + + + + + Greenwich + + + + + Hongkong + + + + + HST + + + + + Iceland + + + + + Iran + + + + + Israel + + + + + Jamaica + + + + + + Japan + + + + + Kwajalein + + + + + Libya + + + + + MET + + + + + MST + + + + + MST7MDT + + + + + Navajo + + + + + NZ + + + + + NZ-CHAT + + + + + Poland + + + + + Portugal + + + + + PRC + + + + + PST8PDT + + + + + ROC + + + + + ROK + + + + + Singapore + + + + + Turkey + + + + + UCT + + + + + Universal + + + + + UTC + + + + + W-SU + + + + + WET + + + + + Zulu + + + + + USA + + + + + Europe + + + + + Australia + + + + + China + + + + + Korea + + + + + Taiwan + + + + + Time Zone: + + + + + Note: this can be overridden when region setting is auto-select + Noot: dit kan worden overschreven wanneer de regio instelling op automatisch selecteren staat. + + + + Japanese (日本語) + Japans (日本語) + + + + English + Engels (English) + + + + 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 (正體中文) + + + + Custom RTC + Handmatige RTC + + + + Language + Taal + + + + RNG Seed + RNG Seed + + + + Mono + Mono + + + + Stereo + Stereo + + + + Surround + Surround + + + + Console ID: + Console ID: + + + + Sound output mode + Geluid uitvoer mode + + + + d MMM yyyy h:mm:ss AP + d MMM jjjj u:mm:ss AP + + + + Regenerate + Herstel + + + + System settings are available only when game is not running. + Systeeminstellingen zijn enkel toegankelijk wanneer er geen game draait. + + + + This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue? + Dit vervangt je huidige virtuele Switch met een nieuwe. Je huidige virtuele Switch kan dan niet meer worden hersteld. Dit kan onverwachte effecten hebben in spellen. Dit werkt niet als je een oude config savegame gebruikt. Doorgaan? + + + + Warning + Waarschuwing + + + + Console ID: 0x%1 + Console ID: 0x%1 + + + + ConfigureTouchFromButton + + + Configure Touchscreen Mappings + + + + + Mapping: + + + + + New + + + + + Delete + + + + + 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 + + + + + Button + + + + + X + X axis + + + + + Y + Y axis + + + + + New Profile + + + + + Enter the name for the new profile. + + + + + Delete Profile + + + + + Delete profile %1? + + + + + Rename Profile + + + + + New name: + + + + + [press key] + + + + + ConfigureTouchscreenAdvanced + + + Configure Touchscreen + Configureer Touchscreen + + + + Warning: The settings in this page affect the inner workings of yuzu'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. + Waarschuwing: Instellingen in deze pagina hebben invloed op de interne werking van yuzu's geemuleerde touchscreen. Veranderingen kunnen ongewenste resultaten hebben, zoals ervoor zorgen dat het touchscreen half of niet werkt. Gebruik deze pagina enkel als je weet wat je doet. + + + + Touch Parameters + Touch Parameters + + + + Touch Diameter Y + Touch Diameter Y + + + + Finger + Vinger + + + + Touch Diameter X + Touch Diameter X + + + + Rotational Angle + Rotatiehoek + + + + Restore Defaults + Herstel Standaardwaardes + + + + ConfigureUi + + + Form + Vorm + + + + General + Algemeen + + + + Note: Changing language will apply your configuration. + Notitie: De taal veranderen past uw configuratie toe. + + + + Interface language: + Interface taal: + + + + Theme: + Thema: + + + + Game List + Game Lijst + + + + Show Add-Ons Column + Toon Add-Ons Kolom + + + + Icon Size: + Icoon Grootte: + + + + Row 1 Text: + Rij 1 Text: + + + + Row 2 Text: + Rij 2 Text: + + + + Screenshots + + + + + Ask Where To Save Screenshots (Windows Only) + + + + + Screenshots Path: + + + + + ... + + + + + Select Screenshots Path... + + + + + <System> + <System> + + + + English + Engels + + + + ConfigureWeb + + + Form + Vorm + + + + yuzu Web Service + yuzu Web Service + + + + By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information. + Door je gebruikersnaam en token te geven, ga je akkoord dat yuzu extra gebruiksdata verzameld, waaronder mogelijk gebruikersidentificatie-informatie. + + + + + Verify + Verifieer + + + + Sign up + Registreer + + + + Token: + Token: + + + + Username: + Gebruikersnaam: + + + + What is my token? + Wat is mijn token? + + + + Telemetry + Telemetrie + + + + Share anonymous usage data with the yuzu team + Deel anonieme gebruiksdata met het yuzu team + + + + Learn more + Leer meer + + + + Telemetry ID: + Telemetrie ID: + + + + Regenerate + Regenereer + + + + Discord Presence + Discord Presence + + + + Show Current Game in your Discord Status + Toon huidige game in je Discord status + + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Learn more</span></a> + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Leer meer</span></a> + + + + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Sign up</span></a> + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Registreer</span></a> + + + + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">What is my token?</span></a> + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">Wat is mijn token?</span></a> + + + + + Telemetry ID: 0x%1 + Telemetrie ID: 0x%1 + + + + + Unspecified + Niet gespecificeerd + + + + Token not verified + Token niet geverifieerd + + + + Token was not verified. The change to your token has not been saved. + Token is niet geverifieerd. De verandering aan uw token zijn niet opgeslagen. + + + + Verifying... + Verifiëren... + + + + Verification failed + Verificatie mislukt + + + + Verification failed. Check that you have entered your token correctly, and that your internet connection is working. + Verificatie mislukt. Check dat uw token correct is en dat uw internet werkt. + + + + GMainWindow + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous data is collected</a> to help improve yuzu. <br/><br/>Would you like to share your usage data with us? + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Annonieme gegevens worden verzameld</a> om yuzu te helpen verbeteren. <br/><br/> Zou je jouw gebruiksgegevens met ons willen delen? + + + + Telemetry + Telemetrie + + + + Text Check Failed + Tekst Check Is Gefaald + + + + Loading Web Applet... + Web Applet Laden... + + + + Exit Web Applet + Web Applet Afsluit + + + + Exit + Afsluiten + + + + To exit the web application, use the game provided controls to select exit, select the 'Exit Web Applet' option in the menu bar, or press the 'Enter' key. + Om de webtoepassing af te sluiten, gebruik de bedieningselementen van het spel om afsluiten te selecteren, selecteer de optie 'Web Applet Afsluiten' in de menubalk of druk op de 'Enter' toets. + + + + Web Applet + Web Applet + + + + This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested. + Deze versie van yuzu is gebouwd zonder ondersteuning van QtWebEngine, wat betekent dat yuzu de gevraagde spelhandleiding of webpagina niet correct kan weergeven. + + + + The amount of shaders currently being built + + + + + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. + Huidige emulatie snelheid. Waardes hoger of lager dan 100% betekent dat de emulatie sneller of langzamer loopt dan de Switch. + + + + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. + Hoeveel frames per seconde de game op dit moment weergeeft. Dit zal veranderen van game naar game en van scène naar scène. + + + + 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. + Tijd gebruikt om een frame van de Switch te emuleren, waarbij framelimiteren of v-sync niet wordt meegerekend. Voor emulatie op volledige snelheid zou dit maximaal 16.67 ms zijn. + + + + DOCK + + + + + ASYNC + + + + + MULTICORE + + + + + VULKAN + + + + + OPENGL + + + + + Clear Recent Files + Recente Bestanden Verwijderen + + + + Warning Outdated Game Format + Waarschuwing Verouderd Spel Formaat + + + + 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><br>For an explanation of the various Switch formats yuzu supports, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our wiki</a>. This message will not be shown again. + Je gebruikt gedeconstrueerd ROM map formaat voor dit Spel, dit is een verouderd formaat en is vervangen door formaten zoals NCA, NAX, XCI of NSP. Gedeconstrueerd ROM map heeft geen iconen, metadata en update understeuning.<br><br>Voor een uitleg over welke Switch formaten yuzu ondersteund, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>kijk op onze wiki</a>. Dit bericht word niet nog een keer weergegeven. + + + + + Error while loading ROM! + Fout tijdens het laden van een ROM! + + + + The ROM format is not supported. + Het formaat van de ROM is niet ondersteunt. + + + + An error occurred initializing the video core. + Er is een fout opgetreden tijdens het initialiseren van de videokern. + + + + yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.Ensure that you have the latest graphics drivers for your GPU. + yuzu is een fout tegengekomen tijdens het uitvoeren van de video kern, kijk alstublieft naar de log for meer details. Voor meer informatie op het vinden van de log, kijk alstublieft naar de volgende pagina : <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>hoe upload je een log bestand</a>. Bevestig dat je dat laaste grafische driver hebt voor je GPU. + + + + Error while loading ROM! + + + + + An unknown error occurred. Please see the log for more details. + Een onbekende fout heeft plaatsgevonden. Kijk in de log voor meer details. + + + + Start + Start + + + + Save Data + Save Data + + + + Mod Data + Mod Data + + + + Error Opening %1 Folder + Fout tijdens het openen van %1 folder + + + + + Folder does not exist! + Folder bestaat niet! + + + + Error Opening Transferable Shader Cache + Fout Bij Het Openen Van Overdraagbare Shader Cache + + + + + A shader cache for this title does not exist. + Er bestaat geen shader cache voor deze game + + + + Contents + + + + + Update + + + + + DLC + + + + + Remove Entry + + + + + Remove Installed Game %1? + + + + + + + + + Successfully Removed + + + + + Successfully removed the installed base game. + + + + + + + Error Removing %1 + + + + + The base game is not installed in the NAND and cannot be removed. + + + + + Successfully removed the installed update. + + + + + There is no update installed for this title. + + + + + There are no DLC installed for this title. + + + + + Successfully removed %1 installed DLC. + + + + + Delete Transferable Shader Cache? + + + + + Remove Custom Game Configuration? + + + + + Remove File + + + + + + Error Removing Transferable Shader Cache + + + + + Successfully removed the transferable shader cache. + + + + + Failed to remove the transferable shader cache. + + + + + + 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. + + + + + RomFS Extraction Failed! + RomFS Extractie Mislukt! + + + + There was an error copying the RomFS files or the user cancelled the operation. + Er was een fout tijdens het kopiëren van de RomFS bestanden of de gebruiker heeft de operatie geannuleerd. + + + + Full + Vol + + + + Skeleton + Skelet + + + + Select RomFS Dump Mode + Selecteer 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. + Selecteer alstublieft hoe je de RomFS wilt dumpen.<br>Volledig kopieërd alle bestanden in een map terwijl <br> skelet maakt alleen het map structuur. + + + + Extracting RomFS... + RomFS uitpakken... + + + + + Cancel + Annuleren + + + + RomFS Extraction Succeeded! + RomFS Extractie Geslaagd! + + + + The operation completed successfully. + De operatie is succesvol voltooid. + + + + Error Opening %1 + Fout bij openen %1 + + + + Select Directory + Selecteer Map + + + + Properties + Eigenschappen + + + + The game properties could not be loaded. + De eigenschappen van de game kunnen niet geladen worden. + + + + Switch Executable (%1);;All Files (*.*) + %1 is an identifier for the Switch executable file extensions. + Switch Executable (%1);;Alle bestanden (*.*) + + + + Load File + Laad Bestand + + + + Open Extracted ROM Directory + Open Gedecomprimeerd ROM Map + + + + Invalid Directory Selected + Ongeldige Map Geselecteerd + + + + The directory you have selected does not contain a 'main' file. + De map die je hebt geselecteerd bevat geen 'main' bestand. + + + + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) + + + + + Install Files + + + + + Installing file "%1"... + Bestand "%1" Installeren... + + + + Install Results + + + + + System Application + Systeem Applicatie + + + + System Archive + Systeem Archief + + + + System Application Update + Systeem Applicatie Update + + + + Firmware Package (Type A) + Filmware Pakket (Type A) + + + + Firmware Package (Type B) + Filmware Pakket (Type B) + + + + Game + Game + + + + Game Update + Game Update + + + + Game DLC + Game DLC + + + + Delta Title + Delta Titel + + + + Select NCA Install Type... + Selecteer NCA Installatie Type... + + + + Please select the type of title you would like to install this NCA as: +(In most instances, the default 'Game' is fine.) + Selecteer het type titel hoe je wilt dat deze NCA installeerd: +(In de meeste gevallen is de standaard 'Game' juist.) + + + + Failed to Install + Installatie Mislukt + + + + The title type you selected for the NCA is invalid. + Het type title dat je hebt geselecteerd voor de NCA is ongeldig. + + + + File not found + Bestand niet gevonden + + + + File "%1" not found + Bestand "%1" niet gevonden + + + + + Continue + Doorgaan + + + + Error Display + Fout Weergave + + + + Missing yuzu Account + Je yuzu account mist + + + + In order to submit a game compatibility test case, you must link your yuzu account.<br><br/>To link your yuzu account, go to Emulation &gt; Configuration &gt; Web. + Om game campatibiliteit te raporteren, moet je je yuzu account koppelen.<br><br/> Om je yuzu account te koppelen, ga naar Emulatie &gt; Configuratie &gt; Web. + + + + Error opening URL + + + + + Unable to open the URL "%1". + + + + + Amiibo File (%1);; All Files (*.*) + Amiibo Bestand (%1);; Alle Bestanden (*.*) + + + + Load Amiibo + Laad Amiibo + + + + Error opening Amiibo data file + Fout tijdens het openen van het Amiibo gegevens bestand + + + + Unable to open Amiibo file "%1" for reading. + Kan Amiibo bestand "%1" niet lezen. + + + + Error reading Amiibo data file + Fout tijdens het lezen van het Amiibo gegevens bestand + + + + Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes. + Kan de volledige Amiibo gegevens niet lezen. Verwacht om %1 bytes te lezen, maar het is alleen mogelijk om %2 bytes te lezen. + + + + Error loading Amiibo data + Fout tijdens het laden van de Amiibo data + + + + Unable to load Amiibo data. + Kan de Amiibo gegevens niet laden. + + + + Capture Screenshot + Screenshot Vastleggen + + + + PNG Image (*.png) + PNG afbeelding (*.png) + + + + Speed: %1% / %2% + Snelheid: %1% / %2% + + + + Speed: %1% + Snelheid: %1% + + + + Game: %1 FPS + Game: %1 FPS + + + + Frame: %1 ms + Frame: %1 ms + + + + The game you are trying to load requires additional files from your Switch to be dumped before playing.<br/><br/>For more information on dumping these files, please see the following wiki page: <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + De game die je probeert te laden heeft extra bestanden nodig van je Switch voordat je het kan spelen. <br/><br/>Voor meer informatie over het dumpen van deze bestanden, volg alsjeblieft onze wiki pagina: <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Het dumpen van Systeem Archieven en de Gedeelde Lettertypen van een Switch console </a>. <br/><br/>Wil je terug gaan naar de game lijst? Verdergaan met de emulatie zal misschien gevolgen hebben als vastlopen, beschadigde opslag data, of andere problemen. + + + + yuzu was unable to locate a Switch system archive. %1 + yuzu was niet in staat om de Switch systeem archieven te vinden. %1 + + + + yuzu was unable to locate a Switch system archive: %1. %2 + yuzu was niet in staat om de Switch systeem archieven te vinden. %1. %2 + + + + System Archive Not Found + Systeem Archief Niet Gevonden + + + + System Archive Missing + Systeem Archief Mist + + + + yuzu was unable to locate the Switch shared fonts. %1 + yuzu was niet in staat om de Switch shared fonts te vinden. %1 + + + + Shared Fonts Not Found + Shared Fonts Niet Gevonden + + + + Shared Font Missing + Gedeelde Lettertypes Niet Gevonden + + + + Fatal Error + Fatale Fout + + + + yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + yuzu is een fatale fout tegengekomen, zie de log voor meer details. Voor meer informatie over toegang krijgen tot de log, zie de volgende pagina: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Hoe upload je een log bestand</a>.<br/><br/>Zou je terug willen naar de game lijst? Doorgaan met emulatie kan resulteren in vastlapen, corrupte save gegevens, of andere problemen. + + + + Fatal Error encountered + Fatale Fout opgetreden + + + + Confirm Key Rederivation + Bevestig Sleutel Herafleiding + + + + You are about to force rederive all of your keys. +If you do not know what this means or what you are doing, +this is a potentially destructive action. +Please make sure this is what you want +and optionally make backups. + +This will delete your autogenerated key files and re-run the key derivation module. + Je bent op het punt al je sleutels geforceerd opnieuw te verkrijgen. +Als je niet weet wat dit doet of wat je aan het doen bent, +dit is potentieel een vernietigende actie. +Zorg ervoor dat je zeker weet dat dit is wat je wilt doen +en optioneel maak backups. + +Dit zal je automatisch gegenereerde sleutel bestanden verwijderen en de sleutel verkrijger module opnieuw starten + + + + Missing fuses + + + + + - Missing BOOT0 + + + + + - Missing BCPKG2-1-Normal-Main + + + + + - Missing PRODINFO + + + + + Derivation Components Missing + + + + + Components are missing that may hinder key derivation from completing. <br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu quickstart guide</a> to get all your keys and games.<br><br><small>(%1)</small> + + + + + Deriving keys... +This may take up to a minute depending +on your system's performance. + Dit zal misschien een paar minuten duren gebaseerd +op je systeem's performatie. + + + + Deriving Keys + Sleutels afleiden + + + + Select RomFS Dump Target + Selecteer RomFS Dump Doel + + + + Please select which RomFS you would like to dump. + Selecteer welke RomFS je zou willen dumpen. + + + + + + yuzu + yuzu + + + + Are you sure you want to close yuzu? + Weet je zeker dat je yuzu wilt sluiten? + + + + Are you sure you want to stop the emulation? Any unsaved progress will be lost. + Weet je zeker dat je de emulatie wilt stoppen? Alle onopgeslagen voortgang will verloren gaan. + + + + The currently running application has requested yuzu to not exit. + +Would you like to bypass this and exit anyway? + De momenteel actieve toepassing heeft yuzu gevraagd om niet af te sluiten. + +Wilt u dit omzeilen en toch afsluiten? + + + + GRenderWindow + + + OpenGL not available! + + + + + yuzu has not been compiled with OpenGL support. + + + + + Vulkan not available! + + + + + yuzu has not been compiled with Vulkan support. + + + + + Error while initializing OpenGL 4.3! + + + + + Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver. + + + + + Error while initializing OpenGL! + + + + + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>Unsupported extensions:<br> + + + + + GameList + + + + Name + Naam + + + + + Compatibility + Compatibiliteit + + + + + Add-ons + Toevoegingen + + + + + + + File type + Bestands type + + + + + + + Size + Grootte + + + + Open Save Data Location + Open Locatie Van Save Gegevens + + + + Open Mod Data Location + Open Mod Data Locatie + + + + Open Transferable Shader Cache + Open Overdraagbare Shader Cache + + + + Remove + + + + + Remove Installed Update + + + + + Remove All Installed DLC + + + + + Remove Shader Cache + + + + + Remove Custom Configuration + + + + + Remove All Installed Contents + + + + + Dump RomFS + Dump RomFS + + + + Copy Title ID to Clipboard + Kopieer Titel ID naar Klembord + + + + Navigate to GameDB entry + Navigeer naar GameDB inzending + + + + Properties + Eigenschappen + + + + Scan Subfolders + Scan Subfolders + + + + Remove Game Directory + Verwijder Game Directory + + + + ▲ Move Up + + + + + ▼ Move Down + + + + + Open Directory Location + Open Directory Locatie + + + + GameListItemCompat + + + Perfect + Perfect + + + + Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without +any workarounds needed. + Game functioneerd foutloos, zonder auditieve of grafische glitches. Alle geteste functionaliteit werkt zoals bedoeld zonder dat er tijdelijke oplossingen nodig zijn. + + + + Great + Goed + + + + Game functions with minor graphical or audio glitches and is playable from start to finish. May require some +workarounds. + Game werkt met kleine grafische of auditieve glitches en is speelbaar van start tot eind. Kan enkele workarounds nodig hebben. + + + + Okay + Okay + + + + Game functions with major graphical or audio glitches, but game is playable from start to finish with +workarounds. + Game werkt met grove grafische of auditieve glitches, maar is speelbaar van start tot eind met workarounds + + + + Bad + Slecht + + + + Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches +even with workarounds. + Game werkt, maar met grove grafische of auditieve glitches. Specifieke gebieden kunnen niet worden uitgespeeld vanwege glitches, zelfs met workarounds. + + + + Intro/Menu + Intro/Menu + + + + Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start +Screen. + Game is compleet onspeelbaar dankzij grove grafische of auditieve glitches. Onmogelijk voorbij het startscherm te gaan. + + + + Won't Boot + Start Niet + + + + The game crashes when attempting to startup. + De Game crasht wanneer hij probeert op te starten. + + + + Not Tested + Niet Getest + + + + The game has not yet been tested. + Deze Game is nog niet getest. + + + + GameListPlaceholder + + + Double-click to add a new folder to the game list + Dubbel-klik om een ​​nieuwe map toe te voegen aan de lijst met games + + + + GameListSearchField + + + Filter: + Filter: + + + + Enter pattern to filter + Voer patroon in om te filteren: + + + + InstallDialog + + + Please confirm these are the files you wish to install. + + + + + Installing an Update or DLC will overwrite the previously installed one. + + + + + Install + + + + + Install Files to NAND + + + + + LoadingScreen + + + Loading Shaders 387 / 1628 + Shaders Laden 387 / 1628 + + + + Loading Shaders %v out of %m + %v Shaders Laden van de %m + + + + Estimated Time 5m 4s + Geschatte Tijd 5m 4s + + + + Loading... + Laden... + + + + Loading Shaders %1 / %2 + Shaders Laden %1 / %2 + + + + Launching... + Starten... + + + + Estimated Time %1 + Geschatte Tijd %1 + + + + MainWindow + + + yuzu + yuzu + + + + &File + &Bestand + + + + Recent Files + Recente Bestanden + + + + &Emulation + &Emulatie + + + + &View + &Weergeven + + + + Debugging + Debugging + + + + Tools + Hulpmiddelen + + + + &Help + &Help + + + + Install Files to NAND... + + + + + Load File... + Laad Bestand... + + + + Load Folder... + Laad Folder... + + + + E&xit + A&fsluiten + + + + &Start + &Start + + + + &Pause + &Pauzeren + + + + &Stop + &Stop + + + + Reinitialize keys... + Sleutels opnieuw initialiseren... + + + + About yuzu + Over yuzu + + + + Single Window Mode + Enkel Venster Modus + + + + Configure... + Configureren... + + + + Display Dock Widget Headers + Dock Widget Rubriek Weergeven + + + + Show Filter Bar + Laat filter balk zien + + + + Show Status Bar + Laat status balk zien + + + + Reset Window Size + + + + + Fullscreen + Volledig Scherm + + + + Restart + Herstart + + + + Load Amiibo... + Laad Amiibo... + + + + Report Compatibility + Raporteer Compatibiliteit + + + + Open Mods Page + + + + + Open Quickstart Guide + + + + + FAQ + + + + + Open yuzu Folder + Open yuzu Folder + + + + Capture Screenshot + Screenshot Vastleggen + + + + Configure Current Game.. + + + + + MicroProfileDialog + + + MicroProfile + MicroProfiel + + + + QObject + + + Installed SD Titles + Geïnstalleerde SD Titels + + + + Installed NAND Titles + Geïnstalleerde NAND Titels + + + + System Titles + Systeem Titels + + + + Add New Game Directory + Voeg Nieuwe Game Map Toe + + + + + + Shift + Shift + + + + + + Ctrl + Ctrl + + + + + + Alt + Alt + + + + + + + [not set] + [niet aangegeven] + + + + + + Hat %1 %2 + Hat %1 %2 + + + + + + Axis %1%2 + Axis %1%2 + + + + + + Button %1 + Knop %1 + + + + + + + [unknown] + [onbekend] + + + + + Click 0 + + + + + + Click 1 + + + + + + Click 2 + + + + + + Click 3 + + + + + + Click 4 + + + + + GC Axis %1%2 + + + + + GC Button %1 + + + + + + [unused] + [ongebruikt] + + + + + Axis %1 + Axis %1 + + + + + GC Axis %1 + + + + + QtErrorDisplay + + + An error has occured. +Please try again or contact the developer of the software. + +Error Code: %1-%2 (0x%3) + Er is een fout opgetreden. +Probeer het opnieuw of neem contact op met de ontwikkelaar van de software. + +Fout Code: %1-%2 (0x%3) + + + + An error occured on %1 at %2. +Please try again or contact the developer of the software. + +Error Code: %3-%4 (0x%5) + Er is een fout opgetreden bij %1 en %2. +Probeer het opnieuw of neem contact op met de ontwikkelaar van de software. + +Fout Code: %3-%4 (0x%5) + + + + An error has occured. +Error Code: %1-%2 (0x%3) + +%4 + +%5 + Er is een fout opgetreden. +Fout Code: %1-%2 (0x%3) + +%4 + +%5 + + + + QtProfileSelectionDialog + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Select a user: + Selecteer een gebruiker: + + + + Users + Gebruikers + + + + Profile Selector + Profiel keuzeschakelaar + + + + QtSoftwareKeyboardDialog + + + Enter text: + Voer tekst in: + + + + Software Keyboard + Software Toetsenbord + + + + SequenceDialog + + + Enter a hotkey + Voer een hotkey in + + + + WaitTreeCallstack + + + Call stack + Call stack + + + + WaitTreeMutexInfo + + + waiting for mutex 0x%1 + wachten op mutex 0x%1 + + + + has waiters: %1 + heeft wachtende: %1 + + + + owner handle: 0x%1 + eigenaar handvat: 0x%1 + + + + WaitTreeObjectList + + + waiting for all objects + wachten op alle objecten + + + + waiting for one of the following objects + wachten op een van de volgende objecten + + + + WaitTreeSynchronizationObject + + + [%1]%2 %3 + + + + + waited by no thread + + + + + WaitTreeThread + + + + running + Bezig met uitvoeren + + + + ready + klaar + + + + + paused + gepauzeerd + + + + waiting for HLE return + wachten op HLE terugkeer + + + + sleeping + slapen + + + + waiting for IPC reply + wachten op IPC antwoord + + + + waiting for objects + wachten op objecten + + + + waiting for mutex + wachten op mutex + + + + waiting for condition variable + wachten op conditie variabele + + + + waiting for address arbiter + wachten op adres arbiter + + + + dormant + slapend + + + + dead + dood + + + + PC = 0x%1 LR = 0x%2 + PC = 0x%1 LR = 0x%2 + + + + ideal + ideaal + + + + core %1 + kern %1 + + + + Unknown processor %1 + Onbekende processor %1 + + + + processor = %1 + processor = %1 + + + + ideal core = %1 + ideale kern = %1 + + + + affinity mask = %1 + affiniteit masker = %1 + + + + thread id = %1 + draad id = %1 + + + + priority = %1(current) / %2(normal) + prioriteit = %1(huidige) / %2(normaal) + + + + last running ticks = %1 + laatste lopende ticks = %1 + + + + not waiting for mutex + Niet wachtend op mutex + + + + WaitTreeThreadList + + + waited by thread + Wachtend door draad + + + + WaitTreeWidget + + + Wait Tree + Wacht Boom + + + \ No newline at end of file diff --git a/dist/languages/pl.ts b/dist/languages/pl.ts new file mode 100644 index 000000000..4cd9b6fca --- /dev/null +++ b/dist/languages/pl.ts @@ -0,0 +1,4713 @@ + + + AboutDialog + + + About yuzu + O yuzu + + + + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + + + + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + + + + <html><head/><body><p>%1 | %2-%3 (%4)</p></body></html> + <html><head/><body><p>%1 | %2-%3 (%4)</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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu jest eksperymentalnym emulatorem o otwartym kodzie źródłowym dla Nintendo Switch w ramach licencji GLPv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">To oprogramowanie nie powinno być używane do gier, których nie nabyłeś legalnie.</span></p></body></html> + + + + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Strona</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Kod źródłowy</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Współautorzy</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">Licencja</span></a></p></body></html> + + + + <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.</span></p></body></html> + <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; jest znakiem towarowym Nintendo. yuzu nie jest stowarzyszony w żaden sposób z Nintendo.</span></p></body></html> + + + + CalibrationConfigurationDialog + + + Communicating with the server... + + + + + Cancel + Anuluj + + + + Touch the top left corner <br>of your touchpad. + + + + + Now touch the bottom right corner <br>of your touchpad. + + + + + Configuration completed! + Konfiguracja zakończona! + + + + OK + OK + + + + 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://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu 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 yuzu 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 yuzu account</li></ul></body></html> + <html><head/><body><p><span style=" font-size:10pt;">Jeśli postanowisz wysłać wyniki testu na </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">listę kompatybilności yuzu</span></a><span style=" font-size:10pt;">, następujące informacja 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;">Informacja o sprzęcie komputerowym (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 yuzu, której używasz</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 yuzu</li></ul></body></html> + + + + Perfect + Idealna + + + + <html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html> + <html><head/><body><p>Gra działa bez zarzutu, bez błędów graficznych lub dźwiękowych.</p></body></html> + + + + Great + Świetna + + + + <html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html> + <html><head/><body><p>Gra działa z pomniejszymi błędami dźwiękowymi lub graficznymi, jest grywalna od początku do końca. Może wymagać kilku obejść/poprawek.</p></body></html> + + + + Okay + W porządku + + + + <html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html> + <html><head/><body><p>Gra działa z większymi błędami dźwiękowymi lub graficznymi, ale jest grywalna od początku do końca. Może wymagać kilku obejść/poprawek.</p></body></html> + + + + Bad + Zła + + + + <html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html> + <html><head/><body><p>Gra działa z większymi błędami dźwiękowymi lub graficznymi. Niemożliwe jest przejście konkretnych miejsc nawet z obejściami.</p></body></html> + + + + Intro/Menu + Intro/Menu + + + + <html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html> + <html><head/><body><p>Gra w żadnym stopniu nie jest grywalna ze względu na poważne błędy graficzne lub dźwiękowe. Niemożliwe jest przejście ekranu początkowego.</p></body></html> + + + + Won't Boot + Nie uruchomia się + + + + <html><head/><body><p>The game crashes when attempting to startup.</p></body></html> + <html><head/><body><p>Gra zawiesza się podczas próby uruchomienia się.</p></body></html> + + + + <html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html> + <html><head/><body><p>Pomijając prędkość lub wydajność, jak dobrze ta gra zachowuje się w tej wersji yuzu?</p></body></html> + + + + Thank you for your submission! + Dziękujemy za Twoją opinię! + + + + Submitting + Wysyłanie + + + + Communication error + Błąd komunikacyjny + + + + An error occured while sending the Testcase + Wystąpił błąd podczas wysyłania wyników testu + + + + Next + Następny + + + + ConfigureAudio + + + Audio + Dźwięk + + + + Output Engine: + Silnik wyjścia + + + + This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency. + Ten efekt post-przetwarzania dostosowuje prędkość dźwięku tak, aby zgadzała się z dźwiękiem emulowanym pomagając przy tym zapobiec zacinaniu się dźwięku. Zwiększa jednak opóźnienie dźwięku. + + + + Enable audio stretching + Włącz rozciąganie dźwięku + + + + Audio Device: + Urządzenie dźwiękowe + + + + Use global volume + + + + + Set volume: + Ustaw głośność: + + + + Volume: + Głośność + + + + 0 % + 0 % + + + + %1% + Volume percentage (e.g. 50%) + %1% + + + + ConfigureCpu + + + Form + + + + + General + Ogólne + + + + Accuracy: + + + + + Accurate + + + + + Unsafe + Niebezpieczne + + + + Enable Debug Mode + Włącz Debug Mode + + + + We recommend setting accuracy to "Accurate". + + + + + Unsafe CPU Optimization Settings + + + + + These settings reduce accuracy for speed. + + + + + Unfuse FMA (improve performance on CPUs without FMA) + + + + + + <div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div> + + + + + + Faster FRSQRTE and FRECPE + + + + + + <div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div> + + + + + + CPU settings are available only when game is not running. + Ustawienia CPU są dostępny gdy gra nie jest uruchomiona + + + + Setting CPU to Debug Mode + + + + + CPU Debug Mode is only intended for developer use. Are you sure you want to enable this? + + + + + ConfigureCpuDebug + + + Form + + + + + Toggle CPU Optimizations + + + + + + <div> + <b>For debugging only.</b> + <br> + If you're not sure what these do, keep all of these enabled. + <br> + These settings only take effect when CPU Accuracy is "Debug Mode". + </div> + + + + + + Enable inline page tables + + + + + + <div style="white-space: nowrap">This optimization speeds up memory accesses by the guest program.</div> + <div style="white-space: nowrap">Enabling it inlines accesses to PageTable::pointers into emitted code.</div> + <div style="white-space: nowrap">Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.</div> + + + + + + Enable block linking + + + + + + <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> + + + + + + Enable return stack buffer + + + + + + <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> + + + + + + Enable fast dispatcher + + + + + + <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> + + + + + + Enable context elimination + + + + + + <div>Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.</div> + + + + + + Enable constant propagation + + + + + + <div>Enables IR optimizations that involve constant propagation.</div> + + + + + + Enable miscellaneous optimizations + + + + + + <div>Enables miscellaneous IR optimizations.</div> + + + + + + Enable misalignment check reduction + + + + + + <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> + + + + + + CPU settings are available only when game is not running. + + + + + ConfigureDebug + + + Form + Forma + + + + GDB + GDB + + + + Enable GDB Stub + Włącz namiastkę GDB + + + + Port: + Port + + + + Logging + Logowanie + + + + Global Log Filter + Globalny filtr rejestrów + + + + Show Log Console (Windows Only) + Pokaż okno rejestrów (tylko Windows) + + + + Open Log Location + Otwórz miejsce rejestrów + + + + Homebrew + Homebrew + + + + Arguments String + Linijka argumentu + + + + Graphics + + + + + When checked, the graphics API enters in a slower debugging mode + + + + + Enable Graphics Debugging + + + + + When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower + + + + + Disable Macro JIT + + + + + Dump + + + + + Enable Verbose Reporting Services + + + + + This will be reset automatically when yuzu closes. + + + + + Advanced + Zaawansowane + + + + Kiosk (Quest) Mode + + + + + ConfigureDebugController + + + Configure Debug Controller + + + + + Clear + Wyczyść + + + + Defaults + Domyślne + + + + ConfigureDialog + + + yuzu Configuration + Ustawienia yuzu + + + + + + + General + Ogólne + + + + + UI + UI + + + + Game List + Lista Gier + + + + + + + System + System + + + + + + Profiles + Profile + + + + + + Filesystem + System plików + + + + + + + Controls + Sterowanie + + + + + + Hotkeys + Skróty klawiszowe + + + + + + + CPU + CPU + + + + + + + + + Debug + Wyszukiwanie usterek + + + + + + + Graphics + Grafika + + + + + Advanced + Zaawansowane + + + + GraphicsAdvanced + + + + + + + + Audio + Dźwięk + + + + + + Web + Web + + + + + + Services + Usługi + + + + ConfigureFilesystem + + + Form + + + + + Storage Directories + + + + + NAND + NAND + + + + + + + + + ... + ... + + + + SD Card + Karta SD + + + + Gamecard + Gamecard + + + + Path + Ścieżka + + + + Inserted + + + + + Current Game + Obecna Gra + + + + Patch Manager + + + + + Dump Decompressed NSOs + + + + + Dump ExeFS + + + + + Mod Load Root + + + + + Dump Root + + + + + Caching + + + + + Cache Directory + + + + + Cache Game List Metadata + + + + + + + + Reset Metadata Cache + + + + + Select Emulated NAND Directory... + + + + + Select Emulated SD Directory... + + + + + Select Gamecard Path... + + + + + Select Dump Directory... + + + + + Select Mod Load Directory... + + + + + Select Cache Directory... + + + + + 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. + + + + + ConfigureGeneral + + + Form + Forma + + + + General + Ogólne + + + + Limit Speed Percent + + + + + % + % + + + + Multicore CPU Emulation + Emulacja CPU Wielordzeniowa + + + + Confirm exit while emulation is running + Potwierdź wyjście podczas emulacji + + + + Prompt for user on game boot + + + + + Pause emulation when in background + + + + + Hide mouse on inactivity + + + + + ConfigureGraphics + + + Form + Forma + + + + API Settings + Ustawienia API + + + + API: + API: + + + + Device: + Urządzenie: + + + + Graphics Settings + Ustawienia Graficzne + + + + Use disk shader cache + + + + + Use asynchronous GPU emulation + + + + + Aspect Ratio: + Format obrazu: + + + + Default (16:9) + Domyślne (16:9) + + + + Force 4:3 + Wymuś 4:3 + + + + Force 21:9 + Wymuś 21:9 + + + + Stretch to Window + Rozciągnij do Okna + + + + + Use global background color + + + + + Set background color: + Ustaw kolor tła: + + + + Background Color: + Kolor tła + + + + OpenGL Graphics Device + Urządzenie graficzne OpenGL + + + + ConfigureGraphicsAdvanced + + + Form + + + + + Advanced Graphics Settings + + + + + Accuracy Level: + + + + + VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference. + + + + + Use VSync (OpenGL only) + + + + + Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental. + + + + + Use assembly shaders (experimental, Nvidia OpenGL only) + + + + + Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental. + + + + + Use asynchronous shader building (experimental) + + + + + Use Fast GPU Time + + + + + Anisotropic Filtering: + + + + + Default + Domyślne + + + + 2x + 2x + + + + 4x + 4x + + + + 8x + 8x + + + + 16x + 16x + + + + ConfigureHotkeys + + + Hotkey Settings + Ustawienia Skrótów Klawiszowych + + + + Double-click on a binding to change it. + + + + + Clear All + Wyczyść wszystko + + + + Restore Defaults + Przywróć domyślne + + + + Action + Akcja + + + + Hotkey + Skrót klawiszowy + + + + Context + Kontekst + + + + + Conflicting Key Sequence + + + + + The entered key sequence is already assigned to: %1 + + + + + Restore Default + Przywróć ustawienia domyślne + + + + Clear + Wyczyść + + + + The default key sequence is already assigned to: %1 + + + + + ConfigureInput + + + ConfigureInput + + + + + + Player 1 + Gracz 1 + + + + + Player 2 + Gracz 2 + + + + + Player 3 + Gracz 3 + + + + + Player 4 + Gracz 4 + + + + + Player 5 + Gracz 5 + + + + + Player 6 + Gracz 6 + + + + + Player 7 + Gracz 7 + + + + + Player 8 + Gracz 8 + + + + + Advanced + Zaawansowane + + + + Console Mode + Tryb Konsoli + + + + Docked + Zadokowany + + + + Undocked + Niezadokowany + + + + Vibration + Wibracje + + + + % + % + + + + Motion + Ruch + + + + Configure + Ustaw + + + + Controllers + Kontrolery + + + + 1 + 1 + + + + 2 + 2 + + + + 3 + 3 + + + + 4 + 4 + + + + 5 + 5 + + + + 6 + 6 + + + + 7 + 7 + + + + 8 + 8 + + + + Connected + Połączone + + + + Defaults + Domyślne + + + + Clear + Wyczyść + + + + ConfigureInputAdvanced + + + Configure Input + Ustawienie wejścia + + + + Joycon Colors + Kolory Joyconów + + + + Player 1 + Gracz 1 + + + + + + + + + + + L Body + + + + + + + + + + + + L Button + + + + + + + + + + + + R Body + + + + + + + + + + + + R Button + + + + + Player 2 + Gracz 2 + + + + Player 3 + Gracz 3 + + + + Player 4 + Gracz 4 + + + + Player 5 + Gracz 5 + + + + Player 6 + Gracz 6 + + + + Player 7 + Gracz 7 + + + + Player 8 + Gracz 8 + + + + Other + Inne + + + + Keyboard + Klawiatura + + + + + Advanced + Zaawansowane + + + + Touchscreen + Ekran dotykowy + + + + Mouse + Mysz + + + + Motion / Touch + Ruch / Dotyk + + + + + Configure + Konfiguruj + + + + Debug Controller + Debuguj kontroler + + + + ConfigureInputPlayer + + + Configure Input + Ustawienie wejścia + + + + Connect Controller + Połacz Kontroler + + + + + + Pro Controller + Pro Controller + + + + + Dual Joycons + Para Joyconów + + + + + Left Joycon + Lewy Joycon + + + + + Right Joycon + Prawy Joycon + + + + + Handheld + Handheld + + + + Input Device + Urządzenie Wejściowe + + + + Any + + + + + Keyboard/Mouse + Klawiatura/Mysz + + + + Profile + Profil + + + + Save + Zapisz + + + + New + Nowy + + + + Delete + Usuń + + + + Left Stick + Lewa gałka + + + + + + + + + Up + Góra + + + + + + + + + Left + Lewo + + + + + + + + + Right + Prawo + + + + + + + + + Down + Dół + + + + + + + Pressed + Naciśnięty + + + + + + + Modifier + Modyfikator + + + + + Range + Zasięg + + + + + % + % + + + + + Deadzone: 0% + Martwa strefa: 0% + + + + + Modifier Range: 0% + Zasięg Modyfikatora: 0% + + + + D-Pad + D-Pad + + + + + L + L + + + + + ZL + ZL + + + + + Minus + Minus + + + + + Capture + Zrzut ekranu + + + + + Plus + Plus + + + + + Home + Home + + + + + R + R + + + + + ZR + ZR + + + + + SL + SL + + + + + SR + SR + + + + Face Buttons + Przednie klawisze + + + + + X + X + + + + + Y + Y + + + + + A + A + + + + + B + B + + + + Right Stick + Prawa gałka + + + + + Deadzone: %1% + Martwa strefa: %1% + + + + + Modifier Range: %1% + Zasięg Modyfikatora: %1% + + + + [waiting] + [oczekiwanie] + + + + ConfigureMotionTouch + + + Configure Motion / Touch + Konfiguruj Ruch / Dotyk + + + + Motion + Ruch + + + + Motion Provider: + + + + + Sensitivity: + Czułość: + + + + Touch + Dotyk + + + + Touch Provider: + + + + + Calibration: + Kalibracja: + + + + (100, 50) - (1800, 850) + (100, 50) - (1800, 850) + + + + + + Configure + Konfiguruj + + + + Use button mapping: + + + + + CemuhookUDP Config + + + + + You may use any Cemuhook compatible UDP input source to provide motion and touch input. + + + + + Server: + Serwer: + + + + Port: + Port: + + + + Pad: + Pad: + + + + Pad 1 + + + + + Pad 2 + + + + + Pad 3 + + + + + Pad 4 + + + + + Learn More + Dowiedz się więcej + + + + + Test + Test + + + + Mouse (Right Click) + Myszka (Prawy Przycisk) + + + + + CemuhookUDP + + + + + Emulator Window + + + + + <a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Learn More</span></a> + + + + + Testing + Testowanie + + + + Configuring + Konfigurowanie + + + + Test Successful + Test Udany + + + + 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. + + + + + Citra + Citra + + + + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. + + + + + ConfigureMouseAdvanced + + + Configure Mouse + Ustaw mysz + + + + Mouse Buttons + Przyciski myszy + + + + Forward: + Do przodu + + + + Back: + Do tyłu + + + + Left: + Lewy + + + + Middle: + Środkowy + + + + Right: + Prawy + + + + + Clear + Wyczyść + + + + Defaults + Domyślne + + + + [not set] + [nie ustawione] + + + + Restore Default + Przywróć ustawienie domyślne + + + + [press key] + [naciśnij przycisk] + + + + ConfigurePerGame + + + Dialog + + + + + Info + Informacje + + + + Name + Nazwa + + + + Title ID + Identyfikator gry + + + + Filename + Nazwa pliku + + + + Format + Format + + + + Version + Wersja + + + + Size + Rozmiar + + + + Developer + Deweloper + + + + Add-Ons + Dodatki + + + + General + Ogólne + + + + System + System + + + + Graphics + Grafika + + + + Adv. Graphics + + + + + Audio + Dźwięk + + + + Properties + Właściwości + + + + Use global configuration (%1) + + + + + ConfigurePerGameAddons + + + Form + Forma + + + + Patch Name + + + + + Version + Wersja + + + + ConfigureProfileManager + + + Form + + + + + Profile Manager + Menedżer Profili + + + + Current User + Obecny użytkownik + + + + Username + Nazwa Użytkownika + + + + Set Image + Ustaw zdjęcie + + + + Add + Dodaj + + + + Rename + Zmień nazwę + + + + Remove + Usuń + + + + Profile management is available only when game is not running. + Menedżer Profili nie jest dostępny gdy gra jest uruchomiona. + + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Enter Username + Wpisz nazwę użytkownika + + + + Users + Użytkownicy + + + + Enter a username for the new user: + Wprowadź nazwę dla nowego użytkownika: + + + + Enter a new username: + Wpisz nową nazwę użytkownika: + + + + Confirm Delete + Potwierdź usunięcie + + + + You are about to delete user with name "%1". Are you sure? + Zamierzasz usunąć użytkownika "%1". Jesteś pewien? + + + + Select User Image + Ustaw zdjęcie użytkownika + + + + JPEG Images (*.jpg *.jpeg) + Obrazki JPEG (*.jpg *.jpeg) + + + + Error deleting image + Bład usunięcia zdjęcia + + + + Error occurred attempting to overwrite previous image at: %1. + Błąd podczas próby nadpisania poprzedniego zdjęcia dla: %1. + + + + Error deleting file + Błąd usunięcia pliku + + + + Unable to delete existing file: %1. + Nie można usunąć istniejącego pliku: %1 + + + + Error creating user image directory + Błąd podczas tworzenia folderu ze zdjęciem użytkownika + + + + Unable to create directory %1 for storing user images. + Nie można utworzyć ścieżki %1 do przechowywania zdjęć użytkownika. + + + + Error copying user image + Błąd kopiowania zdjęcia użytkownika + + + + Unable to copy image from %1 to %2 + Nie można skopiować zdjęcia z %1 do %2 + + + + ConfigureService + + + Form + Forma + + + + BCAT + BCAT + + + + BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content. + + + + + BCAT Backend + + + + + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Learn more about BCAT, Boxcat, and Current Events</span></a></p></body></html> + + + + + The boxcat service is offline or you are not connected to the internet. + + + + + There was an error while processing the boxcat event data. Contact the yuzu developers. + + + + + The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu. + + + + + + There are currently no events on boxcat. + + + + + + Current Boxcat Events + + + + + Yuzu is retrieving the latest boxcat status... + + + + + ConfigureSystem + + + Form + Forma + + + + System Settings + Ustawienia systemu + + + + Region: + Region: + + + + Auto + Automatyczny + + + + Default + Domyślne + + + + CET + CET + + + + CST6CDT + CST6CDT + + + + Cuba + Cuba + + + + EET + EET + + + + Egypt + Egipt + + + + Eire + Irlandia + + + + EST + EST + + + + EST5EDT + EST5EDT + + + + GB + GB + + + + 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 + + + + + Japan + Japonia + + + + 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 + + + + + Turkey + + + + + UCT + + + + + Universal + + + + + UTC + + + + + W-SU + + + + + WET + + + + + Zulu + + + + + USA + + + + + Europe + + + + + Australia + + + + + China + + + + + Korea + + + + + Taiwan + + + + + Time Zone: + + + + + Note: this can be overridden when region setting is auto-select + Uwaga: można to zmienić, gdy ustawienie regionu jest wybierane automatycznie + + + + Japanese (日本語) + Japoński (日本語) + + + + English + Angielski (English) + + + + French (français) + Francuski (français) + + + + German (Deutsch) + Niemiecki (Deutsch) + + + + 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 (正體中文) + + + + Custom RTC + + + + + Language + Język + + + + RNG Seed + Ziarno RNG + + + + Mono + Mono + + + + Stereo + Stereo + + + + Surround + Surround + + + + Console ID: + Indentyfikator konsoli: + + + + Sound output mode + Tryb wyjścia dźwięku + + + + d MMM yyyy h:mm:ss AP + + + + + Regenerate + Wygeneruj ponownie + + + + System settings are available only when game is not running. + Ustawienia systemu są dostępne tylko wtedy, gdy gra nie jest uruchomiona. + + + + This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue? + To zamieni twojego obecnego Switch'a z nowym. Twojego obecnego Switch'a nie będzie można przywrócić. To może wywołać nieoczekiwane problemy w grach. To może nie zadziałać, jeśli używasz nieaktualnej konfiguracji zapisu gry. Kontynuować? + + + + Warning + Ostrzeżenie + + + + Console ID: 0x%1 + Identyfikator konsoli: 0x%1 + + + + ConfigureTouchFromButton + + + Configure Touchscreen Mappings + + + + + Mapping: + + + + + New + + + + + Delete + + + + + 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 + + + + + Button + + + + + X + X axis + + + + + Y + Y axis + + + + + New Profile + + + + + Enter the name for the new profile. + + + + + Delete Profile + + + + + Delete profile %1? + + + + + Rename Profile + + + + + New name: + + + + + [press key] + + + + + ConfigureTouchscreenAdvanced + + + Configure Touchscreen + Skonfiguj ekran dotykowy + + + + Warning: The settings in this page affect the inner workings of yuzu'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. + Ostrzeżenie: Ustawienia na tej stronie mają wpływ na działanie emulowanego ekranu dotykowego Yuzu. ch zmiana może spowodować niepożądane zachowanie, takie jak częściowo lub całkowicie nie działający ekran dotykowy. Powinieneś/naś używać tej strony tylko wtedy, gdy wiesz, co robisz. + + + + Touch Parameters + Parametry dotyku + + + + Touch Diameter Y + Średnica dotyku Y + + + + Finger + Palec + + + + Touch Diameter X + Średnica dotyku X + + + + Rotational Angle + Kąt rotacji + + + + Restore Defaults + Przywróć domyślne + + + + ConfigureUi + + + Form + + + + + General + + + + + Note: Changing language will apply your configuration. + + + + + Interface language: + + + + + Theme: + + + + + Game List + Lista Gier + + + + Show Add-Ons Column + + + + + Icon Size: + + + + + Row 1 Text: + + + + + Row 2 Text: + + + + + Screenshots + + + + + Ask Where To Save Screenshots (Windows Only) + + + + + Screenshots Path: + + + + + ... + + + + + Select Screenshots Path... + + + + + <System> + <System> + + + + English + Angielski + + + + ConfigureWeb + + + Form + Forma + + + + yuzu Web Service + Usługa internetowa yuzu + + + + By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information. + Podając swoją nazwę użytkownika i token, zgadzasz się na umożliwienie yuzu zbierania dodatkowych danych o użytkowaniu, które mogą zawierać informacje umożliwiające identyfikację użytkownika. + + + + + Verify + Zweryfikuj + + + + Sign up + Zaloguj się + + + + Token: + Token: + + + + Username: + Nazwa użytkownika: + + + + What is my token? + Czym jest mój token? + + + + Telemetry + Telemetria + + + + Share anonymous usage data with the yuzu team + Udostępniaj anonimowe dane o użytkowaniu zespołowi yuzu + + + + Learn more + Dowiedz się więcej + + + + Telemetry ID: + Identyfikator telemetrii: + + + + Regenerate + Wygeneruj ponownie + + + + Discord Presence + Obecność na discordzie + + + + Show Current Game in your Discord Status + Pokazuj obecną grę w twoim statusie Discorda + + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Learn more</span></a> + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Dowiedz się więcej</span></a> + + + + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Sign up</span></a> + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Zaloguj się</span></a> + + + + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">What is my token?</span></a> + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">Czym jest mój token?</span></a> + + + + + Telemetry ID: 0x%1 + Identyfikator telemetrii: 0x%1 + + + + + Unspecified + + + + + Token not verified + + + + + Token was not verified. The change to your token has not been saved. + + + + + Verifying... + Weryfikowanie... + + + + Verification failed + Weryfikowanie nieudane + + + + Verification failed. Check that you have entered your token correctly, and that your internet connection is working. + + + + + GMainWindow + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous data is collected</a> to help improve yuzu. <br/><br/>Would you like to share your usage data with us? + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Dane anonimowe są gromadzone</a> aby ulepszyć yuzu. <br/><br/>Czy chcesz udostępnić nam swoje dane o użytkowaniu? + + + + Telemetry + Telemetria + + + + Text Check Failed + Sprawdzanie tekstu nie powiodło się + + + + Loading Web Applet... + + + + + Exit Web Applet + + + + + Exit + + + + + To exit the web application, use the game provided controls to select exit, select the 'Exit Web Applet' option in the menu bar, or press the 'Enter' key. + + + + + Web Applet + + + + + This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested. + + + + + The amount of shaders currently being built + + + + + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. + Aktualna prędkość emulacji. Wartości większe lub niższe niż 100% wskazują, że emulacja działa szybciej lub wolniej niż Switch. + + + + How many frames per second the game is currently displaying. This will vary from game to game and scene to scene. + Ile klatek na sekundę gra aktualnie wyświetla. To będzie się różnić w zależności od gry, od sceny do 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 do emulacji klatki na sekundę Switcha, nie licząc ograniczania klatek ani v-sync. Dla emulacji pełnej szybkości powinno to wynosić co najwyżej 16,67 ms. + + + + DOCK + + + + + ASYNC + + + + + MULTICORE + + + + + VULKAN + + + + + OPENGL + + + + + Clear Recent Files + Usuń "Ostatnie pliki" + + + + 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><br>For an explanation of the various Switch formats yuzu supports, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our wiki</a>. This message will not be shown again. + Używasz zdekonstruowanego formatu katalogu ROM dla tej gry, który jest przestarzałym formatem, który został zastąpiony przez inne, takie jak NCA, NAX, XCI lub NSP. W zdekonstruowanych katalogach ROM brakuje ikon, metadanych i obsługi aktualizacji.<br><br> Aby znaleźć wyjaśnienie różnych formatów Switch obsługiwanych przez yuzu,<a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'> sprawdź nasze wiki</a>. Ta wiadomość nie pojawi się 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. + + + + yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.Ensure that you have the latest graphics drivers for your GPU. + yuzu napotkał błąd podczas działania rdzenia wideo, proszę zobaczyć log po więcej szczegółów. Aby uzyskać więcej informacji na temat uzyskiwania dostępu do pliku log, zobacz następującą stronę: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Jak przesłać plik log? </a>Upewnij się, że masz najnowsze sterowniki karty graficznej. + + + + Error while loading ROM! + + + + + 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. + + + + Start + Start + + + + Save Data + + + + + Mod Data + + + + + Error Opening %1 Folder + Błąd podczas otwarcia folderu %1 + + + + + Folder does not exist! + Folder nie istnieje! + + + + Error Opening Transferable Shader Cache + + + + + + A shader cache for this title does not exist. + + + + + Contents + + + + + Update + + + + + DLC + + + + + Remove Entry + + + + + Remove Installed Game %1? + + + + + + + + + Successfully Removed + + + + + Successfully removed the installed base game. + + + + + + + Error Removing %1 + + + + + The base game is not installed in the NAND and cannot be removed. + + + + + Successfully removed the installed update. + + + + + There is no update installed for this title. + + + + + There are no DLC installed for this title. + + + + + Successfully removed %1 installed DLC. + + + + + Delete Transferable Shader Cache? + + + + + Remove Custom Game Configuration? + + + + + Remove File + + + + + + Error Removing Transferable Shader Cache + + + + + Successfully removed the transferable shader cache. + + + + + Failed to remove the transferable shader cache. + + + + + + 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. + + + + + RomFS Extraction Failed! + Wypakowanie RomFS nieudane! + + + + 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 anulował operację. + + + + Full + + + + + Skeleton + + + + + 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. + Proszę wybrać w jaki sposób chcesz, aby zrzut pliku RomFS został wykonany. <br>Pełna kopia ze wszystkimi plikami do nowego folderu, gdy <br>skielet utworzy tylko strukturę folderu. + + + + Extracting RomFS... + Wypakowywanie RomFS... + + + + + Cancel + Anuluj + + + + RomFS Extraction Succeeded! + Wypakowanie RomFS zakończone pomyślnie! + + + + The operation completed successfully. + Operacja zakończona sukcesem. + + + + Error Opening %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. + Folder wybrany przez ciebie nie zawiera 'głownego' pliku. + + + + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) + + + + + Install Files + + + + + Installing file "%1"... + Instalowanie pliku "%1"... + + + + Install Results + + + + + 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" + + + + + Continue + Kontynuuj + + + + Error Display + + + + + Missing yuzu Account + Brakuje konta Yuzu + + + + In order to submit a game compatibility test case, you must link your yuzu account.<br><br/>To link your yuzu account, go to Emulation &gt; Configuration &gt; Web. + Aby przesłać test zgodności gry, musisz połączyć swoje konto yuzu.<br><br/> Aby połączyć swoje konto yuzu, przejdź do opcji Emulacja &gt; Konfiguracja &gt; Sieć. + + + + Error opening URL + + + + + Unable to open the URL "%1". + + + + + Amiibo File (%1);; All Files (*.*) + Plik Amiibo (%1);;Wszyskie pliki (*.*) + + + + Load Amiibo + Załaduj Amiibo + + + + Error opening Amiibo data file + Błąd otwarcia pliku danych Amiibo + + + + Unable to open Amiibo file "%1" for reading. + Nie można otworzyć pliku Amiibo "%1" do odczytu. + + + + Error reading Amiibo data file + Błąd podczas odczytu pliku danych Amiibo + + + + Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes. + Nie można w pełni odczytać danych Amiibo. Oczekiwano odczytu %1 bajtów, ale był on w stanie odczytać tylko %2 bajty. + + + + Error loading Amiibo data + Błąd podczas ładowania pliku danych Amiibo + + + + Unable to load Amiibo data. + Nie można załadować danych Amiibo. + + + + Capture Screenshot + Zrób zrzut ekranu + + + + PNG Image (*.png) + Obrazek PNG (*.png) + + + + Speed: %1% / %2% + Prędkość: %1% / %2% + + + + Speed: %1% + Prędkość: %1% + + + + Game: %1 FPS + Gra: %1 FPS + + + + Frame: %1 ms + Klatka: %1 ms + + + + The game you are trying to load requires additional files from your Switch to be dumped before playing.<br/><br/>For more information on dumping these files, please see the following wiki page: <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + Gra, którą próbujesz wczytać, wymaga dodatkowych plików z Switch'a, które zostaną zrzucone przed graniem.<br/><br/> Aby uzyskać więcej informacji na temat wyrzucania tych plików, odwiedź następującą stronę wiki:<a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'> Zrzut archiw systemu i udostępnionych czcionek z konsoli Nintendo Switch</a>. <br/><br/>Czy chcesz wrócić do listy gier? Kontynuacja emulacji może spowodować awarie, uszkodzone dane zapisu lub inne błędy. + + + + + yuzu was unable to locate a Switch system archive. %1 + + + + + yuzu was unable to locate a Switch system archive: %1. %2 + + + + + System Archive Not Found + Archiwum systemu nie znalezione. + + + + System Archive Missing + + + + + yuzu was unable to locate the Switch shared fonts. %1 + + + + + Shared Fonts Not Found + Czcionki nie zostały znalezione + + + + Shared Font Missing + + + + + Fatal Error + Fatalny błąd + + + + yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + yuzu napotkał błąd, proszę zobaczyć log po więcej szczegółów. Aby uzyskać więcej informacji na temat uzyskiwania dostępu do pliku log, zobacz następującą stronę: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Jak przesłać plik log</a>?<br/><br/> Czy chcesz wrócić do listy gier? Kontynuacja emulacji może spowodować awarie, uszkodzone dane zapisu lub inne błędy. + + + + Fatal Error encountered + + + + + Confirm Key Rederivation + Potwierdź ponowną aktywacje klucza + + + + You are about to force rederive all of your keys. +If you do not know what this means or what you are doing, +this is a potentially destructive action. +Please make sure this is what you want +and optionally make backups. + +This will delete your autogenerated key files and re-run the key derivation module. + Zamierzasz zmusić wszystkie swoje klucze do ponownej aktywacji. +Jeśli nie wiesz, co to oznacza i co robisz, +jest to potencjalnie destrukcyjne działanie. +Upewnij się, że to jest to, czego chcesz +i opcjonalnie tworzyć kopie zapasowe. + +Spowoduje to usunięcie wygenerowanych automatycznie plików kluczy i ponowne uruchomienie modułu pochodnego klucza. + + + + Missing fuses + + + + + - Missing BOOT0 + + + + + - Missing BCPKG2-1-Normal-Main + + + + + - Missing PRODINFO + + + + + Derivation Components Missing + + + + + Components are missing that may hinder key derivation from completing. <br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu quickstart guide</a> to get all your keys and games.<br><br><small>(%1)</small> + + + + + Deriving keys... +This may take up to a minute depending +on your system's performance. + Wyprowadzanie kluczy... +Zależnie od tego może potrwać do minuty +na wydajność twojego systemu. + + + + Deriving Keys + Wyprowadzanie kluczy... + + + + Select RomFS Dump Target + Wybierz cel zrzutu RomFS + + + + Please select which RomFS you would like to dump. + Proszę wybrać RomFS, jakie chcesz zrzucić. + + + + + + yuzu + yuzu + + + + Are you sure you want to close yuzu? + Czy na pewno chcesz zamknąć yuzu? + + + + 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 yuzu to not exit. + +Would you like to bypass this and exit anyway? + + + + + GRenderWindow + + + OpenGL not available! + + + + + yuzu has not been compiled with OpenGL support. + + + + + Vulkan not available! + + + + + yuzu has not been compiled with Vulkan support. + + + + + Error while initializing OpenGL 4.3! + + + + + Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver. + + + + + Error while initializing OpenGL! + + + + + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>Unsupported extensions:<br> + + + + + GameList + + + + Name + Nazwa gry + + + + + Compatibility + Kompatybilność + + + + + Add-ons + Dodatki + + + + + + + File type + Typ pliku + + + + + + + Size + Rozmiar + + + + Open Save Data Location + Otwórz lokalizację zapisów + + + + Open Mod Data Location + Otwórz lokalizację modyfikacji + + + + Open Transferable Shader Cache + + + + + Remove + + + + + Remove Installed Update + + + + + Remove All Installed DLC + + + + + Remove Shader Cache + + + + + Remove Custom Configuration + + + + + Remove All Installed Contents + + + + + Dump RomFS + Zrzuć RomFS + + + + Copy Title ID to Clipboard + Kopiuj identyfikator gry do schowka + + + + Navigate to GameDB entry + Nawiguj do wpisu kompatybilności gry + + + + Properties + Właściwości + + + + Scan Subfolders + + + + + Remove Game Directory + + + + + ▲ Move Up + + + + + ▼ Move Down + + + + + Open Directory Location + + + + + GameListItemCompat + + + Perfect + Perfekcyjnie + + + + Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without +any workarounds needed. + Funkcje gry są bezbłędne, bez żadnych zakłóceń audio i graficznych, wszystkie przetestowane funkcje działają zgodnie z przeznaczeniem bez +wszelkich potrzebnych obejść. + + + + Great + Świetnie + + + + Game functions with minor graphical or audio glitches and is playable from start to finish. May require some +workarounds. + Funkcje gry z drobnymi usterkami graficznymi lub dźwiękowymi i można je odtwarzać od początku do końca. Może wymagać niektórych +obejść. + + + + Okay + W porządku + + + + Game functions with major graphical or audio glitches, but game is playable from start to finish with +workarounds. + Funkcje gry z dużymi usterkami graficznymi lub dźwiękowymi, ale gra jest odtwarzana od początku do końca z użyciem +obejść. + + + + Bad + Zła + + + + Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches +even with workarounds. + Funkcje gry, ale z dużymi usterkami graficznymi lub dźwiękowymi. Nie można wykonać postępu w określonych obszarach z powodu problemów +nawet z obejściami. + + + + Intro/Menu + Intro/Menu + + + + Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start +Screen. + Gra jest całkowicie niemożliwa do zagrania z powodu poważnych usterków graficznych lub dźwiękowych. Nie można przejść ekran +startowy. + + + + 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. + + + + GameListPlaceholder + + + Double-click to add a new folder to the game list + Kliknij podwójnie aby dodać folder do listy gier + + + + GameListSearchField + + + Filter: + Filter: + + + + Enter pattern to filter + Wpisz typ do filtra + + + + InstallDialog + + + Please confirm these are the files you wish to install. + + + + + Installing an Update or DLC will overwrite the previously installed one. + + + + + Install + + + + + Install Files to NAND + + + + + LoadingScreen + + + Loading Shaders 387 / 1628 + + + + + Loading Shaders %v out of %m + + + + + Estimated Time 5m 4s + + + + + Loading... + + + + + Loading Shaders %1 / %2 + + + + + Launching... + Uruchamianie... + + + + Estimated Time %1 + + + + + MainWindow + + + yuzu + yuzu + + + + &File + &Plik + + + + Recent Files + Ostatnie pliki + + + + &Emulation + &Emulacja + + + + &View + &Widok + + + + Debugging + Debugowanie + + + + Tools + Narzędzia + + + + &Help + &Pomoc + + + + Install Files to NAND... + + + + + Load File... + Załaduj plik... + + + + Load Folder... + Załaduj folder... + + + + E&xit + &Wyjście + + + + &Start + &Start + + + + &Pause + &Pauza + + + + &Stop + &Stop + + + + Reinitialize keys... + Reinicjalizuj klucze... + + + + About yuzu + O yuzu + + + + Single Window Mode + Tryb jednego okna + + + + Configure... + Konfiguruj... + + + + Display Dock Widget Headers + Wyświetlaj nagłówki Dock Widget + + + + Show Filter Bar + Pokaż pasek fitrowania + + + + Show Status Bar + Pokaż pasek statusu + + + + Reset Window Size + + + + + Fullscreen + Pełny ekran + + + + Restart + Zrestartuj + + + + Load Amiibo... + Załaduj Amiibo... + + + + Report Compatibility + Zgłoś kompatybilność + + + + Open Mods Page + + + + + Open Quickstart Guide + + + + + FAQ + + + + + Open yuzu Folder + Otwórz folder yuzu + + + + Capture Screenshot + Zrób zrzut ekranu + + + + Configure Current Game.. + + + + + MicroProfileDialog + + + MicroProfile + MicroProfile + + + + QObject + + + Installed SD Titles + + + + + Installed NAND Titles + + + + + System Titles + + + + + Add New Game Directory + + + + + + + Shift + Shift + + + + + + Ctrl + Ctrl + + + + + + Alt + Alt + + + + + + + [not set] + [nie ustawione] + + + + + + Hat %1 %2 + Krzyżak %1 %2 + + + + + + Axis %1%2 + Oś %1%2 + + + + + + Button %1 + Przycisk %1 + + + + + + + [unknown] + [nieznane] + + + + + Click 0 + + + + + + Click 1 + + + + + + Click 2 + + + + + + Click 3 + + + + + + Click 4 + + + + + GC Axis %1%2 + + + + + GC Button %1 + + + + + + [unused] + [nieużywane] + + + + + Axis %1 + Oś %1 + + + + + GC Axis %1 + + + + + QtErrorDisplay + + + An error has occured. +Please try again or contact the developer of the software. + +Error Code: %1-%2 (0x%3) + + + + + An error occured on %1 at %2. +Please try again or contact the developer of the software. + +Error Code: %3-%4 (0x%5) + + + + + An error has occured. +Error Code: %1-%2 (0x%3) + +%4 + +%5 + + + + + QtProfileSelectionDialog + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Select a user: + Wybierz użytkownika: + + + + Users + + + + + Profile Selector + Wybór profilu + + + + QtSoftwareKeyboardDialog + + + Enter text: + Wpisz tekst: + + + + Software Keyboard + Klawiatura systemowa + + + + SequenceDialog + + + Enter a hotkey + + + + + WaitTreeCallstack + + + Call stack + Stos wywołań + + + + WaitTreeMutexInfo + + + waiting for mutex 0x%1 + czekam na mutex 0x%1 + + + + has waiters: %1 + ma oczekujących: %1 + + + + owner handle: 0x%1 + uchwyt właściciela: 0x%1 + + + + WaitTreeObjectList + + + waiting for all objects + czekam na wszystkie objekty + + + + waiting for one of the following objects + oczekiwanie na jeden z następujących obiektów + + + + WaitTreeSynchronizationObject + + + [%1]%2 %3 + + + + + waited by no thread + + + + + WaitTreeThread + + + + running + Uruchomione + + + + ready + Gotowe + + + + + paused + Spauzowana + + + + waiting for HLE return + czekam na powrót HLE + + + + sleeping + spanie + + + + waiting for IPC reply + czekam na odpowiedź IPC + + + + waiting for objects + oczekiwanie na obiekty + + + + waiting for mutex + czekam na mutex + + + + waiting for condition variable + + + + + waiting for address arbiter + czekam na arbitra adresu + + + + dormant + drzemiący + + + + dead + śmierć + + + + PC = 0x%1 LR = 0x%2 + PC = 0x%1 LR = 0x%2 + + + + ideal + + + + + core %1 + rdzeń %1 + + + + Unknown processor %1 + Nieznany procesor %1 + + + + processor = %1 + procesor = %1 + + + + ideal core = %1 + idealny rdzeń = %1 + + + + affinity mask = %1 + maska powinowactwa = %1 + + + + thread id = %1 + identyfikator wątku = %1 + + + + priority = %1(current) / %2(normal) + piorytet = %1(obecny) / %2(normalny) + + + + last running ticks = %1 + ostatnie działające kleszcze = %1 + + + + not waiting for mutex + nie czekam na mutex + + + + WaitTreeThreadList + + + waited by thread + czekanie na wątek + + + + WaitTreeWidget + + + Wait Tree + Drzewo czekania + + + \ No newline at end of file diff --git a/dist/languages/pt_BR.ts b/dist/languages/pt_BR.ts new file mode 100644 index 000000000..c842793f4 --- /dev/null +++ b/dist/languages/pt_BR.ts @@ -0,0 +1,4757 @@ + + + AboutDialog + + + About yuzu + Sobre o yuzu + + + + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + + + + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + + + + <html><head/><body><p>%1 | %2-%3 (%4)</p></body></html> + <html><head/><body><p>%1 | %2-%3 (%4)</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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu é um emulador experimental de código aberto para o Nintendo Switch licenciado sob a GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">Esse programa não deve ser utilizado para jogar jogos que você não obteve legalmente.</span></p></body></html> + + + + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Site</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Código fonte</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contribuidores</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">Licença</span></a></p></body></html> + + + + <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.</span></p></body></html> + <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; é uma marca comercial da Nintendo. O yuzu não é afiliado com a Nintendo de nenhuma forma.</span></p></body></html> + + + + CalibrationConfigurationDialog + + + Communicating with the server... + Comunicando com o servidor... + + + + Cancel + Cancelar + + + + Touch the top left corner <br>of your touchpad. + Toque no canto superior esquerdo <br>do seu touchpad + + + + Now touch the bottom right corner <br>of your touchpad. + Agora toque no canto inferior direito <br>do seu touchpad + + + + Configuration completed! + Configuração concluída! + + + + OK + OK + + + + CompatDB + + + Report Compatibility + Informar compatibilidade + + + + + Report Game Compatibility + Informar compatibilidade de jogo + + + + <html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu 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 yuzu 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 yuzu account</li></ul></body></html> + <html><head/><body><p><span style=" font-size:10pt;">Ao enviar um caso de teste à </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">lista de compatibilidade do yuzu</span></a><span style=" font-size:10pt;">, as seguintes informações serão recolhidas 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 yuzu você está usando</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A conta do yuzu conectada</li></ul></body></html> + + + + Perfect + Perfeito + + + + <html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html> + <html><head/><body><p>O jogo funciona impecavelmente, sem falhas no áudio ou nos gráficos.</p></body></html> + + + + Great + Ótimo + + + + <html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html> + <html><head/><body><p>O jogo funciona com leves falhas gráficas ou de áudio e é jogável do início ao fim. Pode exigir algumas soluções alternativas.</p></body></html> + + + + Okay + Razoável + + + + <html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html> + <html><head/><body><p>O jogo funciona com grandes falhas gráficas ou de áudio, mas é jogável do início ao fim com o uso de soluções alternativas.</p></body></html> + + + + Bad + Ruim + + + + <html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html> + <html><head/><body><p>O jogo funciona, só que com problemas significativos nos gráficos ou no áudio. Não é possível progredir em áreas específicas por conta de tais problemas, mesmo com soluções alternativas.</p></body></html> + + + + Intro/Menu + Intro/menu + + + + <html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html> + <html><head/><body><p>É impossível jogar o jogo devido a grandes problemas nos gráficos ou no áudio. Não é possível passar da tela inicial do jogo.</p></body></html> + + + + Won't Boot + Não inicia + + + + <html><head/><body><p>The game crashes when attempting to startup.</p></body></html> + <html><head/><body><p>O jogo trava ou se encerra abruptamente ao se tentar iniciá-lo.</p></body></html> + + + + <html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html> + <html><head/><body><p>Sem considerar velocidade e desempenho, quão bem o jogo se comporta, do início ao fim, nesta versão do yuzu?</p></body></html> + + + + Thank you for your submission! + Agradecemos pelo seu relatório! + + + + Submitting + Enviando + + + + Communication error + Erro de comunicação + + + + An error occured while sending the Testcase + Um erro ocorreu ao enviar o caso de teste + + + + Next + Próximo + + + + ConfigureAudio + + + Audio + Áudio + + + + Output Engine: + Mecanismo de saída: + + + + This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency. + Este efeito de pós-processamento ajusta a velocidade do áudio para acompanhar a velocidade de emulação e ajuda a evitar cortes no áudio. No entanto, isto aumenta a latência do áudio. + + + + Enable audio stretching + Ativar alongamento de áudio + + + + Audio Device: + Dispositivo de áudio: + + + + Use global volume + Usar volume global + + + + Set volume: + Definir volume: + + + + Volume: + Volume: + + + + 0 % + 0 % + + + + %1% + Volume percentage (e.g. 50%) + %1% + + + + ConfigureCpu + + + Form + Formulário + + + + General + Geral + + + + Accuracy: + Precisão: + + + + Accurate + Preciso + + + + Unsafe + Não seguro + + + + Enable Debug Mode + Ativar modo de depuração + + + + We recommend setting accuracy to "Accurate". + Recomendamos definir a precisão para "Preciso". + + + + Unsafe CPU Optimization Settings + Ajustes de otimização não seguros de CPU + + + + These settings reduce accuracy for speed. + Estes ajustes reduzem a precisão para aprimorar a velocidade. + + + + Unfuse FMA (improve performance on CPUs without FMA) + Não usar FMA (melhora o desempenho em CPUs sem FMA) + + + + + <div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div> + + + <div>Esta opção melhora o desempenho reduzindo a precisão de instruções de multiplicação-adição unificada (FMA) em CPUs sem suporte nativo a este recurso.</div> + + + + + Faster FRSQRTE and FRECPE + FRSQRTE e FRECPE mais rápidos + + + + + <div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div> + + + <div>Esta opção melhora o desempenho de algumas instruções de ponto flutuante usando aproximações nativas menos precisas.</div> + + + + + CPU settings are available only when game is not running. + Os ajustes de CPU só estão disponíveis enquanto o jogo não estiver em execução. + + + + Setting CPU to Debug Mode + Ativando o modo de depuração de CPU + + + + CPU Debug Mode is only intended for developer use. Are you sure you want to enable this? + O modo de depuração de CPU é intencionado para uso por desenvolvedores. Deseja mesmo ativá-lo? + + + + ConfigureCpuDebug + + + Form + Formulário + + + + Toggle CPU Optimizations + Ative ou desative otimizações de CPU + + + + + <div> + <b>For debugging only.</b> + <br> + If you're not sure what these do, keep all of these enabled. + <br> + These settings only take effect when CPU Accuracy is "Debug Mode". + </div> + + + <div> + <b>Apenas para depuração.</b> + <br> + Se você não tem certeza do que estas opções fazem, mantenha-as todas ativadas. + <br> + Estes ajustes apenas têm efeito quando a precisão da CPU for definida como "Ativar modo de depuração". + </div> + + + + + Enable inline page tables + Ativar tabelas de página em linha + + + + + <div style="white-space: nowrap">This optimization speeds up memory accesses by the guest program.</div> + <div style="white-space: nowrap">Enabling it inlines accesses to PageTable::pointers into emitted code.</div> + <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">Esta otimização acelera acessos de memória pelo programa convidado.</div> + <div style="white-space: nowrap">Quando ativada, permite o acesso inline a PageTable::pointers no código emitido.</div> + <div style="white-space: nowrap">Quando desativada, força a passagem de todos os acessos de memória pelas funções Memory::Read/Memory::Write.</div> + + + + + Enable block linking + Ativar vinculação de blocos + + + + + <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>Esta otimização evita buscas do dispatcher ao permitir que blocos básicos emitidos pulem diretamente para outros blocos básicos se o PC de destino for estático.</div> + + + + + Enable return stack buffer + Ativar buffer de pilha de retorno + + + + + <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>Esta otimização evita buscas do dispatcher ao monitorar possíveis endereços de retorno de instruções BL. Isto se aproxima do que ocorre em um buffer de pilha de retorno em um CPU real.</div> + + + + + Enable fast dispatcher + Ativar dispatcher rápido + + + + + <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>Ativa um sistema de dispatch de dois níveis. Um dispatcher mais rápido, escrito em assembly e que possui um pequeno cache MRU de destinos de pulo, é usado primeiro. Caso falhe, o dispatcher mais lento escrito em C++ será usado em seu lugar.</div> + + + + + Enable context elimination + Ativar eliminação de contexto + + + + + <div>Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.</div> + + + <div>Ativa uma otimização da IR que reduz acessos desnecessários à estrutura de contexto da CPU.</div> + + + + + Enable constant propagation + Ativar propagação de constantes + + + + + <div>Enables IR optimizations that involve constant propagation.</div> + + + <div>Ativa otimizações da IR que envolvem propagação de constantes.</div> + + + + + Enable miscellaneous optimizations + Ativar otimizações diversas + + + + + <div>Enables miscellaneous IR optimizations.</div> + + + <div>Ativa otimizações diversas para a IR.</div> + + + + + Enable misalignment check reduction + Ativar redução de checagem de desalinhamento + + + + + <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">Quando ativada, um desalinhamento só será disparado quando um acesso cruza o limite de uma página.</div> + <div style="white-space: nowrap">Quando desativada, um desalinhamento será disparado em todos os acessos desalinhados.</div> + + + + + CPU settings are available only when game is not running. + Os ajustes de CPU estão disponíveis apenas quando não houver nenhum jogo em execução. + + + + ConfigureDebug + + + Form + Formulário + + + + GDB + GDB + + + + Enable GDB Stub + Ativar GDB stub + + + + Port: + Porta: + + + + Logging + Registros de depuração + + + + Global Log Filter + Filtro global de registros + + + + Show Log Console (Windows Only) + Mostrar console de registros (apenas Windows) + + + + Open Log Location + Abrir local dos registros + + + + Homebrew + Homebrew + + + + Arguments String + Linha de argumentos + + + + Graphics + Gráficos + + + + When checked, the graphics API enters in a slower debugging mode + Quando ativado, a API gráfica entra em um modo de depuração mais lento. + + + + Enable Graphics Debugging + Ativar depuração de gráficos + + + + When checked, it disables the macro Just In Time compiler. Enabled 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 + Desativar macro JIT + + + + Dump + Extrair + + + + Enable Verbose Reporting Services + Ativar serviços de relatório detalhado + + + + This will be reset automatically when yuzu closes. + Isto será restaurado automaticamente assim que o yuzu for fechado. + + + + Advanced + Avançado + + + + Kiosk (Quest) Mode + Modo quiosque (Quest) + + + + ConfigureDebugController + + + Configure Debug Controller + Configurar controle de depuração + + + + Clear + Limpar + + + + Defaults + Padrão + + + + ConfigureDialog + + + yuzu Configuration + Configurações do yuzu + + + + + + + General + Geral + + + + + UI + Interface + + + + Game List + Lista de jogos + + + + + + + System + Sistema + + + + + + Profiles + Perfis + + + + + + Filesystem + Sistema de arquivos + + + + + + + Controls + Controles + + + + + + Hotkeys + Teclas de atalho + + + + + + + CPU + CPU + + + + + + + + + Debug + Depuração + + + + + + + Graphics + Gráficos + + + + + Advanced + Avançado + + + + GraphicsAdvanced + GráficosAvançado + + + + + + + Audio + Áudio + + + + + + Web + Rede + + + + + + Services + Serviços + + + + ConfigureFilesystem + + + Form + Formulário + + + + Storage Directories + Pastas de armazenamento + + + + NAND + NAND + + + + + + + + + ... + ... + + + + SD Card + Cartão SD + + + + Gamecard + Cartão de jogo + + + + Path + Caminho + + + + Inserted + Inserido + + + + Current Game + Jogo atual + + + + Patch Manager + Gerenciador de patches + + + + Dump Decompressed NSOs + Extrair NSOs descompactados + + + + Dump ExeFS + Extrair ExeFS + + + + Mod Load Root + Raiz de carregamento de mods + + + + Dump Root + Extrair raiz + + + + Caching + Ajustes de cache + + + + Cache Directory + Diretório do cache + + + + Cache Game List Metadata + Metadados da lista de jogos em cache + + + + + + + Reset Metadata Cache + Restaurar cache de metadados + + + + Select Emulated NAND Directory... + Selecione a pasta da NAND emulada... + + + + Select Emulated SD Directory... + Selecione a pasta do SD emulado... + + + + Select Gamecard Path... + Selecione o local do Gamecard... + + + + Select Dump Directory... + Selecione a pasta de extração... + + + + Select Mod Load Directory... + Selecione a pasta de carregamento de mods... + + + + Select Cache Directory... + Selecione a pasta de cache... + + + + The metadata cache is already empty. + O cache de metadados já está vazio. + + + + The operation completed successfully. + A operação foi concluída com sucesso. + + + + The metadata cache couldn't be deleted. It might be in use or non-existent. + O cache de metadados não pôde ser excluído. Ele pode estar em uso no momento ou não existe. + + + + ConfigureGeneral + + + Form + Formulário + + + + General + Geral + + + + Limit Speed Percent + Limitar percentual de velocidade + + + + % + % + + + + Multicore CPU Emulation + Emulação de CPU multinúcleo + + + + Confirm exit while emulation is running + Confirmar saída quando a emulação estiver em execução + + + + Prompt for user on game boot + Escolher um usuário ao iniciar um jogo + + + + Pause emulation when in background + Pausar emulação quando a janela ficar em segundo plano + + + + Hide mouse on inactivity + Esconder cursor do mouse quando em inatividade + + + + ConfigureGraphics + + + Form + Formulário + + + + API Settings + Configurações de API + + + + API: + API: + + + + Device: + Dispositivo: + + + + Graphics Settings + Configurações gráficas + + + + Use disk shader cache + Usar cache de shaders em disco + + + + Use asynchronous GPU emulation + Usar emulação assíncrona da GPU + + + + Aspect Ratio: + Proporção de tela: + + + + Default (16:9) + Padrão (16:9) + + + + Force 4:3 + Forçar 4:3 + + + + Force 21:9 + Forçar 21:9 + + + + Stretch to Window + Esticar para a janela + + + + + Use global background color + Usar cor de fundo global + + + + Set background color: + Configurar cor de fundo: + + + + Background Color: + Cor de fundo: + + + + OpenGL Graphics Device + Dispositivo gráfico do OpenGL + + + + ConfigureGraphicsAdvanced + + + Form + Formulário + + + + Advanced Graphics Settings + Configurações gráficas avançadas + + + + Accuracy Level: + Nível de precisão: + + + + VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference. + A sincronização vertical (VSync) evita que as imagens do jogo pareçam cortadas, porém algumas placas gráficas apresentam redução de desempenho quando estiver ativa. Deixe-a ativada se você não reparar alguma diferença de desempenho. + + + + Use VSync (OpenGL only) + Usar sincronização vertical (apenas OpenGL) + + + + Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental. + Ativar isto reduz engasgos de shaders. Ativa os shaders assembly do OpenGL em dispositivos compatíveis da Nvidia (é necessária a extensão NV_gpu_program5). Esta é uma opção experimental. + + + + Use assembly shaders (experimental, Nvidia OpenGL only) + Usar shaders assembly (experimental, apenas OpenGL + Nvidia) + + + + Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental. + Realiza a compilação de shaders de forma assíncrona, o que pode reduzir engasgos de shaders. Esta opção é experimental. + + + + Use asynchronous shader building (experimental) + Usar compilação assíncrona de shaders (experimental) + + + + Use Fast GPU Time + Usar tempo de resposta rápido da GPU + + + + Anisotropic Filtering: + Filtragem anisotrópica: + + + + Default + Padrão + + + + 2x + 2x + + + + 4x + 4x + + + + 8x + 8x + + + + 16x + 16x + + + + ConfigureHotkeys + + + Hotkey Settings + Configurações de teclas de atalho + + + + Double-click on a binding to change it. + Clique duas vezes em um atalho para alterá-lo. + + + + Clear All + Limpar tudo + + + + Restore Defaults + Restaurar padrões + + + + Action + Ação + + + + Hotkey + Atalho + + + + Context + Contexto + + + + + Conflicting Key Sequence + Combinação de teclas já utilizada + + + + The entered key sequence is already assigned to: %1 + A sequência de teclas pressionada já esta atribuída para: %1 + + + + Restore Default + Restaurar padrão + + + + Clear + Limpar + + + + The default key sequence is already assigned to: %1 + A sequência de teclas padrão já esta atribuida para: %1 + + + + ConfigureInput + + + ConfigureInput + ConfigurarEntrada + + + + + Player 1 + Jogador 1 + + + + + Player 2 + Jogador 2 + + + + + Player 3 + Jogador 3 + + + + + Player 4 + Jogador 4 + + + + + Player 5 + Jogador 5 + + + + + Player 6 + Jogador 6 + + + + + Player 7 + Jogador 7 + + + + + Player 8 + Jogador 8 + + + + + Advanced + Avançado + + + + Console Mode + Modo do console + + + + Docked + Na base + + + + Undocked + Fora da base + + + + Vibration + Vibração + + + + % + % + + + + Motion + Movimento + + + + Configure + Configurar + + + + Controllers + Controles + + + + 1 + 1 + + + + 2 + 2 + + + + 3 + 3 + + + + 4 + 4 + + + + 5 + 5 + + + + 6 + 6 + + + + 7 + 7 + + + + 8 + 8 + + + + Connected + Conectado + + + + Defaults + Padrões + + + + Clear + Limpar + + + + ConfigureInputAdvanced + + + Configure Input + Configurar entrada + + + + Joycon Colors + Cores dos Joycon + + + + Player 1 + Jogador 1 + + + + + + + + + + + L Body + Joycon esq. + + + + + + + + + + + L Button + Botão L + + + + + + + + + + + R Body + Joycon dir. + + + + + + + + + + + R Button + Botão R + + + + Player 2 + Jogador 2 + + + + Player 3 + Jogador 3 + + + + Player 4 + Jogador 4 + + + + Player 5 + Jogador 5 + + + + Player 6 + Jogador 6 + + + + Player 7 + Jogador 7 + + + + Player 8 + Jogador 8 + + + + Other + Outro + + + + Keyboard + Teclado + + + + + Advanced + Avançado + + + + Touchscreen + Touchscreen + + + + Mouse + Mouse + + + + Motion / Touch + Movimento/toque + + + + + Configure + Configurar + + + + Debug Controller + Controle de depuração + + + + ConfigureInputPlayer + + + Configure Input + Configurar controles + + + + Connect Controller + Conectar controle + + + + + + Pro Controller + Pro Controller + + + + + Dual Joycons + Par de Joycons + + + + + Left Joycon + Joycon Esquerdo + + + + + Right Joycon + Joycon Direito + + + + + Handheld + Portátil + + + + Input Device + Dispositivo de entrada + + + + Any + Qualquer + + + + Keyboard/Mouse + Teclado/mouse + + + + Profile + Perfil + + + + Save + Salvar + + + + New + Novo + + + + Delete + Excluir + + + + Left Stick + Analógico esquerdo + + + + + + + + + Up + Cima + + + + + + + + + Left + Esquerda + + + + + + + + + Right + Direita + + + + + + + + + Down + Baixo + + + + + + + Pressed + Pressionado + + + + + + + Modifier + Modificador + + + + + Range + Alcance + + + + + % + % + + + + + Deadzone: 0% + Zona morta: 0% + + + + + Modifier Range: 0% + Alcance de modificador: 0% + + + + D-Pad + D-pad + + + + + L + L + + + + + ZL + ZL + + + + + Minus + Menos + + + + + Capture + Capturar + + + + + Plus + Mais + + + + + Home + Botão Home + + + + + R + R + + + + + ZR + ZR + + + + + SL + SL + + + + + SR + SR + + + + Face Buttons + Botões de rosto + + + + + X + X + + + + + Y + Y + + + + + A + A + + + + + B + B + + + + Right Stick + Analógico direito + + + + + Deadzone: %1% + Zona morta: %1% + + + + + Modifier Range: %1% + Alcance de modificador: %1% + + + + [waiting] + [esperando] + + + + ConfigureMotionTouch + + + Configure Motion / Touch + Configurar movimento/toque + + + + Motion + Movimento + + + + Motion Provider: + Fonte dos dados de movimento: + + + + Sensitivity: + Sensibilidade: + + + + Touch + Toque + + + + Touch Provider: + Fonte dos dados de toque: + + + + Calibration: + Calibração: + + + + (100, 50) - (1800, 850) + (100, 50) - (1800, 850) + + + + + + Configure + Configurar + + + + Use button mapping: + Usar mapeamento de botões: + + + + CemuhookUDP Config + Configuração CemuhookUDP + + + + You may use any Cemuhook compatible UDP input source to provide motion and touch input. + Você pode utilizar qualquer dispositivo de entrada compatível com o Cemuhook UDP para prover dados de movimento e toque. + + + + Server: + Servidor: + + + + Port: + Porta: + + + + Pad: + Direcionais: + + + + Pad 1 + Direcional 1 + + + + Pad 2 + Direcional 2 + + + + Pad 3 + Direcional 3 + + + + Pad 4 + Direcional 4 + + + + Learn More + Saiba mais + + + + + Test + Teste + + + + Mouse (Right Click) + Mouse (botão direito) + + + + + CemuhookUDP + CemuhookUDP + + + + Emulator Window + Janela do emulador + + + + <a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Learn More</span></a> + <a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Saiba mais</span></a> + + + + Testing + Testando + + + + Configuring + Configurando + + + + Test Successful + Teste bem-sucedido + + + + Successfully received data from the server. + Dados foram recebidos do servidor com sucesso. + + + + Test Failed + O 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>Verifique se o servidor foi configurado corretamente e o endereço e porta estão corretos. + + + + Citra + Citra + + + + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. + Um teste UDP ou configuração de calibração está em curso no momento.<br>Aguarde até a sua conclusão. + + + + ConfigureMouseAdvanced + + + Configure Mouse + Configurar mouse + + + + Mouse Buttons + Botões do mouse + + + + Forward: + Dianteiro: + + + + Back: + Traseiro: + + + + Left: + Esquerdo: + + + + Middle: + Meio: + + + + Right: + Direito: + + + + + Clear + Limpar + + + + Defaults + Padrões + + + + [not set] + [não definido] + + + + Restore Default + Restaurar padrão + + + + [press key] + [pressione uma tecla] + + + + ConfigurePerGame + + + Dialog + Diálogo + + + + Info + Informações + + + + Name + Nome + + + + Title ID + ID do título + + + + Filename + Nome do arquivo + + + + Format + Formato + + + + Version + Versão + + + + Size + Tamanho + + + + Developer + Desenvolvedor + + + + Add-Ons + Adicionais + + + + General + Geral + + + + System + Sistema + + + + Graphics + Gráficos + + + + Adv. Graphics + Gráficos avanç. + + + + Audio + Áudio + + + + Properties + Propriedades + + + + Use global configuration (%1) + Usar configuração global (%1) + + + + ConfigurePerGameAddons + + + Form + Formulário + + + + Patch Name + Nome do patch + + + + Version + Versão + + + + ConfigureProfileManager + + + Form + Formulário + + + + Profile Manager + Gerenciador de perfis + + + + Current User + Usuário atual + + + + Username + Nome de usuário + + + + Set Image + Definir imagem + + + + Add + Adicionar + + + + Rename + Renomear + + + + Remove + Excluir + + + + Profile management is available only when game is not running. + Esta tela só fica disponível apenas quando não houver nenhum jogo em execução. + + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Enter Username + Escreva o nome de usuário + + + + Users + Usuários + + + + Enter a username for the new user: + Digite o nome do novo usuário: + + + + Enter a new username: + Digite um novo nome de usuário: + + + + Confirm Delete + Confirmar exclusão + + + + You are about to delete user with name "%1". Are you sure? + Você está prestes a excluir o usuário "%1". Tem certeza? + + + + Select User Image + Selecione a imagem do usuário + + + + JPEG Images (*.jpg *.jpeg) + Imagens JPEG (*.jpg *.jpeg) + + + + Error deleting image + Erro ao excluir a imagem + + + + Error occurred attempting to overwrite previous image at: %1. + Ocorreu um erro ao tentar substituir a imagem anterior em: %1. + + + + Error deleting file + Erro ao excluir arquivo + + + + Unable to delete existing file: %1. + Não foi possível excluir o arquivo existente: %1. + + + + Error creating user image directory + Erro ao criar a pasta de imagens do usuário + + + + Unable to create directory %1 for storing user images. + Não foi possível criar a pasta %1 para armazenar as imagens do usuário. + + + + Error copying user image + Erro ao copiar a imagem do usuário + + + + Unable to copy image from %1 to %2 + Não foi possível copiar a imagem de %1 para %2 + + + + ConfigureService + + + Form + Formulário + + + + BCAT + BCAT + + + + BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content. + BCAT é a maneira que a Nintendo usa para enviar dados aos jogos para engajar sua comunidade e desbloquear conteúdo adicional. + + + + BCAT Backend + Backend do BCAT + + + + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Learn more about BCAT, Boxcat, and Current Events</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Saiba mais sobre o BCAT, Boxcat e eventos atuais</span></a></p></body></html> + + + + The boxcat service is offline or you are not connected to the internet. + O serviço Boxcat está offline ou você não está conectado à internet. + + + + There was an error while processing the boxcat event data. Contact the yuzu developers. + Ocorreu um erro ao processar os dados de eventos do Boxcat. Entre em contato com os desenvolvedores do yuzu. + + + + The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu. + A versão do yuzu que você está usando é ou recente demais ou antiga demais para o servidor. Tente atualizar para a versão oficial mais recente do yuzu. + + + + + There are currently no events on boxcat. + Não há eventos no Boxcat atualmente. + + + + + Current Boxcat Events + Eventos Boxcat atuais + + + + Yuzu is retrieving the latest boxcat status... + O yuzu está baixando o status mais recente do Boxcat... + + + + ConfigureSystem + + + Form + Formulário + + + + System Settings + Configurações de sistema + + + + Region: + Região: + + + + Auto + Automático + + + + Default + Padrão + + + + CET + CET + + + + CST6CDT + CST6CDT + + + + Cuba + Cuba + + + + EET + EET + + + + Egypt + Egito + + + + 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 + Islândia + + + + Iran + Irã + + + + Israel + Israel + + + + Jamaica + Jamaica + + + + + Japan + Japão + + + + Kwajalein + Ilhas Marshall + + + + 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 + + + + USA + EUA + + + + Europe + Europa + + + + Australia + Austrália + + + + China + China + + + + Korea + Coréia + + + + Taiwan + Taiwan + + + + Time Zone: + Fuso horário: + + + + Note: this can be overridden when region setting is auto-select + Nota: isso pode ser substituído caso a configuração de região automática esteja ativada + + + + Japanese (日本語) + Japônes (日本語) + + + + English + Inglês (English) + + + + 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 + + + + Russian (Русский) + Russo (Русский) + + + + Taiwanese + Taiwanês + + + + British English + Inglês britânico (British English) + + + + Canadian French + Francês canadense (Canadian French) + + + + Latin American Spanish + Espanhol latino-americano + + + + Simplified Chinese + Chinês simplificado + + + + Traditional Chinese (正體中文) + Chinês tradicional (正體中文) + + + + Custom RTC + Data e hora personalizada + + + + Language + Idioma + + + + RNG Seed + Semente RNG + + + + Mono + Mono + + + + Stereo + Estéreo + + + + Surround + Surround + + + + Console ID: + ID do console: + + + + Sound output mode + Modo de saída de som + + + + d MMM yyyy h:mm:ss AP + d MMM yyyy h:mm:ss AP + + + + Regenerate + Regerar + + + + System settings are available only when game is not running. + As configurações de sistema são acessíveis apenas quando não houver nenhum jogo em execução. + + + + This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue? + Isto substituirá o seu Switch virtual atual por um novo. O seu Switch virtual atual não poderá ser recuperado. Isto pode causar efeitos inesperados em jogos. Isto pode falhar caso você use um jogo salvo com configurações desatualizadas registradas nele. Continuar? + + + + Warning + Aviso + + + + Console ID: 0x%1 + ID do console: 0x%1 + + + + ConfigureTouchFromButton + + + Configure Touchscreen Mappings + Configurar mapeamento de toque + + + + Mapping: + Mapeamento: + + + + New + Novo + + + + Delete + Excluir + + + + Rename + Renomear + + + + 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. + Clique na área inferior para adicionar um ponto, e então pressione um botão para mapear. +Mova os pontos para mudar a posição, ou clique duas vezes nas células da tabela para editar os valores. + + + + Delete Point + Excluir ponto + + + + Button + Botão + + + + X + X axis + X + + + + Y + Y axis + Y + + + + New Profile + Novo perfil + + + + Enter the name for the new profile. + Insira o nome do novo perfil. + + + + Delete Profile + Excluir perfil + + + + Delete profile %1? + Excluir perfil %1? + + + + Rename Profile + Renomear perfil + + + + New name: + Novo nome: + + + + [press key] + [pressione a tecla] + + + + ConfigureTouchscreenAdvanced + + + Configure Touchscreen + Configurar tela de toque + + + + Warning: The settings in this page affect the inner workings of yuzu'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. + Aviso: Os ajustes nesta página afetam o funcionamento interno da tela de toque emulada do yuzu. Alterá-los pode resultar em comportamentos indesejáveis, tais como a tela de toque funcionar parcialmente ou não responder por completo. Apenas faça alterações caso você saiba o que está fazendo. + + + + Touch Parameters + Parâmetros de toque + + + + Touch Diameter Y + Diâmetro de toque Y + + + + Finger + Dedo + + + + Touch Diameter X + Diâmetro de toque X + + + + Rotational Angle + Ângulo rotacional + + + + Restore Defaults + Restaurar padrões + + + + ConfigureUi + + + Form + Formulário + + + + General + Geral + + + + Note: Changing language will apply your configuration. + Nota: alterar o idioma aplicará as suas configurações. + + + + Interface language: + Idioma da interface: + + + + Theme: + Tema: + + + + Game List + Lista de jogos + + + + Show Add-Ons Column + Mostrar coluna de adicionais + + + + Icon Size: + Tamanho do ícone: + + + + Row 1 Text: + Texto da 1ª linha: + + + + Row 2 Text: + Texto da 2ª linha: + + + + Screenshots + Capturas de tela + + + + Ask Where To Save Screenshots (Windows Only) + Perguntar onde salvar capturas de tela (apenas Windows) + + + + Screenshots Path: + Pasta para capturas de tela: + + + + ... + ... + + + + Select Screenshots Path... + Selecione a pasta de capturas de tela... + + + + <System> + <System> + + + + English + Inglês + + + + ConfigureWeb + + + Form + Formulário + + + + yuzu Web Service + yuzu Web Service + + + + By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information. + Ao informar seu usuário e token, você concorda em permitir ao yuzu recolher dados de uso adicionais, que podem incluir informações de identificação de usuário. + + + + + Verify + Verificar + + + + Sign up + Cadastrar-se + + + + Token: + Token: + + + + Username: + Nome de usuário: + + + + What is my token? + Qual é o meu token? + + + + Telemetry + Telemetria + + + + Share anonymous usage data with the yuzu team + Compartilhar anonimamente dados de uso com a equipe do yuzu + + + + Learn more + Saiba mais + + + + Telemetry ID: + ID de telemetria: + + + + Regenerate + Gerar um novo + + + + Discord Presence + Presença no Discord + + + + Show Current Game in your Discord Status + Mostrar o jogo atual no seu status do Discord + + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Learn more</span></a> + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Saiba mais</span></a> + + + + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Sign up</span></a> + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Cadastrar-se</span></a> + + + + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">What is my token?</span></a> + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">Qual é o meu token?</span></a> + + + + + Telemetry ID: 0x%1 + ID de telemetria: 0x%1 + + + + + Unspecified + Não especificado + + + + Token not verified + Token não verificado + + + + Token was not verified. The change to your token has not been saved. + O token não foi verificado. A alteração no seu token não foi salva. + + + + Verifying... + Verificando... + + + + Verification failed + Falha na verificação + + + + Verification failed. Check that you have entered your token correctly, and that your internet connection is working. + Falha na verificação. Verifique se o seu token foi inserido corretamente e se a sua conexão à internet está funcionando. + + + + GMainWindow + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous data is collected</a> to help improve yuzu. <br/><br/>Would you like to share your usage data with us? + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Dados anônimos são recolhidos</a> para ajudar a melhorar o yuzu. <br/><br/>Gostaria de compartilhar os seus dados de uso conosco? + + + + Telemetry + Telemetria + + + + Text Check Failed + Falha na verificação de texto + + + + Loading Web Applet... + Carregando applet web... + + + + Exit Web Applet + Sair do applet web + + + + Exit + Sair + + + + To exit the web application, use the game provided controls to select exit, select the 'Exit Web Applet' option in the menu bar, or press the 'Enter' key. + Para sair do aplicativo web, use os controles fornecidos pelo jogo para sair dele, selecione a opção "Sair do applet web" na barra de menu ou pressione a tecla "Enter". + + + + Web Applet + Applet web + + + + This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested. + Esta versão do yuzu foi compilada sem suporte a QtWebEngine, o que significa que o yuzu não pode exibir adequadamente o manual do jogo ou a página da web solicitada. + + + + The amount of shaders currently being built + A quantidade de shaders sendo construídos + + + + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. + Velocidade atual de emulação. Valores maiores ou menores que 100% indicam que a emulação está rodando mais rápida ou lentamente que em 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á exibindo atualmente. Isto irá variar de jogo para jogo e cena para 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. + Tempo que leva para emular um quadro do Switch, sem considerar o limitador de taxa de quadros ou a sincronização vertical. Um valor menor ou igual a 16.67 ms indica que a emulação está em velocidade plena. + + + + DOCK + NA BASE + + + + ASYNC + ASYNC + + + + MULTICORE + MULTINÚCLEO + + + + VULKAN + VULKAN + + + + OPENGL + OPENGL + + + + Clear Recent Files + Limpar arquivos recentes + + + + Warning Outdated Game Format + Aviso - formato de jogo desatualizado + + + + 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><br>For an explanation of the various Switch formats yuzu supports, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our wiki</a>. This message will not be shown again. + Você está usando neste jogo o formato de ROM desconstruída e extraída em uma pasta, que é um formato desatualizado que foi substituído por outros, como NCA, NAX, XCI ou NSP. Pastas desconstruídas de ROMs não possuem ícones, metadados e suporte a atualizações.<br><br>Para saber mais sobre os vários formatos de ROMs de Switch compatíveis com o yuzu, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>confira a nossa wiki</a>. Esta mensagem não será exibida novamente. + + + + + Error while loading ROM! + Erro ao carregar a ROM! + + + + The ROM format is not supported. + O formato da ROM não é suportado. + + + + An error occurred initializing the video core. + Ocorreu um erro ao inicializar o núcleo de vídeo. + + + + yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.Ensure that you have the latest graphics drivers for your GPU. + O yuzu encontrou um erro ao executar o núcleo de vídeo. Consulte o registro para mais detalhes. Para mais informações em como acessar o registro, veja a seguinte página: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Como enviar o arquivo de registro</a>. Verifique se você está usando o driver de vídeo mais atualizado. + + + + Error while loading ROM! + Erro ao carregar a ROM! + + + + An unknown error occurred. Please see the log for more details. + Ocorreu um erro desconhecido. Consulte o registro para mais detalhes. + + + + Start + Iniciar + + + + Save Data + Dados de jogos salvos + + + + Mod Data + Dados de mods + + + + Error Opening %1 Folder + Erro ao abrir a pasta %1 + + + + + Folder does not exist! + A pasta não existe! + + + + Error Opening Transferable Shader Cache + Erro ao abrir o cache de shaders transferível + + + + + A shader cache for this title does not exist. + Não existe um cache de shaders para este título. + + + + Contents + Conteúdo + + + + Update + Atualização + + + + DLC + DLC + + + + Remove Entry + Remover item + + + + Remove Installed Game %1? + Remover o jogo instalado %1? + + + + + + + + Successfully Removed + Removido com sucesso + + + + Successfully removed the installed base game. + O jogo base foi removido com sucesso. + + + + + + Error Removing %1 + Erro ao remover %1 + + + + The base game is not installed in the NAND and cannot be removed. + O jogo base não está instalado na NAND e não pode ser removido. + + + + Successfully removed the installed update. + A atualização instalada foi removida com sucesso. + + + + There is no update installed for this title. + Não há nenhuma atualização instalada para este título. + + + + There are no DLC installed for this title. + Não há nenhum DLC instalado para este título. + + + + Successfully removed %1 installed DLC. + %1 DLC(s) instalados foram removidos com sucesso. + + + + Delete Transferable Shader Cache? + Excluir o cache de shaders transferível? + + + + Remove Custom Game Configuration? + Remover configurações customizadas do jogo? + + + + Remove File + Remover arquivo + + + + + Error Removing Transferable Shader Cache + Erro ao remover cache de shaders transferível + + + + Successfully removed the transferable shader cache. + O cache de shaders transferível foi removido com sucesso. + + + + Failed to remove the transferable shader cache. + Falha ao remover o cache de shaders transferível. + + + + + Error Removing Custom Configuration + Erro ao remover as configurações customizadas do jogo. + + + + A custom configuration for this title does not exist. + Não há uma configuração customizada para este título. + + + + Successfully removed the custom game configuration. + As configurações customizadas do jogo foram removidas com sucesso. + + + + Failed to remove the custom game configuration. + Falha ao remover as configurações customizadas do jogo. + + + + RomFS Extraction Failed! + Falha ao extrair RomFS! + + + + There was an error copying the RomFS files or the user cancelled the operation. + Houve um erro ao copiar os arquivos RomFS ou o usuário cancelou a operação. + + + + Full + Extração completa + + + + Skeleton + Apenas estrutura + + + + Select RomFS Dump Mode + Selecione o modo de extração do 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. + Selecione a forma como você gostaria que o RomFS seja extraído.<br>"Extração completa" copiará todos os arquivos para a nova pasta, enquanto que <br>"Apenas estrutura" criará apenas a estrutura de pastas. + + + + Extracting RomFS... + Extraindo RomFS... + + + + + Cancel + Cancelar + + + + RomFS Extraction Succeeded! + Extração do RomFS concluida! + + + + The operation completed successfully. + A operação foi concluída com sucesso. + + + + Error Opening %1 + Erro ao abrir %1 + + + + Select Directory + Selecionar pasta + + + + Properties + Propriedades + + + + The game properties could not be loaded. + As propriedades do jogo não puderam ser carregadas. + + + + Switch Executable (%1);;All Files (*.*) + %1 is an identifier for the Switch executable file extensions. + Executável do Switch (%1);;Todos os arquivos (*.*) + + + + Load File + Carregar arquivo + + + + Open Extracted ROM Directory + Abrir pasta da ROM extraída + + + + Invalid Directory Selected + Pasta inválida selecionada + + + + The directory you have selected does not contain a 'main' file. + A pasta que você selecionou não contém um arquivo 'main'. + + + + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) + Arquivo de Switch instalável (*.nca *.nsp *.xci);; Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) + + + + Install Files + Instalar arquivos + + + + Installing file "%1"... + Instalando arquivo "%1"... + + + + Install Results + Resultados da instalação + + + + System Application + Aplicativo do sistema + + + + System Archive + Arquivo do sistema + + + + System Application Update + Atualização de aplicativo do sistema + + + + Firmware Package (Type A) + Pacote de firmware (tipo A) + + + + Firmware Package (Type B) + Pacote de firmware (tipo B) + + + + Game + Jogo + + + + Game Update + Atualização de jogo + + + + Game DLC + DLC de jogo + + + + Delta Title + Título delta + + + + Select NCA Install Type... + Selecione o tipo de instalação do NCA... + + + + Please select the type of title you would like to install this NCA as: +(In most instances, the default 'Game' is fine.) + Selecione o tipo de título como o qual você gostaria de instalar este NCA: +(Na maioria dos casos, o padrão 'Jogo' serve bem.) + + + + Failed to Install + Falha ao instalar + + + + The title type you selected for the NCA is invalid. + O tipo de título que você selecionou para o NCA é inválido. + + + + File not found + Arquivo não encontrado + + + + File "%1" not found + Arquivo "%1" não encontrado + + + + + Continue + Continuar + + + + Error Display + Tela de erro + + + + Missing yuzu Account + Conta do yuzu faltando + + + + In order to submit a game compatibility test case, you must link your yuzu account.<br><br/>To link your yuzu account, go to Emulation &gt; Configuration &gt; Web. + Para enviar um caso de teste de compatibilidade de jogo, você precisa entrar com a sua conta do yuzu.<br><br/>Para isso, vá para Emulação &gt; Configurar... &gt; Rede. + + + + Error opening URL + Erro ao abrir URL + + + + Unable to open the URL "%1". + Não foi possível abrir o URL "%1". + + + + Amiibo File (%1);; All Files (*.*) + Arquivo Amiibo (%1);; Todos os arquivos (*.*) + + + + Load Amiibo + Carregar Amiibo + + + + Error opening Amiibo data file + Erro ao abrir arquivo de dados do Amiibo + + + + Unable to open Amiibo file "%1" for reading. + Não foi possível abrir o arquivo de Amiibo "%1" para leitura. + + + + Error reading Amiibo data file + Erro ao ler arquivo de dados de Amiibo + + + + Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes. + Não foi possível ler completamente os dados do Amiibo. O yuzu esperava ler %1 bytes, mas foi capaz de ler apenas %2 bytes. + + + + Error loading Amiibo data + Erro ao carregar dados do Amiibo + + + + Unable to load Amiibo data. + Não foi possível carregar os dados do Amiibo. + + + + Capture Screenshot + Capturar tela + + + + PNG Image (*.png) + Imagem PNG (*.png) + + + + Speed: %1% / %2% + Velocidade: %1% / %2% + + + + Speed: %1% + Velocidade: %1% + + + + Game: %1 FPS + Jogo: %1 FPS + + + + Frame: %1 ms + Quadro: %1 ms + + + + The game you are trying to load requires additional files from your Switch to be dumped before playing.<br/><br/>For more information on dumping these files, please see the following wiki page: <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + O jogo que você está tentando carregar precisa que arquivos adicionais do seu Switch sejam extraídos antes de jogá-lo.<br/><br/>Para saber mais sobre como extrair esses arquivos, visite a seguinte página da wiki: <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Extraindo arquivos de sistema e fontes compartilhadas de um Switch</a>.<br/><br/> Gostaria de voltar para a lista de jogos? Continuar com a emulação pode resultar em travamentos, dados salvos corrompidos ou outros problemas. + + + + yuzu was unable to locate a Switch system archive. %1 + O yuzu não foi capaz de encontrar um arquivo de sistema do Switch. %1 + + + + yuzu was unable to locate a Switch system archive: %1. %2 + O yuzu não foi capaz de encontrar um arquivo de sistema do Switch. %1. %2 + + + + System Archive Not Found + Arquivo do sistema não encontrado + + + + System Archive Missing + Arquivo de sistema faltando + + + + yuzu was unable to locate the Switch shared fonts. %1 + O yuzu não foi capaz de encontrar as fontes compartilhadas do Switch. %1 + + + + Shared Fonts Not Found + Fontes compartilhadas não encontradas + + + + Shared Font Missing + Fonte compartilhada faltando + + + + Fatal Error + Erro fatal + + + + yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + O yuzu encontrou um erro fatal. Consulte o registro para mais detalhes. Para mais informações sobre como acessar o registro, consulte a seguinte página: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Como enviar o arquivo de registro</a>.<br/><br/>Gostaria de voltar para a lista de jogos? Continuar com a emulação pode resultar em travamentos, dados salvos corrompidos ou outros problemas. + + + + Fatal Error encountered + Erro fatal encontrado + + + + Confirm Key Rederivation + Confirmar rederivação de chave + + + + You are about to force rederive all of your keys. +If you do not know what this means or what you are doing, +this is a potentially destructive action. +Please make sure this is what you want +and optionally make backups. + +This will delete your autogenerated key files and re-run the key derivation module. + Você está prestes a rederivar todas as suas chaves forçadamente. +Se você não sabe o que isso significa ou o que você está fazendo, +esta é uma ação potencialmente destrutiva. +Por favor, confirme que você quer mesmo fazer isto +e opcionalmente faça cópias de segurança. + +Isto excluirá o seus arquivos de chaves geradas automaticamente, e reexecutar o módulo de derivação de chaves. + + + + Missing fuses + Faltando fusíveis + + + + - Missing BOOT0 + - Faltando BOOT0 + + + + - Missing BCPKG2-1-Normal-Main + - Faltando BCPKG2-1-Normal-Main + + + + - Missing PRODINFO + - Faltando PRODINFO + + + + Derivation Components Missing + Faltando componentes de derivação + + + + Components are missing that may hinder key derivation from completing. <br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu quickstart guide</a> to get all your keys and games.<br><br><small>(%1)</small> + Há componentes faltando que podem impedir a conclusão do processo de derivação de chaves. <br>Por favor, siga <a href='https://yuzu-emu.org/help/quickstart/'>o guia de iniciação rápida</a> para obter todas as suas chaves e jogos. <br><br><small>(%1)</small> + + + + Deriving keys... +This may take up to a minute depending +on your system's performance. + Derivando chaves... +Isto pode demorar até um minuto, dependendo +do desempenho do seu sistema. + + + + Deriving Keys + Derivando chaves + + + + Select RomFS Dump Target + Selecionar alvo de extração do RomFS + + + + Please select which RomFS you would like to dump. + Selecione qual RomFS você quer extrair. + + + + + + yuzu + yuzu + + + + Are you sure you want to close yuzu? + Você deseja mesmo fechar o yuzu? + + + + Are you sure you want to stop the emulation? Any unsaved progress will be lost. + Deseja mesmo parar a emulação? Qualquer progresso não salvo será perdido. + + + + The currently running application has requested yuzu to not exit. + +Would you like to bypass this and exit anyway? + O aplicativo rodando no momento solicitou ao yuzu para não sair. + +Deseja ignorar isso e sair mesmo assim? + + + + GRenderWindow + + + OpenGL not available! + OpenGL não disponível! + + + + yuzu has not been compiled with OpenGL support. + O yuzu não foi compilado com suporte para OpenGL. + + + + Vulkan not available! + Vulkan não disponível! + + + + yuzu has not been compiled with Vulkan support. + O yuzu não foi compilado com suporte para Vulkan. + + + + Error while initializing OpenGL 4.3! + Erro ao inicializar OpenGL 4.3! + + + + Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver. + Sua GPU pode não suportar OpenGL 4.3, ou você não tem os drivers gráficos mais recentes. + + + + Error while initializing OpenGL! + Erro ao inicializar o OpenGL! + + + + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>Unsupported extensions:<br> + Sua GPU pode não suportar uma ou mais extensões necessárias do OpenGL. Por favor, verifique se você possui a última versão dos drivers gráficos.<br><br>Extensões não supportadas:<br> + + + + GameList + + + + Name + Nome + + + + + Compatibility + Compatibilidade + + + + + Add-ons + Adicionais + + + + + + + File type + Tipo de arquivo + + + + + + + Size + Tamanho + + + + Open Save Data Location + Abrir local dos jogos salvos + + + + Open Mod Data Location + Abrir local dos dados de mods + + + + Open Transferable Shader Cache + Abrir cache de shaders transferível + + + + Remove + Remover + + + + Remove Installed Update + Remover atualização instalada + + + + Remove All Installed DLC + Remover todos os DLCs instalados + + + + Remove Shader Cache + Remover cache de shaders + + + + Remove Custom Configuration + Remover configuração customizada + + + + Remove All Installed Contents + Remover todo o conteúdo instalado + + + + Dump RomFS + Extrair RomFS + + + + Copy Title ID to Clipboard + Copiar ID do título para a área de transferência + + + + Navigate to GameDB entry + Abrir artigo do jogo no GameDB + + + + Properties + Propriedades + + + + Scan Subfolders + Examinar subpastas + + + + Remove Game Directory + Remover pasta de jogo + + + + ▲ Move Up + ▲ Mover para cima + + + + ▼ Move Down + ▼ Mover para baixo + + + + Open Directory Location + Abrir local da pasta + + + + GameListItemCompat + + + Perfect + Perfeito + + + + Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without +any workarounds needed. + O jogo funciona impecavelmente, sem falhas gráficas ou de áudio. Todas as funcionalidades testadas +do jogo funcionam como deveriam, sem nenhuma solução alternativa necessária. + + + + Great + Ótimo + + + + Game functions with minor graphical or audio glitches and is playable from start to finish. May require some +workarounds. + O jogo funciona com leves falhas gráficas ou de áudio e é jogável do início ao fim. Pode exigir +algumas soluções alternativas. + + + + Okay + Razoável + + + + Game functions with major graphical or audio glitches, but game is playable from start to finish with +workarounds. + O jogo funciona com graves falhas gráficas ou de áudio, mas é jogável do início ao fim +com o uso de soluções alternativas. + + + + Bad + Ruim + + + + Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches +even with workarounds. + O jogo funciona, só que com problemas significativos nos gráficos ou no áudio. Não é possível progredir em áreas +específicas por conta de tais problemas, mesmo com soluções alternativas. + + + + Intro/Menu + Intro/menu + + + + Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start +Screen. + É impossível jogar o jogo devido a grandes problemas nos gráficos ou no áudio. Não é possível passar da +tela inicial do jogo. + + + + Won't Boot + Não inicia + + + + The game crashes when attempting to startup. + O jogo trava ou se encerra abruptamente ao se tentar iniciá-lo. + + + + Not Tested + Não testado + + + + The game has not yet been tested. + Esse jogo ainda não foi testado. + + + + GameListPlaceholder + + + Double-click to add a new folder to the game list + Clique duas vezes para adicionar uma pasta à lista de jogos + + + + GameListSearchField + + + Filter: + Filtro: + + + + Enter pattern to filter + Digite o padrão para filtrar + + + + InstallDialog + + + Please confirm these are the files you wish to install. + Por favor, confirme que esses são os arquivos que deseja instalar. + + + + Installing an Update or DLC will overwrite the previously installed one. + Instalar uma atualização ou DLC irá sobrescrever a instalada anteriormente. + + + + Install + Instalar + + + + Install Files to NAND + Instalar arquivos para a NAND + + + + LoadingScreen + + + Loading Shaders 387 / 1628 + Carregando shaders 387/1628 + + + + Loading Shaders %v out of %m + Carregando shaders (%v de %m) + + + + Estimated Time 5m 4s + Tempo estimado 5m 4s + + + + Loading... + Carregando... + + + + Loading Shaders %1 / %2 + Carregando shaders %1/%2 + + + + Launching... + Iniciando... + + + + Estimated Time %1 + Tempo estimado %1 + + + + MainWindow + + + yuzu + yuzu + + + + &File + &Arquivo + + + + Recent Files + Arquivos recentes + + + + &Emulation + &Emulação + + + + &View + &Exibir + + + + Debugging + Depuração + + + + Tools + Ferramentas + + + + &Help + &Ajuda + + + + Install Files to NAND... + Instalar arquivos para a NAND... + + + + Load File... + Carregar arquivo... + + + + Load Folder... + Carregar pasta... + + + + E&xit + S&air + + + + &Start + &Iniciar + + + + &Pause + &Pausar + + + + &Stop + &Parar + + + + Reinitialize keys... + Reinicializar chaves... + + + + About yuzu + Sobre o yuzu + + + + Single Window Mode + Modo de janela única + + + + Configure... + Configurar... + + + + Display Dock Widget Headers + Exibir barra de títulos de widgets afixados + + + + Show Filter Bar + Exibir barra de filtro + + + + Show Status Bar + Exibir barra de status + + + + Reset Window Size + Restaurar tamanho padrão de janela + + + + Fullscreen + Tela cheia + + + + Restart + Reiniciar + + + + Load Amiibo... + Carregar Amiibo... + + + + Report Compatibility + Informar compatibilidade + + + + Open Mods Page + Abrir pagina de mods + + + + Open Quickstart Guide + Abrir o guia de iniciação rápida + + + + FAQ + FAQ + + + + Open yuzu Folder + Abrir pasta do yuzu + + + + Capture Screenshot + Capturar tela + + + + Configure Current Game.. + Configurar jogo atual.. + + + + MicroProfileDialog + + + MicroProfile + MicroProfile + + + + QObject + + + Installed SD Titles + Títulos instalados no SD + + + + Installed NAND Titles + Títulos instalados na NAND + + + + System Titles + Títulos do sistema + + + + Add New Game Directory + Adicionar pasta de jogos + + + + + + Shift + Shift + + + + + + Ctrl + Ctrl + + + + + + Alt + Alt + + + + + + + [not set] + [não definido] + + + + + + Hat %1 %2 + Hat %1 %2 + + + + + + Axis %1%2 + Eixo %1%2 + + + + + + Button %1 + Botão %1 + + + + + + + [unknown] + [desconhecido] + + + + + Click 0 + Clique 0 + + + + + Click 1 + Clique 1 + + + + + Click 2 + Clique 2 + + + + + Click 3 + Clique 3 + + + + + Click 4 + Clique 4 + + + + GC Axis %1%2 + Eixo GC %1%2 + + + + GC Button %1 + Botão GC %1 + + + + + [unused] + [não utilizado] + + + + + Axis %1 + Eixo %1 + + + + + GC Axis %1 + Eixo GC %1 + + + + QtErrorDisplay + + + An error has occured. +Please try again or contact the developer of the software. + +Error Code: %1-%2 (0x%3) + Ocorreu um erro. +Tente novamente ou entre em contato com o desenvolvedor do software. + +Código de erro: %1-%2 (0x%3) + + + + An error occured on %1 at %2. +Please try again or contact the developer of the software. + +Error Code: %3-%4 (0x%5) + Ocorreu um erro em %1 em %2. +Tente novamente ou entre em contato com o desenvolvedor do software. + +Código de erro: %3-%4 (0x%5) + + + + An error has occured. +Error Code: %1-%2 (0x%3) + +%4 + +%5 + Ocorreu um erro. +Código do erro: %1-%2 (0x%3) + +%4 + +%5 + + + + QtProfileSelectionDialog + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Select a user: + Selecione um usuário: + + + + Users + Usuários + + + + Profile Selector + Seletor de perfil + + + + QtSoftwareKeyboardDialog + + + Enter text: + Digite o texto: + + + + Software Keyboard + Teclado de software + + + + SequenceDialog + + + Enter a hotkey + Digite uma combinação de teclas + + + + WaitTreeCallstack + + + Call stack + Pilha de chamadas + + + + WaitTreeMutexInfo + + + waiting for mutex 0x%1 + esperando pelo mutex 0x%1 + + + + has waiters: %1 + possui os waiters %1 + + + + owner handle: 0x%1 + manejo de proprietário: 0x%1 + + + + WaitTreeObjectList + + + waiting for all objects + esperando por todos os objetos + + + + waiting for one of the following objects + esperando por um dos seguintes objetos + + + + WaitTreeSynchronizationObject + + + [%1]%2 %3 + [%1]%2 %3 + + + + waited by no thread + não aguardando pelo thread + + + + WaitTreeThread + + + + running + rodando + + + + ready + pronto + + + + + paused + pausado + + + + waiting for HLE return + esperando pelo retorno do HLE + + + + sleeping + dormindo + + + + waiting for IPC reply + esperando para resposta do IPC + + + + waiting for objects + esperando por objetos + + + + waiting for mutex + esperando por mutex + + + + waiting for condition variable + aguardando por variável da condição + + + + waiting for address arbiter + esperando para endereção o árbitro + + + + dormant + inativo + + + + dead + morto + + + + PC = 0x%1 LR = 0x%2 + PC = 0x%1 LR = 0x%2 + + + + ideal + ideal + + + + core %1 + núcleo %1 + + + + Unknown processor %1 + Processador desconhecido %1 + + + + processor = %1 + processador = %1 + + + + ideal core = %1 + núcleo ideal = %1 + + + + affinity mask = %1 + máscara de afinidade = %1 + + + + thread id = %1 + thread id = %1 + + + + priority = %1(current) / %2(normal) + prioridade = %1(atual) / %2(normal) + + + + last running ticks = %1 + últimos ticks executados = %1 + + + + not waiting for mutex + não aguardando para mutex + + + + WaitTreeThreadList + + + waited by thread + aguardado pelo thread + + + + WaitTreeWidget + + + Wait Tree + Árvore de espera + + + \ No newline at end of file diff --git a/dist/languages/pt_PT.ts b/dist/languages/pt_PT.ts new file mode 100644 index 000000000..5ba0bcf3b --- /dev/null +++ b/dist/languages/pt_PT.ts @@ -0,0 +1,4725 @@ + + + AboutDialog + + + About yuzu + Sobre Yuzu + + + + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + + + + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + + + + <html><head/><body><p>%1 | %2-%3 (%4)</p></body></html> + <html><head/><body><p>%1 | %2-%3 (%4)</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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head> +<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;">yuzu é um emulador experimental de código aberto para a Nintendo Switch licenciado sob GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">Este software não deve ser usado para jogar jogos que você não tenha obtido legalmente.</span></p><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"></html> + + + + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Site</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Código Fonte</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contribuidores</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">Licença</span></a></p></body></html> + + + + <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.</span></p></body></html> + <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; é uma marca comercial da Nintendo. Yuzu não é afiliado com a Nintendo de qualquer forma.</span></p></body></html> + + + + CalibrationConfigurationDialog + + + Communicating with the server... + + + + + Cancel + + + + + Touch the top left corner <br>of your touchpad. + + + + + Now touch the bottom right corner <br>of your touchpad. + + + + + Configuration completed! + + + + + OK + + + + + 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://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu 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 yuzu 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 yuzu account</li></ul></body></html> + <html><head/><body><p><span style=" font-size:10pt;">Se você optar por enviar um caso de teste para a</span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">lista de compatibilidade do yuzu</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 operativo)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Que versão do yuzu você está executando</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A conta yuzu conectada</li></ul></body></html> + + + + Perfect + Perfeito + + + + <html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html> + <html><head/><body><p>Jogo funciona na perfeição sem falhas de áudio ou gráficas.</p></body></html> + + + + Great + Ótimo + + + + <html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html> + <html><head/><body><p>O jogo funciona com pequenas falhas gráficas ou de áudio e pode ser jogado do início ao fim. Pode exigir algumas soluções alternativas.</p></body></html> + + + + Okay + OK + + + + <html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html> + <html><head/><body><p>O jogo funciona com grandes falhas gráficas ou de áudio, mas o jogo é jogável do início ao fim com soluções alternativas.</p></body></html> + + + + Bad + Mau + + + + <html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html> + <html><head/><body><p>O jogo funciona, mas com grandes falhas gráficas ou de áudio. Não é possível progredir em áreas específicas devido a falhas, mesmo com soluções alternativas.</p></body></html> + + + + Intro/Menu + Introdução / Menu + + + + <html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html> + <html><head/><body><p>O jogo não é jogável devido a grandes falhas gráficas ou de áudio. Não é possível passar da tela inicial.</p></body></html> + + + + Won't Boot + Não Inicia + + + + <html><head/><body><p>The game crashes when attempting to startup.</p></body></html> + <html><head/><body><p>O jogo trava ao tentar iniciar.</p></body></html> + + + + <html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html> + <html><head/><body><p>Independente da velocidade ou performance, como este jogo funciona de principio ao fim nesta versão do yuzu?</p></body></html> + + + + Thank you for your submission! + Obrigado pelo seu envio! + + + + Submitting + Entregando + + + + Communication error + Erro de comunicação + + + + An error occured while sending the Testcase + Ocorreu um erro enquanto enviava o caso de teste + + + + Next + Próximo + + + + ConfigureAudio + + + Audio + Audio + + + + Output Engine: + Motor de Saída: + + + + This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency. + Este efeito de pós-processamento ajusta a velocidade do áudio para corresponder à velocidade de emulação e ajuda a evitar o engasgue do audio. Isto, no entanto, aumenta a latência de áudio. + + + + Enable audio stretching + Activar alongamento de audio + + + + Audio Device: + Dispositivo de áudio: + + + + Use global volume + + + + + Set volume: + + + + + Volume: + Volume: + + + + 0 % + 0 % + + + + %1% + Volume percentage (e.g. 50%) + %1% + + + + ConfigureCpu + + + Form + + + + + General + + + + + Accuracy: + + + + + Accurate + + + + + Unsafe + + + + + Enable Debug Mode + + + + + We recommend setting accuracy to "Accurate". + + + + + Unsafe CPU Optimization Settings + + + + + These settings reduce accuracy for speed. + + + + + Unfuse FMA (improve performance on CPUs without FMA) + + + + + + <div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div> + + + + + + Faster FRSQRTE and FRECPE + + + + + + <div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div> + + + + + + CPU settings are available only when game is not running. + + + + + Setting CPU to Debug Mode + + + + + CPU Debug Mode is only intended for developer use. Are you sure you want to enable this? + + + + + ConfigureCpuDebug + + + Form + + + + + Toggle CPU Optimizations + + + + + + <div> + <b>For debugging only.</b> + <br> + If you're not sure what these do, keep all of these enabled. + <br> + These settings only take effect when CPU Accuracy is "Debug Mode". + </div> + + + + + + Enable inline page tables + + + + + + <div style="white-space: nowrap">This optimization speeds up memory accesses by the guest program.</div> + <div style="white-space: nowrap">Enabling it inlines accesses to PageTable::pointers into emitted code.</div> + <div style="white-space: nowrap">Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.</div> + + + + + + Enable block linking + + + + + + <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> + + + + + + Enable return stack buffer + + + + + + <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> + + + + + + Enable fast dispatcher + + + + + + <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> + + + + + + Enable context elimination + + + + + + <div>Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.</div> + + + + + + Enable constant propagation + + + + + + <div>Enables IR optimizations that involve constant propagation.</div> + + + + + + Enable miscellaneous optimizations + + + + + + <div>Enables miscellaneous IR optimizations.</div> + + + + + + Enable misalignment check reduction + + + + + + <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> + + + + + + CPU settings are available only when game is not running. + + + + + ConfigureDebug + + + Form + Formato + + + + GDB + GDB + + + + Enable GDB Stub + Activar GDB Stub + + + + Port: + Porta: + + + + Logging + Entrando + + + + Global Log Filter + Filtro de registro global + + + + Show Log Console (Windows Only) + Mostrar o registro da consola (Apenas Windows) + + + + Open Log Location + Abrir a localização do registro + + + + Homebrew + Homebrew + + + + Arguments String + Argumentos String + + + + Graphics + + + + + When checked, the graphics API enters in a slower debugging mode + + + + + Enable Graphics Debugging + + + + + When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower + + + + + Disable Macro JIT + + + + + Dump + + + + + Enable Verbose Reporting Services + Ativar o Reporte de Serviços detalhado + + + + This will be reset automatically when yuzu closes. + Isto vai ser resetado automáticamente quando o yuzu fechar. + + + + Advanced + Avançado + + + + Kiosk (Quest) Mode + Modo Quiosque + + + + ConfigureDebugController + + + Configure Debug Controller + + + + + Clear + + + + + Defaults + + + + + ConfigureDialog + + + yuzu Configuration + Configuração yuzu + + + + + + + General + Geral + + + + + UI + IU + + + + Game List + Lista de Jogos + + + + + + + System + Sistema + + + + + + Profiles + Perfis + + + + + + Filesystem + Sistema de Ficheiros + + + + + + + Controls + Controlos + + + + + + Hotkeys + Teclas de Atalhos + + + + + + + CPU + + + + + + + + + + Debug + Depurar + + + + + + + Graphics + Gráficos + + + + + Advanced + + + + + GraphicsAdvanced + + + + + + + + Audio + Audio + + + + + + Web + Rede + + + + + + Services + Serviços + + + + ConfigureFilesystem + + + Form + Formato + + + + Storage Directories + Diretórios de armazenamento + + + + NAND + NAND + + + + + + + + + ... + ... + + + + SD Card + Cartão SD + + + + Gamecard + Cartão de jogo + + + + Path + Caminho + + + + Inserted + Inserido + + + + Current Game + Jogo Atual + + + + Patch Manager + Gestor de Patch + + + + Dump Decompressed NSOs + Dump NSOs Descompactados + + + + Dump ExeFS + Dump ExeFS + + + + Mod Load Root + Raiz dos Mods + + + + Dump Root + Raiz do Dump + + + + Caching + Armazenamento em cache + + + + Cache Directory + Diretório do cache + + + + Cache Game List Metadata + Metadata da Lista de Jogos em Cache + + + + + + + Reset Metadata Cache + Resetar a Cache da Metadata + + + + Select Emulated NAND Directory... + Selecione o Diretório NAND Emulado... + + + + Select Emulated SD Directory... + Selecione o Diretório SD Emulado... + + + + Select Gamecard Path... + Selecione o Diretório do Cartão de Jogo... + + + + Select Dump Directory... + Selecionar o diretório do Dump... + + + + Select Mod Load Directory... + Selecionar o Diretório do Mod Load ... + + + + Select Cache Directory... + Selecionar o diretório do Cache + + + + The metadata cache is already empty. + O cache de metadata já está vazio. + + + + The operation completed successfully. + A operação foi completa com sucesso. + + + + The metadata cache couldn't be deleted. It might be in use or non-existent. + Não foi possível excluir o cache de metadata. Pode estar em uso ou inexistente. + + + + ConfigureGeneral + + + Form + Formato + + + + General + Geral + + + + Limit Speed Percent + Percentagem do limitador de velocidade + + + + % + % + + + + Multicore CPU Emulation + + + + + Confirm exit while emulation is running + Confirme a saída enquanto a emulação está em execução + + + + Prompt for user on game boot + Solicitar para o utilizador na inicialização do jogo + + + + Pause emulation when in background + Pausar o emulador quando estiver em segundo plano + + + + Hide mouse on inactivity + + + + + ConfigureGraphics + + + Form + Formato + + + + API Settings + + + + + API: + + + + + Device: + + + + + Graphics Settings + + + + + Use disk shader cache + Usar cache do disk shader + + + + Use asynchronous GPU emulation + Usar emulação assíncrona de GPU + + + + Aspect Ratio: + + + + + Default (16:9) + + + + + Force 4:3 + + + + + Force 21:9 + + + + + Stretch to Window + + + + + + Use global background color + + + + + Set background color: + + + + + Background Color: + Cor de fundo: + + + + OpenGL Graphics Device + + + + + ConfigureGraphicsAdvanced + + + Form + + + + + Advanced Graphics Settings + + + + + Accuracy Level: + + + + + VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference. + + + + + Use VSync (OpenGL only) + + + + + Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental. + + + + + Use assembly shaders (experimental, Nvidia OpenGL only) + + + + + Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental. + + + + + Use asynchronous shader building (experimental) + + + + + Use Fast GPU Time + + + + + Anisotropic Filtering: + + + + + Default + + + + + 2x + + + + + 4x + + + + + 8x + + + + + 16x + + + + + ConfigureHotkeys + + + Hotkey Settings + Definições de Teclas de Atalho + + + + Double-click on a binding to change it. + Clique duas vezes numa ligação para alterá-la. + + + + Clear All + + + + + Restore Defaults + + + + + Action + Ação + + + + Hotkey + Tecla de Atalho + + + + Context + Contexto + + + + + Conflicting Key Sequence + Sequência de teclas em conflito + + + + The entered key sequence is already assigned to: %1 + + + + + Restore Default + + + + + Clear + + + + + The default key sequence is already assigned to: %1 + + + + + ConfigureInput + + + ConfigureInput + + + + + + Player 1 + Jogador 1 + + + + + Player 2 + Jogador 2 + + + + + Player 3 + Jogador 3 + + + + + Player 4 + Jogador 4 + + + + + Player 5 + Jogador 5 + + + + + Player 6 + Jogador 6 + + + + + Player 7 + Jogador 7 + + + + + Player 8 + Jogador 8 + + + + + Advanced + Avançado + + + + Console Mode + + + + + Docked + + + + + Undocked + + + + + Vibration + + + + + % + + + + + Motion + + + + + Configure + Configurar + + + + Controllers + + + + + 1 + + + + + 2 + + + + + 3 + + + + + 4 + + + + + 5 + + + + + 6 + + + + + 7 + + + + + 8 + + + + + Connected + + + + + Defaults + + + + + Clear + + + + + ConfigureInputAdvanced + + + Configure Input + + + + + Joycon Colors + + + + + Player 1 + + + + + + + + + + + + L Body + + + + + + + + + + + + L Button + + + + + + + + + + + + R Body + + + + + + + + + + + + R Button + + + + + Player 2 + + + + + Player 3 + + + + + Player 4 + + + + + Player 5 + + + + + Player 6 + + + + + Player 7 + + + + + Player 8 + + + + + Other + + + + + Keyboard + + + + + + Advanced + + + + + Touchscreen + + + + + Mouse + + + + + Motion / Touch + + + + + + Configure + + + + + Debug Controller + + + + + ConfigureInputPlayer + + + Configure Input + Configurar Entrada + + + + Connect Controller + + + + + + + Pro Controller + + + + + + Dual Joycons + + + + + + Left Joycon + + + + + + Right Joycon + + + + + + Handheld + + + + + Input Device + + + + + Any + + + + + Keyboard/Mouse + + + + + Profile + + + + + Save + + + + + New + + + + + Delete + + + + + Left Stick + Analógico Esquerdo + + + + + + + + + Up + + + + + + + + + + Left + + + + + + + + + + Right + + + + + + + + + + Down + + + + + + + + Pressed + + + + + + + + Modifier + + + + + + Range + + + + + + % + + + + + + Deadzone: 0% + + + + + + Modifier Range: 0% + + + + + D-Pad + + + + + + L + + + + + + ZL + + + + + + Minus + + + + + + Capture + + + + + + Plus + + + + + + Home + + + + + + R + + + + + + ZR + + + + + + SL + + + + + + SR + + + + + Face Buttons + Botôes de Rosto + + + + + X + + + + + + Y + + + + + + A + + + + + + B + + + + + Right Stick + Analógico Direito + + + + + Deadzone: %1% + + + + + + Modifier Range: %1% + + + + + [waiting] + + + + + ConfigureMotionTouch + + + Configure Motion / Touch + + + + + Motion + + + + + Motion Provider: + + + + + Sensitivity: + + + + + Touch + + + + + Touch Provider: + + + + + Calibration: + + + + + (100, 50) - (1800, 850) + + + + + + + Configure + + + + + Use button mapping: + + + + + CemuhookUDP Config + + + + + You may use any Cemuhook compatible UDP input source to provide motion and touch input. + + + + + Server: + + + + + Port: + + + + + Pad: + + + + + Pad 1 + + + + + Pad 2 + + + + + Pad 3 + + + + + Pad 4 + + + + + Learn More + + + + + + Test + + + + + Mouse (Right Click) + + + + + + CemuhookUDP + + + + + Emulator Window + + + + + <a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Learn More</span></a> + + + + + 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. + + + + + Citra + + + + + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. + + + + + ConfigureMouseAdvanced + + + Configure Mouse + Configurar Rato + + + + Mouse Buttons + Botões do Rato + + + + Forward: + Frente: + + + + Back: + Atrás: + + + + Left: + Esquerda: + + + + Middle: + Meio: + + + + Right: + Direita: + + + + + Clear + Limpar + + + + Defaults + + + + + [not set] + [não configurado] + + + + Restore Default + Restaurar Padrões + + + + [press key] + [pressiona a tecla] + + + + ConfigurePerGame + + + Dialog + + + + + Info + + + + + Name + + + + + Title ID + + + + + Filename + + + + + Format + + + + + Version + + + + + Size + + + + + Developer + + + + + Add-Ons + + + + + General + + + + + System + + + + + Graphics + + + + + Adv. Graphics + + + + + Audio + + + + + Properties + + + + + Use global configuration (%1) + + + + + ConfigurePerGameAddons + + + Form + + + + + Patch Name + + + + + Version + + + + + ConfigureProfileManager + + + Form + Formato + + + + Profile Manager + Gestor de Perfis + + + + Current User + Utilizador Atual + + + + Username + Nome de Utilizador + + + + Set Image + Definir Imagem + + + + Add + Adicionar + + + + Rename + Renomear + + + + Remove + Remover + + + + Profile management is available only when game is not running. + O gestor de perfis só está disponível apenas quando o jogo não está em execução. + + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Enter Username + Introduza o Nome de Utilizador + + + + Users + Utilizadores + + + + Enter a username for the new user: + Introduza um nome de utilizador para o novo utilizador: + + + + Enter a new username: + Introduza um novo nome de utilizador: + + + + Confirm Delete + Confirmar para eliminar + + + + You are about to delete user with name "%1". Are you sure? + Estás preste a eliminar o utilizador com o nome "%1". Tens a certeza? + + + + Select User Image + Definir Imagem de utilizador + + + + JPEG Images (*.jpg *.jpeg) + Imagens JPEG (*.jpg *.jpeg) + + + + Error deleting image + Error ao eliminar a imagem + + + + Error occurred attempting to overwrite previous image at: %1. + Ocorreu um erro ao tentar substituir imagem anterior em: %1. + + + + Error deleting file + Erro ao eliminar o arquivo + + + + Unable to delete existing file: %1. + Não é possível eliminar o arquivo existente: %1. + + + + Error creating user image directory + Erro ao criar o diretório de imagens do utilizador + + + + Unable to create directory %1 for storing user images. + Não é possível criar o diretório %1 para armazenar imagens do utilizador. + + + + Error copying user image + Erro ao copiar a imagem do utilizador + + + + Unable to copy image from %1 to %2 + Não é possível copiar a imagem de %1 para %2 + + + + ConfigureService + + + Form + Formato + + + + BCAT + BCAT + + + + BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content. + O BCAT é a forma pela qual a Nintendo envia dados aos jogos para envolver a sua comunidade e desbloquear conteúdos adicionais. + + + + BCAT Backend + BCAT Backend + + + + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Learn more about BCAT, Boxcat, and Current Events</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Saiba mais sobre o BCAT, Boxcat e eventos atuais</span></a></p></body></html> + + + + The boxcat service is offline or you are not connected to the internet. + O serviço boxcat está offline ou não estás ligado à Internet. + + + + There was an error while processing the boxcat event data. Contact the yuzu developers. + Ocorreu um erro ao processar os dados do evento boxcat. Entre em contato com os desenvolvedores do yuzu. + + + + The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu. + A versão em uso do yuzu é muito nova ou muito antiga para o servidor. Tente atualizar para a ultima versão oficial do yuzu. + + + + + There are currently no events on boxcat. + De momento, não há eventos no boxcat. + + + + + Current Boxcat Events + + + + + Yuzu is retrieving the latest boxcat status... + Yuzu está a atualizar o boxcat ... + + + + ConfigureSystem + + + Form + Formato + + + + System Settings + Configurações de Sistema + + + + Region: + + + + + Auto + + + + + Default + + + + + CET + + + + + CST6CDT + + + + + Cuba + + + + + EET + + + + + Egypt + + + + + Eire + + + + + EST + + + + + EST5EDT + + + + + GB + + + + + GB-Eire + + + + + GMT + + + + + GMT+0 + + + + + GMT-0 + + + + + GMT0 + + + + + Greenwich + + + + + Hongkong + + + + + HST + + + + + Iceland + + + + + Iran + + + + + Israel + + + + + Jamaica + + + + + + Japan + + + + + Kwajalein + + + + + Libya + + + + + MET + + + + + MST + + + + + MST7MDT + + + + + Navajo + + + + + NZ + + + + + NZ-CHAT + + + + + Poland + + + + + Portugal + + + + + PRC + + + + + PST8PDT + + + + + ROC + + + + + ROK + + + + + Singapore + + + + + Turkey + + + + + UCT + + + + + Universal + + + + + UTC + + + + + W-SU + + + + + WET + + + + + Zulu + + + + + USA + + + + + Europe + + + + + Australia + + + + + China + + + + + Korea + + + + + Taiwan + + + + + Time Zone: + + + + + Note: this can be overridden when region setting is auto-select + Nota: isto pode ser substituído quando a configuração da região é de seleção automática + + + + Japanese (日本語) + Japonês (日本語) + + + + English + Inglês + + + + 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 (正 體 中文) + + + + Custom RTC + RTC personalizado + + + + Language + Língua + + + + RNG Seed + Semente de RNG + + + + Mono + Mono + + + + Stereo + Estéreo + + + + Surround + Surround + + + + Console ID: + ID da consola: + + + + Sound output mode + Modo de saída de som + + + + d MMM yyyy h:mm:ss AP + d MMM yyyy h:mm:ss AP + + + + Regenerate + Regenerar + + + + System settings are available only when game is not running. + As configurações do sistema estão disponíveis apenas quando o jogo não está em execução. + + + + This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue? + Isto substituirá o seu Switch virtual actual por um novo. Seu Switch virtual actual não será recuperável. Isso pode ter efeitos inesperados nos jogos. Isto pode falhar, se você usar uma gravação de jogo de configuração desatualizado. Continuar? + + + + Warning + Aviso + + + + Console ID: 0x%1 + ID da Consola: 0x%1 + + + + ConfigureTouchFromButton + + + Configure Touchscreen Mappings + + + + + Mapping: + + + + + New + + + + + Delete + + + + + 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 + + + + + Button + + + + + X + X axis + + + + + Y + Y axis + + + + + New Profile + + + + + Enter the name for the new profile. + + + + + Delete Profile + + + + + Delete profile %1? + + + + + Rename Profile + + + + + New name: + + + + + [press key] + + + + + ConfigureTouchscreenAdvanced + + + Configure Touchscreen + Configurar Ecrã Táctil + + + + Warning: The settings in this page affect the inner workings of yuzu'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. + Aviso: As configurações nesta página afectam o funcionamento interno da tela de toque emulada do yuzu. Alterá-los pode resultar em um comportamento indesejável, como a tela de toque não funcionando parcialmente ou até mesmo na sua totatildade. Você só deve usar esta página se souber o que está fazendo. + + + + Touch Parameters + Parâmetros de Toque + + + + Touch Diameter Y + Diâmetro de Toque Y + + + + Finger + Dedo + + + + Touch Diameter X + Diâmetro de Toque X + + + + Rotational Angle + Ângulo rotacional + + + + Restore Defaults + Restaurar Padrões + + + + ConfigureUi + + + Form + Formato + + + + General + Geral + + + + Note: Changing language will apply your configuration. + Nota: Alterar o idioma aplicará sua configuração. + + + + Interface language: + Idioma da interface: + + + + Theme: + Tema: + + + + Game List + Lista de Jogos + + + + Show Add-Ons Column + Mostrar coluna de Add-Ons + + + + Icon Size: + Tamanho do ícone: + + + + Row 1 Text: + Linha 1 Texto: + + + + Row 2 Text: + Linha 2 Texto: + + + + Screenshots + + + + + Ask Where To Save Screenshots (Windows Only) + + + + + Screenshots Path: + + + + + ... + + + + + Select Screenshots Path... + + + + + <System> + <System> + + + + English + Inglês + + + + ConfigureWeb + + + Form + Formato + + + + yuzu Web Service + Serviço Web do Yuzu + + + + By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information. + Ao fornecer seu nome de usuário e token, você concorda em permitir que o yuzu colete dados de uso adicionais, que podem incluir informações de identificação do usuário. + + + + + Verify + Verificar + + + + Sign up + Inscrever-se + + + + Token: + Token: + + + + Username: + Nome de usuário: + + + + What is my token? + O que é o meu token? + + + + Telemetry + Telemetria + + + + Share anonymous usage data with the yuzu team + Compartilhar dados de uso anônimos com a equipa Yuzu + + + + Learn more + Saber mais + + + + Telemetry ID: + ID de Telemetria: + + + + Regenerate + Regenerar + + + + Discord Presence + Presença do Discord + + + + Show Current Game in your Discord Status + Mostrar o Jogo Atual no seu Estado de Discord + + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Learn more</span></a> + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Saber mais</span></a> + + + + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Sign up</span></a> + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Inscrever-se</span></a> + + + + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">What is my token?</span></a> + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">O que é o meu Token?</span></a> + + + + + Telemetry ID: 0x%1 + ID de Telemetria: 0x%1 + + + + + Unspecified + Não especificado + + + + Token not verified + Token não verificado + + + + Token was not verified. The change to your token has not been saved. + O token não foi verificado. A alteração do token não foi gravada. + + + + Verifying... + A verificar... + + + + Verification failed + Verificação Falhada + + + + Verification failed. Check that you have entered your token correctly, and that your internet connection is working. + Verificação Falhada. Verifique se introduziu seu nome de utilizador e o token correctamente e se a conexão com a Internet está operacional. + + + + GMainWindow + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous data is collected</a> to help improve yuzu. <br/><br/>Would you like to share your usage data with us? + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Dados anônimos são coletados</a>para ajudar a melhorar o yuzu.<br/><br/>Gostaria de compartilhar seus dados de uso conosco? + + + + Telemetry + Telemetria + + + + Text Check Failed + Falha na verificação de texto + + + + Loading Web Applet... + A Carregar o Web Applet ... + + + + Exit Web Applet + Sair do Web Applet + + + + Exit + Sair + + + + To exit the web application, use the game provided controls to select exit, select the 'Exit Web Applet' option in the menu bar, or press the 'Enter' key. + Para sair do aplicativo web, use os controlos fornecidos pelo jogo para selecionar sair, selecione a opção 'Sair do Web Applet' na barra de menus ou pressione a tecla 'Enter'. + + + + Web Applet + Web Applet + + + + This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested. + Esta versão do yuzu foi criada sem o suporte ao QtWebEngine, o que significa que o yuzu não pode exibir corretamente o manual do jogo ou a página da web solicitada. + + + + The amount of shaders currently being built + + + + + Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch. + Velocidade da emulação actual. Valores acima ou abaixo de 100% indicam que a emulação está sendo executada mais depressa ou mais devagar do que a 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á exibindo de momento. Isto irá variar de jogo para jogo e de cena para 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. + Tempo gasto para emular um frame da Switch, sem contar o a limitação de quadros ou o v-sync. Para emulação de velocidade máxima, esta deve ser no máximo 16.67 ms. + + + + DOCK + + + + + ASYNC + + + + + MULTICORE + + + + + VULKAN + + + + + OPENGL + + + + + Clear Recent Files + Limpar Arquivos Recentes + + + + Warning Outdated Game Format + Aviso de Formato de Jogo Desactualizado + + + + 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><br>For an explanation of the various Switch formats yuzu supports, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our wiki</a>. This message will not be shown again. + Você está usando o formato de directório ROM desconstruído para este jogo, que é um formato desactualizado que foi substituído por outros, como NCA, NAX, XCI ou NSP. Os directórios de ROM não construídos não possuem ícones, metadados e suporte de actualização.<br><br>Para uma explicação dos vários formatos de Switch que o yuzu suporta,<a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>Verifique a nossa Wiki</a>. Esta mensagem não será mostrada novamente. + + + + + Error while loading ROM! + Erro ao carregar o ROM! + + + + The ROM format is not supported. + O formato do ROM não é suportado. + + + + An error occurred initializing the video core. + Ocorreu um erro ao inicializar o núcleo do vídeo. + + + + yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.Ensure that you have the latest graphics drivers for your GPU. + yuzu encontrou um erro ao executar o núcleo de vídeo, consulte o log para obter mais detalhes. Para obter mais informações sobre como acessar o log, consulte a seguinte página:<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Como carregar o arquivo de log</a>Assegure-se que tem os drivers gráficos mais recentes para sua GPU. + + + + Error while loading ROM! + + + + + An unknown error occurred. Please see the log for more details. + Ocorreu um erro desconhecido. Por favor, veja o log para mais detalhes. + + + + Start + Começar + + + + Save Data + Save Data + + + + Mod Data + Mod Data + + + + Error Opening %1 Folder + Erro ao abrir a pasta %1 + + + + + Folder does not exist! + A Pasta não existe! + + + + Error Opening Transferable Shader Cache + Erro ao abrir os Shader Cache transferíveis + + + + + A shader cache for this title does not exist. + O Shader Cache para este titulo não existe. + + + + Contents + + + + + Update + + + + + DLC + + + + + Remove Entry + + + + + Remove Installed Game %1? + + + + + + + + + Successfully Removed + + + + + Successfully removed the installed base game. + + + + + + + Error Removing %1 + + + + + The base game is not installed in the NAND and cannot be removed. + + + + + Successfully removed the installed update. + + + + + There is no update installed for this title. + + + + + There are no DLC installed for this title. + + + + + Successfully removed %1 installed DLC. + + + + + Delete Transferable Shader Cache? + + + + + Remove Custom Game Configuration? + + + + + Remove File + + + + + + Error Removing Transferable Shader Cache + + + + + Successfully removed the transferable shader cache. + + + + + Failed to remove the transferable shader cache. + + + + + + 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. + + + + + RomFS Extraction Failed! + A Extração de RomFS falhou! + + + + There was an error copying the RomFS files or the user cancelled the operation. + Houve um erro ao copiar os arquivos RomFS ou o usuário cancelou a operação. + + + + Full + Cheio + + + + Skeleton + Esqueleto + + + + Select RomFS Dump Mode + Selecione o modo de despejo do 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, selecione a forma como você gostaria que o RomFS fosse despejado<br>Full irá copiar todos os arquivos para o novo diretório enquanto<br>skeleton criará apenas a estrutura de diretórios. + + + + Extracting RomFS... + Extraindo o RomFS ... + + + + + Cancel + Cancelar + + + + RomFS Extraction Succeeded! + Extração de RomFS Bem-Sucedida! + + + + The operation completed successfully. + A operação foi completa com sucesso. + + + + Error Opening %1 + Erro ao abrir %1 + + + + Select Directory + Selecione o Diretório + + + + Properties + Propriedades + + + + The game properties could not be loaded. + As propriedades do jogo não puderam ser carregadas. + + + + Switch Executable (%1);;All Files (*.*) + %1 is an identifier for the Switch executable file extensions. + Executáveis Switch (%1);;Todos os Ficheiros (*.*) + + + + Load File + Carregar Arquivo + + + + Open Extracted ROM Directory + Abrir o directório ROM extraído + + + + Invalid Directory Selected + Diretório inválido selecionado + + + + The directory you have selected does not contain a 'main' file. + O diretório que você selecionou não contém um arquivo 'Main'. + + + + Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci) + + + + + Install Files + + + + + Installing file "%1"... + Instalando arquivo "%1"... + + + + Install Results + + + + + System Application + Aplicação do sistema + + + + System Archive + Arquivo do sistema + + + + System Application Update + Atualização do aplicativo do sistema + + + + Firmware Package (Type A) + Pacote de Firmware (Tipo A) + + + + Firmware Package (Type B) + Pacote de Firmware (Tipo B) + + + + Game + Jogo + + + + Game Update + Actualização do Jogo + + + + Game DLC + DLC do Jogo + + + + Delta Title + Título Delta + + + + Select NCA Install Type... + Selecione o tipo de instalação do NCA ... + + + + Please select the type of title you would like to install this NCA as: +(In most instances, the default 'Game' is fine.) + Por favor, selecione o tipo de título que você gostaria de instalar este NCA como: +(Na maioria dos casos, o padrão 'Jogo' é suficiente). + + + + Failed to Install + Falha na instalação + + + + The title type you selected for the NCA is invalid. + O tipo de título que você selecionou para o NCA é inválido. + + + + File not found + Arquivo não encontrado + + + + File "%1" not found + Arquivo "%1" não encontrado + + + + + Continue + Continuar + + + + Error Display + Visualização de erros + + + + Missing yuzu Account + Conta Yuzu Ausente + + + + In order to submit a game compatibility test case, you must link your yuzu account.<br><br/>To link your yuzu account, go to Emulation &gt; Configuration &gt; Web. + Para enviar um caso de teste de compatibilidade de jogos, você deve vincular sua conta yuzu.<br><br/>Para vincular sua conta yuzu, vá para Emulação &gt; Configuração &gt; Rede. + + + + Error opening URL + + + + + Unable to open the URL "%1". + + + + + Amiibo File (%1);; All Files (*.*) + Arquivo Amiibo (%1);; Todos os Arquivos (*.*) + + + + Load Amiibo + Carregar Amiibo + + + + Error opening Amiibo data file + Erro ao abrir o arquivo de dados do Amiibo + + + + Unable to open Amiibo file "%1" for reading. + Não é possível abrir o arquivo Amiibo "%1" para leitura. + + + + Error reading Amiibo data file + Erro ao ler o arquivo de dados do Amiibo + + + + Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes. + Não é possível ler completamente os dados do Amiibo. Espera-se que leia %1 bytes, mas só conseguiu ler %2 bytes. + + + + Error loading Amiibo data + Erro ao carregar dados do Amiibo + + + + Unable to load Amiibo data. + Não foi possível carregar os dados do Amiibo. + + + + Capture Screenshot + Captura de Tela + + + + PNG Image (*.png) + Imagem PNG (*.png) + + + + Speed: %1% / %2% + Velocidade: %1% / %2% + + + + Speed: %1% + Velocidade: %1% + + + + Game: %1 FPS + Jogo: %1 FPS + + + + Frame: %1 ms + Quadro: %1 ms + + + + The game you are trying to load requires additional files from your Switch to be dumped before playing.<br/><br/>For more information on dumping these files, please see the following wiki page: <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + O jogo que você está tentando carregar requer arquivos adicionais do seu Switch para serem despejados antes de jogar.<br/><br/>Para obter mais informações sobre como despejar esses arquivos, consulte a seguinte página da wiki:<a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Despejando arquivos do sistema e as fontes compartilhadas de uma consola Switch</a>.<br/><br/>Você gostaria de regressar para a lista de jogos? Continuar a emulação pode resultar em falhas, dados de salvamento corrompidos ou outros erros. + + + + yuzu was unable to locate a Switch system archive. %1 + O yuzu não conseguiu localizar um arquivo de sistema do Switch. % 1 + + + + yuzu was unable to locate a Switch system archive: %1. %2 + O yuzu não conseguiu localizar um arquivo de sistema do Switch: %1. %2 + + + + System Archive Not Found + Arquivo do Sistema Não Encontrado + + + + System Archive Missing + Arquivo de Sistema em falta + + + + yuzu was unable to locate the Switch shared fonts. %1 + yuzu não conseguiu localizar as fontes compartilhadas do Switch. %1 + + + + Shared Fonts Not Found + Fontes compartilhadas não encontradas + + + + Shared Font Missing + Fontes compartilhadas em falta + + + + Fatal Error + Erro fatal + + + + yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + yuzu encontrou um erro fatal, por favor veja o registro para mais detalhes. Para mais informações sobre como acessar o registro, por favor, veja a seguinte página:<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Como carregar o arquivo de registro</a>.<br/><br/>Você gostaria de regressar para a lista de jogos? Continuar a emulação pode resultar em falhas, dados de salvamento corrompidos ou outros erros. + + + + Fatal Error encountered + Ocorreu um Erro fatal + + + + Confirm Key Rederivation + Confirme a rederivação da chave + + + + You are about to force rederive all of your keys. +If you do not know what this means or what you are doing, +this is a potentially destructive action. +Please make sure this is what you want +and optionally make backups. + +This will delete your autogenerated key files and re-run the key derivation module. + Você está prestes a forçar a rederivação de todas as suas chaves. +Se você não sabe o que isso significa ou o que você está fazendo, +esta é uma acção potencialmente destrutiva. +Por favor, certifique-se que isto é o que você quer +e opcionalmente faça backups. + +Isso irá excluir os seus arquivos de chave gerados automaticamente e executará novamente o módulo de derivação de chave. + + + + Missing fuses + + + + + - Missing BOOT0 + + + + + - Missing BCPKG2-1-Normal-Main + + + + + - Missing PRODINFO + + + + + Derivation Components Missing + + + + + Components are missing that may hinder key derivation from completing. <br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu quickstart guide</a> to get all your keys and games.<br><br><small>(%1)</small> + + + + + Deriving keys... +This may take up to a minute depending +on your system's performance. + Derivando chaves ... +Isto pode demorar até um minuto, dependendo +do desempenho do seu sistema. + + + + Deriving Keys + Derivando Chaves + + + + Select RomFS Dump Target + Selecione o destino de despejo do RomFS + + + + Please select which RomFS you would like to dump. + Por favor, selecione qual o RomFS que você gostaria de despejar. + + + + + + yuzu + yuzu + + + + Are you sure you want to close yuzu? + Tem a certeza que quer fechar o yuzu? + + + + Are you sure you want to stop the emulation? Any unsaved progress will be lost. + Tem a certeza de que quer parar a emulação? Qualquer progresso não salvo será perdido. + + + + The currently running application has requested yuzu to not exit. + +Would you like to bypass this and exit anyway? + O aplicativo atualmente em execução solicitou que o yuzu não fechasse. + +Deseja ignorar isso e sair mesmo assim? + + + + GRenderWindow + + + OpenGL not available! + + + + + yuzu has not been compiled with OpenGL support. + + + + + Vulkan not available! + + + + + yuzu has not been compiled with Vulkan support. + + + + + Error while initializing OpenGL 4.3! + + + + + Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver. + + + + + Error while initializing OpenGL! + + + + + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>Unsupported extensions:<br> + + + + + GameList + + + + Name + Nome + + + + + Compatibility + Compatibilidade + + + + + Add-ons + Acrescentos + + + + + + + File type + Tipo de Arquivo + + + + + + + Size + Tamanho + + + + Open Save Data Location + Abrir Localização de Dados Salvos + + + + Open Mod Data Location + Abrir a Localização de Dados do Mod + + + + Open Transferable Shader Cache + Abrir Shader Cache transferíveis + + + + Remove + + + + + Remove Installed Update + + + + + Remove All Installed DLC + + + + + Remove Shader Cache + + + + + Remove Custom Configuration + + + + + Remove All Installed Contents + + + + + Dump RomFS + Despejar RomFS + + + + 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 + + + + Properties + Propriedades + + + + Scan Subfolders + Examinar Sub-pastas + + + + Remove Game Directory + Remover diretório do Jogo + + + + ▲ Move Up + + + + + ▼ Move Down + + + + + Open Directory Location + Abrir Localização do diretório + + + + GameListItemCompat + + + Perfect + Perfeito + + + + Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without +any workarounds needed. + O Jogo Funciona na Perfeição sem falhas de áudio ou gráficas, todas as funcionalidades testadas funcionam como planeadas sem +quaisquer soluções alternativas necessárias. + + + + Great + Ótimo + + + + Game functions with minor graphical or audio glitches and is playable from start to finish. May require some +workarounds. + O Jogo funciona com pequenas falhas gráficas ou de áudio e pode ser jogado do principio ao fim. Pode exigir algumas +soluções alternativas. + + + + Okay + Ok + + + + Game functions with major graphical or audio glitches, but game is playable from start to finish with +workarounds. + O Jogo funciona com grandes falhas gráficas ou de áudio, mas o jogo é jogável do principio ao fim com +soluções alternativas. + + + + Bad + Mau + + + + Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches +even with workarounds. + Jogo Funcional, mas com grandes falhas gráficas ou de áudio. Incapaz de progredir em áreas específicas devido a falhas +mesmo com soluções alternativas + + + + Intro/Menu + Introdução / Menu + + + + Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start +Screen. + O Jogo não é jogável devido a grandes falhas gráficas ou de áudio. Não é possível 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. + + + + GameListPlaceholder + + + Double-click to add a new folder to the game list + Clique duas vezes para adicionar uma nova pasta à lista de jogos + + + + GameListSearchField + + + Filter: + Filtro: + + + + Enter pattern to filter + Digite o padrão para filtrar + + + + InstallDialog + + + Please confirm these are the files you wish to install. + + + + + Installing an Update or DLC will overwrite the previously installed one. + + + + + Install + + + + + Install Files to NAND + + + + + LoadingScreen + + + Loading Shaders 387 / 1628 + A Carregar Shaders 387 / 1628 + + + + Loading Shaders %v out of %m + A Carregar Shaders %v por %m + + + + Estimated Time 5m 4s + Tempo Estimado 5m 4s + + + + Loading... + A Carregar... + + + + Loading Shaders %1 / %2 + A Carregar Shaders %1 / %2 + + + + Launching... + A iniciar... + + + + Estimated Time %1 + Tempo Estimado %1 + + + + MainWindow + + + yuzu + yuzu + + + + &File + &Arquivo + + + + Recent Files + Arquivos Recentes + + + + &Emulation + &Emulação + + + + &View + &Vista + + + + Debugging + Depuração + + + + Tools + Ferramentas + + + + &Help + &Ajuda + + + + Install Files to NAND... + + + + + Load File... + Carregar Arquivo + + + + Load Folder... + Carregar Pasta + + + + E&xit + &Sair + + + + &Start + &Começar + + + + &Pause + &Pausa + + + + &Stop + &Parar + + + + Reinitialize keys... + Reinicialize as chaves ... + + + + About yuzu + Sobre yuzu + + + + Single Window Mode + Modo de Janela Única + + + + Configure... + Configurar ... + + + + Display Dock Widget Headers + Exibir Cabeçalhos de Ferramenta de Doca + + + + Show Filter Bar + Mostrar Barra de Filtros + + + + Show Status Bar + Mostrar Barra de Estado + + + + Reset Window Size + + + + + Fullscreen + Tela Cheia + + + + Restart + Reiniciar + + + + Load Amiibo... + Carregar Amiibo... + + + + Report Compatibility + Reportar Compatibilidade + + + + Open Mods Page + + + + + Open Quickstart Guide + + + + + FAQ + + + + + Open yuzu Folder + Abrir Pasta yuzu + + + + Capture Screenshot + Captura de Tela + + + + Configure Current Game.. + + + + + MicroProfileDialog + + + MicroProfile + Micro Perfil + + + + QObject + + + 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 + + + + + + Shift + Shift + + + + + + Ctrl + Ctrl + + + + + + Alt + Alt + + + + + + + [not set] + [não configurado] + + + + + + Hat %1 %2 + Hat %1 %2 + + + + + + Axis %1%2 + Eixo %1%2 + + + + + + Button %1 + Botão %1 + + + + + + + [unknown] + [Desconhecido] + + + + + Click 0 + + + + + + Click 1 + + + + + + Click 2 + + + + + + Click 3 + + + + + + Click 4 + + + + + GC Axis %1%2 + + + + + GC Button %1 + + + + + + [unused] + [sem uso] + + + + + Axis %1 + Eixo %1 + + + + + GC Axis %1 + + + + + QtErrorDisplay + + + An error has occured. +Please try again or contact the developer of the software. + +Error Code: %1-%2 (0x%3) + Ocorreu um erro. +Tente novamente ou entre em contato com o desenvolvedor do software. + +Código do Erro: %1-%2 (0x%3) + + + + An error occured on %1 at %2. +Please try again or contact the developer of the software. + +Error Code: %3-%4 (0x%5) + Ocorreu um erro em %1 até %2. +Tente novamente ou entre em contato com o desenvolvedor do software. + +Código do Erro: %3-%4 (0x%5) + + + + An error has occured. +Error Code: %1-%2 (0x%3) + +%4 + +%5 + Ocorreu um erro. +Código de Erro: %1-%2 (0x%3) + +%4 + +%5 + + + + QtProfileSelectionDialog + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Select a user: + Selecione um usuário: + + + + Users + Utilizadores + + + + Profile Selector + Selector de perfil + + + + QtSoftwareKeyboardDialog + + + Enter text: + Digite o texto: + + + + Software Keyboard + Teclado de Software + + + + SequenceDialog + + + Enter a hotkey + Introduza a tecla de atalho + + + + WaitTreeCallstack + + + Call stack + Pilha de Chamadas + + + + WaitTreeMutexInfo + + + waiting for mutex 0x%1 + esperando por mutex 0x% 1 + + + + has waiters: %1 + has waiters: %1 + + + + owner handle: 0x%1 + owner handle: 0x%1 + + + + WaitTreeObjectList + + + waiting for all objects + esperando por todos os objetos + + + + waiting for one of the following objects + esperando por todos os objectos + + + + WaitTreeSynchronizationObject + + + [%1]%2 %3 + + + + + waited by no thread + + + + + WaitTreeThread + + + + running + correr + + + + ready + preparado + + + + + paused + pausado + + + + waiting for HLE return + esperando pelo retorno de HLE + + + + sleeping + dormindo + + + + waiting for IPC reply + aguardando resposta do IPC + + + + waiting for objects + esperando por objectos + + + + waiting for mutex + esperando por mutex + + + + waiting for condition variable + A espera da variável de condição + + + + waiting for address arbiter + esperando pelo árbitro de endereço + + + + dormant + dormente + + + + dead + morto + + + + PC = 0x%1 LR = 0x%2 + PC = 0x%1 LR = 0x%2 + + + + ideal + ideal + + + + core %1 + núcleo %1 + + + + Unknown processor %1 + Processador desconhecido %1 + + + + processor = %1 + processador = %1 + + + + ideal core = %1 + núcleo ideal =% 1 + + + + affinity mask = %1 + máscara de afinidade =% 1 + + + + thread id = %1 + id do segmento =% 1 + + + + priority = %1(current) / %2(normal) + prioridade =%1(atual) / %2(normal) + + + + last running ticks = %1 + últimos tiques em execução =%1 + + + + not waiting for mutex + não esperar por mutex + + + + WaitTreeThreadList + + + waited by thread + esperado por tópico + + + + WaitTreeWidget + + + Wait Tree + Esquema de espera + + + \ No newline at end of file diff --git a/dist/languages/ru_RU.ts b/dist/languages/ru_RU.ts new file mode 100644 index 000000000..357b8e416 --- /dev/null +++ b/dist/languages/ru_RU.ts @@ -0,0 +1,4720 @@ + + + AboutDialog + + + About yuzu + О yuzu + + + + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + + + + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + + + + <html><head/><body><p>%1 | %2-%3 (%4)</p></body></html> + <html><head/><body><p>%1 | %2-%3 (%4)</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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu - экспериментальный эмулятор для Nintendo Switch с открытым исходным кодом, лицензированный под GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">Это ПО не должно использоваться для запуска игр, которые были получены нелегально.</span></p></body></html> + + + + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Веб-сайт</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Исходный код</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Авторы</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/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. yuzu is not affiliated with Nintendo in any way.</span></p></body></html> + <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; является торговой маркой Nintendo. yuzu никак не связан с Nintendo.</span></p></body></html> + + + + CalibrationConfigurationDialog + + + Communicating with the server... + + + + + Cancel + + + + + Touch the top left corner <br>of your touchpad. + + + + + Now touch the bottom right corner <br>of your touchpad. + + + + + Configuration completed! + + + + + OK + + + + + 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://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu 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 yuzu 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 yuzu account</li></ul></body></html> + <html><head/><body><p><span style=" font-size:10pt;">Если вы захотите отправить отчет в </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">Список Совместимости yuzu</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;">Версия yuzu</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Подключенный аккаунт yuzu</li></ul></body></html> + + + + Perfect + Идеально + + + + <html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html> + <html><head/><body><p>Игра функционирует отлично, без звуковых и графических артефактов.</p></body></html> + + + + Great + Отлично + + + + <html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html> + <html><head/><body><p>Игра работает с небольшими графическими или звуковыми артефактами и может быть пройдена от начала до конца. Могут потребоваться обходные пути.</p></body></html> + + + + Okay + Нормально + + + + <html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html> + <html><head/><body><p>Игра работает с существенными графическими или звуковыми артефактами, но с обходными путями может быть пройдена.</p></body></html> + + + + Bad + Плохо + + + + <html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html> + <html><head/><body><p>Игра работает, но с существенными графическими или звуковыми артефактами. В некоторых зонах нельзя продвинуться даже с обходными путями.</p></body></html> + + + + Intro/Menu + Вступление/Меню + + + + <html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html> + <html><head/><body><p>В игру невозможно играть из-за графических или звуковых артефактов. Невозможно продвинуться дальше Стартового Экрана.</p></body></html> + + + + Won't Boot + Не Запускается + + + + <html><head/><body><p>The game crashes when attempting to startup.</p></body></html> + <html><head/><body><p>Игра падает при старте.</p></body></html> + + + + <html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html> + <html><head/><body><p>Независимо от скорости и производительности, насколько хорошо эта игра работает от начала до конца в текущей версии yuzu?</p></body></html> + + + + Thank you for your submission! + Спасибо за ваш отчет! + + + + Submitting + Отправка + + + + Communication error + Ошибка соединения + + + + An error occured while sending the Testcase + Произошла ошибка при отправке Отчета + + + + Next + Далее + + + + ConfigureAudio + + + Audio + Звук + + + + Output Engine: + Механизм вывода: + + + + This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency. + Этот эффект пост-обработки регулирует скорость звука в соответствии со скоростью эмуляции и помогает предотвратить заикание звука. Это, однако, увеличивает задержку звука. + + + + Enable audio stretching + Включить растяжение звука + + + + Audio Device: + Звуковое Устройство: + + + + Use global volume + + + + + Set volume: + + + + + Volume: + Громкость: + + + + 0 % + 0 % + + + + %1% + Volume percentage (e.g. 50%) + %1% + + + + ConfigureCpu + + + Form + + + + + General + + + + + Accuracy: + + + + + Accurate + + + + + Unsafe + + + + + Enable Debug Mode + + + + + We recommend setting accuracy to "Accurate". + + + + + Unsafe CPU Optimization Settings + + + + + These settings reduce accuracy for speed. + + + + + Unfuse FMA (improve performance on CPUs without FMA) + + + + + + <div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div> + + + + + + Faster FRSQRTE and FRECPE + + + + + + <div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div> + + + + + + CPU settings are available only when game is not running. + + + + + Setting CPU to Debug Mode + + + + + CPU Debug Mode is only intended for developer use. Are you sure you want to enable this? + + + + + ConfigureCpuDebug + + + Form + + + + + Toggle CPU Optimizations + + + + + + <div> + <b>For debugging only.</b> + <br> + If you're not sure what these do, keep all of these enabled. + <br> + These settings only take effect when CPU Accuracy is "Debug Mode". + </div> + + + + + + Enable inline page tables + + + + + + <div style="white-space: nowrap">This optimization speeds up memory accesses by the guest program.</div> + <div style="white-space: nowrap">Enabling it inlines accesses to PageTable::pointers into emitted code.</div> + <div style="white-space: nowrap">Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.</div> + + + + + + Enable block linking + + + + + + <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> + + + + + + Enable return stack buffer + + + + + + <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> + + + + + + Enable fast dispatcher + + + + + + <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> + + + + + + Enable context elimination + + + + + + <div>Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.</div> + + + + + + Enable constant propagation + + + + + + <div>Enables IR optimizations that involve constant propagation.</div> + + + + + + Enable miscellaneous optimizations + + + + + + <div>Enables miscellaneous IR optimizations.</div> + + + + + + Enable misalignment check reduction + + + + + + <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> + + + + + + CPU settings are available only when game is not running. + + + + + ConfigureDebug + + + Form + Форма + + + + GDB + GDB + + + + Enable GDB Stub + Включить GDB Stub + + + + Port: + Порт: + + + + Logging + Журналирование + + + + Global Log Filter + Глобальный Фильтр Журнала + + + + Show Log Console (Windows Only) + Показывать Журнал в Консоли (Только Для Windows) + + + + Open Log Location + Открыть Расположение Журнала + + + + Homebrew + Homebrew + + + + Arguments String + Строка Аргументов + + + + Graphics + + + + + When checked, the graphics API enters in a slower debugging mode + + + + + Enable Graphics Debugging + + + + + When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower + + + + + Disable Macro JIT + + + + + Dump + + + + + Enable Verbose Reporting Services + Включить Службы Подробных Отчётов + + + + This will be reset automatically when yuzu closes. + Это будет автоматически сброшено после закрытия yuzu. + + + + Advanced + Дополнительно + + + + Kiosk (Quest) Mode + + + + + ConfigureDebugController + + + Configure Debug Controller + + + + + Clear + + + + + Defaults + + + + + ConfigureDialog + + + yuzu Configuration + Параметры yuzu + + + + + + + General + Основное + + + + + UI + Интерфейс + + + + Game List + Список Игр + + + + + + + System + Система + + + + + + Profiles + Профили + + + + + + Filesystem + Файловая система + + + + + + + Controls + Управление + + + + + + Hotkeys + Горячие клавиши + + + + + + + CPU + + + + + + + + + + Debug + Отладка + + + + + + + Graphics + Графика + + + + + Advanced + + + + + GraphicsAdvanced + + + + + + + + Audio + Звук + + + + + + Web + Веб + + + + + + Services + Сервисы + + + + ConfigureFilesystem + + + Form + Форма + + + + Storage Directories + Директории Хранения + + + + NAND + NAND + + + + + + + + + ... + ... + + + + SD Card + SD карта + + + + Gamecard + Картридж + + + + Path + Путь + + + + Inserted + Вставлен + + + + Current Game + Текущая Игра + + + + Patch Manager + Управление Патчами + + + + Dump Decompressed NSOs + Дампить Распакованные NCO + + + + Dump ExeFS + Дампить ExeFS + + + + Mod Load Root + + + + + Dump Root + + + + + Caching + Кэширование + + + + Cache Directory + Директория Кэша + + + + Cache Game List Metadata + + + + + + + + Reset Metadata Cache + Сбросить Кэш Метаданных + + + + Select Emulated NAND Directory... + Выбрать Папку Для Эмулируемого NAND + + + + Select Emulated SD Directory... + Выбрать Папку Для Эмулируемого SD + + + + Select Gamecard Path... + + + + + Select Dump Directory... + Выберите Папку с Дампами + + + + Select Mod Load Directory... + + + + + Select Cache Directory... + Выбрать Папку с Кэшем... + + + + 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. + Кэш метаданных не может быть удален. Он может использоваться или отсутствовать. + + + + ConfigureGeneral + + + Form + Форма + + + + General + Основное + + + + Limit Speed Percent + Ограничить Скорость в Процентах + + + + % + % + + + + Multicore CPU Emulation + + + + + Confirm exit while emulation is running + Подтверждать выход во время эмуляции + + + + Prompt for user on game boot + + + + + Pause emulation when in background + Приостанавливать эмуляцию, когда в фоновом режиме + + + + Hide mouse on inactivity + + + + + ConfigureGraphics + + + Form + Форма + + + + API Settings + + + + + API: + + + + + Device: + + + + + Graphics Settings + + + + + Use disk shader cache + Использовать кэш шейдеров на диске + + + + Use asynchronous GPU emulation + Использовать асинхронную эмуляцию ГП + + + + Aspect Ratio: + + + + + Default (16:9) + + + + + Force 4:3 + + + + + Force 21:9 + + + + + Stretch to Window + + + + + + Use global background color + + + + + Set background color: + + + + + Background Color: + Фоновый Цвет: + + + + OpenGL Graphics Device + + + + + ConfigureGraphicsAdvanced + + + Form + + + + + Advanced Graphics Settings + + + + + Accuracy Level: + + + + + VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference. + + + + + Use VSync (OpenGL only) + + + + + Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental. + + + + + Use assembly shaders (experimental, Nvidia OpenGL only) + + + + + Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental. + + + + + Use asynchronous shader building (experimental) + + + + + Use Fast GPU Time + + + + + Anisotropic Filtering: + + + + + Default + + + + + 2x + + + + + 4x + + + + + 8x + + + + + 16x + + + + + ConfigureHotkeys + + + Hotkey Settings + Настройки Горячих Клавиш + + + + Double-click on a binding to change it. + + + + + Clear All + + + + + Restore Defaults + + + + + Action + Действие + + + + Hotkey + Сочетание + + + + Context + Контекст + + + + + Conflicting Key Sequence + Конфликтующее Сочетание Клавиш + + + + The entered key sequence is already assigned to: %1 + + + + + Restore Default + + + + + Clear + + + + + The default key sequence is already assigned to: %1 + + + + + ConfigureInput + + + ConfigureInput + + + + + + Player 1 + Игрок 1 + + + + + Player 2 + Игрок 2 + + + + + Player 3 + Игрок 3 + + + + + Player 4 + Игрок 4 + + + + + Player 5 + Игрок 5 + + + + + Player 6 + Игрок 6 + + + + + Player 7 + Игрок 7 + + + + + Player 8 + Игрок 8 + + + + + Advanced + Дополнительно + + + + Console Mode + + + + + Docked + + + + + Undocked + + + + + Vibration + + + + + % + + + + + Motion + + + + + Configure + Настроить + + + + Controllers + + + + + 1 + + + + + 2 + + + + + 3 + + + + + 4 + + + + + 5 + + + + + 6 + + + + + 7 + + + + + 8 + + + + + Connected + + + + + Defaults + + + + + Clear + + + + + ConfigureInputAdvanced + + + Configure Input + + + + + Joycon Colors + + + + + Player 1 + + + + + + + + + + + + L Body + + + + + + + + + + + + L Button + + + + + + + + + + + + R Body + + + + + + + + + + + + R Button + + + + + Player 2 + + + + + Player 3 + + + + + Player 4 + + + + + Player 5 + + + + + Player 6 + + + + + Player 7 + + + + + Player 8 + + + + + Other + + + + + Keyboard + + + + + + Advanced + + + + + Touchscreen + + + + + Mouse + + + + + Motion / Touch + + + + + + Configure + + + + + Debug Controller + + + + + ConfigureInputPlayer + + + Configure Input + Настроить Ввод + + + + Connect Controller + + + + + + + Pro Controller + + + + + + Dual Joycons + + + + + + Left Joycon + + + + + + Right Joycon + + + + + + Handheld + + + + + Input Device + + + + + Any + + + + + Keyboard/Mouse + + + + + Profile + + + + + Save + + + + + New + + + + + Delete + + + + + Left Stick + Левый Стик + + + + + + + + + Up + + + + + + + + + + Left + + + + + + + + + + Right + + + + + + + + + + Down + + + + + + + + Pressed + + + + + + + + Modifier + + + + + + Range + + + + + + % + + + + + + Deadzone: 0% + + + + + + Modifier Range: 0% + + + + + D-Pad + + + + + + L + + + + + + ZL + + + + + + Minus + + + + + + Capture + + + + + + Plus + + + + + + Home + + + + + + R + + + + + + ZR + + + + + + SL + + + + + + SR + + + + + Face Buttons + Основные Кнопки + + + + + X + + + + + + Y + + + + + + A + + + + + + B + + + + + Right Stick + Правый Стик + + + + + Deadzone: %1% + + + + + + Modifier Range: %1% + + + + + [waiting] + + + + + ConfigureMotionTouch + + + Configure Motion / Touch + + + + + Motion + + + + + Motion Provider: + + + + + Sensitivity: + + + + + Touch + + + + + Touch Provider: + + + + + Calibration: + + + + + (100, 50) - (1800, 850) + + + + + + + Configure + + + + + Use button mapping: + + + + + CemuhookUDP Config + + + + + You may use any Cemuhook compatible UDP input source to provide motion and touch input. + + + + + Server: + + + + + Port: + + + + + Pad: + + + + + Pad 1 + + + + + Pad 2 + + + + + Pad 3 + + + + + Pad 4 + + + + + Learn More + + + + + + Test + + + + + Mouse (Right Click) + + + + + + CemuhookUDP + + + + + Emulator Window + + + + + <a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Learn More</span></a> + + + + + 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. + + + + + Citra + + + + + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. + + + + + ConfigureMouseAdvanced + + + Configure Mouse + Настроить Мышь + + + + Mouse Buttons + Кнопки на Мыши + + + + Forward: + Вперед: + + + + Back: + Назад: + + + + Left: + Левая: + + + + Middle: + Средняя: + + + + Right: + Правая: + + + + + Clear + Очистить + + + + Defaults + + + + + [not set] + [не задано] + + + + Restore Default + Настройки по Умолчанию + + + + [press key] + [нажмите кнопку] + + + + ConfigurePerGame + + + Dialog + + + + + Info + + + + + Name + + + + + Title ID + + + + + Filename + + + + + Format + + + + + Version + + + + + Size + + + + + Developer + + + + + Add-Ons + + + + + General + + + + + System + + + + + Graphics + + + + + Adv. Graphics + + + + + Audio + + + + + Properties + + + + + Use global configuration (%1) + + + + + ConfigurePerGameAddons + + + Form + + + + + Patch Name + + + + + Version + + + + + ConfigureProfileManager + + + Form + Форма + + + + Profile Manager + Управление Профилями + + + + Current User + Текущий Пользователь + + + + Username + Имя Пользователя + + + + Set Image + Добавить Изображение + + + + Add + Добавить + + + + Rename + Переименовать + + + + Remove + Удалить + + + + Profile management is available only when game is not running. + Управление профилями доступно только тогда, когда игра не запущена. + + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Enter Username + Введите Имя Пользователя + + + + Users + Пользователи + + + + Enter a username for the new user: + Введите имя пользователя для нового профиля: + + + + Enter a new username: + Введите новое имя пользователя: + + + + Confirm Delete + Подтвердите Удаление + + + + You are about to delete user with name "%1". Are you sure? + Вы собираетесь удалить пользователя "%1". Вы уверены? + + + + Select User Image + Выберите Изображение для Пользователя + + + + JPEG Images (*.jpg *.jpeg) + JPEG Images (*.jpg *.jpeg) + + + + Error deleting image + Ошибка при удалении изображения + + + + Error occurred attempting to overwrite previous image at: %1. + Ошибка при попытке перезаписи предыдущего изображения в: %1. + + + + Error deleting file + Ошибка при удалении файла + + + + Unable to delete existing file: %1. + Не получилось удалить существующий файл: %1. + + + + Error creating user image directory + Ошибка при создании папки пользовательских изображений + + + + Unable to create directory %1 for storing user images. + Не получилось создать папку %1 для хранения изображений пользователя. + + + + Error copying user image + Ошибка при копировании изображения пользователя + + + + Unable to copy image from %1 to %2 + Не получилось скопировать изображение из %1 в %2 + + + + ConfigureService + + + Form + Форма + + + + BCAT + BCAT + + + + BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content. + BCAT - это способ Nintendo отправлять данные в игры, чтобы привлечь сообщество и разблокировать дополнительный контент. + + + + BCAT Backend + + + + + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Learn more about BCAT, Boxcat, and Current Events</span></a></p></body></html> + + + + + The boxcat service is offline or you are not connected to the internet. + + + + + There was an error while processing the boxcat event data. Contact the yuzu developers. + Произошла ошибка при обработке данных событий boxcat. Свяжитесь с разработчиками yuzu. + + + + The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu. + Используемая вами версия yuzu слишком новая или слишком старая по сравнению с версией на сервере. Попробуйте обновить yuzu до последней официальной версии. + + + + + There are currently no events on boxcat. + + + + + + Current Boxcat Events + + + + + Yuzu is retrieving the latest boxcat status... + + + + + ConfigureSystem + + + Form + Форма + + + + System Settings + Настройки Системы + + + + Region: + + + + + Auto + + + + + Default + + + + + CET + + + + + CST6CDT + + + + + Cuba + + + + + EET + + + + + Egypt + + + + + Eire + + + + + EST + + + + + EST5EDT + + + + + GB + + + + + GB-Eire + + + + + GMT + + + + + GMT+0 + + + + + GMT-0 + + + + + GMT0 + + + + + Greenwich + + + + + Hongkong + + + + + HST + + + + + Iceland + + + + + Iran + + + + + Israel + + + + + Jamaica + + + + + + Japan + + + + + Kwajalein + + + + + Libya + + + + + MET + + + + + MST + + + + + MST7MDT + + + + + Navajo + + + + + NZ + + + + + NZ-CHAT + + + + + Poland + + + + + Portugal + + + + + PRC + + + + + PST8PDT + + + + + ROC + + + + + ROK + + + + + Singapore + + + + + Turkey + + + + + UCT + + + + + Universal + + + + + UTC + + + + + W-SU + + + + + WET + + + + + Zulu + + + + + USA + + + + + Europe + + + + + Australia + + + + + China + + + + + Korea + + + + + Taiwan + + + + + Time Zone: + + + + + Note: this can be overridden when region setting is auto-select + Примечание: может быть перезаписано если регион выбирается автоматически + + + + Japanese (日本語) + Японский (日本語) + + + + English + Английский (English) + + + + French (français) + Французский (français) + + + + German (Deutsch) + Немецкий (Deutsch) + + + + Italian (italiano) + Итальянский (italiano) + + + + Spanish (español) + Испанский (español) + + + + Chinese + Китайский (Chinese) + + + + Korean (한국어) + Корейский (한국어) + + + + Dutch (Nederlands) + Голландский (Nederlands) + + + + Portuguese (português) + Португальский (português) + + + + Russian (Русский) + Русский + + + + Taiwanese + Тайваньский + + + + British English + Британский Английский + + + + Canadian French + Канадский Французский + + + + Latin American Spanish + Латиноамериканский Испанский + + + + Simplified Chinese + Упрощенный Китайский + + + + Traditional Chinese (正體中文) + Традиционный Китайский (正體中文) + + + + Custom RTC + Пользовательский RTC + + + + Language + Язык + + + + RNG Seed + Зерно RNG + + + + Mono + Моно + + + + Stereo + Стерео + + + + Surround + Объёмный звук + + + + Console ID: + Идентификатор Консоли: + + + + Sound output mode + Режим вывода звука + + + + d MMM yyyy h:mm:ss AP + + + + + Regenerate + Перегенерировать + + + + System settings are available only when game is not running. + Настройки системы доступны только тогда, когда игра не запущена. + + + + This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue? + Это заменит ваш текущий виртуальный Switch новым. Ваш текущий виртуальный Switch будет безвозвратно потерян. Это может иметь неожиданные последствия в играх. Может не сработать, если вы используете устаревшую конфигурацию сохраненных игр. Продолжить? + + + + Warning + Внимание + + + + Console ID: 0x%1 + Идентификатор Консоли: 0x%1 + + + + ConfigureTouchFromButton + + + Configure Touchscreen Mappings + + + + + Mapping: + + + + + New + + + + + Delete + + + + + 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 + + + + + Button + + + + + X + X axis + + + + + Y + Y axis + + + + + New Profile + + + + + Enter the name for the new profile. + + + + + Delete Profile + + + + + Delete profile %1? + + + + + Rename Profile + + + + + New name: + + + + + [press key] + + + + + ConfigureTouchscreenAdvanced + + + Configure Touchscreen + Настроить Сенсорный Экран + + + + Warning: The settings in this page affect the inner workings of yuzu'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. + Внимание: Настройки на этой странице влияют на внутреннюю работу эмулируемого сенсорного экрана yuzu. Их изменение может привести к нежелательному поведению, как например частичная или полная неработоспособность сенсорного экрана. Используйте их, только если вы знаете, что делаете. + + + + Touch Parameters + Параметры Касания + + + + Touch Diameter Y + Диаметр Касания Y + + + + Finger + Палец + + + + Touch Diameter X + Диаметр Касания X + + + + Rotational Angle + Угол Поворота + + + + Restore Defaults + Настройки по Умолчанию + + + + ConfigureUi + + + Form + Форма + + + + General + Основное + + + + Note: Changing language will apply your configuration. + Примечание: Изменение языка приведет к применению настроек. + + + + Interface language: + Язык интерфейса: + + + + Theme: + Тема: + + + + Game List + Список Игр + + + + Show Add-Ons Column + Показывать Столбец Дополнений + + + + Icon Size: + Размер Иконок: + + + + Row 1 Text: + Текст 1 Строки: + + + + Row 2 Text: + Текст 2 Строки: + + + + Screenshots + + + + + Ask Where To Save Screenshots (Windows Only) + + + + + Screenshots Path: + + + + + ... + + + + + Select Screenshots Path... + + + + + <System> + <System> + + + + English + Английский + + + + ConfigureWeb + + + Form + Форма + + + + yuzu Web Service + yuzu Веб Сервис + + + + By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information. + Предоставляя ваше имя пользователя и токен, вы разрешаете yuzu собирать дополнительную информацию об использовании, которая может включать данные идентифицирующие пользователя. + + + + + Verify + Подтвердить + + + + Sign up + Регистрация + + + + Token: + Токен: + + + + Username: + Имя Пользователя: + + + + What is my token? + Что такое токен? + + + + Telemetry + Телеметрия + + + + Share anonymous usage data with the yuzu team + Делиться анонимной информацией об использовании с командой yuzu + + + + Learn more + Узнать больше + + + + Telemetry ID: + ID Телеметрии: + + + + Regenerate + Перегенерировать + + + + Discord Presence + Интеграция с Discord + + + + Show Current Game in your Discord Status + Показывать Текущую Игру в вашем Статусе Discord + + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Learn more</span></a> + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Узнать больше</span></a> + + + + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Sign up</span></a> + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Регистрация</span></a> + + + + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">What is my token?</span></a> + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">Что такое токен?</span></a> + + + + + Telemetry ID: 0x%1 + ID Телеметрии: 0x%1 + + + + + Unspecified + Отсутствует + + + + Token not verified + Токен не подтвержден + + + + Token was not verified. The change to your token has not been saved. + Токен не подтвержден. Ваш токен не был изменен. + + + + Verifying... + Проверка... + + + + Verification failed + Ошибка подтверждения + + + + Verification failed. Check that you have entered your token correctly, and that your internet connection is working. + Ошибка подтверждения. Убедитесь в том, что токен введен корректно, и ваше интернет соединение активно. + + + + GMainWindow + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous data is collected</a> to help improve yuzu. <br/><br/>Would you like to share your usage data with us? + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Анонимные данные собираются</a> для улучшения yuzu. <br/><br/>Хотели бы вы делиться данными об использовании с нами? + + + + Telemetry + Телеметрия + + + + Text Check Failed + Ошибка Проверки Текста + + + + Loading Web Applet... + + + + + Exit Web Applet + + + + + Exit + Выход + + + + To exit the web application, use the game provided controls to select exit, select the 'Exit Web Applet' option in the menu bar, or press the 'Enter' key. + + + + + Web Applet + + + + + This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested. + Эта версия yuzu была собрана без поддержки QtWebEngine, что означает, что yuzu не может нормально отобразить руководство к игре или запрошенную веб-страницу. + + + + The amount of shaders currently being built + + + + + 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, не принимая во внимание ограничение FPS или v-sync. Для полноскоростной эмуляции значение должно быть не больше 16,67 мс. + + + + DOCK + + + + + ASYNC + + + + + MULTICORE + + + + + VULKAN + + + + + OPENGL + + + + + Clear Recent Files + Очистить Недавние Файлы + + + + 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><br>For an explanation of the various Switch formats yuzu supports, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our wiki</a>. This message will not be shown again. + Для этой игры вы используете разархивированный формат РОМа, который является устаревшим и был заменен другими, такими как NCA, NAX, XCI или NSP. В разархивированных каталогах РОМа отсутствуют значки, метаданные и поддержка обновлений. <br><br>Для получения информации о различных форматах Switch, поддерживаемых yuzu, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>просмотрите нашу вики</a>. Это сообщение больше не будет отображаться. + + + + + Error while loading ROM! + Ошибка при загрузке РОМа! + + + + The ROM format is not supported. + Формат РОМа не поддерживается. + + + + An error occurred initializing the video core. + Произошла ошибка при инициализации видеоядра. + + + + yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.Ensure that you have the latest graphics drivers for your GPU. + yuzu обнаружил ошибку во время работы видеоядра. Проверьте журнал для подробностей. Информацию о доступе к журналу можно найти на этой странице: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Как Отправить Файл Журнала.</a> Убедитесь, что у вас установлены последние графические драйверы. + + + + Error while loading ROM! + + + + + An unknown error occurred. Please see the log for more details. + Произошла неизвестная ошибка. Пожалуйста, проверьте лог для подробностей. + + + + Start + Начать + + + + Save Data + Данные Сохранений + + + + Mod Data + Данные Модов + + + + Error Opening %1 Folder + Ошибка при Открытии Папки %1 + + + + + Folder does not exist! + Папка не существует! + + + + Error Opening Transferable Shader Cache + Ошибка при Открытии Переносного Кеша Шейдеров + + + + + A shader cache for this title does not exist. + Шейдерный кеш для этого идентификатора не существует. + + + + Contents + + + + + Update + + + + + DLC + + + + + Remove Entry + + + + + Remove Installed Game %1? + + + + + + + + + Successfully Removed + + + + + Successfully removed the installed base game. + + + + + + + Error Removing %1 + + + + + The base game is not installed in the NAND and cannot be removed. + + + + + Successfully removed the installed update. + + + + + There is no update installed for this title. + + + + + There are no DLC installed for this title. + + + + + Successfully removed %1 installed DLC. + + + + + Delete Transferable Shader Cache? + + + + + Remove Custom Game Configuration? + + + + + Remove File + + + + + + Error Removing Transferable Shader Cache + + + + + Successfully removed the transferable shader cache. + + + + + Failed to remove the transferable shader cache. + + + + + + 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. + + + + + 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>Структура создаст только структуру каталогов. + + + + 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 + Открыть Папку Извлечённого РОМа + + + + 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 + + + + + Installing file "%1"... + Установка файла "%1"... + + + + Install Results + + + + + System Application + Системное Приложение + + + + System Archive + Системный Архив + + + + System Application Update + Обновление Системного Приложения + + + + Firmware Package (Type A) + Контейнер Прошивки (Тип А) + + + + Firmware Package (Type 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" не найден + + + + + Continue + Продолжить + + + + Error Display + Показ Ошибок + + + + Missing yuzu Account + Отсутствует Аккаунт yuzu + + + + In order to submit a game compatibility test case, you must link your yuzu account.<br><br/>To link your yuzu account, go to Emulation &gt; Configuration &gt; Web. + Чтобы отправить отчет о совместимости игры, необходимо привязать свою учетную запись yuzu.<br><br/>Чтобы привязать свою учетную запись yuzu, перейдите в раздел Эмуляция &gt; Настройка &gt; Веб. + + + + Error opening URL + + + + + Unable to open the URL "%1". + + + + + Amiibo File (%1);; All Files (*.*) + Файл Amiibo (%1);; Все Файлы (*.*) + + + + Load Amiibo + Загрузить Amiibo + + + + Error opening Amiibo data file + Ошибка открытия файла данных Amiibo + + + + Unable to open Amiibo file "%1" for reading. + Невозможно открыть файл Amiibo "%1" для чтения. + + + + Error reading Amiibo data file + Ошибка чтения файла данных Amiibo + + + + Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes. + Невозможно полностью прочитать данные Amiibo. Ожидалось прочитать %1 байт, но удалось прочитать только %2 байт. + + + + Error loading Amiibo data + Ошибка загрузки данных Amiibo + + + + Unable to load Amiibo data. + Невозможно загрузить данные Amiibo. + + + + Capture Screenshot + Сделать Скриншот + + + + PNG Image (*.png) + PNG Image (*.png) + + + + Speed: %1% / %2% + Скорость: %1% / %2% + + + + Speed: %1% + Скорость: %1% + + + + Game: %1 FPS + Игра: %1 FPS + + + + Frame: %1 ms + Один кадр: %1 ms + + + + The game you are trying to load requires additional files from your Switch to be dumped before playing.<br/><br/>For more information on dumping these files, please see the following wiki page: <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + Игра, которую вы пытаетесь загрузить, требует, чтобы дополнительные файлы были сдамплены с вашего Switch перед началом игры. <br/><br/>Для получения дополнительной информации о дампе этих файлов см. следующую вики-страницу: <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Дамп Системных Архивов и Общих Шрифтов с консоли</a>. <br/><br/>Хотите вернуться к списку игр? Продолжение эмуляции может привести к сбоям, повреждению сохраненных данных или другим ошибкам. + + + + yuzu was unable to locate a Switch system archive. %1 + yuzu не удалось найти системный архив Switch. %1 + + + + yuzu was unable to locate a Switch system archive: %1. %2 + yuzu не удалось найти системный архив Switch: %1. %2 + + + + System Archive Not Found + Системный Архив Не Найден + + + + System Archive Missing + Отсутствует Системный Архив + + + + yuzu was unable to locate the Switch shared fonts. %1 + yuzu не удалось найти общие шрифты Switch. %1 + + + + Shared Fonts Not Found + Общие Шрифты Не Найдены + + + + Shared Font Missing + Общие Шрифты Отсутствуют + + + + Fatal Error + Фатальная Ошибка + + + + yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + yuzu столкнулся с фатальной ошибкой, смотрите подробности в журнале. Информацию о доступе к журналу можно найти на этой странице: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>Как Отправить Файл Журнала</a>.<br/><br/>Хотите вернуться в список игр? Продолжение эмуляции может привести к сбоям, повреждению сохраненных данных или другим ошибкам. + + + + Fatal Error encountered + Обнаружена Фатальная ошибка + + + + Confirm Key Rederivation + Подтвердите Перерасчет Ключа + + + + You are about to force rederive all of your keys. +If you do not know what this means or what you are doing, +this is a potentially destructive action. +Please make sure this is what you want +and optionally make backups. + +This will delete your autogenerated key files and re-run the key derivation module. + Вы собираетесь принудительно пересчитать все ваши ключи. +Если вы не знаете, что это значит или что вы делаете, +это потенциально разрушительное действие. +Пожалуйста, убедитесь, что это то, что вы хотите +и при желании сделайте резервные копии. + +Это удалит ваши автоматически сгенерированные файлы ключей и повторно запустит модуль расчета ключей. + + + + Missing fuses + + + + + - Missing BOOT0 + + + + + - Missing BCPKG2-1-Normal-Main + + + + + - Missing PRODINFO + + + + + Derivation Components Missing + + + + + Components are missing that may hinder key derivation from completing. <br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu quickstart guide</a> to get all your keys and games.<br><br><small>(%1)</small> + + + + + Deriving keys... +This may take up to a minute depending +on your system's performance. + Получение ключей... +Это может занять до минуты в зависимости +от производительности вашей системы. + + + + Deriving Keys + Получение Ключей + + + + Select RomFS Dump Target + Выберите Цель для Дампа RomFS + + + + Please select which RomFS you would like to dump. + Пожалуйста, выберите, какой RomFS вы хотите сдампить. + + + + + + yuzu + yuzu + + + + Are you sure you want to close yuzu? + Вы уверены, что хотите закрыть yuzu? + + + + Are you sure you want to stop the emulation? Any unsaved progress will be lost. + Вы уверены, что хотите остановить эмуляцию? Любой несохраненный прогресс будет потерян. + + + + The currently running application has requested yuzu to not exit. + +Would you like to bypass this and exit anyway? + Запущенное в настоящий момент приложение просит yuzu не завершать работу. + +Проигнорировать и выйти в любом случае? + + + + GRenderWindow + + + OpenGL not available! + + + + + yuzu has not been compiled with OpenGL support. + + + + + Vulkan not available! + + + + + yuzu has not been compiled with Vulkan support. + + + + + Error while initializing OpenGL 4.3! + + + + + Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver. + + + + + Error while initializing OpenGL! + + + + + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>Unsupported extensions:<br> + + + + + GameList + + + + Name + Имя + + + + + Compatibility + Совместимость + + + + + Add-ons + Дополнения + + + + + + + File type + Тип Файла + + + + + + + Size + Размер + + + + Open Save Data Location + Открыть Папку Сохранений + + + + Open Mod Data Location + Открыть Папку Модов + + + + Open Transferable Shader Cache + Открыть Переносной Кеш Шейдеров + + + + Remove + + + + + Remove Installed Update + + + + + Remove All Installed DLC + + + + + Remove Shader Cache + + + + + Remove Custom Configuration + + + + + Remove All Installed Contents + + + + + Dump RomFS + Сдампить RomFS + + + + Copy Title ID to Clipboard + Скопировать ID приложения в буфер обмена + + + + Navigate to GameDB entry + Перейти к записи GameDB + + + + Properties + Свойства + + + + Scan Subfolders + Сканировать Подпапки + + + + Remove Game Directory + Удалить Папку с Играми + + + + ▲ Move Up + + + + + ▼ Move Down + + + + + Open Directory Location + Открыть Расположение Папки + + + + GameListItemCompat + + + Perfect + Идеально + + + + Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without +any workarounds needed. + Игра идёт безупречно, без звуковых или графических артефактов, все протестированные функции работают без обходных путей. + + + + Great + Отлично + + + + Game functions with minor graphical or audio glitches and is playable from start to finish. May require some +workarounds. + Игра работает с небольшими графическими или звуковыми артефактами и может быть пройдена от начала до конца. Могут потребоваться обходные пути. + + + + Okay + Нормально + + + + Game functions with major graphical or audio glitches, but game is playable from start to finish with +workarounds. + Игра работает с существенными графическими или звуковыми артефактами, но с обходными путями может быть пройдена. + + + + Bad + Плохо + + + + Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches +even with workarounds. + Игра работает, но с существенными графическими или звуковыми артефактами. В некоторых зонах нельзя продвинуться даже с обходными путями. + + + + Intro/Menu + Вступление/Меню + + + + Game is completely unplayable due to major graphical or audio glitches. 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. + Игру ещё не проверяли на совместимость. + + + + GameListPlaceholder + + + Double-click to add a new folder to the game list + Двойной клик, чтобы добавить новую папку в список игр + + + + GameListSearchField + + + Filter: + Поиск: + + + + Enter pattern to filter + Введите текст для поиска + + + + InstallDialog + + + Please confirm these are the files you wish to install. + + + + + Installing an Update or DLC will overwrite the previously installed one. + + + + + Install + + + + + Install Files to NAND + + + + + LoadingScreen + + + Loading Shaders 387 / 1628 + Загрузка Шейдера 387 / 1628 + + + + Loading Shaders %v out of %m + Загрузка Шейдера %v из %m + + + + Estimated Time 5m 4s + Примерное время 5м 4с + + + + Loading... + Загрузка... + + + + Loading Shaders %1 / %2 + Загрузка Шейдеров %1 / %2 + + + + Launching... + Запуск... + + + + Estimated Time %1 + Примерное Время %1 + + + + MainWindow + + + yuzu + yuzu + + + + &File + &Файл + + + + Recent Files + Недавние Файлы + + + + &Emulation + &Эмуляция + + + + &View + &Вид + + + + Debugging + Отладка + + + + Tools + Инструменты + + + + &Help + &Помощь + + + + Install Files to NAND... + + + + + Load File... + Загрузить Файл... + + + + Load Folder... + Загрузить Папку... + + + + E&xit + &Выход + + + + &Start + &Начать + + + + &Pause + &Пауза + + + + &Stop + &Стоп + + + + Reinitialize keys... + Переинициализировать ключи... + + + + About yuzu + О yuzu + + + + Single Window Mode + Режим Одного Окна + + + + Configure... + Настроить... + + + + Display Dock Widget Headers + Отображать Заголовки Виджетов Дока + + + + Show Filter Bar + Показать Панель Поиска + + + + Show Status Bar + Показать Панель Статуса + + + + Reset Window Size + + + + + Fullscreen + Полный Экран + + + + Restart + Перезапустить + + + + Load Amiibo... + Загрузить Amiibo… + + + + Report Compatibility + Сообщить о Совместимости + + + + Open Mods Page + + + + + Open Quickstart Guide + + + + + FAQ + + + + + Open yuzu Folder + Открыть Папку yuzu + + + + Capture Screenshot + Сделать Скриншот + + + + Configure Current Game.. + + + + + MicroProfileDialog + + + MicroProfile + МикроПрофиль + + + + QObject + + + Installed SD Titles + Установленные SD Игры + + + + Installed NAND Titles + Установленные NAND Игры + + + + System Titles + Системные Игры + + + + Add New Game Directory + Добавить Новую Папку с Играми + + + + + + Shift + Shift + + + + + + Ctrl + Ctrl + + + + + + Alt + Alt + + + + + + + [not set] + [не задано] + + + + + + Hat %1 %2 + Д-Пад %1 %2 + + + + + + Axis %1%2 + Ось %1%2 + + + + + + Button %1 + Кнопка %1 + + + + + + + [unknown] + [неизвестно] + + + + + Click 0 + + + + + + Click 1 + + + + + + Click 2 + + + + + + Click 3 + + + + + + Click 4 + + + + + GC Axis %1%2 + + + + + GC Button %1 + + + + + + [unused] + [не используется] + + + + + Axis %1 + Ось %1 + + + + + GC Axis %1 + + + + + QtErrorDisplay + + + An error has occured. +Please try again or contact the developer of the software. + +Error Code: %1-%2 (0x%3) + Произошла ошибка. +Пожалуйста попробуйте еще раз или свяжитесь с разработчиком ПО. + +Код Ошибки: %1-%2 (0x%3) + + + + An error occured on %1 at %2. +Please try again or contact the developer of the software. + +Error Code: %3-%4 (0x%5) + Произошла ошибка на %1 в %2. +Пожалуйста попробуйте еще раз или свяжитесь с разработчиком ПО. + +Код Ошибки: %3-%4 (0x%5) + + + + An error has occured. +Error Code: %1-%2 (0x%3) + +%4 + +%5 + Произошла ошибка. +Код Ошибки: %1-%2 (0x%3) + +%4 + +%5 + + + + QtProfileSelectionDialog + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Select a user: + Выберите Пользователя: + + + + Users + Пользователи + + + + Profile Selector + Выбор Профиля + + + + QtSoftwareKeyboardDialog + + + Enter text: + Введите текст: + + + + Software Keyboard + Программная Клавиатура + + + + SequenceDialog + + + Enter a hotkey + Введите сочетание + + + + WaitTreeCallstack + + + Call stack + Стэк вызовов + + + + WaitTreeMutexInfo + + + waiting for mutex 0x%1 + ожидание мьютекса 0x%1 + + + + has waiters: %1 + ожидающие: %1 + + + + owner handle: 0x%1 + ссылка на владельца: 0x%1 + + + + WaitTreeObjectList + + + waiting for all objects + в ожидании всех объектов + + + + waiting for one of the following objects + в ожидании одного из объектов + + + + WaitTreeSynchronizationObject + + + [%1]%2 %3 + + + + + waited by no thread + + + + + WaitTreeThread + + + + running + работает + + + + ready + готов + + + + + paused + приостановлен + + + + waiting for HLE return + ожидание возврата HLE + + + + sleeping + сон + + + + waiting for IPC reply + ожидание ответа IPC + + + + waiting for objects + ожидание объектов + + + + waiting for mutex + ожидание мьютекса + + + + waiting for condition variable + ожидание условной переменной + + + + waiting for address arbiter + ожидание адресного арбитра + + + + dormant + бездействует + + + + dead + мертв + + + + PC = 0x%1 LR = 0x%2 + PC = 0x%1 LR = 0x%2 + + + + ideal + идеально + + + + core %1 + ядро %1 + + + + Unknown processor %1 + Неизвестный процессор %1 + + + + processor = %1 + процессор = %1 + + + + ideal core = %1 + идеальное ядро = %1 + + + + affinity mask = %1 + маска сходства = %1 + + + + thread id = %1 + id потока = %1 + + + + priority = %1(current) / %2(normal) + приоритет = %1(текущий) / %2(обычный) + + + + last running ticks = %1 + последние тики = %1 + + + + not waiting for mutex + не ожидает мьютекс + + + + WaitTreeThreadList + + + waited by thread + ожидается потоком + + + + WaitTreeWidget + + + Wait Tree + Дерево Ожидания + + + \ No newline at end of file diff --git a/dist/languages/zh_CN.ts b/dist/languages/zh_CN.ts new file mode 100644 index 000000000..f953f224a --- /dev/null +++ b/dist/languages/zh_CN.ts @@ -0,0 +1,4747 @@ + + + AboutDialog + + + About yuzu + 关于 yuzu + + + + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/yuzu.png"/></p></body></html> + + + + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + <html><head/><body><p><span style=" font-size:28pt;">yuzu</span></p></body></html> + + + + <html><head/><body><p>%1 | %2-%3 (%4)</p></body></html> + <html><head/><body><p>%1 | %2-%3 (%4)</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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">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" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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;">yuzu 是一个实验性的开源 Nintendo Switch 模拟器,以 GPLv2.0+ 授权。</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p> +<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;">这个软件不应该用来运行非法取得的游戏。</span></p></body></html> + + + + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">Website</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">Source Code</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">Contributors</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/master/license.txt"><span style=" text-decoration: underline; color:#039be5;">License</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/"><span style=" text-decoration: underline; color:#039be5;">网站</span></a> | <a href="https://github.com/yuzu-emu"><span style=" text-decoration: underline; color:#039be5;">源代码</span></a> | <a href="https://github.com/yuzu-emu/yuzu/graphs/contributors"><span style=" text-decoration: underline; color:#039be5;">贡献者</span></a> | <a href="https://github.com/yuzu-emu/yuzu/blob/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. yuzu is not affiliated with Nintendo in any way.</span></p></body></html> + <html><head/><body><p><span style=" font-size:7pt;">&quot;Nintendo Switch&quot; 是任天堂的商标。yuzu 与任天堂没有任何关系。</span></p></body></html> + + + + CalibrationConfigurationDialog + + + Communicating with the server... + 正在与服务器通信… + + + + Cancel + 取消 + + + + Touch the top left corner <br>of your touchpad. + 触摸你的触摸板<br>的左上角。 + + + + Now touch the bottom right corner <br>of your touchpad. + 触摸你的触摸板<br>的右下角。 + + + + Configuration completed! + 配置完成! + + + + OK + 确定 + + + + 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://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu 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 yuzu 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 yuzu account</li></ul></body></html> + <html><head/><body><p><span style=" font-size:10pt;">如果您选择向 </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu 兼容性列表</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;">您正在使用的 yuzu 版本</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">已关联的 yuzu 账户信息</li></ul></body></html> + + + + Perfect + 完美 + + + + <html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html> + <html><head/><body><p>游戏运行完美,没有音频或图形问题。</p></body></html> + + + + Great + 良好 + + + + <html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html> + <html><head/><body><p>游戏运行时会有非常轻微的图像或音频问题,但是能从头玩到尾。可能需要一些技巧才能完成游戏。</p></body></html> + + + + Okay + 一般 + + + + <html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html> + <html><head/><body><p>游戏运行时会有很多图像或音频错误,但是在使用一些特殊技巧之后能完整地完成游戏。</p></body></html> + + + + Bad + 较差 + + + + <html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html> + <html><head/><body><p>游戏能运行,但是会有大量图像或音频错误。即使使用一些技巧也无法通过游戏的某些区域。</p></body></html> + + + + Intro/Menu + 开场 / 菜单 + + + + <html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html> + <html><head/><body><p>游戏完全没法玩,图像或音频有重大错误。通过开场菜单后无法继续。</p></body></html> + + + + Won't Boot + 无法打开 + + + + <html><head/><body><p>The game crashes when attempting to startup.</p></body></html> + <html><head/><body><p>在启动游戏时直接崩溃了。</p></body></html> + + + + <html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html> + <body><html><head/><p>在不考虑速度或帧率的情况下,使用此版本 yuzu 玩这款游戏的情况如何?</p></body></html> + + + + Thank you for your submission! + 感谢您向我们提交信息! + + + + Submitting + 提交中 + + + + Communication error + 网络错误 + + + + An error occured while sending the Testcase + 在提交测试用例时发生错误。 + + + + Next + 下一步 + + + + ConfigureAudio + + + Audio + 声音 + + + + Output Engine: + 输出引擎: + + + + This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency. + 这种后处理效果可以调整音频速度以匹配模拟速度,并有助于防止音频卡顿。 但是会增加音频延迟。 + + + + Enable audio stretching + 启动音频拉伸 + + + + Audio Device: + 音频设备: + + + + Use global volume + 使用全局音量 + + + + Set volume: + 音量: + + + + Volume: + 音量: + + + + 0 % + 0 % + + + + %1% + Volume percentage (e.g. 50%) + %1% + + + + ConfigureCpu + + + Form + 类型 + + + + General + 通用 + + + + Accuracy: + 精度: + + + + Accurate + 精确 + + + + Unsafe + 低精度 + + + + Enable Debug Mode + 启用调试模式 + + + + We recommend setting accuracy to "Accurate". + 我们推荐将精度设置为“精确”。 + + + + Unsafe CPU Optimization Settings + 低精度 CPU 优化选项 + + + + These settings reduce accuracy for speed. + 这些设置项提高了速度,但精度有所降低。 + + + + Unfuse FMA (improve performance on CPUs without FMA) + 低精度 FMA (在 CPU 不支持 FMA 指令集的情况下提高性能) + + + + + <div>This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.</div> + + +<div>该选项通过降低积和熔加运算的精度而提高模拟器在不支持 FMA 指令集 CPU 上的运行速度。</div> + + + + Faster FRSQRTE and FRECPE + 快速 FRSQRTE 和 FRECPE + + + + + <div>This option improves the speed of some approximate floating-point functions by using less accurate native approximations.</div> + + +<div>该选项通过使用精度较低的近似值来提高某些浮点函数的运算速度。</div> + + + + CPU settings are available only when game is not running. + 只有当游戏不在运行时,CPU 设置才可用。 + + + + Setting CPU to Debug Mode + CPU 调试模式 + + + + CPU Debug Mode is only intended for developer use. Are you sure you want to enable this? + CPU 调试模式仅用于开发人员。您确定要打开这个选项吗? + + + + ConfigureCpuDebug + + + Form + 类型 + + + + Toggle CPU Optimizations + 切换 CPU 优化 + + + + + <div> + <b>For debugging only.</b> + <br> + If you're not sure what these do, keep all of these enabled. + <br> + These settings only take effect when CPU Accuracy is "Debug Mode". + </div> + + +<div> +<b>仅供调试使用。</b> +<br> +如果您不确定这些选项的作用,则保持它们的启用状态。 +<br> +这些选项仅在 CPU 精度处于“调试模式”时生效。 +</div> + + + + Enable inline page tables + 启用内嵌页表 + + + + + <div style="white-space: nowrap">This optimization speeds up memory accesses by the guest program.</div> + <div style="white-space: nowrap">Enabling it inlines accesses to PageTable::pointers into emitted code.</div> + <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">启用内嵌到 PageTable::指向已发射代码的指针。</div> +<div style="white-space: nowrap">禁用此选项将强制通过 Memory::Read/Memory::Write 函数进行内存访问。</div> + + + + Enable block linking + 启用块链接 + + + + + <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>该选项通过允许发出的基本块直接跳转到其他基本块(如果目标 PC 是静态的)来避免调度器的查找。</div> + + + + + Enable return stack buffer + 启用返回堆栈缓冲区 + + + + + <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>该选项通过跟踪 BL 指令的潜在返回地址来避免调度器查找。这近似于 CPU 返回堆栈缓冲区的情况。</div> + + + + + Enable fast dispatcher + 启用快速调度 + + + + + <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>启用两层调度系统。首先使用一个更快的调度程序跳转至目标 MRU 缓存。如果失败,调度返回到较慢的 C++ 调度程序。</div> + + + + + Enable context elimination + 启用上下文消除 + + + + + <div>Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.</div> + + +<div>启用 IR 优化,以减少 CPU 对上下文结构的不必要访问。</div> + + + + + Enable constant propagation + 启用恒定传播 + + + + + <div>Enables IR optimizations that involve constant propagation.</div> + + +<div>启用涉及恒定传播的 IR 优化。</div> + + + + + Enable miscellaneous optimizations + 启用其他优化 + + + + + <div>Enables miscellaneous IR optimizations.</div> + + +<div>启用其他的 IR 优化。</div> + + + + + Enable misalignment check reduction + 减少偏差检查 + + + + + <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">启用时,只有当访问越过页面边界时才会触发偏移。</div> +<div style="white-space: nowrap">禁用时,所有未对齐的访问都会触发偏移。</div> + + + + + CPU settings are available only when game is not running. + 只有当游戏不在运行时,CPU 设置才可用。 + + + + ConfigureDebug + + + Form + Form + + + + GDB + GDB + + + + Enable GDB Stub + 开启 GDB 调试 + + + + Port: + 端口: + + + + Logging + 日志 + + + + Global Log Filter + 全局日志过滤器 + + + + Show Log Console (Windows Only) + 显示日志窗口(仅限 Windows) + + + + Open Log Location + 打开日志位置 + + + + Homebrew + 自制游戏 + + + + Arguments String + 参数字符串 + + + + Graphics + 图形 + + + + When checked, the graphics API enters in a slower debugging mode + 启用时,图形 API 将进入较慢的调试模式。 + + + + Enable Graphics Debugging + 启用图形调试 + + + + When checked, it disables the macro Just In Time compiler. Enabled this makes games run slower + 启用时,将禁用宏即时编译器。这会降低游戏运行速度。 + + + + Disable Macro JIT + 禁用宏 JIT + + + + Dump + 转储 + + + + Enable Verbose Reporting Services + 启用详细报告服务 + + + + This will be reset automatically when yuzu closes. + 当 yuzu 关闭时会自动重置。 + + + + Advanced + 高级选项 + + + + Kiosk (Quest) Mode + Kiosk (Quest) 模式 + + + + ConfigureDebugController + + + Configure Debug Controller + 调试控制器设置 + + + + Clear + 清除 + + + + Defaults + 系统默认 + + + + ConfigureDialog + + + yuzu Configuration + yuzu 设置 + + + + + + + General + 通用 + + + + + UI + 界面 + + + + Game List + 游戏列表 + + + + + + + System + 系统 + + + + + + Profiles + 用户配置 + + + + + + Filesystem + 文件系统 + + + + + + + Controls + 控制 + + + + + + Hotkeys + 热键 + + + + + + + CPU + CPU + + + + + + + + + Debug + 调试 + + + + + + + Graphics + 图形 + + + + + Advanced + 高级选项 + + + + GraphicsAdvanced + 高级图形选项 + + + + + + + Audio + 声音 + + + + + + Web + 网络 + + + + + + Services + 服务 + + + + ConfigureFilesystem + + + Form + 类型 + + + + Storage Directories + 存储目录 + + + + NAND + NAND + + + + + + + + + ... + ... + + + + SD Card + SD 卡 + + + + Gamecard + 游戏卡带 + + + + Path + 路径 + + + + Inserted + 已插入 + + + + Current Game + 当前游戏 + + + + Patch Manager + 补丁管理 + + + + Dump Decompressed NSOs + 转储已解压的 NSO 文件 + + + + Dump ExeFS + 转储 ExeFS + + + + Mod Load Root + Mod 加载根目录 + + + + Dump Root + 转储根目录 + + + + Caching + 缓存中 + + + + Cache Directory + 缓存目录 + + + + Cache Game List Metadata + 缓存游戏列表数据 + + + + + + + Reset Metadata Cache + 重置缓存数据 + + + + Select Emulated NAND Directory... + 选择模拟 NAND 目录... + + + + Select Emulated SD Directory... + 选择模拟 SD 卡目录... + + + + Select Gamecard Path... + 选择游戏卡带路径... + + + + Select Dump Directory... + 选择转储目录... + + + + Select Mod Load Directory... + 选择 Mod 载入目录... + + + + Select Cache Directory... + 选择缓存目录... + + + + 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. + 缓存数据删除失败。它可能不存在或正在被使用。 + + + + ConfigureGeneral + + + Form + Form + + + + General + 通用 + + + + Limit Speed Percent + 运行速度限制 + + + + % + % + + + + Multicore CPU Emulation + 多核 CPU 仿真 + + + + Confirm exit while emulation is running + 在游戏运行时退出需要确认 + + + + Prompt for user on game boot + 游戏启动时提示选择用户 + + + + Pause emulation when in background + 模拟器位于后台时暂停模拟 + + + + Hide mouse on inactivity + 自动隐藏鼠标光标 + + + + ConfigureGraphics + + + Form + Form + + + + API Settings + API 设置 + + + + API: + API: + + + + Device: + 设备: + + + + Graphics Settings + 图形设置 + + + + Use disk shader cache + 使用磁盘着色器缓存 + + + + Use asynchronous GPU emulation + 使用 GPU 异步模拟 + + + + Aspect Ratio: + 屏幕纵横比: + + + + Default (16:9) + 默认 (16:9) + + + + Force 4:3 + 强制 4:3 + + + + Force 21:9 + 强制 21:9 + + + + Stretch to Window + 拉伸窗口 + + + + + Use global background color + 使用全局背景颜色 + + + + Set background color: + 设置背景颜色: + + + + Background Color: + 背景颜色: + + + + OpenGL Graphics Device + OpenGL 图形设备 + + + + ConfigureGraphicsAdvanced + + + Form + 类型 + + + + Advanced Graphics Settings + 高级图形选项 + + + + Accuracy Level: + 精度: + + + + VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference. + 垂直同步可防止画面产生撕裂感。但启用垂直同步后,某些设备性能可能会有所降低。如果您没有感到性能差异,请保持启用状态。 + + + + Use VSync (OpenGL only) + 启用垂直同步 (仅限 OpenGL 模式) + + + + Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental. + 在支持 NV_gpu_program5 的 Nvidia 设备上启用 OpenGL 程序集着色器。启用此项将减少着色器卡顿。实验性功能。 + + + + Use assembly shaders (experimental, Nvidia OpenGL only) + 启用程序集着色器 (实验性,仅限 Nvidia OpenGL) + + + + Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental. + 启用异步着色器编译,这可能会减少着色器卡顿。实验性功能。 + + + + Use asynchronous shader building (experimental) + 启用异步着色器编译 (实验性) + + + + Use Fast GPU Time + 启用快速 GPU 时钟 + + + + Anisotropic Filtering: + 各向异性过滤: + + + + Default + 系统默认 + + + + 2x + + + + + 4x + + + + + 8x + + + + + 16x + 16× + + + + ConfigureHotkeys + + + Hotkey Settings + 热键设置 + + + + Double-click on a binding to change it. + 双击已绑定的项目以改变设定。 + + + + Clear All + 全部清除 + + + + Restore Defaults + 恢复默认 + + + + Action + 作用 + + + + Hotkey + 热键 + + + + Context + 位置 + + + + + Conflicting Key Sequence + 按键冲突 + + + + The entered key sequence is already assigned to: %1 + 输入的密钥序列已分配给: %1 + + + + Restore Default + 恢复默认 + + + + Clear + 清除 + + + + The default key sequence is already assigned to: %1 + 默认密钥序列已分配给: %1 + + + + ConfigureInput + + + ConfigureInput + 输入设置 + + + + + Player 1 + 玩家 1 + + + + + Player 2 + 玩家 2 + + + + + Player 3 + 玩家 3 + + + + + Player 4 + 玩家 4 + + + + + Player 5 + 玩家 5 + + + + + Player 6 + 玩家 6 + + + + + Player 7 + 玩家 7 + + + + + Player 8 + 玩家 8 + + + + + Advanced + 高级 + + + + Console Mode + 控制台模式 + + + + Docked + Docked + + + + Undocked + Undocked + + + + Vibration + 震动 + + + + % + % + + + + Motion + 体感 + + + + Configure + 设置 + + + + Controllers + 控制器 + + + + 1 + 1 + + + + 2 + 2 + + + + 3 + 3 + + + + 4 + 4 + + + + 5 + 5 + + + + 6 + 6 + + + + 7 + 7 + + + + 8 + 8 + + + + Connected + 已连接 + + + + Defaults + 系统默认 + + + + Clear + 清除 + + + + ConfigureInputAdvanced + + + Configure Input + 输入设置 + + + + Joycon Colors + Joycon 颜色 + + + + Player 1 + 玩家 1 + + + + + + + + + + + L Body + 左侧主体 + + + + + + + + + + + L Button + 左侧按键 + + + + + + + + + + + R Body + 右侧主体 + + + + + + + + + + + R Button + 右侧按键 + + + + Player 2 + 玩家 2 + + + + Player 3 + 玩家 3 + + + + Player 4 + 玩家 4 + + + + Player 5 + 玩家 5 + + + + Player 6 + 玩家 6 + + + + Player 7 + 玩家 7 + + + + Player 8 + 玩家 8 + + + + Other + 其他 + + + + Keyboard + 键盘 + + + + + Advanced + 高级选项 + + + + Touchscreen + 触摸屏 + + + + Mouse + 鼠标 + + + + Motion / Touch + 体感/触摸 + + + + + Configure + 设置 + + + + Debug Controller + 控制器调试 + + + + ConfigureInputPlayer + + + Configure Input + 输入设置 + + + + Connect Controller + 已连接控制器 + + + + + + Pro Controller + Pro Controller + + + + + Dual Joycons + 双 Joycons 手柄 + + + + + Left Joycon + 左 Joycon 手柄 + + + + + Right Joycon + 右 Joycon 手柄 + + + + + Handheld + 掌机模式 + + + + Input Device + 输入设备 + + + + Any + 任意 + + + + Keyboard/Mouse + 键盘/鼠标 + + + + Profile + 用户配置 + + + + Save + 保存 + + + + New + 新建 + + + + Delete + 删除 + + + + Left Stick + 左摇杆 + + + + + + + + + Up + + + + + + + + + + Left + + + + + + + + + + Right + + + + + + + + + + Down + + + + + + + + Pressed + 按下 + + + + + + + Modifier + 轻推 + + + + + Range + 灵敏度 + + + + + % + % + + + + + Deadzone: 0% + 摇杆死区:0% + + + + + Modifier Range: 0% + 摇杆灵敏度:0% + + + + D-Pad + 十字方向键 + + + + + L + L + + + + + ZL + ZL + + + + + Minus + + + + + + Capture + 截图 + + + + + Plus + + + + + + Home + Home + + + + + R + R + + + + + ZR + ZR + + + + + SL + SL + + + + + SR + SR + + + + Face Buttons + 主要按键 + + + + + X + X + + + + + Y + Y + + + + + A + A + + + + + B + B + + + + Right Stick + 右摇杆 + + + + + Deadzone: %1% + 摇杆死区:%1% + + + + + Modifier Range: %1% + 摇杆灵敏度:%1% + + + + [waiting] + [等待中] + + + + ConfigureMotionTouch + + + Configure Motion / Touch + 体感/触摸设置 + + + + Motion + 体感 + + + + Motion Provider: + 体感设备: + + + + Sensitivity: + 灵敏度: + + + + Touch + 触摸 + + + + Touch Provider: + 触摸设备: + + + + Calibration: + 触摸校准: + + + + (100, 50) - (1800, 850) + (100, 50) - (1800, 850) + + + + + + Configure + 设置 + + + + Use button mapping: + 使用按键映射: + + + + CemuhookUDP Config + CemuhookUDP 设置 + + + + You may use any Cemuhook compatible UDP input source to provide motion and touch input. + 您可以使用任何与 Cemuhook 兼容的 UDP 输入源来提供体感和触摸输入。 + + + + Server: + 服务器: + + + + Port: + 端口: + + + + Pad: + Pad: + + + + Pad 1 + Pad 1 + + + + Pad 2 + Pad 2 + + + + Pad 3 + Pad 3 + + + + Pad 4 + Pad 4 + + + + Learn More + 了解更多 + + + + + Test + 测试 + + + + Mouse (Right Click) + 鼠标 (右键) + + + + + CemuhookUDP + CemuhookUDP + + + + Emulator Window + 模拟器窗口 + + + + <a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">Learn More</span></a> + <a href='https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input'><span style="text-decoration: underline; color:#039be5;">了解更多</span></a> + + + + 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>请验证服务器是否正在运行,以及地址和端口是否配置正确。 + + + + Citra + Citra + + + + UDP Test or calibration configuration is in progress.<br>Please wait for them to finish. + UDP 测试或触摸校准正在进行中。<br>请耐心等待。 + + + + ConfigureMouseAdvanced + + + Configure Mouse + 鼠标设置 + + + + Mouse Buttons + 鼠标按键 + + + + Forward: + 前: + + + + Back: + 后: + + + + Left: + 左: + + + + Middle: + 中: + + + + Right: + 右: + + + + + Clear + 清除 + + + + Defaults + 系统默认 + + + + [not set] + [未设置] + + + + Restore Default + 恢复默认 + + + + [press key] + [请按一个键] + + + + ConfigurePerGame + + + Dialog + 对话框 + + + + Info + 信息 + + + + Name + 名称 + + + + Title ID + 游戏 ID + + + + Filename + 文件名 + + + + Format + 格式 + + + + Version + 版本 + + + + Size + 大小 + + + + Developer + 开发商 + + + + Add-Ons + 附加项 + + + + General + 通用 + + + + System + 系统 + + + + Graphics + 图形 + + + + Adv. Graphics + 高级图形 + + + + Audio + 声音 + + + + Properties + 属性 + + + + Use global configuration (%1) + 使用全局设置 (%1) + + + + ConfigurePerGameAddons + + + Form + 类型 + + + + Patch Name + 补丁名称 + + + + Version + 版本 + + + + ConfigureProfileManager + + + Form + 类型 + + + + Profile Manager + 用户配置管理 + + + + Current User + 当前用户 + + + + Username + 用户名 + + + + Set Image + 设置图像 + + + + Add + 添加 + + + + Rename + 重命名 + + + + Remove + 移除 + + + + Profile management is available only when game is not running. + 只有当游戏不在运行时,才能进行用户配置的管理。 + + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Enter Username + 输入用户名 + + + + Users + 用户 + + + + Enter a username for the new user: + 输入新用户的用户名: + + + + Enter a new username: + 输入新的用户名: + + + + Confirm Delete + 确认删除 + + + + You are about to delete user with name "%1". Are you sure? + 您确定要删除用户名为 “%1” 的用户吗? + + + + Select User Image + 选择用户图像 + + + + JPEG Images (*.jpg *.jpeg) + JPEG 图像 (*.jpg *.jpeg) + + + + Error deleting image + 删除图像时出错 + + + + Error occurred attempting to overwrite previous image at: %1. + 尝试覆盖该用户的现有图像时出错: %1 + + + + Error deleting file + 删除文件时出错 + + + + Unable to delete existing file: %1. + 无法删除文件: %1 + + + + Error creating user image directory + 创建用户图像目录时出错 + + + + Unable to create directory %1 for storing user images. + 无法创建存储用户图像的目录 %1 。 + + + + Error copying user image + 复制用户图像时出错 + + + + Unable to copy image from %1 to %2 + 无法将图像从 %1 复制到 %2 + + + + ConfigureService + + + Form + 类型 + + + + BCAT + BCAT + + + + BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content. + BCAT 是任天堂向游戏发送数据的一种方式,以此吸引游戏玩家,并可能解锁额外的内容。 + + + + BCAT Backend + BCAT 后端 + + + + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">Learn more about BCAT, Boxcat, and Current Events</span></a></p></body></html> + <html><head/><body><p><a href="https://yuzu-emu.org/help/feature/boxcat"><span style=" text-decoration: underline; color:#0000ff;">了解关于 BCAT、Boxcat 和当前事件的更多信息</span></a></p></body></html> + + + + The boxcat service is offline or you are not connected to the internet. + Boxcat 服务处于离线状态,或者您没有连接到互联网。 + + + + There was an error while processing the boxcat event data. Contact the yuzu developers. + 处理 Boxcat 事件数据时出错。请联系 yuzu 开发者。 + + + + The version of yuzu you are using is either too new or too old for the server. Try updating to the latest official release of yuzu. + 您使用的 yuzu 版本过新或过旧。尝试更新到官方的最新版本。 + + + + + There are currently no events on boxcat. + 当前 Boxcat 上没有事件。 + + + + + Current Boxcat Events + 当前 Boxcat 事件 + + + + Yuzu is retrieving the latest boxcat status... + Yuzu 正在检索 Boxcat 的最新状态... + + + + ConfigureSystem + + + Form + Form + + + + System Settings + 系统设置 + + + + Region: + 地区: + + + + Auto + 自动 + + + + Default + 系统默认 + + + + 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 + 牙买加 + + + + + Japan + 日本 + + + + Kwajalein + 夸贾林环礁 + + + + Libya + 利比亚 + + + + MET + 中欧时间 + + + + MST + 山区标准时间 (北美) + + + + MST7MDT + 山区标准时间&山区夏令时 (北美) + + + + Navajo + 纳瓦霍 + + + + NZ + 新西兰时间 + + + + NZ-CHAT + 新西兰-查塔姆群岛 + + + + Poland + 波兰 + + + + Portugal + 葡萄牙 + + + + PRC + 中国标准时间 + + + + PST8PDT + 太平洋标准时间&太平洋夏令时 + + + + ROC + ROC + + + + ROK + ROK + + + + Singapore + 新加坡 + + + + Turkey + 土耳其 + + + + UCT + UCT + + + + Universal + 世界时间 + + + + UTC + 协调世界时 + + + + W-SU + 欧洲-莫斯科时间 + + + + WET + 西欧时间 + + + + Zulu + 祖鲁 + + + + USA + 美国 + + + + Europe + 欧洲 + + + + Australia + 澳大利亚 + + + + China + 中国 + + + + Korea + 朝鲜 + + + + Taiwan + 中国台湾 + + + + Time Zone: + 时区: + + + + Note: this can be overridden when region setting is auto-select + 注意:当“地区”设置是“自动选择”时,此设置可能会被覆盖。 + + + + Japanese (日本語) + 日语 (日本語) + + + + 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 (正體中文) + 繁体中文 (正體中文) + + + + Custom RTC + 自定义系统时间 + + + + Language + 语言 + + + + RNG Seed + 随机数生成器种子 + + + + Mono + 单声道 + + + + Stereo + 立体声 + + + + Surround + 环绕声 + + + + Console ID: + 设备 ID: + + + + Sound output mode + 声音输出模式 + + + + d MMM yyyy h:mm:ss AP + d MMM yyyy h:mm:ss AP + + + + Regenerate + 重置 ID + + + + System settings are available only when game is not running. + 只有当游戏不在运行时,系统设置才可用。 + + + + This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue? + 这将使用一个新的虚拟 Switch 取代你当前的虚拟 Switch。您当前的虚拟 Switch 将无法恢复。在部分游戏中可能会出现意外效果。如果你使用一个过时的配置存档这可能会失败。确定要继续吗? + + + + Warning + 警告 + + + + Console ID: 0x%1 + 设备 ID: 0x%1 + + + + ConfigureTouchFromButton + + + Configure Touchscreen Mappings + 配置触摸屏映射 + + + + Mapping: + 映射: + + + + New + 新建 + + + + Delete + 删除 + + + + 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 + 删除点位 + + + + Button + 按键 + + + + X + X axis + X + + + + Y + Y axis + Y + + + + New Profile + 保存自定义设置 + + + + Enter the name for the new profile. + 为新的自定义设置命名。 + + + + Delete Profile + 删除自定义设置 + + + + Delete profile %1? + 真的要删除自定义设置 %1 吗? + + + + Rename Profile + 重命名自定义设置 + + + + New name: + 新名称: + + + + [press key] + [请按一个键] + + + + ConfigureTouchscreenAdvanced + + + Configure Touchscreen + 触摸屏设置 + + + + Warning: The settings in this page affect the inner workings of yuzu'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. + 警告:此页面中的设置会影响 yuzu 的模拟触摸屏的内部工作方式。改变它们可能造成不良后果,例如触摸屏完全或部分失效。如果您不知道自己在做什么,请不要使用此页面。 + + + + Touch Parameters + 触摸参数 + + + + Touch Diameter Y + 触摸直径 Y + + + + Finger + 手指 + + + + Touch Diameter X + 触摸直径 X + + + + Rotational Angle + 旋转角度 + + + + Restore Defaults + 恢复默认 + + + + ConfigureUi + + + Form + 类型 + + + + General + 通用 + + + + Note: Changing language will apply your configuration. + 注意: 切换语言将应用您的配置。 + + + + Interface language: + 界面语言: + + + + Theme: + 主题: + + + + Game List + 游戏列表 + + + + Show Add-Ons Column + 显示“附加项”列 + + + + Icon Size: + 图标大小: + + + + Row 1 Text: + 第一行: + + + + Row 2 Text: + 第二行: + + + + Screenshots + 捕获截图 + + + + Ask Where To Save Screenshots (Windows Only) + 询问保存截图的位置 (仅限 Windows ) + + + + Screenshots Path: + 截图保存位置: + + + + ... + ... + + + + Select Screenshots Path... + 选择截图保存位置... + + + + <System> + <System> + + + + English + 英语 + + + + ConfigureWeb + + + Form + Form + + + + yuzu Web Service + yuzu 网络服务 + + + + By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information. + 提供您的用户名和令牌意味着您同意让 yuzu 收集额外的使用数据,其中可能包括用户识别信息。 + + + + + Verify + 验证 + + + + Sign up + 注册 + + + + Token: + 令牌: + + + + Username: + 用户名: + + + + What is my token? + 我的令牌是? + + + + Telemetry + 使用数据共享 + + + + Share anonymous usage data with the yuzu team + 与 yuzu 团队共享匿名使用数据 + + + + Learn more + 了解更多 + + + + Telemetry ID: + 数据 ID: + + + + Regenerate + 重新生成 + + + + Discord Presence + Discord 状态 + + + + Show Current Game in your Discord Status + 在您的 Discord 状态中显示当前游戏 + + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">Learn more</span></a> + <a href='https://yuzu-emu.org/help/feature/telemetry/'><span style="text-decoration: underline; color:#039be5;">了解更多</span></a> + + + + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">Sign up</span></a> + <a href='https://profile.yuzu-emu.org/'><span style="text-decoration: underline; color:#039be5;">注册</span></a> + + + + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">What is my token?</span></a> + <a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style="text-decoration: underline; color:#039be5;">我的令牌是?</span></a> + + + + + Telemetry ID: 0x%1 + 数据 ID: 0x%1 + + + + + Unspecified + 未指定 + + + + Token not verified + 令牌未被验证 + + + + Token was not verified. The change to your token has not been saved. + 令牌未被验证。您对用户名和令牌的更改尚未保存。 + + + + Verifying... + 验证中... + + + + Verification failed + 验证失败 + + + + Verification failed. Check that you have entered your token correctly, and that your internet connection is working. + 验证失败。请检查您输入的令牌是否正确,并且确保您的互联网连接正常。 + + + + GMainWindow + + + <a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous data is collected</a> to help improve yuzu. <br/><br/>Would you like to share your usage data with us? + <a href='https://yuzu-emu.org/help/feature/telemetry/'>我们收集匿名数据</a>来帮助改进 yuzu 。<br/><br/>您愿意和我们分享您的使用数据吗? + + + + Telemetry + 使用数据共享 + + + + Text Check Failed + 文本检查失败 + + + + Loading Web Applet... + 正在加载 Web 应用程序... + + + + Exit Web Applet + 退出 Web 应用程序 + + + + Exit + 退出 + + + + To exit the web application, use the game provided controls to select exit, select the 'Exit Web Applet' option in the menu bar, or press the 'Enter' key. + 若要退出 Web 应用程序,请使用游戏内提供的方式退出,在菜单栏中选择“退出 Web 应用程序”选项,或者按 "Enter" 键。 + + + + Web Applet + Web 应用程序 + + + + This version of yuzu was built without QtWebEngine support, meaning that yuzu cannot properly display the game manual or web page requested. + 此版本的 yuzu 没有 QtWebEngine 的支持。这意味着 yuzu 无法正确显示所请求的游戏手册或网页。 + + + + The amount of shaders currently being built + 当前正在构建的着色器的数量 + + + + 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 毫秒。 + + + + DOCK + DOCK + + + + ASYNC + 异步 + + + + MULTICORE + 多核 + + + + VULKAN + VULKAN + + + + OPENGL + OPENGL + + + + Clear Recent Files + 清除最近文件 + + + + 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><br>For an explanation of the various Switch formats yuzu supports, <a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our wiki</a>. This message will not be shown again. + 目前使用的游戏为解体的 ROM 目录格式,这是一种过时的格式,已被其他格式替代,如 NCA,NAX,XCI 或 NSP。解体的 ROM 目录缺少图标、元数据和更新支持。<br><br>有关 yuzu 支持的各种 Switch 格式的说明,<a href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>请查看我们的 wiki</a>。此消息将不会再次出现。 + + + + + Error while loading ROM! + 加载 ROM 时出错! + + + + The ROM format is not supported. + 该 ROM 格式不受支持。 + + + + An error occurred initializing the video core. + 在初始化视频核心时发生错误。 + + + + yuzu has encountered an error while running the video core, please see the log for more details.For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.Ensure that you have the latest graphics drivers for your GPU. + yuzu 在运行视频核心时遇到错误,请查看日志了解更多详细信息。有关查看日志的更多信息,请参阅以下页面:<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>。请确保您的 GPU 安装了最新的图形驱动程序。 + + + + Error while loading ROM! + 加载 ROM 时出错! + + + + An unknown error occurred. Please see the log for more details. + 发生了未知错误。请查看日志了解详情。 + + + + Start + 开始 + + + + Save Data + 保存数据 + + + + Mod Data + Mod 数据 + + + + Error Opening %1 Folder + 打开 %1 文件夹时出错 + + + + + Folder does not exist! + 文件夹不存在! + + + + Error Opening Transferable Shader Cache + 打开可转移着色器缓存时出错 + + + + + A shader cache for this title does not exist. + 这个游戏的着色器缓存不存在。 + + + + Contents + 目录 + + + + Update + 游戏更新 + + + + DLC + DLC + + + + Remove Entry + 删除项目 + + + + Remove Installed Game %1? + 删除已安装的游戏 %1 ? + + + + + + + + Successfully Removed + 删除成功 + + + + Successfully removed the installed base game. + 成功删除已安装的游戏。 + + + + + + Error Removing %1 + 删除 %1 时出错 + + + + 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 DLC installed for this title. + 这个游戏没有任何已安装的 DLC 。 + + + + Successfully removed %1 installed DLC. + 成功删除游戏 %1 安装的 DLC 。 + + + + Delete Transferable Shader Cache? + 删除着色器缓存? + + + + Remove Custom Game Configuration? + 移除自定义游戏设置? + + + + Remove File + 删除文件 + + + + + Error Removing Transferable Shader Cache + 删除着色器缓存时出错 + + + + Successfully removed the transferable shader cache. + 成功删除着色器缓存。 + + + + Failed to remove the transferable shader cache. + 删除着色器缓存失败。 + + + + + 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. + 移除自定义游戏设置失败。 + + + + 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>“Full” 会将所有文件复制到新目录中,而<br>“Skeleton” 只会创建目录结构。 + + + + 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 + 安装文件 + + + + Installing file "%1"... + 正在安装文件 "%1"... + + + + Install Results + 安装结果 + + + + 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" 未找到 + + + + + Continue + 继续 + + + + Error Display + 错误显示 + + + + Missing yuzu Account + 未设置 yuzu 账户 + + + + In order to submit a game compatibility test case, you must link your yuzu account.<br><br/>To link your yuzu account, go to Emulation &gt; Configuration &gt; Web. + 要提交游戏兼容性测试用例,您必须设置您的 yuzu 帐户。<br><br/>要设置您的 yuzu 帐户,请转到模拟 &gt; 设置 &gt; 网络。 + + + + Error opening URL + 打开 URL 时出错 + + + + Unable to open the URL "%1". + 无法打开 URL : "%1" 。 + + + + Amiibo File (%1);; All Files (*.*) + Amiibo 文件 (%1);; 全部文件 (*.*) + + + + Load Amiibo + 加载 Amiibo + + + + Error opening Amiibo data file + 打开 Amiibo 数据文件时出错 + + + + Unable to open Amiibo file "%1" for reading. + 无法打开 Amiibo 文件 %1。 + + + + Error reading Amiibo data file + 读取 Amiibo 数据文件时出错 + + + + Unable to fully read Amiibo data. Expected to read %1 bytes, but was only able to read %2 bytes. + 无法完全读取 Amiibo 数据。应读取 %1 个字节,但实际仅能读取 %2 个字节。 + + + + Error loading Amiibo data + 加载 Amiibo 数据时出错 + + + + Unable to load Amiibo data. + 无法加载 Amiibo 数据。 + + + + Capture Screenshot + 捕获截图 + + + + PNG Image (*.png) + PNG 图像 (*.png) + + + + Speed: %1% / %2% + 速度: %1% / %2% + + + + Speed: %1% + 速度: %1% + + + + Game: %1 FPS + FPS: %1 + + + + Frame: %1 ms + 帧延迟:%1 毫秒 + + + + The game you are trying to load requires additional files from your Switch to be dumped before playing.<br/><br/>For more information on dumping these files, please see the following wiki page: <a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + 您正尝试启动的游戏需要从 Switch 转储的其他文件。<br/><br/>有关转储这些文件的更多信息,请参阅以下 wiki 页面:<a href='https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System Archives and the Shared Fonts from a Switch Console</a>。<br/><br/>您要退出并返回至游戏列表吗?继续模拟可能会导致崩溃,存档损坏或其他错误。 + + + + yuzu was unable to locate a Switch system archive. %1 + Yuzu 找不到 Switch 系统档案 %1 + + + + yuzu was unable to locate a Switch system archive: %1. %2 + Yuzu 找不到 Switch 系统档案: %1, %2 + + + + System Archive Not Found + 未找到系统档案 + + + + System Archive Missing + 系统档案缺失 + + + + yuzu was unable to locate the Switch shared fonts. %1 + Yuzu 找不到 Swtich 共享字体 %1 + + + + Shared Fonts Not Found + 未找到共享字体 + + + + Shared Font Missing + 共享字体文件缺失 + + + + Fatal Error + 致命错误 + + + + yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: <a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs. + yuzu 遇到了致命错误,请查看日志了解详情。有关查找日志的更多信息,请参阅以下页面:<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to Upload the Log File</a>。<br/><br/>您要退出并返回至游戏列表吗?继续模拟可能会导致崩溃,存档损坏或其他错误。 + + + + Fatal Error encountered + 发生致命错误 + + + + Confirm Key Rederivation + 确认重新生成密钥 + + + + You are about to force rederive all of your keys. +If you do not know what this means or what you are doing, +this is a potentially destructive action. +Please make sure this is what you want +and optionally make backups. + +This will delete your autogenerated key files and re-run the key derivation module. + 即将强制重新生成您的全部密钥。 +如果您不清楚这意味着什么,或您在做什么, +这可能具有破坏性后果。 +请确保您希望这样做,并且做好备份。 + +这将删除您自动生成的密钥文件并重新运行密钥生成模块。 + + + + Missing fuses + 项目丢失 + + + + - Missing BOOT0 + - 丢失 BOOT0 + + + + - Missing BCPKG2-1-Normal-Main + - 丢失 BCPKG2-1-Normal-Main + + + + - Missing PRODINFO + - 丢失 PRODINFO + + + + Derivation Components Missing + 组件丢失 + + + + Components are missing that may hinder key derivation from completing. <br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu quickstart guide</a> to get all your keys and games.<br><br><small>(%1)</small> + 缺少组件可能会影响密钥的使用。<br>请查看<a href='https://yuzu-emu.org/help/quickstart/'>yuzu 快速导航</a>以获得你的密钥和游戏。<br><br><small>(%1)</small> + + + + Deriving keys... +This may take up to a minute depending +on your system's performance. + 正在生成密钥... +这可能需要最多一分钟,具体取决于 +您的系统性能。 + + + + Deriving Keys + 生成密钥 + + + + Select RomFS Dump Target + 选择 RomFS 转储目标 + + + + Please select which RomFS you would like to dump. + 请选择希望转储的 RomFS。 + + + + + + yuzu + yuzu + + + + Are you sure you want to close yuzu? + 您确定要关闭 yuzu 吗? + + + + Are you sure you want to stop the emulation? Any unsaved progress will be lost. + 您确定要停止模拟吗?未保存的进度将会丢失。 + + + + The currently running application has requested yuzu to not exit. + +Would you like to bypass this and exit anyway? + 当前运行的应用程序请求 yuzu 不要退出。 + +您希望忽略并退出吗? + + + + GRenderWindow + + + OpenGL not available! + OpenGL 模式不可用! + + + + yuzu has not been compiled with OpenGL support. + yuzu 没有使用 OpenGL 进行编译。 + + + + Vulkan not available! + Vulkan 模式不可用! + + + + yuzu has not been compiled with Vulkan support. + yuzu 没有使用 Vulkan 进行编译。 + + + + Error while initializing OpenGL 4.3! + 初始化 OpenGL 4.3 时出错! + + + + Your GPU may not support OpenGL 4.3, or you do not have the latest graphics driver. + 您的 GPU 可能不支持 OpenGL 4.3 ,或者您没有安装最新的显卡驱动。 + + + + Error while initializing OpenGL! + 初始化 OpenGL 时出错! + + + + Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.<br><br>Unsupported extensions:<br> + 您的 GPU 可能不支持某些必需的 OpenGL 扩展。请确保您已经安装最新的显卡驱动。<br><br>不支持的扩展:<br> + + + + GameList + + + + Name + 名称 + + + + + Compatibility + 兼容性 + + + + + Add-ons + 附加项 + + + + + + + File type + 文件类型 + + + + + + + Size + 大小 + + + + Open Save Data Location + 打开存档位置 + + + + Open Mod Data Location + 打开 MOD 数据位置 + + + + Open Transferable Shader Cache + 打开可转移着色器缓存 + + + + Remove + 删除 + + + + Remove Installed Update + 删除已安装的游戏更新 + + + + Remove All Installed DLC + 删除所有已安装 DLC + + + + Remove Shader Cache + 删除着色器缓存 + + + + Remove Custom Configuration + 删除自定义设置 + + + + Remove All Installed Contents + 删除所有安装的项目 + + + + Dump RomFS + 转储 RomFS + + + + Copy Title ID to Clipboard + 复制游戏 ID 到剪贴板 + + + + Navigate to GameDB entry + 查看兼容性报告 + + + + Properties + 属性 + + + + Scan Subfolders + 扫描子文件夹 + + + + Remove Game Directory + 移除游戏目录 + + + + ▲ Move Up + ▲ 向上移动 + + + + ▼ Move Down + ▼ 向下移动 + + + + Open Directory Location + 打开目录位置 + + + + GameListItemCompat + + + Perfect + 完美 + + + + Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without +any workarounds needed. + 游戏功能完美,没有音频或图形问题,所有测试功能按预期工作,无需需要任何解决办法。 + + + + Great + 良好 + + + + Game functions with minor graphical or audio glitches and is playable from start to finish. May require some +workarounds. + 游戏运行时会有非常轻微的图像或音频问题,但是能从头玩到尾。可能需要一些技巧才能完成游戏。 + + + + Okay + 一般 + + + + Game functions with major graphical or audio glitches, but game is playable from start to finish with +workarounds. + 游戏运行时会有很多图像或音频错误,但是在使用一些特殊技巧之后能完整地完成游戏。 + + + + Bad + 较差 + + + + Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches +even with workarounds. + 游戏能运行,但是会有大量图像或音频错误。即使使用一些技巧也无法通过游戏的某些区域。 + + + + Intro/Menu + 开场 / 菜单 + + + + Game is completely unplayable due to major graphical or audio glitches. 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. + 游戏尚未经过测试。 + + + + GameListPlaceholder + + + Double-click to add a new folder to the game list + 双击以添加新的游戏文件夹 + + + + GameListSearchField + + + Filter: + 搜索: + + + + Enter pattern to filter + 搜索游戏 + + + + InstallDialog + + + Please confirm these are the files you wish to install. + 请确认这些您想要安装的文件。 + + + + Installing an Update or DLC will overwrite the previously installed one. + 安装游戏更新或 DLC 时,会覆盖以前安装的内容。 + + + + Install + 安装 + + + + Install Files to NAND + 安装文件到 NAND + + + + LoadingScreen + + + Loading Shaders 387 / 1628 + 正在加载着色器: 387 / 1628 + + + + Loading Shaders %v out of %m + 正在加载着色器: %v / %m + + + + Estimated Time 5m 4s + 所需时间: 5 分 4 秒 + + + + Loading... + 加载中... + + + + Loading Shaders %1 / %2 + 正在加载着色器: %1 / %2 + + + + Launching... + 启动中... + + + + Estimated Time %1 + 所需时间: %1 + + + + MainWindow + + + yuzu + yuzu + + + + &File + 文件 (&F) + + + + Recent Files + 最近文件 + + + + &Emulation + 模拟 (&E) + + + + &View + 视图 (&V) + + + + Debugging + 调试 + + + + Tools + 工具 + + + + &Help + 帮助 (&H) + + + + Install Files to NAND... + 安装文件到 NAND... + + + + Load File... + 加载文件... + + + + Load Folder... + 加载文件夹... + + + + E&xit + 退出 (&X) + + + + &Start + 开始 (&S) + + + + &Pause + 暂停 (&P) + + + + &Stop + 停止 (&S) + + + + Reinitialize keys... + 重新生成密钥... + + + + About yuzu + 关于 yuzu + + + + Single Window Mode + 单窗口模式 + + + + Configure... + 设置... + + + + Display Dock Widget Headers + 显示停靠小部件的标题 + + + + Show Filter Bar + 显示搜索栏 + + + + Show Status Bar + 显示状态栏 + + + + Reset Window Size + 重置窗口大小 + + + + Fullscreen + 全屏 + + + + Restart + 重新启动 + + + + Load Amiibo... + 加载 Amiibo... + + + + Report Compatibility + 报告兼容性 + + + + Open Mods Page + 打开 Mod 页面 + + + + Open Quickstart Guide + 查看快速导航 + + + + FAQ + FAQ + + + + Open yuzu Folder + 打开 yuzu 文件夹 + + + + Capture Screenshot + 捕获截图 + + + + Configure Current Game.. + 配置当前游戏... + + + + MicroProfileDialog + + + MicroProfile + MicroProfile + + + + QObject + + + Installed SD Titles + SD 卡中安装的项目 + + + + Installed NAND Titles + NAND 中安装的项目 + + + + System Titles + 系统项目 + + + + Add New Game Directory + 添加游戏目录 + + + + + + Shift + Shift + + + + + + Ctrl + Ctrl + + + + + + Alt + Alt + + + + + + + [not set] + [未设置] + + + + + + Hat %1 %2 + 方向键 %1 %2 + + + + + + Axis %1%2 + 轴 %1%2 + + + + + + Button %1 + 按键 %1 + + + + + + + [unknown] + [未知] + + + + + Click 0 + 点击 0 + + + + + Click 1 + 点击 1 + + + + + Click 2 + 点击 2 + + + + + Click 3 + 点击 3 + + + + + Click 4 + 点击 4 + + + + GC Axis %1%2 + GC 轴 %1%2 + + + + GC Button %1 + GC 按键 %1 + + + + + [unused] + [未使用] + + + + + Axis %1 + 轴 %1 + + + + + GC Axis %1 + GC 轴 %1 + + + + QtErrorDisplay + + + An error has occured. +Please try again or contact the developer of the software. + +Error Code: %1-%2 (0x%3) + 发生了一个错误。 +请再试一次或联系开发者。 + +错误代码: %1-%2 (0x%3) + + + + An error occured on %1 at %2. +Please try again or contact the developer of the software. + +Error Code: %3-%4 (0x%5) + 在 %2 处的 %1 上发生了一个错误。 +请再试一次或联系开发者。 + +错误代码: %3-%4 (0x%5) + + + + An error has occured. +Error Code: %1-%2 (0x%3) + +%4 + +%5 + 发生了一个错误。 +错误代码: %1-%2 (0x%3) + +%4 + +%5 + + + + QtProfileSelectionDialog + + + %1 +%2 + %1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF)) + %1 +%2 + + + + Select a user: + 选择一个用户: + + + + Users + 用户 + + + + Profile Selector + 选择用户 + + + + QtSoftwareKeyboardDialog + + + Enter text: + 输入文本: + + + + Software Keyboard + 软件键盘 + + + + SequenceDialog + + + Enter a hotkey + 键入热键 + + + + WaitTreeCallstack + + + Call stack + 调用栈 + + + + WaitTreeMutexInfo + + + waiting for mutex 0x%1 + 正在等待互斥锁 0x%1 + + + + has waiters: %1 + 等待中: %1 + + + + owner handle: 0x%1 + 所有者句柄: 0x%1 + + + + WaitTreeObjectList + + + waiting for all objects + 正在等待所有对象 + + + + waiting for one of the following objects + 正在等待下列对象中的一个 + + + + WaitTreeSynchronizationObject + + + [%1]%2 %3 + [%1]%2 %3 + + + + waited by no thread + 没有等待的线程 + + + + WaitTreeThread + + + + running + 运行中 + + + + ready + 就绪 + + + + + paused + 暂停 + + + + waiting for HLE return + 等待 HLE 返回 + + + + sleeping + 睡眠 + + + + waiting for IPC reply + 等待 IPC 响应 + + + + waiting for objects + 等待对象 + + + + waiting for mutex + 等待互斥锁 + + + + waiting for condition variable + 等待条件变量 + + + + waiting for address arbiter + 等待 address arbiter + + + + dormant + 休眠 + + + + dead + 死亡 + + + + PC = 0x%1 LR = 0x%2 + PC = 0x%1 LR = 0x%2 + + + + ideal + ideal + + + + core %1 + 核心 %1 + + + + Unknown processor %1 + 未知的处理器 %1 + + + + processor = %1 + 处理器 = %1 + + + + ideal core = %1 + 理想核心 = %1 + + + + affinity mask = %1 + 关联掩码 = %1 + + + + thread id = %1 + 线程 ID = %1 + + + + priority = %1(current) / %2(normal) + 优先级 = %1 (实时) / %2 (正常) + + + + last running ticks = %1 + 最后运行频率 = %1 + + + + not waiting for mutex + 未等待互斥锁 + + + + WaitTreeThreadList + + + waited by thread + 等待中的线程 + + + + WaitTreeWidget + + + Wait Tree + 等待树 + + + \ No newline at end of file diff --git a/dist/qt_themes/default/style.qss b/dist/qt_themes/default/style.qss index b6dd2063d..836dd25ca 100644 --- a/dist/qt_themes/default/style.qss +++ b/dist/qt_themes/default/style.qss @@ -1,3 +1,7 @@ +QAbstractSpinBox { + min-height: 19px; +} + QPushButton#TogglableStatusBarButton { color: #959595; border: 1px solid transparent; @@ -35,10 +39,10 @@ QPushButton#RendererStatusBarButton:!checked { } QPushButton#buttonRefreshDevices { - min-width: 20px; - min-height: 20px; - max-width: 20px; - max-height: 20px; + min-width: 21px; + min-height: 21px; + max-width: 21px; + max-height: 21px; } QWidget#bottomPerGameInput, @@ -71,7 +75,7 @@ QWidget#middleControllerApplet { QWidget#topPerGameInput QComboBox, QWidget#middleControllerApplet QComboBox { - width: 123px; + width: 120px; } QWidget#connectedControllers { diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss index 66026e8be..2a1e8ddeb 100644 --- a/dist/qt_themes/qdarkstyle/style.qss +++ b/dist/qt_themes/qdarkstyle/style.qss @@ -99,12 +99,19 @@ QGroupBox::indicator:unchecked:disabled { } QRadioButton { - spacing: 5px; - outline: none; color: #eff0f1; + spacing: 3px; + padding: 0px; + border: none; + outline: none; margin-bottom: 2px; } +QGroupBox QRadioButton { + padding-left: 0px; + padding-right: 7px; +} + QRadioButton:disabled { color: #76797C; } @@ -522,13 +529,12 @@ QToolButton#qt_toolbar_ext_button { QPushButton { color: #eff0f1; - border-width: 1px; - border-color: #54575B; - border-style: solid; - padding: 6px 4px; + border: 1px solid #54575B; border-radius: 2px; + padding: 5px 0px 5px 0px; outline: none; min-width: 100px; + min-height: 13px; background-color: #232629; } @@ -553,8 +559,9 @@ QComboBox { selection-background-color: #3daee9; border: 1px solid #54575B; border-radius: 2px; - padding: 4px 6px; - min-width: 75px; + padding: 0px 4px 0px 4px; + min-width: 60px; + min-height: 23px; background-color: #232629; } @@ -608,26 +615,26 @@ QComboBox::down-arrow:focus { } QAbstractSpinBox { - padding: 4px 6px; border: 1px solid #54575B; background-color: #232629; color: #eff0f1; border-radius: 2px; - min-width: 75px; + min-width: 52px; + min-height: 23px; } QAbstractSpinBox:up-button { background-color: transparent; subcontrol-origin: border; subcontrol-position: center right; - left: -6px; + left: -2px; } QAbstractSpinBox:down-button { background-color: transparent; subcontrol-origin: border; subcontrol-position: center left; - right: -6px; + right: -2px; } QAbstractSpinBox::up-arrow, @@ -1277,41 +1284,33 @@ QPushButton#RendererStatusBarButton:!checked { } QPushButton#buttonRefreshDevices { - min-width: 24px; - min-height: 24px; - max-width: 24px; - max-height: 24px; + min-width: 23px; + min-height: 23px; + max-width: 23px; + max-height: 23px; padding: 0px 0px; } QSpinBox#spinboxLStickRange, -QSpinBox#spinboxRStickRange { - padding: 4px 0px 5px 0px; - min-width: 63px; -} - -QSpinBox#vibrationSpin { - padding: 4px 0px 5px 0px; - min-width: 63px; -} - -QSpinBox#spinboxLStickRange:up-button, -QSpinBox#spinboxRStickRange:up-button, -QSpinBox#vibrationSpin:up-button { - left: -2px; -} - -QSpinBox#spinboxLStickRange:down-button, -QSpinBox#spinboxRStickRange:down-button, -QSpinBox#vibrationSpin:down-button { - right: -1px; +QSpinBox#spinboxRStickRange, +QSpinBox#vibrationSpinPlayer1, +QSpinBox#vibrationSpinPlayer2, +QSpinBox#vibrationSpinPlayer3, +QSpinBox#vibrationSpinPlayer4, +QSpinBox#vibrationSpinPlayer5, +QSpinBox#vibrationSpinPlayer6, +QSpinBox#vibrationSpinPlayer7, +QSpinBox#vibrationSpinPlayer8 { + min-width: 68px; } +QDialog#ConfigureVibration QGroupBox::indicator, QGroupBox#motionGroup::indicator, QGroupBox#vibrationGroup::indicator { margin-left: 0px; } +QDialog#ConfigureVibration QGroupBox::title, QGroupBox#motionGroup::title, QGroupBox#vibrationGroup::title { spacing: 2px; @@ -1340,16 +1339,7 @@ QWidget#middleControllerApplet { QWidget#topPerGameInput QComboBox, QWidget#middleControllerApplet QComboBox { - width: 119px; -} - -QRadioButton#radioDocked { - margin-left: -3px; -} - - -QRadioButton#radioUndocked { - margin-right: 5px; + width: 120px; } QWidget#connectedControllers { diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss index c6318ba4e..70e540b06 100644 --- a/dist/qt_themes/qdarkstyle_midnight_blue/style.qss +++ b/dist/qt_themes/qdarkstyle_midnight_blue/style.qss @@ -172,8 +172,8 @@ QCheckBox { color: #F0F0F0; spacing: 4px; outline: none; - padding-top: 4px; - padding-bottom: 4px; + padding-top: 2px; + padding-bottom: 2px; } QCheckBox:focus { @@ -239,7 +239,7 @@ QGroupBox { border: 1px solid #32414B; border-radius: 4px; margin-top: 12px; - padding: 4px; + padding: 2px; } QGroupBox::title { @@ -247,7 +247,7 @@ QGroupBox::title { subcontrol-position: top left; padding-left: 3px; padding-right: 5px; - padding-top: 4px; + padding-top: 2px; } QGroupBox::indicator { @@ -298,6 +298,11 @@ QRadioButton { outline: none; } +QGroupBox QRadioButton { + padding-left: 0px; + padding-right: 7px; +} + QRadioButton:focus { border: none; } @@ -321,7 +326,6 @@ QRadioButton QWidget { QRadioButton::indicator { border: none; outline: none; - margin-left: 4px; height: 16px; width: 16px; } @@ -785,14 +789,8 @@ QAbstractSpinBox { background-color: #19232D; border: 1px solid #32414B; color: #F0F0F0; - /* This fixes 103, 111 */ - padding-top: 2px; - /* This fixes 103, 111 */ - padding-bottom: 2px; - padding-left: 4px; - padding-right: 4px; border-radius: 4px; - /* min-width: 5px; removed to fix 109 */ + min-height: 19px; } QAbstractSpinBox:up-button { @@ -997,10 +995,11 @@ QPushButton { border: 1px solid #32414B; color: #F0F0F0; border-radius: 4px; - padding: 3px; + padding: 3px 0px 3px 0px; outline: none; /* Issue #194 - Special case of QPushButton inside dialogs, for better UI */ min-width: 80px; + min-height: 13px; } QPushButton:disabled { @@ -1008,14 +1007,14 @@ QPushButton:disabled { border: 1px solid #32414B; color: #787878; border-radius: 4px; - padding: 3px; + padding: 3px 0px 3px 0px; } QPushButton:checked { background-color: #32414B; border: 1px solid #32414B; border-radius: 4px; - padding: 3px; + padding: 3px 0px 3px 0px; outline: none; } @@ -1024,7 +1023,7 @@ QPushButton:checked:disabled { border: 1px solid #32414B; color: #787878; border-radius: 4px; - padding: 3px; + padding: 3px 0px 3px 0px; outline: none; } @@ -1197,15 +1196,9 @@ QComboBox { border: 1px solid #32414B; border-radius: 4px; selection-background-color: #1464A0; - padding-left: 4px; - padding-right: 36px; - /* 4 + 16*2 See scrollbar size */ - /* Fixes #103, #111 */ - min-height: 1.5em; - /* padding-top: 2px; removed to fix #132 */ - /* padding-bottom: 2px; removed to fix #132 */ - /* min-width: 75px; removed to fix #109 */ - /* Needed to remove indicator - fix #132 */ + padding: 0px 4px 0px 4px; + min-width: 60px; + min-height: 19px; } QComboBox QAbstractItemView { @@ -2198,29 +2191,40 @@ QPushButton#RendererStatusBarButton:!checked { } QPushButton#buttonRefreshDevices { - min-width: 20px; - min-height: 20px; - max-width: 20px; - max-height: 20px; + min-width: 19px; + min-height: 19px; + max-width: 19px; + max-height: 19px; padding: 0px 0px; } QSpinBox#spinboxLStickRange, -QSpinBox#spinboxRStickRange { - min-width: 38px; +QSpinBox#spinboxRStickRange, +QSpinBox#vibrationSpinPlayer1, +QSpinBox#vibrationSpinPlayer2, +QSpinBox#vibrationSpinPlayer3, +QSpinBox#vibrationSpinPlayer4, +QSpinBox#vibrationSpinPlayer5, +QSpinBox#vibrationSpinPlayer6, +QSpinBox#vibrationSpinPlayer7, +QSpinBox#vibrationSpinPlayer8 { + min-width: 68px; } +QDialog#ConfigureVibration QGroupBox::indicator, QGroupBox#motionGroup::indicator, QGroupBox#vibrationGroup::indicator { margin-left: 0px; } +QDialog#ConfigureVibration QGroupBox, QWidget#bottomPerGameInput QGroupBox#motionGroup, QWidget#bottomPerGameInput QGroupBox#vibrationGroup, QWidget#bottomPerGameInput QGroupBox#inputConfigGroup { padding: 0px; } +QDialog#ConfigureVibration QGroupBox::title, QGroupBox#motionGroup::title, QGroupBox#vibrationGroup::title { spacing: 2px; @@ -2260,26 +2264,7 @@ QWidget#middleControllerApplet { QWidget#topPerGameInput QComboBox, QWidget#middleControllerApplet QComboBox { - padding-right: 2px; - width: 127px; -} - -QGroupBox#handheldGroup { - padding-left: 0px; -} - -QRadioButton#radioDocked { - margin-left: -1px; - padding-left: 0px; -} - -QRadioButton#radioDocked::indicator { - margin-left: 0px; -} - - -QRadioButton#radioUndocked { - margin-right: 2px; + width: 120px; } QWidget#connectedControllers { @@ -2352,7 +2337,7 @@ QCheckBox#checkboxPlayer5Connected, QCheckBox#checkboxPlayer6Connected, QCheckBox#checkboxPlayer7Connected, QCheckBox#checkboxPlayer8Connected { - spacing: 0px; + spacing: 0px; } QWidget#connectedControllers QLabel { @@ -2427,7 +2412,7 @@ QCheckBox#checkboxPlayer7Connected::indicator, QCheckBox#checkboxPlayer8Connected::indicator { width: 14px; height: 14px; - margin-left: 2px; + margin-left: 0px; } QWidget#Player1LEDs QCheckBox::indicator:checked, diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index d1dcc403b..c629bbc5c 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -61,9 +61,7 @@ if (USE_DISCORD_PRESENCE) endif() # Sirit -if (ENABLE_VULKAN) - add_subdirectory(sirit) -endif() +add_subdirectory(sirit) # libzip find_package(Libzip 1.5) @@ -73,23 +71,29 @@ if (NOT LIBZIP_FOUND) endif() if (ENABLE_WEB_SERVICE) - # LibreSSL - set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "") - add_subdirectory(libressl EXCLUDE_FROM_ALL) - target_include_directories(ssl INTERFACE ./libressl/include) - target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP) - get_directory_property(OPENSSL_LIBRARIES - DIRECTORY libressl - DEFINITION OPENSSL_LIBS) - - # lurlparser - add_subdirectory(lurlparser EXCLUDE_FROM_ALL) + find_package(OpenSSL 1.1) + if (OPENSSL_FOUND) + set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto) + else() + # LibreSSL + set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "") + set(OPENSSLDIR "/etc/ssl/") + add_subdirectory(libressl EXCLUDE_FROM_ALL) + target_include_directories(ssl INTERFACE ./libressl/include) + target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP) + get_directory_property(OPENSSL_LIBRARIES + DIRECTORY libressl + DEFINITION OPENSSL_LIBS) + endif() # httplib add_library(httplib INTERFACE) target_include_directories(httplib INTERFACE ./httplib) target_compile_definitions(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT) target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES}) + if (WIN32) + target_link_libraries(httplib INTERFACE crypt32 cryptui ws2_32) + endif() endif() # Opus diff --git a/externals/cubeb b/externals/cubeb index 616d77344..1d66483ad 160000 --- a/externals/cubeb +++ b/externals/cubeb @@ -1 +1 @@ -Subproject commit 616d773441b5355800ce64197a699e6cd6b36172 +Subproject commit 1d66483ad2b93f0e00e175f9480c771af90003a7 diff --git a/externals/dynarmic b/externals/dynarmic index 0e1112b7d..3806284cb 160000 --- a/externals/dynarmic +++ b/externals/dynarmic @@ -1 +1 @@ -Subproject commit 0e1112b7df77ae55a62a51622940d5c8f9e8c84c +Subproject commit 3806284cbefc4115436dcdc687776a45ec313093 diff --git a/externals/find-modules/FindFFmpeg.cmake b/externals/find-modules/FindFFmpeg.cmake new file mode 100644 index 000000000..77b331e00 --- /dev/null +++ b/externals/find-modules/FindFFmpeg.cmake @@ -0,0 +1,100 @@ +# - Try to find ffmpeg libraries (libavcodec, libavformat and libavutil) +# Once done this will define +# +# FFMPEG_FOUND - system has ffmpeg or libav +# FFMPEG_INCLUDE_DIR - the ffmpeg include directory +# FFMPEG_LIBRARIES - Link these to use ffmpeg +# FFMPEG_LIBAVCODEC +# FFMPEG_LIBAVFORMAT +# FFMPEG_LIBAVUTIL +# +# Copyright (c) 2008 Andreas Schneider +# Modified for other libraries by Lasse Kärkkäinen +# Modified for Hedgewars by Stepik777 +# Modified for FFmpeg-example Tuukka Pasanen 2018 +# Modified for yuzu toastUnlimted 2020 +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(FFMPEG + FOUND_VAR FFMPEG_FOUND + REQUIRED_VARS + FFMPEG_LIBRARY + FFMPEG_INCLUDE_DIR + VERSION_VAR FFMPEG_VERSION +) + +if(FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR) + # in cache already + set(FFMPEG_FOUND TRUE) +else() + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(_FFMPEG_AVCODEC libavcodec) + pkg_check_modules(_FFMPEG_AVUTIL libavutil) + pkg_check_modules(_FFMPEG_SWSCALE libswscale) + endif() + + find_path(FFMPEG_AVCODEC_INCLUDE_DIR + NAMES libavcodec/avcodec.h + PATHS ${_FFMPEG_AVCODEC_INCLUDE_DIRS} + /usr/include + /usr/local/include + /opt/local/include + /sw/include + PATH_SUFFIXES ffmpeg libav) + + find_library(FFMPEG_LIBAVCODEC + NAMES avcodec + PATHS ${_FFMPEG_AVCODEC_LIBRARY_DIRS} + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib) + + find_library(FFMPEG_LIBAVUTIL + NAMES avutil + PATHS ${_FFMPEG_AVUTIL_LIBRARY_DIRS} + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib) + + find_library(FFMPEG_LIBSWSCALE + NAMES swscale + PATHS ${_FFMPEG_SWSCALE_LIBRARY_DIRS} + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib) + + if(FFMPEG_LIBAVCODEC AND FFMPEG_LIBAVUTIL AND FFMPEG_LIBSWSCALE) + set(FFMPEG_FOUND TRUE) + endif() + + if(FFMPEG_FOUND) + set(FFMPEG_INCLUDE_DIR ${FFMPEG_AVCODEC_INCLUDE_DIR}) + set(FFMPEG_LIBRARIES + ${FFMPEG_LIBAVCODEC} + ${FFMPEG_LIBAVUTIL} + ${FFMPEG_LIBSWSCALE}) + endif() + + if(FFMPEG_FOUND) + if(NOT FFMPEG_FIND_QUIETLY) + message(STATUS + "Found FFMPEG or Libav: ${FFMPEG_LIBRARIES}, ${FFMPEG_INCLUDE_DIR}") + endif() + else() + if(FFMPEG_FIND_REQUIRED) + message(FATAL_ERROR + "Could not find libavcodec or libavutil or libswscale") + endif() + endif() +endif() diff --git a/externals/httplib/README.md b/externals/httplib/README.md index 73037d297..1940e446c 100644 --- a/externals/httplib/README.md +++ b/externals/httplib/README.md @@ -1,4 +1,4 @@ -From https://github.com/yhirose/cpp-httplib/tree/fce8e6fefdab4ad48bc5b25c98e5ebfda4f3cf53 +From https://github.com/yhirose/cpp-httplib/tree/ff5677ad197947177c158fe857caff4f0e242045 with https://github.com/yhirose/cpp-httplib/pull/701 MIT License diff --git a/externals/httplib/httplib.h b/externals/httplib/httplib.h index e03842e6d..8982054e2 100644 --- a/externals/httplib/httplib.h +++ b/externals/httplib/httplib.h @@ -16,14 +16,18 @@ #define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 #endif -#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND -#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND 0 -#endif - #ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT #define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5 #endif +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 +#endif + #ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND #define CPPHTTPLIB_READ_TIMEOUT_SECOND 5 #endif @@ -32,6 +36,26 @@ #define CPPHTTPLIB_READ_TIMEOUT_USECOND 0 #endif +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND +#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND +#ifdef _WIN32 +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 +#else +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 +#endif +#endif + #ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH #define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 #endif @@ -41,16 +65,26 @@ #endif #ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH -#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (std::numeric_limits::max()) +#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits::max)()) +#endif + +#ifndef CPPHTTPLIB_TCP_NODELAY +#define CPPHTTPLIB_TCP_NODELAY false #endif #ifndef CPPHTTPLIB_RECV_BUFSIZ #define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u) #endif +#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ +#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) +#endif + #ifndef CPPHTTPLIB_THREAD_POOL_COUNT #define CPPHTTPLIB_THREAD_POOL_COUNT \ - (std::max(1u, std::thread::hardware_concurrency() - 1)) + ((std::max)(8u, std::thread::hardware_concurrency() > 0 \ + ? std::thread::hardware_concurrency() - 1 \ + : 0)) #endif /* @@ -92,6 +126,8 @@ using ssize_t = int; #include #include + +#include #include #ifndef WSA_FLAG_NO_HANDLE_INHERIT @@ -100,6 +136,8 @@ using ssize_t = int; #ifdef _MSC_VER #pragma comment(lib, "ws2_32.lib") +#pragma comment(lib, "crypt32.lib") +#pragma comment(lib, "cryptui.lib") #endif #ifndef strcasecmp @@ -118,6 +156,10 @@ using socket_t = SOCKET; #include #include #include +#ifdef __linux__ +#include +#endif +#include #ifdef CPPHTTPLIB_USE_POLL #include #endif @@ -131,20 +173,25 @@ using socket_t = int; #define INVALID_SOCKET (-1) #endif //_WIN32 +#include #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -155,12 +202,17 @@ using socket_t = int; #include #include +#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) +#include +#endif + #include +#include #include -// #if OPENSSL_VERSION_NUMBER < 0x1010100fL -// #error Sorry, OpenSSL versions prior to 1.1.1 are not supported -// #endif +#if OPENSSL_VERSION_NUMBER < 0x1010100fL +#error Sorry, OpenSSL versions prior to 1.1.1 are not supported +#endif #if OPENSSL_VERSION_NUMBER < 0x10100000L #include @@ -174,6 +226,11 @@ inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) { #include #endif +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +#include +#include +#endif + /* * Declaration */ @@ -181,6 +238,27 @@ namespace httplib { namespace detail { +/* + * Backport std::make_unique from C++14. + * + * NOTE: This code came up with the following stackoverflow post: + * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique + * + */ + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(Args &&... args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(std::size_t n) { + typedef typename std::remove_extent::type RT; + return std::unique_ptr(new RT[n]); +} + struct ci { bool operator()(const std::string &s1, const std::string &s2) const { return std::lexicographical_compare( @@ -212,7 +290,8 @@ using MultipartFormDataMap = std::multimap; class DataSink { public: - DataSink() = default; + DataSink() : os(&sb_), sb_(*this) {} + DataSink(const DataSink &) = delete; DataSink &operator=(const DataSink &) = delete; DataSink(DataSink &&) = delete; @@ -221,10 +300,35 @@ public: std::function write; std::function done; std::function is_writable; + std::ostream os; + +private: + class data_sink_streambuf : public std::streambuf { + public: + explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {} + + protected: + std::streamsize xsputn(const char *s, std::streamsize n) { + sink_.write(s, static_cast(n)); + return n; + } + + private: + DataSink &sink_; + }; + + data_sink_streambuf sb_; }; using ContentProvider = - std::function; + std::function; + +using ContentProviderWithoutLength = + std::function; + +using ContentReceiverWithProgress = + std::function; using ContentReceiver = std::function; @@ -238,18 +342,21 @@ public: using MultipartReader = std::function; - ContentReader(Reader reader, MultipartReader muitlpart_reader) - : reader_(reader), muitlpart_reader_(muitlpart_reader) {} + ContentReader(Reader reader, MultipartReader multipart_reader) + : reader_(std::move(reader)), + multipart_reader_(std::move(multipart_reader)) {} bool operator()(MultipartContentHeader header, ContentReceiver receiver) const { - return muitlpart_reader_(header, receiver); + return multipart_reader_(std::move(header), std::move(receiver)); } - bool operator()(ContentReceiver receiver) const { return reader_(receiver); } + bool operator()(ContentReceiver receiver) const { + return reader_(std::move(receiver)); + } Reader reader_; - MultipartReader muitlpart_reader_; + MultipartReader multipart_reader_; }; using Range = std::pair; @@ -261,6 +368,9 @@ struct Request { Headers headers; std::string body; + std::string remote_addr; + int remote_port = -1; + // for server std::string version; std::string target; @@ -272,7 +382,9 @@ struct Request { // for client size_t redirect_count = CPPHTTPLIB_REDIRECT_MAX_COUNT; ResponseHandler response_handler; - ContentReceiver content_receiver; + ContentReceiverWithProgress content_receiver; + size_t content_length = 0; + ContentProvider content_provider; Progress progress; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -281,6 +393,8 @@ struct Request { bool has_header(const char *key) const; std::string get_header_value(const char *key, size_t id = 0) const; + template + T get_header_value(const char *key, size_t id = 0) const; size_t get_header_value_count(const char *key) const; void set_header(const char *key, const char *val); void set_header(const char *key, const std::string &val); @@ -295,35 +409,40 @@ struct Request { MultipartFormData get_file_value(const char *key) const; // private members... - size_t content_length; - ContentProvider content_provider; + size_t authorization_count_ = 0; }; struct Response { std::string version; int status = -1; + std::string reason; Headers headers; std::string body; bool has_header(const char *key) const; std::string get_header_value(const char *key, size_t id = 0) const; + template + T get_header_value(const char *key, size_t id = 0) const; size_t get_header_value_count(const char *key) const; void set_header(const char *key, const char *val); void set_header(const char *key, const std::string &val); - void set_redirect(const char *url); + void set_redirect(const char *url, int status = 302); + void set_redirect(const std::string &url, int status = 302); void set_content(const char *s, size_t n, const char *content_type); - void set_content(const std::string &s, const char *content_type); + void set_content(std::string s, const char *content_type); void set_content_provider( - size_t length, - std::function - provider, - std::function resource_releaser = [] {}); + size_t length, const char *content_type, ContentProvider provider, + const std::function &resource_releaser = nullptr); + + void set_content_provider( + const char *content_type, ContentProviderWithoutLength provider, + const std::function &resource_releaser = nullptr); void set_chunked_content_provider( - std::function provider, - std::function resource_releaser = [] {}); + const char *content_type, ContentProviderWithoutLength provider, + const std::function &resource_releaser = nullptr); Response() = default; Response(const Response &) = default; @@ -331,15 +450,16 @@ struct Response { Response(Response &&) = default; Response &operator=(Response &&) = default; ~Response() { - if (content_provider_resource_releaser) { - content_provider_resource_releaser(); + if (content_provider_resource_releaser_) { + content_provider_resource_releaser_(); } } // private members... - size_t content_length = 0; - ContentProvider content_provider; - std::function content_provider_resource_releaser; + size_t content_length_ = 0; + ContentProvider content_provider_; + std::function content_provider_resource_releaser_; + bool is_chunked_content_provider = false; }; class Stream { @@ -349,22 +469,25 @@ public: virtual bool is_readable() const = 0; virtual bool is_writable() const = 0; - virtual int read(char *ptr, size_t size) = 0; - virtual int write(const char *ptr, size_t size) = 0; - virtual std::string get_remote_addr() const = 0; + virtual ssize_t read(char *ptr, size_t size) = 0; + virtual ssize_t write(const char *ptr, size_t size) = 0; + virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; template - int write_format(const char *fmt, const Args &... args); - int write(const char *ptr); - int write(const std::string &s); + ssize_t write_format(const char *fmt, const Args &... args); + ssize_t write(const char *ptr); + ssize_t write(const std::string &s); }; class TaskQueue { public: TaskQueue() = default; virtual ~TaskQueue() = default; + virtual void enqueue(std::function fn) = 0; virtual void shutdown() = 0; + + virtual void on_idle(){}; }; class ThreadPool : public TaskQueue { @@ -381,7 +504,7 @@ public: void enqueue(std::function fn) override { std::unique_lock lock(mutex_); - jobs_.push_back(fn); + jobs_.push_back(std::move(fn)); cond_.notify_one(); } @@ -439,6 +562,26 @@ private: using Logger = std::function; +using SocketOptions = std::function; + +inline void default_socket_options(socket_t sock) { + int yes = 1; +#ifdef _WIN32 + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + reinterpret_cast(&yes), sizeof(yes)); +#else +#ifdef SO_REUSEPORT + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&yes), + sizeof(yes)); +#else + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), + sizeof(yes)); +#endif +#endif +} + class Server { public: using Handler = std::function; @@ -461,23 +604,30 @@ public: Server &Patch(const char *pattern, Handler handler); Server &Patch(const char *pattern, HandlerWithContentReader handler); Server &Delete(const char *pattern, Handler handler); + Server &Delete(const char *pattern, HandlerWithContentReader handler); Server &Options(const char *pattern, Handler handler); - [[deprecated]] bool set_base_dir(const char *dir, - const char *mount_point = nullptr); - bool set_mount_point(const char *mount_point, const char *dir); + bool set_base_dir(const char *dir, const char *mount_point = nullptr); + bool set_mount_point(const char *mount_point, const char *dir, + Headers headers = Headers()); bool remove_mount_point(const char *mount_point); void set_file_extension_and_mimetype_mapping(const char *ext, const char *mime); void set_file_request_handler(Handler handler); void set_error_handler(Handler handler); + void set_expect_100_continue_handler(Expect100ContinueHandler handler); void set_logger(Logger logger); - void set_expect_100_continue_handler(Expect100ContinueHandler handler); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); void set_keep_alive_max_count(size_t count); - void set_read_timeout(time_t sec, time_t usec); + void set_keep_alive_timeout(time_t sec); + void set_read_timeout(time_t sec, time_t usec = 0); + void set_write_timeout(time_t sec, time_t usec = 0); + void set_idle_interval(time_t sec, time_t usec = 0); + void set_payload_max_length(size_t length); bool bind_to_port(const char *host, int port, int socket_flags = 0); @@ -492,54 +642,66 @@ public: std::function new_task_queue; protected: - bool process_request(Stream &strm, bool last_connection, - bool &connection_close, + bool process_request(Stream &strm, bool close_connection, + bool &connection_closed, const std::function &setup_request); - size_t keep_alive_max_count_; - time_t read_timeout_sec_; - time_t read_timeout_usec_; - size_t payload_max_length_; + std::atomic svr_sock_; + size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; + time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; + time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; + size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; private: using Handlers = std::vector>; using HandlersForContentReader = std::vector>; - socket_t create_server_socket(const char *host, int port, - int socket_flags) const; + socket_t create_server_socket(const char *host, int port, int socket_flags, + SocketOptions socket_options) const; int bind_internal(const char *host, int port, int socket_flags); bool listen_internal(); - bool routing(Request &req, Response &res, Stream &strm, bool last_connection); + bool routing(Request &req, Response &res, Stream &strm); bool handle_file_request(Request &req, Response &res, bool head = false); - bool dispatch_request(Request &req, Response &res, Handlers &handlers); - bool dispatch_request_for_content_reader(Request &req, Response &res, - ContentReader content_reader, - HandlersForContentReader &handlers); + bool dispatch_request(Request &req, Response &res, const Handlers &handlers); + bool + dispatch_request_for_content_reader(Request &req, Response &res, + ContentReader content_reader, + const HandlersForContentReader &handlers); bool parse_request_line(const char *s, Request &req); - bool write_response(Stream &strm, bool last_connection, const Request &req, + bool write_response(Stream &strm, bool close_connection, const Request &req, Response &res); bool write_content_with_provider(Stream &strm, const Request &req, Response &res, const std::string &boundary, const std::string &content_type); - bool read_content(Stream &strm, bool last_connection, Request &req, - Response &res); - bool read_content_with_content_receiver( - Stream &strm, bool last_connection, Request &req, Response &res, - ContentReceiver receiver, MultipartContentHeader multipart_header, - ContentReceiver multipart_receiver); - bool read_content_core(Stream &strm, bool last_connection, Request &req, - Response &res, ContentReceiver receiver, + bool read_content(Stream &strm, Request &req, Response &res); + bool + read_content_with_content_receiver(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + bool read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, MultipartContentHeader mulitpart_header, ContentReceiver multipart_receiver); virtual bool process_and_close_socket(socket_t sock); + struct MountPointEntry { + std::string mount_point; + std::string base_dir; + Headers headers; + }; + std::vector base_dirs_; + std::atomic is_running_; - std::atomic svr_sock_; - std::vector> base_dirs_; std::map file_extension_and_mimetype_map_; Handler file_request_handler_; Handlers get_handlers_; @@ -550,293 +712,454 @@ private: Handlers patch_handlers_; HandlersForContentReader patch_handlers_for_content_reader_; Handlers delete_handlers_; + HandlersForContentReader delete_handlers_for_content_reader_; Handlers options_handlers_; Handler error_handler_; Logger logger_; Expect100ContinueHandler expect_100_continue_handler_; + + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = default_socket_options; }; -class Client { -public: - explicit Client(const std::string &host, int port = 80, - const std::string &client_cert_path = std::string(), - const std::string &client_key_path = std::string()); +enum Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + UnsupportedMultipartBoundaryChars +}; - virtual ~Client(); +class Result { +public: + Result(std::unique_ptr res, Error err) + : res_(std::move(res)), err_(err) {} + operator bool() const { return res_ != nullptr; } + bool operator==(std::nullptr_t) const { return res_ == nullptr; } + bool operator!=(std::nullptr_t) const { return res_ != nullptr; } + const Response &value() const { return *res_; } + Response &value() { return *res_; } + const Response &operator*() const { return *res_; } + Response &operator*() { return *res_; } + const Response *operator->() const { return res_.get(); } + Response *operator->() { return res_.get(); } + Error error() const { return err_; } + +private: + std::unique_ptr res_; + Error err_; +}; + +class ClientImpl { +public: + explicit ClientImpl(const std::string &host); + + explicit ClientImpl(const std::string &host, int port); + + explicit ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + virtual ~ClientImpl(); virtual bool is_valid() const; - std::shared_ptr Get(const char *path); + Result Get(const char *path); + Result Get(const char *path, const Headers &headers); + Result Get(const char *path, Progress progress); + Result Get(const char *path, const Headers &headers, Progress progress); + Result Get(const char *path, ContentReceiver content_receiver); + Result Get(const char *path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const char *path, ContentReceiver content_receiver, + Progress progress); + Result Get(const char *path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + Result Get(const char *path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); - std::shared_ptr Get(const char *path, const Headers &headers); + Result Head(const char *path); + Result Head(const char *path, const Headers &headers); - std::shared_ptr Get(const char *path, Progress progress); + Result Post(const char *path); + Result Post(const char *path, const std::string &body, + const char *content_type); + Result Post(const char *path, const Headers &headers, const std::string &body, + const char *content_type); + Result Post(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Post(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Post(const char *path, const Params ¶ms); + Result Post(const char *path, const Headers &headers, const Params ¶ms); + Result Post(const char *path, const MultipartFormDataItems &items); + Result Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); - std::shared_ptr Get(const char *path, const Headers &headers, - Progress progress); + Result Put(const char *path); + Result Put(const char *path, const std::string &body, + const char *content_type); + Result Put(const char *path, const Headers &headers, const std::string &body, + const char *content_type); + Result Put(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Put(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Put(const char *path, const Params ¶ms); + Result Put(const char *path, const Headers &headers, const Params ¶ms); - std::shared_ptr Get(const char *path, - ContentReceiver content_receiver); + Result Patch(const char *path, const std::string &body, + const char *content_type); + Result Patch(const char *path, const Headers &headers, + const std::string &body, const char *content_type); + Result Patch(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Patch(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); - std::shared_ptr Get(const char *path, const Headers &headers, - ContentReceiver content_receiver); + Result Delete(const char *path); + Result Delete(const char *path, const std::string &body, + const char *content_type); + Result Delete(const char *path, const Headers &headers); + Result Delete(const char *path, const Headers &headers, + const std::string &body, const char *content_type); - std::shared_ptr - Get(const char *path, ContentReceiver content_receiver, Progress progress); - - std::shared_ptr Get(const char *path, const Headers &headers, - ContentReceiver content_receiver, - Progress progress); - - std::shared_ptr Get(const char *path, const Headers &headers, - ResponseHandler response_handler, - ContentReceiver content_receiver); - - std::shared_ptr Get(const char *path, const Headers &headers, - ResponseHandler response_handler, - ContentReceiver content_receiver, - Progress progress); - - std::shared_ptr Head(const char *path); - - std::shared_ptr Head(const char *path, const Headers &headers); - - std::shared_ptr Post(const char *path, const std::string &body, - const char *content_type); - - std::shared_ptr Post(const char *path, const Headers &headers, - const std::string &body, - const char *content_type); - - std::shared_ptr Post(const char *path, size_t content_length, - ContentProvider content_provider, - const char *content_type); - - std::shared_ptr Post(const char *path, const Headers &headers, - size_t content_length, - ContentProvider content_provider, - const char *content_type); - - std::shared_ptr Post(const char *path, const Params ¶ms); - - std::shared_ptr Post(const char *path, const Headers &headers, - const Params ¶ms); - - std::shared_ptr Post(const char *path, - const MultipartFormDataItems &items); - - std::shared_ptr Post(const char *path, const Headers &headers, - const MultipartFormDataItems &items); - - std::shared_ptr Put(const char *path, const std::string &body, - const char *content_type); - - std::shared_ptr Put(const char *path, const Headers &headers, - const std::string &body, - const char *content_type); - - std::shared_ptr Put(const char *path, size_t content_length, - ContentProvider content_provider, - const char *content_type); - - std::shared_ptr Put(const char *path, const Headers &headers, - size_t content_length, - ContentProvider content_provider, - const char *content_type); - - std::shared_ptr Put(const char *path, const Params ¶ms); - - std::shared_ptr Put(const char *path, const Headers &headers, - const Params ¶ms); - - std::shared_ptr Patch(const char *path, const std::string &body, - const char *content_type); - - std::shared_ptr Patch(const char *path, const Headers &headers, - const std::string &body, - const char *content_type); - - std::shared_ptr Patch(const char *path, size_t content_length, - ContentProvider content_provider, - const char *content_type); - - std::shared_ptr Patch(const char *path, const Headers &headers, - size_t content_length, - ContentProvider content_provider, - const char *content_type); - - std::shared_ptr Delete(const char *path); - - std::shared_ptr Delete(const char *path, const std::string &body, - const char *content_type); - - std::shared_ptr Delete(const char *path, const Headers &headers); - - std::shared_ptr Delete(const char *path, const Headers &headers, - const std::string &body, - const char *content_type); - - std::shared_ptr Options(const char *path); - - std::shared_ptr Options(const char *path, const Headers &headers); + Result Options(const char *path); + Result Options(const char *path, const Headers &headers); bool send(const Request &req, Response &res); - bool send(const std::vector &requests, - std::vector &responses); + size_t is_socket_open() const; - void set_timeout_sec(time_t timeout_sec); + void stop(); - void set_read_timeout(time_t sec, time_t usec); + void set_default_headers(Headers headers); - void set_keep_alive_max_count(size_t count); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + void set_read_timeout(time_t sec, time_t usec = 0); + void set_write_timeout(time_t sec, time_t usec = 0); void set_basic_auth(const char *username, const char *password); - + void set_bearer_token_auth(const char *token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT void set_digest_auth(const char *username, const char *password); #endif + void set_keep_alive(bool on); void set_follow_location(bool on); void set_compress(bool on); + void set_decompress(bool on); + void set_interface(const char *intf); void set_proxy(const char *host, int port); - void set_proxy_basic_auth(const char *username, const char *password); - + void set_proxy_bearer_token_auth(const char *token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT void set_proxy_digest_auth(const char *username, const char *password); #endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + void set_logger(Logger logger); protected: - bool process_request(Stream &strm, const Request &req, Response &res, - bool last_connection, bool &connection_close); + struct Socket { + socket_t sock = INVALID_SOCKET; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSL *ssl = nullptr; +#endif + bool is_open() const { return sock != INVALID_SOCKET; } + }; + + virtual bool create_and_connect_socket(Socket &socket); + + // All of: + // shutdown_ssl + // shutdown_socket + // close_socket + // should ONLY be called when socket_mutex_ is locked. + // Also, shutdown_ssl and close_socket should also NOT be called concurrently + // with a DIFFERENT thread sending requests using that socket. + virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); + void shutdown_socket(Socket &socket); + void close_socket(Socket &socket); + + // Similar to shutdown_ssl and close_socket, this should NOT be called + // concurrently with a DIFFERENT thread sending requests from the socket + void lock_socket_and_shutdown_and_close(); + + bool process_request(Stream &strm, const Request &req, Response &res, + bool close_connection); + + Error get_last_error() const; + + void copy_settings(const ClientImpl &rhs); + + // Error state + mutable std::atomic error_; + + // Socket endoint information const std::string host_; const int port_; const std::string host_and_port_; + // Current open socket + Socket socket_; + mutable std::mutex socket_mutex_; + std::recursive_mutex request_mutex_; + + // These are all protected under socket_mutex + int socket_requests_in_flight_ = 0; + std::thread::id socket_requests_are_from_thread_ = std::thread::id(); + bool socket_should_be_closed_when_request_is_done_ = false; + + // Default headers + Headers default_headers_; + // Settings std::string client_cert_path_; std::string client_key_path_; - time_t timeout_sec_ = 300; + time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; + time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; - - size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; std::string basic_auth_username_; std::string basic_auth_password_; + std::string bearer_token_auth_token_; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT std::string digest_auth_username_; std::string digest_auth_password_; #endif + bool keep_alive_ = false; bool follow_location_ = false; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = nullptr; + bool compress_ = false; + bool decompress_ = true; std::string interface_; std::string proxy_host_; - int proxy_port_; + int proxy_port_ = -1; std::string proxy_basic_auth_username_; std::string proxy_basic_auth_password_; + std::string proxy_bearer_token_auth_token_; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT std::string proxy_digest_auth_username_; std::string proxy_digest_auth_password_; #endif - Logger logger_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool server_certificate_verification_ = true; +#endif - void copy_settings(const Client &rhs) { - client_cert_path_ = rhs.client_cert_path_; - client_key_path_ = rhs.client_key_path_; - timeout_sec_ = rhs.timeout_sec_; - read_timeout_sec_ = rhs.read_timeout_sec_; - read_timeout_usec_ = rhs.read_timeout_usec_; - keep_alive_max_count_ = rhs.keep_alive_max_count_; - basic_auth_username_ = rhs.basic_auth_username_; - basic_auth_password_ = rhs.basic_auth_password_; -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT - digest_auth_username_ = rhs.digest_auth_username_; - digest_auth_password_ = rhs.digest_auth_password_; -#endif - follow_location_ = rhs.follow_location_; - compress_ = rhs.compress_; - interface_ = rhs.interface_; - proxy_host_ = rhs.proxy_host_; - proxy_port_ = rhs.proxy_port_; - proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; - proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT - proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; - proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; -#endif - logger_ = rhs.logger_; - } + Logger logger_; private: socket_t create_client_socket() const; bool read_response_line(Stream &strm, Response &res); - bool write_request(Stream &strm, const Request &req, bool last_connection); + bool write_request(Stream &strm, const Request &req, bool close_connection); bool redirect(const Request &req, Response &res); bool handle_request(Stream &strm, const Request &req, Response &res, - bool last_connection, bool &connection_close); -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT - bool connect(socket_t sock, Response &res, bool &error); -#endif - - std::shared_ptr send_with_content_provider( + bool close_connection); + std::unique_ptr send_with_content_provider( const char *method, const char *path, const Headers &headers, const std::string &body, size_t content_length, ContentProvider content_provider, const char *content_type); - virtual bool process_and_close_socket( - socket_t sock, size_t request_count, - std::function - callback); - + // socket is const because this function is called when socket_mutex_ is not locked + virtual bool process_socket(const Socket &socket, + std::function callback); virtual bool is_ssl() const; }; -inline void Get(std::vector &requests, const char *path, - const Headers &headers) { - Request req; - req.method = "GET"; - req.path = path; - req.headers = headers; - requests.emplace_back(std::move(req)); -} +class Client { +public: + // Universal interface + explicit Client(const char *scheme_host_port); -inline void Get(std::vector &requests, const char *path) { - Get(requests, path, Headers()); -} + explicit Client(const char *scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path); -inline void Post(std::vector &requests, const char *path, - const Headers &headers, const std::string &body, - const char *content_type) { - Request req; - req.method = "POST"; - req.path = path; - req.headers = headers; - req.headers.emplace("Content-Type", content_type); - req.body = body; - requests.emplace_back(std::move(req)); -} + // HTTP only interface + explicit Client(const std::string &host, int port); -inline void Post(std::vector &requests, const char *path, - const std::string &body, const char *content_type) { - Post(requests, path, Headers(), body, content_type); -} + explicit Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + ~Client(); + + bool is_valid() const; + + Result Get(const char *path); + Result Get(const char *path, const Headers &headers); + Result Get(const char *path, Progress progress); + Result Get(const char *path, const Headers &headers, Progress progress); + Result Get(const char *path, ContentReceiver content_receiver); + Result Get(const char *path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const char *path, ContentReceiver content_receiver, + Progress progress); + Result Get(const char *path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const char *path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + Result Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + + Result Head(const char *path); + Result Head(const char *path, const Headers &headers); + + Result Post(const char *path); + Result Post(const char *path, const std::string &body, + const char *content_type); + Result Post(const char *path, const Headers &headers, const std::string &body, + const char *content_type); + Result Post(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Post(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Post(const char *path, const Params ¶ms); + Result Post(const char *path, const Headers &headers, const Params ¶ms); + Result Post(const char *path, const MultipartFormDataItems &items); + Result Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const char *path); + Result Put(const char *path, const std::string &body, + const char *content_type); + Result Put(const char *path, const Headers &headers, const std::string &body, + const char *content_type); + Result Put(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Put(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Put(const char *path, const Params ¶ms); + Result Put(const char *path, const Headers &headers, const Params ¶ms); + Result Patch(const char *path, const std::string &body, + const char *content_type); + Result Patch(const char *path, const Headers &headers, + const std::string &body, const char *content_type); + Result Patch(const char *path, size_t content_length, + ContentProvider content_provider, const char *content_type); + Result Patch(const char *path, const Headers &headers, size_t content_length, + ContentProvider content_provider, const char *content_type); + + Result Delete(const char *path); + Result Delete(const char *path, const std::string &body, + const char *content_type); + Result Delete(const char *path, const Headers &headers); + Result Delete(const char *path, const Headers &headers, + const std::string &body, const char *content_type); + + Result Options(const char *path); + Result Options(const char *path, const Headers &headers); + + bool send(const Request &req, Response &res); + + size_t is_socket_open() const; + + void stop(); + + void set_default_headers(Headers headers); + + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + void set_read_timeout(time_t sec, time_t usec = 0); + void set_write_timeout(time_t sec, time_t usec = 0); + + void set_basic_auth(const char *username, const char *password); + void set_bearer_token_auth(const char *token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const char *username, const char *password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const char *intf); + + void set_proxy(const char *host, int port); + void set_proxy_basic_auth(const char *username, const char *password); + void set_proxy_bearer_token_auth(const char *token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const char *username, const char *password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + + // SSL +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const char *ca_cert_file_path, + const char *ca_cert_dir_path = nullptr); + + void set_ca_cert_store(X509_STORE *ca_cert_store); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; +#endif + +private: + std::unique_ptr cli_; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool is_ssl_ = false; +#endif +}; // namespace httplib #ifdef CPPHTTPLIB_OPENSSL_SUPPORT class SSLServer : public Server { @@ -845,43 +1168,58 @@ public: const char *client_ca_cert_file_path = nullptr, const char *client_ca_cert_dir_path = nullptr); - virtual ~SSLServer(); + SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); - virtual bool is_valid() const; + ~SSLServer() override; + + bool is_valid() const override; private: - virtual bool process_and_close_socket(socket_t sock); + bool process_and_close_socket(socket_t sock) override; SSL_CTX *ctx_; std::mutex ctx_mutex_; }; -class SSLClient : public Client { +class SSLClient : public ClientImpl { public: - SSLClient(const std::string &host, int port = 443, - const std::string &client_cert_path = std::string(), - const std::string &client_key_path = std::string()); + explicit SSLClient(const std::string &host); - virtual ~SSLClient(); + explicit SSLClient(const std::string &host, int port); - virtual bool is_valid() const; + explicit SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); - void set_ca_cert_path(const char *ca_ceert_file_path, + explicit SSLClient(const std::string &host, int port, X509 *client_cert, + EVP_PKEY *client_key); + + ~SSLClient() override; + + bool is_valid() const override; + + void set_ca_cert_path(const char *ca_cert_file_path, const char *ca_cert_dir_path = nullptr); - void enable_server_certificate_verification(bool enabled); + void set_ca_cert_store(X509_STORE *ca_cert_store); long get_openssl_verify_result() const; - SSL_CTX *ssl_context() const noexcept; + SSL_CTX *ssl_context() const; private: - virtual bool process_and_close_socket( - socket_t sock, size_t request_count, - std::function - callback); - virtual bool is_ssl() const; + bool create_and_connect_socket(Socket &socket) override; + void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; + + bool process_socket(const Socket &socket, + std::function callback) override; + bool is_ssl() const override; + + bool connect_with_proxy(Socket &sock, Response &res, bool &success); + bool initialize_ssl(Socket &socket); + + bool load_certs(); bool verify_host(X509 *server_cert) const; bool verify_host_with_subject_alt_name(X509 *server_cert) const; @@ -890,12 +1228,15 @@ private: SSL_CTX *ctx_; std::mutex ctx_mutex_; + std::once_flag initialize_cert_; + std::vector host_components_; std::string ca_cert_file_path_; std::string ca_cert_dir_path_; - bool server_certificate_verification_ = false; long verify_result_ = 0; + + friend class ClientImpl; }; #endif @@ -948,31 +1289,39 @@ inline std::string from_i_to_hex(size_t n) { return ret; } +inline bool start_with(const std::string &a, const std::string &b) { + if (a.size() < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; +} + inline size_t to_utf8(int code, char *buff) { if (code < 0x0080) { buff[0] = (code & 0x7F); return 1; } else if (code < 0x0800) { - buff[0] = (0xC0 | ((code >> 6) & 0x1F)); - buff[1] = (0x80 | (code & 0x3F)); + buff[0] = static_cast(0xC0 | ((code >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (code & 0x3F)); return 2; } else if (code < 0xD800) { - buff[0] = (0xE0 | ((code >> 12) & 0xF)); - buff[1] = (0x80 | ((code >> 6) & 0x3F)); - buff[2] = (0x80 | (code & 0x3F)); + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); return 3; } else if (code < 0xE000) { // D800 - DFFF is invalid... return 0; } else if (code < 0x10000) { - buff[0] = (0xE0 | ((code >> 12) & 0xF)); - buff[1] = (0x80 | ((code >> 6) & 0x3F)); - buff[2] = (0x80 | (code & 0x3F)); + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); return 3; } else if (code < 0x110000) { - buff[0] = (0xF0 | ((code >> 18) & 0x7)); - buff[1] = (0x80 | ((code >> 12) & 0x3F)); - buff[2] = (0x80 | ((code >> 6) & 0x3F)); - buff[3] = (0x80 | (code & 0x3F)); + buff[0] = static_cast(0xF0 | ((code >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((code >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (code & 0x3F)); return 4; } @@ -992,8 +1341,8 @@ inline std::string base64_encode(const std::string &in) { int val = 0; int valb = -6; - for (uint8_t c : in) { - val = (val << 8) + c; + for (auto c : in) { + val = (val << 8) + static_cast(c); valb += 8; while (valb >= 0) { out.push_back(lookup[(val >> valb) & 0x3F]); @@ -1057,13 +1406,81 @@ inline bool is_valid_path(const std::string &path) { return true; } +inline std::string encode_url(const std::string &s) { + std::string result; + + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case ' ': result += "%20"; break; + case '+': result += "%2B"; break; + case '\r': result += "%0D"; break; + case '\n': result += "%0A"; break; + case '\'': result += "%27"; break; + case ',': result += "%2C"; break; + // case ':': result += "%3A"; break; // ok? probably... + case ';': result += "%3B"; break; + default: + auto c = static_cast(s[i]); + if (c >= 0x80) { + result += '%'; + char hex[4]; + auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); + assert(len == 2); + result.append(hex, static_cast(len)); + } else { + result += s[i]; + } + break; + } + } + + return result; +} + +inline std::string decode_url(const std::string &s, + bool convert_plus_to_space) { + std::string result; + + for (size_t i = 0; i < s.size(); i++) { + if (s[i] == '%' && i + 1 < s.size()) { + if (s[i + 1] == 'u') { + int val = 0; + if (from_hex_to_i(s, i + 2, 4, val)) { + // 4 digits Unicode codes + char buff[4]; + size_t len = to_utf8(val, buff); + if (len > 0) { result.append(buff, len); } + i += 5; // 'u0000' + } else { + result += s[i]; + } + } else { + int val = 0; + if (from_hex_to_i(s, i + 1, 2, val)) { + // 2 digits hex codes + result += static_cast(val); + i += 2; // '00' + } else { + result += s[i]; + } + } + } else if (convert_plus_to_space && s[i] == '+') { + result += ' '; + } else { + result += s[i]; + } + } + + return result; +} + inline void read_file(const std::string &path, std::string &out) { std::ifstream fs(path, std::ios_base::binary); fs.seekg(0, std::ios_base::end); auto size = fs.tellg(); fs.seekg(0); out.resize(static_cast(size)); - fs.read(&out[0], size); + fs.read(&out[0], static_cast(size)); } inline std::string file_extension(const std::string &path) { @@ -1073,19 +1490,41 @@ inline std::string file_extension(const std::string &path) { return std::string(); } -template void split(const char *b, const char *e, char d, Fn fn) { - int i = 0; - int beg = 0; +inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; } - while (e ? (b + i != e) : (b[i] != '\0')) { +inline std::pair trim(const char *b, const char *e, size_t left, + size_t right) { + while (b + left < e && is_space_or_tab(b[left])) { + left++; + } + while (right > 0 && is_space_or_tab(b[right - 1])) { + right--; + } + return std::make_pair(left, right); +} + +inline std::string trim_copy(const std::string &s) { + auto r = trim(s.data(), s.data() + s.size(), 0, s.size()); + return s.substr(r.first, r.second - r.first); +} + +template void split(const char *b, const char *e, char d, Fn fn) { + size_t i = 0; + size_t beg = 0; + + while (e ? (b + i < e) : (b[i] != '\0')) { if (b[i] == d) { - fn(&b[beg], &b[i]); + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } beg = i + 1; } i++; } - if (i) { fn(&b[beg], &b[i]); } + if (i) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + } } // NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` @@ -1172,7 +1611,17 @@ inline int close_socket(socket_t sock) { #endif } -inline int select_read(socket_t sock, time_t sec, time_t usec) { +template inline ssize_t handle_EINTR(T fn) { + ssize_t res = false; + while (true) { + res = fn(); + if (res < 0 && errno == EINTR) { continue; } + break; + } + return res; +} + +inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { #ifdef CPPHTTPLIB_USE_POLL struct pollfd pfd_read; pfd_read.fd = sock; @@ -1180,7 +1629,7 @@ inline int select_read(socket_t sock, time_t sec, time_t usec) { auto timeout = static_cast(sec * 1000 + usec / 1000); - return poll(&pfd_read, 1, timeout); + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); #else fd_set fds; FD_ZERO(&fds); @@ -1188,13 +1637,15 @@ inline int select_read(socket_t sock, time_t sec, time_t usec) { timeval tv; tv.tv_sec = static_cast(sec); - tv.tv_usec = static_cast(usec); + tv.tv_usec = static_cast(usec); - return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); + return handle_EINTR([&]() { + return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); + }); #endif } -inline int select_write(socket_t sock, time_t sec, time_t usec) { +inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { #ifdef CPPHTTPLIB_USE_POLL struct pollfd pfd_read; pfd_read.fd = sock; @@ -1202,7 +1653,7 @@ inline int select_write(socket_t sock, time_t sec, time_t usec) { auto timeout = static_cast(sec * 1000 + usec / 1000); - return poll(&pfd_read, 1, timeout); + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); #else fd_set fds; FD_ZERO(&fds); @@ -1210,9 +1661,11 @@ inline int select_write(socket_t sock, time_t sec, time_t usec) { timeval tv; tv.tv_sec = static_cast(sec); - tv.tv_usec = static_cast(usec); + tv.tv_usec = static_cast(usec); - return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); + return handle_EINTR([&]() { + return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); + }); #endif } @@ -1224,13 +1677,14 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { auto timeout = static_cast(sec * 1000 + usec / 1000); - if (poll(&pfd_read, 1, timeout) > 0 && - pfd_read.revents & (POLLIN | POLLOUT)) { + auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { int error = 0; socklen_t len = sizeof(error); - return getsockopt(sock, SOL_SOCKET, SO_ERROR, - reinterpret_cast(&error), &len) >= 0 && - !error; + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + return res >= 0 && !error; } return false; #else @@ -1243,10 +1697,13 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { timeval tv; tv.tv_sec = static_cast(sec); - tv.tv_usec = static_cast(usec); + tv.tv_usec = static_cast(usec); - if (select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv) > 0 && - (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { + auto ret = handle_EINTR([&]() { + return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); + }); + + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { int error = 0; socklen_t len = sizeof(error); return getsockopt(sock, SOL_SOCKET, SO_ERROR, @@ -1259,40 +1716,45 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { class SocketStream : public Stream { public: - SocketStream(socket_t sock, time_t read_timeout_sec, - time_t read_timeout_usec); + SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec); ~SocketStream() override; bool is_readable() const override; bool is_writable() const override; - int read(char *ptr, size_t size) override; - int write(const char *ptr, size_t size) override; - std::string get_remote_addr() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; private: socket_t sock_; time_t read_timeout_sec_; time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; }; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT class SSLSocketStream : public Stream { public: SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, - time_t read_timeout_usec); - virtual ~SSLSocketStream(); + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec); + ~SSLSocketStream() override; bool is_readable() const override; bool is_writable() const override; - int read(char *ptr, size_t size) override; - int write(const char *ptr, size_t size) override; - std::string get_remote_addr() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; private: socket_t sock_; SSL *ssl_; time_t read_timeout_sec_; time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; }; #endif @@ -1303,58 +1765,76 @@ public: bool is_readable() const override; bool is_writable() const override; - int read(char *ptr, size_t size) override; - int write(const char *ptr, size_t size) override; - std::string get_remote_addr() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; const std::string &get_buffer() const; private: std::string buffer; - int position = 0; + size_t position = 0; }; -template -inline bool process_socket(bool is_client_request, socket_t sock, - size_t keep_alive_max_count, time_t read_timeout_sec, - time_t read_timeout_usec, T callback) { - assert(keep_alive_max_count > 0); - - auto ret = false; - - if (keep_alive_max_count > 1) { - auto count = keep_alive_max_count; - while (count > 0 && - (is_client_request || - select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND, - CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) { - SocketStream strm(sock, read_timeout_sec, read_timeout_usec); - auto last_connection = count == 1; - auto connection_close = false; - - ret = callback(strm, last_connection, connection_close); - if (!ret || connection_close) { break; } - - count--; +inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) { + using namespace std::chrono; + auto start = steady_clock::now(); + while (true) { + auto val = select_read(sock, 0, 10000); + if (val < 0) { + return false; + } else if (val == 0) { + auto current = steady_clock::now(); + auto duration = duration_cast(current - start); + auto timeout = keep_alive_timeout_sec * 1000; + if (duration.count() > timeout) { return false; } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } else { + return true; } - } else { // keep_alive_max_count is 0 or 1 - SocketStream strm(sock, read_timeout_sec, read_timeout_usec); - auto dummy_connection_close = false; - ret = callback(strm, true, dummy_connection_close); } +} +template +inline bool +process_server_socket_core(socket_t sock, size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, T callback) { + assert(keep_alive_max_count > 0); + auto ret = false; + auto count = keep_alive_max_count; + while (count > 0 && keep_alive(sock, keep_alive_timeout_sec)) { + auto close_connection = count == 1; + auto connection_closed = false; + ret = callback(close_connection, connection_closed); + if (!ret || connection_closed) { break; } + count--; + } return ret; } template -inline bool process_and_close_socket(bool is_client_request, socket_t sock, - size_t keep_alive_max_count, - time_t read_timeout_sec, - time_t read_timeout_usec, T callback) { - auto ret = process_socket(is_client_request, sock, keep_alive_max_count, - read_timeout_sec, read_timeout_usec, callback); - close_socket(sock); - return ret; +inline bool +process_server_socket(socket_t sock, size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +template +inline bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); } inline int shutdown_socket(socket_t sock) { @@ -1365,18 +1845,10 @@ inline int shutdown_socket(socket_t sock) { #endif } -template -socket_t create_socket(const char *host, int port, Fn fn, - int socket_flags = 0) { -#ifdef _WIN32 -#define SO_SYNCHRONOUS_NONALERT 0x20 -#define SO_OPENTYPE 0x7008 - - int opt = SO_SYNCHRONOUS_NONALERT; - setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char *)&opt, - sizeof(opt)); -#endif - +template +socket_t create_socket(const char *host, int port, int socket_flags, + bool tcp_nodelay, SocketOptions socket_options, + BindOrConnect bind_or_connect) { // Get address info struct addrinfo hints; struct addrinfo *result; @@ -1390,6 +1862,9 @@ socket_t create_socket(const char *host, int port, Fn fn, auto service = std::to_string(port); if (getaddrinfo(host, service.c_str(), &hints, &result)) { +#ifdef __linux__ + res_init(); +#endif return INVALID_SOCKET; } @@ -1424,17 +1899,22 @@ socket_t create_socket(const char *host, int port, Fn fn, if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { continue; } #endif - // Make 'reuse address' option available - int yes = 1; - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), - sizeof(yes)); -#ifdef SO_REUSEPORT - setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&yes), - sizeof(yes)); -#endif + if (tcp_nodelay) { + int yes = 1; + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&yes), + sizeof(yes)); + } + + if (socket_options) { socket_options(sock); } + + if (rp->ai_family == AF_INET6) { + int no = 0; + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&no), + sizeof(no)); + } // bind or connect - if (fn(sock, *rp)) { + if (bind_or_connect(sock, *rp)) { freeaddrinfo(result); return sock; } @@ -1479,7 +1959,7 @@ inline bool bind_ip_address(socket_t sock, const char *host) { auto ret = false; for (auto rp = result; rp; rp = rp->ai_next) { const auto &ai = *rp; - if (!::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + if (!::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { ret = true; break; } @@ -1489,8 +1969,12 @@ inline bool bind_ip_address(socket_t sock, const char *host) { return ret; } +#if !defined _WIN32 && !defined ANDROID +#define USE_IF2IP +#endif + +#ifdef USE_IF2IP inline std::string if2ip(const std::string &ifn) { -#ifndef _WIN32 struct ifaddrs *ifap; getifaddrs(&ifap); for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { @@ -1506,51 +1990,83 @@ inline std::string if2ip(const std::string &ifn) { } } freeifaddrs(ifap); -#endif return std::string(); } +#endif inline socket_t create_client_socket(const char *host, int port, - time_t timeout_sec, - const std::string &intf) { - return create_socket( - host, port, [&](socket_t sock, struct addrinfo &ai) -> bool { + bool tcp_nodelay, + SocketOptions socket_options, + time_t timeout_sec, time_t timeout_usec, + const std::string &intf, std::atomic &error) { + auto sock = create_socket( + host, port, 0, tcp_nodelay, std::move(socket_options), + [&](socket_t sock, struct addrinfo &ai) -> bool { if (!intf.empty()) { +#ifdef USE_IF2IP auto ip = if2ip(intf); if (ip.empty()) { ip = intf; } - if (!bind_ip_address(sock, ip.c_str())) { return false; } + if (!bind_ip_address(sock, ip.c_str())) { + error = Error::BindIPAddress; + return false; + } +#endif } set_nonblocking(sock, true); - auto ret = ::connect(sock, ai.ai_addr, static_cast(ai.ai_addrlen)); + auto ret = + ::connect(sock, ai.ai_addr, static_cast(ai.ai_addrlen)); + if (ret < 0) { if (is_connection_error() || - !wait_until_socket_is_ready(sock, timeout_sec, 0)) { + !wait_until_socket_is_ready(sock, timeout_sec, timeout_usec)) { close_socket(sock); + error = Error::Connection; return false; } } set_nonblocking(sock, false); + error = Error::Success; return true; }); -} -inline std::string get_remote_addr(socket_t sock) { - struct sockaddr_storage addr; - socklen_t len = sizeof(addr); - - if (!getpeername(sock, reinterpret_cast(&addr), &len)) { - std::array ipstr{}; - - if (!getnameinfo(reinterpret_cast(&addr), len, - ipstr.data(), static_cast(ipstr.size()), nullptr, 0, NI_NUMERICHOST)) { - return ipstr.data(); - } + if (sock != INVALID_SOCKET) { + error = Error::Success; + } else { + if (error == Error::Success) { error = Error::Connection; } } - return std::string(); + return sock; +} + +inline void get_remote_ip_and_port(const struct sockaddr_storage &addr, + socklen_t addr_len, std::string &ip, + int &port) { + if (addr.ss_family == AF_INET) { + port = ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + port = + ntohs(reinterpret_cast(&addr)->sin6_port); + } + + std::array ipstr{}; + if (!getnameinfo(reinterpret_cast(&addr), addr_len, + ipstr.data(), static_cast(ipstr.size()), nullptr, + 0, NI_NUMERICHOST)) { + ip = ipstr.data(); + } +} + +inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + if (!getpeername(sock, reinterpret_cast(&addr), + &addr_len)) { + get_remote_ip_and_port(addr, addr_len, ip, port); + } } inline const char * @@ -1596,121 +2112,336 @@ find_content_type(const std::string &path, inline const char *status_message(int status) { switch (status) { case 100: return "Continue"; + case 101: return "Switching Protocol"; + case 102: return "Processing"; + case 103: return "Early Hints"; case 200: return "OK"; + case 201: return "Created"; case 202: return "Accepted"; + case 203: return "Non-Authoritative Information"; case 204: return "No Content"; + case 205: return "Reset Content"; case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 208: return "Already Reported"; + case 226: return "IM Used"; + case 300: return "Multiple Choice"; case 301: return "Moved Permanently"; case 302: return "Found"; case 303: return "See Other"; case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 306: return "unused"; + case 307: return "Temporary Redirect"; + case 308: return "Permanent Redirect"; case 400: return "Bad Request"; case 401: return "Unauthorized"; + case 402: return "Payment Required"; case 403: return "Forbidden"; case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; case 413: return "Payload Too Large"; - case 414: return "Request-URI Too Long"; + case 414: return "URI Too Long"; case 415: return "Unsupported Media Type"; case 416: return "Range Not Satisfiable"; case 417: return "Expectation Failed"; + case 418: return "I'm a teapot"; + case 421: return "Misdirected Request"; + case 422: return "Unprocessable Entity"; + case 423: return "Locked"; + case 424: return "Failed Dependency"; + case 425: return "Too Early"; + case 426: return "Upgrade Required"; + case 428: return "Precondition Required"; + case 429: return "Too Many Requests"; + case 431: return "Request Header Fields Too Large"; + case 451: return "Unavailable For Legal Reasons"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + case 506: return "Variant Also Negotiates"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; default: case 500: return "Internal Server Error"; } } -#ifdef CPPHTTPLIB_ZLIB_SUPPORT -inline bool can_compress(const std::string &content_type) { - return !content_type.find("text/") || content_type == "image/svg+xml" || +inline bool can_compress_content_type(const std::string &content_type) { + return (!content_type.find("text/") && content_type != "text/event-stream") || + content_type == "image/svg+xml" || content_type == "application/javascript" || content_type == "application/json" || content_type == "application/xml" || content_type == "application/xhtml+xml"; } -inline bool compress(std::string &content) { - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; +enum class EncodingType { None = 0, Gzip, Brotli }; - auto ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, - Z_DEFAULT_STRATEGY); - if (ret != Z_OK) { return false; } +inline EncodingType encoding_type(const Request &req, const Response &res) { + auto ret = + detail::can_compress_content_type(res.get_header_value("Content-Type")); + if (!ret) { return EncodingType::None; } - strm.avail_in = content.size(); - strm.next_in = - const_cast(reinterpret_cast(content.data())); + const auto &s = req.get_header_value("Accept-Encoding"); + (void)(s); - std::string compressed; +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + // TODO: 'Accept-Encoding' has br, not br;q=0 + ret = s.find("br") != std::string::npos; + if (ret) { return EncodingType::Brotli; } +#endif - std::array buff{}; - do { - strm.avail_out = buff.size(); - strm.next_out = reinterpret_cast(buff.data()); - ret = deflate(&strm, Z_FINISH); - assert(ret != Z_STREAM_ERROR); - compressed.append(buff.data(), buff.size() - strm.avail_out); - } while (strm.avail_out == 0); +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + // TODO: 'Accept-Encoding' has gzip, not gzip;q=0 + ret = s.find("gzip") != std::string::npos; + if (ret) { return EncodingType::Gzip; } +#endif - assert(ret == Z_STREAM_END); - assert(strm.avail_in == 0); - - content.swap(compressed); - - deflateEnd(&strm); - return true; + return EncodingType::None; } +class compressor { +public: + virtual ~compressor(){}; + + typedef std::function Callback; + virtual bool compress(const char *data, size_t data_length, bool last, + Callback callback) = 0; +}; + class decompressor { public: - decompressor() { - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; + virtual ~decompressor() {} - // 15 is the value of wbits, which should be at the maximum possible value - // to ensure that any gzip stream can be decoded. The offset of 16 specifies - // that the stream to decompress will be formatted with a gzip wrapper. - is_valid_ = inflateInit2(&strm, 16 + 15) == Z_OK; + virtual bool is_valid() const = 0; + + typedef std::function Callback; + virtual bool decompress(const char *data, size_t data_length, + Callback callback) = 0; +}; + +class nocompressor : public compressor { +public: + ~nocompressor(){}; + + bool compress(const char *data, size_t data_length, bool /*last*/, + Callback callback) override { + if (!data_length) { return true; } + return callback(data, data_length); + } +}; + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +class gzip_compressor : public compressor { +public: + gzip_compressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, + Z_DEFAULT_STRATEGY) == Z_OK; } - ~decompressor() { inflateEnd(&strm); } + ~gzip_compressor() { deflateEnd(&strm_); } - bool is_valid() const { return is_valid_; } + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override { + assert(is_valid_); + + auto flush = last ? Z_FINISH : Z_NO_FLUSH; + + strm_.avail_in = static_cast(data_length); + strm_.next_in = const_cast(reinterpret_cast(data)); - template - bool decompress(const char *data, size_t data_length, T callback) { int ret = Z_OK; - strm.avail_in = data_length; - strm.next_in = const_cast(reinterpret_cast(data)); - - std::array buff{}; + std::array buff{}; do { - strm.avail_out = buff.size(); - strm.next_out = reinterpret_cast(buff.data()); + strm_.avail_out = buff.size(); + strm_.next_out = reinterpret_cast(buff.data()); - ret = inflate(&strm, Z_NO_FLUSH); + ret = deflate(&strm_, flush); + assert(ret != Z_STREAM_ERROR); + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } while (strm_.avail_out == 0); + + assert((last && ret == Z_STREAM_END) || (!last && ret == Z_OK)); + assert(strm_.avail_in == 0); + return true; + } + +private: + bool is_valid_ = false; + z_stream strm_; +}; + +class gzip_decompressor : public decompressor { +public: + gzip_decompressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + // 15 is the value of wbits, which should be at the maximum possible value + // to ensure that any gzip stream can be decoded. The offset of 32 specifies + // that the stream type should be automatically detected either gzip or + // deflate. + is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; + } + + ~gzip_decompressor() { inflateEnd(&strm_); } + + bool is_valid() const override { return is_valid_; } + + bool decompress(const char *data, size_t data_length, + Callback callback) override { + assert(is_valid_); + + int ret = Z_OK; + + strm_.avail_in = static_cast(data_length); + strm_.next_in = const_cast(reinterpret_cast(data)); + + std::array buff{}; + while (strm_.avail_in > 0) { + strm_.avail_out = buff.size(); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = inflate(&strm_, Z_NO_FLUSH); assert(ret != Z_STREAM_ERROR); switch (ret) { case Z_NEED_DICT: case Z_DATA_ERROR: - case Z_MEM_ERROR: inflateEnd(&strm); return false; + case Z_MEM_ERROR: inflateEnd(&strm_); return false; } - if (!callback(buff.data(), buff.size() - strm.avail_out)) { + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { return false; } - } while (strm.avail_out == 0); + } return ret == Z_OK || ret == Z_STREAM_END; } private: - bool is_valid_; - z_stream strm; + bool is_valid_ = false; + z_stream strm_; +}; +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +class brotli_compressor : public compressor { +public: + brotli_compressor() { + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); + } + + ~brotli_compressor() { BrotliEncoderDestroyInstance(state_); } + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override { + std::array buff{}; + + auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; + auto available_in = data_length; + auto next_in = reinterpret_cast(data); + + for (;;) { + if (last) { + if (BrotliEncoderIsFinished(state_)) { break; } + } else { + if (!available_in) { break; } + } + + auto available_out = buff.size(); + auto next_out = buff.data(); + + if (!BrotliEncoderCompressStream(state_, operation, &available_in, + &next_in, &available_out, &next_out, + nullptr)) { + return false; + } + + auto output_bytes = buff.size() - available_out; + if (output_bytes) { + callback(reinterpret_cast(buff.data()), output_bytes); + } + } + + return true; + } + +private: + BrotliEncoderState *state_ = nullptr; +}; + +class brotli_decompressor : public decompressor { +public: + brotli_decompressor() { + decoder_s = BrotliDecoderCreateInstance(0, 0, 0); + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT + : BROTLI_DECODER_RESULT_ERROR; + } + + ~brotli_decompressor() { + if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } + } + + bool is_valid() const override { return decoder_s; } + + bool decompress(const char *data, size_t data_length, + Callback callback) override { + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return 0; + } + + const uint8_t *next_in = (const uint8_t *)data; + size_t avail_in = data_length; + size_t total_out; + + decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + std::array buff{}; + while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { + char *next_out = buff.data(); + size_t avail_out = buff.size(); + + decoder_r = BrotliDecoderDecompressStream( + decoder_s, &avail_in, &next_in, &avail_out, + reinterpret_cast(&next_out), &total_out); + + if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + } + + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; + } + +private: + BrotliDecoderResult decoder_r; + BrotliDecoderState *decoder_s = nullptr; }; #endif @@ -1720,21 +2451,60 @@ inline bool has_header(const Headers &headers, const char *key) { inline const char *get_header_value(const Headers &headers, const char *key, size_t id = 0, const char *def = nullptr) { - auto it = headers.find(key); - std::advance(it, id); - if (it != headers.end()) { return it->second.c_str(); } + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second.c_str(); } return def; } -inline uint64_t get_header_value_uint64(const Headers &headers, const char *key, - int def = 0) { - auto it = headers.find(key); - if (it != headers.end()) { +template +inline T get_header_value(const Headers & /*headers*/, const char * /*key*/, + size_t /*id*/ = 0, uint64_t /*def*/ = 0) {} + +template <> +inline uint64_t get_header_value(const Headers &headers, + const char *key, size_t id, + uint64_t def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return std::strtoull(it->second.data(), nullptr, 10); } return def; } +template +inline bool parse_header(const char *beg, const char *end, T fn) { + // Skip trailing spaces and tabs. + while (beg < end && is_space_or_tab(end[-1])) { + end--; + } + + auto p = beg; + while (p < end && *p != ':') { + p++; + } + + if (p == end) { return false; } + + auto key_end = p; + + if (*p++ != ':') { return false; } + + while (p < end && is_space_or_tab(*p)) { + p++; + } + + if (p < end) { + fn(std::string(beg, key_end), decode_url(std::string(p, end), false)); + return true; + } + + return false; +} + inline bool read_headers(Stream &strm, Headers &headers) { const auto bufsiz = 2048; char buf[bufsiz]; @@ -1751,42 +2521,31 @@ inline bool read_headers(Stream &strm, Headers &headers) { continue; // Skip invalid line. } - // Skip trailing spaces and tabs. + // Exclude CRLF auto end = line_reader.ptr() + line_reader.size() - 2; - while (line_reader.ptr() < end && (end[-1] == ' ' || end[-1] == '\t')) { - end--; - } - // Horizontal tab and ' ' are considered whitespace and are ignored when on - // the left or right side of the header value: - // - https://stackoverflow.com/questions/50179659/ - // - https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html - static const std::regex re(R"((.+?):[\t ]*(.+))"); - - std::cmatch m; - if (std::regex_match(line_reader.ptr(), end, m, re)) { - auto key = std::string(m[1]); - auto val = std::string(m[2]); - headers.emplace(key, val); - } + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + headers.emplace(std::move(key), std::move(val)); + }); } return true; } inline bool read_content_with_length(Stream &strm, uint64_t len, - Progress progress, ContentReceiver out) { + Progress progress, + ContentReceiverWithProgress out) { char buf[CPPHTTPLIB_RECV_BUFSIZ]; uint64_t r = 0; while (r < len) { auto read_len = static_cast(len - r); - auto n = strm.read(buf, std::min(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); if (n <= 0) { return false; } - if (!out(buf, n)) { return false; } - - r += n; + if (!out(buf, static_cast(n), r, len)) { return false; } + r += static_cast(n); if (progress) { if (!progress(r, len)) { return false; } @@ -1801,14 +2560,16 @@ inline void skip_content_with_length(Stream &strm, uint64_t len) { uint64_t r = 0; while (r < len) { auto read_len = static_cast(len - r); - auto n = strm.read(buf, std::min(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); if (n <= 0) { return; } - r += n; + r += static_cast(n); } } -inline bool read_content_without_length(Stream &strm, ContentReceiver out) { +inline bool read_content_without_length(Stream &strm, + ContentReceiverWithProgress out) { char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; for (;;) { auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); if (n < 0) { @@ -1816,13 +2577,16 @@ inline bool read_content_without_length(Stream &strm, ContentReceiver out) { } else if (n == 0) { return true; } - if (!out(buf, n)) { return false; } + + if (!out(buf, static_cast(n), r, 0)) { return false; } + r += static_cast(n); } return true; } -inline bool read_content_chunked(Stream &strm, ContentReceiver out) { +inline bool read_content_chunked(Stream &strm, + ContentReceiverWithProgress out) { const auto bufsiz = 16; char buf[bufsiz]; @@ -1830,9 +2594,17 @@ inline bool read_content_chunked(Stream &strm, ContentReceiver out) { if (!line_reader.getline()) { return false; } - auto chunk_len = std::stoi(line_reader.ptr(), 0, 16); + unsigned long chunk_len; + while (true) { + char *end_ptr; + + chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); + + if (end_ptr == line_reader.ptr()) { return false; } + if (chunk_len == ULONG_MAX) { return false; } + + if (chunk_len == 0) { break; } - while (chunk_len > 0) { if (!read_content_with_length(strm, chunk_len, nullptr, out)) { return false; } @@ -1842,8 +2614,6 @@ inline bool read_content_chunked(Stream &strm, ContentReceiver out) { if (strcmp(line_reader.ptr(), "\r\n")) { break; } if (!line_reader.getline()) { return false; } - - chunk_len = std::stoi(line_reader.ptr(), 0, 16); } if (chunk_len == 0) { @@ -1860,62 +2630,91 @@ inline bool is_chunked_transfer_encoding(const Headers &headers) { "chunked"); } -template -bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, - Progress progress, ContentReceiver receiver) { - - ContentReceiver out = [&](const char *buf, size_t n) { - return receiver(buf, n); - }; +template +bool prepare_content_receiver(T &x, int &status, + ContentReceiverWithProgress receiver, + bool decompress, U callback) { + if (decompress) { + std::string encoding = x.get_header_value("Content-Encoding"); + std::unique_ptr decompressor; + if (encoding.find("gzip") != std::string::npos || + encoding.find("deflate") != std::string::npos) { #ifdef CPPHTTPLIB_ZLIB_SUPPORT - decompressor decompressor; - - if (!decompressor.is_valid()) { - status = 500; - return false; - } - - if (x.get_header_value("Content-Encoding") == "gzip") { - out = [&](const char *buf, size_t n) { - return decompressor.decompress( - buf, n, [&](const char *buf, size_t n) { return receiver(buf, n); }); - }; - } + decompressor = detail::make_unique(); #else - if (x.get_header_value("Content-Encoding") == "gzip") { - status = 415; - return false; - } + status = 415; + return false; #endif + } else if (encoding.find("br") != std::string::npos) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + decompressor = detail::make_unique(); +#else + status = 415; + return false; +#endif + } - auto ret = true; - auto exceed_payload_max_length = false; - - if (is_chunked_transfer_encoding(x.headers)) { - ret = read_content_chunked(strm, out); - } else if (!has_header(x.headers, "Content-Length")) { - ret = read_content_without_length(strm, out); - } else { - auto len = get_header_value_uint64(x.headers, "Content-Length", 0); - if (len > payload_max_length) { - exceed_payload_max_length = true; - skip_content_with_length(strm, len); - ret = false; - } else if (len > 0) { - ret = read_content_with_length(strm, len, progress, out); + if (decompressor) { + if (decompressor->is_valid()) { + ContentReceiverWithProgress out = [&](const char *buf, size_t n, + uint64_t off, uint64_t len) { + return decompressor->decompress(buf, n, + [&](const char *buf, size_t n) { + return receiver(buf, n, off, len); + }); + }; + return callback(std::move(out)); + } else { + status = 500; + return false; + } } } - if (!ret) { status = exceed_payload_max_length ? 413 : 400; } - - return ret; + ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, + uint64_t len) { + return receiver(buf, n, off, len); + }; + return callback(std::move(out)); } template -inline int write_headers(Stream &strm, const T &info, const Headers &headers) { - auto write_len = 0; +bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, + Progress progress, ContentReceiverWithProgress receiver, + bool decompress) { + return prepare_content_receiver( + x, status, std::move(receiver), decompress, + [&](const ContentReceiverWithProgress &out) { + auto ret = true; + auto exceed_payload_max_length = false; + + if (is_chunked_transfer_encoding(x.headers)) { + ret = read_content_chunked(strm, out); + } else if (!has_header(x.headers, "Content-Length")) { + ret = read_content_without_length(strm, out); + } else { + auto len = get_header_value(x.headers, "Content-Length"); + if (len > payload_max_length) { + exceed_payload_max_length = true; + skip_content_with_length(strm, len); + ret = false; + } else if (len > 0) { + ret = read_content_with_length(strm, len, std::move(progress), out); + } + } + + if (!ret) { status = exceed_payload_max_length ? 413 : 400; } + return ret; + }); +} + +template +inline ssize_t write_headers(Stream &strm, const T &info, + const Headers &headers) { + ssize_t write_len = 0; for (const auto &x : info.headers) { + if (x.first == "EXCEPTION_WHAT") { continue; } auto len = strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); if (len < 0) { return len; } @@ -1933,57 +2732,150 @@ inline int write_headers(Stream &strm, const T &info, const Headers &headers) { return write_len; } +inline bool write_data(Stream &strm, const char *d, size_t l) { + size_t offset = 0; + while (offset < l) { + auto length = strm.write(d + offset, l - offset); + if (length < 0) { return false; } + offset += static_cast(length); + } + return true; +} + +template inline ssize_t write_content(Stream &strm, ContentProvider content_provider, - size_t offset, size_t length) { + size_t offset, size_t length, T is_shutting_down) { size_t begin_offset = offset; size_t end_offset = offset + length; - while (offset < end_offset) { - ssize_t written_length = 0; + auto ok = true; + DataSink data_sink; - DataSink data_sink; - data_sink.write = [&](const char *d, size_t l) { + data_sink.write = [&](const char *d, size_t l) { + if (ok) { offset += l; - written_length = strm.write(d, l); - }; - data_sink.done = [&](void) { written_length = -1; }; - data_sink.is_writable = [&](void) { return strm.is_writable(); }; + if (!write_data(strm, d, l)) { ok = false; } + } + }; - content_provider(offset, end_offset - offset, data_sink); - if (written_length < 0) { return written_length; } + data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; + + while (offset < end_offset && !is_shutting_down()) { + if (!content_provider(offset, end_offset - offset, data_sink)) { + return -1; + } + if (!ok) { return -1; } } + return static_cast(offset - begin_offset); } template +inline ssize_t write_content_without_length(Stream &strm, + ContentProvider content_provider, + T is_shutting_down) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) { + if (ok) { + offset += l; + if (!write_data(strm, d, l)) { ok = false; } + } + }; + + data_sink.done = [&](void) { data_available = false; }; + + data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; + + while (data_available && !is_shutting_down()) { + if (!content_provider(offset, 0, data_sink)) { return -1; } + if (!ok) { return -1; } + } + + return static_cast(offset); +} + +template inline ssize_t write_content_chunked(Stream &strm, ContentProvider content_provider, - T is_shutting_down) { + T is_shutting_down, U &compressor) { size_t offset = 0; auto data_available = true; ssize_t total_written_length = 0; - while (data_available && !is_shutting_down()) { - ssize_t written_length = 0; + auto ok = true; + DataSink data_sink; - DataSink data_sink; - data_sink.write = [&](const char *d, size_t l) { - data_available = l > 0; - offset += l; + data_sink.write = [&](const char *d, size_t l) { + if (!ok) { return; } + data_available = l > 0; + offset += l; + + std::string payload; + if (!compressor.compress(d, l, false, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + ok = false; + return; + } + + if (!payload.empty()) { // Emit chunked response header and footer for each chunk - auto chunk = from_i_to_hex(l) + "\r\n" + std::string(d, l) + "\r\n"; - written_length = strm.write(chunk); - }; - data_sink.done = [&](void) { - data_available = false; - written_length = strm.write("0\r\n\r\n"); - }; - data_sink.is_writable = [&](void) { return strm.is_writable(); }; + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (write_data(strm, chunk.data(), chunk.size())) { + total_written_length += chunk.size(); + } else { + ok = false; + return; + } + } + }; - content_provider(offset, 0, data_sink); + data_sink.done = [&](void) { + if (!ok) { return; } - if (written_length < 0) { return written_length; } - total_written_length += written_length; + data_available = false; + + std::string payload; + if (!compressor.compress(nullptr, 0, true, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + ok = false; + return; + } + + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (write_data(strm, chunk.data(), chunk.size())) { + total_written_length += chunk.size(); + } else { + ok = false; + return; + } + } + + static const std::string done_marker("0\r\n\r\n"); + if (write_data(strm, done_marker.data(), done_marker.size())) { + total_written_length += done_marker.size(); + } else { + ok = false; + } + }; + + data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; + + while (data_available && !is_shutting_down()) { + if (!content_provider(offset, 0, data_sink)) { return -1; } + if (!ok) { return -1; } } + return total_written_length; } @@ -1994,6 +2886,12 @@ inline bool redirect(T &cli, const Request &req, Response &res, new_req.path = path; new_req.redirect_count -= 1; + if (res.status == 303 && (req.method != "GET" && req.method != "HEAD")) { + new_req.method = "GET"; + new_req.body.clear(); + new_req.headers.clear(); + } + Response new_res; auto ret = cli.send(new_req, new_res); @@ -2001,75 +2899,20 @@ inline bool redirect(T &cli, const Request &req, Response &res, return ret; } -inline std::string encode_url(const std::string &s) { - std::string result; +inline std::string params_to_query_str(const Params ¶ms) { + std::string query; - for (auto i = 0; s[i]; i++) { - switch (s[i]) { - case ' ': result += "%20"; break; - case '+': result += "%2B"; break; - case '\r': result += "%0D"; break; - case '\n': result += "%0A"; break; - case '\'': result += "%27"; break; - case ',': result += "%2C"; break; - // case ':': result += "%3A"; break; // ok? probably... - case ';': result += "%3B"; break; - default: - auto c = static_cast(s[i]); - if (c >= 0x80) { - result += '%'; - char hex[4]; - size_t len = snprintf(hex, sizeof(hex) - 1, "%02X", c); - assert(len == 2); - result.append(hex, len); - } else { - result += s[i]; - } - break; - } + for (auto it = params.begin(); it != params.end(); ++it) { + if (it != params.begin()) { query += "&"; } + query += it->first; + query += "="; + query += encode_url(it->second); } - - return result; -} - -inline std::string decode_url(const std::string &s) { - std::string result; - - for (size_t i = 0; i < s.size(); i++) { - if (s[i] == '%' && i + 1 < s.size()) { - if (s[i + 1] == 'u') { - int val = 0; - if (from_hex_to_i(s, i + 2, 4, val)) { - // 4 digits Unicode codes - char buff[4]; - size_t len = to_utf8(val, buff); - if (len > 0) { result.append(buff, len); } - i += 5; // 'u0000' - } else { - result += s[i]; - } - } else { - int val = 0; - if (from_hex_to_i(s, i + 1, 2, val)) { - // 2 digits hex codes - result += static_cast(val); - i += 2; // '00' - } else { - result += s[i]; - } - } - } else if (s[i] == '+') { - result += ' '; - } else { - result += s[i]; - } - } - - return result; + return query; } inline void parse_query_text(const std::string &s, Params ¶ms) { - split(&s[0], &s[s.size()], '&', [&](const char *b, const char *e) { + split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) { std::string key; std::string val; split(b, e, '=', [&](const char *b2, const char *e2) { @@ -2079,7 +2922,10 @@ inline void parse_query_text(const std::string &s, Params ¶ms) { val.assign(b2, e2); } }); - params.emplace(key, decode_url(val)); + + if (!key.empty()) { + params.emplace(decode_url(key, true), decode_url(val, true)); + } }); } @@ -2087,17 +2933,20 @@ inline bool parse_multipart_boundary(const std::string &content_type, std::string &boundary) { auto pos = content_type.find("boundary="); if (pos == std::string::npos) { return false; } - boundary = content_type.substr(pos + 9); - return true; + if (boundary.length() >= 2 && boundary.front() == '"' && + boundary.back() == '"') { + boundary = boundary.substr(1, boundary.size() - 2); + } + return !boundary.empty(); } -inline bool parse_range_header(const std::string &s, Ranges &ranges) { +inline bool parse_range_header(const std::string &s, Ranges &ranges) try { static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))"); std::smatch m; if (std::regex_match(s, m, re_first_range)) { - auto pos = m.position(1); - auto len = m.length(1); + auto pos = static_cast(m.position(1)); + auto len = static_cast(m.length(1)); bool all_valid_ranges = true; split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) { if (!all_valid_ranges) return; @@ -2124,25 +2973,26 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) { return all_valid_ranges; } return false; -} +} catch (...) { return false; } class MultipartFormDataParser { public: - MultipartFormDataParser() {} + MultipartFormDataParser() = default; - void set_boundary(const std::string &boundary) { boundary_ = boundary; } + void set_boundary(std::string &&boundary) { boundary_ = boundary; } bool is_valid() const { return is_valid_; } template - bool parse(const char *buf, size_t n, T content_callback, U header_callback) { - static const std::regex re_content_type(R"(^Content-Type:\s*(.*?)\s*$)", - std::regex_constants::icase); + bool parse(const char *buf, size_t n, const T &content_callback, + const U &header_callback) { static const std::regex re_content_disposition( "^Content-Disposition:\\s*form-data;\\s*name=\"(.*?)\"(?:;\\s*filename=" "\"(.*?)\")?\\s*$", std::regex_constants::icase); + static const std::string dash_ = "--"; + static const std::string crlf_ = "\r\n"; buf_.append(buf, n); // TODO: performance improvement @@ -2152,10 +3002,7 @@ public: auto pattern = dash_ + boundary_ + crlf_; if (pattern.size() > buf_.size()) { return true; } auto pos = buf_.find(pattern); - if (pos != 0) { - is_done_ = true; - return false; - } + if (pos != 0) { return false; } buf_.erase(0, pattern.size()); off_ += pattern.size(); state_ = 1; @@ -2173,7 +3020,6 @@ public: if (pos == 0) { if (!header_callback(file_)) { is_valid_ = false; - is_done_ = false; return false; } buf_.erase(0, crlf_.size()); @@ -2182,12 +3028,13 @@ public: break; } - auto header = buf_.substr(0, pos); - { + static const std::string header_name = "content-type:"; + const auto header = buf_.substr(0, pos); + if (start_with(header, header_name)) { + file_.content_type = trim_copy(header.substr(header_name.size())); + } else { std::smatch m; - if (std::regex_match(header, m, re_content_type)) { - file_.content_type = m[1]; - } else if (std::regex_match(header, m, re_content_disposition)) { + if (std::regex_match(header, m, re_content_disposition)) { file_.name = m[1]; file_.filename = m[2]; } @@ -2197,6 +3044,7 @@ public: off_ += pos + crlf_.size(); pos = buf_.find(crlf_); } + if (state_ != 3) { return true; } break; } case 3: { // Body @@ -2205,10 +3053,17 @@ public: if (pattern.size() > buf_.size()) { return true; } auto pos = buf_.find(pattern); - if (pos == std::string::npos) { pos = buf_.size(); } + if (pos == std::string::npos) { + pos = buf_.size(); + while (pos > 0) { + auto c = buf_[pos - 1]; + if (c != '\r' && c != '\n' && c != '-') { break; } + pos--; + } + } + if (!content_callback(buf_.data(), pos)) { is_valid_ = false; - is_done_ = false; return false; } @@ -2224,7 +3079,6 @@ public: if (pos != std::string::npos) { if (!content_callback(buf_.data(), pos)) { is_valid_ = false; - is_done_ = false; return false; } @@ -2234,7 +3088,6 @@ public: } else { if (!content_callback(buf_.data(), pattern.size())) { is_valid_ = false; - is_done_ = false; return false; } @@ -2246,20 +3099,19 @@ public: } case 4: { // Boundary if (crlf_.size() > buf_.size()) { return true; } - if (buf_.find(crlf_) == 0) { + if (buf_.compare(0, crlf_.size(), crlf_) == 0) { buf_.erase(0, crlf_.size()); off_ += crlf_.size(); state_ = 1; } else { auto pattern = dash_ + crlf_; if (pattern.size() > buf_.size()) { return true; } - if (buf_.find(pattern) == 0) { + if (buf_.compare(0, pattern.size(), pattern) == 0) { buf_.erase(0, pattern.size()); off_ += pattern.size(); is_valid_ = true; state_ = 5; } else { - is_done_ = true; return true; } } @@ -2282,14 +3134,11 @@ private: file_.content_type.clear(); } - const std::string dash_ = "--"; - const std::string crlf_ = "\r\n"; std::string boundary_; std::string buf_; size_t state_ = 0; - size_t is_valid_ = false; - size_t is_done_ = false; + bool is_valid_ = false; size_t off_ = 0; MultipartFormData file_; }; @@ -2308,8 +3157,13 @@ inline std::string make_multipart_data_boundary() { static const char data[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. std::random_device seed_gen; - std::mt19937 engine(seed_gen()); + // Request 128 bits of entropy for initialization + std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()}; + std::mt19937 engine(seed_sequence); std::string result = "--cpp-httplib-multipart-data-"; @@ -2329,12 +3183,14 @@ get_range_offset_and_length(const Request &req, size_t content_length, return std::make_pair(0, content_length); } + auto slen = static_cast(content_length); + if (r.first == -1) { - r.first = content_length - r.second; - r.second = content_length - 1; + r.first = (std::max)(static_cast(0), slen - r.second); + r.second = slen - 1; } - if (r.second == -1) { r.second = content_length - 1; } + if (r.second == -1) { r.second = slen - 1; } return std::make_pair(r.first, r.second - r.first + 1); } @@ -2420,16 +3276,19 @@ get_multipart_ranges_data_length(const Request &req, Response &res, return data_length; } +template inline bool write_multipart_ranges_data(Stream &strm, const Request &req, Response &res, const std::string &boundary, - const std::string &content_type) { + const std::string &content_type, + T is_shutting_down) { return process_multipart_ranges_data( req, res, boundary, content_type, [&](const std::string &token) { strm.write(token); }, [&](const char *token) { strm.write(token); }, [&](size_t offset, size_t length) { - return write_content(strm, res.content_provider, offset, length) >= 0; + return write_content(strm, res.content_provider_, offset, length, + is_shutting_down) >= 0; }); } @@ -2438,20 +3297,31 @@ get_range_offset_and_length(const Request &req, const Response &res, size_t index) { auto r = req.ranges[index]; - if (r.second == -1) { r.second = res.content_length - 1; } + if (r.second == -1) { + r.second = static_cast(res.content_length_) - 1; + } return std::make_pair(r.first, r.second - r.first + 1); } inline bool expect_content(const Request &req) { if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || - req.method == "PRI") { + req.method == "PRI" || req.method == "DELETE") { return true; } // TODO: check if Content-Length is set return false; } +inline bool has_crlf(const char *s) { + auto p = s; + while (*p) { + if (*p == '\r' || *p == '\n') { return true; } + p++; + } + return false; +} + #ifdef CPPHTTPLIB_OPENSSL_SUPPORT template inline std::string message_digest(const std::string &s, Init init, @@ -2489,6 +3359,33 @@ inline std::string SHA_512(const std::string &s) { #endif #ifdef _WIN32 +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store +inline bool load_system_certs_on_windows(X509_STORE *store) { + auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); + + if (!hStore) { return false; } + + PCCERT_CONTEXT pContext = NULL; + while (pContext = CertEnumCertificatesInStore(hStore, pContext)) { + auto encoded_cert = + static_cast(pContext->pbCertEncoded); + + auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + } + } + + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + return true; +} +#endif + class WSInit { public: WSInit() { @@ -2502,31 +3399,6 @@ public: static WSInit wsinit_; #endif -} // namespace detail - -// Header utilities -inline std::pair make_range_header(Ranges ranges) { - std::string field = "bytes="; - auto i = 0; - for (auto r : ranges) { - if (i != 0) { field += ", "; } - if (r.first != -1) { field += std::to_string(r.first); } - field += '-'; - if (r.second != -1) { field += std::to_string(r.second); } - i++; - } - return std::make_pair("Range", field); -} - -inline std::pair -make_basic_authentication_header(const std::string &username, - const std::string &password, - bool is_proxy = false) { - auto field = "Basic " + detail::base64_encode(username + ":" + password); - auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; - return std::make_pair(key, field); -} - #ifdef CPPHTTPLIB_OPENSSL_SUPPORT inline std::pair make_digest_authentication_header( const Request &req, const std::map &auth, @@ -2566,17 +3438,18 @@ inline std::pair make_digest_authentication_header( ":" + qop + ":" + H(A2)); } - auto field = "Digest username=\"hello\", realm=\"" + auth.at("realm") + - "\", nonce=\"" + auth.at("nonce") + "\", uri=\"" + req.path + - "\", algorithm=" + algo + ", qop=" + qop + ", nc=\"" + nc + - "\", cnonce=\"" + cnonce + "\", response=\"" + response + "\""; + auto field = "Digest username=\"" + username + "\", realm=\"" + + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + + "\", uri=\"" + req.path + "\", algorithm=" + algo + + ", qop=" + qop + ", nc=\"" + nc + "\", cnonce=\"" + cnonce + + "\", response=\"" + response + "\""; auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; return std::make_pair(key, field); } #endif -inline bool parse_www_authenticate(const httplib::Response &res, +inline bool parse_www_authenticate(const Response &res, std::map &auth, bool is_proxy) { auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; @@ -2593,9 +3466,13 @@ inline bool parse_www_authenticate(const httplib::Response &res, auto beg = std::sregex_iterator(s.begin(), s.end(), re); for (auto i = beg; i != std::sregex_iterator(); ++i) { auto m = *i; - auto key = s.substr(m.position(1), m.length(1)); - auto val = m.length(2) > 0 ? s.substr(m.position(2), m.length(2)) - : s.substr(m.position(3), m.length(3)); + auto key = s.substr(static_cast(m.position(1)), + static_cast(m.length(1))); + auto val = m.length(2) > 0 + ? s.substr(static_cast(m.position(2)), + static_cast(m.length(2))) + : s.substr(static_cast(m.position(3)), + static_cast(m.length(3))); auth[key] = val; } return true; @@ -2612,13 +3489,60 @@ inline std::string random_string(size_t length) { "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"; const size_t max_index = (sizeof(charset) - 1); - return charset[rand() % max_index]; + return charset[static_cast(rand()) % max_index]; }; std::string str(length, 0); std::generate_n(str.begin(), length, randchar); return str; } +class ContentProviderAdapter { +public: + explicit ContentProviderAdapter( + ContentProviderWithoutLength &&content_provider) + : content_provider_(content_provider) {} + + bool operator()(size_t offset, size_t, DataSink &sink) { + return content_provider_(offset, sink); + } + +private: + ContentProviderWithoutLength content_provider_; +}; + +} // namespace detail + +// Header utilities +inline std::pair make_range_header(Ranges ranges) { + std::string field = "bytes="; + auto i = 0; + for (auto r : ranges) { + if (i != 0) { field += ", "; } + if (r.first != -1) { field += std::to_string(r.first); } + field += '-'; + if (r.second != -1) { field += std::to_string(r.second); } + i++; + } + return std::make_pair("Range", std::move(field)); +} + +inline std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, + bool is_proxy = false) { + auto field = "Basic " + detail::base64_encode(username + ":" + password); + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +inline std::pair +make_bearer_token_authentication_header(const std::string &token, + bool is_proxy = false) { + auto field = "Bearer " + token; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + // Request implementation inline bool Request::has_header(const char *key) const { return detail::has_header(headers, key); @@ -2628,17 +3552,26 @@ inline std::string Request::get_header_value(const char *key, size_t id) const { return detail::get_header_value(headers, key, id, ""); } +template +inline T Request::get_header_value(const char *key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + inline size_t Request::get_header_value_count(const char *key) const { auto r = headers.equal_range(key); - return std::distance(r.first, r.second); + return static_cast(std::distance(r.first, r.second)); } inline void Request::set_header(const char *key, const char *val) { - headers.emplace(key, val); + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } } inline void Request::set_header(const char *key, const std::string &val) { - headers.emplace(key, val); + if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) { + headers.emplace(key, val); + } } inline bool Request::has_param(const char *key) const { @@ -2646,15 +3579,16 @@ inline bool Request::has_param(const char *key) const { } inline std::string Request::get_param_value(const char *key, size_t id) const { - auto it = params.find(key); - std::advance(it, id); - if (it != params.end()) { return it->second; } + auto rng = params.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } return std::string(); } inline size_t Request::get_param_value_count(const char *key) const { auto r = params.equal_range(key); - return std::distance(r.first, r.second); + return static_cast(std::distance(r.first, r.second)); } inline bool Request::is_multipart_form_data() const { @@ -2682,22 +3616,41 @@ inline std::string Response::get_header_value(const char *key, return detail::get_header_value(headers, key, id, ""); } +template +inline T Response::get_header_value(const char *key, size_t id) const { + return detail::get_header_value(headers, key, id, 0); +} + inline size_t Response::get_header_value_count(const char *key) const { auto r = headers.equal_range(key); - return std::distance(r.first, r.second); + return static_cast(std::distance(r.first, r.second)); } inline void Response::set_header(const char *key, const char *val) { - headers.emplace(key, val); + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } } inline void Response::set_header(const char *key, const std::string &val) { - headers.emplace(key, val); + if (!detail::has_crlf(key) && !detail::has_crlf(val.c_str())) { + headers.emplace(key, val); + } } -inline void Response::set_redirect(const char *url) { - set_header("Location", url); - status = 302; +inline void Response::set_redirect(const char *url, int stat) { + if (!detail::has_crlf(url)) { + set_header("Location", url); + if (300 <= stat && stat < 400) { + this->status = stat; + } else { + this->status = 302; + } + } +} + +inline void Response::set_redirect(const std::string &url, int stat) { + set_redirect(url.c_str(), stat); } inline void Response::set_content(const char *s, size_t n, @@ -2706,62 +3659,79 @@ inline void Response::set_content(const char *s, size_t n, set_header("Content-Type", content_type); } -inline void Response::set_content(const std::string &s, - const char *content_type) { - body = s; +inline void Response::set_content(std::string s, const char *content_type) { + body = std::move(s); set_header("Content-Type", content_type); } -inline void Response::set_content_provider( - size_t in_length, - std::function provider, - std::function resource_releaser) { +inline void +Response::set_content_provider(size_t in_length, const char *content_type, + ContentProvider provider, + const std::function &resource_releaser) { assert(in_length > 0); - content_length = in_length; - content_provider = [provider](size_t offset, size_t length, DataSink &sink) { - provider(offset, length, sink); - }; - content_provider_resource_releaser = resource_releaser; + set_header("Content-Type", content_type); + content_length_ = in_length; + content_provider_ = std::move(provider); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider = false; +} + +inline void +Response::set_content_provider(const char *content_type, + ContentProviderWithoutLength provider, + const std::function &resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider = false; } inline void Response::set_chunked_content_provider( - std::function provider, - std::function resource_releaser) { - content_length = 0; - content_provider = [provider](size_t offset, size_t, DataSink &sink) { - provider(offset, sink); - }; - content_provider_resource_releaser = resource_releaser; + const char *content_type, ContentProviderWithoutLength provider, + const std::function &resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = resource_releaser; + is_chunked_content_provider = true; } // Rstream implementation -inline int Stream::write(const char *ptr) { return write(ptr, strlen(ptr)); } +inline ssize_t Stream::write(const char *ptr) { + return write(ptr, strlen(ptr)); +} -inline int Stream::write(const std::string &s) { +inline ssize_t Stream::write(const std::string &s) { return write(s.data(), s.size()); } template -inline int Stream::write_format(const char *fmt, const Args &... args) { - std::array buf; +inline ssize_t Stream::write_format(const char *fmt, const Args &... args) { + const auto bufsiz = 2048; + std::array buf; #if defined(_MSC_VER) && _MSC_VER < 1900 - auto n = _snprintf_s(buf, bufsiz, buf.size() - 1, fmt, args...); + auto sn = _snprintf_s(buf.data(), bufsiz - 1, buf.size() - 1, fmt, args...); #else - auto n = snprintf(buf.data(), buf.size() - 1, fmt, args...); + auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); #endif - if (n <= 0) { return n; } + if (sn <= 0) { return sn; } - if (n >= static_cast(buf.size()) - 1) { + auto n = static_cast(sn); + + if (n >= buf.size() - 1) { std::vector glowable_buf(buf.size()); - while (n >= static_cast(glowable_buf.size() - 1)) { + while (n >= glowable_buf.size() - 1) { glowable_buf.resize(glowable_buf.size() * 2); #if defined(_MSC_VER) && _MSC_VER < 1900 - n = _snprintf_s(&glowable_buf[0], glowable_buf.size(), - glowable_buf.size() - 1, fmt, args...); + n = static_cast(_snprintf_s(&glowable_buf[0], glowable_buf.size(), + glowable_buf.size() - 1, fmt, + args...)); #else - n = snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...); + n = static_cast( + snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); #endif } return write(&glowable_buf[0], n); @@ -2774,32 +3744,53 @@ namespace detail { // Socket stream implementation inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, - time_t read_timeout_usec) + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) : sock_(sock), read_timeout_sec_(read_timeout_sec), - read_timeout_usec_(read_timeout_usec) {} + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec) {} inline SocketStream::~SocketStream() {} inline bool SocketStream::is_readable() const { - return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; } inline bool SocketStream::is_writable() const { - return detail::select_write(sock_, 0, 0) > 0; + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0; } -inline int SocketStream::read(char *ptr, size_t size) { - if (is_readable()) { return recv(sock_, ptr, static_cast(size), 0); } - return -1; +inline ssize_t SocketStream::read(char *ptr, size_t size) { + if (!is_readable()) { return -1; } + +#ifdef _WIN32 + if (size > static_cast((std::numeric_limits::max)())) { + return -1; + } + return recv(sock_, ptr, static_cast(size), 0); +#else + return handle_EINTR([&]() { return recv(sock_, ptr, size, 0); }); +#endif } -inline int SocketStream::write(const char *ptr, size_t size) { - if (is_writable()) { return send(sock_, ptr, static_cast(size), 0); } - return -1; +inline ssize_t SocketStream::write(const char *ptr, size_t size) { + if (!is_writable()) { return -1; } + +#ifdef _WIN32 + if (size > static_cast((std::numeric_limits::max)())) { + return -1; + } + return send(sock_, ptr, static_cast(size), 0); +#else + return handle_EINTR([&]() { return send(sock_, ptr, size, 0); }); +#endif } -inline std::string SocketStream::get_remote_addr() const { - return detail::get_remote_addr(sock_); +inline void SocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + return detail::get_remote_ip_and_port(sock_, ip, port); } // Buffer stream implementation @@ -2807,22 +3798,23 @@ inline bool BufferStream::is_readable() const { return true; } inline bool BufferStream::is_writable() const { return true; } -inline int BufferStream::read(char *ptr, size_t size) { -#if defined(_MSC_VER) && _MSC_VER < 1900 - int len_read = static_cast(buffer._Copy_s(ptr, size, size, position)); +inline ssize_t BufferStream::read(char *ptr, size_t size) { +#if defined(_MSC_VER) && _MSC_VER <= 1900 + auto len_read = buffer._Copy_s(ptr, size, size, position); #else - int len_read = static_cast(buffer.copy(ptr, size, position)); + auto len_read = buffer.copy(ptr, size, position); #endif - position += len_read; - return len_read; + position += static_cast(len_read); + return static_cast(len_read); } -inline int BufferStream::write(const char *ptr, size_t size) { +inline ssize_t BufferStream::write(const char *ptr, size_t size) { buffer.append(ptr, size); - return static_cast(size); + return static_cast(size); } -inline std::string BufferStream::get_remote_addr() const { return ""; } +inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} inline const std::string &BufferStream::get_buffer() const { return buffer; } @@ -2830,67 +3822,77 @@ inline const std::string &BufferStream::get_buffer() const { return buffer; } // HTTP server implementation inline Server::Server() - : keep_alive_max_count_(CPPHTTPLIB_KEEPALIVE_MAX_COUNT), - read_timeout_sec_(CPPHTTPLIB_READ_TIMEOUT_SECOND), - read_timeout_usec_(CPPHTTPLIB_READ_TIMEOUT_USECOND), - payload_max_length_(CPPHTTPLIB_PAYLOAD_MAX_LENGTH), is_running_(false), - svr_sock_(INVALID_SOCKET) { + : new_task_queue( + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }), + svr_sock_(INVALID_SOCKET), is_running_(false) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif - new_task_queue = [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }; } inline Server::~Server() {} inline Server &Server::Get(const char *pattern, Handler handler) { - get_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); + get_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } inline Server &Server::Post(const char *pattern, Handler handler) { - post_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); + post_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } inline Server &Server::Post(const char *pattern, HandlerWithContentReader handler) { post_handlers_for_content_reader_.push_back( - std::make_pair(std::regex(pattern), handler)); + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } inline Server &Server::Put(const char *pattern, Handler handler) { - put_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); + put_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } inline Server &Server::Put(const char *pattern, HandlerWithContentReader handler) { put_handlers_for_content_reader_.push_back( - std::make_pair(std::regex(pattern), handler)); + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } inline Server &Server::Patch(const char *pattern, Handler handler) { - patch_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); + patch_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } inline Server &Server::Patch(const char *pattern, HandlerWithContentReader handler) { patch_handlers_for_content_reader_.push_back( - std::make_pair(std::regex(pattern), handler)); + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } inline Server &Server::Delete(const char *pattern, Handler handler) { - delete_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); + delete_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); + return *this; +} + +inline Server &Server::Delete(const char *pattern, + HandlerWithContentReader handler) { + delete_handlers_for_content_reader_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } inline Server &Server::Options(const char *pattern, Handler handler) { - options_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); + options_handlers_.push_back( + std::make_pair(std::regex(pattern), std::move(handler))); return *this; } @@ -2898,11 +3900,12 @@ inline bool Server::set_base_dir(const char *dir, const char *mount_point) { return set_mount_point(mount_point, dir); } -inline bool Server::set_mount_point(const char *mount_point, const char *dir) { +inline bool Server::set_mount_point(const char *mount_point, const char *dir, + Headers headers) { if (detail::is_dir(dir)) { std::string mnt = mount_point ? mount_point : "/"; if (!mnt.empty() && mnt[0] == '/') { - base_dirs_.emplace_back(mnt, dir); + base_dirs_.push_back({mnt, dir, std::move(headers)}); return true; } } @@ -2911,7 +3914,7 @@ inline bool Server::set_mount_point(const char *mount_point, const char *dir) { inline bool Server::remove_mount_point(const char *mount_point) { for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { - if (it->first == mount_point) { + if (it->mount_point == mount_point) { base_dirs_.erase(it); return true; } @@ -2932,6 +3935,12 @@ inline void Server::set_error_handler(Handler handler) { error_handler_ = std::move(handler); } +inline void Server::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } + +inline void Server::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); +} + inline void Server::set_logger(Logger logger) { logger_ = std::move(logger); } inline void @@ -2943,11 +3952,25 @@ inline void Server::set_keep_alive_max_count(size_t count) { keep_alive_max_count_ = count; } +inline void Server::set_keep_alive_timeout(time_t sec) { + keep_alive_timeout_sec_ = sec; +} + inline void Server::set_read_timeout(time_t sec, time_t usec) { read_timeout_sec_ = sec; read_timeout_usec_ = usec; } +inline void Server::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; +} + +inline void Server::set_idle_interval(time_t sec, time_t usec) { + idle_interval_sec_ = sec; + idle_interval_usec_ = usec; +} + inline void Server::set_payload_max_length(size_t length) { payload_max_length_ = length; } @@ -2987,7 +4010,7 @@ inline bool Server::parse_request_line(const char *s, Request &req) { req.version = std::string(m[5]); req.method = std::string(m[1]); req.target = std::string(m[2]); - req.path = detail::decode_url(m[3]); + req.path = detail::decode_url(m[3], false); // Parse query text auto len = std::distance(m[4].first, m[4].second); @@ -2999,7 +4022,7 @@ inline bool Server::parse_request_line(const char *s, Request &req) { return false; } -inline bool Server::write_response(Stream &strm, bool last_connection, +inline bool Server::write_response(Stream &strm, bool close_connection, const Request &req, Response &res) { assert(res.status != -1); @@ -3014,16 +4037,17 @@ inline bool Server::write_response(Stream &strm, bool last_connection, } // Headers - if (last_connection || req.get_header_value("Connection") == "close") { + if (close_connection || req.get_header_value("Connection") == "close") { res.set_header("Connection", "close"); - } - - if (!last_connection && req.get_header_value("Connection") == "Keep-Alive") { - res.set_header("Connection", "Keep-Alive"); + } else { + std::stringstream ss; + ss << "timeout=" << keep_alive_timeout_sec_ + << ", max=" << keep_alive_max_count_; + res.set_header("Keep-Alive", ss.str()); } if (!res.has_header("Content-Type") && - (!res.body.empty() || res.content_length > 0)) { + (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) { res.set_header("Content-Type", "text/plain"); } @@ -3047,18 +4071,20 @@ inline bool Server::write_response(Stream &strm, bool last_connection, "multipart/byteranges; boundary=" + boundary); } + auto type = detail::encoding_type(req, res); + if (res.body.empty()) { - if (res.content_length > 0) { + if (res.content_length_ > 0) { size_t length = 0; if (req.ranges.empty()) { - length = res.content_length; + length = res.content_length_; } else if (req.ranges.size() == 1) { auto offsets = - detail::get_range_offset_and_length(req, res.content_length, 0); + detail::get_range_offset_and_length(req, res.content_length_, 0); auto offset = offsets.first; length = offsets.second; auto content_range = detail::make_content_range_header_field( - offset, length, res.content_length); + offset, length, res.content_length_); res.set_header("Content-Range", content_range); } else { length = detail::get_multipart_ranges_data_length(req, res, boundary, @@ -3066,8 +4092,15 @@ inline bool Server::write_response(Stream &strm, bool last_connection, } res.set_header("Content-Length", std::to_string(length)); } else { - if (res.content_provider) { - res.set_header("Transfer-Encoding", "chunked"); + if (res.content_provider_) { + if (res.is_chunked_content_provider) { + res.set_header("Transfer-Encoding", "chunked"); + if (type == detail::EncodingType::Gzip) { + res.set_header("Content-Encoding", "gzip"); + } else if (type == detail::EncodingType::Brotli) { + res.set_header("Content-Encoding", "br"); + } + } } else { res.set_header("Content-Length", "0"); } @@ -3089,16 +4122,35 @@ inline bool Server::write_response(Stream &strm, bool last_connection, detail::make_multipart_ranges_data(req, res, boundary, content_type); } + if (type != detail::EncodingType::None) { + std::unique_ptr compressor; + + if (type == detail::EncodingType::Gzip) { #ifdef CPPHTTPLIB_ZLIB_SUPPORT - // TODO: 'Accept-Encoding' has gzip, not gzip;q=0 - const auto &encodings = req.get_header_value("Accept-Encoding"); - if (encodings.find("gzip") != std::string::npos && - detail::can_compress(res.get_header_value("Content-Type"))) { - if (detail::compress(res.body)) { + compressor = detail::make_unique(); res.set_header("Content-Encoding", "gzip"); +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); + res.set_header("Content-Encoding", "brotli"); +#endif + } + + if (compressor) { + std::string compressed; + + if (!compressor->compress(res.body.data(), res.body.size(), true, + [&](const char *data, size_t data_len) { + compressed.append(data, data_len); + return true; + })) { + return false; + } + + res.body.swap(compressed); } } -#endif auto length = std::to_string(res.body.size()); res.set_header("Content-Length", length); @@ -3111,13 +4163,14 @@ inline bool Server::write_response(Stream &strm, bool last_connection, strm.write(data.data(), data.size()); // Body + auto ret = true; if (req.method != "HEAD") { if (!res.body.empty()) { - if (!strm.write(res.body)) { return false; } - } else if (res.content_provider) { + if (!strm.write(res.body)) { ret = false; } + } else if (res.content_provider_) { if (!write_content_with_provider(strm, req, res, boundary, content_type)) { - return false; + ret = false; } } } @@ -3125,119 +4178,158 @@ inline bool Server::write_response(Stream &strm, bool last_connection, // Log if (logger_) { logger_(req, res); } - return true; + return ret; } inline bool Server::write_content_with_provider(Stream &strm, const Request &req, Response &res, const std::string &boundary, const std::string &content_type) { - if (res.content_length) { + auto is_shutting_down = [this]() { + return this->svr_sock_ == INVALID_SOCKET; + }; + + if (res.content_length_ > 0) { if (req.ranges.empty()) { - if (detail::write_content(strm, res.content_provider, 0, - res.content_length) < 0) { + if (detail::write_content(strm, res.content_provider_, 0, + res.content_length_, is_shutting_down) < 0) { return false; } } else if (req.ranges.size() == 1) { auto offsets = - detail::get_range_offset_and_length(req, res.content_length, 0); + detail::get_range_offset_and_length(req, res.content_length_, 0); auto offset = offsets.first; auto length = offsets.second; - if (detail::write_content(strm, res.content_provider, offset, length) < - 0) { + if (detail::write_content(strm, res.content_provider_, offset, length, + is_shutting_down) < 0) { return false; } } else { - if (!detail::write_multipart_ranges_data(strm, req, res, boundary, - content_type)) { + if (!detail::write_multipart_ranges_data( + strm, req, res, boundary, content_type, is_shutting_down)) { return false; } } } else { - auto is_shutting_down = [this]() { - return this->svr_sock_ == INVALID_SOCKET; - }; - if (detail::write_content_chunked(strm, res.content_provider, - is_shutting_down) < 0) { - return false; + if (res.is_chunked_content_provider) { + auto type = detail::encoding_type(req, res); + + std::unique_ptr compressor; + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); +#endif + } else { + compressor = detail::make_unique(); + } + assert(compressor != nullptr); + + if (detail::write_content_chunked(strm, res.content_provider_, + is_shutting_down, *compressor) < 0) { + return false; + } + } else { + if (detail::write_content_without_length(strm, res.content_provider_, + is_shutting_down) < 0) { + return false; + } } } return true; } -inline bool Server::read_content(Stream &strm, bool last_connection, - Request &req, Response &res) { +inline bool Server::read_content(Stream &strm, Request &req, Response &res) { MultipartFormDataMap::iterator cur; - auto ret = read_content_core( - strm, last_connection, req, res, - // Regular - [&](const char *buf, size_t n) { - if (req.body.size() + n > req.body.max_size()) { return false; } - req.body.append(buf, n); - return true; - }, - // Multipart - [&](const MultipartFormData &file) { - cur = req.files.emplace(file.name, file); - return true; - }, - [&](const char *buf, size_t n) { - auto &content = cur->second.content; - if (content.size() + n > content.max_size()) { return false; } - content.append(buf, n); - return true; - }); - - const auto &content_type = req.get_header_value("Content-Type"); - if (!content_type.find("application/x-www-form-urlencoded")) { - detail::parse_query_text(req.body, req.params); + if (read_content_core( + strm, req, res, + // Regular + [&](const char *buf, size_t n) { + if (req.body.size() + n > req.body.max_size()) { return false; } + req.body.append(buf, n); + return true; + }, + // Multipart + [&](const MultipartFormData &file) { + cur = req.files.emplace(file.name, file); + return true; + }, + [&](const char *buf, size_t n) { + auto &content = cur->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + return true; + })) { + const auto &content_type = req.get_header_value("Content-Type"); + if (!content_type.find("application/x-www-form-urlencoded")) { + detail::parse_query_text(req.body, req.params); + } + return true; } - - return ret; + return false; } inline bool Server::read_content_with_content_receiver( - Stream &strm, bool last_connection, Request &req, Response &res, - ContentReceiver receiver, MultipartContentHeader multipart_header, + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + MultipartContentHeader multipart_header, ContentReceiver multipart_receiver) { - return read_content_core(strm, last_connection, req, res, receiver, - multipart_header, multipart_receiver); + return read_content_core(strm, req, res, std::move(receiver), + std::move(multipart_header), + std::move(multipart_receiver)); } -inline bool Server::read_content_core(Stream &strm, bool last_connection, - Request &req, Response &res, +inline bool Server::read_content_core(Stream &strm, Request &req, Response &res, ContentReceiver receiver, MultipartContentHeader mulitpart_header, ContentReceiver multipart_receiver) { detail::MultipartFormDataParser multipart_form_data_parser; - ContentReceiver out; + ContentReceiverWithProgress out; if (req.is_multipart_form_data()) { const auto &content_type = req.get_header_value("Content-Type"); std::string boundary; if (!detail::parse_multipart_boundary(content_type, boundary)) { res.status = 400; - return write_response(strm, last_connection, req, res); + return false; } - multipart_form_data_parser.set_boundary(boundary); - out = [&](const char *buf, size_t n) { + multipart_form_data_parser.set_boundary(std::move(boundary)); + out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { + /* For debug + size_t pos = 0; + while (pos < n) { + auto read_size = std::min(1, n - pos); + auto ret = multipart_form_data_parser.parse( + buf + pos, read_size, multipart_receiver, mulitpart_header); + if (!ret) { return false; } + pos += read_size; + } + return true; + */ return multipart_form_data_parser.parse(buf, n, multipart_receiver, mulitpart_header); }; } else { - out = receiver; + out = [receiver](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { return receiver(buf, n); }; } - if (!detail::read_content(strm, req, payload_max_length_, res.status, - Progress(), out)) { - return write_response(strm, last_connection, req, res); + if (req.method == "DELETE" && !req.has_header("Content-Length")) { + return true; + } + + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, + out, true)) { + return false; } if (req.is_multipart_form_data()) { if (!multipart_form_data_parser.is_valid()) { res.status = 400; - return write_response(strm, last_connection, req, res); + return false; } } @@ -3246,15 +4338,12 @@ inline bool Server::read_content_core(Stream &strm, bool last_connection, inline bool Server::handle_file_request(Request &req, Response &res, bool head) { - for (const auto &kv : base_dirs_) { - const auto &mount_point = kv.first; - const auto &base_dir = kv.second; - + for (const auto &entry : base_dirs_) { // Prefix match - if (!req.path.find(mount_point)) { - std::string sub_path = "/" + req.path.substr(mount_point.size()); + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { + std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); if (detail::is_valid_path(sub_path)) { - auto path = base_dir + sub_path; + auto path = entry.base_dir + sub_path; if (path.back() == '/') { path += "index.html"; } if (detail::is_file(path)) { @@ -3262,6 +4351,9 @@ inline bool Server::handle_file_request(Request &req, Response &res, auto type = detail::find_content_type(path, file_extension_and_mimetype_map_); if (type) { res.set_header("Content-Type", type); } + for (const auto &kv : entry.headers) { + res.set_header(kv.first.c_str(), kv.second); + } res.status = 200; if (!head && file_request_handler_) { file_request_handler_(req, res); @@ -3274,40 +4366,39 @@ inline bool Server::handle_file_request(Request &req, Response &res, return false; } -inline socket_t Server::create_server_socket(const char *host, int port, - int socket_flags) const { +inline socket_t +Server::create_server_socket(const char *host, int port, int socket_flags, + SocketOptions socket_options) const { return detail::create_socket( - host, port, + host, port, socket_flags, tcp_nodelay_, std::move(socket_options), [](socket_t sock, struct addrinfo &ai) -> bool { - if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { return false; } if (::listen(sock, 5)) { // Listen through 5 channels return false; } return true; - }, - socket_flags); + }); } inline int Server::bind_internal(const char *host, int port, int socket_flags) { if (!is_valid()) { return -1; } - svr_sock_ = create_server_socket(host, port, socket_flags); + svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); if (svr_sock_ == INVALID_SOCKET) { return -1; } if (port == 0) { - struct sockaddr_storage address; - socklen_t len = sizeof(address); - if (getsockname(svr_sock_, reinterpret_cast(&address), - &len) == -1) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(svr_sock_, reinterpret_cast(&addr), + &addr_len) == -1) { return -1; } - if (address.ss_family == AF_INET) { - return ntohs(reinterpret_cast(&address)->sin_port); - } else if (address.ss_family == AF_INET6) { - return ntohs( - reinterpret_cast(&address)->sin6_port); + if (addr.ss_family == AF_INET) { + return ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + return ntohs(reinterpret_cast(&addr)->sin6_port); } else { return -1; } @@ -3323,18 +4414,19 @@ inline bool Server::listen_internal() { { std::unique_ptr task_queue(new_task_queue()); - for (;;) { - if (svr_sock_ == INVALID_SOCKET) { - // The server socket was closed by 'stop' method. - break; + while (svr_sock_ != INVALID_SOCKET) { +#ifndef _WIN32 + if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { +#endif + auto val = detail::select_read(svr_sock_, idle_interval_sec_, + idle_interval_usec_); + if (val == 0) { // Timeout + task_queue->on_idle(); + continue; + } +#ifndef _WIN32 } - - auto val = detail::select_read(svr_sock_, 0, 100000); - - if (val == 0) { // Timeout - continue; - } - +#endif socket_t sock = accept(svr_sock_, nullptr, nullptr); if (sock == INVALID_SOCKET) { @@ -3353,7 +4445,11 @@ inline bool Server::listen_internal() { break; } +#if __cplusplus > 201703L + task_queue->enqueue([=, this]() { process_and_close_socket(sock); }); +#else task_queue->enqueue([=]() { process_and_close_socket(sock); }); +#endif } task_queue->shutdown(); @@ -3363,8 +4459,7 @@ inline bool Server::listen_internal() { return ret; } -inline bool Server::routing(Request &req, Response &res, Stream &strm, - bool last_connection) { +inline bool Server::routing(Request &req, Response &res, Stream &strm) { // File handler bool is_head_request = req.method == "HEAD"; if ((req.method == "GET" || is_head_request) && @@ -3378,33 +4473,43 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm, ContentReader reader( [&](ContentReceiver receiver) { return read_content_with_content_receiver( - strm, last_connection, req, res, receiver, nullptr, nullptr); + strm, req, res, std::move(receiver), nullptr, nullptr); }, [&](MultipartContentHeader header, ContentReceiver receiver) { - return read_content_with_content_receiver( - strm, last_connection, req, res, nullptr, header, receiver); + return read_content_with_content_receiver(strm, req, res, nullptr, + std::move(header), + std::move(receiver)); }); if (req.method == "POST") { if (dispatch_request_for_content_reader( - req, res, reader, post_handlers_for_content_reader_)) { + req, res, std::move(reader), + post_handlers_for_content_reader_)) { return true; } } else if (req.method == "PUT") { if (dispatch_request_for_content_reader( - req, res, reader, put_handlers_for_content_reader_)) { + req, res, std::move(reader), + put_handlers_for_content_reader_)) { return true; } } else if (req.method == "PATCH") { if (dispatch_request_for_content_reader( - req, res, reader, patch_handlers_for_content_reader_)) { + req, res, std::move(reader), + patch_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "DELETE") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + delete_handlers_for_content_reader_)) { return true; } } } // Read content into `req.body` - if (!read_content(strm, last_connection, req, res)) { return false; } + if (!read_content(strm, req, res)) { return false; } } // Regular handler @@ -3427,22 +4532,30 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm, } inline bool Server::dispatch_request(Request &req, Response &res, - Handlers &handlers) { - for (const auto &x : handlers) { - const auto &pattern = x.first; - const auto &handler = x.second; + const Handlers &handlers) { + try { + for (const auto &x : handlers) { + const auto &pattern = x.first; + const auto &handler = x.second; - if (std::regex_match(req.path, req.matches, pattern)) { - handler(req, res); - return true; + if (std::regex_match(req.path, req.matches, pattern)) { + handler(req, res); + return true; + } } + } catch (const std::exception &ex) { + res.status = 500; + res.set_header("EXCEPTION_WHAT", ex.what()); + } catch (...) { + res.status = 500; + res.set_header("EXCEPTION_WHAT", "UNKNOWN"); } return false; } inline bool Server::dispatch_request_for_content_reader( Request &req, Response &res, ContentReader content_reader, - HandlersForContentReader &handlers) { + const HandlersForContentReader &handlers) { for (const auto &x : handlers) { const auto &pattern = x.first; const auto &handler = x.second; @@ -3456,8 +4569,8 @@ inline bool Server::dispatch_request_for_content_reader( } inline bool -Server::process_request(Stream &strm, bool last_connection, - bool &connection_close, +Server::process_request(Stream &strm, bool close_connection, + bool &connection_closed, const std::function &setup_request) { std::array buf{}; @@ -3476,31 +4589,34 @@ Server::process_request(Stream &strm, bool last_connection, Headers dummy; detail::read_headers(strm, dummy); res.status = 414; - return write_response(strm, last_connection, req, res); + return write_response(strm, close_connection, req, res); } // Request line and headers if (!parse_request_line(line_reader.ptr(), req) || !detail::read_headers(strm, req.headers)) { res.status = 400; - return write_response(strm, last_connection, req, res); + return write_response(strm, close_connection, req, res); } if (req.get_header_value("Connection") == "close") { - connection_close = true; + connection_closed = true; } if (req.version == "HTTP/1.0" && req.get_header_value("Connection") != "Keep-Alive") { - connection_close = true; + connection_closed = true; } - req.set_header("REMOTE_ADDR", strm.get_remote_addr()); + strm.get_remote_ip_and_port(req.remote_addr, req.remote_port); + req.set_header("REMOTE_ADDR", req.remote_addr); + req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); if (req.has_header("Range")) { const auto &range_header_value = req.get_header_value("Range"); if (!detail::parse_range_header(range_header_value, req.ranges)) { - // TODO: error + res.status = 416; + return write_response(strm, close_connection, req, res); } } @@ -3517,136 +4633,278 @@ Server::process_request(Stream &strm, bool last_connection, strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status, detail::status_message(status)); break; - default: return write_response(strm, last_connection, req, res); + default: return write_response(strm, close_connection, req, res); } } // Rounting - if (routing(req, res, strm, last_connection)) { + if (routing(req, res, strm)) { if (res.status == -1) { res.status = req.ranges.empty() ? 200 : 206; } } else { if (res.status == -1) { res.status = 404; } } - return write_response(strm, last_connection, req, res); + return write_response(strm, close_connection, req, res); } inline bool Server::is_valid() const { return true; } inline bool Server::process_and_close_socket(socket_t sock) { - return detail::process_and_close_socket( - false, sock, keep_alive_max_count_, read_timeout_sec_, read_timeout_usec_, - [this](Stream &strm, bool last_connection, bool &connection_close) { - return process_request(strm, last_connection, connection_close, + auto ret = detail::process_server_socket( + sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_, + read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, + [this](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, nullptr); }); + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; } // HTTP client implementation -inline Client::Client(const std::string &host, int port, - const std::string &client_cert_path, - const std::string &client_key_path) - : host_(host), port_(port), +inline ClientImpl::ClientImpl(const std::string &host) + : ClientImpl(host, 80, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port) + : ClientImpl(host, port, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : error_(Error::Success), host_(host), port_(port), host_and_port_(host_ + ":" + std::to_string(port_)), client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} -inline Client::~Client() {} +inline ClientImpl::~ClientImpl() { lock_socket_and_shutdown_and_close(); } -inline bool Client::is_valid() const { return true; } +inline bool ClientImpl::is_valid() const { return true; } -inline socket_t Client::create_client_socket() const { - if (!proxy_host_.empty()) { - return detail::create_client_socket(proxy_host_.c_str(), proxy_port_, - timeout_sec_, interface_); - } - return detail::create_client_socket(host_.c_str(), port_, timeout_sec_, - interface_); +inline Error ClientImpl::get_last_error() const { return error_; } + +inline void ClientImpl::copy_settings(const ClientImpl &rhs) { + client_cert_path_ = rhs.client_cert_path_; + client_key_path_ = rhs.client_key_path_; + connection_timeout_sec_ = rhs.connection_timeout_sec_; + read_timeout_sec_ = rhs.read_timeout_sec_; + read_timeout_usec_ = rhs.read_timeout_usec_; + write_timeout_sec_ = rhs.write_timeout_sec_; + write_timeout_usec_ = rhs.write_timeout_usec_; + basic_auth_username_ = rhs.basic_auth_username_; + basic_auth_password_ = rhs.basic_auth_password_; + bearer_token_auth_token_ = rhs.bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + digest_auth_username_ = rhs.digest_auth_username_; + digest_auth_password_ = rhs.digest_auth_password_; +#endif + keep_alive_ = rhs.keep_alive_; + follow_location_ = rhs.follow_location_; + tcp_nodelay_ = rhs.tcp_nodelay_; + socket_options_ = rhs.socket_options_; + compress_ = rhs.compress_; + decompress_ = rhs.decompress_; + interface_ = rhs.interface_; + proxy_host_ = rhs.proxy_host_; + proxy_port_ = rhs.proxy_port_; + proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; + proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; + proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; + proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + server_certificate_verification_ = rhs.server_certificate_verification_; +#endif + logger_ = rhs.logger_; } -inline bool Client::read_response_line(Stream &strm, Response &res) { +inline socket_t ClientImpl::create_client_socket() const { + if (!proxy_host_.empty() && proxy_port_ != -1) { + return detail::create_client_socket( + proxy_host_.c_str(), proxy_port_, tcp_nodelay_, socket_options_, + connection_timeout_sec_, connection_timeout_usec_, interface_, error_); + } + return detail::create_client_socket( + host_.c_str(), port_, tcp_nodelay_, socket_options_, + connection_timeout_sec_, connection_timeout_usec_, interface_, error_); +} + +inline bool ClientImpl::create_and_connect_socket(Socket &socket) { + auto sock = create_client_socket(); + if (sock == INVALID_SOCKET) { return false; } + socket.sock = sock; + return true; +} + +inline void ClientImpl::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { + (void)socket; + (void)shutdown_gracefully; + //If there are any requests in flight from threads other than us, then it's + //a thread-unsafe race because individual ssl* objects are not thread-safe. + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); +} + +inline void ClientImpl::shutdown_socket(Socket &socket) { + if (socket.sock == INVALID_SOCKET) + return; + detail::shutdown_socket(socket.sock); +} + +inline void ClientImpl::close_socket(Socket &socket) { + // If there are requests in flight in another thread, usually closing + // the socket will be fine and they will simply receive an error when + // using the closed socket, but it is still a bug since rarely the OS + // may reassign the socket id to be used for a new socket, and then + // suddenly they will be operating on a live socket that is different + // than the one they intended! + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + // It is also a bug if this happens while SSL is still active +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + assert(socket.ssl == nullptr); +#endif + if (socket.sock == INVALID_SOCKET) + return; + detail::close_socket(socket.sock); + socket.sock = INVALID_SOCKET; +} + +inline void ClientImpl::lock_socket_and_shutdown_and_close() { + std::lock_guard guard(socket_mutex_); + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline bool ClientImpl::read_response_line(Stream &strm, Response &res) { std::array buf; detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); if (!line_reader.getline()) { return false; } - const static std::regex re("(HTTP/1\\.[01]) (\\d+?) .*\r\n"); + const static std::regex re("(HTTP/1\\.[01]) (\\d+) (.*?)\r\n"); std::cmatch m; - if (std::regex_match(line_reader.ptr(), m, re)) { + if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + + // Ignore '100 Continue' + while (res.status == 100) { + if (!line_reader.getline()) { return false; } // CRLF + if (!line_reader.getline()) { return false; } // next response line + + if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } res.version = std::string(m[1]); res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); } return true; } -inline bool Client::send(const Request &req, Response &res) { - auto sock = create_client_socket(); - if (sock == INVALID_SOCKET) { return false; } +inline bool ClientImpl::send(const Request &req, Response &res) { + std::lock_guard request_mutex_guard(request_mutex_); -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT - if (is_ssl() && !proxy_host_.empty()) { - bool error; - if (!connect(sock, res, error)) { return error; } - } -#endif + { + std::lock_guard guard(socket_mutex_); + // Set this to false immediately - if it ever gets set to true by the end of the + // request, we know another thread instructed us to close the socket. + socket_should_be_closed_when_request_is_done_ = false; - return process_and_close_socket( - sock, 1, [&](Stream &strm, bool last_connection, bool &connection_close) { - return handle_request(strm, req, res, last_connection, - connection_close); - }); -} - -inline bool Client::send(const std::vector &requests, - std::vector &responses) { - size_t i = 0; - while (i < requests.size()) { - auto sock = create_client_socket(); - if (sock == INVALID_SOCKET) { return false; } - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT - if (is_ssl() && !proxy_host_.empty()) { - Response res; - bool error; - if (!connect(sock, res, error)) { return false; } + auto is_alive = false; + if (socket_.is_open()) { + is_alive = detail::select_write(socket_.sock, 0, 0) > 0; + if (!is_alive) { + // Attempt to avoid sigpipe by shutting down nongracefully if it seems like + // the other side has already closed the connection + // Also, there cannot be any requests in flight from other threads since we locked + // request_mutex_, so safe to close everything immediately + const bool shutdown_gracefully = false; + shutdown_ssl(socket_, shutdown_gracefully); + shutdown_socket(socket_); + close_socket(socket_); + } } -#endif - if (!process_and_close_socket(sock, requests.size() - i, - [&](Stream &strm, bool last_connection, - bool &connection_close) -> bool { - auto &req = requests[i++]; - auto res = Response(); - auto ret = handle_request(strm, req, res, - last_connection, - connection_close); - if (ret) { - responses.emplace_back(std::move(res)); - } - return ret; - })) { - return false; + if (!is_alive) { + if (!create_and_connect_socket(socket_)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // TODO: refactoring + if (is_ssl()) { + auto &scli = static_cast(*this); + if (!proxy_host_.empty() && proxy_port_ != -1) { + bool success = false; + if (!scli.connect_with_proxy(socket_, res, success)) { + return success; + } + } + + if (!scli.initialize_ssl(socket_)) { return false; } + } +#endif + } + + // Mark the current socket as being in use so that it cannot be closed by anyone + // else while this request is ongoing, even though we will be releasing the mutex. + if (socket_requests_in_flight_ > 1) { + assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + socket_requests_in_flight_ += 1; + socket_requests_are_from_thread_ = std::this_thread::get_id(); + } + + auto close_connection = !keep_alive_; + auto ret = process_socket(socket_, [&](Stream &strm) { + return handle_request(strm, req, res, close_connection); + }); + + //Briefly lock mutex in order to mark that a request is no longer ongoing + { + std::lock_guard guard(socket_mutex_); + socket_requests_in_flight_ -= 1; + if (socket_requests_in_flight_ <= 0) { + assert(socket_requests_in_flight_ == 0); + socket_requests_are_from_thread_ = std::thread::id(); + } + + if (socket_should_be_closed_when_request_is_done_ || + close_connection || + !ret ) { + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); } } - return true; + if (!ret) { + if (error_ == Error::Success) { error_ = Error::Unknown; } + } + + return ret; } -inline bool Client::handle_request(Stream &strm, const Request &req, - Response &res, bool last_connection, - bool &connection_close) { - if (req.path.empty()) { return false; } +inline bool ClientImpl::handle_request(Stream &strm, const Request &req, + Response &res, bool close_connection) { + if (req.path.empty()) { + error_ = Error::Connection; + return false; + } bool ret; - if (!is_ssl() && !proxy_host_.empty()) { + if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) { auto req2 = req; req2.path = "http://" + host_and_port_ + req.path; - ret = process_request(strm, req2, res, last_connection, connection_close); + ret = process_request(strm, req2, res, close_connection); } else { - ret = process_request(strm, req, res, last_connection, connection_close); + ret = process_request(strm, req, res, close_connection); } if (!ret) { return false; } @@ -3656,7 +4914,8 @@ inline bool Client::handle_request(Stream &strm, const Request &req, } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - if (res.status == 401 || res.status == 407) { + if ((res.status == 401 || res.status == 407) && + req.authorization_count_ < 5) { auto is_proxy = res.status == 407; const auto &username = is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; @@ -3665,12 +4924,14 @@ inline bool Client::handle_request(Stream &strm, const Request &req, if (!username.empty() && !password.empty()) { std::map auth; - if (parse_www_authenticate(res, auth, is_proxy)) { + if (detail::parse_www_authenticate(res, auth, is_proxy)) { Request new_req = req; - auto key = is_proxy ? "Proxy-Authorization" : "WWW-Authorization"; + new_req.authorization_count_ += 1; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; new_req.headers.erase(key); - new_req.headers.insert(make_digest_authentication_header( - req, auth, 1, random_string(10), username, password, is_proxy)); + new_req.headers.insert(detail::make_digest_authentication_header( + req, auth, new_req.authorization_count_, detail::random_string(10), + username, password, is_proxy)); Response new_res; @@ -3684,102 +4945,64 @@ inline bool Client::handle_request(Stream &strm, const Request &req, return ret; } -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline bool Client::connect(socket_t sock, Response &res, bool &error) { - error = true; - Response res2; - - if (!detail::process_socket( - true, sock, 1, read_timeout_sec_, read_timeout_usec_, - [&](Stream &strm, bool /*last_connection*/, bool &connection_close) { - Request req2; - req2.method = "CONNECT"; - req2.path = host_and_port_; - return process_request(strm, req2, res2, false, connection_close); - })) { - detail::close_socket(sock); - error = false; +inline bool ClientImpl::redirect(const Request &req, Response &res) { + if (req.redirect_count == 0) { + error_ = Error::ExceedRedirectCount; return false; } - if (res2.status == 407) { - if (!proxy_digest_auth_username_.empty() && - !proxy_digest_auth_password_.empty()) { - std::map auth; - if (parse_www_authenticate(res2, auth, true)) { - Response res3; - if (!detail::process_socket( - true, sock, 1, read_timeout_sec_, read_timeout_usec_, - [&](Stream &strm, bool /*last_connection*/, - bool &connection_close) { - Request req3; - req3.method = "CONNECT"; - req3.path = host_and_port_; - req3.headers.insert(make_digest_authentication_header( - req3, auth, 1, random_string(10), - proxy_digest_auth_username_, proxy_digest_auth_password_, - true)); - return process_request(strm, req3, res3, false, - connection_close); - })) { - detail::close_socket(sock); - error = false; - return false; - } - } - } else { - res = res2; - return false; - } - } - - return true; -} -#endif - -inline bool Client::redirect(const Request &req, Response &res) { - if (req.redirect_count == 0) { return false; } - - auto location = res.get_header_value("location"); + auto location = detail::decode_url(res.get_header_value("location"), true); if (location.empty()) { return false; } const static std::regex re( - R"(^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*(?:\?[^#]*)?)(?:#.*)?)"); + R"(^(?:(https?):)?(?://([^:/?#]*)(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)"); std::smatch m; - if (!regex_match(location, m, re)) { return false; } + if (!std::regex_match(location, m, re)) { return false; } auto scheme = is_ssl() ? "https" : "http"; auto next_scheme = m[1].str(); auto next_host = m[2].str(); - auto next_path = m[3].str(); - if (next_scheme.empty()) { next_scheme = scheme; } + auto port_str = m[3].str(); + auto next_path = m[4].str(); + + auto next_port = port_; + if (!port_str.empty()) { + next_port = std::stoi(port_str); + } else if (!next_scheme.empty()) { + next_port = next_scheme == "https" ? 443 : 80; + } + if (next_scheme.empty()) { next_scheme = scheme; } if (next_host.empty()) { next_host = host_; } if (next_path.empty()) { next_path = "/"; } - if (next_scheme == scheme && next_host == host_) { + if (next_scheme == scheme && next_host == host_ && next_port == port_) { return detail::redirect(*this, req, res, next_path); } else { if (next_scheme == "https") { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - SSLClient cli(next_host.c_str()); + SSLClient cli(next_host.c_str(), next_port); cli.copy_settings(*this); - return detail::redirect(cli, req, res, next_path); + auto ret = detail::redirect(cli, req, res, next_path); + if (!ret) { error_ = cli.get_last_error(); } + return ret; #else return false; #endif } else { - Client cli(next_host.c_str()); + ClientImpl cli(next_host.c_str(), next_port); cli.copy_settings(*this); - return detail::redirect(cli, req, res, next_path); + auto ret = detail::redirect(cli, req, res, next_path); + if (!ret) { error_ = cli.get_last_error(); } + return ret; } } } -inline bool Client::write_request(Stream &strm, const Request &req, - bool last_connection) { +inline bool ClientImpl::write_request(Stream &strm, const Request &req, + bool close_connection) { detail::BufferStream bstrm; // Request line @@ -3789,7 +5012,7 @@ inline bool Client::write_request(Stream &strm, const Request &req, // Additonal headers Headers headers; - if (last_connection) { headers.emplace("Connection", "close"); } + if (close_connection) { headers.emplace("Connection", "close"); } if (!req.has_header("Host")) { if (is_ssl()) { @@ -3810,7 +5033,7 @@ inline bool Client::write_request(Stream &strm, const Request &req, if (!req.has_header("Accept")) { headers.emplace("Accept", "*/*"); } if (!req.has_header("User-Agent")) { - headers.emplace("User-Agent", "cpp-httplib/0.5"); + headers.emplace("User-Agent", "cpp-httplib/0.7"); } if (req.body.empty()) { @@ -3818,7 +5041,10 @@ inline bool Client::write_request(Stream &strm, const Request &req, auto length = std::to_string(req.content_length); headers.emplace("Content-Length", length); } else { - headers.emplace("Content-Length", "0"); + if (req.method == "POST" || req.method == "PUT" || + req.method == "PATCH") { + headers.emplace("Content-Length", "0"); + } } } else { if (!req.has_header("Content-Type")) { @@ -3831,7 +5057,7 @@ inline bool Client::write_request(Stream &strm, const Request &req, } } - if (!basic_auth_username_.empty() && !basic_auth_password_.empty()) { + if (!basic_auth_password_.empty()) { headers.insert(make_basic_authentication_header( basic_auth_username_, basic_auth_password_, false)); } @@ -3842,11 +5068,24 @@ inline bool Client::write_request(Stream &strm, const Request &req, proxy_basic_auth_username_, proxy_basic_auth_password_, true)); } + if (!bearer_token_auth_token_.empty()) { + headers.insert(make_bearer_token_authentication_header( + bearer_token_auth_token_, false)); + } + + if (!proxy_bearer_token_auth_token_.empty()) { + headers.insert(make_bearer_token_authentication_header( + proxy_bearer_token_auth_token_, true)); + } + detail::write_headers(bstrm, req, headers); // Flush buffer auto &data = bstrm.get_buffer(); - strm.write(data.data(), data.size()); + if (!detail::write_data(strm, data.data(), data.size())) { + error_ = Error::Write; + return false; + } // Body if (req.body.empty()) { @@ -3854,282 +5093,370 @@ inline bool Client::write_request(Stream &strm, const Request &req, size_t offset = 0; size_t end_offset = req.content_length; + bool ok = true; + DataSink data_sink; data_sink.write = [&](const char *d, size_t l) { - auto written_length = strm.write(d, l); - offset += written_length; + if (ok) { + if (detail::write_data(strm, d, l)) { + offset += l; + } else { + ok = false; + } + } }; - data_sink.is_writable = [&](void) { return strm.is_writable(); }; + data_sink.is_writable = [&](void) { return ok && strm.is_writable(); }; while (offset < end_offset) { - req.content_provider(offset, end_offset - offset, data_sink); + if (!req.content_provider(offset, end_offset - offset, data_sink)) { + error_ = Error::Canceled; + return false; + } + if (!ok) { + error_ = Error::Write; + return false; + } } } } else { - strm.write(req.body); + return detail::write_data(strm, req.body.data(), req.body.size()); } return true; } -inline std::shared_ptr Client::send_with_content_provider( +inline std::unique_ptr ClientImpl::send_with_content_provider( const char *method, const char *path, const Headers &headers, const std::string &body, size_t content_length, ContentProvider content_provider, const char *content_type) { + Request req; req.method = method; - req.headers = headers; + req.headers = default_headers_; + req.headers.insert(headers.begin(), headers.end()); req.path = path; - req.headers.emplace("Content-Type", content_type); + if (content_type) { req.headers.emplace("Content-Type", content_type); } #ifdef CPPHTTPLIB_ZLIB_SUPPORT if (compress_) { + detail::gzip_compressor compressor; + if (content_provider) { + auto ok = true; size_t offset = 0; DataSink data_sink; data_sink.write = [&](const char *data, size_t data_len) { - req.body.append(data, data_len); - offset += data_len; - }; - data_sink.is_writable = [&](void) { return true; }; + if (ok) { + auto last = offset + data_len == content_length; - while (offset < content_length) { - content_provider(offset, content_length - offset, data_sink); + auto ret = compressor.compress( + data, data_len, last, [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + }); + + if (ret) { + offset += data_len; + } else { + ok = false; + } + } + }; + data_sink.is_writable = [&](void) { return ok && true; }; + + while (ok && offset < content_length) { + if (!content_provider(offset, content_length - offset, data_sink)) { + error_ = Error::Canceled; + return nullptr; + } } } else { - req.body = body; + if (!compressor.compress(body.data(), body.size(), true, + [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + })) { + return nullptr; + } } - if (!detail::compress(req.body)) { return nullptr; } req.headers.emplace("Content-Encoding", "gzip"); } else #endif { if (content_provider) { req.content_length = content_length; - req.content_provider = content_provider; + req.content_provider = std::move(content_provider); } else { req.body = body; } } - auto res = std::make_shared(); + auto res = detail::make_unique(); - return send(req, *res) ? res : nullptr; + return send(req, *res) ? std::move(res) : nullptr; } -inline bool Client::process_request(Stream &strm, const Request &req, - Response &res, bool last_connection, - bool &connection_close) { +inline bool ClientImpl::process_request(Stream &strm, const Request &req, + Response &res, bool close_connection) { // Send request - if (!write_request(strm, req, last_connection)) { return false; } + if (!write_request(strm, req, close_connection)) { return false; } // Receive response and headers if (!read_response_line(strm, res) || !detail::read_headers(strm, res.headers)) { + error_ = Error::Read; return false; } - if (res.get_header_value("Connection") == "close" || - res.version == "HTTP/1.0") { - connection_close = true; - } - if (req.response_handler) { - if (!req.response_handler(res)) { return false; } + if (!req.response_handler(res)) { + error_ = Error::Canceled; + return false; + } } // Body if (req.method != "HEAD" && req.method != "CONNECT") { - ContentReceiver out = [&](const char *buf, size_t n) { - if (res.body.size() + n > res.body.max_size()) { return false; } - res.body.append(buf, n); - return true; + auto out = + req.content_receiver + ? static_cast( + [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { error_ = Error::Canceled; } + return ret; + }) + : static_cast( + [&](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { + if (res.body.size() + n > res.body.max_size()) { + return false; + } + res.body.append(buf, n); + return true; + }); + + auto progress = [&](uint64_t current, uint64_t total) { + if (!req.progress) { return true; } + auto ret = req.progress(current, total); + if (!ret) { error_ = Error::Canceled; } + return ret; }; - if (req.content_receiver) { - out = [&](const char *buf, size_t n) { - return req.content_receiver(buf, n); - }; - } - int dummy_status; - if (!detail::read_content(strm, res, std::numeric_limits::max(), - dummy_status, req.progress, out)) { + if (!detail::read_content(strm, res, (std::numeric_limits::max)(), + dummy_status, std::move(progress), std::move(out), + decompress_)) { + if (error_ != Error::Canceled) { error_ = Error::Read; } return false; } } + if (res.get_header_value("Connection") == "close" || + (res.version == "HTTP/1.0" && res.reason != "Connection established")) { + // TODO this requires a not-entirely-obvious chain of calls to be correct + // for this to be safe. Maybe a code refactor (such as moving this out to + // the send function and getting rid of the recursiveness of the mutex) + // could make this more obvious. + + // This is safe to call because process_request is only called by handle_request + // which is only called by send, which locks the request mutex during the process. + // It would be a bug to call it from a different thread since it's a thread-safety + // issue to do these things to the socket if another thread is using the socket. + lock_socket_and_shutdown_and_close(); + } + // Log if (logger_) { logger_(req, res); } return true; } -inline bool Client::process_and_close_socket( - socket_t sock, size_t request_count, - std::function - callback) { - request_count = std::min(request_count, keep_alive_max_count_); - return detail::process_and_close_socket(true, sock, request_count, - read_timeout_sec_, read_timeout_usec_, - callback); +inline bool +ClientImpl::process_socket(const Socket &socket, + std::function callback) { + return detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, std::move(callback)); } -inline bool Client::is_ssl() const { return false; } +inline bool ClientImpl::is_ssl() const { return false; } -inline std::shared_ptr Client::Get(const char *path) { +inline Result ClientImpl::Get(const char *path) { return Get(path, Headers(), Progress()); } -inline std::shared_ptr Client::Get(const char *path, - Progress progress) { +inline Result ClientImpl::Get(const char *path, Progress progress) { return Get(path, Headers(), std::move(progress)); } -inline std::shared_ptr Client::Get(const char *path, - const Headers &headers) { +inline Result ClientImpl::Get(const char *path, const Headers &headers) { return Get(path, headers, Progress()); } -inline std::shared_ptr -Client::Get(const char *path, const Headers &headers, Progress progress) { +inline Result ClientImpl::Get(const char *path, const Headers &headers, + Progress progress) { Request req; req.method = "GET"; req.path = path; - req.headers = headers; + req.headers = default_headers_; + req.headers.insert(headers.begin(), headers.end()); req.progress = std::move(progress); - auto res = std::make_shared(); - return send(req, *res) ? res : nullptr; + auto res = detail::make_unique(); + auto ret = send(req, *res); + return Result{ret ? std::move(res) : nullptr, get_last_error()}; } -inline std::shared_ptr Client::Get(const char *path, - ContentReceiver content_receiver) { - return Get(path, Headers(), nullptr, std::move(content_receiver), Progress()); +inline Result ClientImpl::Get(const char *path, + ContentReceiver content_receiver) { + return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); } -inline std::shared_ptr Client::Get(const char *path, - ContentReceiver content_receiver, - Progress progress) { +inline Result ClientImpl::Get(const char *path, + ContentReceiver content_receiver, + Progress progress) { return Get(path, Headers(), nullptr, std::move(content_receiver), std::move(progress)); } -inline std::shared_ptr Client::Get(const char *path, - const Headers &headers, - ContentReceiver content_receiver) { - return Get(path, headers, nullptr, std::move(content_receiver), Progress()); +inline Result ClientImpl::Get(const char *path, const Headers &headers, + ContentReceiver content_receiver) { + return Get(path, headers, nullptr, std::move(content_receiver), nullptr); } -inline std::shared_ptr Client::Get(const char *path, - const Headers &headers, - ContentReceiver content_receiver, - Progress progress) { +inline Result ClientImpl::Get(const char *path, const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { return Get(path, headers, nullptr, std::move(content_receiver), std::move(progress)); } -inline std::shared_ptr Client::Get(const char *path, - const Headers &headers, - ResponseHandler response_handler, - ContentReceiver content_receiver) { - return Get(path, headers, std::move(response_handler), content_receiver, - Progress()); +inline Result ClientImpl::Get(const char *path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), nullptr); } -inline std::shared_ptr Client::Get(const char *path, - const Headers &headers, - ResponseHandler response_handler, - ContentReceiver content_receiver, - Progress progress) { +inline Result ClientImpl::Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const char *path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { Request req; req.method = "GET"; req.path = path; - req.headers = headers; + req.headers = default_headers_; + req.headers.insert(headers.begin(), headers.end()); req.response_handler = std::move(response_handler); - req.content_receiver = std::move(content_receiver); + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + uint64_t /*offset*/, uint64_t /*total_length*/) { + return content_receiver(data, data_length); + }; req.progress = std::move(progress); - auto res = std::make_shared(); - return send(req, *res) ? res : nullptr; + auto res = detail::make_unique(); + auto ret = send(req, *res); + return Result{ret ? std::move(res) : nullptr, get_last_error()}; } -inline std::shared_ptr Client::Head(const char *path) { +inline Result ClientImpl::Head(const char *path) { return Head(path, Headers()); } -inline std::shared_ptr Client::Head(const char *path, - const Headers &headers) { +inline Result ClientImpl::Head(const char *path, const Headers &headers) { Request req; req.method = "HEAD"; - req.headers = headers; + req.headers = default_headers_; + req.headers.insert(headers.begin(), headers.end()); req.path = path; - auto res = std::make_shared(); - - return send(req, *res) ? res : nullptr; + auto res = detail::make_unique(); + auto ret = send(req, *res); + return Result{ret ? std::move(res) : nullptr, get_last_error()}; } -inline std::shared_ptr Client::Post(const char *path, - const std::string &body, - const char *content_type) { +inline Result ClientImpl::Post(const char *path) { + return Post(path, std::string(), nullptr); +} + +inline Result ClientImpl::Post(const char *path, const std::string &body, + const char *content_type) { return Post(path, Headers(), body, content_type); } -inline std::shared_ptr Client::Post(const char *path, - const Headers &headers, - const std::string &body, - const char *content_type) { - return send_with_content_provider("POST", path, headers, body, 0, nullptr, - content_type); +inline Result ClientImpl::Post(const char *path, const Headers &headers, + const std::string &body, + const char *content_type) { + auto ret = send_with_content_provider("POST", path, headers, body, 0, nullptr, + content_type); + return Result{std::move(ret), get_last_error()}; } -inline std::shared_ptr Client::Post(const char *path, - const Params ¶ms) { +inline Result ClientImpl::Post(const char *path, const Params ¶ms) { return Post(path, Headers(), params); } -inline std::shared_ptr Client::Post(const char *path, - size_t content_length, - ContentProvider content_provider, - const char *content_type) { - return Post(path, Headers(), content_length, content_provider, content_type); +inline Result ClientImpl::Post(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return Post(path, Headers(), content_length, std::move(content_provider), + content_type); } -inline std::shared_ptr -Client::Post(const char *path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const char *content_type) { - return send_with_content_provider("POST", path, headers, std::string(), - content_length, content_provider, - content_type); +inline Result ClientImpl::Post(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + auto ret = send_with_content_provider( + "POST", path, headers, std::string(), content_length, + std::move(content_provider), content_type); + return Result{std::move(ret), get_last_error()}; } -inline std::shared_ptr -Client::Post(const char *path, const Headers &headers, const Params ¶ms) { - std::string query; - for (auto it = params.begin(); it != params.end(); ++it) { - if (it != params.begin()) { query += "&"; } - query += it->first; - query += "="; - query += detail::encode_url(it->second); - } - +inline Result ClientImpl::Post(const char *path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); return Post(path, headers, query, "application/x-www-form-urlencoded"); } -inline std::shared_ptr -Client::Post(const char *path, const MultipartFormDataItems &items) { +inline Result ClientImpl::Post(const char *path, + const MultipartFormDataItems &items) { return Post(path, Headers(), items); } -inline std::shared_ptr -Client::Post(const char *path, const Headers &headers, - const MultipartFormDataItems &items) { - auto boundary = detail::make_multipart_data_boundary(); +inline Result ClientImpl::Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items) { + return Post(path, headers, items, detail::make_multipart_data_boundary()); +} +inline Result ClientImpl::Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + for (size_t i = 0; i < boundary.size(); i++) { + char c = boundary[i]; + if (!std::isalnum(c) && c != '-' && c != '_') { + error_ = Error::UnsupportedMultipartBoundaryChars; + return Result{nullptr, error_}; + } + } std::string body; @@ -4153,182 +5480,240 @@ Client::Post(const char *path, const Headers &headers, return Post(path, headers, body, content_type.c_str()); } -inline std::shared_ptr Client::Put(const char *path, - const std::string &body, - const char *content_type) { +inline Result ClientImpl::Put(const char *path) { + return Put(path, std::string(), nullptr); +} + +inline Result ClientImpl::Put(const char *path, const std::string &body, + const char *content_type) { return Put(path, Headers(), body, content_type); } -inline std::shared_ptr Client::Put(const char *path, - const Headers &headers, - const std::string &body, - const char *content_type) { - return send_with_content_provider("PUT", path, headers, body, 0, nullptr, - content_type); +inline Result ClientImpl::Put(const char *path, const Headers &headers, + const std::string &body, + const char *content_type) { + auto ret = send_with_content_provider("PUT", path, headers, body, 0, nullptr, + content_type); + return Result{std::move(ret), get_last_error()}; } -inline std::shared_ptr Client::Put(const char *path, - size_t content_length, - ContentProvider content_provider, - const char *content_type) { - return Put(path, Headers(), content_length, content_provider, content_type); +inline Result ClientImpl::Put(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type); } -inline std::shared_ptr -Client::Put(const char *path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const char *content_type) { - return send_with_content_provider("PUT", path, headers, std::string(), - content_length, content_provider, - content_type); +inline Result ClientImpl::Put(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + auto ret = send_with_content_provider( + "PUT", path, headers, std::string(), content_length, + std::move(content_provider), content_type); + return Result{std::move(ret), get_last_error()}; } -inline std::shared_ptr Client::Put(const char *path, - const Params ¶ms) { +inline Result ClientImpl::Put(const char *path, const Params ¶ms) { return Put(path, Headers(), params); } -inline std::shared_ptr -Client::Put(const char *path, const Headers &headers, const Params ¶ms) { - std::string query; - for (auto it = params.begin(); it != params.end(); ++it) { - if (it != params.begin()) { query += "&"; } - query += it->first; - query += "="; - query += detail::encode_url(it->second); - } - +inline Result ClientImpl::Put(const char *path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); return Put(path, headers, query, "application/x-www-form-urlencoded"); } -inline std::shared_ptr Client::Patch(const char *path, - const std::string &body, - const char *content_type) { +inline Result ClientImpl::Patch(const char *path, const std::string &body, + const char *content_type) { return Patch(path, Headers(), body, content_type); } -inline std::shared_ptr Client::Patch(const char *path, - const Headers &headers, - const std::string &body, - const char *content_type) { - return send_with_content_provider("PATCH", path, headers, body, 0, nullptr, - content_type); +inline Result ClientImpl::Patch(const char *path, const Headers &headers, + const std::string &body, + const char *content_type) { + auto ret = send_with_content_provider("PATCH", path, headers, body, 0, + nullptr, content_type); + return Result{std::move(ret), get_last_error()}; } -inline std::shared_ptr Client::Patch(const char *path, - size_t content_length, - ContentProvider content_provider, - const char *content_type) { - return Patch(path, Headers(), content_length, content_provider, content_type); +inline Result ClientImpl::Patch(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type); } -inline std::shared_ptr -Client::Patch(const char *path, const Headers &headers, size_t content_length, - ContentProvider content_provider, const char *content_type) { - return send_with_content_provider("PATCH", path, headers, std::string(), - content_length, content_provider, - content_type); +inline Result ClientImpl::Patch(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + auto ret = send_with_content_provider( + "PATCH", path, headers, std::string(), content_length, + std::move(content_provider), content_type); + return Result{std::move(ret), get_last_error()}; } -inline std::shared_ptr Client::Delete(const char *path) { +inline Result ClientImpl::Delete(const char *path) { return Delete(path, Headers(), std::string(), nullptr); } -inline std::shared_ptr Client::Delete(const char *path, - const std::string &body, - const char *content_type) { +inline Result ClientImpl::Delete(const char *path, const std::string &body, + const char *content_type) { return Delete(path, Headers(), body, content_type); } -inline std::shared_ptr Client::Delete(const char *path, - const Headers &headers) { +inline Result ClientImpl::Delete(const char *path, const Headers &headers) { return Delete(path, headers, std::string(), nullptr); } -inline std::shared_ptr Client::Delete(const char *path, - const Headers &headers, - const std::string &body, - const char *content_type) { +inline Result ClientImpl::Delete(const char *path, const Headers &headers, + const std::string &body, + const char *content_type) { Request req; req.method = "DELETE"; - req.headers = headers; + req.headers = default_headers_; + req.headers.insert(headers.begin(), headers.end()); req.path = path; if (content_type) { req.headers.emplace("Content-Type", content_type); } req.body = body; - auto res = std::make_shared(); - - return send(req, *res) ? res : nullptr; + auto res = detail::make_unique(); + auto ret = send(req, *res); + return Result{ret ? std::move(res) : nullptr, get_last_error()}; } -inline std::shared_ptr Client::Options(const char *path) { +inline Result ClientImpl::Options(const char *path) { return Options(path, Headers()); } -inline std::shared_ptr Client::Options(const char *path, - const Headers &headers) { +inline Result ClientImpl::Options(const char *path, const Headers &headers) { Request req; req.method = "OPTIONS"; + req.headers = default_headers_; + req.headers.insert(headers.begin(), headers.end()); req.path = path; - req.headers = headers; - auto res = std::make_shared(); - - return send(req, *res) ? res : nullptr; + auto res = detail::make_unique(); + auto ret = send(req, *res); + return Result{ret ? std::move(res) : nullptr, get_last_error()}; } -inline void Client::set_timeout_sec(time_t timeout_sec) { - timeout_sec_ = timeout_sec; +inline size_t ClientImpl::is_socket_open() const { + std::lock_guard guard(socket_mutex_); + return socket_.is_open(); } -inline void Client::set_read_timeout(time_t sec, time_t usec) { +inline void ClientImpl::stop() { + std::lock_guard guard(socket_mutex_); + // There is no guarantee that this doesn't get overwritten later, but set it so that + // there is a good chance that any threads stopping as a result pick up this error. + error_ = Error::Canceled; + + // If there is anything ongoing right now, the ONLY thread-safe thing we can do + // is to shutdown_socket, so that threads using this socket suddenly discover + // they can't read/write any more and error out. + // Everything else (closing the socket, shutting ssl down) is unsafe because these + // actions are not thread-safe. + if (socket_requests_in_flight_ > 0) { + shutdown_socket(socket_); + // Aside from that, we set a flag for the socket to be closed when we're done. + socket_should_be_closed_when_request_is_done_ = true; + return; + } + + //Otherwise, sitll holding the mutex, we can shut everything down ourselves + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) { + connection_timeout_sec_ = sec; + connection_timeout_usec_ = usec; +} + +inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) { read_timeout_sec_ = sec; read_timeout_usec_ = usec; } -inline void Client::set_keep_alive_max_count(size_t count) { - keep_alive_max_count_ = count; +inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; } -inline void Client::set_basic_auth(const char *username, const char *password) { +inline void ClientImpl::set_basic_auth(const char *username, + const char *password) { basic_auth_username_ = username; basic_auth_password_ = password; } +inline void ClientImpl::set_bearer_token_auth(const char *token) { + bearer_token_auth_token_ = token; +} + #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void Client::set_digest_auth(const char *username, - const char *password) { +inline void ClientImpl::set_digest_auth(const char *username, + const char *password) { digest_auth_username_ = username; digest_auth_password_ = password; } #endif -inline void Client::set_follow_location(bool on) { follow_location_ = on; } +inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } -inline void Client::set_compress(bool on) { compress_ = on; } +inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } -inline void Client::set_interface(const char *intf) { interface_ = intf; } +inline void ClientImpl::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); +} -inline void Client::set_proxy(const char *host, int port) { +inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } + +inline void ClientImpl::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); +} + +inline void ClientImpl::set_compress(bool on) { compress_ = on; } + +inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } + +inline void ClientImpl::set_interface(const char *intf) { interface_ = intf; } + +inline void ClientImpl::set_proxy(const char *host, int port) { proxy_host_ = host; proxy_port_ = port; } -inline void Client::set_proxy_basic_auth(const char *username, - const char *password) { +inline void ClientImpl::set_proxy_basic_auth(const char *username, + const char *password) { proxy_basic_auth_username_ = username; proxy_basic_auth_password_ = password; } +inline void ClientImpl::set_proxy_bearer_token_auth(const char *token) { + proxy_bearer_token_auth_token_ = token; +} + #ifdef CPPHTTPLIB_OPENSSL_SUPPORT -inline void Client::set_proxy_digest_auth(const char *username, - const char *password) { +inline void ClientImpl::set_proxy_digest_auth(const char *username, + const char *password) { proxy_digest_auth_username_ = username; proxy_digest_auth_password_ = password; } #endif -inline void Client::set_logger(Logger logger) { logger_ = std::move(logger); } +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::enable_server_certificate_verification(bool enabled) { + server_certificate_verification_ = enabled; +} +#endif + +inline void ClientImpl::set_logger(Logger logger) { + logger_ = std::move(logger); +} /* * SSL Implementation @@ -4336,72 +5721,69 @@ inline void Client::set_logger(Logger logger) { logger_ = std::move(logger); } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT namespace detail { -template -inline bool process_and_close_socket_ssl( - bool is_client_request, socket_t sock, size_t keep_alive_max_count, - time_t read_timeout_sec, time_t read_timeout_usec, SSL_CTX *ctx, - std::mutex &ctx_mutex, U SSL_connect_or_accept, V setup, T callback) { - assert(keep_alive_max_count > 0); - +template +inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, + U SSL_connect_or_accept, V setup) { SSL *ssl = nullptr; { std::lock_guard guard(ctx_mutex); ssl = SSL_new(ctx); } - if (!ssl) { - close_socket(sock); - return false; - } + if (ssl) { + auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); + SSL_set_bio(ssl, bio, bio); - auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); - SSL_set_bio(ssl, bio, bio); - - if (!setup(ssl)) { - SSL_shutdown(ssl); - { - std::lock_guard guard(ctx_mutex); - SSL_free(ssl); - } - - close_socket(sock); - return false; - } - - auto ret = false; - - if (SSL_connect_or_accept(ssl) == 1) { - if (keep_alive_max_count > 1) { - auto count = keep_alive_max_count; - while (count > 0 && - (is_client_request || - detail::select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND, - CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) { - SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec); - auto last_connection = count == 1; - auto connection_close = false; - - ret = callback(ssl, strm, last_connection, connection_close); - if (!ret || connection_close) { break; } - - count--; + if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { + SSL_shutdown(ssl); + { + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); } - } else { - SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec); - auto dummy_connection_close = false; - ret = callback(ssl, strm, true, dummy_connection_close); + return nullptr; } } - SSL_shutdown(ssl); - { - std::lock_guard guard(ctx_mutex); - SSL_free(ssl); + return ssl; +} + +inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, + bool shutdown_gracefully) { + // sometimes we may want to skip this to try to avoid SIGPIPE if we know + // the remote has closed the network connection + // Note that it is not always possible to avoid SIGPIPE, this is merely a best-efforts. + if (shutdown_gracefully) { + SSL_shutdown(ssl); } - close_socket(sock); + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); +} - return ret; +template +inline bool +process_server_socket_ssl(SSL *ssl, socket_t sock, size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, + time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + T callback) { + return process_server_socket_core( + sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +template +inline bool +process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); } #if OPENSSL_VERSION_NUMBER < 0x10100000L @@ -4420,11 +5802,11 @@ public: private: static void locking_callback(int mode, int type, const char * /*file*/, int /*line*/) { - auto &locks = *openSSL_locks_; + auto &lk = (*openSSL_locks_)[static_cast(type)]; if (mode & CRYPTO_LOCK) { - locks[type].lock(); + lk.lock(); } else { - locks[type].unlock(); + lk.unlock(); } } }; @@ -4458,9 +5840,15 @@ private: // SSL socket stream implementation inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, - time_t read_timeout_usec) + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), - read_timeout_usec_(read_timeout_usec) {} + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec) { + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); +} inline SSLSocketStream::~SSLSocketStream() {} @@ -4469,24 +5857,44 @@ inline bool SSLSocketStream::is_readable() const { } inline bool SSLSocketStream::is_writable() const { - return detail::select_write(sock_, 0, 0) > 0; + return detail::select_write(sock_, write_timeout_sec_, write_timeout_usec_) > + 0; } -inline int SSLSocketStream::read(char *ptr, size_t size) { - if (SSL_pending(ssl_) > 0 || - select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0) { +inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { + if (SSL_pending(ssl_) > 0) { return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + auto ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + while (err == SSL_ERROR_WANT_READ) { + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret >= 0) { + return ret; + } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; } return -1; } -inline int SSLSocketStream::write(const char *ptr, size_t size) { +inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { if (is_writable()) { return SSL_write(ssl_, ptr, static_cast(size)); } return -1; } -inline std::string SSLSocketStream::get_remote_addr() const { - return detail::get_remote_addr(sock_); +inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + detail::get_remote_ip_and_port(sock_, ip, port); } static SSLInit sslinit_; @@ -4532,6 +5940,33 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, } } +inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + ctx_ = SSL_CTX_new(SSLv23_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_store) { + + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + + SSL_CTX_set_verify( + ctx_, + SSL_VERIFY_PEER | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, // SSL_VERIFY_CLIENT_ONCE, + nullptr); + } + } +} + inline SSLServer::~SSLServer() { if (ctx_) { SSL_CTX_free(ctx_); } } @@ -4539,21 +5974,42 @@ inline SSLServer::~SSLServer() { inline bool SSLServer::is_valid() const { return ctx_; } inline bool SSLServer::process_and_close_socket(socket_t sock) { - return detail::process_and_close_socket_ssl( - false, sock, keep_alive_max_count_, read_timeout_sec_, read_timeout_usec_, - ctx_, ctx_mutex_, SSL_accept, [](SSL * /*ssl*/) { return true; }, - [this](SSL *ssl, Stream &strm, bool last_connection, - bool &connection_close) { - return process_request(strm, last_connection, connection_close, - [&](Request &req) { req.ssl = ssl; }); - }); + auto ssl = detail::ssl_new(sock, ctx_, ctx_mutex_, SSL_accept, + [](SSL * /*ssl*/) { return true; }); + + if (ssl) { + auto ret = detail::process_server_socket_ssl( + ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this, ssl](Stream &strm, bool close_connection, + bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + [&](Request &req) { req.ssl = ssl; }); + }); + + detail::ssl_delete(ctx_mutex_, ssl, ret); + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; + } + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return false; } // SSL HTTP client implementation +inline SSLClient::SSLClient(const std::string &host) + : SSLClient(host, 443, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port) + : SSLClient(host, port, std::string(), std::string()) {} + inline SSLClient::SSLClient(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path) - : Client(host, port, client_cert_path, client_key_path) { + : ClientImpl(host, port, client_cert_path, client_key_path) { ctx_ = SSL_CTX_new(SSLv23_client_method()); detail::split(&host_[0], &host_[host_.size()], '.', @@ -4571,8 +6027,30 @@ inline SSLClient::SSLClient(const std::string &host, int port, } } +inline SSLClient::SSLClient(const std::string &host, int port, + X509 *client_cert, EVP_PKEY *client_key) + : ClientImpl(host, port) { + ctx_ = SSL_CTX_new(SSLv23_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(std::string(b, e)); + }); + if (client_cert != nullptr && client_key != nullptr) { + if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + inline SSLClient::~SSLClient() { if (ctx_) { SSL_CTX_free(ctx_); } + // Make sure to shut down SSL since shutdown_ssl will resolve to the + // base function rather than the derived function once we get to the + // base class destructor, and won't free the SSL (causing a leak). + SSLClient::shutdown_ssl(socket_, true); } inline bool SSLClient::is_valid() const { return ctx_; } @@ -4583,67 +6061,188 @@ inline void SSLClient::set_ca_cert_path(const char *ca_cert_file_path, if (ca_cert_dir_path) { ca_cert_dir_path_ = ca_cert_dir_path; } } -inline void SSLClient::enable_server_certificate_verification(bool enabled) { - server_certificate_verification_ = enabled; +inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store) { + if (ctx_) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { + // Free memory allocated for old cert and use new store `ca_cert_store` + SSL_CTX_set_cert_store(ctx_, ca_cert_store); + } + } else { + X509_STORE_free(ca_cert_store); + } + } } inline long SSLClient::get_openssl_verify_result() const { return verify_result_; } -inline SSL_CTX *SSLClient::ssl_context() const noexcept { return ctx_; } +inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } -inline bool SSLClient::process_and_close_socket( - socket_t sock, size_t request_count, - std::function - callback) { +inline bool SSLClient::create_and_connect_socket(Socket &socket) { + return is_valid() && ClientImpl::create_and_connect_socket(socket); +} - request_count = std::min(request_count, keep_alive_max_count_); +// Assumes that socket_mutex_ is locked and that there are no requests in flight +inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, + bool &success) { + success = true; + Response res2; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req2; + req2.method = "CONNECT"; + req2.path = host_and_port_; + return process_request(strm, req2, res2, false); + })) { + // Thread-safe to close everything because we are assuming there are no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } - return is_valid() && - detail::process_and_close_socket_ssl( - true, sock, request_count, read_timeout_sec_, read_timeout_usec_, - ctx_, ctx_mutex_, - [&](SSL *ssl) { - if (ca_cert_file_path_.empty()) { - SSL_CTX_set_verify(ctx_, SSL_VERIFY_NONE, nullptr); - } else { - if (!SSL_CTX_load_verify_locations( - ctx_, ca_cert_file_path_.c_str(), nullptr)) { - return false; - } - SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr); - } + if (res2.status == 407) { + if (!proxy_digest_auth_username_.empty() && + !proxy_digest_auth_password_.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res2, auth, true)) { + Response res3; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req3; + req3.method = "CONNECT"; + req3.path = host_and_port_; + req3.headers.insert(detail::make_digest_authentication_header( + req3, auth, 1, detail::random_string(10), + proxy_digest_auth_username_, proxy_digest_auth_password_, + true)); + return process_request(strm, req3, res3, false); + })) { + // Thread-safe to close everything because we are assuming there are no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + } + } else { + res = res2; + return false; + } + } - if (SSL_connect(ssl) != 1) { return false; } + return true; +} - if (server_certificate_verification_) { - verify_result_ = SSL_get_verify_result(ssl); +inline bool SSLClient::load_certs() { + bool ret = true; - if (verify_result_ != X509_V_OK) { return false; } + std::call_once(initialize_cert_, [&]() { + std::lock_guard guard(ctx_mutex_); + if (!ca_cert_file_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), + nullptr)) { + ret = false; + } + } else if (!ca_cert_dir_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, nullptr, + ca_cert_dir_path_.c_str())) { + ret = false; + } + } else { +#ifdef _WIN32 + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#else + SSL_CTX_set_default_verify_paths(ctx_); +#endif + } + }); - auto server_cert = SSL_get_peer_certificate(ssl); + return ret; +} - if (server_cert == nullptr) { return false; } +inline bool SSLClient::initialize_ssl(Socket &socket) { + auto ssl = detail::ssl_new( + socket.sock, ctx_, ctx_mutex_, + [&](SSL *ssl) { + if (server_certificate_verification_) { + if (!load_certs()) { + error_ = Error::SSLLoadingCerts; + return false; + } + SSL_set_verify(ssl, SSL_VERIFY_NONE, nullptr); + } - if (!verify_host(server_cert)) { - X509_free(server_cert); - return false; - } - X509_free(server_cert); - } + if (SSL_connect(ssl) != 1) { + error_ = Error::SSLConnection; + return false; + } - return true; - }, - [&](SSL *ssl) { - SSL_set_tlsext_host_name(ssl, host_.c_str()); - return true; - }, - [&](SSL * /*ssl*/, Stream &strm, bool last_connection, - bool &connection_close) { - return callback(strm, last_connection, connection_close); - }); + if (server_certificate_verification_) { + verify_result_ = SSL_get_verify_result(ssl); + + if (verify_result_ != X509_V_OK) { + error_ = Error::SSLServerVerification; + return false; + } + + auto server_cert = SSL_get_peer_certificate(ssl); + + if (server_cert == nullptr) { + error_ = Error::SSLServerVerification; + return false; + } + + if (!verify_host(server_cert)) { + X509_free(server_cert); + error_ = Error::SSLServerVerification; + return false; + } + X509_free(server_cert); + } + + return true; + }, + [&](SSL *ssl) { + SSL_set_tlsext_host_name(ssl, host_.c_str()); + return true; + }); + + if (ssl) { + socket.ssl = ssl; + return true; + } + + shutdown_socket(socket); + close_socket(socket); + return false; +} + +inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { + if (socket.sock == INVALID_SOCKET) { + assert(socket.ssl == nullptr); + return; + } + if (socket.ssl) { + detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully); + socket.ssl = nullptr; + } + assert(socket.ssl == nullptr); +} + +inline bool +SSLClient::process_socket(const Socket &socket, + std::function callback) { + assert(socket.ssl); + return detail::process_client_socket_ssl( + socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, std::move(callback)); } inline bool SSLClient::is_ssl() const { return true; } @@ -4703,7 +6302,7 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { auto count = sk_GENERAL_NAME_num(alt_names); - for (auto i = 0; i < count && !dsn_matched; i++) { + for (decltype(count) i = 0; i < count && !dsn_matched; i++) { auto val = sk_GENERAL_NAME_value(alt_names, i); if (val->type == type) { auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5); @@ -4728,7 +6327,6 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { } GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names); - return ret; } @@ -4740,7 +6338,9 @@ inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, name, sizeof(name)); - if (name_len != -1) { return check_host_name(name, name_len); } + if (name_len != -1) { + return check_host_name(name, static_cast(name_len)); + } } return false; @@ -4775,6 +6375,338 @@ inline bool SSLClient::check_host_name(const char *pattern, } #endif +// Universal client implementation +inline Client::Client(const char *scheme_host_port) + : Client(scheme_host_port, std::string(), std::string()) {} + +inline Client::Client(const char *scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path) { + const static std::regex re(R"(^(?:([a-z]+)://)?([^:/?#]+)(?::(\d+))?)"); + + std::cmatch m; + if (std::regex_match(scheme_host_port, m, re)) { + auto scheme = m[1].str(); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!scheme.empty() && (scheme != "http" && scheme != "https")) { +#else + if (!scheme.empty() && scheme != "http") { +#endif + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); + return; + } + + auto is_ssl = scheme == "https"; + + auto host = m[2].str(); + + auto port_str = m[3].str(); + auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); + + if (is_ssl) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + cli_ = detail::make_unique(host.c_str(), port, + client_cert_path, client_key_path); + is_ssl_ = is_ssl; +#endif + } else { + cli_ = detail::make_unique(host.c_str(), port, + client_cert_path, client_key_path); + } + } else { + cli_ = detail::make_unique(scheme_host_port, 80, + client_cert_path, client_key_path); + } +} + +inline Client::Client(const std::string &host, int port) + : cli_(detail::make_unique(host, port)) {} + +inline Client::Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : cli_(detail::make_unique(host, port, client_cert_path, + client_key_path)) {} + +inline Client::~Client() {} + +inline bool Client::is_valid() const { + return cli_ != nullptr && cli_->is_valid(); +} + +inline Result Client::Get(const char *path) { return cli_->Get(path); } +inline Result Client::Get(const char *path, const Headers &headers) { + return cli_->Get(path, headers); +} +inline Result Client::Get(const char *path, Progress progress) { + return cli_->Get(path, std::move(progress)); +} +inline Result Client::Get(const char *path, const Headers &headers, + Progress progress) { + return cli_->Get(path, headers, std::move(progress)); +} +inline Result Client::Get(const char *path, ContentReceiver content_receiver) { + return cli_->Get(path, std::move(content_receiver)); +} +inline Result Client::Get(const char *path, const Headers &headers, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(content_receiver)); +} +inline Result Client::Get(const char *path, ContentReceiver content_receiver, + Progress progress) { + return cli_->Get(path, std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const char *path, const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(content_receiver), + std::move(progress)); +} +inline Result Client::Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const char *path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const char *path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result Client::Head(const char *path) { return cli_->Head(path); } +inline Result Client::Head(const char *path, const Headers &headers) { + return cli_->Head(path, headers); +} + +inline Result Client::Post(const char *path) { return cli_->Post(path); } +inline Result Client::Post(const char *path, const std::string &body, + const char *content_type) { + return cli_->Post(path, body, content_type); +} +inline Result Client::Post(const char *path, const Headers &headers, + const std::string &body, const char *content_type) { + return cli_->Post(path, headers, body, content_type); +} +inline Result Client::Post(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Post(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Post(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const char *path, const Params ¶ms) { + return cli_->Post(path, params); +} +inline Result Client::Post(const char *path, const Headers &headers, + const Params ¶ms) { + return cli_->Post(path, headers, params); +} +inline Result Client::Post(const char *path, + const MultipartFormDataItems &items) { + return cli_->Post(path, items); +} +inline Result Client::Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Post(path, headers, items); +} +inline Result Client::Post(const char *path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Post(path, headers, items, boundary); +} +inline Result Client::Put(const char *path) { return cli_->Put(path); } +inline Result Client::Put(const char *path, const std::string &body, + const char *content_type) { + return cli_->Put(path, body, content_type); +} +inline Result Client::Put(const char *path, const Headers &headers, + const std::string &body, const char *content_type) { + return cli_->Put(path, headers, body, content_type); +} +inline Result Client::Put(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Put(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Put(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const char *path, const Params ¶ms) { + return cli_->Put(path, params); +} +inline Result Client::Put(const char *path, const Headers &headers, + const Params ¶ms) { + return cli_->Put(path, headers, params); +} +inline Result Client::Patch(const char *path, const std::string &body, + const char *content_type) { + return cli_->Patch(path, body, content_type); +} +inline Result Client::Patch(const char *path, const Headers &headers, + const std::string &body, const char *content_type) { + return cli_->Patch(path, headers, body, content_type); +} +inline Result Client::Patch(const char *path, size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Patch(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const char *path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const char *content_type) { + return cli_->Patch(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Delete(const char *path) { return cli_->Delete(path); } +inline Result Client::Delete(const char *path, const std::string &body, + const char *content_type) { + return cli_->Delete(path, body, content_type); +} +inline Result Client::Delete(const char *path, const Headers &headers) { + return cli_->Delete(path, headers); +} +inline Result Client::Delete(const char *path, const Headers &headers, + const std::string &body, + const char *content_type) { + return cli_->Delete(path, headers, body, content_type); +} +inline Result Client::Options(const char *path) { return cli_->Options(path); } +inline Result Client::Options(const char *path, const Headers &headers) { + return cli_->Options(path, headers); +} + +inline bool Client::send(const Request &req, Response &res) { + return cli_->send(req, res); +} + +inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); } + +inline void Client::stop() { cli_->stop(); } + +inline void Client::set_default_headers(Headers headers) { + cli_->set_default_headers(std::move(headers)); +} + +inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } +inline void Client::set_socket_options(SocketOptions socket_options) { + cli_->set_socket_options(std::move(socket_options)); +} + +inline void Client::set_connection_timeout(time_t sec, time_t usec) { + cli_->set_connection_timeout(sec, usec); +} +inline void Client::set_read_timeout(time_t sec, time_t usec) { + cli_->set_read_timeout(sec, usec); +} +inline void Client::set_write_timeout(time_t sec, time_t usec) { + cli_->set_write_timeout(sec, usec); +} + +inline void Client::set_basic_auth(const char *username, const char *password) { + cli_->set_basic_auth(username, password); +} +inline void Client::set_bearer_token_auth(const char *token) { + cli_->set_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_digest_auth(const char *username, + const char *password) { + cli_->set_digest_auth(username, password); +} +#endif + +inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); } +inline void Client::set_follow_location(bool on) { + cli_->set_follow_location(on); +} + +inline void Client::set_compress(bool on) { cli_->set_compress(on); } + +inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } + +inline void Client::set_interface(const char *intf) { + cli_->set_interface(intf); +} + +inline void Client::set_proxy(const char *host, int port) { + cli_->set_proxy(host, port); +} +inline void Client::set_proxy_basic_auth(const char *username, + const char *password) { + cli_->set_proxy_basic_auth(username, password); +} +inline void Client::set_proxy_bearer_token_auth(const char *token) { + cli_->set_proxy_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_proxy_digest_auth(const char *username, + const char *password) { + cli_->set_proxy_digest_auth(username, password); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::enable_server_certificate_verification(bool enabled) { + cli_->enable_server_certificate_verification(enabled); +} +#endif + +inline void Client::set_logger(Logger logger) { cli_->set_logger(logger); } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_ca_cert_path(const char *ca_cert_file_path, + const char *ca_cert_dir_path) { + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_path(ca_cert_file_path, + ca_cert_dir_path); + } +} + +inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_store(ca_cert_store); + } +} + +inline long Client::get_openssl_verify_result() const { + if (is_ssl_) { + return static_cast(*cli_).get_openssl_verify_result(); + } + return -1; // NOTE: -1 doesn't match any of X509_V_ERR_??? +} + +inline SSL_CTX *Client::ssl_context() const { + if (is_ssl_) { return static_cast(*cli_).ssl_context(); } + return nullptr; +} +#endif + // ---------------------------------------------------------------------------- } // namespace httplib diff --git a/externals/inih/inih b/externals/inih/inih index 603729dec..1e80a47df 160000 --- a/externals/inih/inih +++ b/externals/inih/inih @@ -1 +1 @@ -Subproject commit 603729dec89aaca42d7bd08f08bc333165b7d5d1 +Subproject commit 1e80a47dffbda813604f0913e2ad68c7054c14e4 diff --git a/externals/libressl b/externals/libressl index 7d01cb01c..8289d0d07 160000 --- a/externals/libressl +++ b/externals/libressl @@ -1 +1 @@ -Subproject commit 7d01cb01cb1a926ecb4c9c98b107ef3c26f59dfb +Subproject commit 8289d0d07de6553bf4b900bf60e808ea3f7f59da diff --git a/externals/lurlparser/CMakeLists.txt b/externals/lurlparser/CMakeLists.txt deleted file mode 100644 index 45046ffd3..000000000 --- a/externals/lurlparser/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -add_library(lurlparser - LUrlParser.cpp - LUrlParser.h -) - -create_target_directory_groups(lurlparser) - -target_include_directories(lurlparser INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/externals/lurlparser/LUrlParser.cpp b/externals/lurlparser/LUrlParser.cpp deleted file mode 100644 index 9c134e330..000000000 --- a/externals/lurlparser/LUrlParser.cpp +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Lightweight URL & URI parser (RFC 1738, RFC 3986) - * https://github.com/corporateshark/LUrlParser - * - * The MIT License (MIT) - * - * Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com) - * - * 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. - */ - -#include "LUrlParser.h" - -#include -#include -#include - -// check if the scheme name is valid -static bool IsSchemeValid( const std::string& SchemeName ) -{ - for ( auto c : SchemeName ) - { - if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false; - } - - return true; -} - -bool LUrlParser::clParseURL::GetPort( int* OutPort ) const -{ - if ( !IsValid() ) { return false; } - - int Port = atoi( m_Port.c_str() ); - - if ( Port <= 0 || Port > 65535 ) { return false; } - - if ( OutPort ) { *OutPort = Port; } - - return true; -} - -// based on RFC 1738 and RFC 3986 -LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL ) -{ - LUrlParser::clParseURL Result; - - const char* CurrentString = URL.c_str(); - - /* - * : - * := [a-z\+\-\.]+ - * For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names - */ - - // try to read scheme - { - const char* LocalString = strchr( CurrentString, ':' ); - - if ( !LocalString ) - { - return clParseURL( LUrlParserError_NoUrlCharacter ); - } - - // save the scheme name - Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString ); - - if ( !IsSchemeValid( Result.m_Scheme ) ) - { - return clParseURL( LUrlParserError_InvalidSchemeName ); - } - - // scheme should be lowercase - std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower ); - - // skip ':' - CurrentString = LocalString+1; - } - - /* - * //:@:/ - * any ":", "@" and "/" must be normalized - */ - - // skip "//" - if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash ); - if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash ); - - // check if the user name and password are specified - bool bHasUserName = false; - - const char* LocalString = CurrentString; - - while ( *LocalString ) - { - if ( *LocalString == '@' ) - { - // user name and password are specified - bHasUserName = true; - break; - } - else if ( *LocalString == '/' ) - { - // end of : specification - bHasUserName = false; - break; - } - - LocalString++; - } - - // user name and password - LocalString = CurrentString; - - if ( bHasUserName ) - { - // read user name - while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++; - - Result.m_UserName = std::string( CurrentString, LocalString - CurrentString ); - - // proceed with the current pointer - CurrentString = LocalString; - - if ( *CurrentString == ':' ) - { - // skip ':' - CurrentString++; - - // read password - LocalString = CurrentString; - - while ( *LocalString && *LocalString != '@' ) LocalString++; - - Result.m_Password = std::string( CurrentString, LocalString - CurrentString ); - - CurrentString = LocalString; - } - - // skip '@' - if ( *CurrentString != '@' ) - { - return clParseURL( LUrlParserError_NoAtSign ); - } - - CurrentString++; - } - - bool bHasBracket = ( *CurrentString == '[' ); - - // go ahead, read the host name - LocalString = CurrentString; - - while ( *LocalString ) - { - if ( bHasBracket && *LocalString == ']' ) - { - // end of IPv6 address - LocalString++; - break; - } - else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) ) - { - // port number is specified - break; - } - - LocalString++; - } - - Result.m_Host = std::string( CurrentString, LocalString - CurrentString ); - - CurrentString = LocalString; - - // is port number specified? - if ( *CurrentString == ':' ) - { - CurrentString++; - - // read port number - LocalString = CurrentString; - - while ( *LocalString && *LocalString != '/' ) LocalString++; - - Result.m_Port = std::string( CurrentString, LocalString - CurrentString ); - - CurrentString = LocalString; - } - - // end of string - if ( !*CurrentString ) - { - Result.m_ErrorCode = LUrlParserError_Ok; - - return Result; - } - - // skip '/' - if ( *CurrentString != '/' ) - { - return clParseURL( LUrlParserError_NoSlash ); - } - - CurrentString++; - - // parse the path - LocalString = CurrentString; - - while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++; - - Result.m_Path = std::string( CurrentString, LocalString - CurrentString ); - - CurrentString = LocalString; - - // check for query - if ( *CurrentString == '?' ) - { - // skip '?' - CurrentString++; - - // read query - LocalString = CurrentString; - - while ( *LocalString && *LocalString != '#' ) LocalString++; - - Result.m_Query = std::string( CurrentString, LocalString - CurrentString ); - - CurrentString = LocalString; - } - - // check for fragment - if ( *CurrentString == '#' ) - { - // skip '#' - CurrentString++; - - // read fragment - LocalString = CurrentString; - - while ( *LocalString ) LocalString++; - - Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString ); - - CurrentString = LocalString; - } - - Result.m_ErrorCode = LUrlParserError_Ok; - - return Result; -} diff --git a/externals/lurlparser/LUrlParser.h b/externals/lurlparser/LUrlParser.h deleted file mode 100644 index 25d210981..000000000 --- a/externals/lurlparser/LUrlParser.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Lightweight URL & URI parser (RFC 1738, RFC 3986) - * https://github.com/corporateshark/LUrlParser - * - * The MIT License (MIT) - * - * Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com) - * - * 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. - */ - -#pragma once - -#include - -namespace LUrlParser -{ -enum LUrlParserError -{ - LUrlParserError_Ok = 0, - LUrlParserError_Uninitialized = 1, - LUrlParserError_NoUrlCharacter = 2, - LUrlParserError_InvalidSchemeName = 3, - LUrlParserError_NoDoubleSlash = 4, - LUrlParserError_NoAtSign = 5, - LUrlParserError_UnexpectedEndOfLine = 6, - LUrlParserError_NoSlash = 7, -}; - -class clParseURL -{ -public: - LUrlParserError m_ErrorCode; - std::string m_Scheme; - std::string m_Host; - std::string m_Port; - std::string m_Path; - std::string m_Query; - std::string m_Fragment; - std::string m_UserName; - std::string m_Password; - - clParseURL() - : m_ErrorCode( LUrlParserError_Uninitialized ) - {} - - /// return 'true' if the parsing was successful - bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; } - - /// helper to convert the port number to int, return 'true' if the port is valid (within the 0..65535 range) - bool GetPort( int* OutPort ) const; - - /// parse the URL - static clParseURL ParseURL( const std::string& URL ); - -private: - explicit clParseURL( LUrlParserError ErrorCode ) - : m_ErrorCode( ErrorCode ) - {} -}; - -} // namespace LUrlParser diff --git a/externals/lurlparser/README.md b/externals/lurlparser/README.md deleted file mode 100644 index be7f0135a..000000000 --- a/externals/lurlparser/README.md +++ /dev/null @@ -1,19 +0,0 @@ -From https://github.com/corporateshark/LUrlParser/commit/455d5e2d27e3946f11ad0328fee9ee2628e6a8e2 - -MIT License - -=== - -Lightweight URL & URI parser (RFC 1738, RFC 3986) - -(C) Sergey Kosarevsky, 2015 - -@corporateshark sk@linderdaum.com - -http://www.linderdaum.com - -http://blog.linderdaum.com - -============================= - -A tiny and lightweight URL & URI parser (RFC 1738, RFC 3986) written in C++. diff --git a/externals/microprofile/microprofile.h b/externals/microprofile/microprofile.h index 85d5bd5de..a06f6457d 100644 --- a/externals/microprofile/microprofile.h +++ b/externals/microprofile/microprofile.h @@ -902,8 +902,10 @@ inline uint16_t MicroProfileGetGroupIndex(MicroProfileToken t) #include #define snprintf _snprintf +#ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4244) +#endif int64_t MicroProfileTicksPerSecondCpu() { static int64_t nTicksPerSecond = 0; @@ -946,7 +948,11 @@ typedef HANDLE MicroProfileThread; DWORD _stdcall ThreadTrampoline(void* pFunc) { MicroProfileThreadFunc F = (MicroProfileThreadFunc)pFunc; - return (uint32_t)F(0); + + // The return value of F will always return a void*, however, this is for + // compatibility with pthreads. The underlying "address" of the pointer + // is always a 32-bit value, so this cast is safe to perform. + return static_cast(reinterpret_cast(F(0))); } inline void MicroProfileThreadStart(MicroProfileThread* pThread, MicroProfileThreadFunc Func) @@ -1742,10 +1748,10 @@ void MicroProfileFlip() } } } - for(uint32_t i = 0; i < MICROPROFILE_MAX_GROUPS; ++i) + for(uint32_t j = 0; j < MICROPROFILE_MAX_GROUPS; ++j) { - pLog->nGroupTicks[i] += nGroupTicks[i]; - pFrameGroup[i] += nGroupTicks[i]; + pLog->nGroupTicks[j] += nGroupTicks[j]; + pFrameGroup[j] += nGroupTicks[j]; } pLog->nStackPos = nStackPos; } @@ -3328,7 +3334,7 @@ bool MicroProfileIsLocalThread(uint32_t nThreadId) #endif #else -bool MicroProfileIsLocalThread(uint32_t nThreadId){return false;} +bool MicroProfileIsLocalThread([[maybe_unused]] uint32_t nThreadId) { return false; } void MicroProfileStopContextSwitchTrace(){} void MicroProfileStartContextSwitchTrace(){} @@ -3576,7 +3582,7 @@ int MicroProfileGetGpuTickReference(int64_t* pOutCpu, int64_t* pOutGpu) #undef S -#ifdef _WIN32 +#ifdef _MSC_VER #pragma warning(pop) #endif diff --git a/externals/unicorn b/externals/unicorn deleted file mode 160000 index 73f457353..000000000 --- a/externals/unicorn +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 73f45735354396766a4bfb26d0b96b06e5cf31b2 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 71efbb40d..61adbef28 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,7 +32,6 @@ if (MSVC) # /Zc:inline - Let codegen omit inline functions in object files # /Zc:throwingNew - Let codegen assume `operator new` (without std::nothrow) will never return null add_compile_options( - /W3 /MP /Zi /Zo @@ -43,6 +42,18 @@ if (MSVC) /Zc:externConstexpr /Zc:inline /Zc:throwingNew + + # Warnings + /W3 + /we4062 # enumerator 'identifier' in a switch of enum 'enumeration' is not handled + /we4101 # 'identifier': unreferenced local variable + /we4265 # 'class': class has virtual functions, but destructor is not virtual + /we4388 # signed/unsigned mismatch + /we4547 # 'operator' : operator before comma has no effect; expected operator with side-effect + /we4549 # 'operator1': operator before comma has no effect; did you intend 'operator2'? + /we4555 # Expression has no effect; expected expression with side-effect + /we4834 # Discarding return value of function with 'nodiscard' attribute + /we5038 # data member 'member1' will be initialized after data member 'member2' ) # /GS- - No stack buffer overflow checks @@ -56,9 +67,12 @@ else() -Werror=implicit-fallthrough -Werror=missing-declarations -Werror=reorder + -Werror=uninitialized + -Werror=unused-result -Wextra -Wmissing-declarations -Wno-attributes + -Wno-invalid-offsetof -Wno-unused-parameter ) diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index cb00ef60e..d1d177b51 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -44,6 +44,24 @@ add_library(audio_core STATIC create_target_directory_groups(audio_core) +if (NOT MSVC) + target_compile_options(audio_core PRIVATE + -Werror=conversion + -Werror=ignored-qualifiers + -Werror=implicit-fallthrough + -Werror=reorder + -Werror=sign-compare + -Werror=shadow + -Werror=unused-parameter + -Werror=unused-variable + + $<$:-Werror=unused-but-set-parameter> + $<$:-Werror=unused-but-set-variable> + + -Wno-sign-conversion + ) +endif() + target_link_libraries(audio_core PUBLIC common core) target_link_libraries(audio_core PRIVATE SoundTouch) diff --git a/src/audio_core/algorithm/filter.cpp b/src/audio_core/algorithm/filter.cpp index f65bf64f7..01b8dff6b 100644 --- a/src/audio_core/algorithm/filter.cpp +++ b/src/audio_core/algorithm/filter.cpp @@ -31,8 +31,8 @@ Filter Filter::LowPass(double cutoff, double Q) { Filter::Filter() : Filter(1.0, 0.0, 0.0, 1.0, 0.0, 0.0) {} -Filter::Filter(double a0, double a1, double a2, double b0, double b1, double b2) - : a1(a1 / a0), a2(a2 / a0), b0(b0 / a0), b1(b1 / a0), b2(b2 / a0) {} +Filter::Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_) + : a1(a1_ / a0_), a2(a2_ / a0_), b0(b0_ / a0_), b1(b1_ / a0_), b2(b2_ / a0_) {} void Filter::Process(std::vector& signal) { const std::size_t num_frames = signal.size() / 2; @@ -55,7 +55,8 @@ void Filter::Process(std::vector& signal) { /// @param total_count The total number of biquads to be cascaded. /// @param index 0-index of the biquad to calculate the Q value for. static double CascadingBiquadQ(std::size_t total_count, std::size_t index) { - const double pole = M_PI * (2 * index + 1) / (4.0 * total_count); + const auto pole = + M_PI * static_cast(2 * index + 1) / (4.0 * static_cast(total_count)); return 1.0 / (2.0 * std::cos(pole)); } @@ -68,7 +69,7 @@ CascadingFilter CascadingFilter::LowPass(double cutoff, std::size_t cascade_size } CascadingFilter::CascadingFilter() = default; -CascadingFilter::CascadingFilter(std::vector filters) : filters(std::move(filters)) {} +CascadingFilter::CascadingFilter(std::vector filters_) : filters(std::move(filters_)) {} void CascadingFilter::Process(std::vector& signal) { for (auto& filter : filters) { diff --git a/src/audio_core/algorithm/filter.h b/src/audio_core/algorithm/filter.h index 3546d149b..a291fe79b 100644 --- a/src/audio_core/algorithm/filter.h +++ b/src/audio_core/algorithm/filter.h @@ -25,7 +25,7 @@ public: /// Passthrough filter. Filter(); - Filter(double a0, double a1, double a2, double b0, double b1, double b2); + Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_); void Process(std::vector& signal); @@ -51,7 +51,7 @@ public: /// Passthrough. CascadingFilter(); - explicit CascadingFilter(std::vector filters); + explicit CascadingFilter(std::vector filters_); void Process(std::vector& signal); diff --git a/src/audio_core/algorithm/interpolate.cpp b/src/audio_core/algorithm/interpolate.cpp index 689a54508..3b4144e21 100644 --- a/src/audio_core/algorithm/interpolate.cpp +++ b/src/audio_core/algorithm/interpolate.cpp @@ -146,7 +146,7 @@ std::vector Interpolate(InterpolationState& state, std::vector input, return {}; if (ratio <= 0) { - LOG_CRITICAL(Audio, "Nonsensical interpolation ratio {}", ratio); + LOG_ERROR(Audio, "Nonsensical interpolation ratio {}", ratio); return input; } @@ -164,7 +164,8 @@ std::vector Interpolate(InterpolationState& state, std::vector input, const std::size_t num_frames{input.size() / 2}; std::vector output; - output.reserve(static_cast(input.size() / ratio + InterpolationState::taps)); + output.reserve(static_cast(static_cast(input.size()) / ratio + + InterpolationState::taps)); for (std::size_t frame{}; frame < num_frames; ++frame) { const std::size_t lut_index{(state.fraction >> 8) * InterpolationState::taps}; @@ -217,7 +218,7 @@ void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size const auto l2 = lut[lut_index + 2]; const auto l3 = lut[lut_index + 3]; - const auto s0 = static_cast(input[index]); + const auto s0 = static_cast(input[index + 0]); const auto s1 = static_cast(input[index + 1]); const auto s2 = static_cast(input[index + 2]); const auto s3 = static_cast(input[index + 3]); diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp index 8619a3f03..fe3a898ad 100644 --- a/src/audio_core/audio_out.cpp +++ b/src/audio_core/audio_out.cpp @@ -43,6 +43,10 @@ std::vector AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, return stream->GetTagsAndReleaseBuffers(max_count); } +std::vector AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream) { + return stream->GetTagsAndReleaseBuffers(); +} + void AudioOut::StartStream(StreamPtr stream) { stream->Play(); } diff --git a/src/audio_core/audio_out.h b/src/audio_core/audio_out.h index b07588287..6ce08cd0d 100644 --- a/src/audio_core/audio_out.h +++ b/src/audio_core/audio_out.h @@ -31,6 +31,9 @@ public: /// Returns a vector of recently released buffers specified by tag for the specified stream std::vector GetTagsAndReleaseBuffers(StreamPtr stream, std::size_t max_count); + /// Returns a vector of all recently released buffers specified by tag for the specified stream + std::vector GetTagsAndReleaseBuffers(StreamPtr stream); + /// Starts an audio stream for playback void StartStream(StreamPtr stream); diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp index 56dc892b1..d2ce8c814 100644 --- a/src/audio_core/audio_renderer.cpp +++ b/src/audio_core/audio_renderer.cpp @@ -2,43 +2,90 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include -#include "audio_core/algorithm/interpolate.h" + #include "audio_core/audio_out.h" #include "audio_core/audio_renderer.h" -#include "audio_core/codec.h" #include "audio_core/common.h" #include "audio_core/info_updater.h" #include "audio_core/voice_context.h" -#include "common/assert.h" #include "common/logging/log.h" -#include "core/core.h" -#include "core/hle/kernel/writable_event.h" #include "core/memory.h" #include "core/settings.h" +namespace { +[[nodiscard]] static constexpr s16 ClampToS16(s32 value) { + return static_cast(std::clamp(value, s32{std::numeric_limits::min()}, + s32{std::numeric_limits::max()})); +} + +[[nodiscard]] static constexpr s16 Mix2To1(s16 l_channel, s16 r_channel) { + // Mix 50% from left and 50% from right channel + constexpr float l_mix_amount = 50.0f / 100.0f; + constexpr float r_mix_amount = 50.0f / 100.0f; + return ClampToS16(static_cast((static_cast(l_channel) * l_mix_amount) + + (static_cast(r_channel) * r_mix_amount))); +} + +[[nodiscard]] static constexpr std::tuple Mix6To2(s16 fl_channel, s16 fr_channel, + s16 fc_channel, + [[maybe_unused]] s16 lf_channel, + s16 bl_channel, s16 br_channel) { + // Front channels are mixed 36.94%, Center channels are mixed to be 26.12% & the back channels + // are mixed to be 36.94% + + constexpr float front_mix_amount = 36.94f / 100.0f; + constexpr float center_mix_amount = 26.12f / 100.0f; + constexpr float back_mix_amount = 36.94f / 100.0f; + + // Mix 50% from left and 50% from right channel + const auto left = front_mix_amount * static_cast(fl_channel) + + center_mix_amount * static_cast(fc_channel) + + back_mix_amount * static_cast(bl_channel); + + const auto right = front_mix_amount * static_cast(fr_channel) + + center_mix_amount * static_cast(fc_channel) + + back_mix_amount * static_cast(br_channel); + + return {ClampToS16(static_cast(left)), ClampToS16(static_cast(right))}; +} + +[[nodiscard]] static constexpr std::tuple Mix6To2WithCoefficients( + s16 fl_channel, s16 fr_channel, s16 fc_channel, s16 lf_channel, s16 bl_channel, s16 br_channel, + const std::array& coeff) { + const auto left = + static_cast(fl_channel) * coeff[0] + static_cast(fc_channel) * coeff[1] + + static_cast(lf_channel) * coeff[2] + static_cast(bl_channel) * coeff[0]; + + const auto right = + static_cast(fr_channel) * coeff[0] + static_cast(fc_channel) * coeff[1] + + static_cast(lf_channel) * coeff[2] + static_cast(br_channel) * coeff[0]; + + return {ClampToS16(static_cast(left)), ClampToS16(static_cast(right))}; +} + +} // namespace + namespace AudioCore { AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, AudioCommon::AudioRendererParameter params, - std::shared_ptr buffer_event, + Stream::ReleaseCallback&& release_callback, std::size_t instance_number) - : worker_params{params}, buffer_event{buffer_event}, - memory_pool_info(params.effect_count + params.voice_count * 4), + : worker_params{params}, memory_pool_info(params.effect_count + params.voice_count * 4), voice_context(params.voice_count), effect_context(params.effect_count), mix_context(), sink_context(params.sink_count), splitter_context(), voices(params.voice_count), memory{memory_}, command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context, - memory), - temp_mix_buffer(AudioCommon::TOTAL_TEMP_MIX_SIZE) { + memory) { behavior_info.SetUserRevision(params.revision); splitter_context.Initialize(behavior_info, params.splitter_count, params.num_splitter_send_channels); mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count); audio_out = std::make_unique(); - stream = - audio_out->OpenStream(core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS, - fmt::format("AudioRenderer-Instance{}", instance_number), - [=]() { buffer_event->Signal(); }); + stream = audio_out->OpenStream( + core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS, + fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback)); audio_out->StartStream(stream); QueueMixedBuffer(0); @@ -65,10 +112,6 @@ Stream::State AudioRenderer::GetStreamState() const { return stream->GetState(); } -static constexpr s16 ClampToS16(s32 value) { - return static_cast(std::clamp(value, -32768, 32767)); -} - ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector& input_params, std::vector& output_params) { @@ -107,8 +150,8 @@ ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector& input_param } } - auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count, - splitter_context, effect_context); + const auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count, + splitter_context, effect_context); if (mix_result.IsError()) { LOG_ERROR(Audio, "Failed to update mix parameters"); @@ -197,20 +240,22 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { for (std::size_t i = 0; i < BUFFER_SIZE; i++) { if (channel_count == 1) { const auto sample = ClampToS16(mix_buffers[0][i]); - buffer[i * stream_channel_count + 0] = sample; - if (stream_channel_count > 1) { - buffer[i * stream_channel_count + 1] = sample; + + // Place sample in all channels + for (u32 channel = 0; channel < stream_channel_count; channel++) { + buffer[i * stream_channel_count + channel] = sample; } + if (stream_channel_count == 6) { - buffer[i * stream_channel_count + 2] = sample; - buffer[i * stream_channel_count + 4] = sample; - buffer[i * stream_channel_count + 5] = sample; + // Output stream has a LF channel, mute it! + buffer[i * stream_channel_count + 3] = 0; } + } else if (channel_count == 2) { const auto l_sample = ClampToS16(mix_buffers[0][i]); const auto r_sample = ClampToS16(mix_buffers[1][i]); if (stream_channel_count == 1) { - buffer[i * stream_channel_count + 0] = l_sample; + buffer[i * stream_channel_count + 0] = Mix2To1(l_sample, r_sample); } else if (stream_channel_count == 2) { buffer[i * stream_channel_count + 0] = l_sample; buffer[i * stream_channel_count + 1] = r_sample; @@ -218,8 +263,8 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { buffer[i * stream_channel_count + 0] = l_sample; buffer[i * stream_channel_count + 1] = r_sample; - buffer[i * stream_channel_count + 2] = - ClampToS16((static_cast(l_sample) + static_cast(r_sample)) / 2); + // Combine both left and right channels to the center channel + buffer[i * stream_channel_count + 2] = Mix2To1(l_sample, r_sample); buffer[i * stream_channel_count + 4] = l_sample; buffer[i * stream_channel_count + 5] = r_sample; @@ -234,17 +279,25 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { const auto br_sample = ClampToS16(mix_buffers[5][i]); if (stream_channel_count == 1) { - buffer[i * stream_channel_count + 0] = fc_sample; + // Games seem to ignore the center channel half the time, we use the front left + // and right channel for mixing as that's where majority of the audio goes + buffer[i * stream_channel_count + 0] = Mix2To1(fl_sample, fr_sample); } else if (stream_channel_count == 2) { - buffer[i * stream_channel_count + 0] = - static_cast(0.3694f * static_cast(fl_sample) + - 0.2612f * static_cast(fc_sample) + - 0.3694f * static_cast(bl_sample)); - buffer[i * stream_channel_count + 1] = - static_cast(0.3694f * static_cast(fr_sample) + - 0.2612f * static_cast(fc_sample) + - 0.3694f * static_cast(br_sample)); + // Mix all channels into 2 channels + if (sink_context.HasDownMixingCoefficients()) { + const auto [left, right] = Mix6To2WithCoefficients( + fl_sample, fr_sample, fc_sample, lf_sample, bl_sample, br_sample, + sink_context.GetDownmixCoefficients()); + buffer[i * stream_channel_count + 0] = left; + buffer[i * stream_channel_count + 1] = right; + } else { + const auto [left, right] = Mix6To2(fl_sample, fr_sample, fc_sample, + lf_sample, bl_sample, br_sample); + buffer[i * stream_channel_count + 0] = left; + buffer[i * stream_channel_count + 1] = right; + } } else if (stream_channel_count == 6) { + // Pass through buffer[i * stream_channel_count + 0] = fl_sample; buffer[i * stream_channel_count + 1] = fr_sample; buffer[i * stream_channel_count + 2] = fc_sample; @@ -262,7 +315,7 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { } void AudioRenderer::ReleaseAndQueueBuffers() { - const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream, 2)}; + const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)}; for (const auto& tag : released_buffers) { QueueMixedBuffer(tag); } diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h index 2bca795ba..18567f618 100644 --- a/src/audio_core/audio_renderer.h +++ b/src/audio_core/audio_renderer.h @@ -21,53 +21,41 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" -#include "core/hle/kernel/object.h" #include "core/hle/result.h" namespace Core::Timing { class CoreTiming; } -namespace Kernel { -class WritableEvent; -} - namespace Core::Memory { class Memory; } namespace AudioCore { -using DSPStateHolder = std::array; +using DSPStateHolder = std::array; class AudioOut; -struct RendererInfo { - u64_le elasped_frame_count{}; - INSERT_PADDING_WORDS(2); -}; -static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size"); - class AudioRenderer { public: AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, AudioCommon::AudioRendererParameter params, - std::shared_ptr buffer_event, std::size_t instance_number); + Stream::ReleaseCallback&& release_callback, std::size_t instance_number); ~AudioRenderer(); - ResultCode UpdateAudioRenderer(const std::vector& input_params, - std::vector& output_params); + [[nodiscard]] ResultCode UpdateAudioRenderer(const std::vector& input_params, + std::vector& output_params); void QueueMixedBuffer(Buffer::Tag tag); void ReleaseAndQueueBuffers(); - u32 GetSampleRate() const; - u32 GetSampleCount() const; - u32 GetMixBufferCount() const; - Stream::State GetStreamState() const; + [[nodiscard]] u32 GetSampleRate() const; + [[nodiscard]] u32 GetSampleCount() const; + [[nodiscard]] u32 GetMixBufferCount() const; + [[nodiscard]] Stream::State GetStreamState() const; private: BehaviorInfo behavior_info{}; AudioCommon::AudioRendererParameter worker_params; - std::shared_ptr buffer_event; std::vector memory_pool_info; VoiceContext voice_context; EffectContext effect_context; @@ -80,7 +68,6 @@ private: Core::Memory::Memory& memory; CommandGenerator command_generator; std::size_t elapsed_frame_count{}; - std::vector temp_mix_buffer{}; }; } // namespace AudioCore diff --git a/src/audio_core/behavior_info.cpp b/src/audio_core/behavior_info.cpp index 5d62adb0b..3c2e3e6f1 100644 --- a/src/audio_core/behavior_info.cpp +++ b/src/audio_core/behavior_info.cpp @@ -57,15 +57,15 @@ bool BehaviorInfo::IsLongSizePreDelaySupported() const { return AudioCommon::IsRevisionSupported(3, user_revision); } -bool BehaviorInfo::IsAudioRenererProcessingTimeLimit80PercentSupported() const { +bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const { return AudioCommon::IsRevisionSupported(5, user_revision); } -bool BehaviorInfo::IsAudioRenererProcessingTimeLimit75PercentSupported() const { +bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const { return AudioCommon::IsRevisionSupported(4, user_revision); } -bool BehaviorInfo::IsAudioRenererProcessingTimeLimit70PercentSupported() const { +bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const { return AudioCommon::IsRevisionSupported(1, user_revision); } diff --git a/src/audio_core/behavior_info.h b/src/audio_core/behavior_info.h index 50948e8df..5a96bf75e 100644 --- a/src/audio_core/behavior_info.h +++ b/src/audio_core/behavior_info.h @@ -43,22 +43,22 @@ public: void ClearError(); void UpdateFlags(u64_le dest_flags); void SetUserRevision(u32_le revision); - u32_le GetUserRevision() const; - u32_le GetProcessRevision() const; + [[nodiscard]] u32_le GetUserRevision() const; + [[nodiscard]] u32_le GetProcessRevision() const; - bool IsAdpcmLoopContextBugFixed() const; - bool IsSplitterSupported() const; - bool IsLongSizePreDelaySupported() const; - bool IsAudioRenererProcessingTimeLimit80PercentSupported() const; - bool IsAudioRenererProcessingTimeLimit75PercentSupported() const; - bool IsAudioRenererProcessingTimeLimit70PercentSupported() const; - bool IsElapsedFrameCountSupported() const; - bool IsMemoryPoolForceMappingEnabled() const; - bool IsFlushVoiceWaveBuffersSupported() const; - bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const; - bool IsVoicePitchAndSrcSkippedSupported() const; - bool IsMixInParameterDirtyOnlyUpdateSupported() const; - bool IsSplitterBugFixed() const; + [[nodiscard]] bool IsAdpcmLoopContextBugFixed() const; + [[nodiscard]] bool IsSplitterSupported() const; + [[nodiscard]] bool IsLongSizePreDelaySupported() const; + [[nodiscard]] bool IsAudioRendererProcessingTimeLimit80PercentSupported() const; + [[nodiscard]] bool IsAudioRendererProcessingTimeLimit75PercentSupported() const; + [[nodiscard]] bool IsAudioRendererProcessingTimeLimit70PercentSupported() const; + [[nodiscard]] bool IsElapsedFrameCountSupported() const; + [[nodiscard]] bool IsMemoryPoolForceMappingEnabled() const; + [[nodiscard]] bool IsFlushVoiceWaveBuffersSupported() const; + [[nodiscard]] bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const; + [[nodiscard]] bool IsVoicePitchAndSrcSkippedSupported() const; + [[nodiscard]] bool IsMixInParameterDirtyOnlyUpdateSupported() const; + [[nodiscard]] bool IsSplitterBugFixed() const; void CopyErrorInfo(OutParams& dst); private: diff --git a/src/audio_core/buffer.h b/src/audio_core/buffer.h index 5ee09e9aa..ccc46ef82 100644 --- a/src/audio_core/buffer.h +++ b/src/audio_core/buffer.h @@ -18,7 +18,7 @@ class Buffer { public: using Tag = u64; - Buffer(Tag tag, std::vector&& samples) : tag{tag}, samples{std::move(samples)} {} + Buffer(Tag tag_, std::vector&& samples_) : tag{tag_}, samples{std::move(samples_)} {} /// Returns the raw audio data for the buffer std::vector& GetSamples() { diff --git a/src/audio_core/codec.cpp b/src/audio_core/codec.cpp index c5a0d98ce..2fb91c13a 100644 --- a/src/audio_core/codec.cpp +++ b/src/audio_core/codec.cpp @@ -16,8 +16,9 @@ std::vector DecodeADPCM(const u8* const data, std::size_t size, const ADPCM constexpr std::size_t FRAME_LEN = 8; constexpr std::size_t SAMPLES_PER_FRAME = 14; - constexpr std::array SIGNED_NIBBLES = { - {0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1}}; + static constexpr std::array SIGNED_NIBBLES{ + 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1, + }; const std::size_t sample_count = (size / FRAME_LEN) * SAMPLES_PER_FRAME; const std::size_t ret_size = diff --git a/src/audio_core/codec.h b/src/audio_core/codec.h index ef2ce01a8..9507abb1b 100644 --- a/src/audio_core/codec.h +++ b/src/audio_core/codec.h @@ -38,7 +38,7 @@ using ADPCM_Coeff = std::array; * @param state ADPCM state, this is updated with new state * @return Decoded stereo signed PCM16 data, sample_count in length */ -std::vector DecodeADPCM(const u8* const data, std::size_t size, const ADPCM_Coeff& coeff, +std::vector DecodeADPCM(const u8* data, std::size_t size, const ADPCM_Coeff& coeff, ADPCMState& state); }; // namespace AudioCore::Codec diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp index 8f7da49e6..a4a9a757d 100644 --- a/src/audio_core/command_generator.cpp +++ b/src/audio_core/command_generator.cpp @@ -67,12 +67,12 @@ s32 ApplyMixDepop(s32* output, s32 first_sample, s32 delta, s32 sample_count) { } // namespace -CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params, - VoiceContext& voice_context, MixContext& mix_context, - SplitterContext& splitter_context, EffectContext& effect_context, - Core::Memory::Memory& memory) - : worker_params(worker_params), voice_context(voice_context), mix_context(mix_context), - splitter_context(splitter_context), effect_context(effect_context), memory(memory), +CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_, + VoiceContext& voice_context_, MixContext& mix_context_, + SplitterContext& splitter_context_, + EffectContext& effect_context_, Core::Memory::Memory& memory_) + : worker_params(worker_params_), voice_context(voice_context_), mix_context(mix_context_), + splitter_context(splitter_context_), effect_context(effect_context_), memory(memory_), mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) * worker_params.sample_count), sample_buffer(MIX_BUFFER_SIZE), @@ -152,7 +152,7 @@ void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) { if (!destination_data->IsConfigured()) { continue; } - if (destination_data->GetMixId() >= mix_context.GetCount()) { + if (destination_data->GetMixId() >= static_cast(mix_context.GetCount())) { continue; } @@ -255,7 +255,8 @@ void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, Vo void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state, - s32 mix_buffer_count, s32 channel) { + [[maybe_unused]] s32 mix_buffer_count, + [[maybe_unused]] s32 channel) { for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) { const auto& in_params = voice_info.GetInParams(); auto& biquad_filter = in_params.biquad_filter[i]; @@ -278,9 +279,12 @@ void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voic } } -void AudioCore::CommandGenerator::GenerateBiquadFilterCommand( - s32 mix_buffer, const BiquadFilterParameter& params, std::array& state, - std::size_t input_offset, std::size_t output_offset, s32 sample_count, s32 node_id) { +void CommandGenerator::GenerateBiquadFilterCommand([[maybe_unused]] s32 mix_buffer_id, + const BiquadFilterParameter& params, + std::array& state, + std::size_t input_offset, + std::size_t output_offset, s32 sample_count, + s32 node_id) { if (dumping_frame) { LOG_DEBUG(Audio, "(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, " @@ -435,7 +439,7 @@ void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* inf GetMixBuffer(output_index), worker_params.sample_count, offset, write_count); memory.WriteBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP)); - if (samples_read != worker_params.sample_count && + if (samples_read != static_cast(worker_params.sample_count) && samples_read <= params.sample_count) { std::memset(GetMixBuffer(output_index), 0, params.sample_count - samples_read); } @@ -611,7 +615,8 @@ void CommandGenerator::GenerateMixCommands(ServerMixInfo& mix_info) { const auto& dest_mix = mix_context.GetInfo(destination_data->GetMixId()); const auto& dest_in_params = dest_mix.GetInParams(); const auto mix_index = (base - 1) % in_params.buffer_count + in_params.buffer_offset; - for (std::size_t i = 0; i < dest_in_params.buffer_count; i++) { + for (std::size_t i = 0; i < static_cast(dest_in_params.buffer_count); + i++) { const auto mixed_volume = in_params.volume * destination_data->GetMixVolume(i); if (mixed_volume != 0.0f) { GenerateMixCommand(dest_in_params.buffer_offset + i, mix_index, mixed_volume, @@ -704,7 +709,7 @@ s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_s std::vector buffer(samples_processed * channel_count); memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16)); - for (std::size_t i = 0; i < samples_processed; i++) { + for (std::size_t i = 0; i < static_cast(samples_processed); i++) { sample_buffer[mix_offset + i] = buffer[i * channel_count + channel]; } } @@ -713,7 +718,8 @@ s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_s } s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, - s32 sample_count, s32 channel, std::size_t mix_offset) { + s32 sample_count, [[maybe_unused]] s32 channel, + std::size_t mix_offset) { const auto& in_params = voice_info.GetInParams(); const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; if (wave_buffer.buffer_address == 0) { @@ -726,8 +732,9 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s return 0; } - constexpr std::array SIGNED_NIBBLES = { - {0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1}}; + static constexpr std::array SIGNED_NIBBLES{ + 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1, + }; constexpr std::size_t FRAME_LEN = 8; constexpr std::size_t NIBBLES_PER_SAMPLE = 16; @@ -789,9 +796,8 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s position_in_frame += 2; // Decode entire frame - if (remaining_samples >= SAMPLES_PER_FRAME) { + if (remaining_samples >= static_cast(SAMPLES_PER_FRAME)) { for (std::size_t i = 0; i < SAMPLES_PER_FRAME / 2; i++) { - // Sample 1 const s32 s0 = SIGNED_NIBBLES[buffer[buffer_offset] >> 4]; const s32 s1 = SIGNED_NIBBLES[buffer[buffer_offset++] & 0xf]; @@ -800,7 +806,7 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s sample_buffer[cur_mix_offset++] = sample_1; sample_buffer[cur_mix_offset++] = sample_2; } - remaining_samples -= SAMPLES_PER_FRAME; + remaining_samples -= static_cast(SAMPLES_PER_FRAME); position_in_frame += SAMPLES_PER_FRAME; continue; } @@ -866,7 +872,6 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o const auto resample_rate = static_cast( static_cast(in_params.sample_rate) / static_cast(target_sample_rate) * static_cast(static_cast(in_params.pitch * 32768.0f))); - auto* output_base = output; if (dsp_state.fraction + sample_count * resample_rate > static_cast(SCALED_MIX_BUFFER_SIZE - 4ULL)) { return; diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h index 967d24078..b937350b1 100644 --- a/src/audio_core/command_generator.h +++ b/src/audio_core/command_generator.h @@ -7,7 +7,6 @@ #include #include "audio_core/common.h" #include "audio_core/voice_context.h" -#include "common/common_funcs.h" #include "common/common_types.h" namespace Core::Memory { @@ -26,10 +25,10 @@ using MixVolumeBuffer = std::array; class CommandGenerator { public: - explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params, - VoiceContext& voice_context, MixContext& mix_context, - SplitterContext& splitter_context, EffectContext& effect_context, - Core::Memory::Memory& memory); + explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_, + VoiceContext& voice_context_, MixContext& mix_context_, + SplitterContext& splitter_context_, EffectContext& effect_context_, + Core::Memory::Memory& memory_); ~CommandGenerator(); void ClearMixBuffers(); @@ -40,13 +39,13 @@ public: void PreCommand(); void PostCommand(); - s32* GetChannelMixBuffer(s32 channel); - const s32* GetChannelMixBuffer(s32 channel) const; - s32* GetMixBuffer(std::size_t index); - const s32* GetMixBuffer(std::size_t index) const; - std::size_t GetMixChannelBufferOffset(s32 channel) const; + [[nodiscard]] s32* GetChannelMixBuffer(s32 channel); + [[nodiscard]] const s32* GetChannelMixBuffer(s32 channel) const; + [[nodiscard]] s32* GetMixBuffer(std::size_t index); + [[nodiscard]] const s32* GetMixBuffer(std::size_t index) const; + [[nodiscard]] std::size_t GetMixChannelBufferOffset(s32 channel) const; - std::size_t GetTotalMixBufferCount() const; + [[nodiscard]] std::size_t GetTotalMixBufferCount() const; private: void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel); @@ -74,7 +73,7 @@ private: void GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); void GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); - ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index); + [[nodiscard]] ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index); s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, const s32* data, u32 sample_count, u32 write_offset, u32 write_count); diff --git a/src/audio_core/common.h b/src/audio_core/common.h index 72ebce221..ec59a3ba9 100644 --- a/src/audio_core/common.h +++ b/src/audio_core/common.h @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #pragma once + #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" @@ -21,7 +22,7 @@ constexpr std::size_t MAX_CHANNEL_COUNT = 6; constexpr std::size_t MAX_WAVE_BUFFERS = 4; constexpr std::size_t MAX_SAMPLE_HISTORY = 4; constexpr u32 STREAM_SAMPLE_RATE = 48000; -constexpr u32 STREAM_NUM_CHANNELS = 6; +constexpr u32 STREAM_NUM_CHANNELS = 2; constexpr s32 NO_SPLITTER = -1; constexpr s32 NO_MIX = 0x7fffffff; constexpr s32 NO_FINAL_MIX = std::numeric_limits::min(); diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index 83c06c0ed..043447eaa 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -21,15 +21,16 @@ namespace AudioCore { class CubebSinkStream final : public SinkStream { public: - CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, + CubebSinkStream(cubeb* ctx_, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, const std::string& name) - : ctx{ctx}, num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate, - num_channels} { + : ctx{ctx_}, num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate, + num_channels} { cubeb_stream_params params{}; params.rate = sample_rate; params.channels = num_channels; params.format = CUBEB_SAMPLE_S16NE; + params.prefs = CUBEB_STREAM_PREF_PERSIST; switch (num_channels) { case 1: params.layout = CUBEB_LAYOUT_MONO; @@ -93,8 +94,10 @@ public: constexpr s32 clev{707}; // center mixing level coefficient constexpr s32 slev{707}; // surround mixing level coefficient - buf.push_back(left + (clev * center / 1000) + (slev * surround_left / 1000)); - buf.push_back(right + (clev * center / 1000) + (slev * surround_right / 1000)); + buf.push_back(static_cast(left + (clev * center / 1000) + + (slev * surround_left / 1000))); + buf.push_back(static_cast(right + (clev * center / 1000) + + (slev * surround_right / 1000))); } queue.Push(buf); return; @@ -190,10 +193,11 @@ SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, return *sink_streams.back(); } -long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, - void* output_buffer, long num_frames) { - CubebSinkStream* impl = static_cast(user_data); - u8* buffer = reinterpret_cast(output_buffer); +long CubebSinkStream::DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data, + [[maybe_unused]] const void* input_buffer, void* output_buffer, + long num_frames) { + auto* impl = static_cast(user_data); + auto* buffer = static_cast(output_buffer); if (!impl) { return {}; @@ -234,7 +238,9 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const return num_frames; } -void CubebSinkStream::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {} +void CubebSinkStream::StateCallback([[maybe_unused]] cubeb_stream* stream, + [[maybe_unused]] void* user_data, + [[maybe_unused]] cubeb_state state) {} std::vector ListCubebSinkDevices() { std::vector device_list; diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp index adfec3df5..f770b9608 100644 --- a/src/audio_core/effect_context.cpp +++ b/src/audio_core/effect_context.cpp @@ -12,7 +12,7 @@ bool ValidChannelCountForEffect(s32 channel_count) { } } // namespace -EffectContext::EffectContext(std::size_t effect_count) : effect_count(effect_count) { +EffectContext::EffectContext(std::size_t effect_count_) : effect_count(effect_count_) { effects.reserve(effect_count); std::generate_n(std::back_inserter(effects), effect_count, [] { return std::make_unique(); }); @@ -61,13 +61,13 @@ const EffectBase* EffectContext::GetInfo(std::size_t i) const { return effects.at(i).get(); } -EffectStubbed::EffectStubbed() : EffectBase::EffectBase(EffectType::Invalid) {} +EffectStubbed::EffectStubbed() : EffectBase(EffectType::Invalid) {} EffectStubbed::~EffectStubbed() = default; -void EffectStubbed::Update(EffectInfo::InParams& in_params) {} +void EffectStubbed::Update([[maybe_unused]] EffectInfo::InParams& in_params) {} void EffectStubbed::UpdateForCommandGeneration() {} -EffectBase::EffectBase(EffectType effect_type) : effect_type(effect_type) {} +EffectBase::EffectBase(EffectType effect_type_) : effect_type(effect_type_) {} EffectBase::~EffectBase() = default; UsageState EffectBase::GetUsage() const { @@ -90,32 +90,32 @@ s32 EffectBase::GetProcessingOrder() const { return processing_order; } -EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric::EffectGeneric(EffectType::I3dl2Reverb) {} +EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {} EffectI3dl2Reverb::~EffectI3dl2Reverb() = default; void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) { - auto& internal_params = GetParams(); + auto& params = GetParams(); const auto* reverb_params = reinterpret_cast(in_params.raw.data()); if (!ValidChannelCountForEffect(reverb_params->max_channels)) { UNREACHABLE_MSG("Invalid reverb max channel count {}", reverb_params->max_channels); return; } - const auto last_status = internal_params.status; + const auto last_status = params.status; mix_id = in_params.mix_id; processing_order = in_params.processing_order; - internal_params = *reverb_params; + params = *reverb_params; if (!ValidChannelCountForEffect(reverb_params->channel_count)) { - internal_params.channel_count = internal_params.max_channels; + params.channel_count = params.max_channels; } enabled = in_params.is_enabled; if (last_status != ParameterStatus::Updated) { - internal_params.status = last_status; + params.status = last_status; } if (in_params.is_new || skipped) { usage = UsageState::Initialized; - internal_params.status = ParameterStatus::Initialized; + params.status = ParameterStatus::Initialized; skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; } } @@ -129,15 +129,15 @@ void EffectI3dl2Reverb::UpdateForCommandGeneration() { GetParams().status = ParameterStatus::Updated; } -EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric::EffectGeneric(EffectType::BiquadFilter) {} +EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {} EffectBiquadFilter::~EffectBiquadFilter() = default; void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) { - auto& internal_params = GetParams(); + auto& params = GetParams(); const auto* biquad_params = reinterpret_cast(in_params.raw.data()); mix_id = in_params.mix_id; processing_order = in_params.processing_order; - internal_params = *biquad_params; + params = *biquad_params; enabled = in_params.is_enabled; } @@ -150,7 +150,7 @@ void EffectBiquadFilter::UpdateForCommandGeneration() { GetParams().status = ParameterStatus::Updated; } -EffectAuxInfo::EffectAuxInfo() : EffectGeneric::EffectGeneric(EffectType::Aux) {} +EffectAuxInfo::EffectAuxInfo() : EffectGeneric(EffectType::Aux) {} EffectAuxInfo::~EffectAuxInfo() = default; void EffectAuxInfo::Update(EffectInfo::InParams& in_params) { @@ -184,48 +184,48 @@ void EffectAuxInfo::UpdateForCommandGeneration() { } } -const VAddr EffectAuxInfo::GetSendInfo() const { +VAddr EffectAuxInfo::GetSendInfo() const { return send_info; } -const VAddr EffectAuxInfo::GetSendBuffer() const { +VAddr EffectAuxInfo::GetSendBuffer() const { return send_buffer; } -const VAddr EffectAuxInfo::GetRecvInfo() const { +VAddr EffectAuxInfo::GetRecvInfo() const { return recv_info; } -const VAddr EffectAuxInfo::GetRecvBuffer() const { +VAddr EffectAuxInfo::GetRecvBuffer() const { return recv_buffer; } -EffectDelay::EffectDelay() : EffectGeneric::EffectGeneric(EffectType::Delay) {} +EffectDelay::EffectDelay() : EffectGeneric(EffectType::Delay) {} EffectDelay::~EffectDelay() = default; void EffectDelay::Update(EffectInfo::InParams& in_params) { const auto* delay_params = reinterpret_cast(in_params.raw.data()); - auto& internal_params = GetParams(); + auto& params = GetParams(); if (!ValidChannelCountForEffect(delay_params->max_channels)) { return; } - const auto last_status = internal_params.status; + const auto last_status = params.status; mix_id = in_params.mix_id; processing_order = in_params.processing_order; - internal_params = *delay_params; + params = *delay_params; if (!ValidChannelCountForEffect(delay_params->channels)) { - internal_params.channels = internal_params.max_channels; + params.channels = params.max_channels; } enabled = in_params.is_enabled; if (last_status != ParameterStatus::Updated) { - internal_params.status = last_status; + params.status = last_status; } if (in_params.is_new || skipped) { usage = UsageState::Initialized; - internal_params.status = ParameterStatus::Initialized; + params.status = ParameterStatus::Initialized; skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; } } @@ -239,7 +239,7 @@ void EffectDelay::UpdateForCommandGeneration() { GetParams().status = ParameterStatus::Updated; } -EffectBufferMixer::EffectBufferMixer() : EffectGeneric::EffectGeneric(EffectType::BufferMixer) {} +EffectBufferMixer::EffectBufferMixer() : EffectGeneric(EffectType::BufferMixer) {} EffectBufferMixer::~EffectBufferMixer() = default; void EffectBufferMixer::Update(EffectInfo::InParams& in_params) { @@ -257,32 +257,32 @@ void EffectBufferMixer::UpdateForCommandGeneration() { } } -EffectReverb::EffectReverb() : EffectGeneric::EffectGeneric(EffectType::Reverb) {} +EffectReverb::EffectReverb() : EffectGeneric(EffectType::Reverb) {} EffectReverb::~EffectReverb() = default; void EffectReverb::Update(EffectInfo::InParams& in_params) { const auto* reverb_params = reinterpret_cast(in_params.raw.data()); - auto& internal_params = GetParams(); + auto& params = GetParams(); if (!ValidChannelCountForEffect(reverb_params->max_channels)) { return; } - const auto last_status = internal_params.status; + const auto last_status = params.status; mix_id = in_params.mix_id; processing_order = in_params.processing_order; - internal_params = *reverb_params; + params = *reverb_params; if (!ValidChannelCountForEffect(reverb_params->channels)) { - internal_params.channels = internal_params.max_channels; + params.channels = params.max_channels; } enabled = in_params.is_enabled; if (last_status != ParameterStatus::Updated) { - internal_params.status = last_status; + params.status = last_status; } if (in_params.is_new || skipped) { usage = UsageState::Initialized; - internal_params.status = ParameterStatus::Initialized; + params.status = ParameterStatus::Initialized; skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; } } diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h index 2f2da72dd..c5e0b398c 100644 --- a/src/audio_core/effect_context.h +++ b/src/audio_core/effect_context.h @@ -166,13 +166,13 @@ public: std::array raw; }; }; - static_assert(sizeof(EffectInfo::InParams) == 0xc0, "InParams is an invalid size"); + static_assert(sizeof(InParams) == 0xc0, "InParams is an invalid size"); struct OutParams { UsageStatus status{}; INSERT_PADDING_BYTES(15); }; - static_assert(sizeof(EffectInfo::OutParams) == 0x10, "OutParams is an invalid size"); + static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size"); }; struct AuxAddress { @@ -184,16 +184,16 @@ struct AuxAddress { class EffectBase { public: - EffectBase(EffectType effect_type); - ~EffectBase(); + explicit EffectBase(EffectType effect_type_); + virtual ~EffectBase(); virtual void Update(EffectInfo::InParams& in_params) = 0; virtual void UpdateForCommandGeneration() = 0; - UsageState GetUsage() const; - EffectType GetType() const; - bool IsEnabled() const; - s32 GetMixID() const; - s32 GetProcessingOrder() const; + [[nodiscard]] UsageState GetUsage() const; + [[nodiscard]] EffectType GetType() const; + [[nodiscard]] bool IsEnabled() const; + [[nodiscard]] s32 GetMixID() const; + [[nodiscard]] s32 GetProcessingOrder() const; protected: UsageState usage{UsageState::Invalid}; @@ -206,8 +206,7 @@ protected: template class EffectGeneric : public EffectBase { public: - EffectGeneric(EffectType effect_type) : EffectBase::EffectBase(effect_type) {} - ~EffectGeneric() = default; + explicit EffectGeneric(EffectType effect_type_) : EffectBase(effect_type_) {} T& GetParams() { return internal_params; @@ -224,7 +223,7 @@ private: class EffectStubbed : public EffectBase { public: explicit EffectStubbed(); - ~EffectStubbed(); + ~EffectStubbed() override; void Update(EffectInfo::InParams& in_params) override; void UpdateForCommandGeneration() override; @@ -233,7 +232,7 @@ public: class EffectI3dl2Reverb : public EffectGeneric { public: explicit EffectI3dl2Reverb(); - ~EffectI3dl2Reverb(); + ~EffectI3dl2Reverb() override; void Update(EffectInfo::InParams& in_params) override; void UpdateForCommandGeneration() override; @@ -245,7 +244,7 @@ private: class EffectBiquadFilter : public EffectGeneric { public: explicit EffectBiquadFilter(); - ~EffectBiquadFilter(); + ~EffectBiquadFilter() override; void Update(EffectInfo::InParams& in_params) override; void UpdateForCommandGeneration() override; @@ -254,14 +253,14 @@ public: class EffectAuxInfo : public EffectGeneric { public: explicit EffectAuxInfo(); - ~EffectAuxInfo(); + ~EffectAuxInfo() override; void Update(EffectInfo::InParams& in_params) override; void UpdateForCommandGeneration() override; - const VAddr GetSendInfo() const; - const VAddr GetSendBuffer() const; - const VAddr GetRecvInfo() const; - const VAddr GetRecvBuffer() const; + [[nodiscard]] VAddr GetSendInfo() const; + [[nodiscard]] VAddr GetSendBuffer() const; + [[nodiscard]] VAddr GetRecvInfo() const; + [[nodiscard]] VAddr GetRecvBuffer() const; private: VAddr send_info{}; @@ -275,7 +274,7 @@ private: class EffectDelay : public EffectGeneric { public: explicit EffectDelay(); - ~EffectDelay(); + ~EffectDelay() override; void Update(EffectInfo::InParams& in_params) override; void UpdateForCommandGeneration() override; @@ -287,7 +286,7 @@ private: class EffectBufferMixer : public EffectGeneric { public: explicit EffectBufferMixer(); - ~EffectBufferMixer(); + ~EffectBufferMixer() override; void Update(EffectInfo::InParams& in_params) override; void UpdateForCommandGeneration() override; @@ -296,7 +295,7 @@ public: class EffectReverb : public EffectGeneric { public: explicit EffectReverb(); - ~EffectReverb(); + ~EffectReverb() override; void Update(EffectInfo::InParams& in_params) override; void UpdateForCommandGeneration() override; @@ -307,13 +306,13 @@ private: class EffectContext { public: - explicit EffectContext(std::size_t effect_count); + explicit EffectContext(std::size_t effect_count_); ~EffectContext(); - std::size_t GetCount() const; - EffectBase* GetInfo(std::size_t i); - EffectBase* RetargetEffect(std::size_t i, EffectType effect); - const EffectBase* GetInfo(std::size_t i) const; + [[nodiscard]] std::size_t GetCount() const; + [[nodiscard]] EffectBase* GetInfo(std::size_t i); + [[nodiscard]] EffectBase* RetargetEffect(std::size_t i, EffectType effect); + [[nodiscard]] const EffectBase* GetInfo(std::size_t i) const; private: std::size_t effect_count{}; diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp index f53ce21a5..d3ac90827 100644 --- a/src/audio_core/info_updater.cpp +++ b/src/audio_core/info_updater.cpp @@ -14,9 +14,9 @@ namespace AudioCore { -InfoUpdater::InfoUpdater(const std::vector& in_params, std::vector& out_params, - BehaviorInfo& behavior_info) - : in_params(in_params), out_params(out_params), behavior_info(behavior_info) { +InfoUpdater::InfoUpdater(const std::vector& in_params_, std::vector& out_params_, + BehaviorInfo& behavior_info_) + : in_params(in_params_), out_params(out_params_), behavior_info(behavior_info_) { ASSERT( AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader))); std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader)); @@ -64,7 +64,6 @@ bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) { } bool InfoUpdater::UpdateMemoryPools(std::vector& memory_pool_info) { - const auto force_mapping = behavior_info.IsMemoryPoolForceMappingEnabled(); const auto memory_pool_count = memory_pool_info.size(); const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count; const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count; @@ -136,8 +135,8 @@ bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) { } bool InfoUpdater::UpdateVoices(VoiceContext& voice_context, - std::vector& memory_pool_info, - VAddr audio_codec_dsp_addr) { + [[maybe_unused]] std::vector& memory_pool_info, + [[maybe_unused]] VAddr audio_codec_dsp_addr) { const auto voice_count = voice_context.GetVoiceCount(); std::vector voice_in(voice_count); std::vector voice_out(voice_count); @@ -166,28 +165,28 @@ bool InfoUpdater::UpdateVoices(VoiceContext& voice_context, // Update our voices for (std::size_t i = 0; i < voice_count; i++) { - auto& in_params = voice_in[i]; - const auto channel_count = static_cast(in_params.channel_count); + auto& voice_in_params = voice_in[i]; + const auto channel_count = static_cast(voice_in_params.channel_count); // Skip if it's not currently in use - if (!in_params.is_in_use) { + if (!voice_in_params.is_in_use) { continue; } // Voice states for each channel std::array voice_states{}; - ASSERT(in_params.id < voice_count); + ASSERT(static_cast(voice_in_params.id) < voice_count); // Grab our current voice info - auto& voice_info = voice_context.GetInfo(static_cast(in_params.id)); + auto& voice_info = voice_context.GetInfo(static_cast(voice_in_params.id)); ASSERT(channel_count <= AudioCommon::MAX_CHANNEL_COUNT); // Get all our channel voice states for (std::size_t channel = 0; channel < channel_count; channel++) { voice_states[channel] = - &voice_context.GetState(in_params.voice_channel_resource_ids[channel]); + &voice_context.GetState(voice_in_params.voice_channel_resource_ids[channel]); } - if (in_params.is_new) { + if (voice_in_params.is_new) { // Default our values for our voice voice_info.Initialize(); if (channel_count == 0 || channel_count > AudioCommon::MAX_CHANNEL_COUNT) { @@ -201,12 +200,12 @@ bool InfoUpdater::UpdateVoices(VoiceContext& voice_context, } // Update our voice - voice_info.UpdateParameters(in_params, behavior_info); + voice_info.UpdateParameters(voice_in_params, behavior_info); // TODO(ogniK): Handle mapping errors with behavior info based on in params response // Update our wave buffers - voice_info.UpdateWaveBuffers(in_params, voice_states, behavior_info); - voice_info.WriteOutStatus(voice_out[i], in_params, voice_states); + voice_info.UpdateWaveBuffers(voice_in_params, voice_states, behavior_info); + voice_info.WriteOutStatus(voice_out[i], voice_in_params, voice_states); } if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, voice_out_size)) { @@ -352,8 +351,8 @@ ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buf for (std::size_t i = 0; i < mix_count; i++) { const auto& in = mix_in_params[i]; total_buffer_count += in.buffer_count; - if (in.dest_mix_id > mix_count && in.dest_mix_id != AudioCommon::NO_MIX && - in.mix_id != AudioCommon::FINAL_MIX) { + if (static_cast(in.dest_mix_id) > mix_count && + in.dest_mix_id != AudioCommon::NO_MIX && in.mix_id != AudioCommon::FINAL_MIX) { LOG_ERROR( Audio, "Invalid mix destination, mix_id={:X}, dest_mix_id={:X}, mix_buffer_count={:X}", @@ -446,7 +445,7 @@ bool InfoUpdater::UpdatePerformanceBuffer() { return true; } -bool InfoUpdater::UpdateErrorInfo(BehaviorInfo& in_behavior_info) { +bool InfoUpdater::UpdateErrorInfo([[maybe_unused]] BehaviorInfo& in_behavior_info) { const auto total_beahvior_info_out = sizeof(BehaviorInfo::OutParams); if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_beahvior_info_out)) { diff --git a/src/audio_core/info_updater.h b/src/audio_core/info_updater.h index 06f9d770f..d315c91ed 100644 --- a/src/audio_core/info_updater.h +++ b/src/audio_core/info_updater.h @@ -21,8 +21,8 @@ class SplitterContext; class InfoUpdater { public: // TODO(ogniK): Pass process handle when we support it - InfoUpdater(const std::vector& in_params, std::vector& out_params, - BehaviorInfo& behavior_info); + InfoUpdater(const std::vector& in_params_, std::vector& out_params_, + BehaviorInfo& behavior_info_); ~InfoUpdater(); bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info); diff --git a/src/audio_core/memory_pool.cpp b/src/audio_core/memory_pool.cpp index 5a3453063..6b6908d26 100644 --- a/src/audio_core/memory_pool.cpp +++ b/src/audio_core/memory_pool.cpp @@ -10,11 +10,10 @@ namespace AudioCore { ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default; ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default; -bool ServerMemoryPoolInfo::Update(const ServerMemoryPoolInfo::InParams& in_params, - ServerMemoryPoolInfo::OutParams& out_params) { + +bool ServerMemoryPoolInfo::Update(const InParams& in_params, OutParams& out_params) { // Our state does not need to be changed - if (in_params.state != ServerMemoryPoolInfo::State::RequestAttach && - in_params.state != ServerMemoryPoolInfo::State::RequestDetach) { + if (in_params.state != State::RequestAttach && in_params.state != State::RequestDetach) { return true; } @@ -32,11 +31,11 @@ bool ServerMemoryPoolInfo::Update(const ServerMemoryPoolInfo::InParams& in_param return false; } - if (in_params.state == ServerMemoryPoolInfo::State::RequestAttach) { + if (in_params.state == State::RequestAttach) { cpu_address = in_params.address; size = in_params.size; used = true; - out_params.state = ServerMemoryPoolInfo::State::Attached; + out_params.state = State::Attached; } else { // Unexpected address if (cpu_address != in_params.address) { @@ -54,7 +53,7 @@ bool ServerMemoryPoolInfo::Update(const ServerMemoryPoolInfo::InParams& in_param cpu_address = 0; size = 0; used = false; - out_params.state = ServerMemoryPoolInfo::State::Detached; + out_params.state = State::Detached; } return true; } diff --git a/src/audio_core/memory_pool.h b/src/audio_core/memory_pool.h index 8ac503f1c..3e9e777ae 100644 --- a/src/audio_core/memory_pool.h +++ b/src/audio_core/memory_pool.h @@ -28,19 +28,18 @@ public: struct InParams { u64_le address{}; u64_le size{}; - ServerMemoryPoolInfo::State state{}; + State state{}; INSERT_PADDING_WORDS(3); }; - static_assert(sizeof(ServerMemoryPoolInfo::InParams) == 0x20, "InParams are an invalid size"); + static_assert(sizeof(InParams) == 0x20, "InParams are an invalid size"); struct OutParams { - ServerMemoryPoolInfo::State state{}; + State state{}; INSERT_PADDING_WORDS(3); }; - static_assert(sizeof(ServerMemoryPoolInfo::OutParams) == 0x10, "OutParams are an invalid size"); + static_assert(sizeof(OutParams) == 0x10, "OutParams are an invalid size"); - bool Update(const ServerMemoryPoolInfo::InParams& in_params, - ServerMemoryPoolInfo::OutParams& out_params); + bool Update(const InParams& in_params, OutParams& out_params); private: // There's another entry here which is the DSP address, however since we're not talking to the diff --git a/src/audio_core/mix_context.cpp b/src/audio_core/mix_context.cpp index 042891490..4bca72eb0 100644 --- a/src/audio_core/mix_context.cpp +++ b/src/audio_core/mix_context.cpp @@ -53,7 +53,7 @@ void MixContext::UpdateDistancesFromFinalMix() { auto mix_id = in_params.mix_id; // Needs to be referenced out of scope s32 distance_to_final_mix{AudioCommon::FINAL_MIX}; - for (; distance_to_final_mix < info_count; distance_to_final_mix++) { + for (; distance_to_final_mix < static_cast(info_count); distance_to_final_mix++) { if (mix_id == AudioCommon::FINAL_MIX) { // If we're at the final mix, we're done break; @@ -77,7 +77,7 @@ void MixContext::UpdateDistancesFromFinalMix() { } // If we're out of range for our distance, mark it as no final mix - if (distance_to_final_mix >= info_count) { + if (distance_to_final_mix >= static_cast(info_count)) { distance_to_final_mix = AudioCommon::NO_FINAL_MIX; } diff --git a/src/audio_core/mix_context.h b/src/audio_core/mix_context.h index 6a588eeb4..68bc673c6 100644 --- a/src/audio_core/mix_context.h +++ b/src/audio_core/mix_context.h @@ -62,17 +62,17 @@ public: ServerMixInfo(); ~ServerMixInfo(); - const ServerMixInfo::InParams& GetInParams() const; - ServerMixInfo::InParams& GetInParams(); + [[nodiscard]] const ServerMixInfo::InParams& GetInParams() const; + [[nodiscard]] ServerMixInfo::InParams& GetInParams(); bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, BehaviorInfo& behavior_info, SplitterContext& splitter_context, EffectContext& effect_context); - bool HasAnyConnection() const; + [[nodiscard]] bool HasAnyConnection() const; void Cleanup(); void SetEffectCount(std::size_t count); void ResetEffectProcessingOrder(); - s32 GetEffectOrder(std::size_t i) const; + [[nodiscard]] s32 GetEffectOrder(std::size_t i) const; private: std::vector effect_processing_order; @@ -91,15 +91,15 @@ public: void SortInfo(); bool TsortInfo(SplitterContext& splitter_context); - std::size_t GetCount() const; - ServerMixInfo& GetInfo(std::size_t i); - const ServerMixInfo& GetInfo(std::size_t i) const; - ServerMixInfo& GetSortedInfo(std::size_t i); - const ServerMixInfo& GetSortedInfo(std::size_t i) const; - ServerMixInfo& GetFinalMixInfo(); - const ServerMixInfo& GetFinalMixInfo() const; - EdgeMatrix& GetEdgeMatrix(); - const EdgeMatrix& GetEdgeMatrix() const; + [[nodiscard]] std::size_t GetCount() const; + [[nodiscard]] ServerMixInfo& GetInfo(std::size_t i); + [[nodiscard]] const ServerMixInfo& GetInfo(std::size_t i) const; + [[nodiscard]] ServerMixInfo& GetSortedInfo(std::size_t i); + [[nodiscard]] const ServerMixInfo& GetSortedInfo(std::size_t i) const; + [[nodiscard]] ServerMixInfo& GetFinalMixInfo(); + [[nodiscard]] const ServerMixInfo& GetFinalMixInfo() const; + [[nodiscard]] EdgeMatrix& GetEdgeMatrix(); + [[nodiscard]] const EdgeMatrix& GetEdgeMatrix() const; private: void CalcMixBufferOffset(); diff --git a/src/audio_core/sink_context.cpp b/src/audio_core/sink_context.cpp index 0882b411a..a69543696 100644 --- a/src/audio_core/sink_context.cpp +++ b/src/audio_core/sink_context.cpp @@ -5,17 +5,23 @@ #include "audio_core/sink_context.h" namespace AudioCore { -SinkContext::SinkContext(std::size_t sink_count) : sink_count(sink_count) {} +SinkContext::SinkContext(std::size_t sink_count_) : sink_count{sink_count_} {} SinkContext::~SinkContext() = default; std::size_t SinkContext::GetCount() const { return sink_count; } -void SinkContext::UpdateMainSink(SinkInfo::InParams& in) { +void SinkContext::UpdateMainSink(const SinkInfo::InParams& in) { + ASSERT(in.type == SinkTypes::Device); + + has_downmix_coefs = in.device.down_matrix_enabled; + if (has_downmix_coefs) { + downmix_coefficients = in.device.down_matrix_coef; + } in_use = in.in_use; use_count = in.device.input_count; - std::memcpy(buffers.data(), in.device.input.data(), AudioCommon::MAX_CHANNEL_COUNT); + buffers = in.device.input; } bool SinkContext::InUse() const { @@ -28,4 +34,12 @@ std::vector SinkContext::OutputBuffers() const { return buffer_ret; } +bool SinkContext::HasDownMixingCoefficients() const { + return has_downmix_coefs; +} + +const DownmixCoefficients& SinkContext::GetDownmixCoefficients() const { + return downmix_coefficients; +} + } // namespace AudioCore diff --git a/src/audio_core/sink_context.h b/src/audio_core/sink_context.h index d7aa72ba7..05541becb 100644 --- a/src/audio_core/sink_context.h +++ b/src/audio_core/sink_context.h @@ -11,6 +11,8 @@ namespace AudioCore { +using DownmixCoefficients = std::array; + enum class SinkTypes : u8 { Invalid = 0, Device = 1, @@ -40,7 +42,7 @@ public: bool in_use; INSERT_UNION_PADDING_BYTES(5); }; - static_assert(sizeof(SinkInfo::CircularBufferIn) == 0x28, + static_assert(sizeof(CircularBufferIn) == 0x28, "SinkInfo::CircularBufferIn is in invalid size"); struct DeviceIn { @@ -50,9 +52,9 @@ public: std::array input; INSERT_UNION_PADDING_BYTES(1); bool down_matrix_enabled; - std::array down_matrix_coef; + DownmixCoefficients down_matrix_coef; }; - static_assert(sizeof(SinkInfo::DeviceIn) == 0x11c, "SinkInfo::DeviceIn is an invalid size"); + static_assert(sizeof(DeviceIn) == 0x11c, "SinkInfo::DeviceIn is an invalid size"); struct InParams { SinkTypes type{}; @@ -62,28 +64,33 @@ public: INSERT_PADDING_WORDS(6); union { // std::array raw{}; - SinkInfo::DeviceIn device; - SinkInfo::CircularBufferIn circular_buffer; + DeviceIn device; + CircularBufferIn circular_buffer; }; }; - static_assert(sizeof(SinkInfo::InParams) == 0x140, "SinkInfo::InParams are an invalid size!"); + static_assert(sizeof(InParams) == 0x140, "SinkInfo::InParams are an invalid size!"); }; class SinkContext { public: - explicit SinkContext(std::size_t sink_count); + explicit SinkContext(std::size_t sink_count_); ~SinkContext(); - std::size_t GetCount() const; + [[nodiscard]] std::size_t GetCount() const; - void UpdateMainSink(SinkInfo::InParams& in); - bool InUse() const; - std::vector OutputBuffers() const; + void UpdateMainSink(const SinkInfo::InParams& in); + [[nodiscard]] bool InUse() const; + [[nodiscard]] std::vector OutputBuffers() const; + + [[nodiscard]] bool HasDownMixingCoefficients() const; + [[nodiscard]] const DownmixCoefficients& GetDownmixCoefficients() const; private: bool in_use{false}; s32 use_count{}; std::array buffers{}; std::size_t sink_count{}; + bool has_downmix_coefs{false}; + DownmixCoefficients downmix_coefficients{}; }; } // namespace AudioCore diff --git a/src/audio_core/splitter_context.cpp b/src/audio_core/splitter_context.cpp index 79bb2f516..f4bcd0391 100644 --- a/src/audio_core/splitter_context.cpp +++ b/src/audio_core/splitter_context.cpp @@ -10,7 +10,7 @@ namespace AudioCore { -ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id) : id(id) {} +ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id_) : id{id_} {} ServerSplitterDestinationData::~ServerSplitterDestinationData() = default; void ServerSplitterDestinationData::Update(SplitterInfo::InDestinationParams& header) { @@ -87,7 +87,7 @@ void ServerSplitterDestinationData::UpdateInternalState() { needs_update = false; } -ServerSplitterInfo::ServerSplitterInfo(s32 id) : id(id) {} +ServerSplitterInfo::ServerSplitterInfo(s32 id_) : id(id_) {} ServerSplitterInfo::~ServerSplitterInfo() = default; void ServerSplitterInfo::InitializeInfos() { @@ -121,7 +121,7 @@ const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const { } ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) { - auto current_head = head; + auto* current_head = head; for (std::size_t i = 0; i < depth; i++) { if (current_head == nullptr) { return nullptr; @@ -132,7 +132,7 @@ ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) { } const ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) const { - auto current_head = head; + auto* current_head = head; for (std::size_t i = 0; i < depth; i++) { if (current_head == nullptr) { return nullptr; @@ -245,7 +245,7 @@ ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t i const ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info, std::size_t data) const { ASSERT(info < info_count); - auto& cur_info = GetInfo(info); + const auto& cur_info = GetInfo(info); return cur_info.GetData(data); } @@ -267,11 +267,11 @@ std::size_t SplitterContext::GetDataCount() const { return data_count; } -void SplitterContext::Setup(std::size_t _info_count, std::size_t _data_count, +void SplitterContext::Setup(std::size_t info_count_, std::size_t data_count_, bool is_splitter_bug_fixed) { - info_count = _info_count; - data_count = _data_count; + info_count = info_count_; + data_count = data_count_; for (std::size_t i = 0; i < info_count; i++) { auto& splitter = infos.emplace_back(static_cast(i)); @@ -306,7 +306,7 @@ bool SplitterContext::UpdateInfo(const std::vector& input, std::size_t& inpu break; } - if (header.send_id < 0 || header.send_id > info_count) { + if (header.send_id < 0 || static_cast(header.send_id) > info_count) { LOG_ERROR(Audio, "Bad splitter data id"); break; } @@ -348,7 +348,7 @@ bool SplitterContext::UpdateData(const std::vector& input, std::size_t& inpu break; } - if (header.splitter_id < 0 || header.splitter_id > data_count) { + if (header.splitter_id < 0 || static_cast(header.splitter_id) > data_count) { LOG_ERROR(Audio, "Bad splitter data id"); break; } @@ -364,7 +364,7 @@ bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info, // Clear our current destinations auto* current_head = info.GetHead(); while (current_head != nullptr) { - auto next_head = current_head->GetNextDestination(); + auto* next_head = current_head->GetNextDestination(); current_head->SetNextDestination(nullptr); current_head = next_head; } @@ -434,7 +434,7 @@ const std::vector& NodeStates::GetIndexList() const { } void NodeStates::PushTsortResult(s32 index) { - ASSERT(index < node_count); + ASSERT(index < static_cast(node_count)); index_list[index_pos++] = index; } @@ -471,8 +471,8 @@ bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) { continue; } - const auto node_count = edge_matrix.GetNodeCount(); - for (s32 j = 0; j < static_cast(node_count); j++) { + const auto edge_node_count = edge_matrix.GetNodeCount(); + for (s32 j = 0; j < static_cast(edge_node_count); j++) { // Check if our node is connected to our edge matrix if (!edge_matrix.Connected(current_stack_index, j)) { continue; diff --git a/src/audio_core/splitter_context.h b/src/audio_core/splitter_context.h index ea6239fdb..b490627f5 100644 --- a/src/audio_core/splitter_context.h +++ b/src/audio_core/splitter_context.h @@ -63,7 +63,7 @@ public: NodeStates(); ~NodeStates(); - void Initialize(std::size_t _node_count); + void Initialize(std::size_t node_count_); bool Tsort(EdgeMatrix& edge_matrix); std::size_t GetIndexPos() const; const std::vector& GetIndexList() const; @@ -72,15 +72,15 @@ private: void PushTsortResult(s32 index); bool DepthFirstSearch(EdgeMatrix& edge_matrix); void ResetState(); - void UpdateState(NodeStates::State state, std::size_t i); - NodeStates::State GetState(std::size_t i); + void UpdateState(State state, std::size_t i); + State GetState(std::size_t i); std::size_t node_count{}; std::vector was_node_found{}; std::vector was_node_completed{}; std::size_t index_pos{}; std::vector index_list{}; - NodeStates::Stack index_stack{}; + Stack index_stack{}; }; enum class SplitterMagic : u32_le { @@ -97,8 +97,7 @@ public: s32_le data_count{}; INSERT_PADDING_WORDS(5); }; - static_assert(sizeof(SplitterInfo::InHeader) == 0x20, - "SplitterInfo::InHeader is an invalid size"); + static_assert(sizeof(InHeader) == 0x20, "SplitterInfo::InHeader is an invalid size"); struct InInfoPrams { SplitterMagic magic{}; @@ -107,8 +106,7 @@ public: s32_le length{}; s32_le resource_id_base{}; }; - static_assert(sizeof(SplitterInfo::InInfoPrams) == 0x14, - "SplitterInfo::InInfoPrams is an invalid size"); + static_assert(sizeof(InInfoPrams) == 0x14, "SplitterInfo::InInfoPrams is an invalid size"); struct InDestinationParams { SplitterMagic magic{}; @@ -118,13 +116,13 @@ public: bool in_use{}; INSERT_PADDING_BYTES(3); }; - static_assert(sizeof(SplitterInfo::InDestinationParams) == 0x70, + static_assert(sizeof(InDestinationParams) == 0x70, "SplitterInfo::InDestinationParams is an invalid size"); }; class ServerSplitterDestinationData { public: - explicit ServerSplitterDestinationData(s32 id); + explicit ServerSplitterDestinationData(s32 id_); ~ServerSplitterDestinationData(); void Update(SplitterInfo::InDestinationParams& header); @@ -153,7 +151,7 @@ private: class ServerSplitterInfo { public: - explicit ServerSplitterInfo(s32 id); + explicit ServerSplitterInfo(s32 id_); ~ServerSplitterInfo(); void InitializeInfos(); diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp index cb33926bc..afe68c9ed 100644 --- a/src/audio_core/stream.cpp +++ b/src/audio_core/stream.cpp @@ -12,7 +12,6 @@ #include "common/assert.h" #include "common/logging/log.h" #include "core/core_timing.h" -#include "core/core_timing_util.h" #include "core/settings.h" namespace AudioCore { @@ -32,10 +31,10 @@ u32 Stream::GetNumChannels() const { return {}; } -Stream::Stream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, Format format, - ReleaseCallback&& release_callback, SinkStream& sink_stream, std::string&& name_) - : sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)}, - sink_stream{sink_stream}, core_timing{core_timing}, name{std::move(name_)} { +Stream::Stream(Core::Timing::CoreTiming& core_timing_, u32 sample_rate_, Format format_, + ReleaseCallback&& release_callback_, SinkStream& sink_stream_, std::string&& name_) + : sample_rate{sample_rate_}, format{format_}, release_callback{std::move(release_callback_)}, + sink_stream{sink_stream_}, core_timing{core_timing_}, name{std::move(name_)} { release_event = Core::Timing::CreateEvent(name, [this](std::uintptr_t, std::chrono::nanoseconds ns_late) { ReleaseActiveBuffer(ns_late); @@ -123,7 +122,7 @@ bool Stream::QueueBuffer(BufferPtr&& buffer) { return false; } -bool Stream::ContainsBuffer(Buffer::Tag tag) const { +bool Stream::ContainsBuffer([[maybe_unused]] Buffer::Tag tag) const { UNIMPLEMENTED(); return {}; } @@ -131,7 +130,25 @@ bool Stream::ContainsBuffer(Buffer::Tag tag) const { std::vector Stream::GetTagsAndReleaseBuffers(std::size_t max_count) { std::vector tags; for (std::size_t count = 0; count < max_count && !released_buffers.empty(); ++count) { - tags.push_back(released_buffers.front()->GetTag()); + if (released_buffers.front()) { + tags.push_back(released_buffers.front()->GetTag()); + } else { + ASSERT_MSG(false, "Invalid tag in released_buffers!"); + } + released_buffers.pop(); + } + return tags; +} + +std::vector Stream::GetTagsAndReleaseBuffers() { + std::vector tags; + tags.reserve(released_buffers.size()); + while (!released_buffers.empty()) { + if (released_buffers.front()) { + tags.push_back(released_buffers.front()->GetTag()); + } else { + ASSERT_MSG(false, "Invalid tag in released_buffers!"); + } released_buffers.pop(); } return tags; diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h index 6437b8591..506ac536b 100644 --- a/src/audio_core/stream.h +++ b/src/audio_core/stream.h @@ -44,8 +44,8 @@ public: /// Callback function type, used to change guest state on a buffer being released using ReleaseCallback = std::function; - Stream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, Format format, - ReleaseCallback&& release_callback, SinkStream& sink_stream, std::string&& name_); + Stream(Core::Timing::CoreTiming& core_timing_, u32 sample_rate_, Format format_, + ReleaseCallback&& release_callback_, SinkStream& sink_stream_, std::string&& name_); /// Plays the audio stream void Play(); @@ -57,37 +57,40 @@ public: bool QueueBuffer(BufferPtr&& buffer); /// Returns true if the audio stream contains a buffer with the specified tag - bool ContainsBuffer(Buffer::Tag tag) const; + [[nodiscard]] bool ContainsBuffer(Buffer::Tag tag) const; /// Returns a vector of recently released buffers specified by tag - std::vector GetTagsAndReleaseBuffers(std::size_t max_count); + [[nodiscard]] std::vector GetTagsAndReleaseBuffers(std::size_t max_count); + + /// Returns a vector of all recently released buffers specified by tag + [[nodiscard]] std::vector GetTagsAndReleaseBuffers(); void SetVolume(float volume); - float GetVolume() const { + [[nodiscard]] float GetVolume() const { return game_volume; } /// Returns true if the stream is currently playing - bool IsPlaying() const { + [[nodiscard]] bool IsPlaying() const { return state == State::Playing; } /// Returns the number of queued buffers - std::size_t GetQueueSize() const { + [[nodiscard]] std::size_t GetQueueSize() const { return queued_buffers.size(); } /// Gets the sample rate - u32 GetSampleRate() const { + [[nodiscard]] u32 GetSampleRate() const { return sample_rate; } /// Gets the number of channels - u32 GetNumChannels() const; + [[nodiscard]] u32 GetNumChannels() const; /// Get the state - State GetState() const; + [[nodiscard]] State GetState() const; private: /// Plays the next queued buffer in the audio stream, starting playback if necessary @@ -97,7 +100,7 @@ private: void ReleaseActiveBuffer(std::chrono::nanoseconds ns_late = {}); /// Gets the number of core cycles when the specified buffer will be released - std::chrono::nanoseconds GetBufferReleaseNS(const Buffer& buffer) const; + [[nodiscard]] std::chrono::nanoseconds GetBufferReleaseNS(const Buffer& buffer) const; u32 sample_rate; ///< Sample rate of the stream Format format; ///< Format of the stream diff --git a/src/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp index 1d8f69844..867b8fc6b 100644 --- a/src/audio_core/voice_context.cpp +++ b/src/audio_core/voice_context.cpp @@ -8,7 +8,7 @@ namespace AudioCore { -ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id) : id(id) {} +ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id_) : id(id_) {} ServerVoiceChannelResource::~ServerVoiceChannelResource() = default; bool ServerVoiceChannelResource::InUse() const { @@ -128,7 +128,10 @@ void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in, in_params.wave_buffer_count = voice_in.wave_buffer_count; in_params.wave_bufffer_head = voice_in.wave_buffer_head; if (behavior_info.IsFlushVoiceWaveBuffersSupported()) { - in_params.wave_buffer_flush_request_count += voice_in.wave_buffer_flush_request_count; + const auto in_request_count = in_params.wave_buffer_flush_request_count; + const auto voice_request_count = voice_in.wave_buffer_flush_request_count; + in_params.wave_buffer_flush_request_count = + static_cast(in_request_count + voice_request_count); } in_params.mix_id = voice_in.mix_id; if (behavior_info.IsSplitterSupported()) { @@ -206,7 +209,8 @@ void ServerVoiceInfo::UpdateWaveBuffers( void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, const WaveBuffer& in_wave_buffer, SampleFormat sample_format, - bool is_buffer_valid, BehaviorInfo& behavior_info) { + bool is_buffer_valid, + [[maybe_unused]] BehaviorInfo& behavior_info) { if (!is_buffer_valid && out_wavebuffer.sent_to_dsp) { out_wavebuffer.buffer_address = 0; out_wavebuffer.buffer_size = 0; @@ -397,7 +401,7 @@ bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const { return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end(); } -VoiceContext::VoiceContext(std::size_t voice_count) : voice_count(voice_count) { +VoiceContext::VoiceContext(std::size_t voice_count_) : voice_count{voice_count_} { for (std::size_t i = 0; i < voice_count; i++) { voice_channel_resources.emplace_back(static_cast(i)); sorted_voice_info.push_back(&voice_info.emplace_back()); @@ -488,11 +492,11 @@ s32 VoiceContext::DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, // Fast path if (channel_count == 1) { - for (std::size_t i = 0; i < samples_processed; i++) { + for (std::ptrdiff_t i = 0; i < samples_processed; i++) { output_buffer[i] = buffer_data[i]; } } else { - for (std::size_t i = 0; i < samples_processed; i++) { + for (std::ptrdiff_t i = 0; i < samples_processed; i++) { output_buffer[i] = buffer_data[i * channel_count + channel]; } } diff --git a/src/audio_core/voice_context.h b/src/audio_core/voice_context.h index 59d3d7dfb..863248761 100644 --- a/src/audio_core/voice_context.h +++ b/src/audio_core/voice_context.h @@ -118,12 +118,12 @@ public: bool in_use{}; INSERT_PADDING_BYTES(11); }; - static_assert(sizeof(VoiceChannelResource::InParams) == 0x70, "InParams is an invalid size"); + static_assert(sizeof(InParams) == 0x70, "InParams is an invalid size"); }; class ServerVoiceChannelResource { public: - explicit ServerVoiceChannelResource(s32 id); + explicit ServerVoiceChannelResource(s32 id_); ~ServerVoiceChannelResource(); bool InUse() const; @@ -174,7 +174,7 @@ public: BehaviorFlags behavior_flags{}; INSERT_PADDING_BYTES(16); }; - static_assert(sizeof(VoiceInfo::InParams) == 0x170, "InParams is an invalid size"); + static_assert(sizeof(InParams) == 0x170, "InParams is an invalid size"); struct OutParams { u64_le played_sample_count{}; @@ -182,7 +182,7 @@ public: u8 voice_dropped{}; INSERT_PADDING_BYTES(3); }; - static_assert(sizeof(VoiceInfo::OutParams) == 0x10, "OutParams is an invalid size"); + static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size"); }; class ServerVoiceInfo { @@ -263,7 +263,7 @@ private: class VoiceContext { public: - VoiceContext(std::size_t voice_count); + explicit VoiceContext(std::size_t voice_count_); ~VoiceContext(); std::size_t GetVoiceCount() const; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 5d54516eb..2c2bd2ee8 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -102,7 +102,9 @@ add_library(common STATIC atomic_ops.h detached_tasks.cpp detached_tasks.h + bit_cast.h bit_field.h + bit_set.h bit_util.h cityhash.cpp cityhash.h @@ -111,6 +113,7 @@ add_library(common STATIC common_paths.h common_types.h concepts.h + div_ceil.h dynamic_library.cpp dynamic_library.h fiber.cpp @@ -132,13 +135,10 @@ add_library(common STATIC math_util.h memory_detect.cpp memory_detect.h - memory_hook.cpp - memory_hook.h microprofile.cpp microprofile.h microprofileui.h misc.cpp - multi_level_queue.h page_table.cpp page_table.h param_package.cpp @@ -150,6 +150,8 @@ add_library(common STATIC scope_exit.h spin_lock.cpp spin_lock.h + stream.cpp + stream.h string_util.cpp string_util.h swap.h @@ -158,6 +160,8 @@ add_library(common STATIC thread.cpp thread.h thread_queue_list.h + thread_worker.cpp + thread_worker.h threadsafe_queue.h time_zone.cpp time_zone.h @@ -188,8 +192,28 @@ if(ARCHITECTURE_x86_64) ) endif() +if (MSVC) + target_compile_definitions(common PRIVATE + # The standard library doesn't provide any replacement for codecvt yet + # so we can disable this deprecation warning for the time being. + _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING + ) + target_compile_options(common PRIVATE + /W4 + /WX + ) +else() + target_compile_options(common PRIVATE + -Werror + ) +endif() + create_target_directory_groups(common) -find_package(Boost 1.71 COMPONENTS context headers REQUIRED) target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile) -target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd xbyak) +target_link_libraries(common PRIVATE lz4::lz4 xbyak) +if (MSVC) + target_link_libraries(common PRIVATE zstd::zstd) +else() + target_link_libraries(common PRIVATE zstd) +endif() diff --git a/src/common/bit_cast.h b/src/common/bit_cast.h new file mode 100644 index 000000000..a32a063d1 --- /dev/null +++ b/src/common/bit_cast.h @@ -0,0 +1,22 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +namespace Common { + +template +[[nodiscard]] std::enable_if_t && + std::is_trivially_copyable_v, + To> +BitCast(const From& src) noexcept { + To dst; + std::memcpy(&dst, &src, sizeof(To)); + return dst; +} + +} // namespace Common diff --git a/src/common/bit_set.h b/src/common/bit_set.h new file mode 100644 index 000000000..9235ad412 --- /dev/null +++ b/src/common/bit_set.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +#include "common/alignment.h" +#include "common/bit_util.h" +#include "common/common_types.h" + +namespace Common { + +namespace impl { + +template +class BitSet { + +public: + constexpr BitSet() = default; + + constexpr void SetBit(size_t i) { + this->words[i / FlagsPerWord] |= GetBitMask(i % FlagsPerWord); + } + + constexpr void ClearBit(size_t i) { + this->words[i / FlagsPerWord] &= ~GetBitMask(i % FlagsPerWord); + } + + constexpr size_t CountLeadingZero() const { + for (size_t i = 0; i < NumWords; i++) { + if (this->words[i]) { + return FlagsPerWord * i + CountLeadingZeroImpl(this->words[i]); + } + } + return FlagsPerWord * NumWords; + } + + constexpr size_t GetNextSet(size_t n) const { + for (size_t i = (n + 1) / FlagsPerWord; i < NumWords; i++) { + Storage word = this->words[i]; + if (!IsAligned(n + 1, FlagsPerWord)) { + word &= GetBitMask(n % FlagsPerWord) - 1; + } + if (word) { + return FlagsPerWord * i + CountLeadingZeroImpl(word); + } + } + return FlagsPerWord * NumWords; + } + +private: + static_assert(std::is_unsigned_v); + static_assert(sizeof(Storage) <= sizeof(u64)); + + static constexpr size_t FlagsPerWord = BitSize(); + static constexpr size_t NumWords = AlignUp(N, FlagsPerWord) / FlagsPerWord; + + static constexpr auto CountLeadingZeroImpl(Storage word) { + return std::countl_zero(static_cast(word)) - + (BitSize() - FlagsPerWord); + } + + static constexpr Storage GetBitMask(size_t bit) { + return Storage(1) << (FlagsPerWord - 1 - bit); + } + + std::array words{}; +}; + +} // namespace impl + +template +using BitSet8 = impl::BitSet; + +template +using BitSet16 = impl::BitSet; + +template +using BitSet32 = impl::BitSet; + +template +using BitSet64 = impl::BitSet; + +} // namespace Common diff --git a/src/common/concepts.h b/src/common/concepts.h index 5bef3ad67..aa08065a7 100644 --- a/src/common/concepts.h +++ b/src/common/concepts.h @@ -31,4 +31,8 @@ concept DerivedFrom = requires { std::is_convertible_v; }; +// TODO: Replace with std::convertible_to when libc++ implements it. +template +concept ConvertibleTo = std::is_convertible_v; + } // namespace Common diff --git a/src/common/div_ceil.h b/src/common/div_ceil.h new file mode 100644 index 000000000..95e1489a9 --- /dev/null +++ b/src/common/div_ceil.h @@ -0,0 +1,26 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +namespace Common { + +/// Ceiled integer division. +template +requires std::is_integral_v&& std::is_unsigned_v[[nodiscard]] constexpr N DivCeil(N number, + D divisor) { + return static_cast((static_cast(number) + divisor - 1) / divisor); +} + +/// Ceiled integer division with logarithmic divisor in base 2 +template +requires std::is_integral_v&& std::is_unsigned_v[[nodiscard]] constexpr N DivCeilLog2( + N value, D alignment_log2) { + return static_cast((static_cast(value) + (D(1) << alignment_log2) - 1) >> alignment_log2); +} + +} // namespace Common diff --git a/src/common/fiber.cpp b/src/common/fiber.cpp index 1c1d09ccb..3c1eefcb7 100644 --- a/src/common/fiber.cpp +++ b/src/common/fiber.cpp @@ -4,129 +4,51 @@ #include "common/assert.h" #include "common/fiber.h" -#if defined(_WIN32) || defined(WIN32) -#include -#else +#include "common/spin_lock.h" +#include "common/virtual_buffer.h" + #include -#endif namespace Common { -constexpr std::size_t default_stack_size = 256 * 1024; // 256kb - -#if defined(_WIN32) || defined(WIN32) +constexpr std::size_t default_stack_size = 256 * 1024; struct Fiber::FiberImpl { - LPVOID handle = nullptr; - LPVOID rewind_handle = nullptr; + FiberImpl() : stack{default_stack_size}, rewind_stack{default_stack_size} {} + + VirtualBuffer stack; + VirtualBuffer rewind_stack; + + SpinLock guard{}; + std::function entry_point; + std::function rewind_point; + void* rewind_parameter{}; + void* start_parameter{}; + std::shared_ptr previous_fiber; + bool is_thread_fiber{}; + bool released{}; + + u8* stack_limit{}; + u8* rewind_stack_limit{}; + boost::context::detail::fcontext_t context{}; + boost::context::detail::fcontext_t rewind_context{}; }; -void Fiber::Start() { - ASSERT(previous_fiber != nullptr); - previous_fiber->guard.unlock(); - previous_fiber.reset(); - entry_point(start_parameter); - UNREACHABLE(); +void Fiber::SetStartParameter(void* new_parameter) { + impl->start_parameter = new_parameter; } -void Fiber::OnRewind() { - ASSERT(impl->handle != nullptr); - DeleteFiber(impl->handle); - impl->handle = impl->rewind_handle; - impl->rewind_handle = nullptr; - rewind_point(rewind_parameter); - UNREACHABLE(); +void Fiber::SetRewindPoint(std::function&& rewind_func, void* rewind_param) { + impl->rewind_point = std::move(rewind_func); + impl->rewind_parameter = rewind_param; } -void Fiber::FiberStartFunc(void* fiber_parameter) { - auto fiber = static_cast(fiber_parameter); - fiber->Start(); -} - -void Fiber::RewindStartFunc(void* fiber_parameter) { - auto fiber = static_cast(fiber_parameter); - fiber->OnRewind(); -} - -Fiber::Fiber(std::function&& entry_point_func, void* start_parameter) - : entry_point{std::move(entry_point_func)}, start_parameter{start_parameter} { - impl = std::make_unique(); - impl->handle = CreateFiber(default_stack_size, &FiberStartFunc, this); -} - -Fiber::Fiber() : impl{std::make_unique()} {} - -Fiber::~Fiber() { - if (released) { - return; - } - // Make sure the Fiber is not being used - const bool locked = guard.try_lock(); - ASSERT_MSG(locked, "Destroying a fiber that's still running"); - if (locked) { - guard.unlock(); - } - DeleteFiber(impl->handle); -} - -void Fiber::Exit() { - ASSERT_MSG(is_thread_fiber, "Exitting non main thread fiber"); - if (!is_thread_fiber) { - return; - } - ConvertFiberToThread(); - guard.unlock(); - released = true; -} - -void Fiber::SetRewindPoint(std::function&& rewind_func, void* start_parameter) { - rewind_point = std::move(rewind_func); - rewind_parameter = start_parameter; -} - -void Fiber::Rewind() { - ASSERT(rewind_point); - ASSERT(impl->rewind_handle == nullptr); - impl->rewind_handle = CreateFiber(default_stack_size, &RewindStartFunc, this); - SwitchToFiber(impl->rewind_handle); -} - -void Fiber::YieldTo(std::shared_ptr& from, std::shared_ptr& to) { - ASSERT_MSG(from != nullptr, "Yielding fiber is null!"); - ASSERT_MSG(to != nullptr, "Next fiber is null!"); - to->guard.lock(); - to->previous_fiber = from; - SwitchToFiber(to->impl->handle); - ASSERT(from->previous_fiber != nullptr); - from->previous_fiber->guard.unlock(); - from->previous_fiber.reset(); -} - -std::shared_ptr Fiber::ThreadToFiber() { - std::shared_ptr fiber = std::shared_ptr{new Fiber()}; - fiber->guard.lock(); - fiber->impl->handle = ConvertThreadToFiber(nullptr); - fiber->is_thread_fiber = true; - return fiber; -} - -#else - -struct Fiber::FiberImpl { - alignas(64) std::array stack; - alignas(64) std::array rewind_stack; - u8* stack_limit; - u8* rewind_stack_limit; - boost::context::detail::fcontext_t context; - boost::context::detail::fcontext_t rewind_context; -}; - void Fiber::Start(boost::context::detail::transfer_t& transfer) { - ASSERT(previous_fiber != nullptr); - previous_fiber->impl->context = transfer.fctx; - previous_fiber->guard.unlock(); - previous_fiber.reset(); - entry_point(start_parameter); + ASSERT(impl->previous_fiber != nullptr); + impl->previous_fiber->impl->context = transfer.fctx; + impl->previous_fiber->impl->guard.unlock(); + impl->previous_fiber.reset(); + impl->entry_point(impl->start_parameter); UNREACHABLE(); } @@ -137,23 +59,24 @@ void Fiber::OnRewind([[maybe_unused]] boost::context::detail::transfer_t& transf u8* tmp = impl->stack_limit; impl->stack_limit = impl->rewind_stack_limit; impl->rewind_stack_limit = tmp; - rewind_point(rewind_parameter); + impl->rewind_point(impl->rewind_parameter); UNREACHABLE(); } void Fiber::FiberStartFunc(boost::context::detail::transfer_t transfer) { - auto fiber = static_cast(transfer.data); + auto* fiber = static_cast(transfer.data); fiber->Start(transfer); } void Fiber::RewindStartFunc(boost::context::detail::transfer_t transfer) { - auto fiber = static_cast(transfer.data); + auto* fiber = static_cast(transfer.data); fiber->OnRewind(transfer); } Fiber::Fiber(std::function&& entry_point_func, void* start_parameter) - : entry_point{std::move(entry_point_func)}, start_parameter{start_parameter} { - impl = std::make_unique(); + : impl{std::make_unique()} { + impl->entry_point = std::move(entry_point_func); + impl->start_parameter = start_parameter; impl->stack_limit = impl->stack.data(); impl->rewind_stack_limit = impl->rewind_stack.data(); u8* stack_base = impl->stack_limit + default_stack_size; @@ -161,37 +84,31 @@ Fiber::Fiber(std::function&& entry_point_func, void* start_paramete boost::context::detail::make_fcontext(stack_base, impl->stack.size(), FiberStartFunc); } -void Fiber::SetRewindPoint(std::function&& rewind_func, void* start_parameter) { - rewind_point = std::move(rewind_func); - rewind_parameter = start_parameter; -} - Fiber::Fiber() : impl{std::make_unique()} {} Fiber::~Fiber() { - if (released) { + if (impl->released) { return; } // Make sure the Fiber is not being used - const bool locked = guard.try_lock(); + const bool locked = impl->guard.try_lock(); ASSERT_MSG(locked, "Destroying a fiber that's still running"); if (locked) { - guard.unlock(); + impl->guard.unlock(); } } void Fiber::Exit() { - - ASSERT_MSG(is_thread_fiber, "Exitting non main thread fiber"); - if (!is_thread_fiber) { + ASSERT_MSG(impl->is_thread_fiber, "Exitting non main thread fiber"); + if (!impl->is_thread_fiber) { return; } - guard.unlock(); - released = true; + impl->guard.unlock(); + impl->released = true; } void Fiber::Rewind() { - ASSERT(rewind_point); + ASSERT(impl->rewind_point); ASSERT(impl->rewind_context == nullptr); u8* stack_base = impl->rewind_stack_limit + default_stack_size; impl->rewind_context = @@ -199,24 +116,23 @@ void Fiber::Rewind() { boost::context::detail::jump_fcontext(impl->rewind_context, this); } -void Fiber::YieldTo(std::shared_ptr& from, std::shared_ptr& to) { +void Fiber::YieldTo(std::shared_ptr from, std::shared_ptr to) { ASSERT_MSG(from != nullptr, "Yielding fiber is null!"); ASSERT_MSG(to != nullptr, "Next fiber is null!"); - to->guard.lock(); - to->previous_fiber = from; + to->impl->guard.lock(); + to->impl->previous_fiber = from; auto transfer = boost::context::detail::jump_fcontext(to->impl->context, to.get()); - ASSERT(from->previous_fiber != nullptr); - from->previous_fiber->impl->context = transfer.fctx; - from->previous_fiber->guard.unlock(); - from->previous_fiber.reset(); + ASSERT(from->impl->previous_fiber != nullptr); + from->impl->previous_fiber->impl->context = transfer.fctx; + from->impl->previous_fiber->impl->guard.unlock(); + from->impl->previous_fiber.reset(); } std::shared_ptr Fiber::ThreadToFiber() { std::shared_ptr fiber = std::shared_ptr{new Fiber()}; - fiber->guard.lock(); - fiber->is_thread_fiber = true; + fiber->impl->guard.lock(); + fiber->impl->is_thread_fiber = true; return fiber; } -#endif } // namespace Common diff --git a/src/common/fiber.h b/src/common/fiber.h index 89dde5e36..f7f587f8c 100644 --- a/src/common/fiber.h +++ b/src/common/fiber.h @@ -7,14 +7,9 @@ #include #include -#include "common/common_types.h" -#include "common/spin_lock.h" - -#if !defined(_WIN32) && !defined(WIN32) namespace boost::context::detail { struct transfer_t; } -#endif namespace Common { @@ -46,10 +41,10 @@ public: /// Yields control from Fiber 'from' to Fiber 'to' /// Fiber 'from' must be the currently running fiber. - static void YieldTo(std::shared_ptr& from, std::shared_ptr& to); + static void YieldTo(std::shared_ptr from, std::shared_ptr to); [[nodiscard]] static std::shared_ptr ThreadToFiber(); - void SetRewindPoint(std::function&& rewind_func, void* start_parameter); + void SetRewindPoint(std::function&& rewind_func, void* rewind_param); void Rewind(); @@ -57,36 +52,18 @@ public: void Exit(); /// Changes the start parameter of the fiber. Has no effect if the fiber already started - void SetStartParameter(void* new_parameter) { - start_parameter = new_parameter; - } + void SetStartParameter(void* new_parameter); private: Fiber(); -#if defined(_WIN32) || defined(WIN32) - void OnRewind(); - void Start(); - static void FiberStartFunc(void* fiber_parameter); - static void RewindStartFunc(void* fiber_parameter); -#else void OnRewind(boost::context::detail::transfer_t& transfer); void Start(boost::context::detail::transfer_t& transfer); static void FiberStartFunc(boost::context::detail::transfer_t transfer); static void RewindStartFunc(boost::context::detail::transfer_t transfer); -#endif struct FiberImpl; - - SpinLock guard{}; - std::function entry_point; - std::function rewind_point; - void* rewind_parameter{}; - void* start_parameter{}; - std::shared_ptr previous_fiber; std::unique_ptr impl; - bool is_thread_fiber{}; - bool released{}; }; } // namespace Common diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 16c3713e0..18fbfa25b 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -472,13 +472,14 @@ u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry, } bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) { - const auto callback = [recursion](u64* num_entries_out, const std::string& directory, - const std::string& virtual_name) -> bool { - std::string new_path = directory + DIR_SEP_CHR + virtual_name; + const auto callback = [recursion](u64*, const std::string& directory, + const std::string& virtual_name) { + const std::string new_path = directory + DIR_SEP_CHR + virtual_name; if (IsDirectory(new_path)) { - if (recursion == 0) + if (recursion == 0) { return false; + } return DeleteDirRecursively(new_path, recursion - 1); } return Delete(new_path); @@ -492,7 +493,8 @@ bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) return true; } -void CopyDir(const std::string& source_path, const std::string& dest_path) { +void CopyDir([[maybe_unused]] const std::string& source_path, + [[maybe_unused]] const std::string& dest_path) { #ifndef _WIN32 if (source_path == dest_path) { return; @@ -553,7 +555,7 @@ std::optional GetCurrentDir() { std::string strDir = dir; #endif free(dir); - return std::move(strDir); + return strDir; } bool SetCurrentDir(const std::string& directory) { @@ -772,21 +774,23 @@ std::size_t ReadFileToString(bool text_file, const std::string& filename, std::s void SplitFilename83(const std::string& filename, std::array& short_name, std::array& extension) { - const std::string forbidden_characters = ".\"/\\[]:;=, "; + static constexpr std::string_view forbidden_characters = ".\"/\\[]:;=, "; // On a FAT32 partition, 8.3 names are stored as a 11 bytes array, filled with spaces. short_name = {{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\0'}}; extension = {{' ', ' ', ' ', '\0'}}; - std::string::size_type point = filename.rfind('.'); - if (point == filename.size() - 1) + auto point = filename.rfind('.'); + if (point == filename.size() - 1) { point = filename.rfind('.', point); + } // Get short name. int j = 0; for (char letter : filename.substr(0, point)) { - if (forbidden_characters.find(letter, 0) != std::string::npos) + if (forbidden_characters.find(letter, 0) != std::string::npos) { continue; + } if (j == 8) { // TODO(Link Mauve): also do that for filenames containing a space. // TODO(Link Mauve): handle multiple files having the same short name. @@ -794,14 +798,15 @@ void SplitFilename83(const std::string& filename, std::array& short_nam short_name[7] = '1'; break; } - short_name[j++] = toupper(letter); + short_name[j++] = static_cast(std::toupper(letter)); } // Get extension. if (point != std::string::npos) { j = 0; - for (char letter : filename.substr(point + 1, 3)) - extension[j++] = toupper(letter); + for (char letter : filename.substr(point + 1, 3)) { + extension[j++] = static_cast(std::toupper(letter)); + } } } diff --git a/src/common/file_util.h b/src/common/file_util.h index 8b587320f..840cde2a6 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -232,7 +232,7 @@ public: void Swap(IOFile& other) noexcept; - [[nodiscard]] bool Open(const std::string& filename, const char openmode[], int flags = 0); + bool Open(const std::string& filename, const char openmode[], int flags = 0); bool Close(); template diff --git a/src/common/hex_util.h b/src/common/hex_util.h index 120f1a5e6..a8d414fb8 100644 --- a/src/common/hex_util.h +++ b/src/common/hex_util.h @@ -16,14 +16,14 @@ namespace Common { [[nodiscard]] constexpr u8 ToHexNibble(char c) { if (c >= 65 && c <= 70) { - return c - 55; + return static_cast(c - 55); } if (c >= 97 && c <= 102) { - return c - 87; + return static_cast(c - 87); } - return c - 48; + return static_cast(c - 48); } [[nodiscard]] std::vector HexStringToVector(std::string_view str, bool little_endian); @@ -33,11 +33,11 @@ template std::array out{}; if constexpr (le) { for (std::size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2) { - out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]); + out[i / 2] = static_cast((ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1])); } } else { for (std::size_t i = 0; i < 2 * Size; i += 2) { - out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]); + out[i / 2] = static_cast((ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1])); } } return out; diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 62cfde397..631f64d05 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -23,6 +23,7 @@ #include "common/logging/text_formatter.h" #include "common/string_util.h" #include "common/threadsafe_queue.h" +#include "core/settings.h" namespace Log { @@ -152,10 +153,19 @@ FileBackend::FileBackend(const std::string& filename) void FileBackend::Write(const Entry& entry) { // prevent logs from going over the maximum size (in case its spamming and the user doesn't // know) - constexpr std::size_t MAX_BYTES_WRITTEN = 50 * 1024L * 1024L; - if (!file.IsOpen() || bytes_written > MAX_BYTES_WRITTEN) { + constexpr std::size_t MAX_BYTES_WRITTEN = 100 * 1024 * 1024; + constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1024 * 1024 * 1024; + + if (!file.IsOpen()) { return; } + + if (Settings::values.extended_logging && bytes_written > MAX_BYTES_WRITTEN_EXTENDED) { + return; + } else if (!Settings::values.extended_logging && bytes_written > MAX_BYTES_WRITTEN) { + return; + } + bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n')); if (entry.log_level >= Level::Error) { file.Flush(); @@ -222,6 +232,7 @@ void DebuggerBackend::Write(const Entry& entry) { SUB(Service, NPNS) \ SUB(Service, NS) \ SUB(Service, NVDRV) \ + SUB(Service, OLSC) \ SUB(Service, PCIE) \ SUB(Service, PCTL) \ SUB(Service, PCV) \ @@ -274,7 +285,6 @@ const char* GetLogClassName(Class log_class) { case Class::Count: break; } - UNREACHABLE(); return "Invalid"; } @@ -293,7 +303,6 @@ const char* GetLevelName(Level log_level) { break; } #undef LVL - UNREACHABLE(); return "Invalid"; } diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 13a4f1e30..835894918 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -95,6 +95,7 @@ enum class Class : ClassType { Service_NPNS, ///< The NPNS service Service_NS, ///< The NS services Service_NVDRV, ///< The NVDRV (Nvidia driver) service + Service_OLSC, ///< The OLSC service Service_PCIE, ///< The PCIe service Service_PCTL, ///< The PCTL (Parental control) service Service_PCV, ///< The PCV service diff --git a/src/common/math_util.h b/src/common/math_util.h index b35ad8507..4c38d8040 100644 --- a/src/common/math_util.h +++ b/src/common/math_util.h @@ -20,14 +20,14 @@ struct Rectangle { constexpr Rectangle() = default; - constexpr Rectangle(T left, T top, T right, T bottom) - : left(left), top(top), right(right), bottom(bottom) {} + constexpr Rectangle(T left_, T top_, T right_, T bottom_) + : left(left_), top(top_), right(right_), bottom(bottom_) {} [[nodiscard]] T GetWidth() const { if constexpr (std::is_floating_point_v) { return std::abs(right - left); } else { - return std::abs(static_cast>(right - left)); + return static_cast(std::abs(static_cast>(right - left))); } } @@ -35,7 +35,7 @@ struct Rectangle { if constexpr (std::is_floating_point_v) { return std::abs(bottom - top); } else { - return std::abs(static_cast>(bottom - top)); + return static_cast(std::abs(static_cast>(bottom - top))); } } diff --git a/src/common/memory_hook.cpp b/src/common/memory_hook.cpp deleted file mode 100644 index 3986986d6..000000000 --- a/src/common/memory_hook.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/memory_hook.h" - -namespace Common { - -MemoryHook::~MemoryHook() = default; - -} // namespace Common diff --git a/src/common/memory_hook.h b/src/common/memory_hook.h deleted file mode 100644 index adaa4c2c5..000000000 --- a/src/common/memory_hook.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include - -#include "common/common_types.h" - -namespace Common { - -/** - * Memory hooks have two purposes: - * 1. To allow reads and writes to a region of memory to be intercepted. This is used to implement - * texture forwarding and memory breakpoints for debugging. - * 2. To allow for the implementation of MMIO devices. - * - * A hook may be mapped to multiple regions of memory. - * - * If a std::nullopt or false is returned from a function, the read/write request is passed through - * to the underlying memory region. - */ -class MemoryHook { -public: - virtual ~MemoryHook(); - - virtual std::optional IsValidAddress(VAddr addr) = 0; - - virtual std::optional Read8(VAddr addr) = 0; - virtual std::optional Read16(VAddr addr) = 0; - virtual std::optional Read32(VAddr addr) = 0; - virtual std::optional Read64(VAddr addr) = 0; - - virtual bool ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) = 0; - - virtual bool Write8(VAddr addr, u8 data) = 0; - virtual bool Write16(VAddr addr, u16 data) = 0; - virtual bool Write32(VAddr addr, u32 data) = 0; - virtual bool Write64(VAddr addr, u64 data) = 0; - - virtual bool WriteBlock(VAddr dest_addr, const void* src_buffer, std::size_t size) = 0; -}; - -using MemoryHookPointer = std::shared_ptr; -} // namespace Common diff --git a/src/common/misc.cpp b/src/common/misc.cpp index 68cb86cd1..1d5393597 100644 --- a/src/common/misc.cpp +++ b/src/common/misc.cpp @@ -16,16 +16,23 @@ // Call directly after the command or use the error num. // This function might change the error code. std::string GetLastErrorMsg() { - static const std::size_t buff_size = 255; + static constexpr std::size_t buff_size = 255; char err_str[buff_size]; #ifdef _WIN32 FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err_str, buff_size, nullptr); + return std::string(err_str, buff_size); +#elif defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600)) + // Thread safe (GNU-specific) + const char* str = strerror_r(errno, err_str, buff_size); + return std::string(str); #else // Thread safe (XSI-compliant) - strerror_r(errno, err_str, buff_size); + const int success = strerror_r(errno, err_str, buff_size); + if (success != 0) { + return {}; + } + return std::string(err_str); #endif - - return std::string(err_str, buff_size); } diff --git a/src/common/multi_level_queue.h b/src/common/multi_level_queue.h deleted file mode 100644 index 4b305bf40..000000000 --- a/src/common/multi_level_queue.h +++ /dev/null @@ -1,345 +0,0 @@ -// Copyright 2019 TuxSH -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include - -#include "common/bit_util.h" -#include "common/common_types.h" - -namespace Common { - -/** - * A MultiLevelQueue is a type of priority queue which has the following characteristics: - * - iteratable through each of its elements. - * - back can be obtained. - * - O(1) add, lookup (both front and back) - * - discrete priorities and a max of 64 priorities (limited domain) - * This type of priority queue is normaly used for managing threads within an scheduler - */ -template -class MultiLevelQueue { -public: - using value_type = T; - using reference = value_type&; - using const_reference = const value_type&; - using pointer = value_type*; - using const_pointer = const value_type*; - - using difference_type = typename std::pointer_traits::difference_type; - using size_type = std::size_t; - - template - class iterator_impl { - public: - using iterator_category = std::bidirectional_iterator_tag; - using value_type = T; - using pointer = std::conditional_t; - using reference = std::conditional_t; - using difference_type = typename std::pointer_traits::difference_type; - - friend bool operator==(const iterator_impl& lhs, const iterator_impl& rhs) { - if (lhs.IsEnd() && rhs.IsEnd()) - return true; - return std::tie(lhs.current_priority, lhs.it) == std::tie(rhs.current_priority, rhs.it); - } - - friend bool operator!=(const iterator_impl& lhs, const iterator_impl& rhs) { - return !operator==(lhs, rhs); - } - - reference operator*() const { - return *it; - } - - pointer operator->() const { - return it.operator->(); - } - - iterator_impl& operator++() { - if (IsEnd()) { - return *this; - } - - ++it; - - if (it == GetEndItForPrio()) { - u64 prios = mlq.used_priorities; - prios &= ~((1ULL << (current_priority + 1)) - 1); - if (prios == 0) { - current_priority = static_cast(mlq.depth()); - } else { - current_priority = CountTrailingZeroes64(prios); - it = GetBeginItForPrio(); - } - } - return *this; - } - - iterator_impl& operator--() { - if (IsEnd()) { - if (mlq.used_priorities != 0) { - current_priority = 63 - CountLeadingZeroes64(mlq.used_priorities); - it = GetEndItForPrio(); - --it; - } - } else if (it == GetBeginItForPrio()) { - u64 prios = mlq.used_priorities; - prios &= (1ULL << current_priority) - 1; - if (prios != 0) { - current_priority = CountTrailingZeroes64(prios); - it = GetEndItForPrio(); - --it; - } - } else { - --it; - } - return *this; - } - - iterator_impl operator++(int) { - const iterator_impl v{*this}; - ++(*this); - return v; - } - - iterator_impl operator--(int) { - const iterator_impl v{*this}; - --(*this); - return v; - } - - // allow implicit const->non-const - iterator_impl(const iterator_impl& other) - : mlq(other.mlq), it(other.it), current_priority(other.current_priority) {} - - iterator_impl(const iterator_impl& other) - : mlq(other.mlq), it(other.it), current_priority(other.current_priority) {} - - iterator_impl& operator=(const iterator_impl& other) { - mlq = other.mlq; - it = other.it; - current_priority = other.current_priority; - return *this; - } - - friend class iterator_impl; - iterator_impl() = default; - - private: - friend class MultiLevelQueue; - using container_ref = - std::conditional_t; - using list_iterator = std::conditional_t::const_iterator, - typename std::list::iterator>; - - explicit iterator_impl(container_ref mlq, list_iterator it, u32 current_priority) - : mlq(mlq), it(it), current_priority(current_priority) {} - explicit iterator_impl(container_ref mlq, u32 current_priority) - : mlq(mlq), it(), current_priority(current_priority) {} - - bool IsEnd() const { - return current_priority == mlq.depth(); - } - - list_iterator GetBeginItForPrio() const { - return mlq.levels[current_priority].begin(); - } - - list_iterator GetEndItForPrio() const { - return mlq.levels[current_priority].end(); - } - - container_ref mlq; - list_iterator it; - u32 current_priority; - }; - - using iterator = iterator_impl; - using const_iterator = iterator_impl; - - void add(const T& element, u32 priority, bool send_back = true) { - if (send_back) - levels[priority].push_back(element); - else - levels[priority].push_front(element); - used_priorities |= 1ULL << priority; - } - - void remove(const T& element, u32 priority) { - auto it = ListIterateTo(levels[priority], element); - if (it == levels[priority].end()) - return; - levels[priority].erase(it); - if (levels[priority].empty()) { - used_priorities &= ~(1ULL << priority); - } - } - - void adjust(const T& element, u32 old_priority, u32 new_priority, bool adjust_front = false) { - remove(element, old_priority); - add(element, new_priority, !adjust_front); - } - void adjust(const_iterator it, u32 old_priority, u32 new_priority, bool adjust_front = false) { - adjust(*it, old_priority, new_priority, adjust_front); - } - - void transfer_to_front(const T& element, u32 priority, MultiLevelQueue& other) { - ListSplice(other.levels[priority], other.levels[priority].begin(), levels[priority], - ListIterateTo(levels[priority], element)); - - other.used_priorities |= 1ULL << priority; - - if (levels[priority].empty()) { - used_priorities &= ~(1ULL << priority); - } - } - - void transfer_to_front(const_iterator it, u32 priority, MultiLevelQueue& other) { - transfer_to_front(*it, priority, other); - } - - void transfer_to_back(const T& element, u32 priority, MultiLevelQueue& other) { - ListSplice(other.levels[priority], other.levels[priority].end(), levels[priority], - ListIterateTo(levels[priority], element)); - - other.used_priorities |= 1ULL << priority; - - if (levels[priority].empty()) { - used_priorities &= ~(1ULL << priority); - } - } - - void transfer_to_back(const_iterator it, u32 priority, MultiLevelQueue& other) { - transfer_to_back(*it, priority, other); - } - - void yield(u32 priority, std::size_t n = 1) { - ListShiftForward(levels[priority], n); - } - - [[nodiscard]] std::size_t depth() const { - return Depth; - } - - [[nodiscard]] std::size_t size(u32 priority) const { - return levels[priority].size(); - } - - [[nodiscard]] std::size_t size() const { - u64 priorities = used_priorities; - std::size_t size = 0; - while (priorities != 0) { - const u64 current_priority = CountTrailingZeroes64(priorities); - size += levels[current_priority].size(); - priorities &= ~(1ULL << current_priority); - } - return size; - } - - [[nodiscard]] bool empty() const { - return used_priorities == 0; - } - - [[nodiscard]] bool empty(u32 priority) const { - return (used_priorities & (1ULL << priority)) == 0; - } - - [[nodiscard]] u32 highest_priority_set(u32 max_priority = 0) const { - const u64 priorities = - max_priority == 0 ? used_priorities : (used_priorities & ~((1ULL << max_priority) - 1)); - return priorities == 0 ? Depth : static_cast(CountTrailingZeroes64(priorities)); - } - - [[nodiscard]] u32 lowest_priority_set(u32 min_priority = Depth - 1) const { - const u64 priorities = min_priority >= Depth - 1 - ? used_priorities - : (used_priorities & ((1ULL << (min_priority + 1)) - 1)); - return priorities == 0 ? Depth : 63 - CountLeadingZeroes64(priorities); - } - - [[nodiscard]] const_iterator cbegin(u32 max_prio = 0) const { - const u32 priority = highest_priority_set(max_prio); - return priority == Depth ? cend() - : const_iterator{*this, levels[priority].cbegin(), priority}; - } - [[nodiscard]] const_iterator begin(u32 max_prio = 0) const { - return cbegin(max_prio); - } - [[nodiscard]] iterator begin(u32 max_prio = 0) { - const u32 priority = highest_priority_set(max_prio); - return priority == Depth ? end() : iterator{*this, levels[priority].begin(), priority}; - } - - [[nodiscard]] const_iterator cend(u32 min_prio = Depth - 1) const { - return min_prio == Depth - 1 ? const_iterator{*this, Depth} : cbegin(min_prio + 1); - } - [[nodiscard]] const_iterator end(u32 min_prio = Depth - 1) const { - return cend(min_prio); - } - [[nodiscard]] iterator end(u32 min_prio = Depth - 1) { - return min_prio == Depth - 1 ? iterator{*this, Depth} : begin(min_prio + 1); - } - - [[nodiscard]] T& front(u32 max_priority = 0) { - const u32 priority = highest_priority_set(max_priority); - return levels[priority == Depth ? 0 : priority].front(); - } - [[nodiscard]] const T& front(u32 max_priority = 0) const { - const u32 priority = highest_priority_set(max_priority); - return levels[priority == Depth ? 0 : priority].front(); - } - - [[nodiscard]] T& back(u32 min_priority = Depth - 1) { - const u32 priority = lowest_priority_set(min_priority); // intended - return levels[priority == Depth ? 63 : priority].back(); - } - [[nodiscard]] const T& back(u32 min_priority = Depth - 1) const { - const u32 priority = lowest_priority_set(min_priority); // intended - return levels[priority == Depth ? 63 : priority].back(); - } - - void clear() { - used_priorities = 0; - for (std::size_t i = 0; i < Depth; i++) { - levels[i].clear(); - } - } - -private: - using const_list_iterator = typename std::list::const_iterator; - - static void ListShiftForward(std::list& list, const std::size_t shift = 1) { - if (shift >= list.size()) { - return; - } - - const auto begin_range = list.begin(); - const auto end_range = std::next(begin_range, shift); - list.splice(list.end(), list, begin_range, end_range); - } - - static void ListSplice(std::list& in_list, const_list_iterator position, - std::list& out_list, const_list_iterator element) { - in_list.splice(position, out_list, element); - } - - [[nodiscard]] static const_list_iterator ListIterateTo(const std::list& list, - const T& element) { - auto it = list.cbegin(); - while (it != list.cend() && *it != element) { - ++it; - } - return it; - } - - std::array, Depth> levels; - u64 used_priorities = 0; -}; - -} // namespace Common diff --git a/src/common/page_table.cpp b/src/common/page_table.cpp index e5d3090d5..8fd8620fd 100644 --- a/src/common/page_table.cpp +++ b/src/common/page_table.cpp @@ -8,18 +8,12 @@ namespace Common { PageTable::PageTable() = default; -PageTable::~PageTable() = default; +PageTable::~PageTable() noexcept = default; -void PageTable::Resize(std::size_t address_space_width_in_bits, std::size_t page_size_in_bits, - bool has_attribute) { - const std::size_t num_page_table_entries{1ULL - << (address_space_width_in_bits - page_size_in_bits)}; +void PageTable::Resize(size_t address_space_width_in_bits, size_t page_size_in_bits) { + const size_t num_page_table_entries{1ULL << (address_space_width_in_bits - page_size_in_bits)}; pointers.resize(num_page_table_entries); backing_addr.resize(num_page_table_entries); - - if (has_attribute) { - attributes.resize(num_page_table_entries); - } } } // namespace Common diff --git a/src/common/page_table.h b/src/common/page_table.h index cf5eed780..61c5552e0 100644 --- a/src/common/page_table.h +++ b/src/common/page_table.h @@ -4,12 +4,10 @@ #pragma once -#include - -#include +#include +#include #include "common/common_types.h" -#include "common/memory_hook.h" #include "common/virtual_buffer.h" namespace Common { @@ -22,27 +20,6 @@ enum class PageType : u8 { /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and /// invalidation RasterizerCachedMemory, - /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions. - Special, - /// Page is allocated for use. - Allocated, -}; - -struct SpecialRegion { - enum class Type { - DebugHook, - IODevice, - } type; - - MemoryHookPointer handler; - - [[nodiscard]] bool operator<(const SpecialRegion& other) const { - return std::tie(type, handler) < std::tie(other.type, other.handler); - } - - [[nodiscard]] bool operator==(const SpecialRegion& other) const { - return std::tie(type, handler) == std::tie(other.type, other.handler); - } }; /** @@ -50,27 +27,84 @@ struct SpecialRegion { * mimics the way a real CPU page table works. */ struct PageTable { - PageTable(); - ~PageTable(); + /// 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; /** - * Resizes the page table to be able to accomodate enough pages within + * Pair of host pointer and page type attribute. + * This uses the lower bits of a given pointer to store the attribute tag. + * 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 { + public: + /// Returns the page pointer + [[nodiscard]] u8* Pointer() const noexcept { + return ExtractPointer(raw.load(std::memory_order_relaxed)); + } + + /// Returns the page type attribute + [[nodiscard]] PageType Type() const noexcept { + return ExtractType(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)}; + } + + /// 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 a page pointer and type pair atomically + void Store(u8* pointer, PageType type) noexcept { + raw.store(reinterpret_cast(pointer) | static_cast(type)); + } + + /// Unpack a pointer from a page info raw representation + [[nodiscard]] static u8* ExtractPointer(uintptr_t raw) noexcept { + return reinterpret_cast(raw & (~uintptr_t{0} << ATTRIBUTE_BITS)); + } + + /// 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)); + } + + private: + std::atomic raw; + }; + + PageTable(); + ~PageTable() noexcept; + + PageTable(const PageTable&) = delete; + PageTable& operator=(const PageTable&) = delete; + + PageTable(PageTable&&) noexcept = default; + PageTable& operator=(PageTable&&) noexcept = default; + + /** + * Resizes the page table to be able to accommodate enough pages within * a given address space. * * @param address_space_width_in_bits The address size width in bits. + * @param page_size_in_bits The page size in bits. */ - void Resize(std::size_t address_space_width_in_bits, std::size_t page_size_in_bits, - bool has_attribute); + void Resize(size_t address_space_width_in_bits, size_t page_size_in_bits); /** * Vector of memory pointers backing each page. An entry can only be non-null if the - * corresponding entry in the `attributes` vector is of type `Memory`. + * corresponding attribute element is of type `Memory`. */ - VirtualBuffer pointers; + VirtualBuffer pointers; VirtualBuffer backing_addr; - - VirtualBuffer attributes; }; } // namespace Common diff --git a/src/common/scope_exit.h b/src/common/scope_exit.h index 68ef5f197..fa46cb394 100644 --- a/src/common/scope_exit.h +++ b/src/common/scope_exit.h @@ -10,7 +10,7 @@ namespace detail { template struct ScopeExitHelper { - explicit ScopeExitHelper(Func&& func) : func(std::move(func)) {} + explicit ScopeExitHelper(Func&& func_) : func(std::move(func_)) {} ~ScopeExitHelper() { if (active) { func(); diff --git a/src/common/spin_lock.h b/src/common/spin_lock.h index 4f946a258..06ac2f5bb 100644 --- a/src/common/spin_lock.h +++ b/src/common/spin_lock.h @@ -15,6 +15,14 @@ namespace Common { */ class SpinLock { public: + SpinLock() = default; + + SpinLock(const SpinLock&) = delete; + SpinLock& operator=(const SpinLock&) = delete; + + SpinLock(SpinLock&&) = delete; + SpinLock& operator=(SpinLock&&) = delete; + void lock(); void unlock(); [[nodiscard]] bool try_lock(); diff --git a/src/common/stream.cpp b/src/common/stream.cpp new file mode 100644 index 000000000..bf0496c26 --- /dev/null +++ b/src/common/stream.cpp @@ -0,0 +1,47 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/common_types.h" +#include "common/stream.h" + +namespace Common { + +Stream::Stream() = default; +Stream::~Stream() = default; + +void Stream::Seek(s32 offset, SeekOrigin origin) { + if (origin == SeekOrigin::SetOrigin) { + if (offset < 0) { + position = 0; + } else if (position >= buffer.size()) { + position = buffer.size(); + } else { + position = offset; + } + } else if (origin == SeekOrigin::FromCurrentPos) { + Seek(static_cast(position) + offset, SeekOrigin::SetOrigin); + } else if (origin == SeekOrigin::FromEnd) { + Seek(static_cast(buffer.size()) - offset, SeekOrigin::SetOrigin); + } +} + +u8 Stream::ReadByte() { + if (position < buffer.size()) { + return buffer[position++]; + } else { + throw std::out_of_range("Attempting to read a byte not within the buffer range"); + } +} + +void Stream::WriteByte(u8 byte) { + if (position == buffer.size()) { + buffer.push_back(byte); + position++; + } else { + buffer.insert(buffer.begin() + position, byte); + } +} + +} // namespace Common diff --git a/src/common/stream.h b/src/common/stream.h new file mode 100644 index 000000000..0e40692de --- /dev/null +++ b/src/common/stream.h @@ -0,0 +1,56 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/common_types.h" + +namespace Common { + +enum class SeekOrigin { + SetOrigin, + FromCurrentPos, + FromEnd, +}; + +class Stream { +public: + /// Stream creates a bitstream and provides common functionality on the stream. + explicit Stream(); + ~Stream(); + + Stream(const Stream&) = delete; + Stream& operator=(const Stream&) = delete; + + Stream(Stream&&) = default; + Stream& operator=(Stream&&) = default; + + /// Reposition bitstream "cursor" to the specified offset from origin + void Seek(s32 offset, SeekOrigin origin); + + /// Reads next byte in the stream buffer and increments position + u8 ReadByte(); + + /// Writes byte at current position + void WriteByte(u8 byte); + + [[nodiscard]] std::size_t GetPosition() const { + return position; + } + + [[nodiscard]] std::vector& GetBuffer() { + return buffer; + } + + [[nodiscard]] const std::vector& GetBuffer() const { + return buffer; + } + +private: + std::vector buffer; + std::size_t position{0}; +}; + +} // namespace Common diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 84883a1d3..4cba2aaa4 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -8,6 +8,7 @@ #include #include #include + #include "common/common_paths.h" #include "common/logging/log.h" #include "common/string_util.h" @@ -21,14 +22,14 @@ namespace Common { /// Make a string lowercase std::string ToLower(std::string str) { std::transform(str.begin(), str.end(), str.begin(), - [](unsigned char c) { return std::tolower(c); }); + [](unsigned char c) { return static_cast(std::tolower(c)); }); return str; } /// Make a string uppercase std::string ToUpper(std::string str) { std::transform(str.begin(), str.end(), str.begin(), - [](unsigned char c) { return std::toupper(c); }); + [](unsigned char c) { return static_cast(std::toupper(c)); }); return str; } diff --git a/src/common/swap.h b/src/common/swap.h index 7665942a2..a80e191dc 100644 --- a/src/common/swap.h +++ b/src/common/swap.h @@ -394,7 +394,7 @@ public: template friend S operator%(const S& p, const swapped_t v); - // Arithmetics + assignements + // Arithmetics + assignments template friend S operator+=(const S& p, const swapped_t v); @@ -451,7 +451,7 @@ S operator%(const S& i, const swap_struct_t v) { return i % v.swap(); } -// Arithmetics + assignements +// Arithmetics + assignments template S& operator+=(S& i, const swap_struct_t v) { i += v.swap(); diff --git a/src/common/telemetry.h b/src/common/telemetry.h index a50c5d1de..49186e848 100644 --- a/src/common/telemetry.h +++ b/src/common/telemetry.h @@ -52,8 +52,8 @@ public: template class Field : public FieldInterface { public: - Field(FieldType type, std::string name, T value) - : name(std::move(name)), type(type), value(std::move(value)) {} + Field(FieldType type_, std::string name_, T value_) + : name(std::move(name_)), type(type_), value(std::move(value_)) {} Field(const Field&) = default; Field& operator=(const Field&) = default; diff --git a/src/common/thread_worker.cpp b/src/common/thread_worker.cpp new file mode 100644 index 000000000..8f9bf447a --- /dev/null +++ b/src/common/thread_worker.cpp @@ -0,0 +1,58 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/thread.h" +#include "common/thread_worker.h" + +namespace Common { + +ThreadWorker::ThreadWorker(std::size_t num_workers, const std::string& name) { + for (std::size_t i = 0; i < num_workers; ++i) + threads.emplace_back([this, thread_name{std::string{name}}] { + Common::SetCurrentThreadName(thread_name.c_str()); + + // Wait for first request + { + std::unique_lock lock{queue_mutex}; + condition.wait(lock, [this] { return stop || !requests.empty(); }); + } + + while (true) { + std::function task; + + { + std::unique_lock lock{queue_mutex}; + condition.wait(lock, [this] { return stop || !requests.empty(); }); + if (stop || requests.empty()) { + return; + } + task = std::move(requests.front()); + requests.pop(); + } + + task(); + } + }); +} + +ThreadWorker::~ThreadWorker() { + { + std::unique_lock lock{queue_mutex}; + stop = true; + } + condition.notify_all(); + for (std::thread& thread : threads) { + thread.join(); + } +} + +void ThreadWorker::QueueWork(std::function&& work) { + { + std::unique_lock lock{queue_mutex}; + requests.emplace(work); + } + condition.notify_one(); +} + +} // namespace Common diff --git a/src/common/thread_worker.h b/src/common/thread_worker.h new file mode 100644 index 000000000..f1859971f --- /dev/null +++ b/src/common/thread_worker.h @@ -0,0 +1,30 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Common { + +class ThreadWorker final { +public: + explicit ThreadWorker(std::size_t num_workers, const std::string& name); + ~ThreadWorker(); + void QueueWork(std::function&& work); + +private: + std::vector threads; + std::queue> requests; + std::mutex queue_mutex; + std::condition_variable condition; + std::atomic_bool stop{}; +}; + +} // namespace Common diff --git a/src/common/timer.cpp b/src/common/timer.cpp index 2dc15e434..d17dc2a50 100644 --- a/src/common/timer.cpp +++ b/src/common/timer.cpp @@ -142,20 +142,18 @@ std::string Timer::GetTimeFormatted() { // ---------------- double Timer::GetDoubleTime() { // Get continuous timestamp - u64 TmpSeconds = static_cast(Common::Timer::GetTimeSinceJan1970().count()); - double ms = static_cast(GetTimeMs().count()) % 1000; + auto tmp_seconds = static_cast(GetTimeSinceJan1970().count()); + const auto ms = static_cast(static_cast(GetTimeMs().count()) % 1000); // Remove a few years. We only really want enough seconds to make // sure that we are detecting actual actions, perhaps 60 seconds is // enough really, but I leave a year of seconds anyway, in case the // user's clock is incorrect or something like that. - TmpSeconds = TmpSeconds - (38 * 365 * 24 * 60 * 60); + tmp_seconds = tmp_seconds - (38 * 365 * 24 * 60 * 60); // Make a smaller integer that fits in the double - u32 Seconds = static_cast(TmpSeconds); - double TmpTime = Seconds + ms; - - return TmpTime; + const auto seconds = static_cast(tmp_seconds); + return seconds + ms; } } // Namespace Common diff --git a/src/common/vector_math.h b/src/common/vector_math.h index 2a0fcf541..22dba3c2d 100644 --- a/src/common/vector_math.h +++ b/src/common/vector_math.h @@ -87,7 +87,13 @@ public: template [[nodiscard]] constexpr Vec2 operator*(const V& f) const { - return {x * f, y * f}; + 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)), + }; } template @@ -98,7 +104,13 @@ public: template [[nodiscard]] constexpr Vec2 operator/(const V& f) const { - return {x / f, y / f}; + 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)), + }; } template @@ -168,7 +180,10 @@ public: template [[nodiscard]] constexpr Vec2 operator*(const V& f, const Vec2& vec) { - return Vec2(f * vec.x, f * vec.y); + 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; @@ -237,7 +252,14 @@ public: template [[nodiscard]] constexpr Vec3 operator*(const V& f) const { - return {x * f, y * f, z * f}; + 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 @@ -247,7 +269,14 @@ public: } template [[nodiscard]] constexpr Vec3 operator/(const V& f) const { - return {x / f, y / f, z / f}; + 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 @@ -367,7 +396,11 @@ public: template [[nodiscard]] constexpr Vec3 operator*(const V& f, const Vec3& vec) { - return Vec3(f * vec.x, f * vec.y, f * vec.z); + 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 <> @@ -446,7 +479,15 @@ public: template [[nodiscard]] constexpr Vec4 operator*(const V& f) const { - return {x * f, y * f, z * f, w * f}; + 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 @@ -457,7 +498,15 @@ public: template [[nodiscard]] constexpr Vec4 operator/(const V& f) const { - return {x / f, y / f, z / f, w / f}; + 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 @@ -582,7 +631,15 @@ public: template [[nodiscard]] constexpr Vec4 operator*(const V& f, const Vec4& vec) { - return {f * vec.x, f * vec.y, f * vec.z, f * vec.w}; + 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; diff --git a/src/common/virtual_buffer.cpp b/src/common/virtual_buffer.cpp index b009cb500..e3ca29258 100644 --- a/src/common/virtual_buffer.cpp +++ b/src/common/virtual_buffer.cpp @@ -13,7 +13,7 @@ namespace Common { -void* AllocateMemoryPages(std::size_t size) { +void* AllocateMemoryPages(std::size_t size) noexcept { #ifdef _WIN32 void* base{VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE)}; #else @@ -29,7 +29,7 @@ void* AllocateMemoryPages(std::size_t size) { return base; } -void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) { +void FreeMemoryPages(void* base, [[maybe_unused]] std::size_t size) noexcept { if (!base) { return; } diff --git a/src/common/virtual_buffer.h b/src/common/virtual_buffer.h index 125cb42f0..fb1a6f81f 100644 --- a/src/common/virtual_buffer.h +++ b/src/common/virtual_buffer.h @@ -4,29 +4,55 @@ #pragma once -#include "common/common_funcs.h" +#include +#include namespace Common { -void* AllocateMemoryPages(std::size_t size); -void FreeMemoryPages(void* base, std::size_t size); +void* AllocateMemoryPages(std::size_t size) noexcept; +void FreeMemoryPages(void* base, std::size_t size) noexcept; template -class VirtualBuffer final : NonCopyable { +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) : alloc_size{count * sizeof(T)} { base_ptr = reinterpret_cast(AllocateMemoryPages(alloc_size)); } - ~VirtualBuffer() { + ~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) { + const auto new_size = count * sizeof(T); + if (new_size == alloc_size) { + return; + } + FreeMemoryPages(base_ptr, alloc_size); - alloc_size = count * sizeof(T); + alloc_size = new_size; base_ptr = reinterpret_cast(AllocateMemoryPages(alloc_size)); } diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp index 3afbdb898..a8c143f85 100644 --- a/src/common/wall_clock.cpp +++ b/src/common/wall_clock.cpp @@ -15,10 +15,10 @@ namespace Common { using base_timer = std::chrono::steady_clock; using base_time_point = std::chrono::time_point; -class StandardWallClock : public WallClock { +class StandardWallClock final : public WallClock { public: - StandardWallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency) - : WallClock(emulated_cpu_frequency, emulated_clock_frequency, false) { + explicit StandardWallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_) + : WallClock(emulated_cpu_frequency_, emulated_clock_frequency_, false) { start_time = base_timer::now(); } @@ -53,7 +53,7 @@ public: return Common::Divide128On32(temporary, 1000000000).first; } - void Pause(bool is_paused) override { + void Pause([[maybe_unused]] bool is_paused) override { // Do nothing in this clock type. } diff --git a/src/common/wall_clock.h b/src/common/wall_clock.h index 5db30083d..cef3e9499 100644 --- a/src/common/wall_clock.h +++ b/src/common/wall_clock.h @@ -13,6 +13,8 @@ namespace Common { class WallClock { public: + virtual ~WallClock() = default; + /// Returns current wall time in nanoseconds [[nodiscard]] virtual std::chrono::nanoseconds GetTimeNS() = 0; @@ -36,9 +38,9 @@ public: } protected: - WallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, bool is_native) - : emulated_cpu_frequency{emulated_cpu_frequency}, - emulated_clock_frequency{emulated_clock_frequency}, is_native{is_native} {} + explicit WallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, bool is_native_) + : emulated_cpu_frequency{emulated_cpu_frequency_}, + emulated_clock_frequency{emulated_clock_frequency_}, is_native{is_native_} {} u64 emulated_cpu_frequency; u64 emulated_clock_frequency; diff --git a/src/common/x64/native_clock.cpp b/src/common/x64/native_clock.cpp index 424b39b1f..eb8a7782f 100644 --- a/src/common/x64/native_clock.cpp +++ b/src/common/x64/native_clock.cpp @@ -43,10 +43,10 @@ u64 EstimateRDTSCFrequency() { } namespace X64 { -NativeClock::NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, - u64 rtsc_frequency) - : WallClock(emulated_cpu_frequency, emulated_clock_frequency, true), rtsc_frequency{ - rtsc_frequency} { +NativeClock::NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, + u64 rtsc_frequency_) + : WallClock(emulated_cpu_frequency_, emulated_clock_frequency_, true), rtsc_frequency{ + rtsc_frequency_} { _mm_mfence(); last_measure = __rdtsc(); accumulated_ticks = 0U; diff --git a/src/common/x64/native_clock.h b/src/common/x64/native_clock.h index 891a3bbfd..6d1e32ac8 100644 --- a/src/common/x64/native_clock.h +++ b/src/common/x64/native_clock.h @@ -12,9 +12,10 @@ namespace Common { namespace X64 { -class NativeClock : public WallClock { +class NativeClock final : public WallClock { public: - NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, u64 rtsc_frequency); + explicit NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, + u64 rtsc_frequency_); std::chrono::nanoseconds GetTimeNS() override; @@ -34,7 +35,7 @@ private: /// value used to reduce the native clocks accuracy as some apss rely on /// undefined behavior where the level of accuracy in the clock shouldn't /// be higher. - static constexpr u64 inaccuracy_mask = ~(0x400 - 1); + static constexpr u64 inaccuracy_mask = ~(UINT64_C(0x400) - 1); SpinLock rtsc_serialize{}; u64 last_measure{}; diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h index 26e4bfda5..c2c9b6134 100644 --- a/src/common/x64/xbyak_abi.h +++ b/src/common/x64/xbyak_abi.h @@ -11,25 +11,25 @@ namespace Common::X64 { -constexpr std::size_t RegToIndex(const Xbyak::Reg& reg) { +constexpr size_t RegToIndex(const Xbyak::Reg& reg) { using Kind = Xbyak::Reg::Kind; ASSERT_MSG((reg.getKind() & (Kind::REG | Kind::XMM)) != 0, "RegSet only support GPRs and XMM registers."); ASSERT_MSG(reg.getIdx() < 16, "RegSet only supports XXM0-15."); - return reg.getIdx() + (reg.getKind() == Kind::REG ? 0 : 16); + return static_cast(reg.getIdx()) + (reg.getKind() == Kind::REG ? 0 : 16); } -constexpr Xbyak::Reg64 IndexToReg64(std::size_t reg_index) { +constexpr Xbyak::Reg64 IndexToReg64(size_t reg_index) { ASSERT(reg_index < 16); return Xbyak::Reg64(static_cast(reg_index)); } -constexpr Xbyak::Xmm IndexToXmm(std::size_t reg_index) { +constexpr Xbyak::Xmm IndexToXmm(size_t reg_index) { ASSERT(reg_index >= 16 && reg_index < 32); return Xbyak::Xmm(static_cast(reg_index - 16)); } -constexpr Xbyak::Reg IndexToReg(std::size_t reg_index) { +constexpr Xbyak::Reg IndexToReg(size_t reg_index) { if (reg_index < 16) { return IndexToReg64(reg_index); } else { @@ -182,7 +182,7 @@ inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::b size_t rsp_alignment, size_t needed_frame_size = 0) { auto frame_info = ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size); - for (std::size_t i = 0; i < regs.size(); ++i) { + for (size_t i = 0; i < regs.size(); ++i) { if (regs[i] && ABI_ALL_GPRS[i]) { code.push(IndexToReg64(i)); } @@ -192,7 +192,7 @@ inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::b code.sub(code.rsp, frame_info.subtraction); } - for (std::size_t i = 0; i < regs.size(); ++i) { + for (size_t i = 0; i < regs.size(); ++i) { if (regs[i] && ABI_ALL_XMMS[i]) { code.movaps(code.xword[code.rsp + frame_info.xmm_offset], IndexToXmm(i)); frame_info.xmm_offset += 0x10; @@ -206,7 +206,7 @@ inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bits size_t rsp_alignment, size_t needed_frame_size = 0) { auto frame_info = ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size); - for (std::size_t i = 0; i < regs.size(); ++i) { + for (size_t i = 0; i < regs.size(); ++i) { if (regs[i] && ABI_ALL_XMMS[i]) { code.movaps(IndexToXmm(i), code.xword[code.rsp + frame_info.xmm_offset]); frame_info.xmm_offset += 0x10; @@ -218,8 +218,8 @@ inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bits } // GPRs need to be popped in reverse order - for (std::size_t j = 0; j < regs.size(); ++j) { - const std::size_t i = regs.size() - j - 1; + for (size_t j = 0; j < regs.size(); ++j) { + const size_t i = regs.size() - j - 1; if (regs[i] && ABI_ALL_GPRS[i]) { code.pop(IndexToReg64(i)); } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index d0c405ec7..893df433a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,9 +1,3 @@ -if (YUZU_ENABLE_BOXCAT) - set(BCAT_BOXCAT_ADDITIONAL_SOURCES hle/service/bcat/backend/boxcat.cpp hle/service/bcat/backend/boxcat.h) -else() - set(BCAT_BOXCAT_ADDITIONAL_SOURCES) -endif() - add_library(core STATIC arm/arm_interface.h arm/arm_interface.cpp @@ -19,8 +13,6 @@ add_library(core STATIC arm/dynarmic/arm_exclusive_monitor.h arm/exclusive_monitor.cpp arm/exclusive_monitor.h - arm/unicorn/arm_unicorn.cpp - arm/unicorn/arm_unicorn.h constants.cpp constants.h core.cpp @@ -49,6 +41,7 @@ add_library(core STATIC file_sys/bis_factory.h file_sys/card_image.cpp file_sys/card_image.h + file_sys/common_funcs.h file_sys/content_archive.cpp file_sys/content_archive.h file_sys/control_metadata.cpp @@ -142,9 +135,9 @@ add_library(core STATIC frontend/emu_window.h frontend/framebuffer_layout.cpp frontend/framebuffer_layout.h + frontend/input_interpreter.cpp + frontend/input_interpreter.h frontend/input.h - gdbstub/gdbstub.cpp - gdbstub/gdbstub.h hardware_interrupt_manager.cpp hardware_interrupt_manager.h hle/ipc.h @@ -158,10 +151,19 @@ add_library(core STATIC hle/kernel/code_set.cpp hle/kernel/code_set.h hle/kernel/errors.h + hle/kernel/global_scheduler_context.cpp + hle/kernel/global_scheduler_context.h hle/kernel/handle_table.cpp hle/kernel/handle_table.h hle/kernel/hle_ipc.cpp hle/kernel/hle_ipc.h + hle/kernel/k_affinity_mask.h + hle/kernel/k_priority_queue.h + hle/kernel/k_scheduler.cpp + hle/kernel/k_scheduler.h + hle/kernel/k_scheduler_lock.h + hle/kernel/k_scoped_lock.h + hle/kernel/k_scoped_scheduler_lock_and_sleep.h hle/kernel/kernel.cpp hle/kernel/kernel.h hle/kernel/memory/address_space_info.cpp @@ -196,12 +198,12 @@ add_library(core STATIC hle/kernel/readable_event.h hle/kernel/resource_limit.cpp hle/kernel/resource_limit.h - hle/kernel/scheduler.cpp - hle/kernel/scheduler.h hle/kernel/server_port.cpp hle/kernel/server_port.h hle/kernel/server_session.cpp hle/kernel/server_session.h + hle/kernel/service_thread.cpp + hle/kernel/service_thread.h hle/kernel/session.cpp hle/kernel/session.h hle/kernel/shared_memory.cpp @@ -303,7 +305,6 @@ add_library(core STATIC hle/service/audio/hwopus.h hle/service/bcat/backend/backend.cpp hle/service/bcat/backend/backend.h - ${BCAT_BOXCAT_ADDITIONAL_SOURCES} hle/service/bcat/bcat.cpp hle/service/bcat/bcat.h hle/service/bcat/module.cpp @@ -446,6 +447,8 @@ add_library(core STATIC hle/service/nvdrv/devices/nvhost_gpu.h hle/service/nvdrv/devices/nvhost_nvdec.cpp hle/service/nvdrv/devices/nvhost_nvdec.h + hle/service/nvdrv/devices/nvhost_nvdec_common.cpp + hle/service/nvdrv/devices/nvhost_nvdec_common.h hle/service/nvdrv/devices/nvhost_nvjpg.cpp hle/service/nvdrv/devices/nvhost_nvjpg.h hle/service/nvdrv/devices/nvhost_vic.cpp @@ -459,10 +462,14 @@ add_library(core STATIC hle/service/nvdrv/nvdrv.h hle/service/nvdrv/nvmemp.cpp hle/service/nvdrv/nvmemp.h + hle/service/nvdrv/syncpoint_manager.cpp + hle/service/nvdrv/syncpoint_manager.h hle/service/nvflinger/buffer_queue.cpp hle/service/nvflinger/buffer_queue.h hle/service/nvflinger/nvflinger.cpp hle/service/nvflinger/nvflinger.h + hle/service/olsc/olsc.cpp + hle/service/olsc/olsc.h hle/service/pcie/pcie.cpp hle/service/pcie/pcie.h hle/service/pctl/module.cpp @@ -495,7 +502,6 @@ add_library(core STATIC hle/service/sm/controller.h hle/service/sm/sm.cpp hle/service/sm/sm.h - hle/service/sockets/blocking_worker.h hle/service/sockets/bsd.cpp hle/service/sockets/bsd.h hle/service/sockets/ethc.cpp @@ -608,6 +614,13 @@ add_library(core STATIC tools/freezer.h ) +if (YUZU_ENABLE_BOXCAT) + target_sources(core PRIVATE + hle/service/bcat/backend/boxcat.cpp + hle/service/bcat/backend/boxcat.h + ) +endif() + if (MSVC) target_compile_options(core PRIVATE # 'expression' : signed/unsigned mismatch @@ -622,13 +635,29 @@ if (MSVC) /we4267 # 'context' : truncation from 'type1' to 'type2' /we4305 + # 'function' : not all control paths return a value + /we4715 + ) +else() + target_compile_options(core PRIVATE + -Werror=conversion + -Werror=ignored-qualifiers + -Werror=implicit-fallthrough + -Werror=reorder + -Werror=sign-compare + -Werror=unused-variable + + $<$:-Werror=unused-but-set-parameter> + $<$:-Werror=unused-but-set-variable> + + -Wno-sign-conversion ) endif() create_target_directory_groups(core) target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) -target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls opus unicorn zip) +target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls opus zip) if (YUZU_ENABLE_BOXCAT) target_compile_definitions(core PRIVATE -DYUZU_ENABLE_BOXCAT) diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp index d2295ed90..0951e1976 100644 --- a/src/core/arm/arm_interface.cpp +++ b/src/core/arm/arm_interface.cpp @@ -147,10 +147,18 @@ std::vector ARM_Interface::GetBacktraceFromContex auto fp = ctx.cpu_registers[29]; auto lr = ctx.cpu_registers[30]; while (true) { - out.push_back({"", 0, lr, 0}); - if (!fp) { + out.push_back({ + .module = "", + .address = 0, + .original_address = lr, + .offset = 0, + .name = {}, + }); + + if (fp == 0) { break; } + lr = memory.Read64(fp + 8) - 4; fp = memory.Read64(fp); } diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index 1f24051e4..70098c526 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h @@ -64,15 +64,25 @@ public: /// Step CPU by one instruction virtual void Step() = 0; + /// Exits execution from a callback, the callback must rewind the stack + virtual void ExceptionalExit() = 0; + /// Clear all instruction cache virtual void ClearInstructionCache() = 0; - /// Notifies CPU emulation that the current page table has changed. - /// - /// @param new_page_table The new page table. - /// @param new_address_space_size_in_bits The new usable size of the address space in bits. - /// This can be either 32, 36, or 39 on official software. - /// + /** + * Clear instruction cache range + * @param addr Start address of the cache range to clear + * @param size Size of the cache range to clear, starting at addr + */ + virtual void InvalidateCacheRange(VAddr addr, std::size_t size) = 0; + + /** + * Notifies CPU emulation that the current page table has changed. + * @param new_page_table The new page table. + * @param new_address_space_size_in_bits The new usable size of the address space in bits. + * This can be either 32, 36, or 39 on official software. + */ virtual void PageTableChanged(Common::PageTable& new_page_table, std::size_t new_address_space_size_in_bits) = 0; diff --git a/src/core/arm/cpu_interrupt_handler.h b/src/core/arm/cpu_interrupt_handler.h index 71e582f79..c20c280f1 100644 --- a/src/core/arm/cpu_interrupt_handler.h +++ b/src/core/arm/cpu_interrupt_handler.h @@ -21,8 +21,8 @@ public: CPUInterruptHandler(const CPUInterruptHandler&) = delete; CPUInterruptHandler& operator=(const CPUInterruptHandler&) = delete; - CPUInterruptHandler(CPUInterruptHandler&&) = default; - CPUInterruptHandler& operator=(CPUInterruptHandler&&) = default; + CPUInterruptHandler(CPUInterruptHandler&&) = delete; + CPUInterruptHandler& operator=(CPUInterruptHandler&&) = delete; bool IsInterrupted() const { return is_interrupted; diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index b5f28a86e..6c4c8e9e4 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -7,6 +7,7 @@ #include #include #include +#include "common/assert.h" #include "common/logging/log.h" #include "common/page_table.h" #include "core/arm/cpu_interrupt_handler.h" @@ -70,15 +71,8 @@ public: } void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override { - switch (exception) { - case Dynarmic::A32::Exception::UndefinedInstruction: - case Dynarmic::A32::Exception::UnpredictableInstruction: - break; - case Dynarmic::A32::Exception::Breakpoint: - break; - } LOG_CRITICAL(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", - static_cast(exception), pc, MemoryReadCode(pc)); + exception, pc, MemoryReadCode(pc)); UNIMPLEMENTED(); } @@ -132,6 +126,7 @@ std::shared_ptr ARM_Dynarmic_32::MakeJit(Common::PageTable& config.page_table = reinterpret_cast*>( page_table.pointers.data()); config.absolute_offset_page_table = true; + config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS; config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128; config.only_detect_misalignment_via_page_table_on_page_boundary = true; @@ -179,6 +174,9 @@ std::shared_ptr ARM_Dynarmic_32::MakeJit(Common::PageTable& if (Settings::values.cpuopt_unsafe_reduce_fp_error) { config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP; } + if (Settings::values.cpuopt_unsafe_inaccurate_nan) { + config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN; + } } return std::make_unique(config); @@ -188,6 +186,10 @@ void ARM_Dynarmic_32::Run() { jit->Run(); } +void ARM_Dynarmic_32::ExceptionalExit() { + jit->ExceptionalExit(); +} + void ARM_Dynarmic_32::Step() { jit->Step(); } @@ -281,7 +283,17 @@ void ARM_Dynarmic_32::ClearInstructionCache() { jit->ClearCache(); } +void ARM_Dynarmic_32::InvalidateCacheRange(VAddr addr, std::size_t size) { + if (!jit) { + return; + } + jit->InvalidateCacheRange(static_cast(addr), size); +} + void ARM_Dynarmic_32::ClearExclusiveState() { + if (!jit) { + return; + } jit->ClearExclusiveState(); } diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.h b/src/core/arm/dynarmic/arm_dynarmic_32.h index 2bab31b92..35e9ced48 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.h +++ b/src/core/arm/dynarmic/arm_dynarmic_32.h @@ -42,6 +42,7 @@ public: u32 GetPSTATE() const override; void SetPSTATE(u32 pstate) override; void Run() override; + void ExceptionalExit() override; void Step() override; VAddr GetTlsAddress() const override; void SetTlsAddress(VAddr address) override; @@ -58,6 +59,7 @@ public: void ClearExclusiveState() override; void ClearInstructionCache() override; + void InvalidateCacheRange(VAddr addr, std::size_t size) override; void PageTableChanged(Common::PageTable& new_page_table, std::size_t new_address_space_size_in_bits) override; diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index ce9968724..4c5ebca22 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -6,6 +6,7 @@ #include #include #include +#include "common/assert.h" #include "common/logging/log.h" #include "common/page_table.h" #include "core/arm/cpu_interrupt_handler.h" @@ -13,11 +14,9 @@ #include "core/arm/dynarmic/arm_exclusive_monitor.h" #include "core/core.h" #include "core/core_timing.h" -#include "core/core_timing_util.h" -#include "core/gdbstub/gdbstub.h" #include "core/hardware_properties.h" +#include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/svc.h" #include "core/memory.h" #include "core/settings.h" @@ -82,16 +81,9 @@ public: } void InterpreterFallback(u64 pc, std::size_t num_instructions) override { - LOG_INFO(Core_ARM, "Unicorn fallback @ 0x{:X} for {} instructions (instr = {:08X})", pc, - num_instructions, MemoryReadCode(pc)); - - ARM_Interface::ThreadContext64 ctx; - parent.SaveContext(ctx); - parent.inner_unicorn.LoadContext(ctx); - parent.inner_unicorn.ExecuteInstructions(num_instructions); - parent.inner_unicorn.SaveContext(ctx); - parent.LoadContext(ctx); - num_interpreted_instructions += num_instructions; + LOG_ERROR(Core_ARM, + "Unimplemented instruction @ 0x{:X} for {} instructions (instr = {:08X})", pc, + num_instructions, MemoryReadCode(pc)); } void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override { @@ -103,16 +95,6 @@ public: case Dynarmic::A64::Exception::Yield: return; case Dynarmic::A64::Exception::Breakpoint: - if (GDBStub::IsServerEnabled()) { - parent.jit->HaltExecution(); - parent.SetPC(pc); - Kernel::Thread* const thread = parent.system.CurrentScheduler().GetCurrentThread(); - parent.SaveContext(thread->GetContext64()); - GDBStub::Break(); - GDBStub::SendTrap(thread, 5); - return; - } - [[fallthrough]]; default: ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", static_cast(exception), pc, MemoryReadCode(pc)); @@ -127,18 +109,17 @@ public: if (parent.uses_wall_clock) { return; } + // 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 // device a way so that timing is consistent across all cores without increasing the ticks 4 // times. - u64 amortized_ticks = - (ticks - num_interpreted_instructions) / Core::Hardware::NUM_CPU_CORES; + u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES; // Always execute at least one tick. amortized_ticks = std::max(amortized_ticks, 1); parent.system.CoreTiming().AddTicks(amortized_ticks); - num_interpreted_instructions = 0; } u64 GetTicksRemaining() override { @@ -156,7 +137,6 @@ public: } ARM_Dynarmic_64& parent; - std::size_t num_interpreted_instructions = 0; u64 tpidrro_el0 = 0; u64 tpidr_el0 = 0; static constexpr u64 minimum_run_cycles = 1000U; @@ -172,6 +152,7 @@ std::shared_ptr ARM_Dynarmic_64::MakeJit(Common::PageTable& // Memory config.page_table = reinterpret_cast(page_table.pointers.data()); config.page_table_address_space_bits = address_space_bits; + config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS; config.silently_mirror_page_table = false; config.absolute_offset_page_table = true; config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128; @@ -231,6 +212,9 @@ std::shared_ptr ARM_Dynarmic_64::MakeJit(Common::PageTable& if (Settings::values.cpuopt_unsafe_reduce_fp_error) { config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_ReducedErrorFP; } + if (Settings::values.cpuopt_unsafe_inaccurate_nan) { + config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN; + } } return std::make_shared(config); @@ -240,6 +224,10 @@ void ARM_Dynarmic_64::Run() { jit->Run(); } +void ARM_Dynarmic_64::ExceptionalExit() { + jit->ExceptionalExit(); +} + void ARM_Dynarmic_64::Step() { cb->InterpreterFallback(jit->GetPC(), 1); } @@ -248,12 +236,8 @@ ARM_Dynarmic_64::ARM_Dynarmic_64(System& system, CPUInterrupts& interrupt_handle bool uses_wall_clock, ExclusiveMonitor& exclusive_monitor, std::size_t core_index) : ARM_Interface{system, interrupt_handlers, uses_wall_clock}, - cb(std::make_unique(*this)), inner_unicorn{system, interrupt_handlers, - uses_wall_clock, - ARM_Unicorn::Arch::AArch64, - core_index}, - core_index{core_index}, exclusive_monitor{ - dynamic_cast(exclusive_monitor)} {} + cb(std::make_unique(*this)), core_index{core_index}, + exclusive_monitor{dynamic_cast(exclusive_monitor)} {} ARM_Dynarmic_64::~ARM_Dynarmic_64() = default; @@ -342,7 +326,17 @@ void ARM_Dynarmic_64::ClearInstructionCache() { jit->ClearCache(); } +void ARM_Dynarmic_64::InvalidateCacheRange(VAddr addr, std::size_t size) { + if (!jit) { + return; + } + jit->InvalidateCacheRange(addr, size); +} + void ARM_Dynarmic_64::ClearExclusiveState() { + if (!jit) { + return; + } jit->ClearExclusiveState(); } diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h index 403c55961..329b59a32 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.h +++ b/src/core/arm/dynarmic/arm_dynarmic_64.h @@ -12,7 +12,6 @@ #include "common/hash.h" #include "core/arm/arm_interface.h" #include "core/arm/exclusive_monitor.h" -#include "core/arm/unicorn/arm_unicorn.h" namespace Core::Memory { class Memory; @@ -41,6 +40,7 @@ public: void SetPSTATE(u32 pstate) override; void Run() override; void Step() override; + void ExceptionalExit() override; VAddr GetTlsAddress() const override; void SetTlsAddress(VAddr address) override; void SetTPIDR_EL0(u64 value) override; @@ -56,6 +56,7 @@ public: void ClearExclusiveState() override; void ClearInstructionCache() override; + void InvalidateCacheRange(VAddr addr, std::size_t size) override; void PageTableChanged(Common::PageTable& new_page_table, std::size_t new_address_space_size_in_bits) override; @@ -71,7 +72,6 @@ private: std::unique_ptr cb; JitCacheType jit_cache; std::shared_ptr jit; - ARM_Unicorn inner_unicorn; std::size_t core_index; DynarmicExclusiveMonitor& exclusive_monitor; diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp deleted file mode 100644 index 1df3f3ed1..000000000 --- a/src/core/arm/unicorn/arm_unicorn.cpp +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include "common/assert.h" -#include "common/microprofile.h" -#include "core/arm/cpu_interrupt_handler.h" -#include "core/arm/unicorn/arm_unicorn.h" -#include "core/core.h" -#include "core/core_timing.h" -#include "core/hle/kernel/scheduler.h" -#include "core/hle/kernel/svc.h" -#include "core/memory.h" - -namespace Core { - -// Load Unicorn DLL once on Windows using RAII -#ifdef _MSC_VER -#include -struct LoadDll { -private: - LoadDll() { - ASSERT(uc_dyn_load(NULL, 0)); - } - ~LoadDll() { - ASSERT(uc_dyn_free()); - } - static LoadDll g_load_dll; -}; -LoadDll LoadDll::g_load_dll; -#endif - -#define CHECKED(expr) \ - do { \ - if (auto _cerr = (expr)) { \ - ASSERT_MSG(false, "Call " #expr " failed with error: {} ({})\n", _cerr, \ - uc_strerror(_cerr)); \ - } \ - } while (0) - -static void CodeHook(uc_engine* uc, uint64_t address, uint32_t size, void* user_data) { - GDBStub::BreakpointAddress bkpt = - GDBStub::GetNextBreakpointFromAddress(address, GDBStub::BreakpointType::Execute); - if (GDBStub::IsMemoryBreak() || - (bkpt.type != GDBStub::BreakpointType::None && address == bkpt.address)) { - auto core = static_cast(user_data); - core->RecordBreak(bkpt); - uc_emu_stop(uc); - } -} - -static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value, - void* user_data) { - auto* const system = static_cast(user_data); - - ARM_Interface::ThreadContext64 ctx{}; - system->CurrentArmInterface().SaveContext(ctx); - ASSERT_MSG(false, "Attempted to read from unmapped memory: 0x{:X}, pc=0x{:X}, lr=0x{:X}", addr, - ctx.pc, ctx.cpu_registers[30]); - - return false; -} - -ARM_Unicorn::ARM_Unicorn(System& system, CPUInterrupts& interrupt_handlers, bool uses_wall_clock, - Arch architecture, std::size_t core_index) - : ARM_Interface{system, interrupt_handlers, uses_wall_clock}, core_index{core_index} { - const auto arch = architecture == Arch::AArch32 ? UC_ARCH_ARM : UC_ARCH_ARM64; - CHECKED(uc_open(arch, UC_MODE_ARM, &uc)); - - auto fpv = 3 << 20; - CHECKED(uc_reg_write(uc, UC_ARM64_REG_CPACR_EL1, &fpv)); - - uc_hook hook{}; - CHECKED(uc_hook_add(uc, &hook, UC_HOOK_INTR, (void*)InterruptHook, this, 0, UINT64_MAX)); - CHECKED(uc_hook_add(uc, &hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, &system, 0, - UINT64_MAX)); - if (GDBStub::IsServerEnabled()) { - CHECKED(uc_hook_add(uc, &hook, UC_HOOK_CODE, (void*)CodeHook, this, 0, UINT64_MAX)); - last_bkpt_hit = false; - } -} - -ARM_Unicorn::~ARM_Unicorn() { - CHECKED(uc_close(uc)); -} - -void ARM_Unicorn::SetPC(u64 pc) { - CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &pc)); -} - -u64 ARM_Unicorn::GetPC() const { - u64 val{}; - CHECKED(uc_reg_read(uc, UC_ARM64_REG_PC, &val)); - return val; -} - -u64 ARM_Unicorn::GetReg(int regn) const { - u64 val{}; - auto treg = UC_ARM64_REG_SP; - if (regn <= 28) { - treg = (uc_arm64_reg)(UC_ARM64_REG_X0 + regn); - } else if (regn < 31) { - treg = (uc_arm64_reg)(UC_ARM64_REG_X29 + regn - 29); - } - CHECKED(uc_reg_read(uc, treg, &val)); - return val; -} - -void ARM_Unicorn::SetReg(int regn, u64 val) { - auto treg = UC_ARM64_REG_SP; - if (regn <= 28) { - treg = (uc_arm64_reg)(UC_ARM64_REG_X0 + regn); - } else if (regn < 31) { - treg = (uc_arm64_reg)(UC_ARM64_REG_X29 + regn - 29); - } - CHECKED(uc_reg_write(uc, treg, &val)); -} - -u128 ARM_Unicorn::GetVectorReg(int /*index*/) const { - UNIMPLEMENTED(); - static constexpr u128 res{}; - return res; -} - -void ARM_Unicorn::SetVectorReg(int /*index*/, u128 /*value*/) { - UNIMPLEMENTED(); -} - -u32 ARM_Unicorn::GetPSTATE() const { - u64 nzcv{}; - CHECKED(uc_reg_read(uc, UC_ARM64_REG_NZCV, &nzcv)); - return static_cast(nzcv); -} - -void ARM_Unicorn::SetPSTATE(u32 pstate) { - u64 nzcv = pstate; - CHECKED(uc_reg_write(uc, UC_ARM64_REG_NZCV, &nzcv)); -} - -VAddr ARM_Unicorn::GetTlsAddress() const { - u64 base{}; - CHECKED(uc_reg_read(uc, UC_ARM64_REG_TPIDRRO_EL0, &base)); - return base; -} - -void ARM_Unicorn::SetTlsAddress(VAddr base) { - CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDRRO_EL0, &base)); -} - -u64 ARM_Unicorn::GetTPIDR_EL0() const { - u64 value{}; - CHECKED(uc_reg_read(uc, UC_ARM64_REG_TPIDR_EL0, &value)); - return value; -} - -void ARM_Unicorn::SetTPIDR_EL0(u64 value) { - CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDR_EL0, &value)); -} - -void ARM_Unicorn::ChangeProcessorID(std::size_t new_core_id) { - core_index = new_core_id; -} - -void ARM_Unicorn::Run() { - if (GDBStub::IsServerEnabled()) { - ExecuteInstructions(std::max(4000000U, 0U)); - } else { - while (true) { - if (interrupt_handlers[core_index].IsInterrupted()) { - return; - } - ExecuteInstructions(10); - } - } -} - -void ARM_Unicorn::Step() { - ExecuteInstructions(1); -} - -MICROPROFILE_DEFINE(ARM_Jit_Unicorn, "ARM JIT", "Unicorn", MP_RGB(255, 64, 64)); - -void ARM_Unicorn::ExecuteInstructions(std::size_t num_instructions) { - MICROPROFILE_SCOPE(ARM_Jit_Unicorn); - - // Temporarily map the code page for Unicorn - u64 map_addr{GetPC() & ~Memory::PAGE_MASK}; - std::vector page_buffer(Memory::PAGE_SIZE); - system.Memory().ReadBlock(map_addr, page_buffer.data(), page_buffer.size()); - - CHECKED(uc_mem_map_ptr(uc, map_addr, page_buffer.size(), - UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC, page_buffer.data())); - CHECKED(uc_emu_start(uc, GetPC(), 1ULL << 63, 0, num_instructions)); - CHECKED(uc_mem_unmap(uc, map_addr, page_buffer.size())); - if (GDBStub::IsServerEnabled()) { - if (last_bkpt_hit && last_bkpt.type == GDBStub::BreakpointType::Execute) { - uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address); - } - - Kernel::Thread* const thread = system.CurrentScheduler().GetCurrentThread(); - SaveContext(thread->GetContext64()); - if (last_bkpt_hit || GDBStub::IsMemoryBreak() || GDBStub::GetCpuStepFlag()) { - last_bkpt_hit = false; - GDBStub::Break(); - GDBStub::SendTrap(thread, 5); - } - } -} - -void ARM_Unicorn::SaveContext(ThreadContext64& ctx) { - int uregs[32]; - void* tregs[32]; - - CHECKED(uc_reg_read(uc, UC_ARM64_REG_SP, &ctx.sp)); - CHECKED(uc_reg_read(uc, UC_ARM64_REG_PC, &ctx.pc)); - CHECKED(uc_reg_read(uc, UC_ARM64_REG_NZCV, &ctx.pstate)); - - for (auto i = 0; i < 29; ++i) { - uregs[i] = UC_ARM64_REG_X0 + i; - tregs[i] = &ctx.cpu_registers[i]; - } - uregs[29] = UC_ARM64_REG_X29; - tregs[29] = (void*)&ctx.cpu_registers[29]; - uregs[30] = UC_ARM64_REG_X30; - tregs[30] = (void*)&ctx.cpu_registers[30]; - - CHECKED(uc_reg_read_batch(uc, uregs, tregs, 31)); - - for (int i = 0; i < 32; ++i) { - uregs[i] = UC_ARM64_REG_Q0 + i; - tregs[i] = &ctx.vector_registers[i]; - } - - CHECKED(uc_reg_read_batch(uc, uregs, tregs, 32)); -} - -void ARM_Unicorn::LoadContext(const ThreadContext64& ctx) { - int uregs[32]; - void* tregs[32]; - - CHECKED(uc_reg_write(uc, UC_ARM64_REG_SP, &ctx.sp)); - CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &ctx.pc)); - CHECKED(uc_reg_write(uc, UC_ARM64_REG_NZCV, &ctx.pstate)); - - for (int i = 0; i < 29; ++i) { - uregs[i] = UC_ARM64_REG_X0 + i; - tregs[i] = (void*)&ctx.cpu_registers[i]; - } - uregs[29] = UC_ARM64_REG_X29; - tregs[29] = (void*)&ctx.cpu_registers[29]; - uregs[30] = UC_ARM64_REG_X30; - tregs[30] = (void*)&ctx.cpu_registers[30]; - - CHECKED(uc_reg_write_batch(uc, uregs, tregs, 31)); - - for (auto i = 0; i < 32; ++i) { - uregs[i] = UC_ARM64_REG_Q0 + i; - tregs[i] = (void*)&ctx.vector_registers[i]; - } - - CHECKED(uc_reg_write_batch(uc, uregs, tregs, 32)); -} - -void ARM_Unicorn::PrepareReschedule() { - CHECKED(uc_emu_stop(uc)); -} - -void ARM_Unicorn::ClearExclusiveState() {} - -void ARM_Unicorn::ClearInstructionCache() {} - -void ARM_Unicorn::RecordBreak(GDBStub::BreakpointAddress bkpt) { - last_bkpt = bkpt; - last_bkpt_hit = true; -} - -void ARM_Unicorn::InterruptHook(uc_engine* uc, u32 int_no, void* user_data) { - u32 esr{}; - CHECKED(uc_reg_read(uc, UC_ARM64_REG_ESR, &esr)); - - const auto ec = esr >> 26; - const auto iss = esr & 0xFFFFFF; - - auto* const arm_instance = static_cast(user_data); - - switch (ec) { - case 0x15: // SVC - Kernel::Svc::Call(arm_instance->system, iss); - break; - } -} - -} // namespace Core diff --git a/src/core/arm/unicorn/arm_unicorn.h b/src/core/arm/unicorn/arm_unicorn.h deleted file mode 100644 index 810aff311..000000000 --- a/src/core/arm/unicorn/arm_unicorn.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include "common/common_types.h" -#include "core/arm/arm_interface.h" -#include "core/gdbstub/gdbstub.h" - -namespace Core { - -class System; - -class ARM_Unicorn final : public ARM_Interface { -public: - enum class Arch { - AArch32, // 32-bit ARM - AArch64, // 64-bit ARM - }; - - explicit ARM_Unicorn(System& system, CPUInterrupts& interrupt_handlers, bool uses_wall_clock, - Arch architecture, std::size_t core_index); - ~ARM_Unicorn() override; - - void SetPC(u64 pc) override; - u64 GetPC() const override; - u64 GetReg(int index) const override; - void SetReg(int index, u64 value) override; - u128 GetVectorReg(int index) const override; - void SetVectorReg(int index, u128 value) override; - u32 GetPSTATE() const override; - void SetPSTATE(u32 pstate) override; - VAddr GetTlsAddress() const override; - void SetTlsAddress(VAddr address) override; - void SetTPIDR_EL0(u64 value) override; - u64 GetTPIDR_EL0() const override; - void ChangeProcessorID(std::size_t new_core_id) override; - void PrepareReschedule() override; - void ClearExclusiveState() override; - void ExecuteInstructions(std::size_t num_instructions); - void Run() override; - void Step() override; - void ClearInstructionCache() override; - void PageTableChanged(Common::PageTable&, std::size_t) override {} - void RecordBreak(GDBStub::BreakpointAddress bkpt); - - void SaveContext(ThreadContext32& ctx) override {} - void SaveContext(ThreadContext64& ctx) override; - void LoadContext(const ThreadContext32& ctx) override {} - void LoadContext(const ThreadContext64& ctx) override; - -private: - static void InterruptHook(uc_engine* uc, u32 int_no, void* user_data); - - uc_engine* uc{}; - GDBStub::BreakpointAddress last_bkpt{}; - bool last_bkpt_hit = false; - std::size_t core_index; -}; - -} // namespace Core diff --git a/src/core/core.cpp b/src/core/core.cpp index 81e8cc338..1a2002dec 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -25,13 +25,12 @@ #include "core/file_sys/sdmc_factory.h" #include "core/file_sys/vfs_concat.h" #include "core/file_sys/vfs_real.h" -#include "core/gdbstub/gdbstub.h" #include "core/hardware_interrupt_manager.h" #include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/physical_core.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" #include "core/hle/service/am/applets/applets.h" #include "core/hle/service/apm/controller.h" @@ -40,6 +39,7 @@ #include "core/hle/service/lm/manager.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" +#include "core/hle/service/time/time_manager.h" #include "core/loader/loader.h" #include "core/memory.h" #include "core/memory/cheat_engine.h" @@ -91,37 +91,47 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, std::string dir_name; std::string filename; Common::SplitPath(path, &dir_name, &filename, nullptr); + if (filename == "00") { const auto dir = vfs->OpenDirectory(dir_name, FileSys::Mode::Read); std::vector concat; - for (u8 i = 0; i < 0x10; ++i) { - auto next = dir->GetFile(fmt::format("{:02X}", i)); - if (next != nullptr) + + for (u32 i = 0; i < 0x10; ++i) { + const auto file_name = fmt::format("{:02X}", i); + auto next = dir->GetFile(file_name); + + if (next != nullptr) { concat.push_back(std::move(next)); - else { - next = dir->GetFile(fmt::format("{:02x}", i)); - if (next != nullptr) - concat.push_back(std::move(next)); - else + } else { + next = dir->GetFile(file_name); + + if (next == nullptr) { break; + } + + concat.push_back(std::move(next)); } } - if (concat.empty()) + if (concat.empty()) { return nullptr; + } - return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName()); + return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(std::move(concat), + dir->GetName()); } - if (Common::FS::IsDirectory(path)) - return vfs->OpenFile(path + "/" + "main", FileSys::Mode::Read); + if (Common::FS::IsDirectory(path)) { + return vfs->OpenFile(path + "/main", FileSys::Mode::Read); + } return vfs->OpenFile(path, FileSys::Mode::Read); } + struct System::Impl { explicit Impl(System& system) : kernel{system}, fs_controller{system}, memory{system}, - cpu_manager{system}, reporter{system}, applet_manager{system} {} + cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {} ResultStatus Run() { status = ResultStatus::Success; @@ -144,12 +154,12 @@ struct System::Impl { } ResultStatus Init(System& system, Frontend::EmuWindow& emu_window) { - LOG_DEBUG(HW_Memory, "initialized OK"); + LOG_DEBUG(Core, "initialized OK"); device_memory = std::make_unique(); is_multicore = Settings::values.use_multi_core.GetValue(); - is_async_gpu = is_multicore || Settings::values.use_asynchronous_gpu_emulation.GetValue(); + is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue(); kernel.SetMulticore(is_multicore); cpu_manager.SetMulticore(is_multicore); @@ -178,17 +188,19 @@ struct System::Impl { arp_manager.ResetAll(); telemetry_session = std::make_unique(); - service_manager = std::make_shared(kernel); - Service::Init(service_manager, system); - GDBStub::DeferStart(); - - interrupt_manager = std::make_unique(system); gpu_core = VideoCore::CreateGPU(emu_window, system); if (!gpu_core) { return ResultStatus::ErrorVideoCore; } + service_manager = std::make_shared(kernel); + services = std::make_unique(service_manager, system); + interrupt_manager = std::make_unique(system); + + // Initialize time manager, which must happen after kernel is created + time_manager.Initialize(); + is_powered_on = true; exit_lock = false; @@ -202,9 +214,11 @@ struct System::Impl { return ResultStatus::Success; } - ResultStatus Load(System& system, Frontend::EmuWindow& emu_window, - const std::string& filepath) { - app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath)); + ResultStatus Load(System& system, Frontend::EmuWindow& emu_window, const std::string& filepath, + std::size_t program_index) { + app_loader = Loader::GetLoader(system, GetGameFileFromPath(virtual_filesystem, filepath), + program_index); + if (!app_loader) { LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); return ResultStatus::ErrorGetLoader; @@ -218,12 +232,12 @@ struct System::Impl { return init_result; } - telemetry_session->AddInitialInfo(*app_loader); + telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider); auto main_process = Kernel::Process::Create(system, "main", Kernel::Process::ProcessType::Userland); const auto [load_result, load_parameters] = app_loader->Load(*main_process, system); if (load_result != Loader::ResultStatus::Success) { - LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast(load_result)); + LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", load_result); Shutdown(); return static_cast(static_cast(ResultStatus::ErrorLoader) + @@ -231,6 +245,7 @@ struct System::Impl { } AddGlueRegistrationForProcess(*app_loader, *main_process); kernel.MakeCurrentProcess(main_process.get()); + kernel.InitializeCores(); // Initialize cheat engine if (cheat_engine) { @@ -252,8 +267,7 @@ struct System::Impl { u64 title_id{0}; if (app_loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) { - LOG_ERROR(Core, "Failed to find title id for ROM (Error {})", - static_cast(load_result)); + LOG_ERROR(Core, "Failed to find title id for ROM (Error {})", load_result); } perf_stats = std::make_unique(title_id); // Reset counters and set time origin to current frame @@ -289,19 +303,17 @@ struct System::Impl { } // Shutdown emulation session - GDBStub::Shutdown(); - Service::Shutdown(); + services.reset(); service_manager.reset(); cheat_engine.reset(); telemetry_session.reset(); - device_memory.reset(); // Close all CPU/threading state cpu_manager.Shutdown(); // Shutdown kernel and core timing - kernel.Shutdown(); core_timing.Shutdown(); + kernel.Shutdown(); // Close app loader app_loader.reset(); @@ -332,7 +344,7 @@ struct System::Impl { Service::Glue::ApplicationLaunchProperty launch{}; launch.title_id = process.GetTitleID(); - FileSys::PatchManager pm{launch.title_id}; + FileSys::PatchManager pm{launch.title_id, fs_controller, *content_provider}; launch.version = pm.GetGameVersion().value_or(0); // TODO(DarkLordZach): When FSController/Game Card Support is added, if @@ -387,10 +399,14 @@ struct System::Impl { /// Service State Service::Glue::ARPManager arp_manager; Service::LM::Manager lm_manager{reporter}; + Service::Time::TimeManager time_manager; /// Service manager std::shared_ptr service_manager; + /// Services + std::unique_ptr services; + /// Telemetry session for this emulation session std::unique_ptr telemetry_session; @@ -406,6 +422,8 @@ struct System::Impl { bool is_multicore{}; bool is_async_gpu{}; + ExecuteProgramCallback execute_program_callback; + std::array dynarmic_ticks{}; std::array microprofile_dynarmic{}; }; @@ -437,8 +455,17 @@ void System::InvalidateCpuInstructionCaches() { impl->kernel.InvalidateAllInstructionCaches(); } -System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) { - return impl->Load(*this, emu_window, filepath); +void System::InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size) { + impl->kernel.InvalidateCpuInstructionCacheRange(addr, size); +} + +void System::Shutdown() { + impl->Shutdown(); +} + +System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath, + std::size_t program_index) { + return impl->Load(*this, emu_window, filepath, program_index); } bool System::IsPoweredOn() const { @@ -466,11 +493,11 @@ const TelemetrySession& System::TelemetrySession() const { } ARM_Interface& System::CurrentArmInterface() { - return impl->kernel.CurrentScheduler().GetCurrentThread()->ArmInterface(); + return impl->kernel.CurrentPhysicalCore().ArmInterface(); } const ARM_Interface& System::CurrentArmInterface() const { - return impl->kernel.CurrentScheduler().GetCurrentThread()->ArmInterface(); + return impl->kernel.CurrentPhysicalCore().ArmInterface(); } std::size_t System::CurrentCoreIndex() const { @@ -479,14 +506,6 @@ std::size_t System::CurrentCoreIndex() const { return core; } -Kernel::Scheduler& System::CurrentScheduler() { - return impl->kernel.CurrentScheduler(); -} - -const Kernel::Scheduler& System::CurrentScheduler() const { - return impl->kernel.CurrentScheduler(); -} - Kernel::PhysicalCore& System::CurrentPhysicalCore() { return impl->kernel.CurrentPhysicalCore(); } @@ -495,22 +514,14 @@ const Kernel::PhysicalCore& System::CurrentPhysicalCore() const { return impl->kernel.CurrentPhysicalCore(); } -Kernel::Scheduler& System::Scheduler(std::size_t core_index) { - return impl->kernel.Scheduler(core_index); -} - -const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const { - return impl->kernel.Scheduler(core_index); +/// Gets the global scheduler +Kernel::GlobalSchedulerContext& System::GlobalSchedulerContext() { + return impl->kernel.GlobalSchedulerContext(); } /// Gets the global scheduler -Kernel::GlobalScheduler& System::GlobalScheduler() { - return impl->kernel.GlobalScheduler(); -} - -/// Gets the global scheduler -const Kernel::GlobalScheduler& System::GlobalScheduler() const { - return impl->kernel.GlobalScheduler(); +const Kernel::GlobalSchedulerContext& System::GlobalSchedulerContext() const { + return impl->kernel.GlobalSchedulerContext(); } Kernel::Process* System::CurrentProcess() { @@ -530,15 +541,11 @@ const Kernel::Process* System::CurrentProcess() const { } ARM_Interface& System::ArmInterface(std::size_t core_index) { - auto* thread = impl->kernel.Scheduler(core_index).GetCurrentThread(); - ASSERT(thread && !thread->IsHLEThread()); - return thread->ArmInterface(); + return impl->kernel.PhysicalCore(core_index).ArmInterface(); } const ARM_Interface& System::ArmInterface(std::size_t core_index) const { - auto* thread = impl->kernel.Scheduler(core_index).GetCurrentThread(); - ASSERT(thread && !thread->IsHLEThread()); - return thread->ArmInterface(); + return impl->kernel.PhysicalCore(core_index).ArmInterface(); } ExclusiveMonitor& System::Monitor() { @@ -625,7 +632,11 @@ const std::string& System::GetStatusDetails() const { return impl->status_details; } -Loader::AppLoader& System::GetAppLoader() const { +Loader::AppLoader& System::GetAppLoader() { + return *impl->app_loader; +} + +const Loader::AppLoader& System::GetAppLoader() const { return *impl->app_loader; } @@ -717,6 +728,14 @@ const Service::LM::Manager& System::GetLogManager() const { return impl->lm_manager; } +Service::Time::TimeManager& System::GetTimeManager() { + return impl->time_manager; +} + +const Service::Time::TimeManager& System::GetTimeManager() const { + return impl->time_manager; +} + void System::SetExitLock(bool locked) { impl->exit_lock = locked; } @@ -733,14 +752,6 @@ const System::CurrentBuildProcessID& System::GetCurrentProcessBuildID() const { return impl->build_id; } -System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) { - return impl->Init(*this, emu_window); -} - -void System::Shutdown() { - impl->Shutdown(); -} - Service::SM::ServiceManager& System::ServiceManager() { return *impl->service_manager; } @@ -771,4 +782,16 @@ bool System::IsMulticore() const { return impl->is_multicore; } +void System::RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback) { + impl->execute_program_callback = std::move(callback); +} + +void System::ExecuteProgram(std::size_t program_index) { + if (impl->execute_program_callback) { + impl->execute_program_callback(program_index); + } else { + LOG_CRITICAL(Core, "execute_program_callback must be initialized by the frontend"); + } +} + } // namespace Core diff --git a/src/core/core.h b/src/core/core.h index 83ded63a5..579a774e4 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include @@ -25,11 +26,11 @@ class VfsFilesystem; } // namespace FileSys namespace Kernel { -class GlobalScheduler; +class GlobalSchedulerContext; class KernelCore; class PhysicalCore; class Process; -class Scheduler; +class KScheduler; } // namespace Kernel namespace Loader { @@ -69,6 +70,10 @@ namespace SM { class ServiceManager; } // namespace SM +namespace Time { +class TimeManager; +} // namespace Time + } // namespace Service namespace Tegra { @@ -120,7 +125,7 @@ public: * Gets the instance of the System singleton class. * @returns Reference to the instance of the System singleton class. */ - static System& GetInstance() { + [[deprecated("Use of the global system instance is deprecated")]] static System& GetInstance() { return s_instance; } @@ -140,19 +145,19 @@ public: * Run the OS and Application * This function will start emulation and run the relevant devices */ - ResultStatus Run(); + [[nodiscard]] ResultStatus Run(); /** * Pause the OS and Application * This function will pause emulation and stop the relevant devices */ - ResultStatus Pause(); + [[nodiscard]] ResultStatus Pause(); /** * Step the CPU one instruction * @return Result status, indicating whether or not the operation succeeded. */ - ResultStatus SingleStep(); + [[nodiscard]] ResultStatus SingleStep(); /** * Invalidate the CPU instruction caches @@ -161,6 +166,8 @@ public: */ void InvalidateCpuInstructionCaches(); + void InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size); + /// Shutdown the emulated system. void Shutdown(); @@ -169,22 +176,24 @@ public: * @param emu_window Reference to the host-system window used for video output and keyboard * input. * @param filepath String path to the executable application to load on the host file system. + * @param program_index Specifies the index within the container of the program to launch. * @returns ResultStatus code, indicating if the operation succeeded. */ - ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath); + [[nodiscard]] ResultStatus Load(Frontend::EmuWindow& emu_window, const std::string& filepath, + std::size_t program_index = 0); /** * Indicates if the emulated system is powered on (all subsystems initialized and able to run an * application). * @returns True if the emulated system is powered on, otherwise false. */ - bool IsPoweredOn() const; + [[nodiscard]] bool IsPoweredOn() const; /// Gets a reference to the telemetry session for this emulation session. - Core::TelemetrySession& TelemetrySession(); + [[nodiscard]] Core::TelemetrySession& TelemetrySession(); /// Gets a reference to the telemetry session for this emulation session. - const Core::TelemetrySession& TelemetrySession() const; + [[nodiscard]] const Core::TelemetrySession& TelemetrySession() const; /// Prepare the core emulation for a reschedule void PrepareReschedule(); @@ -193,181 +202,166 @@ public: void PrepareReschedule(u32 core_index); /// Gets and resets core performance statistics - PerfStatsResults GetAndResetPerfStats(); + [[nodiscard]] PerfStatsResults GetAndResetPerfStats(); /// Gets an ARM interface to the CPU core that is currently running - ARM_Interface& CurrentArmInterface(); + [[nodiscard]] ARM_Interface& CurrentArmInterface(); /// Gets an ARM interface to the CPU core that is currently running - const ARM_Interface& CurrentArmInterface() const; + [[nodiscard]] const ARM_Interface& CurrentArmInterface() const; /// Gets the index of the currently running CPU core - std::size_t CurrentCoreIndex() const; - - /// Gets the scheduler for the CPU core that is currently running - Kernel::Scheduler& CurrentScheduler(); - - /// Gets the scheduler for the CPU core that is currently running - const Kernel::Scheduler& CurrentScheduler() const; + [[nodiscard]] std::size_t CurrentCoreIndex() const; /// Gets the physical core for the CPU core that is currently running - Kernel::PhysicalCore& CurrentPhysicalCore(); + [[nodiscard]] Kernel::PhysicalCore& CurrentPhysicalCore(); /// Gets the physical core for the CPU core that is currently running - const Kernel::PhysicalCore& CurrentPhysicalCore() const; + [[nodiscard]] const Kernel::PhysicalCore& CurrentPhysicalCore() const; /// Gets a reference to an ARM interface for the CPU core with the specified index - ARM_Interface& ArmInterface(std::size_t core_index); + [[nodiscard]] ARM_Interface& ArmInterface(std::size_t core_index); /// Gets a const reference to an ARM interface from the CPU core with the specified index - const ARM_Interface& ArmInterface(std::size_t core_index) const; + [[nodiscard]] const ARM_Interface& ArmInterface(std::size_t core_index) const; - CpuManager& GetCpuManager(); + /// Gets a reference to the underlying CPU manager. + [[nodiscard]] CpuManager& GetCpuManager(); - const CpuManager& GetCpuManager() const; + /// Gets a const reference to the underlying CPU manager + [[nodiscard]] const CpuManager& GetCpuManager() const; /// Gets a reference to the exclusive monitor - ExclusiveMonitor& Monitor(); + [[nodiscard]] ExclusiveMonitor& Monitor(); /// Gets a constant reference to the exclusive monitor - const ExclusiveMonitor& Monitor() const; + [[nodiscard]] const ExclusiveMonitor& Monitor() const; /// Gets a mutable reference to the system memory instance. - Core::Memory::Memory& Memory(); + [[nodiscard]] Core::Memory::Memory& Memory(); /// Gets a constant reference to the system memory instance. - const Core::Memory::Memory& Memory() const; + [[nodiscard]] const Core::Memory::Memory& Memory() const; /// Gets a mutable reference to the GPU interface - Tegra::GPU& GPU(); + [[nodiscard]] Tegra::GPU& GPU(); /// Gets an immutable reference to the GPU interface. - const Tegra::GPU& GPU() const; + [[nodiscard]] const Tegra::GPU& GPU() const; /// Gets a mutable reference to the renderer. - VideoCore::RendererBase& Renderer(); + [[nodiscard]] VideoCore::RendererBase& Renderer(); /// Gets an immutable reference to the renderer. - const VideoCore::RendererBase& Renderer() const; - - /// Gets the scheduler for the CPU core with the specified index - Kernel::Scheduler& Scheduler(std::size_t core_index); - - /// Gets the scheduler for the CPU core with the specified index - const Kernel::Scheduler& Scheduler(std::size_t core_index) const; + [[nodiscard]] const VideoCore::RendererBase& Renderer() const; /// Gets the global scheduler - Kernel::GlobalScheduler& GlobalScheduler(); + [[nodiscard]] Kernel::GlobalSchedulerContext& GlobalSchedulerContext(); /// Gets the global scheduler - const Kernel::GlobalScheduler& GlobalScheduler() const; + [[nodiscard]] const Kernel::GlobalSchedulerContext& GlobalSchedulerContext() const; /// Gets the manager for the guest device memory - Core::DeviceMemory& DeviceMemory(); + [[nodiscard]] Core::DeviceMemory& DeviceMemory(); /// Gets the manager for the guest device memory - const Core::DeviceMemory& DeviceMemory() const; + [[nodiscard]] const Core::DeviceMemory& DeviceMemory() const; /// Provides a pointer to the current process - Kernel::Process* CurrentProcess(); + [[nodiscard]] Kernel::Process* CurrentProcess(); /// Provides a constant pointer to the current process. - const Kernel::Process* CurrentProcess() const; + [[nodiscard]] const Kernel::Process* CurrentProcess() const; /// Provides a reference to the core timing instance. - Timing::CoreTiming& CoreTiming(); + [[nodiscard]] Timing::CoreTiming& CoreTiming(); /// Provides a constant reference to the core timing instance. - const Timing::CoreTiming& CoreTiming() const; + [[nodiscard]] const Timing::CoreTiming& CoreTiming() const; /// Provides a reference to the interrupt manager instance. - Core::Hardware::InterruptManager& InterruptManager(); + [[nodiscard]] Core::Hardware::InterruptManager& InterruptManager(); /// Provides a constant reference to the interrupt manager instance. - const Core::Hardware::InterruptManager& InterruptManager() const; + [[nodiscard]] const Core::Hardware::InterruptManager& InterruptManager() const; /// Provides a reference to the kernel instance. - Kernel::KernelCore& Kernel(); + [[nodiscard]] Kernel::KernelCore& Kernel(); /// Provides a constant reference to the kernel instance. - const Kernel::KernelCore& Kernel() const; + [[nodiscard]] const Kernel::KernelCore& Kernel() const; /// Provides a reference to the internal PerfStats instance. - Core::PerfStats& GetPerfStats(); + [[nodiscard]] Core::PerfStats& GetPerfStats(); /// Provides a constant reference to the internal PerfStats instance. - const Core::PerfStats& GetPerfStats() const; + [[nodiscard]] const Core::PerfStats& GetPerfStats() const; /// Provides a reference to the frame limiter; - Core::FrameLimiter& FrameLimiter(); + [[nodiscard]] Core::FrameLimiter& FrameLimiter(); /// Provides a constant referent to the frame limiter - const Core::FrameLimiter& FrameLimiter() const; + [[nodiscard]] const Core::FrameLimiter& FrameLimiter() const; /// Gets the name of the current game - Loader::ResultStatus GetGameName(std::string& out) const; + [[nodiscard]] Loader::ResultStatus GetGameName(std::string& out) const; void SetStatus(ResultStatus new_status, const char* details); - const std::string& GetStatusDetails() const; + [[nodiscard]] const std::string& GetStatusDetails() const; - Loader::AppLoader& GetAppLoader() const; + [[nodiscard]] Loader::AppLoader& GetAppLoader(); + [[nodiscard]] const Loader::AppLoader& GetAppLoader() const; - Service::SM::ServiceManager& ServiceManager(); - const Service::SM::ServiceManager& ServiceManager() const; + [[nodiscard]] Service::SM::ServiceManager& ServiceManager(); + [[nodiscard]] const Service::SM::ServiceManager& ServiceManager() const; void SetFilesystem(FileSys::VirtualFilesystem vfs); - FileSys::VirtualFilesystem GetFilesystem() const; + [[nodiscard]] FileSys::VirtualFilesystem GetFilesystem() const; void RegisterCheatList(const std::vector& list, const std::array& build_id, VAddr main_region_begin, u64 main_region_size); void SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set); - void SetDefaultAppletFrontendSet(); - Service::AM::Applets::AppletManager& GetAppletManager(); - - const Service::AM::Applets::AppletManager& GetAppletManager() const; + [[nodiscard]] Service::AM::Applets::AppletManager& GetAppletManager(); + [[nodiscard]] const Service::AM::Applets::AppletManager& GetAppletManager() const; void SetContentProvider(std::unique_ptr provider); - FileSys::ContentProvider& GetContentProvider(); + [[nodiscard]] FileSys::ContentProvider& GetContentProvider(); + [[nodiscard]] const FileSys::ContentProvider& GetContentProvider() const; - const FileSys::ContentProvider& GetContentProvider() const; - - Service::FileSystem::FileSystemController& GetFileSystemController(); - - const Service::FileSystem::FileSystemController& GetFileSystemController() const; + [[nodiscard]] Service::FileSystem::FileSystemController& GetFileSystemController(); + [[nodiscard]] const Service::FileSystem::FileSystemController& GetFileSystemController() const; void RegisterContentProvider(FileSys::ContentProviderUnionSlot slot, FileSys::ContentProvider* provider); void ClearContentProvider(FileSys::ContentProviderUnionSlot slot); - const Reporter& GetReporter() const; + [[nodiscard]] const Reporter& GetReporter() const; - Service::Glue::ARPManager& GetARPManager(); + [[nodiscard]] Service::Glue::ARPManager& GetARPManager(); + [[nodiscard]] const Service::Glue::ARPManager& GetARPManager() const; - const Service::Glue::ARPManager& GetARPManager() const; + [[nodiscard]] Service::APM::Controller& GetAPMController(); + [[nodiscard]] const Service::APM::Controller& GetAPMController() const; - Service::APM::Controller& GetAPMController(); + [[nodiscard]] Service::LM::Manager& GetLogManager(); + [[nodiscard]] const Service::LM::Manager& GetLogManager() const; - const Service::APM::Controller& GetAPMController() const; - - Service::LM::Manager& GetLogManager(); - - const Service::LM::Manager& GetLogManager() const; + [[nodiscard]] Service::Time::TimeManager& GetTimeManager(); + [[nodiscard]] const Service::Time::TimeManager& GetTimeManager() const; void SetExitLock(bool locked); - - bool GetExitLock() const; + [[nodiscard]] bool GetExitLock() const; void SetCurrentProcessBuildID(const CurrentBuildProcessID& id); - - const CurrentBuildProcessID& GetCurrentProcessBuildID() const; + [[nodiscard]] const CurrentBuildProcessID& GetCurrentProcessBuildID() const; /// Register a host thread as an emulated CPU Core. void RegisterCoreThread(std::size_t id); @@ -382,19 +376,28 @@ public: void ExitDynarmicProfile(); /// Tells if system is running on multicore. - bool IsMulticore() const; + [[nodiscard]] bool IsMulticore() const; + + /// Type used for the frontend to designate a callback for System to re-launch the application + /// using a specified program index. + using ExecuteProgramCallback = std::function; + + /** + * Registers a callback from the frontend for System to re-launch the application using a + * specified program index. + * @param callback Callback from the frontend to relaunch the application. + */ + void RegisterExecuteProgramCallback(ExecuteProgramCallback&& callback); + + /** + * Instructs the frontend to re-launch the application using the specified program_index. + * @param program_index Specifies the index within the application of the program to launch. + */ + void ExecuteProgram(std::size_t program_index); private: System(); - /** - * Initialize the emulated system. - * @param emu_window Reference to the host-system window used for video output and keyboard - * input. - * @return ResultStatus code, indicating if the operation succeeded. - */ - ResultStatus Init(Frontend::EmuWindow& emu_window); - struct Impl; std::unique_ptr impl; diff --git a/src/core/core_timing.h b/src/core/core_timing.h index b0b6036e4..77ff4c6fe 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -27,8 +27,8 @@ using TimedCallback = /// Contains the characteristics of a particular event. struct EventType { - EventType(TimedCallback&& callback, std::string&& name) - : callback{std::move(callback)}, name{std::move(name)} {} + explicit EventType(TimedCallback&& callback_, std::string&& name_) + : callback{std::move(callback_)}, name{std::move(name_)} {} /// The event's callback function. TimedCallback callback; @@ -67,8 +67,8 @@ public: void Shutdown(); /// Sets if emulation is multicore or single core, must be set before Initialize - void SetMulticore(bool is_multicore) { - this->is_multicore = is_multicore; + void SetMulticore(bool is_multicore_) { + is_multicore = is_multicore_; } /// Check if it's using host timing. diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp index 688b99eba..373395047 100644 --- a/src/core/cpu_manager.cpp +++ b/src/core/cpu_manager.cpp @@ -4,15 +4,15 @@ #include "common/fiber.h" #include "common/microprofile.h" +#include "common/scope_exit.h" #include "common/thread.h" #include "core/arm/exclusive_monitor.h" #include "core/core.h" #include "core/core_timing.h" #include "core/cpu_manager.h" -#include "core/gdbstub/gdbstub.h" +#include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/physical_core.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" #include "video_core/gpu.h" @@ -109,28 +109,26 @@ void* CpuManager::GetStartFuncParamater() { void CpuManager::MultiCoreRunGuestThread() { auto& kernel = system.Kernel(); - { - auto& sched = kernel.CurrentScheduler(); - sched.OnThreadStart(); - } + kernel.CurrentScheduler()->OnThreadStart(); + auto* thread = kernel.CurrentScheduler()->GetCurrentThread(); + auto& host_context = thread->GetHostContext(); + host_context->SetRewindPoint(GuestRewindFunction, this); MultiCoreRunGuestLoop(); } void CpuManager::MultiCoreRunGuestLoop() { auto& kernel = system.Kernel(); - auto* thread = kernel.CurrentScheduler().GetCurrentThread(); + while (true) { auto* physical_core = &kernel.CurrentPhysicalCore(); - auto& arm_interface = thread->ArmInterface(); system.EnterDynarmicProfile(); while (!physical_core->IsInterrupted()) { - arm_interface.Run(); + physical_core->Run(); physical_core = &kernel.CurrentPhysicalCore(); } system.ExitDynarmicProfile(); - arm_interface.ClearExclusiveState(); - auto& scheduler = kernel.CurrentScheduler(); - scheduler.TryDoContextSwitch(); + physical_core->ArmInterface().ClearExclusiveState(); + kernel.CurrentScheduler()->RescheduleCurrentCore(); } } @@ -139,25 +137,21 @@ void CpuManager::MultiCoreRunIdleThread() { while (true) { auto& physical_core = kernel.CurrentPhysicalCore(); physical_core.Idle(); - auto& scheduler = kernel.CurrentScheduler(); - scheduler.TryDoContextSwitch(); + kernel.CurrentScheduler()->RescheduleCurrentCore(); } } void CpuManager::MultiCoreRunSuspendThread() { auto& kernel = system.Kernel(); - { - auto& sched = kernel.CurrentScheduler(); - sched.OnThreadStart(); - } + kernel.CurrentScheduler()->OnThreadStart(); while (true) { auto core = kernel.GetCurrentHostThreadID(); - auto& scheduler = kernel.CurrentScheduler(); + auto& scheduler = *kernel.CurrentScheduler(); Kernel::Thread* current_thread = scheduler.GetCurrentThread(); Common::Fiber::YieldTo(current_thread->GetHostContext(), core_data[core].host_context); ASSERT(scheduler.ContextSwitchPending()); ASSERT(core == kernel.GetCurrentHostThreadID()); - scheduler.TryDoContextSwitch(); + scheduler.RescheduleCurrentCore(); } } @@ -205,32 +199,31 @@ void CpuManager::MultiCorePause(bool paused) { void CpuManager::SingleCoreRunGuestThread() { auto& kernel = system.Kernel(); - { - auto& sched = kernel.CurrentScheduler(); - sched.OnThreadStart(); - } + kernel.CurrentScheduler()->OnThreadStart(); + auto* thread = kernel.CurrentScheduler()->GetCurrentThread(); + auto& host_context = thread->GetHostContext(); + host_context->SetRewindPoint(GuestRewindFunction, this); SingleCoreRunGuestLoop(); } void CpuManager::SingleCoreRunGuestLoop() { auto& kernel = system.Kernel(); - auto* thread = kernel.CurrentScheduler().GetCurrentThread(); + auto* thread = kernel.CurrentScheduler()->GetCurrentThread(); while (true) { auto* physical_core = &kernel.CurrentPhysicalCore(); - auto& arm_interface = thread->ArmInterface(); system.EnterDynarmicProfile(); if (!physical_core->IsInterrupted()) { - arm_interface.Run(); + physical_core->Run(); physical_core = &kernel.CurrentPhysicalCore(); } system.ExitDynarmicProfile(); thread->SetPhantomMode(true); system.CoreTiming().Advance(); thread->SetPhantomMode(false); - arm_interface.ClearExclusiveState(); + physical_core->ArmInterface().ClearExclusiveState(); PreemptSingleCore(); auto& scheduler = kernel.Scheduler(current_core); - scheduler.TryDoContextSwitch(); + scheduler.RescheduleCurrentCore(); } } @@ -242,51 +235,53 @@ void CpuManager::SingleCoreRunIdleThread() { system.CoreTiming().AddTicks(1000U); idle_count++; auto& scheduler = physical_core.Scheduler(); - scheduler.TryDoContextSwitch(); + scheduler.RescheduleCurrentCore(); } } void CpuManager::SingleCoreRunSuspendThread() { auto& kernel = system.Kernel(); - { - auto& sched = kernel.CurrentScheduler(); - sched.OnThreadStart(); - } + kernel.CurrentScheduler()->OnThreadStart(); while (true) { auto core = kernel.GetCurrentHostThreadID(); - auto& scheduler = kernel.CurrentScheduler(); + auto& scheduler = *kernel.CurrentScheduler(); Kernel::Thread* current_thread = scheduler.GetCurrentThread(); Common::Fiber::YieldTo(current_thread->GetHostContext(), core_data[0].host_context); ASSERT(scheduler.ContextSwitchPending()); ASSERT(core == kernel.GetCurrentHostThreadID()); - scheduler.TryDoContextSwitch(); + scheduler.RescheduleCurrentCore(); } } void CpuManager::PreemptSingleCore(bool from_running_enviroment) { - std::size_t old_core = current_core; - auto& scheduler = system.Kernel().Scheduler(old_core); - Kernel::Thread* current_thread = scheduler.GetCurrentThread(); - if (idle_count >= 4 || from_running_enviroment) { - if (!from_running_enviroment) { - system.CoreTiming().Idle(); + { + auto& scheduler = system.Kernel().Scheduler(current_core); + Kernel::Thread* current_thread = scheduler.GetCurrentThread(); + if (idle_count >= 4 || from_running_enviroment) { + if (!from_running_enviroment) { + system.CoreTiming().Idle(); + idle_count = 0; + } + current_thread->SetPhantomMode(true); + system.CoreTiming().Advance(); + current_thread->SetPhantomMode(false); + } + current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES); + system.CoreTiming().ResetTicks(); + scheduler.Unload(scheduler.GetCurrentThread()); + + auto& next_scheduler = system.Kernel().Scheduler(current_core); + Common::Fiber::YieldTo(current_thread->GetHostContext(), next_scheduler.ControlContext()); + } + + // May have changed scheduler + { + auto& scheduler = system.Kernel().Scheduler(current_core); + scheduler.Reload(scheduler.GetCurrentThread()); + auto* currrent_thread2 = scheduler.GetCurrentThread(); + if (!currrent_thread2->IsIdleThread()) { idle_count = 0; } - current_thread->SetPhantomMode(true); - system.CoreTiming().Advance(); - current_thread->SetPhantomMode(false); - } - current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES); - system.CoreTiming().ResetTicks(); - scheduler.Unload(); - auto& next_scheduler = system.Kernel().Scheduler(current_core); - Common::Fiber::YieldTo(current_thread->GetHostContext(), next_scheduler.ControlContext()); - /// May have changed scheduler - auto& current_scheduler = system.Kernel().Scheduler(current_core); - current_scheduler.Reload(); - auto* currrent_thread2 = current_scheduler.GetCurrentThread(); - if (!currrent_thread2->IsIdleThread()) { - idle_count = 0; } } @@ -343,6 +338,16 @@ void CpuManager::RunThread(std::size_t core) { data.initialized = true; const bool sc_sync = !is_async_gpu && !is_multicore; bool sc_sync_first_use = sc_sync; + + // Cleanup + SCOPE_EXIT({ + data.host_context->Exit(); + data.enter_barrier.reset(); + data.exit_barrier.reset(); + data.initialized = false; + MicroProfileOnThreadExit(); + }); + /// Running while (running_mode) { data.is_running = false; @@ -351,8 +356,13 @@ void CpuManager::RunThread(std::size_t core) { system.GPU().ObtainContext(); sc_sync_first_use = false; } - auto& scheduler = system.Kernel().CurrentScheduler(); - Kernel::Thread* current_thread = scheduler.GetCurrentThread(); + + // Abort if emulation was killed before the session really starts + if (!system.IsPoweredOn()) { + return; + } + + auto current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread(); data.is_running = true; Common::Fiber::YieldTo(data.host_context, current_thread->GetHostContext()); data.is_running = false; @@ -360,11 +370,6 @@ void CpuManager::RunThread(std::size_t core) { data.exit_barrier->Wait(); data.is_paused = false; } - /// Time to cleanup - data.host_context->Exit(); - data.enter_barrier.reset(); - data.exit_barrier.reset(); - data.initialized = false; } } // namespace Core diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 65d246050..cebe2ce37 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -143,6 +143,7 @@ u64 GetSignatureTypeDataSize(SignatureType type) { return 0x3C; } UNREACHABLE(); + return 0; } u64 GetSignatureTypePaddingSize(SignatureType type) { @@ -157,6 +158,7 @@ u64 GetSignatureTypePaddingSize(SignatureType type) { return 0x40; } UNREACHABLE(); + return 0; } SignatureType Ticket::GetSignatureType() const { @@ -169,8 +171,7 @@ SignatureType Ticket::GetSignatureType() const { if (const auto* ticket = std::get_if(&data)) { return ticket->sig_type; } - - UNREACHABLE(); + throw std::bad_variant_access{}; } TicketData& Ticket::GetData() { @@ -183,8 +184,7 @@ TicketData& Ticket::GetData() { if (auto* ticket = std::get_if(&data)) { return ticket->data; } - - UNREACHABLE(); + throw std::bad_variant_access{}; } const TicketData& Ticket::GetData() const { @@ -197,8 +197,7 @@ const TicketData& Ticket::GetData() const { if (const auto* ticket = std::get_if(&data)) { return ticket->data; } - - UNREACHABLE(); + throw std::bad_variant_access{}; } u64 Ticket::GetSize() const { @@ -411,7 +410,7 @@ Loader::ResultStatus DeriveSDKeys(std::array& sd_keys, KeyManager& ke // Combine sources and seed for (auto& source : sd_key_sources) { for (std::size_t i = 0; i < source.size(); ++i) { - source[i] ^= sd_seed[i & 0xF]; + source[i] = static_cast(source[i] ^ sd_seed[i & 0xF]); } } diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 956da68f7..8dee5590b 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -29,7 +29,7 @@ constexpr std::array partition_names{ "logo", }; -XCI::XCI(VirtualFile file_) +XCI::XCI(VirtualFile file_, std::size_t program_index) : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, partitions(partition_names.size()), partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} { @@ -62,7 +62,8 @@ XCI::XCI(VirtualFile file_) } secure_partition = std::make_shared( - main_hfs.GetFile(partition_names[static_cast(XCIPartition::Secure)])); + main_hfs.GetFile(partition_names[static_cast(XCIPartition::Secure)]), + program_index); ncas = secure_partition->GetNCAsCollapsed(); program = diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index 2d0a0f285..4960e90fe 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h @@ -78,7 +78,7 @@ enum class XCIPartition : u8 { Update, Normal, Secure, Logo }; class XCI : public ReadOnlyVfsDirectory { public: - explicit XCI(VirtualFile file); + explicit XCI(VirtualFile file, std::size_t program_index = 0); ~XCI() override; Loader::ResultStatus GetStatus() const; diff --git a/src/core/file_sys/common_funcs.h b/src/core/file_sys/common_funcs.h new file mode 100644 index 000000000..7ed97aa50 --- /dev/null +++ b/src/core/file_sys/common_funcs.h @@ -0,0 +1,56 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +namespace FileSys { + +constexpr u64 AOC_TITLE_ID_MASK = 0x7FF; +constexpr u64 AOC_TITLE_ID_OFFSET = 0x1000; +constexpr u64 BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; + +/** + * Gets the base title ID from a given title ID. + * + * @param title_id The title ID. + * @returns The base title ID. + */ +[[nodiscard]] constexpr u64 GetBaseTitleID(u64 title_id) { + return title_id & BASE_TITLE_ID_MASK; +} + +/** + * Gets the base title ID with a program index offset from a given title ID. + * + * @param title_id The title ID. + * @param program_index The program index. + * @returns The base title ID with a program index offset. + */ +[[nodiscard]] constexpr u64 GetBaseTitleIDWithProgramIndex(u64 title_id, u64 program_index) { + return GetBaseTitleID(title_id) + program_index; +} + +/** + * Gets the AOC (Add-On Content) base title ID from a given title ID. + * + * @param title_id The title ID. + * @returns The AOC base title ID. + */ +[[nodiscard]] constexpr u64 GetAOCBaseTitleID(u64 title_id) { + return GetBaseTitleID(title_id) + AOC_TITLE_ID_OFFSET; +} + +/** + * Gets the AOC (Add-On Content) ID from a given AOC title ID. + * + * @param aoc_title_id The AOC title ID. + * @returns The AOC ID. + */ +[[nodiscard]] constexpr u64 GetAOCID(u64 aoc_title_id) { + return aoc_title_id & AOC_TITLE_ID_MASK; +} + +} // namespace FileSys diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 76af47ff9..a6c0337fa 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -410,8 +410,9 @@ u8 NCA::GetCryptoRevision() const { std::optional NCA::GetKeyAreaKey(NCASectionCryptoType type) const { const auto master_key_id = GetCryptoRevision(); - if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) - return {}; + if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) { + return std::nullopt; + } std::vector key_area(header.key_area.begin(), header.key_area.end()); Core::Crypto::AESCipher cipher( @@ -420,15 +421,17 @@ std::optional NCA::GetKeyAreaKey(NCASectionCryptoType type cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt); Core::Crypto::Key128 out; - if (type == NCASectionCryptoType::XTS) + if (type == NCASectionCryptoType::XTS) { std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin()); - else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) + } else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) { std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin()); - else + } else { LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}", - static_cast(type)); + type); + } + u128 out_128{}; - memcpy(out_128.data(), out.data(), 16); + std::memcpy(out_128.data(), out.data(), sizeof(u128)); LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}", master_key_id, header.key_index, out_128[1], out_128[0]); @@ -507,7 +510,7 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s // TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs default: LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", - static_cast(s_header.raw.header.crypto_type)); + s_header.raw.header.crypto_type); return nullptr; } } @@ -516,15 +519,17 @@ Loader::ResultStatus NCA::GetStatus() const { return status; } -std::vector> NCA::GetFiles() const { - if (status != Loader::ResultStatus::Success) +std::vector NCA::GetFiles() const { + if (status != Loader::ResultStatus::Success) { return {}; + } return files; } -std::vector> NCA::GetSubdirectories() const { - if (status != Loader::ResultStatus::Success) +std::vector NCA::GetSubdirectories() const { + if (status != Loader::ResultStatus::Success) { return {}; + } return dirs; } @@ -532,7 +537,7 @@ std::string NCA::GetName() const { return file->GetName(); } -std::shared_ptr NCA::GetParentDirectory() const { +VirtualDir NCA::GetParentDirectory() const { return file->GetContainingDirectory(); } diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 69292232a..e9eccdea3 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -82,7 +82,7 @@ struct NCAHeader { }; static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); -inline bool IsDirectoryExeFS(const std::shared_ptr& pfs) { +inline bool IsDirectoryExeFS(const VirtualDir& pfs) { // According to switchbrew, an exefs must only contain these two files: return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr; } @@ -104,10 +104,10 @@ public: Loader::ResultStatus GetStatus() const; - std::vector> GetFiles() const override; - std::vector> GetSubdirectories() const override; + std::vector GetFiles() const override; + std::vector GetSubdirectories() const override; std::string GetName() const override; - std::shared_ptr GetParentDirectory() const override; + VirtualDir GetParentDirectory() const override; NCAContentType GetType() const; u64 GetTitleId() const; diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp index 2aff2708a..c52fafb6f 100644 --- a/src/core/file_sys/fsmitm_romfsbuild.cpp +++ b/src/core/file_sys/fsmitm_romfsbuild.cpp @@ -266,8 +266,9 @@ std::multimap RomFSBuildContext::Build() { cur_file->offset = file_partition_size; file_partition_size += cur_file->size; cur_file->entry_offset = entry_offset; - entry_offset += sizeof(RomFSFileEntry) + - Common::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4); + entry_offset += + static_cast(sizeof(RomFSFileEntry) + + Common::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4)); prev_file = cur_file; } // Assign deferred parent/sibling ownership. @@ -284,8 +285,9 @@ std::multimap RomFSBuildContext::Build() { for (const auto& it : directories) { cur_dir = it.second; cur_dir->entry_offset = entry_offset; - entry_offset += sizeof(RomFSDirectoryEntry) + - Common::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4); + entry_offset += + static_cast(sizeof(RomFSDirectoryEntry) + + Common::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4)); } // Assign deferred parent/sibling ownership. for (auto it = directories.rbegin(); it->second != root; ++it) { diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp index dd779310f..a6101f1c0 100644 --- a/src/core/file_sys/ips_layer.cpp +++ b/src/core/file_sys/ips_layer.cpp @@ -299,7 +299,7 @@ void IPSwitchCompiler::Parse() { patch_text->GetName(), offset, Common::HexToString(replace)); } - patch.records.insert_or_assign(offset, std::move(replace)); + patch.records.insert_or_assign(static_cast(offset), std::move(replace)); } patches.push_back(std::move(patch)); diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp index 2d1476e3a..3596541b2 100644 --- a/src/core/file_sys/nca_metadata.cpp +++ b/src/core/file_sys/nca_metadata.cpp @@ -108,7 +108,7 @@ std::vector CNMT::Serialize() const { memcpy(out.data() + sizeof(CNMTHeader), &opt_header, sizeof(OptionalHeader)); } - auto offset = header.table_offset; + u64_le offset = header.table_offset; for (const auto& rec : content_records) { memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(ContentRecord)); diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp index 5990a2fd5..a65ec6798 100644 --- a/src/core/file_sys/nca_patch.cpp +++ b/src/core/file_sys/nca_patch.cpp @@ -51,8 +51,8 @@ std::pair SearchBucketEntry(u64 offset, const BlockTyp low = mid + 1; } } - UNREACHABLE_MSG("Offset could not be found in BKTR block."); + return {0, 0}; } } // Anonymous namespace @@ -191,7 +191,7 @@ bool BKTR::Resize(std::size_t new_size) { return false; } -std::shared_ptr BKTR::GetContainingDirectory() const { +VirtualDir BKTR::GetContainingDirectory() const { return base_romfs->GetContainingDirectory(); } diff --git a/src/core/file_sys/nca_patch.h b/src/core/file_sys/nca_patch.h index 60c544f8e..503cf473e 100644 --- a/src/core/file_sys/nca_patch.h +++ b/src/core/file_sys/nca_patch.h @@ -106,7 +106,7 @@ public: bool Resize(std::size_t new_size) override; - std::shared_ptr GetContainingDirectory() const override; + VirtualDir GetContainingDirectory() const override; bool IsWritable() const override; diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index b9c09b456..7c3284df8 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -12,6 +12,7 @@ #include "common/logging/log.h" #include "common/string_util.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/ips_layer.h" @@ -29,8 +30,7 @@ namespace FileSys { namespace { -constexpr u64 SINGLE_BYTE_MODULUS = 0x100; -constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; +constexpr u32 SINGLE_BYTE_MODULUS = 0x100; constexpr std::array EXEFS_FILE_NAMES{ "main", "main.npdm", "rtld", "sdk", "subsdk0", "subsdk1", "subsdk2", @@ -112,7 +112,10 @@ bool IsDirValidAndNonEmpty(const VirtualDir& dir) { } } // Anonymous namespace -PatchManager::PatchManager(u64 title_id) : title_id(title_id) {} +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_} {} PatchManager::~PatchManager() = default; @@ -128,34 +131,30 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { if (Settings::values.dump_exefs) { LOG_INFO(Loader, "Dumping ExeFS for title_id={:016X}", title_id); - const auto dump_dir = - Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id); + const auto dump_dir = fs_controller.GetModificationDumpRoot(title_id); if (dump_dir != nullptr) { const auto exefs_dir = GetOrCreateDirectoryRelative(dump_dir, "/exefs"); VfsRawCopyD(exefs, exefs_dir); } } - const auto& installed = Core::System::GetInstance().GetContentProvider(); - const auto& disabled = Settings::values.disabled_addons[title_id]; const auto update_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); // Game Updates const auto update_tid = GetUpdateTitleID(title_id); - const auto update = installed.GetEntry(update_tid, ContentRecordType::Program); + const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program); if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr && update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully", - FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0))); + FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); exefs = update->GetExeFS(); } // LayeredExeFS - const auto load_dir = - Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); + const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); if (load_dir != nullptr && load_dir->GetSize() > 0) { auto patch_dirs = load_dir->GetSubdirectories(); std::sort( @@ -241,8 +240,7 @@ std::vector PatchManager::PatchNSO(const std::vector& nso, const std::st if (Settings::values.dump_nso) { LOG_INFO(Loader, "Dumping NSO for name={}, build_id={}, title_id={:016X}", name, build_id, title_id); - const auto dump_dir = - Core::System::GetInstance().GetFileSystemController().GetModificationDumpRoot(title_id); + const auto dump_dir = fs_controller.GetModificationDumpRoot(title_id); if (dump_dir != nullptr) { const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso"); const auto file = nso_dir->CreateFile(fmt::format("{}-{}.nso", name, build_id)); @@ -254,8 +252,7 @@ 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 = - Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); + const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); if (load_dir == nullptr) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); return nso; @@ -298,8 +295,7 @@ bool PatchManager::HasNSOPatch(const BuildID& build_id_) const { LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id); - const auto load_dir = - Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); + const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); if (load_dir == nullptr) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); return false; @@ -313,8 +309,8 @@ bool PatchManager::HasNSOPatch(const BuildID& build_id_) const { } std::vector PatchManager::CreateCheatList( - const Core::System& system, const BuildID& build_id_) const { - const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id); + const BuildID& build_id_) const { + const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); if (load_dir == nullptr) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); return {}; @@ -347,9 +343,9 @@ std::vector PatchManager::CreateCheatList( return out; } -static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { - const auto load_dir = - Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); +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); if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || load_dir == nullptr || load_dir->GetSize() <= 0) { return; @@ -411,19 +407,19 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", title_id, static_cast(type)); - if (type == ContentRecordType::Program || type == ContentRecordType::Data) + if (type == ContentRecordType::Program || type == ContentRecordType::Data) { LOG_INFO(Loader, "{}", log_string); - else + } else { LOG_DEBUG(Loader, "{}", log_string); + } - if (romfs == nullptr) + if (romfs == nullptr) { return romfs; - - const auto& installed = Core::System::GetInstance().GetContentProvider(); + } // Game Updates const auto update_tid = GetUpdateTitleID(title_id); - const auto update = installed.GetEntryRaw(update_tid, type); + const auto update = content_provider.GetEntryRaw(update_tid, type); const auto& disabled = Settings::values.disabled_addons[title_id]; const auto update_disabled = @@ -434,7 +430,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content if (new_nca->GetStatus() == Loader::ResultStatus::Success && new_nca->GetRomFS() != nullptr) { LOG_INFO(Loader, " RomFS: Update ({}) applied successfully", - FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0))); + FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); romfs = new_nca->GetRomFS(); } } else if (!update_disabled && update_raw != nullptr) { @@ -447,7 +443,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content } // LayeredFS - ApplyLayeredFS(romfs, title_id, type); + ApplyLayeredFS(romfs, title_id, type, fs_controller); return romfs; } @@ -458,12 +454,11 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u } std::map> out; - const auto& installed = Core::System::GetInstance().GetContentProvider(); const auto& disabled = Settings::values.disabled_addons[title_id]; // Game Updates const auto update_tid = GetUpdateTitleID(title_id); - PatchManager update{update_tid}; + PatchManager update{update_tid, fs_controller, content_provider}; const auto metadata = update.GetControlMetadata(); const auto& nacp = metadata.first; @@ -474,8 +469,8 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u if (nacp != nullptr) { out.insert_or_assign(update_label, nacp->GetVersionString()); } else { - if (installed.HasEntry(update_tid, ContentRecordType::Program)) { - const auto meta_ver = installed.GetEntryVersion(update_tid); + if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) { + const auto meta_ver = content_provider.GetEntryVersion(update_tid); if (meta_ver.value_or(0) == 0) { out.insert_or_assign(update_label, ""); } else { @@ -487,8 +482,7 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u } // General Mods (LayeredFS and IPS) - const auto mod_dir = - Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); + const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id); if (mod_dir != nullptr && mod_dir->GetSize() > 0) { for (const auto& mod : mod_dir->GetSubdirectories()) { std::string types; @@ -532,13 +526,15 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u } // DLC - const auto dlc_entries = installed.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data); + const auto dlc_entries = + content_provider.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data); std::vector dlc_match; dlc_match.reserve(dlc_entries.size()); std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match), - [this, &installed](const ContentProviderEntry& entry) { - return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id && - installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success; + [this](const ContentProviderEntry& entry) { + return GetBaseTitleID(entry.title_id) == title_id && + content_provider.GetEntry(entry)->GetStatus() == + Loader::ResultStatus::Success; }); if (!dlc_match.empty()) { // Ensure sorted so DLC IDs show in order. @@ -559,19 +555,16 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u } std::optional PatchManager::GetGameVersion() const { - const auto& installed = Core::System::GetInstance().GetContentProvider(); const auto update_tid = GetUpdateTitleID(title_id); - if (installed.HasEntry(update_tid, ContentRecordType::Program)) { - return installed.GetEntryVersion(update_tid); + if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) { + return content_provider.GetEntryVersion(update_tid); } - return installed.GetEntryVersion(title_id); + return content_provider.GetEntryVersion(title_id); } PatchManager::Metadata PatchManager::GetControlMetadata() const { - const auto& installed = Core::System::GetInstance().GetContentProvider(); - - const auto base_control_nca = installed.GetEntry(title_id, ContentRecordType::Control); + const auto base_control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control); if (base_control_nca == nullptr) { return {}; } diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 1f28c6241..fb1853035 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -17,8 +17,13 @@ namespace Core { class System; } +namespace Service::FileSystem { +class FileSystemController; +} + namespace FileSys { +class ContentProvider; class NCA; class NACP; @@ -29,7 +34,9 @@ public: using Metadata = std::pair, VirtualFile>; using PatchVersionNames = std::map>; - explicit PatchManager(u64 title_id); + explicit PatchManager(u64 title_id_, + const Service::FileSystem::FileSystemController& fs_controller_, + const ContentProvider& content_provider_); ~PatchManager(); [[nodiscard]] u64 GetTitleID() const; @@ -50,7 +57,7 @@ public: // Creates a CheatList object with all [[nodiscard]] std::vector CreateCheatList( - const Core::System& system, const BuildID& build_id) const; + const BuildID& build_id) const; // Currently tracked RomFS patches: // - Game Updates @@ -80,6 +87,8 @@ private: const std::string& build_id) const; u64 title_id; + const Service::FileSystem::FileSystemController& fs_controller; + const ContentProvider& content_provider; }; } // namespace FileSys diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index da01002d5..431302f55 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -105,7 +105,8 @@ ContentRecordType GetCRTypeFromNCAType(NCAContentType type) { // TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal. return ContentRecordType::HtmlDocument; default: - UNREACHABLE_MSG("Invalid NCAContentType={:02X}", static_cast(type)); + UNREACHABLE_MSG("Invalid NCAContentType={:02X}", type); + return ContentRecordType{}; } } diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index 5b414b0f0..b08a1687a 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -67,18 +67,18 @@ public: virtual void Refresh() = 0; virtual bool HasEntry(u64 title_id, ContentRecordType type) const = 0; - virtual bool HasEntry(ContentProviderEntry entry) const; + bool HasEntry(ContentProviderEntry entry) const; virtual std::optional GetEntryVersion(u64 title_id) const = 0; virtual VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const = 0; - virtual VirtualFile GetEntryUnparsed(ContentProviderEntry entry) const; + VirtualFile GetEntryUnparsed(ContentProviderEntry entry) const; virtual VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const = 0; - virtual VirtualFile GetEntryRaw(ContentProviderEntry entry) const; + VirtualFile GetEntryRaw(ContentProviderEntry entry) const; virtual std::unique_ptr GetEntry(u64 title_id, ContentRecordType type) const = 0; - virtual std::unique_ptr GetEntry(ContentProviderEntry entry) const; + std::unique_ptr GetEntry(ContentProviderEntry entry) const; virtual std::vector ListEntries() const; diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index e967a254e..f4e16e4be 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp @@ -7,6 +7,7 @@ #include "common/common_types.h" #include "common/logging/log.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/nca_metadata.h" #include "core/file_sys/patch_manager.h" @@ -37,14 +38,37 @@ void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) { } ResultVal RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { - if (!updatable) + if (!updatable) { return MakeResult(file); + } - const PatchManager patch_manager(current_process_title_id); + const PatchManager patch_manager{current_process_title_id, filesystem_controller, + content_provider}; return MakeResult( patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw)); } +ResultVal RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const { + auto nca = content_provider.GetEntry(title_id, type); + + if (nca == nullptr) { + // TODO: Find the right error code to use here + return RESULT_UNKNOWN; + } + + const PatchManager patch_manager{title_id, filesystem_controller, content_provider}; + + return MakeResult( + patch_manager.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), type)); +} + +ResultVal RomFSFactory::OpenPatchedRomFSWithProgramIndex( + u64 title_id, u8 program_index, ContentRecordType type) const { + const auto res_title_id = GetBaseTitleIDWithProgramIndex(title_id, program_index); + + return OpenPatchedRomFS(res_title_id, type); +} + ResultVal RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) const { const std::shared_ptr res = GetEntry(title_id, storage, type); diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index ec704dfa8..96dd0d578 100644 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h @@ -42,6 +42,10 @@ public: void SetPackedUpdate(VirtualFile update_raw); [[nodiscard]] ResultVal OpenCurrentProcess(u64 current_process_title_id) const; + [[nodiscard]] ResultVal OpenPatchedRomFS(u64 title_id, + ContentRecordType type) const; + [[nodiscard]] ResultVal OpenPatchedRomFSWithProgramIndex( + u64 title_id, u8 program_index, ContentRecordType type) const; [[nodiscard]] ResultVal Open(u64 title_id, StorageId storage, ContentRecordType type) const; diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index ba4efee3a..b7bfe0928 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -70,7 +70,8 @@ std::string SaveDataAttribute::DebugInfo() const { static_cast(rank), index); } -SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) { +SaveDataFactory::SaveDataFactory(Core::System& system_, VirtualDir save_directory_) + : dir{std::move(save_directory_)}, system{system_} { // Delete all temporary storages // On hardware, it is expected that temporary storage be empty at first use. dir->DeleteSubdirectoryRecursive("temp"); @@ -83,7 +84,7 @@ ResultVal SaveDataFactory::Create(SaveDataSpaceId space, PrintSaveDataAttributeWarnings(meta); const auto save_directory = - GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id); + GetFullPath(system, space, meta.type, meta.title_id, meta.user_id, meta.save_id); auto out = dir->CreateDirectoryRelative(save_directory); @@ -100,7 +101,7 @@ ResultVal SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const { const auto save_directory = - GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id); + GetFullPath(system, space, meta.type, meta.title_id, meta.user_id, meta.save_id); auto out = dir->GetDirectoryRelative(save_directory); @@ -135,13 +136,14 @@ std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) { } } -std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, - u128 user_id, u64 save_id) { +std::string SaveDataFactory::GetFullPath(Core::System& system, SaveDataSpaceId space, + SaveDataType type, u64 title_id, u128 user_id, + u64 save_id) { // According to switchbrew, if a save is of type SaveData and the title id field is 0, it should // be interpreted as the title id of the current process. if (type == SaveDataType::SaveData || type == SaveDataType::DeviceSaveData) { if (title_id == 0) { - title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); + title_id = system.CurrentProcess()->GetTitleID(); } } @@ -167,7 +169,7 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id, u128 user_id) const { - const auto path = GetFullPath(SaveDataSpaceId::NandUser, type, title_id, user_id, 0); + const auto path = GetFullPath(system, SaveDataSpaceId::NandUser, type, title_id, user_id, 0); const auto dir = GetOrCreateDirectoryRelative(this->dir, path); const auto size_file = dir->GetFile(SAVE_DATA_SIZE_FILENAME); @@ -182,7 +184,7 @@ SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id, void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, SaveDataSize new_value) const { - const auto path = GetFullPath(SaveDataSpaceId::NandUser, type, title_id, user_id, 0); + const auto path = GetFullPath(system, SaveDataSpaceId::NandUser, type, title_id, user_id, 0); const auto dir = GetOrCreateDirectoryRelative(this->dir, path); const auto size_file = dir->CreateFile(SAVE_DATA_SIZE_FILENAME); diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index 6625bbbd8..17f774baa 100644 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -12,6 +12,10 @@ #include "core/file_sys/vfs.h" #include "core/hle/result.h" +namespace Core { +class System; +} + namespace FileSys { enum class SaveDataSpaceId : u8 { @@ -84,7 +88,7 @@ struct SaveDataSize { /// File system interface to the SaveData archive class SaveDataFactory { public: - explicit SaveDataFactory(VirtualDir dir); + explicit SaveDataFactory(Core::System& system_, VirtualDir save_directory_); ~SaveDataFactory(); ResultVal Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const; @@ -93,8 +97,8 @@ public: VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const; static std::string GetSaveDataSpaceIdPath(SaveDataSpaceId space); - static std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, - u128 user_id, u64 save_id); + static std::string GetFullPath(Core::System& system, SaveDataSpaceId space, SaveDataType type, + u64 title_id, u128 user_id, u64 save_id); SaveDataSize ReadSaveDataSize(SaveDataType type, u64 title_id, u128 user_id) const; void WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, @@ -102,6 +106,7 @@ public: private: VirtualDir dir; + Core::System& system; }; } // namespace FileSys diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index aab957bf2..c05735ddd 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp @@ -19,41 +19,9 @@ #include "core/loader/loader.h" namespace FileSys { -namespace { -void SetTicketKeys(const std::vector& files) { - auto& keys = Core::Crypto::KeyManager::Instance(); - for (const auto& ticket_file : files) { - if (ticket_file == nullptr) { - continue; - } - - if (ticket_file->GetExtension() != "tik") { - continue; - } - - if (ticket_file->GetSize() < - Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) { - continue; - } - - Core::Crypto::Key128 key{}; - ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET); - - // We get the name without the extension in order to create the rights ID. - std::string name_only(ticket_file->GetName()); - name_only.erase(name_only.size() - 4); - - const auto rights_id_raw = Common::HexStringToArray<16>(name_only); - u128 rights_id; - std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128)); - keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); - } -} -} // Anonymous namespace - -NSP::NSP(VirtualFile file_) - : file(std::move(file_)), status{Loader::ResultStatus::Success}, +NSP::NSP(VirtualFile file_, std::size_t program_index) + : file(std::move(file_)), program_index(program_index), status{Loader::ResultStatus::Success}, pfs(std::make_shared(file)), keys{Core::Crypto::KeyManager::Instance()} { if (pfs->GetStatus() != Loader::ResultStatus::Success) { status = pfs->GetStatus(); @@ -178,7 +146,7 @@ std::shared_ptr NSP::GetNCA(u64 title_id, ContentRecordType type, TitleType if (extracted) LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); - const auto title_id_iter = ncas.find(title_id); + const auto title_id_iter = ncas.find(title_id + program_index); if (title_id_iter == ncas.end()) return nullptr; @@ -232,6 +200,35 @@ VirtualDir NSP::GetParentDirectory() const { return file->GetContainingDirectory(); } +void NSP::SetTicketKeys(const std::vector& files) { + for (const auto& ticket_file : files) { + if (ticket_file == nullptr) { + continue; + } + + if (ticket_file->GetExtension() != "tik") { + continue; + } + + if (ticket_file->GetSize() < + Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) { + continue; + } + + Core::Crypto::Key128 key{}; + ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET); + + // We get the name without the extension in order to create the rights ID. + std::string name_only(ticket_file->GetName()); + name_only.erase(name_only.size() - 4); + + const auto rights_id_raw = Common::HexStringToArray<16>(name_only); + u128 rights_id; + std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128)); + keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); + } +} + void NSP::InitializeExeFSAndRomFS(const std::vector& files) { exefs = pfs; @@ -286,12 +283,31 @@ void NSP::ReadNCAs(const std::vector& files) { } auto next_nca = std::make_shared(std::move(next_file), nullptr, 0); + if (next_nca->GetType() == NCAContentType::Program) { program_status[next_nca->GetTitleId()] = next_nca->GetStatus(); } - if (next_nca->GetStatus() == Loader::ResultStatus::Success || - (next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS && - (next_nca->GetTitleId() & 0x800) != 0)) { + + if (next_nca->GetStatus() != Loader::ResultStatus::Success && + next_nca->GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { + continue; + } + + // If the last 3 hexadecimal digits of the CNMT TitleID is 0x800 or is missing the + // BKTRBaseRomFS, this is an update NCA. Otherwise, this is a base NCA. + if ((cnmt.GetTitleID() & 0x800) != 0 || + next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { + // 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) { + ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] = + std::move(next_nca); + } else { + ncas[cnmt.GetTitleID()][{cnmt.GetType(), rec.type}] = std::move(next_nca); + } + } else { ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] = std::move(next_nca); } } diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h index 2db5e46b8..54581a6f3 100644 --- a/src/core/file_sys/submission_package.h +++ b/src/core/file_sys/submission_package.h @@ -27,7 +27,7 @@ enum class ContentRecordType : u8; class NSP : public ReadOnlyVfsDirectory { public: - explicit NSP(VirtualFile file); + explicit NSP(VirtualFile file, std::size_t program_index = 0); ~NSP() override; Loader::ResultStatus GetStatus() const; @@ -63,11 +63,14 @@ public: VirtualDir GetParentDirectory() const override; private: + void SetTicketKeys(const std::vector& files); void InitializeExeFSAndRomFS(const std::vector& files); void ReadNCAs(const std::vector& files); VirtualFile file; + const std::size_t program_index; + bool extracted = false; Loader::ResultStatus status; std::map program_status; diff --git a/src/core/file_sys/system_archive/data/font_nintendo_extended.cpp b/src/core/file_sys/system_archive/data/font_nintendo_extended.cpp index 69d62ce8f..29ef110a6 100644 --- a/src/core/file_sys/system_archive/data/font_nintendo_extended.cpp +++ b/src/core/file_sys/system_archive/data/font_nintendo_extended.cpp @@ -6,191 +6,384 @@ namespace FileSys::SystemArchive::SharedFontData { -const std::array FONT_NINTENDO_EXTENDED{{ - 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x80, 0x00, 0x03, 0x00, 0x70, 0x44, 0x53, 0x49, 0x47, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0b, 0x6c, 0x00, 0x00, 0x00, 0x08, 0x4f, 0x53, 0x2f, 0x32, - 0x33, 0x86, 0x1d, 0x9b, 0x00, 0x00, 0x01, 0x78, 0x00, 0x00, 0x00, 0x60, 0x63, 0x6d, 0x61, 0x70, - 0xc2, 0x06, 0x20, 0xde, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x63, 0x76, 0x74, 0x20, - 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x04, 0x2c, 0x00, 0x00, 0x00, 0x06, 0x66, 0x70, 0x67, 0x6d, - 0x06, 0x59, 0x9c, 0x37, 0x00, 0x00, 0x02, 0xa0, 0x00, 0x00, 0x01, 0x73, 0x67, 0x61, 0x73, 0x70, - 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x0b, 0x64, 0x00, 0x00, 0x00, 0x08, 0x67, 0x6c, 0x79, 0x66, - 0x10, 0x31, 0x88, 0x00, 0x00, 0x00, 0x04, 0x34, 0x00, 0x00, 0x04, 0x64, 0x68, 0x65, 0x61, 0x64, - 0x15, 0x9d, 0xef, 0x91, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x36, 0x68, 0x68, 0x65, 0x61, - 0x09, 0x60, 0x03, 0x71, 0x00, 0x00, 0x01, 0x34, 0x00, 0x00, 0x00, 0x24, 0x68, 0x6d, 0x74, 0x78, - 0x0d, 0x2e, 0x03, 0xa7, 0x00, 0x00, 0x01, 0xd8, 0x00, 0x00, 0x00, 0x26, 0x6c, 0x6f, 0x63, 0x61, - 0x05, 0xc0, 0x04, 0x6c, 0x00, 0x00, 0x08, 0x98, 0x00, 0x00, 0x00, 0x1e, 0x6d, 0x61, 0x78, 0x70, - 0x02, 0x1c, 0x00, 0x5f, 0x00, 0x00, 0x01, 0x58, 0x00, 0x00, 0x00, 0x20, 0x6e, 0x61, 0x6d, 0x65, - 0x7c, 0xe0, 0x84, 0x5c, 0x00, 0x00, 0x08, 0xb8, 0x00, 0x00, 0x02, 0x09, 0x70, 0x6f, 0x73, 0x74, - 0x47, 0x4e, 0x74, 0x19, 0x00, 0x00, 0x0a, 0xc4, 0x00, 0x00, 0x00, 0x9e, 0x70, 0x72, 0x65, 0x70, - 0x1c, 0xfc, 0x7d, 0x9c, 0x00, 0x00, 0x04, 0x14, 0x00, 0x00, 0x00, 0x16, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x00, 0x7c, 0xc7, 0xb1, 0x63, 0x5f, 0x0f, 0x3c, 0xf5, 0x00, 0x1b, 0x03, 0xe8, - 0x00, 0x00, 0x00, 0x00, 0xd9, 0x44, 0x2f, 0x5d, 0x00, 0x00, 0x00, 0x00, 0xd9, 0x45, 0x7b, 0x69, - 0x00, 0x00, 0x00, 0x00, 0x03, 0xe6, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x06, 0x00, 0x02, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x84, 0xff, 0x83, 0x01, 0xf4, 0x03, 0xe8, - 0x00, 0x00, 0x00, 0x00, 0x03, 0xe6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x5e, - 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x74, 0x01, 0x90, 0x00, 0x05, - 0x00, 0x04, 0x00, 0xcd, 0x00, 0xcd, 0x00, 0x00, 0x01, 0x1f, 0x00, 0xcd, 0x00, 0xcd, 0x00, 0x00, - 0x03, 0xc3, 0x00, 0x66, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +const std::array FONT_NINTENDO_EXTENDED{{ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x80, 0x00, 0x03, 0x00, 0x60, 0x4F, 0x53, 0x2F, 0x32, + 0x34, 0x00, 0x1E, 0x26, 0x00, 0x00, 0x01, 0x68, 0x00, 0x00, 0x00, 0x60, 0x63, 0x6D, 0x61, 0x70, + 0xC1, 0xE7, 0xC8, 0xF3, 0x00, 0x00, 0x02, 0x0C, 0x00, 0x00, 0x01, 0x72, 0x63, 0x76, 0x74, 0x20, + 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0C, 0x00, 0x00, 0x00, 0x06, 0x66, 0x70, 0x67, 0x6D, + 0x06, 0x59, 0x9C, 0x37, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x01, 0x73, 0x67, 0x61, 0x73, 0x70, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x17, 0x80, 0x00, 0x00, 0x00, 0x08, 0x67, 0x6C, 0x79, 0x66, + 0x50, 0x0B, 0xEA, 0xFA, 0x00, 0x00, 0x05, 0x50, 0x00, 0x00, 0x0F, 0x04, 0x68, 0x65, 0x61, 0x64, + 0x18, 0x65, 0x81, 0x09, 0x00, 0x00, 0x00, 0xEC, 0x00, 0x00, 0x00, 0x36, 0x68, 0x68, 0x65, 0x61, + 0x09, 0x88, 0x03, 0x86, 0x00, 0x00, 0x01, 0x24, 0x00, 0x00, 0x00, 0x24, 0x68, 0x6D, 0x74, 0x78, + 0x0A, 0xF0, 0x01, 0x94, 0x00, 0x00, 0x01, 0xC8, 0x00, 0x00, 0x00, 0x42, 0x6C, 0x6F, 0x63, 0x61, + 0x34, 0x80, 0x30, 0x6E, 0x00, 0x00, 0x05, 0x14, 0x00, 0x00, 0x00, 0x3A, 0x6D, 0x61, 0x78, 0x70, + 0x02, 0x2C, 0x00, 0x72, 0x00, 0x00, 0x01, 0x48, 0x00, 0x00, 0x00, 0x20, 0x6E, 0x61, 0x6D, 0x65, + 0xDB, 0xC5, 0x42, 0x4D, 0x00, 0x00, 0x14, 0x54, 0x00, 0x00, 0x01, 0xFE, 0x70, 0x6F, 0x73, 0x74, + 0xF4, 0xB4, 0xAC, 0xAB, 0x00, 0x00, 0x16, 0x54, 0x00, 0x00, 0x01, 0x2A, 0x70, 0x72, 0x65, 0x70, + 0x1C, 0xFC, 0x7D, 0x9C, 0x00, 0x00, 0x04, 0xF4, 0x00, 0x00, 0x00, 0x16, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0xC9, 0x16, 0x5B, 0x71, 0x5F, 0x0F, 0x3C, 0xF5, 0x00, 0x0B, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xD9, 0x44, 0x2F, 0x5D, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x02, 0x0D, 0xA7, + 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x9A, 0xFF, 0x80, 0x02, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0xEC, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x71, + 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0xC4, 0x01, 0x90, 0x00, 0x05, + 0x00, 0x04, 0x00, 0xD2, 0x00, 0xD2, 0x00, 0x00, 0x01, 0x26, 0x00, 0xD2, 0x00, 0xD2, 0x00, 0x00, + 0x03, 0xDA, 0x00, 0x68, 0x02, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0xc0, 0x00, 0x00, 0xe0, 0xe9, 0x03, 0x84, 0xff, 0x83, - 0x01, 0xf4, 0x02, 0xee, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8, - 0x02, 0xbc, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xfa, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x03, 0xe8, 0x00, 0xeb, 0x01, 0x21, 0x00, 0xff, - 0x00, 0xff, 0x01, 0x3d, 0x01, 0x17, 0x00, 0x42, 0x00, 0x1c, 0x00, 0x3e, 0x00, 0x17, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x68, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x1c, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x68, 0x00, 0x06, 0x00, 0x4c, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0xC0, 0x00, 0x0D, 0xE0, 0xF0, 0x03, 0x9A, 0xFF, 0x80, + 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x02, 0xCD, 0x00, 0x00, 0x00, 0x20, 0x00, 0x01, 0x04, 0x00, 0x00, 0xA4, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, + 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, + 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, + 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6C, + 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x04, 0x00, 0x50, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x20, 0xE0, 0xA9, 0xE0, 0xB4, + 0xE0, 0xE9, 0xE0, 0xF0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x20, 0xE0, 0xA0, + 0xE0, 0xB3, 0xE0, 0xE0, 0xE0, 0xEF, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xF5, 0xFF, 0xE3, 0x1F, 0x64, + 0x1F, 0x5B, 0x1F, 0x30, 0x1F, 0x2B, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x02, 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, 0x03, 0x00, 0x04, 0x00, 0x38, 0x00, 0x00, 0x00, 0x0a, - 0x00, 0x08, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x20, 0xe0, 0xe9, 0xff, 0xff, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x20, 0xe0, 0xe0, 0xff, 0xff, 0x00, 0x01, 0xff, 0xf5, - 0xff, 0xe3, 0x1f, 0x24, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xb8, 0x00, 0x00, 0x2c, 0x4b, 0xb8, 0x00, 0x09, 0x50, 0x58, 0xb1, 0x01, 0x01, 0x8e, 0x59, 0xb8, - 0x01, 0xff, 0x85, 0xb8, 0x00, 0x44, 0x1d, 0xb9, 0x00, 0x09, 0x00, 0x03, 0x5f, 0x5e, 0x2d, 0xb8, - 0x00, 0x01, 0x2c, 0x20, 0x20, 0x45, 0x69, 0x44, 0xb0, 0x01, 0x60, 0x2d, 0xb8, 0x00, 0x02, 0x2c, - 0xb8, 0x00, 0x01, 0x2a, 0x21, 0x2d, 0xb8, 0x00, 0x03, 0x2c, 0x20, 0x46, 0xb0, 0x03, 0x25, 0x46, - 0x52, 0x58, 0x23, 0x59, 0x20, 0x8a, 0x20, 0x8a, 0x49, 0x64, 0x8a, 0x20, 0x46, 0x20, 0x68, 0x61, - 0x64, 0xb0, 0x04, 0x25, 0x46, 0x20, 0x68, 0x61, 0x64, 0x52, 0x58, 0x23, 0x65, 0x8a, 0x59, 0x2f, - 0x20, 0xb0, 0x00, 0x53, 0x58, 0x69, 0x20, 0xb0, 0x00, 0x54, 0x58, 0x21, 0xb0, 0x40, 0x59, 0x1b, - 0x69, 0x20, 0xb0, 0x00, 0x54, 0x58, 0x21, 0xb0, 0x40, 0x65, 0x59, 0x59, 0x3a, 0x2d, 0xb8, 0x00, - 0x04, 0x2c, 0x20, 0x46, 0xb0, 0x04, 0x25, 0x46, 0x52, 0x58, 0x23, 0x8a, 0x59, 0x20, 0x46, 0x20, - 0x6a, 0x61, 0x64, 0xb0, 0x04, 0x25, 0x46, 0x20, 0x6a, 0x61, 0x64, 0x52, 0x58, 0x23, 0x8a, 0x59, - 0x2f, 0xfd, 0x2d, 0xb8, 0x00, 0x05, 0x2c, 0x4b, 0x20, 0xb0, 0x03, 0x26, 0x50, 0x58, 0x51, 0x58, - 0xb0, 0x80, 0x44, 0x1b, 0xb0, 0x40, 0x44, 0x59, 0x1b, 0x21, 0x21, 0x20, 0x45, 0xb0, 0xc0, 0x50, - 0x58, 0xb0, 0xc0, 0x44, 0x1b, 0x21, 0x59, 0x59, 0x2d, 0xb8, 0x00, 0x06, 0x2c, 0x20, 0x20, 0x45, - 0x69, 0x44, 0xb0, 0x01, 0x60, 0x20, 0x20, 0x45, 0x7d, 0x69, 0x18, 0x44, 0xb0, 0x01, 0x60, 0x2d, - 0xb8, 0x00, 0x07, 0x2c, 0xb8, 0x00, 0x06, 0x2a, 0x2d, 0xb8, 0x00, 0x08, 0x2c, 0x4b, 0x20, 0xb0, - 0x03, 0x26, 0x53, 0x58, 0xb0, 0x40, 0x1b, 0xb0, 0x00, 0x59, 0x8a, 0x8a, 0x20, 0xb0, 0x03, 0x26, - 0x53, 0x58, 0x23, 0x21, 0xb0, 0x80, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20, 0xb0, 0x03, 0x26, - 0x53, 0x58, 0x23, 0x21, 0xb8, 0x00, 0xc0, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20, 0xb0, 0x03, - 0x26, 0x53, 0x58, 0x23, 0x21, 0xb8, 0x01, 0x00, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20, 0xb0, - 0x03, 0x26, 0x53, 0x58, 0x23, 0x21, 0xb8, 0x01, 0x40, 0x8a, 0x8a, 0x1b, 0x8a, 0x23, 0x59, 0x20, - 0xb8, 0x00, 0x03, 0x26, 0x53, 0x58, 0xb0, 0x03, 0x25, 0x45, 0xb8, 0x01, 0x80, 0x50, 0x58, 0x23, - 0x21, 0xb8, 0x01, 0x80, 0x23, 0x21, 0x1b, 0xb0, 0x03, 0x25, 0x45, 0x23, 0x21, 0x23, 0x21, 0x59, - 0x1b, 0x21, 0x59, 0x44, 0x2d, 0xb8, 0x00, 0x09, 0x2c, 0x4b, 0x53, 0x58, 0x45, 0x44, 0x1b, 0x21, - 0x21, 0x59, 0x2d, 0x00, 0xb8, 0x00, 0x00, 0x2b, 0x00, 0xba, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07, - 0x2b, 0xb8, 0x00, 0x00, 0x20, 0x45, 0x7d, 0x69, 0x18, 0x44, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x03, 0xe6, 0x03, 0xe8, 0x00, 0x06, - 0x00, 0x00, 0x35, 0x01, 0x33, 0x15, 0x01, 0x23, 0x35, 0x03, 0x52, 0x94, 0xfc, 0xa6, 0x8c, 0x90, - 0x03, 0x58, 0x86, 0xfc, 0xa0, 0x8e, 0x00, 0x00, 0x00, 0x02, 0x00, 0xeb, 0x00, 0xcc, 0x02, 0xfb, - 0x03, 0x1e, 0x00, 0x08, 0x00, 0x0f, 0x00, 0x00, 0x01, 0x33, 0x13, 0x23, 0x27, 0x23, 0x07, 0x23, - 0x13, 0x17, 0x07, 0x06, 0x15, 0x33, 0x27, 0x07, 0x01, 0xbc, 0x6d, 0xd2, 0x7c, 0x26, 0xcc, 0x26, - 0x7c, 0xd1, 0x35, 0x40, 0x02, 0x89, 0x45, 0x02, 0x03, 0x1e, 0xfd, 0xae, 0x77, 0x77, 0x02, 0x52, - 0x9b, 0xcc, 0x08, 0x04, 0xda, 0x02, 0x00, 0x00, 0x00, 0x03, 0x01, 0x21, 0x00, 0xcc, 0x02, 0xc5, - 0x03, 0x1e, 0x00, 0x15, 0x00, 0x1f, 0x00, 0x2b, 0x00, 0x00, 0x25, 0x11, 0x33, 0x32, 0x1e, 0x02, - 0x15, 0x14, 0x0e, 0x02, 0x07, 0x1e, 0x01, 0x15, 0x14, 0x0e, 0x02, 0x2b, 0x01, 0x13, 0x33, 0x32, - 0x36, 0x35, 0x34, 0x26, 0x2b, 0x01, 0x1d, 0x01, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x26, 0x2b, - 0x01, 0x15, 0x01, 0x21, 0xea, 0x25, 0x3f, 0x2e, 0x1a, 0x0e, 0x15, 0x1b, 0x0e, 0x2d, 0x2d, 0x1a, - 0x2e, 0x3f, 0x25, 0xf8, 0x76, 0x62, 0x20, 0x2a, 0x28, 0x22, 0x62, 0x76, 0x10, 0x18, 0x11, 0x09, - 0x22, 0x22, 0x74, 0xcc, 0x02, 0x52, 0x18, 0x2b, 0x3c, 0x24, 0x1d, 0x1f, 0x17, 0x17, 0x14, 0x0f, - 0x48, 0x2f, 0x24, 0x3f, 0x2e, 0x1a, 0x01, 0x5b, 0x29, 0x20, 0x20, 0x2b, 0x94, 0xf8, 0x0e, 0x16, - 0x1c, 0x0e, 0x1f, 0x31, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xff, 0x00, 0xcc, 0x02, 0xe7, - 0x03, 0x1e, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x33, 0x17, 0x37, 0x33, 0x03, 0x13, 0x23, 0x27, 0x07, - 0x23, 0x13, 0x03, 0x01, 0x04, 0x86, 0x69, 0x69, 0x86, 0xa3, 0xa8, 0x88, 0x6c, 0x6c, 0x88, 0xa8, - 0xa3, 0x03, 0x1e, 0xcb, 0xcb, 0xfe, 0xda, 0xfe, 0xd4, 0xcf, 0xcf, 0x01, 0x2c, 0x01, 0x26, 0x00, - 0x00, 0x01, 0x00, 0xff, 0x00, 0xcc, 0x02, 0xe7, 0x03, 0x1e, 0x00, 0x0f, 0x00, 0x00, 0x01, 0x03, - 0x33, 0x17, 0x32, 0x15, 0x1e, 0x01, 0x15, 0x1b, 0x01, 0x33, 0x03, 0x15, 0x23, 0x35, 0x01, 0xb8, - 0xb9, 0x7e, 0x01, 0x01, 0x01, 0x03, 0x70, 0x75, 0x7f, 0xb9, 0x76, 0x01, 0xa3, 0x01, 0x7b, 0x01, - 0x01, 0x01, 0x05, 0x02, 0xff, 0x00, 0x01, 0x0a, 0xfe, 0x85, 0xd7, 0xd7, 0x00, 0x01, 0x01, 0x3d, - 0x00, 0xcc, 0x02, 0xa9, 0x03, 0x1e, 0x00, 0x06, 0x00, 0x00, 0x25, 0x11, 0x33, 0x11, 0x33, 0x15, - 0x21, 0x01, 0x3d, 0x75, 0xf7, 0xfe, 0x94, 0xcc, 0x02, 0x52, 0xfe, 0x10, 0x62, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x01, 0x17, 0x00, 0xbc, 0x02, 0xcf, 0x03, 0x0e, 0x00, 0x15, 0x00, 0x21, 0x00, 0x00, - 0x25, 0x11, 0x33, 0x32, 0x1e, 0x02, 0x1d, 0x01, 0x0e, 0x03, 0x1d, 0x01, 0x17, 0x15, 0x23, 0x27, - 0x23, 0x15, 0x23, 0x13, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x26, 0x2b, 0x01, 0x15, 0x01, 0x17, - 0xf4, 0x27, 0x40, 0x2e, 0x19, 0x01, 0x1f, 0x24, 0x1e, 0x78, 0x7d, 0x6a, 0x5c, 0x75, 0x76, 0x72, - 0x12, 0x19, 0x11, 0x08, 0x26, 0x26, 0x6a, 0xbc, 0x02, 0x52, 0x1d, 0x31, 0x42, 0x25, 0x16, 0x18, - 0x32, 0x2a, 0x1b, 0x02, 0x01, 0xef, 0x06, 0xd7, 0xd7, 0x01, 0x3f, 0x10, 0x1a, 0x1e, 0x0f, 0x23, - 0x36, 0xb0, 0x00, 0x00, 0x00, 0x02, 0x00, 0x42, 0x00, 0xbc, 0x03, 0xa4, 0x03, 0x0e, 0x00, 0x0a, - 0x00, 0x11, 0x00, 0x00, 0x13, 0x35, 0x21, 0x15, 0x01, 0x21, 0x15, 0x21, 0x35, 0x01, 0x21, 0x01, - 0x11, 0x33, 0x11, 0x33, 0x15, 0x21, 0x42, 0x01, 0xa7, 0xfe, 0xeb, 0x01, 0x1b, 0xfe, 0x53, 0x01, - 0x15, 0xfe, 0xeb, 0x01, 0xf7, 0x75, 0xf6, 0xfe, 0x95, 0x02, 0xac, 0x62, 0x45, 0xfe, 0x55, 0x62, - 0x47, 0x01, 0xa9, 0xfe, 0x10, 0x02, 0x52, 0xfe, 0x10, 0x62, 0x00, 0x00, 0x00, 0x03, 0x00, 0x1c, - 0x00, 0xbc, 0x03, 0xca, 0x03, 0x0e, 0x00, 0x0a, 0x00, 0x21, 0x00, 0x2f, 0x00, 0x00, 0x13, 0x35, - 0x21, 0x15, 0x01, 0x21, 0x15, 0x21, 0x35, 0x01, 0x21, 0x01, 0x11, 0x33, 0x32, 0x1e, 0x02, 0x15, - 0x14, 0x06, 0x07, 0x0e, 0x03, 0x15, 0x17, 0x15, 0x23, 0x27, 0x23, 0x15, 0x23, 0x13, 0x33, 0x32, - 0x3e, 0x02, 0x35, 0x34, 0x2e, 0x02, 0x2b, 0x01, 0x15, 0x1c, 0x01, 0xa7, 0xfe, 0xeb, 0x01, 0x1b, - 0xfe, 0x53, 0x01, 0x15, 0xfe, 0xeb, 0x01, 0xf7, 0xf3, 0x27, 0x41, 0x2d, 0x19, 0x1c, 0x20, 0x01, - 0x0d, 0x0e, 0x0a, 0x78, 0x7d, 0x69, 0x5c, 0x75, 0x76, 0x71, 0x11, 0x1a, 0x12, 0x09, 0x0a, 0x14, - 0x1d, 0x13, 0x69, 0x02, 0xac, 0x62, 0x45, 0xfe, 0x55, 0x62, 0x47, 0x01, 0xa9, 0xfe, 0x10, 0x02, - 0x52, 0x1d, 0x31, 0x42, 0x25, 0x2b, 0x44, 0x1d, 0x01, 0x08, 0x09, 0x07, 0x01, 0xf1, 0x06, 0xd7, - 0xd7, 0x01, 0x3f, 0x11, 0x19, 0x1f, 0x0e, 0x11, 0x20, 0x19, 0x0f, 0xb0, 0x00, 0x02, 0x00, 0x3e, - 0x00, 0xb3, 0x03, 0xa8, 0x03, 0x17, 0x00, 0x3a, 0x00, 0x41, 0x00, 0x00, 0x13, 0x34, 0x3e, 0x02, - 0x33, 0x32, 0x1e, 0x02, 0x15, 0x23, 0x27, 0x34, 0x27, 0x2e, 0x01, 0x23, 0x22, 0x0e, 0x02, 0x15, - 0x14, 0x16, 0x15, 0x1e, 0x05, 0x15, 0x14, 0x0e, 0x02, 0x23, 0x22, 0x2e, 0x02, 0x35, 0x33, 0x1e, - 0x01, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x2e, 0x04, 0x35, 0x01, 0x11, 0x33, 0x11, 0x33, 0x15, - 0x21, 0x50, 0x24, 0x3b, 0x4a, 0x27, 0x28, 0x4b, 0x39, 0x22, 0x73, 0x01, 0x01, 0x08, 0x2b, 0x29, - 0x10, 0x20, 0x19, 0x0f, 0x01, 0x0b, 0x35, 0x41, 0x46, 0x3b, 0x25, 0x23, 0x3a, 0x4b, 0x27, 0x2b, - 0x50, 0x3f, 0x26, 0x74, 0x05, 0x34, 0x33, 0x10, 0x20, 0x1a, 0x11, 0x2c, 0x42, 0x4d, 0x42, 0x2c, - 0x01, 0xef, 0x73, 0xf6, 0xfe, 0x97, 0x02, 0x70, 0x2a, 0x3f, 0x2a, 0x14, 0x18, 0x2e, 0x44, 0x2c, - 0x02, 0x03, 0x01, 0x27, 0x27, 0x07, 0x10, 0x1a, 0x12, 0x02, 0x0b, 0x02, 0x1f, 0x22, 0x19, 0x17, - 0x27, 0x3f, 0x34, 0x2c, 0x3e, 0x28, 0x13, 0x1a, 0x32, 0x48, 0x2e, 0x30, 0x30, 0x06, 0x0f, 0x1a, - 0x13, 0x21, 0x27, 0x1e, 0x1b, 0x29, 0x3e, 0x31, 0xfe, 0x4c, 0x02, 0x53, 0xfe, 0x10, 0x63, 0x00, - 0x00, 0x03, 0x00, 0x17, 0x00, 0xb3, 0x03, 0xce, 0x03, 0x17, 0x00, 0x38, 0x00, 0x4f, 0x00, 0x5d, - 0x00, 0x00, 0x13, 0x34, 0x3e, 0x02, 0x33, 0x32, 0x1e, 0x02, 0x15, 0x23, 0x27, 0x34, 0x23, 0x2e, - 0x01, 0x23, 0x22, 0x0e, 0x02, 0x15, 0x14, 0x1e, 0x04, 0x15, 0x14, 0x0e, 0x02, 0x23, 0x22, 0x2e, - 0x02, 0x35, 0x33, 0x1e, 0x01, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x26, 0x27, 0x2e, 0x03, 0x35, - 0x01, 0x11, 0x33, 0x32, 0x1e, 0x02, 0x15, 0x14, 0x06, 0x07, 0x30, 0x0e, 0x02, 0x31, 0x17, 0x15, - 0x23, 0x27, 0x23, 0x15, 0x23, 0x13, 0x33, 0x32, 0x3e, 0x02, 0x35, 0x34, 0x2e, 0x02, 0x2b, 0x01, - 0x15, 0x2a, 0x24, 0x3a, 0x4a, 0x26, 0x29, 0x4b, 0x39, 0x23, 0x73, 0x01, 0x01, 0x08, 0x2a, 0x2a, - 0x10, 0x1f, 0x1a, 0x10, 0x2c, 0x42, 0x4d, 0x42, 0x2c, 0x23, 0x39, 0x4b, 0x27, 0x2b, 0x51, 0x3f, - 0x27, 0x75, 0x05, 0x34, 0x33, 0x10, 0x20, 0x1a, 0x10, 0x1f, 0x1c, 0x25, 0x53, 0x47, 0x2e, 0x01, - 0xed, 0xf3, 0x27, 0x41, 0x2d, 0x19, 0x1c, 0x20, 0x0c, 0x0e, 0x0c, 0x78, 0x7d, 0x68, 0x5d, 0x75, - 0x76, 0x71, 0x11, 0x1a, 0x12, 0x09, 0x0a, 0x14, 0x1d, 0x13, 0x69, 0x02, 0x71, 0x2a, 0x3e, 0x2a, - 0x14, 0x18, 0x2e, 0x44, 0x2c, 0x02, 0x02, 0x27, 0x29, 0x07, 0x11, 0x1a, 0x12, 0x1d, 0x24, 0x1c, - 0x1d, 0x2b, 0x40, 0x32, 0x2c, 0x3f, 0x29, 0x13, 0x1a, 0x31, 0x49, 0x2e, 0x30, 0x30, 0x06, 0x0f, - 0x19, 0x13, 0x1e, 0x22, 0x0b, 0x0e, 0x20, 0x2f, 0x43, 0x30, 0xfe, 0x4b, 0x02, 0x52, 0x1d, 0x32, - 0x42, 0x25, 0x2c, 0x42, 0x1d, 0x08, 0x0a, 0x08, 0xf1, 0x06, 0xd7, 0xd7, 0x01, 0x3f, 0x11, 0x19, - 0x1f, 0x0e, 0x11, 0x20, 0x19, 0x0f, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x12, 0x00, 0x12, - 0x00, 0x12, 0x00, 0x32, 0x00, 0x72, 0x00, 0x8e, 0x00, 0xac, 0x00, 0xbe, 0x00, 0xf0, 0x01, 0x14, - 0x01, 0x5c, 0x01, 0xb6, 0x02, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0xa2, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x00, 0x07, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x2f, - 0x00, 0x17, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x46, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x58, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x06, 0x00, 0x12, 0x00, 0x65, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x01, 0x00, 0x20, - 0x00, 0x77, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x02, 0x00, 0x0e, 0x00, 0x97, 0x00, 0x03, - 0x00, 0x01, 0x04, 0x09, 0x00, 0x03, 0x00, 0x5e, 0x00, 0xa5, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, - 0x00, 0x04, 0x00, 0x24, 0x01, 0x03, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x05, 0x00, 0x1a, - 0x01, 0x27, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x06, 0x00, 0x24, 0x01, 0x41, 0x00, 0x03, - 0x00, 0x01, 0x04, 0x09, 0x00, 0x11, 0x00, 0x02, 0x01, 0x65, 0x59, 0x75, 0x7a, 0x75, 0x4f, 0x53, - 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x67, 0x75, 0x6c, 0x61, - 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x2e, 0x30, 0x30, 0x30, 0x3b, 0x3b, - 0x59, 0x75, 0x7a, 0x75, 0x4f, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, - 0x2d, 0x52, 0x3b, 0x32, 0x30, 0x31, 0x39, 0x3b, 0x46, 0x4c, 0x56, 0x49, 0x2d, 0x36, 0x31, 0x34, - 0x59, 0x75, 0x7a, 0x75, 0x4f, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, - 0x20, 0x52, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x2e, 0x30, 0x30, 0x30, 0x59, - 0x75, 0x7a, 0x75, 0x4f, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x2d, - 0x52, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00, - 0x45, 0x00, 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, - 0x6e, 0x00, 0x52, 0x00, 0x65, 0x00, 0x67, 0x00, 0x75, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x72, 0x00, - 0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00, - 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x3b, 0x00, 0x3b, 0x00, 0x59, 0x00, - 0x75, 0x00, 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00, - 0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x2d, 0x00, - 0x52, 0x00, 0x3b, 0x00, 0x32, 0x00, 0x30, 0x00, 0x31, 0x00, 0x39, 0x00, 0x3b, 0x00, 0x46, 0x00, - 0x4c, 0x00, 0x56, 0x00, 0x49, 0x00, 0x2d, 0x00, 0x36, 0x00, 0x31, 0x00, 0x34, 0x00, 0x59, 0x00, - 0x75, 0x00, 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00, - 0x74, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00, - 0x52, 0x00, 0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, - 0x20, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x59, 0x00, 0x75, 0x00, - 0x7a, 0x00, 0x75, 0x00, 0x4f, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00, 0x74, 0x00, - 0x65, 0x00, 0x6e, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x2d, 0x00, 0x52, 0x00, - 0x52, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x9c, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x01, 0x02, 0x01, 0x03, 0x00, 0x03, 0x01, 0x04, - 0x01, 0x05, 0x01, 0x06, 0x01, 0x07, 0x01, 0x08, 0x01, 0x09, 0x01, 0x0a, 0x01, 0x0b, 0x01, 0x0c, - 0x01, 0x0d, 0x07, 0x75, 0x6e, 0x69, 0x30, 0x30, 0x30, 0x30, 0x07, 0x75, 0x6e, 0x69, 0x30, 0x30, - 0x30, 0x44, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x30, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, - 0x45, 0x31, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x32, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, - 0x45, 0x33, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x34, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, - 0x45, 0x35, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x36, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, - 0x45, 0x37, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, 0x45, 0x38, 0x07, 0x75, 0x6e, 0x69, 0x45, 0x30, - 0x45, 0x39, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0xff, 0xff, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x01, - 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, 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, 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, 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, + 0xB8, 0x00, 0x00, 0x2C, 0x4B, 0xB8, 0x00, 0x09, 0x50, 0x58, 0xB1, 0x01, 0x01, 0x8E, 0x59, 0xB8, + 0x01, 0xFF, 0x85, 0xB8, 0x00, 0x44, 0x1D, 0xB9, 0x00, 0x09, 0x00, 0x03, 0x5F, 0x5E, 0x2D, 0xB8, + 0x00, 0x01, 0x2C, 0x20, 0x20, 0x45, 0x69, 0x44, 0xB0, 0x01, 0x60, 0x2D, 0xB8, 0x00, 0x02, 0x2C, + 0xB8, 0x00, 0x01, 0x2A, 0x21, 0x2D, 0xB8, 0x00, 0x03, 0x2C, 0x20, 0x46, 0xB0, 0x03, 0x25, 0x46, + 0x52, 0x58, 0x23, 0x59, 0x20, 0x8A, 0x20, 0x8A, 0x49, 0x64, 0x8A, 0x20, 0x46, 0x20, 0x68, 0x61, + 0x64, 0xB0, 0x04, 0x25, 0x46, 0x20, 0x68, 0x61, 0x64, 0x52, 0x58, 0x23, 0x65, 0x8A, 0x59, 0x2F, + 0x20, 0xB0, 0x00, 0x53, 0x58, 0x69, 0x20, 0xB0, 0x00, 0x54, 0x58, 0x21, 0xB0, 0x40, 0x59, 0x1B, + 0x69, 0x20, 0xB0, 0x00, 0x54, 0x58, 0x21, 0xB0, 0x40, 0x65, 0x59, 0x59, 0x3A, 0x2D, 0xB8, 0x00, + 0x04, 0x2C, 0x20, 0x46, 0xB0, 0x04, 0x25, 0x46, 0x52, 0x58, 0x23, 0x8A, 0x59, 0x20, 0x46, 0x20, + 0x6A, 0x61, 0x64, 0xB0, 0x04, 0x25, 0x46, 0x20, 0x6A, 0x61, 0x64, 0x52, 0x58, 0x23, 0x8A, 0x59, + 0x2F, 0xFD, 0x2D, 0xB8, 0x00, 0x05, 0x2C, 0x4B, 0x20, 0xB0, 0x03, 0x26, 0x50, 0x58, 0x51, 0x58, + 0xB0, 0x80, 0x44, 0x1B, 0xB0, 0x40, 0x44, 0x59, 0x1B, 0x21, 0x21, 0x20, 0x45, 0xB0, 0xC0, 0x50, + 0x58, 0xB0, 0xC0, 0x44, 0x1B, 0x21, 0x59, 0x59, 0x2D, 0xB8, 0x00, 0x06, 0x2C, 0x20, 0x20, 0x45, + 0x69, 0x44, 0xB0, 0x01, 0x60, 0x20, 0x20, 0x45, 0x7D, 0x69, 0x18, 0x44, 0xB0, 0x01, 0x60, 0x2D, + 0xB8, 0x00, 0x07, 0x2C, 0xB8, 0x00, 0x06, 0x2A, 0x2D, 0xB8, 0x00, 0x08, 0x2C, 0x4B, 0x20, 0xB0, + 0x03, 0x26, 0x53, 0x58, 0xB0, 0x40, 0x1B, 0xB0, 0x00, 0x59, 0x8A, 0x8A, 0x20, 0xB0, 0x03, 0x26, + 0x53, 0x58, 0x23, 0x21, 0xB0, 0x80, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20, 0xB0, 0x03, 0x26, + 0x53, 0x58, 0x23, 0x21, 0xB8, 0x00, 0xC0, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20, 0xB0, 0x03, + 0x26, 0x53, 0x58, 0x23, 0x21, 0xB8, 0x01, 0x00, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20, 0xB0, + 0x03, 0x26, 0x53, 0x58, 0x23, 0x21, 0xB8, 0x01, 0x40, 0x8A, 0x8A, 0x1B, 0x8A, 0x23, 0x59, 0x20, + 0xB8, 0x00, 0x03, 0x26, 0x53, 0x58, 0xB0, 0x03, 0x25, 0x45, 0xB8, 0x01, 0x80, 0x50, 0x58, 0x23, + 0x21, 0xB8, 0x01, 0x80, 0x23, 0x21, 0x1B, 0xB0, 0x03, 0x25, 0x45, 0x23, 0x21, 0x23, 0x21, 0x59, + 0x1B, 0x21, 0x59, 0x44, 0x2D, 0xB8, 0x00, 0x09, 0x2C, 0x4B, 0x53, 0x58, 0x45, 0x44, 0x1B, 0x21, + 0x21, 0x59, 0x2D, 0x00, 0xB8, 0x00, 0x00, 0x2B, 0x00, 0xBA, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07, + 0x2B, 0xB8, 0x00, 0x00, 0x20, 0x45, 0x7D, 0x69, 0x18, 0x44, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x16, 0x00, 0x70, + 0x00, 0xDC, 0x01, 0x34, 0x01, 0x7C, 0x01, 0xA2, 0x01, 0xF4, 0x02, 0x3C, 0x02, 0xA8, 0x03, 0x4C, + 0x03, 0xE2, 0x04, 0x20, 0x04, 0x58, 0x04, 0x9A, 0x04, 0xEE, 0x05, 0x32, 0x05, 0x64, 0x05, 0x80, + 0x05, 0xC6, 0x05, 0xF6, 0x06, 0x54, 0x06, 0xB2, 0x07, 0x38, 0x07, 0x60, 0x07, 0x82, 0x00, 0x00, + 0x00, 0x02, 0x00, 0xA4, 0xFF, 0xFF, 0x03, 0x5C, 0x03, 0x09, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, + 0x13, 0x11, 0x21, 0x11, 0x25, 0x21, 0x11, 0x21, 0xCD, 0x02, 0x66, 0xFD, 0x71, 0x02, 0xB8, 0xFD, + 0x48, 0x02, 0xE0, 0xFD, 0x48, 0x02, 0xB8, 0x29, 0xFC, 0xF6, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14, + 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1F, 0x00, 0x2F, 0x00, 0x39, 0x00, 0x00, + 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x01, 0x24, 0x32, 0x1E, + 0x02, 0x14, 0x0E, 0x02, 0x22, 0x2E, 0x02, 0x34, 0x3E, 0x01, 0x13, 0x12, 0x37, 0x33, 0x13, 0x12, + 0x15, 0x16, 0x23, 0x2F, 0x01, 0x23, 0x07, 0x23, 0x22, 0x26, 0x25, 0x30, 0x27, 0x26, 0x2F, 0x01, + 0x06, 0x07, 0x06, 0x32, 0x02, 0x5A, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, + 0x46, 0x46, 0x77, 0xFE, 0x9E, 0xC8, 0xB7, 0x83, 0x4E, 0x4E, 0x83, 0xB7, 0xC8, 0xB7, 0x83, 0x4E, + 0x4E, 0x83, 0x23, 0x6C, 0x5E, 0x6D, 0x68, 0x68, 0x01, 0x39, 0x38, 0x2E, 0xD1, 0x2B, 0x37, 0x33, + 0x04, 0x01, 0x48, 0x1D, 0x1C, 0x0A, 0x05, 0x01, 0x45, 0x01, 0x89, 0x03, 0x3F, 0x46, 0x77, 0xA4, + 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x4E, 0x83, 0xB7, 0xC8, 0xB7, + 0x83, 0x4E, 0x4E, 0x83, 0xB7, 0xC8, 0xB7, 0x83, 0xFD, 0x64, 0x01, 0x1A, 0xEB, 0xFE, 0xFE, 0xFE, + 0xFD, 0x03, 0x01, 0x01, 0x77, 0x78, 0x01, 0xCF, 0x4C, 0x4C, 0x1C, 0x0C, 0x02, 0xBE, 0x02, 0x00, + 0x00, 0x05, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x2F, + 0x00, 0x3A, 0x00, 0x44, 0x00, 0x00, 0x12, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x02, + 0x22, 0x0E, 0x01, 0x02, 0x10, 0x3E, 0x01, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x26, 0x01, + 0x16, 0x17, 0x14, 0x06, 0x07, 0x06, 0x2B, 0x01, 0x19, 0x01, 0x17, 0x32, 0x17, 0x16, 0x17, 0x16, + 0x07, 0x06, 0x0F, 0x01, 0x36, 0x37, 0x34, 0x2E, 0x01, 0x27, 0x23, 0x15, 0x33, 0x32, 0x27, 0x32, + 0x37, 0x36, 0x26, 0x27, 0x26, 0x2B, 0x01, 0x15, 0x45, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, + 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, + 0xF4, 0xE2, 0x01, 0xF7, 0x61, 0x01, 0x4E, 0x3E, 0x29, 0xAF, 0x4E, 0x81, 0x8B, 0x1D, 0x3C, 0x1F, + 0x19, 0x04, 0x06, 0x39, 0x57, 0x44, 0x01, 0x1B, 0x2D, 0x51, 0x46, 0x46, 0x47, 0x66, 0x70, 0x16, + 0x1F, 0x01, 0x2C, 0x08, 0x4B, 0x4C, 0x01, 0xDE, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, + 0xA4, 0x77, 0x46, 0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, + 0x84, 0x84, 0x01, 0x6D, 0x21, 0x5B, 0x40, 0x50, 0x05, 0x03, 0x01, 0x03, 0x01, 0x05, 0x01, 0x05, + 0x09, 0x30, 0x25, 0x29, 0x40, 0x21, 0xC2, 0x06, 0x3E, 0x1A, 0x21, 0x0B, 0x01, 0x8C, 0xE1, 0x0A, + 0x0E, 0x54, 0x0B, 0x02, 0x79, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, + 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x38, 0x00, 0x00, 0x12, 0x14, 0x1E, 0x02, 0x32, 0x3E, + 0x02, 0x34, 0x2E, 0x02, 0x22, 0x0E, 0x01, 0x02, 0x10, 0x3E, 0x01, 0x20, 0x1E, 0x01, 0x10, 0x0E, + 0x01, 0x20, 0x26, 0x36, 0x34, 0x3F, 0x01, 0x27, 0x26, 0x27, 0x33, 0x17, 0x16, 0x33, 0x36, 0x3F, + 0x02, 0x32, 0x14, 0x06, 0x16, 0x12, 0x14, 0x2B, 0x01, 0x27, 0x26, 0x06, 0x0F, 0x01, 0x23, 0x45, + 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2, + 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x7B, 0x58, 0x58, 0x4D, 0x4F, 0x05, 0x7A, + 0x34, 0x34, 0x02, 0x01, 0x33, 0x32, 0x3C, 0x3C, 0xA1, 0x01, 0xB0, 0x3E, 0x3F, 0x39, 0x3B, 0x02, + 0x3A, 0x38, 0x3F, 0x01, 0xDE, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, + 0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x60, + 0x02, 0x87, 0x88, 0x79, 0x7A, 0x06, 0x54, 0x54, 0x01, 0x53, 0x53, 0x01, 0x01, 0xFB, 0x04, 0xFE, + 0xF8, 0x02, 0x5B, 0x5A, 0x03, 0x59, 0x59, 0x00, 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, + 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E, + 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x01, 0x24, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, + 0x01, 0x10, 0x36, 0x01, 0x35, 0x27, 0x26, 0x34, 0x3B, 0x01, 0x17, 0x16, 0x36, 0x3F, 0x01, 0x33, + 0x03, 0x15, 0x23, 0x02, 0x5A, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, + 0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x01, + 0x36, 0x5E, 0x5F, 0x3C, 0x3D, 0x3D, 0x3D, 0x03, 0x3B, 0x3B, 0x77, 0xBE, 0x68, 0x03, 0x3F, 0x46, + 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2, 0xFE, + 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFD, 0xF9, 0x6E, 0x96, 0x95, 0x01, 0x67, 0x67, + 0x03, 0x66, 0x65, 0xFE, 0xD3, 0xDA, 0x00, 0x00, 0x00, 0x03, 0x00, 0x14, 0xFF, 0xBD, 0x03, 0xEC, + 0x03, 0x4B, 0x00, 0x06, 0x00, 0x0C, 0x00, 0x12, 0x00, 0x00, 0x01, 0x21, 0x22, 0x15, 0x30, 0x11, + 0x21, 0x17, 0x21, 0x11, 0x10, 0x25, 0x21, 0x01, 0x11, 0x33, 0x11, 0x21, 0x15, 0x03, 0xBB, 0xFD, + 0x77, 0xED, 0x03, 0x76, 0x31, 0xFC, 0x28, 0x01, 0x1E, 0x02, 0xBA, 0xFD, 0x5C, 0x68, 0x01, 0x08, + 0x03, 0x1A, 0xEE, 0xFD, 0xC2, 0x31, 0x02, 0x6F, 0x01, 0x1E, 0x01, 0xFD, 0x36, 0x02, 0x07, 0xFE, + 0x50, 0x57, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14, 0xFF, 0xBD, 0x03, 0xEC, 0x03, 0x4B, 0x00, 0x06, + 0x00, 0x0C, 0x00, 0x27, 0x00, 0x32, 0x00, 0x00, 0x05, 0x11, 0x34, 0x27, 0x30, 0x21, 0x11, 0x07, + 0x11, 0x21, 0x20, 0x19, 0x01, 0x25, 0x11, 0x33, 0x32, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16, 0x07, + 0x06, 0x07, 0x06, 0x07, 0x1E, 0x02, 0x15, 0x07, 0x23, 0x27, 0x2E, 0x01, 0x2F, 0x01, 0x15, 0x13, + 0x36, 0x35, 0x34, 0x27, 0x26, 0x27, 0x23, 0x15, 0x33, 0x36, 0x03, 0xBB, 0xED, 0xFD, 0x77, 0x31, + 0x02, 0xBA, 0x01, 0x1E, 0xFD, 0x2A, 0x77, 0x76, 0x15, 0x49, 0x20, 0x35, 0x08, 0x04, 0x06, 0x13, + 0x66, 0x0C, 0x01, 0x1F, 0x2E, 0x65, 0x3D, 0x3D, 0x2A, 0x56, 0x28, 0x2E, 0x19, 0x99, 0x3C, 0x20, + 0x10, 0x56, 0x4F, 0x46, 0x47, 0x12, 0x02, 0x3E, 0xED, 0x01, 0xFC, 0xD4, 0x31, 0x03, 0x8E, 0xFE, + 0xE1, 0xFD, 0x91, 0xC4, 0x02, 0x07, 0x01, 0x04, 0x13, 0x21, 0x44, 0x1D, 0x19, 0x58, 0x15, 0x02, + 0x01, 0x13, 0x2D, 0xA2, 0x01, 0x01, 0x3D, 0x81, 0x1A, 0x01, 0x01, 0xDA, 0x01, 0x2D, 0x08, 0x3A, + 0x29, 0x0F, 0x08, 0x01, 0x85, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14, 0xFF, 0xF5, 0x03, 0xEC, + 0x03, 0x13, 0x00, 0x09, 0x00, 0x11, 0x00, 0x26, 0x00, 0x32, 0x00, 0x00, 0x37, 0x21, 0x34, 0x10, + 0x35, 0x34, 0x27, 0x21, 0x04, 0x11, 0x23, 0x10, 0x25, 0x21, 0x16, 0x15, 0x11, 0x21, 0x37, 0x35, + 0x37, 0x36, 0x22, 0x2B, 0x01, 0x3D, 0x01, 0x3B, 0x01, 0x1D, 0x01, 0x0F, 0x01, 0x3B, 0x01, 0x1D, + 0x01, 0x2B, 0x01, 0x25, 0x35, 0x3B, 0x01, 0x1D, 0x01, 0x3B, 0x01, 0x1D, 0x01, 0x2B, 0x01, 0x45, + 0x03, 0x76, 0x45, 0xFE, 0x2D, 0xFE, 0xA2, 0x31, 0x01, 0x8F, 0x01, 0xD3, 0x76, 0xFC, 0x28, 0xA7, + 0x68, 0x68, 0x01, 0x5B, 0x5D, 0x90, 0x91, 0x6C, 0x6D, 0x71, 0x70, 0xA0, 0xA0, 0x01, 0x75, 0x27, + 0x28, 0x63, 0x63, 0x8B, 0x8A, 0x27, 0x69, 0x01, 0xA4, 0x69, 0x44, 0x01, 0x02, 0xFE, 0xA4, 0x01, + 0x8C, 0x03, 0x01, 0x75, 0xFD, 0x58, 0xBB, 0x24, 0x80, 0x80, 0x21, 0x21, 0x1F, 0x1E, 0x85, 0x86, + 0x20, 0x22, 0xC3, 0xC3, 0xA1, 0xA3, 0x20, 0x22, 0x00, 0x05, 0x00, 0x14, 0xFF, 0xF5, 0x03, 0xEC, + 0x03, 0x13, 0x00, 0x08, 0x00, 0x10, 0x00, 0x2B, 0x00, 0x37, 0x00, 0x44, 0x00, 0x00, 0x37, 0x21, + 0x11, 0x10, 0x25, 0x30, 0x21, 0x06, 0x15, 0x03, 0x11, 0x34, 0x37, 0x21, 0x04, 0x19, 0x01, 0x01, + 0x35, 0x17, 0x32, 0x17, 0x16, 0x17, 0x16, 0x07, 0x06, 0x07, 0x06, 0x17, 0x16, 0x17, 0x16, 0x17, + 0x16, 0x23, 0x2F, 0x01, 0x2E, 0x01, 0x2F, 0x01, 0x15, 0x23, 0x37, 0x32, 0x36, 0x37, 0x36, 0x35, + 0x26, 0x27, 0x26, 0x2B, 0x01, 0x15, 0x05, 0x35, 0x37, 0x36, 0x26, 0x2B, 0x01, 0x35, 0x21, 0x15, + 0x03, 0x17, 0x15, 0x45, 0x03, 0x76, 0xFE, 0xA2, 0xFE, 0x2D, 0x45, 0x31, 0x76, 0x01, 0xD3, 0x01, + 0x8F, 0xFE, 0x1E, 0x65, 0x6F, 0x15, 0x46, 0x10, 0x05, 0x04, 0x0D, 0x4F, 0x09, 0x09, 0x1F, 0x1D, + 0x3A, 0x06, 0x01, 0x30, 0x2F, 0x22, 0x37, 0x1E, 0x29, 0x14, 0x4E, 0x82, 0x34, 0x19, 0x0E, 0x13, + 0x0A, 0x22, 0x07, 0x38, 0x37, 0xFE, 0x3E, 0x68, 0x68, 0x01, 0x5C, 0x5C, 0x01, 0x20, 0xD8, 0xE1, + 0x27, 0x01, 0x5D, 0x01, 0x5B, 0x03, 0x01, 0x44, 0xFD, 0x58, 0x02, 0xA8, 0x75, 0x01, 0x03, 0xFE, + 0x74, 0xFE, 0x71, 0x01, 0x5C, 0xC5, 0x01, 0x04, 0x0C, 0x43, 0x15, 0x1D, 0x44, 0x10, 0x04, 0x06, + 0x14, 0x2B, 0x56, 0x10, 0x01, 0x01, 0x34, 0x52, 0x1C, 0x01, 0x01, 0xA5, 0xE3, 0x04, 0x06, 0x0A, + 0x20, 0x2C, 0x04, 0x01, 0x65, 0xE3, 0x47, 0x80, 0x80, 0x01, 0x42, 0x3D, 0xFE, 0xF5, 0x01, 0x41, + 0x00, 0x04, 0x00, 0x14, 0x00, 0x52, 0x03, 0xEC, 0x02, 0xB6, 0x00, 0x08, 0x00, 0x16, 0x00, 0x64, + 0x00, 0x70, 0x00, 0x00, 0x25, 0x11, 0x21, 0x22, 0x15, 0x30, 0x15, 0x14, 0x33, 0x11, 0x21, 0x32, + 0x15, 0x11, 0x14, 0x27, 0x21, 0x22, 0x26, 0x3D, 0x01, 0x34, 0x36, 0x13, 0x26, 0x27, 0x26, 0x27, + 0x26, 0x37, 0x33, 0x36, 0x37, 0x36, 0x33, 0x16, 0x17, 0x16, 0x17, 0x16, 0x37, 0x36, 0x37, 0x36, + 0x35, 0x34, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, 0x34, 0x37, 0x36, 0x37, + 0x36, 0x37, 0x36, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16, 0x0F, 0x01, 0x22, 0x06, 0x23, + 0x27, 0x26, 0x27, 0x26, 0x23, 0x22, 0x07, 0x06, 0x07, 0x06, 0x17, 0x16, 0x17, 0x16, 0x17, 0x16, + 0x17, 0x16, 0x17, 0x16, 0x07, 0x06, 0x07, 0x06, 0x27, 0x37, 0x35, 0x3B, 0x01, 0x1D, 0x01, 0x3B, + 0x01, 0x1D, 0x01, 0x2B, 0x01, 0x03, 0xBB, 0xFD, 0x2A, 0xA0, 0xA0, 0x02, 0xEE, 0x19, 0x19, 0xFD, + 0x12, 0x57, 0x7A, 0x7A, 0xCA, 0x38, 0x1D, 0x16, 0x08, 0x03, 0x01, 0x02, 0x0F, 0x0C, 0x1E, 0x01, + 0x02, 0x04, 0x0C, 0x2B, 0x0F, 0x0E, 0x18, 0x0C, 0x09, 0x04, 0x15, 0x32, 0x23, 0x12, 0x1C, 0x0E, + 0x09, 0x03, 0x01, 0x01, 0x09, 0x21, 0x0F, 0x14, 0x2E, 0x2A, 0x13, 0x0F, 0x0C, 0x08, 0x0B, 0x05, + 0x02, 0x01, 0x02, 0x03, 0x36, 0x03, 0x02, 0x03, 0x08, 0x0D, 0x23, 0x16, 0x0E, 0x10, 0x01, 0x01, + 0x07, 0x0B, 0x32, 0x25, 0x13, 0x26, 0x0F, 0x09, 0x01, 0x01, 0x0F, 0x11, 0x24, 0x21, 0x2A, 0xE3, + 0x20, 0x20, 0x52, 0x50, 0x71, 0x71, 0x84, 0x02, 0x00, 0xAF, 0xA2, 0xAF, 0x02, 0x32, 0x19, 0xFD, + 0xCE, 0x19, 0x01, 0x84, 0x5C, 0xA2, 0x5C, 0x85, 0xFE, 0x29, 0x04, 0x1E, 0x18, 0x26, 0x0F, 0x01, + 0x02, 0x01, 0x03, 0x05, 0x0B, 0x29, 0x06, 0x02, 0x03, 0x04, 0x11, 0x0B, 0x0D, 0x0A, 0x06, 0x12, + 0x0D, 0x0A, 0x07, 0x0C, 0x18, 0x0D, 0x10, 0x06, 0x18, 0x05, 0x27, 0x14, 0x09, 0x03, 0x0A, 0x0D, + 0x06, 0x09, 0x09, 0x0D, 0x0F, 0x14, 0x0C, 0x06, 0x03, 0x02, 0x04, 0x10, 0x0A, 0x11, 0x08, 0x09, + 0x0E, 0x0C, 0x07, 0x0C, 0x0C, 0x0A, 0x07, 0x0F, 0x20, 0x11, 0x18, 0x1E, 0x1A, 0x1E, 0x0C, 0x0B, + 0x03, 0xAA, 0xA5, 0x89, 0x8A, 0x1C, 0x1B, 0x00, 0x00, 0x05, 0x00, 0x14, 0x00, 0x53, 0x03, 0xEC, + 0x02, 0xB6, 0x00, 0x08, 0x00, 0x16, 0x00, 0x2E, 0x00, 0x38, 0x00, 0x65, 0x00, 0x00, 0x01, 0x30, + 0x21, 0x11, 0x21, 0x32, 0x3D, 0x01, 0x34, 0x27, 0x32, 0x16, 0x1D, 0x01, 0x14, 0x06, 0x23, 0x21, + 0x26, 0x35, 0x11, 0x34, 0x33, 0x01, 0x11, 0x33, 0x32, 0x17, 0x16, 0x17, 0x16, 0x07, 0x06, 0x07, + 0x17, 0x1E, 0x01, 0x1F, 0x01, 0x23, 0x2A, 0x01, 0x2E, 0x01, 0x23, 0x27, 0x15, 0x37, 0x32, 0x37, + 0x36, 0x27, 0x2E, 0x01, 0x2B, 0x01, 0x15, 0x05, 0x26, 0x27, 0x37, 0x32, 0x3F, 0x01, 0x16, 0x17, + 0x1E, 0x01, 0x37, 0x36, 0x27, 0x2E, 0x04, 0x37, 0x3E, 0x01, 0x33, 0x32, 0x17, 0x16, 0x17, 0x14, + 0x06, 0x27, 0x26, 0x27, 0x26, 0x0E, 0x01, 0x1E, 0x02, 0x17, 0x16, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x03, 0x1B, 0xFD, 0x2A, 0x02, 0xD6, 0xA0, 0xA0, 0x57, 0x7A, 0x7A, 0x57, 0xFD, 0x12, 0x19, 0x19, + 0x01, 0xD3, 0x47, 0x44, 0x11, 0x3E, 0x18, 0x21, 0x0B, 0x0C, 0x43, 0x04, 0x17, 0x1C, 0x1E, 0x16, + 0x26, 0x26, 0x03, 0x4D, 0x18, 0x1E, 0x11, 0x25, 0x3A, 0x0C, 0x22, 0x08, 0x03, 0x1B, 0x3E, 0x29, + 0xFE, 0xAC, 0x0D, 0x04, 0x02, 0x02, 0x1E, 0x1D, 0x03, 0x02, 0x0C, 0x4C, 0x13, 0x20, 0x07, 0x04, + 0x1B, 0x56, 0x2D, 0x1C, 0x01, 0x02, 0x44, 0x35, 0x49, 0x1F, 0x10, 0x03, 0x41, 0x01, 0x06, 0x0A, + 0x16, 0x3C, 0x18, 0x0C, 0x16, 0x5D, 0x15, 0x33, 0x03, 0x2B, 0x1E, 0x34, 0x59, 0x02, 0x84, 0xFE, + 0x00, 0xAF, 0xA2, 0xAF, 0x32, 0x85, 0x5C, 0xA2, 0x5C, 0x84, 0x01, 0x17, 0x02, 0x32, 0x19, 0xFE, + 0x2F, 0x01, 0x45, 0x01, 0x02, 0x19, 0x22, 0x32, 0x39, 0x0B, 0x08, 0x0F, 0x27, 0x2F, 0x24, 0x75, + 0x12, 0x01, 0x88, 0xBB, 0x04, 0x09, 0x2A, 0x0F, 0x0D, 0x53, 0x8A, 0x17, 0x1E, 0x04, 0x03, 0x03, + 0x0C, 0x04, 0x26, 0x0E, 0x0C, 0x14, 0x1A, 0x0E, 0x0E, 0x16, 0x16, 0x2C, 0x1A, 0x2D, 0x2D, 0x2A, + 0x16, 0x1D, 0x06, 0x04, 0x01, 0x1A, 0x09, 0x11, 0x09, 0x17, 0x18, 0x0D, 0x17, 0x0C, 0x1B, 0x71, + 0x1B, 0x12, 0x01, 0x03, 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F, + 0x00, 0x1B, 0x00, 0x27, 0x00, 0x00, 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02, + 0x34, 0x2E, 0x01, 0x24, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13, + 0x33, 0x35, 0x33, 0x15, 0x33, 0x15, 0x23, 0x15, 0x23, 0x35, 0x23, 0x02, 0x5A, 0xB4, 0xA4, 0x77, + 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xFE, 0x7C, 0x01, 0x0C, 0xE2, 0x84, + 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0xC5, 0x4E, 0xC5, 0xC4, 0x50, 0xC4, 0x03, 0x3F, + 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2, + 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE, 0xC0, 0xC4, 0xC5, 0x4E, 0xC5, 0xC5, + 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0F, 0x00, 0x1B, 0x00, 0x1F, + 0x00, 0x00, 0x00, 0x22, 0x0E, 0x02, 0x14, 0x1E, 0x02, 0x32, 0x3E, 0x02, 0x34, 0x2E, 0x01, 0x24, + 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13, 0x35, 0x21, 0x15, 0x02, + 0x5A, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xFE, 0x7C, + 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0x01, 0xD8, 0x03, 0x3F, + 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x46, 0x46, 0x77, 0xA4, 0xB4, 0xA4, 0x77, 0x77, 0x84, 0xE2, + 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE, 0x71, 0x4E, 0x4E, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x1B, 0x00, 0x25, + 0x00, 0x00, 0x00, 0x20, 0x0E, 0x01, 0x10, 0x1E, 0x01, 0x20, 0x3E, 0x01, 0x10, 0x26, 0x01, 0x12, + 0x37, 0x33, 0x13, 0x12, 0x15, 0x16, 0x23, 0x2F, 0x01, 0x23, 0x07, 0x23, 0x22, 0x26, 0x25, 0x30, + 0x27, 0x26, 0x2F, 0x01, 0x06, 0x07, 0x06, 0x32, 0x02, 0x86, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, + 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xFD, 0xA0, 0x6C, 0x5E, 0x6D, 0x68, 0x68, 0x01, 0x39, 0x38, 0x2E, + 0xD1, 0x2B, 0x37, 0x33, 0x04, 0x01, 0x48, 0x1D, 0x1C, 0x0A, 0x05, 0x01, 0x45, 0x01, 0x89, 0x03, + 0x70, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFD, 0x9A, 0x01, 0x1A, + 0xEB, 0xFE, 0xFE, 0xFE, 0xFD, 0x03, 0x01, 0x01, 0x77, 0x78, 0x01, 0xCF, 0x4C, 0x4C, 0x1C, 0x0C, + 0x02, 0xBE, 0x02, 0x00, 0x00, 0x04, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, + 0x00, 0x20, 0x00, 0x2B, 0x00, 0x35, 0x00, 0x00, 0x36, 0x10, 0x3E, 0x01, 0x20, 0x1E, 0x01, 0x10, + 0x0E, 0x01, 0x20, 0x26, 0x01, 0x30, 0x37, 0x36, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26, 0x23, 0x27, + 0x19, 0x01, 0x33, 0x32, 0x37, 0x3E, 0x01, 0x35, 0x26, 0x07, 0x06, 0x2B, 0x01, 0x35, 0x33, 0x1E, + 0x02, 0x15, 0x06, 0x27, 0x23, 0x35, 0x33, 0x16, 0x17, 0x16, 0x14, 0x07, 0x06, 0x14, 0x84, 0xE2, + 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x01, 0xF7, 0x0A, 0x3A, 0x05, 0x04, 0x19, + 0x20, 0x3B, 0x1D, 0x8B, 0x81, 0x4E, 0xAF, 0x29, 0x3E, 0x4E, 0x01, 0xAE, 0x0D, 0x47, 0x46, 0x46, + 0x52, 0x2C, 0x1B, 0x01, 0xB7, 0x27, 0x4C, 0x4C, 0x07, 0x2C, 0x1E, 0x16, 0xFE, 0x01, 0x0C, 0xE2, + 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x01, 0x6D, 0x06, 0x21, 0x40, 0x2A, 0x24, 0x30, + 0x09, 0x05, 0x01, 0xFE, 0xFB, 0xFE, 0xFD, 0x03, 0x05, 0x4F, 0x41, 0x5B, 0x9B, 0x01, 0x8C, 0x01, + 0x0B, 0x21, 0x1A, 0x3E, 0xDA, 0x79, 0x01, 0x01, 0x0B, 0x54, 0x0E, 0x0A, 0x00, 0x02, 0x00, 0x14, + 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x29, 0x00, 0x00, 0x36, 0x10, 0x3E, 0x01, + 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x26, 0x36, 0x14, 0x3B, 0x01, 0x37, 0x36, 0x37, 0x36, + 0x1F, 0x01, 0x33, 0x32, 0x34, 0x02, 0x26, 0x36, 0x34, 0x23, 0x0F, 0x01, 0x06, 0x07, 0x22, 0x2F, + 0x01, 0x23, 0x16, 0x1F, 0x01, 0x07, 0x14, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, + 0xF4, 0xE2, 0x7B, 0x3D, 0x3F, 0x38, 0x3A, 0x01, 0x02, 0x3A, 0x39, 0x3F, 0x3E, 0xB0, 0x01, 0xA1, + 0x3C, 0x3C, 0x32, 0x33, 0x01, 0x02, 0x34, 0x34, 0x7A, 0x05, 0x4F, 0x4D, 0x58, 0xFE, 0x01, 0x0C, + 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x62, 0x02, 0x59, 0x59, 0x02, 0x01, 0x5A, + 0x5B, 0x02, 0x01, 0x08, 0x04, 0xFB, 0x01, 0x01, 0x53, 0x53, 0x01, 0x54, 0x54, 0x06, 0x7A, 0x79, + 0x88, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, + 0x00, 0x1B, 0x00, 0x00, 0x00, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, + 0x01, 0x15, 0x33, 0x35, 0x13, 0x23, 0x07, 0x0E, 0x01, 0x2F, 0x01, 0x23, 0x22, 0x16, 0x1F, 0x01, + 0x01, 0x7A, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x01, 0x36, 0x68, + 0xBE, 0x77, 0x3B, 0x3C, 0x02, 0x3D, 0x3D, 0x3D, 0x3D, 0x01, 0x5F, 0x5E, 0x03, 0x70, 0x84, 0xE2, + 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFD, 0xF9, 0x6D, 0xDA, 0x01, 0x2D, 0x65, + 0x66, 0x03, 0x67, 0x67, 0x01, 0x95, 0x96, 0x00, 0x00, 0x02, 0x00, 0x14, 0xFF, 0xBF, 0x03, 0xEC, + 0x03, 0x4A, 0x00, 0x05, 0x00, 0x0B, 0x00, 0x00, 0x05, 0x21, 0x11, 0x10, 0x05, 0x21, 0x01, 0x21, + 0x35, 0x21, 0x11, 0x23, 0x03, 0xEC, 0xFC, 0x28, 0x01, 0x14, 0x02, 0xC4, 0xFD, 0x5C, 0x01, 0x70, + 0xFE, 0xF8, 0x68, 0x41, 0x02, 0x77, 0x01, 0x14, 0x01, 0xFD, 0x38, 0x57, 0x01, 0xB0, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x14, 0xFF, 0xBF, 0x03, 0xEC, 0x03, 0x49, 0x00, 0x05, 0x00, 0x20, 0x00, 0x2B, + 0x00, 0x00, 0x17, 0x11, 0x21, 0x20, 0x19, 0x01, 0x25, 0x33, 0x35, 0x17, 0x1E, 0x01, 0x1F, 0x01, + 0x33, 0x37, 0x2E, 0x02, 0x27, 0x34, 0x37, 0x36, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26, 0x27, 0x26, + 0x2B, 0x01, 0x05, 0x06, 0x2B, 0x01, 0x35, 0x33, 0x16, 0x17, 0x16, 0x15, 0x14, 0x14, 0x02, 0xC4, + 0x01, 0x14, 0xFD, 0x2A, 0x69, 0x19, 0x2E, 0x28, 0x56, 0x2A, 0x3D, 0x3D, 0x01, 0x65, 0x2C, 0x20, + 0x0D, 0x66, 0x13, 0x06, 0x04, 0x09, 0x34, 0x20, 0x49, 0x15, 0x76, 0x77, 0x01, 0x02, 0x0C, 0x47, + 0x46, 0x4F, 0x56, 0x10, 0x20, 0x41, 0x03, 0x8A, 0xFE, 0xED, 0xFD, 0x89, 0xC2, 0xDA, 0x01, 0x01, + 0x1A, 0x81, 0x3D, 0x01, 0x01, 0xA3, 0x2C, 0x13, 0x01, 0x02, 0x13, 0x5A, 0x1A, 0x1C, 0x44, 0x21, + 0x13, 0x04, 0x01, 0xDA, 0x02, 0x85, 0x01, 0x08, 0x0F, 0x29, 0x3A, 0x00, 0x00, 0x03, 0x00, 0x14, + 0xFF, 0xFB, 0x03, 0xEC, 0x03, 0x0E, 0x00, 0x08, 0x00, 0x15, 0x00, 0x1B, 0x00, 0x00, 0x05, 0x21, + 0x11, 0x10, 0x21, 0x30, 0x21, 0x32, 0x15, 0x01, 0x21, 0x35, 0x23, 0x13, 0x35, 0x21, 0x15, 0x33, + 0x32, 0x22, 0x0F, 0x01, 0x05, 0x21, 0x35, 0x23, 0x11, 0x23, 0x03, 0xEC, 0xFC, 0x28, 0x01, 0x8A, + 0x01, 0xEC, 0x62, 0xFC, 0xCF, 0x01, 0x40, 0xE1, 0xD9, 0xFE, 0xDF, 0x5D, 0x5C, 0x01, 0x67, 0x68, + 0x01, 0x75, 0x01, 0x15, 0xC6, 0x4F, 0x05, 0x01, 0x89, 0x01, 0x8A, 0x63, 0xFD, 0xE1, 0x42, 0x01, + 0x0B, 0x3D, 0x42, 0x80, 0x80, 0x48, 0x42, 0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x14, + 0xFF, 0xFB, 0x03, 0xEC, 0x03, 0x0E, 0x00, 0x07, 0x00, 0x22, 0x00, 0x2F, 0x00, 0x3C, 0x00, 0x00, + 0x17, 0x11, 0x34, 0x37, 0x21, 0x20, 0x19, 0x01, 0x01, 0x15, 0x33, 0x35, 0x17, 0x1E, 0x01, 0x1F, + 0x02, 0x32, 0x35, 0x26, 0x27, 0x26, 0x27, 0x26, 0x37, 0x36, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26, + 0x23, 0x27, 0x17, 0x30, 0x23, 0x35, 0x33, 0x32, 0x17, 0x16, 0x17, 0x14, 0x07, 0x0E, 0x01, 0x05, + 0x21, 0x35, 0x27, 0x13, 0x35, 0x21, 0x15, 0x33, 0x32, 0x14, 0x0F, 0x01, 0x14, 0x62, 0x01, 0xEC, + 0x01, 0x8A, 0xFE, 0x1E, 0x4E, 0x14, 0x29, 0x1E, 0x37, 0x22, 0x2F, 0x2F, 0x06, 0x3A, 0x1D, 0x1F, + 0x09, 0x09, 0x4E, 0x0E, 0x04, 0x05, 0x0F, 0x47, 0x15, 0x6F, 0x65, 0x82, 0x34, 0x37, 0x38, 0x07, + 0x23, 0x09, 0x13, 0x0D, 0x1A, 0xFD, 0xD6, 0x01, 0x40, 0xE1, 0xD8, 0xFE, 0xE0, 0x5C, 0x5C, 0x67, + 0x68, 0x05, 0x02, 0xB0, 0x62, 0x01, 0xFE, 0x76, 0xFE, 0x77, 0x01, 0x56, 0xC5, 0xA5, 0x01, 0x01, + 0x1C, 0x52, 0x34, 0x01, 0x01, 0x0E, 0x58, 0x2C, 0x13, 0x06, 0x04, 0x0F, 0x45, 0x1E, 0x14, 0x42, + 0x0D, 0x04, 0x01, 0xA7, 0x65, 0x01, 0x04, 0x2C, 0x21, 0x09, 0x07, 0x03, 0xE3, 0x41, 0x01, 0x01, + 0x0B, 0x3D, 0x42, 0x01, 0x80, 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x14, 0x00, 0x5D, 0x03, 0xEC, + 0x02, 0xAB, 0x00, 0x08, 0x00, 0x37, 0x00, 0x3D, 0x00, 0x00, 0x13, 0x30, 0x21, 0x11, 0x21, 0x22, + 0x3D, 0x01, 0x34, 0x05, 0x37, 0x34, 0x27, 0x26, 0x27, 0x26, 0x07, 0x06, 0x07, 0x0E, 0x01, 0x17, + 0x1E, 0x01, 0x17, 0x16, 0x14, 0x07, 0x06, 0x26, 0x27, 0x26, 0x27, 0x22, 0x06, 0x07, 0x22, 0x17, + 0x1E, 0x01, 0x17, 0x16, 0x37, 0x36, 0x27, 0x26, 0x27, 0x2E, 0x02, 0x37, 0x36, 0x33, 0x32, 0x1F, + 0x02, 0x33, 0x35, 0x23, 0x11, 0x23, 0xD6, 0x03, 0x16, 0xFC, 0xEA, 0xC2, 0x01, 0xC6, 0x02, 0x01, + 0x0C, 0x3A, 0x2B, 0x2D, 0x13, 0x10, 0x2B, 0x01, 0x33, 0x17, 0x55, 0x15, 0x04, 0x09, 0x14, 0x58, + 0x0C, 0x04, 0x02, 0x02, 0x26, 0x14, 0x01, 0x03, 0x08, 0x33, 0x38, 0x5F, 0x20, 0x10, 0x01, 0x03, + 0x3C, 0x12, 0x59, 0x11, 0x01, 0x02, 0x39, 0x2C, 0x09, 0x02, 0x9D, 0xE2, 0xA2, 0x40, 0x02, 0xAB, + 0xFD, 0xB2, 0xD2, 0xAA, 0xD2, 0xDC, 0x03, 0x07, 0x0B, 0x38, 0x10, 0x0C, 0x09, 0x04, 0x08, 0x19, + 0x6C, 0x17, 0x0B, 0x17, 0x11, 0x07, 0x17, 0x0A, 0x1A, 0x0A, 0x29, 0x0C, 0x04, 0x04, 0x02, 0x10, + 0x25, 0x37, 0x04, 0x06, 0x37, 0x1D, 0x1C, 0x3F, 0x19, 0x08, 0x16, 0x13, 0x0B, 0x1F, 0x2B, 0x04, + 0xE9, 0x37, 0x01, 0x13, 0x00, 0x04, 0x00, 0x14, 0x00, 0x5D, 0x03, 0xEC, 0x02, 0xAB, 0x00, 0x07, + 0x00, 0x1F, 0x00, 0x2A, 0x00, 0x58, 0x00, 0x00, 0x01, 0x32, 0x1D, 0x01, 0x14, 0x23, 0x21, 0x11, + 0x01, 0x33, 0x35, 0x17, 0x1E, 0x03, 0x3B, 0x01, 0x27, 0x2E, 0x01, 0x2F, 0x01, 0x36, 0x37, 0x36, + 0x27, 0x26, 0x27, 0x26, 0x2B, 0x01, 0x17, 0x30, 0x23, 0x35, 0x33, 0x32, 0x16, 0x17, 0x16, 0x07, + 0x06, 0x05, 0x16, 0x37, 0x36, 0x37, 0x3E, 0x01, 0x27, 0x2E, 0x03, 0x3E, 0x01, 0x17, 0x16, 0x17, + 0x30, 0x37, 0x36, 0x27, 0x26, 0x27, 0x26, 0x27, 0x22, 0x06, 0x07, 0x06, 0x1E, 0x03, 0x17, 0x16, + 0x07, 0x06, 0x26, 0x27, 0x26, 0x27, 0x07, 0x06, 0x23, 0x07, 0x16, 0x03, 0x2A, 0xC2, 0xC2, 0xFC, + 0xEA, 0x01, 0xEC, 0x41, 0x11, 0x1F, 0x17, 0x4D, 0x02, 0x27, 0x26, 0x16, 0x1E, 0x1C, 0x17, 0x04, + 0x43, 0x0C, 0x0B, 0x21, 0x18, 0x3E, 0x0F, 0x46, 0x47, 0x66, 0x25, 0x29, 0x3E, 0x1B, 0x03, 0x08, + 0x22, 0x0C, 0xFE, 0x4D, 0x22, 0x59, 0x34, 0x1E, 0x2B, 0x03, 0x33, 0x16, 0x5C, 0x16, 0x0C, 0x18, + 0x3C, 0x16, 0x0B, 0x05, 0x22, 0x21, 0x01, 0x03, 0x10, 0x1F, 0x49, 0x36, 0x43, 0x02, 0x01, 0x1C, + 0x2D, 0x56, 0x1B, 0x04, 0x07, 0x20, 0x13, 0x4B, 0x0D, 0x01, 0x04, 0x1D, 0x1E, 0x02, 0x02, 0x04, + 0x02, 0xAB, 0xD2, 0xAA, 0xD2, 0x02, 0x4E, 0xFE, 0x39, 0x89, 0x01, 0x01, 0x11, 0x75, 0x01, 0x25, + 0x2F, 0x27, 0x0F, 0x08, 0x0C, 0x38, 0x33, 0x21, 0x19, 0x02, 0x01, 0x8A, 0x53, 0x0D, 0x0F, 0x2A, + 0x09, 0x04, 0x8A, 0x3A, 0x03, 0x01, 0x12, 0x1B, 0x71, 0x1B, 0x0C, 0x17, 0x0D, 0x18, 0x17, 0x09, + 0x11, 0x09, 0x1A, 0x01, 0x01, 0x07, 0x1E, 0x15, 0x29, 0x01, 0x2D, 0x2D, 0x1A, 0x2C, 0x16, 0x16, + 0x0D, 0x0F, 0x1A, 0x14, 0x0C, 0x0D, 0x27, 0x04, 0x0C, 0x03, 0x03, 0x04, 0x1E, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x17, 0x00, 0x00, + 0x00, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13, 0x15, 0x33, 0x15, + 0x33, 0x35, 0x33, 0x35, 0x23, 0x35, 0x23, 0x15, 0x01, 0x7A, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, + 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0xC4, 0x50, 0xC4, 0xC5, 0x4E, 0x03, 0x70, 0x84, 0xE2, 0xFE, + 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE, 0xC0, 0x4F, 0xC5, 0xC5, 0x4E, 0xC5, 0xC4, + 0x00, 0x02, 0x00, 0x14, 0xFF, 0x98, 0x03, 0xEC, 0x03, 0x70, 0x00, 0x0B, 0x00, 0x0F, 0x00, 0x00, + 0x00, 0x20, 0x1E, 0x01, 0x10, 0x0E, 0x01, 0x20, 0x2E, 0x01, 0x10, 0x36, 0x13, 0x21, 0x35, 0x21, + 0x01, 0x7A, 0x01, 0x0C, 0xE2, 0x84, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0x7C, 0x01, 0xD8, + 0xFE, 0x28, 0x03, 0x70, 0x84, 0xE2, 0xFE, 0xF4, 0xE2, 0x84, 0x84, 0xE2, 0x01, 0x0C, 0xE2, 0xFE, + 0x71, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0xAE, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x15, 0x00, 0x2C, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, + 0x00, 0x64, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x85, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0xAF, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x10, 0x00, 0xE2, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x0D, + 0x01, 0x0F, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x10, 0x01, 0x3F, 0x00, 0x03, + 0x00, 0x01, 0x04, 0x09, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, + 0x00, 0x01, 0x00, 0x20, 0x00, 0x42, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x02, 0x00, 0x0E, + 0x00, 0x75, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x03, 0x00, 0x20, 0x00, 0x8D, 0x00, 0x03, + 0x00, 0x01, 0x04, 0x09, 0x00, 0x04, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, + 0x00, 0x05, 0x00, 0x1A, 0x00, 0xF3, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x06, 0x00, 0x20, + 0x01, 0x1D, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x20, 0x00, 0x45, 0x00, 0x6D, + 0x00, 0x75, 0x00, 0x6C, 0x00, 0x61, 0x00, 0x74, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x20, 0x00, 0x50, + 0x00, 0x72, 0x00, 0x6F, 0x00, 0x6A, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x00, 0x59, 0x75, + 0x7A, 0x75, 0x20, 0x45, 0x6D, 0x75, 0x6C, 0x61, 0x74, 0x6F, 0x72, 0x20, 0x50, 0x72, 0x6F, 0x6A, + 0x65, 0x63, 0x74, 0x00, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53, + 0x00, 0x53, 0x00, 0x45, 0x00, 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69, + 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74, + 0x65, 0x6E, 0x73, 0x69, 0x6F, 0x6E, 0x00, 0x00, 0x52, 0x00, 0x65, 0x00, 0x67, 0x00, 0x75, 0x00, + 0x6C, 0x00, 0x61, 0x00, 0x72, 0x00, 0x00, 0x52, 0x65, 0x67, 0x75, 0x6C, 0x61, 0x72, 0x00, 0x00, + 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, + 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00, + 0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6E, 0x73, 0x69, 0x6F, + 0x6E, 0x00, 0x00, 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53, 0x00, 0x53, + 0x00, 0x45, 0x00, 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F, + 0x00, 0x6E, 0x00, 0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6E, + 0x73, 0x69, 0x6F, 0x6E, 0x00, 0x00, 0x56, 0x00, 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x69, 0x00, + 0x6F, 0x00, 0x6E, 0x00, 0x20, 0x00, 0x31, 0x00, 0x2E, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, + 0x00, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x31, 0x2E, 0x30, 0x30, 0x30, 0x00, 0x00, + 0x59, 0x00, 0x75, 0x00, 0x7A, 0x00, 0x75, 0x00, 0x4F, 0x00, 0x53, 0x00, 0x53, 0x00, 0x45, 0x00, + 0x78, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x73, 0x00, 0x69, 0x00, 0x6F, 0x00, 0x6E, 0x00, + 0x00, 0x59, 0x75, 0x7A, 0x75, 0x4F, 0x53, 0x53, 0x45, 0x78, 0x74, 0x65, 0x6E, 0x73, 0x69, 0x6F, + 0x6E, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xB5, 0x00, 0x32, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x01, 0x02, 0x01, 0x03, 0x00, 0x03, 0x01, 0x04, + 0x01, 0x05, 0x01, 0x06, 0x01, 0x07, 0x01, 0x08, 0x01, 0x09, 0x01, 0x0A, 0x01, 0x0B, 0x01, 0x0C, + 0x01, 0x0D, 0x01, 0x0E, 0x01, 0x0F, 0x01, 0x10, 0x01, 0x11, 0x01, 0x12, 0x01, 0x13, 0x01, 0x14, + 0x01, 0x15, 0x01, 0x16, 0x01, 0x17, 0x01, 0x18, 0x01, 0x19, 0x01, 0x1A, 0x01, 0x1B, 0x07, 0x75, + 0x6E, 0x69, 0x30, 0x30, 0x30, 0x30, 0x07, 0x75, 0x6E, 0x69, 0x30, 0x30, 0x30, 0x44, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x41, 0x30, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x31, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x41, 0x32, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x33, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x41, 0x34, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x35, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x41, 0x36, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x37, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x41, 0x38, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x41, 0x39, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x42, 0x33, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x42, 0x34, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x45, 0x30, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x31, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x45, 0x32, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x33, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x45, 0x34, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x35, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x45, 0x36, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x37, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x45, 0x38, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x45, 0x39, 0x07, 0x75, + 0x6E, 0x69, 0x45, 0x30, 0x45, 0x46, 0x07, 0x75, 0x6E, 0x69, 0x45, 0x30, 0x46, 0x30, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x0F, }}; } // namespace FileSys::SystemArchive::SharedFontData diff --git a/src/core/file_sys/system_archive/data/font_nintendo_extended.h b/src/core/file_sys/system_archive/data/font_nintendo_extended.h index 2089f3db9..edb9df914 100644 --- a/src/core/file_sys/system_archive/data/font_nintendo_extended.h +++ b/src/core/file_sys/system_archive/data/font_nintendo_extended.h @@ -8,6 +8,6 @@ namespace FileSys::SystemArchive::SharedFontData { -extern const std::array FONT_NINTENDO_EXTENDED; +extern const std::array FONT_NINTENDO_EXTENDED; } // namespace FileSys::SystemArchive::SharedFontData diff --git a/src/core/file_sys/system_archive/system_version.cpp b/src/core/file_sys/system_archive/system_version.cpp index aa313de66..7bfbc9a67 100644 --- a/src/core/file_sys/system_archive/system_version.cpp +++ b/src/core/file_sys/system_archive/system_version.cpp @@ -12,17 +12,17 @@ namespace SystemVersionData { // This section should reflect the best system version to describe yuzu's HLE api. // TODO(DarkLordZach): Update when HLE gets better. -constexpr u8 VERSION_MAJOR = 10; +constexpr u8 VERSION_MAJOR = 11; constexpr u8 VERSION_MINOR = 0; -constexpr u8 VERSION_MICRO = 2; +constexpr u8 VERSION_MICRO = 0; -constexpr u8 REVISION_MAJOR = 1; +constexpr u8 REVISION_MAJOR = 5; constexpr u8 REVISION_MINOR = 0; constexpr char PLATFORM_STRING[] = "NX"; -constexpr char VERSION_HASH[] = "f90143fa8bbc061d4f68c35f95f04f8080c0ecdc"; -constexpr char DISPLAY_VERSION[] = "10.0.2"; -constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 10.0.2-1.0"; +constexpr char VERSION_HASH[] = "34197eba8810e2edd5e9dfcfbde7b340882e856d"; +constexpr char DISPLAY_VERSION[] = "11.0.0"; +constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 11.0.0-5.0"; } // namespace SystemVersionData diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index b2f026b6d..f497e9396 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp @@ -203,7 +203,7 @@ std::string VfsFile::GetFullPath() const { return GetContainingDirectory()->GetFullPath() + "/" + GetName(); } -std::shared_ptr VfsDirectory::GetFileRelative(std::string_view path) const { +VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const { auto vec = Common::FS::SplitPathComponents(path); vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), vec.end()); @@ -231,7 +231,7 @@ std::shared_ptr VfsDirectory::GetFileRelative(std::string_view path) co return dir->GetFile(vec.back()); } -std::shared_ptr VfsDirectory::GetFileAbsolute(std::string_view path) const { +VirtualFile VfsDirectory::GetFileAbsolute(std::string_view path) const { if (IsRoot()) { return GetFileRelative(path); } @@ -239,7 +239,7 @@ std::shared_ptr VfsDirectory::GetFileAbsolute(std::string_view path) co return GetParentDirectory()->GetFileAbsolute(path); } -std::shared_ptr VfsDirectory::GetDirectoryRelative(std::string_view path) const { +VirtualDir VfsDirectory::GetDirectoryRelative(std::string_view path) const { auto vec = Common::FS::SplitPathComponents(path); vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), vec.end()); @@ -261,7 +261,7 @@ std::shared_ptr VfsDirectory::GetDirectoryRelative(std::string_vie return dir; } -std::shared_ptr VfsDirectory::GetDirectoryAbsolute(std::string_view path) const { +VirtualDir VfsDirectory::GetDirectoryAbsolute(std::string_view path) const { if (IsRoot()) { return GetDirectoryRelative(path); } @@ -269,14 +269,14 @@ std::shared_ptr VfsDirectory::GetDirectoryAbsolute(std::string_vie return GetParentDirectory()->GetDirectoryAbsolute(path); } -std::shared_ptr VfsDirectory::GetFile(std::string_view name) const { +VirtualFile VfsDirectory::GetFile(std::string_view name) const { const auto& files = GetFiles(); const auto iter = std::find_if(files.begin(), files.end(), [&name](const auto& file1) { return name == file1->GetName(); }); return iter == files.end() ? nullptr : *iter; } -std::shared_ptr VfsDirectory::GetSubdirectory(std::string_view name) const { +VirtualDir VfsDirectory::GetSubdirectory(std::string_view name) const { const auto& subs = GetSubdirectories(); const auto iter = std::find_if(subs.begin(), subs.end(), [&name](const auto& file1) { return name == file1->GetName(); }); @@ -301,7 +301,7 @@ std::size_t VfsDirectory::GetSize() const { return file_total + subdir_total; } -std::shared_ptr VfsDirectory::CreateFileRelative(std::string_view path) { +VirtualFile VfsDirectory::CreateFileRelative(std::string_view path) { auto vec = Common::FS::SplitPathComponents(path); vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), vec.end()); @@ -324,7 +324,7 @@ std::shared_ptr VfsDirectory::CreateFileRelative(std::string_view path) return dir->CreateFileRelative(Common::FS::GetPathWithoutTop(path)); } -std::shared_ptr VfsDirectory::CreateFileAbsolute(std::string_view path) { +VirtualFile VfsDirectory::CreateFileAbsolute(std::string_view path) { if (IsRoot()) { return CreateFileRelative(path); } @@ -332,7 +332,7 @@ std::shared_ptr VfsDirectory::CreateFileAbsolute(std::string_view path) return GetParentDirectory()->CreateFileAbsolute(path); } -std::shared_ptr VfsDirectory::CreateDirectoryRelative(std::string_view path) { +VirtualDir VfsDirectory::CreateDirectoryRelative(std::string_view path) { auto vec = Common::FS::SplitPathComponents(path); vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), vec.end()); @@ -355,7 +355,7 @@ std::shared_ptr VfsDirectory::CreateDirectoryRelative(std::string_ return dir->CreateDirectoryRelative(Common::FS::GetPathWithoutTop(path)); } -std::shared_ptr VfsDirectory::CreateDirectoryAbsolute(std::string_view path) { +VirtualDir VfsDirectory::CreateDirectoryAbsolute(std::string_view path) { if (IsRoot()) { return CreateDirectoryRelative(path); } @@ -446,27 +446,27 @@ bool ReadOnlyVfsDirectory::IsReadable() const { return true; } -std::shared_ptr ReadOnlyVfsDirectory::CreateSubdirectory(std::string_view name) { +VirtualDir ReadOnlyVfsDirectory::CreateSubdirectory(std::string_view name) { return nullptr; } -std::shared_ptr ReadOnlyVfsDirectory::CreateFile(std::string_view name) { +VirtualFile ReadOnlyVfsDirectory::CreateFile(std::string_view name) { return nullptr; } -std::shared_ptr ReadOnlyVfsDirectory::CreateFileAbsolute(std::string_view path) { +VirtualFile ReadOnlyVfsDirectory::CreateFileAbsolute(std::string_view path) { return nullptr; } -std::shared_ptr ReadOnlyVfsDirectory::CreateFileRelative(std::string_view path) { +VirtualFile ReadOnlyVfsDirectory::CreateFileRelative(std::string_view path) { return nullptr; } -std::shared_ptr ReadOnlyVfsDirectory::CreateDirectoryAbsolute(std::string_view path) { +VirtualDir ReadOnlyVfsDirectory::CreateDirectoryAbsolute(std::string_view path) { return nullptr; } -std::shared_ptr ReadOnlyVfsDirectory::CreateDirectoryRelative(std::string_view path) { +VirtualDir ReadOnlyVfsDirectory::CreateDirectoryRelative(std::string_view path) { return nullptr; } diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h index 954094772..afd64e95c 100644 --- a/src/core/file_sys/vfs.h +++ b/src/core/file_sys/vfs.h @@ -91,7 +91,7 @@ public: // Resizes the file to new_size. Returns whether or not the operation was successful. virtual bool Resize(std::size_t new_size) = 0; // Gets a pointer to the directory containing this file, returning nullptr if there is none. - virtual std::shared_ptr GetContainingDirectory() const = 0; + virtual VirtualDir GetContainingDirectory() const = 0; // Returns whether or not the file can be written to. virtual bool IsWritable() const = 0; @@ -183,27 +183,27 @@ public: // Retrives the file located at path as if the current directory was root. Returns nullptr if // not found. - virtual std::shared_ptr GetFileRelative(std::string_view path) const; + virtual VirtualFile GetFileRelative(std::string_view path) const; // Calls GetFileRelative(path) on the root of the current directory. - virtual std::shared_ptr GetFileAbsolute(std::string_view path) const; + virtual VirtualFile GetFileAbsolute(std::string_view path) const; // Retrives the directory located at path as if the current directory was root. Returns nullptr // if not found. - virtual std::shared_ptr GetDirectoryRelative(std::string_view path) const; + virtual VirtualDir GetDirectoryRelative(std::string_view path) const; // Calls GetDirectoryRelative(path) on the root of the current directory. - virtual std::shared_ptr GetDirectoryAbsolute(std::string_view path) const; + virtual VirtualDir GetDirectoryAbsolute(std::string_view path) const; // Returns a vector containing all of the files in this directory. - virtual std::vector> GetFiles() const = 0; + virtual std::vector GetFiles() const = 0; // Returns the file with filename matching name. Returns nullptr if directory dosen't have a // file with name. - virtual std::shared_ptr GetFile(std::string_view name) const; + virtual VirtualFile GetFile(std::string_view name) const; // Returns a vector containing all of the subdirectories in this directory. - virtual std::vector> GetSubdirectories() const = 0; + virtual std::vector GetSubdirectories() const = 0; // Returns the directory with name matching name. Returns nullptr if directory dosen't have a // directory with name. - virtual std::shared_ptr GetSubdirectory(std::string_view name) const; + virtual VirtualDir GetSubdirectory(std::string_view name) const; // Returns whether or not the directory can be written to. virtual bool IsWritable() const = 0; @@ -219,31 +219,31 @@ public: virtual std::size_t GetSize() const; // Returns the parent directory of this directory. Returns nullptr if this directory is root or // has no parent. - virtual std::shared_ptr GetParentDirectory() const = 0; + virtual VirtualDir GetParentDirectory() const = 0; // Creates a new subdirectory with name name. Returns a pointer to the new directory or nullptr // if the operation failed. - virtual std::shared_ptr CreateSubdirectory(std::string_view name) = 0; + virtual VirtualDir CreateSubdirectory(std::string_view name) = 0; // Creates a new file with name name. Returns a pointer to the new file or nullptr if the // operation failed. - virtual std::shared_ptr CreateFile(std::string_view name) = 0; + virtual VirtualFile CreateFile(std::string_view name) = 0; // Creates a new file at the path relative to this directory. Also creates directories if // they do not exist and is supported by this implementation. Returns nullptr on any failure. - virtual std::shared_ptr CreateFileRelative(std::string_view path); + virtual VirtualFile CreateFileRelative(std::string_view path); // Creates a new file at the path relative to root of this directory. Also creates directories // if they do not exist and is supported by this implementation. Returns nullptr on any failure. - virtual std::shared_ptr CreateFileAbsolute(std::string_view path); + virtual VirtualFile CreateFileAbsolute(std::string_view path); // Creates a new directory at the path relative to this directory. Also creates directories if // they do not exist and is supported by this implementation. Returns nullptr on any failure. - virtual std::shared_ptr CreateDirectoryRelative(std::string_view path); + virtual VirtualDir CreateDirectoryRelative(std::string_view path); // Creates a new directory at the path relative to root of this directory. Also creates // directories if they do not exist and is supported by this implementation. Returns nullptr on // any failure. - virtual std::shared_ptr CreateDirectoryAbsolute(std::string_view path); + virtual VirtualDir CreateDirectoryAbsolute(std::string_view path); // Deletes the subdirectory with the given name and returns true on success. virtual bool DeleteSubdirectory(std::string_view name) = 0; @@ -280,12 +280,12 @@ class ReadOnlyVfsDirectory : public VfsDirectory { public: bool IsWritable() const override; bool IsReadable() const override; - std::shared_ptr CreateSubdirectory(std::string_view name) override; - std::shared_ptr CreateFile(std::string_view name) override; - std::shared_ptr CreateFileAbsolute(std::string_view path) override; - std::shared_ptr CreateFileRelative(std::string_view path) override; - std::shared_ptr CreateDirectoryAbsolute(std::string_view path) override; - std::shared_ptr CreateDirectoryRelative(std::string_view path) override; + VirtualDir CreateSubdirectory(std::string_view name) override; + VirtualFile CreateFile(std::string_view name) override; + VirtualFile CreateFileAbsolute(std::string_view path) override; + VirtualFile CreateFileRelative(std::string_view path) override; + VirtualDir CreateDirectoryAbsolute(std::string_view path) override; + VirtualDir CreateDirectoryRelative(std::string_view path) override; bool DeleteSubdirectory(std::string_view name) override; bool DeleteSubdirectoryRecursive(std::string_view name) override; bool CleanSubdirectoryRecursive(std::string_view name) override; diff --git a/src/core/file_sys/vfs_concat.cpp b/src/core/file_sys/vfs_concat.cpp index e0ff70174..3c5a7d87a 100644 --- a/src/core/file_sys/vfs_concat.cpp +++ b/src/core/file_sys/vfs_concat.cpp @@ -46,7 +46,7 @@ VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::vector f if (files.size() == 1) return files[0]; - return std::shared_ptr(new ConcatenatedVfsFile(std::move(files), std::move(name))); + return VirtualFile(new ConcatenatedVfsFile(std::move(files), std::move(name))); } VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte, @@ -71,20 +71,23 @@ VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte, if (files.begin()->first != 0) files.emplace(0, std::make_shared(filler_byte, files.begin()->first)); - return std::shared_ptr(new ConcatenatedVfsFile(std::move(files), std::move(name))); + return VirtualFile(new ConcatenatedVfsFile(std::move(files), std::move(name))); } std::string ConcatenatedVfsFile::GetName() const { - if (files.empty()) + if (files.empty()) { return ""; - if (!name.empty()) + } + if (!name.empty()) { return name; + } return files.begin()->second->GetName(); } std::size_t ConcatenatedVfsFile::GetSize() const { - if (files.empty()) + if (files.empty()) { return 0; + } return files.rbegin()->first + files.rbegin()->second->GetSize(); } @@ -92,9 +95,10 @@ bool ConcatenatedVfsFile::Resize(std::size_t new_size) { return false; } -std::shared_ptr ConcatenatedVfsFile::GetContainingDirectory() const { - if (files.empty()) +VirtualDir ConcatenatedVfsFile::GetContainingDirectory() const { + if (files.empty()) { return nullptr; + } return files.begin()->second->GetContainingDirectory(); } diff --git a/src/core/file_sys/vfs_concat.h b/src/core/file_sys/vfs_concat.h index 7a26343c0..287c72555 100644 --- a/src/core/file_sys/vfs_concat.h +++ b/src/core/file_sys/vfs_concat.h @@ -31,7 +31,7 @@ public: std::string GetName() const override; std::size_t GetSize() const override; bool Resize(std::size_t new_size) override; - std::shared_ptr GetContainingDirectory() const override; + VirtualDir GetContainingDirectory() const override; bool IsWritable() const override; bool IsReadable() const override; std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; diff --git a/src/core/file_sys/vfs_layered.cpp b/src/core/file_sys/vfs_layered.cpp index 338e398da..434b03cec 100644 --- a/src/core/file_sys/vfs_layered.cpp +++ b/src/core/file_sys/vfs_layered.cpp @@ -20,10 +20,10 @@ VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector dir if (dirs.size() == 1) return dirs[0]; - return std::shared_ptr(new LayeredVfsDirectory(std::move(dirs), std::move(name))); + return VirtualDir(new LayeredVfsDirectory(std::move(dirs), std::move(name))); } -std::shared_ptr LayeredVfsDirectory::GetFileRelative(std::string_view path) const { +VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path) const { for (const auto& layer : dirs) { const auto file = layer->GetFileRelative(path); if (file != nullptr) @@ -33,23 +33,23 @@ std::shared_ptr LayeredVfsDirectory::GetFileRelative(std::string_view p return nullptr; } -std::shared_ptr LayeredVfsDirectory::GetDirectoryRelative( - std::string_view path) const { +VirtualDir LayeredVfsDirectory::GetDirectoryRelative(std::string_view path) const { std::vector out; for (const auto& layer : dirs) { auto dir = layer->GetDirectoryRelative(path); - if (dir != nullptr) + if (dir != nullptr) { out.push_back(std::move(dir)); + } } return MakeLayeredDirectory(std::move(out)); } -std::shared_ptr LayeredVfsDirectory::GetFile(std::string_view name) const { +VirtualFile LayeredVfsDirectory::GetFile(std::string_view name) const { return GetFileRelative(name); } -std::shared_ptr LayeredVfsDirectory::GetSubdirectory(std::string_view name) const { +VirtualDir LayeredVfsDirectory::GetSubdirectory(std::string_view name) const { return GetDirectoryRelative(name); } @@ -57,7 +57,7 @@ std::string LayeredVfsDirectory::GetFullPath() const { return dirs[0]->GetFullPath(); } -std::vector> LayeredVfsDirectory::GetFiles() const { +std::vector LayeredVfsDirectory::GetFiles() const { std::vector out; for (const auto& layer : dirs) { for (const auto& file : layer->GetFiles()) { @@ -72,7 +72,7 @@ std::vector> LayeredVfsDirectory::GetFiles() const { return out; } -std::vector> LayeredVfsDirectory::GetSubdirectories() const { +std::vector LayeredVfsDirectory::GetSubdirectories() const { std::vector names; for (const auto& layer : dirs) { for (const auto& sd : layer->GetSubdirectories()) { @@ -101,15 +101,15 @@ std::string LayeredVfsDirectory::GetName() const { return name.empty() ? dirs[0]->GetName() : name; } -std::shared_ptr LayeredVfsDirectory::GetParentDirectory() const { +VirtualDir LayeredVfsDirectory::GetParentDirectory() const { return dirs[0]->GetParentDirectory(); } -std::shared_ptr LayeredVfsDirectory::CreateSubdirectory(std::string_view name) { +VirtualDir LayeredVfsDirectory::CreateSubdirectory(std::string_view name) { return nullptr; } -std::shared_ptr LayeredVfsDirectory::CreateFile(std::string_view name) { +VirtualFile LayeredVfsDirectory::CreateFile(std::string_view name) { return nullptr; } diff --git a/src/core/file_sys/vfs_layered.h b/src/core/file_sys/vfs_layered.h index 8a25c3428..6d7513ac6 100644 --- a/src/core/file_sys/vfs_layered.h +++ b/src/core/file_sys/vfs_layered.h @@ -21,20 +21,20 @@ public: /// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases. static VirtualDir MakeLayeredDirectory(std::vector dirs, std::string name = ""); - std::shared_ptr GetFileRelative(std::string_view path) const override; - std::shared_ptr GetDirectoryRelative(std::string_view path) const override; - std::shared_ptr GetFile(std::string_view name) const override; - std::shared_ptr GetSubdirectory(std::string_view name) const override; + VirtualFile GetFileRelative(std::string_view path) const override; + VirtualDir GetDirectoryRelative(std::string_view path) const override; + VirtualFile GetFile(std::string_view name) const override; + VirtualDir GetSubdirectory(std::string_view name) const override; std::string GetFullPath() const override; - std::vector> GetFiles() const override; - std::vector> GetSubdirectories() const override; + std::vector GetFiles() const override; + std::vector GetSubdirectories() const override; bool IsWritable() const override; bool IsReadable() const override; std::string GetName() const override; - std::shared_ptr GetParentDirectory() const override; - std::shared_ptr CreateSubdirectory(std::string_view name) override; - std::shared_ptr CreateFile(std::string_view name) override; + VirtualDir GetParentDirectory() const override; + VirtualDir CreateSubdirectory(std::string_view name) override; + VirtualFile CreateFile(std::string_view name) override; bool DeleteSubdirectory(std::string_view name) override; bool DeleteFile(std::string_view name) override; bool Rename(std::string_view name) override; diff --git a/src/core/file_sys/vfs_offset.cpp b/src/core/file_sys/vfs_offset.cpp index 7714d3de5..056737b54 100644 --- a/src/core/file_sys/vfs_offset.cpp +++ b/src/core/file_sys/vfs_offset.cpp @@ -9,7 +9,7 @@ namespace FileSys { -OffsetVfsFile::OffsetVfsFile(std::shared_ptr file_, std::size_t size_, std::size_t offset_, +OffsetVfsFile::OffsetVfsFile(VirtualFile file_, std::size_t size_, std::size_t offset_, std::string name_, VirtualDir parent_) : file(file_), offset(offset_), size(size_), name(std::move(name_)), parent(parent_ == nullptr ? file->GetContainingDirectory() : std::move(parent_)) {} @@ -37,7 +37,7 @@ bool OffsetVfsFile::Resize(std::size_t new_size) { return true; } -std::shared_ptr OffsetVfsFile::GetContainingDirectory() const { +VirtualDir OffsetVfsFile::GetContainingDirectory() const { return parent; } diff --git a/src/core/file_sys/vfs_offset.h b/src/core/file_sys/vfs_offset.h index f7b7a3256..b2ccc5c7b 100644 --- a/src/core/file_sys/vfs_offset.h +++ b/src/core/file_sys/vfs_offset.h @@ -17,14 +17,14 @@ namespace FileSys { // the size of this wrapper. class OffsetVfsFile : public VfsFile { public: - OffsetVfsFile(std::shared_ptr file, std::size_t size, std::size_t offset = 0, + OffsetVfsFile(VirtualFile file, std::size_t size, std::size_t offset = 0, std::string new_name = "", VirtualDir new_parent = nullptr); ~OffsetVfsFile() override; std::string GetName() const override; std::size_t GetSize() const override; bool Resize(std::size_t new_size) override; - std::shared_ptr GetContainingDirectory() const override; + VirtualDir GetContainingDirectory() const override; bool IsWritable() const override; bool IsReadable() const override; std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; @@ -42,7 +42,7 @@ public: private: std::size_t TrimToFit(std::size_t r_size, std::size_t r_offset) const; - std::shared_ptr file; + VirtualFile file; std::size_t offset; std::size_t size; std::string name; diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index 488687ba9..a287eebe3 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -263,7 +263,7 @@ bool RealVfsFile::Resize(std::size_t new_size) { return backing->Resize(new_size); } -std::shared_ptr RealVfsFile::GetContainingDirectory() const { +VirtualDir RealVfsFile::GetContainingDirectory() const { return base.OpenDirectory(parent_path, perms); } @@ -352,7 +352,7 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& RealVfsDirectory::~RealVfsDirectory() = default; -std::shared_ptr RealVfsDirectory::GetFileRelative(std::string_view path) const { +VirtualFile RealVfsDirectory::GetFileRelative(std::string_view path) const { const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path)); if (!FS::Exists(full_path) || FS::IsDirectory(full_path)) { return nullptr; @@ -360,7 +360,7 @@ std::shared_ptr RealVfsDirectory::GetFileRelative(std::string_view path return base.OpenFile(full_path, perms); } -std::shared_ptr RealVfsDirectory::GetDirectoryRelative(std::string_view path) const { +VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view path) const { const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path)); if (!FS::Exists(full_path) || !FS::IsDirectory(full_path)) { return nullptr; @@ -368,20 +368,20 @@ std::shared_ptr RealVfsDirectory::GetDirectoryRelative(std::string return base.OpenDirectory(full_path, perms); } -std::shared_ptr RealVfsDirectory::GetFile(std::string_view name) const { +VirtualFile RealVfsDirectory::GetFile(std::string_view name) const { return GetFileRelative(name); } -std::shared_ptr RealVfsDirectory::GetSubdirectory(std::string_view name) const { +VirtualDir RealVfsDirectory::GetSubdirectory(std::string_view name) const { return GetDirectoryRelative(name); } -std::shared_ptr RealVfsDirectory::CreateFileRelative(std::string_view path) { +VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view path) { const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path)); return base.CreateFile(full_path, perms); } -std::shared_ptr RealVfsDirectory::CreateDirectoryRelative(std::string_view path) { +VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view path) { const auto full_path = FS::SanitizePath(this->path + DIR_SEP + std::string(path)); return base.CreateDirectory(full_path, perms); } @@ -391,11 +391,11 @@ bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) { return base.DeleteDirectory(full_path); } -std::vector> RealVfsDirectory::GetFiles() const { +std::vector RealVfsDirectory::GetFiles() const { return IterateEntries(); } -std::vector> RealVfsDirectory::GetSubdirectories() const { +std::vector RealVfsDirectory::GetSubdirectories() const { return IterateEntries(); } @@ -411,7 +411,7 @@ std::string RealVfsDirectory::GetName() const { return path_components.back(); } -std::shared_ptr RealVfsDirectory::GetParentDirectory() const { +VirtualDir RealVfsDirectory::GetParentDirectory() const { if (path_components.size() <= 1) { return nullptr; } @@ -419,12 +419,12 @@ std::shared_ptr RealVfsDirectory::GetParentDirectory() const { return base.OpenDirectory(parent_path, perms); } -std::shared_ptr RealVfsDirectory::CreateSubdirectory(std::string_view name) { +VirtualDir RealVfsDirectory::CreateSubdirectory(std::string_view name) { const std::string subdir_path = (path + DIR_SEP).append(name); return base.CreateDirectory(subdir_path, perms); } -std::shared_ptr RealVfsDirectory::CreateFile(std::string_view name) { +VirtualFile RealVfsDirectory::CreateFile(std::string_view name) { const std::string file_path = (path + DIR_SEP).append(name); return base.CreateFile(file_path, perms); } diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index 0b537b22c..23e99865e 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h @@ -50,7 +50,7 @@ public: std::string GetName() const override; std::size_t GetSize() const override; bool Resize(std::size_t new_size) override; - std::shared_ptr GetContainingDirectory() const override; + VirtualDir GetContainingDirectory() const override; bool IsWritable() const override; bool IsReadable() const override; std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; @@ -79,21 +79,21 @@ class RealVfsDirectory : public VfsDirectory { public: ~RealVfsDirectory() override; - std::shared_ptr GetFileRelative(std::string_view path) const override; - std::shared_ptr GetDirectoryRelative(std::string_view path) const override; - std::shared_ptr GetFile(std::string_view name) const override; - std::shared_ptr GetSubdirectory(std::string_view name) const override; - std::shared_ptr CreateFileRelative(std::string_view path) override; - std::shared_ptr CreateDirectoryRelative(std::string_view path) override; + VirtualFile GetFileRelative(std::string_view path) const override; + VirtualDir GetDirectoryRelative(std::string_view path) const override; + VirtualFile GetFile(std::string_view name) const override; + VirtualDir GetSubdirectory(std::string_view name) const override; + VirtualFile CreateFileRelative(std::string_view path) override; + VirtualDir CreateDirectoryRelative(std::string_view path) override; bool DeleteSubdirectoryRecursive(std::string_view name) override; - std::vector> GetFiles() const override; - std::vector> GetSubdirectories() const override; + std::vector GetFiles() const override; + std::vector GetSubdirectories() const override; bool IsWritable() const override; bool IsReadable() const override; std::string GetName() const override; - std::shared_ptr GetParentDirectory() const override; - std::shared_ptr CreateSubdirectory(std::string_view name) override; - std::shared_ptr CreateFile(std::string_view name) override; + VirtualDir GetParentDirectory() const override; + VirtualDir CreateSubdirectory(std::string_view name) override; + VirtualFile CreateFile(std::string_view name) override; bool DeleteSubdirectory(std::string_view name) override; bool DeleteFile(std::string_view name) override; bool Rename(std::string_view name) override; diff --git a/src/core/file_sys/vfs_static.h b/src/core/file_sys/vfs_static.h index 8b27c30fa..c840b24b9 100644 --- a/src/core/file_sys/vfs_static.h +++ b/src/core/file_sys/vfs_static.h @@ -31,7 +31,7 @@ public: return true; } - std::shared_ptr GetContainingDirectory() const override { + VirtualDir GetContainingDirectory() const override { return parent; } diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp index 75fc04302..c1ec1e645 100644 --- a/src/core/file_sys/vfs_vector.cpp +++ b/src/core/file_sys/vfs_vector.cpp @@ -25,7 +25,7 @@ bool VectorVfsFile::Resize(size_t new_size) { return true; } -std::shared_ptr VectorVfsFile::GetContainingDirectory() const { +VirtualDir VectorVfsFile::GetContainingDirectory() const { return parent; } @@ -68,11 +68,11 @@ VectorVfsDirectory::VectorVfsDirectory(std::vector files_, VectorVfsDirectory::~VectorVfsDirectory() = default; -std::vector> VectorVfsDirectory::GetFiles() const { +std::vector VectorVfsDirectory::GetFiles() const { return files; } -std::vector> VectorVfsDirectory::GetSubdirectories() const { +std::vector VectorVfsDirectory::GetSubdirectories() const { return dirs; } @@ -88,7 +88,7 @@ std::string VectorVfsDirectory::GetName() const { return name; } -std::shared_ptr VectorVfsDirectory::GetParentDirectory() const { +VirtualDir VectorVfsDirectory::GetParentDirectory() const { return parent; } @@ -116,11 +116,11 @@ bool VectorVfsDirectory::Rename(std::string_view name_) { return true; } -std::shared_ptr VectorVfsDirectory::CreateSubdirectory(std::string_view name) { +VirtualDir VectorVfsDirectory::CreateSubdirectory(std::string_view name) { return nullptr; } -std::shared_ptr VectorVfsDirectory::CreateFile(std::string_view name) { +VirtualFile VectorVfsDirectory::CreateFile(std::string_view name) { return nullptr; } diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h index 95d3da2f2..2aff9ca34 100644 --- a/src/core/file_sys/vfs_vector.h +++ b/src/core/file_sys/vfs_vector.h @@ -17,9 +17,9 @@ namespace FileSys { template class ArrayVfsFile : public VfsFile { public: - explicit ArrayVfsFile(const std::array& data, std::string name = "", - VirtualDir parent = nullptr) - : data(data), name(std::move(name)), parent(std::move(parent)) {} + explicit ArrayVfsFile(const std::array& data_, std::string name_ = "", + VirtualDir parent_ = nullptr) + : data(data_), name(std::move(name_)), parent(std::move(parent_)) {} std::string GetName() const override { return name; @@ -33,7 +33,7 @@ public: return false; } - std::shared_ptr GetContainingDirectory() const override { + VirtualDir GetContainingDirectory() const override { return parent; } @@ -51,12 +51,12 @@ public: return read; } - std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override { + std::size_t Write(const u8* data_, std::size_t length, std::size_t offset) override { return 0; } - bool Rename(std::string_view name) override { - this->name = name; + bool Rename(std::string_view new_name) override { + name = new_name; return true; } @@ -82,7 +82,7 @@ public: std::string GetName() const override; std::size_t GetSize() const override; bool Resize(std::size_t new_size) override; - std::shared_ptr GetContainingDirectory() const override; + VirtualDir GetContainingDirectory() const override; bool IsWritable() const override; bool IsReadable() const override; std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; @@ -106,17 +106,17 @@ public: VirtualDir parent = nullptr); ~VectorVfsDirectory() override; - std::vector> GetFiles() const override; - std::vector> GetSubdirectories() const override; + std::vector GetFiles() const override; + std::vector GetSubdirectories() const override; bool IsWritable() const override; bool IsReadable() const override; std::string GetName() const override; - std::shared_ptr GetParentDirectory() const override; + VirtualDir GetParentDirectory() const override; bool DeleteSubdirectory(std::string_view name) override; bool DeleteFile(std::string_view name) override; bool Rename(std::string_view name) override; - std::shared_ptr CreateSubdirectory(std::string_view name) override; - std::shared_ptr CreateFile(std::string_view name) override; + VirtualDir CreateSubdirectory(std::string_view name) override; + VirtualFile CreateFile(std::string_view name) override; virtual void AddFile(VirtualFile file); virtual void AddDirectory(VirtualDir dir); diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp index 24c58e7ae..814fd5680 100644 --- a/src/core/file_sys/xts_archive.cpp +++ b/src/core/file_sys/xts_archive.cpp @@ -152,11 +152,11 @@ NAXContentType NAX::GetContentType() const { return type; } -std::vector> NAX::GetFiles() const { +std::vector NAX::GetFiles() const { return {dec_file}; } -std::vector> NAX::GetSubdirectories() const { +std::vector NAX::GetSubdirectories() const { return {}; } @@ -164,7 +164,7 @@ std::string NAX::GetName() const { return file->GetName(); } -std::shared_ptr NAX::GetParentDirectory() const { +VirtualDir NAX::GetParentDirectory() const { return file->GetContainingDirectory(); } diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h index c472e226e..63a032b68 100644 --- a/src/core/file_sys/xts_archive.h +++ b/src/core/file_sys/xts_archive.h @@ -47,13 +47,13 @@ public: NAXContentType GetContentType() const; - std::vector> GetFiles() const override; + std::vector GetFiles() const override; - std::vector> GetSubdirectories() const override; + std::vector GetSubdirectories() const override; std::string GetName() const override; - std::shared_ptr GetParentDirectory() const override; + VirtualDir GetParentDirectory() const override; private: Loader::ResultStatus Parse(std::string_view path); diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp index 4505da758..03bbedf8b 100644 --- a/src/core/frontend/applets/controller.cpp +++ b/src/core/frontend/applets/controller.cpp @@ -4,7 +4,6 @@ #include "common/assert.h" #include "common/logging/log.h" -#include "core/core.h" #include "core/frontend/applets/controller.h" #include "core/hle/service/hid/controllers/npad.h" #include "core/hle/service/hid/hid.h" @@ -14,32 +13,33 @@ namespace Core::Frontend { ControllerApplet::~ControllerApplet() = default; +DefaultControllerApplet::DefaultControllerApplet(Service::SM::ServiceManager& service_manager_) + : service_manager{service_manager_} {} + DefaultControllerApplet::~DefaultControllerApplet() = default; void DefaultControllerApplet::ReconfigureControllers(std::function callback, - ControllerParameters parameters) const { + const ControllerParameters& parameters) const { LOG_INFO(Service_HID, "called, deducing the best configuration based on the given parameters!"); auto& npad = - Core::System::GetInstance() - .ServiceManager() - .GetService("hid") + service_manager.GetService("hid") ->GetAppletResource() ->GetController(Service::HID::HidController::NPad); - auto& players = Settings::values.players; + auto& players = Settings::values.players.GetValue(); const std::size_t min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; // Disconnect Handheld first. - npad.DisconnectNPadAtIndex(8); + npad.DisconnectNpadAtIndex(8); // Deduce the best configuration based on the input parameters. for (std::size_t index = 0; index < players.size() - 2; ++index) { // First, disconnect all controllers regardless of the value of keep_controllers_connected. // This makes it easy to connect the desired controllers. - npad.DisconnectNPadAtIndex(index); + npad.DisconnectNpadAtIndex(index); // Only connect the minimum number of required players. if (index >= min_supported_players) { @@ -66,7 +66,7 @@ void DefaultControllerApplet::ReconfigureControllers(std::function callb npad.MapSettingsTypeToNPad(Settings::ControllerType::RightJoycon), index); } } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld && - !Settings::values.use_docked_mode) { + !Settings::values.use_docked_mode.GetValue()) { // We should *never* reach here under any normal circumstances. npad.AddNewControllerAt(npad.MapSettingsTypeToNPad(Settings::ControllerType::Handheld), index); diff --git a/src/core/frontend/applets/controller.h b/src/core/frontend/applets/controller.h index a227f15cd..dff71d8d9 100644 --- a/src/core/frontend/applets/controller.h +++ b/src/core/frontend/applets/controller.h @@ -8,6 +8,10 @@ #include "common/common_types.h" +namespace Service::SM { +class ServiceManager; +} + namespace Core::Frontend { using BorderColor = std::array; @@ -34,15 +38,19 @@ public: virtual ~ControllerApplet(); virtual void ReconfigureControllers(std::function callback, - ControllerParameters parameters) const = 0; + const ControllerParameters& parameters) const = 0; }; class DefaultControllerApplet final : public ControllerApplet { public: + explicit DefaultControllerApplet(Service::SM::ServiceManager& service_manager_); ~DefaultControllerApplet() override; void ReconfigureControllers(std::function callback, - ControllerParameters parameters) const override; + const ControllerParameters& parameters) const override; + +private: + Service::SM::ServiceManager& service_manager; }; } // namespace Core::Frontend diff --git a/src/core/frontend/applets/error.cpp b/src/core/frontend/applets/error.cpp index 4002a9211..dceb20ff8 100644 --- a/src/core/frontend/applets/error.cpp +++ b/src/core/frontend/applets/error.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/logging/log.h" #include "core/frontend/applets/error.h" namespace Core::Frontend { @@ -10,7 +11,7 @@ ErrorApplet::~ErrorApplet() = default; void DefaultErrorApplet::ShowError(ResultCode error, std::function finished) const { LOG_CRITICAL(Service_Fatal, "Application requested error display: {:04}-{:04} (raw={:08X})", - static_cast(error.module.Value()), error.description.Value(), error.raw); + error.module.Value(), error.description.Value(), error.raw); } void DefaultErrorApplet::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time, @@ -18,7 +19,7 @@ void DefaultErrorApplet::ShowErrorWithTimestamp(ResultCode error, std::chrono::s LOG_CRITICAL( Service_Fatal, "Application requested error display: {:04X}-{:04X} (raw={:08X}) with timestamp={:016X}", - static_cast(error.module.Value()), error.description.Value(), error.raw, time.count()); + error.module.Value(), error.description.Value(), error.raw, time.count()); } void DefaultErrorApplet::ShowCustomErrorText(ResultCode error, std::string main_text, @@ -26,7 +27,7 @@ void DefaultErrorApplet::ShowCustomErrorText(ResultCode error, std::string main_ std::function finished) const { LOG_CRITICAL(Service_Fatal, "Application requested custom error with error_code={:04X}-{:04X} (raw={:08X})", - static_cast(error.module.Value()), error.description.Value(), error.raw); + error.module.Value(), error.description.Value(), error.raw); LOG_CRITICAL(Service_Fatal, " Main Text: {}", main_text); LOG_CRITICAL(Service_Fatal, " Detail Text: {}", detail_text); } diff --git a/src/core/frontend/applets/general_frontend.cpp b/src/core/frontend/applets/general_frontend.cpp index c30b36de7..7483ffb76 100644 --- a/src/core/frontend/applets/general_frontend.cpp +++ b/src/core/frontend/applets/general_frontend.cpp @@ -53,72 +53,4 @@ void DefaultPhotoViewerApplet::ShowAllPhotos(std::function finished) con finished(); } -ECommerceApplet::~ECommerceApplet() = default; - -DefaultECommerceApplet::~DefaultECommerceApplet() = default; - -void DefaultECommerceApplet::ShowApplicationInformation( - std::function finished, u64 title_id, std::optional user_id, - std::optional full_display, std::optional extra_parameter) { - const auto value = user_id.value_or(u128{}); - LOG_INFO(Service_AM, - "Application requested frontend show application information for EShop, " - "title_id={:016X}, user_id={:016X}{:016X}, full_display={}, extra_parameter={}", - title_id, value[1], value[0], - full_display.has_value() ? fmt::format("{}", *full_display) : "null", - extra_parameter.value_or("null")); - finished(); -} - -void DefaultECommerceApplet::ShowAddOnContentList(std::function finished, u64 title_id, - std::optional user_id, - std::optional full_display) { - const auto value = user_id.value_or(u128{}); - LOG_INFO(Service_AM, - "Application requested frontend show add on content list for EShop, " - "title_id={:016X}, user_id={:016X}{:016X}, full_display={}", - title_id, value[1], value[0], - full_display.has_value() ? fmt::format("{}", *full_display) : "null"); - finished(); -} - -void DefaultECommerceApplet::ShowSubscriptionList(std::function finished, u64 title_id, - std::optional user_id) { - const auto value = user_id.value_or(u128{}); - LOG_INFO(Service_AM, - "Application requested frontend show subscription list for EShop, title_id={:016X}, " - "user_id={:016X}{:016X}", - title_id, value[1], value[0]); - finished(); -} - -void DefaultECommerceApplet::ShowConsumableItemList(std::function finished, u64 title_id, - std::optional user_id) { - const auto value = user_id.value_or(u128{}); - LOG_INFO( - Service_AM, - "Application requested frontend show consumable item list for EShop, title_id={:016X}, " - "user_id={:016X}{:016X}", - title_id, value[1], value[0]); - finished(); -} - -void DefaultECommerceApplet::ShowShopHome(std::function finished, u128 user_id, - bool full_display) { - LOG_INFO(Service_AM, - "Application requested frontend show home menu for EShop, user_id={:016X}{:016X}, " - "full_display={}", - user_id[1], user_id[0], full_display); - finished(); -} - -void DefaultECommerceApplet::ShowSettings(std::function finished, u128 user_id, - bool full_display) { - LOG_INFO(Service_AM, - "Application requested frontend show settings menu for EShop, user_id={:016X}{:016X}, " - "full_display={}", - user_id[1], user_id[0], full_display); - finished(); -} - } // namespace Core::Frontend diff --git a/src/core/frontend/applets/general_frontend.h b/src/core/frontend/applets/general_frontend.h index 4b63f828e..b713b14ee 100644 --- a/src/core/frontend/applets/general_frontend.h +++ b/src/core/frontend/applets/general_frontend.h @@ -58,55 +58,4 @@ public: void ShowAllPhotos(std::function finished) const override; }; -class ECommerceApplet { -public: - virtual ~ECommerceApplet(); - - // Shows a page with application icons, description, name, and price. - virtual void ShowApplicationInformation(std::function finished, u64 title_id, - std::optional user_id = {}, - std::optional full_display = {}, - std::optional extra_parameter = {}) = 0; - - // Shows a page with all of the add on content available for a game, with name, description, and - // price. - virtual void ShowAddOnContentList(std::function finished, u64 title_id, - std::optional user_id = {}, - std::optional full_display = {}) = 0; - - // Shows a page with all of the subscriptions (recurring payments) for a game, with name, - // description, price, and renewal period. - virtual void ShowSubscriptionList(std::function finished, u64 title_id, - std::optional user_id = {}) = 0; - - // Shows a page with a list of any additional game related purchasable items (DLC, - // subscriptions, etc) for a particular game, with name, description, type, and price. - virtual void ShowConsumableItemList(std::function finished, u64 title_id, - std::optional user_id = {}) = 0; - - // Shows the home page of the shop. - virtual void ShowShopHome(std::function finished, u128 user_id, bool full_display) = 0; - - // Shows the user settings page of the shop. - virtual void ShowSettings(std::function finished, u128 user_id, bool full_display) = 0; -}; - -class DefaultECommerceApplet : public ECommerceApplet { -public: - ~DefaultECommerceApplet() override; - - void ShowApplicationInformation(std::function finished, u64 title_id, - std::optional user_id, std::optional full_display, - std::optional extra_parameter) override; - void ShowAddOnContentList(std::function finished, u64 title_id, - std::optional user_id, - std::optional full_display) override; - void ShowSubscriptionList(std::function finished, u64 title_id, - std::optional user_id) override; - void ShowConsumableItemList(std::function finished, u64 title_id, - std::optional user_id) override; - void ShowShopHome(std::function finished, u128 user_id, bool full_display) override; - void ShowSettings(std::function finished, u128 user_id, bool full_display) override; -}; - } // namespace Core::Frontend diff --git a/src/core/frontend/applets/web_browser.cpp b/src/core/frontend/applets/web_browser.cpp index 528295ffc..50db6a654 100644 --- a/src/core/frontend/applets/web_browser.cpp +++ b/src/core/frontend/applets/web_browser.cpp @@ -11,14 +11,22 @@ WebBrowserApplet::~WebBrowserApplet() = default; DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default; -void DefaultWebBrowserApplet::OpenPageLocal(std::string_view filename, - std::function unpack_romfs_callback, - std::function finished_callback) { - LOG_INFO(Service_AM, - "(STUBBED) called - No suitable web browser implementation found to open website page " - "at '{}'!", - filename); - finished_callback(); +void DefaultWebBrowserApplet::OpenLocalWebPage( + std::string_view local_url, std::function extract_romfs_callback, + std::function callback) const { + LOG_WARNING(Service_AM, "(STUBBED) called, backend requested to open local web page at {}", + local_url); + + callback(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/"); +} + +void DefaultWebBrowserApplet::OpenExternalWebPage( + std::string_view external_url, + std::function callback) const { + LOG_WARNING(Service_AM, "(STUBBED) called, backend requested to open external web page at {}", + external_url); + + callback(Service::AM::Applets::WebExitReason::WindowClosed, "http://localhost/"); } } // namespace Core::Frontend diff --git a/src/core/frontend/applets/web_browser.h b/src/core/frontend/applets/web_browser.h index 110e33bc4..1c5ef19a9 100644 --- a/src/core/frontend/applets/web_browser.h +++ b/src/core/frontend/applets/web_browser.h @@ -7,22 +7,34 @@ #include #include +#include "core/hle/service/am/applets/web_types.h" + namespace Core::Frontend { class WebBrowserApplet { public: virtual ~WebBrowserApplet(); - virtual void OpenPageLocal(std::string_view url, std::function unpack_romfs_callback, - std::function finished_callback) = 0; + virtual void OpenLocalWebPage( + std::string_view local_url, std::function extract_romfs_callback, + std::function callback) const = 0; + + virtual void OpenExternalWebPage( + std::string_view external_url, + std::function callback) const = 0; }; class DefaultWebBrowserApplet final : public WebBrowserApplet { public: ~DefaultWebBrowserApplet() override; - void OpenPageLocal(std::string_view url, std::function unpack_romfs_callback, - std::function finished_callback) override; + void OpenLocalWebPage(std::string_view local_url, std::function extract_romfs_callback, + std::function + callback) const override; + + void OpenExternalWebPage(std::string_view external_url, + std::function + callback) const override; }; } // namespace Core::Frontend diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 9a081fbd4..8c1193894 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -84,10 +84,12 @@ void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) { return; std::lock_guard guard{touch_state->mutex}; - touch_state->touch_x = static_cast(framebuffer_x - framebuffer_layout.screen.left) / - (framebuffer_layout.screen.right - framebuffer_layout.screen.left); - touch_state->touch_y = static_cast(framebuffer_y - framebuffer_layout.screen.top) / - (framebuffer_layout.screen.bottom - framebuffer_layout.screen.top); + touch_state->touch_x = + static_cast(framebuffer_x - framebuffer_layout.screen.left) / + static_cast(framebuffer_layout.screen.right - framebuffer_layout.screen.left); + touch_state->touch_y = + static_cast(framebuffer_y - framebuffer_layout.screen.top) / + static_cast(framebuffer_layout.screen.bottom - framebuffer_layout.screen.top); touch_state->touch_pressed = true; } diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 3e8780243..276d2b906 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -102,8 +102,8 @@ public: float render_surface_scale = 1.0f; }; - /// Polls window events - virtual void PollEvents() = 0; + /// Called from GPU thread when a frame is displayed. + virtual void OnFrameDisplayed() {} /** * Returns a GraphicsContext that the frontend provides to be used for rendering. diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index c1fbc235b..b9a270a55 100644 --- a/src/core/frontend/framebuffer_layout.cpp +++ b/src/core/frontend/framebuffer_layout.cpp @@ -14,8 +14,8 @@ namespace Layout { template static Common::Rectangle MaxRectangle(Common::Rectangle window_area, float screen_aspect_ratio) { - float scale = std::min(static_cast(window_area.GetWidth()), - window_area.GetHeight() / screen_aspect_ratio); + const float scale = std::min(static_cast(window_area.GetWidth()), + static_cast(window_area.GetHeight()) / screen_aspect_ratio); return Common::Rectangle{0, 0, static_cast(std::round(scale)), static_cast(std::round(scale * screen_aspect_ratio))}; } @@ -27,7 +27,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) { // so just calculate them both even if the other isn't showing. FramebufferLayout res{width, height, false, {}}; - const float window_aspect_ratio = static_cast(height) / width; + const float window_aspect_ratio = static_cast(height) / static_cast(width); const float emulation_aspect_ratio = EmulationAspectRatio( static_cast(Settings::values.aspect_ratio.GetValue()), window_aspect_ratio); @@ -47,7 +47,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) { FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale) { u32 width, height; - if (Settings::values.use_docked_mode) { + if (Settings::values.use_docked_mode.GetValue()) { width = ScreenDocked::Width * res_scale; height = ScreenDocked::Height * res_scale; } else { diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h index 9da0d2829..de51a754e 100644 --- a/src/core/frontend/input.h +++ b/src/core/frontend/input.h @@ -30,7 +30,12 @@ public: virtual StatusType GetStatus() const { return {}; } - virtual bool GetAnalogDirectionStatus(AnalogDirection direction) const { + virtual bool GetAnalogDirectionStatus([[maybe_unused]] AnalogDirection direction) const { + return {}; + } + virtual bool SetRumblePlay([[maybe_unused]] f32 amp_low, [[maybe_unused]] f32 freq_low, + [[maybe_unused]] f32 amp_high, + [[maybe_unused]] f32 freq_high) const { return {}; } }; @@ -118,6 +123,13 @@ using ButtonDevice = InputDevice; */ using AnalogDevice = InputDevice>; +/** + * A vibration device is an input device that returns an unsigned byte as status. + * It represents whether the vibration device supports vibration or not. + * If the status returns 1, it supports vibration. Otherwise, it does not support vibration. + */ +using VibrationDevice = InputDevice; + /** * A motion status is an object that returns a tuple of accelerometer state vector, * gyroscope state vector, rotation state vector and orientation state matrix. @@ -151,10 +163,15 @@ using MotionStatus = std::tuple, Common::Vec3, Common using MotionDevice = InputDevice; /** - * A touch device is an input device that returns a tuple of two floats and a bool. The floats are + * A touch status is an object that returns a tuple of two floats and a bool. The floats are * x and y coordinates in the range 0.0 - 1.0, and the bool indicates whether it is pressed. */ -using TouchDevice = InputDevice>; +using TouchStatus = std::tuple; + +/** + * A touch device is an input device that returns a touch status object + */ +using TouchDevice = InputDevice; /** * A mouse device is an input device that returns a tuple of two floats and four ints. diff --git a/src/core/frontend/input_interpreter.cpp b/src/core/frontend/input_interpreter.cpp new file mode 100644 index 000000000..66ae506cd --- /dev/null +++ b/src/core/frontend/input_interpreter.cpp @@ -0,0 +1,45 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/core.h" +#include "core/frontend/input_interpreter.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/sm/sm.h" + +InputInterpreter::InputInterpreter(Core::System& system) + : npad{system.ServiceManager() + .GetService("hid") + ->GetAppletResource() + ->GetController(Service::HID::HidController::NPad)} {} + +InputInterpreter::~InputInterpreter() = default; + +void InputInterpreter::PollInput() { + const u32 button_state = npad.GetAndResetPressState(); + + previous_index = current_index; + current_index = (current_index + 1) % button_states.size(); + + button_states[current_index] = button_state; +} + +bool InputInterpreter::IsButtonPressedOnce(HIDButton button) const { + const bool current_press = + (button_states[current_index] & (1U << static_cast(button))) != 0; + const bool previous_press = + (button_states[previous_index] & (1U << static_cast(button))) != 0; + + return current_press && !previous_press; +} + +bool InputInterpreter::IsButtonHeld(HIDButton button) const { + u32 held_buttons{button_states[0]}; + + for (std::size_t i = 1; i < button_states.size(); ++i) { + held_buttons &= button_states[i]; + } + + return (held_buttons & (1U << static_cast(button))) != 0; +} diff --git a/src/core/frontend/input_interpreter.h b/src/core/frontend/input_interpreter.h new file mode 100644 index 000000000..fea9aebe6 --- /dev/null +++ b/src/core/frontend/input_interpreter.h @@ -0,0 +1,120 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "common/common_types.h" + +namespace Core { +class System; +} + +namespace Service::HID { +class Controller_NPad; +} + +enum class HIDButton : u8 { + A, + B, + X, + Y, + LStick, + RStick, + L, + R, + ZL, + ZR, + Plus, + Minus, + + DLeft, + DUp, + DRight, + DDown, + + LStickLeft, + LStickUp, + LStickRight, + LStickDown, + + RStickLeft, + RStickUp, + RStickRight, + RStickDown, + + LeftSL, + LeftSR, + + RightSL, + RightSR, +}; + +/** + * The InputInterpreter class interfaces with HID to retrieve button press states. + * Input is intended to be polled every 50ms so that a button is considered to be + * held down after 400ms has elapsed since the initial button press and subsequent + * repeated presses occur every 50ms. + */ +class InputInterpreter { +public: + explicit InputInterpreter(Core::System& system); + virtual ~InputInterpreter(); + + /// Gets a button state from HID and inserts it into the array of button states. + void PollInput(); + + /** + * The specified button is considered to be pressed once + * if it is currently pressed and not pressed previously. + * + * @param button The button to check. + * + * @returns True when the button is pressed once. + */ + [[nodiscard]] bool IsButtonPressedOnce(HIDButton button) const; + + /** + * Checks whether any of the buttons in the parameter list is pressed once. + * + * @tparam HIDButton The buttons to check. + * + * @returns True when at least one of the buttons is pressed once. + */ + template + [[nodiscard]] bool IsAnyButtonPressedOnce() { + return (IsButtonPressedOnce(T) || ...); + } + + /** + * The specified button is considered to be held down if it is pressed in all 9 button states. + * + * @param button The button to check. + * + * @returns True when the button is held down. + */ + [[nodiscard]] bool IsButtonHeld(HIDButton button) const; + + /** + * Checks whether any of the buttons in the parameter list is held down. + * + * @tparam HIDButton The buttons to check. + * + * @returns True when at least one of the buttons is held down. + */ + template + [[nodiscard]] bool IsAnyButtonHeld() { + return (IsButtonHeld(T) || ...); + } + +private: + Service::HID::Controller_NPad& npad; + + /// Stores 9 consecutive button states polled from HID. + std::array button_states{}; + + std::size_t previous_index{}; + std::size_t current_index{}; +}; diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp deleted file mode 100644 index 79f22a403..000000000 --- a/src/core/gdbstub/gdbstub.cpp +++ /dev/null @@ -1,1397 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -// Originally written by Sven Peter for anergistic. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#include -// winsock2.h needs to be included first to prevent winsock.h being included by other includes -#include -#include -#include -#define SHUT_RDWR 2 -#else -#include -#include -#include -#include -#include -#endif - -#include "common/logging/log.h" -#include "common/string_util.h" -#include "common/swap.h" -#include "core/arm/arm_interface.h" -#include "core/core.h" -#include "core/gdbstub/gdbstub.h" -#include "core/hle/kernel/memory/page_table.h" -#include "core/hle/kernel/process.h" -#include "core/hle/kernel/scheduler.h" -#include "core/loader/loader.h" -#include "core/memory.h" - -namespace GDBStub { -namespace { -constexpr int GDB_BUFFER_SIZE = 10000; - -constexpr char GDB_STUB_START = '$'; -constexpr char GDB_STUB_END = '#'; -constexpr char GDB_STUB_ACK = '+'; -constexpr char GDB_STUB_NACK = '-'; - -#ifndef SIGTRAP -constexpr u32 SIGTRAP = 5; -#endif - -#ifndef SIGTERM -constexpr u32 SIGTERM = 15; -#endif - -#ifndef MSG_WAITALL -constexpr u32 MSG_WAITALL = 8; -#endif - -constexpr u32 LR_REGISTER = 30; -constexpr u32 SP_REGISTER = 31; -constexpr u32 PC_REGISTER = 32; -constexpr u32 PSTATE_REGISTER = 33; -constexpr u32 UC_ARM64_REG_Q0 = 34; -constexpr u32 FPCR_REGISTER = 66; - -// For sample XML files see the GDB source /gdb/features -// GDB also wants the l character at the start -// This XML defines what the registers are for this specific ARM device -constexpr char target_xml[] = - R"(l - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -)"; - -int gdbserver_socket = -1; -bool defer_start = false; - -u8 command_buffer[GDB_BUFFER_SIZE]; -u32 command_length; - -u32 latest_signal = 0; -bool memory_break = false; - -Kernel::Thread* current_thread = nullptr; -u32 current_core = 0; - -// Binding to a port within the reserved ports range (0-1023) requires root permissions, -// so default to a port outside of that range. -u16 gdbstub_port = 24689; - -bool halt_loop = true; -bool step_loop = false; -bool send_trap = false; - -// If set to false, the server will never be started and no -// gdbstub-related functions will be executed. -std::atomic server_enabled(false); - -#ifdef _WIN32 -WSADATA InitData; -#endif - -struct Breakpoint { - bool active; - VAddr addr; - u64 len; - std::array inst; -}; - -using BreakpointMap = std::map; -BreakpointMap breakpoints_execute; -BreakpointMap breakpoints_read; -BreakpointMap breakpoints_write; - -struct Module { - std::string name; - VAddr beg; - VAddr end; -}; - -std::vector modules; -} // Anonymous namespace - -void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext) { - Module module; - if (add_elf_ext) { - Common::SplitPath(name, nullptr, &module.name, nullptr); - module.name += ".elf"; - } else { - module.name = std::move(name); - } - module.beg = beg; - module.end = end; - modules.push_back(std::move(module)); -} - -static Kernel::Thread* FindThreadById(s64 id) { - const auto& threads = Core::System::GetInstance().GlobalScheduler().GetThreadList(); - for (auto& thread : threads) { - if (thread->GetThreadID() == static_cast(id)) { - current_core = thread->GetProcessorID(); - return thread.get(); - } - } - return nullptr; -} - -static u64 RegRead(std::size_t id, Kernel::Thread* thread = nullptr) { - if (!thread) { - return 0; - } - - const auto& thread_context = thread->GetContext64(); - - if (id < SP_REGISTER) { - return thread_context.cpu_registers[id]; - } else if (id == SP_REGISTER) { - return thread_context.sp; - } else if (id == PC_REGISTER) { - return thread_context.pc; - } else if (id == PSTATE_REGISTER) { - return thread_context.pstate; - } else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) { - return thread_context.vector_registers[id - UC_ARM64_REG_Q0][0]; - } else { - return 0; - } -} - -static void RegWrite(std::size_t id, u64 val, Kernel::Thread* thread = nullptr) { - if (!thread) { - return; - } - - auto& thread_context = thread->GetContext64(); - - if (id < SP_REGISTER) { - thread_context.cpu_registers[id] = val; - } else if (id == SP_REGISTER) { - thread_context.sp = val; - } else if (id == PC_REGISTER) { - thread_context.pc = val; - } else if (id == PSTATE_REGISTER) { - thread_context.pstate = static_cast(val); - } else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) { - thread_context.vector_registers[id - (PSTATE_REGISTER + 1)][0] = val; - } -} - -static u128 FpuRead(std::size_t id, Kernel::Thread* thread = nullptr) { - if (!thread) { - return u128{0}; - } - - auto& thread_context = thread->GetContext64(); - - if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { - return thread_context.vector_registers[id - UC_ARM64_REG_Q0]; - } else if (id == FPCR_REGISTER) { - return u128{thread_context.fpcr, 0}; - } else { - return u128{0}; - } -} - -static void FpuWrite(std::size_t id, u128 val, Kernel::Thread* thread = nullptr) { - if (!thread) { - return; - } - - auto& thread_context = thread->GetContext64(); - - if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { - thread_context.vector_registers[id - UC_ARM64_REG_Q0] = val; - } else if (id == FPCR_REGISTER) { - thread_context.fpcr = static_cast(val[0]); - } -} - -/** - * Turns hex string character into the equivalent byte. - * - * @param hex Input hex character to be turned into byte. - */ -static u8 HexCharToValue(u8 hex) { - if (hex >= '0' && hex <= '9') { - return hex - '0'; - } else if (hex >= 'a' && hex <= 'f') { - return hex - 'a' + 0xA; - } else if (hex >= 'A' && hex <= 'F') { - return hex - 'A' + 0xA; - } - - LOG_ERROR(Debug_GDBStub, "Invalid nibble: {} ({:02X})", hex, hex); - return 0; -} - -/** - * Turn nibble of byte into hex string character. - * - * @param n Nibble to be turned into hex character. - */ -static u8 NibbleToHex(u8 n) { - n &= 0xF; - if (n < 0xA) { - return '0' + n; - } else { - return 'a' + n - 0xA; - } -} - -/** - * Converts input hex string characters into an array of equivalent of u8 bytes. - * - * @param src Pointer to array of output hex string characters. - * @param len Length of src array. - */ -static u32 HexToInt(const u8* src, std::size_t len) { - u32 output = 0; - while (len-- > 0) { - output = (output << 4) | HexCharToValue(src[0]); - src++; - } - return output; -} - -/** - * Converts input hex string characters into an array of equivalent of u8 bytes. - * - * @param src Pointer to array of output hex string characters. - * @param len Length of src array. - */ -static u64 HexToLong(const u8* src, std::size_t len) { - u64 output = 0; - while (len-- > 0) { - output = (output << 4) | HexCharToValue(src[0]); - src++; - } - return output; -} - -/** - * Converts input array of u8 bytes into their equivalent hex string characters. - * - * @param dest Pointer to buffer to store output hex string characters. - * @param src Pointer to array of u8 bytes. - * @param len Length of src array. - */ -static void MemToGdbHex(u8* dest, const u8* src, std::size_t len) { - while (len-- > 0) { - u8 tmp = *src++; - *dest++ = NibbleToHex(tmp >> 4); - *dest++ = NibbleToHex(tmp); - } -} - -/** - * Converts input gdb-formatted hex string characters into an array of equivalent of u8 bytes. - * - * @param dest Pointer to buffer to store u8 bytes. - * @param src Pointer to array of output hex string characters. - * @param len Length of src array. - */ -static void GdbHexToMem(u8* dest, const u8* src, std::size_t len) { - while (len-- > 0) { - *dest++ = (HexCharToValue(src[0]) << 4) | HexCharToValue(src[1]); - src += 2; - } -} - -/** - * Convert a u32 into a gdb-formatted hex string. - * - * @param dest Pointer to buffer to store output hex string characters. - * @param v Value to convert. - */ -static void IntToGdbHex(u8* dest, u32 v) { - for (int i = 0; i < 8; i += 2) { - dest[i + 1] = NibbleToHex(static_cast(v >> (4 * i))); - dest[i] = NibbleToHex(static_cast(v >> (4 * (i + 1)))); - } -} - -/** - * Convert a u64 into a gdb-formatted hex string. - * - * @param dest Pointer to buffer to store output hex string characters. - * @param v Value to convert. - */ -static void LongToGdbHex(u8* dest, u64 v) { - for (int i = 0; i < 16; i += 2) { - dest[i + 1] = NibbleToHex(static_cast(v >> (4 * i))); - dest[i] = NibbleToHex(static_cast(v >> (4 * (i + 1)))); - } -} - -/** - * Convert a gdb-formatted hex string into a u32. - * - * @param src Pointer to hex string. - */ -static u32 GdbHexToInt(const u8* src) { - u32 output = 0; - - for (int i = 0; i < 8; i += 2) { - output = (output << 4) | HexCharToValue(src[7 - i - 1]); - output = (output << 4) | HexCharToValue(src[7 - i]); - } - - return output; -} - -/** - * Convert a gdb-formatted hex string into a u64. - * - * @param src Pointer to hex string. - */ -static u64 GdbHexToLong(const u8* src) { - u64 output = 0; - - for (int i = 0; i < 16; i += 2) { - output = (output << 4) | HexCharToValue(src[15 - i - 1]); - output = (output << 4) | HexCharToValue(src[15 - i]); - } - - return output; -} - -/** - * Convert a gdb-formatted hex string into a u128. - * - * @param src Pointer to hex string. - */ -static u128 GdbHexToU128(const u8* src) { - u128 output; - - for (int i = 0; i < 16; i += 2) { - output[0] = (output[0] << 4) | HexCharToValue(src[15 - i - 1]); - output[0] = (output[0] << 4) | HexCharToValue(src[15 - i]); - } - - for (int i = 0; i < 16; i += 2) { - output[1] = (output[1] << 4) | HexCharToValue(src[16 + 15 - i - 1]); - output[1] = (output[1] << 4) | HexCharToValue(src[16 + 15 - i]); - } - - return output; -} - -/// Read a byte from the gdb client. -static u8 ReadByte() { - u8 c; - std::size_t received_size = recv(gdbserver_socket, reinterpret_cast(&c), 1, MSG_WAITALL); - if (received_size != 1) { - LOG_ERROR(Debug_GDBStub, "recv failed: {}", received_size); - Shutdown(); - } - - return c; -} - -/// Calculate the checksum of the current command buffer. -static u8 CalculateChecksum(const u8* buffer, std::size_t length) { - return static_cast(std::accumulate(buffer, buffer + length, u8{0}, - [](u8 lhs, u8 rhs) { return u8(lhs + rhs); })); -} - -/** - * Get the map of breakpoints for a given breakpoint type. - * - * @param type Type of breakpoint map. - */ -static BreakpointMap& GetBreakpointMap(BreakpointType type) { - switch (type) { - case BreakpointType::Execute: - return breakpoints_execute; - case BreakpointType::Read: - return breakpoints_read; - case BreakpointType::Write: - return breakpoints_write; - default: - return breakpoints_read; - } -} - -/** - * Remove the breakpoint from the given address of the specified type. - * - * @param type Type of breakpoint. - * @param addr Address of breakpoint. - */ -static void RemoveBreakpoint(BreakpointType type, VAddr addr) { - BreakpointMap& p = GetBreakpointMap(type); - - const auto bp = p.find(addr); - if (bp == p.end()) { - return; - } - - LOG_DEBUG(Debug_GDBStub, "gdb: removed a breakpoint: {:016X} bytes at {:016X} of type {}", - bp->second.len, bp->second.addr, static_cast(type)); - - if (type == BreakpointType::Execute) { - auto& system = Core::System::GetInstance(); - system.Memory().WriteBlock(bp->second.addr, bp->second.inst.data(), bp->second.inst.size()); - system.InvalidateCpuInstructionCaches(); - } - p.erase(addr); -} - -BreakpointAddress GetNextBreakpointFromAddress(VAddr addr, BreakpointType type) { - const BreakpointMap& p = GetBreakpointMap(type); - const auto next_breakpoint = p.lower_bound(addr); - BreakpointAddress breakpoint; - - if (next_breakpoint != p.end()) { - breakpoint.address = next_breakpoint->first; - breakpoint.type = type; - } else { - breakpoint.address = 0; - breakpoint.type = BreakpointType::None; - } - - return breakpoint; -} - -bool CheckBreakpoint(VAddr addr, BreakpointType type) { - if (!IsConnected()) { - return false; - } - - const BreakpointMap& p = GetBreakpointMap(type); - const auto bp = p.find(addr); - - if (bp == p.end()) { - return false; - } - - u64 len = bp->second.len; - - // IDA Pro defaults to 4-byte breakpoints for all non-hardware breakpoints - // no matter if it's a 4-byte or 2-byte instruction. When you execute a - // Thumb instruction with a 4-byte breakpoint set, it will set a breakpoint on - // two instructions instead of the single instruction you placed the breakpoint - // on. So, as a way to make sure that execution breakpoints are only breaking - // on the instruction that was specified, set the length of an execution - // breakpoint to 1. This should be fine since the CPU should never begin executing - // an instruction anywhere except the beginning of the instruction. - if (type == BreakpointType::Execute) { - len = 1; - } - - if (bp->second.active && (addr >= bp->second.addr && addr < bp->second.addr + len)) { - LOG_DEBUG(Debug_GDBStub, - "Found breakpoint type {} @ {:016X}, range: {:016X}" - " - {:016X} ({:X} bytes)", - static_cast(type), addr, bp->second.addr, bp->second.addr + len, len); - return true; - } - - return false; -} - -/** - * Send packet to gdb client. - * - * @param packet Packet to be sent to client. - */ -static void SendPacket(const char packet) { - std::size_t sent_size = send(gdbserver_socket, &packet, 1, 0); - if (sent_size != 1) { - LOG_ERROR(Debug_GDBStub, "send failed"); - } -} - -/** - * Send reply to gdb client. - * - * @param reply Reply to be sent to client. - */ -static void SendReply(const char* reply) { - if (!IsConnected()) { - return; - } - - LOG_DEBUG(Debug_GDBStub, "Reply: {}", reply); - - memset(command_buffer, 0, sizeof(command_buffer)); - - command_length = static_cast(strlen(reply)); - if (command_length + 4 > sizeof(command_buffer)) { - LOG_ERROR(Debug_GDBStub, "command_buffer overflow in SendReply"); - return; - } - - memcpy(command_buffer + 1, reply, command_length); - - u8 checksum = CalculateChecksum(command_buffer, command_length + 1); - command_buffer[0] = GDB_STUB_START; - command_buffer[command_length + 1] = GDB_STUB_END; - command_buffer[command_length + 2] = NibbleToHex(checksum >> 4); - command_buffer[command_length + 3] = NibbleToHex(checksum); - - u8* ptr = command_buffer; - u32 left = command_length + 4; - while (left > 0) { - int sent_size = send(gdbserver_socket, reinterpret_cast(ptr), left, 0); - if (sent_size < 0) { - LOG_ERROR(Debug_GDBStub, "gdb: send failed"); - return Shutdown(); - } - - left -= sent_size; - ptr += sent_size; - } -} - -/// Handle query command from gdb client. -static void HandleQuery() { - LOG_DEBUG(Debug_GDBStub, "gdb: query '{}'", command_buffer + 1); - - const char* query = reinterpret_cast(command_buffer + 1); - - if (strcmp(query, "TStatus") == 0) { - SendReply("T0"); - } else if (strncmp(query, "Supported", strlen("Supported")) == 0) { - // PacketSize needs to be large enough for target xml - std::string buffer = "PacketSize=2000;qXfer:features:read+;qXfer:threads:read+"; - if (!modules.empty()) { - buffer += ";qXfer:libraries:read+"; - } - SendReply(buffer.c_str()); - } else if (strncmp(query, "Xfer:features:read:target.xml:", - strlen("Xfer:features:read:target.xml:")) == 0) { - SendReply(target_xml); - } else if (strncmp(query, "Offsets", strlen("Offsets")) == 0) { - const VAddr base_address = - Core::System::GetInstance().CurrentProcess()->PageTable().GetCodeRegionStart(); - std::string buffer = fmt::format("TextSeg={:0x}", base_address); - SendReply(buffer.c_str()); - } else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) { - std::string val = "m"; - const auto& threads = Core::System::GetInstance().GlobalScheduler().GetThreadList(); - for (const auto& thread : threads) { - val += fmt::format("{:x},", thread->GetThreadID()); - } - val.pop_back(); - SendReply(val.c_str()); - } else if (strncmp(query, "sThreadInfo", strlen("sThreadInfo")) == 0) { - SendReply("l"); - } else if (strncmp(query, "Xfer:threads:read", strlen("Xfer:threads:read")) == 0) { - std::string buffer; - buffer += "l"; - buffer += ""; - const auto& threads = Core::System::GetInstance().GlobalScheduler().GetThreadList(); - for (const auto& thread : threads) { - buffer += - fmt::format(R"*()*", - thread->GetThreadID(), thread->GetProcessorID(), thread->GetThreadID()); - } - buffer += ""; - SendReply(buffer.c_str()); - } else if (strncmp(query, "Xfer:libraries:read", strlen("Xfer:libraries:read")) == 0) { - std::string buffer; - buffer += "l"; - buffer += ""; - for (const auto& module : modules) { - buffer += - fmt::format(R"*(")*", - module.name, module.beg); - } - buffer += ""; - SendReply(buffer.c_str()); - } else { - SendReply(""); - } -} - -/// Handle set thread command from gdb client. -static void HandleSetThread() { - int thread_id = -1; - if (command_buffer[2] != '-') { - thread_id = static_cast(HexToInt(command_buffer + 2, command_length - 2)); - } - if (thread_id >= 1) { - current_thread = FindThreadById(thread_id); - } - if (!current_thread) { - thread_id = 1; - current_thread = FindThreadById(thread_id); - } - if (current_thread) { - SendReply("OK"); - return; - } - SendReply("E01"); -} - -/// Handle thread alive command from gdb client. -static void HandleThreadAlive() { - int thread_id = static_cast(HexToInt(command_buffer + 1, command_length - 1)); - if (thread_id == 0) { - thread_id = 1; - } - if (FindThreadById(thread_id)) { - SendReply("OK"); - return; - } - SendReply("E01"); -} - -/** - * Send signal packet to client. - * - * @param signal Signal to be sent to client. - */ -static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) { - if (gdbserver_socket == -1) { - return; - } - - latest_signal = signal; - - if (!thread) { - full = false; - } - - std::string buffer; - if (full) { - buffer = fmt::format("T{:02x}{:02x}:{:016x};{:02x}:{:016x};{:02x}:{:016x}", latest_signal, - PC_REGISTER, Common::swap64(RegRead(PC_REGISTER, thread)), SP_REGISTER, - Common::swap64(RegRead(SP_REGISTER, thread)), LR_REGISTER, - Common::swap64(RegRead(LR_REGISTER, thread))); - } else { - buffer = fmt::format("T{:02x}", latest_signal); - } - - if (thread) { - buffer += fmt::format(";thread:{:x};", thread->GetThreadID()); - } - - SendReply(buffer.c_str()); -} - -/// Read command from gdb client. -static void ReadCommand() { - command_length = 0; - memset(command_buffer, 0, sizeof(command_buffer)); - - u8 c = ReadByte(); - if (c == '+') { - // ignore ack - return; - } else if (c == 0x03) { - LOG_INFO(Debug_GDBStub, "gdb: found break command"); - halt_loop = true; - SendSignal(current_thread, SIGTRAP); - return; - } else if (c != GDB_STUB_START) { - LOG_DEBUG(Debug_GDBStub, "gdb: read invalid byte {:02X}", c); - return; - } - - while ((c = ReadByte()) != GDB_STUB_END) { - if (command_length >= sizeof(command_buffer)) { - LOG_ERROR(Debug_GDBStub, "gdb: command_buffer overflow"); - SendPacket(GDB_STUB_NACK); - return; - } - command_buffer[command_length++] = c; - } - - u8 checksum_received = HexCharToValue(ReadByte()) << 4; - checksum_received |= HexCharToValue(ReadByte()); - - u8 checksum_calculated = CalculateChecksum(command_buffer, command_length); - - if (checksum_received != checksum_calculated) { - LOG_ERROR(Debug_GDBStub, - "gdb: invalid checksum: calculated {:02X} and read {:02X} for ${}# (length: {})", - checksum_calculated, checksum_received, command_buffer, command_length); - - command_length = 0; - - SendPacket(GDB_STUB_NACK); - return; - } - - SendPacket(GDB_STUB_ACK); -} - -/// Check if there is data to be read from the gdb client. -static bool IsDataAvailable() { - if (!IsConnected()) { - return false; - } - - fd_set fd_socket; - - FD_ZERO(&fd_socket); - FD_SET(static_cast(gdbserver_socket), &fd_socket); - - struct timeval t; - t.tv_sec = 0; - t.tv_usec = 0; - - if (select(gdbserver_socket + 1, &fd_socket, nullptr, nullptr, &t) < 0) { - LOG_ERROR(Debug_GDBStub, "select failed"); - return false; - } - - return FD_ISSET(gdbserver_socket, &fd_socket) != 0; -} - -/// Send requested register to gdb client. -static void ReadRegister() { - static u8 reply[64]; - memset(reply, 0, sizeof(reply)); - - u32 id = HexCharToValue(command_buffer[1]); - if (command_buffer[2] != '\0') { - id <<= 4; - id |= HexCharToValue(command_buffer[2]); - } - - if (id <= SP_REGISTER) { - LongToGdbHex(reply, RegRead(id, current_thread)); - } else if (id == PC_REGISTER) { - LongToGdbHex(reply, RegRead(id, current_thread)); - } else if (id == PSTATE_REGISTER) { - IntToGdbHex(reply, static_cast(RegRead(id, current_thread))); - } else if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { - u128 r = FpuRead(id, current_thread); - LongToGdbHex(reply, r[0]); - LongToGdbHex(reply + 16, r[1]); - } else if (id == FPCR_REGISTER) { - u128 r = FpuRead(id, current_thread); - IntToGdbHex(reply, static_cast(r[0])); - } else if (id == FPCR_REGISTER + 1) { - u128 r = FpuRead(id, current_thread); - IntToGdbHex(reply, static_cast(r[0] >> 32)); - } - - SendReply(reinterpret_cast(reply)); -} - -/// Send all registers to the gdb client. -static void ReadRegisters() { - static u8 buffer[GDB_BUFFER_SIZE - 4]; - memset(buffer, 0, sizeof(buffer)); - - u8* bufptr = buffer; - - for (u32 reg = 0; reg <= SP_REGISTER; reg++) { - LongToGdbHex(bufptr + reg * 16, RegRead(reg, current_thread)); - } - - bufptr += 32 * 16; - - LongToGdbHex(bufptr, RegRead(PC_REGISTER, current_thread)); - - bufptr += 16; - - IntToGdbHex(bufptr, static_cast(RegRead(PSTATE_REGISTER, current_thread))); - - bufptr += 8; - - u128 r; - - for (u32 reg = UC_ARM64_REG_Q0; reg < FPCR_REGISTER; reg++) { - r = FpuRead(reg, current_thread); - LongToGdbHex(bufptr + reg * 32, r[0]); - LongToGdbHex(bufptr + reg * 32 + 16, r[1]); - } - - bufptr += 32 * 32; - - r = FpuRead(FPCR_REGISTER, current_thread); - IntToGdbHex(bufptr, static_cast(r[0])); - - bufptr += 8; - - SendReply(reinterpret_cast(buffer)); -} - -/// Modify data of register specified by gdb client. -static void WriteRegister() { - const u8* buffer_ptr = command_buffer + 3; - - u32 id = HexCharToValue(command_buffer[1]); - if (command_buffer[2] != '=') { - ++buffer_ptr; - id <<= 4; - id |= HexCharToValue(command_buffer[2]); - } - - if (id <= SP_REGISTER) { - RegWrite(id, GdbHexToLong(buffer_ptr), current_thread); - } else if (id == PC_REGISTER) { - RegWrite(id, GdbHexToLong(buffer_ptr), current_thread); - } else if (id == PSTATE_REGISTER) { - RegWrite(id, GdbHexToInt(buffer_ptr), current_thread); - } else if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { - FpuWrite(id, GdbHexToU128(buffer_ptr), current_thread); - } else if (id == FPCR_REGISTER) { - } else if (id == FPCR_REGISTER + 1) { - } - - // Update ARM context, skipping scheduler - no running threads at this point - Core::System::GetInstance() - .ArmInterface(current_core) - .LoadContext(current_thread->GetContext64()); - - SendReply("OK"); -} - -/// Modify all registers with data received from the client. -static void WriteRegisters() { - const u8* buffer_ptr = command_buffer + 1; - - if (command_buffer[0] != 'G') - return SendReply("E01"); - - for (u32 i = 0, reg = 0; reg <= FPCR_REGISTER; i++, reg++) { - if (reg <= SP_REGISTER) { - RegWrite(reg, GdbHexToLong(buffer_ptr + i * 16), current_thread); - } else if (reg == PC_REGISTER) { - RegWrite(PC_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread); - } else if (reg == PSTATE_REGISTER) { - RegWrite(PSTATE_REGISTER, GdbHexToInt(buffer_ptr + i * 16), current_thread); - } else if (reg >= UC_ARM64_REG_Q0 && reg < FPCR_REGISTER) { - RegWrite(reg, GdbHexToLong(buffer_ptr + i * 16), current_thread); - } else if (reg == FPCR_REGISTER) { - RegWrite(FPCR_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread); - } else if (reg == FPCR_REGISTER + 1) { - RegWrite(FPCR_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread); - } - } - - // Update ARM context, skipping scheduler - no running threads at this point - Core::System::GetInstance() - .ArmInterface(current_core) - .LoadContext(current_thread->GetContext64()); - - SendReply("OK"); -} - -/// Read location in memory specified by gdb client. -static void ReadMemory() { - static u8 reply[GDB_BUFFER_SIZE - 4]; - - auto start_offset = command_buffer + 1; - const auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); - const VAddr addr = HexToLong(start_offset, static_cast(addr_pos - start_offset)); - - start_offset = addr_pos + 1; - const u64 len = - HexToLong(start_offset, static_cast((command_buffer + command_length) - start_offset)); - - LOG_DEBUG(Debug_GDBStub, "gdb: addr: {:016X} len: {:016X}", addr, len); - - if (len * 2 > sizeof(reply)) { - SendReply("E01"); - } - - auto& memory = Core::System::GetInstance().Memory(); - if (!memory.IsValidVirtualAddress(addr)) { - return SendReply("E00"); - } - - std::vector data(len); - memory.ReadBlock(addr, data.data(), len); - - MemToGdbHex(reply, data.data(), len); - reply[len * 2] = '\0'; - SendReply(reinterpret_cast(reply)); -} - -/// Modify location in memory with data received from the gdb client. -static void WriteMemory() { - auto start_offset = command_buffer + 1; - const auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); - const VAddr addr = HexToLong(start_offset, static_cast(addr_pos - start_offset)); - - start_offset = addr_pos + 1; - const auto len_pos = std::find(start_offset, command_buffer + command_length, ':'); - const u64 len = HexToLong(start_offset, static_cast(len_pos - start_offset)); - - auto& system = Core::System::GetInstance(); - auto& memory = system.Memory(); - if (!memory.IsValidVirtualAddress(addr)) { - return SendReply("E00"); - } - - std::vector data(len); - GdbHexToMem(data.data(), len_pos + 1, len); - memory.WriteBlock(addr, data.data(), len); - system.InvalidateCpuInstructionCaches(); - SendReply("OK"); -} - -void Break(bool is_memory_break) { - send_trap = true; - - memory_break = is_memory_break; -} - -/// Tell the CPU that it should perform a single step. -static void Step() { - if (command_length > 1) { - RegWrite(PC_REGISTER, GdbHexToLong(command_buffer + 1), current_thread); - // Update ARM context, skipping scheduler - no running threads at this point - Core::System::GetInstance() - .ArmInterface(current_core) - .LoadContext(current_thread->GetContext64()); - } - step_loop = true; - halt_loop = true; - send_trap = true; - Core::System::GetInstance().InvalidateCpuInstructionCaches(); -} - -/// Tell the CPU if we hit a memory breakpoint. -bool IsMemoryBreak() { - if (!IsConnected()) { - return false; - } - - return memory_break; -} - -/// Tell the CPU to continue executing. -static void Continue() { - memory_break = false; - step_loop = false; - halt_loop = false; - Core::System::GetInstance().InvalidateCpuInstructionCaches(); -} - -/** - * Commit breakpoint to list of breakpoints. - * - * @param type Type of breakpoint. - * @param addr Address of breakpoint. - * @param len Length of breakpoint. - */ -static bool CommitBreakpoint(BreakpointType type, VAddr addr, u64 len) { - BreakpointMap& p = GetBreakpointMap(type); - - Breakpoint breakpoint; - breakpoint.active = true; - breakpoint.addr = addr; - breakpoint.len = len; - - auto& system = Core::System::GetInstance(); - auto& memory = system.Memory(); - memory.ReadBlock(addr, breakpoint.inst.data(), breakpoint.inst.size()); - - static constexpr std::array btrap{0x00, 0x7d, 0x20, 0xd4}; - if (type == BreakpointType::Execute) { - memory.WriteBlock(addr, btrap.data(), btrap.size()); - system.InvalidateCpuInstructionCaches(); - } - p.insert({addr, breakpoint}); - - LOG_DEBUG(Debug_GDBStub, "gdb: added {} breakpoint: {:016X} bytes at {:016X}", - static_cast(type), breakpoint.len, breakpoint.addr); - - return true; -} - -/// Handle add breakpoint command from gdb client. -static void AddBreakpoint() { - BreakpointType type; - - u8 type_id = HexCharToValue(command_buffer[1]); - switch (type_id) { - case 0: - case 1: - type = BreakpointType::Execute; - break; - case 2: - type = BreakpointType::Write; - break; - case 3: - type = BreakpointType::Read; - break; - case 4: - type = BreakpointType::Access; - break; - default: - return SendReply("E01"); - } - - auto start_offset = command_buffer + 3; - auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); - VAddr addr = HexToLong(start_offset, static_cast(addr_pos - start_offset)); - - start_offset = addr_pos + 1; - u64 len = - HexToLong(start_offset, static_cast((command_buffer + command_length) - start_offset)); - - if (type == BreakpointType::Access) { - // Access is made up of Read and Write types, so add both breakpoints - type = BreakpointType::Read; - - if (!CommitBreakpoint(type, addr, len)) { - return SendReply("E02"); - } - - type = BreakpointType::Write; - } - - if (!CommitBreakpoint(type, addr, len)) { - return SendReply("E02"); - } - - SendReply("OK"); -} - -/// Handle remove breakpoint command from gdb client. -static void RemoveBreakpoint() { - BreakpointType type; - - u8 type_id = HexCharToValue(command_buffer[1]); - switch (type_id) { - case 0: - case 1: - type = BreakpointType::Execute; - break; - case 2: - type = BreakpointType::Write; - break; - case 3: - type = BreakpointType::Read; - break; - case 4: - type = BreakpointType::Access; - break; - default: - return SendReply("E01"); - } - - auto start_offset = command_buffer + 3; - auto addr_pos = std::find(start_offset, command_buffer + command_length, ','); - VAddr addr = HexToLong(start_offset, static_cast(addr_pos - start_offset)); - - if (type == BreakpointType::Access) { - // Access is made up of Read and Write types, so add both breakpoints - type = BreakpointType::Read; - RemoveBreakpoint(type, addr); - - type = BreakpointType::Write; - } - - RemoveBreakpoint(type, addr); - SendReply("OK"); -} - -void HandlePacket() { - if (!IsConnected()) { - if (defer_start) { - ToggleServer(true); - } - return; - } - - if (!IsDataAvailable()) { - return; - } - - ReadCommand(); - if (command_length == 0) { - return; - } - - LOG_DEBUG(Debug_GDBStub, "Packet: {}", command_buffer); - - switch (command_buffer[0]) { - case 'q': - HandleQuery(); - break; - case 'H': - HandleSetThread(); - break; - case '?': - SendSignal(current_thread, latest_signal); - break; - case 'k': - Shutdown(); - LOG_INFO(Debug_GDBStub, "killed by gdb"); - return; - case 'g': - ReadRegisters(); - break; - case 'G': - WriteRegisters(); - break; - case 'p': - ReadRegister(); - break; - case 'P': - WriteRegister(); - break; - case 'm': - ReadMemory(); - break; - case 'M': - WriteMemory(); - break; - case 's': - Step(); - return; - case 'C': - case 'c': - Continue(); - return; - case 'z': - RemoveBreakpoint(); - break; - case 'Z': - AddBreakpoint(); - break; - case 'T': - HandleThreadAlive(); - break; - default: - SendReply(""); - break; - } -} - -void SetServerPort(u16 port) { - gdbstub_port = port; -} - -void ToggleServer(bool status) { - if (status) { - server_enabled = status; - - // Start server - if (!IsConnected() && Core::System::GetInstance().IsPoweredOn()) { - Init(); - } - } else { - // Stop server - if (IsConnected()) { - Shutdown(); - } - - server_enabled = status; - } -} - -void DeferStart() { - defer_start = true; -} - -static void Init(u16 port) { - if (!server_enabled) { - // Set the halt loop to false in case the user enabled the gdbstub mid-execution. - // This way the CPU can still execute normally. - halt_loop = false; - step_loop = false; - return; - } - - // Setup initial gdbstub status - halt_loop = true; - step_loop = false; - - breakpoints_execute.clear(); - breakpoints_read.clear(); - breakpoints_write.clear(); - - modules.clear(); - - // Start gdb server - LOG_INFO(Debug_GDBStub, "Starting GDB server on port {}...", port); - - sockaddr_in saddr_server = {}; - saddr_server.sin_family = AF_INET; - saddr_server.sin_port = htons(port); - saddr_server.sin_addr.s_addr = INADDR_ANY; - -#ifdef _WIN32 - WSAStartup(MAKEWORD(2, 2), &InitData); -#endif - - int tmpsock = static_cast(socket(PF_INET, SOCK_STREAM, 0)); - if (tmpsock == -1) { - LOG_ERROR(Debug_GDBStub, "Failed to create gdb socket"); - } - - // Set socket to SO_REUSEADDR so it can always bind on the same port - int reuse_enabled = 1; - if (setsockopt(tmpsock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse_enabled, - sizeof(reuse_enabled)) < 0) { - LOG_ERROR(Debug_GDBStub, "Failed to set gdb socket option"); - } - - const sockaddr* server_addr = reinterpret_cast(&saddr_server); - socklen_t server_addrlen = sizeof(saddr_server); - if (bind(tmpsock, server_addr, server_addrlen) < 0) { - LOG_ERROR(Debug_GDBStub, "Failed to bind gdb socket"); - } - - if (listen(tmpsock, 1) < 0) { - LOG_ERROR(Debug_GDBStub, "Failed to listen to gdb socket"); - } - - // Wait for gdb to connect - LOG_INFO(Debug_GDBStub, "Waiting for gdb to connect..."); - sockaddr_in saddr_client; - sockaddr* client_addr = reinterpret_cast(&saddr_client); - socklen_t client_addrlen = sizeof(saddr_client); - gdbserver_socket = static_cast(accept(tmpsock, client_addr, &client_addrlen)); - if (gdbserver_socket < 0) { - // In the case that we couldn't start the server for whatever reason, just start CPU - // execution like normal. - halt_loop = false; - step_loop = false; - - LOG_ERROR(Debug_GDBStub, "Failed to accept gdb client"); - } else { - LOG_INFO(Debug_GDBStub, "Client connected."); - saddr_client.sin_addr.s_addr = ntohl(saddr_client.sin_addr.s_addr); - } - - // Clean up temporary socket if it's still alive at this point. - if (tmpsock != -1) { - shutdown(tmpsock, SHUT_RDWR); - } -} - -void Init() { - Init(gdbstub_port); -} - -void Shutdown() { - if (!server_enabled) { - return; - } - defer_start = false; - - LOG_INFO(Debug_GDBStub, "Stopping GDB ..."); - if (gdbserver_socket != -1) { - shutdown(gdbserver_socket, SHUT_RDWR); - gdbserver_socket = -1; - } - -#ifdef _WIN32 - WSACleanup(); -#endif - - LOG_INFO(Debug_GDBStub, "GDB stopped."); -} - -bool IsServerEnabled() { - return server_enabled; -} - -bool IsConnected() { - return IsServerEnabled() && gdbserver_socket != -1; -} - -bool GetCpuHaltFlag() { - return halt_loop; -} - -bool GetCpuStepFlag() { - return step_loop; -} - -void SetCpuStepFlag(bool is_step) { - step_loop = is_step; -} - -void SendTrap(Kernel::Thread* thread, int trap) { - if (!send_trap) { - return; - } - - current_thread = thread; - SendSignal(thread, trap); - - halt_loop = true; - send_trap = false; -} -}; // namespace GDBStub diff --git a/src/core/gdbstub/gdbstub.h b/src/core/gdbstub/gdbstub.h deleted file mode 100644 index 8fe3c320b..000000000 --- a/src/core/gdbstub/gdbstub.h +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -// Originally written by Sven Peter for anergistic. - -#pragma once - -#include -#include "common/common_types.h" -#include "core/hle/kernel/thread.h" - -namespace GDBStub { - -/// Breakpoint Method -enum class BreakpointType { - None, ///< None - Execute, ///< Execution Breakpoint - Read, ///< Read Breakpoint - Write, ///< Write Breakpoint - Access ///< Access (R/W) Breakpoint -}; - -struct BreakpointAddress { - VAddr address; - BreakpointType type; -}; - -/** - * Set the port the gdbstub should use to listen for connections. - * - * @param port Port to listen for connection - */ -void SetServerPort(u16 port); - -/** - * Starts or stops the server if possible. - * - * @param status Set the server to enabled or disabled. - */ -void ToggleServer(bool status); - -/// Start the gdbstub server. -void Init(); - -/** - * Defer initialization of the gdbstub to the first packet processing functions. - * This avoids a case where the gdbstub thread is frozen after initialization - * and fails to respond in time to packets. - */ -void DeferStart(); - -/// Stop gdbstub server. -void Shutdown(); - -/// Checks if the gdbstub server is enabled. -bool IsServerEnabled(); - -/// Returns true if there is an active socket connection. -bool IsConnected(); - -/// Register module. -void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext = true); - -/** - * Signal to the gdbstub server that it should halt CPU execution. - * - * @param is_memory_break If true, the break resulted from a memory breakpoint. - */ -void Break(bool is_memory_break = false); - -/// Determine if there was a memory breakpoint. -bool IsMemoryBreak(); - -/// Read and handle packet from gdb client. -void HandlePacket(); - -/** - * Get the nearest breakpoint of the specified type at the given address. - * - * @param addr Address to search from. - * @param type Type of breakpoint. - */ -BreakpointAddress GetNextBreakpointFromAddress(VAddr addr, GDBStub::BreakpointType type); - -/** - * Check if a breakpoint of the specified type exists at the given address. - * - * @param addr Address of breakpoint. - * @param type Type of breakpoint. - */ -bool CheckBreakpoint(VAddr addr, GDBStub::BreakpointType type); - -/// If set to true, the CPU will halt at the beginning of the next CPU loop. -bool GetCpuHaltFlag(); - -/// If set to true and the CPU is halted, the CPU will step one instruction. -bool GetCpuStepFlag(); - -/** - * When set to true, the CPU will step one instruction when the CPU is halted next. - * - * @param is_step - */ -void SetCpuStepFlag(bool is_step); - -/** - * Send trap signal from thread back to the gdbstub server. - * - * @param thread Sending thread. - * @param trap Trap no. - */ -void SendTrap(Kernel::Thread* thread, int trap); -} // namespace GDBStub diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 1b503331f..56cc911d1 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -12,7 +12,6 @@ #include #include "common/assert.h" #include "common/common_types.h" -#include "core/core.h" #include "core/hle/ipc.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" @@ -38,10 +37,11 @@ public: explicit RequestHelperBase(Kernel::HLERequestContext& context) : context(&context), cmdbuf(context.CommandBuffer()) {} - void Skip(unsigned size_in_words, bool set_to_null) { - if (set_to_null) + 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; + } + index += static_cast(size_in_words); } /** @@ -49,15 +49,15 @@ public: */ void AlignWithPadding() { if (index & 3) { - Skip(4 - (index & 3), true); + Skip(static_cast(4 - (index & 3)), true); } } - unsigned GetCurrentOffset() const { - return static_cast(index); + u32 GetCurrentOffset() const { + return static_cast(index); } - void SetCurrentOffset(unsigned offset) { + void SetCurrentOffset(u32 offset) { index = static_cast(offset); } }; @@ -72,14 +72,12 @@ public: AlwaysMoveHandles = 1, }; - explicit ResponseBuilder(u32* command_buffer) : RequestHelperBase(command_buffer) {} - explicit ResponseBuilder(Kernel::HLERequestContext& context, u32 normal_params_size, u32 num_handles_to_copy = 0, u32 num_objects_to_move = 0, Flags flags = Flags::None) - : RequestHelperBase(context), normal_params_size(normal_params_size), - num_handles_to_copy(num_handles_to_copy), num_objects_to_move(num_objects_to_move) { + num_handles_to_copy(num_handles_to_copy), + num_objects_to_move(num_objects_to_move), kernel{context.kernel} { memset(cmdbuf, 0, sizeof(u32) * IPC::COMMAND_BUFFER_LENGTH); @@ -89,7 +87,7 @@ public: // The entire size of the raw data section in u32 units, including the 16 bytes of mandatory // padding. - u32 raw_data_size = sizeof(IPC::DataPayloadHeader) / 4 + 4 + normal_params_size; + u64 raw_data_size = sizeof(IPC::DataPayloadHeader) / 4 + 4 + normal_params_size; u32 num_handles_to_move{}; u32 num_domain_objects{}; @@ -105,7 +103,7 @@ public: raw_data_size += sizeof(DomainMessageHeader) / 4 + num_domain_objects; } - header.data_size.Assign(raw_data_size); + header.data_size.Assign(static_cast(raw_data_size)); if (num_handles_to_copy || num_handles_to_move) { header.enable_handle_descriptor.Assign(1); } @@ -139,7 +137,6 @@ public: if (context->Session()->IsDomain()) { context->AddDomainObject(std::move(iface)); } else { - auto& kernel = Core::System::GetInstance().Kernel(); auto [client, server] = Kernel::Session::Create(kernel, iface->GetServiceName()); context->AddMoveObject(std::move(client)); iface->ClientConnected(std::move(server)); @@ -169,8 +166,23 @@ public: ValidateHeader(); } + void PushImpl(s8 value); + void PushImpl(s16 value); + void PushImpl(s32 value); + void PushImpl(s64 value); + void PushImpl(u8 value); + void PushImpl(u16 value); + void PushImpl(u32 value); + void PushImpl(u64 value); + void PushImpl(float value); + void PushImpl(double value); + void PushImpl(bool value); + void PushImpl(ResultCode value); + template - void Push(T value); + void Push(T value) { + return PushImpl(value); + } template void Push(const First& first_value, const Other&... other_values); @@ -213,17 +225,16 @@ private: u32 num_handles_to_copy{}; u32 num_objects_to_move{}; ///< Domain objects or move handles, context dependent std::ptrdiff_t datapayload_index{}; + Kernel::KernelCore& kernel; }; /// Push /// -template <> -inline void ResponseBuilder::Push(s32 value) { +inline void ResponseBuilder::PushImpl(s32 value) { cmdbuf[index++] = static_cast(value); } -template <> -inline void ResponseBuilder::Push(u32 value) { +inline void ResponseBuilder::PushImpl(u32 value) { cmdbuf[index++] = value; } @@ -235,62 +246,52 @@ void ResponseBuilder::PushRaw(const T& value) { index += (sizeof(T) + 3) / 4; // round up to word length } -template <> -inline void ResponseBuilder::Push(ResultCode value) { +inline void ResponseBuilder::PushImpl(ResultCode value) { // Result codes are actually 64-bit in the IPC buffer, but only the high part is discarded. Push(value.raw); Push(0); } -template <> -inline void ResponseBuilder::Push(s8 value) { +inline void ResponseBuilder::PushImpl(s8 value) { PushRaw(value); } -template <> -inline void ResponseBuilder::Push(s16 value) { +inline void ResponseBuilder::PushImpl(s16 value) { PushRaw(value); } -template <> -inline void ResponseBuilder::Push(s64 value) { - Push(static_cast(value)); - Push(static_cast(value >> 32)); +inline void ResponseBuilder::PushImpl(s64 value) { + PushImpl(static_cast(value)); + PushImpl(static_cast(value >> 32)); } -template <> -inline void ResponseBuilder::Push(u8 value) { +inline void ResponseBuilder::PushImpl(u8 value) { PushRaw(value); } -template <> -inline void ResponseBuilder::Push(u16 value) { +inline void ResponseBuilder::PushImpl(u16 value) { PushRaw(value); } -template <> -inline void ResponseBuilder::Push(u64 value) { - Push(static_cast(value)); - Push(static_cast(value >> 32)); +inline void ResponseBuilder::PushImpl(u64 value) { + PushImpl(static_cast(value)); + PushImpl(static_cast(value >> 32)); } -template <> -inline void ResponseBuilder::Push(float value) { +inline void ResponseBuilder::PushImpl(float value) { u32 integral; std::memcpy(&integral, &value, sizeof(u32)); - Push(integral); + PushImpl(integral); } -template <> -inline void ResponseBuilder::Push(double value) { +inline void ResponseBuilder::PushImpl(double value) { u64 integral; std::memcpy(&integral, &value, sizeof(u64)); - Push(integral); + PushImpl(integral); } -template <> -inline void ResponseBuilder::Push(bool value) { - Push(static_cast(value)); +inline void ResponseBuilder::PushImpl(bool value) { + PushImpl(static_cast(value)); } template diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp index b882eaa0f..20ffa7d47 100644 --- a/src/core/hle/kernel/address_arbiter.cpp +++ b/src/core/hle/kernel/address_arbiter.cpp @@ -12,8 +12,9 @@ #include "core/hle/kernel/address_arbiter.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/time_manager.h" #include "core/hle/result.h" @@ -58,7 +59,7 @@ ResultCode AddressArbiter::SignalToAddress(VAddr address, SignalType type, s32 v } ResultCode AddressArbiter::SignalToAddressOnly(VAddr address, s32 num_to_wake) { - SchedulerLock lock(system.Kernel()); + KScopedSchedulerLock lock(system.Kernel()); const std::vector> waiting_threads = GetThreadsWaitingOnAddress(address); WakeThreads(waiting_threads, num_to_wake); @@ -67,7 +68,7 @@ ResultCode AddressArbiter::SignalToAddressOnly(VAddr address, s32 num_to_wake) { ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake) { - SchedulerLock lock(system.Kernel()); + KScopedSchedulerLock lock(system.Kernel()); auto& memory = system.Memory(); // Ensure that we can write to the address. @@ -92,7 +93,7 @@ ResultCode AddressArbiter::IncrementAndSignalToAddressIfEqual(VAddr address, s32 ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 value, s32 num_to_wake) { - SchedulerLock lock(system.Kernel()); + KScopedSchedulerLock lock(system.Kernel()); auto& memory = system.Memory(); // Ensure that we can write to the address. @@ -153,11 +154,11 @@ ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s6 bool should_decrement) { auto& memory = system.Memory(); auto& kernel = system.Kernel(); - Thread* current_thread = system.CurrentScheduler().GetCurrentThread(); + Thread* current_thread = kernel.CurrentScheduler()->GetCurrentThread(); Handle event_handle = InvalidHandle; { - SchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout); + KScopedSchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout); if (current_thread->IsPendingTermination()) { lock.CancelSleep(); @@ -210,7 +211,7 @@ ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s6 } { - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); if (current_thread->IsWaitingForArbitration()) { RemoveThread(SharedFrom(current_thread)); current_thread->WaitForArbitration(false); @@ -223,11 +224,11 @@ ResultCode AddressArbiter::WaitForAddressIfLessThan(VAddr address, s32 value, s6 ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) { auto& memory = system.Memory(); auto& kernel = system.Kernel(); - Thread* current_thread = system.CurrentScheduler().GetCurrentThread(); + Thread* current_thread = kernel.CurrentScheduler()->GetCurrentThread(); Handle event_handle = InvalidHandle; { - SchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout); + KScopedSchedulerLockAndSleep lock(kernel, event_handle, current_thread, timeout); if (current_thread->IsPendingTermination()) { lock.CancelSleep(); @@ -265,7 +266,7 @@ ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 t } { - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); if (current_thread->IsWaitingForArbitration()) { RemoveThread(SharedFrom(current_thread)); current_thread->WaitForArbitration(false); @@ -275,12 +276,6 @@ ResultCode AddressArbiter::WaitForAddressIfEqual(VAddr address, s32 value, s64 t return current_thread->GetSignalingResult(); } -void AddressArbiter::HandleWakeupThread(std::shared_ptr thread) { - ASSERT(thread->GetStatus() == ThreadStatus::WaitArb); - RemoveThread(thread); - thread->SetArbiterWaitAddress(0); -} - void AddressArbiter::InsertThread(std::shared_ptr thread) { const VAddr arb_addr = thread->GetArbiterWaitAddress(); std::list>& thread_list = arb_threads[arb_addr]; diff --git a/src/core/hle/kernel/address_arbiter.h b/src/core/hle/kernel/address_arbiter.h index 0b05d533c..b91edc67d 100644 --- a/src/core/hle/kernel/address_arbiter.h +++ b/src/core/hle/kernel/address_arbiter.h @@ -50,9 +50,6 @@ public: /// Waits on an address with a particular arbitration type. ResultCode WaitForAddress(VAddr address, ArbitrationType type, s32 value, s64 timeout_ns); - /// Removes a thread from the container and resets its address arbiter adress to 0 - void HandleWakeupThread(std::shared_ptr thread); - private: /// Signals an address being waited on. ResultCode SignalToAddressOnly(VAddr address, s32 num_to_wake); diff --git a/src/core/hle/kernel/global_scheduler_context.cpp b/src/core/hle/kernel/global_scheduler_context.cpp new file mode 100644 index 000000000..a133e8ed0 --- /dev/null +++ b/src/core/hle/kernel/global_scheduler_context.cpp @@ -0,0 +1,52 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "common/assert.h" +#include "core/core.h" +#include "core/hle/kernel/global_scheduler_context.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/kernel.h" + +namespace Kernel { + +GlobalSchedulerContext::GlobalSchedulerContext(KernelCore& kernel) + : kernel{kernel}, scheduler_lock{kernel} {} + +GlobalSchedulerContext::~GlobalSchedulerContext() = default; + +void GlobalSchedulerContext::AddThread(std::shared_ptr thread) { + std::scoped_lock lock{global_list_guard}; + thread_list.push_back(std::move(thread)); +} + +void GlobalSchedulerContext::RemoveThread(std::shared_ptr thread) { + std::scoped_lock lock{global_list_guard}; + thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread), + thread_list.end()); +} + +void GlobalSchedulerContext::PreemptThreads() { + // 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 preemption_priorities{ + 59, + 59, + 59, + 63, + }; + + ASSERT(IsLocked()); + for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { + const u32 priority = preemption_priorities[core_id]; + kernel.Scheduler(core_id).RotateScheduledQueue(core_id, priority); + } +} + +bool GlobalSchedulerContext::IsLocked() const { + return scheduler_lock.IsLockedByCurrentThread(); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/global_scheduler_context.h b/src/core/hle/kernel/global_scheduler_context.h new file mode 100644 index 000000000..5c7b89290 --- /dev/null +++ b/src/core/hle/kernel/global_scheduler_context.h @@ -0,0 +1,81 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "common/common_types.h" +#include "common/spin_lock.h" +#include "core/hardware_properties.h" +#include "core/hle/kernel/k_priority_queue.h" +#include "core/hle/kernel/k_scheduler_lock.h" +#include "core/hle/kernel/thread.h" + +namespace Kernel { + +class KernelCore; +class SchedulerLock; + +using KSchedulerPriorityQueue = + KPriorityQueue; +constexpr s32 HighestCoreMigrationAllowedPriority = 2; + +class GlobalSchedulerContext final { + friend class KScheduler; + +public: + using LockType = KAbstractSchedulerLock; + + explicit GlobalSchedulerContext(KernelCore& kernel); + ~GlobalSchedulerContext(); + + /// Adds a new thread to the scheduler + void AddThread(std::shared_ptr thread); + + /// Removes a thread from the scheduler + void RemoveThread(std::shared_ptr thread); + + /// Returns a list of all threads managed by the scheduler + [[nodiscard]] const std::vector>& GetThreadList() const { + return thread_list; + } + + /** + * Rotates the scheduling queues of threads at a preemption priority and then does + * some core rebalancing. Preemption priorities can be found in the array + * 'preemption_priorities'. + * + * @note This operation happens every 10ms. + */ + void PreemptThreads(); + + /// Returns true if the global scheduler lock is acquired + bool IsLocked() const; + + [[nodiscard]] LockType& SchedulerLock() { + return scheduler_lock; + } + + [[nodiscard]] const LockType& SchedulerLock() const { + return scheduler_lock; + } + +private: + friend class KScopedSchedulerLock; + friend class KScopedSchedulerLockAndSleep; + + KernelCore& kernel; + + std::atomic_bool scheduler_update_needed{}; + KSchedulerPriorityQueue priority_queue; + LockType scheduler_lock; + + /// Lists all thread ids that aren't deleted/etc. + std::vector> thread_list; + Common::SpinLock global_list_guard{}; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp index fb30b6f8b..40988b0fd 100644 --- a/src/core/hle/kernel/handle_table.cpp +++ b/src/core/hle/kernel/handle_table.cpp @@ -8,9 +8,9 @@ #include "core/core.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" namespace Kernel { @@ -105,7 +105,7 @@ bool HandleTable::IsValid(Handle handle) const { std::shared_ptr HandleTable::GetGeneric(Handle handle) const { if (handle == CurrentThread) { - return SharedFrom(kernel.CurrentScheduler().GetCurrentThread()); + return SharedFrom(kernel.CurrentScheduler()->GetCurrentThread()); } else if (handle == CurrentProcess) { return SharedFrom(kernel.CurrentProcess()); } @@ -118,7 +118,7 @@ std::shared_ptr HandleTable::GetGeneric(Handle handle) const { void HandleTable::Clear() { for (u16 i = 0; i < table_size; ++i) { - generations[i] = i + 1; + generations[i] = static_cast(i + 1); objects[i] = nullptr; } next_free_slot = 0; diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 81f85643b..83decf6cf 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -17,11 +17,12 @@ #include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/server_session.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/time_manager.h" @@ -45,44 +46,6 @@ void SessionRequestHandler::ClientDisconnected( boost::range::remove_erase(connected_sessions, server_session); } -std::shared_ptr HLERequestContext::SleepClientThread( - const std::string& reason, u64 timeout, WakeupCallback&& callback, - std::shared_ptr writable_event) { - // Put the client thread to sleep until the wait event is signaled or the timeout expires. - - if (!writable_event) { - // Create event if not provided - const auto pair = WritableEvent::CreateEventPair(kernel, "HLE Pause Event: " + reason); - writable_event = pair.writable; - } - - { - Handle event_handle = InvalidHandle; - SchedulerLockAndSleep lock(kernel, event_handle, thread.get(), timeout); - thread->SetHLECallback( - [context = *this, callback](std::shared_ptr thread) mutable -> bool { - ThreadWakeupReason reason = thread->GetSignalingResult() == RESULT_TIMEOUT - ? ThreadWakeupReason::Timeout - : ThreadWakeupReason::Signal; - callback(thread, context, reason); - context.WriteToOutgoingCommandBuffer(*thread); - return true; - }); - const auto readable_event{writable_event->GetReadableEvent()}; - writable_event->Clear(); - thread->SetHLESyncObject(readable_event.get()); - thread->SetStatus(ThreadStatus::WaitHLEEvent); - thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT); - readable_event->AddWaitingThread(thread); - lock.Release(); - thread->SetHLETimeEvent(event_handle); - } - - is_thread_waiting = true; - - return writable_event; -} - HLERequestContext::HLERequestContext(KernelCore& kernel, Core::Memory::Memory& memory, std::shared_ptr server_session, std::shared_ptr thread) diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index f3277b766..b112e1ebd 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -24,6 +24,10 @@ namespace Core::Memory { class Memory; } +namespace IPC { +class ResponseBuilder; +} + namespace Service { class ServiceFrameworkBase; } @@ -125,23 +129,6 @@ public: using WakeupCallback = std::function thread, HLERequestContext& context, ThreadWakeupReason reason)>; - /** - * Puts the specified guest thread to sleep until the returned event is signaled or until the - * specified timeout expires. - * @param reason Reason for pausing the thread, to be used for debugging purposes. - * @param timeout Timeout in nanoseconds after which the thread will be awoken and the callback - * invoked with a Timeout reason. - * @param callback Callback to be invoked when the thread is resumed. This callback must write - * the entire command response once again, regardless of the state of it before this function - * was called. - * @param writable_event Event to use to wake up the thread. If unspecified, an event will be - * created. - * @returns Event that when signaled will resume the thread and call the callback function. - */ - std::shared_ptr SleepClientThread( - const std::string& reason, u64 timeout, WakeupCallback&& callback, - std::shared_ptr writable_event = nullptr); - /// Populates this context with data from the requesting process/thread. ResultCode PopulateFromIncomingCommandBuffer(const HandleTable& handle_table, u32_le* src_cmdbuf); @@ -287,6 +274,8 @@ public: } private: + friend class IPC::ResponseBuilder; + void ParseCommandBuffer(const HandleTable& handle_table, u32_le* src_cmdbuf, bool incoming); std::array cmd_buf; diff --git a/src/core/hle/kernel/k_affinity_mask.h b/src/core/hle/kernel/k_affinity_mask.h new file mode 100644 index 000000000..dd73781cd --- /dev/null +++ b/src/core/hle/kernel/k_affinity_mask.h @@ -0,0 +1,58 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#pragma once + +#include "common/assert.h" +#include "common/common_types.h" +#include "core/hardware_properties.h" + +namespace Kernel { + +class KAffinityMask { +public: + constexpr KAffinityMask() = default; + + [[nodiscard]] constexpr u64 GetAffinityMask() const { + return this->mask; + } + + constexpr void SetAffinityMask(u64 new_mask) { + ASSERT((new_mask & ~AllowedAffinityMask) == 0); + this->mask = new_mask; + } + + [[nodiscard]] constexpr bool GetAffinity(s32 core) const { + return this->mask & GetCoreBit(core); + } + + constexpr void SetAffinity(s32 core, bool set) { + ASSERT(0 <= core && core < static_cast(Core::Hardware::NUM_CPU_CORES)); + + if (set) { + this->mask |= GetCoreBit(core); + } else { + this->mask &= ~GetCoreBit(core); + } + } + + constexpr void SetAll() { + this->mask = AllowedAffinityMask; + } + +private: + [[nodiscard]] static constexpr u64 GetCoreBit(s32 core) { + ASSERT(0 <= core && core < static_cast(Core::Hardware::NUM_CPU_CORES)); + return (1ULL << core); + } + + static constexpr u64 AllowedAffinityMask = (1ULL << Core::Hardware::NUM_CPU_CORES) - 1; + + u64 mask{}; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_priority_queue.h b/src/core/hle/kernel/k_priority_queue.h new file mode 100644 index 000000000..99fb8fe93 --- /dev/null +++ b/src/core/hle/kernel/k_priority_queue.h @@ -0,0 +1,451 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#pragma once + +#include +#include + +#include "common/assert.h" +#include "common/bit_set.h" +#include "common/bit_util.h" +#include "common/common_types.h" +#include "common/concepts.h" + +namespace Kernel { + +class Thread; + +template +concept KPriorityQueueAffinityMask = !std::is_reference_v && requires(T & t) { + { t.GetAffinityMask() } + ->Common::ConvertibleTo; + {t.SetAffinityMask(std::declval())}; + + { t.GetAffinity(std::declval()) } + ->std::same_as; + {t.SetAffinity(std::declval(), std::declval())}; + {t.SetAll()}; +}; + +template +concept KPriorityQueueMember = !std::is_reference_v && requires(T & t) { + {typename T::QueueEntry()}; + {(typename T::QueueEntry()).Initialize()}; + {(typename T::QueueEntry()).SetPrev(std::addressof(t))}; + {(typename T::QueueEntry()).SetNext(std::addressof(t))}; + { (typename T::QueueEntry()).GetNext() } + ->std::same_as; + { (typename T::QueueEntry()).GetPrev() } + ->std::same_as; + { t.GetPriorityQueueEntry(std::declval()) } + ->std::same_as; + + {t.GetAffinityMask()}; + { typename std::remove_cvref::type() } + ->KPriorityQueueAffinityMask; + + { t.GetActiveCore() } + ->Common::ConvertibleTo; + { t.GetPriority() } + ->Common::ConvertibleTo; +}; + +template +requires KPriorityQueueMember class KPriorityQueue { +public: + using AffinityMaskType = typename std::remove_cv_t< + typename std::remove_reference().GetAffinityMask())>::type>; + + static_assert(LowestPriority >= 0); + static_assert(HighestPriority >= 0); + static_assert(LowestPriority >= HighestPriority); + static constexpr size_t NumPriority = LowestPriority - HighestPriority + 1; + static constexpr size_t NumCores = _NumCores; + + static constexpr bool IsValidCore(s32 core) { + return 0 <= core && core < static_cast(NumCores); + } + + static constexpr bool IsValidPriority(s32 priority) { + return HighestPriority <= priority && priority <= LowestPriority + 1; + } + +private: + using Entry = typename Member::QueueEntry; + +public: + class KPerCoreQueue { + private: + std::array root{}; + + public: + constexpr KPerCoreQueue() { + for (auto& per_core_root : root) { + per_core_root.Initialize(); + } + } + + constexpr bool PushBack(s32 core, Member* member) { + // Get the entry associated with the member. + Entry& member_entry = member->GetPriorityQueueEntry(core); + + // Get the entry associated with the end of the queue. + Member* tail = this->root[core].GetPrev(); + Entry& tail_entry = + (tail != nullptr) ? tail->GetPriorityQueueEntry(core) : this->root[core]; + + // Link the entries. + member_entry.SetPrev(tail); + member_entry.SetNext(nullptr); + tail_entry.SetNext(member); + this->root[core].SetPrev(member); + + return tail == nullptr; + } + + constexpr bool PushFront(s32 core, Member* member) { + // Get the entry associated with the member. + Entry& member_entry = member->GetPriorityQueueEntry(core); + + // Get the entry associated with the front of the queue. + Member* head = this->root[core].GetNext(); + Entry& head_entry = + (head != nullptr) ? head->GetPriorityQueueEntry(core) : this->root[core]; + + // Link the entries. + member_entry.SetPrev(nullptr); + member_entry.SetNext(head); + head_entry.SetPrev(member); + this->root[core].SetNext(member); + + return (head == nullptr); + } + + constexpr bool Remove(s32 core, Member* member) { + // Get the entry associated with the member. + Entry& member_entry = member->GetPriorityQueueEntry(core); + + // Get the entries associated with next and prev. + Member* prev = member_entry.GetPrev(); + Member* next = member_entry.GetNext(); + Entry& prev_entry = + (prev != nullptr) ? prev->GetPriorityQueueEntry(core) : this->root[core]; + Entry& next_entry = + (next != nullptr) ? next->GetPriorityQueueEntry(core) : this->root[core]; + + // Unlink. + prev_entry.SetNext(next); + next_entry.SetPrev(prev); + + return (this->GetFront(core) == nullptr); + } + + constexpr Member* GetFront(s32 core) const { + return this->root[core].GetNext(); + } + }; + + class KPriorityQueueImpl { + public: + constexpr KPriorityQueueImpl() = default; + + constexpr void PushBack(s32 priority, s32 core, Member* member) { + ASSERT(IsValidCore(core)); + ASSERT(IsValidPriority(priority)); + + if (priority > LowestPriority) { + return; + } + + if (this->queues[priority].PushBack(core, member)) { + this->available_priorities[core].SetBit(priority); + } + } + + constexpr void PushFront(s32 priority, s32 core, Member* member) { + ASSERT(IsValidCore(core)); + ASSERT(IsValidPriority(priority)); + + if (priority > LowestPriority) { + return; + } + + if (this->queues[priority].PushFront(core, member)) { + this->available_priorities[core].SetBit(priority); + } + } + + constexpr void Remove(s32 priority, s32 core, Member* member) { + ASSERT(IsValidCore(core)); + ASSERT(IsValidPriority(priority)); + + if (priority > LowestPriority) { + return; + } + + if (this->queues[priority].Remove(core, member)) { + this->available_priorities[core].ClearBit(priority); + } + } + + constexpr Member* GetFront(s32 core) const { + ASSERT(IsValidCore(core)); + + const s32 priority = + static_cast(this->available_priorities[core].CountLeadingZero()); + if (priority <= LowestPriority) { + return this->queues[priority].GetFront(core); + } else { + return nullptr; + } + } + + constexpr Member* GetFront(s32 priority, s32 core) const { + ASSERT(IsValidCore(core)); + ASSERT(IsValidPriority(priority)); + + if (priority <= LowestPriority) { + return this->queues[priority].GetFront(core); + } else { + return nullptr; + } + } + + constexpr Member* GetNext(s32 core, const Member* member) const { + ASSERT(IsValidCore(core)); + + Member* next = member->GetPriorityQueueEntry(core).GetNext(); + if (next == nullptr) { + const s32 priority = static_cast( + this->available_priorities[core].GetNextSet(member->GetPriority())); + if (priority <= LowestPriority) { + next = this->queues[priority].GetFront(core); + } + } + return next; + } + + constexpr void MoveToFront(s32 priority, s32 core, Member* member) { + ASSERT(IsValidCore(core)); + ASSERT(IsValidPriority(priority)); + + if (priority <= LowestPriority) { + this->queues[priority].Remove(core, member); + this->queues[priority].PushFront(core, member); + } + } + + constexpr Member* MoveToBack(s32 priority, s32 core, Member* member) { + ASSERT(IsValidCore(core)); + ASSERT(IsValidPriority(priority)); + + if (priority <= LowestPriority) { + this->queues[priority].Remove(core, member); + this->queues[priority].PushBack(core, member); + return this->queues[priority].GetFront(core); + } else { + return nullptr; + } + } + + private: + std::array queues{}; + std::array, NumCores> available_priorities{}; + }; + +private: + KPriorityQueueImpl scheduled_queue; + KPriorityQueueImpl suggested_queue; + +private: + constexpr void ClearAffinityBit(u64& affinity, s32 core) { + affinity &= ~(u64(1) << core); + } + + constexpr s32 GetNextCore(u64& affinity) { + const s32 core = Common::CountTrailingZeroes64(affinity); + ClearAffinityBit(affinity, core); + return core; + } + + constexpr void PushBack(s32 priority, Member* member) { + ASSERT(IsValidPriority(priority)); + + // Push onto the scheduled queue for its core, if we can. + u64 affinity = member->GetAffinityMask().GetAffinityMask(); + if (const s32 core = member->GetActiveCore(); core >= 0) { + this->scheduled_queue.PushBack(priority, core, member); + ClearAffinityBit(affinity, core); + } + + // And suggest the thread for all other cores. + while (affinity) { + this->suggested_queue.PushBack(priority, GetNextCore(affinity), member); + } + } + + constexpr void PushFront(s32 priority, Member* member) { + ASSERT(IsValidPriority(priority)); + + // Push onto the scheduled queue for its core, if we can. + u64 affinity = member->GetAffinityMask().GetAffinityMask(); + if (const s32 core = member->GetActiveCore(); core >= 0) { + this->scheduled_queue.PushFront(priority, core, member); + ClearAffinityBit(affinity, core); + } + + // And suggest the thread for all other cores. + // Note: Nintendo pushes onto the back of the suggested queue, not the front. + while (affinity) { + this->suggested_queue.PushBack(priority, GetNextCore(affinity), member); + } + } + + constexpr void Remove(s32 priority, Member* member) { + ASSERT(IsValidPriority(priority)); + + // Remove from the scheduled queue for its core. + u64 affinity = member->GetAffinityMask().GetAffinityMask(); + if (const s32 core = member->GetActiveCore(); core >= 0) { + this->scheduled_queue.Remove(priority, core, member); + ClearAffinityBit(affinity, core); + } + + // Remove from the suggested queue for all other cores. + while (affinity) { + this->suggested_queue.Remove(priority, GetNextCore(affinity), member); + } + } + +public: + constexpr KPriorityQueue() = default; + + // Getters. + constexpr Member* GetScheduledFront(s32 core) const { + return this->scheduled_queue.GetFront(core); + } + + constexpr Member* GetScheduledFront(s32 core, s32 priority) const { + return this->scheduled_queue.GetFront(priority, core); + } + + constexpr Member* GetSuggestedFront(s32 core) const { + return this->suggested_queue.GetFront(core); + } + + constexpr Member* GetSuggestedFront(s32 core, s32 priority) const { + return this->suggested_queue.GetFront(priority, core); + } + + constexpr Member* GetScheduledNext(s32 core, const Member* member) const { + return this->scheduled_queue.GetNext(core, member); + } + + constexpr Member* GetSuggestedNext(s32 core, const Member* member) const { + return this->suggested_queue.GetNext(core, member); + } + + constexpr Member* GetSamePriorityNext(s32 core, const Member* member) const { + return member->GetPriorityQueueEntry(core).GetNext(); + } + + // Mutators. + constexpr void PushBack(Member* member) { + this->PushBack(member->GetPriority(), member); + } + + constexpr void Remove(Member* member) { + this->Remove(member->GetPriority(), member); + } + + constexpr void MoveToScheduledFront(Member* member) { + this->scheduled_queue.MoveToFront(member->GetPriority(), member->GetActiveCore(), member); + } + + constexpr Thread* MoveToScheduledBack(Member* member) { + return this->scheduled_queue.MoveToBack(member->GetPriority(), member->GetActiveCore(), + member); + } + + // First class fancy operations. + constexpr void ChangePriority(s32 prev_priority, bool is_running, Member* member) { + ASSERT(IsValidPriority(prev_priority)); + + // Remove the member from the queues. + const s32 new_priority = member->GetPriority(); + this->Remove(prev_priority, member); + + // And enqueue. If the member is running, we want to keep it running. + if (is_running) { + this->PushFront(new_priority, member); + } else { + this->PushBack(new_priority, member); + } + } + + constexpr void ChangeAffinityMask(s32 prev_core, const AffinityMaskType& prev_affinity, + Member* member) { + // Get the new information. + const s32 priority = member->GetPriority(); + const AffinityMaskType& new_affinity = member->GetAffinityMask(); + const s32 new_core = member->GetActiveCore(); + + // Remove the member from all queues it was in before. + for (s32 core = 0; core < static_cast(NumCores); core++) { + if (prev_affinity.GetAffinity(core)) { + if (core == prev_core) { + this->scheduled_queue.Remove(priority, core, member); + } else { + this->suggested_queue.Remove(priority, core, member); + } + } + } + + // And add the member to all queues it should be in now. + for (s32 core = 0; core < static_cast(NumCores); core++) { + if (new_affinity.GetAffinity(core)) { + if (core == new_core) { + this->scheduled_queue.PushBack(priority, core, member); + } else { + this->suggested_queue.PushBack(priority, core, member); + } + } + } + } + + constexpr void ChangeCore(s32 prev_core, Member* member, bool to_front = false) { + // Get the new information. + const s32 new_core = member->GetActiveCore(); + const s32 priority = member->GetPriority(); + + // We don't need to do anything if the core is the same. + if (prev_core != new_core) { + // Remove from the scheduled queue for the previous core. + if (prev_core >= 0) { + this->scheduled_queue.Remove(priority, prev_core, member); + } + + // Remove from the suggested queue and add to the scheduled queue for the new core. + if (new_core >= 0) { + this->suggested_queue.Remove(priority, new_core, member); + if (to_front) { + this->scheduled_queue.PushFront(priority, new_core, member); + } else { + this->scheduled_queue.PushBack(priority, new_core, member); + } + } + + // Add to the suggested queue for the previous core. + if (prev_core >= 0) { + this->suggested_queue.PushBack(priority, prev_core, member); + } + } + } +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp new file mode 100644 index 000000000..c5fd82a6b --- /dev/null +++ b/src/core/hle/kernel/k_scheduler.cpp @@ -0,0 +1,784 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#include "common/assert.h" +#include "common/bit_util.h" +#include "common/fiber.h" +#include "common/logging/log.h" +#include "core/arm/arm_interface.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/cpu_manager.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/physical_core.h" +#include "core/hle/kernel/process.h" +#include "core/hle/kernel/thread.h" +#include "core/hle/kernel/time_manager.h" + +namespace Kernel { + +static void IncrementScheduledCount(Kernel::Thread* thread) { + if (auto process = thread->GetOwnerProcess(); process) { + process->IncrementScheduledCount(); + } +} + +void KScheduler::RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule, + Core::EmuThreadHandle global_thread) { + u32 current_core = global_thread.host_handle; + bool must_context_switch = global_thread.guest_handle != InvalidHandle && + (current_core < Core::Hardware::NUM_CPU_CORES); + + while (cores_pending_reschedule != 0) { + u32 core = Common::CountTrailingZeroes64(cores_pending_reschedule); + ASSERT(core < Core::Hardware::NUM_CPU_CORES); + if (!must_context_switch || core != current_core) { + auto& phys_core = kernel.PhysicalCore(core); + phys_core.Interrupt(); + } else { + must_context_switch = true; + } + cores_pending_reschedule &= ~(1ULL << core); + } + if (must_context_switch) { + auto core_scheduler = kernel.CurrentScheduler(); + kernel.ExitSVCProfile(); + core_scheduler->RescheduleCurrentCore(); + kernel.EnterSVCProfile(); + } +} + +u64 KScheduler::UpdateHighestPriorityThread(Thread* highest_thread) { + std::scoped_lock lock{guard}; + if (Thread* prev_highest_thread = this->state.highest_priority_thread; + prev_highest_thread != highest_thread) { + if (prev_highest_thread != nullptr) { + IncrementScheduledCount(prev_highest_thread); + prev_highest_thread->SetLastScheduledTick(system.CoreTiming().GetCPUTicks()); + } + if (this->state.should_count_idle) { + if (highest_thread != nullptr) { + // if (Process* process = highest_thread->GetOwnerProcess(); process != nullptr) { + // process->SetRunningThread(this->core_id, highest_thread, + // this->state.idle_count); + //} + } else { + this->state.idle_count++; + } + } + + this->state.highest_priority_thread = highest_thread; + this->state.needs_scheduling = true; + return (1ULL << this->core_id); + } else { + return 0; + } +} + +u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + // Clear that we need to update. + ClearSchedulerUpdateNeeded(kernel); + + u64 cores_needing_scheduling = 0, idle_cores = 0; + Thread* top_threads[Core::Hardware::NUM_CPU_CORES]; + auto& priority_queue = GetPriorityQueue(kernel); + + /// We want to go over all cores, finding the highest priority thread and determining if + /// scheduling is needed for that core. + for (size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { + Thread* top_thread = priority_queue.GetScheduledFront(static_cast(core_id)); + if (top_thread != nullptr) { + // If the thread has no waiters, we need to check if the process has a thread pinned. + // TODO(bunnei): Implement thread pinning + } else { + idle_cores |= (1ULL << core_id); + } + + top_threads[core_id] = top_thread; + cores_needing_scheduling |= + kernel.Scheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]); + } + + // Idle cores are bad. We're going to try to migrate threads to each idle core in turn. + while (idle_cores != 0) { + u32 core_id = Common::CountTrailingZeroes64(idle_cores); + if (Thread* suggested = priority_queue.GetSuggestedFront(core_id); suggested != nullptr) { + s32 migration_candidates[Core::Hardware::NUM_CPU_CORES]; + size_t num_candidates = 0; + + // While we have a suggested thread, try to migrate it! + while (suggested != nullptr) { + // Check if the suggested thread is the top thread on its core. + const s32 suggested_core = suggested->GetActiveCore(); + if (Thread* top_thread = + (suggested_core >= 0) ? top_threads[suggested_core] : nullptr; + top_thread != suggested) { + // Make sure we're not dealing with threads too high priority for migration. + if (top_thread != nullptr && + top_thread->GetPriority() < HighestCoreMigrationAllowedPriority) { + break; + } + + // The suggested thread isn't bound to its core, so we can migrate it! + 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]); + break; + } + + // Note this core as a candidate for migration. + ASSERT(num_candidates < Core::Hardware::NUM_CPU_CORES); + migration_candidates[num_candidates++] = suggested_core; + suggested = priority_queue.GetSuggestedNext(core_id, suggested); + } + + // If suggested is nullptr, we failed to migrate a specific thread. So let's try all our + // candidate cores' top threads. + if (suggested == nullptr) { + for (size_t i = 0; i < num_candidates; i++) { + // Check if there's some other thread that can run on the candidate core. + const s32 candidate_core = migration_candidates[i]; + suggested = top_threads[candidate_core]; + if (Thread* next_on_candidate_core = + priority_queue.GetScheduledNext(candidate_core, suggested); + next_on_candidate_core != nullptr) { + // 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]); + + // 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]); + break; + } + } + } + } + + idle_cores &= ~(1ULL << core_id); + } + + return cores_needing_scheduling; +} + +void KScheduler::OnThreadStateChanged(KernelCore& kernel, Thread* thread, u32 old_state) { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + // Check if the state has changed, because if it hasn't there's nothing to do. + const auto cur_state = thread->scheduling_state; + if (cur_state == old_state) { + return; + } + + // Update the priority queues. + if (old_state == static_cast(ThreadSchedStatus::Runnable)) { + // If we were previously runnable, then we're not runnable now, and we should remove. + GetPriorityQueue(kernel).Remove(thread); + IncrementScheduledCount(thread); + SetSchedulerUpdateNeeded(kernel); + } else if (cur_state == static_cast(ThreadSchedStatus::Runnable)) { + // If we're now runnable, then we weren't previously, and we should add. + GetPriorityQueue(kernel).PushBack(thread); + IncrementScheduledCount(thread); + SetSchedulerUpdateNeeded(kernel); + } +} + +void KScheduler::OnThreadPriorityChanged(KernelCore& kernel, Thread* thread, Thread* current_thread, + u32 old_priority) { + + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + // If the thread is runnable, we want to change its priority in the queue. + if (thread->scheduling_state == static_cast(ThreadSchedStatus::Runnable)) { + GetPriorityQueue(kernel).ChangePriority( + old_priority, thread == kernel.CurrentScheduler()->GetCurrentThread(), thread); + IncrementScheduledCount(thread); + SetSchedulerUpdateNeeded(kernel); + } +} + +void KScheduler::OnThreadAffinityMaskChanged(KernelCore& kernel, Thread* thread, + const KAffinityMask& old_affinity, s32 old_core) { + ASSERT(kernel.GlobalSchedulerContext().IsLocked()); + + // If the thread is runnable, we want to change its affinity in the queue. + if (thread->scheduling_state == static_cast(ThreadSchedStatus::Runnable)) { + GetPriorityQueue(kernel).ChangeAffinityMask(old_core, old_affinity, thread); + IncrementScheduledCount(thread); + SetSchedulerUpdateNeeded(kernel); + } +} + +void KScheduler::RotateScheduledQueue(s32 core_id, s32 priority) { + ASSERT(system.GlobalSchedulerContext().IsLocked()); + + // Get a reference to the priority queue. + auto& kernel = system.Kernel(); + auto& priority_queue = GetPriorityQueue(kernel); + + // Rotate the front of the queue to the end. + Thread* top_thread = priority_queue.GetScheduledFront(core_id, priority); + Thread* next_thread = nullptr; + if (top_thread != nullptr) { + next_thread = priority_queue.MoveToScheduledBack(top_thread); + if (next_thread != top_thread) { + IncrementScheduledCount(top_thread); + IncrementScheduledCount(next_thread); + } + } + + // While we have a suggested thread, try to migrate it! + { + Thread* suggested = priority_queue.GetSuggestedFront(core_id, priority); + while (suggested != nullptr) { + // Check if the suggested thread is the top thread on its core. + const s32 suggested_core = suggested->GetActiveCore(); + if (Thread* top_on_suggested_core = + (suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core) + : nullptr; + top_on_suggested_core != suggested) { + // If the next thread is a new thread that has been waiting longer than our + // suggestion, we prefer it to our suggestion. + if (top_thread != next_thread && next_thread != nullptr && + next_thread->GetLastScheduledTick() < suggested->GetLastScheduledTick()) { + suggested = nullptr; + break; + } + + // If we're allowed to do a migration, do one. + // NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the suggestion + // to the front of the queue. + if (top_on_suggested_core == nullptr || + top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) { + suggested->SetActiveCore(core_id); + priority_queue.ChangeCore(suggested_core, suggested, true); + IncrementScheduledCount(suggested); + break; + } + } + + // Get the next suggestion. + suggested = priority_queue.GetSamePriorityNext(core_id, suggested); + } + } + + // Now that we might have migrated a thread with the same priority, check if we can do better. + + { + Thread* best_thread = priority_queue.GetScheduledFront(core_id); + if (best_thread == GetCurrentThread()) { + best_thread = priority_queue.GetScheduledNext(core_id, best_thread); + } + + // If the best thread we can choose has a priority the same or worse than ours, try to + // migrate a higher priority thread. + if (best_thread != nullptr && best_thread->GetPriority() >= static_cast(priority)) { + Thread* suggested = priority_queue.GetSuggestedFront(core_id); + while (suggested != nullptr) { + // If the suggestion's priority is the same as ours, don't bother. + if (suggested->GetPriority() >= best_thread->GetPriority()) { + break; + } + + // Check if the suggested thread is the top thread on its core. + const s32 suggested_core = suggested->GetActiveCore(); + if (Thread* top_on_suggested_core = + (suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core) + : nullptr; + top_on_suggested_core != suggested) { + // If we're allowed to do a migration, do one. + // NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the + // suggestion to the front of the queue. + if (top_on_suggested_core == nullptr || + top_on_suggested_core->GetPriority() >= + HighestCoreMigrationAllowedPriority) { + suggested->SetActiveCore(core_id); + priority_queue.ChangeCore(suggested_core, suggested, true); + IncrementScheduledCount(suggested); + break; + } + } + + // Get the next suggestion. + suggested = priority_queue.GetSuggestedNext(core_id, suggested); + } + } + } + + // After a rotation, we need a scheduler update. + SetSchedulerUpdateNeeded(kernel); +} + +bool KScheduler::CanSchedule(KernelCore& kernel) { + return kernel.CurrentScheduler()->GetCurrentThread()->GetDisableDispatchCount() <= 1; +} + +bool KScheduler::IsSchedulerUpdateNeeded(const KernelCore& kernel) { + return kernel.GlobalSchedulerContext().scheduler_update_needed.load(std::memory_order_acquire); +} + +void KScheduler::SetSchedulerUpdateNeeded(KernelCore& kernel) { + kernel.GlobalSchedulerContext().scheduler_update_needed.store(true, std::memory_order_release); +} + +void KScheduler::ClearSchedulerUpdateNeeded(KernelCore& kernel) { + kernel.GlobalSchedulerContext().scheduler_update_needed.store(false, std::memory_order_release); +} + +void KScheduler::DisableScheduling(KernelCore& kernel) { + if (auto* scheduler = kernel.CurrentScheduler(); scheduler) { + ASSERT(scheduler->GetCurrentThread()->GetDisableDispatchCount() >= 0); + scheduler->GetCurrentThread()->DisableDispatch(); + } +} + +void KScheduler::EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling, + Core::EmuThreadHandle global_thread) { + if (auto* scheduler = kernel.CurrentScheduler(); scheduler) { + scheduler->GetCurrentThread()->EnableDispatch(); + } + RescheduleCores(kernel, cores_needing_scheduling, global_thread); +} + +u64 KScheduler::UpdateHighestPriorityThreads(KernelCore& kernel) { + if (IsSchedulerUpdateNeeded(kernel)) { + return UpdateHighestPriorityThreadsImpl(kernel); + } else { + return 0; + } +} + +KSchedulerPriorityQueue& KScheduler::GetPriorityQueue(KernelCore& kernel) { + return kernel.GlobalSchedulerContext().priority_queue; +} + +void KScheduler::YieldWithoutCoreMigration() { + auto& kernel = system.Kernel(); + + // Validate preconditions. + ASSERT(CanSchedule(kernel)); + ASSERT(kernel.CurrentProcess() != nullptr); + + // Get the current thread and process. + Thread& cur_thread = *GetCurrentThread(); + Process& cur_process = *kernel.CurrentProcess(); + + // If the thread's yield count matches, there's nothing for us to do. + if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) { + return; + } + + // Get a reference to the priority queue. + auto& priority_queue = GetPriorityQueue(kernel); + + // Perform the yield. + { + KScopedSchedulerLock lock(kernel); + + const auto cur_state = cur_thread.scheduling_state; + if (cur_state == static_cast(ThreadSchedStatus::Runnable)) { + // Put the current thread at the back of the queue. + Thread* next_thread = priority_queue.MoveToScheduledBack(std::addressof(cur_thread)); + IncrementScheduledCount(std::addressof(cur_thread)); + + // If the next thread is different, we have an update to perform. + if (next_thread != std::addressof(cur_thread)) { + SetSchedulerUpdateNeeded(kernel); + } else { + // Otherwise, set the thread's yield count so that we won't waste work until the + // process is scheduled again. + cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount()); + } + } + } +} + +void KScheduler::YieldWithCoreMigration() { + auto& kernel = system.Kernel(); + + // Validate preconditions. + ASSERT(CanSchedule(kernel)); + ASSERT(kernel.CurrentProcess() != nullptr); + + // Get the current thread and process. + Thread& cur_thread = *GetCurrentThread(); + Process& cur_process = *kernel.CurrentProcess(); + + // If the thread's yield count matches, there's nothing for us to do. + if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) { + return; + } + + // Get a reference to the priority queue. + auto& priority_queue = GetPriorityQueue(kernel); + + // Perform the yield. + { + KScopedSchedulerLock lock(kernel); + + const auto cur_state = cur_thread.scheduling_state; + if (cur_state == static_cast(ThreadSchedStatus::Runnable)) { + // Get the current active core. + const s32 core_id = cur_thread.GetActiveCore(); + + // Put the current thread at the back of the queue. + Thread* next_thread = priority_queue.MoveToScheduledBack(std::addressof(cur_thread)); + IncrementScheduledCount(std::addressof(cur_thread)); + + // While we have a suggested thread, try to migrate it! + bool recheck = false; + Thread* suggested = priority_queue.GetSuggestedFront(core_id); + while (suggested != nullptr) { + // Check if the suggested thread is the thread running on its core. + const s32 suggested_core = suggested->GetActiveCore(); + + if (Thread* running_on_suggested_core = + (suggested_core >= 0) + ? kernel.Scheduler(suggested_core).state.highest_priority_thread + : nullptr; + running_on_suggested_core != suggested) { + // If the current thread's priority is higher than our suggestion's we prefer + // the next thread to the suggestion. We also prefer the next thread when the + // current thread's priority is equal to the suggestions, but the next thread + // has been waiting longer. + if ((suggested->GetPriority() > cur_thread.GetPriority()) || + (suggested->GetPriority() == cur_thread.GetPriority() && + next_thread != std::addressof(cur_thread) && + next_thread->GetLastScheduledTick() < suggested->GetLastScheduledTick())) { + suggested = nullptr; + break; + } + + // If we're allowed to do a migration, do one. + // NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the + // suggestion to the front of the queue. + if (running_on_suggested_core == nullptr || + running_on_suggested_core->GetPriority() >= + HighestCoreMigrationAllowedPriority) { + suggested->SetActiveCore(core_id); + priority_queue.ChangeCore(suggested_core, suggested, true); + IncrementScheduledCount(suggested); + break; + } else { + // We couldn't perform a migration, but we should check again on a future + // yield. + recheck = true; + } + } + + // Get the next suggestion. + suggested = priority_queue.GetSuggestedNext(core_id, suggested); + } + + // If we still have a suggestion or the next thread is different, we have an update to + // perform. + if (suggested != nullptr || next_thread != std::addressof(cur_thread)) { + SetSchedulerUpdateNeeded(kernel); + } else if (!recheck) { + // Otherwise if we don't need to re-check, set the thread's yield count so that we + // won't waste work until the process is scheduled again. + cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount()); + } + } + } +} + +void KScheduler::YieldToAnyThread() { + auto& kernel = system.Kernel(); + + // Validate preconditions. + ASSERT(CanSchedule(kernel)); + ASSERT(kernel.CurrentProcess() != nullptr); + + // Get the current thread and process. + Thread& cur_thread = *GetCurrentThread(); + Process& cur_process = *kernel.CurrentProcess(); + + // If the thread's yield count matches, there's nothing for us to do. + if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) { + return; + } + + // Get a reference to the priority queue. + auto& priority_queue = GetPriorityQueue(kernel); + + // Perform the yield. + { + KScopedSchedulerLock lock(kernel); + + const auto cur_state = cur_thread.scheduling_state; + if (cur_state == static_cast(ThreadSchedStatus::Runnable)) { + // Get the current active core. + const s32 core_id = cur_thread.GetActiveCore(); + + // Migrate the current thread to core -1. + cur_thread.SetActiveCore(-1); + priority_queue.ChangeCore(core_id, std::addressof(cur_thread)); + IncrementScheduledCount(std::addressof(cur_thread)); + + // If there's nothing scheduled, we can try to perform a migration. + if (priority_queue.GetScheduledFront(core_id) == nullptr) { + // While we have a suggested thread, try to migrate it! + Thread* suggested = priority_queue.GetSuggestedFront(core_id); + while (suggested != nullptr) { + // Check if the suggested thread is the top thread on its core. + const s32 suggested_core = suggested->GetActiveCore(); + if (Thread* top_on_suggested_core = + (suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core) + : nullptr; + top_on_suggested_core != suggested) { + // If we're allowed to do a migration, do one. + if (top_on_suggested_core == nullptr || + top_on_suggested_core->GetPriority() >= + HighestCoreMigrationAllowedPriority) { + suggested->SetActiveCore(core_id); + priority_queue.ChangeCore(suggested_core, suggested); + IncrementScheduledCount(suggested); + } + + // Regardless of whether we migrated, we had a candidate, so we're done. + break; + } + + // Get the next suggestion. + suggested = priority_queue.GetSuggestedNext(core_id, suggested); + } + + // If the suggestion is different from the current thread, we need to perform an + // update. + if (suggested != std::addressof(cur_thread)) { + SetSchedulerUpdateNeeded(kernel); + } else { + // Otherwise, set the thread's yield count so that we won't waste work until the + // process is scheduled again. + cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount()); + } + } else { + // Otherwise, we have an update to perform. + SetSchedulerUpdateNeeded(kernel); + } + } + } +} + +KScheduler::KScheduler(Core::System& system, std::size_t core_id) + : system(system), core_id(core_id) { + switch_fiber = std::make_shared(OnSwitch, this); + this->state.needs_scheduling = true; + this->state.interrupt_task_thread_runnable = false; + this->state.should_count_idle = false; + this->state.idle_count = 0; + this->state.idle_thread_stack = nullptr; + this->state.highest_priority_thread = nullptr; +} + +KScheduler::~KScheduler() = default; + +Thread* KScheduler::GetCurrentThread() const { + if (current_thread) { + return current_thread; + } + return idle_thread; +} + +u64 KScheduler::GetLastContextSwitchTicks() const { + return last_context_switch_time; +} + +void KScheduler::RescheduleCurrentCore() { + ASSERT(GetCurrentThread()->GetDisableDispatchCount() == 1); + + auto& phys_core = system.Kernel().PhysicalCore(core_id); + if (phys_core.IsInterrupted()) { + phys_core.ClearInterrupt(); + } + guard.lock(); + if (this->state.needs_scheduling) { + Schedule(); + } else { + guard.unlock(); + } +} + +void KScheduler::OnThreadStart() { + SwitchContextStep2(); +} + +void KScheduler::Unload(Thread* thread) { + if (thread) { + thread->SetIsRunning(false); + if (thread->IsContinuousOnSVC() && !thread->IsHLEThread()) { + system.ArmInterface(core_id).ExceptionalExit(); + thread->SetContinuousOnSVC(false); + } + if (!thread->IsHLEThread() && !thread->HasExited()) { + Core::ARM_Interface& cpu_core = system.ArmInterface(core_id); + cpu_core.SaveContext(thread->GetContext32()); + cpu_core.SaveContext(thread->GetContext64()); + // Save the TPIDR_EL0 system register in case it was modified. + thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0()); + cpu_core.ClearExclusiveState(); + } + thread->context_guard.unlock(); + } +} + +void KScheduler::Reload(Thread* thread) { + if (thread) { + ASSERT_MSG(thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable, + "Thread must be runnable."); + + // Cancel any outstanding wakeup events for this thread + thread->SetIsRunning(true); + thread->SetWasRunning(false); + + auto* const thread_owner_process = thread->GetOwnerProcess(); + if (thread_owner_process != nullptr) { + system.Kernel().MakeCurrentProcess(thread_owner_process); + } + if (!thread->IsHLEThread()) { + Core::ARM_Interface& cpu_core = system.ArmInterface(core_id); + cpu_core.LoadContext(thread->GetContext32()); + cpu_core.LoadContext(thread->GetContext64()); + cpu_core.SetTlsAddress(thread->GetTLSAddress()); + cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0()); + cpu_core.ClearExclusiveState(); + } + } +} + +void KScheduler::SwitchContextStep2() { + // Load context of new thread + Reload(current_thread); + + RescheduleCurrentCore(); +} + +void KScheduler::ScheduleImpl() { + Thread* previous_thread = current_thread; + current_thread = state.highest_priority_thread; + + this->state.needs_scheduling = false; + + if (current_thread == previous_thread) { + guard.unlock(); + return; + } + + Process* const previous_process = system.Kernel().CurrentProcess(); + + UpdateLastContextSwitchTime(previous_thread, previous_process); + + // Save context for previous thread + Unload(previous_thread); + + std::shared_ptr* old_context; + if (previous_thread != nullptr) { + old_context = &previous_thread->GetHostContext(); + } else { + old_context = &idle_thread->GetHostContext(); + } + guard.unlock(); + + Common::Fiber::YieldTo(*old_context, switch_fiber); + /// When a thread wakes up, the scheduler may have changed to other in another core. + auto& next_scheduler = *system.Kernel().CurrentScheduler(); + next_scheduler.SwitchContextStep2(); +} + +void KScheduler::OnSwitch(void* this_scheduler) { + KScheduler* sched = static_cast(this_scheduler); + sched->SwitchToCurrent(); +} + +void KScheduler::SwitchToCurrent() { + while (true) { + { + std::scoped_lock lock{guard}; + current_thread = state.highest_priority_thread; + this->state.needs_scheduling = false; + } + const auto is_switch_pending = [this] { + std::scoped_lock lock{guard}; + return state.needs_scheduling.load(std::memory_order_relaxed); + }; + do { + if (current_thread != nullptr && !current_thread->IsHLEThread()) { + current_thread->context_guard.lock(); + if (!current_thread->IsRunnable()) { + current_thread->context_guard.unlock(); + break; + } + if (static_cast(current_thread->GetProcessorID()) != core_id) { + current_thread->context_guard.unlock(); + break; + } + } + std::shared_ptr* next_context; + if (current_thread != nullptr) { + next_context = ¤t_thread->GetHostContext(); + } else { + next_context = &idle_thread->GetHostContext(); + } + Common::Fiber::YieldTo(switch_fiber, *next_context); + } while (!is_switch_pending()); + } +} + +void KScheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) { + const u64 prev_switch_ticks = last_context_switch_time; + const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks(); + const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks; + + if (thread != nullptr) { + thread->UpdateCPUTimeTicks(update_ticks); + } + + if (process != nullptr) { + process->UpdateCPUTimeTicks(update_ticks); + } + + last_context_switch_time = most_recent_switch_ticks; +} + +void KScheduler::Initialize() { + std::string name = "Idle Thread Id:" + std::to_string(core_id); + std::function init_func = Core::CpuManager::GetIdleThreadStartFunc(); + void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater(); + ThreadType type = static_cast(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_IDLE); + auto thread_res = Thread::Create(system, type, name, 0, 64, 0, static_cast(core_id), 0, + nullptr, std::move(init_func), init_func_parameter); + idle_thread = thread_res.Unwrap().get(); + + { + KScopedSchedulerLock lock{system.Kernel()}; + idle_thread->SetStatus(ThreadStatus::Ready); + } +} + +KScopedSchedulerLock::KScopedSchedulerLock(KernelCore& kernel) + : KScopedLock(kernel.GlobalSchedulerContext().SchedulerLock()) {} + +KScopedSchedulerLock::~KScopedSchedulerLock() = default; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_scheduler.h b/src/core/hle/kernel/k_scheduler.h new file mode 100644 index 000000000..e84abc84c --- /dev/null +++ b/src/core/hle/kernel/k_scheduler.h @@ -0,0 +1,201 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#pragma once + +#include + +#include "common/common_types.h" +#include "common/spin_lock.h" +#include "core/hle/kernel/global_scheduler_context.h" +#include "core/hle/kernel/k_priority_queue.h" +#include "core/hle/kernel/k_scheduler_lock.h" +#include "core/hle/kernel/k_scoped_lock.h" + +namespace Common { +class Fiber; +} + +namespace Core { +class System; +} + +namespace Kernel { + +class KernelCore; +class Process; +class SchedulerLock; +class Thread; + +class KScheduler final { +public: + explicit KScheduler(Core::System& system, std::size_t core_id); + ~KScheduler(); + + /// Reschedules to the next available thread (call after current thread is suspended) + void RescheduleCurrentCore(); + + /// Reschedules cores pending reschedule, to be called on EnableScheduling. + static void RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedule, + Core::EmuThreadHandle global_thread); + + /// The next two are for SingleCore Only. + /// Unload current thread before preempting core. + void Unload(Thread* thread); + + /// Reload current thread after core preemption. + void Reload(Thread* thread); + + /// Gets the current running thread + [[nodiscard]] Thread* GetCurrentThread() const; + + /// Gets the timestamp for the last context switch in ticks. + [[nodiscard]] u64 GetLastContextSwitchTicks() const; + + [[nodiscard]] bool ContextSwitchPending() const { + return state.needs_scheduling.load(std::memory_order_relaxed); + } + + void Initialize(); + + void OnThreadStart(); + + [[nodiscard]] std::shared_ptr& ControlContext() { + return switch_fiber; + } + + [[nodiscard]] const std::shared_ptr& ControlContext() const { + return switch_fiber; + } + + [[nodiscard]] u64 UpdateHighestPriorityThread(Thread* highest_thread); + + /** + * Takes a thread and moves it to the back of the it's priority list. + * + * @note This operation can be redundant and no scheduling is changed if marked as so. + */ + void YieldWithoutCoreMigration(); + + /** + * Takes a thread and moves it to the back of the it's priority list. + * Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or + * a better priority than the next thread in the core. + * + * @note This operation can be redundant and no scheduling is changed if marked as so. + */ + void YieldWithCoreMigration(); + + /** + * Takes a thread and moves it out of the scheduling queue. + * and into the suggested queue. If no thread can be scheduled afterwards in that core, + * a suggested thread is obtained instead. + * + * @note This operation can be redundant and no scheduling is changed if marked as so. + */ + void YieldToAnyThread(); + + /// Notify the scheduler a thread's status has changed. + static void OnThreadStateChanged(KernelCore& kernel, Thread* thread, u32 old_state); + + /// Notify the scheduler a thread's priority has changed. + static void OnThreadPriorityChanged(KernelCore& kernel, Thread* thread, Thread* current_thread, + u32 old_priority); + + /// Notify the scheduler a thread's core and/or affinity mask has changed. + static void OnThreadAffinityMaskChanged(KernelCore& kernel, Thread* thread, + const KAffinityMask& old_affinity, s32 old_core); + + static bool CanSchedule(KernelCore& kernel); + static bool IsSchedulerUpdateNeeded(const KernelCore& kernel); + static void SetSchedulerUpdateNeeded(KernelCore& kernel); + static void ClearSchedulerUpdateNeeded(KernelCore& kernel); + static void DisableScheduling(KernelCore& kernel); + static void EnableScheduling(KernelCore& kernel, u64 cores_needing_scheduling, + Core::EmuThreadHandle global_thread); + [[nodiscard]] static u64 UpdateHighestPriorityThreads(KernelCore& kernel); + +private: + friend class GlobalSchedulerContext; + + /** + * Takes care of selecting the new scheduled threads in three steps: + * + * 1. First a thread is selected from the top of the priority queue. If no thread + * is obtained then we move to step two, else we are done. + * + * 2. Second we try to get a suggested thread that's not assigned to any core or + * that is not the top thread in that core. + * + * 3. Third is no suggested thread is found, we do a second pass and pick a running + * thread in another core and swap it with its current thread. + * + * returns the cores needing scheduling. + */ + [[nodiscard]] static u64 UpdateHighestPriorityThreadsImpl(KernelCore& kernel); + + [[nodiscard]] static KSchedulerPriorityQueue& GetPriorityQueue(KernelCore& kernel); + + void RotateScheduledQueue(s32 core_id, s32 priority); + + void Schedule() { + ASSERT(GetCurrentThread()->GetDisableDispatchCount() == 1); + this->ScheduleImpl(); + } + + /// Switches the CPU's active thread context to that of the specified thread + void ScheduleImpl(); + + /// When a thread wakes up, it must run this through it's new scheduler + void SwitchContextStep2(); + + /** + * Called on every context switch to update the internal timestamp + * This also updates the running time ticks for the given thread and + * process using the following difference: + * + * ticks += most_recent_ticks - last_context_switch_ticks + * + * The internal tick timestamp for the scheduler is simply the + * most recent tick count retrieved. No special arithmetic is + * applied to it. + */ + void UpdateLastContextSwitchTime(Thread* thread, Process* process); + + static void OnSwitch(void* this_scheduler); + void SwitchToCurrent(); + + Thread* current_thread{}; + Thread* idle_thread{}; + + std::shared_ptr switch_fiber{}; + + struct SchedulingState { + std::atomic needs_scheduling; + bool interrupt_task_thread_runnable{}; + bool should_count_idle{}; + u64 idle_count{}; + Thread* highest_priority_thread{}; + void* idle_thread_stack{}; + }; + + SchedulingState state; + + Core::System& system; + u64 last_context_switch_time{}; + const std::size_t core_id; + + Common::SpinLock guard{}; +}; + +class KScopedSchedulerLock : KScopedLock { +public: + explicit KScopedSchedulerLock(KernelCore& kernel); + ~KScopedSchedulerLock(); +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_scheduler_lock.h b/src/core/hle/kernel/k_scheduler_lock.h new file mode 100644 index 000000000..2f1c1f691 --- /dev/null +++ b/src/core/hle/kernel/k_scheduler_lock.h @@ -0,0 +1,75 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#pragma once + +#include "common/assert.h" +#include "common/spin_lock.h" +#include "core/hardware_properties.h" +#include "core/hle/kernel/kernel.h" + +namespace Kernel { + +class KernelCore; + +template +class KAbstractSchedulerLock { +public: + explicit KAbstractSchedulerLock(KernelCore& kernel) : kernel{kernel} {} + + bool IsLockedByCurrentThread() const { + return this->owner_thread == kernel.GetCurrentEmuThreadID(); + } + + void Lock() { + if (this->IsLockedByCurrentThread()) { + // If we already own the lock, we can just increment the count. + ASSERT(this->lock_count > 0); + this->lock_count++; + } else { + // Otherwise, we want to disable scheduling and acquire the spinlock. + SchedulerType::DisableScheduling(kernel); + this->spin_lock.lock(); + + // For debug, ensure that our state is valid. + ASSERT(this->lock_count == 0); + ASSERT(this->owner_thread == Core::EmuThreadHandle::InvalidHandle()); + + // Increment count, take ownership. + this->lock_count = 1; + this->owner_thread = kernel.GetCurrentEmuThreadID(); + } + } + + void Unlock() { + ASSERT(this->IsLockedByCurrentThread()); + ASSERT(this->lock_count > 0); + + // Release an instance of the lock. + if ((--this->lock_count) == 0) { + // We're no longer going to hold the lock. Take note of what cores need scheduling. + const u64 cores_needing_scheduling = + SchedulerType::UpdateHighestPriorityThreads(kernel); + Core::EmuThreadHandle leaving_thread = owner_thread; + + // Note that we no longer hold the lock, and unlock the spinlock. + this->owner_thread = Core::EmuThreadHandle::InvalidHandle(); + this->spin_lock.unlock(); + + // Enable scheduling, and perform a rescheduling operation. + SchedulerType::EnableScheduling(kernel, cores_needing_scheduling, leaving_thread); + } + } + +private: + KernelCore& kernel; + Common::SpinLock spin_lock{}; + s32 lock_count{}; + Core::EmuThreadHandle owner_thread{Core::EmuThreadHandle::InvalidHandle()}; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_scoped_lock.h b/src/core/hle/kernel/k_scoped_lock.h new file mode 100644 index 000000000..d7cc557b2 --- /dev/null +++ b/src/core/hle/kernel/k_scoped_lock.h @@ -0,0 +1,41 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#pragma once + +#include "common/common_types.h" + +namespace Kernel { + +template +concept KLockable = !std::is_reference_v && requires(T & t) { + { t.Lock() } + ->std::same_as; + { t.Unlock() } + ->std::same_as; +}; + +template +requires KLockable class KScopedLock { +public: + explicit KScopedLock(T* l) : lock_ptr(l) { + this->lock_ptr->Lock(); + } + explicit KScopedLock(T& l) : KScopedLock(std::addressof(l)) { /* ... */ + } + ~KScopedLock() { + this->lock_ptr->Unlock(); + } + + KScopedLock(const KScopedLock&) = delete; + KScopedLock(KScopedLock&&) = delete; + +private: + T* lock_ptr; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h new file mode 100644 index 000000000..2bb3817fa --- /dev/null +++ b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h @@ -0,0 +1,50 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This file references various implementation details from Atmosphere, an open-source firmware for +// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. + +#pragma once + +#include "common/common_types.h" +#include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/thread.h" +#include "core/hle/kernel/time_manager.h" + +namespace Kernel { + +class KScopedSchedulerLockAndSleep { +public: + explicit KScopedSchedulerLockAndSleep(KernelCore& kernel, Handle& event_handle, Thread* t, + s64 timeout) + : kernel(kernel), event_handle(event_handle), thread(t), timeout_tick(timeout) { + event_handle = InvalidHandle; + + // Lock the scheduler. + kernel.GlobalSchedulerContext().scheduler_lock.Lock(); + } + + ~KScopedSchedulerLockAndSleep() { + // Register the sleep. + if (this->timeout_tick > 0) { + kernel.TimeManager().ScheduleTimeEvent(event_handle, this->thread, this->timeout_tick); + } + + // Unlock the scheduler. + kernel.GlobalSchedulerContext().scheduler_lock.Unlock(); + } + + void CancelSleep() { + this->timeout_tick = 0; + } + +private: + KernelCore& kernel; + Handle& event_handle; + Thread* thread{}; + s64 timeout_tick{}; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index f2b0fe2fd..e8ece8164 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -7,15 +7,15 @@ #include #include #include -#include #include -#include +#include #include #include "common/assert.h" #include "common/logging/log.h" #include "common/microprofile.h" #include "common/thread.h" +#include "common/thread_worker.h" #include "core/arm/arm_interface.h" #include "core/arm/cpu_interrupt_handler.h" #include "core/arm/exclusive_monitor.h" @@ -28,6 +28,7 @@ #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/memory/memory_layout.h" #include "core/hle/kernel/memory/memory_manager.h" @@ -35,7 +36,7 @@ #include "core/hle/kernel/physical_core.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" -#include "core/hle/kernel/scheduler.h" +#include "core/hle/kernel/service_thread.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/synchronization.h" #include "core/hle/kernel/thread.h" @@ -50,17 +51,20 @@ namespace Kernel { struct KernelCore::Impl { explicit Impl(Core::System& system, KernelCore& kernel) - : global_scheduler{kernel}, synchronization{system}, time_manager{system}, - global_handle_table{kernel}, system{system} {} + : synchronization{system}, time_manager{system}, global_handle_table{kernel}, system{ + system} {} void SetMulticore(bool is_multicore) { this->is_multicore = is_multicore; } void Initialize(KernelCore& kernel) { - Shutdown(); RegisterHostThread(); + global_scheduler_context = std::make_unique(kernel); + service_thread_manager = + std::make_unique(1, "yuzu:ServiceThreadManager"); + InitializePhysicalCores(); InitializeSystemResourceLimit(kernel); InitializeMemoryLayout(); @@ -69,7 +73,19 @@ struct KernelCore::Impl { InitializeSuspendThreads(); } + void InitializeCores() { + for (auto& core : cores) { + core.Initialize(current_process->Is64BitProcess()); + } + } + void Shutdown() { + process_list.clear(); + + // Ensures all service threads gracefully shutdown + service_thread_manager.reset(); + service_threads.clear(); + next_object_id = 0; next_kernel_process_id = Process::InitialKIPIDMin; next_user_process_id = Process::ProcessIDMin; @@ -81,41 +97,30 @@ struct KernelCore::Impl { } } - for (std::size_t i = 0; i < cores.size(); i++) { - cores[i].Shutdown(); - schedulers[i].reset(); - } cores.clear(); - registered_core_threads.reset(); - - process_list.clear(); current_process = nullptr; system_resource_limit = nullptr; global_handle_table.Clear(); - preemption_event = nullptr; - global_scheduler.Shutdown(); + preemption_event = nullptr; named_ports.clear(); - for (auto& core : cores) { - core.Shutdown(); - } - cores.clear(); - exclusive_monitor.reset(); - host_thread_ids.clear(); + + // Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others + next_host_thread_id = Core::Hardware::NUM_CPU_CORES; } void InitializePhysicalCores() { exclusive_monitor = Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES); for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { - schedulers[i] = std::make_unique(system, i); - cores.emplace_back(system, i, *schedulers[i], interrupts[i]); + schedulers[i] = std::make_unique(system, i); + cores.emplace_back(i, system, *schedulers[i], interrupts); } } @@ -147,8 +152,8 @@ struct KernelCore::Impl { preemption_event = Core::Timing::CreateEvent( "PreemptionCallback", [this, &kernel](std::uintptr_t, std::chrono::nanoseconds) { { - SchedulerLock lock(kernel); - global_scheduler.PreemptThreads(); + KScopedSchedulerLock lock(kernel); + global_scheduler_context->PreemptThreads(); } const auto time_interval = std::chrono::nanoseconds{ Core::Timing::msToCycles(std::chrono::milliseconds(10))}; @@ -177,63 +182,62 @@ struct KernelCore::Impl { void MakeCurrentProcess(Process* process) { current_process = process; - if (process == nullptr) { return; } - u32 core_id = GetCurrentHostThreadID(); + const u32 core_id = GetCurrentHostThreadID(); if (core_id < Core::Hardware::NUM_CPU_CORES) { system.Memory().SetCurrentPageTable(*process, core_id); } } + /// Creates a new host thread ID, should only be called by GetHostThreadId + u32 AllocateHostThreadId(std::optional core_id) { + if (core_id) { + // The first for slots are reserved for CPU core threads + ASSERT(*core_id < Core::Hardware::NUM_CPU_CORES); + return static_cast(*core_id); + } else { + return next_host_thread_id++; + } + } + + /// Gets the host thread ID for the caller, allocating a new one if this is the first time + u32 GetHostThreadId(std::optional core_id = std::nullopt) { + const thread_local auto host_thread_id{AllocateHostThreadId(core_id)}; + return host_thread_id; + } + + /// Registers a CPU core thread by allocating a host thread ID for it void RegisterCoreThread(std::size_t core_id) { - std::unique_lock lock{register_thread_mutex}; - if (!is_multicore) { - single_core_thread_id = std::this_thread::get_id(); - } - const std::thread::id this_id = std::this_thread::get_id(); - const auto it = host_thread_ids.find(this_id); ASSERT(core_id < Core::Hardware::NUM_CPU_CORES); - ASSERT(it == host_thread_ids.end()); - ASSERT(!registered_core_threads[core_id]); - host_thread_ids[this_id] = static_cast(core_id); - registered_core_threads.set(core_id); - } - - void RegisterHostThread() { - std::unique_lock lock{register_thread_mutex}; - const std::thread::id this_id = std::this_thread::get_id(); - const auto it = host_thread_ids.find(this_id); - if (it != host_thread_ids.end()) { - return; - } - host_thread_ids[this_id] = registered_thread_ids++; - } - - u32 GetCurrentHostThreadID() const { - const std::thread::id this_id = std::this_thread::get_id(); + const auto this_id = GetHostThreadId(core_id); if (!is_multicore) { - if (single_core_thread_id == this_id) { - return static_cast(system.GetCpuManager().CurrentCore()); - } + single_core_thread_id = this_id; } - std::unique_lock lock{register_thread_mutex}; - const auto it = host_thread_ids.find(this_id); - if (it == host_thread_ids.end()) { - return Core::INVALID_HOST_THREAD_ID; - } - return it->second; } - Core::EmuThreadHandle GetCurrentEmuThreadID() const { + /// Registers a new host thread by allocating a host thread ID for it + void RegisterHostThread() { + [[maybe_unused]] const auto this_id = GetHostThreadId(); + } + + [[nodiscard]] u32 GetCurrentHostThreadID() { + const auto this_id = GetHostThreadId(); + if (!is_multicore && single_core_thread_id == this_id) { + return static_cast(system.GetCpuManager().CurrentCore()); + } + return this_id; + } + + [[nodiscard]] Core::EmuThreadHandle GetCurrentEmuThreadID() { Core::EmuThreadHandle result = Core::EmuThreadHandle::InvalidHandle(); result.host_handle = GetCurrentHostThreadID(); if (result.host_handle >= Core::Hardware::NUM_CPU_CORES) { return result; } - const Kernel::Scheduler& sched = cores[result.host_handle].Scheduler(); + const Kernel::KScheduler& sched = cores[result.host_handle].Scheduler(); const Kernel::Thread* current = sched.GetCurrentThread(); if (current != nullptr && !current->IsPhantomMode()) { result.guest_handle = current->GetGlobalHandle(); @@ -302,7 +306,7 @@ struct KernelCore::Impl { // Lists all processes that exist in the current session. std::vector> process_list; Process* current_process = nullptr; - Kernel::GlobalScheduler global_scheduler; + std::unique_ptr global_scheduler_context; Kernel::Synchronization synchronization; Kernel::TimeManager time_manager; @@ -321,11 +325,8 @@ struct KernelCore::Impl { std::unique_ptr exclusive_monitor; std::vector cores; - // 0-3 IDs represent core threads, >3 represent others - std::unordered_map host_thread_ids; - u32 registered_thread_ids{Core::Hardware::NUM_CPU_CORES}; - std::bitset registered_core_threads; - mutable std::mutex register_thread_mutex; + // Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others + std::atomic next_host_thread_id{Core::Hardware::NUM_CPU_CORES}; // Kernel memory management std::unique_ptr memory_manager; @@ -337,12 +338,19 @@ struct KernelCore::Impl { std::shared_ptr irs_shared_mem; std::shared_ptr time_shared_mem; + // Threads used for services + std::unordered_set> service_threads; + + // Service threads are managed by a worker thread, so that a calling service thread can queue up + // the release of itself + std::unique_ptr service_thread_manager; + std::array, Core::Hardware::NUM_CPU_CORES> suspend_threads{}; std::array interrupts{}; - std::array, Core::Hardware::NUM_CPU_CORES> schedulers{}; + std::array, Core::Hardware::NUM_CPU_CORES> schedulers{}; bool is_multicore{}; - std::thread::id single_core_thread_id{}; + u32 single_core_thread_id{}; std::array svc_ticks{}; @@ -363,6 +371,10 @@ void KernelCore::Initialize() { impl->Initialize(*this); } +void KernelCore::InitializeCores() { + impl->InitializeCores(); +} + void KernelCore::Shutdown() { impl->Shutdown(); } @@ -395,19 +407,19 @@ const std::vector>& KernelCore::GetProcessList() const return impl->process_list; } -Kernel::GlobalScheduler& KernelCore::GlobalScheduler() { - return impl->global_scheduler; +Kernel::GlobalSchedulerContext& KernelCore::GlobalSchedulerContext() { + return *impl->global_scheduler_context; } -const Kernel::GlobalScheduler& KernelCore::GlobalScheduler() const { - return impl->global_scheduler; +const Kernel::GlobalSchedulerContext& KernelCore::GlobalSchedulerContext() const { + return *impl->global_scheduler_context; } -Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) { +Kernel::KScheduler& KernelCore::Scheduler(std::size_t id) { return *impl->schedulers[id]; } -const Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) const { +const Kernel::KScheduler& KernelCore::Scheduler(std::size_t id) const { return *impl->schedulers[id]; } @@ -431,16 +443,13 @@ const Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() const { return impl->cores[core_id]; } -Kernel::Scheduler& KernelCore::CurrentScheduler() { +Kernel::KScheduler* KernelCore::CurrentScheduler() { u32 core_id = impl->GetCurrentHostThreadID(); - ASSERT(core_id < Core::Hardware::NUM_CPU_CORES); - return *impl->schedulers[core_id]; -} - -const Kernel::Scheduler& KernelCore::CurrentScheduler() const { - u32 core_id = impl->GetCurrentHostThreadID(); - ASSERT(core_id < Core::Hardware::NUM_CPU_CORES); - return *impl->schedulers[core_id]; + if (core_id >= Core::Hardware::NUM_CPU_CORES) { + // This is expected when called from not a guest thread + return {}; + } + return impl->schedulers[core_id].get(); } std::array& KernelCore::Interrupts() { @@ -477,12 +486,17 @@ const Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() const { } void KernelCore::InvalidateAllInstructionCaches() { - auto& threads = GlobalScheduler().GetThreadList(); - for (auto& thread : threads) { - if (!thread->IsHLEThread()) { - auto& arm_interface = thread->ArmInterface(); - arm_interface.ClearInstructionCache(); + for (auto& physical_core : impl->cores) { + physical_core.ArmInterface().ClearInstructionCache(); + } +} + +void KernelCore::InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size) { + for (auto& physical_core : impl->cores) { + if (!physical_core.IsInitialized()) { + continue; } + physical_core.ArmInterface().InvalidateCacheRange(addr, size); } } @@ -598,7 +612,7 @@ const Kernel::SharedMemory& KernelCore::GetTimeSharedMem() const { void KernelCore::Suspend(bool in_suspention) { const bool should_suspend = exception_exited || in_suspention; { - SchedulerLock lock(*this); + KScopedSchedulerLock lock(*this); ThreadStatus status = should_suspend ? ThreadStatus::Ready : ThreadStatus::WaitSleep; for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { impl->suspend_threads[i]->SetStatus(status); @@ -625,4 +639,19 @@ void KernelCore::ExitSVCProfile() { MicroProfileLeave(MICROPROFILE_TOKEN(Kernel_SVC), impl->svc_ticks[core]); } +std::weak_ptr KernelCore::CreateServiceThread(const std::string& name) { + auto service_thread = std::make_shared(*this, 1, name); + impl->service_thread_manager->QueueWork( + [this, service_thread] { impl->service_threads.emplace(service_thread); }); + return service_thread; +} + +void KernelCore::ReleaseServiceThread(std::weak_ptr service_thread) { + impl->service_thread_manager->QueueWork([this, service_thread] { + if (auto strong_ptr = service_thread.lock()) { + impl->service_threads.erase(strong_ptr); + } + }); +} + } // namespace Kernel diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 16285c3f0..e3169f5a7 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -35,13 +35,14 @@ class SlabHeap; class AddressArbiter; class ClientPort; -class GlobalScheduler; +class GlobalSchedulerContext; class HandleTable; class PhysicalCore; class Process; class ResourceLimit; -class Scheduler; +class KScheduler; class SharedMemory; +class ServiceThread; class Synchronization; class Thread; class TimeManager; @@ -74,6 +75,9 @@ public: /// Resets the kernel to a clean slate for use. void Initialize(); + /// Initializes the CPU cores. + void InitializeCores(); + /// Clears all resources in use by the kernel instance. void Shutdown(); @@ -99,16 +103,16 @@ public: const std::vector>& GetProcessList() const; /// Gets the sole instance of the global scheduler - Kernel::GlobalScheduler& GlobalScheduler(); + Kernel::GlobalSchedulerContext& GlobalSchedulerContext(); /// Gets the sole instance of the global scheduler - const Kernel::GlobalScheduler& GlobalScheduler() const; + const Kernel::GlobalSchedulerContext& GlobalSchedulerContext() const; /// Gets the sole instance of the Scheduler assoviated with cpu core 'id' - Kernel::Scheduler& Scheduler(std::size_t id); + Kernel::KScheduler& Scheduler(std::size_t id); /// Gets the sole instance of the Scheduler assoviated with cpu core 'id' - const Kernel::Scheduler& Scheduler(std::size_t id) const; + const Kernel::KScheduler& Scheduler(std::size_t id) const; /// Gets the an instance of the respective physical CPU core. Kernel::PhysicalCore& PhysicalCore(std::size_t id); @@ -117,10 +121,7 @@ public: const Kernel::PhysicalCore& PhysicalCore(std::size_t id) const; /// Gets the sole instance of the Scheduler at the current running core. - Kernel::Scheduler& CurrentScheduler(); - - /// Gets the sole instance of the Scheduler at the current running core. - const Kernel::Scheduler& CurrentScheduler() const; + Kernel::KScheduler* CurrentScheduler(); /// Gets the an instance of the current physical CPU core. Kernel::PhysicalCore& CurrentPhysicalCore(); @@ -153,6 +154,8 @@ public: void InvalidateAllInstructionCaches(); + void InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size); + /// Adds a port to the named port table void AddNamedPort(std::string name, std::shared_ptr port); @@ -225,6 +228,22 @@ public: void ExitSVCProfile(); + /** + * Creates an HLE service thread, which are used to execute service routines asynchronously. + * While these are allocated per ServerSession, these need to be owned and managed outside of + * ServerSession to avoid a circular dependency. + * @param name String name for the ServerSession creating this thread, used for debug purposes. + * @returns The a weak pointer newly created service thread. + */ + std::weak_ptr CreateServiceThread(const std::string& name); + + /** + * Releases a HLE service thread, instructing KernelCore to free it. This should be called when + * the ServerSession associated with the thread is destroyed. + * @param service_thread Service thread to release. + */ + void ReleaseServiceThread(std::weak_ptr service_thread); + private: friend class Object; friend class Process; diff --git a/src/core/hle/kernel/memory/address_space_info.cpp b/src/core/hle/kernel/memory/address_space_info.cpp index e4288cab4..6cf43ba24 100644 --- a/src/core/hle/kernel/memory/address_space_info.cpp +++ b/src/core/hle/kernel/memory/address_space_info.cpp @@ -96,6 +96,7 @@ u64 AddressSpaceInfo::GetAddressSpaceStart(std::size_t width, Type type) { return AddressSpaceInfos[AddressSpaceIndices39Bit[index]].address; } UNREACHABLE(); + return 0; } std::size_t AddressSpaceInfo::GetAddressSpaceSize(std::size_t width, Type type) { @@ -112,6 +113,7 @@ std::size_t AddressSpaceInfo::GetAddressSpaceSize(std::size_t width, Type type) return AddressSpaceInfos[AddressSpaceIndices39Bit[index]].size; } UNREACHABLE(); + return 0; } } // namespace Kernel::Memory diff --git a/src/core/hle/kernel/memory/memory_block.h b/src/core/hle/kernel/memory/memory_block.h index 9d7839d08..83acece1e 100644 --- a/src/core/hle/kernel/memory/memory_block.h +++ b/src/core/hle/kernel/memory/memory_block.h @@ -73,12 +73,12 @@ enum class MemoryState : u32 { ThreadLocal = static_cast(Svc::MemoryState::ThreadLocal) | FlagMapped | FlagReferenceCounted, - Transfered = static_cast(Svc::MemoryState::Transfered) | FlagsMisc | - FlagCanAlignedDeviceMap | FlagCanChangeAttribute | FlagCanUseIpc | - FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, + Transferred = static_cast(Svc::MemoryState::Transferred) | FlagsMisc | + FlagCanAlignedDeviceMap | FlagCanChangeAttribute | FlagCanUseIpc | + FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, - SharedTransfered = static_cast(Svc::MemoryState::SharedTransfered) | FlagsMisc | - FlagCanAlignedDeviceMap | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, + SharedTransferred = static_cast(Svc::MemoryState::SharedTransferred) | FlagsMisc | + FlagCanAlignedDeviceMap | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, SharedCode = static_cast(Svc::MemoryState::SharedCode) | FlagMapped | FlagReferenceCounted | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc, @@ -111,8 +111,8 @@ static_assert(static_cast(MemoryState::AliasCodeData) == 0x03FFBD09); static_assert(static_cast(MemoryState::Ipc) == 0x005C3C0A); static_assert(static_cast(MemoryState::Stack) == 0x005C3C0B); static_assert(static_cast(MemoryState::ThreadLocal) == 0x0040200C); -static_assert(static_cast(MemoryState::Transfered) == 0x015C3C0D); -static_assert(static_cast(MemoryState::SharedTransfered) == 0x005C380E); +static_assert(static_cast(MemoryState::Transferred) == 0x015C3C0D); +static_assert(static_cast(MemoryState::SharedTransferred) == 0x005C380E); static_assert(static_cast(MemoryState::SharedCode) == 0x0040380F); static_assert(static_cast(MemoryState::Inaccessible) == 0x00000010); static_assert(static_cast(MemoryState::NonSecureIpc) == 0x005C3811); @@ -222,9 +222,9 @@ public: public: constexpr MemoryBlock() = default; - constexpr MemoryBlock(VAddr addr, std::size_t num_pages, MemoryState state, - MemoryPermission perm, MemoryAttribute attribute) - : addr{addr}, num_pages(num_pages), state{state}, perm{perm}, attribute{attribute} {} + constexpr MemoryBlock(VAddr addr_, std::size_t num_pages_, MemoryState state_, + MemoryPermission perm_, MemoryAttribute attribute_) + : addr{addr_}, num_pages(num_pages_), state{state_}, perm{perm_}, attribute{attribute_} {} constexpr VAddr GetAddress() const { return addr; diff --git a/src/core/hle/kernel/memory/memory_block_manager.h b/src/core/hle/kernel/memory/memory_block_manager.h index 6e1d41075..f57d1bbcc 100644 --- a/src/core/hle/kernel/memory/memory_block_manager.h +++ b/src/core/hle/kernel/memory/memory_block_manager.h @@ -57,8 +57,8 @@ public: private: void MergeAdjacent(iterator it, iterator& next_it); - const VAddr start_addr; - const VAddr end_addr; + [[maybe_unused]] const VAddr start_addr; + [[maybe_unused]] const VAddr end_addr; MemoryBlockTree memory_block_tree; }; diff --git a/src/core/hle/kernel/memory/page_table.cpp b/src/core/hle/kernel/memory/page_table.cpp index a3fadb533..080886554 100644 --- a/src/core/hle/kernel/memory/page_table.cpp +++ b/src/core/hle/kernel/memory/page_table.cpp @@ -265,7 +265,7 @@ ResultCode PageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_t physical_memory_usage = 0; memory_pool = pool; - page_table_impl.Resize(address_space_width, PageBits, true); + page_table_impl.Resize(address_space_width, PageBits); return InitializeMemoryLayout(start, end); } @@ -670,6 +670,11 @@ ResultCode PageTable::SetCodeMemoryPermission(VAddr addr, std::size_t size, Memo return RESULT_SUCCESS; } + if ((prev_perm & MemoryPermission::Execute) != (perm & MemoryPermission::Execute)) { + // Memory execution state is changing, invalidate CPU cache range + system.InvalidateCpuInstructionCacheRange(addr, size); + } + const std::size_t num_pages{size / PageSize}; const OperationType operation{(perm & MemoryPermission::Execute) != MemoryPermission::None ? OperationType::ChangePermissionsAndRefresh @@ -1002,8 +1007,8 @@ constexpr VAddr PageTable::GetRegionAddress(MemoryState state) const { case MemoryState::Shared: case MemoryState::AliasCode: case MemoryState::AliasCodeData: - case MemoryState::Transfered: - case MemoryState::SharedTransfered: + case MemoryState::Transferred: + case MemoryState::SharedTransferred: case MemoryState::SharedCode: case MemoryState::GeneratedCode: case MemoryState::CodeOut: @@ -1037,8 +1042,8 @@ constexpr std::size_t PageTable::GetRegionSize(MemoryState state) const { case MemoryState::Shared: case MemoryState::AliasCode: case MemoryState::AliasCodeData: - case MemoryState::Transfered: - case MemoryState::SharedTransfered: + case MemoryState::Transferred: + case MemoryState::SharedTransferred: case MemoryState::SharedCode: case MemoryState::GeneratedCode: case MemoryState::CodeOut: @@ -1075,8 +1080,8 @@ constexpr bool PageTable::CanContain(VAddr addr, std::size_t size, MemoryState s case MemoryState::AliasCodeData: case MemoryState::Stack: case MemoryState::ThreadLocal: - case MemoryState::Transfered: - case MemoryState::SharedTransfered: + case MemoryState::Transferred: + case MemoryState::SharedTransferred: case MemoryState::SharedCode: case MemoryState::GeneratedCode: case MemoryState::CodeOut: diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp index 8f6c944d1..4f8075e0e 100644 --- a/src/core/hle/kernel/mutex.cpp +++ b/src/core/hle/kernel/mutex.cpp @@ -11,11 +11,11 @@ #include "core/core.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/mutex.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" #include "core/hle/result.h" #include "core/memory.h" @@ -73,9 +73,9 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle, auto& kernel = system.Kernel(); std::shared_ptr current_thread = - SharedFrom(kernel.CurrentScheduler().GetCurrentThread()); + SharedFrom(kernel.CurrentScheduler()->GetCurrentThread()); { - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); // The mutex address must be 4-byte aligned if ((address % sizeof(u32)) != 0) { return ERR_INVALID_ADDRESS; @@ -114,7 +114,7 @@ ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle, } { - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); auto* owner = current_thread->GetLockOwner(); if (owner != nullptr) { owner->RemoveMutexWaiter(current_thread); @@ -153,10 +153,10 @@ std::pair> Mutex::Unlock(std::shared_ptr current_thread = - SharedFrom(kernel.CurrentScheduler().GetCurrentThread()); + SharedFrom(kernel.CurrentScheduler()->GetCurrentThread()); auto [result, new_owner] = Unlock(current_thread, address); diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp index c6bbdb080..7fea45f96 100644 --- a/src/core/hle/kernel/physical_core.cpp +++ b/src/core/hle/kernel/physical_core.cpp @@ -2,54 +2,60 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/assert.h" -#include "common/logging/log.h" #include "common/spin_lock.h" -#include "core/arm/arm_interface.h" -#ifdef ARCHITECTURE_x86_64 +#include "core/arm/cpu_interrupt_handler.h" #include "core/arm/dynarmic/arm_dynarmic_32.h" #include "core/arm/dynarmic/arm_dynarmic_64.h" -#endif -#include "core/arm/cpu_interrupt_handler.h" -#include "core/arm/exclusive_monitor.h" -#include "core/arm/unicorn/arm_unicorn.h" #include "core/core.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/physical_core.h" -#include "core/hle/kernel/scheduler.h" -#include "core/hle/kernel/thread.h" namespace Kernel { -PhysicalCore::PhysicalCore(Core::System& system, std::size_t id, Kernel::Scheduler& scheduler, - Core::CPUInterruptHandler& interrupt_handler) - : interrupt_handler{interrupt_handler}, core_index{id}, scheduler{scheduler} { - - guard = std::make_unique(); -} +PhysicalCore::PhysicalCore(std::size_t core_index, Core::System& system, + Kernel::KScheduler& scheduler, Core::CPUInterrupts& interrupts) + : core_index{core_index}, system{system}, scheduler{scheduler}, + interrupts{interrupts}, guard{std::make_unique()} {} PhysicalCore::~PhysicalCore() = default; -void PhysicalCore::Idle() { - interrupt_handler.AwaitInterrupt(); +void PhysicalCore::Initialize([[maybe_unused]] bool is_64_bit) { +#ifdef ARCHITECTURE_x86_64 + auto& kernel = system.Kernel(); + if (is_64_bit) { + arm_interface = std::make_unique( + system, interrupts, kernel.IsMulticore(), kernel.GetExclusiveMonitor(), core_index); + } else { + arm_interface = std::make_unique( + system, interrupts, kernel.IsMulticore(), kernel.GetExclusiveMonitor(), core_index); + } +#else +#error Platform not supported yet. +#endif } -void PhysicalCore::Shutdown() { - scheduler.Shutdown(); +void PhysicalCore::Run() { + arm_interface->Run(); +} + +void PhysicalCore::Idle() { + interrupts[core_index].AwaitInterrupt(); } bool PhysicalCore::IsInterrupted() const { - return interrupt_handler.IsInterrupted(); + return interrupts[core_index].IsInterrupted(); } void PhysicalCore::Interrupt() { guard->lock(); - interrupt_handler.SetInterrupt(true); + interrupts[core_index].SetInterrupt(true); guard->unlock(); } void PhysicalCore::ClearInterrupt() { guard->lock(); - interrupt_handler.SetInterrupt(false); + interrupts[core_index].SetInterrupt(false); guard->unlock(); } diff --git a/src/core/hle/kernel/physical_core.h b/src/core/hle/kernel/physical_core.h index d7a7a951c..f2b0911aa 100644 --- a/src/core/hle/kernel/physical_core.h +++ b/src/core/hle/kernel/physical_core.h @@ -4,19 +4,21 @@ #pragma once +#include #include #include +#include "core/arm/arm_interface.h" + namespace Common { class SpinLock; } namespace Kernel { -class Scheduler; +class KScheduler; } // namespace Kernel namespace Core { -class ARM_Interface; class CPUInterruptHandler; class ExclusiveMonitor; class System; @@ -26,17 +28,24 @@ namespace Kernel { class PhysicalCore { public: - PhysicalCore(Core::System& system, std::size_t id, Kernel::Scheduler& scheduler, - Core::CPUInterruptHandler& interrupt_handler); + PhysicalCore(std::size_t core_index, Core::System& system, Kernel::KScheduler& scheduler, + Core::CPUInterrupts& interrupts); ~PhysicalCore(); PhysicalCore(const PhysicalCore&) = delete; PhysicalCore& operator=(const PhysicalCore&) = delete; PhysicalCore(PhysicalCore&&) = default; - PhysicalCore& operator=(PhysicalCore&&) = default; + PhysicalCore& operator=(PhysicalCore&&) = delete; + + /// Initialize the core for the specified parameters. + void Initialize(bool is_64_bit); + + /// Execute current jit state + void Run(); void Idle(); + /// Interrupt this physical core. void Interrupt(); @@ -46,8 +55,17 @@ public: /// Check if this core is interrupted bool IsInterrupted() const; - // Shutdown this physical core. - void Shutdown(); + bool IsInitialized() const { + return arm_interface != nullptr; + } + + Core::ARM_Interface& ArmInterface() { + return *arm_interface; + } + + const Core::ARM_Interface& ArmInterface() const { + return *arm_interface; + } bool IsMainCore() const { return core_index == 0; @@ -61,19 +79,21 @@ public: return core_index; } - Kernel::Scheduler& Scheduler() { + Kernel::KScheduler& Scheduler() { return scheduler; } - const Kernel::Scheduler& Scheduler() const { + const Kernel::KScheduler& Scheduler() const { return scheduler; } private: - Core::CPUInterruptHandler& interrupt_handler; - std::size_t core_index; - Kernel::Scheduler& scheduler; + const std::size_t core_index; + Core::System& system; + Kernel::KScheduler& scheduler; + Core::CPUInterrupts& interrupts; std::unique_ptr guard; + std::unique_ptr arm_interface; }; } // namespace Kernel diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index ff9d9248b..b905b486a 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include "common/alignment.h" @@ -14,13 +15,13 @@ #include "core/file_sys/program_metadata.h" #include "core/hle/kernel/code_set.h" #include "core/hle/kernel/errors.h" +#include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/memory/memory_block_manager.h" #include "core/hle/kernel/memory/page_table.h" #include "core/hle/kernel/memory/slab_heap.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" #include "core/hle/lock.h" #include "core/memory.h" @@ -53,7 +54,7 @@ void SetupMainThread(Core::System& system, Process& owner_process, u32 priority, auto& kernel = system.Kernel(); // Threads by default are dormant, wake up the main thread so it runs when the scheduler fires { - SchedulerLock lock{kernel}; + KScopedSchedulerLock lock{kernel}; thread->SetStatus(ThreadStatus::Ready); } } @@ -123,7 +124,7 @@ std::shared_ptr Process::Create(Core::System& system, std::string name, : kernel.CreateNewUserProcessID(); process->capabilities.InitializeForMetadatalessProcess(); - std::mt19937 rng(Settings::values.rng_seed.GetValue().value_or(0)); + std::mt19937 rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr))); std::uniform_int_distribution distribution; std::generate(process->random_entropy.begin(), process->random_entropy.end(), [&] { return distribution(rng); }); @@ -212,7 +213,7 @@ void Process::UnregisterThread(const Thread* thread) { } ResultCode Process::ClearSignalState() { - SchedulerLock lock(system.Kernel()); + KScopedSchedulerLock lock(system.Kernel()); if (status == ProcessStatus::Exited) { LOG_ERROR(Kernel, "called on a terminated process instance."); return ERR_INVALID_STATE; @@ -313,7 +314,7 @@ void Process::PrepareForTermination() { if (thread->GetOwnerProcess() != this) continue; - if (thread.get() == system.CurrentScheduler().GetCurrentThread()) + if (thread.get() == kernel.CurrentScheduler()->GetCurrentThread()) continue; // TODO(Subv): When are the other running/ready threads terminated? @@ -324,7 +325,7 @@ void Process::PrepareForTermination() { } }; - stop_threads(system.GlobalScheduler().GetThreadList()); + stop_threads(system.GlobalSchedulerContext().GetThreadList()); FreeTLSRegion(tls_region_address); tls_region_address = 0; @@ -346,7 +347,7 @@ static auto FindTLSPageWithAvailableSlots(std::vector& tls_pages) { } VAddr Process::CreateTLSRegion() { - SchedulerLock lock(system.Kernel()); + KScopedSchedulerLock lock(system.Kernel()); if (auto tls_page_iter{FindTLSPageWithAvailableSlots(tls_pages)}; tls_page_iter != tls_pages.cend()) { return *tls_page_iter->ReserveSlot(); @@ -377,7 +378,7 @@ VAddr Process::CreateTLSRegion() { } void Process::FreeTLSRegion(VAddr tls_address) { - SchedulerLock lock(system.Kernel()); + KScopedSchedulerLock lock(system.Kernel()); const VAddr aligned_address = Common::AlignDown(tls_address, Core::Memory::PAGE_SIZE); auto iter = std::find_if(tls_pages.begin(), tls_pages.end(), [aligned_address](const auto& page) { diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index f45cb5674..e412e58aa 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -216,6 +216,16 @@ public: total_process_running_time_ticks += ticks; } + /// Gets the process schedule count, used for thread yelding + s64 GetScheduledCount() const { + return schedule_count; + } + + /// Increments the process schedule count, used for thread yielding. + void IncrementScheduledCount() { + ++schedule_count; + } + /// Gets 8 bytes of random data for svcGetInfo RandomEntropy u64 GetRandomEntropy(std::size_t index) const { return random_entropy.at(index); @@ -397,6 +407,9 @@ private: /// Name of this process std::string name; + /// Schedule count of this process + s64 schedule_count{}; + /// System context Core::System& system; }; diff --git a/src/core/hle/kernel/process_capability.cpp b/src/core/hle/kernel/process_capability.cpp index 63880f13d..0f128c586 100644 --- a/src/core/hle/kernel/process_capability.cpp +++ b/src/core/hle/kernel/process_capability.cpp @@ -199,7 +199,7 @@ ResultCode ProcessCapabilities::ParseSingleFlagCapability(u32& set_flags, u32& s break; } - LOG_ERROR(Kernel, "Invalid capability type! type={}", static_cast(type)); + LOG_ERROR(Kernel, "Invalid capability type! type={}", type); return ERR_INVALID_CAPABILITY_DESCRIPTOR; } diff --git a/src/core/hle/kernel/readable_event.cpp b/src/core/hle/kernel/readable_event.cpp index 6e286419e..cea262ce0 100644 --- a/src/core/hle/kernel/readable_event.cpp +++ b/src/core/hle/kernel/readable_event.cpp @@ -6,10 +6,10 @@ #include "common/assert.h" #include "common/logging/log.h" #include "core/hle/kernel/errors.h" +#include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/readable_event.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" namespace Kernel { @@ -39,7 +39,7 @@ void ReadableEvent::Clear() { } ResultCode ReadableEvent::Reset() { - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); if (!is_signaled) { LOG_TRACE(Kernel, "Handle is not signaled! object_id={}, object_type={}, object_name={}", GetObjectId(), GetTypeName(), GetName()); diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp index 212e442f4..7bf50339d 100644 --- a/src/core/hle/kernel/resource_limit.cpp +++ b/src/core/hle/kernel/resource_limit.cpp @@ -65,8 +65,8 @@ ResultCode ResourceLimit::SetLimitValue(ResourceType resource, s64 value) { limit[index] = value; return RESULT_SUCCESS; } else { - LOG_ERROR(Kernel, "Limit value is too large! resource={}, value={}, index={}", - static_cast(resource), value, index); + LOG_ERROR(Kernel, "Limit value is too large! resource={}, value={}, index={}", resource, + value, index); return ERR_INVALID_STATE; } } diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp deleted file mode 100644 index 5cbd3b912..000000000 --- a/src/core/hle/kernel/scheduler.cpp +++ /dev/null @@ -1,849 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. -// -// SelectThreads, Yield functions originally by TuxSH. -// licensed under GPLv2 or later under exception provided by the author. - -#include -#include -#include -#include -#include - -#include "common/assert.h" -#include "common/bit_util.h" -#include "common/fiber.h" -#include "common/logging/log.h" -#include "core/arm/arm_interface.h" -#include "core/core.h" -#include "core/core_timing.h" -#include "core/cpu_manager.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/physical_core.h" -#include "core/hle/kernel/process.h" -#include "core/hle/kernel/scheduler.h" -#include "core/hle/kernel/time_manager.h" - -namespace Kernel { - -GlobalScheduler::GlobalScheduler(KernelCore& kernel) : kernel{kernel} {} - -GlobalScheduler::~GlobalScheduler() = default; - -void GlobalScheduler::AddThread(std::shared_ptr thread) { - std::scoped_lock lock{global_list_guard}; - thread_list.push_back(std::move(thread)); -} - -void GlobalScheduler::RemoveThread(std::shared_ptr thread) { - std::scoped_lock lock{global_list_guard}; - thread_list.erase(std::remove(thread_list.begin(), thread_list.end(), thread), - thread_list.end()); -} - -u32 GlobalScheduler::SelectThreads() { - ASSERT(is_locked); - const auto update_thread = [](Thread* thread, Scheduler& sched) { - std::scoped_lock lock{sched.guard}; - if (thread != sched.selected_thread_set.get()) { - if (thread == nullptr) { - ++sched.idle_selection_count; - } - sched.selected_thread_set = SharedFrom(thread); - } - const bool reschedule_pending = - sched.is_context_switch_pending || (sched.selected_thread_set != sched.current_thread); - sched.is_context_switch_pending = reschedule_pending; - std::atomic_thread_fence(std::memory_order_seq_cst); - return reschedule_pending; - }; - if (!is_reselection_pending.load()) { - return 0; - } - std::array top_threads{}; - - u32 idle_cores{}; - - // Step 1: Get top thread in schedule queue. - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - Thread* top_thread = - scheduled_queue[core].empty() ? nullptr : scheduled_queue[core].front(); - if (top_thread != nullptr) { - // TODO(Blinkhawk): Implement Thread Pinning - } else { - idle_cores |= (1ul << core); - } - top_threads[core] = top_thread; - } - - while (idle_cores != 0) { - u32 core_id = Common::CountTrailingZeroes32(idle_cores); - - if (!suggested_queue[core_id].empty()) { - std::array migration_candidates{}; - std::size_t num_candidates = 0; - auto iter = suggested_queue[core_id].begin(); - Thread* suggested = nullptr; - // Step 2: Try selecting a suggested thread. - while (iter != suggested_queue[core_id].end()) { - suggested = *iter; - iter++; - s32 suggested_core_id = suggested->GetProcessorID(); - Thread* top_thread = - suggested_core_id >= 0 ? top_threads[suggested_core_id] : nullptr; - if (top_thread != suggested) { - if (top_thread != nullptr && - top_thread->GetPriority() < THREADPRIO_MAX_CORE_MIGRATION) { - suggested = nullptr; - break; - // There's a too high thread to do core migration, cancel - } - TransferToCore(suggested->GetPriority(), static_cast(core_id), suggested); - break; - } - suggested = nullptr; - migration_candidates[num_candidates++] = suggested_core_id; - } - // Step 3: Select a suggested thread from another core - if (suggested == nullptr) { - for (std::size_t i = 0; i < num_candidates; i++) { - s32 candidate_core = migration_candidates[i]; - suggested = top_threads[candidate_core]; - auto it = scheduled_queue[candidate_core].begin(); - it++; - Thread* next = it != scheduled_queue[candidate_core].end() ? *it : nullptr; - if (next != nullptr) { - TransferToCore(suggested->GetPriority(), static_cast(core_id), - suggested); - top_threads[candidate_core] = next; - break; - } else { - suggested = nullptr; - } - } - } - top_threads[core_id] = suggested; - } - - idle_cores &= ~(1ul << core_id); - } - u32 cores_needing_context_switch{}; - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - Scheduler& sched = kernel.Scheduler(core); - ASSERT(top_threads[core] == nullptr || - static_cast(top_threads[core]->GetProcessorID()) == core); - if (update_thread(top_threads[core], sched)) { - cores_needing_context_switch |= (1ul << core); - } - } - return cores_needing_context_switch; -} - -bool GlobalScheduler::YieldThread(Thread* yielding_thread) { - ASSERT(is_locked); - // Note: caller should use critical section, etc. - if (!yielding_thread->IsRunnable()) { - // Normally this case shouldn't happen except for SetThreadActivity. - is_reselection_pending.store(true, std::memory_order_release); - return false; - } - const u32 core_id = static_cast(yielding_thread->GetProcessorID()); - const u32 priority = yielding_thread->GetPriority(); - - // Yield the thread - Reschedule(priority, core_id, yielding_thread); - const Thread* const winner = scheduled_queue[core_id].front(); - if (kernel.GetCurrentHostThreadID() != core_id) { - is_reselection_pending.store(true, std::memory_order_release); - } - - return AskForReselectionOrMarkRedundant(yielding_thread, winner); -} - -bool GlobalScheduler::YieldThreadAndBalanceLoad(Thread* yielding_thread) { - ASSERT(is_locked); - // Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section, - // etc. - if (!yielding_thread->IsRunnable()) { - // Normally this case shouldn't happen except for SetThreadActivity. - is_reselection_pending.store(true, std::memory_order_release); - return false; - } - const u32 core_id = static_cast(yielding_thread->GetProcessorID()); - const u32 priority = yielding_thread->GetPriority(); - - // Yield the thread - Reschedule(priority, core_id, yielding_thread); - - std::array current_threads; - for (std::size_t i = 0; i < current_threads.size(); i++) { - current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front(); - } - - Thread* next_thread = scheduled_queue[core_id].front(priority); - Thread* winner = nullptr; - for (auto& thread : suggested_queue[core_id]) { - const s32 source_core = thread->GetProcessorID(); - if (source_core >= 0) { - if (current_threads[source_core] != nullptr) { - if (thread == current_threads[source_core] || - current_threads[source_core]->GetPriority() < min_regular_priority) { - continue; - } - } - } - if (next_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks() || - next_thread->GetPriority() < thread->GetPriority()) { - if (thread->GetPriority() <= priority) { - winner = thread; - break; - } - } - } - - if (winner != nullptr) { - if (winner != yielding_thread) { - TransferToCore(winner->GetPriority(), s32(core_id), winner); - } - } else { - winner = next_thread; - } - - if (kernel.GetCurrentHostThreadID() != core_id) { - is_reselection_pending.store(true, std::memory_order_release); - } - - return AskForReselectionOrMarkRedundant(yielding_thread, winner); -} - -bool GlobalScheduler::YieldThreadAndWaitForLoadBalancing(Thread* yielding_thread) { - ASSERT(is_locked); - // Note: caller should check if !thread.IsSchedulerOperationRedundant and use critical section, - // etc. - if (!yielding_thread->IsRunnable()) { - // Normally this case shouldn't happen except for SetThreadActivity. - is_reselection_pending.store(true, std::memory_order_release); - return false; - } - Thread* winner = nullptr; - const u32 core_id = static_cast(yielding_thread->GetProcessorID()); - - // Remove the thread from its scheduled mlq, put it on the corresponding "suggested" one instead - TransferToCore(yielding_thread->GetPriority(), -1, yielding_thread); - - // If the core is idle, perform load balancing, excluding the threads that have just used this - // function... - if (scheduled_queue[core_id].empty()) { - // Here, "current_threads" is calculated after the ""yield"", unlike yield -1 - std::array current_threads; - for (std::size_t i = 0; i < current_threads.size(); i++) { - current_threads[i] = scheduled_queue[i].empty() ? nullptr : scheduled_queue[i].front(); - } - for (auto& thread : suggested_queue[core_id]) { - const s32 source_core = thread->GetProcessorID(); - if (source_core < 0 || thread == current_threads[source_core]) { - continue; - } - if (current_threads[source_core] == nullptr || - current_threads[source_core]->GetPriority() >= min_regular_priority) { - winner = thread; - } - break; - } - if (winner != nullptr) { - if (winner != yielding_thread) { - TransferToCore(winner->GetPriority(), static_cast(core_id), winner); - } - } else { - winner = yielding_thread; - } - } else { - winner = scheduled_queue[core_id].front(); - } - - if (kernel.GetCurrentHostThreadID() != core_id) { - is_reselection_pending.store(true, std::memory_order_release); - } - - return AskForReselectionOrMarkRedundant(yielding_thread, winner); -} - -void GlobalScheduler::PreemptThreads() { - ASSERT(is_locked); - for (std::size_t core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) { - const u32 priority = preemption_priorities[core_id]; - - if (scheduled_queue[core_id].size(priority) > 0) { - if (scheduled_queue[core_id].size(priority) > 1) { - scheduled_queue[core_id].front(priority)->IncrementYieldCount(); - } - scheduled_queue[core_id].yield(priority); - if (scheduled_queue[core_id].size(priority) > 1) { - scheduled_queue[core_id].front(priority)->IncrementYieldCount(); - } - } - - Thread* current_thread = - scheduled_queue[core_id].empty() ? nullptr : scheduled_queue[core_id].front(); - Thread* winner = nullptr; - for (auto& thread : suggested_queue[core_id]) { - const s32 source_core = thread->GetProcessorID(); - if (thread->GetPriority() != priority) { - continue; - } - if (source_core >= 0) { - Thread* next_thread = scheduled_queue[source_core].empty() - ? nullptr - : scheduled_queue[source_core].front(); - if (next_thread != nullptr && next_thread->GetPriority() < 2) { - break; - } - if (next_thread == thread) { - continue; - } - } - if (current_thread != nullptr && - current_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks()) { - winner = thread; - break; - } - } - - if (winner != nullptr) { - TransferToCore(winner->GetPriority(), s32(core_id), winner); - current_thread = - winner->GetPriority() <= current_thread->GetPriority() ? winner : current_thread; - } - - if (current_thread != nullptr && current_thread->GetPriority() > priority) { - for (auto& thread : suggested_queue[core_id]) { - const s32 source_core = thread->GetProcessorID(); - if (thread->GetPriority() < priority) { - continue; - } - if (source_core >= 0) { - Thread* next_thread = scheduled_queue[source_core].empty() - ? nullptr - : scheduled_queue[source_core].front(); - if (next_thread != nullptr && next_thread->GetPriority() < 2) { - break; - } - if (next_thread == thread) { - continue; - } - } - if (current_thread != nullptr && - current_thread->GetLastRunningTicks() >= thread->GetLastRunningTicks()) { - winner = thread; - break; - } - } - - if (winner != nullptr) { - TransferToCore(winner->GetPriority(), s32(core_id), winner); - current_thread = winner; - } - } - - is_reselection_pending.store(true, std::memory_order_release); - } -} - -void GlobalScheduler::EnableInterruptAndSchedule(u32 cores_pending_reschedule, - Core::EmuThreadHandle global_thread) { - u32 current_core = global_thread.host_handle; - bool must_context_switch = global_thread.guest_handle != InvalidHandle && - (current_core < Core::Hardware::NUM_CPU_CORES); - while (cores_pending_reschedule != 0) { - u32 core = Common::CountTrailingZeroes32(cores_pending_reschedule); - ASSERT(core < Core::Hardware::NUM_CPU_CORES); - if (!must_context_switch || core != current_core) { - auto& phys_core = kernel.PhysicalCore(core); - phys_core.Interrupt(); - } else { - must_context_switch = true; - } - cores_pending_reschedule &= ~(1ul << core); - } - if (must_context_switch) { - auto& core_scheduler = kernel.CurrentScheduler(); - kernel.ExitSVCProfile(); - core_scheduler.TryDoContextSwitch(); - kernel.EnterSVCProfile(); - } -} - -void GlobalScheduler::Suggest(u32 priority, std::size_t core, Thread* thread) { - ASSERT(is_locked); - suggested_queue[core].add(thread, priority); -} - -void GlobalScheduler::Unsuggest(u32 priority, std::size_t core, Thread* thread) { - ASSERT(is_locked); - suggested_queue[core].remove(thread, priority); -} - -void GlobalScheduler::Schedule(u32 priority, std::size_t core, Thread* thread) { - ASSERT(is_locked); - ASSERT_MSG(thread->GetProcessorID() == s32(core), "Thread must be assigned to this core."); - scheduled_queue[core].add(thread, priority); -} - -void GlobalScheduler::SchedulePrepend(u32 priority, std::size_t core, Thread* thread) { - ASSERT(is_locked); - ASSERT_MSG(thread->GetProcessorID() == s32(core), "Thread must be assigned to this core."); - scheduled_queue[core].add(thread, priority, false); -} - -void GlobalScheduler::Reschedule(u32 priority, std::size_t core, Thread* thread) { - ASSERT(is_locked); - scheduled_queue[core].remove(thread, priority); - scheduled_queue[core].add(thread, priority); -} - -void GlobalScheduler::Unschedule(u32 priority, std::size_t core, Thread* thread) { - ASSERT(is_locked); - scheduled_queue[core].remove(thread, priority); -} - -void GlobalScheduler::TransferToCore(u32 priority, s32 destination_core, Thread* thread) { - ASSERT(is_locked); - const bool schedulable = thread->GetPriority() < THREADPRIO_COUNT; - const s32 source_core = thread->GetProcessorID(); - if (source_core == destination_core || !schedulable) { - return; - } - thread->SetProcessorID(destination_core); - if (source_core >= 0) { - Unschedule(priority, static_cast(source_core), thread); - } - if (destination_core >= 0) { - Unsuggest(priority, static_cast(destination_core), thread); - Schedule(priority, static_cast(destination_core), thread); - } - if (source_core >= 0) { - Suggest(priority, static_cast(source_core), thread); - } -} - -bool GlobalScheduler::AskForReselectionOrMarkRedundant(Thread* current_thread, - const Thread* winner) { - if (current_thread == winner) { - current_thread->IncrementYieldCount(); - return true; - } else { - is_reselection_pending.store(true, std::memory_order_release); - return false; - } -} - -void GlobalScheduler::AdjustSchedulingOnStatus(Thread* thread, u32 old_flags) { - if (old_flags == thread->scheduling_state) { - return; - } - ASSERT(is_locked); - - if (old_flags == static_cast(ThreadSchedStatus::Runnable)) { - // In this case the thread was running, now it's pausing/exitting - if (thread->processor_id >= 0) { - Unschedule(thread->current_priority, static_cast(thread->processor_id), thread); - } - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (core != static_cast(thread->processor_id) && - ((thread->affinity_mask >> core) & 1) != 0) { - Unsuggest(thread->current_priority, core, thread); - } - } - } else if (thread->scheduling_state == static_cast(ThreadSchedStatus::Runnable)) { - // The thread is now set to running from being stopped - if (thread->processor_id >= 0) { - Schedule(thread->current_priority, static_cast(thread->processor_id), thread); - } - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (core != static_cast(thread->processor_id) && - ((thread->affinity_mask >> core) & 1) != 0) { - Suggest(thread->current_priority, core, thread); - } - } - } - - SetReselectionPending(); -} - -void GlobalScheduler::AdjustSchedulingOnPriority(Thread* thread, u32 old_priority) { - if (thread->scheduling_state != static_cast(ThreadSchedStatus::Runnable)) { - return; - } - ASSERT(is_locked); - if (thread->processor_id >= 0) { - Unschedule(old_priority, static_cast(thread->processor_id), thread); - } - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (core != static_cast(thread->processor_id) && - ((thread->affinity_mask >> core) & 1) != 0) { - Unsuggest(old_priority, core, thread); - } - } - - if (thread->processor_id >= 0) { - if (thread == kernel.CurrentScheduler().GetCurrentThread()) { - SchedulePrepend(thread->current_priority, static_cast(thread->processor_id), - thread); - } else { - Schedule(thread->current_priority, static_cast(thread->processor_id), thread); - } - } - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (core != static_cast(thread->processor_id) && - ((thread->affinity_mask >> core) & 1) != 0) { - Suggest(thread->current_priority, core, thread); - } - } - thread->IncrementYieldCount(); - SetReselectionPending(); -} - -void GlobalScheduler::AdjustSchedulingOnAffinity(Thread* thread, u64 old_affinity_mask, - s32 old_core) { - if (thread->scheduling_state != static_cast(ThreadSchedStatus::Runnable) || - thread->current_priority >= THREADPRIO_COUNT) { - return; - } - ASSERT(is_locked); - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (((old_affinity_mask >> core) & 1) != 0) { - if (core == static_cast(old_core)) { - Unschedule(thread->current_priority, core, thread); - } else { - Unsuggest(thread->current_priority, core, thread); - } - } - } - - for (u32 core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - if (((thread->affinity_mask >> core) & 1) != 0) { - if (core == static_cast(thread->processor_id)) { - Schedule(thread->current_priority, core, thread); - } else { - Suggest(thread->current_priority, core, thread); - } - } - } - - thread->IncrementYieldCount(); - SetReselectionPending(); -} - -void GlobalScheduler::Shutdown() { - for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) { - scheduled_queue[core].clear(); - suggested_queue[core].clear(); - } - thread_list.clear(); -} - -void GlobalScheduler::Lock() { - Core::EmuThreadHandle current_thread = kernel.GetCurrentEmuThreadID(); - ASSERT(!current_thread.IsInvalid()); - if (current_thread == current_owner) { - ++scope_lock; - } else { - inner_lock.lock(); - is_locked = true; - current_owner = current_thread; - ASSERT(current_owner != Core::EmuThreadHandle::InvalidHandle()); - scope_lock = 1; - } -} - -void GlobalScheduler::Unlock() { - if (--scope_lock != 0) { - ASSERT(scope_lock > 0); - return; - } - u32 cores_pending_reschedule = SelectThreads(); - Core::EmuThreadHandle leaving_thread = current_owner; - current_owner = Core::EmuThreadHandle::InvalidHandle(); - scope_lock = 1; - is_locked = false; - inner_lock.unlock(); - EnableInterruptAndSchedule(cores_pending_reschedule, leaving_thread); -} - -Scheduler::Scheduler(Core::System& system, std::size_t core_id) : system(system), core_id(core_id) { - switch_fiber = std::make_shared(std::function(OnSwitch), this); -} - -Scheduler::~Scheduler() = default; - -bool Scheduler::HaveReadyThreads() const { - return system.GlobalScheduler().HaveReadyThreads(core_id); -} - -Thread* Scheduler::GetCurrentThread() const { - if (current_thread) { - return current_thread.get(); - } - return idle_thread.get(); -} - -Thread* Scheduler::GetSelectedThread() const { - return selected_thread.get(); -} - -u64 Scheduler::GetLastContextSwitchTicks() const { - return last_context_switch_time; -} - -void Scheduler::TryDoContextSwitch() { - auto& phys_core = system.Kernel().CurrentPhysicalCore(); - if (phys_core.IsInterrupted()) { - phys_core.ClearInterrupt(); - } - guard.lock(); - if (is_context_switch_pending) { - SwitchContext(); - } else { - guard.unlock(); - } -} - -void Scheduler::OnThreadStart() { - SwitchContextStep2(); -} - -void Scheduler::Unload() { - Thread* thread = current_thread.get(); - if (thread) { - thread->SetContinuousOnSVC(false); - thread->last_running_ticks = system.CoreTiming().GetCPUTicks(); - thread->SetIsRunning(false); - if (!thread->IsHLEThread() && !thread->HasExited()) { - Core::ARM_Interface& cpu_core = thread->ArmInterface(); - cpu_core.SaveContext(thread->GetContext32()); - cpu_core.SaveContext(thread->GetContext64()); - // Save the TPIDR_EL0 system register in case it was modified. - thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0()); - cpu_core.ClearExclusiveState(); - } - thread->context_guard.unlock(); - } -} - -void Scheduler::Reload() { - Thread* thread = current_thread.get(); - if (thread) { - ASSERT_MSG(thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable, - "Thread must be runnable."); - - // Cancel any outstanding wakeup events for this thread - thread->SetIsRunning(true); - thread->SetWasRunning(false); - thread->last_running_ticks = system.CoreTiming().GetCPUTicks(); - - auto* const thread_owner_process = thread->GetOwnerProcess(); - if (thread_owner_process != nullptr) { - system.Kernel().MakeCurrentProcess(thread_owner_process); - } - if (!thread->IsHLEThread()) { - Core::ARM_Interface& cpu_core = thread->ArmInterface(); - cpu_core.LoadContext(thread->GetContext32()); - cpu_core.LoadContext(thread->GetContext64()); - cpu_core.SetTlsAddress(thread->GetTLSAddress()); - cpu_core.SetTPIDR_EL0(thread->GetTPIDR_EL0()); - cpu_core.ChangeProcessorID(this->core_id); - cpu_core.ClearExclusiveState(); - } - } -} - -void Scheduler::SwitchContextStep2() { - // Load context of new thread - if (selected_thread) { - ASSERT_MSG(selected_thread->GetSchedulingStatus() == ThreadSchedStatus::Runnable, - "Thread must be runnable."); - - // Cancel any outstanding wakeup events for this thread - selected_thread->SetIsRunning(true); - selected_thread->last_running_ticks = system.CoreTiming().GetCPUTicks(); - selected_thread->SetWasRunning(false); - - auto* const thread_owner_process = current_thread->GetOwnerProcess(); - if (thread_owner_process != nullptr) { - system.Kernel().MakeCurrentProcess(thread_owner_process); - } - if (!selected_thread->IsHLEThread()) { - Core::ARM_Interface& cpu_core = selected_thread->ArmInterface(); - cpu_core.LoadContext(selected_thread->GetContext32()); - cpu_core.LoadContext(selected_thread->GetContext64()); - cpu_core.SetTlsAddress(selected_thread->GetTLSAddress()); - cpu_core.SetTPIDR_EL0(selected_thread->GetTPIDR_EL0()); - cpu_core.ChangeProcessorID(this->core_id); - cpu_core.ClearExclusiveState(); - } - } - - TryDoContextSwitch(); -} - -void Scheduler::SwitchContext() { - current_thread_prev = current_thread; - selected_thread = selected_thread_set; - Thread* previous_thread = current_thread_prev.get(); - Thread* new_thread = selected_thread.get(); - current_thread = selected_thread; - - is_context_switch_pending = false; - - if (new_thread == previous_thread) { - guard.unlock(); - return; - } - - Process* const previous_process = system.Kernel().CurrentProcess(); - - UpdateLastContextSwitchTime(previous_thread, previous_process); - - // Save context for previous thread - if (previous_thread) { - if (new_thread != nullptr && new_thread->IsSuspendThread()) { - previous_thread->SetWasRunning(true); - } - previous_thread->SetContinuousOnSVC(false); - previous_thread->last_running_ticks = system.CoreTiming().GetCPUTicks(); - previous_thread->SetIsRunning(false); - if (!previous_thread->IsHLEThread() && !previous_thread->HasExited()) { - Core::ARM_Interface& cpu_core = previous_thread->ArmInterface(); - cpu_core.SaveContext(previous_thread->GetContext32()); - cpu_core.SaveContext(previous_thread->GetContext64()); - // Save the TPIDR_EL0 system register in case it was modified. - previous_thread->SetTPIDR_EL0(cpu_core.GetTPIDR_EL0()); - cpu_core.ClearExclusiveState(); - } - previous_thread->context_guard.unlock(); - } - - std::shared_ptr* old_context; - if (previous_thread != nullptr) { - old_context = &previous_thread->GetHostContext(); - } else { - old_context = &idle_thread->GetHostContext(); - } - guard.unlock(); - - Common::Fiber::YieldTo(*old_context, switch_fiber); - /// When a thread wakes up, the scheduler may have changed to other in another core. - auto& next_scheduler = system.Kernel().CurrentScheduler(); - next_scheduler.SwitchContextStep2(); -} - -void Scheduler::OnSwitch(void* this_scheduler) { - Scheduler* sched = static_cast(this_scheduler); - sched->SwitchToCurrent(); -} - -void Scheduler::SwitchToCurrent() { - while (true) { - { - std::scoped_lock lock{guard}; - selected_thread = selected_thread_set; - current_thread = selected_thread; - is_context_switch_pending = false; - } - const auto is_switch_pending = [this] { - std::scoped_lock lock{guard}; - return is_context_switch_pending; - }; - do { - if (current_thread != nullptr && !current_thread->IsHLEThread()) { - current_thread->context_guard.lock(); - if (!current_thread->IsRunnable()) { - current_thread->context_guard.unlock(); - break; - } - if (current_thread->GetProcessorID() != core_id) { - current_thread->context_guard.unlock(); - break; - } - } - std::shared_ptr* next_context; - if (current_thread != nullptr) { - next_context = ¤t_thread->GetHostContext(); - } else { - next_context = &idle_thread->GetHostContext(); - } - Common::Fiber::YieldTo(switch_fiber, *next_context); - } while (!is_switch_pending()); - } -} - -void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) { - const u64 prev_switch_ticks = last_context_switch_time; - const u64 most_recent_switch_ticks = system.CoreTiming().GetCPUTicks(); - const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks; - - if (thread != nullptr) { - thread->UpdateCPUTimeTicks(update_ticks); - } - - if (process != nullptr) { - process->UpdateCPUTimeTicks(update_ticks); - } - - last_context_switch_time = most_recent_switch_ticks; -} - -void Scheduler::Initialize() { - std::string name = "Idle Thread Id:" + std::to_string(core_id); - std::function init_func = Core::CpuManager::GetIdleThreadStartFunc(); - void* init_func_parameter = system.GetCpuManager().GetStartFuncParamater(); - ThreadType type = static_cast(THREADTYPE_KERNEL | THREADTYPE_HLE | THREADTYPE_IDLE); - auto thread_res = Thread::Create(system, type, name, 0, 64, 0, static_cast(core_id), 0, - nullptr, std::move(init_func), init_func_parameter); - idle_thread = std::move(thread_res).Unwrap(); -} - -void Scheduler::Shutdown() { - current_thread = nullptr; - selected_thread = nullptr; -} - -SchedulerLock::SchedulerLock(KernelCore& kernel) : kernel{kernel} { - kernel.GlobalScheduler().Lock(); -} - -SchedulerLock::~SchedulerLock() { - kernel.GlobalScheduler().Unlock(); -} - -SchedulerLockAndSleep::SchedulerLockAndSleep(KernelCore& kernel, Handle& event_handle, - Thread* time_task, s64 nanoseconds) - : SchedulerLock{kernel}, event_handle{event_handle}, time_task{time_task}, nanoseconds{ - nanoseconds} { - event_handle = InvalidHandle; -} - -SchedulerLockAndSleep::~SchedulerLockAndSleep() { - if (sleep_cancelled) { - return; - } - auto& time_manager = kernel.TimeManager(); - time_manager.ScheduleTimeEvent(event_handle, time_task, nanoseconds); -} - -void SchedulerLockAndSleep::Release() { - if (sleep_cancelled) { - return; - } - auto& time_manager = kernel.TimeManager(); - time_manager.ScheduleTimeEvent(event_handle, time_task, nanoseconds); - sleep_cancelled = true; -} - -} // namespace Kernel diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h deleted file mode 100644 index b6f04dcea..000000000 --- a/src/core/hle/kernel/scheduler.h +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include - -#include "common/common_types.h" -#include "common/multi_level_queue.h" -#include "common/spin_lock.h" -#include "core/hardware_properties.h" -#include "core/hle/kernel/thread.h" - -namespace Common { -class Fiber; -} - -namespace Core { -class ARM_Interface; -class System; -} // namespace Core - -namespace Kernel { - -class KernelCore; -class Process; -class SchedulerLock; - -class GlobalScheduler final { -public: - explicit GlobalScheduler(KernelCore& kernel); - ~GlobalScheduler(); - - /// Adds a new thread to the scheduler - void AddThread(std::shared_ptr thread); - - /// Removes a thread from the scheduler - void RemoveThread(std::shared_ptr thread); - - /// Returns a list of all threads managed by the scheduler - const std::vector>& GetThreadList() const { - return thread_list; - } - - /// Notify the scheduler a thread's status has changed. - void AdjustSchedulingOnStatus(Thread* thread, u32 old_flags); - - /// Notify the scheduler a thread's priority has changed. - void AdjustSchedulingOnPriority(Thread* thread, u32 old_priority); - - /// Notify the scheduler a thread's core and/or affinity mask has changed. - void AdjustSchedulingOnAffinity(Thread* thread, u64 old_affinity_mask, s32 old_core); - - /** - * Takes care of selecting the new scheduled threads in three steps: - * - * 1. First a thread is selected from the top of the priority queue. If no thread - * is obtained then we move to step two, else we are done. - * - * 2. Second we try to get a suggested thread that's not assigned to any core or - * that is not the top thread in that core. - * - * 3. Third is no suggested thread is found, we do a second pass and pick a running - * thread in another core and swap it with its current thread. - * - * returns the cores needing scheduling. - */ - u32 SelectThreads(); - - bool HaveReadyThreads(std::size_t core_id) const { - return !scheduled_queue[core_id].empty(); - } - - /** - * Takes a thread and moves it to the back of the it's priority list. - * - * @note This operation can be redundant and no scheduling is changed if marked as so. - */ - bool YieldThread(Thread* thread); - - /** - * Takes a thread and moves it to the back of the it's priority list. - * Afterwards, tries to pick a suggested thread from the suggested queue that has worse time or - * a better priority than the next thread in the core. - * - * @note This operation can be redundant and no scheduling is changed if marked as so. - */ - bool YieldThreadAndBalanceLoad(Thread* thread); - - /** - * Takes a thread and moves it out of the scheduling queue. - * and into the suggested queue. If no thread can be scheduled afterwards in that core, - * a suggested thread is obtained instead. - * - * @note This operation can be redundant and no scheduling is changed if marked as so. - */ - bool YieldThreadAndWaitForLoadBalancing(Thread* thread); - - /** - * Rotates the scheduling queues of threads at a preemption priority and then does - * some core rebalancing. Preemption priorities can be found in the array - * 'preemption_priorities'. - * - * @note This operation happens every 10ms. - */ - void PreemptThreads(); - - u32 CpuCoresCount() const { - return Core::Hardware::NUM_CPU_CORES; - } - - void SetReselectionPending() { - is_reselection_pending.store(true, std::memory_order_release); - } - - bool IsReselectionPending() const { - return is_reselection_pending.load(std::memory_order_acquire); - } - - void Shutdown(); - -private: - friend class SchedulerLock; - - /// Lock the scheduler to the current thread. - void Lock(); - - /// Unlocks the scheduler, reselects threads, interrupts cores for rescheduling - /// and reschedules current core if needed. - void Unlock(); - - void EnableInterruptAndSchedule(u32 cores_pending_reschedule, - Core::EmuThreadHandle global_thread); - - /** - * Add a thread to the suggested queue of a cpu core. Suggested threads may be - * picked if no thread is scheduled to run on the core. - */ - void Suggest(u32 priority, std::size_t core, Thread* thread); - - /** - * Remove a thread to the suggested queue of a cpu core. Suggested threads may be - * picked if no thread is scheduled to run on the core. - */ - void Unsuggest(u32 priority, std::size_t core, Thread* thread); - - /** - * Add a thread to the scheduling queue of a cpu core. The thread is added at the - * back the queue in its priority level. - */ - void Schedule(u32 priority, std::size_t core, Thread* thread); - - /** - * Add a thread to the scheduling queue of a cpu core. The thread is added at the - * front the queue in its priority level. - */ - void SchedulePrepend(u32 priority, std::size_t core, Thread* thread); - - /// Reschedule an already scheduled thread based on a new priority - void Reschedule(u32 priority, std::size_t core, Thread* thread); - - /// Unschedules a thread. - void Unschedule(u32 priority, std::size_t core, Thread* thread); - - /** - * Transfers a thread into an specific core. If the destination_core is -1 - * it will be unscheduled from its source code and added into its suggested - * queue. - */ - void TransferToCore(u32 priority, s32 destination_core, Thread* thread); - - bool AskForReselectionOrMarkRedundant(Thread* current_thread, const Thread* winner); - - static constexpr u32 min_regular_priority = 2; - std::array, Core::Hardware::NUM_CPU_CORES> - scheduled_queue; - std::array, Core::Hardware::NUM_CPU_CORES> - suggested_queue; - std::atomic is_reselection_pending{false}; - - // The priority levels at which the global scheduler preempts threads every 10 ms. They are - // ordered from Core 0 to Core 3. - std::array preemption_priorities = {59, 59, 59, 62}; - - /// Scheduler lock mechanisms. - bool is_locked{}; - std::mutex inner_lock; - std::atomic scope_lock{}; - Core::EmuThreadHandle current_owner{Core::EmuThreadHandle::InvalidHandle()}; - - Common::SpinLock global_list_guard{}; - - /// Lists all thread ids that aren't deleted/etc. - std::vector> thread_list; - KernelCore& kernel; -}; - -class Scheduler final { -public: - explicit Scheduler(Core::System& system, std::size_t core_id); - ~Scheduler(); - - /// Returns whether there are any threads that are ready to run. - bool HaveReadyThreads() const; - - /// Reschedules to the next available thread (call after current thread is suspended) - void TryDoContextSwitch(); - - /// The next two are for SingleCore Only. - /// Unload current thread before preempting core. - void Unload(); - /// Reload current thread after core preemption. - void Reload(); - - /// Gets the current running thread - Thread* GetCurrentThread() const; - - /// Gets the currently selected thread from the top of the multilevel queue - Thread* GetSelectedThread() const; - - /// Gets the timestamp for the last context switch in ticks. - u64 GetLastContextSwitchTicks() const; - - bool ContextSwitchPending() const { - return is_context_switch_pending; - } - - void Initialize(); - - /// Shutdowns the scheduler. - void Shutdown(); - - void OnThreadStart(); - - std::shared_ptr& ControlContext() { - return switch_fiber; - } - - const std::shared_ptr& ControlContext() const { - return switch_fiber; - } - -private: - friend class GlobalScheduler; - - /// Switches the CPU's active thread context to that of the specified thread - void SwitchContext(); - - /// When a thread wakes up, it must run this through it's new scheduler - void SwitchContextStep2(); - - /** - * Called on every context switch to update the internal timestamp - * This also updates the running time ticks for the given thread and - * process using the following difference: - * - * ticks += most_recent_ticks - last_context_switch_ticks - * - * The internal tick timestamp for the scheduler is simply the - * most recent tick count retrieved. No special arithmetic is - * applied to it. - */ - void UpdateLastContextSwitchTime(Thread* thread, Process* process); - - static void OnSwitch(void* this_scheduler); - void SwitchToCurrent(); - - std::shared_ptr current_thread = nullptr; - std::shared_ptr selected_thread = nullptr; - std::shared_ptr current_thread_prev = nullptr; - std::shared_ptr selected_thread_set = nullptr; - std::shared_ptr idle_thread = nullptr; - - std::shared_ptr switch_fiber = nullptr; - - Core::System& system; - u64 last_context_switch_time = 0; - u64 idle_selection_count = 0; - const std::size_t core_id; - - Common::SpinLock guard{}; - - bool is_context_switch_pending = false; -}; - -class SchedulerLock { -public: - [[nodiscard]] explicit SchedulerLock(KernelCore& kernel); - ~SchedulerLock(); - -protected: - KernelCore& kernel; -}; - -class SchedulerLockAndSleep : public SchedulerLock { -public: - explicit SchedulerLockAndSleep(KernelCore& kernel, Handle& event_handle, Thread* time_task, - s64 nanoseconds); - ~SchedulerLockAndSleep(); - - void CancelSleep() { - sleep_cancelled = true; - } - - void Release(); - -private: - Handle& event_handle; - Thread* time_task; - s64 nanoseconds; - bool sleep_cancelled{}; -}; - -} // namespace Kernel diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index 8c19f2534..b40fe3916 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -14,9 +14,9 @@ #include "core/hle/kernel/client_session.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/server_session.h" #include "core/hle/kernel/session.h" #include "core/hle/kernel/thread.h" @@ -25,19 +25,19 @@ namespace Kernel { ServerSession::ServerSession(KernelCore& kernel) : SynchronizationObject{kernel} {} -ServerSession::~ServerSession() = default; + +ServerSession::~ServerSession() { + kernel.ReleaseServiceThread(service_thread); +} ResultVal> ServerSession::Create(KernelCore& kernel, std::shared_ptr parent, std::string name) { std::shared_ptr session{std::make_shared(kernel)}; - session->request_event = - Core::Timing::CreateEvent(name, [session](std::uintptr_t, std::chrono::nanoseconds) { - session->CompleteSyncRequest(); - }); session->name = std::move(name); session->parent = std::move(parent); + session->service_thread = kernel.CreateServiceThread(session->name); return MakeResult(std::move(session)); } @@ -130,8 +130,7 @@ ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& con } } - LOG_CRITICAL(IPC, "Unknown domain command={}", - static_cast(domain_message_header.command.Value())); + LOG_CRITICAL(IPC, "Unknown domain command={}", domain_message_header.command.Value()); ASSERT(false); return RESULT_SUCCESS; } @@ -143,16 +142,16 @@ ResultCode ServerSession::QueueSyncRequest(std::shared_ptr thread, std::make_shared(kernel, memory, SharedFrom(this), std::move(thread)); context->PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf); - request_queue.Push(std::move(context)); + + if (auto strong_ptr = service_thread.lock()) { + strong_ptr->QueueSyncRequest(*this, std::move(context)); + return RESULT_SUCCESS; + } return RESULT_SUCCESS; } -ResultCode ServerSession::CompleteSyncRequest() { - ASSERT(!request_queue.Empty()); - - auto& context = *request_queue.Front(); - +ResultCode ServerSession::CompleteSyncRequest(HLERequestContext& context) { ResultCode result = RESULT_SUCCESS; // If the session has been converted to a domain, handle the domain request if (IsDomain() && context.HasDomainMessageHeader()) { @@ -171,25 +170,20 @@ ResultCode ServerSession::CompleteSyncRequest() { // Some service requests require the thread to block { - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); if (!context.IsThreadWaiting()) { context.GetThread().ResumeFromWait(); context.GetThread().SetSynchronizationResults(nullptr, result); } } - request_queue.Pop(); - return result; } ResultCode ServerSession::HandleSyncRequest(std::shared_ptr thread, Core::Memory::Memory& memory, Core::Timing::CoreTiming& core_timing) { - const ResultCode result = QueueSyncRequest(std::move(thread), memory); - const auto delay = std::chrono::nanoseconds{kernel.IsMulticore() ? 0 : 20000}; - core_timing.ScheduleEvent(delay, request_event, {}); - return result; + return QueueSyncRequest(std::move(thread), memory); } } // namespace Kernel diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index d23e9ec68..e8d1d99ea 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -10,6 +10,7 @@ #include #include "common/threadsafe_queue.h" +#include "core/hle/kernel/service_thread.h" #include "core/hle/kernel/synchronization_object.h" #include "core/hle/result.h" @@ -43,6 +44,8 @@ class Thread; * TLS buffer and control is transferred back to it. */ class ServerSession final : public SynchronizationObject { + friend class ServiceThread; + public: explicit ServerSession(KernelCore& kernel); ~ServerSession() override; @@ -132,7 +135,7 @@ private: ResultCode QueueSyncRequest(std::shared_ptr thread, Core::Memory::Memory& memory); /// Completes a sync request from the emulated application. - ResultCode CompleteSyncRequest(); + ResultCode CompleteSyncRequest(HLERequestContext& context); /// Handles a SyncRequest to a domain, forwarding the request to the proper object or closing an /// object handle. @@ -163,11 +166,8 @@ private: /// The name of this session (optional) std::string name; - /// Core timing event used to schedule the service request at some point in the future - std::shared_ptr request_event; - - /// Queue of scheduled service requests - Common::MPSCQueue> request_queue; + /// Thread to dispatch service requests + std::weak_ptr service_thread; }; } // namespace Kernel diff --git a/src/core/hle/kernel/service_thread.cpp b/src/core/hle/kernel/service_thread.cpp new file mode 100644 index 000000000..ee46f3e21 --- /dev/null +++ b/src/core/hle/kernel/service_thread.cpp @@ -0,0 +1,110 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include + +#include "common/assert.h" +#include "common/scope_exit.h" +#include "common/thread.h" +#include "core/core.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/server_session.h" +#include "core/hle/kernel/service_thread.h" +#include "core/hle/lock.h" +#include "video_core/renderer_base.h" + +namespace Kernel { + +class ServiceThread::Impl final { +public: + explicit Impl(KernelCore& kernel, std::size_t num_threads, const std::string& name); + ~Impl(); + + void QueueSyncRequest(ServerSession& session, std::shared_ptr&& context); + +private: + std::vector threads; + std::queue> requests; + std::mutex queue_mutex; + std::condition_variable condition; + const std::string service_name; + bool stop{}; +}; + +ServiceThread::Impl::Impl(KernelCore& kernel, std::size_t num_threads, const std::string& name) + : service_name{name} { + for (std::size_t i = 0; i < num_threads; ++i) + threads.emplace_back([this, &kernel] { + Common::SetCurrentThreadName(std::string{"yuzu:HleService:" + service_name}.c_str()); + + // Wait for first request before trying to acquire a render context + { + std::unique_lock lock{queue_mutex}; + condition.wait(lock, [this] { return stop || !requests.empty(); }); + } + + kernel.RegisterHostThread(); + + while (true) { + std::function task; + + { + std::unique_lock lock{queue_mutex}; + condition.wait(lock, [this] { return stop || !requests.empty(); }); + if (stop || requests.empty()) { + return; + } + task = std::move(requests.front()); + requests.pop(); + } + + task(); + } + }); +} + +void ServiceThread::Impl::QueueSyncRequest(ServerSession& session, + std::shared_ptr&& context) { + { + std::unique_lock lock{queue_mutex}; + + // ServerSession owns the service thread, so we cannot caption a strong pointer here in the + // event that the ServerSession is terminated. + std::weak_ptr weak_ptr{SharedFrom(&session)}; + requests.emplace([weak_ptr, context{std::move(context)}]() { + if (auto strong_ptr = weak_ptr.lock()) { + strong_ptr->CompleteSyncRequest(*context); + } + }); + } + condition.notify_one(); +} + +ServiceThread::Impl::~Impl() { + { + std::unique_lock lock{queue_mutex}; + stop = true; + } + condition.notify_all(); + for (std::thread& thread : threads) { + thread.join(); + } +} + +ServiceThread::ServiceThread(KernelCore& kernel, std::size_t num_threads, const std::string& name) + : impl{std::make_unique(kernel, num_threads, name)} {} + +ServiceThread::~ServiceThread() = default; + +void ServiceThread::QueueSyncRequest(ServerSession& session, + std::shared_ptr&& context) { + impl->QueueSyncRequest(session, std::move(context)); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/service_thread.h b/src/core/hle/kernel/service_thread.h new file mode 100644 index 000000000..025ab8fb5 --- /dev/null +++ b/src/core/hle/kernel/service_thread.h @@ -0,0 +1,28 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +namespace Kernel { + +class HLERequestContext; +class KernelCore; +class ServerSession; + +class ServiceThread final { +public: + explicit ServiceThread(KernelCore& kernel, std::size_t num_threads, const std::string& name); + ~ServiceThread(); + + void QueueSyncRequest(ServerSession& session, std::shared_ptr&& context); + +private: + class Impl; + std::unique_ptr impl; +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index bafd1ced7..de3ed25da 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -24,6 +24,8 @@ #include "core/hle/kernel/client_session.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/memory/memory_block.h" #include "core/hle/kernel/memory/page_table.h" @@ -32,7 +34,6 @@ #include "core/hle/kernel/process.h" #include "core/hle/kernel/readable_event.h" #include "core/hle/kernel/resource_limit.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/svc.h" #include "core/hle/kernel/svc_types.h" @@ -234,8 +235,7 @@ static ResultCode SetMemoryAttribute(Core::System& system, VAddr address, u64 si static ResultCode SetMemoryAttribute32(Core::System& system, u32 address, u32 size, u32 mask, u32 attribute) { - return SetMemoryAttribute(system, static_cast(address), static_cast(size), - mask, attribute); + return SetMemoryAttribute(system, address, size, mask, attribute); } /// Maps a memory range into a different range. @@ -255,8 +255,7 @@ static ResultCode MapMemory(Core::System& system, VAddr dst_addr, VAddr src_addr } static ResultCode MapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) { - return MapMemory(system, static_cast(dst_addr), static_cast(src_addr), - static_cast(size)); + return MapMemory(system, dst_addr, src_addr, size); } /// Unmaps a region that was previously mapped with svcMapMemory @@ -276,8 +275,7 @@ static ResultCode UnmapMemory(Core::System& system, VAddr dst_addr, VAddr src_ad } static ResultCode UnmapMemory32(Core::System& system, u32 dst_addr, u32 src_addr, u32 size) { - return UnmapMemory(system, static_cast(dst_addr), static_cast(src_addr), - static_cast(size)); + return UnmapMemory(system, dst_addr, src_addr, size); } /// Connect to an OS service given the port name, returns the handle to the port to out @@ -332,7 +330,8 @@ static ResultCode ConnectToNamedPort32(Core::System& system, Handle* out_handle, /// Makes a blocking IPC call to an OS service. static ResultCode SendSyncRequest(Core::System& system, Handle handle) { - const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable(); + auto& kernel = system.Kernel(); + const auto& handle_table = kernel.CurrentProcess()->GetHandleTable(); std::shared_ptr session = handle_table.Get(handle); if (!session) { LOG_ERROR(Kernel_SVC, "called with invalid handle=0x{:08X}", handle); @@ -341,9 +340,9 @@ static ResultCode SendSyncRequest(Core::System& system, Handle handle) { LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName()); - auto thread = system.CurrentScheduler().GetCurrentThread(); + auto thread = kernel.CurrentScheduler()->GetCurrentThread(); { - SchedulerLock lock(system.Kernel()); + KScopedSchedulerLock lock(kernel); thread->InvalidateHLECallback(); thread->SetStatus(ThreadStatus::WaitIPC); session->SendSyncRequest(SharedFrom(thread), system.Memory(), system.CoreTiming()); @@ -352,12 +351,12 @@ static ResultCode SendSyncRequest(Core::System& system, Handle handle) { if (thread->HasHLECallback()) { Handle event_handle = thread->GetHLETimeEvent(); if (event_handle != InvalidHandle) { - auto& time_manager = system.Kernel().TimeManager(); + auto& time_manager = kernel.TimeManager(); time_manager.UnscheduleTimeEvent(event_handle); } { - SchedulerLock lock(system.Kernel()); + KScopedSchedulerLock lock(kernel); auto* sync_object = thread->GetHLESyncObject(); sync_object->RemoveWaitingThread(SharedFrom(thread)); } @@ -531,8 +530,7 @@ static ResultCode ArbitrateLock(Core::System& system, Handle holding_thread_hand static ResultCode ArbitrateLock32(Core::System& system, Handle holding_thread_handle, u32 mutex_addr, Handle requesting_thread_handle) { - return ArbitrateLock(system, holding_thread_handle, static_cast(mutex_addr), - requesting_thread_handle); + return ArbitrateLock(system, holding_thread_handle, mutex_addr, requesting_thread_handle); } /// Unlock a mutex @@ -555,7 +553,7 @@ static ResultCode ArbitrateUnlock(Core::System& system, VAddr mutex_addr) { } static ResultCode ArbitrateUnlock32(Core::System& system, u32 mutex_addr) { - return ArbitrateUnlock(system, static_cast(mutex_addr)); + return ArbitrateUnlock(system, mutex_addr); } enum class BreakType : u32 { @@ -658,7 +656,6 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { info2, has_dumped_buffer ? std::make_optional(debug_buffer) : std::nullopt); if (!break_reason.signal_debugger) { - SchedulerLock lock(system.Kernel()); LOG_CRITICAL( Debug_Emulated, "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", @@ -666,22 +663,18 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) { handle_debug_buffer(info1, info2); - auto* const current_thread = system.CurrentScheduler().GetCurrentThread(); + auto* const current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread(); const auto thread_processor_id = current_thread->GetProcessorID(); system.ArmInterface(static_cast(thread_processor_id)).LogBacktrace(); - - // Kill the current thread - system.Kernel().ExceptionalExit(); - current_thread->Stop(); } } static void Break32(Core::System& system, u32 reason, u32 info1, u32 info2) { - Break(system, reason, static_cast(info1), static_cast(info2)); + Break(system, reason, info1, info2); } /// Used to output a message on a debug hardware unit - does nothing on a retail unit -static void OutputDebugString([[maybe_unused]] Core::System& system, VAddr address, u64 len) { +static void OutputDebugString(Core::System& system, VAddr address, u64 len) { if (len == 0) { return; } @@ -922,7 +915,7 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha } const auto& core_timing = system.CoreTiming(); - const auto& scheduler = system.CurrentScheduler(); + const auto& scheduler = *system.Kernel().CurrentScheduler(); const auto* const current_thread = scheduler.GetCurrentThread(); const bool same_thread = current_thread == thread.get(); @@ -948,7 +941,7 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha static ResultCode GetInfo32(Core::System& system, u32* result_low, u32* result_high, u32 sub_id_low, u32 info_id, u32 handle, u32 sub_id_high) { - const u64 sub_id{static_cast(sub_id_low | (static_cast(sub_id_high) << 32))}; + const u64 sub_id{u64{sub_id_low} | (u64{sub_id_high} << 32)}; u64 res_value{}; const ResultCode result{GetInfo(system, &res_value, info_id, handle, sub_id)}; @@ -1009,7 +1002,7 @@ static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size) } static ResultCode MapPhysicalMemory32(Core::System& system, u32 addr, u32 size) { - return MapPhysicalMemory(system, static_cast(addr), static_cast(size)); + return MapPhysicalMemory(system, addr, size); } /// Unmaps memory previously mapped via MapPhysicalMemory @@ -1063,7 +1056,7 @@ static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size } static ResultCode UnmapPhysicalMemory32(Core::System& system, u32 addr, u32 size) { - return UnmapPhysicalMemory(system, static_cast(addr), static_cast(size)); + return UnmapPhysicalMemory(system, addr, size); } /// Sets the thread activity @@ -1090,7 +1083,7 @@ static ResultCode SetThreadActivity(Core::System& system, Handle handle, u32 act return ERR_INVALID_HANDLE; } - if (thread.get() == system.CurrentScheduler().GetCurrentThread()) { + if (thread.get() == system.Kernel().CurrentScheduler()->GetCurrentThread()) { LOG_ERROR(Kernel_SVC, "The thread handle specified is the current running thread"); return ERR_BUSY; } @@ -1123,7 +1116,7 @@ static ResultCode GetThreadContext(Core::System& system, VAddr thread_context, H return ERR_INVALID_HANDLE; } - if (thread.get() == system.CurrentScheduler().GetCurrentThread()) { + if (thread.get() == system.Kernel().CurrentScheduler()->GetCurrentThread()) { LOG_ERROR(Kernel_SVC, "The thread handle specified is the current running thread"); return ERR_BUSY; } @@ -1144,7 +1137,7 @@ static ResultCode GetThreadContext(Core::System& system, VAddr thread_context, H } static ResultCode GetThreadContext32(Core::System& system, u32 thread_context, Handle handle) { - return GetThreadContext(system, static_cast(thread_context), handle); + return GetThreadContext(system, thread_context, handle); } /// Gets the priority for the specified thread @@ -1281,8 +1274,7 @@ static ResultCode MapSharedMemory(Core::System& system, Handle shared_memory_han static ResultCode MapSharedMemory32(Core::System& system, Handle shared_memory_handle, u32 addr, u32 size, u32 permissions) { - return MapSharedMemory(system, shared_memory_handle, static_cast(addr), - static_cast(size), permissions); + return MapSharedMemory(system, shared_memory_handle, addr, size, permissions); } static ResultCode QueryProcessMemory(Core::System& system, VAddr memory_info_address, @@ -1480,7 +1472,7 @@ static void ExitProcess(Core::System& system) { current_process->PrepareForTermination(); // Kill the current thread - system.CurrentScheduler().GetCurrentThread()->Stop(); + system.Kernel().CurrentScheduler()->GetCurrentThread()->Stop(); } static void ExitProcess32(Core::System& system) { @@ -1552,8 +1544,7 @@ static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr e static ResultCode CreateThread32(Core::System& system, Handle* out_handle, u32 priority, u32 entry_point, u32 arg, u32 stack_top, s32 processor_id) { - return CreateThread(system, out_handle, static_cast(entry_point), static_cast(arg), - static_cast(stack_top), priority, processor_id); + return CreateThread(system, out_handle, entry_point, arg, stack_top, priority, processor_id); } /// Starts the thread for the provided handle @@ -1581,8 +1572,8 @@ static ResultCode StartThread32(Core::System& system, Handle thread_handle) { static void ExitThread(Core::System& system) { LOG_DEBUG(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC()); - auto* const current_thread = system.CurrentScheduler().GetCurrentThread(); - system.GlobalScheduler().RemoveThread(SharedFrom(current_thread)); + auto* const current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread(); + system.GlobalSchedulerContext().RemoveThread(SharedFrom(current_thread)); current_thread->Stop(); } @@ -1592,53 +1583,39 @@ static void ExitThread32(Core::System& system) { /// Sleep the current thread static void SleepThread(Core::System& system, s64 nanoseconds) { - LOG_DEBUG(Kernel_SVC, "called nanoseconds={}", nanoseconds); + LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); enum class SleepType : s64 { - YieldWithoutLoadBalancing = 0, - YieldWithLoadBalancing = -1, + YieldWithoutCoreMigration = 0, + YieldWithCoreMigration = -1, YieldAndWaitForLoadBalancing = -2, }; - auto& scheduler = system.CurrentScheduler(); - auto* const current_thread = scheduler.GetCurrentThread(); - bool is_redundant = false; - + auto& scheduler = *system.Kernel().CurrentScheduler(); if (nanoseconds <= 0) { switch (static_cast(nanoseconds)) { - case SleepType::YieldWithoutLoadBalancing: { - auto pair = current_thread->YieldSimple(); - is_redundant = pair.second; + case SleepType::YieldWithoutCoreMigration: { + scheduler.YieldWithoutCoreMigration(); break; } - case SleepType::YieldWithLoadBalancing: { - auto pair = current_thread->YieldAndBalanceLoad(); - is_redundant = pair.second; + case SleepType::YieldWithCoreMigration: { + scheduler.YieldWithCoreMigration(); break; } case SleepType::YieldAndWaitForLoadBalancing: { - auto pair = current_thread->YieldAndWaitForLoadBalancing(); - is_redundant = pair.second; + scheduler.YieldToAnyThread(); break; } default: UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds); } } else { - current_thread->Sleep(nanoseconds); - } - - if (is_redundant && !system.Kernel().IsMulticore()) { - system.Kernel().ExitSVCProfile(); - system.CoreTiming().AddTicks(1000U); - system.GetCpuManager().PreemptSingleCore(); - system.Kernel().EnterSVCProfile(); + scheduler.GetCurrentThread()->Sleep(nanoseconds); } } static void SleepThread32(Core::System& system, u32 nanoseconds_low, u32 nanoseconds_high) { - const s64 nanoseconds = static_cast(static_cast(nanoseconds_low) | - (static_cast(nanoseconds_high) << 32)); + const auto nanoseconds = static_cast(u64{nanoseconds_low} | (u64{nanoseconds_high} << 32)); SleepThread(system, nanoseconds); } @@ -1668,10 +1645,10 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_add ASSERT(condition_variable_addr == Common::AlignDown(condition_variable_addr, 4)); auto& kernel = system.Kernel(); Handle event_handle; - Thread* current_thread = system.CurrentScheduler().GetCurrentThread(); - auto* const current_process = system.Kernel().CurrentProcess(); + Thread* current_thread = kernel.CurrentScheduler()->GetCurrentThread(); + auto* const current_process = kernel.CurrentProcess(); { - SchedulerLockAndSleep lock(kernel, event_handle, current_thread, nano_seconds); + KScopedSchedulerLockAndSleep lock(kernel, event_handle, current_thread, nano_seconds); const auto& handle_table = current_process->GetHandleTable(); std::shared_ptr thread = handle_table.Get(thread_handle); ASSERT(thread); @@ -1707,7 +1684,7 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_add } { - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); auto* owner = current_thread->GetLockOwner(); if (owner != nullptr) { @@ -1724,10 +1701,8 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr mutex_add static ResultCode WaitProcessWideKeyAtomic32(Core::System& system, u32 mutex_addr, u32 condition_variable_addr, Handle thread_handle, u32 nanoseconds_low, u32 nanoseconds_high) { - const s64 nanoseconds = - static_cast(nanoseconds_low | (static_cast(nanoseconds_high) << 32)); - return WaitProcessWideKeyAtomic(system, static_cast(mutex_addr), - static_cast(condition_variable_addr), thread_handle, + const auto nanoseconds = static_cast(nanoseconds_low | (u64{nanoseconds_high} << 32)); + return WaitProcessWideKeyAtomic(system, mutex_addr, condition_variable_addr, thread_handle, nanoseconds); } @@ -1740,7 +1715,7 @@ static void SignalProcessWideKey(Core::System& system, VAddr condition_variable_ // Retrieve a list of all threads that are waiting for this condition variable. auto& kernel = system.Kernel(); - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); auto* const current_process = kernel.CurrentProcess(); std::vector> waiting_threads = current_process->GetConditionVariableThreads(condition_variable_addr); @@ -1833,8 +1808,8 @@ static ResultCode WaitForAddress(Core::System& system, VAddr address, u32 type, static ResultCode WaitForAddress32(Core::System& system, u32 address, u32 type, s32 value, u32 timeout_low, u32 timeout_high) { - s64 timeout = static_cast(timeout_low | (static_cast(timeout_high) << 32)); - return WaitForAddress(system, static_cast(address), type, value, timeout); + const auto timeout = static_cast(timeout_low | (u64{timeout_high} << 32)); + return WaitForAddress(system, address, type, value, timeout); } // Signals to an address (via Address Arbiter) @@ -1862,7 +1837,7 @@ static ResultCode SignalToAddress(Core::System& system, VAddr address, u32 type, static ResultCode SignalToAddress32(Core::System& system, u32 address, u32 type, s32 value, s32 num_to_wake) { - return SignalToAddress(system, static_cast(address), type, value, num_to_wake); + return SignalToAddress(system, address, type, value, num_to_wake); } static void KernelDebug([[maybe_unused]] Core::System& system, @@ -1893,7 +1868,7 @@ static u64 GetSystemTick(Core::System& system) { } static void GetSystemTick32(Core::System& system, u32* time_low, u32* time_high) { - u64 time = GetSystemTick(system); + const auto time = GetSystemTick(system); *time_low = static_cast(time); *time_high = static_cast(time >> 32); } @@ -1984,8 +1959,7 @@ static ResultCode CreateTransferMemory(Core::System& system, Handle* handle, VAd static ResultCode CreateTransferMemory32(Core::System& system, Handle* handle, u32 addr, u32 size, u32 permissions) { - return CreateTransferMemory(system, handle, static_cast(addr), - static_cast(size), permissions); + return CreateTransferMemory(system, handle, addr, size, permissions); } static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle, u32* core, @@ -2003,7 +1977,7 @@ static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle, } *core = thread->GetIdealCore(); - *mask = thread->GetAffinityMask(); + *mask = thread->GetAffinityMask().GetAffinityMask(); return RESULT_SUCCESS; } @@ -2075,8 +2049,7 @@ static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle, static ResultCode SetThreadCoreMask32(Core::System& system, Handle thread_handle, u32 core, u32 affinity_mask_low, u32 affinity_mask_high) { - const u64 affinity_mask = - static_cast(affinity_mask_low) | (static_cast(affinity_mask_high) << 32); + const auto affinity_mask = u64{affinity_mask_low} | (u64{affinity_mask_high} << 32); return SetThreadCoreMask(system, thread_handle, core, affinity_mask); } @@ -2341,9 +2314,10 @@ static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAdd return RESULT_SUCCESS; } -static ResultCode FlushProcessDataCache32(Core::System& system, Handle handle, u32 address, - u32 size) { - // Note(Blinkhawk): For emulation purposes of the data cache this is mostly a nope +static ResultCode FlushProcessDataCache32([[maybe_unused]] Core::System& system, + [[maybe_unused]] Handle handle, + [[maybe_unused]] u32 address, [[maybe_unused]] u32 size) { + // Note(Blinkhawk): For emulation purposes of the data cache this is mostly a no-op, // as all emulation is done in the same cache level in host architecture, thus data cache // does not need flushing. LOG_DEBUG(Kernel_SVC, "called"); @@ -2639,6 +2613,9 @@ void Call(Core::System& system, u32 immediate) { auto& kernel = system.Kernel(); kernel.EnterSVCProfile(); + auto* thread = kernel.CurrentScheduler()->GetCurrentThread(); + thread->SetContinuousOnSVC(true); + const FunctionDef* info = system.CurrentProcess()->Is64BitProcess() ? GetSVCInfo64(immediate) : GetSVCInfo32(immediate); if (info) { @@ -2652,6 +2629,12 @@ void Call(Core::System& system, u32 immediate) { } kernel.ExitSVCProfile(); + + if (!thread->IsContinuousOnSVC()) { + auto* host_context = thread->GetHostContext().get(); + host_context->Rewind(); + } + system.EnterDynarmicProfile(); } diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h index 986724beb..11e1d8e2d 100644 --- a/src/core/hle/kernel/svc_types.h +++ b/src/core/hle/kernel/svc_types.h @@ -23,8 +23,8 @@ enum class MemoryState : u32 { Ipc = 0x0A, Stack = 0x0B, ThreadLocal = 0x0C, - Transfered = 0x0D, - SharedTransfered = 0x0E, + Transferred = 0x0D, + SharedTransferred = 0x0E, SharedCode = 0x0F, Inaccessible = 0x10, NonSecureIpc = 0x11, diff --git a/src/core/hle/kernel/synchronization.cpp b/src/core/hle/kernel/synchronization.cpp index 8b875d853..d3f520ea2 100644 --- a/src/core/hle/kernel/synchronization.cpp +++ b/src/core/hle/kernel/synchronization.cpp @@ -5,8 +5,9 @@ #include "core/core.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/synchronization.h" #include "core/hle/kernel/synchronization_object.h" #include "core/hle/kernel/thread.h" @@ -18,7 +19,7 @@ Synchronization::Synchronization(Core::System& system) : system{system} {} void Synchronization::SignalObject(SynchronizationObject& obj) const { auto& kernel = system.Kernel(); - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); if (obj.IsSignaled()) { for (auto thread : obj.GetWaitingThreads()) { if (thread->GetSchedulingStatus() == ThreadSchedStatus::Paused) { @@ -37,10 +38,10 @@ void Synchronization::SignalObject(SynchronizationObject& obj) const { std::pair Synchronization::WaitFor( std::vector>& sync_objects, s64 nano_seconds) { auto& kernel = system.Kernel(); - auto* const thread = system.CurrentScheduler().GetCurrentThread(); + auto* const thread = kernel.CurrentScheduler()->GetCurrentThread(); Handle event_handle = InvalidHandle; { - SchedulerLockAndSleep lock(kernel, event_handle, thread, nano_seconds); + KScopedSchedulerLockAndSleep lock(kernel, event_handle, thread, nano_seconds); const auto itr = std::find_if(sync_objects.begin(), sync_objects.end(), [thread](const std::shared_ptr& object) { @@ -89,7 +90,7 @@ std::pair Synchronization::WaitFor( } { - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); ResultCode signaling_result = thread->GetSignalingResult(); SynchronizationObject* signaling_object = thread->GetSignalingObject(); thread->SetSynchronizationObjects(nullptr); diff --git a/src/core/hle/kernel/synchronization_object.h b/src/core/hle/kernel/synchronization_object.h index f89b24204..7408ed51f 100644 --- a/src/core/hle/kernel/synchronization_object.h +++ b/src/core/hle/kernel/synchronization_object.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include @@ -56,7 +57,7 @@ public: void ClearWaitingThreads(); protected: - bool is_signaled{}; // Tells if this sync object is signalled; + std::atomic_bool is_signaled{}; // Tells if this sync object is signaled private: /// Threads waiting for this object to become available diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index d132aba34..a4f9e0d97 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -12,17 +12,16 @@ #include "common/fiber.h" #include "common/logging/log.h" #include "common/thread_queue_list.h" -#include "core/arm/arm_interface.h" -#include "core/arm/unicorn/arm_unicorn.h" #include "core/core.h" #include "core/cpu_manager.h" #include "core/hardware_properties.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/process.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/time_manager.h" #include "core/hle/result.h" @@ -52,7 +51,7 @@ Thread::~Thread() = default; void Thread::Stop() { { - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); SetStatus(ThreadStatus::Dead); Signal(); kernel.GlobalHandleTable().Close(global_handle); @@ -63,14 +62,13 @@ void Thread::Stop() { // Mark the TLS slot in the thread's page as free. owner_process->FreeTLSRegion(tls_address); } - arm_interface.reset(); has_exited = true; } global_handle = 0; } void Thread::ResumeFromWait() { - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); switch (status) { case ThreadStatus::Paused: case ThreadStatus::WaitSynch: @@ -91,10 +89,6 @@ void Thread::ResumeFromWait() { // before actually resuming. We can ignore subsequent wakeups if the thread status has // already been set to ThreadStatus::Ready. return; - - case ThreadStatus::Running: - DEBUG_ASSERT_MSG(false, "Thread with object id {} has already resumed.", GetObjectId()); - return; case ThreadStatus::Dead: // This should never happen, as threads must complete before being stopped. DEBUG_ASSERT_MSG(false, "Thread with object id {} cannot be resumed because it's DEAD.", @@ -106,19 +100,18 @@ void Thread::ResumeFromWait() { } void Thread::OnWakeUp() { - SchedulerLock lock(kernel); - + KScopedSchedulerLock lock(kernel); SetStatus(ThreadStatus::Ready); } ResultCode Thread::Start() { - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); SetStatus(ThreadStatus::Ready); return RESULT_SUCCESS; } void Thread::CancelWait() { - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); if (GetSchedulingStatus() != ThreadSchedStatus::Paused || !is_waiting_on_sync) { is_sync_cancelled = true; return; @@ -193,12 +186,14 @@ ResultVal> Thread::Create(Core::System& system, ThreadTy thread->status = ThreadStatus::Dormant; thread->entry_point = entry_point; thread->stack_top = stack_top; + thread->disable_count = 1; thread->tpidr_el0 = 0; thread->nominal_priority = thread->current_priority = priority; - thread->last_running_ticks = 0; + thread->schedule_count = -1; + thread->last_scheduled_tick = 0; thread->processor_id = processor_id; thread->ideal_core = processor_id; - thread->affinity_mask = 1ULL << processor_id; + thread->affinity_mask.SetAffinity(processor_id, true); thread->wait_objects = nullptr; thread->mutex_wait_address = 0; thread->condvar_wait_address = 0; @@ -208,7 +203,7 @@ ResultVal> Thread::Create(Core::System& system, ThreadTy thread->owner_process = owner_process; thread->type = type_flags; if ((type_flags & THREADTYPE_IDLE) == 0) { - auto& scheduler = kernel.GlobalScheduler(); + auto& scheduler = kernel.GlobalSchedulerContext(); scheduler.AddThread(thread); } if (owner_process) { @@ -217,33 +212,10 @@ ResultVal> Thread::Create(Core::System& system, ThreadTy } else { thread->tls_address = 0; } + // TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used // to initialize the context - thread->arm_interface.reset(); if ((type_flags & THREADTYPE_HLE) == 0) { -#ifdef ARCHITECTURE_x86_64 - if (owner_process && !owner_process->Is64BitProcess()) { - thread->arm_interface = std::make_unique( - system, kernel.Interrupts(), kernel.IsMulticore(), kernel.GetExclusiveMonitor(), - processor_id); - } else { - thread->arm_interface = std::make_unique( - system, kernel.Interrupts(), kernel.IsMulticore(), kernel.GetExclusiveMonitor(), - processor_id); - } - -#else - if (owner_process && !owner_process->Is64BitProcess()) { - thread->arm_interface = std::make_shared( - system, kernel.Interrupts(), kernel.IsMulticore(), ARM_Unicorn::Arch::AArch32, - processor_id); - } else { - thread->arm_interface = std::make_shared( - system, kernel.Interrupts(), kernel.IsMulticore(), ARM_Unicorn::Arch::AArch64, - processor_id); - } - LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available"); -#endif ResetThreadContext32(thread->context_32, static_cast(stack_top), static_cast(entry_point), static_cast(arg)); ResetThreadContext64(thread->context_64, stack_top, entry_point, arg); @@ -255,7 +227,7 @@ ResultVal> Thread::Create(Core::System& system, ThreadTy } void Thread::SetPriority(u32 priority) { - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST, "Invalid priority value."); nominal_priority = priority; @@ -279,14 +251,6 @@ VAddr Thread::GetCommandBufferAddress() const { return GetTLSAddress() + command_header_offset; } -Core::ARM_Interface& Thread::ArmInterface() { - return *arm_interface; -} - -const Core::ARM_Interface& Thread::ArmInterface() const { - return *arm_interface; -} - void Thread::SetStatus(ThreadStatus new_status) { if (new_status == status) { return; @@ -294,7 +258,6 @@ void Thread::SetStatus(ThreadStatus new_status) { switch (new_status) { case ThreadStatus::Ready: - case ThreadStatus::Running: SetSchedulingStatus(ThreadSchedStatus::Runnable); break; case ThreadStatus::Dormant: @@ -401,7 +364,7 @@ bool Thread::InvokeHLECallback(std::shared_ptr thread) { } ResultCode Thread::SetActivity(ThreadActivity value) { - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); auto sched_status = GetSchedulingStatus(); @@ -430,7 +393,7 @@ ResultCode Thread::SetActivity(ThreadActivity value) { ResultCode Thread::Sleep(s64 nanoseconds) { Handle event_handle{}; { - SchedulerLockAndSleep lock(kernel, event_handle, this, nanoseconds); + KScopedSchedulerLockAndSleep lock(kernel, event_handle, this, nanoseconds); SetStatus(ThreadStatus::WaitSleep); } @@ -441,39 +404,12 @@ ResultCode Thread::Sleep(s64 nanoseconds) { return RESULT_SUCCESS; } -std::pair Thread::YieldSimple() { - bool is_redundant = false; - { - SchedulerLock lock(kernel); - is_redundant = kernel.GlobalScheduler().YieldThread(this); - } - return {RESULT_SUCCESS, is_redundant}; -} - -std::pair Thread::YieldAndBalanceLoad() { - bool is_redundant = false; - { - SchedulerLock lock(kernel); - is_redundant = kernel.GlobalScheduler().YieldThreadAndBalanceLoad(this); - } - return {RESULT_SUCCESS, is_redundant}; -} - -std::pair Thread::YieldAndWaitForLoadBalancing() { - bool is_redundant = false; - { - SchedulerLock lock(kernel); - is_redundant = kernel.GlobalScheduler().YieldThreadAndWaitForLoadBalancing(this); - } - return {RESULT_SUCCESS, is_redundant}; -} - void Thread::AddSchedulingFlag(ThreadSchedFlags flag) { const u32 old_state = scheduling_state; pausing_state |= static_cast(flag); const u32 base_scheduling = static_cast(GetSchedulingStatus()); scheduling_state = base_scheduling | pausing_state; - kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state); + KScheduler::OnThreadStateChanged(kernel, this, old_state); } void Thread::RemoveSchedulingFlag(ThreadSchedFlags flag) { @@ -481,23 +417,24 @@ void Thread::RemoveSchedulingFlag(ThreadSchedFlags flag) { pausing_state &= ~static_cast(flag); const u32 base_scheduling = static_cast(GetSchedulingStatus()); scheduling_state = base_scheduling | pausing_state; - kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state); + KScheduler::OnThreadStateChanged(kernel, this, old_state); } void Thread::SetSchedulingStatus(ThreadSchedStatus new_status) { const u32 old_state = scheduling_state; scheduling_state = (scheduling_state & static_cast(ThreadSchedMasks::HighMask)) | static_cast(new_status); - kernel.GlobalScheduler().AdjustSchedulingOnStatus(this, old_state); + KScheduler::OnThreadStateChanged(kernel, this, old_state); } void Thread::SetCurrentPriority(u32 new_priority) { const u32 old_priority = std::exchange(current_priority, new_priority); - kernel.GlobalScheduler().AdjustSchedulingOnPriority(this, old_priority); + KScheduler::OnThreadPriorityChanged(kernel, this, kernel.CurrentScheduler()->GetCurrentThread(), + old_priority); } ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) { - SchedulerLock lock(kernel); + KScopedSchedulerLock lock(kernel); const auto HighestSetCore = [](u64 mask, u32 max_cores) { for (s32 core = static_cast(max_cores - 1); core >= 0; core--) { if (((mask >> core) & 1) != 0) { @@ -518,20 +455,21 @@ ResultCode Thread::SetCoreAndAffinityMask(s32 new_core, u64 new_affinity_mask) { } if (use_override) { ideal_core_override = new_core; - affinity_mask_override = new_affinity_mask; } else { - const u64 old_affinity_mask = std::exchange(affinity_mask, new_affinity_mask); + const auto old_affinity_mask = affinity_mask; + affinity_mask.SetAffinityMask(new_affinity_mask); ideal_core = new_core; - if (old_affinity_mask != new_affinity_mask) { + if (old_affinity_mask.GetAffinityMask() != new_affinity_mask) { const s32 old_core = processor_id; - if (processor_id >= 0 && ((affinity_mask >> processor_id) & 1) == 0) { + if (processor_id >= 0 && !affinity_mask.GetAffinity(processor_id)) { if (static_cast(ideal_core) < 0) { - processor_id = HighestSetCore(affinity_mask, Core::Hardware::NUM_CPU_CORES); + processor_id = HighestSetCore(affinity_mask.GetAffinityMask(), + Core::Hardware::NUM_CPU_CORES); } else { processor_id = ideal_core; } } - kernel.GlobalScheduler().AdjustSchedulingOnAffinity(this, old_affinity_mask, old_core); + KScheduler::OnThreadAffinityMaskChanged(kernel, this, old_affinity_mask, old_core); } } return RESULT_SUCCESS; diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 8daf79fac..11ef29888 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -12,6 +13,7 @@ #include "common/common_types.h" #include "common/spin_lock.h" #include "core/arm/arm_interface.h" +#include "core/hle/kernel/k_affinity_mask.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/synchronization_object.h" #include "core/hle/result.h" @@ -27,10 +29,10 @@ class System; namespace Kernel { -class GlobalScheduler; +class GlobalSchedulerContext; class KernelCore; class Process; -class Scheduler; +class KScheduler; enum ThreadPriority : u32 { THREADPRIO_HIGHEST = 0, ///< Highest thread priority @@ -72,7 +74,6 @@ enum ThreadProcessorId : s32 { }; enum class ThreadStatus { - Running, ///< Currently running Ready, ///< Ready to run Paused, ///< Paused by SetThreadActivity or debug WaitHLEEvent, ///< Waiting for hle event to finish @@ -248,10 +249,6 @@ public: void SetSynchronizationResults(SynchronizationObject* object, ResultCode result); - Core::ARM_Interface& ArmInterface(); - - const Core::ARM_Interface& ArmInterface() const; - SynchronizationObject* GetSignalingObject() const { return signaling_object; } @@ -350,8 +347,12 @@ public: void SetStatus(ThreadStatus new_status); - u64 GetLastRunningTicks() const { - return last_running_ticks; + s64 GetLastScheduledTick() const { + return this->last_scheduled_tick; + } + + void SetLastScheduledTick(s64 tick) { + this->last_scheduled_tick = tick; } u64 GetTotalCPUTimeTicks() const { @@ -366,10 +367,18 @@ public: return processor_id; } + s32 GetActiveCore() const { + return GetProcessorID(); + } + void SetProcessorID(s32 new_core) { processor_id = new_core; } + void SetActiveCore(s32 new_core) { + processor_id = new_core; + } + Process* GetOwnerProcess() { return owner_process; } @@ -474,7 +483,7 @@ public: return ideal_core; } - u64 GetAffinityMask() const { + const KAffinityMask& GetAffinityMask() const { return affinity_mask; } @@ -483,21 +492,12 @@ public: /// Sleeps this thread for the given amount of nanoseconds. ResultCode Sleep(s64 nanoseconds); - /// Yields this thread without rebalancing loads. - std::pair YieldSimple(); - - /// Yields this thread and does a load rebalancing. - std::pair YieldAndBalanceLoad(); - - /// Yields this thread and if the core is left idle, loads are rebalanced - std::pair YieldAndWaitForLoadBalancing(); - - void IncrementYieldCount() { - yield_count++; + s64 GetYieldScheduleCount() const { + return this->schedule_count; } - u64 GetYieldCount() const { - return yield_count; + void SetYieldScheduleCount(s64 count) { + this->schedule_count = count; } ThreadSchedStatus GetSchedulingStatus() const { @@ -573,9 +573,59 @@ public: return has_exited; } + class QueueEntry { + public: + constexpr QueueEntry() = default; + + constexpr void Initialize() { + this->prev = nullptr; + this->next = nullptr; + } + + constexpr Thread* GetPrev() const { + return this->prev; + } + constexpr Thread* GetNext() const { + return this->next; + } + constexpr void SetPrev(Thread* thread) { + this->prev = thread; + } + constexpr void SetNext(Thread* thread) { + this->next = thread; + } + + private: + Thread* prev{}; + Thread* next{}; + }; + + QueueEntry& GetPriorityQueueEntry(s32 core) { + return this->per_core_priority_queue_entry[core]; + } + + const QueueEntry& GetPriorityQueueEntry(s32 core) const { + return this->per_core_priority_queue_entry[core]; + } + + s32 GetDisableDispatchCount() const { + return disable_count; + } + + void DisableDispatch() { + ASSERT(GetDisableDispatchCount() >= 0); + disable_count++; + } + + void EnableDispatch() { + ASSERT(GetDisableDispatchCount() > 0); + disable_count--; + } + private: - friend class GlobalScheduler; - friend class Scheduler; + friend class GlobalSchedulerContext; + friend class KScheduler; + friend class Process; void SetSchedulingStatus(ThreadSchedStatus new_status); void AddSchedulingFlag(ThreadSchedFlags flag); @@ -586,15 +636,16 @@ private: Common::SpinLock context_guard{}; ThreadContext32 context_32{}; ThreadContext64 context_64{}; - std::unique_ptr arm_interface{}; std::shared_ptr host_context{}; + ThreadStatus status = ThreadStatus::Dormant; + u32 scheduling_state = 0; + u64 thread_id = 0; - ThreadStatus status = ThreadStatus::Dormant; - VAddr entry_point = 0; VAddr stack_top = 0; + std::atomic_int disable_count = 0; ThreadType type; @@ -608,9 +659,8 @@ private: u32 current_priority = 0; u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks. - u64 last_running_ticks = 0; ///< CPU tick when thread was last running - u64 yield_count = 0; ///< Number of redundant yields carried by this thread. - ///< a redundant yield is one where no scheduling is changed + s64 schedule_count{}; + s64 last_scheduled_tick{}; s32 processor_id = 0; @@ -652,16 +702,16 @@ private: Handle hle_time_event; SynchronizationObject* hle_object; - Scheduler* scheduler = nullptr; + KScheduler* scheduler = nullptr; + + std::array per_core_priority_queue_entry{}; u32 ideal_core{0xFFFFFFFF}; - u64 affinity_mask{0x1}; + KAffinityMask affinity_mask{}; s32 ideal_core_override = -1; - u64 affinity_mask_override = 0x1; u32 affinity_override_count = 0; - u32 scheduling_state = 0; u32 pausing_state = 0; bool is_running = false; bool is_waiting_on_sync = false; diff --git a/src/core/hle/kernel/time_manager.cpp b/src/core/hle/kernel/time_manager.cpp index 95f2446c9..79628e2b4 100644 --- a/src/core/hle/kernel/time_manager.cpp +++ b/src/core/hle/kernel/time_manager.cpp @@ -7,8 +7,8 @@ #include "core/core_timing.h" #include "core/core_timing_util.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/k_scheduler.h" #include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/time_manager.h" @@ -18,17 +18,27 @@ TimeManager::TimeManager(Core::System& system_) : system{system_} { time_manager_event_type = Core::Timing::CreateEvent( "Kernel::TimeManagerCallback", [this](std::uintptr_t thread_handle, std::chrono::nanoseconds) { - const SchedulerLock lock(system.Kernel()); + const KScopedSchedulerLock lock(system.Kernel()); const auto proper_handle = static_cast(thread_handle); - if (cancelled_events[proper_handle]) { - return; + + std::shared_ptr thread; + { + std::lock_guard lock{mutex}; + if (cancelled_events[proper_handle]) { + return; + } + thread = system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle); + } + + if (thread) { + // Thread can be null if process has exited + thread->OnWakeUp(); } - auto thread = this->system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle); - thread->OnWakeUp(); }); } void TimeManager::ScheduleTimeEvent(Handle& event_handle, Thread* timetask, s64 nanoseconds) { + std::lock_guard lock{mutex}; event_handle = timetask->GetGlobalHandle(); if (nanoseconds > 0) { ASSERT(timetask); @@ -43,6 +53,7 @@ void TimeManager::ScheduleTimeEvent(Handle& event_handle, Thread* timetask, s64 } void TimeManager::UnscheduleTimeEvent(Handle event_handle) { + std::lock_guard lock{mutex}; if (event_handle == InvalidHandle) { return; } @@ -51,7 +62,8 @@ void TimeManager::UnscheduleTimeEvent(Handle event_handle) { } void TimeManager::CancelTimeEvent(Thread* time_task) { - Handle event_handle = time_task->GetGlobalHandle(); + std::lock_guard lock{mutex}; + const Handle event_handle = time_task->GetGlobalHandle(); UnscheduleTimeEvent(event_handle); } diff --git a/src/core/hle/kernel/time_manager.h b/src/core/hle/kernel/time_manager.h index 307a18765..f39df39a0 100644 --- a/src/core/hle/kernel/time_manager.h +++ b/src/core/hle/kernel/time_manager.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include "core/hle/kernel/object.h" @@ -42,6 +43,7 @@ private: Core::System& system; std::shared_ptr time_manager_event_type; std::unordered_map cancelled_events; + std::mutex mutex; }; } // namespace Kernel diff --git a/src/core/hle/result.h b/src/core/hle/result.h index b6bdbd988..8feda7ad7 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -119,7 +119,7 @@ union ResultCode { BitField<0, 9, ErrorModule> module; BitField<9, 13, u32> description; - constexpr explicit ResultCode(u32 raw) : raw(raw) {} + constexpr explicit ResultCode(u32 raw_) : raw(raw_) {} constexpr ResultCode(ErrorModule module_, u32 description_) : raw(module.FormatValue(module_) | description.FormatValue(description_)) {} diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index 6b1613510..6981f8ee7 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -11,6 +11,7 @@ #include "common/string_util.h" #include "common/swap.h" #include "core/constants.h" +#include "core/core.h" #include "core/core_timing.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" @@ -46,8 +47,8 @@ static constexpr u32 SanitizeJPEGSize(std::size_t size) { class IManagerForSystemService final : public ServiceFramework { public: - explicit IManagerForSystemService(Common::UUID user_id) - : ServiceFramework("IManagerForSystemService") { + explicit IManagerForSystemService(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IManagerForSystemService"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CheckAvailability"}, @@ -82,8 +83,8 @@ public: // 3.0.0+ class IFloatingRegistrationRequest final : public ServiceFramework { public: - explicit IFloatingRegistrationRequest(Common::UUID user_id) - : ServiceFramework("IFloatingRegistrationRequest") { + explicit IFloatingRegistrationRequest(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IFloatingRegistrationRequest"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetSessionId"}, @@ -107,7 +108,8 @@ public: class IAdministrator final : public ServiceFramework { public: - explicit IAdministrator(Common::UUID user_id) : ServiceFramework("IAdministrator") { + explicit IAdministrator(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IAdministrator"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CheckAvailability"}, @@ -164,8 +166,8 @@ public: class IAuthorizationRequest final : public ServiceFramework { public: - explicit IAuthorizationRequest(Common::UUID user_id) - : ServiceFramework("IAuthorizationRequest") { + explicit IAuthorizationRequest(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IAuthorizationRequest"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetSessionId"}, @@ -183,7 +185,8 @@ public: class IOAuthProcedure final : public ServiceFramework { public: - explicit IOAuthProcedure(Common::UUID user_id) : ServiceFramework("IOAuthProcedure") { + explicit IOAuthProcedure(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IOAuthProcedure"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "PrepareAsync"}, @@ -201,8 +204,8 @@ public: // 3.0.0+ class IOAuthProcedureForExternalNsa final : public ServiceFramework { public: - explicit IOAuthProcedureForExternalNsa(Common::UUID user_id) - : ServiceFramework("IOAuthProcedureForExternalNsa") { + explicit IOAuthProcedureForExternalNsa(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IOAuthProcedureForExternalNsa"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "PrepareAsync"}, @@ -224,8 +227,8 @@ public: class IOAuthProcedureForNintendoAccountLinkage final : public ServiceFramework { public: - explicit IOAuthProcedureForNintendoAccountLinkage(Common::UUID user_id) - : ServiceFramework("IOAuthProcedureForNintendoAccountLinkage") { + explicit IOAuthProcedureForNintendoAccountLinkage(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IOAuthProcedureForNintendoAccountLinkage"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "PrepareAsync"}, @@ -245,7 +248,8 @@ public: class INotifier final : public ServiceFramework { public: - explicit INotifier(Common::UUID user_id) : ServiceFramework("INotifier") { + explicit INotifier(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "INotifier"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetSystemEvent"}, @@ -258,9 +262,9 @@ public: class IProfileCommon : public ServiceFramework { public: - explicit IProfileCommon(const char* name, bool editor_commands, Common::UUID user_id, - ProfileManager& profile_manager) - : ServiceFramework(name), profile_manager(profile_manager), user_id(user_id) { + explicit IProfileCommon(Core::System& system_, const char* name, bool editor_commands, + Common::UUID user_id_, ProfileManager& profile_manager_) + : ServiceFramework{system_, name}, profile_manager{profile_manager_}, user_id{user_id_} { static const FunctionInfo functions[] = { {0, &IProfileCommon::Get, "Get"}, {1, &IProfileCommon::GetBase, "GetBase"}, @@ -426,19 +430,21 @@ protected: class IProfile final : public IProfileCommon { public: - IProfile(Common::UUID user_id, ProfileManager& profile_manager) - : IProfileCommon("IProfile", false, user_id, profile_manager) {} + explicit IProfile(Core::System& system_, Common::UUID user_id_, + ProfileManager& profile_manager_) + : IProfileCommon{system_, "IProfile", false, user_id_, profile_manager_} {} }; class IProfileEditor final : public IProfileCommon { public: - IProfileEditor(Common::UUID user_id, ProfileManager& profile_manager) - : IProfileCommon("IProfileEditor", true, user_id, profile_manager) {} + explicit IProfileEditor(Core::System& system_, Common::UUID user_id_, + ProfileManager& profile_manager_) + : IProfileCommon{system_, "IProfileEditor", true, user_id_, profile_manager_} {} }; class IAsyncContext final : public ServiceFramework { public: - explicit IAsyncContext(Common::UUID user_id) : ServiceFramework("IAsyncContext") { + explicit IAsyncContext(Core::System& system_) : ServiceFramework{system_, "IAsyncContext"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetSystemEvent"}, @@ -454,7 +460,8 @@ public: class ISessionObject final : public ServiceFramework { public: - explicit ISessionObject(Common::UUID user_id) : ServiceFramework("ISessionObject") { + explicit ISessionObject(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "ISessionObject"} { // clang-format off static const FunctionInfo functions[] = { {999, nullptr, "Dummy"}, @@ -467,7 +474,8 @@ public: class IGuestLoginRequest final : public ServiceFramework { public: - explicit IGuestLoginRequest(Common::UUID) : ServiceFramework("IGuestLoginRequest") { + explicit IGuestLoginRequest(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IGuestLoginRequest"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetSessionId"}, @@ -486,8 +494,8 @@ public: class IManagerForApplication final : public ServiceFramework { public: - explicit IManagerForApplication(Common::UUID user_id) - : ServiceFramework("IManagerForApplication"), user_id(user_id) { + explicit IManagerForApplication(Core::System& system_, Common::UUID user_id_) + : ServiceFramework{system_, "IManagerForApplication"}, user_id{user_id_} { // clang-format off static const FunctionInfo functions[] = { {0, &IManagerForApplication::CheckAvailability, "CheckAvailability"}, @@ -496,7 +504,7 @@ public: {3, nullptr, "LoadIdTokenCache"}, {130, nullptr, "GetNintendoAccountUserResourceCacheForApplication"}, {150, nullptr, "CreateAuthorizationRequest"}, - {160, nullptr, "StoreOpenContext"}, + {160, &IManagerForApplication::StoreOpenContext, "StoreOpenContext"}, {170, nullptr, "LoadNetworkServiceLicenseKindAsync"}, }; // clang-format on @@ -520,6 +528,12 @@ private: rb.PushRaw(user_id.GetNintendoID()); } + void StoreOpenContext(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + Common::UUID user_id; }; @@ -527,8 +541,8 @@ private: class IAsyncNetworkServiceLicenseKindContext final : public ServiceFramework { public: - explicit IAsyncNetworkServiceLicenseKindContext(Common::UUID user_id) - : ServiceFramework("IAsyncNetworkServiceLicenseKindContext") { + explicit IAsyncNetworkServiceLicenseKindContext(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IAsyncNetworkServiceLicenseKindContext"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetSystemEvent"}, @@ -547,8 +561,8 @@ public: class IOAuthProcedureForUserRegistration final : public ServiceFramework { public: - explicit IOAuthProcedureForUserRegistration(Common::UUID user_id) - : ServiceFramework("IOAuthProcedureForUserRegistration") { + explicit IOAuthProcedureForUserRegistration(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IOAuthProcedureForUserRegistration"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "PrepareAsync"}, @@ -571,7 +585,7 @@ public: class DAUTH_O final : public ServiceFramework { public: - explicit DAUTH_O(Common::UUID) : ServiceFramework("dauth:o") { + explicit DAUTH_O(Core::System& system_, Common::UUID) : ServiceFramework{system_, "dauth:o"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "EnsureAuthenticationTokenCacheAsync"}, // [5.0.0-5.1.0] GeneratePostData @@ -590,7 +604,8 @@ public: // 6.0.0+ class IAsyncResult final : public ServiceFramework { public: - explicit IAsyncResult(Common::UUID user_id) : ServiceFramework("IAsyncResult") { + explicit IAsyncResult(Core::System& system_, Common::UUID) + : ServiceFramework{system_, "IAsyncResult"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetResult"}, @@ -649,7 +664,7 @@ void Module::Interface::GetProfile(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(user_id, *profile_manager); + rb.PushIpcInterface(system, user_id, *profile_manager); } void Module::Interface::IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx) { @@ -724,7 +739,7 @@ void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestCo LOG_DEBUG(Service_ACC, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(profile_manager->GetLastOpenedUser()); + rb.PushIpcInterface(system, profile_manager->GetLastOpenedUser()); } void Module::Interface::IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx) { @@ -735,8 +750,10 @@ void Module::Interface::IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx bool is_locked = false; if (res != Loader::ResultStatus::Success) { - FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()}; - auto nacp_unique = pm.GetControlMetadata().first; + const FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID(), + system.GetFileSystemController(), + system.GetContentProvider()}; + const auto nacp_unique = pm.GetControlMetadata().first; if (nacp_unique != nullptr) { is_locked = nacp_unique->GetUserAccountSwitchLock(); @@ -760,7 +777,7 @@ void Module::Interface::GetProfileEditor(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(user_id, *profile_manager); + rb.PushIpcInterface(system, user_id, *profile_manager); } void Module::Interface::ListQualifiedUsers(Kernel::HLERequestContext& ctx) { @@ -782,7 +799,7 @@ void Module::Interface::LoadOpenContext(Kernel::HLERequestContext& ctx) { // TODO: Find the differences between this and GetBaasAccountManagerForApplication IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(profile_manager->GetLastOpenedUser()); + rb.PushIpcInterface(system, profile_manager->GetLastOpenedUser()); } void Module::Interface::ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx) { @@ -818,11 +835,11 @@ void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContex rb.PushRaw(profile_manager->GetUser(0)->uuid); } -Module::Interface::Interface(std::shared_ptr module, - std::shared_ptr profile_manager, Core::System& system, - const char* name) - : ServiceFramework(name), module(std::move(module)), - profile_manager(std::move(profile_manager)), system(system) {} +Module::Interface::Interface(std::shared_ptr module_, + std::shared_ptr profile_manager_, + Core::System& system_, const char* name) + : ServiceFramework{system_, name}, module{std::move(module_)}, profile_manager{std::move( + profile_manager_)} {} Module::Interface::~Interface() = default; diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h index c611efd89..ab8edc049 100644 --- a/src/core/hle/service/acc/acc.h +++ b/src/core/hle/service/acc/acc.h @@ -15,8 +15,8 @@ class Module final { public: class Interface : public ServiceFramework { public: - explicit Interface(std::shared_ptr module, - std::shared_ptr profile_manager, Core::System& system, + explicit Interface(std::shared_ptr module_, + std::shared_ptr profile_manager_, Core::System& system_, const char* name); ~Interface() override; @@ -60,7 +60,6 @@ public: protected: std::shared_ptr module; std::shared_ptr profile_manager; - Core::System& system; }; }; diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index d7a81f64a..c9808060a 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -64,7 +64,7 @@ struct LaunchParameterAccountPreselectedUser { static_assert(sizeof(LaunchParameterAccountPreselectedUser) == 0x88); IWindowController::IWindowController(Core::System& system_) - : ServiceFramework("IWindowController"), system{system_} { + : ServiceFramework{system_, "IWindowController"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CreateWindow"}, @@ -99,7 +99,8 @@ void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx) rb.Push(RESULT_SUCCESS); } -IAudioController::IAudioController() : ServiceFramework("IAudioController") { +IAudioController::IAudioController(Core::System& system_) + : ServiceFramework{system_, "IAudioController"} { // clang-format off static const FunctionInfo functions[] = { {0, &IAudioController::SetExpectedMasterVolume, "SetExpectedMasterVolume"}, @@ -180,7 +181,8 @@ void IAudioController::SetTransparentAudioRate(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } -IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") { +IDisplayController::IDisplayController(Core::System& system_) + : ServiceFramework{system_, "IDisplayController"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetLastForegroundCaptureImage"}, @@ -219,7 +221,8 @@ IDisplayController::IDisplayController() : ServiceFramework("IDisplayController" IDisplayController::~IDisplayController() = default; -IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} { +IDebugFunctions::IDebugFunctions(Core::System& system_) + : ServiceFramework{system_, "IDebugFunctions"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "NotifyMessageToHomeMenuForDebug"}, @@ -246,9 +249,8 @@ IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} { IDebugFunctions::~IDebugFunctions() = default; -ISelfController::ISelfController(Core::System& system, - std::shared_ptr nvflinger) - : ServiceFramework("ISelfController"), system(system), nvflinger(std::move(nvflinger)) { +ISelfController::ISelfController(Core::System& system_, NVFlinger::NVFlinger& nvflinger_) + : ServiceFramework{system_, "ISelfController"}, nvflinger{nvflinger_} { // clang-format off static const FunctionInfo functions[] = { {0, &ISelfController::Exit, "Exit"}, @@ -458,8 +460,8 @@ void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx) // TODO(Subv): Find out how AM determines the display to use, for now just // create the layer in the Default display. - const auto display_id = nvflinger->OpenDisplay("Default"); - const auto layer_id = nvflinger->CreateLayer(*display_id); + const auto display_id = nvflinger.OpenDisplay("Default"); + const auto layer_id = nvflinger.CreateLayer(*display_id); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); @@ -476,8 +478,8 @@ void ISelfController::CreateManagedDisplaySeparableLayer(Kernel::HLERequestConte // Outputting 1 layer id instead of the expected 2 has not been observed to cause any adverse // side effects. // TODO: Support multiple layers - const auto display_id = nvflinger->OpenDisplay("Default"); - const auto layer_id = nvflinger->CreateLayer(*display_id); + const auto display_id = nvflinger.OpenDisplay("Default"); + const auto layer_id = nvflinger.CreateLayer(*display_id); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); @@ -558,14 +560,14 @@ void ISelfController::GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequest AppletMessageQueue::AppletMessageQueue(Kernel::KernelCore& kernel) { on_new_message = - Kernel::WritableEvent::CreateEventPair(kernel, "AMMessageQueue:OnMessageRecieved"); + Kernel::WritableEvent::CreateEventPair(kernel, "AMMessageQueue:OnMessageReceived"); on_operation_mode_changed = Kernel::WritableEvent::CreateEventPair(kernel, "AMMessageQueue:OperationModeChanged"); } AppletMessageQueue::~AppletMessageQueue() = default; -const std::shared_ptr& AppletMessageQueue::GetMesssageRecieveEvent() const { +const std::shared_ptr& AppletMessageQueue::GetMessageReceiveEvent() const { return on_new_message.readable; } @@ -606,9 +608,9 @@ void AppletMessageQueue::RequestExit() { PushMessage(AppletMessage::ExitRequested); } -ICommonStateGetter::ICommonStateGetter(Core::System& system, - std::shared_ptr msg_queue) - : ServiceFramework("ICommonStateGetter"), system(system), msg_queue(std::move(msg_queue)) { +ICommonStateGetter::ICommonStateGetter(Core::System& system_, + std::shared_ptr msg_queue_) + : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)} { // clang-format off static const FunctionInfo functions[] = { {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"}, @@ -673,7 +675,7 @@ void ICommonStateGetter::GetEventHandle(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(msg_queue->GetMesssageRecieveEvent()); + rb.PushCopyObjects(msg_queue->GetMessageReceiveEvent()); } void ICommonStateGetter::ReceiveMessage(Kernel::HLERequestContext& ctx) { @@ -751,7 +753,7 @@ void ICommonStateGetter::GetDefaultDisplayResolution(Kernel::HLERequestContext& IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - if (Settings::values.use_docked_mode) { + if (Settings::values.use_docked_mode.GetValue()) { rb.Push(static_cast(Service::VI::DisplayResolution::DockedWidth) * static_cast(Settings::values.resolution_factor.GetValue())); rb.Push(static_cast(Service::VI::DisplayResolution::DockedHeight) * @@ -796,8 +798,9 @@ private: std::vector buffer; }; -IStorage::IStorage(std::vector&& buffer) - : ServiceFramework("IStorage"), impl{std::make_shared(std::move(buffer))} { +IStorage::IStorage(Core::System& system_, std::vector&& buffer) + : ServiceFramework{system_, "IStorage"}, impl{std::make_shared( + std::move(buffer))} { Register(); } @@ -820,11 +823,11 @@ void IStorage::Open(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(*this); + rb.PushIpcInterface(system, *this); } void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) { - const bool use_docked_mode{Settings::values.use_docked_mode}; + const bool use_docked_mode{Settings::values.use_docked_mode.GetValue()}; LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode); IPC::ResponseBuilder rb{ctx, 3}; @@ -842,8 +845,8 @@ void ICommonStateGetter::GetPerformanceMode(Kernel::HLERequestContext& ctx) { class ILibraryAppletAccessor final : public ServiceFramework { public: - explicit ILibraryAppletAccessor(std::shared_ptr applet) - : ServiceFramework("ILibraryAppletAccessor"), applet(std::move(applet)) { + explicit ILibraryAppletAccessor(Core::System& system_, std::shared_ptr applet_) + : ServiceFramework{system_, "ILibraryAppletAccessor"}, applet{std::move(applet_)} { // clang-format off static const FunctionInfo functions[] = { {0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"}, @@ -998,8 +1001,8 @@ private: std::shared_ptr applet; }; -IStorageAccessor::IStorageAccessor(IStorage& storage) - : ServiceFramework("IStorageAccessor"), backing(storage) { +IStorageAccessor::IStorageAccessor(Core::System& system_, IStorage& backing_) + : ServiceFramework{system_, "IStorageAccessor"}, backing{backing_} { // clang-format off static const FunctionInfo functions[] = { {0, &IStorageAccessor::GetSize, "GetSize"}, @@ -1070,7 +1073,7 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) { } ILibraryAppletCreator::ILibraryAppletCreator(Core::System& system_) - : ServiceFramework("ILibraryAppletCreator"), system{system_} { + : ServiceFramework{system_, "ILibraryAppletCreator"} { static const FunctionInfo functions[] = { {0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"}, {1, nullptr, "TerminateAllLibraryApplets"}, @@ -1089,14 +1092,14 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) const auto applet_id = rp.PopRaw(); const auto applet_mode = rp.PopRaw(); - LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", - static_cast(applet_id), applet_mode); + LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", applet_id, + applet_mode); const auto& applet_manager{system.GetAppletManager()}; const auto applet = applet_manager.GetApplet(applet_id); if (applet == nullptr) { - LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", static_cast(applet_id)); + LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_UNKNOWN); @@ -1106,7 +1109,7 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(applet); + rb.PushIpcInterface(system, applet); } void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) { @@ -1118,7 +1121,7 @@ void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(std::move(buffer)); + rb.PushIpcInterface(system, std::move(buffer)); } void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx) { @@ -1145,11 +1148,11 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(std::move(memory)); + rb.PushIpcInterface(system, std::move(memory)); } IApplicationFunctions::IApplicationFunctions(Core::System& system_) - : ServiceFramework("IApplicationFunctions"), system{system_} { + : ServiceFramework{system_, "IApplicationFunctions"} { // clang-format off static const FunctionInfo functions[] = { {1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"}, @@ -1189,9 +1192,9 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_) {102, &IApplicationFunctions::SetApplicationCopyrightVisibility, "SetApplicationCopyrightVisibility"}, {110, &IApplicationFunctions::QueryApplicationPlayStatistics, "QueryApplicationPlayStatistics"}, {111, &IApplicationFunctions::QueryApplicationPlayStatisticsByUid, "QueryApplicationPlayStatisticsByUid"}, - {120, nullptr, "ExecuteProgram"}, - {121, nullptr, "ClearUserChannel"}, - {122, nullptr, "UnpopToUserChannel"}, + {120, &IApplicationFunctions::ExecuteProgram, "ExecuteProgram"}, + {121, &IApplicationFunctions::ClearUserChannel, "ClearUserChannel"}, + {122, &IApplicationFunctions::UnpopToUserChannel, "UnpopToUserChannel"}, {123, &IApplicationFunctions::GetPreviousProgramIndex, "GetPreviousProgramIndex"}, {124, nullptr, "EnableApplicationAllThreadDumpOnCrash"}, {130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"}, @@ -1201,6 +1204,8 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_) {151, nullptr, "TryPopFromNotificationStorageChannel"}, {160, nullptr, "GetHealthWarningDisappearedSystemEvent"}, {170, nullptr, "SetHdcpAuthenticationActivated"}, + {180, nullptr, "GetLaunchRequiredVersion"}, + {181, nullptr, "UpgradeLaunchRequiredVersion"}, {500, nullptr, "StartContinuousRecordingFlushForDebug"}, {1000, nullptr, "CreateMovieMaker"}, {1001, nullptr, "PrepareForJit"}, @@ -1285,7 +1290,7 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto kind = rp.PopEnum(); - LOG_DEBUG(Service_AM, "called, kind={:08X}", static_cast(kind)); + LOG_DEBUG(Service_AM, "called, kind={:08X}", kind); if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) { const auto backend = BCAT::CreateBackendFromSettings(system, [this](u64 tid) { @@ -1299,7 +1304,7 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) { if (data.has_value()) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(std::move(*data)); + rb.PushIpcInterface(system, std::move(*data)); launch_popped_application_specific = true; return; } @@ -1322,7 +1327,7 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) { std::vector buffer(sizeof(LaunchParameterAccountPreselectedUser)); std::memcpy(buffer.data(), ¶ms, buffer.size()); - rb.PushIpcInterface(std::move(buffer)); + rb.PushIpcInterface(system, std::move(buffer)); launch_popped_account_preselect = true; return; } @@ -1379,13 +1384,16 @@ void IApplicationFunctions::GetDisplayVersion(Kernel::HLERequestContext& ctx) { const auto res = [this] { const auto title_id = system.CurrentProcess()->GetTitleID(); - FileSys::PatchManager pm{title_id}; + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + system.GetContentProvider()}; auto res = pm.GetControlMetadata(); if (res.first != nullptr) { return res; } - FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id)}; + const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id), + system.GetFileSystemController(), + system.GetContentProvider()}; return pm_update.GetControlMetadata(); }(); @@ -1413,13 +1421,16 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) { const auto res = [this] { const auto title_id = system.CurrentProcess()->GetTitleID(); - FileSys::PatchManager pm{title_id}; + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + system.GetContentProvider()}; auto res = pm.GetControlMetadata(); if (res.first != nullptr) { return res; } - FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id)}; + const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id), + system.GetFileSystemController(), + system.GetContentProvider()}; return pm_update.GetControlMetadata(); }(); @@ -1526,8 +1537,8 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto [type, user_id] = rp.PopRaw(); - LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", static_cast(type), - user_id[1], user_id[0]); + LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", type, user_id[1], + user_id[0]); const auto size = system.GetFileSystemController().ReadSaveDataSize( type, system.CurrentProcess()->GetTitleID(), user_id); @@ -1554,6 +1565,34 @@ void IApplicationFunctions::QueryApplicationPlayStatisticsByUid(Kernel::HLEReque rb.Push(0); } +void IApplicationFunctions::ExecuteProgram(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::RequestParser rp{ctx}; + [[maybe_unused]] const auto unk_1 = rp.Pop(); + [[maybe_unused]] const auto unk_2 = rp.Pop(); + const auto program_index = rp.Pop(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + system.ExecuteProgram(program_index); +} + +void IApplicationFunctions::ClearUserChannel(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void IApplicationFunctions::UnpopToUserChannel(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + void IApplicationFunctions::GetPreviousProgramIndex(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_AM, "(STUBBED) called"); @@ -1578,22 +1617,22 @@ void IApplicationFunctions::GetFriendInvitationStorageChannelEvent(Kernel::HLERe rb.PushCopyObjects(friend_invitation_storage_channel_event.readable); } -void InstallInterfaces(SM::ServiceManager& service_manager, - std::shared_ptr nvflinger, Core::System& system) { +void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger, + Core::System& system) { auto message_queue = std::make_shared(system.Kernel()); // Needed on game boot message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); std::make_shared(nvflinger, message_queue, system)->InstallAsService(service_manager); std::make_shared(nvflinger, message_queue, system)->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); } -IHomeMenuFunctions::IHomeMenuFunctions(Kernel::KernelCore& kernel) - : ServiceFramework("IHomeMenuFunctions"), kernel(kernel) { +IHomeMenuFunctions::IHomeMenuFunctions(Core::System& system_) + : ServiceFramework{system_, "IHomeMenuFunctions"} { // clang-format off static const FunctionInfo functions[] = { {10, &IHomeMenuFunctions::RequestToGetForeground, "RequestToGetForeground"}, @@ -1612,7 +1651,7 @@ IHomeMenuFunctions::IHomeMenuFunctions(Kernel::KernelCore& kernel) RegisterHandlers(functions); pop_from_general_channel_event = Kernel::WritableEvent::CreateEventPair( - kernel, "IHomeMenuFunctions:PopFromGeneralChannelEvent"); + system.Kernel(), "IHomeMenuFunctions:PopFromGeneralChannelEvent"); } IHomeMenuFunctions::~IHomeMenuFunctions() = default; @@ -1632,7 +1671,8 @@ void IHomeMenuFunctions::GetPopFromGeneralChannelEvent(Kernel::HLERequestContext rb.PushCopyObjects(pop_from_general_channel_event.readable); } -IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStateController") { +IGlobalStateController::IGlobalStateController(Core::System& system_) + : ServiceFramework{system_, "IGlobalStateController"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestToEnterSleep"}, @@ -1655,7 +1695,8 @@ IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStat IGlobalStateController::~IGlobalStateController() = default; -IApplicationCreator::IApplicationCreator() : ServiceFramework("IApplicationCreator") { +IApplicationCreator::IApplicationCreator(Core::System& system_) + : ServiceFramework{system_, "IApplicationCreator"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CreateApplication"}, @@ -1670,8 +1711,8 @@ IApplicationCreator::IApplicationCreator() : ServiceFramework("IApplicationCreat IApplicationCreator::~IApplicationCreator() = default; -IProcessWindingController::IProcessWindingController() - : ServiceFramework("IProcessWindingController") { +IProcessWindingController::IProcessWindingController(Core::System& system_) + : ServiceFramework{system_, "IProcessWindingController"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetLaunchReason"}, diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index bcc06affe..f51aca1af 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -55,7 +55,7 @@ public: explicit AppletMessageQueue(Kernel::KernelCore& kernel); ~AppletMessageQueue(); - const std::shared_ptr& GetMesssageRecieveEvent() const; + const std::shared_ptr& GetMessageReceiveEvent() const; const std::shared_ptr& GetOperationModeChangedEvent() const; void PushMessage(AppletMessage msg); AppletMessage PopMessage(); @@ -77,13 +77,11 @@ public: private: void GetAppletResourceUserId(Kernel::HLERequestContext& ctx); void AcquireForegroundRights(Kernel::HLERequestContext& ctx); - - Core::System& system; }; class IAudioController final : public ServiceFramework { public: - IAudioController(); + explicit IAudioController(Core::System& system_); ~IAudioController() override; private: @@ -109,20 +107,19 @@ private: class IDisplayController final : public ServiceFramework { public: - IDisplayController(); + explicit IDisplayController(Core::System& system_); ~IDisplayController() override; }; class IDebugFunctions final : public ServiceFramework { public: - IDebugFunctions(); + explicit IDebugFunctions(Core::System& system_); ~IDebugFunctions() override; }; class ISelfController final : public ServiceFramework { public: - explicit ISelfController(Core::System& system_, - std::shared_ptr nvflinger_); + explicit ISelfController(Core::System& system_, NVFlinger::NVFlinger& nvflinger_); ~ISelfController() override; private: @@ -155,8 +152,7 @@ private: Disable = 2, }; - Core::System& system; - std::shared_ptr nvflinger; + NVFlinger::NVFlinger& nvflinger; Kernel::EventPair launchable_event; Kernel::EventPair accumulated_suspended_tick_changed_event; @@ -168,8 +164,8 @@ private: class ICommonStateGetter final : public ServiceFramework { public: - explicit ICommonStateGetter(Core::System& system, - std::shared_ptr msg_queue); + explicit ICommonStateGetter(Core::System& system_, + std::shared_ptr msg_queue_); ~ICommonStateGetter() override; private: @@ -197,7 +193,6 @@ private: void GetDefaultDisplayResolution(Kernel::HLERequestContext& ctx); void SetCpuBoostMode(Kernel::HLERequestContext& ctx); - Core::System& system; std::shared_ptr msg_queue; bool vr_mode_state{}; }; @@ -212,7 +207,7 @@ public: class IStorage final : public ServiceFramework { public: - explicit IStorage(std::vector&& buffer); + explicit IStorage(Core::System& system_, std::vector&& buffer); ~IStorage() override; std::vector& GetData() { @@ -236,7 +231,7 @@ private: class IStorageAccessor final : public ServiceFramework { public: - explicit IStorageAccessor(IStorage& backing); + explicit IStorageAccessor(Core::System& system_, IStorage& backing_); ~IStorageAccessor() override; private: @@ -256,8 +251,6 @@ private: void CreateLibraryApplet(Kernel::HLERequestContext& ctx); void CreateStorage(Kernel::HLERequestContext& ctx); void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx); - - Core::System& system; }; class IApplicationFunctions final : public ServiceFramework { @@ -288,6 +281,9 @@ private: void SetApplicationCopyrightVisibility(Kernel::HLERequestContext& ctx); void QueryApplicationPlayStatistics(Kernel::HLERequestContext& ctx); void QueryApplicationPlayStatisticsByUid(Kernel::HLERequestContext& ctx); + void ExecuteProgram(Kernel::HLERequestContext& ctx); + void ClearUserChannel(Kernel::HLERequestContext& ctx); + void UnpopToUserChannel(Kernel::HLERequestContext& ctx); void GetPreviousProgramIndex(Kernel::HLERequestContext& ctx); void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx); void GetFriendInvitationStorageChannelEvent(Kernel::HLERequestContext& ctx); @@ -297,12 +293,11 @@ private: s32 previous_program_index{-1}; Kernel::EventPair gpu_error_detected_event; Kernel::EventPair friend_invitation_storage_channel_event; - Core::System& system; }; class IHomeMenuFunctions final : public ServiceFramework { public: - explicit IHomeMenuFunctions(Kernel::KernelCore& kernel); + explicit IHomeMenuFunctions(Core::System& system_); ~IHomeMenuFunctions() override; private: @@ -310,29 +305,28 @@ private: void GetPopFromGeneralChannelEvent(Kernel::HLERequestContext& ctx); Kernel::EventPair pop_from_general_channel_event; - Kernel::KernelCore& kernel; }; class IGlobalStateController final : public ServiceFramework { public: - IGlobalStateController(); + explicit IGlobalStateController(Core::System& system_); ~IGlobalStateController() override; }; class IApplicationCreator final : public ServiceFramework { public: - IApplicationCreator(); + explicit IApplicationCreator(Core::System& system_); ~IApplicationCreator() override; }; class IProcessWindingController final : public ServiceFramework { public: - IProcessWindingController(); + explicit IProcessWindingController(Core::System& system_); ~IProcessWindingController() override; }; /// Registers all AM services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager, - std::shared_ptr nvflinger, Core::System& system); +void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger, + Core::System& system); } // namespace Service::AM diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp index 9df286d17..5421e0da0 100644 --- a/src/core/hle/service/am/applet_ae.cpp +++ b/src/core/hle/service/am/applet_ae.cpp @@ -3,8 +3,8 @@ // Refer to the license.txt file included. #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/process.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/nvflinger/nvflinger.h" @@ -13,11 +13,11 @@ namespace Service::AM { class ILibraryAppletProxy final : public ServiceFramework { public: - explicit ILibraryAppletProxy(std::shared_ptr nvflinger, - std::shared_ptr msg_queue, - Core::System& system) - : ServiceFramework("ILibraryAppletProxy"), nvflinger(std::move(nvflinger)), - msg_queue(std::move(msg_queue)), system(system) { + explicit ILibraryAppletProxy(NVFlinger::NVFlinger& nvflinger_, + std::shared_ptr msg_queue_, + Core::System& system_) + : ServiceFramework{system_, "ILibraryAppletProxy"}, nvflinger{nvflinger_}, + msg_queue{std::move(msg_queue_)} { // clang-format off static const FunctionInfo functions[] = { {0, &ILibraryAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"}, @@ -66,7 +66,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } void GetDisplayController(Kernel::HLERequestContext& ctx) { @@ -74,7 +74,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } void GetProcessWindingController(Kernel::HLERequestContext& ctx) { @@ -82,7 +82,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } void GetDebugFunctions(Kernel::HLERequestContext& ctx) { @@ -90,7 +90,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) { @@ -109,17 +109,17 @@ private: rb.PushIpcInterface(system); } - std::shared_ptr nvflinger; + NVFlinger::NVFlinger& nvflinger; std::shared_ptr msg_queue; - Core::System& system; }; class ISystemAppletProxy final : public ServiceFramework { public: - explicit ISystemAppletProxy(std::shared_ptr nvflinger, - std::shared_ptr msg_queue, Core::System& system) - : ServiceFramework("ISystemAppletProxy"), nvflinger(std::move(nvflinger)), - msg_queue(std::move(msg_queue)), system(system) { + explicit ISystemAppletProxy(NVFlinger::NVFlinger& nvflinger_, + std::shared_ptr msg_queue_, + Core::System& system_) + : ServiceFramework{system_, "ISystemAppletProxy"}, nvflinger{nvflinger_}, + msg_queue{std::move(msg_queue_)} { // clang-format off static const FunctionInfo functions[] = { {0, &ISystemAppletProxy::GetCommonStateGetter, "GetCommonStateGetter"}, @@ -170,7 +170,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } void GetDisplayController(Kernel::HLERequestContext& ctx) { @@ -178,7 +178,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } void GetDebugFunctions(Kernel::HLERequestContext& ctx) { @@ -186,7 +186,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } void GetLibraryAppletCreator(Kernel::HLERequestContext& ctx) { @@ -202,7 +202,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(system.Kernel()); + rb.PushIpcInterface(system); } void GetGlobalStateController(Kernel::HLERequestContext& ctx) { @@ -210,7 +210,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } void GetApplicationCreator(Kernel::HLERequestContext& ctx) { @@ -218,11 +218,11 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } - std::shared_ptr nvflinger; + + NVFlinger::NVFlinger& nvflinger; std::shared_ptr msg_queue; - Core::System& system; }; void AppletAE::OpenSystemAppletProxy(Kernel::HLERequestContext& ctx) { @@ -249,10 +249,10 @@ void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) { rb.PushIpcInterface(nvflinger, msg_queue, system); } -AppletAE::AppletAE(std::shared_ptr nvflinger, - std::shared_ptr msg_queue, Core::System& system) - : ServiceFramework("appletAE"), nvflinger(std::move(nvflinger)), - msg_queue(std::move(msg_queue)), system(system) { +AppletAE::AppletAE(NVFlinger::NVFlinger& nvflinger_, std::shared_ptr msg_queue_, + Core::System& system_) + : ServiceFramework{system_, "appletAE"}, nvflinger{nvflinger_}, msg_queue{ + std::move(msg_queue_)} { // clang-format off static const FunctionInfo functions[] = { {100, &AppletAE::OpenSystemAppletProxy, "OpenSystemAppletProxy"}, diff --git a/src/core/hle/service/am/applet_ae.h b/src/core/hle/service/am/applet_ae.h index 2e3e45915..adb207349 100644 --- a/src/core/hle/service/am/applet_ae.h +++ b/src/core/hle/service/am/applet_ae.h @@ -23,8 +23,8 @@ class AppletMessageQueue; class AppletAE final : public ServiceFramework { public: - explicit AppletAE(std::shared_ptr nvflinger, - std::shared_ptr msg_queue, Core::System& system); + explicit AppletAE(NVFlinger::NVFlinger& nvflinger_, + std::shared_ptr msg_queue_, Core::System& system_); ~AppletAE() override; const std::shared_ptr& GetMessageQueue() const; @@ -34,9 +34,8 @@ private: void OpenLibraryAppletProxy(Kernel::HLERequestContext& ctx); void OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx); - std::shared_ptr nvflinger; + NVFlinger::NVFlinger& nvflinger; std::shared_ptr msg_queue; - Core::System& system; }; } // namespace AM diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp index a2ffaa440..f9eba8f52 100644 --- a/src/core/hle/service/am/applet_oe.cpp +++ b/src/core/hle/service/am/applet_oe.cpp @@ -12,10 +12,11 @@ namespace Service::AM { class IApplicationProxy final : public ServiceFramework { public: - explicit IApplicationProxy(std::shared_ptr nvflinger, - std::shared_ptr msg_queue, Core::System& system) - : ServiceFramework("IApplicationProxy"), nvflinger(std::move(nvflinger)), - msg_queue(std::move(msg_queue)), system(system) { + explicit IApplicationProxy(NVFlinger::NVFlinger& nvflinger_, + std::shared_ptr msg_queue_, + Core::System& system_) + : ServiceFramework{system_, "IApplicationProxy"}, nvflinger{nvflinger_}, + msg_queue{std::move(msg_queue_)} { // clang-format off static const FunctionInfo functions[] = { {0, &IApplicationProxy::GetCommonStateGetter, "GetCommonStateGetter"}, @@ -39,7 +40,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } void GetDisplayController(Kernel::HLERequestContext& ctx) { @@ -47,7 +48,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } void GetDebugFunctions(Kernel::HLERequestContext& ctx) { @@ -55,7 +56,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } void GetWindowController(Kernel::HLERequestContext& ctx) { @@ -98,9 +99,8 @@ private: rb.PushIpcInterface(system); } - std::shared_ptr nvflinger; + NVFlinger::NVFlinger& nvflinger; std::shared_ptr msg_queue; - Core::System& system; }; void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) { @@ -111,10 +111,10 @@ void AppletOE::OpenApplicationProxy(Kernel::HLERequestContext& ctx) { rb.PushIpcInterface(nvflinger, msg_queue, system); } -AppletOE::AppletOE(std::shared_ptr nvflinger, - std::shared_ptr msg_queue, Core::System& system) - : ServiceFramework("appletOE"), nvflinger(std::move(nvflinger)), - msg_queue(std::move(msg_queue)), system(system) { +AppletOE::AppletOE(NVFlinger::NVFlinger& nvflinger_, std::shared_ptr msg_queue_, + Core::System& system_) + : ServiceFramework{system_, "appletOE"}, nvflinger{nvflinger_}, msg_queue{ + std::move(msg_queue_)} { static const FunctionInfo functions[] = { {0, &AppletOE::OpenApplicationProxy, "OpenApplicationProxy"}, }; diff --git a/src/core/hle/service/am/applet_oe.h b/src/core/hle/service/am/applet_oe.h index 758da792d..6c1aa255a 100644 --- a/src/core/hle/service/am/applet_oe.h +++ b/src/core/hle/service/am/applet_oe.h @@ -23,8 +23,8 @@ class AppletMessageQueue; class AppletOE final : public ServiceFramework { public: - explicit AppletOE(std::shared_ptr nvflinger, - std::shared_ptr msg_queue, Core::System& system); + explicit AppletOE(NVFlinger::NVFlinger& nvflinger_, + std::shared_ptr msg_queue_, Core::System& system_); ~AppletOE() override; const std::shared_ptr& GetMessageQueue() const; @@ -32,9 +32,8 @@ public: private: void OpenApplicationProxy(Kernel::HLERequestContext& ctx); - std::shared_ptr nvflinger; + NVFlinger::NVFlinger& nvflinger; std::shared_ptr msg_queue; - Core::System& system; }; } // namespace AM diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp index 4e0800f9a..08676c3fc 100644 --- a/src/core/hle/service/am/applets/applets.cpp +++ b/src/core/hle/service/am/applets/applets.cpp @@ -142,14 +142,14 @@ void Applet::Initialize() { AppletFrontendSet::AppletFrontendSet() = default; -AppletFrontendSet::AppletFrontendSet(ControllerApplet controller, ECommerceApplet e_commerce, - ErrorApplet error, ParentalControlsApplet parental_controls, - PhotoViewer photo_viewer, ProfileSelect profile_select, - SoftwareKeyboard software_keyboard, WebBrowser web_browser) - : controller{std::move(controller)}, e_commerce{std::move(e_commerce)}, error{std::move(error)}, - parental_controls{std::move(parental_controls)}, photo_viewer{std::move(photo_viewer)}, - profile_select{std::move(profile_select)}, software_keyboard{std::move(software_keyboard)}, - web_browser{std::move(web_browser)} {} +AppletFrontendSet::AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet, + ParentalControlsApplet parental_controls_applet, + PhotoViewer photo_viewer_, ProfileSelect profile_select_, + SoftwareKeyboard software_keyboard_, WebBrowser web_browser_) + : controller{std::move(controller_applet)}, error{std::move(error_applet)}, + parental_controls{std::move(parental_controls_applet)}, + photo_viewer{std::move(photo_viewer_)}, profile_select{std::move(profile_select_)}, + software_keyboard{std::move(software_keyboard_)}, web_browser{std::move(web_browser_)} {} AppletFrontendSet::~AppletFrontendSet() = default; @@ -170,10 +170,6 @@ void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) { frontend.controller = std::move(set.controller); } - if (set.e_commerce != nullptr) { - frontend.e_commerce = std::move(set.e_commerce); - } - if (set.error != nullptr) { frontend.error = std::move(set.error); } @@ -206,11 +202,8 @@ void AppletManager::SetDefaultAppletFrontendSet() { void AppletManager::SetDefaultAppletsIfMissing() { if (frontend.controller == nullptr) { - frontend.controller = std::make_unique(); - } - - if (frontend.e_commerce == nullptr) { - frontend.e_commerce = std::make_unique(); + frontend.controller = + std::make_unique(system.ServiceManager()); } if (frontend.error == nullptr) { @@ -256,13 +249,14 @@ std::shared_ptr AppletManager::GetApplet(AppletId id) const { return std::make_shared(system, *frontend.profile_select); case AppletId::SoftwareKeyboard: return std::make_shared(system, *frontend.software_keyboard); + case AppletId::Web: + case AppletId::Shop: + case AppletId::OfflineWeb: + case AppletId::LoginShare: + case AppletId::WebAuth: + return std::make_shared(system, *frontend.web_browser); case AppletId::PhotoViewer: return std::make_shared(system, *frontend.photo_viewer); - case AppletId::LibAppletShop: - return std::make_shared(system, *frontend.web_browser, - frontend.e_commerce.get()); - case AppletId::LibAppletOff: - return std::make_shared(system, *frontend.web_browser); default: UNIMPLEMENTED_MSG( "No backend implementation exists for applet_id={:02X}! Falling back to stub applet.", diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h index a1f4cf897..4fd792c05 100644 --- a/src/core/hle/service/am/applets/applets.h +++ b/src/core/hle/service/am/applets/applets.h @@ -50,13 +50,13 @@ enum class AppletId : u32 { ProfileSelect = 0x10, SoftwareKeyboard = 0x11, MiiEdit = 0x12, - LibAppletWeb = 0x13, - LibAppletShop = 0x14, + Web = 0x13, + Shop = 0x14, PhotoViewer = 0x15, Settings = 0x16, - LibAppletOff = 0x17, - LibAppletWhitelisted = 0x18, - LibAppletAuth = 0x19, + OfflineWeb = 0x17, + LoginShare = 0x18, + WebAuth = 0x19, MyPage = 0x1A, }; @@ -157,7 +157,6 @@ protected: struct AppletFrontendSet { using ControllerApplet = std::unique_ptr; - using ECommerceApplet = std::unique_ptr; using ErrorApplet = std::unique_ptr; using ParentalControlsApplet = std::unique_ptr; using PhotoViewer = std::unique_ptr; @@ -166,10 +165,10 @@ struct AppletFrontendSet { using WebBrowser = std::unique_ptr; AppletFrontendSet(); - AppletFrontendSet(ControllerApplet controller, ECommerceApplet e_commerce, ErrorApplet error, - ParentalControlsApplet parental_controls, PhotoViewer photo_viewer, - ProfileSelect profile_select, SoftwareKeyboard software_keyboard, - WebBrowser web_browser); + AppletFrontendSet(ControllerApplet controller_applet, ErrorApplet error_applet, + ParentalControlsApplet parental_controls_applet, PhotoViewer photo_viewer_, + ProfileSelect profile_select_, SoftwareKeyboard software_keyboard_, + WebBrowser web_browser_); ~AppletFrontendSet(); AppletFrontendSet(const AppletFrontendSet&) = delete; @@ -179,7 +178,6 @@ struct AppletFrontendSet { AppletFrontendSet& operator=(AppletFrontendSet&&) noexcept; ControllerApplet controller; - ECommerceApplet e_commerce; ErrorApplet error; ParentalControlsApplet parental_controls; PhotoViewer photo_viewer; diff --git a/src/core/hle/service/am/applets/controller.cpp b/src/core/hle/service/am/applets/controller.cpp index 2151da783..7edfca64e 100644 --- a/src/core/hle/service/am/applets/controller.cpp +++ b/src/core/hle/service/am/applets/controller.cpp @@ -25,18 +25,18 @@ namespace Service::AM::Applets { static Core::Frontend::ControllerParameters ConvertToFrontendParameters( ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, bool enable_text, std::vector identification_colors, std::vector text) { - HID::Controller_NPad::NPadType npad_style_set; + HID::Controller_NPad::NpadStyleSet npad_style_set; npad_style_set.raw = private_arg.style_set; return { - .min_players = std::max(s8(1), header.player_count_min), + .min_players = std::max(s8{1}, header.player_count_min), .max_players = header.player_count_max, .keep_controllers_connected = header.enable_take_over_connection, .enable_single_mode = header.enable_single_mode, .enable_border_color = header.enable_identification_color, - .border_colors = identification_colors, + .border_colors = std::move(identification_colors), .enable_explain_text = enable_text, - .explain_text = text, + .explain_text = std::move(text), .allow_pro_controller = npad_style_set.pro_controller == 1, .allow_handheld = npad_style_set.handheld == 1, .allow_dual_joycons = npad_style_set.joycon_dual == 1, @@ -46,7 +46,7 @@ static Core::Frontend::ControllerParameters ConvertToFrontendParameters( } Controller::Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_) - : Applet{system_.Kernel()}, frontend(frontend_) {} + : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} Controller::~Controller() = default; @@ -62,7 +62,7 @@ void Controller::Initialize() { common_args.play_startup_sound, common_args.size, common_args.system_tick, common_args.theme_color); - library_applet_version = LibraryAppletVersion{common_args.library_version}; + controller_applet_version = ControllerAppletVersion{common_args.library_version}; const auto private_arg_storage = broker.PopNormalDataToApplet(); ASSERT(private_arg_storage != nullptr); @@ -70,39 +70,78 @@ void Controller::Initialize() { const auto& private_arg = private_arg_storage->GetData(); ASSERT(private_arg.size() == sizeof(ControllerSupportArgPrivate)); - std::memcpy(&controller_private_arg, private_arg.data(), sizeof(ControllerSupportArgPrivate)); + std::memcpy(&controller_private_arg, private_arg.data(), private_arg.size()); ASSERT_MSG(controller_private_arg.arg_private_size == sizeof(ControllerSupportArgPrivate), "Unknown ControllerSupportArgPrivate revision={} with size={}", - library_applet_version, controller_private_arg.arg_private_size); + controller_applet_version, controller_private_arg.arg_private_size); + + // Some games such as Cave Story+ set invalid values for the ControllerSupportMode. + // Defer to arg_size to set the ControllerSupportMode. + if (controller_private_arg.mode >= ControllerSupportMode::MaxControllerSupportMode) { + switch (controller_private_arg.arg_size) { + case sizeof(ControllerSupportArgOld): + case sizeof(ControllerSupportArgNew): + controller_private_arg.mode = ControllerSupportMode::ShowControllerSupport; + break; + case sizeof(ControllerUpdateFirmwareArg): + controller_private_arg.mode = ControllerSupportMode::ShowControllerFirmwareUpdate; + break; + default: + UNIMPLEMENTED_MSG("Unknown ControllerPrivateArg mode={} with arg_size={}", + controller_private_arg.mode, controller_private_arg.arg_size); + controller_private_arg.mode = ControllerSupportMode::ShowControllerSupport; + break; + } + } + + // Some games such as Cave Story+ set invalid values for the ControllerSupportCaller. + // This is always 0 (Application) except with ShowControllerFirmwareUpdateForSystem. + if (controller_private_arg.caller >= ControllerSupportCaller::MaxControllerSupportCaller) { + if (controller_private_arg.flag_1 && + controller_private_arg.mode == ControllerSupportMode::ShowControllerFirmwareUpdate) { + controller_private_arg.caller = ControllerSupportCaller::System; + } else { + controller_private_arg.caller = ControllerSupportCaller::Application; + } + } switch (controller_private_arg.mode) { - case ControllerSupportMode::ShowControllerSupport: { + case ControllerSupportMode::ShowControllerSupport: + case ControllerSupportMode::ShowControllerStrapGuide: { const auto user_arg_storage = broker.PopNormalDataToApplet(); ASSERT(user_arg_storage != nullptr); const auto& user_arg = user_arg_storage->GetData(); - switch (library_applet_version) { - case LibraryAppletVersion::Version3: - case LibraryAppletVersion::Version4: - case LibraryAppletVersion::Version5: + switch (controller_applet_version) { + case ControllerAppletVersion::Version3: + case ControllerAppletVersion::Version4: + case ControllerAppletVersion::Version5: ASSERT(user_arg.size() == sizeof(ControllerSupportArgOld)); - std::memcpy(&controller_user_arg_old, user_arg.data(), sizeof(ControllerSupportArgOld)); + std::memcpy(&controller_user_arg_old, user_arg.data(), user_arg.size()); break; - case LibraryAppletVersion::Version7: + case ControllerAppletVersion::Version7: ASSERT(user_arg.size() == sizeof(ControllerSupportArgNew)); - std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew)); + std::memcpy(&controller_user_arg_new, user_arg.data(), user_arg.size()); break; default: UNIMPLEMENTED_MSG("Unknown ControllerSupportArg revision={} with size={}", - library_applet_version, controller_private_arg.arg_size); + controller_applet_version, controller_private_arg.arg_size); ASSERT(user_arg.size() >= sizeof(ControllerSupportArgNew)); std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew)); break; } break; } - case ControllerSupportMode::ShowControllerStrapGuide: - case ControllerSupportMode::ShowControllerFirmwareUpdate: + case ControllerSupportMode::ShowControllerFirmwareUpdate: { + const auto update_arg_storage = broker.PopNormalDataToApplet(); + ASSERT(update_arg_storage != nullptr); + + const auto& update_arg = update_arg_storage->GetData(); + ASSERT(update_arg.size() == sizeof(ControllerUpdateFirmwareArg)); + + std::memcpy(&controller_update_arg, update_arg.data(), update_arg.size()); + break; + } default: { UNIMPLEMENTED_MSG("Unimplemented ControllerSupportMode={}", controller_private_arg.mode); break; @@ -126,10 +165,10 @@ void Controller::Execute() { switch (controller_private_arg.mode) { case ControllerSupportMode::ShowControllerSupport: { const auto parameters = [this] { - switch (library_applet_version) { - case LibraryAppletVersion::Version3: - case LibraryAppletVersion::Version4: - case LibraryAppletVersion::Version5: + switch (controller_applet_version) { + case ControllerAppletVersion::Version3: + case ControllerAppletVersion::Version4: + case ControllerAppletVersion::Version5: return ConvertToFrontendParameters( controller_private_arg, controller_user_arg_old.header, controller_user_arg_old.enable_explain_text, @@ -138,7 +177,7 @@ void Controller::Execute() { controller_user_arg_old.identification_colors.end()), std::vector(controller_user_arg_old.explain_text.begin(), controller_user_arg_old.explain_text.end())); - case LibraryAppletVersion::Version7: + case ControllerAppletVersion::Version7: default: return ConvertToFrontendParameters( controller_private_arg, controller_user_arg_new.header, @@ -170,6 +209,9 @@ void Controller::Execute() { } case ControllerSupportMode::ShowControllerStrapGuide: case ControllerSupportMode::ShowControllerFirmwareUpdate: + UNIMPLEMENTED_MSG("ControllerSupportMode={} is not implemented", + controller_private_arg.mode); + [[fallthrough]]; default: { ConfigurationComplete(); break; @@ -180,20 +222,19 @@ void Controller::Execute() { void Controller::ConfigurationComplete() { ControllerSupportResultInfo result_info{}; - const auto& players = Settings::values.players; + const auto& players = Settings::values.players.GetValue(); // If enable_single_mode is enabled, player_count is 1 regardless of any other parameters. // Otherwise, only count connected players from P1-P8. result_info.player_count = - is_single_mode ? 1 - : static_cast(std::count_if( - players.begin(), players.end() - 2, - [](Settings::PlayerInput player) { return player.connected; })); + is_single_mode + ? 1 + : static_cast(std::count_if(players.begin(), players.end() - 2, + [](const auto& player) { return player.connected; })); - result_info.selected_id = HID::Controller_NPad::IndexToNPad( - std::distance(players.begin(), - std::find_if(players.begin(), players.end(), - [](Settings::PlayerInput player) { return player.connected; }))); + result_info.selected_id = HID::Controller_NPad::IndexToNPad(std::distance( + players.begin(), std::find_if(players.begin(), players.end(), + [](const auto& player) { return player.connected; }))); result_info.result = 0; @@ -203,7 +244,7 @@ void Controller::ConfigurationComplete() { complete = true; out_data = std::vector(sizeof(ControllerSupportResultInfo)); std::memcpy(out_data.data(), &result_info, out_data.size()); - broker.PushNormalDataFromApplet(std::make_shared(std::move(out_data))); + broker.PushNormalDataFromApplet(std::make_shared(system, std::move(out_data))); broker.SignalStateChanged(); } diff --git a/src/core/hle/service/am/applets/controller.h b/src/core/hle/service/am/applets/controller.h index f7bb3fba9..d4c9da7b1 100644 --- a/src/core/hle/service/am/applets/controller.h +++ b/src/core/hle/service/am/applets/controller.h @@ -21,7 +21,7 @@ namespace Service::AM::Applets { using IdentificationColor = std::array; using ExplainText = std::array; -enum class LibraryAppletVersion : u32_le { +enum class ControllerAppletVersion : u32_le { Version3 = 0x3, // 1.0.0 - 2.3.0 Version4 = 0x4, // 3.0.0 - 5.1.0 Version5 = 0x5, // 6.0.0 - 7.0.1 @@ -29,14 +29,18 @@ enum class LibraryAppletVersion : u32_le { }; enum class ControllerSupportMode : u8 { - ShowControllerSupport = 0, - ShowControllerStrapGuide = 1, - ShowControllerFirmwareUpdate = 2, + ShowControllerSupport, + ShowControllerStrapGuide, + ShowControllerFirmwareUpdate, + + MaxControllerSupportMode, }; enum class ControllerSupportCaller : u8 { - Application = 0, - System = 1, + Application, + System, + + MaxControllerSupportCaller, }; struct ControllerSupportArgPrivate { @@ -84,6 +88,13 @@ struct ControllerSupportArgNew { static_assert(sizeof(ControllerSupportArgNew) == 0x430, "ControllerSupportArgNew has incorrect size."); +struct ControllerUpdateFirmwareArg { + bool enable_force_update{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(ControllerUpdateFirmwareArg) == 0x4, + "ControllerUpdateFirmwareArg has incorrect size."); + struct ControllerSupportResultInfo { s8 player_count{}; INSERT_PADDING_BYTES(3); @@ -109,11 +120,13 @@ public: private: const Core::Frontend::ControllerApplet& frontend; + Core::System& system; - LibraryAppletVersion library_applet_version; + ControllerAppletVersion controller_applet_version; ControllerSupportArgPrivate controller_private_arg; ControllerSupportArgOld controller_user_arg_old; ControllerSupportArgNew controller_user_arg_new; + ControllerUpdateFirmwareArg controller_update_arg; bool complete{false}; ResultCode status{RESULT_SUCCESS}; bool is_single_mode{false}; diff --git a/src/core/hle/service/am/applets/error.cpp b/src/core/hle/service/am/applets/error.cpp index f12fd7f89..d85505082 100644 --- a/src/core/hle/service/am/applets/error.cpp +++ b/src/core/hle/service/am/applets/error.cpp @@ -87,7 +87,7 @@ ResultCode Decode64BitError(u64 error) { } // Anonymous namespace Error::Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_) - : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {} + : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} Error::~Error() = default; @@ -125,7 +125,7 @@ void Error::Initialize() { error_code = Decode64BitError(args->error_record.error_code_64); break; default: - UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", static_cast(mode)); + UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", mode); } } @@ -179,14 +179,14 @@ void Error::Execute() { error_code, std::chrono::seconds{args->error_record.posix_time}, callback); break; default: - UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", static_cast(mode)); + UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", mode); DisplayCompleted(); } } void Error::DisplayCompleted() { complete = true; - broker.PushNormalDataFromApplet(std::make_shared(std::vector{})); + broker.PushNormalDataFromApplet(std::make_shared(system, std::vector{})); broker.SignalStateChanged(); } diff --git a/src/core/hle/service/am/applets/general_backend.cpp b/src/core/hle/service/am/applets/general_backend.cpp index 104501ac5..4d1df5cbe 100644 --- a/src/core/hle/service/am/applets/general_backend.cpp +++ b/src/core/hle/service/am/applets/general_backend.cpp @@ -38,7 +38,7 @@ static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix) } Auth::Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_) - : Applet{system_.Kernel()}, frontend(frontend_) {} + : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} Auth::~Auth() = default; @@ -90,7 +90,7 @@ void Auth::Execute() { const auto unimplemented_log = [this] { UNIMPLEMENTED_MSG("Unimplemented Auth applet type for type={:08X}, arg0={:02X}, " "arg1={:02X}, arg2={:02X}", - static_cast(type), arg0, arg1, arg2); + type, arg0, arg1, arg2); }; switch (type) { @@ -135,8 +135,8 @@ void Auth::Execute() { } } -void Auth::AuthFinished(bool successful) { - this->successful = successful; +void Auth::AuthFinished(bool is_successful) { + successful = is_successful; struct Return { ResultCode result_code; @@ -148,12 +148,12 @@ void Auth::AuthFinished(bool successful) { std::vector out(sizeof(Return)); std::memcpy(out.data(), &return_, sizeof(Return)); - broker.PushNormalDataFromApplet(std::make_shared(std::move(out))); + broker.PushNormalDataFromApplet(std::make_shared(system, std::move(out))); broker.SignalStateChanged(); } PhotoViewer::PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_) - : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {} + : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} PhotoViewer::~PhotoViewer() = default; @@ -193,17 +193,17 @@ void PhotoViewer::Execute() { frontend.ShowAllPhotos(callback); break; default: - UNIMPLEMENTED_MSG("Unimplemented PhotoViewer applet mode={:02X}!", static_cast(mode)); + UNIMPLEMENTED_MSG("Unimplemented PhotoViewer applet mode={:02X}!", mode); } } void PhotoViewer::ViewFinished() { - broker.PushNormalDataFromApplet(std::make_shared(std::vector{})); + broker.PushNormalDataFromApplet(std::make_shared(system, std::vector{})); broker.SignalStateChanged(); } StubApplet::StubApplet(Core::System& system_, AppletId id_) - : Applet{system_.Kernel()}, id(id_), system{system_} {} + : Applet{system_.Kernel()}, id{id_}, system{system_} {} StubApplet::~StubApplet() = default; @@ -234,8 +234,9 @@ void StubApplet::ExecuteInteractive() { LOG_WARNING(Service_AM, "called (STUBBED)"); LogCurrentStorage(broker, "ExecuteInteractive"); - broker.PushNormalDataFromApplet(std::make_shared(std::vector(0x1000))); - broker.PushInteractiveDataFromApplet(std::make_shared(std::vector(0x1000))); + broker.PushNormalDataFromApplet(std::make_shared(system, std::vector(0x1000))); + broker.PushInteractiveDataFromApplet( + std::make_shared(system, std::vector(0x1000))); broker.SignalStateChanged(); } @@ -243,8 +244,9 @@ void StubApplet::Execute() { LOG_WARNING(Service_AM, "called (STUBBED)"); LogCurrentStorage(broker, "Execute"); - broker.PushNormalDataFromApplet(std::make_shared(std::vector(0x1000))); - broker.PushInteractiveDataFromApplet(std::make_shared(std::vector(0x1000))); + broker.PushNormalDataFromApplet(std::make_shared(system, std::vector(0x1000))); + broker.PushInteractiveDataFromApplet( + std::make_shared(system, std::vector(0x1000))); broker.SignalStateChanged(); } diff --git a/src/core/hle/service/am/applets/general_backend.h b/src/core/hle/service/am/applets/general_backend.h index cfa2df369..ba76ae3d3 100644 --- a/src/core/hle/service/am/applets/general_backend.h +++ b/src/core/hle/service/am/applets/general_backend.h @@ -29,10 +29,11 @@ public: void ExecuteInteractive() override; void Execute() override; - void AuthFinished(bool successful = true); + void AuthFinished(bool is_successful = true); private: Core::Frontend::ParentalControlsApplet& frontend; + Core::System& system; bool complete = false; bool successful = false; diff --git a/src/core/hle/service/am/applets/profile_select.cpp b/src/core/hle/service/am/applets/profile_select.cpp index 70cc23552..77fba16c7 100644 --- a/src/core/hle/service/am/applets/profile_select.cpp +++ b/src/core/hle/service/am/applets/profile_select.cpp @@ -17,7 +17,7 @@ constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1}; ProfileSelect::ProfileSelect(Core::System& system_, const Core::Frontend::ProfileSelectApplet& frontend_) - : Applet{system_.Kernel()}, frontend(frontend_) {} + : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} ProfileSelect::~ProfileSelect() = default; @@ -50,7 +50,7 @@ void ProfileSelect::ExecuteInteractive() { void ProfileSelect::Execute() { if (complete) { - broker.PushNormalDataFromApplet(std::make_shared(std::move(final_data))); + broker.PushNormalDataFromApplet(std::make_shared(system, std::move(final_data))); return; } @@ -71,7 +71,7 @@ void ProfileSelect::SelectionComplete(std::optional uuid) { final_data = std::vector(sizeof(UserSelectionOutput)); std::memcpy(final_data.data(), &output, final_data.size()); - broker.PushNormalDataFromApplet(std::make_shared(std::move(final_data))); + broker.PushNormalDataFromApplet(std::make_shared(system, std::move(final_data))); broker.SignalStateChanged(); } diff --git a/src/core/hle/service/am/applets/profile_select.h b/src/core/hle/service/am/applets/profile_select.h index 16364ead7..648d33a24 100644 --- a/src/core/hle/service/am/applets/profile_select.h +++ b/src/core/hle/service/am/applets/profile_select.h @@ -53,6 +53,7 @@ private: bool complete = false; ResultCode status = RESULT_SUCCESS; std::vector final_data; + Core::System& system; }; } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp index bdeb0737a..3022438b1 100644 --- a/src/core/hle/service/am/applets/software_keyboard.cpp +++ b/src/core/hle/service/am/applets/software_keyboard.cpp @@ -53,7 +53,7 @@ static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters( SoftwareKeyboard::SoftwareKeyboard(Core::System& system_, const Core::Frontend::SoftwareKeyboardApplet& frontend_) - : Applet{system_.Kernel()}, frontend(frontend_) {} + : Applet{system_.Kernel()}, frontend{frontend_}, system{system_} {} SoftwareKeyboard::~SoftwareKeyboard() = default; @@ -122,7 +122,7 @@ void SoftwareKeyboard::ExecuteInteractive() { switch (request) { case Request::Calc: { - broker.PushNormalDataFromApplet(std::make_shared(std::vector{1})); + broker.PushNormalDataFromApplet(std::make_shared(system, std::vector{1})); broker.SignalStateChanged(); break; } @@ -135,7 +135,7 @@ void SoftwareKeyboard::ExecuteInteractive() { void SoftwareKeyboard::Execute() { if (complete) { - broker.PushNormalDataFromApplet(std::make_shared(std::move(final_data))); + broker.PushNormalDataFromApplet(std::make_shared(system, std::move(final_data))); broker.SignalStateChanged(); return; } @@ -179,15 +179,17 @@ void SoftwareKeyboard::WriteText(std::optional text) { final_data = output_main; if (complete) { - broker.PushNormalDataFromApplet(std::make_shared(std::move(output_main))); + broker.PushNormalDataFromApplet( + std::make_shared(system, std::move(output_main))); broker.SignalStateChanged(); } else { - broker.PushInteractiveDataFromApplet(std::make_shared(std::move(output_sub))); + broker.PushInteractiveDataFromApplet( + std::make_shared(system, std::move(output_sub))); } } else { output_main[0] = 1; complete = true; - broker.PushNormalDataFromApplet(std::make_shared(std::move(output_main))); + broker.PushNormalDataFromApplet(std::make_shared(system, std::move(output_main))); broker.SignalStateChanged(); } } diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h index 5a3824b5a..1d260fef8 100644 --- a/src/core/hle/service/am/applets/software_keyboard.h +++ b/src/core/hle/service/am/applets/software_keyboard.h @@ -80,6 +80,7 @@ private: bool complete = false; bool is_inline = false; std::vector final_data; + Core::System& system; }; } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp index efe595c4f..2ab420789 100644 --- a/src/core/hle/service/am/applets/web_browser.cpp +++ b/src/core/hle/service/am/applets/web_browser.cpp @@ -1,238 +1,271 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include -#include -#include - #include "common/assert.h" -#include "common/common_funcs.h" #include "common/common_paths.h" #include "common/file_util.h" -#include "common/hex_util.h" #include "common/logging/log.h" #include "common/string_util.h" #include "core/core.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/mode.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/system_archive/system_archive.h" -#include "core/file_sys/vfs_types.h" -#include "core/frontend/applets/general_frontend.h" +#include "core/file_sys/vfs_vector.h" #include "core/frontend/applets/web_browser.h" #include "core/hle/kernel/process.h" +#include "core/hle/result.h" +#include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/web_browser.h" #include "core/hle/service/filesystem/filesystem.h" -#include "core/loader/loader.h" +#include "core/hle/service/ns/pl_u.h" namespace Service::AM::Applets { -enum class WebArgTLVType : u16 { - InitialURL = 0x1, - ShopArgumentsURL = 0x2, ///< TODO(DarkLordZach): This is not the official name. - CallbackURL = 0x3, - CallbackableURL = 0x4, - ApplicationID = 0x5, - DocumentPath = 0x6, - DocumentKind = 0x7, - SystemDataID = 0x8, - ShareStartPage = 0x9, - Whitelist = 0xA, - News = 0xB, - UserID = 0xE, - AlbumEntry0 = 0xF, - ScreenShotEnabled = 0x10, - EcClientCertEnabled = 0x11, - Unk12 = 0x12, - PlayReportEnabled = 0x13, - Unk14 = 0x14, - Unk15 = 0x15, - BootDisplayKind = 0x17, - BackgroundKind = 0x18, - FooterEnabled = 0x19, - PointerEnabled = 0x1A, - LeftStickMode = 0x1B, - KeyRepeatFrame1 = 0x1C, - KeyRepeatFrame2 = 0x1D, - BootAsMediaPlayerInv = 0x1E, - DisplayUrlKind = 0x1F, - BootAsMediaPlayer = 0x21, - ShopJumpEnabled = 0x22, - MediaAutoPlayEnabled = 0x23, - LobbyParameter = 0x24, - ApplicationAlbumEntry = 0x26, - JsExtensionEnabled = 0x27, - AdditionalCommentText = 0x28, - TouchEnabledOnContents = 0x29, - UserAgentAdditionalString = 0x2A, - AdditionalMediaData0 = 0x2B, - MediaPlayerAutoCloseEnabled = 0x2C, - PageCacheEnabled = 0x2D, - WebAudioEnabled = 0x2E, - Unk2F = 0x2F, - YouTubeVideoWhitelist = 0x31, - FooterFixedKind = 0x32, - PageFadeEnabled = 0x33, - MediaCreatorApplicationRatingAge = 0x34, - BootLoadingIconEnabled = 0x35, - PageScrollIndicationEnabled = 0x36, - MediaPlayerSpeedControlEnabled = 0x37, - AlbumEntry1 = 0x38, - AlbumEntry2 = 0x39, - AlbumEntry3 = 0x3A, - AdditionalMediaData1 = 0x3B, - AdditionalMediaData2 = 0x3C, - AdditionalMediaData3 = 0x3D, - BootFooterButton = 0x3E, - OverrideWebAudioVolume = 0x3F, - OverrideMediaAudioVolume = 0x40, - BootMode = 0x41, - WebSessionEnabled = 0x42, -}; - -enum class ShimKind : u32 { - Shop = 1, - Login = 2, - Offline = 3, - Share = 4, - Web = 5, - Wifi = 6, - Lobby = 7, -}; - -enum class ShopWebTarget { - ApplicationInfo, - AddOnContentList, - SubscriptionList, - ConsumableItemList, - Home, - Settings, -}; - namespace { -constexpr std::size_t SHIM_KIND_COUNT = 0x8; +template +void ParseRawValue(T& value, const std::vector& data) { + static_assert(std::is_trivially_copyable_v, + "It's undefined behavior to use memcpy with non-trivially copyable objects"); + std::memcpy(&value, data.data(), data.size()); +} -struct WebArgHeader { - u16 count; - INSERT_PADDING_BYTES(2); - ShimKind kind; -}; -static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size."); +template +T ParseRawValue(const std::vector& data) { + T value; + ParseRawValue(value, data); + return value; +} -struct WebArgTLV { - WebArgTLVType type; - u16 size; - u32 offset; -}; -static_assert(sizeof(WebArgTLV) == 0x8, "WebArgTLV has incorrect size."); +std::string ParseStringValue(const std::vector& data) { + return Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast(data.data()), + data.size()); +} -struct WebCommonReturnValue { - u32 result_code; - INSERT_PADDING_BYTES(0x4); - std::array last_url; - u64 last_url_size; -}; -static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size."); +std::string GetMainURL(const std::string& url) { + const auto index = url.find('?'); -struct WebWifiPageArg { - INSERT_PADDING_BYTES(4); - std::array connection_test_url; - std::array initial_url; - std::array nifm_network_uuid; - u32 nifm_requirement; -}; -static_assert(sizeof(WebWifiPageArg) == 0x518, "WebWifiPageArg has incorrect size."); + if (index == std::string::npos) { + return url; + } -struct WebWifiReturnValue { - INSERT_PADDING_BYTES(4); - u32 result; -}; -static_assert(sizeof(WebWifiReturnValue) == 0x8, "WebWifiReturnValue has incorrect size."); + return url.substr(0, index); +} -enum class OfflineWebSource : u32 { - OfflineHtmlPage = 0x1, - ApplicationLegalInformation = 0x2, - SystemDataPage = 0x3, -}; +WebArgInputTLVMap ReadWebArgs(const std::vector& web_arg, WebArgHeader& web_arg_header) { + std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader)); -std::map> GetWebArguments(const std::vector& arg) { - if (arg.size() < sizeof(WebArgHeader)) + if (web_arg.size() == sizeof(WebArgHeader)) { return {}; - - WebArgHeader header{}; - std::memcpy(&header, arg.data(), sizeof(WebArgHeader)); - - std::map> out; - u64 offset = sizeof(WebArgHeader); - for (std::size_t i = 0; i < header.count; ++i) { - if (arg.size() < (offset + sizeof(WebArgTLV))) - return out; - - WebArgTLV tlv{}; - std::memcpy(&tlv, arg.data() + offset, sizeof(WebArgTLV)); - offset += sizeof(WebArgTLV); - - offset += tlv.offset; - if (arg.size() < (offset + tlv.size)) - return out; - - std::vector data(tlv.size); - std::memcpy(data.data(), arg.data() + offset, tlv.size); - offset += tlv.size; - - out.insert_or_assign(tlv.type, data); } - return out; + WebArgInputTLVMap input_tlv_map; + + u64 current_offset = sizeof(WebArgHeader); + + for (std::size_t i = 0; i < web_arg_header.total_tlv_entries; ++i) { + if (web_arg.size() < current_offset + sizeof(WebArgInputTLV)) { + return input_tlv_map; + } + + WebArgInputTLV input_tlv; + std::memcpy(&input_tlv, web_arg.data() + current_offset, sizeof(WebArgInputTLV)); + + current_offset += sizeof(WebArgInputTLV); + + if (web_arg.size() < current_offset + input_tlv.arg_data_size) { + return input_tlv_map; + } + + std::vector data(input_tlv.arg_data_size); + std::memcpy(data.data(), web_arg.data() + current_offset, input_tlv.arg_data_size); + + current_offset += input_tlv.arg_data_size; + + input_tlv_map.insert_or_assign(input_tlv.input_tlv_type, std::move(data)); + } + + return input_tlv_map; } -FileSys::VirtualFile GetApplicationRomFS(const Core::System& system, u64 title_id, - FileSys::ContentRecordType type) { - const auto& installed{system.GetContentProvider()}; - const auto res = installed.GetEntry(title_id, type); +FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id, + FileSys::ContentRecordType nca_type) { + if (nca_type == FileSys::ContentRecordType::Data) { + const auto nca = + system.GetFileSystemController().GetSystemNANDContents()->GetEntry(title_id, nca_type); - if (res != nullptr) { - return res->GetRomFS(); + if (nca == nullptr) { + LOG_ERROR(Service_AM, + "NCA of type={} with title_id={:016X} is not found in the System NAND!", + nca_type, title_id); + return FileSys::SystemArchive::SynthesizeSystemArchive(title_id); + } + + return nca->GetRomFS(); + } else { + const auto nca = system.GetContentProvider().GetEntry(title_id, nca_type); + + if (nca == nullptr) { + LOG_ERROR(Service_AM, + "NCA of type={} with title_id={:016X} is not found in the ContentProvider!", + nca_type, title_id); + return nullptr; + } + + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + system.GetContentProvider()}; + + return pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), nca_type); } - - if (type == FileSys::ContentRecordType::Data) { - return FileSys::SystemArchive::SynthesizeSystemArchive(title_id); - } - - return nullptr; } -} // Anonymous namespace +void ExtractSharedFonts(Core::System& system) { + static constexpr std::array DECRYPTED_SHARED_FONTS{ + "FontStandard.ttf", + "FontChineseSimplified.ttf", + "FontExtendedChineseSimplified.ttf", + "FontChineseTraditional.ttf", + "FontKorean.ttf", + "FontNintendoExtended.ttf", + "FontNintendoExtended2.ttf", + }; -WebBrowser::WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_, - Core::Frontend::ECommerceApplet* frontend_e_commerce_) - : Applet{system_.Kernel()}, frontend(frontend_), - frontend_e_commerce(frontend_e_commerce_), system{system_} {} + for (std::size_t i = 0; i < NS::SHARED_FONTS.size(); ++i) { + const auto fonts_dir = Common::FS::SanitizePath( + fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)), + Common::FS::DirectorySeparator::PlatformDefault); + + const auto font_file_path = + Common::FS::SanitizePath(fmt::format("{}/{}", fonts_dir, DECRYPTED_SHARED_FONTS[i]), + Common::FS::DirectorySeparator::PlatformDefault); + + if (Common::FS::Exists(font_file_path)) { + continue; + } + + const auto font = NS::SHARED_FONTS[i]; + const auto font_title_id = static_cast(font.first); + + const auto nca = system.GetFileSystemController().GetSystemNANDContents()->GetEntry( + font_title_id, FileSys::ContentRecordType::Data); + + FileSys::VirtualFile romfs; + + if (!nca) { + romfs = FileSys::SystemArchive::SynthesizeSystemArchive(font_title_id); + } else { + romfs = nca->GetRomFS(); + } + + if (!romfs) { + LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} cannot be extracted!", + font_title_id); + continue; + } + + const auto extracted_romfs = FileSys::ExtractRomFS(romfs); + + if (!extracted_romfs) { + LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} failed to extract!", + font_title_id); + continue; + } + + const auto font_file = extracted_romfs->GetFile(font.second); + + if (!font_file) { + LOG_ERROR(Service_AM, "SharedFont RomFS with title_id={:016X} has no font file \"{}\"!", + font_title_id, font.second); + continue; + } + + std::vector font_data_u32(font_file->GetSize() / sizeof(u32)); + font_file->ReadBytes(font_data_u32.data(), font_file->GetSize()); + + std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(), + Common::swap32); + + std::vector decrypted_data(font_file->GetSize() - 8); + + NS::DecryptSharedFontToTTF(font_data_u32, decrypted_data); + + FileSys::VirtualFile decrypted_font = std::make_shared( + std::move(decrypted_data), DECRYPTED_SHARED_FONTS[i]); + + const auto temp_dir = + system.GetFilesystem()->CreateDirectory(fonts_dir, FileSys::Mode::ReadWrite); + + const auto out_file = temp_dir->CreateFile(DECRYPTED_SHARED_FONTS[i]); + + FileSys::VfsRawCopy(decrypted_font, out_file); + } +} + +} // namespace + +WebBrowser::WebBrowser(Core::System& system_, const Core::Frontend::WebBrowserApplet& frontend_) + : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {} WebBrowser::~WebBrowser() = default; void WebBrowser::Initialize() { Applet::Initialize(); - complete = false; - temporary_dir.clear(); - filename.clear(); - status = RESULT_SUCCESS; + LOG_INFO(Service_AM, "Initializing Web Browser Applet."); + + LOG_DEBUG(Service_AM, + "Initializing Applet with common_args: arg_version={}, lib_version={}, " + "play_startup_sound={}, size={}, system_tick={}, theme_color={}", + common_args.arguments_version, common_args.library_version, + common_args.play_startup_sound, common_args.size, common_args.system_tick, + common_args.theme_color); + + web_applet_version = WebAppletVersion{common_args.library_version}; const auto web_arg_storage = broker.PopNormalDataToApplet(); ASSERT(web_arg_storage != nullptr); + const auto& web_arg = web_arg_storage->GetData(); + ASSERT_OR_EXECUTE(web_arg.size() >= sizeof(WebArgHeader), { return; }); - ASSERT(web_arg.size() >= 0x8); - std::memcpy(&kind, web_arg.data() + 0x4, sizeof(ShimKind)); + web_arg_input_tlv_map = ReadWebArgs(web_arg, web_arg_header); - args = GetWebArguments(web_arg); + LOG_DEBUG(Service_AM, "WebArgHeader: total_tlv_entries={}, shim_kind={}", + web_arg_header.total_tlv_entries, web_arg_header.shim_kind); - InitializeInternal(); + ExtractSharedFonts(system); + + switch (web_arg_header.shim_kind) { + case ShimKind::Shop: + InitializeShop(); + break; + case ShimKind::Login: + InitializeLogin(); + break; + case ShimKind::Offline: + InitializeOffline(); + break; + case ShimKind::Share: + InitializeShare(); + break; + case ShimKind::Web: + InitializeWeb(); + break; + case ShimKind::Wifi: + InitializeWifi(); + break; + case ShimKind::Lobby: + InitializeLobby(); + break; + default: + UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind); + break; + } } bool WebBrowser::TransactionComplete() const { @@ -244,315 +277,202 @@ ResultCode WebBrowser::GetStatus() const { } void WebBrowser::ExecuteInteractive() { - UNIMPLEMENTED_MSG("Unexpected interactive data recieved!"); + UNIMPLEMENTED_MSG("WebSession is not implemented"); } void WebBrowser::Execute() { - if (complete) { - return; - } - - if (status != RESULT_SUCCESS) { - complete = true; - - // This is a workaround in order not to softlock yuzu when an error happens during the - // webapplet init. In order to avoid an svcBreak, the status is set to RESULT_SUCCESS - Finalize(); - status = RESULT_SUCCESS; - - return; - } - - ExecuteInternal(); -} - -void WebBrowser::UnpackRomFS() { - if (unpacked) - return; - - ASSERT(offline_romfs != nullptr); - const auto dir = - FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard); - const auto& vfs{system.GetFilesystem()}; - const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite); - FileSys::VfsRawCopyD(dir, temp_dir); - - unpacked = true; -} - -void WebBrowser::Finalize() { - complete = true; - - WebCommonReturnValue out{}; - out.result_code = 0; - out.last_url_size = 0; - - std::vector data(sizeof(WebCommonReturnValue)); - std::memcpy(data.data(), &out, sizeof(WebCommonReturnValue)); - - broker.PushNormalDataFromApplet(std::make_shared(std::move(data))); - broker.SignalStateChanged(); - - if (!temporary_dir.empty() && Common::FS::IsDirectory(temporary_dir)) { - Common::FS::DeleteDirRecursively(temporary_dir); - } -} - -void WebBrowser::InitializeInternal() { - using WebAppletInitializer = void (WebBrowser::*)(); - - constexpr std::array functions{ - nullptr, &WebBrowser::InitializeShop, - nullptr, &WebBrowser::InitializeOffline, - nullptr, nullptr, - nullptr, nullptr, - }; - - const auto index = static_cast(kind); - - if (index > functions.size() || functions[index] == nullptr) { - LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index); - return; - } - - const auto function = functions[index]; - (this->*function)(); -} - -void WebBrowser::ExecuteInternal() { - using WebAppletExecutor = void (WebBrowser::*)(); - - constexpr std::array functions{ - nullptr, &WebBrowser::ExecuteShop, - nullptr, &WebBrowser::ExecuteOffline, - nullptr, nullptr, - nullptr, nullptr, - }; - - const auto index = static_cast(kind); - - if (index > functions.size() || functions[index] == nullptr) { - LOG_ERROR(Service_AM, "Invalid shim_kind={:08X}", index); - return; - } - - const auto function = functions[index]; - (this->*function)(); -} - -void WebBrowser::InitializeShop() { - if (frontend_e_commerce == nullptr) { - LOG_ERROR(Service_AM, "Missing ECommerce Applet frontend!"); - status = RESULT_UNKNOWN; - return; - } - - const auto user_id_data = args.find(WebArgTLVType::UserID); - - user_id = std::nullopt; - if (user_id_data != args.end()) { - user_id = u128{}; - std::memcpy(user_id->data(), user_id_data->second.data(), sizeof(u128)); - } - - const auto url = args.find(WebArgTLVType::ShopArgumentsURL); - - if (url == args.end()) { - LOG_ERROR(Service_AM, "Missing EShop Arguments URL for initialization!"); - status = RESULT_UNKNOWN; - return; - } - - std::vector split_query; - Common::SplitString(Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast(url->second.data()), url->second.size()), - '?', split_query); - - // 2 -> Main URL '?' Query Parameters - // Less is missing info, More is malformed - if (split_query.size() != 2) { - LOG_ERROR(Service_AM, "EShop Arguments has more than one question mark, malformed"); - status = RESULT_UNKNOWN; - return; - } - - std::vector queries; - Common::SplitString(split_query[1], '&', queries); - - const auto split_single_query = - [](const std::string& in) -> std::pair { - const auto index = in.find('='); - if (index == std::string::npos || index == in.size() - 1) { - return {in, ""}; - } - - return {in.substr(0, index), in.substr(index + 1)}; - }; - - std::transform(queries.begin(), queries.end(), - std::inserter(shop_query, std::next(shop_query.begin())), split_single_query); - - const auto scene = shop_query.find("scene"); - - if (scene == shop_query.end()) { - LOG_ERROR(Service_AM, "No scene parameter was passed via shop query!"); - status = RESULT_UNKNOWN; - return; - } - - const std::map> target_map{ - {"product_detail", ShopWebTarget::ApplicationInfo}, - {"aocs", ShopWebTarget::AddOnContentList}, - {"subscriptions", ShopWebTarget::SubscriptionList}, - {"consumption", ShopWebTarget::ConsumableItemList}, - {"settings", ShopWebTarget::Settings}, - {"top", ShopWebTarget::Home}, - }; - - const auto target = target_map.find(scene->second); - if (target == target_map.end()) { - LOG_ERROR(Service_AM, "Scene for shop query is invalid! (scene={})", scene->second); - status = RESULT_UNKNOWN; - return; - } - - shop_web_target = target->second; - - const auto title_id_data = shop_query.find("dst_app_id"); - if (title_id_data != shop_query.end()) { - title_id = std::stoull(title_id_data->second, nullptr, 0x10); - } - - const auto mode_data = shop_query.find("mode"); - if (mode_data != shop_query.end()) { - shop_full_display = mode_data->second == "full"; - } -} - -void WebBrowser::InitializeOffline() { - if (args.find(WebArgTLVType::DocumentPath) == args.end() || - args.find(WebArgTLVType::DocumentKind) == args.end() || - args.find(WebArgTLVType::ApplicationID) == args.end()) { - status = RESULT_UNKNOWN; - LOG_ERROR(Service_AM, "Missing necessary parameters for initialization!"); - } - - const auto url_data = args[WebArgTLVType::DocumentPath]; - filename = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast(url_data.data()), url_data.size()); - - OfflineWebSource source; - ASSERT(args[WebArgTLVType::DocumentKind].size() >= 4); - std::memcpy(&source, args[WebArgTLVType::DocumentKind].data(), sizeof(OfflineWebSource)); - - constexpr std::array WEB_SOURCE_NAMES{ - "manual", - "legal", - "system", - }; - - temporary_dir = - Common::FS::SanitizePath(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + - "web_applet_" + WEB_SOURCE_NAMES[static_cast(source) - 1], - Common::FS::DirectorySeparator::PlatformDefault); - Common::FS::DeleteDirRecursively(temporary_dir); - - u64 title_id = 0; // 0 corresponds to current process - ASSERT(args[WebArgTLVType::ApplicationID].size() >= 0x8); - std::memcpy(&title_id, args[WebArgTLVType::ApplicationID].data(), sizeof(u64)); - FileSys::ContentRecordType type = FileSys::ContentRecordType::Data; - - switch (source) { - case OfflineWebSource::OfflineHtmlPage: - // While there is an AppID TLV field, in official SW this is always ignored. - title_id = 0; - type = FileSys::ContentRecordType::HtmlDocument; + switch (web_arg_header.shim_kind) { + case ShimKind::Shop: + ExecuteShop(); break; - case OfflineWebSource::ApplicationLegalInformation: - type = FileSys::ContentRecordType::LegalInformation; + case ShimKind::Login: + ExecuteLogin(); break; - case OfflineWebSource::SystemDataPage: - type = FileSys::ContentRecordType::Data; + case ShimKind::Offline: + ExecuteOffline(); break; - } - - if (title_id == 0) { - title_id = system.CurrentProcess()->GetTitleID(); - } - - offline_romfs = GetApplicationRomFS(system, title_id, type); - if (offline_romfs == nullptr) { - status = RESULT_UNKNOWN; - LOG_ERROR(Service_AM, "Failed to find offline data for request!"); - } - - std::string path_additional_directory; - if (source == OfflineWebSource::OfflineHtmlPage) { - path_additional_directory = std::string(DIR_SEP).append("html-document"); - } - - filename = - Common::FS::SanitizePath(temporary_dir + path_additional_directory + DIR_SEP + filename, - Common::FS::DirectorySeparator::PlatformDefault); -} - -void WebBrowser::ExecuteShop() { - const auto callback = [this]() { Finalize(); }; - - const auto check_optional_parameter = [this](const auto& p) { - if (!p.has_value()) { - LOG_ERROR(Service_AM, "Missing one or more necessary parameters for execution!"); - status = RESULT_UNKNOWN; - return false; - } - - return true; - }; - - switch (shop_web_target) { - case ShopWebTarget::ApplicationInfo: - if (!check_optional_parameter(title_id)) - return; - frontend_e_commerce->ShowApplicationInformation(callback, *title_id, user_id, - shop_full_display, shop_extra_parameter); + case ShimKind::Share: + ExecuteShare(); break; - case ShopWebTarget::AddOnContentList: - if (!check_optional_parameter(title_id)) - return; - frontend_e_commerce->ShowAddOnContentList(callback, *title_id, user_id, shop_full_display); + case ShimKind::Web: + ExecuteWeb(); break; - case ShopWebTarget::ConsumableItemList: - if (!check_optional_parameter(title_id)) - return; - frontend_e_commerce->ShowConsumableItemList(callback, *title_id, user_id); + case ShimKind::Wifi: + ExecuteWifi(); break; - case ShopWebTarget::Home: - if (!check_optional_parameter(user_id)) - return; - if (!check_optional_parameter(shop_full_display)) - return; - frontend_e_commerce->ShowShopHome(callback, *user_id, *shop_full_display); - break; - case ShopWebTarget::Settings: - if (!check_optional_parameter(user_id)) - return; - if (!check_optional_parameter(shop_full_display)) - return; - frontend_e_commerce->ShowSettings(callback, *user_id, *shop_full_display); - break; - case ShopWebTarget::SubscriptionList: - if (!check_optional_parameter(title_id)) - return; - frontend_e_commerce->ShowSubscriptionList(callback, *title_id, user_id); + case ShimKind::Lobby: + ExecuteLobby(); break; default: - UNREACHABLE(); + UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind); + WebBrowserExit(WebExitReason::EndButtonPressed); + break; } } +void WebBrowser::ExtractOfflineRomFS() { + LOG_DEBUG(Service_AM, "Extracting RomFS to {}", offline_cache_dir); + + const auto extracted_romfs_dir = + FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard); + + const auto temp_dir = + system.GetFilesystem()->CreateDirectory(offline_cache_dir, FileSys::Mode::ReadWrite); + + FileSys::VfsRawCopyD(extracted_romfs_dir, temp_dir); +} + +void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) { + if ((web_arg_header.shim_kind == ShimKind::Share && + web_applet_version >= WebAppletVersion::Version196608) || + (web_arg_header.shim_kind == ShimKind::Web && + web_applet_version >= WebAppletVersion::Version524288)) { + // TODO: Push Output TLVs instead of a WebCommonReturnValue + } + + WebCommonReturnValue web_common_return_value; + + web_common_return_value.exit_reason = exit_reason; + std::memcpy(&web_common_return_value.last_url, last_url.data(), last_url.size()); + web_common_return_value.last_url_size = last_url.size(); + + LOG_DEBUG(Service_AM, "WebCommonReturnValue: exit_reason={}, last_url={}, last_url_size={}", + exit_reason, last_url, last_url.size()); + + complete = true; + std::vector out_data(sizeof(WebCommonReturnValue)); + std::memcpy(out_data.data(), &web_common_return_value, out_data.size()); + broker.PushNormalDataFromApplet(std::make_shared(system, std::move(out_data))); + broker.SignalStateChanged(); +} + +bool WebBrowser::InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const { + return web_arg_input_tlv_map.find(input_tlv_type) != web_arg_input_tlv_map.end(); +} + +std::optional> WebBrowser::GetInputTLVData(WebArgInputTLVType input_tlv_type) { + const auto map_it = web_arg_input_tlv_map.find(input_tlv_type); + + if (map_it == web_arg_input_tlv_map.end()) { + return std::nullopt; + } + + return map_it->second; +} + +void WebBrowser::InitializeShop() {} + +void WebBrowser::InitializeLogin() {} + +void WebBrowser::InitializeOffline() { + const auto document_path = + ParseStringValue(GetInputTLVData(WebArgInputTLVType::DocumentPath).value()); + + const auto document_kind = + ParseRawValue(GetInputTLVData(WebArgInputTLVType::DocumentKind).value()); + + std::string additional_paths; + + switch (document_kind) { + case DocumentKind::OfflineHtmlPage: + default: + title_id = system.CurrentProcess()->GetTitleID(); + nca_type = FileSys::ContentRecordType::HtmlDocument; + additional_paths = "html-document"; + break; + case DocumentKind::ApplicationLegalInformation: + title_id = ParseRawValue(GetInputTLVData(WebArgInputTLVType::ApplicationID).value()); + nca_type = FileSys::ContentRecordType::LegalInformation; + break; + case DocumentKind::SystemDataPage: + title_id = ParseRawValue(GetInputTLVData(WebArgInputTLVType::SystemDataID).value()); + nca_type = FileSys::ContentRecordType::Data; + break; + } + + static constexpr std::array RESOURCE_TYPES{ + "manual", + "legal_information", + "system_data", + }; + + offline_cache_dir = Common::FS::SanitizePath( + fmt::format("{}/offline_web_applet_{}/{:016X}", + Common::FS::GetUserPath(Common::FS::UserPath::CacheDir), + RESOURCE_TYPES[static_cast(document_kind) - 1], title_id), + Common::FS::DirectorySeparator::PlatformDefault); + + offline_document = Common::FS::SanitizePath( + fmt::format("{}/{}/{}", offline_cache_dir, additional_paths, document_path), + Common::FS::DirectorySeparator::PlatformDefault); +} + +void WebBrowser::InitializeShare() {} + +void WebBrowser::InitializeWeb() { + external_url = ParseStringValue(GetInputTLVData(WebArgInputTLVType::InitialURL).value()); +} + +void WebBrowser::InitializeWifi() {} + +void WebBrowser::InitializeLobby() {} + +void WebBrowser::ExecuteShop() { + LOG_WARNING(Service_AM, "(STUBBED) called, Shop Applet is not implemented"); + WebBrowserExit(WebExitReason::EndButtonPressed); +} + +void WebBrowser::ExecuteLogin() { + LOG_WARNING(Service_AM, "(STUBBED) called, Login Applet is not implemented"); + WebBrowserExit(WebExitReason::EndButtonPressed); +} + void WebBrowser::ExecuteOffline() { - frontend.OpenPageLocal( - filename, [this] { UnpackRomFS(); }, [this] { Finalize(); }); + const auto main_url = Common::FS::SanitizePath(GetMainURL(offline_document), + Common::FS::DirectorySeparator::PlatformDefault); + + if (!Common::FS::Exists(main_url)) { + offline_romfs = GetOfflineRomFS(system, title_id, nca_type); + + if (offline_romfs == nullptr) { + LOG_ERROR(Service_AM, + "RomFS with title_id={:016X} and nca_type={} cannot be extracted!", title_id, + nca_type); + WebBrowserExit(WebExitReason::WindowClosed); + return; + } + } + + LOG_INFO(Service_AM, "Opening offline document at {}", offline_document); + + frontend.OpenLocalWebPage( + offline_document, [this] { ExtractOfflineRomFS(); }, + [this](WebExitReason exit_reason, std::string last_url) { + WebBrowserExit(exit_reason, last_url); + }); } +void WebBrowser::ExecuteShare() { + LOG_WARNING(Service_AM, "(STUBBED) called, Share Applet is not implemented"); + WebBrowserExit(WebExitReason::EndButtonPressed); +} + +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); + }); +} + +void WebBrowser::ExecuteWifi() { + LOG_WARNING(Service_AM, "(STUBBED) called, Wifi Applet is not implemented"); + WebBrowserExit(WebExitReason::EndButtonPressed); +} + +void WebBrowser::ExecuteLobby() { + LOG_WARNING(Service_AM, "(STUBBED) called, Lobby Applet is not implemented"); + WebBrowserExit(WebExitReason::EndButtonPressed); +} } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h index 8d4027411..04c274754 100644 --- a/src/core/hle/service/am/applets/web_browser.h +++ b/src/core/hle/service/am/applets/web_browser.h @@ -1,28 +1,31 @@ -// Copyright 2018 yuzu emulator team +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once -#include +#include + +#include "common/common_funcs.h" +#include "common/common_types.h" #include "core/file_sys/vfs_types.h" -#include "core/hle/service/am/am.h" +#include "core/hle/result.h" #include "core/hle/service/am/applets/applets.h" +#include "core/hle/service/am/applets/web_types.h" namespace Core { class System; } -namespace Service::AM::Applets { +namespace FileSys { +enum class ContentRecordType : u8; +} -enum class ShimKind : u32; -enum class ShopWebTarget; -enum class WebArgTLVType : u16; +namespace Service::AM::Applets { class WebBrowser final : public Applet { public: - WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_, - Core::Frontend::ECommerceApplet* frontend_e_commerce_ = nullptr); + WebBrowser(Core::System& system_, const Core::Frontend::WebBrowserApplet& frontend_); ~WebBrowser() override; @@ -33,49 +36,50 @@ public: void ExecuteInteractive() override; void Execute() override; - // Callback to be fired when the frontend needs the manual RomFS unpacked to temporary - // directory. This is a blocking call and may take a while as some manuals can be up to 100MB in - // size. Attempting to access files at filename before invocation is likely to not work. - void UnpackRomFS(); + void ExtractOfflineRomFS(); - // Callback to be fired when the frontend is finished browsing. This will delete the temporary - // manual RomFS extracted files, so ensure this is only called at actual finalization. - void Finalize(); + void WebBrowserExit(WebExitReason exit_reason, std::string last_url = ""); private: - void InitializeInternal(); - void ExecuteInternal(); + bool InputTLVExistsInMap(WebArgInputTLVType input_tlv_type) const; - // Specific initializers for the types of web applets + std::optional> GetInputTLVData(WebArgInputTLVType input_tlv_type); + + // Initializers for the various types of browser applets void InitializeShop(); + void InitializeLogin(); void InitializeOffline(); + void InitializeShare(); + void InitializeWeb(); + void InitializeWifi(); + void InitializeLobby(); - // Specific executors for the types of web applets + // Executors for the various types of browser applets void ExecuteShop(); + void ExecuteLogin(); void ExecuteOffline(); + void ExecuteShare(); + void ExecuteWeb(); + void ExecuteWifi(); + void ExecuteLobby(); - Core::Frontend::WebBrowserApplet& frontend; + const Core::Frontend::WebBrowserApplet& frontend; - // Extra frontends for specialized functions - Core::Frontend::ECommerceApplet* frontend_e_commerce; + bool complete{false}; + ResultCode status{RESULT_SUCCESS}; - bool complete = false; - bool unpacked = false; - ResultCode status = RESULT_SUCCESS; - - ShimKind kind; - std::map> args; + WebAppletVersion web_applet_version; + WebExitReason web_exit_reason; + WebArgHeader web_arg_header; + WebArgInputTLVMap web_arg_input_tlv_map; + u64 title_id; + FileSys::ContentRecordType nca_type; + std::string offline_cache_dir; + std::string offline_document; FileSys::VirtualFile offline_romfs; - std::string temporary_dir; - std::string filename; - ShopWebTarget shop_web_target; - std::map> shop_query; - std::optional title_id = 0; - std::optional user_id; - std::optional shop_full_display; - std::string shop_extra_parameter; + std::string external_url; Core::System& system; }; diff --git a/src/core/hle/service/am/applets/web_types.h b/src/core/hle/service/am/applets/web_types.h new file mode 100644 index 000000000..419c2bf79 --- /dev/null +++ b/src/core/hle/service/am/applets/web_types.h @@ -0,0 +1,178 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +namespace Service::AM::Applets { + +enum class WebAppletVersion : u32_le { + Version0 = 0x0, // Only used by WifiWebAuthApplet + Version131072 = 0x20000, // 1.0.0 - 2.3.0 + Version196608 = 0x30000, // 3.0.0 - 4.1.0 + Version327680 = 0x50000, // 5.0.0 - 5.1.0 + Version393216 = 0x60000, // 6.0.0 - 7.0.1 + Version524288 = 0x80000, // 8.0.0+ +}; + +enum class ShimKind : u32 { + Shop = 1, + Login = 2, + Offline = 3, + Share = 4, + Web = 5, + Wifi = 6, + Lobby = 7, +}; + +enum class WebExitReason : u32 { + EndButtonPressed = 0, + BackButtonPressed = 1, + ExitRequested = 2, + CallbackURL = 3, + WindowClosed = 4, + ErrorDialog = 7, +}; + +enum class WebArgInputTLVType : u16 { + InitialURL = 0x1, + CallbackURL = 0x3, + CallbackableURL = 0x4, + ApplicationID = 0x5, + DocumentPath = 0x6, + DocumentKind = 0x7, + SystemDataID = 0x8, + ShareStartPage = 0x9, + Whitelist = 0xA, + News = 0xB, + UserID = 0xE, + AlbumEntry0 = 0xF, + ScreenShotEnabled = 0x10, + EcClientCertEnabled = 0x11, + PlayReportEnabled = 0x13, + BootDisplayKind = 0x17, + BackgroundKind = 0x18, + FooterEnabled = 0x19, + PointerEnabled = 0x1A, + LeftStickMode = 0x1B, + KeyRepeatFrame1 = 0x1C, + KeyRepeatFrame2 = 0x1D, + BootAsMediaPlayerInverted = 0x1E, + DisplayURLKind = 0x1F, + BootAsMediaPlayer = 0x21, + ShopJumpEnabled = 0x22, + MediaAutoPlayEnabled = 0x23, + LobbyParameter = 0x24, + ApplicationAlbumEntry = 0x26, + JsExtensionEnabled = 0x27, + AdditionalCommentText = 0x28, + TouchEnabledOnContents = 0x29, + UserAgentAdditionalString = 0x2A, + AdditionalMediaData0 = 0x2B, + MediaPlayerAutoCloseEnabled = 0x2C, + PageCacheEnabled = 0x2D, + WebAudioEnabled = 0x2E, + YouTubeVideoWhitelist = 0x31, + FooterFixedKind = 0x32, + PageFadeEnabled = 0x33, + MediaCreatorApplicationRatingAge = 0x34, + BootLoadingIconEnabled = 0x35, + PageScrollIndicatorEnabled = 0x36, + MediaPlayerSpeedControlEnabled = 0x37, + AlbumEntry1 = 0x38, + AlbumEntry2 = 0x39, + AlbumEntry3 = 0x3A, + AdditionalMediaData1 = 0x3B, + AdditionalMediaData2 = 0x3C, + AdditionalMediaData3 = 0x3D, + BootFooterButton = 0x3E, + OverrideWebAudioVolume = 0x3F, + OverrideMediaAudioVolume = 0x40, + BootMode = 0x41, + WebSessionEnabled = 0x42, + MediaPlayerOfflineEnabled = 0x43, +}; + +enum class WebArgOutputTLVType : u16 { + ShareExitReason = 0x1, + LastURL = 0x2, + LastURLSize = 0x3, + SharePostResult = 0x4, + PostServiceName = 0x5, + PostServiceNameSize = 0x6, + PostID = 0x7, + PostIDSize = 0x8, + MediaPlayerAutoClosedByCompletion = 0x9, +}; + +enum class DocumentKind : u32 { + OfflineHtmlPage = 1, + ApplicationLegalInformation = 2, + SystemDataPage = 3, +}; + +enum class ShareStartPage : u32 { + Default, + Settings, +}; + +enum class BootDisplayKind : u32 { + Default, + White, + Black, +}; + +enum class BackgroundKind : u32 { + Default, +}; + +enum class LeftStickMode : u32 { + Pointer, + Cursor, +}; + +enum class WebSessionBootMode : u32 { + AllForeground, + AllForegroundInitiallyHidden, +}; + +struct WebArgHeader { + u16 total_tlv_entries{}; + INSERT_PADDING_BYTES(2); + ShimKind shim_kind{}; +}; +static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size."); + +struct WebArgInputTLV { + WebArgInputTLVType input_tlv_type{}; + u16 arg_data_size{}; + INSERT_PADDING_WORDS(1); +}; +static_assert(sizeof(WebArgInputTLV) == 0x8, "WebArgInputTLV has incorrect size."); + +struct WebArgOutputTLV { + WebArgOutputTLVType output_tlv_type{}; + u16 arg_data_size{}; + INSERT_PADDING_WORDS(1); +}; +static_assert(sizeof(WebArgOutputTLV) == 0x8, "WebArgOutputTLV has incorrect size."); + +struct WebCommonReturnValue { + WebExitReason exit_reason{}; + INSERT_PADDING_WORDS(1); + std::array last_url{}; + u64 last_url_size{}; +}; +static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size."); + +using WebArgInputTLVMap = std::unordered_map>; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/idle.cpp b/src/core/hle/service/am/idle.cpp index d256d57c8..6196773d5 100644 --- a/src/core/hle/service/am/idle.cpp +++ b/src/core/hle/service/am/idle.cpp @@ -6,7 +6,7 @@ namespace Service::AM { -IdleSys::IdleSys() : ServiceFramework{"idle:sys"} { +IdleSys::IdleSys(Core::System& system_) : ServiceFramework{system_, "idle:sys"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetAutoPowerDownEvent"}, diff --git a/src/core/hle/service/am/idle.h b/src/core/hle/service/am/idle.h index c44e856b1..e290c30b1 100644 --- a/src/core/hle/service/am/idle.h +++ b/src/core/hle/service/am/idle.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::AM { class IdleSys final : public ServiceFramework { public: - explicit IdleSys(); + explicit IdleSys(Core::System& system_); ~IdleSys() override; }; diff --git a/src/core/hle/service/am/omm.cpp b/src/core/hle/service/am/omm.cpp index 37389ccda..55de67e1d 100644 --- a/src/core/hle/service/am/omm.cpp +++ b/src/core/hle/service/am/omm.cpp @@ -6,7 +6,7 @@ namespace Service::AM { -OMM::OMM() : ServiceFramework{"omm"} { +OMM::OMM(Core::System& system_) : ServiceFramework{system_, "omm"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetOperationMode"}, diff --git a/src/core/hle/service/am/omm.h b/src/core/hle/service/am/omm.h index 59dc91b72..3766150fe 100644 --- a/src/core/hle/service/am/omm.h +++ b/src/core/hle/service/am/omm.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::AM { class OMM final : public ServiceFramework { public: - explicit OMM(); + explicit OMM(Core::System& system_); ~OMM() override; }; diff --git a/src/core/hle/service/am/spsm.cpp b/src/core/hle/service/am/spsm.cpp index f27729ce7..95218d9ee 100644 --- a/src/core/hle/service/am/spsm.cpp +++ b/src/core/hle/service/am/spsm.cpp @@ -6,7 +6,7 @@ namespace Service::AM { -SPSM::SPSM() : ServiceFramework{"spsm"} { +SPSM::SPSM(Core::System& system_) : ServiceFramework{system_, "spsm"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetState"}, diff --git a/src/core/hle/service/am/spsm.h b/src/core/hle/service/am/spsm.h index 3a0b979fa..04bbf9e68 100644 --- a/src/core/hle/service/am/spsm.h +++ b/src/core/hle/service/am/spsm.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::AM { class SPSM final : public ServiceFramework { public: - explicit SPSM(); + explicit SPSM(Core::System& system_); ~SPSM() override; }; diff --git a/src/core/hle/service/am/tcap.cpp b/src/core/hle/service/am/tcap.cpp index a75cbdda8..4d0971c03 100644 --- a/src/core/hle/service/am/tcap.cpp +++ b/src/core/hle/service/am/tcap.cpp @@ -6,7 +6,7 @@ namespace Service::AM { -TCAP::TCAP() : ServiceFramework{"tcap"} { +TCAP::TCAP(Core::System& system_) : ServiceFramework{system_, "tcap"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetContinuousHighSkinTemperatureEvent"}, diff --git a/src/core/hle/service/am/tcap.h b/src/core/hle/service/am/tcap.h index 2021b55d1..e9578f16e 100644 --- a/src/core/hle/service/am/tcap.h +++ b/src/core/hle/service/am/tcap.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::AM { class TCAP final : public ServiceFramework { public: - explicit TCAP(); + explicit TCAP(Core::System& system_); ~TCAP() override; }; diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 8e79f707b..23e28565b 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -6,6 +6,8 @@ #include #include #include "common/logging/log.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" @@ -22,11 +24,8 @@ namespace Service::AOC { -constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; -constexpr u64 DLC_BASE_TO_AOC_ID = 0x1000; - static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) { - return (title_id & DLC_BASE_TITLE_ID_MASK) == base; + return FileSys::GetBaseTitleID(title_id) == base; } static std::vector AccumulateAOCTitleIDs(Core::System& system) { @@ -47,8 +46,64 @@ static std::vector AccumulateAOCTitleIDs(Core::System& system) { return add_on_content; } -AOC_U::AOC_U(Core::System& system) - : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs(system)), system(system) { +class IPurchaseEventManager final : public ServiceFramework { +public: + explicit IPurchaseEventManager(Core::System& system_) + : ServiceFramework{system_, "IPurchaseEventManager"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IPurchaseEventManager::SetDefaultDeliveryTarget, "SetDefaultDeliveryTarget"}, + {1, &IPurchaseEventManager::SetDeliveryTarget, "SetDeliveryTarget"}, + {2, &IPurchaseEventManager::GetPurchasedEventReadableHandle, "GetPurchasedEventReadableHandle"}, + {3, nullptr, "PopPurchasedProductInfo"}, + {4, nullptr, "PopPurchasedProductInfoWithUid"}, + }; + // clang-format on + + RegisterHandlers(functions); + + purchased_event = Kernel::WritableEvent::CreateEventPair( + system.Kernel(), "IPurchaseEventManager:PurchasedEvent"); + } + +private: + void SetDefaultDeliveryTarget(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto unknown_1 = rp.Pop(); + [[maybe_unused]] const auto unknown_2 = ctx.ReadBuffer(); + + LOG_WARNING(Service_AOC, "(STUBBED) called, unknown_1={}", unknown_1); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void SetDeliveryTarget(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto unknown_1 = rp.Pop(); + [[maybe_unused]] const auto unknown_2 = ctx.ReadBuffer(); + + LOG_WARNING(Service_AOC, "(STUBBED) called, unknown_1={}", unknown_1); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetPurchasedEventReadableHandle(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AOC, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(purchased_event.readable); + } + + Kernel::EventPair purchased_event; +}; + +AOC_U::AOC_U(Core::System& system_) + : ServiceFramework{system_, "aoc:u"}, add_on_content{AccumulateAOCTitleIDs(system)} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CountAddOnContentByApplicationId"}, @@ -61,8 +116,8 @@ AOC_U::AOC_U(Core::System& system) {7, &AOC_U::PrepareAddOnContent, "PrepareAddOnContent"}, {8, &AOC_U::GetAddOnContentListChangedEvent, "GetAddOnContentListChangedEvent"}, {9, nullptr, "GetAddOnContentLostErrorCode"}, - {100, nullptr, "CreateEcPurchasedEventManager"}, - {101, nullptr, "CreatePermanentEcPurchasedEventManager"}, + {100, &AOC_U::CreateEcPurchasedEventManager, "CreateEcPurchasedEventManager"}, + {101, &AOC_U::CreatePermanentEcPurchasedEventManager, "CreatePermanentEcPurchasedEventManager"}, }; // clang-format on @@ -122,11 +177,11 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { const auto& disabled = Settings::values.disabled_addons[current]; if (std::find(disabled.begin(), disabled.end(), "DLC") == disabled.end()) { for (u64 content_id : add_on_content) { - if ((content_id & DLC_BASE_TITLE_ID_MASK) != current) { + if (FileSys::GetBaseTitleID(content_id) != current) { continue; } - out.push_back(static_cast(content_id & 0x7FF)); + out.push_back(static_cast(FileSys::GetAOCID(content_id))); } } @@ -163,11 +218,12 @@ void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); const auto title_id = system.CurrentProcess()->GetTitleID(); - FileSys::PatchManager pm{title_id}; + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + system.GetContentProvider()}; const auto res = pm.GetControlMetadata(); if (res.first == nullptr) { - rb.Push(title_id + DLC_BASE_TO_AOC_ID); + rb.Push(FileSys::GetAOCBaseTitleID(title_id)); return; } @@ -199,6 +255,22 @@ void AOC_U::GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx) { rb.PushCopyObjects(aoc_change_event.readable); } +void AOC_U::CreateEcPurchasedEventManager(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AOC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface(system); +} + +void AOC_U::CreatePermanentEcPurchasedEventManager(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AOC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface(system); +} + void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { std::make_shared(system)->InstallAsService(service_manager); } diff --git a/src/core/hle/service/aoc/aoc_u.h b/src/core/hle/service/aoc/aoc_u.h index 848b2f416..26ee51be0 100644 --- a/src/core/hle/service/aoc/aoc_u.h +++ b/src/core/hle/service/aoc/aoc_u.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class WritableEvent; } @@ -23,10 +27,11 @@ private: void GetAddOnContentBaseId(Kernel::HLERequestContext& ctx); void PrepareAddOnContent(Kernel::HLERequestContext& ctx); void GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx); + void CreateEcPurchasedEventManager(Kernel::HLERequestContext& ctx); + void CreatePermanentEcPurchasedEventManager(Kernel::HLERequestContext& ctx); std::vector add_on_content; Kernel::EventPair aoc_change_event; - Core::System& system; }; /// Registers all AOC services with the specified service manager. diff --git a/src/core/hle/service/apm/apm.cpp b/src/core/hle/service/apm/apm.cpp index 85bbf5988..97d6619dd 100644 --- a/src/core/hle/service/apm/apm.cpp +++ b/src/core/hle/service/apm/apm.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/apm/apm.h" #include "core/hle/service/apm/interface.h" @@ -13,13 +14,14 @@ Module::~Module() = default; void InstallInterfaces(Core::System& system) { auto module_ = std::make_shared(); - std::make_shared(module_, system.GetAPMController(), "apm") + std::make_shared(system, module_, system.GetAPMController(), "apm") ->InstallAsService(system.ServiceManager()); - std::make_shared(module_, system.GetAPMController(), "apm:p") + std::make_shared(system, module_, system.GetAPMController(), "apm:p") ->InstallAsService(system.ServiceManager()); - std::make_shared(module_, system.GetAPMController(), "apm:am") + std::make_shared(system, module_, system.GetAPMController(), "apm:am") + ->InstallAsService(system.ServiceManager()); + std::make_shared(system, system.GetAPMController()) ->InstallAsService(system.ServiceManager()); - std::make_shared(system.GetAPMController())->InstallAsService(system.ServiceManager()); } } // namespace Service::APM diff --git a/src/core/hle/service/apm/apm.h b/src/core/hle/service/apm/apm.h index cf4c2bb11..691fe6c16 100644 --- a/src/core/hle/service/apm/apm.h +++ b/src/core/hle/service/apm/apm.h @@ -4,7 +4,9 @@ #pragma once -#include "core/hle/service/service.h" +namespace Core { +class System; +} namespace Service::APM { diff --git a/src/core/hle/service/apm/controller.cpp b/src/core/hle/service/apm/controller.cpp index 25a886238..03636642b 100644 --- a/src/core/hle/service/apm/controller.cpp +++ b/src/core/hle/service/apm/controller.cpp @@ -48,8 +48,7 @@ void Controller::SetPerformanceConfiguration(PerformanceMode mode, [config](const auto& entry) { return entry.first == config; }); if (iter == config_to_speed.cend()) { - LOG_ERROR(Service_APM, "Invalid performance configuration value provided: {}", - static_cast(config)); + LOG_ERROR(Service_APM, "Invalid performance configuration value provided: {}", config); return; } @@ -69,7 +68,8 @@ void Controller::SetFromCpuBoostMode(CpuBoostMode mode) { } PerformanceMode Controller::GetCurrentPerformanceMode() const { - return Settings::values.use_docked_mode ? PerformanceMode::Docked : PerformanceMode::Handheld; + return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Docked + : PerformanceMode::Handheld; } PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) { diff --git a/src/core/hle/service/apm/interface.cpp b/src/core/hle/service/apm/interface.cpp index 06f0f8edd..0bff97a37 100644 --- a/src/core/hle/service/apm/interface.cpp +++ b/src/core/hle/service/apm/interface.cpp @@ -12,7 +12,8 @@ namespace Service::APM { class ISession final : public ServiceFramework { public: - ISession(Controller& controller) : ServiceFramework("ISession"), controller(controller) { + explicit ISession(Core::System& system_, Controller& controller_) + : ServiceFramework{system_, "ISession"}, controller{controller_} { static const FunctionInfo functions[] = { {0, &ISession::SetPerformanceConfiguration, "SetPerformanceConfiguration"}, {1, &ISession::GetPerformanceConfiguration, "GetPerformanceConfiguration"}, @@ -27,8 +28,7 @@ private: const auto mode = rp.PopEnum(); const auto config = rp.PopEnum(); - LOG_DEBUG(Service_APM, "called mode={} config={}", static_cast(mode), - static_cast(config)); + LOG_DEBUG(Service_APM, "called mode={} config={}", mode, config); controller.SetPerformanceConfiguration(mode, config); @@ -40,7 +40,7 @@ private: IPC::RequestParser rp{ctx}; const auto mode = rp.PopEnum(); - LOG_DEBUG(Service_APM, "called mode={}", static_cast(mode)); + LOG_DEBUG(Service_APM, "called mode={}", mode); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); @@ -50,12 +50,13 @@ private: Controller& controller; }; -APM::APM(std::shared_ptr apm, Controller& controller, const char* name) - : ServiceFramework(name), apm(std::move(apm)), controller(controller) { +APM::APM(Core::System& system_, std::shared_ptr apm_, Controller& controller_, + const char* name) + : ServiceFramework{system_, name}, apm(std::move(apm_)), controller{controller_} { static const FunctionInfo functions[] = { {0, &APM::OpenSession, "OpenSession"}, {1, &APM::GetPerformanceMode, "GetPerformanceMode"}, - {6, nullptr, "IsCpuOverclockEnabled"}, + {6, &APM::IsCpuOverclockEnabled, "IsCpuOverclockEnabled"}, }; RegisterHandlers(functions); } @@ -67,7 +68,7 @@ void APM::OpenSession(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(controller); + rb.PushIpcInterface(system, controller); } void APM::GetPerformanceMode(Kernel::HLERequestContext& ctx) { @@ -77,7 +78,16 @@ void APM::GetPerformanceMode(Kernel::HLERequestContext& ctx) { rb.PushEnum(controller.GetCurrentPerformanceMode()); } -APM_Sys::APM_Sys(Controller& controller) : ServiceFramework{"apm:sys"}, controller(controller) { +void APM::IsCpuOverclockEnabled(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_APM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(false); +} + +APM_Sys::APM_Sys(Core::System& system_, Controller& controller_) + : ServiceFramework{system_, "apm:sys"}, controller{controller_} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestPerformanceMode"}, @@ -101,14 +111,14 @@ void APM_Sys::GetPerformanceEvent(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(controller); + rb.PushIpcInterface(system, controller); } void APM_Sys::SetCpuBoostMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto mode = rp.PopEnum(); - LOG_DEBUG(Service_APM, "called, mode={:08X}", static_cast(mode)); + LOG_DEBUG(Service_APM, "called, mode={:08X}", mode); controller.SetFromCpuBoostMode(mode); diff --git a/src/core/hle/service/apm/interface.h b/src/core/hle/service/apm/interface.h index de1b89437..063ad5308 100644 --- a/src/core/hle/service/apm/interface.h +++ b/src/core/hle/service/apm/interface.h @@ -13,12 +13,14 @@ class Module; class APM final : public ServiceFramework { public: - explicit APM(std::shared_ptr apm, Controller& controller, const char* name); + explicit APM(Core::System& system_, std::shared_ptr apm_, Controller& controller_, + const char* name); ~APM() override; private: void OpenSession(Kernel::HLERequestContext& ctx); void GetPerformanceMode(Kernel::HLERequestContext& ctx); + void IsCpuOverclockEnabled(Kernel::HLERequestContext& ctx); std::shared_ptr apm; Controller& controller; @@ -26,7 +28,7 @@ private: class APM_Sys final : public ServiceFramework { public: - explicit APM_Sys(Controller& controller); + explicit APM_Sys(Core::System& system_, Controller& controller); ~APM_Sys() override; void SetCpuBoostMode(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/audio/audctl.cpp b/src/core/hle/service/audio/audctl.cpp index 6ddb547fb..84890be72 100644 --- a/src/core/hle/service/audio/audctl.cpp +++ b/src/core/hle/service/audio/audctl.cpp @@ -8,7 +8,7 @@ namespace Service::Audio { -AudCtl::AudCtl() : ServiceFramework{"audctl"} { +AudCtl::AudCtl(Core::System& system_) : ServiceFramework{system_, "audctl"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetTargetVolume"}, diff --git a/src/core/hle/service/audio/audctl.h b/src/core/hle/service/audio/audctl.h index c7fafc02e..15f6c77a0 100644 --- a/src/core/hle/service/audio/audctl.h +++ b/src/core/hle/service/audio/audctl.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Audio { class AudCtl final : public ServiceFramework { public: - explicit AudCtl(); + explicit AudCtl(Core::System& system_); ~AudCtl() override; private: diff --git a/src/core/hle/service/audio/auddbg.cpp b/src/core/hle/service/audio/auddbg.cpp index 8fff3e4b4..6264e4bda 100644 --- a/src/core/hle/service/audio/auddbg.cpp +++ b/src/core/hle/service/audio/auddbg.cpp @@ -6,7 +6,7 @@ namespace Service::Audio { -AudDbg::AudDbg(const char* name) : ServiceFramework{name} { +AudDbg::AudDbg(Core::System& system_, const char* name) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestSuspendForDebug"}, diff --git a/src/core/hle/service/audio/auddbg.h b/src/core/hle/service/audio/auddbg.h index 6689f4759..d1653eedd 100644 --- a/src/core/hle/service/audio/auddbg.h +++ b/src/core/hle/service/audio/auddbg.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Audio { class AudDbg final : public ServiceFramework { public: - explicit AudDbg(const char* name); + explicit AudDbg(Core::System& system_, const char* name); ~AudDbg() override; }; diff --git a/src/core/hle/service/audio/audin_a.cpp b/src/core/hle/service/audio/audin_a.cpp index ddd12f35e..79c3aa920 100644 --- a/src/core/hle/service/audio/audin_a.cpp +++ b/src/core/hle/service/audio/audin_a.cpp @@ -6,7 +6,7 @@ namespace Service::Audio { -AudInA::AudInA() : ServiceFramework{"audin:a"} { +AudInA::AudInA(Core::System& system_) : ServiceFramework{system_, "audin:a"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestSuspendAudioIns"}, diff --git a/src/core/hle/service/audio/audin_a.h b/src/core/hle/service/audio/audin_a.h index e7623bc29..15120a4b6 100644 --- a/src/core/hle/service/audio/audin_a.h +++ b/src/core/hle/service/audio/audin_a.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Audio { class AudInA final : public ServiceFramework { public: - explicit AudInA(); + explicit AudInA(Core::System& system_); ~AudInA() override; }; diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp index 3e2299426..26a6deddf 100644 --- a/src/core/hle/service/audio/audin_u.cpp +++ b/src/core/hle/service/audio/audin_u.cpp @@ -11,7 +11,7 @@ namespace Service::Audio { class IAudioIn final : public ServiceFramework { public: - IAudioIn() : ServiceFramework("IAudioIn") { + explicit IAudioIn(Core::System& system_) : ServiceFramework{system_, "IAudioIn"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetAudioInState"}, @@ -36,7 +36,7 @@ public: } }; -AudInU::AudInU() : ServiceFramework("audin:u") { +AudInU::AudInU(Core::System& system_) : ServiceFramework{system_, "audin:u"} { // clang-format off static const FunctionInfo functions[] = { {0, &AudInU::ListAudioIns, "ListAudioIns"}, @@ -96,7 +96,7 @@ void AudInU::OpenInOutImpl(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 6, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushRaw(params); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } void AudInU::OpenAudioIn(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/audio/audin_u.h b/src/core/hle/service/audio/audin_u.h index a599f4a64..0d75ae5ac 100644 --- a/src/core/hle/service/audio/audin_u.h +++ b/src/core/hle/service/audio/audin_u.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class HLERequestContext; } @@ -14,7 +18,7 @@ namespace Service::Audio { class AudInU final : public ServiceFramework { public: - explicit AudInU(); + explicit AudInU(Core::System& system_); ~AudInU() override; private: diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp index 1781bec83..b3f24f9bb 100644 --- a/src/core/hle/service/audio/audio.cpp +++ b/src/core/hle/service/audio/audio.cpp @@ -20,22 +20,22 @@ namespace Service::Audio { void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); std::make_shared(system)->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); std::make_shared(system)->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); - std::make_shared("audin:d")->InstallAsService(service_manager); - std::make_shared("audout:d")->InstallAsService(service_manager); - std::make_shared("audrec:d")->InstallAsService(service_manager); - std::make_shared("audren:d")->InstallAsService(service_manager); + std::make_shared(system, "audin:d")->InstallAsService(service_manager); + std::make_shared(system, "audout:d")->InstallAsService(service_manager); + std::make_shared(system, "audrec:d")->InstallAsService(service_manager); + std::make_shared(system, "audren:d")->InstallAsService(service_manager); } } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audout_a.cpp b/src/core/hle/service/audio/audout_a.cpp index 85febbca3..19825fd5d 100644 --- a/src/core/hle/service/audio/audout_a.cpp +++ b/src/core/hle/service/audio/audout_a.cpp @@ -6,7 +6,7 @@ namespace Service::Audio { -AudOutA::AudOutA() : ServiceFramework{"audout:a"} { +AudOutA::AudOutA(Core::System& system_) : ServiceFramework{system_, "audout:a"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestSuspendAudioOuts"}, diff --git a/src/core/hle/service/audio/audout_a.h b/src/core/hle/service/audio/audout_a.h index d65b66e8e..2043dfb77 100644 --- a/src/core/hle/service/audio/audout_a.h +++ b/src/core/hle/service/audio/audout_a.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Audio { class AudOutA final : public ServiceFramework { public: - explicit AudOutA(); + explicit AudOutA(Core::System& system_); ~AudOutA() override; }; diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index 9b4910e53..0cd797109 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -40,11 +40,11 @@ enum class AudioState : u32 { class IAudioOut final : public ServiceFramework { public: - IAudioOut(Core::System& system, AudoutParams audio_params, AudioCore::AudioOut& audio_core, - std::string&& device_name, std::string&& unique_name) - : ServiceFramework("IAudioOut"), audio_core(audio_core), - device_name(std::move(device_name)), - audio_params(audio_params), main_memory{system.Memory()} { + IAudioOut(Core::System& system_, AudoutParams audio_params_, AudioCore::AudioOut& audio_core_, + std::string&& device_name_, std::string&& unique_name) + : ServiceFramework{system_, "IAudioOut"}, audio_core{audio_core_}, + device_name{std::move(device_name_)}, audio_params{audio_params_}, main_memory{ + system.Memory()} { // clang-format off static const FunctionInfo functions[] = { {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"}, @@ -70,8 +70,10 @@ public: Kernel::WritableEvent::CreateEventPair(system.Kernel(), "IAudioOutBufferReleased"); stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate, - audio_params.channel_count, std::move(unique_name), - [this] { buffer_event.writable->Signal(); }); + audio_params.channel_count, std::move(unique_name), [this] { + const auto guard = LockService(); + buffer_event.writable->Signal(); + }); } private: @@ -213,7 +215,7 @@ private: Core::Memory::Memory& main_memory; }; -AudOutU::AudOutU(Core::System& system_) : ServiceFramework("audout:u"), system{system_} { +AudOutU::AudOutU(Core::System& system_) : ServiceFramework{system_, "audout:u"} { // clang-format off static const FunctionInfo functions[] = { {0, &AudOutU::ListAudioOutsImpl, "ListAudioOuts"}, diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h index c9f532ccd..f7ae2f2bf 100644 --- a/src/core/hle/service/audio/audout_u.h +++ b/src/core/hle/service/audio/audout_u.h @@ -34,8 +34,6 @@ private: std::vector> audio_out_interfaces; std::unique_ptr audio_core; - - Core::System& system; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audrec_a.cpp b/src/core/hle/service/audio/audrec_a.cpp index ce1bfb48d..c5ab7cad4 100644 --- a/src/core/hle/service/audio/audrec_a.cpp +++ b/src/core/hle/service/audio/audrec_a.cpp @@ -6,7 +6,7 @@ namespace Service::Audio { -AudRecA::AudRecA() : ServiceFramework{"audrec:a"} { +AudRecA::AudRecA(Core::System& system_) : ServiceFramework{system_, "audrec:a"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestSuspendFinalOutputRecorders"}, diff --git a/src/core/hle/service/audio/audrec_a.h b/src/core/hle/service/audio/audrec_a.h index 384d24c69..2cce90b1d 100644 --- a/src/core/hle/service/audio/audrec_a.h +++ b/src/core/hle/service/audio/audrec_a.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Audio { class AudRecA final : public ServiceFramework { public: - explicit AudRecA(); + explicit AudRecA(Core::System& system_); ~AudRecA() override; }; diff --git a/src/core/hle/service/audio/audrec_u.cpp b/src/core/hle/service/audio/audrec_u.cpp index 1a5aed9ed..eb5c63c62 100644 --- a/src/core/hle/service/audio/audrec_u.cpp +++ b/src/core/hle/service/audio/audrec_u.cpp @@ -8,7 +8,8 @@ namespace Service::Audio { class IFinalOutputRecorder final : public ServiceFramework { public: - IFinalOutputRecorder() : ServiceFramework("IFinalOutputRecorder") { + explicit IFinalOutputRecorder(Core::System& system_) + : ServiceFramework{system_, "IFinalOutputRecorder"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetFinalOutputRecorderState"}, @@ -29,7 +30,7 @@ public: } }; -AudRecU::AudRecU() : ServiceFramework("audrec:u") { +AudRecU::AudRecU(Core::System& system_) : ServiceFramework{system_, "audrec:u"} { static const FunctionInfo functions[] = { {0, nullptr, "OpenFinalOutputRecorder"}, }; diff --git a/src/core/hle/service/audio/audrec_u.h b/src/core/hle/service/audio/audrec_u.h index ca3d638e8..f79d49e5c 100644 --- a/src/core/hle/service/audio/audrec_u.h +++ b/src/core/hle/service/audio/audrec_u.h @@ -6,15 +6,15 @@ #include "core/hle/service/service.h" -namespace Kernel { -class HLERequestContext; +namespace Core { +class System; } namespace Service::Audio { class AudRecU final : public ServiceFramework { public: - explicit AudRecU(); + explicit AudRecU(Core::System& system_); ~AudRecU() override; }; diff --git a/src/core/hle/service/audio/audren_a.cpp b/src/core/hle/service/audio/audren_a.cpp index edb66d985..5e9f866f0 100644 --- a/src/core/hle/service/audio/audren_a.cpp +++ b/src/core/hle/service/audio/audren_a.cpp @@ -6,7 +6,7 @@ namespace Service::Audio { -AudRenA::AudRenA() : ServiceFramework{"audren:a"} { +AudRenA::AudRenA(Core::System& system_) : ServiceFramework{system_, "audren:a"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestSuspendAudioRenderers"}, diff --git a/src/core/hle/service/audio/audren_a.h b/src/core/hle/service/audio/audren_a.h index 81fef0ffe..5d0a626ad 100644 --- a/src/core/hle/service/audio/audren_a.h +++ b/src/core/hle/service/audio/audren_a.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Audio { class AudRenA final : public ServiceFramework { public: - explicit AudRenA(); + explicit AudRenA(Core::System& system_); ~AudRenA() override; }; diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index a2d3ded7b..c5c22d053 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -28,7 +28,7 @@ class IAudioRenderer final : public ServiceFramework { public: explicit IAudioRenderer(Core::System& system, AudioCommon::AudioRendererParameter audren_params, const std::size_t instance_number) - : ServiceFramework("IAudioRenderer") { + : ServiceFramework{system, "IAudioRenderer"} { // clang-format off static const FunctionInfo functions[] = { {0, &IAudioRenderer::GetSampleRate, "GetSampleRate"}, @@ -49,16 +49,16 @@ public: system_event = Kernel::WritableEvent::CreateEventPair(system.Kernel(), "IAudioRenderer:SystemEvent"); - renderer = std::make_unique(system.CoreTiming(), system.Memory(), - audren_params, system_event.writable, - instance_number); + renderer = std::make_unique( + system.CoreTiming(), system.Memory(), audren_params, + [this]() { + const auto guard = LockService(); + system_event.writable->Signal(); + }, + instance_number); } private: - void UpdateAudioCallback() { - system_event.writable->Signal(); - } - void GetSampleRate(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); @@ -167,8 +167,8 @@ private: class IAudioDevice final : public ServiceFramework { public: - explicit IAudioDevice(Core::System& system, u32_le revision_num) - : ServiceFramework("IAudioDevice"), revision{revision_num} { + explicit IAudioDevice(Core::System& system_, u32_le revision_num) + : ServiceFramework{system_, "IAudioDevice"}, revision{revision_num} { static const FunctionInfo functions[] = { {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, @@ -325,7 +325,7 @@ private: }; // namespace Audio -AudRenU::AudRenU(Core::System& system_) : ServiceFramework("audren:u"), system{system_} { +AudRenU::AudRenU(Core::System& system_) : ServiceFramework{system_, "audren:u"} { // clang-format off static const FunctionInfo functions[] = { {0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"}, diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h index 4e0ccc792..d693dc406 100644 --- a/src/core/hle/service/audio/audren_u.h +++ b/src/core/hle/service/audio/audren_u.h @@ -31,7 +31,6 @@ private: void OpenAudioRendererImpl(Kernel::HLERequestContext& ctx); std::size_t audren_instance_count = 0; - Core::System& system; }; // Describes a particular audio feature that may be supported in a particular revision. diff --git a/src/core/hle/service/audio/codecctl.cpp b/src/core/hle/service/audio/codecctl.cpp index c6864146d..94afec1b6 100644 --- a/src/core/hle/service/audio/codecctl.cpp +++ b/src/core/hle/service/audio/codecctl.cpp @@ -2,14 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/logging/log.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/audio/codecctl.h" namespace Service::Audio { -CodecCtl::CodecCtl() : ServiceFramework("codecctl") { +CodecCtl::CodecCtl(Core::System& system_) : ServiceFramework{system_, "codecctl"} { static const FunctionInfo functions[] = { {0, nullptr, "InitializeCodecController"}, {1, nullptr, "FinalizeCodecController"}, diff --git a/src/core/hle/service/audio/codecctl.h b/src/core/hle/service/audio/codecctl.h index 2fe75b6e2..58e53259e 100644 --- a/src/core/hle/service/audio/codecctl.h +++ b/src/core/hle/service/audio/codecctl.h @@ -6,15 +6,15 @@ #include "core/hle/service/service.h" -namespace Kernel { -class HLERequestContext; +namespace Core { +class System; } namespace Service::Audio { class CodecCtl final : public ServiceFramework { public: - explicit CodecCtl(); + explicit CodecCtl(Core::System& system_); ~CodecCtl() override; }; diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index f1d81602c..ea3414fd2 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -160,8 +160,9 @@ private: class IHardwareOpusDecoderManager final : public ServiceFramework { public: - explicit IHardwareOpusDecoderManager(OpusDecoderState decoder_state) - : ServiceFramework("IHardwareOpusDecoderManager"), decoder_state{std::move(decoder_state)} { + explicit IHardwareOpusDecoderManager(Core::System& system_, OpusDecoderState decoder_state) + : ServiceFramework{system_, "IHardwareOpusDecoderManager"}, decoder_state{ + std::move(decoder_state)} { // clang-format off static const FunctionInfo functions[] = { {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"}, @@ -287,10 +288,10 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface( - OpusDecoderState{std::move(decoder), sample_rate, channel_count}); + system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); } -HwOpus::HwOpus() : ServiceFramework("hwopus") { +HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} { static const FunctionInfo functions[] = { {0, &HwOpus::OpenOpusDecoder, "OpenOpusDecoder"}, {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"}, diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h index 602ede8ba..4f921f18e 100644 --- a/src/core/hle/service/audio/hwopus.h +++ b/src/core/hle/service/audio/hwopus.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Audio { class HwOpus final : public ServiceFramework { public: - explicit HwOpus(); + explicit HwOpus(Core::System& system_); ~HwOpus() override; private: diff --git a/src/core/hle/service/bcat/backend/backend.cpp b/src/core/hle/service/bcat/backend/backend.cpp index def3410cc..174388445 100644 --- a/src/core/hle/service/bcat/backend/backend.cpp +++ b/src/core/hle/service/bcat/backend/backend.cpp @@ -84,7 +84,7 @@ void ProgressServiceBackend::FinishDownload(ResultCode result) { void ProgressServiceBackend::SignalUpdate() const { if (need_hle_lock) { - std::lock_guard lock(HLE::g_hle_lock); + std::lock_guard lock(HLE::g_hle_lock); event.writable->Signal(); } else { event.writable->Signal(); diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp index ca021a99f..e43f3f47f 100644 --- a/src/core/hle/service/bcat/backend/boxcat.cpp +++ b/src/core/hle/service/bcat/backend/boxcat.cpp @@ -196,7 +196,9 @@ private: const std::string& content_type_name) { if (client == nullptr) { client = std::make_unique(BOXCAT_HOSTNAME, PORT); - client->set_timeout_sec(timeout_seconds); + client->set_connection_timeout(timeout_seconds); + client->set_read_timeout(timeout_seconds); + client->set_write_timeout(timeout_seconds); } httplib::Headers headers{ @@ -255,7 +257,7 @@ private: return out; } - std::unique_ptr client; + std::unique_ptr client; std::string path; u64 title_id; u64 build_id; @@ -443,13 +445,25 @@ std::optional> Boxcat::GetLaunchParameter(TitleIDVersion title) Boxcat::StatusResult Boxcat::GetStatus(std::optional& global, std::map& games) { httplib::SSLClient client{BOXCAT_HOSTNAME, static_cast(PORT)}; - client.set_timeout_sec(static_cast(TIMEOUT_SECONDS)); + client.set_connection_timeout(static_cast(TIMEOUT_SECONDS)); + client.set_read_timeout(static_cast(TIMEOUT_SECONDS)); + client.set_write_timeout(static_cast(TIMEOUT_SECONDS)); httplib::Headers headers{ {std::string("Game-Assets-API-Version"), std::string(BOXCAT_API_VERSION)}, {std::string("Boxcat-Client-Type"), std::string(BOXCAT_CLIENT_TYPE)}, }; + if (!client.is_valid()) { + LOG_ERROR(Service_BCAT, "Client is invalid, going offline!"); + return StatusResult::Offline; + } + + if (!client.is_socket_open()) { + LOG_ERROR(Service_BCAT, "Failed to open socket, going offline!"); + return StatusResult::Offline; + } + const auto response = client.Get(BOXCAT_PATHNAME_EVENTS, headers); if (response == nullptr) return StatusResult::Offline; @@ -469,7 +483,7 @@ Boxcat::StatusResult Boxcat::GetStatus(std::optional& global, global = json["global"].get(); if (json["games"].is_array()) { - for (const auto object : json["games"]) { + for (const auto& object : json["games"]) { if (object.is_object() && object.find("name") != object.end()) { EventStatus detail{}; if (object["header"].is_string()) { diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp index db0e06ca1..b8696a395 100644 --- a/src/core/hle/service/bcat/module.cpp +++ b/src/core/hle/service/bcat/module.cpp @@ -8,6 +8,7 @@ #include "common/hex_util.h" #include "common/logging/log.h" #include "common/string_util.h" +#include "core/core.h" #include "core/file_sys/vfs.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/process.h" @@ -87,9 +88,11 @@ struct DeliveryCacheDirectoryEntry { class IDeliveryCacheProgressService final : public ServiceFramework { public: - IDeliveryCacheProgressService(std::shared_ptr event, - const DeliveryCacheProgressImpl& impl) - : ServiceFramework{"IDeliveryCacheProgressService"}, event(std::move(event)), impl(impl) { + explicit IDeliveryCacheProgressService(Core::System& system_, + std::shared_ptr event_, + const DeliveryCacheProgressImpl& impl_) + : ServiceFramework{system_, "IDeliveryCacheProgressService"}, event{std::move(event_)}, + impl{impl_} { // clang-format off static const FunctionInfo functions[] = { {0, &IDeliveryCacheProgressService::GetEvent, "GetEvent"}, @@ -125,7 +128,7 @@ private: class IBcatService final : public ServiceFramework { public: explicit IBcatService(Core::System& system_, Backend& backend_) - : ServiceFramework("IBcatService"), system{system_}, backend{backend_}, + : ServiceFramework{system_, "IBcatService"}, backend{backend_}, progress{{ ProgressServiceBackend{system_.Kernel(), "Normal"}, ProgressServiceBackend{system_.Kernel(), "Directory"}, @@ -170,7 +173,7 @@ private: std::shared_ptr CreateProgressService(SyncType type) { auto& backend{progress.at(static_cast(type))}; - return std::make_shared(backend.GetEvent(), + return std::make_shared(system, backend.GetEvent(), backend.GetImpl()); } @@ -260,7 +263,6 @@ private: rb.Push(RESULT_SUCCESS); } - Core::System& system; Backend& backend; std::array(SyncType::Count)> progress; @@ -276,8 +278,8 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) { class IDeliveryCacheFileService final : public ServiceFramework { public: - IDeliveryCacheFileService(FileSys::VirtualDir root_) - : ServiceFramework{"IDeliveryCacheFileService"}, root(std::move(root_)) { + explicit IDeliveryCacheFileService(Core::System& system_, FileSys::VirtualDir root_) + : ServiceFramework{system_, "IDeliveryCacheFileService"}, root(std::move(root_)) { // clang-format off static const FunctionInfo functions[] = { {0, &IDeliveryCacheFileService::Open, "Open"}, @@ -393,8 +395,8 @@ private: class IDeliveryCacheDirectoryService final : public ServiceFramework { public: - IDeliveryCacheDirectoryService(FileSys::VirtualDir root_) - : ServiceFramework{"IDeliveryCacheDirectoryService"}, root(std::move(root_)) { + explicit IDeliveryCacheDirectoryService(Core::System& system_, FileSys::VirtualDir root_) + : ServiceFramework{system_, "IDeliveryCacheDirectoryService"}, root(std::move(root_)) { // clang-format off static const FunctionInfo functions[] = { {0, &IDeliveryCacheDirectoryService::Open, "Open"}, @@ -491,8 +493,8 @@ private: class IDeliveryCacheStorageService final : public ServiceFramework { public: - IDeliveryCacheStorageService(FileSys::VirtualDir root_) - : ServiceFramework{"IDeliveryCacheStorageService"}, root(std::move(root_)) { + explicit IDeliveryCacheStorageService(Core::System& system_, FileSys::VirtualDir root_) + : ServiceFramework{system_, "IDeliveryCacheStorageService"}, root(std::move(root_)) { // clang-format off static const FunctionInfo functions[] = { {0, &IDeliveryCacheStorageService::CreateFileService, "CreateFileService"}, @@ -517,7 +519,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(root); + rb.PushIpcInterface(system, root); } void CreateDirectoryService(Kernel::HLERequestContext& ctx) { @@ -525,7 +527,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(root); + rb.PushIpcInterface(system, root); } void EnumerateDeliveryCacheDirectory(Kernel::HLERequestContext& ctx) { @@ -550,10 +552,10 @@ private: void Module::Interface::CreateDeliveryCacheStorageService(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_BCAT, "called"); + const auto title_id = system.CurrentProcess()->GetTitleID(); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface( - fsc.GetBCATDirectory(system.CurrentProcess()->GetTitleID())); + rb.PushIpcInterface(system, fsc.GetBCATDirectory(title_id)); } void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId( @@ -565,7 +567,7 @@ void Module::Interface::CreateDeliveryCacheStorageServiceWithApplicationId( IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(fsc.GetBCATDirectory(title_id)); + rb.PushIpcInterface(system, fsc.GetBCATDirectory(title_id)); } std::unique_ptr CreateBackendFromSettings([[maybe_unused]] Core::System& system, @@ -581,10 +583,9 @@ std::unique_ptr CreateBackendFromSettings([[maybe_unused]] Core::System Module::Interface::Interface(Core::System& system_, std::shared_ptr module_, FileSystem::FileSystemController& fsc_, const char* name) - : ServiceFramework(name), fsc{fsc_}, module{std::move(module_)}, + : ServiceFramework{system_, name}, fsc{fsc_}, module{std::move(module_)}, backend{CreateBackendFromSettings(system_, - [&fsc_](u64 tid) { return fsc_.GetBCATDirectory(tid); })}, - system{system_} {} + [&fsc_](u64 tid) { return fsc_.GetBCATDirectory(tid); })} {} Module::Interface::~Interface() = default; diff --git a/src/core/hle/service/bcat/module.h b/src/core/hle/service/bcat/module.h index e4ba23ba0..738731c06 100644 --- a/src/core/hle/service/bcat/module.h +++ b/src/core/hle/service/bcat/module.h @@ -37,9 +37,6 @@ public: std::shared_ptr module; std::unique_ptr backend; - - private: - Core::System& system; }; }; diff --git a/src/core/hle/service/bpc/bpc.cpp b/src/core/hle/service/bpc/bpc.cpp index fac6b2f9c..e4630320e 100644 --- a/src/core/hle/service/bpc/bpc.cpp +++ b/src/core/hle/service/bpc/bpc.cpp @@ -12,7 +12,7 @@ namespace Service::BPC { class BPC final : public ServiceFramework { public: - explicit BPC() : ServiceFramework{"bpc"} { + explicit BPC(Core::System& system_) : ServiceFramework{system_, "bpc"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "ShutdownSystem"}, @@ -40,7 +40,7 @@ public: class BPC_R final : public ServiceFramework { public: - explicit BPC_R() : ServiceFramework{"bpc:r"} { + explicit BPC_R(Core::System& system_) : ServiceFramework{system_, "bpc:r"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetRtcTime"}, @@ -55,9 +55,9 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); } } // namespace Service::BPC diff --git a/src/core/hle/service/bpc/bpc.h b/src/core/hle/service/bpc/bpc.h index eaa37be8d..6ec25aa9b 100644 --- a/src/core/hle/service/bpc/bpc.h +++ b/src/core/hle/service/bpc/bpc.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::BPC { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::BPC diff --git a/src/core/hle/service/btdrv/btdrv.cpp b/src/core/hle/service/btdrv/btdrv.cpp index f311afa2f..2de86f1f1 100644 --- a/src/core/hle/service/btdrv/btdrv.cpp +++ b/src/core/hle/service/btdrv/btdrv.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/kernel.h" @@ -16,7 +17,7 @@ namespace Service::BtDrv { class Bt final : public ServiceFramework { public: - explicit Bt(Core::System& system) : ServiceFramework{"bt"} { + explicit Bt(Core::System& system_) : ServiceFramework{system_, "bt"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "LeClientReadCharacteristic"}, @@ -51,7 +52,7 @@ private: class BtDrv final : public ServiceFramework { public: - explicit BtDrv() : ServiceFramework{"btdrv"} { + explicit BtDrv(Core::System& system_) : ServiceFramework{system_, "btdrv"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "InitializeBluetoothDriver"}, @@ -165,7 +166,7 @@ public: }; void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { - std::make_shared()->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); std::make_shared(system)->InstallAsService(sm); } diff --git a/src/core/hle/service/btm/btm.cpp b/src/core/hle/service/btm/btm.cpp index 0d251c6d0..38b55300e 100644 --- a/src/core/hle/service/btm/btm.cpp +++ b/src/core/hle/service/btm/btm.cpp @@ -5,6 +5,7 @@ #include #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/kernel.h" @@ -17,7 +18,7 @@ namespace Service::BTM { class IBtmUserCore final : public ServiceFramework { public: - explicit IBtmUserCore(Core::System& system) : ServiceFramework{"IBtmUserCore"} { + explicit IBtmUserCore(Core::System& system_) : ServiceFramework{system_, "IBtmUserCore"} { // clang-format off static const FunctionInfo functions[] = { {0, &IBtmUserCore::AcquireBleScanEvent, "AcquireBleScanEvent"}, @@ -106,7 +107,7 @@ private: class BTM_USR final : public ServiceFramework { public: - explicit BTM_USR(Core::System& system) : ServiceFramework{"btm:u"}, system(system) { + explicit BTM_USR(Core::System& system_) : ServiceFramework{system_, "btm:u"} { // clang-format off static const FunctionInfo functions[] = { {0, &BTM_USR::GetCore, "GetCore"}, @@ -123,13 +124,11 @@ private: rb.Push(RESULT_SUCCESS); rb.PushIpcInterface(system); } - - Core::System& system; }; class BTM final : public ServiceFramework { public: - explicit BTM() : ServiceFramework{"btm"} { + explicit BTM(Core::System& system_) : ServiceFramework{system_, "btm"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetState"}, @@ -206,7 +205,7 @@ public: class BTM_DBG final : public ServiceFramework { public: - explicit BTM_DBG() : ServiceFramework{"btm:dbg"} { + explicit BTM_DBG(Core::System& system_) : ServiceFramework{system_, "btm:dbg"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "AcquireDiscoveryEvent"}, @@ -231,7 +230,7 @@ public: class IBtmSystemCore final : public ServiceFramework { public: - explicit IBtmSystemCore() : ServiceFramework{"IBtmSystemCore"} { + explicit IBtmSystemCore(Core::System& system_) : ServiceFramework{system_, "IBtmSystemCore"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "StartGamepadPairing"}, @@ -253,7 +252,7 @@ public: class BTM_SYS final : public ServiceFramework { public: - explicit BTM_SYS() : ServiceFramework{"btm:sys"} { + explicit BTM_SYS(Core::System& system_) : ServiceFramework{system_, "btm:sys"} { // clang-format off static const FunctionInfo functions[] = { {0, &BTM_SYS::GetCore, "GetCore"}, @@ -269,14 +268,14 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } }; void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); std::make_shared(system)->InstallAsService(sm); } diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp index ba5749b84..5b7fe8e9b 100644 --- a/src/core/hle/service/caps/caps.cpp +++ b/src/core/hle/service/caps/caps.cpp @@ -13,13 +13,13 @@ namespace Service::Capture { -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); } } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps.h b/src/core/hle/service/caps/caps.h index b8c67b6e2..3c4290c88 100644 --- a/src/core/hle/service/caps/caps.h +++ b/src/core/hle/service/caps/caps.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } @@ -87,6 +91,6 @@ static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30, "ApplicationAlbumFileEntry has incorrect size."); /// Registers all Capture services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp index a0a3b2ae3..1fe4f0e14 100644 --- a/src/core/hle/service/caps/caps_a.cpp +++ b/src/core/hle/service/caps/caps_a.cpp @@ -8,7 +8,8 @@ namespace Service::Capture { class IAlbumAccessorSession final : public ServiceFramework { public: - explicit IAlbumAccessorSession() : ServiceFramework{"IAlbumAccessorSession"} { + explicit IAlbumAccessorSession(Core::System& system_) + : ServiceFramework{system_, "IAlbumAccessorSession"} { // clang-format off static const FunctionInfo functions[] = { {2001, nullptr, "OpenAlbumMovieReadStream"}, @@ -26,7 +27,7 @@ public: } }; -CAPS_A::CAPS_A() : ServiceFramework("caps:a") { +CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetAlbumFileCount"}, diff --git a/src/core/hle/service/caps/caps_a.h b/src/core/hle/service/caps/caps_a.h index cb93aad5b..389cc6dbe 100644 --- a/src/core/hle/service/caps/caps_a.h +++ b/src/core/hle/service/caps/caps_a.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class HLERequestContext; } @@ -14,7 +18,7 @@ namespace Service::Capture { class CAPS_A final : public ServiceFramework { public: - explicit CAPS_A(); + explicit CAPS_A(Core::System& system_); ~CAPS_A() override; }; diff --git a/src/core/hle/service/caps/caps_c.cpp b/src/core/hle/service/caps/caps_c.cpp index ab17a187e..45c1c9d30 100644 --- a/src/core/hle/service/caps/caps_c.cpp +++ b/src/core/hle/service/caps/caps_c.cpp @@ -2,13 +2,16 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" #include "core/hle/service/caps/caps_c.h" namespace Service::Capture { class IAlbumControlSession final : public ServiceFramework { public: - explicit IAlbumControlSession() : ServiceFramework{"IAlbumControlSession"} { + explicit IAlbumControlSession(Core::System& system_) + : ServiceFramework{system_, "IAlbumControlSession"} { // clang-format off static const FunctionInfo functions[] = { {2001, nullptr, "OpenAlbumMovieReadStream"}, @@ -42,12 +45,12 @@ public: } }; -CAPS_C::CAPS_C() : ServiceFramework("caps:c") { +CAPS_C::CAPS_C(Core::System& system_) : ServiceFramework{system_, "caps:c"} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "CaptureRawImage"}, {2, nullptr, "CaptureRawImageWithTimeout"}, - {33, nullptr, "Unknown33"}, + {33, &CAPS_C::SetShimLibraryVersion, "SetShimLibraryVersion"}, {1001, nullptr, "RequestTakingScreenShot"}, {1002, nullptr, "RequestTakingScreenShotWithTimeout"}, {1011, nullptr, "NotifyTakingScreenShotRefused"}, @@ -72,4 +75,16 @@ CAPS_C::CAPS_C() : ServiceFramework("caps:c") { CAPS_C::~CAPS_C() = default; +void CAPS_C::SetShimLibraryVersion(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto library_version{rp.Pop()}; + const auto applet_resource_user_id{rp.Pop()}; + + LOG_WARNING(Service_Capture, "(STUBBED) called. library_version={}, applet_resource_user_id={}", + library_version, applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps_c.h b/src/core/hle/service/caps/caps_c.h index a9d028689..c6d1dfdce 100644 --- a/src/core/hle/service/caps/caps_c.h +++ b/src/core/hle/service/caps/caps_c.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class HLERequestContext; } @@ -14,8 +18,11 @@ namespace Service::Capture { class CAPS_C final : public ServiceFramework { public: - explicit CAPS_C(); + explicit CAPS_C(Core::System& system_); ~CAPS_C() override; + +private: + void SetShimLibraryVersion(Kernel::HLERequestContext& ctx); }; } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps_sc.cpp b/src/core/hle/service/caps/caps_sc.cpp index 822ee96c8..d91e18e80 100644 --- a/src/core/hle/service/caps/caps_sc.cpp +++ b/src/core/hle/service/caps/caps_sc.cpp @@ -6,7 +6,7 @@ namespace Service::Capture { -CAPS_SC::CAPS_SC() : ServiceFramework("caps:sc") { +CAPS_SC::CAPS_SC(Core::System& system_) : ServiceFramework{system_, "caps:sc"} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "CaptureRawImage"}, diff --git a/src/core/hle/service/caps/caps_sc.h b/src/core/hle/service/caps/caps_sc.h index ac3e929ca..e79a33ee5 100644 --- a/src/core/hle/service/caps/caps_sc.h +++ b/src/core/hle/service/caps/caps_sc.h @@ -6,15 +6,15 @@ #include "core/hle/service/service.h" -namespace Kernel { -class HLERequestContext; +namespace Core { +class System; } namespace Service::Capture { class CAPS_SC final : public ServiceFramework { public: - explicit CAPS_SC(); + explicit CAPS_SC(Core::System& system_); ~CAPS_SC() override; }; diff --git a/src/core/hle/service/caps/caps_ss.cpp b/src/core/hle/service/caps/caps_ss.cpp index 24dc716e7..2b5314691 100644 --- a/src/core/hle/service/caps/caps_ss.cpp +++ b/src/core/hle/service/caps/caps_ss.cpp @@ -6,7 +6,7 @@ namespace Service::Capture { -CAPS_SS::CAPS_SS() : ServiceFramework("caps:ss") { +CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} { // clang-format off static const FunctionInfo functions[] = { {201, nullptr, "SaveScreenShot"}, diff --git a/src/core/hle/service/caps/caps_ss.h b/src/core/hle/service/caps/caps_ss.h index 450686e4f..1816f7885 100644 --- a/src/core/hle/service/caps/caps_ss.h +++ b/src/core/hle/service/caps/caps_ss.h @@ -6,15 +6,15 @@ #include "core/hle/service/service.h" -namespace Kernel { -class HLERequestContext; +namespace Core { +class System; } namespace Service::Capture { class CAPS_SS final : public ServiceFramework { public: - explicit CAPS_SS(); + explicit CAPS_SS(Core::System& system_); ~CAPS_SS() override; }; diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp index fffb2ecf9..eae39eb7b 100644 --- a/src/core/hle/service/caps/caps_su.cpp +++ b/src/core/hle/service/caps/caps_su.cpp @@ -8,7 +8,7 @@ namespace Service::Capture { -CAPS_SU::CAPS_SU() : ServiceFramework("caps:su") { +CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} { // clang-format off static const FunctionInfo functions[] = { {32, &CAPS_SU::SetShimLibraryVersion, "SetShimLibraryVersion"}, @@ -25,7 +25,12 @@ CAPS_SU::CAPS_SU() : ServiceFramework("caps:su") { CAPS_SU::~CAPS_SU() = default; void CAPS_SU::SetShimLibraryVersion(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Capture, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + const auto library_version{rp.Pop()}; + const auto applet_resource_user_id{rp.Pop()}; + + LOG_WARNING(Service_Capture, "(STUBBED) called. library_version={}, applet_resource_user_id={}", + library_version, applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); diff --git a/src/core/hle/service/caps/caps_su.h b/src/core/hle/service/caps/caps_su.h index 62c9603a9..b366fdb13 100644 --- a/src/core/hle/service/caps/caps_su.h +++ b/src/core/hle/service/caps/caps_su.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class HLERequestContext; } @@ -14,7 +18,7 @@ namespace Service::Capture { class CAPS_SU final : public ServiceFramework { public: - explicit CAPS_SU(); + explicit CAPS_SU(Core::System& system_); ~CAPS_SU() override; private: diff --git a/src/core/hle/service/caps/caps_u.cpp b/src/core/hle/service/caps/caps_u.cpp index f36d8de2d..842316a2e 100644 --- a/src/core/hle/service/caps/caps_u.cpp +++ b/src/core/hle/service/caps/caps_u.cpp @@ -12,8 +12,8 @@ namespace Service::Capture { class IAlbumAccessorApplicationSession final : public ServiceFramework { public: - explicit IAlbumAccessorApplicationSession() - : ServiceFramework{"IAlbumAccessorApplicationSession"} { + explicit IAlbumAccessorApplicationSession(Core::System& system_) + : ServiceFramework{system_, "IAlbumAccessorApplicationSession"} { // clang-format off static const FunctionInfo functions[] = { {2001, nullptr, "OpenAlbumMovieReadStream"}, @@ -28,11 +28,10 @@ public: } }; -CAPS_U::CAPS_U() : ServiceFramework("caps:u") { +CAPS_U::CAPS_U(Core::System& system_) : ServiceFramework{system_, "caps:u"} { // clang-format off static const FunctionInfo functions[] = { - {31, nullptr, "GetShimLibraryVersion"}, - {32, nullptr, "SetShimLibraryVersion"}, + {32, &CAPS_U::SetShimLibraryVersion, "SetShimLibraryVersion"}, {102, &CAPS_U::GetAlbumContentsFileListForApplication, "GetAlbumContentsFileListForApplication"}, {103, nullptr, "DeleteAlbumContentsFileForApplication"}, {104, nullptr, "GetAlbumContentsFileSizeForApplication"}, @@ -42,7 +41,7 @@ CAPS_U::CAPS_U() : ServiceFramework("caps:u") { {130, nullptr, "PrecheckToCreateContentsForApplication"}, {140, nullptr, "GetAlbumFileList1AafeAruidDeprecated"}, {141, nullptr, "GetAlbumFileList2AafeUidAruidDeprecated"}, - {142, nullptr, "GetAlbumFileList3AaeAruid"}, + {142, &CAPS_U::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"}, {143, nullptr, "GetAlbumFileList4AaeUidAruid"}, {60002, nullptr, "OpenAccessorSessionForApplication"}, }; @@ -53,6 +52,18 @@ CAPS_U::CAPS_U() : ServiceFramework("caps:u") { CAPS_U::~CAPS_U() = default; +void CAPS_U::SetShimLibraryVersion(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto library_version{rp.Pop()}; + const auto applet_resource_user_id{rp.Pop()}; + + LOG_WARNING(Service_Capture, "(STUBBED) called. library_version={}, applet_resource_user_id={}", + library_version, applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + void CAPS_U::GetAlbumContentsFileListForApplication(Kernel::HLERequestContext& ctx) { // Takes a type-0x6 output buffer containing an array of ApplicationAlbumFileEntry, a PID, an // u8 ContentType, two s64s, and an u64 AppletResourceUserId. Returns an output u64 for total @@ -66,17 +77,24 @@ void CAPS_U::GetAlbumContentsFileListForApplication(Kernel::HLERequestContext& c // TODO: Update this when we implement the album. // Currently we do not have a method of accessing album entries, set this to 0 for now. - constexpr s32 total_entries{0}; + constexpr u32 total_entries_1{}; + constexpr u32 total_entries_2{}; - LOG_WARNING(Service_Capture, - "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, " - "end_posix_time={}, applet_resource_user_id={}, total_entries={}", - pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id, - total_entries); + LOG_WARNING( + Service_Capture, + "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, " + "end_posix_time={}, applet_resource_user_id={}, total_entries_1={}, total_entries_2={}", + pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id, + total_entries_1, total_entries_2); - IPC::ResponseBuilder rb{ctx, 3}; + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push(total_entries); + rb.Push(total_entries_1); + rb.Push(total_entries_2); +} + +void CAPS_U::GetAlbumFileList3AaeAruid(Kernel::HLERequestContext& ctx) { + GetAlbumContentsFileListForApplication(ctx); } } // namespace Service::Capture diff --git a/src/core/hle/service/caps/caps_u.h b/src/core/hle/service/caps/caps_u.h index 689364de4..e7e0d8775 100644 --- a/src/core/hle/service/caps/caps_u.h +++ b/src/core/hle/service/caps/caps_u.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class HLERequestContext; } @@ -14,11 +18,13 @@ namespace Service::Capture { class CAPS_U final : public ServiceFramework { public: - explicit CAPS_U(); + explicit CAPS_U(Core::System& system_); ~CAPS_U() override; private: + void SetShimLibraryVersion(Kernel::HLERequestContext& ctx); void GetAlbumContentsFileListForApplication(Kernel::HLERequestContext& ctx); + void GetAlbumFileList3AaeAruid(Kernel::HLERequestContext& ctx); }; } // namespace Service::Capture diff --git a/src/core/hle/service/erpt/erpt.cpp b/src/core/hle/service/erpt/erpt.cpp index 4ec8c3093..4924c61c3 100644 --- a/src/core/hle/service/erpt/erpt.cpp +++ b/src/core/hle/service/erpt/erpt.cpp @@ -12,7 +12,7 @@ namespace Service::ERPT { class ErrorReportContext final : public ServiceFramework { public: - explicit ErrorReportContext() : ServiceFramework{"erpt:c"} { + explicit ErrorReportContext(Core::System& system_) : ServiceFramework{system_, "erpt:c"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SubmitContext"}, @@ -35,7 +35,7 @@ public: class ErrorReportSession final : public ServiceFramework { public: - explicit ErrorReportSession() : ServiceFramework{"erpt:r"} { + explicit ErrorReportSession(Core::System& system_) : ServiceFramework{system_, "erpt:r"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "OpenReport"}, @@ -48,9 +48,9 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); } } // namespace Service::ERPT diff --git a/src/core/hle/service/erpt/erpt.h b/src/core/hle/service/erpt/erpt.h index de439ab6d..8cd5c081f 100644 --- a/src/core/hle/service/erpt/erpt.h +++ b/src/core/hle/service/erpt/erpt.h @@ -4,6 +4,10 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } @@ -11,6 +15,6 @@ class ServiceManager; namespace Service::ERPT { /// Registers all ERPT services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::ERPT diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index c2737a365..26d1e3306 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -14,7 +14,7 @@ constexpr ResultCode ERROR_INVALID_RIGHTS_ID{ErrorModule::ETicket, 3}; class ETicket final : public ServiceFramework { public: - explicit ETicket() : ServiceFramework{"es"} { + explicit ETicket(Core::System& system_) : ServiceFramework{system_, "es"} { // clang-format off static const FunctionInfo functions[] = { {1, &ETicket::ImportTicket, "ImportTicket"}, @@ -305,8 +305,8 @@ private: Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); }; -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared()->InstallAsService(service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + std::make_shared(system)->InstallAsService(service_manager); } } // namespace Service::ES diff --git a/src/core/hle/service/es/es.h b/src/core/hle/service/es/es.h index afe70465b..2a7b27d12 100644 --- a/src/core/hle/service/es/es.h +++ b/src/core/hle/service/es/es.h @@ -4,6 +4,10 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } @@ -11,6 +15,6 @@ class ServiceManager; namespace Service::ES { /// Registers all ES services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::ES diff --git a/src/core/hle/service/eupld/eupld.cpp b/src/core/hle/service/eupld/eupld.cpp index 0d6d244f4..2d650b1b7 100644 --- a/src/core/hle/service/eupld/eupld.cpp +++ b/src/core/hle/service/eupld/eupld.cpp @@ -12,7 +12,7 @@ namespace Service::EUPLD { class ErrorUploadContext final : public ServiceFramework { public: - explicit ErrorUploadContext() : ServiceFramework{"eupld:c"} { + explicit ErrorUploadContext(Core::System& system_) : ServiceFramework{system_, "eupld:c"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SetUrl"}, @@ -29,7 +29,7 @@ public: class ErrorUploadRequest final : public ServiceFramework { public: - explicit ErrorUploadRequest() : ServiceFramework{"eupld:r"} { + explicit ErrorUploadRequest(Core::System& system_) : ServiceFramework{system_, "eupld:r"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -45,9 +45,9 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); } } // namespace Service::EUPLD diff --git a/src/core/hle/service/eupld/eupld.h b/src/core/hle/service/eupld/eupld.h index 6eef2c15f..539993a9d 100644 --- a/src/core/hle/service/eupld/eupld.h +++ b/src/core/hle/service/eupld/eupld.h @@ -4,6 +4,10 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } @@ -11,6 +15,6 @@ class ServiceManager; namespace Service::EUPLD { /// Registers all EUPLD services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::EUPLD diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp index 2546d7595..13147472e 100644 --- a/src/core/hle/service/fatal/fatal.cpp +++ b/src/core/hle/service/fatal/fatal.cpp @@ -20,8 +20,9 @@ namespace Service::Fatal { -Module::Interface::Interface(std::shared_ptr module, Core::System& system, const char* name) - : ServiceFramework(name), module(std::move(module)), system(system) {} +Module::Interface::Interface(std::shared_ptr module_, Core::System& system_, + const char* name) + : ServiceFramework{system_, name}, module{std::move(module_)} {} Module::Interface::~Interface() = default; @@ -110,8 +111,9 @@ static void GenerateErrorReport(Core::System& system, ResultCode error_code, static void ThrowFatalError(Core::System& system, ResultCode error_code, FatalType fatal_type, const FatalInfo& info) { - LOG_ERROR(Service_Fatal, "Threw fatal error type {} with error code 0x{:X}", - static_cast(fatal_type), error_code.raw); + LOG_ERROR(Service_Fatal, "Threw fatal error type {} with error code 0x{:X}", fatal_type, + error_code.raw); + switch (fatal_type) { case FatalType::ErrorReportAndScreen: GenerateErrorReport(system, error_code, info); diff --git a/src/core/hle/service/fatal/fatal.h b/src/core/hle/service/fatal/fatal.h index bd9339dfc..2095bf89f 100644 --- a/src/core/hle/service/fatal/fatal.h +++ b/src/core/hle/service/fatal/fatal.h @@ -16,7 +16,8 @@ class Module final { public: class Interface : public ServiceFramework { public: - explicit Interface(std::shared_ptr module, Core::System& system, const char* name); + explicit Interface(std::shared_ptr module_, Core::System& system_, + const char* name); ~Interface() override; void ThrowFatal(Kernel::HLERequestContext& ctx); @@ -25,7 +26,6 @@ public: protected: std::shared_ptr module; - Core::System& system; }; }; diff --git a/src/core/hle/service/fatal/fatal_p.cpp b/src/core/hle/service/fatal/fatal_p.cpp index 066ccf6b0..8672b85dc 100644 --- a/src/core/hle/service/fatal/fatal_p.cpp +++ b/src/core/hle/service/fatal/fatal_p.cpp @@ -6,8 +6,8 @@ namespace Service::Fatal { -Fatal_P::Fatal_P(std::shared_ptr module, Core::System& system) - : Module::Interface(std::move(module), system, "fatal:p") {} +Fatal_P::Fatal_P(std::shared_ptr module_, Core::System& system_) + : Interface(std::move(module_), system_, "fatal:p") {} Fatal_P::~Fatal_P() = default; diff --git a/src/core/hle/service/fatal/fatal_p.h b/src/core/hle/service/fatal/fatal_p.h index c6d953cb5..ffa5b7b98 100644 --- a/src/core/hle/service/fatal/fatal_p.h +++ b/src/core/hle/service/fatal/fatal_p.h @@ -10,7 +10,7 @@ namespace Service::Fatal { class Fatal_P final : public Module::Interface { public: - explicit Fatal_P(std::shared_ptr module, Core::System& system); + explicit Fatal_P(std::shared_ptr module_, Core::System& system_); ~Fatal_P() override; }; diff --git a/src/core/hle/service/fatal/fatal_u.cpp b/src/core/hle/service/fatal/fatal_u.cpp index 8d72ed485..82993938a 100644 --- a/src/core/hle/service/fatal/fatal_u.cpp +++ b/src/core/hle/service/fatal/fatal_u.cpp @@ -6,8 +6,8 @@ namespace Service::Fatal { -Fatal_U::Fatal_U(std::shared_ptr module, Core::System& system) - : Module::Interface(std::move(module), system, "fatal:u") { +Fatal_U::Fatal_U(std::shared_ptr module_, Core::System& system_) + : Interface(std::move(module_), system_, "fatal:u") { static const FunctionInfo functions[] = { {0, &Fatal_U::ThrowFatal, "ThrowFatal"}, {1, &Fatal_U::ThrowFatalWithPolicy, "ThrowFatalWithPolicy"}, diff --git a/src/core/hle/service/fatal/fatal_u.h b/src/core/hle/service/fatal/fatal_u.h index 34c5c7f95..0b58c9112 100644 --- a/src/core/hle/service/fatal/fatal_u.h +++ b/src/core/hle/service/fatal/fatal_u.h @@ -10,7 +10,7 @@ namespace Service::Fatal { class Fatal_U final : public Module::Interface { public: - explicit Fatal_U(std::shared_ptr module, Core::System& system); + explicit Fatal_U(std::shared_ptr module_, Core::System& system_); ~Fatal_U() override; }; diff --git a/src/core/hle/service/fgm/fgm.cpp b/src/core/hle/service/fgm/fgm.cpp index e461274c1..9dc1bc52e 100644 --- a/src/core/hle/service/fgm/fgm.cpp +++ b/src/core/hle/service/fgm/fgm.cpp @@ -14,7 +14,7 @@ namespace Service::FGM { class IRequest final : public ServiceFramework { public: - explicit IRequest() : ServiceFramework{"IRequest"} { + explicit IRequest(Core::System& system_) : ServiceFramework{system_, "IRequest"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -30,7 +30,7 @@ public: class FGM final : public ServiceFramework { public: - explicit FGM(const char* name) : ServiceFramework{name} { + explicit FGM(Core::System& system_, const char* name) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { {0, &FGM::Initialize, "Initialize"}, @@ -46,13 +46,13 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } }; class FGM_DBG final : public ServiceFramework { public: - explicit FGM_DBG() : ServiceFramework{"fgm:dbg"} { + explicit FGM_DBG(Core::System& system_) : ServiceFramework{system_, "fgm:dbg"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -65,11 +65,11 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared("fgm")->InstallAsService(sm); - std::make_shared("fgm:0")->InstallAsService(sm); - std::make_shared("fgm:9")->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared(system, "fgm")->InstallAsService(sm); + std::make_shared(system, "fgm:0")->InstallAsService(sm); + std::make_shared(system, "fgm:9")->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); } } // namespace Service::FGM diff --git a/src/core/hle/service/fgm/fgm.h b/src/core/hle/service/fgm/fgm.h index e59691264..75978f2ed 100644 --- a/src/core/hle/service/fgm/fgm.h +++ b/src/core/hle/service/fgm/fgm.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::FGM { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::FGM diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 54a5fb84b..b15c737e1 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -79,7 +79,7 @@ ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) cons } auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); - if (dir->GetFile(Common::FS::GetFilename(path)) == nullptr) { + if (dir == nullptr || dir->GetFile(Common::FS::GetFilename(path)) == nullptr) { return FileSys::ERROR_PATH_NOT_FOUND; } if (!dir->DeleteFile(Common::FS::GetFilename(path))) { @@ -93,8 +93,9 @@ ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) cons ResultCode VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) const { std::string path(Common::FS::SanitizePath(path_)); auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); - if (dir == nullptr && Common::FS::GetFilename(Common::FS::GetParentPath(path)).empty()) + if (dir == nullptr || Common::FS::GetFilename(Common::FS::GetParentPath(path)).empty()) { dir = backing; + } auto new_dir = dir->CreateSubdirectory(Common::FS::GetFilename(path)); if (new_dir == nullptr) { // TODO(DarkLordZach): Find a better error code for this @@ -297,10 +298,35 @@ ResultVal FileSystemController::OpenRomFSCurrentProcess() return romfs_factory->OpenCurrentProcess(system.CurrentProcess()->GetTitleID()); } +ResultVal FileSystemController::OpenPatchedRomFS( + u64 title_id, FileSys::ContentRecordType type) const { + LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}", title_id); + + if (romfs_factory == nullptr) { + // TODO: Find a better error code for this + return RESULT_UNKNOWN; + } + + return romfs_factory->OpenPatchedRomFS(title_id, type); +} + +ResultVal FileSystemController::OpenPatchedRomFSWithProgramIndex( + u64 title_id, u8 program_index, FileSys::ContentRecordType type) const { + LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}, program_index={}", title_id, + program_index); + + if (romfs_factory == nullptr) { + // TODO: Find a better error code for this + return RESULT_UNKNOWN; + } + + return romfs_factory->OpenPatchedRomFSWithProgramIndex(title_id, program_index, type); +} + ResultVal FileSystemController::OpenRomFS( u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const { LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}, storage_id={:02X}, type={:02X}", - title_id, static_cast(storage_id), static_cast(type)); + title_id, storage_id, type); if (romfs_factory == nullptr) { // TODO(bunnei): Find a better error code for this @@ -312,8 +338,8 @@ ResultVal FileSystemController::OpenRomFS( ResultVal FileSystemController::CreateSaveData( FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const { - LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}", - static_cast(space), save_struct.DebugInfo()); + LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}", space, + save_struct.DebugInfo()); if (save_data_factory == nullptr) { return FileSys::ERROR_ENTITY_NOT_FOUND; @@ -324,8 +350,8 @@ ResultVal FileSystemController::CreateSaveData( ResultVal FileSystemController::OpenSaveData( FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& attribute) const { - LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}", - static_cast(space), attribute.DebugInfo()); + LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}", space, + attribute.DebugInfo()); if (save_data_factory == nullptr) { return FileSys::ERROR_ENTITY_NOT_FOUND; @@ -336,7 +362,7 @@ ResultVal FileSystemController::OpenSaveData( ResultVal FileSystemController::OpenSaveDataSpace( FileSys::SaveDataSpaceId space) const { - LOG_TRACE(Service_FS, "Opening Save Data Space for space_id={:01X}", static_cast(space)); + LOG_TRACE(Service_FS, "Opening Save Data Space for space_id={:01X}", space); if (save_data_factory == nullptr) { return FileSys::ERROR_ENTITY_NOT_FOUND; @@ -357,7 +383,7 @@ ResultVal FileSystemController::OpenSDMC() const { ResultVal FileSystemController::OpenBISPartition( FileSys::BisPartitionId id) const { - LOG_TRACE(Service_FS, "Opening BIS Partition with id={:08X}", static_cast(id)); + LOG_TRACE(Service_FS, "Opening BIS Partition with id={:08X}", id); if (bis_factory == nullptr) { return FileSys::ERROR_ENTITY_NOT_FOUND; @@ -373,7 +399,7 @@ ResultVal FileSystemController::OpenBISPartition( ResultVal FileSystemController::OpenBISPartitionStorage( FileSys::BisPartitionId id) const { - LOG_TRACE(Service_FS, "Opening BIS Partition Storage with id={:08X}", static_cast(id)); + LOG_TRACE(Service_FS, "Opening BIS Partition Storage with id={:08X}", id); if (bis_factory == nullptr) { return FileSys::ERROR_ENTITY_NOT_FOUND; @@ -454,7 +480,9 @@ FileSys::SaveDataSize FileSystemController::ReadSaveDataSize(FileSys::SaveDataTy const auto res = system.GetAppLoader().ReadControlData(nacp); if (res != Loader::ResultStatus::Success) { - FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()}; + const FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID(), + system.GetFileSystemController(), + system.GetContentProvider()}; const auto metadata = pm.GetControlMetadata(); const auto& nacp_unique = metadata.first; @@ -714,7 +742,8 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove } if (save_data_factory == nullptr) { - save_data_factory = std::make_unique(std::move(nand_directory)); + save_data_factory = + std::make_unique(system, std::move(nand_directory)); } if (sdmc_factory == nullptr) { @@ -725,10 +754,9 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove } void InstallInterfaces(Core::System& system) { - std::make_shared()->InstallAsService(system.ServiceManager()); - std::make_shared()->InstallAsService(system.ServiceManager()); - std::make_shared(system.GetFileSystemController(), system.GetReporter()) - ->InstallAsService(system.ServiceManager()); + std::make_shared(system)->InstallAsService(system.ServiceManager()); + std::make_shared(system)->InstallAsService(system.ServiceManager()); + std::make_shared(system)->InstallAsService(system.ServiceManager()); } } // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 6dbbf0b2b..7102d3f9a 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -66,6 +66,10 @@ public: void SetPackedUpdate(FileSys::VirtualFile update_raw); ResultVal OpenRomFSCurrentProcess() const; + ResultVal OpenPatchedRomFS(u64 title_id, + FileSys::ContentRecordType type) const; + ResultVal OpenPatchedRomFSWithProgramIndex( + u64 title_id, u8 program_index, FileSys::ContentRecordType type) const; ResultVal OpenRomFS(u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const; ResultVal CreateSaveData( diff --git a/src/core/hle/service/filesystem/fsp_ldr.cpp b/src/core/hle/service/filesystem/fsp_ldr.cpp index fb487d5bc..1f6c17ba5 100644 --- a/src/core/hle/service/filesystem/fsp_ldr.cpp +++ b/src/core/hle/service/filesystem/fsp_ldr.cpp @@ -7,7 +7,7 @@ namespace Service::FileSystem { -FSP_LDR::FSP_LDR() : ServiceFramework{"fsp:ldr"} { +FSP_LDR::FSP_LDR(Core::System& system_) : ServiceFramework{system_, "fsp:ldr"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "OpenCodeFileSystem"}, diff --git a/src/core/hle/service/filesystem/fsp_ldr.h b/src/core/hle/service/filesystem/fsp_ldr.h index 8210b7729..d6432a0e1 100644 --- a/src/core/hle/service/filesystem/fsp_ldr.h +++ b/src/core/hle/service/filesystem/fsp_ldr.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::FileSystem { class FSP_LDR final : public ServiceFramework { public: - explicit FSP_LDR(); + explicit FSP_LDR(Core::System& system_); ~FSP_LDR() override; }; diff --git a/src/core/hle/service/filesystem/fsp_pr.cpp b/src/core/hle/service/filesystem/fsp_pr.cpp index 378201610..00e4d1662 100644 --- a/src/core/hle/service/filesystem/fsp_pr.cpp +++ b/src/core/hle/service/filesystem/fsp_pr.cpp @@ -7,7 +7,7 @@ namespace Service::FileSystem { -FSP_PR::FSP_PR() : ServiceFramework{"fsp:pr"} { +FSP_PR::FSP_PR(Core::System& system_) : ServiceFramework{system_, "fsp:pr"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RegisterProgram"}, diff --git a/src/core/hle/service/filesystem/fsp_pr.h b/src/core/hle/service/filesystem/fsp_pr.h index 556ae5ce9..9e622518c 100644 --- a/src/core/hle/service/filesystem/fsp_pr.h +++ b/src/core/hle/service/filesystem/fsp_pr.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::FileSystem { class FSP_PR final : public ServiceFramework { public: - explicit FSP_PR(); + explicit FSP_PR(Core::System& system_); ~FSP_PR() override; }; diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 649128be4..9cc260515 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -14,6 +14,7 @@ #include "common/hex_util.h" #include "common/logging/log.h" #include "common/string_util.h" +#include "core/core.h" #include "core/file_sys/directory.h" #include "core/file_sys/errors.h" #include "core/file_sys/mode.h" @@ -56,8 +57,8 @@ enum class FileSystemType : u8 { class IStorage final : public ServiceFramework { public: - explicit IStorage(FileSys::VirtualFile backend_) - : ServiceFramework("IStorage"), backend(std::move(backend_)) { + explicit IStorage(Core::System& system_, FileSys::VirtualFile backend_) + : ServiceFramework{system_, "IStorage"}, backend(std::move(backend_)) { static const FunctionInfo functions[] = { {0, &IStorage::Read, "Read"}, {1, nullptr, "Write"}, @@ -114,8 +115,8 @@ private: class IFile final : public ServiceFramework { public: - explicit IFile(FileSys::VirtualFile backend_) - : ServiceFramework("IFile"), backend(std::move(backend_)) { + explicit IFile(Core::System& system_, FileSys::VirtualFile backend_) + : ServiceFramework{system_, "IFile"}, backend(std::move(backend_)) { static const FunctionInfo functions[] = { {0, &IFile::Read, "Read"}, {1, &IFile::Write, "Write"}, {2, &IFile::Flush, "Flush"}, {3, &IFile::SetSize, "SetSize"}, @@ -246,8 +247,8 @@ static void BuildEntryIndex(std::vector& entries, const std::vec class IDirectory final : public ServiceFramework { public: - explicit IDirectory(FileSys::VirtualDir backend_) - : ServiceFramework("IDirectory"), backend(std::move(backend_)) { + explicit IDirectory(Core::System& system_, FileSys::VirtualDir backend_) + : ServiceFramework{system_, "IDirectory"}, backend(std::move(backend_)) { static const FunctionInfo functions[] = { {0, &IDirectory::Read, "Read"}, {1, &IDirectory::GetEntryCount, "GetEntryCount"}, @@ -302,8 +303,9 @@ private: class IFileSystem final : public ServiceFramework { public: - explicit IFileSystem(FileSys::VirtualDir backend, SizeGetter size) - : ServiceFramework("IFileSystem"), backend(std::move(backend)), size(std::move(size)) { + explicit IFileSystem(Core::System& system_, FileSys::VirtualDir backend_, SizeGetter size_) + : ServiceFramework{system_, "IFileSystem"}, backend{std::move(backend_)}, size{std::move( + size_)} { static const FunctionInfo functions[] = { {0, &IFileSystem::CreateFile, "CreateFile"}, {1, &IFileSystem::DeleteFile, "DeleteFile"}, @@ -411,7 +413,7 @@ public: const auto mode = static_cast(rp.Pop()); - LOG_DEBUG(Service_FS, "called. file={}, mode={}", name, static_cast(mode)); + LOG_DEBUG(Service_FS, "called. file={}, mode={}", name, mode); auto result = backend.OpenFile(name, mode); if (result.Failed()) { @@ -420,7 +422,7 @@ public: return; } - auto file = std::make_shared(result.Unwrap()); + auto file = std::make_shared(system, result.Unwrap()); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -445,7 +447,7 @@ public: return; } - auto directory = std::make_shared(result.Unwrap()); + auto directory = std::make_shared(system, result.Unwrap()); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -500,8 +502,9 @@ private: class ISaveDataInfoReader final : public ServiceFramework { public: - explicit ISaveDataInfoReader(FileSys::SaveDataSpaceId space, FileSystemController& fsc) - : ServiceFramework("ISaveDataInfoReader"), fsc(fsc) { + explicit ISaveDataInfoReader(Core::System& system_, FileSys::SaveDataSpaceId space, + FileSystemController& fsc_) + : ServiceFramework{system_, "ISaveDataInfoReader"}, fsc{fsc_} { static const FunctionInfo functions[] = { {0, &ISaveDataInfoReader::ReadSaveDataInfo, "ReadSaveDataInfo"}, }; @@ -550,8 +553,7 @@ private: const auto save_root = fsc.OpenSaveDataSpace(space); if (save_root.Failed() || *save_root == nullptr) { - LOG_ERROR(Service_FS, "The save root for the space_id={:02X} was invalid!", - static_cast(space)); + LOG_ERROR(Service_FS, "The save root for the space_id={:02X} was invalid!", space); return; } @@ -650,8 +652,9 @@ private: u64 next_entry_index = 0; }; -FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter) - : ServiceFramework("fsp-srv"), fsc(fsc), reporter(reporter) { +FSP_SRV::FSP_SRV(Core::System& system_) + : ServiceFramework{system_, "fsp-srv"}, fsc{system.GetFileSystemController()}, + content_provider{system.GetContentProvider()}, reporter{system.GetReporter()} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "OpenFileSystem"}, @@ -714,7 +717,7 @@ FSP_SRV::FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter) {202, &FSP_SRV::OpenDataStorageByDataId, "OpenDataStorageByDataId"}, {203, &FSP_SRV::OpenPatchDataStorageByCurrentProcess, "OpenPatchDataStorageByCurrentProcess"}, {204, nullptr, "OpenDataFileSystemByProgramIndex"}, - {205, nullptr, "OpenDataStorageByProgramIndex"}, + {205, &FSP_SRV::OpenDataStorageWithProgramIndex, "OpenDataStorageWithProgramIndex"}, {400, nullptr, "OpenDeviceOperator"}, {500, nullptr, "OpenSdCardDetectionEventNotifier"}, {501, nullptr, "OpenGameCardDetectionEventNotifier"}, @@ -791,8 +794,7 @@ void FSP_SRV::OpenFileSystemWithPatch(Kernel::HLERequestContext& ctx) { const auto type = rp.PopRaw(); const auto title_id = rp.PopRaw(); - LOG_WARNING(Service_FS, "(STUBBED) called with type={}, title_id={:016X}", - static_cast(type), title_id); + LOG_WARNING(Service_FS, "(STUBBED) called with type={}, title_id={:016X}", type, title_id); IPC::ResponseBuilder rb{ctx, 2, 0, 0}; rb.Push(RESULT_UNKNOWN); @@ -801,8 +803,9 @@ void FSP_SRV::OpenFileSystemWithPatch(Kernel::HLERequestContext& ctx) { void FSP_SRV::OpenSdCardFileSystem(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "called"); - auto filesystem = std::make_shared( - fsc.OpenSDMC().Unwrap(), SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard)); + auto filesystem = + std::make_shared(system, fsc.OpenSDMC().Unwrap(), + SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -862,8 +865,8 @@ void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) { UNREACHABLE(); } - auto filesystem = - std::make_shared(std::move(dir.Unwrap()), SizeGetter::FromStorageId(fsc, id)); + auto filesystem = std::make_shared(system, std::move(dir.Unwrap()), + SizeGetter::FromStorageId(fsc, id)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -878,11 +881,12 @@ void FSP_SRV::OpenReadOnlySaveDataFileSystem(Kernel::HLERequestContext& ctx) { void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto space = rp.PopRaw(); - LOG_INFO(Service_FS, "called, space={}", static_cast(space)); + LOG_INFO(Service_FS, "called, space={}", space); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(std::make_shared(space, fsc)); + rb.PushIpcInterface( + std::make_shared(system, space, fsc)); } void FSP_SRV::WriteSaveDataFileSystemExtraDataBySaveDataAttribute(Kernel::HLERequestContext& ctx) { @@ -909,10 +913,10 @@ void FSP_SRV::ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute( "(STUBBED) called, flags={}, space_id={}, attribute.title_id={:016X}\n" "attribute.user_id={:016X}{:016X}, attribute.save_id={:016X}\n" "attribute.type={}, attribute.rank={}, attribute.index={}", - flags, static_cast(parameters.space_id), parameters.attribute.title_id, + flags, parameters.space_id, parameters.attribute.title_id, parameters.attribute.user_id[1], parameters.attribute.user_id[0], - parameters.attribute.save_id, static_cast(parameters.attribute.type), - static_cast(parameters.attribute.rank), parameters.attribute.index); + parameters.attribute.save_id, parameters.attribute.type, parameters.attribute.rank, + parameters.attribute.index); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); @@ -931,7 +935,7 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { return; } - auto storage = std::make_shared(std::move(romfs.Unwrap())); + auto storage = std::make_shared(system, std::move(romfs.Unwrap())); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -945,7 +949,7 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) { const auto title_id = rp.PopRaw(); LOG_DEBUG(Service_FS, "called with storage_id={:02X}, unknown={:08X}, title_id={:016X}", - static_cast(storage_id), unknown, title_id); + storage_id, unknown, title_id); auto data = fsc.OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data); @@ -955,23 +959,23 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) { if (archive != nullptr) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(std::make_shared(archive)); + rb.PushIpcInterface(std::make_shared(system, archive)); return; } // TODO(DarkLordZach): Find the right error code to use here LOG_ERROR(Service_FS, "could not open data storage with title_id={:016X}, storage_id={:02X}", title_id, - static_cast(storage_id)); + storage_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_UNKNOWN); return; } - FileSys::PatchManager pm{title_id}; + const FileSys::PatchManager pm{title_id, fsc, content_provider}; auto storage = std::make_shared( - pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data)); + system, pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -981,21 +985,46 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) { void FSP_SRV::OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - auto storage_id = rp.PopRaw(); - auto title_id = rp.PopRaw(); + const auto storage_id = rp.PopRaw(); + const auto title_id = rp.PopRaw(); - LOG_DEBUG(Service_FS, "called with storage_id={:02X}, title_id={:016X}", - static_cast(storage_id), title_id); + LOG_DEBUG(Service_FS, "called with storage_id={:02X}, title_id={:016X}", storage_id, title_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND); } +void FSP_SRV::OpenDataStorageWithProgramIndex(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto program_index = rp.PopRaw(); + + LOG_DEBUG(Service_FS, "called, program_index={}", program_index); + + auto romfs = fsc.OpenPatchedRomFSWithProgramIndex( + system.CurrentProcess()->GetTitleID(), program_index, FileSys::ContentRecordType::Program); + + if (romfs.Failed()) { + // TODO: Find the right error code to use here + LOG_ERROR(Service_FS, "could not open storage with program_index={}", program_index); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_UNKNOWN); + return; + } + + auto storage = std::make_shared(system, std::move(romfs.Unwrap())); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface(std::move(storage)); +} + void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; log_mode = rp.PopEnum(); - LOG_DEBUG(Service_FS, "called, log_mode={:08X}", static_cast(log_mode)); + LOG_DEBUG(Service_FS, "called, log_mode={:08X}", log_mode); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -1033,7 +1062,8 @@ void FSP_SRV::GetAccessLogVersionInfo(Kernel::HLERequestContext& ctx) { class IMultiCommitManager final : public ServiceFramework { public: - explicit IMultiCommitManager() : ServiceFramework("IMultiCommitManager") { + explicit IMultiCommitManager(Core::System& system_) + : ServiceFramework{system_, "IMultiCommitManager"} { static const FunctionInfo functions[] = { {1, &IMultiCommitManager::Add, "Add"}, {2, &IMultiCommitManager::Commit, "Commit"}, @@ -1064,7 +1094,7 @@ void FSP_SRV::OpenMultiCommitManager(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(std::make_shared()); + rb.PushIpcInterface(std::make_shared(system)); } } // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h index 4964e874e..8ed933279 100644 --- a/src/core/hle/service/filesystem/fsp_srv.h +++ b/src/core/hle/service/filesystem/fsp_srv.h @@ -12,8 +12,9 @@ class Reporter; } namespace FileSys { +class ContentProvider; class FileSystemBackend; -} +} // namespace FileSys namespace Service::FileSystem { @@ -32,7 +33,7 @@ enum class LogMode : u32 { class FSP_SRV final : public ServiceFramework { public: - explicit FSP_SRV(FileSystemController& fsc, const Core::Reporter& reporter); + explicit FSP_SRV(Core::System& system_); ~FSP_SRV() override; private: @@ -48,6 +49,7 @@ private: void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); void OpenDataStorageByDataId(Kernel::HLERequestContext& ctx); void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); + void OpenDataStorageWithProgramIndex(Kernel::HLERequestContext& ctx); void SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx); void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx); void OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx); @@ -55,6 +57,7 @@ private: void OpenMultiCommitManager(Kernel::HLERequestContext& ctx); FileSystemController& fsc; + const FileSys::ContentProvider& content_provider; FileSys::VirtualFile romfs; u64 current_process_id = 0; diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index b7adaffc7..c5b053c31 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -5,6 +5,7 @@ #include #include "common/logging/log.h" #include "common/uuid.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/readable_event.h" #include "core/hle/kernel/writable_event.h" @@ -16,7 +17,7 @@ namespace Service::Friend { class IFriendService final : public ServiceFramework { public: - IFriendService() : ServiceFramework("IFriendService") { + explicit IFriendService(Core::System& system_) : ServiceFramework{system_, "IFriendService"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetCompletionEvent"}, @@ -170,8 +171,8 @@ private: class INotificationService final : public ServiceFramework { public: - INotificationService(Common::UUID uuid, Core::System& system) - : ServiceFramework("INotificationService"), uuid(uuid) { + explicit INotificationService(Common::UUID uuid_, Core::System& system_) + : ServiceFramework{system_, "INotificationService"}, uuid{uuid_} { // clang-format off static const FunctionInfo functions[] = { {0, &INotificationService::GetEvent, "GetEvent"}, @@ -228,8 +229,7 @@ private: break; default: // HOS seems not have an error case for an unknown notification - LOG_WARNING(Service_ACC, "Unknown notification {:08X}", - static_cast(notification.notification_type)); + LOG_WARNING(Service_ACC, "Unknown notification {:08X}", notification.notification_type); break; } @@ -266,7 +266,7 @@ private: void Module::Interface::CreateFriendService(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); LOG_DEBUG(Service_ACC, "called"); } @@ -281,8 +281,9 @@ void Module::Interface::CreateNotificationService(Kernel::HLERequestContext& ctx rb.PushIpcInterface(uuid, system); } -Module::Interface::Interface(std::shared_ptr module, Core::System& system, const char* name) - : ServiceFramework(name), module(std::move(module)), system(system) {} +Module::Interface::Interface(std::shared_ptr module_, Core::System& system_, + const char* name) + : ServiceFramework{system_, name}, module{std::move(module_)} {} Module::Interface::~Interface() = default; diff --git a/src/core/hle/service/friend/friend.h b/src/core/hle/service/friend/friend.h index 24f3fc969..8be3321db 100644 --- a/src/core/hle/service/friend/friend.h +++ b/src/core/hle/service/friend/friend.h @@ -16,7 +16,8 @@ class Module final { public: class Interface : public ServiceFramework { public: - explicit Interface(std::shared_ptr module, Core::System& system, const char* name); + explicit Interface(std::shared_ptr module_, Core::System& system_, + const char* name); ~Interface() override; void CreateFriendService(Kernel::HLERequestContext& ctx); @@ -24,7 +25,6 @@ public: protected: std::shared_ptr module; - Core::System& system; }; }; diff --git a/src/core/hle/service/friend/interface.cpp b/src/core/hle/service/friend/interface.cpp index 58155f652..7368ccec2 100644 --- a/src/core/hle/service/friend/interface.cpp +++ b/src/core/hle/service/friend/interface.cpp @@ -6,8 +6,8 @@ namespace Service::Friend { -Friend::Friend(std::shared_ptr module, Core::System& system, const char* name) - : Interface(std::move(module), system, name) { +Friend::Friend(std::shared_ptr module_, Core::System& system_, const char* name) + : Interface(std::move(module_), system_, name) { static const FunctionInfo functions[] = { {0, &Friend::CreateFriendService, "CreateFriendService"}, {1, &Friend::CreateNotificationService, "CreateNotificationService"}, diff --git a/src/core/hle/service/friend/interface.h b/src/core/hle/service/friend/interface.h index 465a35770..43d914b32 100644 --- a/src/core/hle/service/friend/interface.h +++ b/src/core/hle/service/friend/interface.h @@ -10,7 +10,7 @@ namespace Service::Friend { class Friend final : public Module::Interface { public: - explicit Friend(std::shared_ptr module, Core::System& system, const char* name); + explicit Friend(std::shared_ptr module_, Core::System& system_, const char* name); ~Friend() override; }; diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp index b591ce31b..fc77e7286 100644 --- a/src/core/hle/service/glue/arp.cpp +++ b/src/core/hle/service/glue/arp.cpp @@ -5,6 +5,7 @@ #include #include "common/logging/log.h" +#include "core/core.h" #include "core/file_sys/control_metadata.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/hle_ipc.h" @@ -32,8 +33,8 @@ std::optional GetTitleIDForProcessID(const Core::System& system, u64 proces } } // Anonymous namespace -ARP_R::ARP_R(const Core::System& system, const ARPManager& manager) - : ServiceFramework{"arp:r"}, system(system), manager(manager) { +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"}, @@ -151,8 +152,9 @@ class IRegistrar final : public ServiceFramework { public: explicit IRegistrar( + Core::System& system_, std::function)> issuer) - : ServiceFramework{"IRegistrar"}, issue_process_id(std::move(issuer)) { + : ServiceFramework{system_, "IRegistrar"}, issue_process_id{std::move(issuer)} { // clang-format off static const FunctionInfo functions[] = { {0, &IRegistrar::Issue, "Issue"}, @@ -236,8 +238,8 @@ private: std::vector control; }; -ARP_W::ARP_W(const Core::System& system, ARPManager& manager) - : ServiceFramework{"arp:w"}, system(system), manager(manager) { +ARP_W::ARP_W(Core::System& system_, ARPManager& manager_) + : ServiceFramework{system_, "arp:w"}, manager{manager_} { // clang-format off static const FunctionInfo functions[] = { {0, &ARP_W::AcquireRegistrar, "AcquireRegistrar"}, @@ -254,7 +256,7 @@ void ARP_W::AcquireRegistrar(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_ARP, "called"); registrar = std::make_shared( - [this](u64 process_id, ApplicationLaunchProperty launch, std::vector control) { + system, [this](u64 process_id, ApplicationLaunchProperty launch, std::vector control) { const auto res = GetTitleIDForProcessID(system, process_id); if (!res.has_value()) { return ERR_NOT_REGISTERED; diff --git a/src/core/hle/service/glue/arp.h b/src/core/hle/service/glue/arp.h index d5f8a7e7a..34b412e26 100644 --- a/src/core/hle/service/glue/arp.h +++ b/src/core/hle/service/glue/arp.h @@ -13,7 +13,7 @@ class IRegistrar; class ARP_R final : public ServiceFramework { public: - explicit ARP_R(const Core::System& system, const ARPManager& manager); + explicit ARP_R(Core::System& system_, const ARPManager& manager_); ~ARP_R() override; private: @@ -22,20 +22,18 @@ private: void GetApplicationControlProperty(Kernel::HLERequestContext& ctx); void GetApplicationControlPropertyWithApplicationId(Kernel::HLERequestContext& ctx); - const Core::System& system; const ARPManager& manager; }; class ARP_W final : public ServiceFramework { public: - explicit ARP_W(const Core::System& system, ARPManager& manager); + explicit ARP_W(Core::System& system_, ARPManager& manager_); ~ARP_W() override; private: void AcquireRegistrar(Kernel::HLERequestContext& ctx); void DeleteProperties(Kernel::HLERequestContext& ctx); - const Core::System& system; ARPManager& manager; std::shared_ptr registrar; }; diff --git a/src/core/hle/service/glue/bgtc.cpp b/src/core/hle/service/glue/bgtc.cpp index cd89d088f..a478b68e1 100644 --- a/src/core/hle/service/glue/bgtc.cpp +++ b/src/core/hle/service/glue/bgtc.cpp @@ -6,7 +6,7 @@ namespace Service::Glue { -BGTC_T::BGTC_T() : ServiceFramework{"bgtc:t"} { +BGTC_T::BGTC_T(Core::System& system_) : ServiceFramework{system_, "bgtc:t"} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "NotifyTaskStarting"}, @@ -31,7 +31,7 @@ BGTC_T::BGTC_T() : ServiceFramework{"bgtc:t"} { BGTC_T::~BGTC_T() = default; -BGTC_SC::BGTC_SC() : ServiceFramework{"bgtc:sc"} { +BGTC_SC::BGTC_SC(Core::System& system_) : ServiceFramework{system_, "bgtc:sc"} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "GetState"}, diff --git a/src/core/hle/service/glue/bgtc.h b/src/core/hle/service/glue/bgtc.h index 81844f03e..906116ba6 100644 --- a/src/core/hle/service/glue/bgtc.h +++ b/src/core/hle/service/glue/bgtc.h @@ -6,17 +6,21 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Glue { class BGTC_T final : public ServiceFramework { public: - BGTC_T(); + explicit BGTC_T(Core::System& system_); ~BGTC_T() override; }; class BGTC_SC final : public ServiceFramework { public: - BGTC_SC(); + explicit BGTC_SC(Core::System& system_); ~BGTC_SC() override; }; diff --git a/src/core/hle/service/glue/glue.cpp b/src/core/hle/service/glue/glue.cpp index c728e815c..4eafbe5fa 100644 --- a/src/core/hle/service/glue/glue.cpp +++ b/src/core/hle/service/glue/glue.cpp @@ -18,8 +18,8 @@ void InstallInterfaces(Core::System& system) { ->InstallAsService(system.ServiceManager()); // BackGround Task Controller - std::make_shared()->InstallAsService(system.ServiceManager()); - std::make_shared()->InstallAsService(system.ServiceManager()); + std::make_shared(system)->InstallAsService(system.ServiceManager()); + std::make_shared(system)->InstallAsService(system.ServiceManager()); } } // namespace Service::Glue diff --git a/src/core/hle/service/grc/grc.cpp b/src/core/hle/service/grc/grc.cpp index 401e0b208..a502ab47f 100644 --- a/src/core/hle/service/grc/grc.cpp +++ b/src/core/hle/service/grc/grc.cpp @@ -12,7 +12,7 @@ namespace Service::GRC { class GRC final : public ServiceFramework { public: - explicit GRC() : ServiceFramework{"grc:c"} { + explicit GRC(Core::System& system) : ServiceFramework{system, "grc:c"} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "OpenContinuousRecorder"}, @@ -27,8 +27,8 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared(system)->InstallAsService(sm); } } // namespace Service::GRC diff --git a/src/core/hle/service/grc/grc.h b/src/core/hle/service/grc/grc.h index e0d29e70d..9069fe756 100644 --- a/src/core/hle/service/grc/grc.h +++ b/src/core/hle/service/grc/grc.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::GRC { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::GRC diff --git a/src/core/hle/service/hid/controllers/controller_base.h b/src/core/hle/service/hid/controllers/controller_base.h index 8bc69c372..f47a9e61c 100644 --- a/src/core/hle/service/hid/controllers/controller_base.h +++ b/src/core/hle/service/hid/controllers/controller_base.h @@ -31,6 +31,10 @@ public: virtual void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) = 0; + // When the controller is requesting a motion update for the shared memory + virtual void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, + std::size_t size) {} + // Called when input devices should be loaded virtual void OnLoadInputDevices() = 0; diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp index 0b896d5ad..59b694cd4 100644 --- a/src/core/hle/service/hid/controllers/keyboard.cpp +++ b/src/core/hle/service/hid/controllers/keyboard.cpp @@ -42,8 +42,8 @@ void Controller_Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing, cur_entry.modifier = 0; if (Settings::values.keyboard_enabled) { for (std::size_t i = 0; i < keyboard_keys.size(); ++i) { - cur_entry.key[i / KEYS_PER_BYTE] |= - (keyboard_keys[i]->GetStatus() << (i % KEYS_PER_BYTE)); + auto& entry = cur_entry.key[i / KEYS_PER_BYTE]; + entry = static_cast(entry | (keyboard_keys[i]->GetStatus() << (i % KEYS_PER_BYTE))); } for (std::size_t i = 0; i < keyboard_mods.size(); ++i) { diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 620386cd1..d280e7caf 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -116,8 +116,36 @@ u32 Controller_NPad::IndexToNPad(std::size_t index) { } } +bool Controller_NPad::IsNpadIdValid(u32 npad_id) { + switch (npad_id) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case NPAD_UNKNOWN: + case NPAD_HANDHELD: + return true; + default: + LOG_ERROR(Service_HID, "Invalid npad id {}", npad_id); + return false; + } +} + +bool Controller_NPad::IsDeviceHandleValid(const DeviceHandle& device_handle) { + return IsNpadIdValid(device_handle.npad_id) && + device_handle.npad_type < NpadType::MaxNpadType && + device_handle.device_index < DeviceIndex::MaxDeviceIndex; +} + Controller_NPad::Controller_NPad(Core::System& system) : ControllerBase(system), system(system) {} -Controller_NPad::~Controller_NPad() = default; + +Controller_NPad::~Controller_NPad() { + OnRelease(); +} void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) { const auto controller_type = connected_controllers[controller_idx].type; @@ -139,7 +167,7 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) { controller.properties.is_vertical.Assign(1); controller.properties.use_plus.Assign(1); controller.properties.use_minus.Assign(1); - controller.pad_assignment = NPadAssignments::Single; + controller.pad_assignment = NpadAssignments::Single; break; case NPadControllerType::Handheld: controller.joy_styles.handheld.Assign(1); @@ -147,7 +175,7 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) { controller.properties.is_vertical.Assign(1); controller.properties.use_plus.Assign(1); controller.properties.use_minus.Assign(1); - controller.pad_assignment = NPadAssignments::Dual; + controller.pad_assignment = NpadAssignments::Dual; break; case NPadControllerType::JoyDual: controller.joy_styles.joycon_dual.Assign(1); @@ -156,26 +184,26 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) { controller.properties.is_vertical.Assign(1); controller.properties.use_plus.Assign(1); controller.properties.use_minus.Assign(1); - controller.pad_assignment = NPadAssignments::Dual; + controller.pad_assignment = NpadAssignments::Dual; break; case NPadControllerType::JoyLeft: controller.joy_styles.joycon_left.Assign(1); controller.device_type.joycon_left.Assign(1); controller.properties.is_horizontal.Assign(1); controller.properties.use_minus.Assign(1); - controller.pad_assignment = NPadAssignments::Single; + controller.pad_assignment = NpadAssignments::Single; break; case NPadControllerType::JoyRight: controller.joy_styles.joycon_right.Assign(1); controller.device_type.joycon_right.Assign(1); controller.properties.is_horizontal.Assign(1); controller.properties.use_plus.Assign(1); - controller.pad_assignment = NPadAssignments::Single; + controller.pad_assignment = NpadAssignments::Single; break; case NPadControllerType::Pokeball: controller.joy_styles.pokeball.Assign(1); controller.device_type.pokeball.Assign(1); - controller.pad_assignment = NPadAssignments::Single; + controller.pad_assignment = NpadAssignments::Single; break; } @@ -184,11 +212,14 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) { controller.single_color.button_color = 0; controller.dual_color_error = ColorReadError::ReadOk; - controller.left_color.body_color = Settings::values.players[controller_idx].body_color_left; - controller.left_color.button_color = Settings::values.players[controller_idx].button_color_left; - controller.right_color.body_color = Settings::values.players[controller_idx].body_color_right; + controller.left_color.body_color = + Settings::values.players.GetValue()[controller_idx].body_color_left; + controller.left_color.button_color = + Settings::values.players.GetValue()[controller_idx].button_color_left; + controller.right_color.body_color = + Settings::values.players.GetValue()[controller_idx].body_color_right; controller.right_color.button_color = - Settings::values.players[controller_idx].button_color_right; + Settings::values.players.GetValue()[controller_idx].button_color_right; controller.battery_level[0] = BATTERY_FULL; controller.battery_level[1] = BATTERY_FULL; @@ -199,7 +230,7 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) { void Controller_NPad::OnInit() { auto& kernel = system.Kernel(); - for (std::size_t i = 0; i < styleset_changed_events.size(); i++) { + for (std::size_t i = 0; i < styleset_changed_events.size(); ++i) { styleset_changed_events[i] = Kernel::WritableEvent::CreateEventPair( kernel, fmt::format("npad:NpadStyleSetChanged_{}", i)); } @@ -208,6 +239,8 @@ void Controller_NPad::OnInit() { return; } + OnLoadInputDevices(); + if (style.raw == 0) { // We want to support all controllers style.handheld.Assign(1); @@ -218,12 +251,27 @@ void Controller_NPad::OnInit() { style.pokeball.Assign(1); } - std::transform(Settings::values.players.begin(), Settings::values.players.end(), - connected_controllers.begin(), [](const Settings::PlayerInput& player) { + std::transform(Settings::values.players.GetValue().begin(), + Settings::values.players.GetValue().end(), connected_controllers.begin(), + [](const Settings::PlayerInput& player) { return ControllerHolder{MapSettingsTypeToNPad(player.controller_type), player.connected}; }); + // Connect the Player 1 or Handheld controller if none are connected. + if (std::none_of(connected_controllers.begin(), connected_controllers.end(), + [](const ControllerHolder& controller) { return controller.is_connected; })) { + const auto controller = + MapSettingsTypeToNPad(Settings::values.players.GetValue()[0].controller_type); + if (controller == NPadControllerType::Handheld) { + Settings::values.players.GetValue()[HANDHELD_INDEX].connected = true; + connected_controllers[HANDHELD_INDEX] = {controller, true}; + } else { + Settings::values.players.GetValue()[0].connected = true; + connected_controllers[0] = {controller, true}; + } + } + // Account for handheld if (connected_controllers[HANDHELD_INDEX].is_connected) { connected_controllers[HANDHELD_INDEX].type = NPadControllerType::Handheld; @@ -242,7 +290,7 @@ void Controller_NPad::OnInit() { } void Controller_NPad::OnLoadInputDevices() { - const auto& players = Settings::values.players; + const auto& players = Settings::values.players.GetValue(); for (std::size_t i = 0; i < players.size(); ++i) { std::transform(players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_END, @@ -250,17 +298,30 @@ void Controller_NPad::OnLoadInputDevices() { std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END, sticks[i].begin(), Input::CreateDevice); + std::transform(players[i].vibrations.begin() + + Settings::NativeVibration::VIBRATION_HID_BEGIN, + players[i].vibrations.begin() + Settings::NativeVibration::VIBRATION_HID_END, + vibrations[i].begin(), Input::CreateDevice); std::transform(players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_BEGIN, players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_END, motions[i].begin(), Input::CreateDevice); + for (std::size_t device_idx = 0; device_idx < vibrations[i].size(); ++device_idx) { + InitializeVibrationDeviceAtIndex(i, device_idx); + } } } -void Controller_NPad::OnRelease() {} +void Controller_NPad::OnRelease() { + for (std::size_t npad_idx = 0; npad_idx < vibrations.size(); ++npad_idx) { + for (std::size_t device_idx = 0; device_idx < vibrations[npad_idx].size(); ++device_idx) { + VibrateControllerAtIndex(npad_idx, device_idx, {}); + } + } +} void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { const auto controller_idx = NPadIdToIndex(npad_id); - [[maybe_unused]] const auto controller_type = connected_controllers[controller_idx].type; + const auto controller_type = connected_controllers[controller_idx].type; if (!connected_controllers[controller_idx].is_connected) { return; } @@ -269,61 +330,69 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { auto& rstick_entry = npad_pad_states[controller_idx].r_stick; const auto& button_state = buttons[controller_idx]; const auto& analog_state = sticks[controller_idx]; - const auto& motion_state = motions[controller_idx]; const auto [stick_l_x_f, stick_l_y_f] = analog_state[static_cast(JoystickId::Joystick_Left)]->GetStatus(); const auto [stick_r_x_f, stick_r_y_f] = analog_state[static_cast(JoystickId::Joystick_Right)]->GetStatus(); using namespace Settings::NativeButton; - pad_state.a.Assign(button_state[A - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.b.Assign(button_state[B - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.x.Assign(button_state[X - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.y.Assign(button_state[Y - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.l_stick.Assign(button_state[LStick - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r_stick.Assign(button_state[RStick - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.l.Assign(button_state[L - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.zl.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.zr.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.plus.Assign(button_state[Plus - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.minus.Assign(button_state[Minus - BUTTON_HID_BEGIN]->GetStatus()); + if (controller_type != NPadControllerType::JoyLeft) { + pad_state.a.Assign(button_state[A - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.b.Assign(button_state[B - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.x.Assign(button_state[X - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.y.Assign(button_state[Y - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r_stick.Assign(button_state[RStick - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.zr.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.plus.Assign(button_state[Plus - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.d_left.Assign(button_state[DLeft - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.d_up.Assign(button_state[DUp - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.d_right.Assign(button_state[DRight - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.d_down.Assign(button_state[DDown - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r_stick_right.Assign( + analog_state[static_cast(JoystickId::Joystick_Right)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::RIGHT)); + pad_state.r_stick_left.Assign( + analog_state[static_cast(JoystickId::Joystick_Right)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::LEFT)); + pad_state.r_stick_up.Assign( + analog_state[static_cast(JoystickId::Joystick_Right)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::UP)); + pad_state.r_stick_down.Assign( + analog_state[static_cast(JoystickId::Joystick_Right)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::DOWN)); + rstick_entry.x = static_cast(stick_r_x_f * HID_JOYSTICK_MAX); + rstick_entry.y = static_cast(stick_r_y_f * HID_JOYSTICK_MAX); + } - pad_state.l_stick_right.Assign( - analog_state[static_cast(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus( - Input::AnalogDirection::RIGHT)); - pad_state.l_stick_left.Assign( - analog_state[static_cast(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus( - Input::AnalogDirection::LEFT)); - pad_state.l_stick_up.Assign( - analog_state[static_cast(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus( - Input::AnalogDirection::UP)); - pad_state.l_stick_down.Assign( - analog_state[static_cast(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus( - Input::AnalogDirection::DOWN)); + if (controller_type != NPadControllerType::JoyRight) { + pad_state.d_left.Assign(button_state[DLeft - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.d_up.Assign(button_state[DUp - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.d_right.Assign(button_state[DRight - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.d_down.Assign(button_state[DDown - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l_stick.Assign(button_state[LStick - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l.Assign(button_state[L - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.zl.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.minus.Assign(button_state[Minus - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r_stick_right.Assign( - analog_state[static_cast(JoystickId::Joystick_Right)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::RIGHT)); - pad_state.r_stick_left.Assign(analog_state[static_cast(JoystickId::Joystick_Right)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::LEFT)); - pad_state.r_stick_up.Assign(analog_state[static_cast(JoystickId::Joystick_Right)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::UP)); - pad_state.r_stick_down.Assign(analog_state[static_cast(JoystickId::Joystick_Right)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::DOWN)); + pad_state.l_stick_right.Assign( + analog_state[static_cast(JoystickId::Joystick_Left)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::RIGHT)); + pad_state.l_stick_left.Assign( + analog_state[static_cast(JoystickId::Joystick_Left)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::LEFT)); + pad_state.l_stick_up.Assign( + analog_state[static_cast(JoystickId::Joystick_Left)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::UP)); + pad_state.l_stick_down.Assign( + analog_state[static_cast(JoystickId::Joystick_Left)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::DOWN)); + lstick_entry.x = static_cast(stick_l_x_f * HID_JOYSTICK_MAX); + lstick_entry.y = static_cast(stick_l_y_f * HID_JOYSTICK_MAX); + } - pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus()); - - lstick_entry.x = static_cast(stick_l_x_f * HID_JOYSTICK_MAX); - lstick_entry.y = static_cast(stick_l_y_f * HID_JOYSTICK_MAX); - rstick_entry.x = static_cast(stick_r_x_f * HID_JOYSTICK_MAX); - rstick_entry.y = static_cast(stick_r_y_f * HID_JOYSTICK_MAX); + if (controller_type == NPadControllerType::JoyLeft || + controller_type == NPadControllerType::JoyRight) { + pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus()); + } } void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, @@ -331,7 +400,7 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* if (!IsControllerActivated()) { return; } - for (std::size_t i = 0; i < shared_memory_entries.size(); i++) { + for (std::size_t i = 0; i < shared_memory_entries.size(); ++i) { auto& npad = shared_memory_entries[i]; const std::array controller_npads{&npad.main_controller_states, &npad.handheld_states, @@ -365,6 +434,123 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* } const u32 npad_index = static_cast(i); + RequestPadStateUpdate(npad_index); + auto& pad_state = npad_pad_states[npad_index]; + + auto& main_controller = + npad.main_controller_states.npad[npad.main_controller_states.common.last_entry_index]; + auto& handheld_entry = + npad.handheld_states.npad[npad.handheld_states.common.last_entry_index]; + auto& dual_entry = npad.dual_states.npad[npad.dual_states.common.last_entry_index]; + auto& left_entry = npad.left_joy_states.npad[npad.left_joy_states.common.last_entry_index]; + auto& right_entry = + npad.right_joy_states.npad[npad.right_joy_states.common.last_entry_index]; + auto& pokeball_entry = + npad.pokeball_states.npad[npad.pokeball_states.common.last_entry_index]; + auto& libnx_entry = npad.libnx.npad[npad.libnx.common.last_entry_index]; + + libnx_entry.connection_status.raw = 0; + libnx_entry.connection_status.IsConnected.Assign(1); + + switch (controller_type) { + case NPadControllerType::None: + UNREACHABLE(); + break; + case NPadControllerType::ProController: + main_controller.connection_status.raw = 0; + main_controller.connection_status.IsConnected.Assign(1); + main_controller.connection_status.IsWired.Assign(1); + main_controller.pad.pad_states.raw = pad_state.pad_states.raw; + main_controller.pad.l_stick = pad_state.l_stick; + main_controller.pad.r_stick = pad_state.r_stick; + + libnx_entry.connection_status.IsWired.Assign(1); + break; + case NPadControllerType::Handheld: + handheld_entry.connection_status.raw = 0; + handheld_entry.connection_status.IsConnected.Assign(1); + handheld_entry.connection_status.IsWired.Assign(1); + handheld_entry.connection_status.IsLeftJoyConnected.Assign(1); + handheld_entry.connection_status.IsRightJoyConnected.Assign(1); + handheld_entry.connection_status.IsLeftJoyWired.Assign(1); + handheld_entry.connection_status.IsRightJoyWired.Assign(1); + handheld_entry.pad.pad_states.raw = pad_state.pad_states.raw; + handheld_entry.pad.l_stick = pad_state.l_stick; + handheld_entry.pad.r_stick = pad_state.r_stick; + + libnx_entry.connection_status.IsWired.Assign(1); + libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); + libnx_entry.connection_status.IsRightJoyConnected.Assign(1); + libnx_entry.connection_status.IsLeftJoyWired.Assign(1); + libnx_entry.connection_status.IsRightJoyWired.Assign(1); + break; + case NPadControllerType::JoyDual: + dual_entry.connection_status.raw = 0; + dual_entry.connection_status.IsConnected.Assign(1); + dual_entry.connection_status.IsLeftJoyConnected.Assign(1); + dual_entry.connection_status.IsRightJoyConnected.Assign(1); + dual_entry.pad.pad_states.raw = pad_state.pad_states.raw; + dual_entry.pad.l_stick = pad_state.l_stick; + dual_entry.pad.r_stick = pad_state.r_stick; + + libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); + libnx_entry.connection_status.IsRightJoyConnected.Assign(1); + break; + case NPadControllerType::JoyLeft: + left_entry.connection_status.raw = 0; + left_entry.connection_status.IsConnected.Assign(1); + left_entry.connection_status.IsLeftJoyConnected.Assign(1); + left_entry.pad.pad_states.raw = pad_state.pad_states.raw; + left_entry.pad.l_stick = pad_state.l_stick; + left_entry.pad.r_stick = pad_state.r_stick; + + libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); + break; + case NPadControllerType::JoyRight: + right_entry.connection_status.raw = 0; + right_entry.connection_status.IsConnected.Assign(1); + right_entry.connection_status.IsRightJoyConnected.Assign(1); + right_entry.pad.pad_states.raw = pad_state.pad_states.raw; + right_entry.pad.l_stick = pad_state.l_stick; + right_entry.pad.r_stick = pad_state.r_stick; + + libnx_entry.connection_status.IsRightJoyConnected.Assign(1); + break; + case NPadControllerType::Pokeball: + pokeball_entry.connection_status.raw = 0; + pokeball_entry.connection_status.IsConnected.Assign(1); + pokeball_entry.pad.pad_states.raw = pad_state.pad_states.raw; + pokeball_entry.pad.l_stick = pad_state.l_stick; + pokeball_entry.pad.r_stick = pad_state.r_stick; + break; + } + + // LibNX exclusively uses this section, so we always update it since LibNX doesn't activate + // any controllers. + libnx_entry.pad.pad_states.raw = pad_state.pad_states.raw; + libnx_entry.pad.l_stick = pad_state.l_stick; + libnx_entry.pad.r_stick = pad_state.r_stick; + + press_state |= static_cast(pad_state.pad_states.raw); + } + std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(), + shared_memory_entries.size() * sizeof(NPadEntry)); +} + +void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, + std::size_t data_len) { + if (!IsControllerActivated()) { + return; + } + for (std::size_t i = 0; i < shared_memory_entries.size(); ++i) { + auto& npad = shared_memory_entries[i]; + + const auto& controller_type = connected_controllers[i].type; + + if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) { + continue; + } + const std::array controller_sixaxes{ &npad.sixaxis_full, &npad.sixaxis_handheld, &npad.sixaxis_dual_left, &npad.sixaxis_dual_right, &npad.sixaxis_left, &npad.sixaxis_right, @@ -390,7 +576,7 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* // Try to read sixaxis sensor states std::array motion_devices; - if (sixaxis_sensors_enabled && Settings::values.motion_enabled) { + if (sixaxis_sensors_enabled && Settings::values.motion_enabled.GetValue()) { sixaxis_at_rest = true; for (std::size_t e = 0; e < motion_devices.size(); ++e) { const auto& device = motions[i][e]; @@ -403,23 +589,6 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* } } - RequestPadStateUpdate(npad_index); - auto& pad_state = npad_pad_states[npad_index]; - - auto& main_controller = - npad.main_controller_states.npad[npad.main_controller_states.common.last_entry_index]; - auto& handheld_entry = - npad.handheld_states.npad[npad.handheld_states.common.last_entry_index]; - auto& dual_entry = npad.dual_states.npad[npad.dual_states.common.last_entry_index]; - auto& left_entry = npad.left_joy_states.npad[npad.left_joy_states.common.last_entry_index]; - auto& right_entry = - npad.right_joy_states.npad[npad.right_joy_states.common.last_entry_index]; - auto& pokeball_entry = - npad.pokeball_states.npad[npad.pokeball_states.common.last_entry_index]; - auto& libnx_entry = npad.libnx.npad[npad.libnx.common.last_entry_index]; - - libnx_entry.connection_status.raw = 0; - libnx_entry.connection_status.IsConnected.Assign(1); auto& full_sixaxis_entry = npad.sixaxis_full.sixaxis[npad.sixaxis_full.common.last_entry_index]; auto& handheld_sixaxis_entry = @@ -438,15 +607,6 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* UNREACHABLE(); break; case NPadControllerType::ProController: - main_controller.connection_status.raw = 0; - main_controller.connection_status.IsConnected.Assign(1); - main_controller.connection_status.IsWired.Assign(1); - main_controller.pad.pad_states.raw = pad_state.pad_states.raw; - main_controller.pad.l_stick = pad_state.l_stick; - main_controller.pad.r_stick = pad_state.r_stick; - - libnx_entry.connection_status.IsWired.Assign(1); - if (sixaxis_sensors_enabled && motions[i][0]) { full_sixaxis_entry.accel = motion_devices[0].accel; full_sixaxis_entry.gyro = motion_devices[0].gyro; @@ -455,23 +615,6 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* } break; case NPadControllerType::Handheld: - handheld_entry.connection_status.raw = 0; - handheld_entry.connection_status.IsConnected.Assign(1); - handheld_entry.connection_status.IsWired.Assign(1); - handheld_entry.connection_status.IsLeftJoyConnected.Assign(1); - handheld_entry.connection_status.IsRightJoyConnected.Assign(1); - handheld_entry.connection_status.IsLeftJoyWired.Assign(1); - handheld_entry.connection_status.IsRightJoyWired.Assign(1); - handheld_entry.pad.pad_states.raw = pad_state.pad_states.raw; - handheld_entry.pad.l_stick = pad_state.l_stick; - handheld_entry.pad.r_stick = pad_state.r_stick; - - libnx_entry.connection_status.IsWired.Assign(1); - libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); - libnx_entry.connection_status.IsRightJoyConnected.Assign(1); - libnx_entry.connection_status.IsLeftJoyWired.Assign(1); - libnx_entry.connection_status.IsRightJoyWired.Assign(1); - if (sixaxis_sensors_enabled && motions[i][0]) { handheld_sixaxis_entry.accel = motion_devices[0].accel; handheld_sixaxis_entry.gyro = motion_devices[0].gyro; @@ -480,17 +623,6 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* } break; case NPadControllerType::JoyDual: - dual_entry.connection_status.raw = 0; - dual_entry.connection_status.IsConnected.Assign(1); - dual_entry.connection_status.IsLeftJoyConnected.Assign(1); - dual_entry.connection_status.IsRightJoyConnected.Assign(1); - dual_entry.pad.pad_states.raw = pad_state.pad_states.raw; - dual_entry.pad.l_stick = pad_state.l_stick; - dual_entry.pad.r_stick = pad_state.r_stick; - - libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); - libnx_entry.connection_status.IsRightJoyConnected.Assign(1); - if (sixaxis_sensors_enabled && motions[i][0]) { // Set motion for the left joycon dual_left_sixaxis_entry.accel = motion_devices[0].accel; @@ -507,15 +639,6 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* } break; case NPadControllerType::JoyLeft: - left_entry.connection_status.raw = 0; - left_entry.connection_status.IsConnected.Assign(1); - left_entry.connection_status.IsLeftJoyConnected.Assign(1); - left_entry.pad.pad_states.raw = pad_state.pad_states.raw; - left_entry.pad.l_stick = pad_state.l_stick; - left_entry.pad.r_stick = pad_state.r_stick; - - libnx_entry.connection_status.IsLeftJoyConnected.Assign(1); - if (sixaxis_sensors_enabled && motions[i][0]) { left_sixaxis_entry.accel = motion_devices[0].accel; left_sixaxis_entry.gyro = motion_devices[0].gyro; @@ -524,15 +647,6 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* } break; case NPadControllerType::JoyRight: - right_entry.connection_status.raw = 0; - right_entry.connection_status.IsConnected.Assign(1); - right_entry.connection_status.IsRightJoyConnected.Assign(1); - right_entry.pad.pad_states.raw = pad_state.pad_states.raw; - right_entry.pad.l_stick = pad_state.l_stick; - right_entry.pad.r_stick = pad_state.r_stick; - - libnx_entry.connection_status.IsRightJoyConnected.Assign(1); - if (sixaxis_sensors_enabled && motions[i][1]) { right_sixaxis_entry.accel = motion_devices[1].accel; right_sixaxis_entry.gyro = motion_devices[1].gyro; @@ -541,35 +655,22 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* } break; case NPadControllerType::Pokeball: - pokeball_entry.connection_status.raw = 0; - pokeball_entry.connection_status.IsConnected.Assign(1); - pokeball_entry.pad.pad_states.raw = pad_state.pad_states.raw; - pokeball_entry.pad.l_stick = pad_state.l_stick; - pokeball_entry.pad.r_stick = pad_state.r_stick; break; } - - // LibNX exclusively uses this section, so we always update it since LibNX doesn't activate - // any controllers. - libnx_entry.pad.pad_states.raw = pad_state.pad_states.raw; - libnx_entry.pad.l_stick = pad_state.l_stick; - libnx_entry.pad.r_stick = pad_state.r_stick; - - press_state |= static_cast(pad_state.pad_states.raw); } std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(), shared_memory_entries.size() * sizeof(NPadEntry)); } -void Controller_NPad::SetSupportedStyleSet(NPadType style_set) { +void Controller_NPad::SetSupportedStyleSet(NpadStyleSet style_set) { style.raw = style_set.raw; } -Controller_NPad::NPadType Controller_NPad::GetSupportedStyleSet() const { +Controller_NPad::NpadStyleSet Controller_NPad::GetSupportedStyleSet() const { return style; } -void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) { +void Controller_NPad::SetSupportedNpadIdTypes(u8* data, std::size_t length) { ASSERT(length > 0 && (length % sizeof(u32)) == 0); supported_npad_id_types.clear(); supported_npad_id_types.resize(length / sizeof(u32)); @@ -581,7 +682,7 @@ void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length) std::memcpy(data, supported_npad_id_types.data(), supported_npad_id_types.size()); } -std::size_t Controller_NPad::GetSupportedNPadIdTypesSize() const { +std::size_t Controller_NPad::GetSupportedNpadIdTypesSize() const { return supported_npad_id_types.size(); } @@ -601,7 +702,15 @@ Controller_NPad::NpadHandheldActivationMode Controller_NPad::GetNpadHandheldActi return handheld_activation_mode; } -void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) { +void Controller_NPad::SetNpadCommunicationMode(NpadCommunicationMode communication_mode_) { + communication_mode = communication_mode_; +} + +Controller_NPad::NpadCommunicationMode Controller_NPad::GetNpadCommunicationMode() const { + return communication_mode; +} + +void Controller_NPad::SetNpadMode(u32 npad_id, NpadAssignments assignment_mode) { const std::size_t npad_index = NPadIdToIndex(npad_id); ASSERT(npad_index < shared_memory_entries.size()); if (shared_memory_entries[npad_index].pad_assignment != assignment_mode) { @@ -609,24 +718,156 @@ void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) } } -void Controller_NPad::VibrateController(const std::vector& controller_ids, - const std::vector& vibrations) { - LOG_DEBUG(Service_HID, "(STUBBED) called"); +bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index, + const VibrationValue& vibration_value) { + if (!connected_controllers[npad_index].is_connected || !vibrations[npad_index][device_index]) { + return false; + } - if (!Settings::values.vibration_enabled || !can_controllers_vibrate) { - return; - } - for (std::size_t i = 0; i < controller_ids.size(); i++) { - std::size_t controller_pos = NPadIdToIndex(static_cast(i)); - if (connected_controllers[controller_pos].is_connected) { - // TODO(ogniK): Vibrate the physical controller + const auto& player = Settings::values.players.GetValue()[npad_index]; + + if (!player.vibration_enabled) { + if (latest_vibration_values[npad_index][device_index].amp_low != 0.0f || + latest_vibration_values[npad_index][device_index].amp_high != 0.0f) { + // Send an empty vibration to stop any vibrations. + vibrations[npad_index][device_index]->SetRumblePlay(0.0f, 160.0f, 0.0f, 320.0f); + // Then reset the vibration value to its default value. + latest_vibration_values[npad_index][device_index] = {}; } + + return false; } - last_processed_vibration = vibrations.back(); + + if (!Settings::values.enable_accurate_vibrations.GetValue()) { + using std::chrono::duration_cast; + using std::chrono::milliseconds; + using std::chrono::steady_clock; + + const auto now = steady_clock::now(); + + // Filter out non-zero vibrations that are within 10ms of each other. + if ((vibration_value.amp_low != 0.0f || vibration_value.amp_high != 0.0f) && + duration_cast(now - last_vibration_timepoints[npad_index][device_index]) < + milliseconds(10)) { + return false; + } + + last_vibration_timepoints[npad_index][device_index] = now; + } + + auto& vibration = vibrations[npad_index][device_index]; + const auto player_vibration_strength = static_cast(player.vibration_strength); + const auto amp_low = + std::min(vibration_value.amp_low * player_vibration_strength / 100.0f, 1.0f); + const auto amp_high = + std::min(vibration_value.amp_high * player_vibration_strength / 100.0f, 1.0f); + return vibration->SetRumblePlay(amp_low, vibration_value.freq_low, amp_high, + vibration_value.freq_high); } -Controller_NPad::Vibration Controller_NPad::GetLastVibration() const { - return last_processed_vibration; +void Controller_NPad::VibrateController(const DeviceHandle& vibration_device_handle, + const VibrationValue& vibration_value) { + if (!IsDeviceHandleValid(vibration_device_handle)) { + return; + } + + if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) { + return; + } + + const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id); + const auto device_index = static_cast(vibration_device_handle.device_index); + + if (!vibration_devices_mounted[npad_index][device_index] || + !connected_controllers[npad_index].is_connected) { + return; + } + + if (vibration_device_handle.device_index == DeviceIndex::None) { + UNREACHABLE_MSG("DeviceIndex should never be None!"); + return; + } + + // Some games try to send mismatched parameters in the device handle, block these. + if ((connected_controllers[npad_index].type == NPadControllerType::JoyLeft && + (vibration_device_handle.npad_type == NpadType::JoyconRight || + vibration_device_handle.device_index == DeviceIndex::Right)) || + (connected_controllers[npad_index].type == NPadControllerType::JoyRight && + (vibration_device_handle.npad_type == NpadType::JoyconLeft || + vibration_device_handle.device_index == DeviceIndex::Left))) { + return; + } + + // Filter out vibrations with equivalent values to reduce unnecessary state changes. + if (vibration_value.amp_low == latest_vibration_values[npad_index][device_index].amp_low && + vibration_value.amp_high == latest_vibration_values[npad_index][device_index].amp_high) { + return; + } + + if (VibrateControllerAtIndex(npad_index, device_index, vibration_value)) { + latest_vibration_values[npad_index][device_index] = vibration_value; + } +} + +void Controller_NPad::VibrateControllers(const std::vector& vibration_device_handles, + const std::vector& vibration_values) { + if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) { + return; + } + + ASSERT_OR_EXECUTE_MSG( + vibration_device_handles.size() == vibration_values.size(), { return; }, + "The amount of device handles does not match with the amount of vibration values," + "this is undefined behavior!"); + + for (std::size_t i = 0; i < vibration_device_handles.size(); ++i) { + VibrateController(vibration_device_handles[i], vibration_values[i]); + } +} + +Controller_NPad::VibrationValue Controller_NPad::GetLastVibration( + const DeviceHandle& vibration_device_handle) const { + if (!IsDeviceHandleValid(vibration_device_handle)) { + return {}; + } + + const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id); + const auto device_index = static_cast(vibration_device_handle.device_index); + return latest_vibration_values[npad_index][device_index]; +} + +void Controller_NPad::InitializeVibrationDevice(const DeviceHandle& vibration_device_handle) { + if (!IsDeviceHandleValid(vibration_device_handle)) { + return; + } + + const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id); + const auto device_index = static_cast(vibration_device_handle.device_index); + InitializeVibrationDeviceAtIndex(npad_index, device_index); +} + +void Controller_NPad::InitializeVibrationDeviceAtIndex(std::size_t npad_index, + std::size_t device_index) { + if (vibrations[npad_index][device_index]) { + vibration_devices_mounted[npad_index][device_index] = + vibrations[npad_index][device_index]->GetStatus() == 1; + } else { + vibration_devices_mounted[npad_index][device_index] = false; + } +} + +void Controller_NPad::SetPermitVibrationSession(bool permit_vibration_session) { + permit_vibration_session_enabled = permit_vibration_session; +} + +bool Controller_NPad::IsVibrationDeviceMounted(const DeviceHandle& vibration_device_handle) const { + if (!IsDeviceHandleValid(vibration_device_handle)) { + return false; + } + + const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id); + const auto device_index = static_cast(vibration_device_handle.device_index); + return vibration_devices_mounted[npad_index][device_index]; } std::shared_ptr Controller_NPad::GetStyleSetChangedEvent(u32 npad_id) const { @@ -645,31 +886,38 @@ void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::siz void Controller_NPad::UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected) { if (!connected) { - DisconnectNPadAtIndex(npad_index); + DisconnectNpadAtIndex(npad_index); return; } if (controller == NPadControllerType::Handheld) { - Settings::values.players[HANDHELD_INDEX].controller_type = + Settings::values.players.GetValue()[HANDHELD_INDEX].controller_type = MapNPadToSettingsType(controller); - Settings::values.players[HANDHELD_INDEX].connected = true; + Settings::values.players.GetValue()[HANDHELD_INDEX].connected = true; connected_controllers[HANDHELD_INDEX] = {controller, true}; InitNewlyAddedController(HANDHELD_INDEX); return; } - Settings::values.players[npad_index].controller_type = MapNPadToSettingsType(controller); - Settings::values.players[npad_index].connected = true; + Settings::values.players.GetValue()[npad_index].controller_type = + MapNPadToSettingsType(controller); + Settings::values.players.GetValue()[npad_index].connected = true; connected_controllers[npad_index] = {controller, true}; InitNewlyAddedController(npad_index); } -void Controller_NPad::DisconnectNPad(u32 npad_id) { - DisconnectNPadAtIndex(NPadIdToIndex(npad_id)); +void Controller_NPad::DisconnectNpad(u32 npad_id) { + DisconnectNpadAtIndex(NPadIdToIndex(npad_id)); } -void Controller_NPad::DisconnectNPadAtIndex(std::size_t npad_index) { - Settings::values.players[npad_index].connected = false; +void Controller_NPad::DisconnectNpadAtIndex(std::size_t npad_index) { + for (std::size_t device_idx = 0; device_idx < vibrations[npad_index].size(); ++device_idx) { + // Send an empty vibration to stop any vibrations. + VibrateControllerAtIndex(npad_index, device_idx, {}); + vibration_devices_mounted[npad_index][device_idx] = false; + } + + Settings::values.players.GetValue()[npad_index].connected = false; connected_controllers[npad_index].is_connected = false; auto& controller = shared_memory_entries[npad_index]; @@ -707,7 +955,7 @@ void Controller_NPad::MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2) { (connected_controllers[npad_index_2].type == NPadControllerType::JoyLeft && connected_controllers[npad_index_1].type == NPadControllerType::JoyRight)) { // Disconnect the joycon at the second id and connect the dual joycon at the first index. - DisconnectNPad(npad_id_2); + DisconnectNpad(npad_id_2); AddNewControllerAt(NPadControllerType::JoyDual, npad_index_1); } } @@ -770,12 +1018,13 @@ Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) { } } -void Controller_NPad::SetVibrationEnabled(bool can_vibrate) { - can_controllers_vibrate = can_vibrate; +bool Controller_NPad::IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const { + return unintended_home_button_input_protection[NPadIdToIndex(npad_id)]; } -bool Controller_NPad::IsVibrationEnabled() const { - return can_controllers_vibrate; +void Controller_NPad::SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, + u32 npad_id) { + unintended_home_button_input_protection[NPadIdToIndex(npad_id)] = is_protection_enabled; } void Controller_NPad::ClearAllConnectedControllers() { @@ -809,7 +1058,7 @@ void Controller_NPad::ClearAllControllers() { } u32 Controller_NPad::GetAndResetPressState() { - return std::exchange(press_state, 0); + return press_state.exchange(0); } bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const { @@ -822,7 +1071,7 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const return false; } // Handheld should not be supported in docked mode - if (Settings::values.use_docked_mode) { + if (Settings::values.use_docked_mode.GetValue()) { return false; } diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 654d97c3f..e2e826623 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include "common/bit_field.h" #include "common/common_types.h" #include "core/frontend/input.h" @@ -32,31 +33,39 @@ public: // When the controller is requesting an update for the shared memory void OnUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, std::size_t size) override; + // When the controller is requesting a motion update for the shared memory + void OnMotionUpdate(const Core::Timing::CoreTiming& core_timing, u8* data, + std::size_t size) override; + // Called when input devices should be loaded void OnLoadInputDevices() override; - struct NPadType { - union { - u32_le raw{}; - - BitField<0, 1, u32> pro_controller; - BitField<1, 1, u32> handheld; - BitField<2, 1, u32> joycon_dual; - BitField<3, 1, u32> joycon_left; - BitField<4, 1, u32> joycon_right; - - BitField<6, 1, u32> pokeball; // TODO(ogniK): Confirm when possible - }; + enum class NPadControllerType { + None, + ProController, + Handheld, + JoyDual, + JoyLeft, + JoyRight, + Pokeball, }; - static_assert(sizeof(NPadType) == 4, "NPadType is an invalid size"); - struct Vibration { - f32 amp_low; - f32 freq_low; - f32 amp_high; - f32 freq_high; + enum class NpadType : u8 { + ProController = 3, + Handheld = 4, + JoyconDual = 5, + JoyconLeft = 6, + JoyconRight = 7, + Pokeball = 9, + MaxNpadType = 10, + }; + + enum class DeviceIndex : u8 { + Left = 0, + Right = 1, + None = 2, + MaxDeviceIndex = 3, }; - static_assert(sizeof(Vibration) == 0x10, "Vibration is an invalid size"); enum class GyroscopeZeroDriftMode : u32 { Loose = 0, @@ -69,7 +78,7 @@ public: Horizontal = 1, }; - enum class NPadAssignments : u32_le { + enum class NpadAssignments : u32 { Dual = 0, Single = 1, }; @@ -80,16 +89,44 @@ public: None = 2, }; - enum class NPadControllerType { - None, - ProController, - Handheld, - JoyDual, - JoyLeft, - JoyRight, - Pokeball, + enum class NpadCommunicationMode : u64 { + Unknown0 = 0, + Unknown1 = 1, + Unknown2 = 2, + Unknown3 = 3, }; + struct DeviceHandle { + NpadType npad_type{}; + u8 npad_id{}; + DeviceIndex device_index{}; + INSERT_PADDING_BYTES(1); + }; + static_assert(sizeof(DeviceHandle) == 4, "DeviceHandle is an invalid size"); + + struct NpadStyleSet { + union { + u32_le raw{}; + + BitField<0, 1, u32> pro_controller; + BitField<1, 1, u32> handheld; + BitField<2, 1, u32> joycon_dual; + BitField<3, 1, u32> joycon_left; + BitField<4, 1, u32> joycon_right; + + BitField<6, 1, u32> pokeball; // TODO(ogniK): Confirm when possible + }; + }; + static_assert(sizeof(NpadStyleSet) == 4, "NpadStyleSet is an invalid size"); + + struct VibrationValue { + f32 amp_low{0.0f}; + f32 freq_low{160.0f}; + f32 amp_high{0.0f}; + f32 freq_high{320.0f}; + }; + static_assert(sizeof(VibrationValue) == 0x10, "Vibration is an invalid size"); + struct LedPattern { explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) { position1.Assign(light1); @@ -106,12 +143,12 @@ public: }; }; - void SetSupportedStyleSet(NPadType style_set); - NPadType GetSupportedStyleSet() const; + void SetSupportedStyleSet(NpadStyleSet style_set); + NpadStyleSet GetSupportedStyleSet() const; - void SetSupportedNPadIdTypes(u8* data, std::size_t length); + void SetSupportedNpadIdTypes(u8* data, std::size_t length); void GetSupportedNpadIdTypes(u32* data, std::size_t max_length); - std::size_t GetSupportedNPadIdTypesSize() const; + std::size_t GetSupportedNpadIdTypesSize() const; void SetHoldType(NpadHoldType joy_hold_type); NpadHoldType GetHoldType() const; @@ -119,12 +156,29 @@ public: void SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode); NpadHandheldActivationMode GetNpadHandheldActivationMode() const; - void SetNpadMode(u32 npad_id, NPadAssignments assignment_mode); + void SetNpadCommunicationMode(NpadCommunicationMode communication_mode_); + NpadCommunicationMode GetNpadCommunicationMode() const; - void VibrateController(const std::vector& controller_ids, - const std::vector& vibrations); + void SetNpadMode(u32 npad_id, NpadAssignments assignment_mode); - Vibration GetLastVibration() const; + bool VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index, + const VibrationValue& vibration_value); + + void VibrateController(const DeviceHandle& vibration_device_handle, + const VibrationValue& vibration_value); + + void VibrateControllers(const std::vector& vibration_device_handles, + const std::vector& vibration_values); + + VibrationValue GetLastVibration(const DeviceHandle& vibration_device_handle) const; + + void InitializeVibrationDevice(const DeviceHandle& vibration_device_handle); + + void InitializeVibrationDeviceAtIndex(std::size_t npad_index, std::size_t device_index); + + void SetPermitVibrationSession(bool permit_vibration_session); + + bool IsVibrationDeviceMounted(const DeviceHandle& vibration_device_handle) const; std::shared_ptr GetStyleSetChangedEvent(u32 npad_id) const; void SignalStyleSetChangedEvent(u32 npad_id) const; @@ -134,16 +188,16 @@ public: // Adds a new controller at an index with connection status. void UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected); - void DisconnectNPad(u32 npad_id); - void DisconnectNPadAtIndex(std::size_t index); + void DisconnectNpad(u32 npad_id); + void DisconnectNpadAtIndex(std::size_t index); void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode); GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const; bool IsSixAxisSensorAtRest() const; void SetSixAxisEnabled(bool six_axis_status); LedPattern GetLedPattern(u32 npad_id); - void SetVibrationEnabled(bool can_vibrate); - bool IsVibrationEnabled() const; + bool IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const; + void SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, u32 npad_id); void ClearAllConnectedControllers(); void DisconnectAllConnectedControllers(); void ConnectAllDisconnectedControllers(); @@ -162,6 +216,8 @@ public: static Settings::ControllerType MapNPadToSettingsType(Controller_NPad::NPadControllerType type); static std::size_t NPadIdToIndex(u32 npad_id); static u32 IndexToNPad(std::size_t index); + static bool IsNpadIdValid(u32 npad_id); + static bool IsDeviceHandleValid(const DeviceHandle& device_handle); private: struct CommonHeader { @@ -318,8 +374,8 @@ private: }; struct NPadEntry { - NPadType joy_styles; - NPadAssignments pad_assignment; + NpadStyleSet joy_styles; + NpadAssignments pad_assignment; ColorReadError single_color_error; ControllerColor single_color; @@ -360,9 +416,9 @@ private: bool IsControllerSupported(NPadControllerType controller) const; void RequestPadStateUpdate(u32 npad_id); - u32 press_state{}; + std::atomic press_state{}; - NPadType style{}; + NpadStyleSet style{}; std::array shared_memory_entries{}; using ButtonArray = std::array< std::array, Settings::NativeButton::NUM_BUTTONS_HID>, @@ -370,21 +426,30 @@ private: using StickArray = std::array< std::array, Settings::NativeAnalog::NUM_STICKS_HID>, 10>; + using VibrationArray = std::array, + Settings::NativeVibration::NUM_VIBRATIONS_HID>, + 10>; using MotionArray = std::array< - std::array, Settings::NativeMotion::NUM_MOTION_HID>, + std::array, Settings::NativeMotion::NUM_MOTIONS_HID>, 10>; ButtonArray buttons; StickArray sticks; + VibrationArray vibrations; MotionArray motions; std::vector supported_npad_id_types{}; NpadHoldType hold_type{NpadHoldType::Vertical}; NpadHandheldActivationMode handheld_activation_mode{NpadHandheldActivationMode::Dual}; + // NpadCommunicationMode is unknown, default value is 1 + NpadCommunicationMode communication_mode{NpadCommunicationMode::Unknown1}; // Each controller should have their own styleset changed event std::array styleset_changed_events; - Vibration last_processed_vibration{}; + std::array, 10> last_vibration_timepoints; + std::array, 10> latest_vibration_values{}; + bool permit_vibration_session_enabled{false}; + std::array, 10> vibration_devices_mounted{}; std::array connected_controllers{}; + std::array unintended_home_button_input_protection{}; GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard}; - bool can_controllers_vibrate{true}; bool sixaxis_sensors_enabled{true}; bool sixaxis_at_rest{true}; std::array npad_pad_states{}; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 395e83b3f..8d95f74e6 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -40,11 +40,12 @@ namespace Service::HID { // Updating period for each HID device. // HID is polled every 15ms, this value was derived from // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering#joy-con-status-data-packet -constexpr auto pad_update_ns = std::chrono::nanoseconds{1000 * 1000}; // (1ms, 1000Hz) +constexpr auto pad_update_ns = std::chrono::nanoseconds{1000 * 1000}; // (1ms, 1000Hz) +constexpr auto motion_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000}; // (15ms, 66.666Hz) constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000; -IAppletResource::IAppletResource(Core::System& system) - : ServiceFramework("IAppletResource"), system(system) { +IAppletResource::IAppletResource(Core::System& system_) + : ServiceFramework{system_, "IAppletResource"} { static const FunctionInfo functions[] = { {0, &IAppletResource::GetSharedMemoryHandle, "GetSharedMemoryHandle"}, }; @@ -77,12 +78,18 @@ IAppletResource::IAppletResource(Core::System& system) pad_update_event = Core::Timing::CreateEvent( "HID::UpdatePadCallback", [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { + const auto guard = LockService(); UpdateControllers(user_data, ns_late); }); - - // TODO(shinyquagsire23): Other update callbacks? (accel, gyro?) + motion_update_event = Core::Timing::CreateEvent( + "HID::MotionPadCallback", + [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { + const auto guard = LockService(); + UpdateMotion(user_data, ns_late); + }); system.CoreTiming().ScheduleEvent(pad_update_ns, pad_update_event); + system.CoreTiming().ScheduleEvent(motion_update_ns, motion_update_event); ReloadInputDevices(); } @@ -122,22 +129,50 @@ void IAppletResource::UpdateControllers(std::uintptr_t user_data, core_timing.ScheduleEvent(pad_update_ns - ns_late, pad_update_event); } +void IAppletResource::UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { + auto& core_timing = system.CoreTiming(); + + for (const auto& controller : controllers) { + controller->OnMotionUpdate(core_timing, shared_mem->GetPointer(), SHARED_MEMORY_SIZE); + } + + core_timing.ScheduleEvent(motion_update_ns - ns_late, motion_update_event); +} + class IActiveVibrationDeviceList final : public ServiceFramework { public: - IActiveVibrationDeviceList() : ServiceFramework("IActiveVibrationDeviceList") { + explicit IActiveVibrationDeviceList(Core::System& system_, + std::shared_ptr applet_resource_) + : ServiceFramework{system_, "IActiveVibrationDeviceList"}, + applet_resource(applet_resource_) { + // clang-format off static const FunctionInfo functions[] = { - {0, &IActiveVibrationDeviceList::ActivateVibrationDevice, "ActivateVibrationDevice"}, + {0, &IActiveVibrationDeviceList::InitializeVibrationDevice, "InitializeVibrationDevice"}, }; + // clang-format on + RegisterHandlers(functions); } private: - void ActivateVibrationDevice(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_HID, "(STUBBED) called"); + void InitializeVibrationDevice(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto vibration_device_handle{rp.PopRaw()}; + + if (applet_resource != nullptr) { + applet_resource->GetController(HidController::NPad) + .InitializeVibrationDevice(vibration_device_handle); + } + + LOG_DEBUG(Service_HID, "called, npad_type={}, npad_id={}, device_index={}", + vibration_device_handle.npad_type, vibration_device_handle.npad_id, + vibration_device_handle.device_index); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } + + std::shared_ptr applet_resource; }; std::shared_ptr Hid::GetAppletResource() { @@ -148,7 +183,7 @@ std::shared_ptr Hid::GetAppletResource() { return applet_resource; } -Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { +Hid::Hid(Core::System& system_) : ServiceFramework{system_, "hid"} { // clang-format off static const FunctionInfo functions[] = { {0, &Hid::CreateAppletResource, "CreateAppletResource"}, @@ -173,7 +208,7 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { {66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"}, {67, &Hid::StopSixAxisSensor, "StopSixAxisSensor"}, {68, nullptr, "IsSixAxisSensorFusionEnabled"}, - {69, nullptr, "EnableSixAxisSensorFusion"}, + {69, &Hid::EnableSixAxisSensorFusion, "EnableSixAxisSensorFusion"}, {70, nullptr, "SetSixAxisSensorFusionParameters"}, {71, nullptr, "GetSixAxisSensorFusionParameters"}, {72, nullptr, "ResetSixAxisSensorFusionParameters"}, @@ -209,8 +244,8 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { {128, &Hid::SetNpadHandheldActivationMode, "SetNpadHandheldActivationMode"}, {129, &Hid::GetNpadHandheldActivationMode, "GetNpadHandheldActivationMode"}, {130, &Hid::SwapNpadAssignment, "SwapNpadAssignment"}, - {131, nullptr, "IsUnintendedHomeButtonInputProtectionEnabled"}, - {132, nullptr, "EnableUnintendedHomeButtonInputProtection"}, + {131, &Hid::IsUnintendedHomeButtonInputProtectionEnabled, "IsUnintendedHomeButtonInputProtectionEnabled"}, + {132, &Hid::EnableUnintendedHomeButtonInputProtection, "EnableUnintendedHomeButtonInputProtection"}, {133, nullptr, "SetNpadJoyAssignmentModeSingleWithDestination"}, {134, nullptr, "SetNpadAnalogStickUseCenterClamp"}, {135, nullptr, "SetNpadCaptureButtonAssignment"}, @@ -226,7 +261,7 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { {208, nullptr, "GetActualVibrationGcErmCommand"}, {209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"}, {210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"}, - {211, nullptr, "IsVibrationDeviceMounted"}, + {211, &Hid::IsVibrationDeviceMounted, "IsVibrationDeviceMounted"}, {300, &Hid::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"}, {301, &Hid::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"}, {302, &Hid::StopConsoleSixAxisSensor, "StopConsoleSixAxisSensor"}, @@ -245,7 +280,7 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { {404, nullptr, "HasLeftRightBattery"}, {405, nullptr, "GetNpadInterfaceType"}, {406, nullptr, "GetNpadLeftRightInterfaceType"}, - {407, nullptr, "GetNpadOfHighestBatteryLevelForJoyLeft"}, + {407, nullptr, "GetNpadOfHighestBatteryLevel"}, {408, nullptr, "GetNpadOfHighestBatteryLevelForJoyRight"}, {500, nullptr, "GetPalmaConnectionHandle"}, {501, nullptr, "InitializePalma"}, @@ -277,8 +312,8 @@ Hid::Hid(Core::System& system) : ServiceFramework("hid"), system(system) { {527, nullptr, "EnablePalmaBoostMode"}, {528, nullptr, "GetPalmaBluetoothAddress"}, {529, nullptr, "SetDisallowedPalmaConnection"}, - {1000, nullptr, "SetNpadCommunicationMode"}, - {1001, nullptr, "GetNpadCommunicationMode"}, + {1000, &Hid::SetNpadCommunicationMode, "SetNpadCommunicationMode"}, + {1001, &Hid::GetNpadCommunicationMode, "GetNpadCommunicationMode"}, {1002, nullptr, "SetTouchScreenConfiguration"}, {1003, nullptr, "IsFirmwareUpdateNeededForNotification"}, {2000, nullptr, "ActivateDigitizer"}, @@ -305,15 +340,79 @@ void Hid::CreateAppletResource(Kernel::HLERequestContext& ctx) { rb.PushIpcInterface(applet_resource); } -void Hid::ActivateXpad(Kernel::HLERequestContext& ctx) { +void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto basic_xpad_id{rp.Pop()}; const auto applet_resource_user_id{rp.Pop()}; - LOG_DEBUG(Service_HID, "called, basic_xpad_id={}, applet_resource_user_id={}", basic_xpad_id, - applet_resource_user_id); + applet_resource->ActivateController(HidController::DebugPad); + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::ActivateTouchScreen(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop()}; + + applet_resource->ActivateController(HidController::Touchscreen); + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::ActivateMouse(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop()}; + + applet_resource->ActivateController(HidController::Mouse); + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::ActivateKeyboard(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop()}; + + applet_resource->ActivateController(HidController::Keyboard); + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::SendKeyboardLockKeyEvent(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto flags{rp.Pop()}; + + LOG_WARNING(Service_HID, "(STUBBED) called. flags={}", flags); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::ActivateXpad(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + u32 basic_xpad_id{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw()}; applet_resource->ActivateController(HidController::XPad); + + LOG_DEBUG(Service_HID, "called, basic_xpad_id={}, applet_resource_user_id={}", + parameters.basic_xpad_id, parameters.applet_resource_user_id); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -331,11 +430,20 @@ void Hid::GetXpadIDs(Kernel::HLERequestContext& ctx) { void Hid::ActivateSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop()}; - const auto applet_resource_user_id{rp.Pop()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw()}; + applet_resource->GetController(HidController::NPad).SetSixAxisEnabled(true); - LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -343,104 +451,41 @@ void Hid::ActivateSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop()}; - const auto applet_resource_user_id{rp.Pop()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw()}; + applet_resource->GetController(HidController::NPad).SetSixAxisEnabled(false); - LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::ActivateDebugPad(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop()}; - - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - - applet_resource->ActivateController(HidController::DebugPad); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} - -void Hid::ActivateTouchScreen(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop()}; - - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - - applet_resource->ActivateController(HidController::Touchscreen); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} - -void Hid::ActivateMouse(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop()}; - - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - - applet_resource->ActivateController(HidController::Mouse); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} - -void Hid::ActivateKeyboard(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop()}; - - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - - applet_resource->ActivateController(HidController::Keyboard); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} - -void Hid::SendKeyboardLockKeyEvent(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto flags{rp.Pop()}; - LOG_WARNING(Service_HID, "(STUBBED) called. flags={}", flags); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} - -void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto unknown{rp.Pop()}; - const auto applet_resource_user_id{rp.Pop()}; - - LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown, - applet_resource_user_id); - - applet_resource->ActivateController(HidController::Gesture); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} - -void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) { - // Should have no effect with how our npad sets up the data - IPC::RequestParser rp{ctx}; - const auto unknown{rp.Pop()}; - const auto applet_resource_user_id{rp.Pop()}; - - LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", unknown, - applet_resource_user_id); - - applet_resource->ActivateController(HidController::NPad); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} - void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop()}; - const auto applet_resource_user_id{rp.Pop()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + const auto parameters{rp.PopRaw()}; + + applet_resource->GetController(HidController::NPad).SetSixAxisEnabled(true); + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -448,11 +493,42 @@ void Hid::StartSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop()}; - const auto applet_resource_user_id{rp.Pop()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + const auto parameters{rp.PopRaw()}; + + applet_resource->GetController(HidController::NPad).SetSixAxisEnabled(false); + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + bool enable_sixaxis_sensor_fusion{}; + INSERT_PADDING_BYTES(3); + Controller_NPad::DeviceHandle sixaxis_handle{}; + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw()}; + + LOG_WARNING(Service_HID, + "(STUBBED) called, enable_sixaxis_sensor_fusion={}, npad_type={}, npad_id={}, " + "device_index={}, applet_resource_user_id={}", + parameters.enable_sixaxis_sensor_fusion, parameters.sixaxis_handle.npad_type, + parameters.sixaxis_handle.npad_id, parameters.sixaxis_handle.device_index, + parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -460,14 +536,17 @@ void Hid::StopSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop()}; - const auto drift_mode{rp.Pop()}; + const auto sixaxis_handle{rp.PopRaw()}; + const auto drift_mode{rp.PopEnum()}; const auto applet_resource_user_id{rp.Pop()}; applet_resource->GetController(HidController::NPad) - .SetGyroscopeZeroDriftMode(Controller_NPad::GyroscopeZeroDriftMode{drift_mode}); + .SetGyroscopeZeroDriftMode(drift_mode); - LOG_DEBUG(Service_HID, "called, handle={}, drift_mode={}, applet_resource_user_id={}", handle, + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, drift_mode={}, " + "applet_resource_user_id={}", + sixaxis_handle.npad_type, sixaxis_handle.npad_id, sixaxis_handle.device_index, drift_mode, applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; @@ -476,29 +555,42 @@ void Hid::SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { void Hid::GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop()}; - const auto applet_resource_user_id{rp.Pop()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + const auto parameters{rp.PopRaw()}; + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push( - static_cast(applet_resource->GetController(HidController::NPad) - .GetGyroscopeZeroDriftMode())); + rb.PushEnum(applet_resource->GetController(HidController::NPad) + .GetGyroscopeZeroDriftMode()); } void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop()}; - const auto applet_resource_user_id{rp.Pop()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw()}; applet_resource->GetController(HidController::NPad) .SetGyroscopeZeroDriftMode(Controller_NPad::GyroscopeZeroDriftMode::Standard); - LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -506,11 +598,18 @@ void Hid::ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx) { void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop()}; - const auto applet_resource_user_id{rp.Pop()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_DEBUG(Service_HID, "called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + const auto parameters{rp.PopRaw()}; + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); @@ -518,15 +617,34 @@ void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) { .IsSixAxisSensorAtRest()); } +void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + u32 unknown{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw()}; + + applet_resource->ActivateController(HidController::Gesture); + + LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", parameters.unknown, + parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + void Hid::SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto supported_styleset{rp.Pop()}; - LOG_DEBUG(Service_HID, "called, supported_styleset={}", supported_styleset); - applet_resource->GetController(HidController::NPad) .SetSupportedStyleSet({supported_styleset}); + LOG_DEBUG(Service_HID, "called, supported_styleset={}", supported_styleset); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -537,21 +655,22 @@ void Hid::GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - auto& controller = applet_resource->GetController(HidController::NPad); - IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(controller.GetSupportedStyleSet().raw); + rb.Push(applet_resource->GetController(HidController::NPad) + .GetSupportedStyleSet() + .raw); } void Hid::SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop()}; + applet_resource->GetController(HidController::NPad) + .SetSupportedNpadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - applet_resource->GetController(HidController::NPad) - .SetSupportedNPadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -560,48 +679,62 @@ void Hid::ActivateNpad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop()}; + applet_resource->ActivateController(HidController::NPad); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - applet_resource->ActivateController(HidController::NPad); } void Hid::DeactivateNpad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop()}; + applet_resource->DeactivateController(HidController::NPad); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - applet_resource->DeactivateController(HidController::NPad); } void Hid::AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop()}; - const auto applet_resource_user_id{rp.Pop()}; - const auto unknown{rp.Pop()}; + struct Parameters { + u32 npad_id{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + u64 unknown{}; + }; - LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}", npad_id, - applet_resource_user_id, unknown); + const auto parameters{rp.PopRaw()}; + + LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}, unknown={}", + parameters.npad_id, parameters.applet_resource_user_id, parameters.unknown); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(applet_resource->GetController(HidController::NPad) - .GetStyleSetChangedEvent(npad_id)); + .GetStyleSetChangedEvent(parameters.npad_id)); } void Hid::DisconnectNpad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop()}; - const auto applet_resource_user_id{rp.Pop()}; + struct Parameters { + u32 npad_id{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id, - applet_resource_user_id); + const auto parameters{rp.PopRaw()}; + + applet_resource->GetController(HidController::NPad) + .DisconnectNpad(parameters.npad_id); + + LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id, + parameters.applet_resource_user_id); - applet_resource->GetController(HidController::NPad).DisconnectNPad(npad_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -614,22 +747,41 @@ void Hid::GetPlayerLedPattern(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.PushRaw(applet_resource->GetController(HidController::NPad) - .GetLedPattern(npad_id) - .raw); + rb.Push(applet_resource->GetController(HidController::NPad) + .GetLedPattern(npad_id) + .raw); +} + +void Hid::ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) { + // Should have no effect with how our npad sets up the data + IPC::RequestParser rp{ctx}; + struct Parameters { + u32 unknown{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw()}; + + applet_resource->ActivateController(HidController::NPad); + + LOG_DEBUG(Service_HID, "called, unknown={}, applet_resource_user_id={}", parameters.unknown, + parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); } void Hid::SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop()}; - const auto hold_type{rp.Pop()}; + const auto hold_type{rp.PopEnum()}; + + applet_resource->GetController(HidController::NPad).SetHoldType(hold_type); LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, hold_type={}", applet_resource_user_id, hold_type); - auto& controller = applet_resource->GetController(HidController::NPad); - controller.SetHoldType(Controller_NPad::NpadHoldType{hold_type}); - IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -640,22 +792,26 @@ void Hid::GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - const auto& controller = applet_resource->GetController(HidController::NPad); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push(static_cast(controller.GetHoldType())); + rb.PushEnum(applet_resource->GetController(HidController::NPad).GetHoldType()); } void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop()}; - const auto applet_resource_user_id{rp.Pop()}; + struct Parameters { + u32 npad_id{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", npad_id, - applet_resource_user_id); + const auto parameters{rp.PopRaw()}; - auto& controller = applet_resource->GetController(HidController::NPad); - controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Single); + applet_resource->GetController(HidController::NPad) + .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Single); + + LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", + parameters.npad_id, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -664,16 +820,22 @@ void Hid::SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) { // TODO: Check the differences between this and SetNpadJoyAssignmentModeSingleByDefault IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop()}; - const auto applet_resource_user_id{rp.Pop()}; - const auto npad_joy_device_type{rp.Pop()}; + struct Parameters { + u32 npad_id{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + u64 npad_joy_device_type{}; + }; + + const auto parameters{rp.PopRaw()}; + + applet_resource->GetController(HidController::NPad) + .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Single); LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}", - npad_id, applet_resource_user_id, npad_joy_device_type); - - auto& controller = applet_resource->GetController(HidController::NPad); - controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Single); + parameters.npad_id, parameters.applet_resource_user_id, + parameters.npad_joy_device_type); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -681,14 +843,19 @@ void Hid::SetNpadJoyAssignmentModeSingle(Kernel::HLERequestContext& ctx) { void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_id{rp.Pop()}; - const auto applet_resource_user_id{rp.Pop()}; + struct Parameters { + u32 npad_id{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", npad_id, - applet_resource_user_id); + const auto parameters{rp.PopRaw()}; - auto& controller = applet_resource->GetController(HidController::NPad); - controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual); + applet_resource->GetController(HidController::NPad) + .SetNpadMode(parameters.npad_id, Controller_NPad::NpadAssignments::Dual); + + LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", + parameters.npad_id, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -700,12 +867,12 @@ void Hid::MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) { const auto npad_id_2{rp.Pop()}; const auto applet_resource_user_id{rp.Pop()}; + applet_resource->GetController(HidController::NPad) + .MergeSingleJoyAsDualJoy(npad_id_1, npad_id_2); + LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}", npad_id_1, npad_id_2, applet_resource_user_id); - auto& controller = applet_resource->GetController(HidController::NPad); - controller.MergeSingleJoyAsDualJoy(npad_id_1, npad_id_2); - IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -714,9 +881,9 @@ void Hid::StartLrAssignmentMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop()}; + applet_resource->GetController(HidController::NPad).StartLRAssignmentMode(); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - auto& controller = applet_resource->GetController(HidController::NPad); - controller.StartLRAssignmentMode(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -726,9 +893,9 @@ void Hid::StopLrAssignmentMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop()}; + applet_resource->GetController(HidController::NPad).StopLRAssignmentMode(); + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - auto& controller = applet_resource->GetController(HidController::NPad); - controller.StopLRAssignmentMode(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -737,13 +904,13 @@ void Hid::StopLrAssignmentMode(Kernel::HLERequestContext& ctx) { void Hid::SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop()}; - const auto mode{rp.Pop()}; - - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, mode={}", applet_resource_user_id, - mode); + const auto activation_mode{rp.PopEnum()}; applet_resource->GetController(HidController::NPad) - .SetNpadHandheldActivationMode(Controller_NPad::NpadHandheldActivationMode{mode}); + .SetNpadHandheldActivationMode(activation_mode); + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, activation_mode={}", + applet_resource_user_id, activation_mode); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -757,23 +924,24 @@ void Hid::GetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push( - static_cast(applet_resource->GetController(HidController::NPad) - .GetNpadHandheldActivationMode())); + rb.PushEnum(applet_resource->GetController(HidController::NPad) + .GetNpadHandheldActivationMode()); } void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto npad_1{rp.Pop()}; - const auto npad_2{rp.Pop()}; + const auto npad_id_1{rp.Pop()}; + const auto npad_id_2{rp.Pop()}; const auto applet_resource_user_id{rp.Pop()}; - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, npad_1={}, npad_2={}", - applet_resource_user_id, npad_1, npad_2); + const bool res = applet_resource->GetController(HidController::NPad) + .SwapNpadAssignment(npad_id_1, npad_id_2); + + LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}", + npad_id_1, npad_id_2, applet_resource_user_id); - auto& controller = applet_resource->GetController(HidController::NPad); IPC::ResponseBuilder rb{ctx, 2}; - if (controller.SwapNpadAssignment(npad_1, npad_2)) { + if (res) { rb.Push(RESULT_SUCCESS); } else { LOG_ERROR(Service_HID, "Npads are not connected!"); @@ -781,61 +949,99 @@ void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) { } } -void Hid::BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) { +void Hid::IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop()}; + struct Parameters { + u32 npad_id{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + const auto parameters{rp.PopRaw()}; + + LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", + parameters.npad_id, parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(applet_resource->GetController(HidController::NPad) + .IsUnintendedHomeButtonInputProtectionEnabled(parameters.npad_id)); +} + +void Hid::EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + bool unintended_home_button_input_protection{}; + INSERT_PADDING_BYTES(3); + u32 npad_id{}; + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw()}; + + applet_resource->GetController(HidController::NPad) + .SetUnintendedHomeButtonInputProtectionEnabled( + parameters.unintended_home_button_input_protection, parameters.npad_id); + + LOG_WARNING(Service_HID, + "(STUBBED) called, unintended_home_button_input_protection={}, npad_id={}," + "applet_resource_user_id={}", + parameters.unintended_home_button_input_protection, parameters.npad_id, + parameters.applet_resource_user_id); - applet_resource->GetController(HidController::NPad).SetVibrationEnabled(true); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } -void Hid::EndPermitVibrationSession(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_HID, "called"); +void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto vibration_device_handle{rp.PopRaw()}; - applet_resource->GetController(HidController::NPad).SetVibrationEnabled(false); - IPC::ResponseBuilder rb{ctx, 2}; + VibrationDeviceInfo vibration_device_info; + + vibration_device_info.type = VibrationDeviceType::LinearResonantActuator; + + switch (vibration_device_handle.device_index) { + case Controller_NPad::DeviceIndex::Left: + vibration_device_info.position = VibrationDevicePosition::Left; + break; + case Controller_NPad::DeviceIndex::Right: + vibration_device_info.position = VibrationDevicePosition::Right; + break; + case Controller_NPad::DeviceIndex::None: + default: + UNREACHABLE_MSG("DeviceIndex should never be None!"); + vibration_device_info.position = VibrationDevicePosition::None; + break; + } + + LOG_DEBUG(Service_HID, "called, vibration_device_type={}, vibration_device_position={}", + vibration_device_info.type, vibration_device_info.position); + + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); + rb.PushRaw(vibration_device_info); } void Hid::SendVibrationValue(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto controller_id{rp.Pop()}; - const auto vibration_values{rp.PopRaw()}; - const auto applet_resource_user_id{rp.Pop()}; + struct Parameters { + Controller_NPad::DeviceHandle vibration_device_handle{}; + Controller_NPad::VibrationValue vibration_value{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}", controller_id, - applet_resource_user_id); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); + const auto parameters{rp.PopRaw()}; applet_resource->GetController(HidController::NPad) - .VibrateController({controller_id}, {vibration_values}); -} + .VibrateController(parameters.vibration_device_handle, parameters.vibration_value); -void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto applet_resource_user_id{rp.Pop()}; - - LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); - - const auto controllers = ctx.ReadBuffer(0); - const auto vibrations = ctx.ReadBuffer(1); - - std::vector controller_list(controllers.size() / sizeof(u32)); - std::vector vibration_list(vibrations.size() / - sizeof(Controller_NPad::Vibration)); - - std::memcpy(controller_list.data(), controllers.data(), controllers.size()); - std::memcpy(vibration_list.data(), vibrations.data(), vibrations.size()); - std::transform(controller_list.begin(), controller_list.end(), controller_list.begin(), - [](u32 controller_id) { return controller_id - 3; }); - - applet_resource->GetController(HidController::NPad) - .VibrateController(controller_list, vibration_list); + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.vibration_device_handle.npad_type, + parameters.vibration_device_handle.npad_id, + parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -843,25 +1049,24 @@ void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) { void Hid::GetActualVibrationValue(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto controller_id{rp.Pop()}; - const auto applet_resource_user_id{rp.Pop()}; + struct Parameters { + Controller_NPad::DeviceHandle vibration_device_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_DEBUG(Service_HID, "called, controller_id={}, applet_resource_user_id={}", controller_id, - applet_resource_user_id); + const auto parameters{rp.PopRaw()}; + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.vibration_device_handle.npad_type, + parameters.vibration_device_handle.npad_id, + parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 6}; rb.Push(RESULT_SUCCESS); - rb.PushRaw( - applet_resource->GetController(HidController::NPad).GetLastVibration()); -} - -void Hid::GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_HID, "called"); - - IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(RESULT_SUCCESS); - rb.Push(1); - rb.Push(0); + rb.PushRaw(applet_resource->GetController(HidController::NPad) + .GetLastVibration(parameters.vibration_device_handle)); } void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) { @@ -869,13 +1074,14 @@ void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system, applet_resource); } void Hid::PermitVibration(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto can_vibrate{rp.Pop()}; - Settings::values.vibration_enabled = can_vibrate; + + Settings::values.vibration_enabled.SetValue(can_vibrate); LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate); @@ -888,7 +1094,76 @@ void Hid::IsVibrationPermitted(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(Settings::values.vibration_enabled); + rb.Push(Settings::values.vibration_enabled.GetValue()); +} + +void Hid::SendVibrationValues(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop()}; + + const auto handles = ctx.ReadBuffer(0); + const auto vibrations = ctx.ReadBuffer(1); + + std::vector vibration_device_handles( + handles.size() / sizeof(Controller_NPad::DeviceHandle)); + std::vector vibration_values( + vibrations.size() / sizeof(Controller_NPad::VibrationValue)); + + std::memcpy(vibration_device_handles.data(), handles.data(), handles.size()); + std::memcpy(vibration_values.data(), vibrations.data(), vibrations.size()); + + applet_resource->GetController(HidController::NPad) + .VibrateControllers(vibration_device_handles, vibration_values); + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop()}; + + applet_resource->GetController(HidController::NPad) + .SetPermitVibrationSession(true); + + LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::EndPermitVibrationSession(Kernel::HLERequestContext& ctx) { + applet_resource->GetController(HidController::NPad) + .SetPermitVibrationSession(false); + + LOG_DEBUG(Service_HID, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::IsVibrationDeviceMounted(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + struct Parameters { + Controller_NPad::DeviceHandle vibration_device_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; + + const auto parameters{rp.PopRaw()}; + + LOG_DEBUG(Service_HID, + "called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.vibration_device_handle.npad_type, + parameters.vibration_device_handle.npad_id, + parameters.vibration_device_handle.device_index, parameters.applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(applet_resource->GetController(HidController::NPad) + .IsVibrationDeviceMounted(parameters.vibration_device_handle)); } void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { @@ -904,11 +1179,19 @@ void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop()}; - const auto applet_resource_user_id{rp.Pop()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + const auto parameters{rp.PopRaw()}; + + LOG_WARNING( + Service_HID, + "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -916,11 +1199,19 @@ void Hid::StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { void Hid::StopConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto handle{rp.Pop()}; - const auto applet_resource_user_id{rp.Pop()}; + struct Parameters { + Controller_NPad::DeviceHandle sixaxis_handle{}; + INSERT_PADDING_WORDS(1); + u64 applet_resource_user_id{}; + }; - LOG_WARNING(Service_HID, "(STUBBED) called, handle={}, applet_resource_user_id={}", handle, - applet_resource_user_id); + const auto parameters{rp.PopRaw()}; + + LOG_WARNING( + Service_HID, + "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", + parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, + parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -1011,9 +1302,37 @@ void Hid::SetPalmaBoostMode(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } +void Hid::SetNpadCommunicationMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop()}; + const auto communication_mode{rp.PopEnum()}; + + applet_resource->GetController(HidController::NPad) + .SetNpadCommunicationMode(communication_mode); + + LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, communication_mode={}", + applet_resource_user_id, communication_mode); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +void Hid::GetNpadCommunicationMode(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_resource_user_id{rp.Pop()}; + + LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}", + applet_resource_user_id); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum(applet_resource->GetController(HidController::NPad) + .GetNpadCommunicationMode()); +} + class HidDbg final : public ServiceFramework { public: - explicit HidDbg() : ServiceFramework{"hid:dbg"} { + explicit HidDbg(Core::System& system_) : ServiceFramework{system_, "hid:dbg"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "DeactivateDebugPad"}, @@ -1140,7 +1459,7 @@ public: class HidSys final : public ServiceFramework { public: - explicit HidSys() : ServiceFramework{"hid:sys"} { + explicit HidSys(Core::System& system_) : ServiceFramework{system_, "hid:sys"} { // clang-format off static const FunctionInfo functions[] = { {31, nullptr, "SendKeyboardLockKeyEvent"}, @@ -1274,7 +1593,7 @@ public: class HidTmp final : public ServiceFramework { public: - explicit HidTmp() : ServiceFramework{"hid:tmp"} { + explicit HidTmp(Core::System& system_) : ServiceFramework{system_, "hid:tmp"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetConsoleSixAxisSensorCalibrationValues"}, @@ -1287,7 +1606,7 @@ public: class HidBus final : public ServiceFramework { public: - explicit HidBus() : ServiceFramework{"hidbus"} { + explicit HidBus(Core::System& system_) : ServiceFramework{system_, "hidbus"} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "GetBusHandle"}, @@ -1317,15 +1636,15 @@ void ReloadInputDevices() { void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { std::make_shared(system)->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); std::make_shared(system)->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); } } // namespace Service::HID diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index e04aaf1e9..b87bfdde1 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -41,7 +41,7 @@ enum class HidController : std::size_t { class IAppletResource final : public ServiceFramework { public: - explicit IAppletResource(Core::System& system); + explicit IAppletResource(Core::System& system_); ~IAppletResource() override; void ActivateController(HidController controller); @@ -65,11 +65,12 @@ private: void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx); void UpdateControllers(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); + void UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late); std::shared_ptr shared_mem; std::shared_ptr pad_update_event; - Core::System& system; + std::shared_ptr motion_update_event; std::array, static_cast(HidController::MaxControllers)> controllers{}; @@ -77,30 +78,30 @@ private: class Hid final : public ServiceFramework { public: - explicit Hid(Core::System& system); + explicit Hid(Core::System& system_); ~Hid() override; std::shared_ptr GetAppletResource(); private: void CreateAppletResource(Kernel::HLERequestContext& ctx); - void ActivateXpad(Kernel::HLERequestContext& ctx); - void GetXpadIDs(Kernel::HLERequestContext& ctx); - void ActivateSixAxisSensor(Kernel::HLERequestContext& ctx); - void DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx); void ActivateDebugPad(Kernel::HLERequestContext& ctx); void ActivateTouchScreen(Kernel::HLERequestContext& ctx); void ActivateMouse(Kernel::HLERequestContext& ctx); void ActivateKeyboard(Kernel::HLERequestContext& ctx); void SendKeyboardLockKeyEvent(Kernel::HLERequestContext& ctx); - void ActivateGesture(Kernel::HLERequestContext& ctx); - void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx); + void ActivateXpad(Kernel::HLERequestContext& ctx); + void GetXpadIDs(Kernel::HLERequestContext& ctx); + void ActivateSixAxisSensor(Kernel::HLERequestContext& ctx); + void DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx); void StartSixAxisSensor(Kernel::HLERequestContext& ctx); void StopSixAxisSensor(Kernel::HLERequestContext& ctx); + void EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx); void SetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx); void GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx); void ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx); void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx); + void ActivateGesture(Kernel::HLERequestContext& ctx); void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx); void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx); void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx); @@ -109,6 +110,7 @@ private: void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx); void DisconnectNpad(Kernel::HLERequestContext& ctx); void GetPlayerLedPattern(Kernel::HLERequestContext& ctx); + void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx); void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx); void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx); void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx); @@ -120,15 +122,18 @@ private: void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx); void GetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx); void SwapNpadAssignment(Kernel::HLERequestContext& ctx); - void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx); - void EndPermitVibrationSession(Kernel::HLERequestContext& ctx); - void SendVibrationValue(Kernel::HLERequestContext& ctx); - void SendVibrationValues(Kernel::HLERequestContext& ctx); - void GetActualVibrationValue(Kernel::HLERequestContext& ctx); + void IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext& ctx); + void EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& ctx); void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx); + void SendVibrationValue(Kernel::HLERequestContext& ctx); + void GetActualVibrationValue(Kernel::HLERequestContext& ctx); void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx); void PermitVibration(Kernel::HLERequestContext& ctx); void IsVibrationPermitted(Kernel::HLERequestContext& ctx); + void SendVibrationValues(Kernel::HLERequestContext& ctx); + void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx); + void EndPermitVibrationSession(Kernel::HLERequestContext& ctx); + void IsVibrationDeviceMounted(Kernel::HLERequestContext& ctx); void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); void StopConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); @@ -140,9 +145,26 @@ private: void ResetSevenSixAxisSensorTimestamp(Kernel::HLERequestContext& ctx); void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx); void SetPalmaBoostMode(Kernel::HLERequestContext& ctx); + void SetNpadCommunicationMode(Kernel::HLERequestContext& ctx); + void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx); + + enum class VibrationDeviceType : u32 { + LinearResonantActuator = 1, + }; + + enum class VibrationDevicePosition : u32 { + None = 0, + Left = 1, + Right = 2, + }; + + struct VibrationDeviceInfo { + VibrationDeviceType type{}; + VibrationDevicePosition position{}; + }; + static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incorrect size."); std::shared_ptr applet_resource; - Core::System& system; }; /// Reload input devices. Used when input configuration changed diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp index e82fd031b..c8413099f 100644 --- a/src/core/hle/service/hid/irs.cpp +++ b/src/core/hle/service/hid/irs.cpp @@ -12,7 +12,7 @@ namespace Service::HID { -IRS::IRS(Core::System& system) : ServiceFramework{"irs"}, system(system) { +IRS::IRS(Core::System& system_) : ServiceFramework{system_, "irs"} { // clang-format off static const FunctionInfo functions[] = { {302, &IRS::ActivateIrsensor, "ActivateIrsensor"}, @@ -175,7 +175,7 @@ void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) { IRS::~IRS() = default; -IRS_SYS::IRS_SYS() : ServiceFramework{"irs:sys"} { +IRS_SYS::IRS_SYS(Core::System& system_) : ServiceFramework{system_, "irs:sys"} { // clang-format off static const FunctionInfo functions[] = { {500, nullptr, "SetAppletResourceUserId"}, diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h index 8918ad6ca..be0c486ba 100644 --- a/src/core/hle/service/hid/irs.h +++ b/src/core/hle/service/hid/irs.h @@ -7,6 +7,10 @@ #include "core/hle/kernel/object.h" #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class SharedMemory; } @@ -15,7 +19,7 @@ namespace Service::HID { class IRS final : public ServiceFramework { public: - explicit IRS(Core::System& system); + explicit IRS(Core::System& system_); ~IRS() override; private: @@ -37,14 +41,14 @@ private: void RunIrLedProcessor(Kernel::HLERequestContext& ctx); void StopImageProcessorAsync(Kernel::HLERequestContext& ctx); void ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx); + std::shared_ptr shared_mem; const u32 device_handle{0xABCD}; - Core::System& system; }; class IRS_SYS final : public ServiceFramework { public: - explicit IRS_SYS(); + explicit IRS_SYS(Core::System& system); ~IRS_SYS() override; }; diff --git a/src/core/hle/service/hid/xcd.cpp b/src/core/hle/service/hid/xcd.cpp index c8e9125f6..43a8840d0 100644 --- a/src/core/hle/service/hid/xcd.cpp +++ b/src/core/hle/service/hid/xcd.cpp @@ -6,7 +6,7 @@ namespace Service::HID { -XCD_SYS::XCD_SYS() : ServiceFramework{"xcd:sys"} { +XCD_SYS::XCD_SYS(Core::System& system_) : ServiceFramework{system_, "xcd:sys"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetDataFormat"}, diff --git a/src/core/hle/service/hid/xcd.h b/src/core/hle/service/hid/xcd.h index fd506d303..54932c228 100644 --- a/src/core/hle/service/hid/xcd.h +++ b/src/core/hle/service/hid/xcd.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::HID { class XCD_SYS final : public ServiceFramework { public: - explicit XCD_SYS(); + explicit XCD_SYS(Core::System& system_); ~XCD_SYS() override; }; diff --git a/src/core/hle/service/lbl/lbl.cpp b/src/core/hle/service/lbl/lbl.cpp index 17350b403..6ad3a2877 100644 --- a/src/core/hle/service/lbl/lbl.cpp +++ b/src/core/hle/service/lbl/lbl.cpp @@ -15,7 +15,7 @@ namespace Service::LBL { class LBL final : public ServiceFramework { public: - explicit LBL() : ServiceFramework{"lbl"} { + explicit LBL(Core::System& system_) : ServiceFramework{system_, "lbl"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SaveCurrentSetting"}, @@ -84,8 +84,8 @@ private: bool vr_mode_enabled = false; }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared(system)->InstallAsService(sm); } } // namespace Service::LBL diff --git a/src/core/hle/service/lbl/lbl.h b/src/core/hle/service/lbl/lbl.h index bf6f400f8..9c2021026 100644 --- a/src/core/hle/service/lbl/lbl.h +++ b/src/core/hle/service/lbl/lbl.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::LBL { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::LBL diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp index 49972cd69..ee908f399 100644 --- a/src/core/hle/service/ldn/ldn.cpp +++ b/src/core/hle/service/ldn/ldn.cpp @@ -13,7 +13,7 @@ namespace Service::LDN { class IMonitorService final : public ServiceFramework { public: - explicit IMonitorService() : ServiceFramework{"IMonitorService"} { + explicit IMonitorService(Core::System& system_) : ServiceFramework{system_, "IMonitorService"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetStateForMonitor"}, @@ -33,7 +33,7 @@ public: class LDNM final : public ServiceFramework { public: - explicit LDNM() : ServiceFramework{"ldn:m"} { + explicit LDNM(Core::System& system_) : ServiceFramework{system_, "ldn:m"} { // clang-format off static const FunctionInfo functions[] = { {0, &LDNM::CreateMonitorService, "CreateMonitorService"} @@ -48,15 +48,15 @@ public: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } }; class ISystemLocalCommunicationService final : public ServiceFramework { public: - explicit ISystemLocalCommunicationService() - : ServiceFramework{"ISystemLocalCommunicationService"} { + explicit ISystemLocalCommunicationService(Core::System& system_) + : ServiceFramework{system_, "ISystemLocalCommunicationService"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetState"}, @@ -99,7 +99,8 @@ public: class IUserLocalCommunicationService final : public ServiceFramework { public: - explicit IUserLocalCommunicationService() : ServiceFramework{"IUserLocalCommunicationService"} { + explicit IUserLocalCommunicationService(Core::System& system_) + : ServiceFramework{system_, "IUserLocalCommunicationService"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetState"}, @@ -148,7 +149,7 @@ public: class LDNS final : public ServiceFramework { public: - explicit LDNS() : ServiceFramework{"ldn:s"} { + explicit LDNS(Core::System& system_) : ServiceFramework{system_, "ldn:s"} { // clang-format off static const FunctionInfo functions[] = { {0, &LDNS::CreateSystemLocalCommunicationService, "CreateSystemLocalCommunicationService"}, @@ -163,13 +164,13 @@ public: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } }; class LDNU final : public ServiceFramework { public: - explicit LDNU() : ServiceFramework{"ldn:u"} { + explicit LDNU(Core::System& system_) : ServiceFramework{system_, "ldn:u"} { // clang-format off static const FunctionInfo functions[] = { {0, &LDNU::CreateUserLocalCommunicationService, "CreateUserLocalCommunicationService"}, @@ -184,14 +185,14 @@ public: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); } } // namespace Service::LDN diff --git a/src/core/hle/service/ldn/ldn.h b/src/core/hle/service/ldn/ldn.h index 6b2a3c2b2..3ccd9738b 100644 --- a/src/core/hle/service/ldn/ldn.h +++ b/src/core/hle/service/ldn/ldn.h @@ -4,6 +4,10 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } @@ -11,6 +15,6 @@ class ServiceManager; namespace Service::LDN { /// Registers all LDN services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::LDN diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index d8cd10e31..9da786b4e 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -9,6 +9,7 @@ #include "common/alignment.h" #include "common/hex_util.h" #include "common/scope_exit.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/memory/page_table.h" @@ -23,7 +24,7 @@ namespace Service::LDR { constexpr ResultCode ERROR_INSUFFICIENT_ADDRESS_SPACE{ErrorModule::RO, 2}; -constexpr ResultCode ERROR_INVALID_MEMORY_STATE{ErrorModule::Loader, 51}; +[[maybe_unused]] constexpr ResultCode ERROR_INVALID_MEMORY_STATE{ErrorModule::Loader, 51}; constexpr ResultCode ERROR_INVALID_NRO{ErrorModule::Loader, 52}; constexpr ResultCode ERROR_INVALID_NRR{ErrorModule::Loader, 53}; constexpr ResultCode ERROR_MISSING_NRR_HASH{ErrorModule::Loader, 54}; @@ -33,7 +34,7 @@ constexpr ResultCode ERROR_ALREADY_LOADED{ErrorModule::Loader, 57}; constexpr ResultCode ERROR_INVALID_ALIGNMENT{ErrorModule::Loader, 81}; constexpr ResultCode ERROR_INVALID_SIZE{ErrorModule::Loader, 82}; constexpr ResultCode ERROR_INVALID_NRO_ADDRESS{ErrorModule::Loader, 84}; -constexpr ResultCode ERROR_INVALID_NRR_ADDRESS{ErrorModule::Loader, 85}; +[[maybe_unused]] constexpr ResultCode ERROR_INVALID_NRR_ADDRESS{ErrorModule::Loader, 85}; constexpr ResultCode ERROR_NOT_INITIALIZED{ErrorModule::Loader, 87}; constexpr std::size_t MAXIMUM_LOADED_RO{0x40}; @@ -114,7 +115,7 @@ static_assert(sizeof(NROInfo) == 0x60, "NROInfo has invalid size."); class DebugMonitor final : public ServiceFramework { public: - explicit DebugMonitor() : ServiceFramework{"ldr:dmnt"} { + explicit DebugMonitor(Core::System& system_) : ServiceFramework{system_, "ldr:dmnt"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "AddProcessToDebugLaunchQueue"}, @@ -129,7 +130,7 @@ public: class ProcessManager final : public ServiceFramework { public: - explicit ProcessManager() : ServiceFramework{"ldr:pm"} { + explicit ProcessManager(Core::System& system_) : ServiceFramework{system_, "ldr:pm"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CreateProcess"}, @@ -146,7 +147,7 @@ public: class Shell final : public ServiceFramework { public: - explicit Shell() : ServiceFramework{"ldr:shel"} { + explicit Shell(Core::System& system_) : ServiceFramework{system_, "ldr:shel"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "AddProcessToLaunchQueue"}, @@ -160,13 +161,13 @@ public: class RelocatableObject final : public ServiceFramework { public: - explicit RelocatableObject(Core::System& system) : ServiceFramework{"ldr:ro"}, system(system) { + explicit RelocatableObject(Core::System& system_) : ServiceFramework{system_, "ldr:ro"} { // clang-format off static const FunctionInfo functions[] = { {0, &RelocatableObject::LoadNro, "LoadNro"}, {1, &RelocatableObject::UnloadNro, "UnloadNro"}, {2, &RelocatableObject::LoadNrr, "LoadNrr"}, - {3, nullptr, "UnloadNrr"}, + {3, &RelocatableObject::UnloadNrr, "UnloadNrr"}, {4, &RelocatableObject::Initialize, "Initialize"}, {10, nullptr, "LoadNrrEx"}, }; @@ -272,6 +273,20 @@ public: rb.Push(RESULT_SUCCESS); } + void UnloadNrr(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto pid = rp.Pop(); + const auto nrr_address = rp.Pop(); + + LOG_DEBUG(Service_LDR, "called with pid={}, nrr_address={:016X}", pid, nrr_address); + + nrr.erase(nrr_address); + + IPC::ResponseBuilder rb{ctx, 2}; + + rb.Push(RESULT_SUCCESS); + } + bool ValidateRegionForMap(Kernel::Memory::PageTable& page_table, VAddr start, std::size_t size) const { constexpr std::size_t padding_size{4 * Kernel::Memory::PageSize}; @@ -512,9 +527,6 @@ public: header.segment_headers[RO_INDEX].memory_size, header.segment_headers[DATA_INDEX].memory_size, nro_address}); - // Invalidate JIT caches for the newly mapped process code - system.InvalidateCpuInstructionCaches(); - IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push(*map_result); @@ -575,8 +587,6 @@ public: const auto result{UnmapNro(iter->second)}; - system.InvalidateCpuInstructionCaches(); - nro.erase(iter); IPC::ResponseBuilder rb{ctx, 2}; @@ -624,13 +634,12 @@ private: Common::Is4KBAligned(header.segment_headers[RO_INDEX].memory_size) && Common::Is4KBAligned(header.segment_headers[DATA_INDEX].memory_size); } - Core::System& system; }; void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); std::make_shared(system)->InstallAsService(sm); } diff --git a/src/core/hle/service/ldr/ldr.h b/src/core/hle/service/ldr/ldr.h index 7ac8c0b65..104fc15c5 100644 --- a/src/core/hle/service/ldr/ldr.h +++ b/src/core/hle/service/ldr/ldr.h @@ -4,6 +4,10 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp index dec96b771..8e49b068c 100644 --- a/src/core/hle/service/lm/lm.cpp +++ b/src/core/hle/service/lm/lm.cpp @@ -7,6 +7,7 @@ #include "common/logging/log.h" #include "common/scope_exit.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/service/lm/lm.h" #include "core/hle/service/lm/manager.h" @@ -17,8 +18,9 @@ namespace Service::LM { class ILogger final : public ServiceFramework { public: - explicit ILogger(Manager& manager_, Core::Memory::Memory& memory_) - : ServiceFramework("ILogger"), manager{manager_}, memory{memory_} { + explicit ILogger(Core::System& system_) + : ServiceFramework{system_, "ILogger"}, manager{system_.GetLogManager()}, + memory{system_.Memory()} { static const FunctionInfo functions[] = { {0, &ILogger::Log, "Log"}, {1, &ILogger::SetDestination, "SetDestination"}, @@ -66,7 +68,7 @@ private: IPC::RequestParser rp{ctx}; const auto destination = rp.PopEnum(); - LOG_DEBUG(Service_LM, "called, destination={:08X}", static_cast(destination)); + LOG_DEBUG(Service_LM, "called, destination={:08X}", destination); manager.SetDestination(destination); @@ -80,8 +82,7 @@ private: class LM final : public ServiceFramework { public: - explicit LM(Manager& manager_, Core::Memory::Memory& memory_) - : ServiceFramework{"lm"}, manager{manager_}, memory{memory_} { + explicit LM(Core::System& system_) : ServiceFramework{system_, "lm"} { // clang-format off static const FunctionInfo functions[] = { {0, &LM::OpenLogger, "OpenLogger"}, @@ -97,16 +98,12 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(manager, memory); + rb.PushIpcInterface(system); } - - Manager& manager; - Core::Memory::Memory& memory; }; void InstallInterfaces(Core::System& system) { - std::make_shared(system.GetLogManager(), system.Memory()) - ->InstallAsService(system.ServiceManager()); + std::make_shared(system)->InstallAsService(system.ServiceManager()); } } // namespace Service::LM diff --git a/src/core/hle/service/mig/mig.cpp b/src/core/hle/service/mig/mig.cpp index 113a4665c..1599d941b 100644 --- a/src/core/hle/service/mig/mig.cpp +++ b/src/core/hle/service/mig/mig.cpp @@ -12,7 +12,7 @@ namespace Service::Migration { class MIG_USR final : public ServiceFramework { public: - explicit MIG_USR() : ServiceFramework{"mig:usr"} { + explicit MIG_USR(Core::System& system_) : ServiceFramework{system_, "mig:usr"} { // clang-format off static const FunctionInfo functions[] = { {10, nullptr, "TryGetLastMigrationInfo"}, @@ -33,8 +33,8 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared(system)->InstallAsService(sm); } } // namespace Service::Migration diff --git a/src/core/hle/service/mig/mig.h b/src/core/hle/service/mig/mig.h index 288c1c1b3..2b24cdf2c 100644 --- a/src/core/hle/service/mig/mig.h +++ b/src/core/hle/service/mig/mig.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::Migration { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::Migration diff --git a/src/core/hle/service/mii/manager.cpp b/src/core/hle/service/mii/manager.cpp index 4730070cb..d73b90015 100644 --- a/src/core/hle/service/mii/manager.cpp +++ b/src/core/hle/service/mii/manager.cpp @@ -131,7 +131,7 @@ template T GetRandomValue(T min, T max) { std::random_device device; std::mt19937 gen(device()); - std::uniform_int_distribution distribution(0, static_cast(max)); + std::uniform_int_distribution distribution(static_cast(min), static_cast(max)); return static_cast(distribution(gen)); } @@ -428,7 +428,7 @@ bool MiiManager::IsFullDatabase() const { } u32 MiiManager::GetCount(SourceFlag source_flag) const { - u32 count{}; + std::size_t count{}; if ((source_flag & SourceFlag::Database) != SourceFlag::None) { // TODO(bunnei): We don't implement the Mii database, but when we do, update this count += 0; @@ -436,7 +436,7 @@ u32 MiiManager::GetCount(SourceFlag source_flag) const { if ((source_flag & SourceFlag::Default) != SourceFlag::None) { count += DefaultMiiCount; } - return count; + return static_cast(count); } ResultVal MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info, diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp index b81bf6277..26be9e45b 100644 --- a/src/core/hle/service/mii/mii.cpp +++ b/src/core/hle/service/mii/mii.cpp @@ -18,7 +18,8 @@ constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1}; class IDatabaseService final : public ServiceFramework { public: - explicit IDatabaseService() : ServiceFramework{"IDatabaseService"} { + explicit IDatabaseService(Core::System& system_) + : ServiceFramework{system_, "IDatabaseService"} { // clang-format off static const FunctionInfo functions[] = { {0, &IDatabaseService::IsUpdated, "IsUpdated"}, @@ -47,6 +48,7 @@ public: {23, nullptr, "Convert"}, {24, nullptr, "ConvertCoreDataToCharInfo"}, {25, nullptr, "ConvertCharInfoToCoreData"}, + {26, nullptr, "Append"}, }; // clang-format on @@ -251,7 +253,8 @@ private: class MiiDBModule final : public ServiceFramework { public: - explicit MiiDBModule(const char* name) : ServiceFramework{name} { + explicit MiiDBModule(Core::System& system_, const char* name) + : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"}, @@ -265,7 +268,7 @@ private: void GetDatabaseService(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); LOG_DEBUG(Service_Mii, "called"); } @@ -273,7 +276,7 @@ private: class MiiImg final : public ServiceFramework { public: - explicit MiiImg() : ServiceFramework{"miiimg"} { + explicit MiiImg(Core::System& system_) : ServiceFramework{system_, "miiimg"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -297,11 +300,11 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared("mii:e")->InstallAsService(sm); - std::make_shared("mii:u")->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared(system, "mii:e")->InstallAsService(sm); + std::make_shared(system, "mii:u")->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); } } // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii.h b/src/core/hle/service/mii/mii.h index 7ce9be50e..9d3238e72 100644 --- a/src/core/hle/service/mii/mii.h +++ b/src/core/hle/service/mii/mii.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::Mii { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::Mii diff --git a/src/core/hle/service/mm/mm_u.cpp b/src/core/hle/service/mm/mm_u.cpp index 25c24e537..b0cb07d24 100644 --- a/src/core/hle/service/mm/mm_u.cpp +++ b/src/core/hle/service/mm/mm_u.cpp @@ -6,12 +6,13 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_session.h" #include "core/hle/service/mm/mm_u.h" +#include "core/hle/service/sm/sm.h" namespace Service::MM { class MM_U final : public ServiceFramework { public: - explicit MM_U() : ServiceFramework{"mm:u"} { + explicit MM_U(Core::System& system_) : ServiceFramework{system_, "mm:u"} { // clang-format off static const FunctionInfo functions[] = { {0, &MM_U::InitializeOld, "InitializeOld"}, @@ -104,8 +105,8 @@ private: u32 id{1}; }; -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared()->InstallAsService(service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + std::make_shared(system)->InstallAsService(service_manager); } } // namespace Service::MM diff --git a/src/core/hle/service/mm/mm_u.h b/src/core/hle/service/mm/mm_u.h index 5439fa653..49b6a3355 100644 --- a/src/core/hle/service/mm/mm_u.h +++ b/src/core/hle/service/mm/mm_u.h @@ -4,11 +4,17 @@ #pragma once -#include "core/hle/service/service.h" +namespace Core { +class System; +} + +namespace Service::SM { +class ServiceManager; +} namespace Service::MM { /// Registers all MM services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::MM diff --git a/src/core/hle/service/ncm/ncm.cpp b/src/core/hle/service/ncm/ncm.cpp index e38dea1f4..2dcda16f6 100644 --- a/src/core/hle/service/ncm/ncm.cpp +++ b/src/core/hle/service/ncm/ncm.cpp @@ -14,8 +14,8 @@ namespace Service::NCM { class ILocationResolver final : public ServiceFramework { public: - explicit ILocationResolver(FileSys::StorageId id) - : ServiceFramework{"ILocationResolver"}, storage(id) { + explicit ILocationResolver(Core::System& system_, FileSys::StorageId id) + : ServiceFramework{system_, "ILocationResolver"}, storage{id} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "ResolveProgramPath"}, @@ -45,12 +45,13 @@ public: } private: - FileSys::StorageId storage; + [[maybe_unused]] FileSys::StorageId storage; }; class IRegisteredLocationResolver final : public ServiceFramework { public: - explicit IRegisteredLocationResolver() : ServiceFramework{"IRegisteredLocationResolver"} { + explicit IRegisteredLocationResolver(Core::System& system_) + : ServiceFramework{system_, "IRegisteredLocationResolver"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "ResolveProgramPath"}, @@ -72,7 +73,8 @@ public: class IAddOnContentLocationResolver final : public ServiceFramework { public: - explicit IAddOnContentLocationResolver() : ServiceFramework{"IAddOnContentLocationResolver"} { + explicit IAddOnContentLocationResolver(Core::System& system_) + : ServiceFramework{system_, "IAddOnContentLocationResolver"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "ResolveAddOnContentPath"}, @@ -89,7 +91,7 @@ public: class LR final : public ServiceFramework { public: - explicit LR() : ServiceFramework{"lr"} { + explicit LR(Core::System& system_) : ServiceFramework{system_, "lr"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "OpenLocationResolver"}, @@ -105,7 +107,7 @@ public: class NCM final : public ServiceFramework { public: - explicit NCM() : ServiceFramework{"ncm"} { + explicit NCM(Core::System& system_) : ServiceFramework{system_, "ncm"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CreateContentStorage"}, @@ -130,9 +132,9 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); } } // namespace Service::NCM diff --git a/src/core/hle/service/ncm/ncm.h b/src/core/hle/service/ncm/ncm.h index 7bc8518a6..ee01eddc0 100644 --- a/src/core/hle/service/ncm/ncm.h +++ b/src/core/hle/service/ncm/ncm.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::NCM { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::NCM diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp index 780ea30fe..6ab35de47 100644 --- a/src/core/hle/service/nfc/nfc.cpp +++ b/src/core/hle/service/nfc/nfc.cpp @@ -16,7 +16,7 @@ namespace Service::NFC { class IAm final : public ServiceFramework { public: - explicit IAm() : ServiceFramework{"NFC::IAm"} { + explicit IAm(Core::System& system_) : ServiceFramework{system_, "NFC::IAm"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -31,7 +31,7 @@ public: class NFC_AM final : public ServiceFramework { public: - explicit NFC_AM() : ServiceFramework{"nfc:am"} { + explicit NFC_AM(Core::System& system_) : ServiceFramework{system_, "nfc:am"} { // clang-format off static const FunctionInfo functions[] = { {0, &NFC_AM::CreateAmInterface, "CreateAmInterface"}, @@ -47,13 +47,13 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } }; class MFIUser final : public ServiceFramework { public: - explicit MFIUser() : ServiceFramework{"NFC::MFIUser"} { + explicit MFIUser(Core::System& system_) : ServiceFramework{system_, "NFC::MFIUser"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -79,7 +79,7 @@ public: class NFC_MF_U final : public ServiceFramework { public: - explicit NFC_MF_U() : ServiceFramework{"nfc:mf:u"} { + explicit NFC_MF_U(Core::System& system_) : ServiceFramework{system_, "nfc:mf:u"} { // clang-format off static const FunctionInfo functions[] = { {0, &NFC_MF_U::CreateUserInterface, "CreateUserInterface"}, @@ -95,13 +95,13 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } }; class IUser final : public ServiceFramework { public: - explicit IUser() : ServiceFramework{"NFC::IUser"} { + explicit IUser(Core::System& system_) : ServiceFramework{system_, "NFC::IUser"} { // clang-format off static const FunctionInfo functions[] = { {0, &IUser::InitializeOld, "InitializeOld"}, @@ -171,7 +171,7 @@ private: class NFC_U final : public ServiceFramework { public: - explicit NFC_U() : ServiceFramework{"nfc:user"} { + explicit NFC_U(Core::System& system_) : ServiceFramework{system_, "nfc:user"} { // clang-format off static const FunctionInfo functions[] = { {0, &NFC_U::CreateUserInterface, "CreateUserInterface"}, @@ -187,13 +187,13 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } }; class ISystem final : public ServiceFramework { public: - explicit ISystem() : ServiceFramework{"ISystem"} { + explicit ISystem(Core::System& system_) : ServiceFramework{system_, "ISystem"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -230,7 +230,7 @@ public: class NFC_SYS final : public ServiceFramework { public: - explicit NFC_SYS() : ServiceFramework{"nfc:sys"} { + explicit NFC_SYS(Core::System& system_) : ServiceFramework{system_, "nfc:sys"} { // clang-format off static const FunctionInfo functions[] = { {0, &NFC_SYS::CreateSystemInterface, "CreateSystemInterface"}, @@ -246,15 +246,15 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); } } // namespace Service::NFC diff --git a/src/core/hle/service/nfc/nfc.h b/src/core/hle/service/nfc/nfc.h index 4d2d815f9..5a94b076d 100644 --- a/src/core/hle/service/nfc/nfc.h +++ b/src/core/hle/service/nfc/nfc.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::NFC { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::NFC diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index a0469ffbd..5557da72e 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -21,8 +21,9 @@ namespace ErrCodes { constexpr ResultCode ERR_NO_APPLICATION_AREA(ErrorModule::NFP, 152); } // namespace ErrCodes -Module::Interface::Interface(std::shared_ptr module, Core::System& system, const char* name) - : ServiceFramework(name), module(std::move(module)), system(system) { +Module::Interface::Interface(std::shared_ptr module_, Core::System& system_, + const char* name) + : ServiceFramework{system_, name}, module{std::move(module_)} { auto& kernel = system.Kernel(); nfc_tag_load = Kernel::WritableEvent::CreateEventPair(kernel, "IUser:NFCTagDetected"); } @@ -31,8 +32,8 @@ Module::Interface::~Interface() = default; class IUser final : public ServiceFramework { public: - IUser(Module::Interface& nfp_interface, Core::System& system) - : ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface) { + explicit IUser(Module::Interface& nfp_interface_, Core::System& system_) + : ServiceFramework{system_, "NFP::IUser"}, nfp_interface{nfp_interface_} { static const FunctionInfo functions[] = { {0, &IUser::Initialize, "Initialize"}, {1, &IUser::Finalize, "Finalize"}, diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h index 200013795..295de535b 100644 --- a/src/core/hle/service/nfp/nfp.h +++ b/src/core/hle/service/nfp/nfp.h @@ -16,7 +16,8 @@ class Module final { public: class Interface : public ServiceFramework { public: - explicit Interface(std::shared_ptr module, Core::System& system, const char* name); + explicit Interface(std::shared_ptr module_, Core::System& system_, + const char* name); ~Interface() override; struct ModelInfo { @@ -43,7 +44,6 @@ public: protected: std::shared_ptr module; - Core::System& system; }; }; diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp index 298184f17..10b0ef944 100644 --- a/src/core/hle/service/nfp/nfp_user.cpp +++ b/src/core/hle/service/nfp/nfp_user.cpp @@ -6,8 +6,8 @@ namespace Service::NFP { -NFP_User::NFP_User(std::shared_ptr module, Core::System& system) - : Module::Interface(std::move(module), system, "nfp:user") { +NFP_User::NFP_User(std::shared_ptr module_, Core::System& system_) + : Interface(std::move(module_), system_, "nfp:user") { static const FunctionInfo functions[] = { {0, &NFP_User::CreateUserInterface, "CreateUserInterface"}, }; diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h index 1686ebf20..7f3c124f6 100644 --- a/src/core/hle/service/nfp/nfp_user.h +++ b/src/core/hle/service/nfp/nfp_user.h @@ -10,7 +10,7 @@ namespace Service::NFP { class NFP_User final : public Module::Interface { public: - explicit NFP_User(std::shared_ptr module, Core::System& system); + explicit NFP_User(std::shared_ptr module_, Core::System& system_); ~NFP_User() override; }; diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index 2e9d95195..ef5176bea 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -23,7 +23,7 @@ enum class RequestState : u32 { class IScanRequest final : public ServiceFramework { public: - explicit IScanRequest() : ServiceFramework("IScanRequest") { + explicit IScanRequest(Core::System& system_) : ServiceFramework{system_, "IScanRequest"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Submit"}, @@ -40,7 +40,7 @@ public: class IRequest final : public ServiceFramework { public: - explicit IRequest(Core::System& system) : ServiceFramework("IRequest") { + explicit IRequest(Core::System& system_) : ServiceFramework{system_, "IRequest"} { static const FunctionInfo functions[] = { {0, &IRequest::GetRequestState, "GetRequestState"}, {1, &IRequest::GetResult, "GetResult"}, @@ -62,7 +62,7 @@ public: {18, nullptr, "SetRequirementByRevision"}, {19, nullptr, "GetRequirement"}, {20, nullptr, "GetRevision"}, - {21, nullptr, "GetAppletInfo"}, + {21, &IRequest::GetAppletInfo, "GetAppletInfo"}, {22, nullptr, "GetAdditionalInfo"}, {23, nullptr, "SetKeptInSleep"}, {24, nullptr, "RegisterSocketDescriptor"}, @@ -125,12 +125,22 @@ private: rb.Push(RESULT_SUCCESS); } + void GetAppletInfo(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 8}; + rb.Push(RESULT_SUCCESS); + rb.Push(0); + rb.Push(0); + rb.Push(0); + } + Kernel::EventPair event1, event2; }; class INetworkProfile final : public ServiceFramework { public: - explicit INetworkProfile() : ServiceFramework("INetworkProfile") { + explicit INetworkProfile(Core::System& system_) : ServiceFramework{system_, "INetworkProfile"} { static const FunctionInfo functions[] = { {0, nullptr, "Update"}, {1, nullptr, "PersistOld"}, @@ -142,7 +152,7 @@ public: class IGeneralService final : public ServiceFramework { public: - IGeneralService(Core::System& system); + explicit IGeneralService(Core::System& system_); private: void GetClientId(Kernel::HLERequestContext& ctx) { @@ -159,7 +169,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } void CreateRequest(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NIFM, "called"); @@ -197,7 +207,7 @@ private: IPC::ResponseBuilder rb{ctx, 6, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); rb.PushRaw(uuid); } void IsWirelessCommunicationEnabled(Kernel::HLERequestContext& ctx) { @@ -229,11 +239,10 @@ private: rb.Push(1); } } - Core::System& system; }; -IGeneralService::IGeneralService(Core::System& system) - : ServiceFramework("IGeneralService"), system(system) { +IGeneralService::IGeneralService(Core::System& system_) + : ServiceFramework{system_, "IGeneralService"} { // clang-format off static const FunctionInfo functions[] = { {1, &IGeneralService::GetClientId, "GetClientId"}, @@ -286,8 +295,8 @@ IGeneralService::IGeneralService(Core::System& system) class NetworkInterface final : public ServiceFramework { public: - explicit NetworkInterface(const char* name, Core::System& system) - : ServiceFramework{name}, system(system) { + explicit NetworkInterface(const char* name, Core::System& system_) + : ServiceFramework{system_, name} { static const FunctionInfo functions[] = { {4, &NetworkInterface::CreateGeneralServiceOld, "CreateGeneralServiceOld"}, {5, &NetworkInterface::CreateGeneralService, "CreateGeneralService"}, @@ -295,6 +304,7 @@ public: RegisterHandlers(functions); } +private: void CreateGeneralServiceOld(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NIFM, "called"); @@ -310,9 +320,6 @@ public: rb.Push(RESULT_SUCCESS); rb.PushIpcInterface(system); } - -private: - Core::System& system; }; void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h index 6857e18f9..c3dd4f386 100644 --- a/src/core/hle/service/nifm/nifm.h +++ b/src/core/hle/service/nifm/nifm.h @@ -4,14 +4,14 @@ #pragma once -namespace Service::SM { -class ServiceManager; -} - namespace Core { class System; } +namespace Service::SM { +class ServiceManager; +} + namespace Service::NIFM { /// Registers all NIFM services with the specified service manager. diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp index 11aa74828..d16223064 100644 --- a/src/core/hle/service/nim/nim.cpp +++ b/src/core/hle/service/nim/nim.cpp @@ -17,7 +17,8 @@ namespace Service::NIM { class IShopServiceAsync final : public ServiceFramework { public: - IShopServiceAsync() : ServiceFramework("IShopServiceAsync") { + explicit IShopServiceAsync(Core::System& system_) + : ServiceFramework{system_, "IShopServiceAsync"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Cancel"}, @@ -35,7 +36,8 @@ public: class IShopServiceAccessor final : public ServiceFramework { public: - IShopServiceAccessor() : ServiceFramework("IShopServiceAccessor") { + explicit IShopServiceAccessor(Core::System& system_) + : ServiceFramework{system_, "IShopServiceAccessor"} { // clang-format off static const FunctionInfo functions[] = { {0, &IShopServiceAccessor::CreateAsyncInterface, "CreateAsyncInterface"}, @@ -50,13 +52,14 @@ private: LOG_WARNING(Service_NIM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } }; class IShopServiceAccessServer final : public ServiceFramework { public: - IShopServiceAccessServer() : ServiceFramework("IShopServiceAccessServer") { + explicit IShopServiceAccessServer(Core::System& system_) + : ServiceFramework{system_, "IShopServiceAccessServer"} { // clang-format off static const FunctionInfo functions[] = { {0, &IShopServiceAccessServer::CreateAccessorInterface, "CreateAccessorInterface"}, @@ -71,13 +74,13 @@ private: LOG_WARNING(Service_NIM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } }; class NIM final : public ServiceFramework { public: - explicit NIM() : ServiceFramework{"nim"} { + explicit NIM(Core::System& system_) : ServiceFramework{system_, "nim"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CreateSystemUpdateTask"}, @@ -207,14 +210,14 @@ public: class NIM_ECA final : public ServiceFramework { public: - explicit NIM_ECA() : ServiceFramework{"nim:eca"} { + explicit NIM_ECA(Core::System& system_) : ServiceFramework{system_, "nim:eca"} { // clang-format off static const FunctionInfo functions[] = { {0, &NIM_ECA::CreateServerInterface, "CreateServerInterface"}, {1, nullptr, "RefreshDebugAvailability"}, {2, nullptr, "ClearDebugResponse"}, {3, nullptr, "RegisterDebugResponse"}, - {4, nullptr, "IsLargeResourceAvailable"}, + {4, &NIM_ECA::IsLargeResourceAvailable, "IsLargeResourceAvailable"}, }; // clang-format on @@ -226,13 +229,25 @@ private: LOG_WARNING(Service_NIM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); + } + + void IsLargeResourceAvailable(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto unknown{rp.Pop()}; + + LOG_INFO(Service_NIM, "(STUBBED) called, unknown={}", unknown); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(false); } }; class NIM_SHP final : public ServiceFramework { public: - explicit NIM_SHP() : ServiceFramework{"nim:shp"} { + explicit NIM_SHP(Core::System& system_) : ServiceFramework{system_, "nim:shp"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestDeviceAuthenticationToken"}, @@ -272,8 +287,8 @@ public: class IEnsureNetworkClockAvailabilityService final : public ServiceFramework { public: - explicit IEnsureNetworkClockAvailabilityService(Core::System& system) - : ServiceFramework("IEnsureNetworkClockAvailabilityService") { + explicit IEnsureNetworkClockAvailabilityService(Core::System& system_) + : ServiceFramework{system_, "IEnsureNetworkClockAvailabilityService"} { static const FunctionInfo functions[] = { {0, &IEnsureNetworkClockAvailabilityService::StartTask, "StartTask"}, {1, &IEnsureNetworkClockAvailabilityService::GetFinishNotificationEvent, @@ -345,7 +360,7 @@ private: class NTC final : public ServiceFramework { public: - explicit NTC(Core::System& system) : ServiceFramework{"ntc"}, system(system) { + explicit NTC(Core::System& system_) : ServiceFramework{system_, "ntc"} { // clang-format off static const FunctionInfo functions[] = { {0, &NTC::OpenEnsureNetworkClockAvailabilityService, "OpenEnsureNetworkClockAvailabilityService"}, @@ -380,13 +395,12 @@ private: IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } - Core::System& system; }; void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); std::make_shared(system)->InstallAsService(sm); } diff --git a/src/core/hle/service/nim/nim.h b/src/core/hle/service/nim/nim.h index dbe25dc01..571153fe6 100644 --- a/src/core/hle/service/nim/nim.h +++ b/src/core/hle/service/nim/nim.h @@ -4,14 +4,14 @@ #pragma once -namespace Service::SM { -class ServiceManager; -} - namespace Core { class System; } +namespace Service::SM { +class ServiceManager; +} + namespace Service::NIM { void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); diff --git a/src/core/hle/service/npns/npns.cpp b/src/core/hle/service/npns/npns.cpp index 8fa16fb08..f7a58f659 100644 --- a/src/core/hle/service/npns/npns.cpp +++ b/src/core/hle/service/npns/npns.cpp @@ -12,7 +12,7 @@ namespace Service::NPNS { class NPNS_S final : public ServiceFramework { public: - explicit NPNS_S() : ServiceFramework{"npns:s"} { + explicit NPNS_S(Core::System& system_) : ServiceFramework{system_, "npns:s"} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "ListenAll"}, @@ -62,7 +62,7 @@ public: class NPNS_U final : public ServiceFramework { public: - explicit NPNS_U() : ServiceFramework{"npns:u"} { + explicit NPNS_U(Core::System& system_) : ServiceFramework{system_, "npns:u"} { // clang-format off static const FunctionInfo functions[] = { {1, nullptr, "ListenAll"}, @@ -91,9 +91,9 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); } } // namespace Service::NPNS diff --git a/src/core/hle/service/npns/npns.h b/src/core/hle/service/npns/npns.h index 861cd3e48..3b7596b6b 100644 --- a/src/core/hle/service/npns/npns.h +++ b/src/core/hle/service/npns/npns.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::NPNS { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::NPNS diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp index 58ee1f712..6ccf8995c 100644 --- a/src/core/hle/service/ns/ns.cpp +++ b/src/core/hle/service/ns/ns.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include "common/logging/log.h" +#include "core/core.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/vfs.h" @@ -17,7 +18,8 @@ namespace Service::NS { -IAccountProxyInterface::IAccountProxyInterface() : ServiceFramework{"IAccountProxyInterface"} { +IAccountProxyInterface::IAccountProxyInterface(Core::System& system_) + : ServiceFramework{system_, "IAccountProxyInterface"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CreateUserAccount"}, @@ -29,8 +31,8 @@ IAccountProxyInterface::IAccountProxyInterface() : ServiceFramework{"IAccountPro IAccountProxyInterface::~IAccountProxyInterface() = default; -IApplicationManagerInterface::IApplicationManagerInterface() - : ServiceFramework{"IApplicationManagerInterface"} { +IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_) + : ServiceFramework{system_, "IApplicationManagerInterface"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "ListApplicationRecord"}, @@ -298,7 +300,8 @@ void IApplicationManagerInterface::GetApplicationControlData(Kernel::HLERequestC const auto size = ctx.GetWriteBufferSize(); - const FileSys::PatchManager pm{title_id}; + const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), + system.GetContentProvider()}; const auto control = pm.GetControlMetadata(); std::vector out; @@ -426,8 +429,8 @@ ResultVal IApplicationManagerInterface::ConvertApplicationLanguageToLanguag return MakeResult(static_cast(*language_code)); } -IApplicationVersionInterface::IApplicationVersionInterface() - : ServiceFramework{"IApplicationVersionInterface"} { +IApplicationVersionInterface::IApplicationVersionInterface(Core::System& system_) + : ServiceFramework{system_, "IApplicationVersionInterface"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetLaunchRequiredVersion"}, @@ -447,8 +450,8 @@ IApplicationVersionInterface::IApplicationVersionInterface() IApplicationVersionInterface::~IApplicationVersionInterface() = default; -IContentManagementInterface::IContentManagementInterface() - : ServiceFramework{"IContentManagementInterface"} { +IContentManagementInterface::IContentManagementInterface(Core::System& system_) + : ServiceFramework{system_, "IContentManagementInterface"} { // clang-format off static const FunctionInfo functions[] = { {11, nullptr, "CalculateApplicationOccupiedSize"}, @@ -467,7 +470,8 @@ IContentManagementInterface::IContentManagementInterface() IContentManagementInterface::~IContentManagementInterface() = default; -IDocumentInterface::IDocumentInterface() : ServiceFramework{"IDocumentInterface"} { +IDocumentInterface::IDocumentInterface(Core::System& system_) + : ServiceFramework{system_, "IDocumentInterface"} { // clang-format off static const FunctionInfo functions[] = { {21, nullptr, "GetApplicationContentPath"}, @@ -481,7 +485,8 @@ IDocumentInterface::IDocumentInterface() : ServiceFramework{"IDocumentInterface" IDocumentInterface::~IDocumentInterface() = default; -IDownloadTaskInterface::IDownloadTaskInterface() : ServiceFramework{"IDownloadTaskInterface"} { +IDownloadTaskInterface::IDownloadTaskInterface(Core::System& system_) + : ServiceFramework{system_, "IDownloadTaskInterface"} { // clang-format off static const FunctionInfo functions[] = { {701, nullptr, "ClearTaskStatusList"}, @@ -501,7 +506,8 @@ IDownloadTaskInterface::IDownloadTaskInterface() : ServiceFramework{"IDownloadTa IDownloadTaskInterface::~IDownloadTaskInterface() = default; -IECommerceInterface::IECommerceInterface() : ServiceFramework{"IECommerceInterface"} { +IECommerceInterface::IECommerceInterface(Core::System& system_) + : ServiceFramework{system_, "IECommerceInterface"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestLinkDevice"}, @@ -519,8 +525,8 @@ IECommerceInterface::IECommerceInterface() : ServiceFramework{"IECommerceInterfa IECommerceInterface::~IECommerceInterface() = default; -IFactoryResetInterface::IFactoryResetInterface::IFactoryResetInterface() - : ServiceFramework{"IFactoryResetInterface"} { +IFactoryResetInterface::IFactoryResetInterface(Core::System& system_) + : ServiceFramework{system_, "IFactoryResetInterface"} { // clang-format off static const FunctionInfo functions[] = { {100, nullptr, "ResetToFactorySettings"}, @@ -538,14 +544,14 @@ IFactoryResetInterface::IFactoryResetInterface::IFactoryResetInterface() IFactoryResetInterface::~IFactoryResetInterface() = default; -NS::NS(const char* name) : ServiceFramework{name} { +NS::NS(const char* name, Core::System& system_) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { {7992, &NS::PushInterface, "GetECommerceInterface"}, {7993, &NS::PushInterface, "GetApplicationVersionInterface"}, {7994, &NS::PushInterface, "GetFactoryResetInterface"}, {7995, &NS::PushInterface, "GetAccountProxyInterface"}, - {7996, &NS::PushInterface, "GetApplicationManagerInterface"}, + {7996, &NS::PushIApplicationManagerInterface, "GetApplicationManagerInterface"}, {7997, &NS::PushInterface, "GetDownloadTaskInterface"}, {7998, &NS::PushInterface, "GetContentManagementInterface"}, {7999, &NS::PushInterface, "GetDocumentInterface"}, @@ -558,12 +564,12 @@ NS::NS(const char* name) : ServiceFramework{name} { NS::~NS() = default; std::shared_ptr NS::GetApplicationManagerInterface() const { - return GetInterface(); + return GetInterface(system); } class NS_DEV final : public ServiceFramework { public: - explicit NS_DEV() : ServiceFramework{"ns:dev"} { + explicit NS_DEV(Core::System& system_) : ServiceFramework{system_, "ns:dev"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "LaunchProgram"}, @@ -590,7 +596,8 @@ public: class ISystemUpdateControl final : public ServiceFramework { public: - explicit ISystemUpdateControl() : ServiceFramework{"ISystemUpdateControl"} { + explicit ISystemUpdateControl(Core::System& system_) + : ServiceFramework{system_, "ISystemUpdateControl"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "HasDownloaded"}, @@ -625,7 +632,7 @@ public: class NS_SU final : public ServiceFramework { public: - explicit NS_SU() : ServiceFramework{"ns:su"} { + explicit NS_SU(Core::System& system_) : ServiceFramework{system_, "ns:su"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetBackgroundNetworkUpdateState"}, @@ -657,16 +664,16 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } }; class NS_VM final : public ServiceFramework { public: - explicit NS_VM() : ServiceFramework{"ns:vm"} { + explicit NS_VM(Core::System& system_) : ServiceFramework{system_, "ns:vm"} { // clang-format off static const FunctionInfo functions[] = { - {1200, nullptr, "NeedsUpdateVulnerability"}, + {1200, &NS_VM::NeedsUpdateVulnerability, "NeedsUpdateVulnerability"}, {1201, nullptr, "UpdateSafeSystemVersionForDebug"}, {1202, nullptr, "GetSafeSystemVersion"}, }; @@ -674,19 +681,28 @@ public: RegisterHandlers(functions); } + +private: + void NeedsUpdateVulnerability(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NS, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push(false); + } }; void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { - std::make_shared("ns:am2")->InstallAsService(service_manager); - std::make_shared("ns:ec")->InstallAsService(service_manager); - std::make_shared("ns:rid")->InstallAsService(service_manager); - std::make_shared("ns:rt")->InstallAsService(service_manager); - std::make_shared("ns:web")->InstallAsService(service_manager); + std::make_shared("ns:am2", system)->InstallAsService(service_manager); + std::make_shared("ns:ec", system)->InstallAsService(service_manager); + std::make_shared("ns:rid", system)->InstallAsService(service_manager); + std::make_shared("ns:rt", system)->InstallAsService(service_manager); + std::make_shared("ns:web", system)->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); std::make_shared(system)->InstallAsService(service_manager); } diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h index c2554b878..991271f3e 100644 --- a/src/core/hle/service/ns/ns.h +++ b/src/core/hle/service/ns/ns.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service { namespace FileSystem { @@ -16,13 +20,13 @@ namespace NS { class IAccountProxyInterface final : public ServiceFramework { public: - explicit IAccountProxyInterface(); + explicit IAccountProxyInterface(Core::System& system_); ~IAccountProxyInterface() override; }; class IApplicationManagerInterface final : public ServiceFramework { public: - explicit IApplicationManagerInterface(); + explicit IApplicationManagerInterface(Core::System& system_); ~IApplicationManagerInterface() override; ResultVal GetApplicationDesiredLanguage(u32 supported_languages); @@ -36,63 +40,71 @@ private: class IApplicationVersionInterface final : public ServiceFramework { public: - explicit IApplicationVersionInterface(); + explicit IApplicationVersionInterface(Core::System& system_); ~IApplicationVersionInterface() override; }; class IContentManagementInterface final : public ServiceFramework { public: - explicit IContentManagementInterface(); + explicit IContentManagementInterface(Core::System& system_); ~IContentManagementInterface() override; }; class IDocumentInterface final : public ServiceFramework { public: - explicit IDocumentInterface(); + explicit IDocumentInterface(Core::System& system_); ~IDocumentInterface() override; }; class IDownloadTaskInterface final : public ServiceFramework { public: - explicit IDownloadTaskInterface(); + explicit IDownloadTaskInterface(Core::System& system_); ~IDownloadTaskInterface() override; }; class IECommerceInterface final : public ServiceFramework { public: - explicit IECommerceInterface(); + explicit IECommerceInterface(Core::System& system_); ~IECommerceInterface() override; }; class IFactoryResetInterface final : public ServiceFramework { public: - explicit IFactoryResetInterface(); + explicit IFactoryResetInterface(Core::System& system_); ~IFactoryResetInterface() override; }; class NS final : public ServiceFramework { public: - explicit NS(const char* name); + explicit NS(const char* name, Core::System& system_); ~NS() override; std::shared_ptr GetApplicationManagerInterface() const; private: - template + template void PushInterface(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NS, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } - template - std::shared_ptr GetInterface() const { + void PushIApplicationManagerInterface(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NS, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface(system); + } + + template + std::shared_ptr GetInterface(Args&&... args) const { static_assert(std::is_base_of_v, "Not a base of ServiceFrameworkBase"); - return std::make_shared(); + return std::make_shared(std::forward(args)...); } }; diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp index 40838a225..71c7587db 100644 --- a/src/core/hle/service/ns/pl_u.cpp +++ b/src/core/hle/service/ns/pl_u.cpp @@ -27,42 +27,14 @@ namespace Service::NS { -enum class FontArchives : u64 { - Extension = 0x0100000000000810, - Standard = 0x0100000000000811, - Korean = 0x0100000000000812, - ChineseTraditional = 0x0100000000000813, - ChineseSimple = 0x0100000000000814, -}; - struct FontRegion { u32 offset; u32 size; }; -constexpr std::array, 7> SHARED_FONTS{ - std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"), - std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"), - std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"), - std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"), - std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"), - std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"), - std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"), -}; - -constexpr std::array SHARED_FONTS_TTF{ - "FontStandard.ttf", - "FontChineseSimplified.ttf", - "FontExtendedChineseSimplified.ttf", - "FontChineseTraditional.ttf", - "FontKorean.ttf", - "FontNintendoExtended.ttf", - "FontNintendoExtended2.ttf", -}; - // The below data is specific to shared font data dumped from Switch on f/w 2.2 // Virtual address and offsets/sizes likely will vary by dump -constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL}; +[[maybe_unused]] constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL}; constexpr u32 EXPECTED_RESULT{0x7f9a0218}; // What we expect the decrypted bfttf first 4 bytes to be constexpr u32 EXPECTED_MAGIC{0x36f81a1e}; // What we expect the encrypted bfttf first 4 bytes to be constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000}; @@ -90,6 +62,18 @@ static void DecryptSharedFont(const std::vector& input, Kernel::PhysicalMem offset += transformed_font.size() * sizeof(u32); } +void DecryptSharedFontToTTF(const std::vector& input, std::vector& output) { + ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number"); + + const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor + std::vector transformed_font(input.size()); + // TODO(ogniK): Figure out a better way to do this + std::transform(input.begin(), input.end(), transformed_font.begin(), + [&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); }); + transformed_font[1] = Common::swap32(transformed_font[1]) ^ KEY; // "re-encrypt" the size + std::memcpy(output.data(), transformed_font.data() + 2, transformed_font.size() * sizeof(u32)); +} + void EncryptSharedFont(const std::vector& input, std::vector& output, std::size_t& offset) { ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE, @@ -151,8 +135,8 @@ struct PL_U::Impl { std::vector shared_font_regions; }; -PL_U::PL_U(Core::System& system) - : ServiceFramework("pl:u"), impl{std::make_unique()}, system(system) { +PL_U::PL_U(Core::System& system_) + : ServiceFramework{system_, "pl:u"}, impl{std::make_unique()} { // clang-format off static const FunctionInfo functions[] = { {0, &PL_U::RequestLoad, "RequestLoad"}, @@ -192,21 +176,18 @@ PL_U::PL_U(Core::System& system) } if (!romfs) { - LOG_ERROR(Service_NS, "Failed to find or synthesize {:016X}! Skipping", - static_cast(font.first)); + LOG_ERROR(Service_NS, "Failed to find or synthesize {:016X}! Skipping", font.first); continue; } const auto extracted_romfs = FileSys::ExtractRomFS(romfs); if (!extracted_romfs) { - LOG_ERROR(Service_NS, "Failed to extract RomFS for {:016X}! Skipping", - static_cast(font.first)); + LOG_ERROR(Service_NS, "Failed to extract RomFS for {:016X}! Skipping", font.first); continue; } const auto font_fp = extracted_romfs->GetFile(font.second); if (!font_fp) { - LOG_ERROR(Service_NS, "{:016X} has no file \"{}\"! Skipping", - static_cast(font.first), font.second); + LOG_ERROR(Service_NS, "{:016X} has no file \"{}\"! Skipping", font.first, font.second); continue; } std::vector font_data_u32(font_fp->GetSize() / sizeof(u32)); diff --git a/src/core/hle/service/ns/pl_u.h b/src/core/hle/service/ns/pl_u.h index 27161bd7a..f920c7f69 100644 --- a/src/core/hle/service/ns/pl_u.h +++ b/src/core/hle/service/ns/pl_u.h @@ -16,11 +16,30 @@ class FileSystemController; namespace NS { +enum class FontArchives : u64 { + Extension = 0x0100000000000810, + Standard = 0x0100000000000811, + Korean = 0x0100000000000812, + ChineseTraditional = 0x0100000000000813, + ChineseSimple = 0x0100000000000814, +}; + +constexpr std::array, 7> SHARED_FONTS{ + std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"), + std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"), + std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"), + std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"), + std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"), + std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"), + std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"), +}; + +void DecryptSharedFontToTTF(const std::vector& input, std::vector& output); void EncryptSharedFont(const std::vector& input, std::vector& output, std::size_t& offset); class PL_U final : public ServiceFramework { public: - explicit PL_U(Core::System& system); + explicit PL_U(Core::System& system_); ~PL_U() override; private: @@ -33,7 +52,6 @@ private: struct Impl; std::unique_ptr impl; - Core::System& system; }; } // namespace NS diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h index 0240d6643..5681599ba 100644 --- a/src/core/hle/service/nvdrv/devices/nvdevice.h +++ b/src/core/hle/service/nvdrv/devices/nvdevice.h @@ -24,25 +24,37 @@ public: explicit nvdevice(Core::System& system) : system{system} {} virtual ~nvdevice() = default; - union Ioctl { - u32_le raw; - BitField<0, 8, u32> cmd; - BitField<8, 8, u32> group; - BitField<16, 14, u32> length; - BitField<30, 1, u32> is_in; - BitField<31, 1, u32> is_out; - }; - /** - * Handles an ioctl request. + * Handles an ioctl1 request. * @param command The ioctl command id. * @param input A buffer containing the input data for the ioctl. * @param output A buffer where the output data will be written to. * @returns The result code of the ioctl. */ - virtual u32 ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) = 0; + virtual NvResult Ioctl1(Ioctl command, const std::vector& input, + std::vector& output) = 0; + + /** + * Handles an ioctl2 request. + * @param command The ioctl command id. + * @param input A buffer containing the input data for the ioctl. + * @param inline_input A buffer containing the input data for the ioctl which has been inlined. + * @param output A buffer where the output data will be written to. + * @returns The result code of the ioctl. + */ + virtual NvResult Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) = 0; + + /** + * Handles an ioctl3 request. + * @param command The ioctl command id. + * @param input A buffer containing the input data for the ioctl. + * @param output A buffer where the output data will be written to. + * @param inline_output A buffer where the inlined output data will be written to. + * @returns The result code of the ioctl. + */ + virtual NvResult Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_output) = 0; protected: Core::System& system; diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp index 3f7b8e670..ce615c758 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp @@ -18,11 +18,22 @@ nvdisp_disp0::nvdisp_disp0(Core::System& system, std::shared_ptr nvmap_de : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} nvdisp_disp0 ::~nvdisp_disp0() = default; -u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; +NvResult nvdisp_disp0::Ioctl1(Ioctl command, const std::vector& input, + std::vector& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvdisp_disp0::Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvdisp_disp0::Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h index 6fcdeee84..55a33b7e4 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h @@ -20,9 +20,11 @@ public: explicit nvdisp_disp0(Core::System& system, std::shared_ptr nvmap_dev); ~nvdisp_disp0() override; - u32 ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; + NvResult Ioctl1(Ioctl command, const std::vector& input, std::vector& output) override; + NvResult Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) override; + NvResult Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_output) override; /// Performs a screen flip, drawing the buffer pointed to by the handle. void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride, 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 39bd2a45b..6b062e10e 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -17,57 +17,77 @@ namespace Service::Nvidia::Devices { -namespace NvErrCodes { -constexpr u32 Success{}; -constexpr u32 OutOfMemory{static_cast(-12)}; -constexpr u32 InvalidInput{static_cast(-22)}; -} // namespace NvErrCodes - nvhost_as_gpu::nvhost_as_gpu(Core::System& system, std::shared_ptr nvmap_dev) : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} nvhost_as_gpu::~nvhost_as_gpu() = default; -u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", - command.raw, input.size(), output.size()); - - switch (static_cast(command.raw)) { - case IoctlCommand::IocInitalizeExCommand: - return InitalizeEx(input, output); - case IoctlCommand::IocAllocateSpaceCommand: - return AllocateSpace(input, output); - case IoctlCommand::IocMapBufferExCommand: - return MapBufferEx(input, output); - case IoctlCommand::IocBindChannelCommand: - return BindChannel(input, output); - case IoctlCommand::IocGetVaRegionsCommand: - return GetVARegions(input, output); - case IoctlCommand::IocUnmapBufferCommand: - return UnmapBuffer(input, output); +NvResult nvhost_as_gpu::Ioctl1(Ioctl command, const std::vector& input, + std::vector& output) { + switch (command.group) { + case 'A': + switch (command.cmd) { + case 0x1: + return BindChannel(input, output); + case 0x2: + return AllocateSpace(input, output); + case 0x3: + return FreeSpace(input, output); + case 0x5: + return UnmapBuffer(input, output); + case 0x6: + return MapBufferEx(input, output); + case 0x8: + return GetVARegions(input, output); + case 0x9: + return InitalizeEx(input, output); + case 0x14: + return Remap(input, output); + default: + break; + } + break; default: break; } - if (static_cast(command.cmd.Value()) == IoctlCommand::IocRemapCommand) { - return Remap(input, output); - } - - UNIMPLEMENTED_MSG("Unimplemented ioctl command"); - return 0; + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } -u32 nvhost_as_gpu::InitalizeEx(const std::vector& input, std::vector& output) { +NvResult nvhost_as_gpu::Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvhost_as_gpu::Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_output) { + switch (command.group) { + case 'A': + switch (command.cmd) { + case 0x8: + return GetVARegions(input, output, inline_output); + default: + break; + } + break; + default: + break; + } + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvhost_as_gpu::InitalizeEx(const std::vector& input, std::vector& output) { IoctlInitalizeEx params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_WARNING(Service_NVDRV, "(STUBBED) called, big_page_size=0x{:X}", params.big_page_size); - return 0; + return NvResult::Success; } -u32 nvhost_as_gpu::AllocateSpace(const std::vector& input, std::vector& output) { +NvResult nvhost_as_gpu::AllocateSpace(const std::vector& input, std::vector& output) { IoctlAllocSpace params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -81,22 +101,36 @@ u32 nvhost_as_gpu::AllocateSpace(const std::vector& input, std::vector& params.offset = system.GPU().MemoryManager().Allocate(size, params.align); } - auto result{NvErrCodes::Success}; + auto result = NvResult::Success; if (!params.offset) { LOG_CRITICAL(Service_NVDRV, "allocation failed for size {}", size); - result = NvErrCodes::OutOfMemory; + result = NvResult::InsufficientMemory; } std::memcpy(output.data(), ¶ms, output.size()); return result; } -u32 nvhost_as_gpu::Remap(const std::vector& input, std::vector& output) { +NvResult nvhost_as_gpu::FreeSpace(const std::vector& input, std::vector& output) { + IoctlFreeSpace params{}; + std::memcpy(¶ms, input.data(), input.size()); + + LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset, + params.pages, params.page_size); + + system.GPU().MemoryManager().Unmap(params.offset, + static_cast(params.pages) * params.page_size); + + std::memcpy(output.data(), ¶ms, output.size()); + return NvResult::Success; +} + +NvResult nvhost_as_gpu::Remap(const std::vector& input, std::vector& output) { const auto num_entries = input.size() / sizeof(IoctlRemapEntry); LOG_DEBUG(Service_NVDRV, "called, num_entries=0x{:X}", num_entries); - auto result{NvErrCodes::Success}; + auto result = NvResult::Success; std::vector entries(num_entries); std::memcpy(entries.data(), input.data(), input.size()); @@ -107,7 +141,7 @@ u32 nvhost_as_gpu::Remap(const std::vector& input, std::vector& output) const auto object{nvmap_dev->GetObject(entry.nvmap_handle)}; if (!object) { LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", entry.nvmap_handle); - result = NvErrCodes::InvalidInput; + result = NvResult::InvalidState; break; } @@ -118,7 +152,7 @@ u32 nvhost_as_gpu::Remap(const std::vector& input, std::vector& output) if (!addr) { LOG_CRITICAL(Service_NVDRV, "map returned an invalid address!"); - result = NvErrCodes::InvalidInput; + result = NvResult::InvalidState; break; } } @@ -127,7 +161,7 @@ u32 nvhost_as_gpu::Remap(const std::vector& input, std::vector& output) return result; } -u32 nvhost_as_gpu::MapBufferEx(const std::vector& input, std::vector& output) { +NvResult nvhost_as_gpu::MapBufferEx(const std::vector& input, std::vector& output) { IoctlMapBufferEx params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -141,7 +175,7 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector& input, std::vector& ou if (!object) { LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", params.nvmap_handle); std::memcpy(output.data(), ¶ms, output.size()); - return NvErrCodes::InvalidInput; + return NvResult::InvalidState; } // The real nvservices doesn't make a distinction between handles and ids, and @@ -168,16 +202,16 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector& input, std::vector& ou params.mapping_size, params.offset); std::memcpy(output.data(), ¶ms, output.size()); - return NvErrCodes::InvalidInput; + return NvResult::InvalidState; } std::memcpy(output.data(), ¶ms, output.size()); - return NvErrCodes::Success; + return NvResult::Success; } else { LOG_CRITICAL(Service_NVDRV, "address not mapped offset={}", params.offset); std::memcpy(output.data(), ¶ms, output.size()); - return NvErrCodes::InvalidInput; + return NvResult::InvalidState; } } @@ -197,10 +231,10 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector& input, std::vector& ou params.offset = gpu.MemoryManager().Map(physical_address, params.offset, size); } - auto result{NvErrCodes::Success}; + auto result = NvResult::Success; if (!params.offset) { LOG_CRITICAL(Service_NVDRV, "failed to map size={}", size); - result = NvErrCodes::InvalidInput; + result = NvResult::InvalidState; } else { AddBufferMap(params.offset, size, physical_address, is_alloc); } @@ -209,7 +243,7 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector& input, std::vector& ou return result; } -u32 nvhost_as_gpu::UnmapBuffer(const std::vector& input, std::vector& output) { +NvResult nvhost_as_gpu::UnmapBuffer(const std::vector& input, std::vector& output) { IoctlUnmapBuffer params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -222,20 +256,19 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector& input, std::vector& ou } std::memcpy(output.data(), ¶ms, output.size()); - return NvErrCodes::Success; + return NvResult::Success; } -u32 nvhost_as_gpu::BindChannel(const std::vector& input, std::vector& output) { +NvResult nvhost_as_gpu::BindChannel(const std::vector& input, std::vector& output) { IoctlBindChannel params{}; std::memcpy(¶ms, input.data(), input.size()); - - LOG_DEBUG(Service_NVDRV, "called, fd={:X}", params.fd); + LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}", params.fd); channel = params.fd; - return 0; + return NvResult::Success; } -u32 nvhost_as_gpu::GetVARegions(const std::vector& input, std::vector& output) { +NvResult nvhost_as_gpu::GetVARegions(const std::vector& input, std::vector& output) { IoctlGetVaRegions params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -254,7 +287,31 @@ u32 nvhost_as_gpu::GetVARegions(const std::vector& input, std::vector& o // TODO(ogniK): This probably can stay stubbed but should add support way way later std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; +} + +NvResult nvhost_as_gpu::GetVARegions(const std::vector& input, std::vector& output, + std::vector& inline_output) { + IoctlGetVaRegions params{}; + std::memcpy(¶ms, input.data(), input.size()); + + LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr, + params.buf_size); + + params.buf_size = 0x30; + params.regions[0].offset = 0x04000000; + params.regions[0].page_size = 0x1000; + params.regions[0].pages = 0x3fbfff; + + params.regions[1].offset = 0x04000000; + params.regions[1].page_size = 0x10000; + params.regions[1].pages = 0x1bffff; + + // TODO(ogniK): This probably can stay stubbed but should add support way way later + + std::memcpy(output.data(), ¶ms, output.size()); + std::memcpy(inline_output.data(), ¶ms.regions, inline_output.size()); + return NvResult::Success; } std::optional nvhost_as_gpu::FindBufferMap(GPUVAddr gpu_addr) const { 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 9a0cdff0c..08035fa0e 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h @@ -30,9 +30,11 @@ public: explicit nvhost_as_gpu(Core::System& system, std::shared_ptr nvmap_dev); ~nvhost_as_gpu() override; - u32 ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; + NvResult Ioctl1(Ioctl command, const std::vector& input, std::vector& output) override; + NvResult Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) override; + NvResult Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_output) override; private: class BufferMap final { @@ -74,31 +76,21 @@ private: bool is_allocated{}; }; - enum class IoctlCommand : u32_le { - IocInitalizeExCommand = 0x40284109, - IocAllocateSpaceCommand = 0xC0184102, - IocRemapCommand = 0x00000014, - IocMapBufferExCommand = 0xC0284106, - IocBindChannelCommand = 0x40044101, - IocGetVaRegionsCommand = 0xC0404108, - IocUnmapBufferCommand = 0xC0084105, - }; - struct IoctlInitalizeEx { - u32_le big_page_size; // depends on GPU's available_big_page_sizes; 0=default - s32_le as_fd; // ignored; passes 0 - u32_le flags; // passes 0 - u32_le reserved; // ignored; passes 0 - u64_le unk0; - u64_le unk1; - u64_le unk2; + u32_le big_page_size{}; // depends on GPU's available_big_page_sizes; 0=default + s32_le as_fd{}; // ignored; passes 0 + u32_le flags{}; // passes 0 + u32_le reserved{}; // ignored; passes 0 + u64_le unk0{}; + u64_le unk1{}; + u64_le unk2{}; }; static_assert(sizeof(IoctlInitalizeEx) == 40, "IoctlInitalizeEx is incorrect size"); struct IoctlAllocSpace { - u32_le pages; - u32_le page_size; - AddressSpaceFlags flags; + u32_le pages{}; + u32_le page_size{}; + AddressSpaceFlags flags{}; INSERT_PADDING_WORDS(1); union { u64_le offset; @@ -107,63 +99,74 @@ private: }; static_assert(sizeof(IoctlAllocSpace) == 24, "IoctlInitalizeEx is incorrect size"); + struct IoctlFreeSpace { + u64_le offset{}; + u32_le pages{}; + u32_le page_size{}; + }; + static_assert(sizeof(IoctlFreeSpace) == 16, "IoctlFreeSpace is incorrect size"); + struct IoctlRemapEntry { - u16_le flags; - u16_le kind; - u32_le nvmap_handle; - u32_le map_offset; - u32_le offset; - u32_le pages; + u16_le flags{}; + u16_le kind{}; + u32_le nvmap_handle{}; + u32_le map_offset{}; + u32_le offset{}; + u32_le pages{}; }; static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size"); struct IoctlMapBufferEx { - AddressSpaceFlags flags; // bit0: fixed_offset, bit2: cacheable - u32_le kind; // -1 is default - u32_le nvmap_handle; - u32_le page_size; // 0 means don't care - s64_le buffer_offset; - u64_le mapping_size; - s64_le offset; + AddressSpaceFlags flags{}; // bit0: fixed_offset, bit2: cacheable + u32_le kind{}; // -1 is default + u32_le nvmap_handle{}; + u32_le page_size{}; // 0 means don't care + s64_le buffer_offset{}; + u64_le mapping_size{}; + s64_le offset{}; }; static_assert(sizeof(IoctlMapBufferEx) == 40, "IoctlMapBufferEx is incorrect size"); struct IoctlUnmapBuffer { - s64_le offset; + s64_le offset{}; }; static_assert(sizeof(IoctlUnmapBuffer) == 8, "IoctlUnmapBuffer is incorrect size"); struct IoctlBindChannel { - u32_le fd; + s32_le fd{}; }; static_assert(sizeof(IoctlBindChannel) == 4, "IoctlBindChannel is incorrect size"); struct IoctlVaRegion { - u64_le offset; - u32_le page_size; + u64_le offset{}; + u32_le page_size{}; INSERT_PADDING_WORDS(1); - u64_le pages; + u64_le pages{}; }; static_assert(sizeof(IoctlVaRegion) == 24, "IoctlVaRegion is incorrect size"); struct IoctlGetVaRegions { - u64_le buf_addr; // (contained output user ptr on linux, ignored) - u32_le buf_size; // forced to 2*sizeof(struct va_region) - u32_le reserved; - IoctlVaRegion regions[2]; + u64_le buf_addr{}; // (contained output user ptr on linux, ignored) + u32_le buf_size{}; // forced to 2*sizeof(struct va_region) + u32_le reserved{}; + IoctlVaRegion regions[2]{}; }; static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(IoctlVaRegion) * 2, "IoctlGetVaRegions is incorrect size"); - u32 channel{}; + s32 channel{}; - u32 InitalizeEx(const std::vector& input, std::vector& output); - u32 AllocateSpace(const std::vector& input, std::vector& output); - u32 Remap(const std::vector& input, std::vector& output); - u32 MapBufferEx(const std::vector& input, std::vector& output); - u32 UnmapBuffer(const std::vector& input, std::vector& output); - u32 BindChannel(const std::vector& input, std::vector& output); - u32 GetVARegions(const std::vector& input, std::vector& output); + NvResult InitalizeEx(const std::vector& input, std::vector& output); + NvResult AllocateSpace(const std::vector& input, std::vector& output); + NvResult Remap(const std::vector& input, std::vector& output); + NvResult MapBufferEx(const std::vector& input, std::vector& output); + NvResult UnmapBuffer(const std::vector& input, std::vector& output); + NvResult FreeSpace(const std::vector& input, std::vector& output); + NvResult BindChannel(const std::vector& input, std::vector& output); + + NvResult GetVARegions(const std::vector& input, std::vector& output); + NvResult GetVARegions(const std::vector& input, std::vector& output, + std::vector& inline_output); std::optional FindBufferMap(GPUVAddr gpu_addr) const; void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp index b27ee0502..fea3b7b9f 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp @@ -15,45 +15,59 @@ namespace Service::Nvidia::Devices { -nvhost_ctrl::nvhost_ctrl(Core::System& system, EventInterface& events_interface) - : nvdevice(system), events_interface{events_interface} {} +nvhost_ctrl::nvhost_ctrl(Core::System& system, EventInterface& events_interface, + SyncpointManager& syncpoint_manager) + : nvdevice(system), events_interface{events_interface}, syncpoint_manager{syncpoint_manager} {} nvhost_ctrl::~nvhost_ctrl() = default; -u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", - command.raw, input.size(), output.size()); - - switch (static_cast(command.raw)) { - case IoctlCommand::IocGetConfigCommand: - return NvOsGetConfigU32(input, output); - case IoctlCommand::IocCtrlEventWaitCommand: - return IocCtrlEventWait(input, output, false, ctrl); - case IoctlCommand::IocCtrlEventWaitAsyncCommand: - return IocCtrlEventWait(input, output, true, ctrl); - case IoctlCommand::IocCtrlEventRegisterCommand: - return IocCtrlEventRegister(input, output); - case IoctlCommand::IocCtrlEventUnregisterCommand: - return IocCtrlEventUnregister(input, output); - case IoctlCommand::IocCtrlEventSignalCommand: - return IocCtrlEventSignal(input, output); +NvResult nvhost_ctrl::Ioctl1(Ioctl command, const std::vector& input, std::vector& output) { + switch (command.group) { + case 0x0: + switch (command.cmd) { + case 0x1b: + return NvOsGetConfigU32(input, output); + case 0x1c: + return IocCtrlClearEventWait(input, output); + case 0x1d: + return IocCtrlEventWait(input, output, false); + case 0x1e: + return IocCtrlEventWait(input, output, true); + case 0x1f: + return IocCtrlEventRegister(input, output); + case 0x20: + return IocCtrlEventUnregister(input, output); + } + break; default: - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; + break; } + + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } -u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector& input, std::vector& output) { +NvResult nvhost_ctrl::Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvhost_ctrl::Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_outpu) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvhost_ctrl::NvOsGetConfigU32(const std::vector& input, std::vector& output) { IocGetConfigParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); LOG_TRACE(Service_NVDRV, "called, setting={}!{}", params.domain_str.data(), params.param_str.data()); - return 0x30006; // Returns error on production mode + return NvResult::ConfigVarNotFound; // Returns error on production mode } -u32 nvhost_ctrl::IocCtrlEventWait(const std::vector& input, std::vector& output, - bool is_async, IoctlCtrl& ctrl) { +NvResult nvhost_ctrl::IocCtrlEventWait(const std::vector& input, std::vector& output, + bool is_async) { IocCtrlEventWaitParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_async={}", @@ -70,19 +84,33 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector& input, std::vector& return NvResult::BadParameter; } + if (syncpoint_manager.IsSyncpointExpired(params.syncpt_id, params.threshold)) { + params.value = syncpoint_manager.GetSyncpointMin(params.syncpt_id); + std::memcpy(output.data(), ¶ms, sizeof(params)); + return NvResult::Success; + } + + if (const auto new_value = syncpoint_manager.RefreshSyncpoint(params.syncpt_id); + syncpoint_manager.IsSyncpointExpired(params.syncpt_id, params.threshold)) { + params.value = new_value; + std::memcpy(output.data(), ¶ms, sizeof(params)); + return NvResult::Success; + } + auto event = events_interface.events[event_id]; auto& gpu = system.GPU(); + // This is mostly to take into account unimplemented features. As synced // gpu is always synced. if (!gpu.IsAsync()) { - event.writable->Signal(); + event.event.writable->Signal(); return NvResult::Success; } auto lock = gpu.LockSync(); - const u32 current_syncpoint_value = gpu.GetSyncpointValue(params.syncpt_id); + const u32 current_syncpoint_value = event.fence.value; const s32 diff = current_syncpoint_value - params.threshold; if (diff >= 0) { - event.writable->Signal(); + event.event.writable->Signal(); params.value = current_syncpoint_value; std::memcpy(output.data(), ¶ms, sizeof(params)); return NvResult::Success; @@ -109,14 +137,8 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector& input, std::vector& params.value = ((params.syncpt_id & 0xfff) << 16) | 0x10000000; } params.value |= event_id; - event.writable->Clear(); + event.event.writable->Clear(); gpu.RegisterSyncptInterrupt(params.syncpt_id, target_value); - if (!is_async && ctrl.fresh_call) { - ctrl.must_delay = true; - ctrl.timeout = params.timeout; - ctrl.event_id = event_id; - return NvResult::Timeout; - } std::memcpy(output.data(), ¶ms, sizeof(params)); return NvResult::Timeout; } @@ -124,7 +146,7 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector& input, std::vector& return NvResult::BadParameter; } -u32 nvhost_ctrl::IocCtrlEventRegister(const std::vector& input, std::vector& output) { +NvResult nvhost_ctrl::IocCtrlEventRegister(const std::vector& input, std::vector& output) { IocCtrlEventRegisterParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); const u32 event_id = params.user_event_id & 0x00FF; @@ -139,7 +161,8 @@ u32 nvhost_ctrl::IocCtrlEventRegister(const std::vector& input, std::vector< return NvResult::Success; } -u32 nvhost_ctrl::IocCtrlEventUnregister(const std::vector& input, std::vector& output) { +NvResult nvhost_ctrl::IocCtrlEventUnregister(const std::vector& input, + std::vector& output) { IocCtrlEventUnregisterParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); const u32 event_id = params.user_event_id & 0x00FF; @@ -154,24 +177,22 @@ u32 nvhost_ctrl::IocCtrlEventUnregister(const std::vector& input, std::vecto return NvResult::Success; } -u32 nvhost_ctrl::IocCtrlEventSignal(const std::vector& input, std::vector& output) { +NvResult nvhost_ctrl::IocCtrlClearEventWait(const std::vector& input, std::vector& output) { IocCtrlEventSignalParams params{}; std::memcpy(¶ms, input.data(), sizeof(params)); - // TODO(Blinkhawk): This is normally called when an NvEvents timeout on WaitSynchronization - // It is believed from RE to cancel the GPU Event. However, better research is required - u32 event_id = params.user_event_id & 0x00FF; - LOG_WARNING(Service_NVDRV, "(STUBBED) called, user_event_id: {:X}", event_id); + + u32 event_id = params.event_id & 0x00FF; + LOG_WARNING(Service_NVDRV, "cleared event wait on, event_id: {:X}", event_id); + if (event_id >= MaxNvEvents) { return NvResult::BadParameter; } if (events_interface.status[event_id] == EventState::Waiting) { - auto& gpu = system.GPU(); - if (gpu.CancelSyncptInterrupt(events_interface.assigned_syncpt[event_id], - events_interface.assigned_value[event_id])) { - events_interface.LiberateEvent(event_id); - events_interface.events[event_id].writable->Signal(); - } + events_interface.LiberateEvent(event_id); } + + syncpoint_manager.RefreshSyncpoint(events_interface.events[event_id].fence.id); + return NvResult::Success; } diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h index 9898623de..c5aa1362a 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h @@ -14,137 +14,120 @@ namespace Service::Nvidia::Devices { class nvhost_ctrl final : public nvdevice { public: - explicit nvhost_ctrl(Core::System& system, EventInterface& events_interface); + explicit nvhost_ctrl(Core::System& system, EventInterface& events_interface, + SyncpointManager& syncpoint_manager); ~nvhost_ctrl() override; - u32 ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; + NvResult Ioctl1(Ioctl command, const std::vector& input, std::vector& output) override; + NvResult Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) override; + NvResult Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_output) override; private: - enum class IoctlCommand : u32_le { - IocSyncptReadCommand = 0xC0080014, - IocSyncptIncrCommand = 0x40040015, - IocSyncptWaitCommand = 0xC00C0016, - IocModuleMutexCommand = 0x40080017, - IocModuleRegRDWRCommand = 0xC0180018, - IocSyncptWaitexCommand = 0xC0100019, - IocSyncptReadMaxCommand = 0xC008001A, - IocGetConfigCommand = 0xC183001B, - IocCtrlEventSignalCommand = 0xC004001C, - IocCtrlEventWaitCommand = 0xC010001D, - IocCtrlEventWaitAsyncCommand = 0xC010001E, - IocCtrlEventRegisterCommand = 0xC004001F, - IocCtrlEventUnregisterCommand = 0xC0040020, - IocCtrlEventKillCommand = 0x40080021, - }; struct IocSyncptReadParams { - u32_le id; - u32_le value; + u32_le id{}; + u32_le value{}; }; static_assert(sizeof(IocSyncptReadParams) == 8, "IocSyncptReadParams is incorrect size"); struct IocSyncptIncrParams { - u32_le id; + u32_le id{}; }; static_assert(sizeof(IocSyncptIncrParams) == 4, "IocSyncptIncrParams is incorrect size"); struct IocSyncptWaitParams { - u32_le id; - u32_le thresh; - s32_le timeout; + u32_le id{}; + u32_le thresh{}; + s32_le timeout{}; }; static_assert(sizeof(IocSyncptWaitParams) == 12, "IocSyncptWaitParams is incorrect size"); struct IocModuleMutexParams { - u32_le id; - u32_le lock; // (0 = unlock and 1 = lock) + u32_le id{}; + u32_le lock{}; // (0 = unlock and 1 = lock) }; static_assert(sizeof(IocModuleMutexParams) == 8, "IocModuleMutexParams is incorrect size"); struct IocModuleRegRDWRParams { - u32_le id; - u32_le num_offsets; - u32_le block_size; - u32_le offsets; - u32_le values; - u32_le write; + u32_le id{}; + u32_le num_offsets{}; + u32_le block_size{}; + u32_le offsets{}; + u32_le values{}; + u32_le write{}; }; static_assert(sizeof(IocModuleRegRDWRParams) == 24, "IocModuleRegRDWRParams is incorrect size"); struct IocSyncptWaitexParams { - u32_le id; - u32_le thresh; - s32_le timeout; - u32_le value; + u32_le id{}; + u32_le thresh{}; + s32_le timeout{}; + u32_le value{}; }; static_assert(sizeof(IocSyncptWaitexParams) == 16, "IocSyncptWaitexParams is incorrect size"); struct IocSyncptReadMaxParams { - u32_le id; - u32_le value; + u32_le id{}; + u32_le value{}; }; static_assert(sizeof(IocSyncptReadMaxParams) == 8, "IocSyncptReadMaxParams is incorrect size"); struct IocGetConfigParams { - std::array domain_str; - std::array param_str; - std::array config_str; + std::array domain_str{}; + std::array param_str{}; + std::array config_str{}; }; static_assert(sizeof(IocGetConfigParams) == 387, "IocGetConfigParams is incorrect size"); struct IocCtrlEventSignalParams { - u32_le user_event_id; + u32_le event_id{}; }; static_assert(sizeof(IocCtrlEventSignalParams) == 4, "IocCtrlEventSignalParams is incorrect size"); struct IocCtrlEventWaitParams { - u32_le syncpt_id; - u32_le threshold; - s32_le timeout; - u32_le value; + u32_le syncpt_id{}; + u32_le threshold{}; + s32_le timeout{}; + u32_le value{}; }; static_assert(sizeof(IocCtrlEventWaitParams) == 16, "IocCtrlEventWaitParams is incorrect size"); struct IocCtrlEventWaitAsyncParams { - u32_le syncpt_id; - u32_le threshold; - u32_le timeout; - u32_le value; + u32_le syncpt_id{}; + u32_le threshold{}; + u32_le timeout{}; + u32_le value{}; }; static_assert(sizeof(IocCtrlEventWaitAsyncParams) == 16, "IocCtrlEventWaitAsyncParams is incorrect size"); struct IocCtrlEventRegisterParams { - u32_le user_event_id; + u32_le user_event_id{}; }; static_assert(sizeof(IocCtrlEventRegisterParams) == 4, "IocCtrlEventRegisterParams is incorrect size"); struct IocCtrlEventUnregisterParams { - u32_le user_event_id; + u32_le user_event_id{}; }; static_assert(sizeof(IocCtrlEventUnregisterParams) == 4, "IocCtrlEventUnregisterParams is incorrect size"); struct IocCtrlEventKill { - u64_le user_events; + u64_le user_events{}; }; static_assert(sizeof(IocCtrlEventKill) == 8, "IocCtrlEventKill is incorrect size"); - u32 NvOsGetConfigU32(const std::vector& input, std::vector& output); - - u32 IocCtrlEventWait(const std::vector& input, std::vector& output, bool is_async, - IoctlCtrl& ctrl); - - u32 IocCtrlEventRegister(const std::vector& input, std::vector& output); - - u32 IocCtrlEventUnregister(const std::vector& input, std::vector& output); - - u32 IocCtrlEventSignal(const std::vector& input, std::vector& output); + NvResult NvOsGetConfigU32(const std::vector& input, std::vector& output); + NvResult IocCtrlEventWait(const std::vector& input, std::vector& output, bool is_async); + NvResult IocCtrlEventRegister(const std::vector& input, std::vector& output); + NvResult IocCtrlEventUnregister(const std::vector& input, std::vector& output); + NvResult IocCtrlClearEventWait(const std::vector& input, std::vector& output); EventInterface& events_interface; + SyncpointManager& syncpoint_manager; }; } // namespace Service::Nvidia::Devices 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 fba89e7a6..0320d3ae2 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -15,39 +15,112 @@ namespace Service::Nvidia::Devices { nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system) : nvdevice(system) {} nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default; -u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector& input, - const std::vector& input2, std::vector& output, - std::vector& output2, IoctlCtrl& ctrl, IoctlVersion version) { - LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", - command.raw, input.size(), output.size()); - - switch (static_cast(command.raw)) { - case IoctlCommand::IocGetCharacteristicsCommand: - return GetCharacteristics(input, output, output2, version); - case IoctlCommand::IocGetTPCMasksCommand: - return GetTPCMasks(input, output, output2, version); - case IoctlCommand::IocGetActiveSlotMaskCommand: - return GetActiveSlotMask(input, output); - case IoctlCommand::IocZcullGetCtxSizeCommand: - return ZCullGetCtxSize(input, output); - case IoctlCommand::IocZcullGetInfo: - return ZCullGetInfo(input, output); - case IoctlCommand::IocZbcSetTable: - return ZBCSetTable(input, output); - case IoctlCommand::IocZbcQueryTable: - return ZBCQueryTable(input, output); - case IoctlCommand::IocFlushL2: - return FlushL2(input, output); - case IoctlCommand::IocGetGpuTime: - return GetGpuTime(input, output); - default: - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; +NvResult nvhost_ctrl_gpu::Ioctl1(Ioctl command, const std::vector& input, + std::vector& output) { + switch (command.group) { + case 'G': + switch (command.cmd) { + case 0x1: + return ZCullGetCtxSize(input, output); + case 0x2: + return ZCullGetInfo(input, output); + case 0x3: + return ZBCSetTable(input, output); + case 0x4: + return ZBCQueryTable(input, output); + case 0x5: + return GetCharacteristics(input, output); + case 0x6: + return GetTPCMasks(input, output); + case 0x7: + return FlushL2(input, output); + case 0x14: + return GetActiveSlotMask(input, output); + case 0x1c: + return GetGpuTime(input, output); + default: + break; + } + break; } + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } -u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector& input, std::vector& output, - std::vector& output2, IoctlVersion version) { +NvResult nvhost_ctrl_gpu::Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvhost_ctrl_gpu::Ioctl3(Ioctl command, const std::vector& input, + std::vector& output, std::vector& inline_output) { + switch (command.group) { + case 'G': + switch (command.cmd) { + case 0x5: + return GetCharacteristics(input, output, inline_output); + case 0x6: + return GetTPCMasks(input, output, inline_output); + default: + break; + } + break; + default: + break; + } + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvhost_ctrl_gpu::GetCharacteristics(const std::vector& input, + std::vector& output) { + LOG_DEBUG(Service_NVDRV, "called"); + IoctlCharacteristics params{}; + std::memcpy(¶ms, input.data(), input.size()); + params.gc.arch = 0x120; + params.gc.impl = 0xb; + params.gc.rev = 0xa1; + params.gc.num_gpc = 0x1; + params.gc.l2_cache_size = 0x40000; + params.gc.on_board_video_memory_size = 0x0; + params.gc.num_tpc_per_gpc = 0x2; + params.gc.bus_type = 0x20; + params.gc.big_page_size = 0x20000; + params.gc.compression_page_size = 0x20000; + params.gc.pde_coverage_bit_count = 0x1B; + params.gc.available_big_page_sizes = 0x30000; + params.gc.gpc_mask = 0x1; + params.gc.sm_arch_sm_version = 0x503; + params.gc.sm_arch_spa_version = 0x503; + params.gc.sm_arch_warp_count = 0x80; + params.gc.gpu_va_bit_count = 0x28; + params.gc.reserved = 0x0; + params.gc.flags = 0x55; + params.gc.twod_class = 0x902D; + params.gc.threed_class = 0xB197; + params.gc.compute_class = 0xB1C0; + params.gc.gpfifo_class = 0xB06F; + params.gc.inline_to_memory_class = 0xA140; + params.gc.dma_copy_class = 0xB0B5; + params.gc.max_fbps_count = 0x1; + params.gc.fbp_en_mask = 0x0; + params.gc.max_ltc_per_fbp = 0x2; + params.gc.max_lts_per_ltc = 0x1; + params.gc.max_tex_per_tpc = 0x0; + params.gc.max_gpc_count = 0x1; + params.gc.rop_l2_en_mask_0 = 0x21D70; + params.gc.rop_l2_en_mask_1 = 0x0; + params.gc.chipname = 0x6230326D67; + params.gc.gr_compbit_store_base_hw = 0x0; + params.gpu_characteristics_buf_size = 0xA0; + params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED) + std::memcpy(output.data(), ¶ms, output.size()); + return NvResult::Success; +} + +NvResult nvhost_ctrl_gpu::GetCharacteristics(const std::vector& input, std::vector& output, + std::vector& inline_output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlCharacteristics params{}; std::memcpy(¶ms, input.data(), input.size()); @@ -89,35 +162,36 @@ u32 nvhost_ctrl_gpu::GetCharacteristics(const std::vector& input, std::vecto params.gpu_characteristics_buf_size = 0xA0; params.gpu_characteristics_buf_addr = 0xdeadbeef; // Cannot be 0 (UNUSED) - if (version == IoctlVersion::Version3) { - std::memcpy(output.data(), input.data(), output.size()); - std::memcpy(output2.data(), ¶ms.gc, output2.size()); - } else { - std::memcpy(output.data(), ¶ms, output.size()); - } - return 0; + std::memcpy(output.data(), ¶ms, output.size()); + std::memcpy(inline_output.data(), ¶ms.gc, inline_output.size()); + return NvResult::Success; } -u32 nvhost_ctrl_gpu::GetTPCMasks(const std::vector& input, std::vector& output, - std::vector& output2, IoctlVersion version) { +NvResult nvhost_ctrl_gpu::GetTPCMasks(const std::vector& input, std::vector& output) { IoctlGpuGetTpcMasksArgs params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size=0x{:X}", params.mask_buffer_size); if (params.mask_buffer_size != 0) { params.tcp_mask = 3; } - - if (version == IoctlVersion::Version3) { - std::memcpy(output.data(), input.data(), output.size()); - std::memcpy(output2.data(), ¶ms.tcp_mask, output2.size()); - } else { - std::memcpy(output.data(), ¶ms, output.size()); - } - - return 0; + std::memcpy(output.data(), ¶ms, output.size()); + return NvResult::Success; } -u32 nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector& input, std::vector& output) { +NvResult nvhost_ctrl_gpu::GetTPCMasks(const std::vector& input, std::vector& output, + std::vector& inline_output) { + IoctlGpuGetTpcMasksArgs params{}; + std::memcpy(¶ms, input.data(), input.size()); + LOG_DEBUG(Service_NVDRV, "called, mask_buffer_size=0x{:X}", params.mask_buffer_size); + if (params.mask_buffer_size != 0) { + params.tcp_mask = 3; + } + std::memcpy(output.data(), ¶ms, output.size()); + std::memcpy(inline_output.data(), ¶ms.tcp_mask, inline_output.size()); + return NvResult::Success; +} + +NvResult nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector& input, std::vector& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlActiveSlotMask params{}; @@ -127,10 +201,10 @@ u32 nvhost_ctrl_gpu::GetActiveSlotMask(const std::vector& input, std::vector params.slot = 0x07; params.mask = 0x01; std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector& input, std::vector& output) { +NvResult nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector& input, std::vector& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlZcullGetCtxSize params{}; @@ -139,10 +213,10 @@ u32 nvhost_ctrl_gpu::ZCullGetCtxSize(const std::vector& input, std::vector& input, std::vector& output) { +NvResult nvhost_ctrl_gpu::ZCullGetInfo(const std::vector& input, std::vector& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlNvgpuGpuZcullGetInfoArgs params{}; @@ -162,47 +236,47 @@ u32 nvhost_ctrl_gpu::ZCullGetInfo(const std::vector& input, std::vector& params.subregion_height_align_pixels = 0x40; params.subregion_count = 0x10; std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_ctrl_gpu::ZBCSetTable(const std::vector& input, std::vector& output) { +NvResult nvhost_ctrl_gpu::ZBCSetTable(const std::vector& input, std::vector& output) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); IoctlZbcSetTable params{}; std::memcpy(¶ms, input.data(), input.size()); // TODO(ogniK): What does this even actually do? std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_ctrl_gpu::ZBCQueryTable(const std::vector& input, std::vector& output) { +NvResult nvhost_ctrl_gpu::ZBCQueryTable(const std::vector& input, std::vector& output) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); IoctlZbcQueryTable params{}; std::memcpy(¶ms, input.data(), input.size()); // TODO : To implement properly std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_ctrl_gpu::FlushL2(const std::vector& input, std::vector& output) { +NvResult nvhost_ctrl_gpu::FlushL2(const std::vector& input, std::vector& output) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); IoctlFlushL2 params{}; std::memcpy(¶ms, input.data(), input.size()); // TODO : To implement properly std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_ctrl_gpu::GetGpuTime(const std::vector& input, std::vector& output) { +NvResult nvhost_ctrl_gpu::GetGpuTime(const std::vector& input, std::vector& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlGetGpuTime params{}; std::memcpy(¶ms, input.data(), input.size()); params.gpu_time = static_cast(system.CoreTiming().GetGlobalTimeNs().count()); std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } } // namespace Service::Nvidia::Devices 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 ef60f72ce..137b88238 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h @@ -16,32 +16,13 @@ public: explicit nvhost_ctrl_gpu(Core::System& system); ~nvhost_ctrl_gpu() override; - u32 ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; + NvResult Ioctl1(Ioctl command, const std::vector& input, std::vector& output) override; + NvResult Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) override; + NvResult Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_output) override; private: - enum class IoctlCommand : u32_le { - IocGetCharacteristicsCommand = 0xC0B04705, - IocGetTPCMasksCommand = 0xC0184706, - IocGetActiveSlotMaskCommand = 0x80084714, - IocZcullGetCtxSizeCommand = 0x80044701, - IocZcullGetInfo = 0x80284702, - IocZbcSetTable = 0x402C4703, - IocZbcQueryTable = 0xC0344704, - IocFlushL2 = 0x40084707, - IocInvalICache = 0x4008470D, - IocSetMmudebugMode = 0x4008470E, - IocSetSmDebugMode = 0x4010470F, - IocWaitForPause = 0xC0084710, - IocGetTcpExceptionEnStatus = 0x80084711, - IocNumVsms = 0x80084712, - IocVsmsMapping = 0xC0044713, - IocGetErrorChannelUserData = 0xC008471B, - IocGetGpuTime = 0xC010471C, - IocGetCpuTimeCorrelationInfo = 0xC108471D, - }; - struct IoctlGpuCharacteristics { u32_le arch; // 0x120 (NVGPU_GPU_ARCH_GM200) u32_le impl; // 0xB (NVGPU_GPU_IMPL_GM20B) @@ -159,17 +140,21 @@ private: }; static_assert(sizeof(IoctlGetGpuTime) == 0x10, "IoctlGetGpuTime is incorrect size"); - u32 GetCharacteristics(const std::vector& input, std::vector& output, - std::vector& output2, IoctlVersion version); - u32 GetTPCMasks(const std::vector& input, std::vector& output, std::vector& output2, - IoctlVersion version); - u32 GetActiveSlotMask(const std::vector& input, std::vector& output); - u32 ZCullGetCtxSize(const std::vector& input, std::vector& output); - u32 ZCullGetInfo(const std::vector& input, std::vector& output); - u32 ZBCSetTable(const std::vector& input, std::vector& output); - u32 ZBCQueryTable(const std::vector& input, std::vector& output); - u32 FlushL2(const std::vector& input, std::vector& output); - u32 GetGpuTime(const std::vector& input, std::vector& output); + NvResult GetCharacteristics(const std::vector& input, std::vector& output); + NvResult GetCharacteristics(const std::vector& input, std::vector& output, + std::vector& inline_output); + + NvResult GetTPCMasks(const std::vector& input, std::vector& output); + NvResult GetTPCMasks(const std::vector& input, std::vector& output, + std::vector& inline_output); + + NvResult GetActiveSlotMask(const std::vector& input, std::vector& output); + NvResult ZCullGetCtxSize(const std::vector& input, std::vector& output); + NvResult ZCullGetInfo(const std::vector& input, std::vector& output); + NvResult ZBCSetTable(const std::vector& input, std::vector& output); + NvResult ZBCQueryTable(const std::vector& input, std::vector& output); + NvResult FlushL2(const std::vector& input, std::vector& output); + NvResult GetGpuTime(const std::vector& input, std::vector& output); }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index f1966ac0e..af8b3d9f1 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -7,117 +7,148 @@ #include "common/logging/log.h" #include "core/core.h" #include "core/hle/service/nvdrv/devices/nvhost_gpu.h" +#include "core/hle/service/nvdrv/syncpoint_manager.h" #include "core/memory.h" #include "video_core/gpu.h" #include "video_core/memory_manager.h" namespace Service::Nvidia::Devices { -nvhost_gpu::nvhost_gpu(Core::System& system, std::shared_ptr nvmap_dev) - : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} +nvhost_gpu::nvhost_gpu(Core::System& system, std::shared_ptr nvmap_dev, + SyncpointManager& syncpoint_manager) + : nvdevice(system), nvmap_dev(std::move(nvmap_dev)), syncpoint_manager{syncpoint_manager} { + channel_fence.id = syncpoint_manager.AllocateSyncpoint(); + channel_fence.value = system.GPU().GetSyncpointValue(channel_fence.id); +} + nvhost_gpu::~nvhost_gpu() = default; -u32 nvhost_gpu::ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", - command.raw, input.size(), output.size()); - - switch (static_cast(command.raw)) { - case IoctlCommand::IocSetNVMAPfdCommand: - return SetNVMAPfd(input, output); - case IoctlCommand::IocSetClientDataCommand: - return SetClientData(input, output); - case IoctlCommand::IocGetClientDataCommand: - return GetClientData(input, output); - case IoctlCommand::IocZCullBind: - return ZCullBind(input, output); - case IoctlCommand::IocSetErrorNotifierCommand: - return SetErrorNotifier(input, output); - case IoctlCommand::IocChannelSetPriorityCommand: - return SetChannelPriority(input, output); - case IoctlCommand::IocAllocGPFIFOEx2Command: - return AllocGPFIFOEx2(input, output); - case IoctlCommand::IocAllocObjCtxCommand: - return AllocateObjectContext(input, output); - case IoctlCommand::IocChannelGetWaitbaseCommand: - return GetWaitbase(input, output); - case IoctlCommand::IocChannelSetTimeoutCommand: - return ChannelSetTimeout(input, output); - case IoctlCommand::IocChannelSetTimeslice: - return ChannelSetTimeslice(input, output); - default: +NvResult nvhost_gpu::Ioctl1(Ioctl command, const std::vector& input, std::vector& output) { + switch (command.group) { + case 0x0: + switch (command.cmd) { + case 0x3: + return GetWaitbase(input, output); + default: + break; + } + break; + case 'H': + switch (command.cmd) { + case 0x1: + return SetNVMAPfd(input, output); + case 0x3: + return ChannelSetTimeout(input, output); + case 0x8: + return SubmitGPFIFOBase(input, output, false); + case 0x9: + return AllocateObjectContext(input, output); + case 0xb: + return ZCullBind(input, output); + case 0xc: + return SetErrorNotifier(input, output); + case 0xd: + return SetChannelPriority(input, output); + case 0x1a: + return AllocGPFIFOEx2(input, output); + case 0x1b: + return SubmitGPFIFOBase(input, output, true); + case 0x1d: + return ChannelSetTimeslice(input, output); + default: + break; + } + break; + case 'G': + switch (command.cmd) { + case 0x14: + return SetClientData(input, output); + case 0x15: + return GetClientData(input, output); + default: + break; + } break; } - - if (command.group == NVGPU_IOCTL_MAGIC) { - if (command.cmd == NVGPU_IOCTL_CHANNEL_SUBMIT_GPFIFO) { - return SubmitGPFIFO(input, output); - } - if (command.cmd == NVGPU_IOCTL_CHANNEL_KICKOFF_PB) { - return KickoffPB(input, output, input2, version); - } - } - - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; }; -u32 nvhost_gpu::SetNVMAPfd(const std::vector& input, std::vector& output) { +NvResult nvhost_gpu::Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) { + switch (command.group) { + case 'H': + switch (command.cmd) { + case 0x1b: + return SubmitGPFIFOBase(input, inline_input, output); + } + break; + } + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvhost_gpu::Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvhost_gpu::SetNVMAPfd(const std::vector& input, std::vector& output) { IoctlSetNvmapFD params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); nvmap_fd = params.nvmap_fd; - return 0; + return NvResult::Success; } -u32 nvhost_gpu::SetClientData(const std::vector& input, std::vector& output) { +NvResult nvhost_gpu::SetClientData(const std::vector& input, std::vector& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlClientData params{}; std::memcpy(¶ms, input.data(), input.size()); user_data = params.data; - return 0; + return NvResult::Success; } -u32 nvhost_gpu::GetClientData(const std::vector& input, std::vector& output) { +NvResult nvhost_gpu::GetClientData(const std::vector& input, std::vector& output) { LOG_DEBUG(Service_NVDRV, "called"); IoctlClientData params{}; std::memcpy(¶ms, input.data(), input.size()); params.data = user_data; std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::ZCullBind(const std::vector& input, std::vector& output) { +NvResult nvhost_gpu::ZCullBind(const std::vector& input, std::vector& output) { std::memcpy(&zcull_params, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, gpu_va={:X}, mode={:X}", zcull_params.gpu_va, zcull_params.mode); std::memcpy(output.data(), &zcull_params, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::SetErrorNotifier(const std::vector& input, std::vector& output) { +NvResult nvhost_gpu::SetErrorNotifier(const std::vector& input, std::vector& output) { IoctlSetErrorNotifier params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_WARNING(Service_NVDRV, "(STUBBED) called, offset={:X}, size={:X}, mem={:X}", params.offset, params.size, params.mem); std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::SetChannelPriority(const std::vector& input, std::vector& output) { +NvResult nvhost_gpu::SetChannelPriority(const std::vector& input, std::vector& output) { std::memcpy(&channel_priority, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "(STUBBED) called, priority={:X}", channel_priority); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector& input, std::vector& output) { +NvResult nvhost_gpu::AllocGPFIFOEx2(const std::vector& input, std::vector& output) { IoctlAllocGpfifoEx2 params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_WARNING(Service_NVDRV, @@ -126,15 +157,15 @@ u32 nvhost_gpu::AllocGPFIFOEx2(const std::vector& input, std::vector& ou params.num_entries, params.flags, params.unk0, params.unk1, params.unk2, params.unk3); - auto& gpu = system.GPU(); - params.fence_out.id = assigned_syncpoints; - params.fence_out.value = gpu.GetSyncpointValue(assigned_syncpoints); - assigned_syncpoints++; + channel_fence.value = system.GPU().GetSyncpointValue(channel_fence.id); + + params.fence_out = channel_fence; + std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::AllocateObjectContext(const std::vector& input, std::vector& output) { +NvResult nvhost_gpu::AllocateObjectContext(const std::vector& input, std::vector& output) { IoctlAllocObjCtx params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_WARNING(Service_NVDRV, "(STUBBED) called, class_num={:X}, flags={:X}", params.class_num, @@ -142,102 +173,149 @@ u32 nvhost_gpu::AllocateObjectContext(const std::vector& input, std::vector< params.obj_id = 0x0; std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::SubmitGPFIFO(const std::vector& input, std::vector& output) { - if (input.size() < sizeof(IoctlSubmitGpfifo)) { - UNIMPLEMENTED(); +static std::vector BuildWaitCommandList(Fence fence) { + return { + Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1, + Tegra::SubmissionMode::Increasing), + {fence.value}, + Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceAction, 1, + Tegra::SubmissionMode::Increasing), + Tegra::GPU::FenceAction::Build(Tegra::GPU::FenceOperation::Acquire, fence.id), + }; +} + +static std::vector BuildIncrementCommandList(Fence fence, u32 add_increment) { + std::vector result{ + Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1, + Tegra::SubmissionMode::Increasing), + {}}; + + for (u32 count = 0; count < add_increment; ++count) { + result.emplace_back(Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceAction, 1, + Tegra::SubmissionMode::Increasing)); + result.emplace_back( + Tegra::GPU::FenceAction::Build(Tegra::GPU::FenceOperation::Increment, fence.id)); } - IoctlSubmitGpfifo params{}; - std::memcpy(¶ms, input.data(), sizeof(IoctlSubmitGpfifo)); + + return result; +} + +static std::vector BuildIncrementWithWfiCommandList(Fence fence, + u32 add_increment) { + std::vector result{ + Tegra::BuildCommandHeader(Tegra::BufferMethods::WaitForInterrupt, 1, + Tegra::SubmissionMode::Increasing), + {}}; + const std::vector increment{ + BuildIncrementCommandList(fence, add_increment)}; + + result.insert(result.end(), increment.begin(), increment.end()); + + return result; +} + +NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, std::vector& output, + Tegra::CommandList&& entries) { LOG_TRACE(Service_NVDRV, "called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address, params.num_entries, params.flags.raw); - ASSERT_MSG(input.size() == sizeof(IoctlSubmitGpfifo) + - params.num_entries * sizeof(Tegra::CommandListHeader), - "Incorrect input size"); - - Tegra::CommandList entries(params.num_entries); - std::memcpy(entries.data(), &input[sizeof(IoctlSubmitGpfifo)], - params.num_entries * sizeof(Tegra::CommandListHeader)); - - UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0); - UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0); - auto& gpu = system.GPU(); - u32 current_syncpoint_value = gpu.GetSyncpointValue(params.fence_out.id); - if (params.flags.increment.Value()) { - params.fence_out.value += current_syncpoint_value; - } else { - params.fence_out.value = current_syncpoint_value; + + params.fence_out.id = channel_fence.id; + + if (params.flags.add_wait.Value() && + !syncpoint_manager.IsSyncpointExpired(params.fence_out.id, params.fence_out.value)) { + gpu.PushGPUEntries(Tegra::CommandList{BuildWaitCommandList(params.fence_out)}); } + + if (params.flags.add_increment.Value() || params.flags.increment.Value()) { + const u32 increment_value = params.flags.increment.Value() ? params.fence_out.value : 0; + params.fence_out.value = syncpoint_manager.IncreaseSyncpoint( + params.fence_out.id, params.AddIncrementValue() + increment_value); + } else { + params.fence_out.value = syncpoint_manager.GetSyncpointMax(params.fence_out.id); + } + gpu.PushGPUEntries(std::move(entries)); + if (params.flags.add_increment.Value()) { + if (params.flags.suppress_wfi) { + gpu.PushGPUEntries(Tegra::CommandList{ + BuildIncrementCommandList(params.fence_out, params.AddIncrementValue())}); + } else { + gpu.PushGPUEntries(Tegra::CommandList{ + BuildIncrementWithWfiCommandList(params.fence_out, params.AddIncrementValue())}); + } + } + std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmitGpfifo)); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::KickoffPB(const std::vector& input, std::vector& output, - const std::vector& input2, IoctlVersion version) { +NvResult nvhost_gpu::SubmitGPFIFOBase(const std::vector& input, std::vector& output, + bool kickoff) { if (input.size() < sizeof(IoctlSubmitGpfifo)) { UNIMPLEMENTED(); + return NvResult::InvalidSize; } IoctlSubmitGpfifo params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlSubmitGpfifo)); - LOG_TRACE(Service_NVDRV, "called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address, - params.num_entries, params.flags.raw); - Tegra::CommandList entries(params.num_entries); - if (version == IoctlVersion::Version2) { - std::memcpy(entries.data(), input2.data(), - params.num_entries * sizeof(Tegra::CommandListHeader)); - } else { - system.Memory().ReadBlock(params.address, entries.data(), + + if (kickoff) { + system.Memory().ReadBlock(params.address, entries.command_lists.data(), params.num_entries * sizeof(Tegra::CommandListHeader)); - } - UNIMPLEMENTED_IF(params.flags.add_wait.Value() != 0); - UNIMPLEMENTED_IF(params.flags.add_increment.Value() != 0); - - auto& gpu = system.GPU(); - u32 current_syncpoint_value = gpu.GetSyncpointValue(params.fence_out.id); - if (params.flags.increment.Value()) { - params.fence_out.value += current_syncpoint_value; } else { - params.fence_out.value = current_syncpoint_value; + std::memcpy(entries.command_lists.data(), &input[sizeof(IoctlSubmitGpfifo)], + params.num_entries * sizeof(Tegra::CommandListHeader)); } - gpu.PushGPUEntries(std::move(entries)); - std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return SubmitGPFIFOImpl(params, output, std::move(entries)); } -u32 nvhost_gpu::GetWaitbase(const std::vector& input, std::vector& output) { +NvResult nvhost_gpu::SubmitGPFIFOBase(const std::vector& input, + const std::vector& input_inline, + std::vector& output) { + if (input.size() < sizeof(IoctlSubmitGpfifo)) { + UNIMPLEMENTED(); + return NvResult::InvalidSize; + } + IoctlSubmitGpfifo params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlSubmitGpfifo)); + Tegra::CommandList entries(params.num_entries); + std::memcpy(entries.command_lists.data(), input_inline.data(), input_inline.size()); + return SubmitGPFIFOImpl(params, output, std::move(entries)); +} + +NvResult nvhost_gpu::GetWaitbase(const std::vector& input, std::vector& output) { IoctlGetWaitbase params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlGetWaitbase)); LOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown); params.value = 0; // Seems to be hard coded at 0 std::memcpy(output.data(), ¶ms, output.size()); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::ChannelSetTimeout(const std::vector& input, std::vector& output) { +NvResult nvhost_gpu::ChannelSetTimeout(const std::vector& input, std::vector& output) { IoctlChannelSetTimeout params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlChannelSetTimeout)); LOG_INFO(Service_NVDRV, "called, timeout=0x{:X}", params.timeout); - return 0; + return NvResult::Success; } -u32 nvhost_gpu::ChannelSetTimeslice(const std::vector& input, std::vector& output) { +NvResult nvhost_gpu::ChannelSetTimeslice(const std::vector& input, std::vector& output) { IoctlSetTimeslice params{}; std::memcpy(¶ms, input.data(), sizeof(IoctlSetTimeslice)); LOG_INFO(Service_NVDRV, "called, timeslice=0x{:X}", params.timeslice); channel_timeslice = params.timeslice; - return 0; + return NvResult::Success; } } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h index 2ac74743f..e0298b4fe 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h @@ -11,46 +11,28 @@ #include "common/swap.h" #include "core/hle/service/nvdrv/devices/nvdevice.h" #include "core/hle/service/nvdrv/nvdata.h" +#include "video_core/dma_pusher.h" + +namespace Service::Nvidia { +class SyncpointManager; +} namespace Service::Nvidia::Devices { class nvmap; -constexpr u32 NVGPU_IOCTL_MAGIC('H'); -constexpr u32 NVGPU_IOCTL_CHANNEL_SUBMIT_GPFIFO(0x8); -constexpr u32 NVGPU_IOCTL_CHANNEL_KICKOFF_PB(0x1b); - class nvhost_gpu final : public nvdevice { public: - explicit nvhost_gpu(Core::System& system, std::shared_ptr nvmap_dev); + explicit nvhost_gpu(Core::System& system, std::shared_ptr nvmap_dev, + SyncpointManager& syncpoint_manager); ~nvhost_gpu() override; - u32 ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; + NvResult Ioctl1(Ioctl command, const std::vector& input, std::vector& output) override; + NvResult Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) override; + NvResult Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_output) override; private: - enum class IoctlCommand : u32_le { - IocSetNVMAPfdCommand = 0x40044801, - IocAllocGPFIFOCommand = 0x40084805, - IocSetClientDataCommand = 0x40084714, - IocGetClientDataCommand = 0x80084715, - IocZCullBind = 0xc010480b, - IocSetErrorNotifierCommand = 0xC018480C, - IocChannelSetPriorityCommand = 0x4004480D, - IocEnableCommand = 0x0000480E, - IocDisableCommand = 0x0000480F, - IocPreemptCommand = 0x00004810, - IocForceResetCommand = 0x00004811, - IocEventIdControlCommand = 0x40084812, - IocGetErrorNotificationCommand = 0xC0104817, - IocAllocGPFIFOExCommand = 0x40204818, - IocAllocGPFIFOEx2Command = 0xC020481A, - IocAllocObjCtxCommand = 0xC0104809, - IocChannelGetWaitbaseCommand = 0xC0080003, - IocChannelSetTimeoutCommand = 0x40044803, - IocChannelSetTimeslice = 0xC004481D, - }; - enum class CtxObjects : u32_le { Ctx2D = 0x902D, Ctx3D = 0xB197, @@ -61,63 +43,63 @@ private: }; struct IoctlSetNvmapFD { - u32_le nvmap_fd; + s32_le nvmap_fd{}; }; static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size"); struct IoctlChannelSetTimeout { - u32_le timeout; + u32_le timeout{}; }; static_assert(sizeof(IoctlChannelSetTimeout) == 4, "IoctlChannelSetTimeout is incorrect size"); struct IoctlAllocGPFIFO { - u32_le num_entries; - u32_le flags; + u32_le num_entries{}; + u32_le flags{}; }; static_assert(sizeof(IoctlAllocGPFIFO) == 8, "IoctlAllocGPFIFO is incorrect size"); struct IoctlClientData { - u64_le data; + u64_le data{}; }; static_assert(sizeof(IoctlClientData) == 8, "IoctlClientData is incorrect size"); struct IoctlZCullBind { - u64_le gpu_va; - u32_le mode; // 0=global, 1=no_ctxsw, 2=separate_buffer, 3=part_of_regular_buf + u64_le gpu_va{}; + u32_le mode{}; // 0=global, 1=no_ctxsw, 2=separate_buffer, 3=part_of_regular_buf INSERT_PADDING_WORDS(1); }; static_assert(sizeof(IoctlZCullBind) == 16, "IoctlZCullBind is incorrect size"); struct IoctlSetErrorNotifier { - u64_le offset; - u64_le size; - u32_le mem; // nvmap object handle + u64_le offset{}; + u64_le size{}; + u32_le mem{}; // nvmap object handle INSERT_PADDING_WORDS(1); }; static_assert(sizeof(IoctlSetErrorNotifier) == 24, "IoctlSetErrorNotifier is incorrect size"); struct IoctlChannelSetPriority { - u32_le priority; + u32_le priority{}; }; static_assert(sizeof(IoctlChannelSetPriority) == 4, "IoctlChannelSetPriority is incorrect size"); struct IoctlSetTimeslice { - u32_le timeslice; + u32_le timeslice{}; }; static_assert(sizeof(IoctlSetTimeslice) == 4, "IoctlSetTimeslice is incorrect size"); struct IoctlEventIdControl { - u32_le cmd; // 0=disable, 1=enable, 2=clear - u32_le id; + u32_le cmd{}; // 0=disable, 1=enable, 2=clear + u32_le id{}; }; static_assert(sizeof(IoctlEventIdControl) == 8, "IoctlEventIdControl is incorrect size"); struct IoctlGetErrorNotification { - u64_le timestamp; - u32_le info32; - u16_le info16; - u16_le status; // always 0xFFFF + u64_le timestamp{}; + u32_le info32{}; + u16_le info16{}; + u16_le status{}; // always 0xFFFF }; static_assert(sizeof(IoctlGetErrorNotification) == 16, "IoctlGetErrorNotification is incorrect size"); @@ -125,80 +107,89 @@ private: static_assert(sizeof(Fence) == 8, "Fence is incorrect size"); struct IoctlAllocGpfifoEx { - u32_le num_entries; - u32_le flags; - u32_le unk0; - u32_le unk1; - u32_le unk2; - u32_le unk3; - u32_le unk4; - u32_le unk5; + u32_le num_entries{}; + u32_le flags{}; + u32_le unk0{}; + u32_le unk1{}; + u32_le unk2{}; + u32_le unk3{}; + u32_le unk4{}; + u32_le unk5{}; }; static_assert(sizeof(IoctlAllocGpfifoEx) == 32, "IoctlAllocGpfifoEx is incorrect size"); struct IoctlAllocGpfifoEx2 { - u32_le num_entries; // in - u32_le flags; // in - u32_le unk0; // in (1 works) - Fence fence_out; // out - u32_le unk1; // in - u32_le unk2; // in - u32_le unk3; // in + u32_le num_entries{}; // in + u32_le flags{}; // in + u32_le unk0{}; // in (1 works) + Fence fence_out{}; // out + u32_le unk1{}; // in + u32_le unk2{}; // in + u32_le unk3{}; // in }; static_assert(sizeof(IoctlAllocGpfifoEx2) == 32, "IoctlAllocGpfifoEx2 is incorrect size"); struct IoctlAllocObjCtx { - u32_le class_num; // 0x902D=2d, 0xB197=3d, 0xB1C0=compute, 0xA140=kepler, 0xB0B5=DMA, - // 0xB06F=channel_gpfifo - u32_le flags; - u64_le obj_id; // (ignored) used for FREE_OBJ_CTX ioctl, which is not supported + u32_le class_num{}; // 0x902D=2d, 0xB197=3d, 0xB1C0=compute, 0xA140=kepler, 0xB0B5=DMA, + // 0xB06F=channel_gpfifo + u32_le flags{}; + u64_le obj_id{}; // (ignored) used for FREE_OBJ_CTX ioctl, which is not supported }; static_assert(sizeof(IoctlAllocObjCtx) == 16, "IoctlAllocObjCtx is incorrect size"); struct IoctlSubmitGpfifo { - u64_le address; // pointer to gpfifo entry structs - u32_le num_entries; // number of fence objects being submitted + u64_le address{}; // pointer to gpfifo entry structs + u32_le num_entries{}; // number of fence objects being submitted union { u32_le raw; BitField<0, 1, u32_le> add_wait; // append a wait sync_point to the list BitField<1, 1, u32_le> add_increment; // append an increment to the list - BitField<2, 1, u32_le> new_hw_format; // Mostly ignored + BitField<2, 1, u32_le> new_hw_format; // mostly ignored + BitField<4, 1, u32_le> suppress_wfi; // suppress wait for interrupt BitField<8, 1, u32_le> increment; // increment the returned fence } flags; - Fence fence_out; // returned new fence object for others to wait on + Fence fence_out{}; // returned new fence object for others to wait on + + u32 AddIncrementValue() const { + return flags.add_increment.Value() << 1; + } }; static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(Fence), "IoctlSubmitGpfifo is incorrect size"); struct IoctlGetWaitbase { - u32 unknown; // seems to be ignored? Nintendo added this - u32 value; + u32 unknown{}; // seems to be ignored? Nintendo added this + u32 value{}; }; static_assert(sizeof(IoctlGetWaitbase) == 8, "IoctlGetWaitbase is incorrect size"); - u32_le nvmap_fd{}; + s32_le nvmap_fd{}; u64_le user_data{}; IoctlZCullBind zcull_params{}; u32_le channel_priority{}; u32_le channel_timeslice{}; - u32 SetNVMAPfd(const std::vector& input, std::vector& output); - u32 SetClientData(const std::vector& input, std::vector& output); - u32 GetClientData(const std::vector& input, std::vector& output); - u32 ZCullBind(const std::vector& input, std::vector& output); - u32 SetErrorNotifier(const std::vector& input, std::vector& output); - u32 SetChannelPriority(const std::vector& input, std::vector& output); - u32 AllocGPFIFOEx2(const std::vector& input, std::vector& output); - u32 AllocateObjectContext(const std::vector& input, std::vector& output); - u32 SubmitGPFIFO(const std::vector& input, std::vector& output); - u32 KickoffPB(const std::vector& input, std::vector& output, - const std::vector& input2, IoctlVersion version); - u32 GetWaitbase(const std::vector& input, std::vector& output); - u32 ChannelSetTimeout(const std::vector& input, std::vector& output); - u32 ChannelSetTimeslice(const std::vector& input, std::vector& output); + NvResult SetNVMAPfd(const std::vector& input, std::vector& output); + NvResult SetClientData(const std::vector& input, std::vector& output); + NvResult GetClientData(const std::vector& input, std::vector& output); + NvResult ZCullBind(const std::vector& input, std::vector& output); + NvResult SetErrorNotifier(const std::vector& input, std::vector& output); + NvResult SetChannelPriority(const std::vector& input, std::vector& output); + NvResult AllocGPFIFOEx2(const std::vector& input, std::vector& output); + NvResult AllocateObjectContext(const std::vector& input, std::vector& output); + NvResult SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, std::vector& output, + Tegra::CommandList&& entries); + NvResult SubmitGPFIFOBase(const std::vector& input, std::vector& output, + bool kickoff = false); + NvResult SubmitGPFIFOBase(const std::vector& input, const std::vector& input_inline, + std::vector& output); + NvResult GetWaitbase(const std::vector& input, std::vector& output); + NvResult ChannelSetTimeout(const std::vector& input, std::vector& output); + NvResult ChannelSetTimeslice(const std::vector& input, std::vector& output); std::shared_ptr nvmap_dev; - u32 assigned_syncpoints{}; + SyncpointManager& syncpoint_manager; + Fence channel_fence; }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp index bdae8b887..36970f828 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp @@ -2,39 +2,72 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include - #include "common/assert.h" #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/service/nvdrv/devices/nvhost_nvdec.h" +#include "video_core/memory_manager.h" +#include "video_core/renderer_base.h" namespace Service::Nvidia::Devices { -nvhost_nvdec::nvhost_nvdec(Core::System& system) : nvdevice(system) {} +nvhost_nvdec::nvhost_nvdec(Core::System& system, std::shared_ptr nvmap_dev, + SyncpointManager& syncpoint_manager) + : nvhost_nvdec_common(system, std::move(nvmap_dev), syncpoint_manager) {} nvhost_nvdec::~nvhost_nvdec() = default; -u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", - command.raw, input.size(), output.size()); - - switch (static_cast(command.raw)) { - case IoctlCommand::IocSetNVMAPfdCommand: - return SetNVMAPfd(input, output); +NvResult nvhost_nvdec::Ioctl1(Ioctl command, const std::vector& input, + std::vector& output) { + switch (command.group) { + case 0x0: + switch (command.cmd) { + case 0x1: + return Submit(input, output); + case 0x2: + return GetSyncpoint(input, output); + case 0x3: + return GetWaitbase(input, output); + case 0x7: + return SetSubmitTimeout(input, output); + case 0x9: + return MapBuffer(input, output); + case 0xa: { + if (command.length == 0x1c) { + LOG_INFO(Service_NVDRV, "NVDEC video stream ended"); + Tegra::ChCommandHeaderList cmdlist(1); + cmdlist[0] = Tegra::ChCommandHeader{0xDEADB33F}; + system.GPU().PushCommandBuffer(cmdlist); + } + return UnmapBuffer(input, output); + } + default: + break; + } + break; + case 'H': + switch (command.cmd) { + case 0x1: + return SetNVMAPfd(input); + default: + break; + } + break; } - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } -u32 nvhost_nvdec::SetNVMAPfd(const std::vector& input, std::vector& output) { - IoctlSetNvmapFD params{}; - std::memcpy(¶ms, input.data(), input.size()); - LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); +NvResult nvhost_nvdec::Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} - nvmap_fd = params.nvmap_fd; - return 0; +NvResult nvhost_nvdec::Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h index cbdac8069..77ef53cdd 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h @@ -4,35 +4,22 @@ #pragma once -#include -#include "common/common_types.h" -#include "common/swap.h" -#include "core/hle/service/nvdrv/devices/nvdevice.h" +#include +#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h" namespace Service::Nvidia::Devices { -class nvhost_nvdec final : public nvdevice { +class nvhost_nvdec final : public nvhost_nvdec_common { public: - explicit nvhost_nvdec(Core::System& system); + explicit nvhost_nvdec(Core::System& system, std::shared_ptr nvmap_dev, + SyncpointManager& syncpoint_manager); ~nvhost_nvdec() override; - u32 ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; - -private: - enum class IoctlCommand : u32_le { - IocSetNVMAPfdCommand = 0x40044801, - }; - - struct IoctlSetNvmapFD { - u32_le nvmap_fd; - }; - static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size"); - - u32_le nvmap_fd{}; - - u32 SetNVMAPfd(const std::vector& input, std::vector& output); + NvResult Ioctl1(Ioctl command, const std::vector& input, std::vector& output) override; + NvResult Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) override; + NvResult Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_output) override; }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp new file mode 100644 index 000000000..4898dc27a --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp @@ -0,0 +1,244 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +#include "common/assert.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h" +#include "core/hle/service/nvdrv/devices/nvmap.h" +#include "core/hle/service/nvdrv/syncpoint_manager.h" +#include "core/memory.h" +#include "video_core/memory_manager.h" +#include "video_core/renderer_base.h" + +namespace Service::Nvidia::Devices { + +namespace { +// Splice vectors will copy count amount of type T from the input vector into the dst vector. +template +std::size_t SpliceVectors(const std::vector& input, std::vector& dst, std::size_t count, + std::size_t offset) { + std::memcpy(dst.data(), input.data() + offset, count * sizeof(T)); + offset += count * sizeof(T); + return offset; +} + +// Write vectors will write data to the output buffer +template +std::size_t WriteVectors(std::vector& dst, const std::vector& src, std::size_t offset) { + std::memcpy(dst.data() + offset, src.data(), src.size() * sizeof(T)); + offset += src.size() * sizeof(T); + return offset; +} +} // Anonymous namespace + +nvhost_nvdec_common::nvhost_nvdec_common(Core::System& system, std::shared_ptr nvmap_dev, + SyncpointManager& syncpoint_manager) + : nvdevice(system), nvmap_dev(std::move(nvmap_dev)), syncpoint_manager(syncpoint_manager) {} +nvhost_nvdec_common::~nvhost_nvdec_common() = default; + +NvResult nvhost_nvdec_common::SetNVMAPfd(const std::vector& input) { + IoctlSetNvmapFD params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlSetNvmapFD)); + LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); + + nvmap_fd = params.nvmap_fd; + return NvResult::Success; +} + +NvResult nvhost_nvdec_common::Submit(const std::vector& input, std::vector& output) { + IoctlSubmit params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlSubmit)); + LOG_DEBUG(Service_NVDRV, "called NVDEC Submit, cmd_buffer_count={}", params.cmd_buffer_count); + + // Instantiate param buffers + std::size_t offset = sizeof(IoctlSubmit); + std::vector command_buffers(params.cmd_buffer_count); + std::vector relocs(params.relocation_count); + std::vector reloc_shifts(params.relocation_count); + std::vector syncpt_increments(params.syncpoint_count); + std::vector wait_checks(params.syncpoint_count); + std::vector fences(params.fence_count); + + // Splice input into their respective buffers + offset = SpliceVectors(input, command_buffers, params.cmd_buffer_count, offset); + offset = SpliceVectors(input, relocs, params.relocation_count, offset); + offset = SpliceVectors(input, reloc_shifts, params.relocation_count, offset); + offset = SpliceVectors(input, syncpt_increments, params.syncpoint_count, offset); + offset = SpliceVectors(input, wait_checks, params.syncpoint_count, offset); + offset = SpliceVectors(input, fences, params.fence_count, offset); + + auto& gpu = system.GPU(); + if (gpu.UseNvdec()) { + for (std::size_t i = 0; i < syncpt_increments.size(); i++) { + const SyncptIncr& syncpt_incr = syncpt_increments[i]; + fences[i].id = syncpt_incr.id; + fences[i].value = + syncpoint_manager.IncreaseSyncpoint(syncpt_incr.id, syncpt_incr.increments); + } + } + for (const auto& cmd_buffer : command_buffers) { + auto object = nvmap_dev->GetObject(cmd_buffer.memory_id); + ASSERT_OR_EXECUTE(object, return NvResult::InvalidState;); + const auto map = FindBufferMap(object->dma_map_addr); + if (!map) { + LOG_ERROR(Service_NVDRV, "Tried to submit an invalid offset 0x{:X} dma 0x{:X}", + object->addr, object->dma_map_addr); + return NvResult::Success; + } + Tegra::ChCommandHeaderList cmdlist(cmd_buffer.word_count); + gpu.MemoryManager().ReadBlock(map->StartAddr() + cmd_buffer.offset, cmdlist.data(), + cmdlist.size() * sizeof(u32)); + gpu.PushCommandBuffer(cmdlist); + } + if (gpu.UseNvdec()) { + + fences[0].value = syncpoint_manager.IncreaseSyncpoint(fences[0].id, 1); + + Tegra::ChCommandHeaderList cmdlist{{(4 << 28) | fences[0].id}}; + gpu.PushCommandBuffer(cmdlist); + } + std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmit)); + // Some games expect command_buffers to be written back + offset = sizeof(IoctlSubmit); + offset = WriteVectors(output, command_buffers, offset); + offset = WriteVectors(output, relocs, offset); + offset = WriteVectors(output, reloc_shifts, offset); + offset = WriteVectors(output, syncpt_increments, offset); + offset = WriteVectors(output, wait_checks, offset); + offset = WriteVectors(output, fences, offset); + + return NvResult::Success; +} + +NvResult nvhost_nvdec_common::GetSyncpoint(const std::vector& input, std::vector& output) { + IoctlGetSyncpoint params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlGetSyncpoint)); + LOG_DEBUG(Service_NVDRV, "called GetSyncpoint, id={}", params.param); + + if (device_syncpoints[params.param] == 0 && system.GPU().UseNvdec()) { + device_syncpoints[params.param] = syncpoint_manager.AllocateSyncpoint(); + } + params.value = device_syncpoints[params.param]; + std::memcpy(output.data(), ¶ms, sizeof(IoctlGetSyncpoint)); + + return NvResult::Success; +} + +NvResult nvhost_nvdec_common::GetWaitbase(const std::vector& input, std::vector& output) { + IoctlGetWaitbase params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlGetWaitbase)); + params.value = 0; // Seems to be hard coded at 0 + std::memcpy(output.data(), ¶ms, sizeof(IoctlGetWaitbase)); + return NvResult::Success; +} + +NvResult nvhost_nvdec_common::MapBuffer(const std::vector& input, std::vector& output) { + IoctlMapBuffer params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlMapBuffer)); + std::vector cmd_buffer_handles(params.num_entries); + + SpliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer)); + + auto& gpu = system.GPU(); + + for (auto& cmf_buff : cmd_buffer_handles) { + auto object{nvmap_dev->GetObject(cmf_buff.map_handle)}; + if (!object) { + LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmf_buff.map_handle); + std::memcpy(output.data(), ¶ms, output.size()); + return NvResult::InvalidState; + } + if (object->dma_map_addr == 0) { + // NVDEC and VIC memory is in the 32-bit address space + // MapAllocate32 will attempt to map a lower 32-bit value in the shared gpu memory space + const GPUVAddr low_addr = gpu.MemoryManager().MapAllocate32(object->addr, object->size); + object->dma_map_addr = static_cast(low_addr); + // Ensure that the dma_map_addr is indeed in the lower 32-bit address space. + ASSERT(object->dma_map_addr == low_addr); + } + if (!object->dma_map_addr) { + LOG_ERROR(Service_NVDRV, "failed to map size={}", object->size); + } else { + cmf_buff.map_address = object->dma_map_addr; + AddBufferMap(object->dma_map_addr, object->size, object->addr, + object->status == nvmap::Object::Status::Allocated); + } + } + std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBuffer)); + std::memcpy(output.data() + sizeof(IoctlMapBuffer), cmd_buffer_handles.data(), + cmd_buffer_handles.size() * sizeof(MapBufferEntry)); + + return NvResult::Success; +} + +NvResult nvhost_nvdec_common::UnmapBuffer(const std::vector& input, std::vector& output) { + IoctlMapBuffer params{}; + std::memcpy(¶ms, input.data(), sizeof(IoctlMapBuffer)); + std::vector cmd_buffer_handles(params.num_entries); + SpliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer)); + + auto& gpu = system.GPU(); + + for (auto& cmf_buff : cmd_buffer_handles) { + const auto object{nvmap_dev->GetObject(cmf_buff.map_handle)}; + if (!object) { + LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmf_buff.map_handle); + std::memcpy(output.data(), ¶ms, output.size()); + return NvResult::InvalidState; + } + if (const auto size{RemoveBufferMap(object->dma_map_addr)}; size) { + gpu.MemoryManager().Unmap(object->dma_map_addr, *size); + } else { + // This occurs quite frequently, however does not seem to impact functionality + LOG_DEBUG(Service_NVDRV, "invalid offset=0x{:X} dma=0x{:X}", object->addr, + object->dma_map_addr); + } + object->dma_map_addr = 0; + } + std::memset(output.data(), 0, output.size()); + return NvResult::Success; +} + +NvResult nvhost_nvdec_common::SetSubmitTimeout(const std::vector& input, + std::vector& output) { + std::memcpy(&submit_timeout, input.data(), input.size()); + LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + return NvResult::Success; +} + +std::optional nvhost_nvdec_common::FindBufferMap( + GPUVAddr gpu_addr) const { + const auto it = std::find_if( + buffer_mappings.begin(), buffer_mappings.upper_bound(gpu_addr), [&](const auto& entry) { + return (gpu_addr >= entry.second.StartAddr() && gpu_addr < entry.second.EndAddr()); + }); + + ASSERT(it != buffer_mappings.end()); + return it->second; +} + +void nvhost_nvdec_common::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, + bool is_allocated) { + buffer_mappings.insert_or_assign(gpu_addr, BufferMap{gpu_addr, size, cpu_addr, is_allocated}); +} + +std::optional nvhost_nvdec_common::RemoveBufferMap(GPUVAddr gpu_addr) { + const auto iter{buffer_mappings.find(gpu_addr)}; + if (iter == buffer_mappings.end()) { + return std::nullopt; + } + std::size_t size = 0; + if (iter->second.IsAllocated()) { + size = iter->second.Size(); + } + buffer_mappings.erase(iter); + return size; +} + +} // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h new file mode 100644 index 000000000..4c9d4ba41 --- /dev/null +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h @@ -0,0 +1,170 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/service/nvdrv/devices/nvdevice.h" + +namespace Service::Nvidia { +class SyncpointManager; + +namespace Devices { +class nvmap; + +class nvhost_nvdec_common : public nvdevice { +public: + explicit nvhost_nvdec_common(Core::System& system, std::shared_ptr nvmap_dev, + SyncpointManager& syncpoint_manager); + ~nvhost_nvdec_common() override; + +protected: + class BufferMap final { + public: + constexpr BufferMap() = default; + + constexpr BufferMap(GPUVAddr start_addr, std::size_t size) + : start_addr{start_addr}, end_addr{start_addr + size} {} + + constexpr BufferMap(GPUVAddr start_addr, std::size_t size, VAddr cpu_addr, + bool is_allocated) + : start_addr{start_addr}, end_addr{start_addr + size}, cpu_addr{cpu_addr}, + is_allocated{is_allocated} {} + + constexpr VAddr StartAddr() const { + return start_addr; + } + + constexpr VAddr EndAddr() const { + return end_addr; + } + + constexpr std::size_t Size() const { + return end_addr - start_addr; + } + + constexpr VAddr CpuAddr() const { + return cpu_addr; + } + + constexpr bool IsAllocated() const { + return is_allocated; + } + + private: + GPUVAddr start_addr{}; + GPUVAddr end_addr{}; + VAddr cpu_addr{}; + bool is_allocated{}; + }; + + struct IoctlSetNvmapFD { + s32_le nvmap_fd{}; + }; + static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size"); + + struct IoctlSubmitCommandBuffer { + u32_le id{}; + u32_le offset{}; + u32_le count{}; + }; + static_assert(sizeof(IoctlSubmitCommandBuffer) == 0xC, + "IoctlSubmitCommandBuffer is incorrect size"); + struct IoctlSubmit { + u32_le cmd_buffer_count{}; + u32_le relocation_count{}; + u32_le syncpoint_count{}; + u32_le fence_count{}; + }; + static_assert(sizeof(IoctlSubmit) == 0x10, "IoctlSubmit has incorrect size"); + + struct CommandBuffer { + s32 memory_id{}; + u32 offset{}; + s32 word_count{}; + }; + static_assert(sizeof(CommandBuffer) == 0xC, "CommandBuffer has incorrect size"); + + struct Reloc { + s32 cmdbuffer_memory{}; + s32 cmdbuffer_offset{}; + s32 target{}; + s32 target_offset{}; + }; + static_assert(sizeof(Reloc) == 0x10, "CommandBuffer has incorrect size"); + + struct SyncptIncr { + u32 id{}; + u32 increments{}; + }; + static_assert(sizeof(SyncptIncr) == 0x8, "CommandBuffer has incorrect size"); + + struct Fence { + u32 id{}; + u32 value{}; + }; + static_assert(sizeof(Fence) == 0x8, "CommandBuffer has incorrect size"); + + struct IoctlGetSyncpoint { + // Input + u32_le param{}; + // Output + u32_le value{}; + }; + static_assert(sizeof(IoctlGetSyncpoint) == 8, "IocGetIdParams has wrong size"); + + struct IoctlGetWaitbase { + u32_le unknown{}; // seems to be ignored? Nintendo added this + u32_le value{}; + }; + static_assert(sizeof(IoctlGetWaitbase) == 0x8, "IoctlGetWaitbase is incorrect size"); + + struct IoctlMapBuffer { + u32_le num_entries{}; + u32_le data_address{}; // Ignored by the driver. + u32_le attach_host_ch_das{}; + }; + static_assert(sizeof(IoctlMapBuffer) == 0x0C, "IoctlMapBuffer is incorrect size"); + + struct IocGetIdParams { + // Input + u32_le param{}; + // Output + u32_le value{}; + }; + static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size"); + + // Used for mapping and unmapping command buffers + struct MapBufferEntry { + u32_le map_handle{}; + u32_le map_address{}; + }; + static_assert(sizeof(IoctlMapBuffer) == 0x0C, "IoctlMapBuffer is incorrect size"); + + /// Ioctl command implementations + NvResult SetNVMAPfd(const std::vector& input); + NvResult Submit(const std::vector& input, std::vector& output); + NvResult GetSyncpoint(const std::vector& input, std::vector& output); + NvResult GetWaitbase(const std::vector& input, std::vector& output); + NvResult MapBuffer(const std::vector& input, std::vector& output); + NvResult UnmapBuffer(const std::vector& input, std::vector& output); + NvResult SetSubmitTimeout(const std::vector& input, std::vector& output); + + std::optional FindBufferMap(GPUVAddr gpu_addr) const; + void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated); + std::optional RemoveBufferMap(GPUVAddr gpu_addr); + + s32_le nvmap_fd{}; + u32_le submit_timeout{}; + std::shared_ptr nvmap_dev; + SyncpointManager& syncpoint_manager; + std::array device_syncpoints{}; + // This is expected to be ordered, therefore we must use a map, not unordered_map + std::map buffer_mappings; +}; +}; // namespace Devices +} // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp index 96e7b7dab..2d06955c0 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp @@ -13,28 +13,44 @@ namespace Service::Nvidia::Devices { nvhost_nvjpg::nvhost_nvjpg(Core::System& system) : nvdevice(system) {} nvhost_nvjpg::~nvhost_nvjpg() = default; -u32 nvhost_nvjpg::ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", - command.raw, input.size(), output.size()); - - switch (static_cast(command.raw)) { - case IoctlCommand::IocSetNVMAPfdCommand: - return SetNVMAPfd(input, output); +NvResult nvhost_nvjpg::Ioctl1(Ioctl command, const std::vector& input, + std::vector& output) { + switch (command.group) { + case 'H': + switch (command.cmd) { + case 0x1: + return SetNVMAPfd(input, output); + default: + break; + } + break; + default: + break; } - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } -u32 nvhost_nvjpg::SetNVMAPfd(const std::vector& input, std::vector& output) { +NvResult nvhost_nvjpg::Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvhost_nvjpg::Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvhost_nvjpg::SetNVMAPfd(const std::vector& input, std::vector& output) { IoctlSetNvmapFD params{}; std::memcpy(¶ms, input.data(), input.size()); LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); nvmap_fd = params.nvmap_fd; - return 0; + return NvResult::Success; } } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h index 98dcac52f..43948d18d 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h @@ -16,23 +16,21 @@ public: explicit nvhost_nvjpg(Core::System& system); ~nvhost_nvjpg() override; - u32 ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; + NvResult Ioctl1(Ioctl command, const std::vector& input, std::vector& output) override; + NvResult Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) override; + NvResult Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_output) override; private: - enum class IoctlCommand : u32_le { - IocSetNVMAPfdCommand = 0x40044801, - }; - struct IoctlSetNvmapFD { - u32_le nvmap_fd; + s32_le nvmap_fd{}; }; static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size"); - u32_le nvmap_fd{}; + s32_le nvmap_fd{}; - u32 SetNVMAPfd(const std::vector& input, std::vector& output); + NvResult SetNVMAPfd(const std::vector& input, std::vector& output); }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp index c695b8863..72499654c 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp @@ -2,39 +2,64 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include - #include "common/assert.h" #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/service/nvdrv/devices/nvhost_vic.h" +#include "video_core/memory_manager.h" +#include "video_core/renderer_base.h" namespace Service::Nvidia::Devices { +nvhost_vic::nvhost_vic(Core::System& system, std::shared_ptr nvmap_dev, + SyncpointManager& syncpoint_manager) + : nvhost_nvdec_common(system, std::move(nvmap_dev), syncpoint_manager) {} -nvhost_vic::nvhost_vic(Core::System& system) : nvdevice(system) {} nvhost_vic::~nvhost_vic() = default; -u32 nvhost_vic::ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", - command.raw, input.size(), output.size()); - - switch (static_cast(command.raw)) { - case IoctlCommand::IocSetNVMAPfdCommand: - return SetNVMAPfd(input, output); +NvResult nvhost_vic::Ioctl1(Ioctl command, const std::vector& input, std::vector& output) { + switch (command.group) { + case 0x0: + switch (command.cmd) { + case 0x1: + return Submit(input, output); + case 0x2: + return GetSyncpoint(input, output); + case 0x3: + return GetWaitbase(input, output); + case 0x9: + return MapBuffer(input, output); + case 0xa: + return UnmapBuffer(input, output); + default: + break; + } + break; + case 'H': + switch (command.cmd) { + case 0x1: + return SetNVMAPfd(input); + default: + break; + } + break; + default: + break; } - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } -u32 nvhost_vic::SetNVMAPfd(const std::vector& input, std::vector& output) { - IoctlSetNvmapFD params{}; - std::memcpy(¶ms, input.data(), input.size()); - LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); +NvResult nvhost_vic::Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} - nvmap_fd = params.nvmap_fd; - return 0; +NvResult nvhost_vic::Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; } } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.h b/src/core/hle/service/nvdrv/devices/nvhost_vic.h index bec32bea1..f401c61fa 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_vic.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.h @@ -4,35 +4,20 @@ #pragma once -#include -#include "common/common_types.h" -#include "common/swap.h" -#include "core/hle/service/nvdrv/devices/nvdevice.h" +#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h" namespace Service::Nvidia::Devices { -class nvhost_vic final : public nvdevice { +class nvhost_vic final : public nvhost_nvdec_common { public: - explicit nvhost_vic(Core::System& system); - ~nvhost_vic() override; + explicit nvhost_vic(Core::System& system, std::shared_ptr nvmap_dev, + SyncpointManager& syncpoint_manager); + ~nvhost_vic(); - u32 ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; - -private: - enum class IoctlCommand : u32_le { - IocSetNVMAPfdCommand = 0x40044801, - }; - - struct IoctlSetNvmapFD { - u32_le nvmap_fd; - }; - static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size"); - - u32_le nvmap_fd{}; - - u32 SetNVMAPfd(const std::vector& input, std::vector& output); + NvResult Ioctl1(Ioctl command, const std::vector& input, std::vector& output) override; + NvResult Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) override; + NvResult Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_output) override; }; - } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp index 9436e16ad..4015a2740 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.cpp +++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp @@ -11,13 +11,6 @@ namespace Service::Nvidia::Devices { -namespace NvErrCodes { -enum { - OperationNotPermitted = -1, - InvalidValue = -22, -}; -} - nvmap::nvmap(Core::System& system) : nvdevice(system) { // Handle 0 appears to be used when remapping, so we create a placeholder empty nvmap object to // represent this. @@ -26,6 +19,46 @@ nvmap::nvmap(Core::System& system) : nvdevice(system) { nvmap::~nvmap() = default; +NvResult nvmap::Ioctl1(Ioctl command, const std::vector& input, std::vector& output) { + switch (command.group) { + case 0x1: + switch (command.cmd) { + case 0x1: + return IocCreate(input, output); + case 0x3: + return IocFromId(input, output); + case 0x4: + return IocAlloc(input, output); + case 0x5: + return IocFree(input, output); + case 0x9: + return IocParam(input, output); + case 0xe: + return IocGetId(input, output); + default: + break; + } + break; + default: + break; + } + + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvmap::Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + +NvResult nvmap::Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_output) { + UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); + return NvResult::NotImplemented; +} + VAddr nvmap::GetObjectAddress(u32 handle) const { auto object = GetObject(handle); ASSERT(object); @@ -33,28 +66,6 @@ VAddr nvmap::GetObjectAddress(u32 handle) const { return object->addr; } -u32 nvmap::ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - switch (static_cast(command.raw)) { - case IoctlCommand::Create: - return IocCreate(input, output); - case IoctlCommand::Alloc: - return IocAlloc(input, output); - case IoctlCommand::GetId: - return IocGetId(input, output); - case IoctlCommand::FromId: - return IocFromId(input, output); - case IoctlCommand::Param: - return IocParam(input, output); - case IoctlCommand::Free: - return IocFree(input, output); - } - - UNIMPLEMENTED_MSG("Unimplemented ioctl"); - return 0; -} - u32 nvmap::CreateObject(u32 size) { // Create a new nvmap object and obtain a handle to it. auto object = std::make_shared(); @@ -70,35 +81,35 @@ u32 nvmap::CreateObject(u32 size) { return handle; } -u32 nvmap::IocCreate(const std::vector& input, std::vector& output) { +NvResult nvmap::IocCreate(const std::vector& input, std::vector& output) { IocCreateParams params; std::memcpy(¶ms, input.data(), sizeof(params)); LOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size); if (!params.size) { LOG_ERROR(Service_NVDRV, "Size is 0"); - return static_cast(NvErrCodes::InvalidValue); + return NvResult::BadValue; } params.handle = CreateObject(params.size); std::memcpy(output.data(), ¶ms, sizeof(params)); - return 0; + return NvResult::Success; } -u32 nvmap::IocAlloc(const std::vector& input, std::vector& output) { +NvResult nvmap::IocAlloc(const std::vector& input, std::vector& output) { IocAllocParams params; std::memcpy(¶ms, input.data(), sizeof(params)); LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr); if (!params.handle) { LOG_ERROR(Service_NVDRV, "Handle is 0"); - return static_cast(NvErrCodes::InvalidValue); + return NvResult::BadValue; } if ((params.align - 1) & params.align) { LOG_ERROR(Service_NVDRV, "Incorrect alignment used, alignment={:08X}", params.align); - return static_cast(NvErrCodes::InvalidValue); + return NvResult::BadValue; } const u32 min_alignment = 0x1000; @@ -109,12 +120,12 @@ u32 nvmap::IocAlloc(const std::vector& input, std::vector& output) { auto object = GetObject(params.handle); if (!object) { LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle); - return static_cast(NvErrCodes::InvalidValue); + return NvResult::BadValue; } if (object->status == Object::Status::Allocated) { LOG_ERROR(Service_NVDRV, "Object is already allocated, handle={:08X}", params.handle); - return static_cast(NvErrCodes::OperationNotPermitted); + return NvResult::InsufficientMemory; } object->flags = params.flags; @@ -124,10 +135,10 @@ u32 nvmap::IocAlloc(const std::vector& input, std::vector& output) { object->status = Object::Status::Allocated; std::memcpy(output.data(), ¶ms, sizeof(params)); - return 0; + return NvResult::Success; } -u32 nvmap::IocGetId(const std::vector& input, std::vector& output) { +NvResult nvmap::IocGetId(const std::vector& input, std::vector& output) { IocGetIdParams params; std::memcpy(¶ms, input.data(), sizeof(params)); @@ -135,22 +146,22 @@ u32 nvmap::IocGetId(const std::vector& input, std::vector& output) { if (!params.handle) { LOG_ERROR(Service_NVDRV, "Handle is zero"); - return static_cast(NvErrCodes::InvalidValue); + return NvResult::BadValue; } auto object = GetObject(params.handle); if (!object) { LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle); - return static_cast(NvErrCodes::OperationNotPermitted); + return NvResult::BadValue; } params.id = object->id; std::memcpy(output.data(), ¶ms, sizeof(params)); - return 0; + return NvResult::Success; } -u32 nvmap::IocFromId(const std::vector& input, std::vector& output) { +NvResult nvmap::IocFromId(const std::vector& input, std::vector& output) { IocFromIdParams params; std::memcpy(¶ms, input.data(), sizeof(params)); @@ -160,13 +171,13 @@ u32 nvmap::IocFromId(const std::vector& input, std::vector& output) { [&](const auto& entry) { return entry.second->id == params.id; }); if (itr == handles.end()) { LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle); - return static_cast(NvErrCodes::InvalidValue); + return NvResult::BadValue; } auto& object = itr->second; if (object->status != Object::Status::Allocated) { LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle); - return static_cast(NvErrCodes::InvalidValue); + return NvResult::BadValue; } itr->second->refcount++; @@ -175,10 +186,10 @@ u32 nvmap::IocFromId(const std::vector& input, std::vector& output) { params.handle = itr->first; std::memcpy(output.data(), ¶ms, sizeof(params)); - return 0; + return NvResult::Success; } -u32 nvmap::IocParam(const std::vector& input, std::vector& output) { +NvResult nvmap::IocParam(const std::vector& input, std::vector& output) { enum class ParamTypes { Size = 1, Alignment = 2, Base = 3, Heap = 4, Kind = 5, Compr = 6 }; IocParamParams params; @@ -189,12 +200,12 @@ u32 nvmap::IocParam(const std::vector& input, std::vector& output) { auto object = GetObject(params.handle); if (!object) { LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle); - return static_cast(NvErrCodes::InvalidValue); + return NvResult::BadValue; } if (object->status != Object::Status::Allocated) { LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle); - return static_cast(NvErrCodes::OperationNotPermitted); + return NvResult::BadValue; } switch (static_cast(params.param)) { @@ -216,10 +227,10 @@ u32 nvmap::IocParam(const std::vector& input, std::vector& output) { } std::memcpy(output.data(), ¶ms, sizeof(params)); - return 0; + return NvResult::Success; } -u32 nvmap::IocFree(const std::vector& input, std::vector& output) { +NvResult nvmap::IocFree(const std::vector& input, std::vector& output) { // TODO(Subv): These flags are unconfirmed. enum FreeFlags { Freed = 0, @@ -234,14 +245,14 @@ u32 nvmap::IocFree(const std::vector& input, std::vector& output) { auto itr = handles.find(params.handle); if (itr == handles.end()) { LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle); - return static_cast(NvErrCodes::InvalidValue); + return NvResult::BadValue; } if (!itr->second->refcount) { LOG_ERROR( Service_NVDRV, "There is no references to this object. The object is already freed. handle={:08X}", params.handle); - return static_cast(NvErrCodes::InvalidValue); + return NvResult::BadValue; } itr->second->refcount--; @@ -261,7 +272,7 @@ u32 nvmap::IocFree(const std::vector& input, std::vector& output) { handles.erase(params.handle); std::memcpy(output.data(), ¶ms, sizeof(params)); - return 0; + return NvResult::Success; } } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h index 84624be00..4484bd79f 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.h +++ b/src/core/hle/service/nvdrv/devices/nvmap.h @@ -19,13 +19,15 @@ public: explicit nvmap(Core::System& system); ~nvmap() override; + NvResult Ioctl1(Ioctl command, const std::vector& input, std::vector& output) override; + NvResult Ioctl2(Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) override; + NvResult Ioctl3(Ioctl command, const std::vector& input, std::vector& output, + std::vector& inline_output) override; + /// Returns the allocated address of an nvmap object given its handle. VAddr GetObjectAddress(u32 handle) const; - u32 ioctl(Ioctl command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) override; - /// Represents an nvmap object. struct Object { enum class Status { Created, Allocated }; @@ -37,6 +39,7 @@ public: VAddr addr; Status status; u32 refcount; + u32 dma_map_addr; }; std::shared_ptr GetObject(u32 handle) const { @@ -57,76 +60,68 @@ private: /// Mapping of currently allocated handles to the objects they represent. std::unordered_map> handles; - enum class IoctlCommand : u32 { - Create = 0xC0080101, - FromId = 0xC0080103, - Alloc = 0xC0200104, - Free = 0xC0180105, - Param = 0xC00C0109, - GetId = 0xC008010E, - }; struct IocCreateParams { // Input - u32_le size; + u32_le size{}; // Output - u32_le handle; + u32_le handle{}; }; static_assert(sizeof(IocCreateParams) == 8, "IocCreateParams has wrong size"); struct IocFromIdParams { // Input - u32_le id; + u32_le id{}; // Output - u32_le handle; + u32_le handle{}; }; static_assert(sizeof(IocFromIdParams) == 8, "IocFromIdParams has wrong size"); struct IocAllocParams { // Input - u32_le handle; - u32_le heap_mask; - u32_le flags; - u32_le align; - u8 kind; + u32_le handle{}; + u32_le heap_mask{}; + u32_le flags{}; + u32_le align{}; + u8 kind{}; INSERT_PADDING_BYTES(7); - u64_le addr; + u64_le addr{}; }; static_assert(sizeof(IocAllocParams) == 32, "IocAllocParams has wrong size"); struct IocFreeParams { - u32_le handle; + u32_le handle{}; INSERT_PADDING_BYTES(4); - u64_le address; - u32_le size; - u32_le flags; + u64_le address{}; + u32_le size{}; + u32_le flags{}; }; static_assert(sizeof(IocFreeParams) == 24, "IocFreeParams has wrong size"); struct IocParamParams { // Input - u32_le handle; - u32_le param; + u32_le handle{}; + u32_le param{}; // Output - u32_le result; + u32_le result{}; }; static_assert(sizeof(IocParamParams) == 12, "IocParamParams has wrong size"); struct IocGetIdParams { // Output - u32_le id; + u32_le id{}; // Input - u32_le handle; + u32_le handle{}; }; static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size"); u32 CreateObject(u32 size); - u32 IocCreate(const std::vector& input, std::vector& output); - u32 IocAlloc(const std::vector& input, std::vector& output); - u32 IocGetId(const std::vector& input, std::vector& output); - u32 IocFromId(const std::vector& input, std::vector& output); - u32 IocParam(const std::vector& input, std::vector& output); - u32 IocFree(const std::vector& input, std::vector& output); + NvResult IocCreate(const std::vector& input, std::vector& output); + NvResult IocAlloc(const std::vector& input, std::vector& output); + NvResult IocGetId(const std::vector& input, std::vector& output); + NvResult IocFromId(const std::vector& input, std::vector& output); + NvResult IocParam(const std::vector& input, std::vector& output); + NvResult IocFree(const std::vector& input, std::vector& output); }; } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp index 88fbfa9b0..cc23b001c 100644 --- a/src/core/hle/service/nvdrv/interface.cpp +++ b/src/core/hle/service/nvdrv/interface.cpp @@ -23,124 +23,167 @@ void NVDRV::SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) { void NVDRV::Open(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NVDRV, "called"); - const auto& buffer = ctx.ReadBuffer(); - std::string device_name(buffer.begin(), buffer.end()); + if (!is_initialized) { + ServiceError(ctx, NvResult::NotInitialized); + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; + } + + const auto& buffer = ctx.ReadBuffer(); + const std::string device_name(buffer.begin(), buffer.end()); + DeviceFD fd = nvdrv->Open(device_name); - u32 fd = nvdrv->Open(device_name); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push(fd); - rb.Push(0); + rb.Push(fd); + rb.PushEnum(fd != INVALID_NVDRV_FD ? NvResult::Success : NvResult::FileOperationFailed); } -void NVDRV::IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version) { - IPC::RequestParser rp{ctx}; - u32 fd = rp.Pop(); - u32 command = rp.Pop(); - - /// Ioctl 3 has 2 outputs, first in the input params, second is the result - std::vector output(ctx.GetWriteBufferSize(0)); - std::vector output2; - if (version == IoctlVersion::Version3) { - output2.resize((ctx.GetWriteBufferSize(1))); - } - - /// Ioctl2 has 2 inputs. It's used to pass data directly instead of providing a pointer. - /// KickOfPB uses this - auto input = ctx.ReadBuffer(0); - - std::vector input2; - if (version == IoctlVersion::Version2) { - input2 = ctx.ReadBuffer(1); - } - - IoctlCtrl ctrl{}; - - u32 result = nvdrv->Ioctl(fd, command, input, input2, output, output2, ctrl, version); - - if (ctrl.must_delay) { - ctrl.fresh_call = false; - ctx.SleepClientThread( - "NVServices::DelayedResponse", ctrl.timeout, - [=, this](std::shared_ptr thread, Kernel::HLERequestContext& ctx_, - Kernel::ThreadWakeupReason reason) { - IoctlCtrl ctrl2{ctrl}; - std::vector tmp_output = output; - std::vector tmp_output2 = output2; - const u32 ioctl_result = nvdrv->Ioctl(fd, command, input, input2, tmp_output, - tmp_output2, ctrl2, version); - ctx_.WriteBuffer(tmp_output, 0); - if (version == IoctlVersion::Version3) { - ctx_.WriteBuffer(tmp_output2, 1); - } - IPC::ResponseBuilder rb{ctx_, 3}; - rb.Push(RESULT_SUCCESS); - rb.Push(ioctl_result); - }, - nvdrv->GetEventWriteable(ctrl.event_id)); - } else { - ctx.WriteBuffer(output); - if (version == IoctlVersion::Version3) { - ctx.WriteBuffer(output2, 1); - } - } +void NVDRV::ServiceError(Kernel::HLERequestContext& ctx, NvResult result) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(result); + rb.PushEnum(result); } -void NVDRV::Ioctl(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_NVDRV, "called"); - IoctlBase(ctx, IoctlVersion::Version1); +void NVDRV::Ioctl1(Kernel::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); + + if (!is_initialized) { + ServiceError(ctx, NvResult::NotInitialized); + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; + } + + // Check device + std::vector output_buffer(ctx.GetWriteBufferSize(0)); + const auto input_buffer = ctx.ReadBuffer(0); + + const auto nv_result = nvdrv->Ioctl1(fd, command, input_buffer, output_buffer); + if (command.is_out != 0) { + ctx.WriteBuffer(output_buffer); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum(nv_result); } void NVDRV::Ioctl2(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_NVDRV, "called"); - IoctlBase(ctx, IoctlVersion::Version2); + 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); + + if (!is_initialized) { + ServiceError(ctx, NvResult::NotInitialized); + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; + } + + const auto input_buffer = ctx.ReadBuffer(0); + const auto input_inlined_buffer = ctx.ReadBuffer(1); + std::vector output_buffer(ctx.GetWriteBufferSize(0)); + + const auto nv_result = + nvdrv->Ioctl2(fd, command, input_buffer, input_inlined_buffer, output_buffer); + if (command.is_out != 0) { + ctx.WriteBuffer(output_buffer); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum(nv_result); } void NVDRV::Ioctl3(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_NVDRV, "called"); - IoctlBase(ctx, IoctlVersion::Version3); + 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); + + if (!is_initialized) { + ServiceError(ctx, NvResult::NotInitialized); + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; + } + + const auto input_buffer = ctx.ReadBuffer(0); + std::vector output_buffer(ctx.GetWriteBufferSize(0)); + std::vector output_buffer_inline(ctx.GetWriteBufferSize(1)); + + const auto nv_result = + nvdrv->Ioctl3(fd, command, input_buffer, output_buffer, output_buffer_inline); + if (command.is_out != 0) { + ctx.WriteBuffer(output_buffer, 0); + ctx.WriteBuffer(output_buffer_inline, 1); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum(nv_result); } void NVDRV::Close(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NVDRV, "called"); + if (!is_initialized) { + ServiceError(ctx, NvResult::NotInitialized); + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; + } + IPC::RequestParser rp{ctx}; - u32 fd = rp.Pop(); + const auto fd = rp.Pop(); + const auto result = nvdrv->Close(fd); - auto result = nvdrv->Close(fd); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum(result); } void NVDRV::Initialize(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); + is_initialized = true; + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(0); + rb.PushEnum(NvResult::Success); } void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - u32 fd = rp.Pop(); - // TODO(Blinkhawk): Figure the meaning of the flag at bit 16 - u32 event_id = rp.Pop() & 0x000000FF; + const auto fd = rp.Pop(); + const auto event_id = rp.Pop() & 0x00FF; LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}, event_id={:X}", fd, event_id); - IPC::ResponseBuilder rb{ctx, 3, 1}; - rb.Push(RESULT_SUCCESS); + if (!is_initialized) { + ServiceError(ctx, NvResult::NotInitialized); + LOG_ERROR(Service_NVDRV, "NvServices is not initalized!"); + return; + } + + const auto nv_result = nvdrv->VerifyFD(fd); + if (nv_result != NvResult::Success) { + LOG_ERROR(Service_NVDRV, "Invalid FD specified DeviceFD={}!", fd); + ServiceError(ctx, nv_result); + return; + } + if (event_id < MaxNvEvents) { + IPC::ResponseBuilder rb{ctx, 3, 1}; + rb.Push(RESULT_SUCCESS); auto event = nvdrv->GetEvent(event_id); event->Clear(); rb.PushCopyObjects(event); - rb.Push(NvResult::Success); + rb.PushEnum(NvResult::Success); } else { - rb.Push(0); - rb.Push(NvResult::BadParameter); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum(NvResult::BadParameter); } } @@ -151,7 +194,7 @@ void NVDRV::SetAruid(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(0); + rb.PushEnum(NvResult::Success); } void NVDRV::SetGraphicsFirmwareMemoryMarginEnabled(Kernel::HLERequestContext& ctx) { @@ -164,8 +207,9 @@ void NVDRV::SetGraphicsFirmwareMemoryMarginEnabled(Kernel::HLERequestContext& ct void NVDRV::GetStatus(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NVDRV, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 2}; + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); + rb.PushEnum(NvResult::Success); } void NVDRV::DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx) { @@ -177,11 +221,11 @@ void NVDRV::DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } -NVDRV::NVDRV(std::shared_ptr nvdrv, const char* name) - : ServiceFramework(name), nvdrv(std::move(nvdrv)) { +NVDRV::NVDRV(Core::System& system_, std::shared_ptr nvdrv_, const char* name) + : ServiceFramework{system_, name}, nvdrv{std::move(nvdrv_)} { static const FunctionInfo functions[] = { {0, &NVDRV::Open, "Open"}, - {1, &NVDRV::Ioctl, "Ioctl"}, + {1, &NVDRV::Ioctl1, "Ioctl"}, {2, &NVDRV::Close, "Close"}, {3, &NVDRV::Initialize, "Initialize"}, {4, &NVDRV::QueryEvent, "QueryEvent"}, diff --git a/src/core/hle/service/nvdrv/interface.h b/src/core/hle/service/nvdrv/interface.h index 72e17a728..5c777c59b 100644 --- a/src/core/hle/service/nvdrv/interface.h +++ b/src/core/hle/service/nvdrv/interface.h @@ -16,14 +16,14 @@ namespace Service::Nvidia { class NVDRV final : public ServiceFramework { public: - NVDRV(std::shared_ptr nvdrv, const char* name); + explicit NVDRV(Core::System& system_, std::shared_ptr nvdrv_, const char* name); ~NVDRV() override; - void SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value); + void SignalGPUInterruptSyncpt(u32 syncpoint_id, u32 value); private: void Open(Kernel::HLERequestContext& ctx); - void Ioctl(Kernel::HLERequestContext& ctx); + void Ioctl1(Kernel::HLERequestContext& ctx); void Ioctl2(Kernel::HLERequestContext& ctx); void Ioctl3(Kernel::HLERequestContext& ctx); void Close(Kernel::HLERequestContext& ctx); @@ -33,11 +33,13 @@ private: void SetGraphicsFirmwareMemoryMarginEnabled(Kernel::HLERequestContext& ctx); void GetStatus(Kernel::HLERequestContext& ctx); void DumpGraphicsMemoryInfo(Kernel::HLERequestContext& ctx); - void IoctlBase(Kernel::HLERequestContext& ctx, IoctlVersion version); + + void ServiceError(Kernel::HLERequestContext& ctx, NvResult result); std::shared_ptr nvdrv; u64 pid{}; + bool is_initialized{}; }; } // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/nvdata.h b/src/core/hle/service/nvdrv/nvdata.h index 529b03471..3294bc0e7 100644 --- a/src/core/hle/service/nvdrv/nvdata.h +++ b/src/core/hle/service/nvdrv/nvdata.h @@ -1,12 +1,16 @@ #pragma once #include +#include "common/bit_field.h" #include "common/common_types.h" namespace Service::Nvidia { constexpr u32 MaxSyncPoints = 192; constexpr u32 MaxNvEvents = 64; +using DeviceFD = s32; + +constexpr DeviceFD INVALID_NVDRV_FD = -1; struct Fence { s32 id; @@ -20,11 +24,61 @@ struct MultiFence { std::array fences; }; -enum NvResult : u32 { - Success = 0, - BadParameter = 4, - Timeout = 5, - ResourceError = 15, +enum class NvResult : u32 { + Success = 0x0, + NotImplemented = 0x1, + NotSupported = 0x2, + NotInitialized = 0x3, + BadParameter = 0x4, + Timeout = 0x5, + InsufficientMemory = 0x6, + ReadOnlyAttribute = 0x7, + InvalidState = 0x8, + InvalidAddress = 0x9, + InvalidSize = 0xA, + BadValue = 0xB, + AlreadyAllocated = 0xD, + Busy = 0xE, + ResourceError = 0xF, + CountMismatch = 0x10, + OverFlow = 0x11, + InsufficientTransferMemory = 0x1000, + InsufficientVideoMemory = 0x10000, + BadSurfaceColorScheme = 0x10001, + InvalidSurface = 0x10002, + SurfaceNotSupported = 0x10003, + DispInitFailed = 0x20000, + DispAlreadyAttached = 0x20001, + DispTooManyDisplays = 0x20002, + DispNoDisplaysAttached = 0x20003, + DispModeNotSupported = 0x20004, + DispNotFound = 0x20005, + DispAttachDissallowed = 0x20006, + DispTypeNotSupported = 0x20007, + DispAuthenticationFailed = 0x20008, + DispNotAttached = 0x20009, + DispSamePwrState = 0x2000A, + DispEdidFailure = 0x2000B, + DispDsiReadAckError = 0x2000C, + DispDsiReadInvalidResp = 0x2000D, + FileWriteFailed = 0x30000, + FileReadFailed = 0x30001, + EndOfFile = 0x30002, + FileOperationFailed = 0x30003, + DirOperationFailed = 0x30004, + EndOfDirList = 0x30005, + ConfigVarNotFound = 0x30006, + InvalidConfigVar = 0x30007, + LibraryNotFound = 0x30008, + SymbolNotFound = 0x30009, + MemoryMapFailed = 0x3000A, + IoctlFailed = 0x3000F, + AccessDenied = 0x30010, + DeviceNotFound = 0x30011, + KernelDriverNotFound = 0x30012, + FileNotFound = 0x30013, + PathAlreadyExists = 0x30014, + ModuleNotPresent = 0xA000E, }; enum class EventState { @@ -34,21 +88,13 @@ enum class EventState { Busy = 3, }; -enum class IoctlVersion : u32 { - Version1, - Version2, - Version3, -}; - -struct IoctlCtrl { - // First call done to the servioce for services that call itself again after a call. - bool fresh_call{true}; - // Tells the Ioctl Wrapper that it must delay the IPC response and send the thread to sleep - bool must_delay{}; - // Timeout for the delay - s64 timeout{}; - // NV Event Id - s32 event_id{-1}; +union Ioctl { + u32_le raw; + BitField<0, 8, u32> cmd; + BitField<8, 8, u32> group; + BitField<16, 14, u32> length; + BitField<30, 1, u32> is_in; + BitField<31, 1, u32> is_out; }; } // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp index 197c77db0..620c18728 100644 --- a/src/core/hle/service/nvdrv/nvdrv.cpp +++ b/src/core/hle/service/nvdrv/nvdrv.cpp @@ -5,6 +5,7 @@ #include #include +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/readable_event.h" #include "core/hle/kernel/writable_event.h" @@ -21,6 +22,7 @@ #include "core/hle/service/nvdrv/interface.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvdrv/nvmemp.h" +#include "core/hle/service/nvdrv/syncpoint_manager.h" #include "core/hle/service/nvflinger/nvflinger.h" namespace Service::Nvidia { @@ -28,66 +30,135 @@ namespace Service::Nvidia { void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger, Core::System& system) { auto module_ = std::make_shared(system); - std::make_shared(module_, "nvdrv")->InstallAsService(service_manager); - std::make_shared(module_, "nvdrv:a")->InstallAsService(service_manager); - std::make_shared(module_, "nvdrv:s")->InstallAsService(service_manager); - std::make_shared(module_, "nvdrv:t")->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); + std::make_shared(system, module_, "nvdrv")->InstallAsService(service_manager); + std::make_shared(system, module_, "nvdrv:a")->InstallAsService(service_manager); + std::make_shared(system, module_, "nvdrv:s")->InstallAsService(service_manager); + std::make_shared(system, module_, "nvdrv:t")->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); nvflinger.SetNVDrvInstance(module_); } -Module::Module(Core::System& system) { +Module::Module(Core::System& system) : syncpoint_manager{system.GPU()} { auto& kernel = system.Kernel(); for (u32 i = 0; i < MaxNvEvents; i++) { std::string event_label = fmt::format("NVDRV::NvEvent_{}", i); - events_interface.events[i] = Kernel::WritableEvent::CreateEventPair(kernel, event_label); + events_interface.events[i] = {Kernel::WritableEvent::CreateEventPair(kernel, event_label)}; events_interface.status[i] = EventState::Free; events_interface.registered[i] = false; } auto nvmap_dev = std::make_shared(system); devices["/dev/nvhost-as-gpu"] = std::make_shared(system, nvmap_dev); - devices["/dev/nvhost-gpu"] = std::make_shared(system, nvmap_dev); + devices["/dev/nvhost-gpu"] = + std::make_shared(system, nvmap_dev, syncpoint_manager); devices["/dev/nvhost-ctrl-gpu"] = std::make_shared(system); devices["/dev/nvmap"] = nvmap_dev; devices["/dev/nvdisp_disp0"] = std::make_shared(system, nvmap_dev); - devices["/dev/nvhost-ctrl"] = std::make_shared(system, events_interface); - devices["/dev/nvhost-nvdec"] = std::make_shared(system); + devices["/dev/nvhost-ctrl"] = + std::make_shared(system, events_interface, syncpoint_manager); + devices["/dev/nvhost-nvdec"] = + std::make_shared(system, nvmap_dev, syncpoint_manager); devices["/dev/nvhost-nvjpg"] = std::make_shared(system); - devices["/dev/nvhost-vic"] = std::make_shared(system); + devices["/dev/nvhost-vic"] = + std::make_shared(system, nvmap_dev, syncpoint_manager); } Module::~Module() = default; -u32 Module::Open(const std::string& device_name) { - ASSERT_MSG(devices.find(device_name) != devices.end(), "Trying to open unknown device {}", - device_name); +NvResult Module::VerifyFD(DeviceFD fd) const { + if (fd < 0) { + LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd); + return NvResult::InvalidState; + } + + if (open_files.find(fd) == open_files.end()) { + LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd); + return NvResult::NotImplemented; + } + + return NvResult::Success; +} + +DeviceFD Module::Open(const std::string& device_name) { + if (devices.find(device_name) == devices.end()) { + LOG_ERROR(Service_NVDRV, "Trying to open unknown device {}", device_name); + return INVALID_NVDRV_FD; + } auto device = devices[device_name]; - const u32 fd = next_fd++; + const DeviceFD fd = next_fd++; open_files[fd] = std::move(device); return fd; } -u32 Module::Ioctl(u32 fd, u32 command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version) { - auto itr = open_files.find(fd); - ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device"); +NvResult Module::Ioctl1(DeviceFD fd, Ioctl command, const std::vector& input, + std::vector& output) { + if (fd < 0) { + LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd); + return NvResult::InvalidState; + } - auto& device = itr->second; - return device->ioctl({command}, input, input2, output, output2, ctrl, version); + const auto itr = open_files.find(fd); + + if (itr == open_files.end()) { + LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd); + return NvResult::NotImplemented; + } + + return itr->second->Ioctl1(command, input, output); } -ResultCode Module::Close(u32 fd) { - auto itr = open_files.find(fd); - ASSERT_MSG(itr != open_files.end(), "Tried to talk to an invalid device"); +NvResult Module::Ioctl2(DeviceFD fd, Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output) { + if (fd < 0) { + LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd); + return NvResult::InvalidState; + } + + const auto itr = open_files.find(fd); + + if (itr == open_files.end()) { + LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd); + return NvResult::NotImplemented; + } + + return itr->second->Ioctl2(command, input, inline_input, output); +} + +NvResult Module::Ioctl3(DeviceFD fd, Ioctl command, const std::vector& input, + std::vector& output, std::vector& inline_output) { + if (fd < 0) { + LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd); + return NvResult::InvalidState; + } + + const auto itr = open_files.find(fd); + + if (itr == open_files.end()) { + LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd); + return NvResult::NotImplemented; + } + + return itr->second->Ioctl3(command, input, output, inline_output); +} + +NvResult Module::Close(DeviceFD fd) { + if (fd < 0) { + LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd); + return NvResult::InvalidState; + } + + const auto itr = open_files.find(fd); + + if (itr == open_files.end()) { + LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd); + return NvResult::NotImplemented; + } open_files.erase(itr); - // TODO(flerovium): return correct result code if operation failed. - return RESULT_SUCCESS; + return NvResult::Success; } void Module::SignalSyncpt(const u32 syncpoint_id, const u32 value) { @@ -95,17 +166,17 @@ void Module::SignalSyncpt(const u32 syncpoint_id, const u32 value) { if (events_interface.assigned_syncpt[i] == syncpoint_id && events_interface.assigned_value[i] == value) { events_interface.LiberateEvent(i); - events_interface.events[i].writable->Signal(); + events_interface.events[i].event.writable->Signal(); } } } std::shared_ptr Module::GetEvent(const u32 event_id) const { - return events_interface.events[event_id].readable; + return events_interface.events[event_id].event.readable; } std::shared_ptr Module::GetEventWriteable(const u32 event_id) const { - return events_interface.events[event_id].writable; + return events_interface.events[event_id].event.writable; } } // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h index 7706a5590..144e657e5 100644 --- a/src/core/hle/service/nvdrv/nvdrv.h +++ b/src/core/hle/service/nvdrv/nvdrv.h @@ -10,6 +10,7 @@ #include "common/common_types.h" #include "core/hle/kernel/writable_event.h" #include "core/hle/service/nvdrv/nvdata.h" +#include "core/hle/service/nvdrv/syncpoint_manager.h" #include "core/hle/service/service.h" namespace Core { @@ -22,15 +23,23 @@ class NVFlinger; namespace Service::Nvidia { +class SyncpointManager; + namespace Devices { class nvdevice; } +/// Represents an Nvidia event +struct NvEvent { + Kernel::EventPair event; + Fence fence{}; +}; + struct EventInterface { // Mask representing currently busy events u64 events_mask{}; // Each kernel event associated to an NV event - std::array events; + std::array events; // The status of the current NVEvent std::array status{}; // Tells if an NVEvent is registered or not @@ -91,7 +100,7 @@ struct EventInterface { class Module final { public: - Module(Core::System& system); + explicit Module(Core::System& system_); ~Module(); /// Returns a pointer to one of the available devices, identified by its name. @@ -103,14 +112,23 @@ public: return std::static_pointer_cast(itr->second); } + NvResult VerifyFD(DeviceFD fd) const; + /// Opens a device node and returns a file descriptor to it. - u32 Open(const std::string& device_name); + DeviceFD Open(const std::string& device_name); + /// Sends an ioctl command to the specified file descriptor. - u32 Ioctl(u32 fd, u32 command, const std::vector& input, const std::vector& input2, - std::vector& output, std::vector& output2, IoctlCtrl& ctrl, - IoctlVersion version); + NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector& input, + std::vector& output); + + NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector& input, + const std::vector& inline_input, std::vector& output); + + NvResult Ioctl3(DeviceFD fd, Ioctl command, const std::vector& input, + std::vector& output, std::vector& inline_output); + /// Closes a device file descriptor and returns operation success. - ResultCode Close(u32 fd); + NvResult Close(DeviceFD fd); void SignalSyncpt(const u32 syncpoint_id, const u32 value); @@ -119,11 +137,14 @@ public: std::shared_ptr GetEventWriteable(u32 event_id) const; private: + /// Manages syncpoints on the host + SyncpointManager syncpoint_manager; + /// Id to use for the next open file descriptor. - u32 next_fd = 1; + DeviceFD next_fd = 1; /// Mapping of file descriptors to the devices they reference. - std::unordered_map> open_files; + std::unordered_map> open_files; /// Mapping of device node names to their implementation. std::unordered_map> devices; diff --git a/src/core/hle/service/nvdrv/nvmemp.cpp b/src/core/hle/service/nvdrv/nvmemp.cpp index 73b37e805..331c02243 100644 --- a/src/core/hle/service/nvdrv/nvmemp.cpp +++ b/src/core/hle/service/nvdrv/nvmemp.cpp @@ -8,7 +8,7 @@ namespace Service::Nvidia { -NVMEMP::NVMEMP() : ServiceFramework("nvmemp") { +NVMEMP::NVMEMP(Core::System& system_) : ServiceFramework{system_, "nvmemp"} { static const FunctionInfo functions[] = { {0, &NVMEMP::Open, "Open"}, {1, &NVMEMP::GetAruid, "GetAruid"}, diff --git a/src/core/hle/service/nvdrv/nvmemp.h b/src/core/hle/service/nvdrv/nvmemp.h index c453ee4db..724c27ef9 100644 --- a/src/core/hle/service/nvdrv/nvmemp.h +++ b/src/core/hle/service/nvdrv/nvmemp.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Nvidia { class NVMEMP final : public ServiceFramework { public: - NVMEMP(); + explicit NVMEMP(Core::System& system_); ~NVMEMP() override; private: diff --git a/src/core/hle/service/nvdrv/syncpoint_manager.cpp b/src/core/hle/service/nvdrv/syncpoint_manager.cpp new file mode 100644 index 000000000..0151a03b7 --- /dev/null +++ b/src/core/hle/service/nvdrv/syncpoint_manager.cpp @@ -0,0 +1,39 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "core/hle/service/nvdrv/syncpoint_manager.h" +#include "video_core/gpu.h" + +namespace Service::Nvidia { + +SyncpointManager::SyncpointManager(Tegra::GPU& gpu) : gpu{gpu} {} + +SyncpointManager::~SyncpointManager() = default; + +u32 SyncpointManager::RefreshSyncpoint(u32 syncpoint_id) { + syncpoints[syncpoint_id].min = gpu.GetSyncpointValue(syncpoint_id); + return GetSyncpointMin(syncpoint_id); +} + +u32 SyncpointManager::AllocateSyncpoint() { + for (u32 syncpoint_id = 1; syncpoint_id < MaxSyncPoints; syncpoint_id++) { + if (!syncpoints[syncpoint_id].is_allocated) { + syncpoints[syncpoint_id].is_allocated = true; + return syncpoint_id; + } + } + UNREACHABLE_MSG("No more available syncpoints!"); + return {}; +} + +u32 SyncpointManager::IncreaseSyncpoint(u32 syncpoint_id, u32 value) { + for (u32 index = 0; index < value; ++index) { + syncpoints[syncpoint_id].max.fetch_add(1, std::memory_order_relaxed); + } + + return GetSyncpointMax(syncpoint_id); +} + +} // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/syncpoint_manager.h b/src/core/hle/service/nvdrv/syncpoint_manager.h new file mode 100644 index 000000000..d395c5d0b --- /dev/null +++ b/src/core/hle/service/nvdrv/syncpoint_manager.h @@ -0,0 +1,85 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "common/common_types.h" +#include "core/hle/service/nvdrv/nvdata.h" + +namespace Tegra { +class GPU; +} + +namespace Service::Nvidia { + +class SyncpointManager final { +public: + explicit SyncpointManager(Tegra::GPU& gpu); + ~SyncpointManager(); + + /** + * Returns true if the specified syncpoint is expired for the given value. + * @param syncpoint_id Syncpoint ID to check. + * @param value Value to check against the specified syncpoint. + * @returns True if the specified syncpoint is expired for the given value, otherwise False. + */ + bool IsSyncpointExpired(u32 syncpoint_id, u32 value) const { + return (GetSyncpointMax(syncpoint_id) - value) >= (GetSyncpointMin(syncpoint_id) - value); + } + + /** + * Gets the lower bound for the specified syncpoint. + * @param syncpoint_id Syncpoint ID to get the lower bound for. + * @returns The lower bound for the specified syncpoint. + */ + u32 GetSyncpointMin(u32 syncpoint_id) const { + return syncpoints.at(syncpoint_id).min.load(std::memory_order_relaxed); + } + + /** + * Gets the uper bound for the specified syncpoint. + * @param syncpoint_id Syncpoint ID to get the upper bound for. + * @returns The upper bound for the specified syncpoint. + */ + u32 GetSyncpointMax(u32 syncpoint_id) const { + return syncpoints.at(syncpoint_id).max.load(std::memory_order_relaxed); + } + + /** + * Refreshes the minimum value for the specified syncpoint. + * @param syncpoint_id Syncpoint ID to be refreshed. + * @returns The new syncpoint minimum value. + */ + u32 RefreshSyncpoint(u32 syncpoint_id); + + /** + * Allocates a new syncoint. + * @returns The syncpoint ID for the newly allocated syncpoint. + */ + u32 AllocateSyncpoint(); + + /** + * Increases the maximum value for the specified syncpoint. + * @param syncpoint_id Syncpoint ID to be increased. + * @param value Value to increase the specified syncpoint by. + * @returns The new syncpoint maximum value. + */ + u32 IncreaseSyncpoint(u32 syncpoint_id, u32 value); + +private: + struct Syncpoint { + std::atomic min; + std::atomic max; + std::atomic is_allocated; + }; + + std::array syncpoints{}; + + Tegra::GPU& gpu; +}; + +} // namespace Service::Nvidia diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp index 637b310d7..5578181a4 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.cpp +++ b/src/core/hle/service/nvflinger/buffer_queue.cpp @@ -22,127 +22,169 @@ BufferQueue::BufferQueue(Kernel::KernelCore& kernel, u32 id, u64 layer_id) BufferQueue::~BufferQueue() = default; void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) { + ASSERT(slot < buffer_slots); LOG_WARNING(Service, "Adding graphics buffer {}", slot); - free_buffers.push_back(slot); - queue.push_back({ + { + std::unique_lock lock{free_buffers_mutex}; + free_buffers.push_back(slot); + } + free_buffers_condition.notify_one(); + + buffers[slot] = { .slot = slot, .status = Buffer::Status::Free, .igbp_buffer = igbp_buffer, - }); + .transform = {}, + .crop_rect = {}, + .swap_interval = 0, + .multi_fence = {}, + }; buffer_wait_event.writable->Signal(); } std::optional> BufferQueue::DequeueBuffer(u32 width, u32 height) { + // Wait for first request before trying to dequeue + { + std::unique_lock lock{free_buffers_mutex}; + free_buffers_condition.wait(lock, [this] { return !free_buffers.empty() || !is_connect; }); + } - if (free_buffers.empty()) { + if (!is_connect) { + // Buffer was disconnected while the thread was blocked, this is most likely due to + // emulation being stopped return std::nullopt; } + std::unique_lock lock{free_buffers_mutex}; + auto f_itr = free_buffers.begin(); - auto itr = queue.end(); + auto slot = buffers.size(); while (f_itr != free_buffers.end()) { - auto slot = *f_itr; - itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) { - // Only consider free buffers. Buffers become free once again after they've been - // Acquired and Released by the compositor, see the NVFlinger::Compose method. - if (buffer.status != Buffer::Status::Free) { - return false; - } - - if (buffer.slot != slot) { - return false; - } - - // Make sure that the parameters match. - return buffer.igbp_buffer.width == width && buffer.igbp_buffer.height == height; - }); - - if (itr != queue.end()) { + const Buffer& buffer = buffers[*f_itr]; + if (buffer.status == Buffer::Status::Free && buffer.igbp_buffer.width == width && + buffer.igbp_buffer.height == height) { + slot = *f_itr; free_buffers.erase(f_itr); break; } ++f_itr; } - - if (itr == queue.end()) { + if (slot == buffers.size()) { return std::nullopt; } - - itr->status = Buffer::Status::Dequeued; - return {{itr->slot, &itr->multi_fence}}; + buffers[slot].status = Buffer::Status::Dequeued; + return {{buffers[slot].slot, &buffers[slot].multi_fence}}; } const IGBPBuffer& BufferQueue::RequestBuffer(u32 slot) const { - auto itr = std::find_if(queue.begin(), queue.end(), - [&](const Buffer& buffer) { return buffer.slot == slot; }); - ASSERT(itr != queue.end()); - ASSERT(itr->status == Buffer::Status::Dequeued); - return itr->igbp_buffer; + ASSERT(slot < buffers.size()); + ASSERT(buffers[slot].status == Buffer::Status::Dequeued); + ASSERT(buffers[slot].slot == slot); + + return buffers[slot].igbp_buffer; } void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform, const Common::Rectangle& crop_rect, u32 swap_interval, Service::Nvidia::MultiFence& multi_fence) { - auto itr = std::find_if(queue.begin(), queue.end(), - [&](const Buffer& buffer) { return buffer.slot == slot; }); - ASSERT(itr != queue.end()); - ASSERT(itr->status == Buffer::Status::Dequeued); - itr->status = Buffer::Status::Queued; - itr->transform = transform; - itr->crop_rect = crop_rect; - itr->swap_interval = swap_interval; - itr->multi_fence = multi_fence; + ASSERT(slot < buffers.size()); + ASSERT(buffers[slot].status == Buffer::Status::Dequeued); + ASSERT(buffers[slot].slot == slot); + + buffers[slot].status = Buffer::Status::Queued; + buffers[slot].transform = transform; + buffers[slot].crop_rect = crop_rect; + buffers[slot].swap_interval = swap_interval; + buffers[slot].multi_fence = multi_fence; + std::unique_lock lock{queue_sequence_mutex}; queue_sequence.push_back(slot); } -std::optional> BufferQueue::AcquireBuffer() { - auto itr = queue.end(); - // Iterate to find a queued buffer matching the requested slot. - while (itr == queue.end() && !queue_sequence.empty()) { - const u32 slot = queue_sequence.front(); - itr = std::find_if(queue.begin(), queue.end(), [&slot](const Buffer& buffer) { - return buffer.status == Buffer::Status::Queued && buffer.slot == slot; - }); - queue_sequence.pop_front(); - } - if (itr == queue.end()) { - return std::nullopt; - } - itr->status = Buffer::Status::Acquired; - return *itr; -} +void BufferQueue::CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence) { + ASSERT(slot < buffers.size()); + ASSERT(buffers[slot].status != Buffer::Status::Free); + ASSERT(buffers[slot].slot == slot); -void BufferQueue::ReleaseBuffer(u32 slot) { - auto itr = std::find_if(queue.begin(), queue.end(), - [&](const Buffer& buffer) { return buffer.slot == slot; }); - ASSERT(itr != queue.end()); - ASSERT(itr->status == Buffer::Status::Acquired); - itr->status = Buffer::Status::Free; - free_buffers.push_back(slot); + buffers[slot].status = Buffer::Status::Free; + buffers[slot].multi_fence = multi_fence; + buffers[slot].swap_interval = 0; + + { + std::unique_lock lock{free_buffers_mutex}; + free_buffers.push_back(slot); + } + free_buffers_condition.notify_one(); buffer_wait_event.writable->Signal(); } -void BufferQueue::Disconnect() { - queue.clear(); +std::optional> BufferQueue::AcquireBuffer() { + std::unique_lock lock{queue_sequence_mutex}; + std::size_t buffer_slot = buffers.size(); + // Iterate to find a queued buffer matching the requested slot. + while (buffer_slot == buffers.size() && !queue_sequence.empty()) { + const auto slot = static_cast(queue_sequence.front()); + ASSERT(slot < buffers.size()); + if (buffers[slot].status == Buffer::Status::Queued) { + ASSERT(buffers[slot].slot == slot); + buffer_slot = slot; + } + queue_sequence.pop_front(); + } + if (buffer_slot == buffers.size()) { + return std::nullopt; + } + buffers[buffer_slot].status = Buffer::Status::Acquired; + return {{buffers[buffer_slot]}}; +} + +void BufferQueue::ReleaseBuffer(u32 slot) { + ASSERT(slot < buffers.size()); + ASSERT(buffers[slot].status == Buffer::Status::Acquired); + ASSERT(buffers[slot].slot == slot); + + buffers[slot].status = Buffer::Status::Free; + { + std::unique_lock lock{free_buffers_mutex}; + free_buffers.push_back(slot); + } + free_buffers_condition.notify_one(); + + buffer_wait_event.writable->Signal(); +} + +void BufferQueue::Connect() { + std::unique_lock lock{queue_sequence_mutex}; queue_sequence.clear(); - id = 1; - layer_id = 1; + is_connect = true; +} + +void BufferQueue::Disconnect() { + buffers.fill({}); + { + std::unique_lock lock{queue_sequence_mutex}; + queue_sequence.clear(); + } + buffer_wait_event.writable->Signal(); + is_connect = false; + free_buffers_condition.notify_one(); } u32 BufferQueue::Query(QueryType type) { - LOG_WARNING(Service, "(STUBBED) called type={}", static_cast(type)); + LOG_WARNING(Service, "(STUBBED) called type={}", type); switch (type) { case QueryType::NativeWindowFormat: return static_cast(PixelFormat::RGBA8888); + case QueryType::NativeWindowWidth: + case QueryType::NativeWindowHeight: + break; } - - UNIMPLEMENTED(); + UNIMPLEMENTED_MSG("Unimplemented query type={}", type); return 0; } diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h index 8a837e5aa..ad7469277 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.h +++ b/src/core/hle/service/nvflinger/buffer_queue.h @@ -4,7 +4,9 @@ #pragma once +#include #include +#include #include #include @@ -21,6 +23,7 @@ class KernelCore; namespace Service::NVFlinger { +constexpr u32 buffer_slots = 0x40; struct IGBPBuffer { u32_le magic; u32_le width; @@ -95,8 +98,10 @@ public: void QueueBuffer(u32 slot, BufferTransformFlags transform, const Common::Rectangle& crop_rect, u32 swap_interval, Service::Nvidia::MultiFence& multi_fence); + void CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence); std::optional> AcquireBuffer(); void ReleaseBuffer(u32 slot); + void Connect(); void Disconnect(); u32 Query(QueryType type); @@ -104,18 +109,30 @@ public: return id; } + bool IsConnected() const { + return is_connect; + } + std::shared_ptr GetWritableBufferWaitEvent() const; std::shared_ptr GetBufferWaitEvent() const; private: - u32 id; - u64 layer_id; + BufferQueue(const BufferQueue&) = delete; + + u32 id{}; + u64 layer_id{}; + std::atomic_bool is_connect{}; std::list free_buffers; - std::vector queue; + std::array buffers; std::list queue_sequence; Kernel::EventPair buffer_wait_event; + + std::mutex free_buffers_mutex; + std::condition_variable free_buffers_condition; + + std::mutex queue_sequence_mutex; }; } // namespace Service::NVFlinger diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index c64673dba..4b3581949 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -88,6 +88,10 @@ NVFlinger::NVFlinger(Core::System& system) : system(system) { } NVFlinger::~NVFlinger() { + for (auto& buffer_queue : buffer_queues) { + buffer_queue->Disconnect(); + } + if (system.IsMulticore()) { is_running = false; wait_event->Set(); @@ -104,6 +108,8 @@ void NVFlinger::SetNVDrvInstance(std::shared_ptr instance) { } std::optional NVFlinger::OpenDisplay(std::string_view name) { + const auto guard = Lock(); + LOG_DEBUG(Service, "Opening \"{}\" display", name); // TODO(Subv): Currently we only support the Default display. @@ -121,6 +127,7 @@ std::optional NVFlinger::OpenDisplay(std::string_view name) { } std::optional NVFlinger::CreateLayer(u64 display_id) { + const auto guard = Lock(); auto* const display = FindDisplay(display_id); if (display == nullptr) { @@ -129,18 +136,22 @@ std::optional NVFlinger::CreateLayer(u64 display_id) { const u64 layer_id = next_layer_id++; const u32 buffer_queue_id = next_buffer_queue_id++; - buffer_queues.emplace_back(system.Kernel(), buffer_queue_id, layer_id); - display->CreateLayer(layer_id, buffer_queues.back()); + buffer_queues.emplace_back( + std::make_unique(system.Kernel(), buffer_queue_id, layer_id)); + display->CreateLayer(layer_id, *buffer_queues.back()); return layer_id; } void NVFlinger::CloseLayer(u64 layer_id) { + const auto guard = Lock(); + for (auto& display : displays) { display.CloseLayer(layer_id); } } std::optional NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) const { + const auto guard = Lock(); const auto* const layer = FindLayer(display_id, layer_id); if (layer == nullptr) { @@ -151,6 +162,7 @@ std::optional NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) co } std::shared_ptr NVFlinger::FindVsyncEvent(u64 display_id) const { + const auto guard = Lock(); auto* const display = FindDisplay(display_id); if (display == nullptr) { @@ -160,20 +172,16 @@ std::shared_ptr NVFlinger::FindVsyncEvent(u64 display_id) return display->GetVSyncEvent(); } -BufferQueue& NVFlinger::FindBufferQueue(u32 id) { +BufferQueue* NVFlinger::FindBufferQueue(u32 id) { + const auto guard = Lock(); const auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(), - [id](const auto& queue) { return queue.GetId() == id; }); + [id](const auto& queue) { return queue->GetId() == id; }); - ASSERT(itr != buffer_queues.end()); - return *itr; -} + if (itr == buffer_queues.end()) { + return nullptr; + } -const BufferQueue& NVFlinger::FindBufferQueue(u32 id) const { - const auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(), - [id](const auto& queue) { return queue.GetId() == id; }); - - ASSERT(itr != buffer_queues.end()); - return *itr; + return itr->get(); } VI::Display* NVFlinger::FindDisplay(u64 display_id) { @@ -242,6 +250,10 @@ void NVFlinger::Compose() { const auto& igbp_buffer = buffer->get().igbp_buffer; + if (!system.IsPoweredOn()) { + return; // We are likely shutting down + } + auto& gpu = system.GPU(); const auto& multi_fence = buffer->get().multi_fence; guard->unlock(); diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h index 1ebe949c0..c6765259f 100644 --- a/src/core/hle/service/nvflinger/nvflinger.h +++ b/src/core/hle/service/nvflinger/nvflinger.h @@ -75,10 +75,7 @@ public: [[nodiscard]] std::shared_ptr FindVsyncEvent(u64 display_id) const; /// Obtains a buffer queue identified by the ID. - [[nodiscard]] BufferQueue& FindBufferQueue(u32 id); - - /// Obtains a buffer queue identified by the ID. - [[nodiscard]] const BufferQueue& FindBufferQueue(u32 id) const; + [[nodiscard]] BufferQueue* FindBufferQueue(u32 id); /// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when /// finished. @@ -86,11 +83,11 @@ public: [[nodiscard]] s64 GetNextTicks() const; +private: [[nodiscard]] std::unique_lock Lock() const { return std::unique_lock{*guard}; } -private: /// Finds the display identified by the specified ID. [[nodiscard]] VI::Display* FindDisplay(u64 display_id); @@ -110,7 +107,7 @@ private: std::shared_ptr nvdrv; std::vector displays; - std::vector buffer_queues; + std::vector> buffer_queues; /// Id to use for the next layer that is created, this counter is shared among all displays. u64 next_layer_id = 1; diff --git a/src/core/hle/service/olsc/olsc.cpp b/src/core/hle/service/olsc/olsc.cpp new file mode 100644 index 000000000..4440135ed --- /dev/null +++ b/src/core/hle/service/olsc/olsc.cpp @@ -0,0 +1,69 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/service/olsc/olsc.h" +#include "core/hle/service/service.h" +#include "core/hle/service/sm/sm.h" + +namespace Service::OLSC { + +class OLSC final : public ServiceFramework { +public: + explicit OLSC(Core::System& system_) : ServiceFramework{system_, "olsc:u"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &OLSC::Initialize, "Initialize"}, + {10, nullptr, "VerifySaveDataBackupLicenseAsync"}, + {13, nullptr, "GetSaveDataBackupSetting"}, + {14, &OLSC::SetSaveDataBackupSettingEnabled, "SetSaveDataBackupSettingEnabled"}, + {15, nullptr, "SetCustomData"}, + {16, nullptr, "DeleteSaveDataBackupSetting"}, + {18, nullptr, "GetSaveDataBackupInfoCache"}, + {19, nullptr, "UpdateSaveDataBackupInfoCacheAsync"}, + {22, nullptr, "DeleteSaveDataBackupAsync"}, + {25, nullptr, "ListDownloadableSaveDataBackupInfoAsync"}, + {26, nullptr, "DownloadSaveDataBackupAsync"}, + {9010, nullptr, "VerifySaveDataBackupLicenseAsyncForDebug"}, + {9013, nullptr, "GetSaveDataBackupSettingForDebug"}, + {9014, nullptr, "SetSaveDataBackupSettingEnabledForDebug"}, + {9015, nullptr, "SetCustomDataForDebug"}, + {9016, nullptr, "DeleteSaveDataBackupSettingForDebug"}, + {9018, nullptr, "GetSaveDataBackupInfoCacheForDebug"}, + {9019, nullptr, "UpdateSaveDataBackupInfoCacheAsyncForDebug"}, + {9022, nullptr, "DeleteSaveDataBackupAsyncForDebug"}, + {9025, nullptr, "ListDownloadableSaveDataBackupInfoAsyncForDebug"}, + {9026, nullptr, "DownloadSaveDataBackupAsyncForDebug"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void Initialize(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_OLSC, "(STUBBED) called"); + + initialized = true; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void SetSaveDataBackupSettingEnabled(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_OLSC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + bool initialized{}; +}; + +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + std::make_shared(system)->InstallAsService(service_manager); +} + +} // namespace Service::OLSC diff --git a/src/core/hle/service/olsc/olsc.h b/src/core/hle/service/olsc/olsc.h new file mode 100644 index 000000000..24f24ca6b --- /dev/null +++ b/src/core/hle/service/olsc/olsc.h @@ -0,0 +1,20 @@ +// Copyright 2020 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +namespace Core { +class System; +} + +namespace Service::SM { +class ServiceManager; +} + +namespace Service::OLSC { + +/// Registers all SSL services with the specified service manager. +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); + +} // namespace Service::OLSC diff --git a/src/core/hle/service/pcie/pcie.cpp b/src/core/hle/service/pcie/pcie.cpp index c568a0adc..f6686fc4d 100644 --- a/src/core/hle/service/pcie/pcie.cpp +++ b/src/core/hle/service/pcie/pcie.cpp @@ -12,7 +12,7 @@ namespace Service::PCIe { class ISession final : public ServiceFramework { public: - explicit ISession() : ServiceFramework{"ISession"} { + explicit ISession(Core::System& system_) : ServiceFramework{system_, "ISession"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "QueryFunctions"}, @@ -48,7 +48,7 @@ public: class PCIe final : public ServiceFramework { public: - explicit PCIe() : ServiceFramework{"pcie"} { + explicit PCIe(Core::System& system_) : ServiceFramework{system_, "pcie"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RegisterClassDriver"}, @@ -60,8 +60,8 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared(system)->InstallAsService(sm); } } // namespace Service::PCIe diff --git a/src/core/hle/service/pcie/pcie.h b/src/core/hle/service/pcie/pcie.h index 59c22ca45..e5709a72f 100644 --- a/src/core/hle/service/pcie/pcie.h +++ b/src/core/hle/service/pcie/pcie.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::PCIe { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::PCIe diff --git a/src/core/hle/service/pctl/module.cpp b/src/core/hle/service/pctl/module.cpp index caf14ed61..6ab1e4124 100644 --- a/src/core/hle/service/pctl/module.cpp +++ b/src/core/hle/service/pctl/module.cpp @@ -11,7 +11,8 @@ namespace Service::PCTL { class IParentalControlService final : public ServiceFramework { public: - IParentalControlService() : ServiceFramework("IParentalControlService") { + explicit IParentalControlService(Core::System& system_) + : ServiceFramework{system_, "IParentalControlService"} { // clang-format off static const FunctionInfo functions[] = { {1, &IParentalControlService::Initialize, "Initialize"}, @@ -137,7 +138,7 @@ void Module::Interface::CreateService(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } void Module::Interface::CreateServiceWithoutInitialize(Kernel::HLERequestContext& ctx) { @@ -145,20 +146,20 @@ void Module::Interface::CreateServiceWithoutInitialize(Kernel::HLERequestContext IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } -Module::Interface::Interface(std::shared_ptr module, const char* name) - : ServiceFramework(name), module(std::move(module)) {} +Module::Interface::Interface(Core::System& system_, std::shared_ptr module_, const char* name) + : ServiceFramework{system_, name}, module{std::move(module_)} {} Module::Interface::~Interface() = default; -void InstallInterfaces(SM::ServiceManager& service_manager) { +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { auto module = std::make_shared(); - std::make_shared(module, "pctl")->InstallAsService(service_manager); - std::make_shared(module, "pctl:a")->InstallAsService(service_manager); - std::make_shared(module, "pctl:r")->InstallAsService(service_manager); - std::make_shared(module, "pctl:s")->InstallAsService(service_manager); + std::make_shared(system, module, "pctl")->InstallAsService(service_manager); + std::make_shared(system, module, "pctl:a")->InstallAsService(service_manager); + std::make_shared(system, module, "pctl:r")->InstallAsService(service_manager); + std::make_shared(system, module, "pctl:s")->InstallAsService(service_manager); } } // namespace Service::PCTL diff --git a/src/core/hle/service/pctl/module.h b/src/core/hle/service/pctl/module.h index 3e449110d..4c7e09a3b 100644 --- a/src/core/hle/service/pctl/module.h +++ b/src/core/hle/service/pctl/module.h @@ -6,13 +6,18 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::PCTL { class Module final { public: class Interface : public ServiceFramework { public: - explicit Interface(std::shared_ptr module, const char* name); + explicit Interface(Core::System& system_, std::shared_ptr module_, + const char* name); ~Interface() override; void CreateService(Kernel::HLERequestContext& ctx); @@ -24,6 +29,6 @@ public: }; /// Registers all PCTL services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::PCTL diff --git a/src/core/hle/service/pctl/pctl.cpp b/src/core/hle/service/pctl/pctl.cpp index af9d1433a..16dd34f90 100644 --- a/src/core/hle/service/pctl/pctl.cpp +++ b/src/core/hle/service/pctl/pctl.cpp @@ -6,8 +6,8 @@ namespace Service::PCTL { -PCTL::PCTL(std::shared_ptr module, const char* name) - : Module::Interface(std::move(module), name) { +PCTL::PCTL(Core::System& system_, std::shared_ptr module_, const char* name) + : Interface{system_, std::move(module_), name} { static const FunctionInfo functions[] = { {0, &PCTL::CreateService, "CreateService"}, {1, &PCTL::CreateServiceWithoutInitialize, "CreateServiceWithoutInitialize"}, diff --git a/src/core/hle/service/pctl/pctl.h b/src/core/hle/service/pctl/pctl.h index c33ea80b6..275d23007 100644 --- a/src/core/hle/service/pctl/pctl.h +++ b/src/core/hle/service/pctl/pctl.h @@ -6,11 +6,15 @@ #include "core/hle/service/pctl/module.h" +namespace Core { +class System; +} + namespace Service::PCTL { class PCTL final : public Module::Interface { public: - explicit PCTL(std::shared_ptr module, const char* name); + explicit PCTL(Core::System& system_, std::shared_ptr module_, const char* name); ~PCTL() override; }; diff --git a/src/core/hle/service/pcv/pcv.cpp b/src/core/hle/service/pcv/pcv.cpp index 8bfc0276e..68b2c4178 100644 --- a/src/core/hle/service/pcv/pcv.cpp +++ b/src/core/hle/service/pcv/pcv.cpp @@ -12,7 +12,7 @@ namespace Service::PCV { class PCV final : public ServiceFramework { public: - explicit PCV() : ServiceFramework{"pcv"} { + explicit PCV(Core::System& system_) : ServiceFramework{system_, "pcv"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SetPowerEnabled"}, @@ -54,7 +54,7 @@ public: class PCV_ARB final : public ServiceFramework { public: - explicit PCV_ARB() : ServiceFramework{"pcv:arb"} { + explicit PCV_ARB(Core::System& system_) : ServiceFramework{system_, "pcv:arb"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "ReleaseControl"}, @@ -67,7 +67,7 @@ public: class PCV_IMM final : public ServiceFramework { public: - explicit PCV_IMM() : ServiceFramework{"pcv:imm"} { + explicit PCV_IMM(Core::System& system_) : ServiceFramework{system_, "pcv:imm"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SetClockRate"}, @@ -78,10 +78,10 @@ public: } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); } } // namespace Service::PCV diff --git a/src/core/hle/service/pcv/pcv.h b/src/core/hle/service/pcv/pcv.h index 219a893c3..c61a0b591 100644 --- a/src/core/hle/service/pcv/pcv.h +++ b/src/core/hle/service/pcv/pcv.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::PCV { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::PCV diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp index f43122ad2..68736c40c 100644 --- a/src/core/hle/service/pm/pm.cpp +++ b/src/core/hle/service/pm/pm.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" @@ -43,7 +44,7 @@ void GetApplicationPidGeneric(Kernel::HLERequestContext& ctx, class BootMode final : public ServiceFramework { public: - explicit BootMode() : ServiceFramework{"pm:bm"} { + explicit BootMode(Core::System& system_) : ServiceFramework{system_, "pm:bm"} { static const FunctionInfo functions[] = { {0, &BootMode::GetBootMode, "GetBootMode"}, {1, &BootMode::SetMaintenanceBoot, "SetMaintenanceBoot"}, @@ -74,8 +75,8 @@ private: class DebugMonitor final : public ServiceFramework { public: - explicit DebugMonitor(const Kernel::KernelCore& kernel) - : ServiceFramework{"pm:dmnt"}, kernel(kernel) { + explicit DebugMonitor(Core::System& system_) + : ServiceFramework{system_, "pm:dmnt"}, kernel{system_.Kernel()} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetJitDebugProcessIdList"}, @@ -124,8 +125,9 @@ private: class Info final : public ServiceFramework { public: - explicit Info(const std::vector>& process_list) - : ServiceFramework{"pm:info"}, process_list(process_list) { + explicit Info(Core::System& system_, + const std::vector>& process_list_) + : ServiceFramework{system_, "pm:info"}, process_list{process_list_} { static const FunctionInfo functions[] = { {0, &Info::GetTitleId, "GetTitleId"}, }; @@ -159,8 +161,8 @@ private: class Shell final : public ServiceFramework { public: - explicit Shell(const Kernel::KernelCore& kernel) - : ServiceFramework{"pm:shell"}, kernel(kernel) { + explicit Shell(Core::System& system_) + : ServiceFramework{system_, "pm:shell"}, kernel{system_.Kernel()} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "LaunchProgram"}, @@ -189,11 +191,11 @@ private: }; void InstallInterfaces(Core::System& system) { - std::make_shared()->InstallAsService(system.ServiceManager()); - std::make_shared(system.Kernel())->InstallAsService(system.ServiceManager()); - std::make_shared(system.Kernel().GetProcessList()) + std::make_shared(system)->InstallAsService(system.ServiceManager()); + std::make_shared(system)->InstallAsService(system.ServiceManager()); + std::make_shared(system, system.Kernel().GetProcessList()) ->InstallAsService(system.ServiceManager()); - std::make_shared(system.Kernel())->InstallAsService(system.ServiceManager()); + std::make_shared(system)->InstallAsService(system.ServiceManager()); } } // namespace Service::PM diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp index cde3312da..b417624c9 100644 --- a/src/core/hle/service/prepo/prepo.cpp +++ b/src/core/hle/service/prepo/prepo.cpp @@ -4,6 +4,7 @@ #include "common/hex_util.h" #include "common/logging/log.h" +#include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/process.h" #include "core/hle/service/acc/profile_manager.h" @@ -15,8 +16,7 @@ namespace Service::PlayReport { class PlayReport final : public ServiceFramework { public: - explicit PlayReport(const char* name, Core::System& system) - : ServiceFramework{name}, system(system) { + explicit PlayReport(const char* name, Core::System& system_) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { {10100, &PlayReport::SaveReport, "SaveReportOld"}, @@ -65,7 +65,7 @@ private: } LOG_DEBUG(Service_PREPO, "called, type={:02X}, process_id={:016X}, data1_size={:016X}", - static_cast(Type), process_id, data[0].size()); + Type, process_id, data[0].size()); const auto& reporter{system.GetReporter()}; reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), data, process_id); @@ -92,7 +92,7 @@ private: LOG_DEBUG( Service_PREPO, "called, type={:02X}, user_id={:016X}{:016X}, process_id={:016X}, data1_size={:016X}", - static_cast(Type), user_id[1], user_id[0], process_id, data[0].size()); + Type, user_id[1], user_id[0], process_id, data[0].size()); const auto& reporter{system.GetReporter()}; reporter.SavePlayReport(Type, system.CurrentProcess()->GetTitleID(), data, process_id, @@ -139,8 +139,6 @@ private: IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } - - Core::System& system; }; void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { diff --git a/src/core/hle/service/prepo/prepo.h b/src/core/hle/service/prepo/prepo.h index a5682ee26..395b57ead 100644 --- a/src/core/hle/service/prepo/prepo.h +++ b/src/core/hle/service/prepo/prepo.h @@ -4,14 +4,14 @@ #pragma once -namespace Service::SM { -class ServiceManager; -} - namespace Core { class System; } +namespace Service::SM { +class ServiceManager; +} + namespace Service::PlayReport { void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); diff --git a/src/core/hle/service/psc/psc.cpp b/src/core/hle/service/psc/psc.cpp index 99e1c9042..5a52b2b05 100644 --- a/src/core/hle/service/psc/psc.cpp +++ b/src/core/hle/service/psc/psc.cpp @@ -14,7 +14,7 @@ namespace Service::PSC { class PSC_C final : public ServiceFramework { public: - explicit PSC_C() : ServiceFramework{"psc:c"} { + explicit PSC_C(Core::System& system_) : ServiceFramework{system_, "psc:c"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -35,7 +35,7 @@ public: class IPmModule final : public ServiceFramework { public: - explicit IPmModule() : ServiceFramework{"IPmModule"} { + explicit IPmModule(Core::System& system_) : ServiceFramework{system_, "IPmModule"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -52,7 +52,7 @@ public: class PSC_M final : public ServiceFramework { public: - explicit PSC_M() : ServiceFramework{"psc:m"} { + explicit PSC_M(Core::System& system_) : ServiceFramework{system_, "psc:m"} { // clang-format off static const FunctionInfo functions[] = { {0, &PSC_M::GetPmModule, "GetPmModule"}, @@ -68,13 +68,13 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared()->InstallAsService(sm); - std::make_shared()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared(system)->InstallAsService(sm); + std::make_shared(system)->InstallAsService(sm); } } // namespace Service::PSC diff --git a/src/core/hle/service/psc/psc.h b/src/core/hle/service/psc/psc.h index 5052eb02c..89344f32d 100644 --- a/src/core/hle/service/psc/psc.h +++ b/src/core/hle/service/psc/psc.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::PSC { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::PSC diff --git a/src/core/hle/service/ptm/psm.cpp b/src/core/hle/service/ptm/psm.cpp index 6d9e6bd09..b4b0dd241 100644 --- a/src/core/hle/service/ptm/psm.cpp +++ b/src/core/hle/service/ptm/psm.cpp @@ -14,7 +14,7 @@ namespace Service::PSM { class PSM final : public ServiceFramework { public: - explicit PSM() : ServiceFramework{"psm"} { + explicit PSM(Core::System& system_) : ServiceFramework{system_, "psm"} { // clang-format off static const FunctionInfo functions[] = { {0, &PSM::GetBatteryChargePercentage, "GetBatteryChargePercentage"}, @@ -72,8 +72,8 @@ private: ChargerType charger_type{ChargerType::RegularCharger}; }; -void InstallInterfaces(SM::ServiceManager& sm) { - std::make_shared()->InstallAsService(sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) { + std::make_shared(system)->InstallAsService(sm); } } // namespace Service::PSM diff --git a/src/core/hle/service/ptm/psm.h b/src/core/hle/service/ptm/psm.h index a286793ae..2930ce26a 100644 --- a/src/core/hle/service/ptm/psm.h +++ b/src/core/hle/service/ptm/psm.h @@ -4,12 +4,16 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } namespace Service::PSM { -void InstallInterfaces(SM::ServiceManager& sm); +void InstallInterfaces(SM::ServiceManager& sm, Core::System& system); } // namespace Service::PSM diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 76b3533ec..ff2a5b1db 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -51,6 +51,7 @@ #include "core/hle/service/ns/ns.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvflinger/nvflinger.h" +#include "core/hle/service/olsc/olsc.h" #include "core/hle/service/pcie/pcie.h" #include "core/hle/service/pctl/module.h" #include "core/hle/service/pcv/pcv.h" @@ -72,13 +73,36 @@ namespace Service { -ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_sessions, - InvokerFn* handler_invoker) - : service_name(service_name), max_sessions(max_sessions), handler_invoker(handler_invoker) {} +/** + * Creates a function string for logging, complete with the name (or header code, depending + * on what's passed in) the port name, and all the cmd_buff arguments. + */ +[[maybe_unused]] static std::string MakeFunctionString(std::string_view name, + std::string_view port_name, + const u32* cmd_buff) { + // Number of params == bits 0-5 + bits 6-11 + int num_params = (cmd_buff[0] & 0x3F) + ((cmd_buff[0] >> 6) & 0x3F); -ServiceFrameworkBase::~ServiceFrameworkBase() = default; + std::string function_string = fmt::format("function '{}': port={}", name, port_name); + for (int i = 1; i <= num_params; ++i) { + function_string += fmt::format(", cmd_buff[{}]=0x{:X}", i, cmd_buff[i]); + } + return function_string; +} + +ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* service_name_, + u32 max_sessions_, InvokerFn* handler_invoker_) + : system{system_}, service_name{service_name_}, max_sessions{max_sessions_}, + handler_invoker{handler_invoker_} {} + +ServiceFrameworkBase::~ServiceFrameworkBase() { + // Wait for other threads to release access before destroying + const auto guard = LockService(); +} void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) { + const auto guard = LockService(); + ASSERT(!port_installed); auto port = service_manager.RegisterService(service_name, max_sessions).Unwrap(); @@ -87,6 +111,8 @@ void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) } void ServiceFrameworkBase::InstallAsNamedPort(Kernel::KernelCore& kernel) { + const auto guard = LockService(); + ASSERT(!port_installed); auto [server_port, client_port] = @@ -96,17 +122,6 @@ void ServiceFrameworkBase::InstallAsNamedPort(Kernel::KernelCore& kernel) { port_installed = true; } -std::shared_ptr ServiceFrameworkBase::CreatePort(Kernel::KernelCore& kernel) { - ASSERT(!port_installed); - - auto [server_port, client_port] = - Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name); - auto port = MakeResult(std::move(server_port)).Unwrap(); - port->SetHleHandler(shared_from_this()); - port_installed = true; - return client_port; -} - void ServiceFrameworkBase::RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n) { handlers.reserve(handlers.size() + n); for (std::size_t i = 0; i < n; ++i) { @@ -128,8 +143,8 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext } buf.push_back('}'); - Core::System::GetInstance().GetReporter().SaveUnimplementedFunctionReport( - ctx, ctx.GetCommand(), function_name, service_name); + system.GetReporter().SaveUnimplementedFunctionReport(ctx, ctx.GetCommand(), function_name, + service_name); UNIMPLEMENTED_MSG("Unknown / unimplemented {}", fmt::to_string(buf)); } @@ -145,6 +160,8 @@ void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) { } ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& context) { + const auto guard = LockService(); + switch (context.GetCommandType()) { case IPC::CommandType::Close: { IPC::ResponseBuilder rb{context, 2}; @@ -153,7 +170,7 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co } case IPC::CommandType::ControlWithContext: case IPC::CommandType::Control: { - Core::System::GetInstance().ServiceManager().InvokeControlRequest(context); + system.ServiceManager().InvokeControlRequest(context); break; } case IPC::CommandType::RequestWithContext: @@ -162,79 +179,82 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co break; } default: - UNIMPLEMENTED_MSG("command_type={}", static_cast(context.GetCommandType())); + UNIMPLEMENTED_MSG("command_type={}", context.GetCommandType()); } - context.WriteToOutgoingCommandBuffer(context.GetThread()); + // If emulation was shutdown, we are closing service threads, do not write the response back to + // memory that may be shutting down as well. + if (system.IsPoweredOn()) { + context.WriteToOutgoingCommandBuffer(context.GetThread()); + } return RESULT_SUCCESS; } -/// Initialize ServiceManager -void Init(std::shared_ptr& sm, Core::System& system) { +/// Initialize Services +Services::Services(std::shared_ptr& sm, Core::System& system) + : nv_flinger{std::make_unique(system)} { + // NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it // here and pass it into the respective InstallInterfaces functions. - auto nv_flinger = std::make_shared(system); + system.GetFileSystemController().CreateFactories(*system.GetFilesystem(), false); - SM::ServiceManager::InstallInterfaces(sm, system.Kernel()); + SM::ServiceManager::InstallInterfaces(sm, system); Account::InstallInterfaces(system); - AM::InstallInterfaces(*sm, nv_flinger, system); + AM::InstallInterfaces(*sm, *nv_flinger, system); AOC::InstallInterfaces(*sm, system); APM::InstallInterfaces(system); Audio::InstallInterfaces(*sm, system); BCAT::InstallInterfaces(system); - BPC::InstallInterfaces(*sm); + BPC::InstallInterfaces(*sm, system); BtDrv::InstallInterfaces(*sm, system); BTM::InstallInterfaces(*sm, system); - Capture::InstallInterfaces(*sm); - ERPT::InstallInterfaces(*sm); - ES::InstallInterfaces(*sm); - EUPLD::InstallInterfaces(*sm); + Capture::InstallInterfaces(*sm, system); + ERPT::InstallInterfaces(*sm, system); + ES::InstallInterfaces(*sm, system); + EUPLD::InstallInterfaces(*sm, system); Fatal::InstallInterfaces(*sm, system); - FGM::InstallInterfaces(*sm); + FGM::InstallInterfaces(*sm, system); FileSystem::InstallInterfaces(system); Friend::InstallInterfaces(*sm, system); Glue::InstallInterfaces(system); - GRC::InstallInterfaces(*sm); + GRC::InstallInterfaces(*sm, system); HID::InstallInterfaces(*sm, system); - LBL::InstallInterfaces(*sm); - LDN::InstallInterfaces(*sm); + LBL::InstallInterfaces(*sm, system); + LDN::InstallInterfaces(*sm, system); LDR::InstallInterfaces(*sm, system); LM::InstallInterfaces(system); - Migration::InstallInterfaces(*sm); - Mii::InstallInterfaces(*sm); - MM::InstallInterfaces(*sm); - NCM::InstallInterfaces(*sm); - NFC::InstallInterfaces(*sm); + Migration::InstallInterfaces(*sm, system); + Mii::InstallInterfaces(*sm, system); + MM::InstallInterfaces(*sm, system); + NCM::InstallInterfaces(*sm, system); + NFC::InstallInterfaces(*sm, system); NFP::InstallInterfaces(*sm, system); NIFM::InstallInterfaces(*sm, system); NIM::InstallInterfaces(*sm, system); - NPNS::InstallInterfaces(*sm); + NPNS::InstallInterfaces(*sm, system); NS::InstallInterfaces(*sm, system); Nvidia::InstallInterfaces(*sm, *nv_flinger, system); - PCIe::InstallInterfaces(*sm); - PCTL::InstallInterfaces(*sm); - PCV::InstallInterfaces(*sm); + OLSC::InstallInterfaces(*sm, system); + PCIe::InstallInterfaces(*sm, system); + PCTL::InstallInterfaces(*sm, system); + PCV::InstallInterfaces(*sm, system); PlayReport::InstallInterfaces(*sm, system); PM::InstallInterfaces(system); - PSC::InstallInterfaces(*sm); - PSM::InstallInterfaces(*sm); - Set::InstallInterfaces(*sm); + PSC::InstallInterfaces(*sm, system); + PSM::InstallInterfaces(*sm, system); + Set::InstallInterfaces(*sm, system); Sockets::InstallInterfaces(*sm, system); - SPL::InstallInterfaces(*sm); - SSL::InstallInterfaces(*sm); + SPL::InstallInterfaces(*sm, system); + SSL::InstallInterfaces(*sm, system); Time::InstallInterfaces(system); - USB::InstallInterfaces(*sm); - VI::InstallInterfaces(*sm, nv_flinger); - WLAN::InstallInterfaces(*sm); - - LOG_DEBUG(Service, "initialized OK"); + USB::InstallInterfaces(*sm, system); + VI::InstallInterfaces(*sm, system, *nv_flinger); + WLAN::InstallInterfaces(*sm, system); } -/// Shutdown ServiceManager -void Shutdown() { - LOG_DEBUG(Service, "shutdown OK"); -} +Services::~Services() = default; + } // namespace Service diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index a01ef3353..916445517 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -5,9 +5,11 @@ #pragma once #include +#include #include #include #include "common/common_types.h" +#include "common/spin_lock.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/object.h" @@ -29,7 +31,11 @@ namespace Service { namespace FileSystem { class FileSystemController; -} // namespace FileSystem +} + +namespace NVFlinger { +class NVFlinger; +} namespace SM { class ServiceManager; @@ -64,11 +70,9 @@ public: void InstallAsService(SM::ServiceManager& service_manager); /// Creates a port pair and registers it on the kernel's global port registry. void InstallAsNamedPort(Kernel::KernelCore& kernel); - /// Creates and returns an unregistered port for the service. - std::shared_ptr CreatePort(Kernel::KernelCore& kernel); - + /// Invokes a service request routine. void InvokeRequest(Kernel::HLERequestContext& ctx); - + /// Handles a synchronization request for the service. ResultCode HandleSyncRequest(Kernel::HLERequestContext& context) override; protected: @@ -76,6 +80,14 @@ protected: template using HandlerFnP = void (Self::*)(Kernel::HLERequestContext&); + /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread. + [[nodiscard]] std::scoped_lock LockService() { + return std::scoped_lock{lock_service}; + } + + /// System context that the service operates under. + Core::System& system; + private: template friend class ServiceFramework; @@ -89,7 +101,8 @@ private: using InvokerFn = void(ServiceFrameworkBase* object, HandlerFnP member, Kernel::HLERequestContext& ctx); - ServiceFrameworkBase(const char* service_name, u32 max_sessions, InvokerFn* handler_invoker); + explicit ServiceFrameworkBase(Core::System& system_, const char* service_name_, + u32 max_sessions_, InvokerFn* handler_invoker_); ~ServiceFrameworkBase() override; void RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n); @@ -107,6 +120,9 @@ private: /// Function used to safely up-cast pointers to the derived class before invoking a handler. InvokerFn* handler_invoker; boost::container::flat_map handlers; + + /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread. + Common::SpinLock lock_service; }; /** @@ -147,11 +163,15 @@ protected: /** * Initializes the handler with no functions installed. - * @param max_sessions Maximum number of sessions that can be - * connected to this service at the same time. + * + * @param system_ The system context to construct this service under. + * @param service_name_ Name of the service. + * @param max_sessions_ Maximum number of sessions that can be + * connected to this service at the same time. */ - explicit ServiceFramework(const char* service_name, u32 max_sessions = DefaultMaxSessions) - : ServiceFrameworkBase(service_name, max_sessions, Invoker) {} + explicit ServiceFramework(Core::System& system_, const char* service_name_, + u32 max_sessions_ = DefaultMaxSessions) + : ServiceFrameworkBase(system_, service_name_, max_sessions_, Invoker) {} /// Registers handlers in the service. template @@ -181,10 +201,17 @@ private: } }; -/// Initialize ServiceManager -void Init(std::shared_ptr& sm, Core::System& system); +/** + * The purpose of this class is to own any objects that need to be shared across the other service + * implementations. Will be torn down when the global system instance is shutdown. + */ +class Services final { +public: + explicit Services(std::shared_ptr& sm, Core::System& system); + ~Services(); -/// Shutdown ServiceManager -void Shutdown(); +private: + std::unique_ptr nv_flinger; +}; } // namespace Service diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp index e64777668..d953b4303 100644 --- a/src/core/hle/service/set/set.cpp +++ b/src/core/hle/service/set/set.cpp @@ -188,7 +188,7 @@ void SET::GetKeyCodeMap2(Kernel::HLERequestContext& ctx) { GetKeyCodeMapImpl(ctx); } -SET::SET() : ServiceFramework("set") { +SET::SET(Core::System& system_) : ServiceFramework{system_, "set"} { // clang-format off static const FunctionInfo functions[] = { {0, &SET::GetLanguageCode, "GetLanguageCode"}, @@ -202,6 +202,7 @@ SET::SET() : ServiceFramework("set") { {8, &SET::GetQuestFlag, "GetQuestFlag"}, {9, &SET::GetKeyCodeMap2, "GetKeyCodeMap2"}, {10, nullptr, "GetFirmwareVersionForDebug"}, + {11, nullptr, "GetDeviceNickName"}, }; // clang-format on diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h index 8ac9c169d..d5bd7828d 100644 --- a/src/core/hle/service/set/set.h +++ b/src/core/hle/service/set/set.h @@ -6,6 +6,10 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Set { /// This is "nn::settings::LanguageCode", which is a NUL-terminated string stored in a u64. @@ -32,7 +36,7 @@ LanguageCode GetLanguageCodeFromIndex(std::size_t idx); class SET final : public ServiceFramework { public: - explicit SET(); + explicit SET(Core::System& system_); ~SET() override; private: diff --git a/src/core/hle/service/set/set_cal.cpp b/src/core/hle/service/set/set_cal.cpp index 3fbfecc9e..b2aa7bc0c 100644 --- a/src/core/hle/service/set/set_cal.cpp +++ b/src/core/hle/service/set/set_cal.cpp @@ -6,7 +6,7 @@ namespace Service::Set { -SET_CAL::SET_CAL() : ServiceFramework("set:cal") { +SET_CAL::SET_CAL(Core::System& system_) : ServiceFramework{system_, "set:cal"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetBluetoothBdAddress"}, diff --git a/src/core/hle/service/set/set_cal.h b/src/core/hle/service/set/set_cal.h index a0677e815..a29fc3ddd 100644 --- a/src/core/hle/service/set/set_cal.h +++ b/src/core/hle/service/set/set_cal.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Set { class SET_CAL final : public ServiceFramework { public: - explicit SET_CAL(); + explicit SET_CAL(Core::System& system_); ~SET_CAL() override; }; diff --git a/src/core/hle/service/set/set_fd.cpp b/src/core/hle/service/set/set_fd.cpp index 565882a31..f04dc5047 100644 --- a/src/core/hle/service/set/set_fd.cpp +++ b/src/core/hle/service/set/set_fd.cpp @@ -6,7 +6,7 @@ namespace Service::Set { -SET_FD::SET_FD() : ServiceFramework("set:fd") { +SET_FD::SET_FD(Core::System& system_) : ServiceFramework{system_, "set:fd"} { // clang-format off static const FunctionInfo functions[] = { {2, nullptr, "SetSettingsItemValue"}, diff --git a/src/core/hle/service/set/set_fd.h b/src/core/hle/service/set/set_fd.h index 216e65f1f..c28cb301e 100644 --- a/src/core/hle/service/set/set_fd.h +++ b/src/core/hle/service/set/set_fd.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Set { class SET_FD final : public ServiceFramework { public: - explicit SET_FD(); + explicit SET_FD(Core::System& system_); ~SET_FD() override; }; diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp index 8bd4c7e79..b58b2c8c5 100644 --- a/src/core/hle/service/set/set_sys.cpp +++ b/src/core/hle/service/set/set_sys.cpp @@ -34,9 +34,9 @@ void GetFirmwareVersionImpl(Kernel::HLERequestContext& ctx, GetFirmwareVersionTy // consistence (currently reports as 5.1.0-0.0) const auto archive = FileSys::SystemArchive::SystemVersion(); - const auto early_exit_failure = [&ctx](const std::string& desc, ResultCode code) { + const auto early_exit_failure = [&ctx](std::string_view desc, ResultCode code) { LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).", - desc.c_str()); + desc); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(code); }; @@ -103,7 +103,7 @@ void SET_SYS::SetColorSetId(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } -SET_SYS::SET_SYS() : ServiceFramework("set:sys") { +SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SetLanguageCode"}, @@ -300,6 +300,8 @@ SET_SYS::SET_SYS() : ServiceFramework("set:sys") { {198, nullptr, "SetButtonConfigRegisteredSettingsEmbedded"}, {199, nullptr, "GetButtonConfigRegisteredSettings"}, {200, nullptr, "SetButtonConfigRegisteredSettings"}, + {201, nullptr, "GetFieldTestingFlag"}, + {202, nullptr, "SetFieldTestingFlag"}, }; // clang-format on diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h index 13ee2cf46..edb185a68 100644 --- a/src/core/hle/service/set/set_sys.h +++ b/src/core/hle/service/set/set_sys.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Set { class SET_SYS final : public ServiceFramework { public: - explicit SET_SYS(); + explicit SET_SYS(Core::System& system_); ~SET_SYS() override; private: diff --git a/src/core/hle/service/set/settings.cpp b/src/core/hle/service/set/settings.cpp index cf5541ca8..212ebc427 100644 --- a/src/core/hle/service/set/settings.cpp +++ b/src/core/hle/service/set/settings.cpp @@ -7,14 +7,15 @@ #include "core/hle/service/set/set_fd.h" #include "core/hle/service/set/set_sys.h" #include "core/hle/service/set/settings.h" +#include "core/hle/service/sm/sm.h" namespace Service::Set { -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); } } // namespace Service::Set diff --git a/src/core/hle/service/set/settings.h b/src/core/hle/service/set/settings.h index 6606ce776..7a6950dd0 100644 --- a/src/core/hle/service/set/settings.h +++ b/src/core/hle/service/set/settings.h @@ -4,11 +4,17 @@ #pragma once -#include "core/hle/service/service.h" +namespace Core { +class System; +} + +namespace Service::SM { +class ServiceManager; +} namespace Service::Set { /// Registers all Settings services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::Set diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp index 972aaa6d9..916177efd 100644 --- a/src/core/hle/service/sm/controller.cpp +++ b/src/core/hle/service/sm/controller.cpp @@ -48,7 +48,7 @@ void Controller::QueryPointerBufferSize(Kernel::HLERequestContext& ctx) { } // https://switchbrew.org/wiki/IPC_Marshalling -Controller::Controller() : ServiceFramework("IpcController") { +Controller::Controller(Core::System& system_) : ServiceFramework{system_, "IpcController"} { static const FunctionInfo functions[] = { {0, &Controller::ConvertCurrentObjectToDomain, "ConvertCurrentObjectToDomain"}, {1, nullptr, "CopyFromCurrentDomain"}, diff --git a/src/core/hle/service/sm/controller.h b/src/core/hle/service/sm/controller.h index 180c6da50..7494f898d 100644 --- a/src/core/hle/service/sm/controller.h +++ b/src/core/hle/service/sm/controller.h @@ -6,11 +6,15 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::SM { class Controller final : public ServiceFramework { public: - Controller(); + explicit Controller(Core::System& system_); ~Controller() override; private: diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index 9c1da361b..4da69f503 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -38,14 +38,13 @@ static ResultCode ValidateServiceName(const std::string& name) { return RESULT_SUCCESS; } -void ServiceManager::InstallInterfaces(std::shared_ptr self, - Kernel::KernelCore& kernel) { +void ServiceManager::InstallInterfaces(std::shared_ptr self, Core::System& system) { ASSERT(self->sm_interface.expired()); - auto sm = std::make_shared(self, kernel); - sm->InstallAsNamedPort(kernel); + auto sm = std::make_shared(self, system); + sm->InstallAsNamedPort(system.Kernel()); self->sm_interface = sm; - self->controller_interface = std::make_unique(); + self->controller_interface = std::make_unique(system); } ResultVal> ServiceManager::RegisterService(std::string name, @@ -190,8 +189,9 @@ void SM::UnregisterService(Kernel::HLERequestContext& ctx) { rb.Push(service_manager->UnregisterService(name)); } -SM::SM(std::shared_ptr service_manager, Kernel::KernelCore& kernel) - : ServiceFramework{"sm:", 4}, service_manager{std::move(service_manager)}, kernel{kernel} { +SM::SM(std::shared_ptr service_manager_, Core::System& system_) + : ServiceFramework{system_, "sm:", 4}, + service_manager{std::move(service_manager_)}, kernel{system_.Kernel()} { static const FunctionInfo functions[] = { {0x00000000, &SM::Initialize, "Initialize"}, {0x00000001, &SM::GetService, "GetService"}, diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index 6790c86f0..3f46ae44f 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -16,6 +16,10 @@ #include "core/hle/result.h" #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Kernel { class ClientPort; class ClientSession; @@ -31,7 +35,7 @@ class Controller; /// Interface to "sm:" service class SM final : public ServiceFramework { public: - explicit SM(std::shared_ptr service_manager, Kernel::KernelCore& kernel); + explicit SM(std::shared_ptr service_manager_, Core::System& system_); ~SM() override; private: @@ -46,7 +50,7 @@ private: class ServiceManager { public: - static void InstallInterfaces(std::shared_ptr self, Kernel::KernelCore& kernel); + static void InstallInterfaces(std::shared_ptr self, Core::System& system); explicit ServiceManager(Kernel::KernelCore& kernel_); ~ServiceManager(); diff --git a/src/core/hle/service/sockets/blocking_worker.h b/src/core/hle/service/sockets/blocking_worker.h deleted file mode 100644 index 2d53e52b6..000000000 --- a/src/core/hle/service/sockets/blocking_worker.h +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2020 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "common/assert.h" -#include "common/microprofile.h" -#include "common/thread.h" -#include "core/core.h" -#include "core/hle/kernel/hle_ipc.h" -#include "core/hle/kernel/kernel.h" -#include "core/hle/kernel/thread.h" -#include "core/hle/kernel/writable_event.h" - -namespace Service::Sockets { - -/** - * Worker abstraction to execute blocking calls on host without blocking the guest thread - * - * @tparam Service Service where the work is executed - * @tparam Types Types of work to execute - */ -template -class BlockingWorker { - using This = BlockingWorker; - using WorkVariant = std::variant; - -public: - /// Create a new worker - static std::unique_ptr Create(Core::System& system, Service* service, - std::string_view name) { - return std::unique_ptr(new This(system, service, name)); - } - - ~BlockingWorker() { - while (!is_available.load(std::memory_order_relaxed)) { - // Busy wait until work is finished - std::this_thread::yield(); - } - // Monostate means to exit the thread - work = std::monostate{}; - work_event.Set(); - thread.join(); - } - - /** - * Try to capture the worker to send work after a success - * @returns True when the worker has been successfully captured - */ - bool TryCapture() { - bool expected = true; - return is_available.compare_exchange_weak(expected, false, std::memory_order_relaxed, - std::memory_order_relaxed); - } - - /** - * Send work to this worker abstraction - * @see TryCapture must be called before attempting to call this function - */ - template - void SendWork(Work new_work) { - ASSERT_MSG(!is_available, "Trying to send work on a worker that's not captured"); - work = std::move(new_work); - work_event.Set(); - } - - /// Generate a callback for @see SleepClientThread - template - auto Callback() { - return [this](std::shared_ptr, Kernel::HLERequestContext& ctx, - Kernel::ThreadWakeupReason reason) { - ASSERT(reason == Kernel::ThreadWakeupReason::Signal); - std::get(work).Response(ctx); - is_available.store(true); - }; - } - - /// Get kernel event that will be signalled by the worker when the host operation finishes - std::shared_ptr KernelEvent() const { - return kernel_event; - } - -private: - explicit BlockingWorker(Core::System& system, Service* service, std::string_view name) { - auto pair = Kernel::WritableEvent::CreateEventPair(system.Kernel(), std::string(name)); - kernel_event = std::move(pair.writable); - thread = std::thread([this, &system, service, name] { Run(system, service, name); }); - } - - void Run(Core::System& system, Service* service, std::string_view name) { - system.RegisterHostThread(); - - const std::string thread_name = fmt::format("yuzu:{}", name); - MicroProfileOnThreadCreate(thread_name.c_str()); - Common::SetCurrentThreadName(thread_name.c_str()); - - bool keep_running = true; - while (keep_running) { - work_event.Wait(); - - const auto visit_fn = [service, &keep_running](T&& w) { - if constexpr (std::is_same_v, std::monostate>) { - keep_running = false; - } else { - w.Execute(service); - } - }; - std::visit(visit_fn, work); - - kernel_event->Signal(); - } - } - - std::thread thread; - WorkVariant work; - Common::Event work_event; - std::shared_ptr kernel_event; - std::atomic_bool is_available{true}; -}; - -template -class BlockingWorkerPool { - using Worker = BlockingWorker; - -public: - explicit BlockingWorkerPool(Core::System& system_, Service* service_) - : system{system_}, service{service_} {} - - /// Returns a captured worker thread, creating new ones if necessary - Worker* CaptureWorker() { - for (auto& worker : workers) { - if (worker->TryCapture()) { - return worker.get(); - } - } - auto new_worker = Worker::Create(system, service, fmt::format("BSD:{}", workers.size())); - [[maybe_unused]] const bool success = new_worker->TryCapture(); - ASSERT(success); - - return workers.emplace_back(std::move(new_worker)).get(); - } - -private: - Core::System& system; - Service* const service; - - std::vector> workers; -}; - -} // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index a74be9370..2b824059d 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -30,7 +30,7 @@ bool IsConnectionBased(Type type) { case Type::DGRAM: return false; default: - UNIMPLEMENTED_MSG("Unimplemented type={}", static_cast(type)); + UNIMPLEMENTED_MSG("Unimplemented type={}", type); return false; } } @@ -178,13 +178,12 @@ void BSD::Poll(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service, "called. nfds={} timeout={}", nfds, timeout); - ExecuteWork(ctx, "BSD:Poll", timeout != 0, - PollWork{ - .nfds = nfds, - .timeout = timeout, - .read_buffer = ctx.ReadBuffer(), - .write_buffer = std::vector(ctx.GetWriteBufferSize()), - }); + ExecuteWork(ctx, PollWork{ + .nfds = nfds, + .timeout = timeout, + .read_buffer = ctx.ReadBuffer(), + .write_buffer = std::vector(ctx.GetWriteBufferSize()), + }); } void BSD::Accept(Kernel::HLERequestContext& ctx) { @@ -193,11 +192,10 @@ void BSD::Accept(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service, "called. fd={}", fd); - ExecuteWork(ctx, "BSD:Accept", IsBlockingSocket(fd), - AcceptWork{ - .fd = fd, - .write_buffer = std::vector(ctx.GetWriteBufferSize()), - }); + ExecuteWork(ctx, AcceptWork{ + .fd = fd, + .write_buffer = std::vector(ctx.GetWriteBufferSize()), + }); } void BSD::Bind(Kernel::HLERequestContext& ctx) { @@ -215,11 +213,10 @@ void BSD::Connect(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize()); - ExecuteWork(ctx, "BSD:Connect", IsBlockingSocket(fd), - ConnectWork{ - .fd = fd, - .addr = ctx.ReadBuffer(), - }); + ExecuteWork(ctx, ConnectWork{ + .fd = fd, + .addr = ctx.ReadBuffer(), + }); } void BSD::GetPeerName(Kernel::HLERequestContext& ctx) { @@ -327,12 +324,11 @@ void BSD::Recv(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetWriteBufferSize()); - ExecuteWork(ctx, "BSD:Recv", IsBlockingSocket(fd), - RecvWork{ - .fd = fd, - .flags = flags, - .message = std::vector(ctx.GetWriteBufferSize()), - }); + ExecuteWork(ctx, RecvWork{ + .fd = fd, + .flags = flags, + .message = std::vector(ctx.GetWriteBufferSize()), + }); } void BSD::RecvFrom(Kernel::HLERequestContext& ctx) { @@ -344,13 +340,12 @@ void BSD::RecvFrom(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={} addrlen={}", fd, flags, ctx.GetWriteBufferSize(0), ctx.GetWriteBufferSize(1)); - ExecuteWork(ctx, "BSD:RecvFrom", IsBlockingSocket(fd), - RecvFromWork{ - .fd = fd, - .flags = flags, - .message = std::vector(ctx.GetWriteBufferSize(0)), - .addr = std::vector(ctx.GetWriteBufferSize(1)), - }); + ExecuteWork(ctx, RecvFromWork{ + .fd = fd, + .flags = flags, + .message = std::vector(ctx.GetWriteBufferSize(0)), + .addr = std::vector(ctx.GetWriteBufferSize(1)), + }); } void BSD::Send(Kernel::HLERequestContext& ctx) { @@ -361,12 +356,11 @@ void BSD::Send(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetReadBufferSize()); - ExecuteWork(ctx, "BSD:Send", IsBlockingSocket(fd), - SendWork{ - .fd = fd, - .flags = flags, - .message = ctx.ReadBuffer(), - }); + ExecuteWork(ctx, SendWork{ + .fd = fd, + .flags = flags, + .message = ctx.ReadBuffer(), + }); } void BSD::SendTo(Kernel::HLERequestContext& ctx) { @@ -377,13 +371,12 @@ void BSD::SendTo(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service, "called. fd={} flags=0x{} len={} addrlen={}", fd, flags, ctx.GetReadBufferSize(0), ctx.GetReadBufferSize(1)); - ExecuteWork(ctx, "BSD:SendTo", IsBlockingSocket(fd), - SendToWork{ - .fd = fd, - .flags = flags, - .message = ctx.ReadBuffer(0), - .addr = ctx.ReadBuffer(1), - }); + ExecuteWork(ctx, SendToWork{ + .fd = fd, + .flags = flags, + .message = ctx.ReadBuffer(0), + .addr = ctx.ReadBuffer(1), + }); } void BSD::Write(Kernel::HLERequestContext& ctx) { @@ -392,12 +385,11 @@ void BSD::Write(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service, "called. fd={} len={}", fd, ctx.GetReadBufferSize()); - ExecuteWork(ctx, "BSD:Write", IsBlockingSocket(fd), - SendWork{ - .fd = fd, - .flags = 0, - .message = ctx.ReadBuffer(), - }); + ExecuteWork(ctx, SendWork{ + .fd = fd, + .flags = 0, + .message = ctx.ReadBuffer(), + }); } void BSD::Close(Kernel::HLERequestContext& ctx) { @@ -410,24 +402,9 @@ void BSD::Close(Kernel::HLERequestContext& ctx) { } template -void BSD::ExecuteWork(Kernel::HLERequestContext& ctx, std::string_view sleep_reason, - bool is_blocking, Work work) { - if (!is_blocking) { - work.Execute(this); - work.Response(ctx); - return; - } - - // Signal a dummy response to make IPC validation happy - // This will be overwritten by the SleepClientThread callback +void BSD::ExecuteWork(Kernel::HLERequestContext& ctx, Work work) { + work.Execute(this); work.Response(ctx); - - auto worker = worker_pool.CaptureWorker(); - - ctx.SleepClientThread(std::string(sleep_reason), std::numeric_limits::max(), - worker->Callback(), worker->KernelEvent()); - - worker->SendWork(std::move(work)); } std::pair BSD::SocketImpl(Domain domain, Type type, Protocol protocol) { @@ -489,18 +466,18 @@ std::pair BSD::PollImpl(std::vector& write_buffer, std::vector static_cast(MAX_FD) || pollfd.fd < 0) { LOG_ERROR(Service, "File descriptor handle={} is invalid", pollfd.fd); - pollfd.revents = 0; + pollfd.revents = PollEvents{}; return {0, Errno::SUCCESS}; } const std::optional& descriptor = file_descriptors[pollfd.fd]; if (!descriptor) { LOG_ERROR(Service, "File descriptor handle={} is not allocated", pollfd.fd); - pollfd.revents = POLL_NVAL; + pollfd.revents = PollEvents::Nval; return {0, Errno::SUCCESS}; } } @@ -510,7 +487,7 @@ std::pair BSD::PollImpl(std::vector& write_buffer, std::vectorsocket.get(); result.events = TranslatePollEventsToHost(pollfd.events); - result.revents = 0; + result.revents = Network::PollEvents{}; return result; }); @@ -636,7 +613,7 @@ std::pair BSD::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) { return {0, Errno::SUCCESS}; } default: - UNIMPLEMENTED_MSG("Unimplemented cmd={}", static_cast(cmd)); + UNIMPLEMENTED_MSG("Unimplemented cmd={}", cmd); return {-1, Errno::SUCCESS}; } } @@ -679,7 +656,7 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, con case OptName::RCVTIMEO: return Translate(socket->SetRcvTimeo(value)); default: - UNIMPLEMENTED_MSG("Unimplemented optname={}", static_cast(optname)); + UNIMPLEMENTED_MSG("Unimplemented optname={}", optname); return Errno::SUCCESS; } } @@ -807,18 +784,6 @@ bool BSD::IsFileDescriptorValid(s32 fd) const noexcept { return true; } -bool BSD::IsBlockingSocket(s32 fd) const noexcept { - // Inform invalid sockets as non-blocking - // This way we avoid using a worker thread as it will fail without blocking host - if (fd > static_cast(MAX_FD) || fd < 0) { - return false; - } - if (!file_descriptors[fd]) { - return false; - } - return (file_descriptors[fd]->flags & FLAG_O_NONBLOCK) != 0; -} - void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept { IPC::ResponseBuilder rb{ctx, 4}; @@ -827,8 +792,7 @@ void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) co rb.PushEnum(bsd_errno); } -BSD::BSD(Core::System& system, const char* name) - : ServiceFramework(name), worker_pool{system, this} { +BSD::BSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { {0, &BSD::RegisterClient, "RegisterClient"}, @@ -873,7 +837,7 @@ BSD::BSD(Core::System& system, const char* name) BSD::~BSD() = default; -BSDCFG::BSDCFG() : ServiceFramework{"bsdcfg"} { +BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SetIfUp"}, diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index 357531951..6da0bfeb2 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h @@ -11,7 +11,6 @@ #include "common/common_types.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/service.h" -#include "core/hle/service/sockets/blocking_worker.h" #include "core/hle/service/sockets/sockets.h" namespace Core { @@ -26,7 +25,7 @@ namespace Service::Sockets { class BSD final : public ServiceFramework { public: - explicit BSD(Core::System& system, const char* name); + explicit BSD(Core::System& system_, const char* name); ~BSD() override; private: @@ -138,8 +137,7 @@ private: void Close(Kernel::HLERequestContext& ctx); template - void ExecuteWork(Kernel::HLERequestContext& ctx, std::string_view sleep_reason, - bool is_blocking, Work work); + void ExecuteWork(Kernel::HLERequestContext& ctx, Work work); std::pair SocketImpl(Domain domain, Type type, Protocol protocol); std::pair PollImpl(std::vector& write_buffer, std::vector read_buffer, @@ -163,20 +161,15 @@ private: s32 FindFreeFileDescriptorHandle() noexcept; bool IsFileDescriptorValid(s32 fd) const noexcept; - bool IsBlockingSocket(s32 fd) const noexcept; void BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept; std::array, MAX_FD> file_descriptors; - - BlockingWorkerPool - worker_pool; }; class BSDCFG final : public ServiceFramework { public: - explicit BSDCFG(); + explicit BSDCFG(Core::System& system_); ~BSDCFG() override; }; diff --git a/src/core/hle/service/sockets/ethc.cpp b/src/core/hle/service/sockets/ethc.cpp index abbeb4c50..05681ca2d 100644 --- a/src/core/hle/service/sockets/ethc.cpp +++ b/src/core/hle/service/sockets/ethc.cpp @@ -6,7 +6,7 @@ namespace Service::Sockets { -ETHC_C::ETHC_C() : ServiceFramework{"ethc:c"} { +ETHC_C::ETHC_C(Core::System& system_) : ServiceFramework{system_, "ethc:c"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -23,7 +23,7 @@ ETHC_C::ETHC_C() : ServiceFramework{"ethc:c"} { ETHC_C::~ETHC_C() = default; -ETHC_I::ETHC_I() : ServiceFramework{"ethc:i"} { +ETHC_I::ETHC_I(Core::System& system_) : ServiceFramework{system_, "ethc:i"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetReadableHandle"}, diff --git a/src/core/hle/service/sockets/ethc.h b/src/core/hle/service/sockets/ethc.h index da2c7f741..71884182e 100644 --- a/src/core/hle/service/sockets/ethc.h +++ b/src/core/hle/service/sockets/ethc.h @@ -6,17 +6,21 @@ #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Sockets { class ETHC_C final : public ServiceFramework { public: - explicit ETHC_C(); + explicit ETHC_C(Core::System& system_); ~ETHC_C() override; }; class ETHC_I final : public ServiceFramework { public: - explicit ETHC_I(); + explicit ETHC_I(Core::System& system_); ~ETHC_I() override; }; diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp index 40d781124..51c3739bb 100644 --- a/src/core/hle/service/sockets/nsd.cpp +++ b/src/core/hle/service/sockets/nsd.cpp @@ -6,7 +6,7 @@ namespace Service::Sockets { -NSD::NSD(const char* name) : ServiceFramework(name) { +NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { {10, nullptr, "GetSettingName"}, diff --git a/src/core/hle/service/sockets/nsd.h b/src/core/hle/service/sockets/nsd.h index d842e3232..becf93125 100644 --- a/src/core/hle/service/sockets/nsd.h +++ b/src/core/hle/service/sockets/nsd.h @@ -4,14 +4,17 @@ #pragma once -#include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Sockets { class NSD final : public ServiceFramework { public: - explicit NSD(const char* name); + explicit NSD(Core::System& system_, const char* name); ~NSD() override; }; diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index e3017451f..3a6329f56 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -7,25 +7,7 @@ namespace Service::Sockets { -void SFDNSRES::GetAddrInfoRequest(Kernel::HLERequestContext& ctx) { - struct Parameters { - u8 use_nsd_resolve; - u32 unknown; - u64 process_id; - }; - - IPC::RequestParser rp{ctx}; - const auto parameters = rp.PopRaw(); - - LOG_WARNING(Service, - "(STUBBED) called. use_nsd_resolve={}, unknown=0x{:08X}, process_id=0x{:016X}", - parameters.use_nsd_resolve, parameters.unknown, parameters.process_id); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} - -SFDNSRES::SFDNSRES() : ServiceFramework("sfdnsres") { +SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres"} { static const FunctionInfo functions[] = { {0, nullptr, "SetDnsAddressesPrivate"}, {1, nullptr, "GetDnsAddressPrivate"}, @@ -49,4 +31,22 @@ SFDNSRES::SFDNSRES() : ServiceFramework("sfdnsres") { SFDNSRES::~SFDNSRES() = default; +void SFDNSRES::GetAddrInfoRequest(Kernel::HLERequestContext& ctx) { + struct Parameters { + u8 use_nsd_resolve; + u32 unknown; + u64 process_id; + }; + + IPC::RequestParser rp{ctx}; + const auto parameters = rp.PopRaw(); + + LOG_WARNING(Service, + "(STUBBED) called. use_nsd_resolve={}, unknown=0x{:08X}, process_id=0x{:016X}", + parameters.use_nsd_resolve, parameters.unknown, parameters.process_id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sfdnsres.h b/src/core/hle/service/sockets/sfdnsres.h index acd3647bb..faa6b7d0d 100644 --- a/src/core/hle/service/sockets/sfdnsres.h +++ b/src/core/hle/service/sockets/sfdnsres.h @@ -7,11 +7,15 @@ #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::Sockets { class SFDNSRES final : public ServiceFramework { public: - explicit SFDNSRES(); + explicit SFDNSRES(Core::System& system_); ~SFDNSRES() override; private: diff --git a/src/core/hle/service/sockets/sockets.cpp b/src/core/hle/service/sockets/sockets.cpp index 1d27f7906..96f73bce3 100644 --- a/src/core/hle/service/sockets/sockets.cpp +++ b/src/core/hle/service/sockets/sockets.cpp @@ -13,15 +13,15 @@ namespace Service::Sockets { void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { std::make_shared(system, "bsd:s")->InstallAsService(service_manager); std::make_shared(system, "bsd:u")->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); - std::make_shared("nsd:a")->InstallAsService(service_manager); - std::make_shared("nsd:u")->InstallAsService(service_manager); + std::make_shared(system, "nsd:a")->InstallAsService(service_manager); + std::make_shared(system, "nsd:u")->InstallAsService(service_manager); - std::make_shared()->InstallAsService(service_manager); + std::make_shared(system)->InstallAsService(service_manager); } } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index 89a410076..5a65ed2a9 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h @@ -69,10 +69,22 @@ struct SockAddrIn { std::array zeroes; }; +enum class PollEvents : u16 { + // Using Pascal case because IN is a macro on Windows. + In = 1 << 0, + Pri = 1 << 1, + Out = 1 << 2, + Err = 1 << 3, + Hup = 1 << 4, + Nval = 1 << 5, +}; + +DECLARE_ENUM_FLAG_OPERATORS(PollEvents); + struct PollFD { s32 fd; - u16 events; - u16 revents; + PollEvents events; + PollEvents revents; }; struct Linger { @@ -80,13 +92,6 @@ struct Linger { u32 linger; }; -constexpr u16 POLL_IN = 0x01; -constexpr u16 POLL_PRI = 0x02; -constexpr u16 POLL_OUT = 0x04; -constexpr u16 POLL_ERR = 0x08; -constexpr u16 POLL_HUP = 0x10; -constexpr u16 POLL_NVAL = 0x20; - constexpr u32 FLAG_MSG_DONTWAIT = 0x80; constexpr u32 FLAG_O_NONBLOCK = 0x800; diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp index 139743e1d..ca61d72ca 100644 --- a/src/core/hle/service/sockets/sockets_translate.cpp +++ b/src/core/hle/service/sockets/sockets_translate.cpp @@ -27,7 +27,7 @@ Errno Translate(Network::Errno value) { case Network::Errno::NOTCONN: return Errno::NOTCONN; default: - UNIMPLEMENTED_MSG("Unimplemented errno={}", static_cast(value)); + UNIMPLEMENTED_MSG("Unimplemented errno={}", value); return Errno::SUCCESS; } } @@ -41,7 +41,7 @@ Network::Domain Translate(Domain domain) { case Domain::INET: return Network::Domain::INET; default: - UNIMPLEMENTED_MSG("Unimplemented domain={}", static_cast(domain)); + UNIMPLEMENTED_MSG("Unimplemented domain={}", domain); return {}; } } @@ -51,7 +51,7 @@ Domain Translate(Network::Domain domain) { case Network::Domain::INET: return Domain::INET; default: - UNIMPLEMENTED_MSG("Unimplemented domain={}", static_cast(domain)); + UNIMPLEMENTED_MSG("Unimplemented domain={}", domain); return {}; } } @@ -63,7 +63,8 @@ Network::Type Translate(Type type) { case Type::DGRAM: return Network::Type::DGRAM; default: - UNIMPLEMENTED_MSG("Unimplemented type={}", static_cast(type)); + UNIMPLEMENTED_MSG("Unimplemented type={}", type); + return Network::Type{}; } } @@ -84,47 +85,47 @@ Network::Protocol Translate(Type type, Protocol protocol) { case Protocol::UDP: return Network::Protocol::UDP; default: - UNIMPLEMENTED_MSG("Unimplemented protocol={}", static_cast(protocol)); + UNIMPLEMENTED_MSG("Unimplemented protocol={}", protocol); return Network::Protocol::TCP; } } -u16 TranslatePollEventsToHost(u16 flags) { - u16 result = 0; - const auto translate = [&result, &flags](u16 from, u16 to) { - if ((flags & from) != 0) { +Network::PollEvents TranslatePollEventsToHost(PollEvents flags) { + Network::PollEvents result{}; + const auto translate = [&result, &flags](PollEvents from, Network::PollEvents to) { + if (True(flags & from)) { flags &= ~from; result |= to; } }; - translate(POLL_IN, Network::POLL_IN); - translate(POLL_PRI, Network::POLL_PRI); - translate(POLL_OUT, Network::POLL_OUT); - translate(POLL_ERR, Network::POLL_ERR); - translate(POLL_HUP, Network::POLL_HUP); - translate(POLL_NVAL, Network::POLL_NVAL); + translate(PollEvents::In, Network::PollEvents::In); + translate(PollEvents::Pri, Network::PollEvents::Pri); + translate(PollEvents::Out, Network::PollEvents::Out); + translate(PollEvents::Err, Network::PollEvents::Err); + translate(PollEvents::Hup, Network::PollEvents::Hup); + translate(PollEvents::Nval, Network::PollEvents::Nval); - UNIMPLEMENTED_IF_MSG(flags != 0, "Unimplemented flags={}", flags); + UNIMPLEMENTED_IF_MSG((u16)flags != 0, "Unimplemented flags={}", (u16)flags); return result; } -u16 TranslatePollEventsToGuest(u16 flags) { - u16 result = 0; - const auto translate = [&result, &flags](u16 from, u16 to) { - if ((flags & from) != 0) { +PollEvents TranslatePollEventsToGuest(Network::PollEvents flags) { + PollEvents result{}; + const auto translate = [&result, &flags](Network::PollEvents from, PollEvents to) { + if (True(flags & from)) { flags &= ~from; result |= to; } }; - translate(Network::POLL_IN, POLL_IN); - translate(Network::POLL_PRI, POLL_PRI); - translate(Network::POLL_OUT, POLL_OUT); - translate(Network::POLL_ERR, POLL_ERR); - translate(Network::POLL_HUP, POLL_HUP); - translate(Network::POLL_NVAL, POLL_NVAL); + translate(Network::PollEvents::In, PollEvents::In); + translate(Network::PollEvents::Pri, PollEvents::Pri); + translate(Network::PollEvents::Out, PollEvents::Out); + translate(Network::PollEvents::Err, PollEvents::Err); + translate(Network::PollEvents::Hup, PollEvents::Hup); + translate(Network::PollEvents::Nval, PollEvents::Nval); - UNIMPLEMENTED_IF_MSG(flags != 0, "Unimplemented flags={}", flags); + UNIMPLEMENTED_IF_MSG((u16)flags != 0, "Unimplemented flags={}", (u16)flags); return result; } @@ -157,7 +158,7 @@ Network::ShutdownHow Translate(ShutdownHow how) { case ShutdownHow::RDWR: return Network::ShutdownHow::RDWR; default: - UNIMPLEMENTED_MSG("Unimplemented how={}", static_cast(how)); + UNIMPLEMENTED_MSG("Unimplemented how={}", how); return {}; } } diff --git a/src/core/hle/service/sockets/sockets_translate.h b/src/core/hle/service/sockets/sockets_translate.h index 8ed041e31..057d1ff22 100644 --- a/src/core/hle/service/sockets/sockets_translate.h +++ b/src/core/hle/service/sockets/sockets_translate.h @@ -31,10 +31,10 @@ Network::Type Translate(Type type); Network::Protocol Translate(Type type, Protocol protocol); /// Translate abstract poll event flags to guest poll event flags -u16 TranslatePollEventsToHost(u16 flags); +Network::PollEvents TranslatePollEventsToHost(PollEvents flags); /// Translate guest poll event flags to abstract poll event flags -u16 TranslatePollEventsToGuest(u16 flags); +PollEvents TranslatePollEventsToGuest(Network::PollEvents flags); /// Translate guest socket address structure to abstract socket address structure Network::SockAddrIn Translate(SockAddrIn value); diff --git a/src/core/hle/service/spl/csrng.cpp b/src/core/hle/service/spl/csrng.cpp index 674928798..1beca417c 100644 --- a/src/core/hle/service/spl/csrng.cpp +++ b/src/core/hle/service/spl/csrng.cpp @@ -6,7 +6,8 @@ namespace Service::SPL { -CSRNG::CSRNG(std::shared_ptr module) : Module::Interface(std::move(module), "csrng") { +CSRNG::CSRNG(Core::System& system_, std::shared_ptr module_) + : Interface(system_, std::move(module_), "csrng") { static const FunctionInfo functions[] = { {0, &CSRNG::GetRandomBytes, "GetRandomBytes"}, }; diff --git a/src/core/hle/service/spl/csrng.h b/src/core/hle/service/spl/csrng.h index 764d5ceb0..5c0bd2199 100644 --- a/src/core/hle/service/spl/csrng.h +++ b/src/core/hle/service/spl/csrng.h @@ -6,11 +6,15 @@ #include "core/hle/service/spl/module.h" +namespace Core { +class System; +} + namespace Service::SPL { class CSRNG final : public Module::Interface { public: - explicit CSRNG(std::shared_ptr module); + explicit CSRNG(Core::System& system_, std::shared_ptr module_); ~CSRNG() override; }; diff --git a/src/core/hle/service/spl/module.cpp b/src/core/hle/service/spl/module.cpp index 865ed3b91..dea6b0fe0 100644 --- a/src/core/hle/service/spl/module.cpp +++ b/src/core/hle/service/spl/module.cpp @@ -17,8 +17,9 @@ namespace Service::SPL { -Module::Interface::Interface(std::shared_ptr module, const char* name) - : ServiceFramework(name), module(std::move(module)), +Module::Interface::Interface(Core::System& system_, std::shared_ptr module_, + const char* name) + : ServiceFramework{system_, name}, module{std::move(module_)}, rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr))) {} Module::Interface::~Interface() = default; @@ -38,10 +39,10 @@ void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } -void InstallInterfaces(SM::ServiceManager& service_manager) { +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { auto module = std::make_shared(); - std::make_shared(module)->InstallAsService(service_manager); - std::make_shared(module)->InstallAsService(service_manager); + std::make_shared(system, module)->InstallAsService(service_manager); + std::make_shared(system, module)->InstallAsService(service_manager); } } // namespace Service::SPL diff --git a/src/core/hle/service/spl/module.h b/src/core/hle/service/spl/module.h index afa1f0295..71855c1bf 100644 --- a/src/core/hle/service/spl/module.h +++ b/src/core/hle/service/spl/module.h @@ -7,13 +7,18 @@ #include #include "core/hle/service/service.h" +namespace Core { +class System; +} + namespace Service::SPL { class Module final { public: class Interface : public ServiceFramework { public: - explicit Interface(std::shared_ptr module, const char* name); + explicit Interface(Core::System& system_, std::shared_ptr module_, + const char* name); ~Interface() override; void GetRandomBytes(Kernel::HLERequestContext& ctx); @@ -27,6 +32,6 @@ public: }; /// Registers all SPL services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::SPL diff --git a/src/core/hle/service/spl/spl.cpp b/src/core/hle/service/spl/spl.cpp index 773551464..3fabc2c79 100644 --- a/src/core/hle/service/spl/spl.cpp +++ b/src/core/hle/service/spl/spl.cpp @@ -6,7 +6,8 @@ namespace Service::SPL { -SPL::SPL(std::shared_ptr module) : Module::Interface(std::move(module), "spl:") { +SPL::SPL(Core::System& system_, std::shared_ptr module_) + : Interface(system_, std::move(module_), "spl:") { static const FunctionInfo functions[] = { {0, nullptr, "GetConfig"}, {1, nullptr, "ModularExponentiate"}, diff --git a/src/core/hle/service/spl/spl.h b/src/core/hle/service/spl/spl.h index 3637d1623..d27d16b86 100644 --- a/src/core/hle/service/spl/spl.h +++ b/src/core/hle/service/spl/spl.h @@ -6,11 +6,15 @@ #include "core/hle/service/spl/module.h" +namespace Core { +class System; +} + namespace Service::SPL { class SPL final : public Module::Interface { public: - explicit SPL(std::shared_ptr module); + explicit SPL(Core::System& system_, std::shared_ptr module_); ~SPL() override; }; diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index 1ba8c19a0..dc2baca4a 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -12,7 +12,7 @@ namespace Service::SSL { class ISslConnection final : public ServiceFramework { public: - ISslConnection() : ServiceFramework("ISslConnection") { + explicit ISslConnection(Core::System& system_) : ServiceFramework{system_, "ISslConnection"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "SetSocketDescriptor"}, @@ -52,7 +52,7 @@ public: class ISslContext final : public ServiceFramework { public: - ISslContext() : ServiceFramework("ISslContext") { + explicit ISslContext(Core::System& system_) : ServiceFramework{system_, "ISslContext"} { static const FunctionInfo functions[] = { {0, &ISslContext::SetOption, "SetOption"}, {1, nullptr, "GetOption"}, @@ -92,13 +92,13 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } }; class SSL final : public ServiceFramework { public: - explicit SSL() : ServiceFramework{"ssl"} { + explicit SSL(Core::System& system_) : ServiceFramework{system_, "ssl"} { // clang-format off static const FunctionInfo functions[] = { {0, &SSL::CreateContext, "CreateContext"}, @@ -123,7 +123,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(); + rb.PushIpcInterface(system); } void SetInterfaceVersion(Kernel::HLERequestContext& ctx) { @@ -137,8 +137,8 @@ private: } }; -void InstallInterfaces(SM::ServiceManager& service_manager) { - std::make_shared()->InstallAsService(service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { + std::make_shared(system)->InstallAsService(service_manager); } } // namespace Service::SSL diff --git a/src/core/hle/service/ssl/ssl.h b/src/core/hle/service/ssl/ssl.h index 5cb04c3b9..a3aa4b4b5 100644 --- a/src/core/hle/service/ssl/ssl.h +++ b/src/core/hle/service/ssl/ssl.h @@ -4,6 +4,10 @@ #pragma once +namespace Core { +class System; +} + namespace Service::SM { class ServiceManager; } @@ -11,6 +15,6 @@ class ServiceManager; namespace Service::SSL { /// Registers all SSL services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); } // namespace Service::SSL diff --git a/src/core/hle/service/time/interface.cpp b/src/core/hle/service/time/interface.cpp index ba8fd6152..a01d9e0ff 100644 --- a/src/core/hle/service/time/interface.cpp +++ b/src/core/hle/service/time/interface.cpp @@ -7,7 +7,7 @@ namespace Service::Time { Time::Time(std::shared_ptr module, Core::System& system, const char* name) - : Module::Interface(std::move(module), system, name) { + : Interface(std::move(module), system, name) { // clang-format off static const FunctionInfo functions[] = { {0, &Time::GetStandardUserSystemClock, "GetStandardUserSystemClock"}, diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp index ee4fa4b48..abc753d5d 100644 --- a/src/core/hle/service/time/time.cpp +++ b/src/core/hle/service/time/time.cpp @@ -10,7 +10,8 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" -#include "core/hle/kernel/scheduler.h" +#include "core/hle/kernel/k_scheduler.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/service/time/interface.h" #include "core/hle/service/time/time.h" #include "core/hle/service/time/time_sharedmemory.h" @@ -20,8 +21,8 @@ namespace Service::Time { class ISystemClock final : public ServiceFramework { public: - explicit ISystemClock(Clock::SystemClockCore& clock_core, Core::System& system) - : ServiceFramework("ISystemClock"), clock_core{clock_core}, system{system} { + explicit ISystemClock(Clock::SystemClockCore& clock_core_, Core::System& system_) + : ServiceFramework{system_, "ISystemClock"}, clock_core{clock_core_} { // clang-format off static const FunctionInfo functions[] = { {0, &ISystemClock::GetCurrentTime, "GetCurrentTime"}, @@ -81,13 +82,12 @@ private: } Clock::SystemClockCore& clock_core; - Core::System& system; }; class ISteadyClock final : public ServiceFramework { public: - explicit ISteadyClock(Clock::SteadyClockCore& clock_core, Core::System& system) - : ServiceFramework("ISteadyClock"), clock_core{clock_core}, system{system} { + explicit ISteadyClock(Clock::SteadyClockCore& clock_core_, Core::System& system_) + : ServiceFramework{system_, "ISteadyClock"}, clock_core{clock_core_} { static const FunctionInfo functions[] = { {0, &ISteadyClock::GetCurrentTimePoint, "GetCurrentTimePoint"}, {2, nullptr, "GetTestOffset"}, @@ -118,14 +118,13 @@ private: } Clock::SteadyClockCore& clock_core; - Core::System& system; }; ResultCode Module::Interface::GetClockSnapshotFromSystemClockContextInternal( Kernel::Thread* thread, Clock::SystemClockContext user_context, Clock::SystemClockContext network_context, u8 type, Clock::ClockSnapshot& clock_snapshot) { - auto& time_manager{module->GetTimeManager()}; + auto& time_manager{system.GetTimeManager()}; clock_snapshot.is_automatic_correction_enabled = time_manager.GetStandardUserSystemClockCore().IsAutomaticCorrectionEnabled(); @@ -182,7 +181,7 @@ void Module::Interface::GetStandardUserSystemClock(Kernel::HLERequestContext& ct LOG_DEBUG(Service_Time, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(module->GetTimeManager().GetStandardUserSystemClockCore(), + rb.PushIpcInterface(system.GetTimeManager().GetStandardUserSystemClockCore(), system); } @@ -190,7 +189,7 @@ void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext& LOG_DEBUG(Service_Time, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(module->GetTimeManager().GetStandardNetworkSystemClockCore(), + rb.PushIpcInterface(system.GetTimeManager().GetStandardNetworkSystemClockCore(), system); } @@ -198,29 +197,29 @@ void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(module->GetTimeManager().GetStandardSteadyClockCore(), - system); + rb.PushIpcInterface(system.GetTimeManager().GetStandardSteadyClockCore(), system); } void Module::Interface::GetTimeZoneService(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(module->GetTimeManager().GetTimeZoneContentManager()); + rb.PushIpcInterface(system, + system.GetTimeManager().GetTimeZoneContentManager()); } void Module::Interface::GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface(module->GetTimeManager().GetStandardLocalSystemClockCore(), + rb.PushIpcInterface(system.GetTimeManager().GetStandardLocalSystemClockCore(), system); } void Module::Interface::IsStandardNetworkSystemClockAccuracySufficient( Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - auto& clock_core{module->GetTimeManager().GetStandardNetworkSystemClockCore()}; + auto& clock_core{system.GetTimeManager().GetStandardNetworkSystemClockCore()}; IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(clock_core.IsStandardNetworkSystemClockAccuracySufficient(system)); @@ -229,7 +228,7 @@ void Module::Interface::IsStandardNetworkSystemClockAccuracySufficient( void Module::Interface::CalculateMonotonicSystemClockBaseTimePoint(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - auto& steady_clock_core{module->GetTimeManager().GetStandardSteadyClockCore()}; + auto& steady_clock_core{system.GetTimeManager().GetStandardSteadyClockCore()}; if (!steady_clock_core.IsInitialized()) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERROR_UNINITIALIZED_CLOCK); @@ -262,8 +261,8 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) { Clock::SystemClockContext user_context{}; if (const ResultCode result{ - module->GetTimeManager().GetStandardUserSystemClockCore().GetClockContext( - system, user_context)}; + system.GetTimeManager().GetStandardUserSystemClockCore().GetClockContext(system, + user_context)}; result.IsError()) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(result); @@ -271,7 +270,7 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) { } Clock::SystemClockContext network_context{}; if (const ResultCode result{ - module->GetTimeManager().GetStandardNetworkSystemClockCore().GetClockContext( + system.GetTimeManager().GetStandardNetworkSystemClockCore().GetClockContext( system, network_context)}; result.IsError()) { IPC::ResponseBuilder rb{ctx, 2}; @@ -372,16 +371,17 @@ void Module::Interface::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& c LOG_DEBUG(Service_Time, "called"); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(module->GetTimeManager().GetSharedMemory().GetSharedMemoryHolder()); + rb.PushCopyObjects(SharedFrom(&system.Kernel().GetTimeSharedMem())); } -Module::Interface::Interface(std::shared_ptr module, Core::System& system, const char* name) - : ServiceFramework(name), module{std::move(module)}, system{system} {} +Module::Interface::Interface(std::shared_ptr module_, Core::System& system_, + const char* name) + : ServiceFramework{system_, name}, module{std::move(module_)} {} Module::Interface::~Interface() = default; void InstallInterfaces(Core::System& system) { - auto module{std::make_shared(system)}; + auto module{std::make_shared()}; std::make_shared