Compare commits
315 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bb429999e | |||
| 40e7d78179 | |||
| 159aab9a97 | |||
| c0cedbae94 | |||
| cd138540e2 | |||
| ad712926d6 | |||
| 2efe42fc93 | |||
| 6fa86989f1 | |||
| 32b2a72e7b | |||
| 0f795603fc | |||
| 5710e90150 | |||
| e54d08fc1f | |||
| e9e1e7aa3a | |||
| c4a49eb1dd | |||
| 7d0a77a825 | |||
| 49707916db | |||
| 5669692b4e | |||
| 58ba508e9a | |||
| 2158ccda3b | |||
| 93cc6e4d99 | |||
| 0a7cdc1981 | |||
| b6e5a6bda8 | |||
| 0d1a9a12c9 | |||
| 4ec50dfd4f | |||
| abda68f3a4 | |||
| c55147b24a | |||
| 9b0563fa87 | |||
| b82a098968 | |||
| 20c7084892 | |||
| cc821bfae1 | |||
| 4cdf69c378 | |||
| dc7ab4c5d6 | |||
| cdfb3795af | |||
| 44b981fd3e | |||
| a68af583ea | |||
| 693cad8e9b | |||
| 5086380a63 | |||
| 76a4356e55 | |||
| 34b1ea9c19 | |||
| f99f618d45 | |||
| 31e54c4573 | |||
| 78df1ddce8 | |||
| 9184b6ed2b | |||
| ff2bbc8205 | |||
| 02ac593257 | |||
| 9705094a57 | |||
| a436467152 | |||
| c6eab71d5c | |||
| 31229dd245 | |||
| 380dcde154 | |||
| f78068d7bf | |||
| d9ee7c3297 | |||
| fafa92cfb8 | |||
| 340f15d1fa | |||
| 7d77798f0e | |||
| 4a307a7b3a | |||
| b40aefb39e | |||
| d05ea2f3eb | |||
| 459fb2b213 | |||
| 5cb437703f | |||
| 527dad7097 | |||
| e1a3bda4d9 | |||
| 1c08d532e0 | |||
| 6d6b7bdbc3 | |||
| 6e33731f29 | |||
| 751d36e739 | |||
| f09a023292 | |||
| 5676c2e17f | |||
| 594b2ade6d | |||
| 6aa6301acd | |||
| 36d5e0a411 | |||
| ed5fa10e97 | |||
| a4074001fe | |||
| 18c9f8eeed | |||
| 2d802893e7 | |||
| d80e6c399b | |||
| 475370c8f8 | |||
| 9ca3a4758a | |||
| 9f08b3c3c9 | |||
| 67c3c65f7b | |||
| 413df0811d | |||
| db6cb9cc0a | |||
| 887ed5c0e2 | |||
| 82e2ac6026 | |||
| 1551f97950 | |||
| 5a106cf11e | |||
| 83754e4789 | |||
| 28fde29924 | |||
| 58a10ed5d0 | |||
| 9fc7ca1731 | |||
| f1a0ce0e70 | |||
| 2817af0961 | |||
| 0953cdd271 | |||
| 42b16bb33a | |||
| 50c86b3c2a | |||
| a0e8e5b22e | |||
| 122a8faa38 | |||
| ce0b8d618d | |||
| 80a55c1663 | |||
| d7dbd8e774 | |||
| 4294429a07 | |||
| bb2aec00a2 | |||
| 3ba53f2511 | |||
| e9c3d16f6f | |||
| 2f2ef5b147 | |||
| 0e8f98a441 | |||
| 87b02f78e1 | |||
| c277dad25f | |||
| cbcf210c19 | |||
| 0eae0b6803 | |||
| 74404261d2 | |||
| e2c68edd35 | |||
| 9f974ea818 | |||
| 385ddef8c3 | |||
| fa8581e900 | |||
| eaf425bd32 | |||
| 1caa84e652 | |||
| fda0e7e989 | |||
| 6bf1436f5c | |||
| ffb12f4f9f | |||
| a4cd747122 | |||
| 3be8312120 | |||
| 279005448a | |||
| 948ac976d1 | |||
| 0df43e4e07 | |||
| efbb6fe288 | |||
| a5693afa03 | |||
| 66e4a48b75 | |||
| 115bf20448 | |||
| 926438b68d | |||
| c19c8ac92c | |||
| d34275a260 | |||
| 432d48d9c8 | |||
| cc92b7fd94 | |||
| 444b25bae1 | |||
| 2e4dde12c7 | |||
| 9ed4c13758 | |||
| c0f17e1b27 | |||
| 4bda2b475f | |||
| 7ef897a277 | |||
| 457826a83b | |||
| 8b251fc3f6 | |||
| 3c05988df2 | |||
| 6d74490139 | |||
| 020dbcdbc7 | |||
| 5bcbb8de45 | |||
| 990fe2b3fc | |||
| f6245dc40a | |||
| 8042ce7e19 | |||
| eaca61e073 | |||
| 3e33a878dc | |||
| 1ee0540f82 | |||
| 58fec43768 | |||
| 12b4c9c04c | |||
| 68ed60cee4 | |||
| 6c812a0c84 | |||
| 625a4af73a | |||
| 9e2997c4b6 | |||
| 8804a4eb23 | |||
| 3f0985c7b0 | |||
| c1cc99584c | |||
| bbfad79c89 | |||
| 1428451722 | |||
| e07976a22b | |||
| 9c9008ac81 | |||
| 8945fafcc0 | |||
| f2aa816679 | |||
| f4626512ff | |||
| 67d4f190f7 | |||
| 16809c1fa7 | |||
| b56ad93bbc | |||
| 2d0c4f2b1d | |||
| af5ecb0b15 | |||
| 688a9fbfa6 | |||
| 6c7eb81f7d | |||
| e82e3e06be | |||
| 4d9af4a9d2 | |||
| 013b689153 | |||
| 1e8cee2ddf | |||
| 7a2bd13f5b | |||
| d0fe27708e | |||
| a4269c285a | |||
| 301e9bbc03 | |||
| 66ae79de13 | |||
| b78328f19a | |||
| 3ecc03ec1b | |||
| a0c697124c | |||
| 03ccd8bf43 | |||
| bbeb6e460c | |||
| ddbf851ef6 | |||
| a045e860dd | |||
| b62ffb612d | |||
| 6f031f08fe | |||
| 04cb05fce0 | |||
| a7e610403d | |||
| eceee8c3d9 | |||
| 2b110d61e7 | |||
| 6804a43f49 | |||
| 48bcb91a2e | |||
| 09c9be3703 | |||
| 80bcc18788 | |||
| ebd811b535 | |||
| 306c791e67 | |||
| 1dfcf19c36 | |||
| f9c6d39a6c | |||
| 4814d87385 | |||
| d09aa0182f | |||
| 581a7d785b | |||
| 3630bfaef3 | |||
| 2793304117 | |||
| 8d694701bc | |||
| 4c82e47edd | |||
| d33251db93 | |||
| f800e485c9 | |||
| c897c55e3c | |||
| cb1497d0d7 | |||
| ce448ce770 | |||
| 18637766ef | |||
| aad0cbf024 | |||
| 93ac5a6a6d | |||
| c541559767 | |||
| 0f89828073 | |||
| a5a94f52ff | |||
| d46c9c4659 | |||
| a12a4f2a13 | |||
| bee395d2f0 | |||
| aa13ee5c4a | |||
| 92103d4ff3 | |||
| b2d8bbe0e0 | |||
| 0b228fde15 | |||
| fd1831b65b | |||
| 4d7be85e73 | |||
| 72722986d0 | |||
| 88f5d38b93 | |||
| 5d36d4b143 | |||
| 28cfc2146f | |||
| 0ac34bd058 | |||
| 05c709bebe | |||
| c3af6d8372 | |||
| 73167b12b5 | |||
| 1df5d12b19 | |||
| 8594f287b8 | |||
| 10eaf31af3 | |||
| 9fdacb5e3a | |||
| 9ce23bde24 | |||
| c18cf05a06 | |||
| 60419dd35e | |||
| be43b62d79 | |||
| 3c2aa183a1 | |||
| 106cf27135 | |||
| 2ccf175324 | |||
| c5de54d509 | |||
| 343c01b87a | |||
| 2d7f9fb21b | |||
| dce2649daf | |||
| 6a397bc8ed | |||
| 86d5b4e556 | |||
| ac00ead7d4 | |||
| bc4126acd7 | |||
| 7584d36922 | |||
| 1209d428f1 | |||
| c448b3af2f | |||
| 28d9c30861 | |||
| 3392fdac9b | |||
| 9933121256 | |||
| c6767704fb | |||
| ea70d9c79e | |||
| 3e6850f00b | |||
| cb7f0c2ec3 | |||
| c86e21abe4 | |||
| 201733d1b5 | |||
| db15142ac9 | |||
| fa231645f2 | |||
| 646656412f | |||
| c3a5522830 | |||
| 99eccf581e | |||
| 80670a5b6c | |||
| 60ce34aa80 | |||
| ae6015a69b | |||
| 053ad04d3f | |||
| 1b11e0f0d3 | |||
| fe126f993d | |||
| c6590ad07b | |||
| 9f199c8b0b | |||
| 6cb6b2da8e | |||
| 64869807e2 | |||
| 61e4f2d931 | |||
| bdef22ff85 | |||
| 4bc2d82130 | |||
| cfc34dd41d | |||
| 88ba5a7f22 | |||
| e44d1fe73c | |||
| b60a93a936 | |||
| 42d81aab32 | |||
| 864c8e4b2f | |||
| 690a4c9438 | |||
| 190ded7f48 | |||
| c770f25ccb | |||
| 67c0d714c5 | |||
| cf01a507fb | |||
| fcc93a445f | |||
| 79f1f326c7 | |||
| 2724ffd6e3 | |||
| ee71404d71 | |||
| dcc8abf254 | |||
| 56b0f979eb | |||
| c218c7d4da | |||
| f999d268f9 | |||
| c489cbee29 | |||
| dffeca66fa | |||
| fd1ea0fd84 | |||
| 48108a8c9b | |||
| 92ce241d4d | |||
| d3123079e8 | |||
| 243404bf34 |
@@ -11,6 +11,7 @@ ccache -s
|
||||
mkdir build || true && cd build
|
||||
cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_CXX_FLAGS="-march=x86-64-v2" \
|
||||
-DCMAKE_CXX_COMPILER=/usr/lib/ccache/clang++ \
|
||||
-DCMAKE_C_COMPILER=/usr/lib/ccache/clang \
|
||||
-DCMAKE_INSTALL_PREFIX="/usr" \
|
||||
|
||||
Regular → Executable
+1
-1
@@ -10,7 +10,7 @@ if grep -nrI '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .ci* dis
|
||||
fi
|
||||
|
||||
# Default clang-format points to default 3.5 version one
|
||||
CLANG_FORMAT=clang-format-12
|
||||
CLANG_FORMAT=${CLANG_FORMAT:-clang-format-12}
|
||||
$CLANG_FORMAT --version
|
||||
|
||||
if [ "$TRAVIS_EVENT_TYPE" = "pull_request" ]; then
|
||||
|
||||
@@ -12,6 +12,7 @@ mkdir build || true && cd build
|
||||
cmake .. \
|
||||
-DBoost_USE_STATIC_LIBS=ON \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_CXX_FLAGS="-march=x86-64-v2" \
|
||||
-DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ \
|
||||
-DCMAKE_C_COMPILER=/usr/lib/ccache/gcc \
|
||||
-DCMAKE_INSTALL_PREFIX="/usr" \
|
||||
|
||||
@@ -9,7 +9,7 @@ parameters:
|
||||
steps:
|
||||
- script: choco install vulkan-sdk
|
||||
displayName: 'Install vulkan-sdk'
|
||||
- script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 17 2022" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_CRASH_DUMPS=ON .. && cd ..
|
||||
- script: refreshenv && mkdir build && cd build && cmake -E env CXXFLAGS="/Gw /GA /Gr /Ob2" cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -DCMAKE_POLICY_DEFAULT_CMP0069=NEW -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_CRASH_DUMPS=ON .. && cd ..
|
||||
displayName: 'Configure CMake'
|
||||
- task: MSBuild@1
|
||||
displayName: 'Build'
|
||||
|
||||
+30
-20
@@ -3,17 +3,14 @@
|
||||
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
# Dynarmic has cmake_minimum_required(3.12) and we may want to override
|
||||
# some of its variables, which is only possible in 3.13+
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
||||
project(yuzu)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/find-modules")
|
||||
|
||||
include(DownloadExternals)
|
||||
include(CMakeDependentOption)
|
||||
|
||||
project(yuzu)
|
||||
include(CTest)
|
||||
|
||||
# Set bundled sdl2/qt as dependent options.
|
||||
# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON
|
||||
@@ -22,6 +19,8 @@ CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" ON
|
||||
# On Linux system SDL2 is likely to be lacking HIDAPI support which have drawbacks but is needed for SDL motion
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_USE_EXTERNAL_SDL2 "Compile external SDL2" ON "ENABLE_SDL2;NOT MSVC" OFF)
|
||||
|
||||
option(ENABLE_LIBUSB "Enable the use of LibUSB" ON)
|
||||
|
||||
option(ENABLE_OPENGL "Enable OpenGL" ON)
|
||||
mark_as_advanced(FORCE ENABLE_OPENGL)
|
||||
option(ENABLE_QT "Enable the Qt frontend" ON)
|
||||
@@ -35,6 +34,8 @@ option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||
|
||||
option(YUZU_USE_BUNDLED_FFMPEG "Download/Build bundled FFmpeg" "${WIN32}")
|
||||
|
||||
option(YUZU_USE_EXTERNAL_VULKAN_HEADERS "Use Vulkan-Headers from externals" ON)
|
||||
|
||||
option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF)
|
||||
|
||||
option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
|
||||
@@ -43,10 +44,12 @@ option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||
|
||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||
|
||||
option(YUZU_TESTS "Compile tests" ON)
|
||||
option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}")
|
||||
|
||||
option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
|
||||
|
||||
option(YUZU_ROOM "Compile LDN room server" ON)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF)
|
||||
|
||||
option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}")
|
||||
@@ -201,40 +204,48 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
|
||||
# System imported libraries
|
||||
# =======================================================================
|
||||
|
||||
find_package(enet 1.3)
|
||||
# Enforce the search mode of non-required packages for better and shorter failure messages
|
||||
find_package(enet 1.3 MODULE)
|
||||
find_package(fmt 9 REQUIRED)
|
||||
find_package(inih)
|
||||
find_package(libusb 1.0.24)
|
||||
find_package(inih MODULE)
|
||||
find_package(LLVM MODULE)
|
||||
find_package(lz4 REQUIRED)
|
||||
find_package(nlohmann_json 3.8 REQUIRED)
|
||||
find_package(Opus 1.3)
|
||||
find_package(Vulkan 1.3.213)
|
||||
find_package(Opus 1.3 MODULE)
|
||||
find_package(ZLIB 1.2 REQUIRED)
|
||||
find_package(zstd 1.5 REQUIRED)
|
||||
|
||||
if (NOT YUZU_USE_EXTERNAL_VULKAN_HEADERS)
|
||||
find_package(Vulkan 1.3.238 REQUIRED)
|
||||
endif()
|
||||
|
||||
if (ENABLE_LIBUSB)
|
||||
find_package(libusb 1.0.24 MODULE)
|
||||
endif()
|
||||
|
||||
if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64)
|
||||
find_package(xbyak 6)
|
||||
find_package(xbyak 6 CONFIG)
|
||||
endif()
|
||||
|
||||
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
|
||||
find_package(dynarmic 6.4.0)
|
||||
find_package(dynarmic 6.4.0 CONFIG)
|
||||
endif()
|
||||
|
||||
if (ENABLE_CUBEB)
|
||||
find_package(cubeb)
|
||||
find_package(cubeb CONFIG)
|
||||
endif()
|
||||
|
||||
if (USE_DISCORD_PRESENCE)
|
||||
find_package(DiscordRPC)
|
||||
find_package(DiscordRPC MODULE)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
find_package(cpp-jwt 1.4)
|
||||
find_package(httplib 0.11)
|
||||
find_package(cpp-jwt 1.4 CONFIG)
|
||||
find_package(httplib 0.11 MODULE)
|
||||
endif()
|
||||
|
||||
if (YUZU_TESTS)
|
||||
find_package(Catch2 2.13.7 REQUIRED)
|
||||
find_package(Catch2 3.0.1 REQUIRED)
|
||||
endif()
|
||||
|
||||
find_package(Boost 1.73.0 COMPONENTS context)
|
||||
@@ -598,7 +609,6 @@ if (YUZU_USE_FASTER_LD AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(externals)
|
||||
add_subdirectory(src)
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# SPDX-FileCopyrightText: 2023 Alexandre Bouvier <contact@amb.tf>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
find_package(LLVM QUIET CONFIG)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(LLVM CONFIG_MODE)
|
||||
|
||||
if (LLVM_FOUND AND NOT TARGET LLVM::Demangle)
|
||||
add_library(LLVM::Demangle INTERFACE IMPORTED)
|
||||
llvm_map_components_to_libnames(LLVM_LIBRARIES demangle)
|
||||
target_compile_definitions(LLVM::Demangle INTERFACE ${LLVM_DEFINITIONS})
|
||||
target_include_directories(LLVM::Demangle INTERFACE ${LLVM_INCLUDE_DIRS})
|
||||
target_link_libraries(LLVM::Demangle INTERFACE ${LLVM_LIBRARIES})
|
||||
endif()
|
||||
@@ -5,7 +5,7 @@
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
find_package(httplib QUIET CONFIG)
|
||||
if (httplib_FOUND)
|
||||
if (httplib_CONSIDERED_CONFIGS)
|
||||
find_package_handle_standard_args(httplib CONFIG_MODE)
|
||||
else()
|
||||
find_package(PkgConfig QUIET)
|
||||
@@ -4,7 +4,7 @@
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
find_package(lz4 QUIET CONFIG)
|
||||
if (lz4_FOUND)
|
||||
if (lz4_CONSIDERED_CONFIGS)
|
||||
find_package_handle_standard_args(lz4 CONFIG_MODE)
|
||||
else()
|
||||
find_package(PkgConfig QUIET)
|
||||
@@ -4,7 +4,7 @@
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
find_package(zstd QUIET CONFIG)
|
||||
if (zstd_FOUND)
|
||||
if (zstd_CONSIDERED_CONFIGS)
|
||||
find_package_handle_standard_args(zstd CONFIG_MODE)
|
||||
else()
|
||||
find_package(PkgConfig QUIET)
|
||||
@@ -0,0 +1,15 @@
|
||||
---- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
Vendored
+734
-600
File diff suppressed because it is too large
Load Diff
Vendored
+734
-600
File diff suppressed because it is too large
Load Diff
Vendored
+733
-599
File diff suppressed because it is too large
Load Diff
Vendored
+757
-623
File diff suppressed because it is too large
Load Diff
Vendored
+733
-599
File diff suppressed because it is too large
Load Diff
Vendored
+736
-602
File diff suppressed because it is too large
Load Diff
Vendored
+740
-603
File diff suppressed because it is too large
Load Diff
Vendored
+733
-599
File diff suppressed because it is too large
Load Diff
Vendored
+782
-649
File diff suppressed because it is too large
Load Diff
Vendored
+734
-600
File diff suppressed because it is too large
Load Diff
Vendored
+734
-600
File diff suppressed because it is too large
Load Diff
Vendored
+734
-600
File diff suppressed because it is too large
Load Diff
Vendored
+734
-600
File diff suppressed because it is too large
Load Diff
Vendored
+734
-601
File diff suppressed because it is too large
Load Diff
Vendored
+759
-625
File diff suppressed because it is too large
Load Diff
Vendored
+741
-607
File diff suppressed because it is too large
Load Diff
Vendored
+819
-682
File diff suppressed because it is too large
Load Diff
Vendored
+734
-600
File diff suppressed because it is too large
Load Diff
Vendored
+742
-604
File diff suppressed because it is too large
Load Diff
Vendored
+806
-669
File diff suppressed because it is too large
Load Diff
Vendored
+733
-599
File diff suppressed because it is too large
Load Diff
Vendored
+733
-599
File diff suppressed because it is too large
Load Diff
Vendored
+737
-600
File diff suppressed because it is too large
Load Diff
Vendored
+739
-602
File diff suppressed because it is too large
Load Diff
Vendored
+5
-5
@@ -53,10 +53,10 @@ SPDX-License-Identifier: CC0-1.0
|
||||
<developer_name>yuzu Emulator Team</developer_name>
|
||||
<content_rating type="oars-1.0"/>
|
||||
<screenshots>
|
||||
<screenshot type="default">https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/001-Super%20Mario%20Odyssey%20.png</screenshot>
|
||||
<screenshot>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/004-The%20Legend%20of%20Zelda%20Skyward%20Sword%20HD.png</screenshot>
|
||||
<screenshot>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/007-Pokemon%20Sword.png</screenshot>
|
||||
<screenshot>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/010-Hyrule%20Warriors%20Age%20of%20Calamity.png</screenshot>
|
||||
<screenshot>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/039-Pok%C3%A9mon%20Mystery%20Dungeon%20Rescue%20Team%20DX.png.png.png</screenshot>
|
||||
<screenshot type="default"><image>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/001-Super%20Mario%20Odyssey%20.png</image></screenshot>
|
||||
<screenshot><image>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/004-The%20Legend%20of%20Zelda%20Skyward%20Sword%20HD.png</image></screenshot>
|
||||
<screenshot><image>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/007-Pokemon%20Sword.png</image></screenshot>
|
||||
<screenshot><image>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/010-Hyrule%20Warriors%20Age%20of%20Calamity.png</image></screenshot>
|
||||
<screenshot><image>https://raw.githubusercontent.com/yuzu-emu/yuzu-emu.github.io/master/images/screenshots/039-Pok%C3%A9mon%20Mystery%20Dungeon%20Rescue%20Team%20DX.png.png.png</image></screenshot>
|
||||
</screenshots>
|
||||
</component>
|
||||
|
||||
Vendored
+21
-12
@@ -1,9 +1,12 @@
|
||||
# SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/CMakeModules")
|
||||
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/externals/find-modules")
|
||||
include(DownloadExternals)
|
||||
# Dynarmic has cmake_minimum_required(3.12) and we may want to override
|
||||
# some of its variables, which is only possible in 3.13+
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
||||
|
||||
# Disable tests in all externals supporting the standard option name
|
||||
set(BUILD_TESTING OFF)
|
||||
|
||||
# xbyak
|
||||
if ((ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) AND NOT TARGET xbyak::xbyak)
|
||||
@@ -12,8 +15,7 @@ endif()
|
||||
|
||||
# Dynarmic
|
||||
if ((ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) AND NOT TARGET dynarmic::dynarmic)
|
||||
set(DYNARMIC_NO_BUNDLED_FMT ON)
|
||||
set(DYNARMIC_IGNORE_ASSERTS ON CACHE BOOL "" FORCE)
|
||||
set(DYNARMIC_IGNORE_ASSERTS ON)
|
||||
add_subdirectory(dynarmic EXCLUDE_FROM_ALL)
|
||||
add_library(dynarmic::dynarmic ALIAS dynarmic)
|
||||
endif()
|
||||
@@ -45,7 +47,7 @@ if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12" AND CMAKE_CXX_COMPILER
|
||||
endif()
|
||||
|
||||
# libusb
|
||||
if (NOT TARGET libusb::usb)
|
||||
if (ENABLE_LIBUSB AND NOT TARGET libusb::usb)
|
||||
add_subdirectory(libusb EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
@@ -60,10 +62,10 @@ if (YUZU_USE_EXTERNAL_SDL2)
|
||||
Locale Power Render)
|
||||
foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS})
|
||||
string(TOUPPER ${_SUB} _OPT)
|
||||
option(SDL_${_OPT} "" OFF)
|
||||
set(SDL_${_OPT} OFF)
|
||||
endforeach()
|
||||
|
||||
option(HIDAPI "" ON)
|
||||
set(HIDAPI ON)
|
||||
endif()
|
||||
set(SDL_STATIC ON)
|
||||
set(SDL_SHARED OFF)
|
||||
@@ -83,7 +85,7 @@ endif()
|
||||
|
||||
# Cubeb
|
||||
if (ENABLE_CUBEB AND NOT TARGET cubeb::cubeb)
|
||||
set(BUILD_TESTS OFF CACHE BOOL "")
|
||||
set(BUILD_TESTS OFF)
|
||||
add_subdirectory(cubeb EXCLUDE_FROM_ALL)
|
||||
add_library(cubeb::cubeb ALIAS cubeb)
|
||||
endif()
|
||||
@@ -98,6 +100,7 @@ endif()
|
||||
# Sirit
|
||||
add_subdirectory(sirit EXCLUDE_FROM_ALL)
|
||||
|
||||
# httplib
|
||||
if (ENABLE_WEB_SERVICE AND NOT TARGET httplib::httplib)
|
||||
if (NOT WIN32)
|
||||
find_package(OpenSSL 1.1)
|
||||
@@ -108,7 +111,7 @@ if (ENABLE_WEB_SERVICE AND NOT TARGET httplib::httplib)
|
||||
|
||||
if (WIN32 OR NOT OPENSSL_FOUND)
|
||||
# LibreSSL
|
||||
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
|
||||
set(LIBRESSL_SKIP_INSTALL ON)
|
||||
set(OPENSSLDIR "/etc/ssl/")
|
||||
add_subdirectory(libressl EXCLUDE_FROM_ALL)
|
||||
target_include_directories(ssl INTERFACE ./libressl/include)
|
||||
@@ -118,7 +121,6 @@ if (ENABLE_WEB_SERVICE AND NOT TARGET httplib::httplib)
|
||||
DEFINITION OPENSSL_LIBS)
|
||||
endif()
|
||||
|
||||
# httplib
|
||||
add_library(httplib INTERFACE)
|
||||
target_include_directories(httplib INTERFACE ./cpp-httplib)
|
||||
target_compile_definitions(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
@@ -152,6 +154,13 @@ if (YUZU_USE_BUNDLED_FFMPEG)
|
||||
endif()
|
||||
|
||||
# Vulkan-Headers
|
||||
if (NOT TARGET Vulkan::Headers)
|
||||
if (YUZU_USE_EXTERNAL_VULKAN_HEADERS)
|
||||
add_subdirectory(Vulkan-Headers EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
if (NOT TARGET LLVM::Demangle)
|
||||
add_library(demangle STATIC)
|
||||
target_include_directories(demangle PUBLIC ./demangle)
|
||||
target_sources(demangle PRIVATE demangle/ItaniumDemangle.cpp)
|
||||
add_library(LLVM::Demangle ALIAS demangle)
|
||||
endif()
|
||||
|
||||
Vendored
+1
-1
Submodule externals/Vulkan-Headers updated: 2826791bed...00671c64ba
+588
@@ -0,0 +1,588 @@
|
||||
//===------------------------- ItaniumDemangle.cpp ------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-FileCopyrightText: Part of the LLVM Project
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// FIXME: (possibly) incomplete list of features that clang mangles that this
|
||||
// file does not yet support:
|
||||
// - C++ modules TS
|
||||
|
||||
#include "llvm/Demangle/Demangle.h"
|
||||
#include "llvm/Demangle/ItaniumDemangle.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace llvm;
|
||||
using namespace llvm::itanium_demangle;
|
||||
|
||||
constexpr const char *itanium_demangle::FloatData<float>::spec;
|
||||
constexpr const char *itanium_demangle::FloatData<double>::spec;
|
||||
constexpr const char *itanium_demangle::FloatData<long double>::spec;
|
||||
|
||||
// <discriminator> := _ <non-negative number> # when number < 10
|
||||
// := __ <non-negative number> _ # when number >= 10
|
||||
// extension := decimal-digit+ # at the end of string
|
||||
const char *itanium_demangle::parse_discriminator(const char *first,
|
||||
const char *last) {
|
||||
// parse but ignore discriminator
|
||||
if (first != last) {
|
||||
if (*first == '_') {
|
||||
const char *t1 = first + 1;
|
||||
if (t1 != last) {
|
||||
if (std::isdigit(*t1))
|
||||
first = t1 + 1;
|
||||
else if (*t1 == '_') {
|
||||
for (++t1; t1 != last && std::isdigit(*t1); ++t1)
|
||||
;
|
||||
if (t1 != last && *t1 == '_')
|
||||
first = t1 + 1;
|
||||
}
|
||||
}
|
||||
} else if (std::isdigit(*first)) {
|
||||
const char *t1 = first + 1;
|
||||
for (; t1 != last && std::isdigit(*t1); ++t1)
|
||||
;
|
||||
if (t1 == last)
|
||||
first = last;
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
namespace {
|
||||
struct DumpVisitor {
|
||||
unsigned Depth = 0;
|
||||
bool PendingNewline = false;
|
||||
|
||||
template<typename NodeT> static constexpr bool wantsNewline(const NodeT *) {
|
||||
return true;
|
||||
}
|
||||
static bool wantsNewline(NodeArray A) { return !A.empty(); }
|
||||
static constexpr bool wantsNewline(...) { return false; }
|
||||
|
||||
template<typename ...Ts> static bool anyWantNewline(Ts ...Vs) {
|
||||
for (bool B : {wantsNewline(Vs)...})
|
||||
if (B)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void printStr(const char *S) { fprintf(stderr, "%s", S); }
|
||||
void print(StringView SV) {
|
||||
fprintf(stderr, "\"%.*s\"", (int)SV.size(), SV.begin());
|
||||
}
|
||||
void print(const Node *N) {
|
||||
if (N)
|
||||
N->visit(std::ref(*this));
|
||||
else
|
||||
printStr("<null>");
|
||||
}
|
||||
void print(NodeOrString NS) {
|
||||
if (NS.isNode())
|
||||
print(NS.asNode());
|
||||
else if (NS.isString())
|
||||
print(NS.asString());
|
||||
else
|
||||
printStr("NodeOrString()");
|
||||
}
|
||||
void print(NodeArray A) {
|
||||
++Depth;
|
||||
printStr("{");
|
||||
bool First = true;
|
||||
for (const Node *N : A) {
|
||||
if (First)
|
||||
print(N);
|
||||
else
|
||||
printWithComma(N);
|
||||
First = false;
|
||||
}
|
||||
printStr("}");
|
||||
--Depth;
|
||||
}
|
||||
|
||||
// Overload used when T is exactly 'bool', not merely convertible to 'bool'.
|
||||
void print(bool B) { printStr(B ? "true" : "false"); }
|
||||
|
||||
template <class T>
|
||||
typename std::enable_if<std::is_unsigned<T>::value>::type print(T N) {
|
||||
fprintf(stderr, "%llu", (unsigned long long)N);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
typename std::enable_if<std::is_signed<T>::value>::type print(T N) {
|
||||
fprintf(stderr, "%lld", (long long)N);
|
||||
}
|
||||
|
||||
void print(ReferenceKind RK) {
|
||||
switch (RK) {
|
||||
case ReferenceKind::LValue:
|
||||
return printStr("ReferenceKind::LValue");
|
||||
case ReferenceKind::RValue:
|
||||
return printStr("ReferenceKind::RValue");
|
||||
}
|
||||
}
|
||||
void print(FunctionRefQual RQ) {
|
||||
switch (RQ) {
|
||||
case FunctionRefQual::FrefQualNone:
|
||||
return printStr("FunctionRefQual::FrefQualNone");
|
||||
case FunctionRefQual::FrefQualLValue:
|
||||
return printStr("FunctionRefQual::FrefQualLValue");
|
||||
case FunctionRefQual::FrefQualRValue:
|
||||
return printStr("FunctionRefQual::FrefQualRValue");
|
||||
}
|
||||
}
|
||||
void print(Qualifiers Qs) {
|
||||
if (!Qs) return printStr("QualNone");
|
||||
struct QualName { Qualifiers Q; const char *Name; } Names[] = {
|
||||
{QualConst, "QualConst"},
|
||||
{QualVolatile, "QualVolatile"},
|
||||
{QualRestrict, "QualRestrict"},
|
||||
};
|
||||
for (QualName Name : Names) {
|
||||
if (Qs & Name.Q) {
|
||||
printStr(Name.Name);
|
||||
Qs = Qualifiers(Qs & ~Name.Q);
|
||||
if (Qs) printStr(" | ");
|
||||
}
|
||||
}
|
||||
}
|
||||
void print(SpecialSubKind SSK) {
|
||||
switch (SSK) {
|
||||
case SpecialSubKind::allocator:
|
||||
return printStr("SpecialSubKind::allocator");
|
||||
case SpecialSubKind::basic_string:
|
||||
return printStr("SpecialSubKind::basic_string");
|
||||
case SpecialSubKind::string:
|
||||
return printStr("SpecialSubKind::string");
|
||||
case SpecialSubKind::istream:
|
||||
return printStr("SpecialSubKind::istream");
|
||||
case SpecialSubKind::ostream:
|
||||
return printStr("SpecialSubKind::ostream");
|
||||
case SpecialSubKind::iostream:
|
||||
return printStr("SpecialSubKind::iostream");
|
||||
}
|
||||
}
|
||||
void print(TemplateParamKind TPK) {
|
||||
switch (TPK) {
|
||||
case TemplateParamKind::Type:
|
||||
return printStr("TemplateParamKind::Type");
|
||||
case TemplateParamKind::NonType:
|
||||
return printStr("TemplateParamKind::NonType");
|
||||
case TemplateParamKind::Template:
|
||||
return printStr("TemplateParamKind::Template");
|
||||
}
|
||||
}
|
||||
|
||||
void newLine() {
|
||||
printStr("\n");
|
||||
for (unsigned I = 0; I != Depth; ++I)
|
||||
printStr(" ");
|
||||
PendingNewline = false;
|
||||
}
|
||||
|
||||
template<typename T> void printWithPendingNewline(T V) {
|
||||
print(V);
|
||||
if (wantsNewline(V))
|
||||
PendingNewline = true;
|
||||
}
|
||||
|
||||
template<typename T> void printWithComma(T V) {
|
||||
if (PendingNewline || wantsNewline(V)) {
|
||||
printStr(",");
|
||||
newLine();
|
||||
} else {
|
||||
printStr(", ");
|
||||
}
|
||||
|
||||
printWithPendingNewline(V);
|
||||
}
|
||||
|
||||
struct CtorArgPrinter {
|
||||
DumpVisitor &Visitor;
|
||||
|
||||
template<typename T, typename ...Rest> void operator()(T V, Rest ...Vs) {
|
||||
if (Visitor.anyWantNewline(V, Vs...))
|
||||
Visitor.newLine();
|
||||
Visitor.printWithPendingNewline(V);
|
||||
int PrintInOrder[] = { (Visitor.printWithComma(Vs), 0)..., 0 };
|
||||
(void)PrintInOrder;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename NodeT> void operator()(const NodeT *Node) {
|
||||
Depth += 2;
|
||||
fprintf(stderr, "%s(", itanium_demangle::NodeKind<NodeT>::name());
|
||||
Node->match(CtorArgPrinter{*this});
|
||||
fprintf(stderr, ")");
|
||||
Depth -= 2;
|
||||
}
|
||||
|
||||
void operator()(const ForwardTemplateReference *Node) {
|
||||
Depth += 2;
|
||||
fprintf(stderr, "ForwardTemplateReference(");
|
||||
if (Node->Ref && !Node->Printing) {
|
||||
Node->Printing = true;
|
||||
CtorArgPrinter{*this}(Node->Ref);
|
||||
Node->Printing = false;
|
||||
} else {
|
||||
CtorArgPrinter{*this}(Node->Index);
|
||||
}
|
||||
fprintf(stderr, ")");
|
||||
Depth -= 2;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void itanium_demangle::Node::dump() const {
|
||||
DumpVisitor V;
|
||||
visit(std::ref(V));
|
||||
V.newLine();
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
class BumpPointerAllocator {
|
||||
struct BlockMeta {
|
||||
BlockMeta* Next;
|
||||
size_t Current;
|
||||
};
|
||||
|
||||
static constexpr size_t AllocSize = 4096;
|
||||
static constexpr size_t UsableAllocSize = AllocSize - sizeof(BlockMeta);
|
||||
|
||||
alignas(long double) char InitialBuffer[AllocSize];
|
||||
BlockMeta* BlockList = nullptr;
|
||||
|
||||
void grow() {
|
||||
char* NewMeta = static_cast<char *>(std::malloc(AllocSize));
|
||||
if (NewMeta == nullptr)
|
||||
std::terminate();
|
||||
BlockList = new (NewMeta) BlockMeta{BlockList, 0};
|
||||
}
|
||||
|
||||
void* allocateMassive(size_t NBytes) {
|
||||
NBytes += sizeof(BlockMeta);
|
||||
BlockMeta* NewMeta = reinterpret_cast<BlockMeta*>(std::malloc(NBytes));
|
||||
if (NewMeta == nullptr)
|
||||
std::terminate();
|
||||
BlockList->Next = new (NewMeta) BlockMeta{BlockList->Next, 0};
|
||||
return static_cast<void*>(NewMeta + 1);
|
||||
}
|
||||
|
||||
public:
|
||||
BumpPointerAllocator()
|
||||
: BlockList(new (InitialBuffer) BlockMeta{nullptr, 0}) {}
|
||||
|
||||
void* allocate(size_t N) {
|
||||
N = (N + 15u) & ~15u;
|
||||
if (N + BlockList->Current >= UsableAllocSize) {
|
||||
if (N > UsableAllocSize)
|
||||
return allocateMassive(N);
|
||||
grow();
|
||||
}
|
||||
BlockList->Current += N;
|
||||
return static_cast<void*>(reinterpret_cast<char*>(BlockList + 1) +
|
||||
BlockList->Current - N);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
while (BlockList) {
|
||||
BlockMeta* Tmp = BlockList;
|
||||
BlockList = BlockList->Next;
|
||||
if (reinterpret_cast<char*>(Tmp) != InitialBuffer)
|
||||
std::free(Tmp);
|
||||
}
|
||||
BlockList = new (InitialBuffer) BlockMeta{nullptr, 0};
|
||||
}
|
||||
|
||||
~BumpPointerAllocator() { reset(); }
|
||||
};
|
||||
|
||||
class DefaultAllocator {
|
||||
BumpPointerAllocator Alloc;
|
||||
|
||||
public:
|
||||
void reset() { Alloc.reset(); }
|
||||
|
||||
template<typename T, typename ...Args> T *makeNode(Args &&...args) {
|
||||
return new (Alloc.allocate(sizeof(T)))
|
||||
T(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
void *allocateNodeArray(size_t sz) {
|
||||
return Alloc.allocate(sizeof(Node *) * sz);
|
||||
}
|
||||
};
|
||||
} // unnamed namespace
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Code beyond this point should not be synchronized with libc++abi.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
using Demangler = itanium_demangle::ManglingParser<DefaultAllocator>;
|
||||
|
||||
char *llvm::itaniumDemangle(const char *MangledName, char *Buf,
|
||||
size_t *N, int *Status) {
|
||||
if (MangledName == nullptr || (Buf != nullptr && N == nullptr)) {
|
||||
if (Status)
|
||||
*Status = demangle_invalid_args;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int InternalStatus = demangle_success;
|
||||
Demangler Parser(MangledName, MangledName + std::strlen(MangledName));
|
||||
OutputStream S;
|
||||
|
||||
Node *AST = Parser.parse();
|
||||
|
||||
if (AST == nullptr)
|
||||
InternalStatus = demangle_invalid_mangled_name;
|
||||
else if (!initializeOutputStream(Buf, N, S, 1024))
|
||||
InternalStatus = demangle_memory_alloc_failure;
|
||||
else {
|
||||
assert(Parser.ForwardTemplateRefs.empty());
|
||||
AST->print(S);
|
||||
S += '\0';
|
||||
if (N != nullptr)
|
||||
*N = S.getCurrentPosition();
|
||||
Buf = S.getBuffer();
|
||||
}
|
||||
|
||||
if (Status)
|
||||
*Status = InternalStatus;
|
||||
return InternalStatus == demangle_success ? Buf : nullptr;
|
||||
}
|
||||
|
||||
ItaniumPartialDemangler::ItaniumPartialDemangler()
|
||||
: RootNode(nullptr), Context(new Demangler{nullptr, nullptr}) {}
|
||||
|
||||
ItaniumPartialDemangler::~ItaniumPartialDemangler() {
|
||||
delete static_cast<Demangler *>(Context);
|
||||
}
|
||||
|
||||
ItaniumPartialDemangler::ItaniumPartialDemangler(
|
||||
ItaniumPartialDemangler &&Other)
|
||||
: RootNode(Other.RootNode), Context(Other.Context) {
|
||||
Other.Context = Other.RootNode = nullptr;
|
||||
}
|
||||
|
||||
ItaniumPartialDemangler &ItaniumPartialDemangler::
|
||||
operator=(ItaniumPartialDemangler &&Other) {
|
||||
std::swap(RootNode, Other.RootNode);
|
||||
std::swap(Context, Other.Context);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Demangle MangledName into an AST, storing it into this->RootNode.
|
||||
bool ItaniumPartialDemangler::partialDemangle(const char *MangledName) {
|
||||
Demangler *Parser = static_cast<Demangler *>(Context);
|
||||
size_t Len = std::strlen(MangledName);
|
||||
Parser->reset(MangledName, MangledName + Len);
|
||||
RootNode = Parser->parse();
|
||||
return RootNode == nullptr;
|
||||
}
|
||||
|
||||
static char *printNode(const Node *RootNode, char *Buf, size_t *N) {
|
||||
OutputStream S;
|
||||
if (!initializeOutputStream(Buf, N, S, 128))
|
||||
return nullptr;
|
||||
RootNode->print(S);
|
||||
S += '\0';
|
||||
if (N != nullptr)
|
||||
*N = S.getCurrentPosition();
|
||||
return S.getBuffer();
|
||||
}
|
||||
|
||||
char *ItaniumPartialDemangler::getFunctionBaseName(char *Buf, size_t *N) const {
|
||||
if (!isFunction())
|
||||
return nullptr;
|
||||
|
||||
const Node *Name = static_cast<const FunctionEncoding *>(RootNode)->getName();
|
||||
|
||||
while (true) {
|
||||
switch (Name->getKind()) {
|
||||
case Node::KAbiTagAttr:
|
||||
Name = static_cast<const AbiTagAttr *>(Name)->Base;
|
||||
continue;
|
||||
case Node::KStdQualifiedName:
|
||||
Name = static_cast<const StdQualifiedName *>(Name)->Child;
|
||||
continue;
|
||||
case Node::KNestedName:
|
||||
Name = static_cast<const NestedName *>(Name)->Name;
|
||||
continue;
|
||||
case Node::KLocalName:
|
||||
Name = static_cast<const LocalName *>(Name)->Entity;
|
||||
continue;
|
||||
case Node::KNameWithTemplateArgs:
|
||||
Name = static_cast<const NameWithTemplateArgs *>(Name)->Name;
|
||||
continue;
|
||||
default:
|
||||
return printNode(Name, Buf, N);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char *ItaniumPartialDemangler::getFunctionDeclContextName(char *Buf,
|
||||
size_t *N) const {
|
||||
if (!isFunction())
|
||||
return nullptr;
|
||||
const Node *Name = static_cast<const FunctionEncoding *>(RootNode)->getName();
|
||||
|
||||
OutputStream S;
|
||||
if (!initializeOutputStream(Buf, N, S, 128))
|
||||
return nullptr;
|
||||
|
||||
KeepGoingLocalFunction:
|
||||
while (true) {
|
||||
if (Name->getKind() == Node::KAbiTagAttr) {
|
||||
Name = static_cast<const AbiTagAttr *>(Name)->Base;
|
||||
continue;
|
||||
}
|
||||
if (Name->getKind() == Node::KNameWithTemplateArgs) {
|
||||
Name = static_cast<const NameWithTemplateArgs *>(Name)->Name;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch (Name->getKind()) {
|
||||
case Node::KStdQualifiedName:
|
||||
S += "std";
|
||||
break;
|
||||
case Node::KNestedName:
|
||||
static_cast<const NestedName *>(Name)->Qual->print(S);
|
||||
break;
|
||||
case Node::KLocalName: {
|
||||
auto *LN = static_cast<const LocalName *>(Name);
|
||||
LN->Encoding->print(S);
|
||||
S += "::";
|
||||
Name = LN->Entity;
|
||||
goto KeepGoingLocalFunction;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
S += '\0';
|
||||
if (N != nullptr)
|
||||
*N = S.getCurrentPosition();
|
||||
return S.getBuffer();
|
||||
}
|
||||
|
||||
char *ItaniumPartialDemangler::getFunctionName(char *Buf, size_t *N) const {
|
||||
if (!isFunction())
|
||||
return nullptr;
|
||||
auto *Name = static_cast<FunctionEncoding *>(RootNode)->getName();
|
||||
return printNode(Name, Buf, N);
|
||||
}
|
||||
|
||||
char *ItaniumPartialDemangler::getFunctionParameters(char *Buf,
|
||||
size_t *N) const {
|
||||
if (!isFunction())
|
||||
return nullptr;
|
||||
NodeArray Params = static_cast<FunctionEncoding *>(RootNode)->getParams();
|
||||
|
||||
OutputStream S;
|
||||
if (!initializeOutputStream(Buf, N, S, 128))
|
||||
return nullptr;
|
||||
|
||||
S += '(';
|
||||
Params.printWithComma(S);
|
||||
S += ')';
|
||||
S += '\0';
|
||||
if (N != nullptr)
|
||||
*N = S.getCurrentPosition();
|
||||
return S.getBuffer();
|
||||
}
|
||||
|
||||
char *ItaniumPartialDemangler::getFunctionReturnType(
|
||||
char *Buf, size_t *N) const {
|
||||
if (!isFunction())
|
||||
return nullptr;
|
||||
|
||||
OutputStream S;
|
||||
if (!initializeOutputStream(Buf, N, S, 128))
|
||||
return nullptr;
|
||||
|
||||
if (const Node *Ret =
|
||||
static_cast<const FunctionEncoding *>(RootNode)->getReturnType())
|
||||
Ret->print(S);
|
||||
|
||||
S += '\0';
|
||||
if (N != nullptr)
|
||||
*N = S.getCurrentPosition();
|
||||
return S.getBuffer();
|
||||
}
|
||||
|
||||
char *ItaniumPartialDemangler::finishDemangle(char *Buf, size_t *N) const {
|
||||
assert(RootNode != nullptr && "must call partialDemangle()");
|
||||
return printNode(static_cast<Node *>(RootNode), Buf, N);
|
||||
}
|
||||
|
||||
bool ItaniumPartialDemangler::hasFunctionQualifiers() const {
|
||||
assert(RootNode != nullptr && "must call partialDemangle()");
|
||||
if (!isFunction())
|
||||
return false;
|
||||
auto *E = static_cast<const FunctionEncoding *>(RootNode);
|
||||
return E->getCVQuals() != QualNone || E->getRefQual() != FrefQualNone;
|
||||
}
|
||||
|
||||
bool ItaniumPartialDemangler::isCtorOrDtor() const {
|
||||
const Node *N = static_cast<const Node *>(RootNode);
|
||||
while (N) {
|
||||
switch (N->getKind()) {
|
||||
default:
|
||||
return false;
|
||||
case Node::KCtorDtorName:
|
||||
return true;
|
||||
|
||||
case Node::KAbiTagAttr:
|
||||
N = static_cast<const AbiTagAttr *>(N)->Base;
|
||||
break;
|
||||
case Node::KFunctionEncoding:
|
||||
N = static_cast<const FunctionEncoding *>(N)->getName();
|
||||
break;
|
||||
case Node::KLocalName:
|
||||
N = static_cast<const LocalName *>(N)->Entity;
|
||||
break;
|
||||
case Node::KNameWithTemplateArgs:
|
||||
N = static_cast<const NameWithTemplateArgs *>(N)->Name;
|
||||
break;
|
||||
case Node::KNestedName:
|
||||
N = static_cast<const NestedName *>(N)->Name;
|
||||
break;
|
||||
case Node::KStdQualifiedName:
|
||||
N = static_cast<const StdQualifiedName *>(N)->Child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ItaniumPartialDemangler::isFunction() const {
|
||||
assert(RootNode != nullptr && "must call partialDemangle()");
|
||||
return static_cast<const Node *>(RootNode)->getKind() ==
|
||||
Node::KFunctionEncoding;
|
||||
}
|
||||
|
||||
bool ItaniumPartialDemangler::isSpecialName() const {
|
||||
assert(RootNode != nullptr && "must call partialDemangle()");
|
||||
auto K = static_cast<const Node *>(RootNode)->getKind();
|
||||
return K == Node::KSpecialName || K == Node::KCtorVtableSpecialName;
|
||||
}
|
||||
|
||||
bool ItaniumPartialDemangler::isData() const {
|
||||
return !isFunction() && !isSpecialName();
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
//===--- Demangle.h ---------------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-FileCopyrightText: Part of the LLVM Project
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_DEMANGLE_DEMANGLE_H
|
||||
#define LLVM_DEMANGLE_DEMANGLE_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
namespace llvm {
|
||||
/// This is a llvm local version of __cxa_demangle. Other than the name and
|
||||
/// being in the llvm namespace it is identical.
|
||||
///
|
||||
/// The mangled_name is demangled into buf and returned. If the buffer is not
|
||||
/// large enough, realloc is used to expand it.
|
||||
///
|
||||
/// The *status will be set to a value from the following enumeration
|
||||
enum : int {
|
||||
demangle_unknown_error = -4,
|
||||
demangle_invalid_args = -3,
|
||||
demangle_invalid_mangled_name = -2,
|
||||
demangle_memory_alloc_failure = -1,
|
||||
demangle_success = 0,
|
||||
};
|
||||
|
||||
char *itaniumDemangle(const char *mangled_name, char *buf, size_t *n,
|
||||
int *status);
|
||||
|
||||
|
||||
enum MSDemangleFlags {
|
||||
MSDF_None = 0,
|
||||
MSDF_DumpBackrefs = 1 << 0,
|
||||
MSDF_NoAccessSpecifier = 1 << 1,
|
||||
MSDF_NoCallingConvention = 1 << 2,
|
||||
MSDF_NoReturnType = 1 << 3,
|
||||
MSDF_NoMemberType = 1 << 4,
|
||||
};
|
||||
char *microsoftDemangle(const char *mangled_name, char *buf, size_t *n,
|
||||
int *status, MSDemangleFlags Flags = MSDF_None);
|
||||
|
||||
/// "Partial" demangler. This supports demangling a string into an AST
|
||||
/// (typically an intermediate stage in itaniumDemangle) and querying certain
|
||||
/// properties or partially printing the demangled name.
|
||||
struct ItaniumPartialDemangler {
|
||||
ItaniumPartialDemangler();
|
||||
|
||||
ItaniumPartialDemangler(ItaniumPartialDemangler &&Other);
|
||||
ItaniumPartialDemangler &operator=(ItaniumPartialDemangler &&Other);
|
||||
|
||||
/// Demangle into an AST. Subsequent calls to the rest of the member functions
|
||||
/// implicitly operate on the AST this produces.
|
||||
/// \return true on error, false otherwise
|
||||
bool partialDemangle(const char *MangledName);
|
||||
|
||||
/// Just print the entire mangled name into Buf. Buf and N behave like the
|
||||
/// second and third parameters to itaniumDemangle.
|
||||
char *finishDemangle(char *Buf, size_t *N) const;
|
||||
|
||||
/// Get the base name of a function. This doesn't include trailing template
|
||||
/// arguments, ie for "a::b<int>" this function returns "b".
|
||||
char *getFunctionBaseName(char *Buf, size_t *N) const;
|
||||
|
||||
/// Get the context name for a function. For "a::b::c", this function returns
|
||||
/// "a::b".
|
||||
char *getFunctionDeclContextName(char *Buf, size_t *N) const;
|
||||
|
||||
/// Get the entire name of this function.
|
||||
char *getFunctionName(char *Buf, size_t *N) const;
|
||||
|
||||
/// Get the parameters for this function.
|
||||
char *getFunctionParameters(char *Buf, size_t *N) const;
|
||||
char *getFunctionReturnType(char *Buf, size_t *N) const;
|
||||
|
||||
/// If this function has any any cv or reference qualifiers. These imply that
|
||||
/// the function is a non-static member function.
|
||||
bool hasFunctionQualifiers() const;
|
||||
|
||||
/// If this symbol describes a constructor or destructor.
|
||||
bool isCtorOrDtor() const;
|
||||
|
||||
/// If this symbol describes a function.
|
||||
bool isFunction() const;
|
||||
|
||||
/// If this symbol describes a variable.
|
||||
bool isData() const;
|
||||
|
||||
/// If this symbol is a <special-name>. These are generally implicitly
|
||||
/// generated by the implementation, such as vtables and typeinfo names.
|
||||
bool isSpecialName() const;
|
||||
|
||||
~ItaniumPartialDemangler();
|
||||
private:
|
||||
void *RootNode;
|
||||
void *Context;
|
||||
};
|
||||
} // namespace llvm
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,93 @@
|
||||
//===--- DemangleConfig.h ---------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-FileCopyrightText: Part of the LLVM Project
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file contains a variety of feature test macros copied from
|
||||
// include/llvm/Support/Compiler.h so that LLVMDemangle does not need to take
|
||||
// a dependency on LLVMSupport.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_DEMANGLE_COMPILER_H
|
||||
#define LLVM_DEMANGLE_COMPILER_H
|
||||
|
||||
#ifndef __has_feature
|
||||
#define __has_feature(x) 0
|
||||
#endif
|
||||
|
||||
#ifndef __has_cpp_attribute
|
||||
#define __has_cpp_attribute(x) 0
|
||||
#endif
|
||||
|
||||
#ifndef __has_attribute
|
||||
#define __has_attribute(x) 0
|
||||
#endif
|
||||
|
||||
#ifndef __has_builtin
|
||||
#define __has_builtin(x) 0
|
||||
#endif
|
||||
|
||||
#ifndef DEMANGLE_GNUC_PREREQ
|
||||
#if defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__)
|
||||
#define DEMANGLE_GNUC_PREREQ(maj, min, patch) \
|
||||
((__GNUC__ << 20) + (__GNUC_MINOR__ << 10) + __GNUC_PATCHLEVEL__ >= \
|
||||
((maj) << 20) + ((min) << 10) + (patch))
|
||||
#elif defined(__GNUC__) && defined(__GNUC_MINOR__)
|
||||
#define DEMANGLE_GNUC_PREREQ(maj, min, patch) \
|
||||
((__GNUC__ << 20) + (__GNUC_MINOR__ << 10) >= ((maj) << 20) + ((min) << 10))
|
||||
#else
|
||||
#define DEMANGLE_GNUC_PREREQ(maj, min, patch) 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if __has_attribute(used) || DEMANGLE_GNUC_PREREQ(3, 1, 0)
|
||||
#define DEMANGLE_ATTRIBUTE_USED __attribute__((__used__))
|
||||
#else
|
||||
#define DEMANGLE_ATTRIBUTE_USED
|
||||
#endif
|
||||
|
||||
#if __has_builtin(__builtin_unreachable) || DEMANGLE_GNUC_PREREQ(4, 5, 0)
|
||||
#define DEMANGLE_UNREACHABLE __builtin_unreachable()
|
||||
#elif defined(_MSC_VER)
|
||||
#define DEMANGLE_UNREACHABLE __assume(false)
|
||||
#else
|
||||
#define DEMANGLE_UNREACHABLE
|
||||
#endif
|
||||
|
||||
#if __has_attribute(noinline) || DEMANGLE_GNUC_PREREQ(3, 4, 0)
|
||||
#define DEMANGLE_ATTRIBUTE_NOINLINE __attribute__((noinline))
|
||||
#elif defined(_MSC_VER)
|
||||
#define DEMANGLE_ATTRIBUTE_NOINLINE __declspec(noinline)
|
||||
#else
|
||||
#define DEMANGLE_ATTRIBUTE_NOINLINE
|
||||
#endif
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
#define DEMANGLE_DUMP_METHOD DEMANGLE_ATTRIBUTE_NOINLINE DEMANGLE_ATTRIBUTE_USED
|
||||
#else
|
||||
#define DEMANGLE_DUMP_METHOD DEMANGLE_ATTRIBUTE_NOINLINE
|
||||
#endif
|
||||
|
||||
#if __cplusplus > 201402L && __has_cpp_attribute(fallthrough)
|
||||
#define DEMANGLE_FALLTHROUGH [[fallthrough]]
|
||||
#elif __has_cpp_attribute(gnu::fallthrough)
|
||||
#define DEMANGLE_FALLTHROUGH [[gnu::fallthrough]]
|
||||
#elif !__cplusplus
|
||||
// Workaround for llvm.org/PR23435, since clang 3.6 and below emit a spurious
|
||||
// error when __has_cpp_attribute is given a scoped attribute in C mode.
|
||||
#define DEMANGLE_FALLTHROUGH
|
||||
#elif __has_cpp_attribute(clang::fallthrough)
|
||||
#define DEMANGLE_FALLTHROUGH [[clang::fallthrough]]
|
||||
#else
|
||||
#define DEMANGLE_FALLTHROUGH
|
||||
#endif
|
||||
|
||||
#define DEMANGLE_NAMESPACE_BEGIN namespace llvm { namespace itanium_demangle {
|
||||
#define DEMANGLE_NAMESPACE_END } }
|
||||
|
||||
#endif
|
||||
+5582
File diff suppressed because it is too large
Load Diff
+127
@@ -0,0 +1,127 @@
|
||||
//===--- StringView.h -------------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-FileCopyrightText: Part of the LLVM Project
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// FIXME: Use std::string_view instead when we support C++17.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef DEMANGLE_STRINGVIEW_H
|
||||
#define DEMANGLE_STRINGVIEW_H
|
||||
|
||||
#include "DemangleConfig.h"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
DEMANGLE_NAMESPACE_BEGIN
|
||||
|
||||
class StringView {
|
||||
const char *First;
|
||||
const char *Last;
|
||||
|
||||
public:
|
||||
static const size_t npos = ~size_t(0);
|
||||
|
||||
template <size_t N>
|
||||
StringView(const char (&Str)[N]) : First(Str), Last(Str + N - 1) {}
|
||||
StringView(const char *First_, const char *Last_)
|
||||
: First(First_), Last(Last_) {}
|
||||
StringView(const char *First_, size_t Len)
|
||||
: First(First_), Last(First_ + Len) {}
|
||||
StringView(const char *Str) : First(Str), Last(Str + std::strlen(Str)) {}
|
||||
StringView() : First(nullptr), Last(nullptr) {}
|
||||
|
||||
StringView substr(size_t From) const {
|
||||
return StringView(begin() + From, size() - From);
|
||||
}
|
||||
|
||||
size_t find(char C, size_t From = 0) const {
|
||||
size_t FindBegin = std::min(From, size());
|
||||
// Avoid calling memchr with nullptr.
|
||||
if (FindBegin < size()) {
|
||||
// Just forward to memchr, which is faster than a hand-rolled loop.
|
||||
if (const void *P = ::memchr(First + FindBegin, C, size() - FindBegin))
|
||||
return size_t(static_cast<const char *>(P) - First);
|
||||
}
|
||||
return npos;
|
||||
}
|
||||
|
||||
StringView substr(size_t From, size_t To) const {
|
||||
if (To >= size())
|
||||
To = size() - 1;
|
||||
if (From >= size())
|
||||
From = size() - 1;
|
||||
return StringView(First + From, First + To);
|
||||
}
|
||||
|
||||
StringView dropFront(size_t N = 1) const {
|
||||
if (N >= size())
|
||||
N = size();
|
||||
return StringView(First + N, Last);
|
||||
}
|
||||
|
||||
StringView dropBack(size_t N = 1) const {
|
||||
if (N >= size())
|
||||
N = size();
|
||||
return StringView(First, Last - N);
|
||||
}
|
||||
|
||||
char front() const {
|
||||
assert(!empty());
|
||||
return *begin();
|
||||
}
|
||||
|
||||
char back() const {
|
||||
assert(!empty());
|
||||
return *(end() - 1);
|
||||
}
|
||||
|
||||
char popFront() {
|
||||
assert(!empty());
|
||||
return *First++;
|
||||
}
|
||||
|
||||
bool consumeFront(char C) {
|
||||
if (!startsWith(C))
|
||||
return false;
|
||||
*this = dropFront(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool consumeFront(StringView S) {
|
||||
if (!startsWith(S))
|
||||
return false;
|
||||
*this = dropFront(S.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool startsWith(char C) const { return !empty() && *begin() == C; }
|
||||
|
||||
bool startsWith(StringView Str) const {
|
||||
if (Str.size() > size())
|
||||
return false;
|
||||
return std::equal(Str.begin(), Str.end(), begin());
|
||||
}
|
||||
|
||||
const char &operator[](size_t Idx) const { return *(begin() + Idx); }
|
||||
|
||||
const char *begin() const { return First; }
|
||||
const char *end() const { return Last; }
|
||||
size_t size() const { return static_cast<size_t>(Last - First); }
|
||||
bool empty() const { return First == Last; }
|
||||
};
|
||||
|
||||
inline bool operator==(const StringView &LHS, const StringView &RHS) {
|
||||
return LHS.size() == RHS.size() &&
|
||||
std::equal(LHS.begin(), LHS.end(), RHS.begin());
|
||||
}
|
||||
|
||||
DEMANGLE_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
//===--- Utility.h ----------------------------------------------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-FileCopyrightText: Part of the LLVM Project
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Provide some utility classes for use in the demangler(s).
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef DEMANGLE_UTILITY_H
|
||||
#define DEMANGLE_UTILITY_H
|
||||
|
||||
#include "StringView.h"
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
|
||||
DEMANGLE_NAMESPACE_BEGIN
|
||||
|
||||
// Stream that AST nodes write their string representation into after the AST
|
||||
// has been parsed.
|
||||
class OutputStream {
|
||||
char *Buffer;
|
||||
size_t CurrentPosition;
|
||||
size_t BufferCapacity;
|
||||
|
||||
// Ensure there is at least n more positions in buffer.
|
||||
void grow(size_t N) {
|
||||
if (N + CurrentPosition >= BufferCapacity) {
|
||||
BufferCapacity *= 2;
|
||||
if (BufferCapacity < N + CurrentPosition)
|
||||
BufferCapacity = N + CurrentPosition;
|
||||
Buffer = static_cast<char *>(std::realloc(Buffer, BufferCapacity));
|
||||
if (Buffer == nullptr)
|
||||
std::terminate();
|
||||
}
|
||||
}
|
||||
|
||||
void writeUnsigned(uint64_t N, bool isNeg = false) {
|
||||
// Handle special case...
|
||||
if (N == 0) {
|
||||
*this << '0';
|
||||
return;
|
||||
}
|
||||
|
||||
char Temp[21];
|
||||
char *TempPtr = std::end(Temp);
|
||||
|
||||
while (N) {
|
||||
*--TempPtr = '0' + char(N % 10);
|
||||
N /= 10;
|
||||
}
|
||||
|
||||
// Add negative sign...
|
||||
if (isNeg)
|
||||
*--TempPtr = '-';
|
||||
this->operator<<(StringView(TempPtr, std::end(Temp)));
|
||||
}
|
||||
|
||||
public:
|
||||
OutputStream(char *StartBuf, size_t Size)
|
||||
: Buffer(StartBuf), CurrentPosition(0), BufferCapacity(Size) {}
|
||||
OutputStream() = default;
|
||||
void reset(char *Buffer_, size_t BufferCapacity_) {
|
||||
CurrentPosition = 0;
|
||||
Buffer = Buffer_;
|
||||
BufferCapacity = BufferCapacity_;
|
||||
}
|
||||
|
||||
/// If a ParameterPackExpansion (or similar type) is encountered, the offset
|
||||
/// into the pack that we're currently printing.
|
||||
unsigned CurrentPackIndex = std::numeric_limits<unsigned>::max();
|
||||
unsigned CurrentPackMax = std::numeric_limits<unsigned>::max();
|
||||
|
||||
OutputStream &operator+=(StringView R) {
|
||||
size_t Size = R.size();
|
||||
if (Size == 0)
|
||||
return *this;
|
||||
grow(Size);
|
||||
std::memmove(Buffer + CurrentPosition, R.begin(), Size);
|
||||
CurrentPosition += Size;
|
||||
return *this;
|
||||
}
|
||||
|
||||
OutputStream &operator+=(char C) {
|
||||
grow(1);
|
||||
Buffer[CurrentPosition++] = C;
|
||||
return *this;
|
||||
}
|
||||
|
||||
OutputStream &operator<<(StringView R) { return (*this += R); }
|
||||
|
||||
OutputStream &operator<<(char C) { return (*this += C); }
|
||||
|
||||
OutputStream &operator<<(long long N) {
|
||||
if (N < 0)
|
||||
writeUnsigned(static_cast<unsigned long long>(-N), true);
|
||||
else
|
||||
writeUnsigned(static_cast<unsigned long long>(N));
|
||||
return *this;
|
||||
}
|
||||
|
||||
OutputStream &operator<<(unsigned long long N) {
|
||||
writeUnsigned(N, false);
|
||||
return *this;
|
||||
}
|
||||
|
||||
OutputStream &operator<<(long N) {
|
||||
return this->operator<<(static_cast<long long>(N));
|
||||
}
|
||||
|
||||
OutputStream &operator<<(unsigned long N) {
|
||||
return this->operator<<(static_cast<unsigned long long>(N));
|
||||
}
|
||||
|
||||
OutputStream &operator<<(int N) {
|
||||
return this->operator<<(static_cast<long long>(N));
|
||||
}
|
||||
|
||||
OutputStream &operator<<(unsigned int N) {
|
||||
return this->operator<<(static_cast<unsigned long long>(N));
|
||||
}
|
||||
|
||||
size_t getCurrentPosition() const { return CurrentPosition; }
|
||||
void setCurrentPosition(size_t NewPos) { CurrentPosition = NewPos; }
|
||||
|
||||
char back() const {
|
||||
return CurrentPosition ? Buffer[CurrentPosition - 1] : '\0';
|
||||
}
|
||||
|
||||
bool empty() const { return CurrentPosition == 0; }
|
||||
|
||||
char *getBuffer() { return Buffer; }
|
||||
char *getBufferEnd() { return Buffer + CurrentPosition - 1; }
|
||||
size_t getBufferCapacity() { return BufferCapacity; }
|
||||
};
|
||||
|
||||
template <class T> class SwapAndRestore {
|
||||
T &Restore;
|
||||
T OriginalValue;
|
||||
bool ShouldRestore = true;
|
||||
|
||||
public:
|
||||
SwapAndRestore(T &Restore_) : SwapAndRestore(Restore_, Restore_) {}
|
||||
|
||||
SwapAndRestore(T &Restore_, T NewVal)
|
||||
: Restore(Restore_), OriginalValue(Restore) {
|
||||
Restore = std::move(NewVal);
|
||||
}
|
||||
~SwapAndRestore() {
|
||||
if (ShouldRestore)
|
||||
Restore = std::move(OriginalValue);
|
||||
}
|
||||
|
||||
void shouldRestore(bool ShouldRestore_) { ShouldRestore = ShouldRestore_; }
|
||||
|
||||
void restoreNow(bool Force) {
|
||||
if (!Force && !ShouldRestore)
|
||||
return;
|
||||
|
||||
Restore = std::move(OriginalValue);
|
||||
ShouldRestore = false;
|
||||
}
|
||||
|
||||
SwapAndRestore(const SwapAndRestore &) = delete;
|
||||
SwapAndRestore &operator=(const SwapAndRestore &) = delete;
|
||||
};
|
||||
|
||||
inline bool initializeOutputStream(char *Buf, size_t *N, OutputStream &S,
|
||||
size_t InitSize) {
|
||||
size_t BufferSize;
|
||||
if (Buf == nullptr) {
|
||||
Buf = static_cast<char *>(std::malloc(InitSize));
|
||||
if (Buf == nullptr)
|
||||
return false;
|
||||
BufferSize = InitSize;
|
||||
} else
|
||||
BufferSize = *N;
|
||||
|
||||
S.reset(Buf, BufferSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
DEMANGLE_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
Vendored
+1
-1
Submodule externals/dynarmic updated: bd570e093c...befe547d56
Vendored
+1
-1
Submodule externals/sirit updated: d7ad93a888...ab75463999
Vendored
+1
-1
Submodule externals/xbyak updated: 348e3e548e...a1ac3750f9
+4
-1
@@ -161,7 +161,10 @@ add_subdirectory(video_core)
|
||||
add_subdirectory(network)
|
||||
add_subdirectory(input_common)
|
||||
add_subdirectory(shader_recompiler)
|
||||
add_subdirectory(dedicated_room)
|
||||
|
||||
if (YUZU_ROOM)
|
||||
add_subdirectory(dedicated_room)
|
||||
endif()
|
||||
|
||||
if (YUZU_TESTS)
|
||||
add_subdirectory(tests)
|
||||
|
||||
@@ -187,11 +187,7 @@ add_library(audio_core STATIC
|
||||
renderer/voice/voice_info.cpp
|
||||
renderer/voice/voice_info.h
|
||||
renderer/voice/voice_state.h
|
||||
sink/cubeb_sink.cpp
|
||||
sink/cubeb_sink.h
|
||||
sink/null_sink.h
|
||||
sink/sdl2_sink.cpp
|
||||
sink/sdl2_sink.h
|
||||
sink/sink.h
|
||||
sink/sink_details.cpp
|
||||
sink/sink_details.h
|
||||
@@ -222,11 +218,22 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
|
||||
target_link_libraries(audio_core PRIVATE dynarmic::dynarmic)
|
||||
endif()
|
||||
|
||||
if(ENABLE_CUBEB)
|
||||
if (ENABLE_CUBEB)
|
||||
target_sources(audio_core PRIVATE
|
||||
sink/cubeb_sink.cpp
|
||||
sink/cubeb_sink.h
|
||||
)
|
||||
|
||||
target_link_libraries(audio_core PRIVATE cubeb::cubeb)
|
||||
target_compile_definitions(audio_core PRIVATE -DHAVE_CUBEB=1)
|
||||
endif()
|
||||
if(ENABLE_SDL2)
|
||||
|
||||
if (ENABLE_SDL2)
|
||||
target_sources(audio_core PRIVATE
|
||||
sink/sdl2_sink.cpp
|
||||
sink/sdl2_sink.h
|
||||
)
|
||||
|
||||
target_link_libraries(audio_core PRIVATE SDL2::SDL2)
|
||||
target_compile_definitions(audio_core PRIVATE HAVE_SDL2)
|
||||
endif()
|
||||
|
||||
@@ -20,25 +20,25 @@ static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
|
||||
const u32 target_sample_count, const u32 source_sample_count,
|
||||
UpsamplerState* state) {
|
||||
constexpr u32 WindowSize = 10;
|
||||
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow1{
|
||||
51.93359375f, -18.80078125f, 9.73046875f, -5.33203125f, 2.84375f,
|
||||
-1.41015625f, 0.62109375f, -0.2265625f, 0.0625f, -0.00390625f,
|
||||
constexpr std::array<Common::FixedPoint<17, 15>, WindowSize> WindowedSinc1{
|
||||
0.95376587f, -0.12872314f, 0.060028076f, -0.032470703f, 0.017669678f,
|
||||
-0.009124756f, 0.004272461f, -0.001739502f, 0.000579834f, -0.000091552734f,
|
||||
};
|
||||
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow2{
|
||||
105.35546875f, -24.52734375f, 11.9609375f, -6.515625f, 3.52734375f,
|
||||
-1.796875f, 0.828125f, -0.32421875f, 0.1015625f, -0.015625f,
|
||||
constexpr std::array<Common::FixedPoint<17, 15>, WindowSize> WindowedSinc2{
|
||||
0.8230896f, -0.19161987f, 0.093444824f, -0.05090332f, 0.027557373f,
|
||||
-0.014038086f, 0.0064697266f, -0.002532959f, 0.00079345703f, -0.00012207031f,
|
||||
};
|
||||
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow3{
|
||||
122.08203125f, -16.47656250f, 7.68359375f, -4.15625000f, 2.26171875f,
|
||||
-1.16796875f, 0.54687500f, -0.22265625f, 0.07421875f, -0.01171875f,
|
||||
constexpr std::array<Common::FixedPoint<17, 15>, WindowSize> WindowedSinc3{
|
||||
0.6298828f, -0.19274902f, 0.09725952f, -0.05319214f, 0.028625488f,
|
||||
-0.014373779f, 0.006500244f, -0.0024719238f, 0.0007324219f, -0.000091552734f,
|
||||
};
|
||||
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow4{
|
||||
23.73437500f, -9.62109375f, 5.07812500f, -2.78125000f, 1.46875000f,
|
||||
-0.71484375f, 0.30859375f, -0.10546875f, 0.02734375f, 0.00000000f,
|
||||
constexpr std::array<Common::FixedPoint<17, 15>, WindowSize> WindowedSinc4{
|
||||
0.4057312f, -0.1468811f, 0.07601929f, -0.041656494f, 0.022216797f,
|
||||
-0.011016846f, 0.004852295f, -0.0017700195f, 0.00048828125f, -0.000030517578f,
|
||||
};
|
||||
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow5{
|
||||
80.62500000f, -24.67187500f, 12.44921875f, -6.80859375f, 3.66406250f,
|
||||
-1.83984375f, 0.83203125f, -0.31640625f, 0.09375000f, -0.01171875f,
|
||||
constexpr std::array<Common::FixedPoint<17, 15>, WindowSize> WindowedSinc5{
|
||||
0.1854248f, -0.075164795f, 0.03967285f, -0.021728516f, 0.011474609f,
|
||||
-0.005584717f, 0.0024108887f, -0.0008239746f, 0.00021362305f, 0.0f,
|
||||
};
|
||||
|
||||
if (!state->initialized) {
|
||||
@@ -91,52 +91,31 @@ static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
|
||||
static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
|
||||
};
|
||||
|
||||
auto calculate_sample = [&state](std::span<const Common::FixedPoint<24, 8>> coeffs1,
|
||||
std::span<const Common::FixedPoint<24, 8>> coeffs2) -> s32 {
|
||||
auto calculate_sample = [&state](std::span<const Common::FixedPoint<17, 15>> coeffs1,
|
||||
std::span<const Common::FixedPoint<17, 15>> coeffs2) -> s32 {
|
||||
auto output_index{state->history_output_index};
|
||||
auto start_pos{output_index - state->history_start_index + 1U};
|
||||
auto end_pos{10U};
|
||||
u64 result{0};
|
||||
|
||||
if (start_pos < 10) {
|
||||
end_pos = start_pos;
|
||||
}
|
||||
for (u32 coeff_index = 0; coeff_index < 10; coeff_index++) {
|
||||
result += static_cast<u64>(state->history[output_index].to_raw()) *
|
||||
coeffs1[coeff_index].to_raw();
|
||||
|
||||
u64 prev_contrib{0};
|
||||
u32 coeff_index{0};
|
||||
for (; coeff_index < end_pos; coeff_index++, output_index--) {
|
||||
prev_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
|
||||
coeffs1[coeff_index].to_raw();
|
||||
}
|
||||
|
||||
auto end_index{state->history_end_index};
|
||||
for (; start_pos < 9; start_pos++, coeff_index++, end_index--) {
|
||||
prev_contrib += static_cast<u64>(state->history[end_index].to_raw()) *
|
||||
coeffs1[coeff_index].to_raw();
|
||||
output_index = output_index == state->history_start_index ? state->history_end_index
|
||||
: output_index - 1;
|
||||
}
|
||||
|
||||
output_index =
|
||||
static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
|
||||
start_pos = state->history_end_index - output_index + 1U;
|
||||
end_pos = 10U;
|
||||
|
||||
if (start_pos < 10) {
|
||||
end_pos = start_pos;
|
||||
for (u32 coeff_index = 0; coeff_index < 10; coeff_index++) {
|
||||
result += static_cast<u64>(state->history[output_index].to_raw()) *
|
||||
coeffs2[coeff_index].to_raw();
|
||||
|
||||
output_index = output_index == state->history_end_index ? state->history_start_index
|
||||
: output_index + 1;
|
||||
}
|
||||
|
||||
u64 next_contrib{0};
|
||||
coeff_index = 0;
|
||||
for (; coeff_index < end_pos; coeff_index++, output_index++) {
|
||||
next_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
|
||||
coeffs2[coeff_index].to_raw();
|
||||
}
|
||||
|
||||
auto start_index{state->history_start_index};
|
||||
for (; start_pos < 9; start_pos++, start_index++, coeff_index++) {
|
||||
next_contrib += static_cast<u64>(state->history[start_index].to_raw()) *
|
||||
coeffs2[coeff_index].to_raw();
|
||||
}
|
||||
|
||||
return static_cast<s32>(((prev_contrib >> 15) + (next_contrib >> 15)) >> 8);
|
||||
return static_cast<s32>(result >> (8 + 15));
|
||||
};
|
||||
|
||||
switch (state->ratio.to_int_floor()) {
|
||||
@@ -150,23 +129,23 @@ static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
|
||||
break;
|
||||
|
||||
case 1:
|
||||
output[write_index] = calculate_sample(SincWindow3, SincWindow4);
|
||||
output[write_index] = calculate_sample(WindowedSinc1, WindowedSinc5);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
output[write_index] = calculate_sample(SincWindow2, SincWindow1);
|
||||
output[write_index] = calculate_sample(WindowedSinc2, WindowedSinc4);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
output[write_index] = calculate_sample(SincWindow5, SincWindow5);
|
||||
output[write_index] = calculate_sample(WindowedSinc3, WindowedSinc3);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
output[write_index] = calculate_sample(SincWindow1, SincWindow2);
|
||||
output[write_index] = calculate_sample(WindowedSinc4, WindowedSinc2);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
output[write_index] = calculate_sample(SincWindow4, SincWindow3);
|
||||
output[write_index] = calculate_sample(WindowedSinc5, WindowedSinc1);
|
||||
break;
|
||||
}
|
||||
state->sample_index = static_cast<u8>((state->sample_index + 1) % 6);
|
||||
@@ -183,11 +162,11 @@ static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
|
||||
break;
|
||||
|
||||
case 1:
|
||||
output[write_index] = calculate_sample(SincWindow2, SincWindow1);
|
||||
output[write_index] = calculate_sample(WindowedSinc2, WindowedSinc4);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
output[write_index] = calculate_sample(SincWindow1, SincWindow2);
|
||||
output[write_index] = calculate_sample(WindowedSinc4, WindowedSinc2);
|
||||
break;
|
||||
}
|
||||
state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);
|
||||
@@ -204,12 +183,12 @@ static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
|
||||
break;
|
||||
|
||||
case 1:
|
||||
output[write_index] = calculate_sample(SincWindow1, SincWindow2);
|
||||
output[write_index] = calculate_sample(WindowedSinc4, WindowedSinc2);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
increment();
|
||||
output[write_index] = calculate_sample(SincWindow2, SincWindow1);
|
||||
output[write_index] = calculate_sample(WindowedSinc2, WindowedSinc4);
|
||||
break;
|
||||
}
|
||||
state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);
|
||||
|
||||
@@ -38,6 +38,8 @@ add_library(common STATIC
|
||||
common_precompiled_headers.h
|
||||
common_types.h
|
||||
concepts.h
|
||||
demangle.cpp
|
||||
demangle.h
|
||||
div_ceil.h
|
||||
dynamic_library.cpp
|
||||
dynamic_library.h
|
||||
@@ -78,6 +80,7 @@ add_library(common STATIC
|
||||
logging/types.h
|
||||
lz4_compression.cpp
|
||||
lz4_compression.h
|
||||
make_unique_for_overwrite.h
|
||||
math_util.h
|
||||
memory_detect.cpp
|
||||
memory_detect.h
|
||||
@@ -96,11 +99,13 @@ add_library(common STATIC
|
||||
point.h
|
||||
precompiled_headers.h
|
||||
quaternion.h
|
||||
range_map.h
|
||||
reader_writer_queue.h
|
||||
ring_buffer.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/scm_rev.cpp
|
||||
scm_rev.h
|
||||
scope_exit.h
|
||||
scratch_buffer.h
|
||||
settings.cpp
|
||||
settings.h
|
||||
settings_input.cpp
|
||||
@@ -172,7 +177,7 @@ endif()
|
||||
create_target_directory_groups(common)
|
||||
|
||||
target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads)
|
||||
target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd)
|
||||
target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd LLVM::Demangle)
|
||||
|
||||
if (YUZU_USE_PRECOMPILED_HEADERS)
|
||||
target_precompile_headers(common PRIVATE precompiled_headers.h)
|
||||
|
||||
+2
-2
@@ -69,7 +69,7 @@ void assert_fail_impl();
|
||||
#define ASSERT_OR_EXECUTE(_a_, _b_) \
|
||||
do { \
|
||||
ASSERT(_a_); \
|
||||
if (!(_a_)) { \
|
||||
if (!(_a_)) [[unlikely]] { \
|
||||
_b_ \
|
||||
} \
|
||||
} while (0)
|
||||
@@ -78,7 +78,7 @@ void assert_fail_impl();
|
||||
#define ASSERT_OR_EXECUTE_MSG(_a_, _b_, ...) \
|
||||
do { \
|
||||
ASSERT_MSG(_a_, __VA_ARGS__); \
|
||||
if (!(_a_)) { \
|
||||
if (!(_a_)) [[unlikely]] { \
|
||||
_b_ \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <llvm/Demangle/Demangle.h>
|
||||
|
||||
#include "common/demangle.h"
|
||||
#include "common/scope_exit.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
std::string DemangleSymbol(const std::string& mangled) {
|
||||
auto is_itanium = [](const std::string& name) -> bool {
|
||||
// A valid Itanium encoding requires 1-4 leading underscores, followed by 'Z'.
|
||||
auto pos = name.find_first_not_of('_');
|
||||
return pos > 0 && pos <= 4 && pos < name.size() && name[pos] == 'Z';
|
||||
};
|
||||
|
||||
if (mangled.empty()) {
|
||||
return mangled;
|
||||
}
|
||||
|
||||
char* demangled = nullptr;
|
||||
SCOPE_EXIT({ std::free(demangled); });
|
||||
|
||||
if (is_itanium(mangled)) {
|
||||
demangled = llvm::itaniumDemangle(mangled.c_str(), nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
if (!demangled) {
|
||||
return mangled;
|
||||
}
|
||||
return demangled;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,12 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Common {
|
||||
|
||||
std::string DemangleSymbol(const std::string& mangled);
|
||||
|
||||
} // namespace Common
|
||||
@@ -393,12 +393,27 @@ public:
|
||||
}
|
||||
|
||||
// Virtual memory initialization
|
||||
virtual_base = static_cast<u8*>(
|
||||
mmap(nullptr, virtual_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
|
||||
#if defined(__FreeBSD__)
|
||||
virtual_base =
|
||||
static_cast<u8*>(mmap(nullptr, virtual_size, PROT_NONE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_ALIGNED_SUPER, -1, 0));
|
||||
if (virtual_base == MAP_FAILED) {
|
||||
virtual_base = static_cast<u8*>(
|
||||
mmap(nullptr, virtual_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
|
||||
if (virtual_base == MAP_FAILED) {
|
||||
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
}
|
||||
#else
|
||||
virtual_base = static_cast<u8*>(mmap(nullptr, virtual_size, PROT_NONE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0));
|
||||
if (virtual_base == MAP_FAILED) {
|
||||
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
madvise(virtual_base, virtual_size, MADV_HUGEPAGE);
|
||||
#endif
|
||||
|
||||
good = true;
|
||||
}
|
||||
|
||||
+41
-30
@@ -51,6 +51,8 @@ enum class PollingMode {
|
||||
NFC,
|
||||
// Enable infrared camera polling
|
||||
IR,
|
||||
// Enable ring controller polling
|
||||
Ring,
|
||||
};
|
||||
|
||||
enum class CameraFormat {
|
||||
@@ -62,21 +64,22 @@ enum class CameraFormat {
|
||||
None,
|
||||
};
|
||||
|
||||
// Vibration reply from the controller
|
||||
enum class VibrationError {
|
||||
None,
|
||||
// Different results that can happen from a device request
|
||||
enum class DriverResult {
|
||||
Success,
|
||||
WrongReply,
|
||||
Timeout,
|
||||
UnsupportedControllerType,
|
||||
HandleInUse,
|
||||
ErrorReadingData,
|
||||
ErrorWritingData,
|
||||
NoDeviceDetected,
|
||||
InvalidHandle,
|
||||
NotSupported,
|
||||
Disabled,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
// Polling mode reply from the controller
|
||||
enum class PollingError {
|
||||
None,
|
||||
NotSupported,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
// Nfc reply from the controller
|
||||
enum class NfcState {
|
||||
Success,
|
||||
@@ -90,13 +93,6 @@ enum class NfcState {
|
||||
Unknown,
|
||||
};
|
||||
|
||||
// Ir camera reply from the controller
|
||||
enum class CameraError {
|
||||
None,
|
||||
NotSupported,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
// Hint for amplification curve to be used
|
||||
enum class VibrationAmplificationType {
|
||||
Linear,
|
||||
@@ -190,6 +186,8 @@ struct TouchStatus {
|
||||
struct BodyColorStatus {
|
||||
u32 body{};
|
||||
u32 buttons{};
|
||||
u32 left_grip{};
|
||||
u32 right_grip{};
|
||||
};
|
||||
|
||||
// HD rumble data
|
||||
@@ -228,17 +226,31 @@ enum class ButtonNames {
|
||||
Engine,
|
||||
// This will display the button by value instead of the button name
|
||||
Value,
|
||||
|
||||
// Joycon button names
|
||||
ButtonLeft,
|
||||
ButtonRight,
|
||||
ButtonDown,
|
||||
ButtonUp,
|
||||
TriggerZ,
|
||||
TriggerR,
|
||||
TriggerL,
|
||||
ButtonA,
|
||||
ButtonB,
|
||||
ButtonX,
|
||||
ButtonY,
|
||||
ButtonPlus,
|
||||
ButtonMinus,
|
||||
ButtonHome,
|
||||
ButtonCapture,
|
||||
ButtonStickL,
|
||||
ButtonStickR,
|
||||
TriggerL,
|
||||
TriggerZL,
|
||||
TriggerSL,
|
||||
TriggerR,
|
||||
TriggerZR,
|
||||
TriggerSR,
|
||||
|
||||
// GC button names
|
||||
TriggerZ,
|
||||
ButtonStart,
|
||||
|
||||
// DS4 button names
|
||||
@@ -292,9 +304,6 @@ class InputDevice {
|
||||
public:
|
||||
virtual ~InputDevice() = default;
|
||||
|
||||
// Request input device to update if necessary
|
||||
virtual void SoftUpdate() {}
|
||||
|
||||
// Force input device to update data regardless of the current state
|
||||
virtual void ForceUpdate() {}
|
||||
|
||||
@@ -319,22 +328,24 @@ class OutputDevice {
|
||||
public:
|
||||
virtual ~OutputDevice() = default;
|
||||
|
||||
virtual void SetLED([[maybe_unused]] const LedStatus& led_status) {}
|
||||
virtual DriverResult SetLED([[maybe_unused]] const LedStatus& led_status) {
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
virtual VibrationError SetVibration([[maybe_unused]] const VibrationStatus& vibration_status) {
|
||||
return VibrationError::NotSupported;
|
||||
virtual DriverResult SetVibration([[maybe_unused]] const VibrationStatus& vibration_status) {
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
virtual bool IsVibrationEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
|
||||
return PollingError::NotSupported;
|
||||
virtual DriverResult SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
virtual CameraError SetCameraFormat([[maybe_unused]] CameraFormat camera_format) {
|
||||
return CameraError::NotSupported;
|
||||
virtual DriverResult SetCameraFormat([[maybe_unused]] CameraFormat camera_format) {
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
virtual NfcState SupportsNfc() const {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <class T>
|
||||
requires(!std::is_array_v<T>) std::unique_ptr<T> make_unique_for_overwrite() {
|
||||
return std::unique_ptr<T>(new T);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
requires std::is_unbounded_array_v<T> std::unique_ptr<T> make_unique_for_overwrite(std::size_t n) {
|
||||
return std::unique_ptr<T>(new std::remove_extent_t<T>[n]);
|
||||
}
|
||||
|
||||
template <class T, class... Args>
|
||||
requires std::is_bounded_array_v<T>
|
||||
void make_unique_for_overwrite(Args&&...) = delete;
|
||||
|
||||
} // namespace Common
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
#ifdef __cpp_lib_jthread
|
||||
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <stop_token>
|
||||
#include <thread>
|
||||
|
||||
@@ -21,11 +23,23 @@ void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) {
|
||||
cv.wait(lock, token, std::move(pred));
|
||||
}
|
||||
|
||||
template <typename Rep, typename Period>
|
||||
bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep, Period>& rel_time) {
|
||||
std::condition_variable_any cv;
|
||||
std::mutex m;
|
||||
|
||||
// Perform the timed wait.
|
||||
std::unique_lock lk{m};
|
||||
return !cv.wait_for(lk, token, rel_time, [&] { return token.stop_requested(); });
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#else
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
@@ -318,6 +332,28 @@ void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) {
|
||||
cv.wait(lock, [&] { return pred() || token.stop_requested(); });
|
||||
}
|
||||
|
||||
template <typename Rep, typename Period>
|
||||
bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep, Period>& rel_time) {
|
||||
if (token.stop_requested()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool stop_requested = false;
|
||||
std::condition_variable cv;
|
||||
std::mutex m;
|
||||
|
||||
std::stop_callback cb(token, [&] {
|
||||
// Wake up the waiting thread.
|
||||
std::unique_lock lk{m};
|
||||
stop_requested = true;
|
||||
cv.notify_one();
|
||||
});
|
||||
|
||||
// Perform the timed wait.
|
||||
std::unique_lock lk{m};
|
||||
return !cv.wait_for(lk, rel_time, [&] { return stop_requested; });
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <type_traits>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename KeyTBase, typename ValueT>
|
||||
class RangeMap {
|
||||
private:
|
||||
using KeyT =
|
||||
std::conditional_t<std::is_signed_v<KeyTBase>, KeyTBase, std::make_signed_t<KeyTBase>>;
|
||||
|
||||
public:
|
||||
explicit RangeMap(ValueT null_value_) : null_value{null_value_} {
|
||||
container.emplace(std::numeric_limits<KeyT>::min(), null_value);
|
||||
};
|
||||
~RangeMap() = default;
|
||||
|
||||
void Map(KeyTBase address, KeyTBase address_end, ValueT value) {
|
||||
KeyT new_address = static_cast<KeyT>(address);
|
||||
KeyT new_address_end = static_cast<KeyT>(address_end);
|
||||
if (new_address < 0) {
|
||||
new_address = 0;
|
||||
}
|
||||
if (new_address_end < 0) {
|
||||
new_address_end = 0;
|
||||
}
|
||||
InternalMap(new_address, new_address_end, value);
|
||||
}
|
||||
|
||||
void Unmap(KeyTBase address, KeyTBase address_end) {
|
||||
Map(address, address_end, null_value);
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t GetContinousSizeFrom(KeyTBase address) const {
|
||||
const KeyT new_address = static_cast<KeyT>(address);
|
||||
if (new_address < 0) {
|
||||
return 0;
|
||||
}
|
||||
return ContinousSizeInternal(new_address);
|
||||
}
|
||||
|
||||
[[nodiscard]] ValueT GetValueAt(KeyT address) const {
|
||||
const KeyT new_address = static_cast<KeyT>(address);
|
||||
if (new_address < 0) {
|
||||
return null_value;
|
||||
}
|
||||
return GetValueInternal(new_address);
|
||||
}
|
||||
|
||||
private:
|
||||
using MapType = std::map<KeyT, ValueT>;
|
||||
using IteratorType = typename MapType::iterator;
|
||||
using ConstIteratorType = typename MapType::const_iterator;
|
||||
|
||||
size_t ContinousSizeInternal(KeyT address) const {
|
||||
const auto it = GetFirstElementBeforeOrOn(address);
|
||||
if (it == container.end() || it->second == null_value) {
|
||||
return 0;
|
||||
}
|
||||
const auto it_end = std::next(it);
|
||||
if (it_end == container.end()) {
|
||||
return std::numeric_limits<KeyT>::max() - address;
|
||||
}
|
||||
return it_end->first - address;
|
||||
}
|
||||
|
||||
ValueT GetValueInternal(KeyT address) const {
|
||||
const auto it = GetFirstElementBeforeOrOn(address);
|
||||
if (it == container.end()) {
|
||||
return null_value;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
ConstIteratorType GetFirstElementBeforeOrOn(KeyT address) const {
|
||||
auto it = container.lower_bound(address);
|
||||
if (it == container.begin()) {
|
||||
return it;
|
||||
}
|
||||
if (it != container.end() && (it->first == address)) {
|
||||
return it;
|
||||
}
|
||||
--it;
|
||||
return it;
|
||||
}
|
||||
|
||||
ValueT GetFirstValueWithin(KeyT address) {
|
||||
auto it = container.lower_bound(address);
|
||||
if (it == container.begin()) {
|
||||
return it->second;
|
||||
}
|
||||
if (it == container.end()) [[unlikely]] { // this would be a bug
|
||||
return null_value;
|
||||
}
|
||||
--it;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
ValueT GetLastValueWithin(KeyT address) {
|
||||
auto it = container.upper_bound(address);
|
||||
if (it == container.end()) {
|
||||
return null_value;
|
||||
}
|
||||
if (it == container.begin()) [[unlikely]] { // this would be a bug
|
||||
return it->second;
|
||||
}
|
||||
--it;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void InternalMap(KeyT address, KeyT address_end, ValueT value) {
|
||||
const bool must_add_start = GetFirstValueWithin(address) != value;
|
||||
const ValueT last_value = GetLastValueWithin(address_end);
|
||||
const bool must_add_end = last_value != value;
|
||||
auto it = container.lower_bound(address);
|
||||
const auto it_end = container.upper_bound(address_end);
|
||||
while (it != it_end) {
|
||||
it = container.erase(it);
|
||||
}
|
||||
if (must_add_start) {
|
||||
container.emplace(address, value);
|
||||
}
|
||||
if (must_add_end) {
|
||||
container.emplace(address_end, last_value);
|
||||
}
|
||||
}
|
||||
|
||||
ValueT null_value;
|
||||
MapType container;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,95 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/make_unique_for_overwrite.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
/**
|
||||
* ScratchBuffer class
|
||||
* This class creates a default initialized heap allocated buffer for cases such as intermediate
|
||||
* buffers being copied into entirely, where value initializing members during allocation or resize
|
||||
* is redundant.
|
||||
*/
|
||||
template <typename T>
|
||||
class ScratchBuffer {
|
||||
public:
|
||||
ScratchBuffer() = default;
|
||||
|
||||
explicit ScratchBuffer(size_t initial_capacity)
|
||||
: last_requested_size{initial_capacity}, buffer_capacity{initial_capacity},
|
||||
buffer{Common::make_unique_for_overwrite<T[]>(initial_capacity)} {}
|
||||
|
||||
~ScratchBuffer() = default;
|
||||
|
||||
/// This will only grow the buffer's capacity if size is greater than the current capacity.
|
||||
/// The previously held data will remain intact.
|
||||
void resize(size_t size) {
|
||||
if (size > buffer_capacity) {
|
||||
auto new_buffer = Common::make_unique_for_overwrite<T[]>(size);
|
||||
std::move(buffer.get(), buffer.get() + buffer_capacity, new_buffer.get());
|
||||
buffer = std::move(new_buffer);
|
||||
buffer_capacity = size;
|
||||
}
|
||||
last_requested_size = size;
|
||||
}
|
||||
|
||||
/// This will only grow the buffer's capacity if size is greater than the current capacity.
|
||||
/// The previously held data will be destroyed if a reallocation occurs.
|
||||
void resize_destructive(size_t size) {
|
||||
if (size > buffer_capacity) {
|
||||
buffer_capacity = size;
|
||||
buffer = Common::make_unique_for_overwrite<T[]>(buffer_capacity);
|
||||
}
|
||||
last_requested_size = size;
|
||||
}
|
||||
|
||||
[[nodiscard]] T* data() noexcept {
|
||||
return buffer.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] const T* data() const noexcept {
|
||||
return buffer.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] T* begin() noexcept {
|
||||
return data();
|
||||
}
|
||||
|
||||
[[nodiscard]] const T* begin() const noexcept {
|
||||
return data();
|
||||
}
|
||||
|
||||
[[nodiscard]] T* end() noexcept {
|
||||
return data() + last_requested_size;
|
||||
}
|
||||
|
||||
[[nodiscard]] const T* end() const noexcept {
|
||||
return data() + last_requested_size;
|
||||
}
|
||||
|
||||
[[nodiscard]] T& operator[](size_t i) {
|
||||
return buffer[i];
|
||||
}
|
||||
|
||||
[[nodiscard]] const T& operator[](size_t i) const {
|
||||
return buffer[i];
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t size() const noexcept {
|
||||
return last_requested_size;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t capacity() const noexcept {
|
||||
return buffer_capacity;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t last_requested_size{};
|
||||
size_t buffer_capacity{};
|
||||
std::unique_ptr<T[]> buffer{};
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
@@ -129,6 +129,10 @@ void UpdateRescalingInfo() {
|
||||
info.up_scale = 1;
|
||||
info.down_shift = 0;
|
||||
break;
|
||||
case ResolutionSetup::Res3_2X:
|
||||
info.up_scale = 3;
|
||||
info.down_shift = 1;
|
||||
break;
|
||||
case ResolutionSetup::Res2X:
|
||||
info.up_scale = 2;
|
||||
info.down_shift = 0;
|
||||
@@ -149,6 +153,14 @@ void UpdateRescalingInfo() {
|
||||
info.up_scale = 6;
|
||||
info.down_shift = 0;
|
||||
break;
|
||||
case ResolutionSetup::Res7X:
|
||||
info.up_scale = 7;
|
||||
info.down_shift = 0;
|
||||
break;
|
||||
case ResolutionSetup::Res8X:
|
||||
info.up_scale = 8;
|
||||
info.down_shift = 0;
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
info.up_scale = 1;
|
||||
@@ -185,6 +197,7 @@ void RestoreGlobalState(bool is_powered_on) {
|
||||
// Renderer
|
||||
values.fsr_sharpening_slider.SetGlobal(true);
|
||||
values.renderer_backend.SetGlobal(true);
|
||||
values.renderer_force_max_clock.SetGlobal(true);
|
||||
values.vulkan_device.SetGlobal(true);
|
||||
values.aspect_ratio.SetGlobal(true);
|
||||
values.max_anisotropy.SetGlobal(true);
|
||||
@@ -200,6 +213,7 @@ void RestoreGlobalState(bool is_powered_on) {
|
||||
values.use_asynchronous_shaders.SetGlobal(true);
|
||||
values.use_fast_gpu_time.SetGlobal(true);
|
||||
values.use_pessimistic_flushes.SetGlobal(true);
|
||||
values.use_vulkan_driver_pipeline_cache.SetGlobal(true);
|
||||
values.bg_red.SetGlobal(true);
|
||||
values.bg_green.SetGlobal(true);
|
||||
values.bg_blue.SetGlobal(true);
|
||||
|
||||
+13
-5
@@ -56,11 +56,14 @@ enum class ResolutionSetup : u32 {
|
||||
Res1_2X = 0,
|
||||
Res3_4X = 1,
|
||||
Res1X = 2,
|
||||
Res2X = 3,
|
||||
Res3X = 4,
|
||||
Res4X = 5,
|
||||
Res5X = 6,
|
||||
Res6X = 7,
|
||||
Res3_2X = 3,
|
||||
Res2X = 4,
|
||||
Res3X = 5,
|
||||
Res4X = 6,
|
||||
Res5X = 7,
|
||||
Res6X = 8,
|
||||
Res7X = 9,
|
||||
Res8X = 10,
|
||||
};
|
||||
|
||||
enum class ScalingFilter : u32 {
|
||||
@@ -415,6 +418,7 @@ struct Values {
|
||||
// Renderer
|
||||
SwitchableSetting<RendererBackend, true> renderer_backend{
|
||||
RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Null, "backend"};
|
||||
SwitchableSetting<bool> renderer_force_max_clock{false, "force_max_clock"};
|
||||
Setting<bool> renderer_debug{false, "debug"};
|
||||
Setting<bool> renderer_shader_feedback{false, "shader_feedback"};
|
||||
Setting<bool> enable_nsight_aftermath{false, "nsight_aftermath"};
|
||||
@@ -451,6 +455,8 @@ struct Values {
|
||||
SwitchableSetting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"};
|
||||
SwitchableSetting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"};
|
||||
SwitchableSetting<bool> use_pessimistic_flushes{false, "use_pessimistic_flushes"};
|
||||
SwitchableSetting<bool> use_vulkan_driver_pipeline_cache{true,
|
||||
"use_vulkan_driver_pipeline_cache"};
|
||||
|
||||
SwitchableSetting<u8> bg_red{0, "bg_red"};
|
||||
SwitchableSetting<u8> bg_green{0, "bg_green"};
|
||||
@@ -477,6 +483,7 @@ struct Values {
|
||||
|
||||
Setting<bool> enable_raw_input{false, "enable_raw_input"};
|
||||
Setting<bool> controller_navigation{true, "controller_navigation"};
|
||||
Setting<bool> enable_joycon_driver{true, "enable_joycon_driver"};
|
||||
|
||||
SwitchableSetting<bool> vibration_enabled{true, "vibration_enabled"};
|
||||
SwitchableSetting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"};
|
||||
@@ -531,6 +538,7 @@ struct Values {
|
||||
Setting<bool> reporting_services{false, "reporting_services"};
|
||||
Setting<bool> quest_flag{false, "quest_flag"};
|
||||
Setting<bool> disable_macro_jit{false, "disable_macro_jit"};
|
||||
Setting<bool> disable_macro_hle{false, "disable_macro_hle"};
|
||||
Setting<bool> extended_logging{false, "extended_logging"};
|
||||
Setting<bool> use_debug_asserts{false, "use_debug_asserts"};
|
||||
Setting<bool> use_auto_stub{false, "use_auto_stub"};
|
||||
|
||||
@@ -201,6 +201,9 @@ add_library(core STATIC
|
||||
hle/kernel/k_event_info.h
|
||||
hle/kernel/k_handle_table.cpp
|
||||
hle/kernel/k_handle_table.h
|
||||
hle/kernel/k_hardware_timer_base.h
|
||||
hle/kernel/k_hardware_timer.cpp
|
||||
hle/kernel/k_hardware_timer.h
|
||||
hle/kernel/k_interrupt_manager.cpp
|
||||
hle/kernel/k_interrupt_manager.h
|
||||
hle/kernel/k_light_condition_variable.cpp
|
||||
@@ -223,6 +226,7 @@ add_library(core STATIC
|
||||
hle/kernel/k_page_buffer.h
|
||||
hle/kernel/k_page_heap.cpp
|
||||
hle/kernel/k_page_heap.h
|
||||
hle/kernel/k_page_group.cpp
|
||||
hle/kernel/k_page_group.h
|
||||
hle/kernel/k_page_table.cpp
|
||||
hle/kernel/k_page_table.h
|
||||
@@ -268,6 +272,7 @@ add_library(core STATIC
|
||||
hle/kernel/k_thread_local_page.h
|
||||
hle/kernel/k_thread_queue.cpp
|
||||
hle/kernel/k_thread_queue.h
|
||||
hle/kernel/k_timer_task.h
|
||||
hle/kernel/k_trace.h
|
||||
hle/kernel/k_transfer_memory.cpp
|
||||
hle/kernel/k_transfer_memory.h
|
||||
@@ -290,8 +295,6 @@ add_library(core STATIC
|
||||
hle/kernel/svc_common.h
|
||||
hle/kernel/svc_types.h
|
||||
hle/kernel/svc_wrap.h
|
||||
hle/kernel/time_manager.cpp
|
||||
hle/kernel/time_manager.h
|
||||
hle/result.h
|
||||
hle/service/acc/acc.cpp
|
||||
hle/service/acc/acc.h
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/demangle.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/arm/symbols.h"
|
||||
@@ -71,20 +69,8 @@ void ARM_Interface::SymbolicateBacktrace(Core::System& system, std::vector<Backt
|
||||
const auto symbol_set = symbols.find(entry.module);
|
||||
if (symbol_set != symbols.end()) {
|
||||
const auto symbol = Symbols::GetSymbolName(symbol_set->second, entry.offset);
|
||||
if (symbol.has_value()) {
|
||||
#ifdef _MSC_VER
|
||||
// TODO(DarkLordZach): Add demangling of symbol names.
|
||||
entry.name = *symbol;
|
||||
#else
|
||||
int status{-1};
|
||||
char* demangled{abi::__cxa_demangle(symbol->c_str(), nullptr, nullptr, &status)};
|
||||
if (status == 0 && demangled != nullptr) {
|
||||
entry.name = demangled;
|
||||
std::free(demangled);
|
||||
} else {
|
||||
entry.name = *symbol;
|
||||
}
|
||||
#endif
|
||||
if (symbol) {
|
||||
entry.name = Common::DemangleSymbol(*symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +229,11 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
|
||||
config.enable_cycle_counting = true;
|
||||
|
||||
// Code cache size
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
config.code_cache_size = 128_MiB;
|
||||
#else
|
||||
config.code_cache_size = 512_MiB;
|
||||
#endif
|
||||
|
||||
// Allow memory fault handling to work
|
||||
if (system.DebuggerEnabled()) {
|
||||
|
||||
@@ -288,7 +288,11 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
|
||||
config.enable_cycle_counting = true;
|
||||
|
||||
// Code cache size
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
config.code_cache_size = 128_MiB;
|
||||
#else
|
||||
config.code_cache_size = 512_MiB;
|
||||
#endif
|
||||
|
||||
// Allow memory fault handling to work
|
||||
if (system.DebuggerEnabled()) {
|
||||
|
||||
+6
-12
@@ -183,26 +183,20 @@ struct System::Impl {
|
||||
Initialize(system);
|
||||
}
|
||||
|
||||
SystemResultStatus Run() {
|
||||
void Run() {
|
||||
std::unique_lock<std::mutex> lk(suspend_guard);
|
||||
status = SystemResultStatus::Success;
|
||||
|
||||
kernel.Suspend(false);
|
||||
core_timing.SyncPause(false);
|
||||
is_paused.store(false, std::memory_order_relaxed);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
SystemResultStatus Pause() {
|
||||
void Pause() {
|
||||
std::unique_lock<std::mutex> lk(suspend_guard);
|
||||
status = SystemResultStatus::Success;
|
||||
|
||||
core_timing.SyncPause(true);
|
||||
kernel.Suspend(true);
|
||||
is_paused.store(true, std::memory_order_relaxed);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool IsPaused() const {
|
||||
@@ -553,12 +547,12 @@ void System::Initialize() {
|
||||
impl->Initialize(*this);
|
||||
}
|
||||
|
||||
SystemResultStatus System::Run() {
|
||||
return impl->Run();
|
||||
void System::Run() {
|
||||
impl->Run();
|
||||
}
|
||||
|
||||
SystemResultStatus System::Pause() {
|
||||
return impl->Pause();
|
||||
void System::Pause() {
|
||||
impl->Pause();
|
||||
}
|
||||
|
||||
bool System::IsPaused() const {
|
||||
|
||||
+2
-2
@@ -152,13 +152,13 @@ public:
|
||||
* Run the OS and Application
|
||||
* This function will start emulation and run the relevant devices
|
||||
*/
|
||||
[[nodiscard]] SystemResultStatus Run();
|
||||
void Run();
|
||||
|
||||
/**
|
||||
* Pause the OS and Application
|
||||
* This function will pause emulation and stop the relevant devices
|
||||
*/
|
||||
[[nodiscard]] SystemResultStatus Pause();
|
||||
void Pause();
|
||||
|
||||
/// Check if the core is currently paused.
|
||||
[[nodiscard]] bool IsPaused() const;
|
||||
|
||||
+17
-23
@@ -142,16 +142,24 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
|
||||
}
|
||||
|
||||
void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
|
||||
std::uintptr_t user_data) {
|
||||
std::scoped_lock scope{basic_lock};
|
||||
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
|
||||
return e.type.lock().get() == event_type.get() && e.user_data == user_data;
|
||||
});
|
||||
std::uintptr_t user_data, bool wait) {
|
||||
{
|
||||
std::scoped_lock lk{basic_lock};
|
||||
const auto itr =
|
||||
std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
|
||||
return e.type.lock().get() == event_type.get() && e.user_data == user_data;
|
||||
});
|
||||
|
||||
// Removing random items breaks the invariant so we have to re-establish it.
|
||||
if (itr != event_queue.end()) {
|
||||
event_queue.erase(itr, event_queue.end());
|
||||
std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
// Removing random items breaks the invariant so we have to re-establish it.
|
||||
if (itr != event_queue.end()) {
|
||||
event_queue.erase(itr, event_queue.end());
|
||||
std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
}
|
||||
}
|
||||
|
||||
// Force any in-progress events to finish
|
||||
if (wait) {
|
||||
std::scoped_lock lk{advance_lock};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,20 +198,6 @@ u64 CoreTiming::GetClockTicks() const {
|
||||
return CpuCyclesToClockCycles(ticks);
|
||||
}
|
||||
|
||||
void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
|
||||
std::scoped_lock lock{basic_lock};
|
||||
|
||||
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
|
||||
return e.type.lock().get() == event_type.get();
|
||||
});
|
||||
|
||||
// Removing random items breaks the invariant so we have to re-establish it.
|
||||
if (itr != event_queue.end()) {
|
||||
event_queue.erase(itr, event_queue.end());
|
||||
std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<s64> CoreTiming::Advance() {
|
||||
std::scoped_lock lock{advance_lock, basic_lock};
|
||||
global_timer = GetGlobalTimeNs().count();
|
||||
|
||||
@@ -98,10 +98,13 @@ public:
|
||||
const std::shared_ptr<EventType>& event_type,
|
||||
std::uintptr_t user_data = 0, bool absolute_time = false);
|
||||
|
||||
void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data);
|
||||
void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, std::uintptr_t user_data,
|
||||
bool wait = true);
|
||||
|
||||
/// We only permit one event of each type in the queue at a time.
|
||||
void RemoveEvent(const std::shared_ptr<EventType>& event_type);
|
||||
void UnscheduleEventWithoutWait(const std::shared_ptr<EventType>& event_type,
|
||||
std::uintptr_t user_data) {
|
||||
UnscheduleEvent(event_type, user_data, false);
|
||||
}
|
||||
|
||||
void AddTicks(u64 ticks_to_add);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
#include "core/debugger/gdbstub.h"
|
||||
@@ -731,7 +732,25 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
|
||||
auto* process = system.CurrentProcess();
|
||||
auto& page_table = process->PageTable();
|
||||
|
||||
if (command_str == "get info") {
|
||||
const char* commands = "Commands:\n"
|
||||
" get fastmem\n"
|
||||
" get info\n"
|
||||
" get mappings\n";
|
||||
|
||||
if (command_str == "get fastmem") {
|
||||
if (Settings::IsFastmemEnabled()) {
|
||||
const auto& impl = page_table.PageTableImpl();
|
||||
const auto region = reinterpret_cast<uintptr_t>(impl.fastmem_arena);
|
||||
const auto region_bits = impl.current_address_space_width_in_bits;
|
||||
const auto region_size = 1ULL << region_bits;
|
||||
|
||||
reply = fmt::format("Region bits: {}\n"
|
||||
"Host address: {:#x} - {:#x}\n",
|
||||
region_bits, region, region + region_size - 1);
|
||||
} else {
|
||||
reply = "Fastmem is not enabled.\n";
|
||||
}
|
||||
} else if (command_str == "get info") {
|
||||
Loader::AppLoader::Modules modules;
|
||||
system.GetAppLoader().ReadNSOModules(modules);
|
||||
|
||||
@@ -787,9 +806,10 @@ void GDBStub::HandleRcmd(const std::vector<u8>& command) {
|
||||
cur_addr = next_address;
|
||||
}
|
||||
} else if (command_str == "help") {
|
||||
reply = "Commands:\n get info\n get mappings\n";
|
||||
reply = commands;
|
||||
} else {
|
||||
reply = "Unknown command.\nCommands:\n get info\n get mappings\n";
|
||||
reply = "Unknown command.\n";
|
||||
reply += commands;
|
||||
}
|
||||
|
||||
std::span<const u8> reply_span{reinterpret_cast<u8*>(&reply.front()), reply.size()};
|
||||
|
||||
@@ -194,9 +194,9 @@ std::size_t VfsFile::WriteBytes(const std::vector<u8>& data, std::size_t offset)
|
||||
|
||||
std::string VfsFile::GetFullPath() const {
|
||||
if (GetContainingDirectory() == nullptr)
|
||||
return "/" + GetName();
|
||||
return '/' + GetName();
|
||||
|
||||
return GetContainingDirectory()->GetFullPath() + "/" + GetName();
|
||||
return GetContainingDirectory()->GetFullPath() + '/' + GetName();
|
||||
}
|
||||
|
||||
VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const {
|
||||
@@ -435,7 +435,7 @@ std::string VfsDirectory::GetFullPath() const {
|
||||
if (IsRoot())
|
||||
return GetName();
|
||||
|
||||
return GetParentDirectory()->GetFullPath() + "/" + GetName();
|
||||
return GetParentDirectory()->GetFullPath() + '/' + GetName();
|
||||
}
|
||||
|
||||
bool ReadOnlyVfsDirectory::IsWritable() const {
|
||||
|
||||
@@ -40,6 +40,11 @@ void EmulatedConsole::SetTouchParams() {
|
||||
touch_params[index++] = std::move(touchscreen_param);
|
||||
}
|
||||
|
||||
if (Settings::values.touch_from_button_maps.empty()) {
|
||||
LOG_WARNING(Input, "touch_from_button_maps is unset by frontend config");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto button_index =
|
||||
static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue());
|
||||
const auto& touch_buttons = Settings::values.touch_from_button_maps[button_index].buttons;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <common/scope_exit.h>
|
||||
|
||||
#include "common/polyfill_ranges.h"
|
||||
#include "common/thread.h"
|
||||
@@ -11,6 +12,11 @@
|
||||
namespace Core::HID {
|
||||
constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
|
||||
constexpr s32 HID_TRIGGER_MAX = 0x7fff;
|
||||
// Use a common UUID for TAS and Virtual Gamepad
|
||||
constexpr Common::UUID TAS_UUID =
|
||||
Common::UUID{{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
|
||||
constexpr Common::UUID VIRTUAL_UUID =
|
||||
Common::UUID{{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
|
||||
|
||||
EmulatedController::EmulatedController(NpadIdType npad_id_type_) : npad_id_type(npad_id_type_) {}
|
||||
|
||||
@@ -88,6 +94,7 @@ void EmulatedController::ReloadFromSettings() {
|
||||
motion_params[index] = Common::ParamPackage(player.motions[index]);
|
||||
}
|
||||
|
||||
controller.color_values = {};
|
||||
controller.colors_state.fullkey = {
|
||||
.body = GetNpadColor(player.body_color_left),
|
||||
.button = GetNpadColor(player.button_color_left),
|
||||
@@ -101,6 +108,8 @@ void EmulatedController::ReloadFromSettings() {
|
||||
.button = GetNpadColor(player.button_color_right),
|
||||
};
|
||||
|
||||
ring_params[0] = Common::ParamPackage(Settings::values.ringcon_analogs);
|
||||
|
||||
// Other or debug controller should always be a pro controller
|
||||
if (npad_id_type != NpadIdType::Other) {
|
||||
SetNpadStyleIndex(MapSettingsTypeToNPad(player.controller_type));
|
||||
@@ -127,24 +136,35 @@ void EmulatedController::LoadDevices() {
|
||||
trigger_params[LeftIndex] = button_params[Settings::NativeButton::ZL];
|
||||
trigger_params[RightIndex] = button_params[Settings::NativeButton::ZR];
|
||||
|
||||
color_params[LeftIndex] = left_joycon;
|
||||
color_params[RightIndex] = right_joycon;
|
||||
color_params[LeftIndex].Set("color", true);
|
||||
color_params[RightIndex].Set("color", true);
|
||||
|
||||
battery_params[LeftIndex] = left_joycon;
|
||||
battery_params[RightIndex] = right_joycon;
|
||||
battery_params[LeftIndex].Set("battery", true);
|
||||
battery_params[RightIndex].Set("battery", true);
|
||||
|
||||
camera_params = Common::ParamPackage{"engine:camera,camera:1"};
|
||||
nfc_params = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
|
||||
camera_params[0] = right_joycon;
|
||||
camera_params[0].Set("camera", true);
|
||||
camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"};
|
||||
ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
|
||||
nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
|
||||
nfc_params[1] = right_joycon;
|
||||
nfc_params[1].Set("nfc", true);
|
||||
|
||||
output_params[LeftIndex] = left_joycon;
|
||||
output_params[RightIndex] = right_joycon;
|
||||
output_params[2] = camera_params;
|
||||
output_params[3] = nfc_params;
|
||||
output_params[2] = camera_params[1];
|
||||
output_params[3] = nfc_params[0];
|
||||
output_params[LeftIndex].Set("output", true);
|
||||
output_params[RightIndex].Set("output", true);
|
||||
output_params[2].Set("output", true);
|
||||
output_params[3].Set("output", true);
|
||||
|
||||
LoadTASParams();
|
||||
LoadVirtualGamepadParams();
|
||||
|
||||
std::ranges::transform(button_params, button_devices.begin(), Common::Input::CreateInputDevice);
|
||||
std::ranges::transform(stick_params, stick_devices.begin(), Common::Input::CreateInputDevice);
|
||||
@@ -153,8 +173,11 @@ void EmulatedController::LoadDevices() {
|
||||
Common::Input::CreateInputDevice);
|
||||
std::ranges::transform(battery_params, battery_devices.begin(),
|
||||
Common::Input::CreateInputDevice);
|
||||
camera_devices = Common::Input::CreateInputDevice(camera_params);
|
||||
nfc_devices = Common::Input::CreateInputDevice(nfc_params);
|
||||
std::ranges::transform(color_params, color_devices.begin(), Common::Input::CreateInputDevice);
|
||||
std::ranges::transform(camera_params, camera_devices.begin(), Common::Input::CreateInputDevice);
|
||||
std::ranges::transform(ring_params, ring_analog_devices.begin(),
|
||||
Common::Input::CreateInputDevice);
|
||||
std::ranges::transform(nfc_params, nfc_devices.begin(), Common::Input::CreateInputDevice);
|
||||
std::ranges::transform(output_params, output_devices.begin(),
|
||||
Common::Input::CreateOutputDevice);
|
||||
|
||||
@@ -163,6 +186,12 @@ void EmulatedController::LoadDevices() {
|
||||
Common::Input::CreateInputDevice);
|
||||
std::ranges::transform(tas_stick_params, tas_stick_devices.begin(),
|
||||
Common::Input::CreateInputDevice);
|
||||
|
||||
// Initialize virtual gamepad devices
|
||||
std::ranges::transform(virtual_button_params, virtual_button_devices.begin(),
|
||||
Common::Input::CreateInputDevice);
|
||||
std::ranges::transform(virtual_stick_params, virtual_stick_devices.begin(),
|
||||
Common::Input::CreateInputDevice);
|
||||
}
|
||||
|
||||
void EmulatedController::LoadTASParams() {
|
||||
@@ -203,6 +232,53 @@ void EmulatedController::LoadTASParams() {
|
||||
tas_stick_params[Settings::NativeAnalog::LStick].Set("axis_y", 1);
|
||||
tas_stick_params[Settings::NativeAnalog::RStick].Set("axis_x", 2);
|
||||
tas_stick_params[Settings::NativeAnalog::RStick].Set("axis_y", 3);
|
||||
|
||||
// set to optimal stick to avoid sanitizing the stick and tweaking the coordinates
|
||||
// making sure they play back in the game as originally written down in the script file
|
||||
tas_stick_params[Settings::NativeAnalog::LStick].Set("deadzone", 0.0f);
|
||||
tas_stick_params[Settings::NativeAnalog::LStick].Set("range", 1.0f);
|
||||
tas_stick_params[Settings::NativeAnalog::RStick].Set("deadzone", 0.0f);
|
||||
tas_stick_params[Settings::NativeAnalog::RStick].Set("range", 1.0f);
|
||||
}
|
||||
|
||||
void EmulatedController::LoadVirtualGamepadParams() {
|
||||
const auto player_index = NpadIdTypeToIndex(npad_id_type);
|
||||
Common::ParamPackage common_params{};
|
||||
common_params.Set("engine", "virtual_gamepad");
|
||||
common_params.Set("port", static_cast<int>(player_index));
|
||||
for (auto& param : virtual_button_params) {
|
||||
param = common_params;
|
||||
}
|
||||
for (auto& param : virtual_stick_params) {
|
||||
param = common_params;
|
||||
}
|
||||
|
||||
// TODO(german77): Replace this with an input profile or something better
|
||||
virtual_button_params[Settings::NativeButton::A].Set("button", 0);
|
||||
virtual_button_params[Settings::NativeButton::B].Set("button", 1);
|
||||
virtual_button_params[Settings::NativeButton::X].Set("button", 2);
|
||||
virtual_button_params[Settings::NativeButton::Y].Set("button", 3);
|
||||
virtual_button_params[Settings::NativeButton::LStick].Set("button", 4);
|
||||
virtual_button_params[Settings::NativeButton::RStick].Set("button", 5);
|
||||
virtual_button_params[Settings::NativeButton::L].Set("button", 6);
|
||||
virtual_button_params[Settings::NativeButton::R].Set("button", 7);
|
||||
virtual_button_params[Settings::NativeButton::ZL].Set("button", 8);
|
||||
virtual_button_params[Settings::NativeButton::ZR].Set("button", 9);
|
||||
virtual_button_params[Settings::NativeButton::Plus].Set("button", 10);
|
||||
virtual_button_params[Settings::NativeButton::Minus].Set("button", 11);
|
||||
virtual_button_params[Settings::NativeButton::DLeft].Set("button", 12);
|
||||
virtual_button_params[Settings::NativeButton::DUp].Set("button", 13);
|
||||
virtual_button_params[Settings::NativeButton::DRight].Set("button", 14);
|
||||
virtual_button_params[Settings::NativeButton::DDown].Set("button", 15);
|
||||
virtual_button_params[Settings::NativeButton::SL].Set("button", 16);
|
||||
virtual_button_params[Settings::NativeButton::SR].Set("button", 17);
|
||||
virtual_button_params[Settings::NativeButton::Home].Set("button", 18);
|
||||
virtual_button_params[Settings::NativeButton::Screenshot].Set("button", 19);
|
||||
|
||||
virtual_stick_params[Settings::NativeAnalog::LStick].Set("axis_x", 0);
|
||||
virtual_stick_params[Settings::NativeAnalog::LStick].Set("axis_y", 1);
|
||||
virtual_stick_params[Settings::NativeAnalog::RStick].Set("axis_x", 2);
|
||||
virtual_stick_params[Settings::NativeAnalog::RStick].Set("axis_y", 3);
|
||||
}
|
||||
|
||||
void EmulatedController::ReloadInput() {
|
||||
@@ -263,6 +339,19 @@ void EmulatedController::ReloadInput() {
|
||||
battery_devices[index]->ForceUpdate();
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < color_devices.size(); ++index) {
|
||||
if (!color_devices[index]) {
|
||||
continue;
|
||||
}
|
||||
color_devices[index]->SetCallback({
|
||||
.on_change =
|
||||
[this, index](const Common::Input::CallbackStatus& callback) {
|
||||
SetColors(callback, index);
|
||||
},
|
||||
});
|
||||
color_devices[index]->ForceUpdate();
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < motion_devices.size(); ++index) {
|
||||
if (!motion_devices[index]) {
|
||||
continue;
|
||||
@@ -276,27 +365,38 @@ void EmulatedController::ReloadInput() {
|
||||
motion_devices[index]->ForceUpdate();
|
||||
}
|
||||
|
||||
if (camera_devices) {
|
||||
camera_devices->SetCallback({
|
||||
for (std::size_t index = 0; index < camera_devices.size(); ++index) {
|
||||
if (!camera_devices[index]) {
|
||||
continue;
|
||||
}
|
||||
camera_devices[index]->SetCallback({
|
||||
.on_change =
|
||||
[this](const Common::Input::CallbackStatus& callback) { SetCamera(callback); },
|
||||
});
|
||||
camera_devices->ForceUpdate();
|
||||
camera_devices[index]->ForceUpdate();
|
||||
}
|
||||
|
||||
if (nfc_devices) {
|
||||
if (npad_id_type == NpadIdType::Handheld || npad_id_type == NpadIdType::Player1) {
|
||||
nfc_devices->SetCallback({
|
||||
.on_change =
|
||||
[this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); },
|
||||
});
|
||||
nfc_devices->ForceUpdate();
|
||||
for (std::size_t index = 0; index < ring_analog_devices.size(); ++index) {
|
||||
if (!ring_analog_devices[index]) {
|
||||
continue;
|
||||
}
|
||||
ring_analog_devices[index]->SetCallback({
|
||||
.on_change =
|
||||
[this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); },
|
||||
});
|
||||
ring_analog_devices[index]->ForceUpdate();
|
||||
}
|
||||
|
||||
// Use a common UUID for TAS
|
||||
static constexpr Common::UUID TAS_UUID = Common::UUID{
|
||||
{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
|
||||
for (std::size_t index = 0; index < nfc_devices.size(); ++index) {
|
||||
if (!nfc_devices[index]) {
|
||||
continue;
|
||||
}
|
||||
nfc_devices[index]->SetCallback({
|
||||
.on_change =
|
||||
[this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); },
|
||||
});
|
||||
nfc_devices[index]->ForceUpdate();
|
||||
}
|
||||
|
||||
// Register TAS devices. No need to force update
|
||||
for (std::size_t index = 0; index < tas_button_devices.size(); ++index) {
|
||||
@@ -322,6 +422,31 @@ void EmulatedController::ReloadInput() {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Register virtual devices. No need to force update
|
||||
for (std::size_t index = 0; index < virtual_button_devices.size(); ++index) {
|
||||
if (!virtual_button_devices[index]) {
|
||||
continue;
|
||||
}
|
||||
virtual_button_devices[index]->SetCallback({
|
||||
.on_change =
|
||||
[this, index](const Common::Input::CallbackStatus& callback) {
|
||||
SetButton(callback, index, VIRTUAL_UUID);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < virtual_stick_devices.size(); ++index) {
|
||||
if (!virtual_stick_devices[index]) {
|
||||
continue;
|
||||
}
|
||||
virtual_stick_devices[index]->SetCallback({
|
||||
.on_change =
|
||||
[this, index](const Common::Input::CallbackStatus& callback) {
|
||||
SetStick(callback, index, VIRTUAL_UUID);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::UnloadInput() {
|
||||
@@ -340,6 +465,9 @@ void EmulatedController::UnloadInput() {
|
||||
for (auto& battery : battery_devices) {
|
||||
battery.reset();
|
||||
}
|
||||
for (auto& color : color_devices) {
|
||||
color.reset();
|
||||
}
|
||||
for (auto& output : output_devices) {
|
||||
output.reset();
|
||||
}
|
||||
@@ -349,8 +477,21 @@ void EmulatedController::UnloadInput() {
|
||||
for (auto& stick : tas_stick_devices) {
|
||||
stick.reset();
|
||||
}
|
||||
camera_devices.reset();
|
||||
nfc_devices.reset();
|
||||
for (auto& button : virtual_button_devices) {
|
||||
button.reset();
|
||||
}
|
||||
for (auto& stick : virtual_stick_devices) {
|
||||
stick.reset();
|
||||
}
|
||||
for (auto& camera : camera_devices) {
|
||||
camera.reset();
|
||||
}
|
||||
for (auto& ring : ring_analog_devices) {
|
||||
ring.reset();
|
||||
}
|
||||
for (auto& nfc : nfc_devices) {
|
||||
nfc.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::EnableConfiguration() {
|
||||
@@ -362,6 +503,11 @@ void EmulatedController::EnableConfiguration() {
|
||||
void EmulatedController::DisableConfiguration() {
|
||||
is_configuring = false;
|
||||
|
||||
// Get Joycon colors before turning on the controller
|
||||
for (const auto& color_device : color_devices) {
|
||||
color_device->ForceUpdate();
|
||||
}
|
||||
|
||||
// Apply temporary npad type to the real controller
|
||||
if (tmp_npad_type != npad_type) {
|
||||
if (is_connected) {
|
||||
@@ -415,6 +561,9 @@ void EmulatedController::SaveCurrentConfig() {
|
||||
for (std::size_t index = 0; index < player.motions.size(); ++index) {
|
||||
player.motions[index] = motion_params[index].Serialize();
|
||||
}
|
||||
if (npad_id_type == NpadIdType::Player1) {
|
||||
Settings::values.ringcon_analogs = ring_params[0].Serialize();
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::RestoreConfig() {
|
||||
@@ -686,12 +835,21 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
|
||||
if (index >= controller.stick_values.size()) {
|
||||
return;
|
||||
}
|
||||
std::unique_lock lock{mutex};
|
||||
auto trigger_guard =
|
||||
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Stick, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
const auto stick_value = TransformToStick(callback);
|
||||
|
||||
// Only read stick values that have the same uuid or are over the threshold to avoid flapping
|
||||
if (controller.stick_values[index].uuid != uuid) {
|
||||
if (!stick_value.down && !stick_value.up && !stick_value.left && !stick_value.right) {
|
||||
const bool is_tas = uuid == TAS_UUID;
|
||||
if (is_tas && stick_value.x.value == 0 && stick_value.y.value == 0) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
}
|
||||
if (!is_tas && !stick_value.down && !stick_value.up && !stick_value.left &&
|
||||
!stick_value.right) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -702,8 +860,6 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
|
||||
if (is_configuring) {
|
||||
controller.analog_stick_state.left = {};
|
||||
controller.analog_stick_state.right = {};
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Stick, false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -728,9 +884,6 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback,
|
||||
controller.npad_button_state.stick_r_down.Assign(controller.stick_values[index].down);
|
||||
break;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Stick, true);
|
||||
}
|
||||
|
||||
void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callback,
|
||||
@@ -738,7 +891,9 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
|
||||
if (index >= controller.trigger_values.size()) {
|
||||
return;
|
||||
}
|
||||
std::unique_lock lock{mutex};
|
||||
auto trigger_guard =
|
||||
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Trigger, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
const auto trigger_value = TransformToTrigger(callback);
|
||||
|
||||
// Only read trigger values that have the same uuid or are pressed once
|
||||
@@ -754,13 +909,12 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
|
||||
if (is_configuring) {
|
||||
controller.gc_trigger_state.left = 0;
|
||||
controller.gc_trigger_state.right = 0;
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Trigger, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only GC controllers have analog triggers
|
||||
if (npad_type != NpadStyleIndex::GameCube) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -777,9 +931,6 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
|
||||
controller.npad_button_state.zr.Assign(trigger.pressed.value);
|
||||
break;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Trigger, true);
|
||||
}
|
||||
|
||||
void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback,
|
||||
@@ -787,7 +938,8 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
|
||||
if (index >= controller.motion_values.size()) {
|
||||
return;
|
||||
}
|
||||
std::unique_lock lock{mutex};
|
||||
SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Motion, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
auto& raw_status = controller.motion_values[index].raw_status;
|
||||
auto& emulated = controller.motion_values[index].emulated;
|
||||
|
||||
@@ -808,8 +960,6 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
|
||||
force_update_motion = raw_status.force_update;
|
||||
|
||||
if (is_configuring) {
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Motion, false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -819,9 +969,56 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
|
||||
motion.rotation = emulated.GetRotations();
|
||||
motion.orientation = emulated.GetOrientation();
|
||||
motion.is_at_rest = !emulated.IsMoving(motion_sensitivity);
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Motion, true);
|
||||
void EmulatedController::SetColors(const Common::Input::CallbackStatus& callback,
|
||||
std::size_t index) {
|
||||
if (index >= controller.color_values.size()) {
|
||||
return;
|
||||
}
|
||||
auto trigger_guard =
|
||||
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Color, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
controller.color_values[index] = TransformToColor(callback);
|
||||
|
||||
if (is_configuring) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (controller.color_values[index].body == 0) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
controller.colors_state.fullkey = {
|
||||
.body = GetNpadColor(controller.color_values[index].body),
|
||||
.button = GetNpadColor(controller.color_values[index].buttons),
|
||||
};
|
||||
if (npad_type == NpadStyleIndex::ProController) {
|
||||
controller.colors_state.left = {
|
||||
.body = GetNpadColor(controller.color_values[index].left_grip),
|
||||
.button = GetNpadColor(controller.color_values[index].buttons),
|
||||
};
|
||||
controller.colors_state.right = {
|
||||
.body = GetNpadColor(controller.color_values[index].right_grip),
|
||||
.button = GetNpadColor(controller.color_values[index].buttons),
|
||||
};
|
||||
} else {
|
||||
switch (index) {
|
||||
case LeftIndex:
|
||||
controller.colors_state.left = {
|
||||
.body = GetNpadColor(controller.color_values[index].body),
|
||||
.button = GetNpadColor(controller.color_values[index].buttons),
|
||||
};
|
||||
break;
|
||||
case RightIndex:
|
||||
controller.colors_state.right = {
|
||||
.body = GetNpadColor(controller.color_values[index].body),
|
||||
.button = GetNpadColor(controller.color_values[index].buttons),
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callback,
|
||||
@@ -829,12 +1026,11 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
|
||||
if (index >= controller.battery_values.size()) {
|
||||
return;
|
||||
}
|
||||
std::unique_lock lock{mutex};
|
||||
SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Battery, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
controller.battery_values[index] = TransformToBattery(callback);
|
||||
|
||||
if (is_configuring) {
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Battery, false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -890,18 +1086,14 @@ void EmulatedController::SetBattery(const Common::Input::CallbackStatus& callbac
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Battery, true);
|
||||
}
|
||||
|
||||
void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback) {
|
||||
std::unique_lock lock{mutex};
|
||||
SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::IrSensor, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
controller.camera_values = TransformToCamera(callback);
|
||||
|
||||
if (is_configuring) {
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::IrSensor, false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -909,18 +1101,28 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback
|
||||
controller.camera_state.format =
|
||||
static_cast<Core::IrSensor::ImageTransferProcessorFormat>(controller.camera_values.format);
|
||||
controller.camera_state.data = controller.camera_values.data;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::IrSensor, true);
|
||||
void EmulatedController::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
|
||||
SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::RingController, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
const auto force_value = TransformToStick(callback);
|
||||
|
||||
controller.ring_analog_value = force_value.x;
|
||||
|
||||
if (is_configuring) {
|
||||
return;
|
||||
}
|
||||
|
||||
controller.ring_analog_state.force = force_value.x.value;
|
||||
}
|
||||
|
||||
void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
|
||||
std::unique_lock lock{mutex};
|
||||
SCOPE_EXIT({ TriggerOnChange(ControllerTriggerType::Nfc, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
controller.nfc_values = TransformToNfc(callback);
|
||||
|
||||
if (is_configuring) {
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Nfc, false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -928,9 +1130,6 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
|
||||
controller.nfc_values.state,
|
||||
controller.nfc_values.data,
|
||||
};
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Nfc, true);
|
||||
}
|
||||
|
||||
bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
|
||||
@@ -962,7 +1161,7 @@ bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue v
|
||||
.type = type,
|
||||
};
|
||||
return output_devices[device_index]->SetVibration(status) ==
|
||||
Common::Input::VibrationError::None;
|
||||
Common::Input::DriverResult::Success;
|
||||
}
|
||||
|
||||
bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
|
||||
@@ -984,16 +1183,32 @@ bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
|
||||
return output_devices[device_index]->IsVibrationEnabled();
|
||||
}
|
||||
|
||||
bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) {
|
||||
LOG_INFO(Service_HID, "Set polling mode {}", polling_mode);
|
||||
auto& output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
|
||||
Common::Input::DriverResult EmulatedController::SetPollingMode(
|
||||
EmulatedDeviceIndex device_index, Common::Input::PollingMode polling_mode) {
|
||||
LOG_INFO(Service_HID, "Set polling mode {}, device_index={}", polling_mode, device_index);
|
||||
|
||||
auto& left_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Left)];
|
||||
auto& right_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
|
||||
auto& nfc_output_device = output_devices[3];
|
||||
|
||||
const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode);
|
||||
const auto mapped_nfc_result = output_device->SetPollingMode(polling_mode);
|
||||
if (device_index == EmulatedDeviceIndex::LeftIndex) {
|
||||
return left_output_device->SetPollingMode(polling_mode);
|
||||
}
|
||||
|
||||
return virtual_nfc_result == Common::Input::PollingError::None ||
|
||||
mapped_nfc_result == Common::Input::PollingError::None;
|
||||
if (device_index == EmulatedDeviceIndex::RightIndex) {
|
||||
const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode);
|
||||
const auto mapped_nfc_result = right_output_device->SetPollingMode(polling_mode);
|
||||
|
||||
if (virtual_nfc_result == Common::Input::DriverResult::Success) {
|
||||
return virtual_nfc_result;
|
||||
}
|
||||
return mapped_nfc_result;
|
||||
}
|
||||
|
||||
left_output_device->SetPollingMode(polling_mode);
|
||||
right_output_device->SetPollingMode(polling_mode);
|
||||
nfc_output_device->SetPollingMode(polling_mode);
|
||||
return Common::Input::DriverResult::Success;
|
||||
}
|
||||
|
||||
bool EmulatedController::SetCameraFormat(
|
||||
@@ -1004,13 +1219,22 @@ bool EmulatedController::SetCameraFormat(
|
||||
auto& camera_output_device = output_devices[2];
|
||||
|
||||
if (right_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>(
|
||||
camera_format)) == Common::Input::CameraError::None) {
|
||||
camera_format)) == Common::Input::DriverResult::Success) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback to Qt camera if native device doesn't have support
|
||||
return camera_output_device->SetCameraFormat(static_cast<Common::Input::CameraFormat>(
|
||||
camera_format)) == Common::Input::CameraError::None;
|
||||
camera_format)) == Common::Input::DriverResult::Success;
|
||||
}
|
||||
|
||||
Common::ParamPackage EmulatedController::GetRingParam() const {
|
||||
return ring_params[0];
|
||||
}
|
||||
|
||||
void EmulatedController::SetRingParam(Common::ParamPackage param) {
|
||||
ring_params[0] = std::move(param);
|
||||
ReloadInput();
|
||||
}
|
||||
|
||||
bool EmulatedController::HasNfc() const {
|
||||
@@ -1164,39 +1388,35 @@ void EmulatedController::Connect(bool use_temporary_value) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock lock{mutex};
|
||||
auto trigger_guard =
|
||||
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Connected, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
if (is_configuring) {
|
||||
tmp_is_connected = true;
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Connected, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_connected) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
}
|
||||
is_connected = true;
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Connected, true);
|
||||
}
|
||||
|
||||
void EmulatedController::Disconnect() {
|
||||
std::unique_lock lock{mutex};
|
||||
auto trigger_guard =
|
||||
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
if (is_configuring) {
|
||||
tmp_is_connected = false;
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Disconnected, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_connected) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
}
|
||||
is_connected = false;
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Disconnected, true);
|
||||
}
|
||||
|
||||
bool EmulatedController::IsConnected(bool get_temporary_value) const {
|
||||
@@ -1221,19 +1441,21 @@ NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) c
|
||||
}
|
||||
|
||||
void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
|
||||
std::unique_lock lock{mutex};
|
||||
auto trigger_guard =
|
||||
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Type, !is_configuring); });
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
if (is_configuring) {
|
||||
if (tmp_npad_type == npad_type_) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
}
|
||||
tmp_npad_type = npad_type_;
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Type, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (npad_type == npad_type_) {
|
||||
trigger_guard.Cancel();
|
||||
return;
|
||||
}
|
||||
if (is_connected) {
|
||||
@@ -1241,9 +1463,6 @@ void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
|
||||
NpadIdTypeToIndex(npad_id_type));
|
||||
}
|
||||
npad_type = npad_type_;
|
||||
|
||||
lock.unlock();
|
||||
TriggerOnChange(ControllerTriggerType::Type, true);
|
||||
}
|
||||
|
||||
LedPattern EmulatedController::GetLedPattern() const {
|
||||
@@ -1304,6 +1523,10 @@ CameraValues EmulatedController::GetCameraValues() const {
|
||||
return controller.camera_values;
|
||||
}
|
||||
|
||||
RingAnalogValue EmulatedController::GetRingSensorValues() const {
|
||||
return controller.ring_analog_value;
|
||||
}
|
||||
|
||||
HomeButtonState EmulatedController::GetHomeButtons() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
if (is_configuring) {
|
||||
@@ -1337,22 +1560,12 @@ DebugPadButton EmulatedController::GetDebugPadButtons() const {
|
||||
}
|
||||
|
||||
AnalogSticks EmulatedController::GetSticks() const {
|
||||
std::unique_lock lock{mutex};
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
if (is_configuring) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Some drivers like stick from buttons need constant refreshing
|
||||
for (auto& device : stick_devices) {
|
||||
if (!device) {
|
||||
continue;
|
||||
}
|
||||
lock.unlock();
|
||||
device->SoftUpdate();
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
return controller.analog_stick_state;
|
||||
}
|
||||
|
||||
@@ -1397,6 +1610,10 @@ const CameraState& EmulatedController::GetCamera() const {
|
||||
return controller.camera_state;
|
||||
}
|
||||
|
||||
RingSensorForce EmulatedController::GetRingSensorForce() const {
|
||||
return controller.ring_analog_state;
|
||||
}
|
||||
|
||||
const NfcState& EmulatedController::GetNfc() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return controller.nfc_state;
|
||||
|
||||
@@ -35,19 +35,27 @@ using ControllerMotionDevices =
|
||||
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeMotion::NumMotions>;
|
||||
using TriggerDevices =
|
||||
std::array<std::unique_ptr<Common::Input::InputDevice>, Settings::NativeTrigger::NumTriggers>;
|
||||
using ColorDevices =
|
||||
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
|
||||
using BatteryDevices =
|
||||
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
|
||||
using CameraDevices = std::unique_ptr<Common::Input::InputDevice>;
|
||||
using NfcDevices = std::unique_ptr<Common::Input::InputDevice>;
|
||||
using CameraDevices =
|
||||
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
|
||||
using RingAnalogDevices =
|
||||
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
|
||||
using NfcDevices =
|
||||
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
|
||||
using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices_size>;
|
||||
|
||||
using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
|
||||
using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
|
||||
using ControllerMotionParams = std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions>;
|
||||
using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
|
||||
using ColorParams = std::array<Common::ParamPackage, max_emulated_controllers>;
|
||||
using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
|
||||
using CameraParams = Common::ParamPackage;
|
||||
using NfcParams = Common::ParamPackage;
|
||||
using CameraParams = std::array<Common::ParamPackage, max_emulated_controllers>;
|
||||
using RingAnalogParams = std::array<Common::ParamPackage, max_emulated_controllers>;
|
||||
using NfcParams = std::array<Common::ParamPackage, max_emulated_controllers>;
|
||||
using OutputParams = std::array<Common::ParamPackage, output_devices_size>;
|
||||
|
||||
using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
|
||||
@@ -58,6 +66,7 @@ using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::Native
|
||||
using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;
|
||||
using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;
|
||||
using CameraValues = Common::Input::CameraStatus;
|
||||
using RingAnalogValue = Common::Input::AnalogStatus;
|
||||
using NfcValues = Common::Input::NfcStatus;
|
||||
using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>;
|
||||
|
||||
@@ -84,6 +93,10 @@ struct CameraState {
|
||||
std::size_t sample{};
|
||||
};
|
||||
|
||||
struct RingSensorForce {
|
||||
f32 force;
|
||||
};
|
||||
|
||||
struct NfcState {
|
||||
Common::Input::NfcState state{};
|
||||
std::vector<u8> data{};
|
||||
@@ -116,6 +129,7 @@ struct ControllerStatus {
|
||||
BatteryValues battery_values{};
|
||||
VibrationValues vibration_values{};
|
||||
CameraValues camera_values{};
|
||||
RingAnalogValue ring_analog_value{};
|
||||
NfcValues nfc_values{};
|
||||
|
||||
// Data for HID serices
|
||||
@@ -129,6 +143,7 @@ struct ControllerStatus {
|
||||
ControllerColors colors_state{};
|
||||
BatteryLevelState battery_state{};
|
||||
CameraState camera_state{};
|
||||
RingSensorForce ring_analog_state{};
|
||||
NfcState nfc_state{};
|
||||
};
|
||||
|
||||
@@ -141,6 +156,7 @@ enum class ControllerTriggerType {
|
||||
Battery,
|
||||
Vibration,
|
||||
IrSensor,
|
||||
RingController,
|
||||
Nfc,
|
||||
Connected,
|
||||
Disconnected,
|
||||
@@ -294,6 +310,9 @@ public:
|
||||
/// Returns the latest camera status from the controller with parameters
|
||||
CameraValues GetCameraValues() const;
|
||||
|
||||
/// Returns the latest status of analog input from the ring sensor with parameters
|
||||
RingAnalogValue GetRingSensorValues() const;
|
||||
|
||||
/// Returns the latest status of button input for the hid::HomeButton service
|
||||
HomeButtonState GetHomeButtons() const;
|
||||
|
||||
@@ -324,6 +343,9 @@ public:
|
||||
/// Returns the latest camera status from the controller
|
||||
const CameraState& GetCamera() const;
|
||||
|
||||
/// Returns the latest ringcon force sensor value
|
||||
RingSensorForce GetRingSensorForce() const;
|
||||
|
||||
/// Returns the latest ntag status from the controller
|
||||
const NfcState& GetNfc() const;
|
||||
|
||||
@@ -341,10 +363,12 @@ public:
|
||||
|
||||
/**
|
||||
* Sets the desired data to be polled from a controller
|
||||
* @param device_index index of the controller to set the polling mode
|
||||
* @param polling_mode type of input desired buttons, gyro, nfc, ir, etc.
|
||||
* @return true if SetPollingMode was successfull
|
||||
* @return driver result from this command
|
||||
*/
|
||||
bool SetPollingMode(Common::Input::PollingMode polling_mode);
|
||||
Common::Input::DriverResult SetPollingMode(EmulatedDeviceIndex device_index,
|
||||
Common::Input::PollingMode polling_mode);
|
||||
|
||||
/**
|
||||
* Sets the desired camera format to be polled from a controller
|
||||
@@ -353,6 +377,15 @@ public:
|
||||
*/
|
||||
bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format);
|
||||
|
||||
// Returns the current mapped ring device
|
||||
Common::ParamPackage GetRingParam() const;
|
||||
|
||||
/**
|
||||
* Updates the current mapped ring device
|
||||
* @param param ParamPackage with ring sensor data to be mapped
|
||||
*/
|
||||
void SetRingParam(Common::ParamPackage param);
|
||||
|
||||
/// Returns true if the device has nfc support
|
||||
bool HasNfc() const;
|
||||
|
||||
@@ -385,6 +418,9 @@ private:
|
||||
/// Set the params for TAS devices
|
||||
void LoadTASParams();
|
||||
|
||||
/// Set the params for virtual pad devices
|
||||
void LoadVirtualGamepadParams();
|
||||
|
||||
/**
|
||||
* @param use_temporary_value If true tmp_npad_type will be used
|
||||
* @return true if the controller style is fullkey
|
||||
@@ -429,10 +465,17 @@ private:
|
||||
*/
|
||||
void SetMotion(const Common::Input::CallbackStatus& callback, std::size_t index);
|
||||
|
||||
/**
|
||||
* Updates the color status of the controller
|
||||
* @param callback A CallbackStatus containing the color status
|
||||
* @param index color ID of the to be updated
|
||||
*/
|
||||
void SetColors(const Common::Input::CallbackStatus& callback, std::size_t index);
|
||||
|
||||
/**
|
||||
* Updates the battery status of the controller
|
||||
* @param callback A CallbackStatus containing the battery status
|
||||
* @param index Button ID of the to be updated
|
||||
* @param index battery ID of the to be updated
|
||||
*/
|
||||
void SetBattery(const Common::Input::CallbackStatus& callback, std::size_t index);
|
||||
|
||||
@@ -442,6 +485,12 @@ private:
|
||||
*/
|
||||
void SetCamera(const Common::Input::CallbackStatus& callback);
|
||||
|
||||
/**
|
||||
* Updates the ring analog sensor status of the ring controller
|
||||
* @param callback A CallbackStatus containing the force status
|
||||
*/
|
||||
void SetRingAnalog(const Common::Input::CallbackStatus& callback);
|
||||
|
||||
/**
|
||||
* Updates the nfc status of the controller
|
||||
* @param callback A CallbackStatus containing the nfc status
|
||||
@@ -481,7 +530,9 @@ private:
|
||||
ControllerMotionParams motion_params;
|
||||
TriggerParams trigger_params;
|
||||
BatteryParams battery_params;
|
||||
ColorParams color_params;
|
||||
CameraParams camera_params;
|
||||
RingAnalogParams ring_params;
|
||||
NfcParams nfc_params;
|
||||
OutputParams output_params;
|
||||
|
||||
@@ -490,7 +541,9 @@ private:
|
||||
ControllerMotionDevices motion_devices;
|
||||
TriggerDevices trigger_devices;
|
||||
BatteryDevices battery_devices;
|
||||
ColorDevices color_devices;
|
||||
CameraDevices camera_devices;
|
||||
RingAnalogDevices ring_analog_devices;
|
||||
NfcDevices nfc_devices;
|
||||
OutputDevices output_devices;
|
||||
|
||||
@@ -500,6 +553,12 @@ private:
|
||||
ButtonDevices tas_button_devices;
|
||||
StickDevices tas_stick_devices;
|
||||
|
||||
// Virtual gamepad related variables
|
||||
ButtonParams virtual_button_params;
|
||||
StickParams virtual_stick_params;
|
||||
ButtonDevices virtual_button_devices;
|
||||
StickDevices virtual_stick_devices;
|
||||
|
||||
mutable std::mutex mutex;
|
||||
mutable std::mutex callback_mutex;
|
||||
std::unordered_map<int, ControllerUpdateCallback> callback_list;
|
||||
|
||||
@@ -14,7 +14,6 @@ EmulatedDevices::EmulatedDevices() = default;
|
||||
EmulatedDevices::~EmulatedDevices() = default;
|
||||
|
||||
void EmulatedDevices::ReloadFromSettings() {
|
||||
ring_params = Common::ParamPackage(Settings::values.ringcon_analogs);
|
||||
ReloadInput();
|
||||
}
|
||||
|
||||
@@ -66,8 +65,6 @@ void EmulatedDevices::ReloadInput() {
|
||||
key_index++;
|
||||
}
|
||||
|
||||
ring_analog_device = Common::Input::CreateInputDevice(ring_params);
|
||||
|
||||
for (std::size_t index = 0; index < mouse_button_devices.size(); ++index) {
|
||||
if (!mouse_button_devices[index]) {
|
||||
continue;
|
||||
@@ -122,13 +119,6 @@ void EmulatedDevices::ReloadInput() {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (ring_analog_device) {
|
||||
ring_analog_device->SetCallback({
|
||||
.on_change =
|
||||
[this](const Common::Input::CallbackStatus& callback) { SetRingAnalog(callback); },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void EmulatedDevices::UnloadInput() {
|
||||
@@ -145,7 +135,6 @@ void EmulatedDevices::UnloadInput() {
|
||||
for (auto& button : keyboard_modifier_devices) {
|
||||
button.reset();
|
||||
}
|
||||
ring_analog_device.reset();
|
||||
}
|
||||
|
||||
void EmulatedDevices::EnableConfiguration() {
|
||||
@@ -165,7 +154,6 @@ void EmulatedDevices::SaveCurrentConfig() {
|
||||
if (!is_configuring) {
|
||||
return;
|
||||
}
|
||||
Settings::values.ringcon_analogs = ring_params.Serialize();
|
||||
}
|
||||
|
||||
void EmulatedDevices::RestoreConfig() {
|
||||
@@ -175,15 +163,6 @@ void EmulatedDevices::RestoreConfig() {
|
||||
ReloadFromSettings();
|
||||
}
|
||||
|
||||
Common::ParamPackage EmulatedDevices::GetRingParam() const {
|
||||
return ring_params;
|
||||
}
|
||||
|
||||
void EmulatedDevices::SetRingParam(Common::ParamPackage param) {
|
||||
ring_params = std::move(param);
|
||||
ReloadInput();
|
||||
}
|
||||
|
||||
void EmulatedDevices::SetKeyboardButton(const Common::Input::CallbackStatus& callback,
|
||||
std::size_t index) {
|
||||
if (index >= device_status.keyboard_values.size()) {
|
||||
@@ -430,23 +409,6 @@ void EmulatedDevices::SetMouseStick(const Common::Input::CallbackStatus& callbac
|
||||
TriggerOnChange(DeviceTriggerType::Mouse);
|
||||
}
|
||||
|
||||
void EmulatedDevices::SetRingAnalog(const Common::Input::CallbackStatus& callback) {
|
||||
std::lock_guard lock{mutex};
|
||||
const auto force_value = TransformToStick(callback);
|
||||
|
||||
device_status.ring_analog_value = force_value.x;
|
||||
|
||||
if (is_configuring) {
|
||||
device_status.ring_analog_value = {};
|
||||
TriggerOnChange(DeviceTriggerType::RingController);
|
||||
return;
|
||||
}
|
||||
|
||||
device_status.ring_analog_state.force = force_value.x.value;
|
||||
|
||||
TriggerOnChange(DeviceTriggerType::RingController);
|
||||
}
|
||||
|
||||
KeyboardValues EmulatedDevices::GetKeyboardValues() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return device_status.keyboard_values;
|
||||
@@ -462,10 +424,6 @@ MouseButtonValues EmulatedDevices::GetMouseButtonsValues() const {
|
||||
return device_status.mouse_button_values;
|
||||
}
|
||||
|
||||
RingAnalogValue EmulatedDevices::GetRingSensorValues() const {
|
||||
return device_status.ring_analog_value;
|
||||
}
|
||||
|
||||
KeyboardKey EmulatedDevices::GetKeyboard() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return device_status.keyboard_state;
|
||||
@@ -491,10 +449,6 @@ AnalogStickState EmulatedDevices::GetMouseWheel() const {
|
||||
return device_status.mouse_wheel_state;
|
||||
}
|
||||
|
||||
RingSensorForce EmulatedDevices::GetRingSensorForce() const {
|
||||
return device_status.ring_analog_state;
|
||||
}
|
||||
|
||||
void EmulatedDevices::TriggerOnChange(DeviceTriggerType type) {
|
||||
std::scoped_lock lock{callback_mutex};
|
||||
for (const auto& poller_pair : callback_list) {
|
||||
|
||||
@@ -26,11 +26,9 @@ using MouseButtonDevices = std::array<std::unique_ptr<Common::Input::InputDevice
|
||||
using MouseAnalogDevices = std::array<std::unique_ptr<Common::Input::InputDevice>,
|
||||
Settings::NativeMouseWheel::NumMouseWheels>;
|
||||
using MouseStickDevice = std::unique_ptr<Common::Input::InputDevice>;
|
||||
using RingAnalogDevice = std::unique_ptr<Common::Input::InputDevice>;
|
||||
|
||||
using MouseButtonParams =
|
||||
std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons>;
|
||||
using RingAnalogParams = Common::ParamPackage;
|
||||
|
||||
using KeyboardValues =
|
||||
std::array<Common::Input::ButtonStatus, Settings::NativeKeyboard::NumKeyboardKeys>;
|
||||
@@ -41,17 +39,12 @@ using MouseButtonValues =
|
||||
using MouseAnalogValues =
|
||||
std::array<Common::Input::AnalogStatus, Settings::NativeMouseWheel::NumMouseWheels>;
|
||||
using MouseStickValue = Common::Input::TouchStatus;
|
||||
using RingAnalogValue = Common::Input::AnalogStatus;
|
||||
|
||||
struct MousePosition {
|
||||
f32 x;
|
||||
f32 y;
|
||||
};
|
||||
|
||||
struct RingSensorForce {
|
||||
f32 force;
|
||||
};
|
||||
|
||||
struct DeviceStatus {
|
||||
// Data from input_common
|
||||
KeyboardValues keyboard_values{};
|
||||
@@ -59,7 +52,6 @@ struct DeviceStatus {
|
||||
MouseButtonValues mouse_button_values{};
|
||||
MouseAnalogValues mouse_analog_values{};
|
||||
MouseStickValue mouse_stick_value{};
|
||||
RingAnalogValue ring_analog_value{};
|
||||
|
||||
// Data for HID serices
|
||||
KeyboardKey keyboard_state{};
|
||||
@@ -67,7 +59,6 @@ struct DeviceStatus {
|
||||
MouseButton mouse_button_state{};
|
||||
MousePosition mouse_position_state{};
|
||||
AnalogStickState mouse_wheel_state{};
|
||||
RingSensorForce ring_analog_state{};
|
||||
};
|
||||
|
||||
enum class DeviceTriggerType {
|
||||
@@ -138,9 +129,6 @@ public:
|
||||
/// Returns the latest status of button input from the mouse with parameters
|
||||
MouseButtonValues GetMouseButtonsValues() const;
|
||||
|
||||
/// Returns the latest status of analog input from the ring sensor with parameters
|
||||
RingAnalogValue GetRingSensorValues() const;
|
||||
|
||||
/// Returns the latest status of button input from the keyboard
|
||||
KeyboardKey GetKeyboard() const;
|
||||
|
||||
@@ -156,9 +144,6 @@ public:
|
||||
/// Returns the latest mouse wheel change
|
||||
AnalogStickState GetMouseWheel() const;
|
||||
|
||||
/// Returns the latest ringcon force sensor value
|
||||
RingSensorForce GetRingSensorForce() const;
|
||||
|
||||
/**
|
||||
* Adds a callback to the list of events
|
||||
* @param update_callback InterfaceUpdateCallback that will be triggered
|
||||
@@ -224,14 +209,11 @@ private:
|
||||
|
||||
bool is_configuring{false};
|
||||
|
||||
RingAnalogParams ring_params;
|
||||
|
||||
KeyboardDevices keyboard_devices;
|
||||
KeyboardModifierDevices keyboard_modifier_devices;
|
||||
MouseButtonDevices mouse_button_devices;
|
||||
MouseAnalogDevices mouse_analog_devices;
|
||||
MouseStickDevice mouse_stick_device;
|
||||
RingAnalogDevice ring_analog_device;
|
||||
|
||||
mutable std::mutex mutex;
|
||||
mutable std::mutex callback_mutex;
|
||||
|
||||
@@ -304,6 +304,18 @@ Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& cal
|
||||
return nfc;
|
||||
}
|
||||
|
||||
Common::Input::BodyColorStatus TransformToColor(const Common::Input::CallbackStatus& callback) {
|
||||
switch (callback.type) {
|
||||
case Common::Input::InputType::Color:
|
||||
return callback.color_status;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Input, "Conversion from type {} to color not implemented", callback.type);
|
||||
return {};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {
|
||||
const auto& properties = analog.properties;
|
||||
float& raw_value = analog.raw_value;
|
||||
|
||||
@@ -88,10 +88,18 @@ Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatu
|
||||
* Converts raw input data into a valid nfc status.
|
||||
*
|
||||
* @param callback Supported callbacks: Nfc.
|
||||
* @return A valid CameraObject object.
|
||||
* @return A valid data tag vector.
|
||||
*/
|
||||
Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback);
|
||||
|
||||
/**
|
||||
* Converts raw input data into a valid color status.
|
||||
*
|
||||
* @param callback Supported callbacks: Color.
|
||||
* @return A valid Color object.
|
||||
*/
|
||||
Common::Input::BodyColorStatus TransformToColor(const Common::Input::CallbackStatus& callback);
|
||||
|
||||
/**
|
||||
* Converts raw analog data into a valid analog value
|
||||
* @param analog An analog object containing raw data and properties
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "core/hle/kernel/k_thread_queue.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/svc_results.h"
|
||||
#include "core/hle/kernel/time_manager.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
@@ -27,13 +27,13 @@ Result KCodeMemory::Initialize(Core::DeviceMemory& device_memory, VAddr addr, si
|
||||
auto& page_table = m_owner->PageTable();
|
||||
|
||||
// Construct the page group.
|
||||
m_page_group = {};
|
||||
m_page_group.emplace(kernel, page_table.GetBlockInfoManager());
|
||||
|
||||
// Lock the memory.
|
||||
R_TRY(page_table.LockForCodeMemory(&m_page_group, addr, size))
|
||||
R_TRY(page_table.LockForCodeMemory(std::addressof(*m_page_group), addr, size))
|
||||
|
||||
// Clear the memory.
|
||||
for (const auto& block : m_page_group.Nodes()) {
|
||||
for (const auto& block : *m_page_group) {
|
||||
std::memset(device_memory.GetPointer<void>(block.GetAddress()), 0xFF, block.GetSize());
|
||||
}
|
||||
|
||||
@@ -51,12 +51,13 @@ Result KCodeMemory::Initialize(Core::DeviceMemory& device_memory, VAddr addr, si
|
||||
void KCodeMemory::Finalize() {
|
||||
// Unlock.
|
||||
if (!m_is_mapped && !m_is_owner_mapped) {
|
||||
const size_t size = m_page_group.GetNumPages() * PageSize;
|
||||
m_owner->PageTable().UnlockForCodeMemory(m_address, size, m_page_group);
|
||||
const size_t size = m_page_group->GetNumPages() * PageSize;
|
||||
m_owner->PageTable().UnlockForCodeMemory(m_address, size, *m_page_group);
|
||||
}
|
||||
|
||||
// Close the page group.
|
||||
m_page_group = {};
|
||||
m_page_group->Close();
|
||||
m_page_group->Finalize();
|
||||
|
||||
// Close our reference to our owner.
|
||||
m_owner->Close();
|
||||
@@ -64,7 +65,7 @@ void KCodeMemory::Finalize() {
|
||||
|
||||
Result KCodeMemory::Map(VAddr address, size_t size) {
|
||||
// Validate the size.
|
||||
R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
|
||||
R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
|
||||
|
||||
// Lock ourselves.
|
||||
KScopedLightLock lk(m_lock);
|
||||
@@ -73,8 +74,8 @@ Result KCodeMemory::Map(VAddr address, size_t size) {
|
||||
R_UNLESS(!m_is_mapped, ResultInvalidState);
|
||||
|
||||
// Map the memory.
|
||||
R_TRY(kernel.CurrentProcess()->PageTable().MapPages(
|
||||
address, m_page_group, KMemoryState::CodeOut, KMemoryPermission::UserReadWrite));
|
||||
R_TRY(kernel.CurrentProcess()->PageTable().MapPageGroup(
|
||||
address, *m_page_group, KMemoryState::CodeOut, KMemoryPermission::UserReadWrite));
|
||||
|
||||
// Mark ourselves as mapped.
|
||||
m_is_mapped = true;
|
||||
@@ -84,14 +85,14 @@ Result KCodeMemory::Map(VAddr address, size_t size) {
|
||||
|
||||
Result KCodeMemory::Unmap(VAddr address, size_t size) {
|
||||
// Validate the size.
|
||||
R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
|
||||
R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
|
||||
|
||||
// Lock ourselves.
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
// Unmap the memory.
|
||||
R_TRY(kernel.CurrentProcess()->PageTable().UnmapPages(address, m_page_group,
|
||||
KMemoryState::CodeOut));
|
||||
R_TRY(kernel.CurrentProcess()->PageTable().UnmapPageGroup(address, *m_page_group,
|
||||
KMemoryState::CodeOut));
|
||||
|
||||
// Mark ourselves as unmapped.
|
||||
m_is_mapped = false;
|
||||
@@ -101,7 +102,7 @@ Result KCodeMemory::Unmap(VAddr address, size_t size) {
|
||||
|
||||
Result KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermission perm) {
|
||||
// Validate the size.
|
||||
R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
|
||||
R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
|
||||
|
||||
// Lock ourselves.
|
||||
KScopedLightLock lk(m_lock);
|
||||
@@ -124,8 +125,8 @@ Result KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermission
|
||||
}
|
||||
|
||||
// Map the memory.
|
||||
R_TRY(
|
||||
m_owner->PageTable().MapPages(address, m_page_group, KMemoryState::GeneratedCode, k_perm));
|
||||
R_TRY(m_owner->PageTable().MapPageGroup(address, *m_page_group, KMemoryState::GeneratedCode,
|
||||
k_perm));
|
||||
|
||||
// Mark ourselves as mapped.
|
||||
m_is_owner_mapped = true;
|
||||
@@ -135,13 +136,13 @@ Result KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermission
|
||||
|
||||
Result KCodeMemory::UnmapFromOwner(VAddr address, size_t size) {
|
||||
// Validate the size.
|
||||
R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
|
||||
R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
|
||||
|
||||
// Lock ourselves.
|
||||
KScopedLightLock lk(m_lock);
|
||||
|
||||
// Unmap the memory.
|
||||
R_TRY(m_owner->PageTable().UnmapPages(address, m_page_group, KMemoryState::GeneratedCode));
|
||||
R_TRY(m_owner->PageTable().UnmapPageGroup(address, *m_page_group, KMemoryState::GeneratedCode));
|
||||
|
||||
// Mark ourselves as unmapped.
|
||||
m_is_owner_mapped = false;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/device_memory.h"
|
||||
#include "core/hle/kernel/k_auto_object.h"
|
||||
@@ -49,11 +51,11 @@ public:
|
||||
return m_address;
|
||||
}
|
||||
size_t GetSize() const {
|
||||
return m_is_initialized ? m_page_group.GetNumPages() * PageSize : 0;
|
||||
return m_is_initialized ? m_page_group->GetNumPages() * PageSize : 0;
|
||||
}
|
||||
|
||||
private:
|
||||
KPageGroup m_page_group{};
|
||||
std::optional<KPageGroup> m_page_group{};
|
||||
KProcess* m_owner{};
|
||||
VAddr m_address{};
|
||||
KLightLock m_lock;
|
||||
|
||||
@@ -171,7 +171,7 @@ Result KConditionVariable::WaitForAddress(Handle handle, VAddr addr, u32 value)
|
||||
R_UNLESS(owner_thread != nullptr, ResultInvalidHandle);
|
||||
|
||||
// Update the lock.
|
||||
cur_thread->SetAddressKey(addr, value);
|
||||
cur_thread->SetUserAddressKey(addr, value);
|
||||
owner_thread->AddWaiter(cur_thread);
|
||||
|
||||
// Begin waiting.
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/k_hardware_timer.h"
|
||||
#include "core/hle/kernel/k_scheduler.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
void KHardwareTimer::Initialize() {
|
||||
// Create the timing callback to register with CoreTiming.
|
||||
m_event_type = Core::Timing::CreateEvent(
|
||||
"KHardwareTimer::Callback", [](std::uintptr_t timer_handle, s64, std::chrono::nanoseconds) {
|
||||
reinterpret_cast<KHardwareTimer*>(timer_handle)->DoTask();
|
||||
return std::nullopt;
|
||||
});
|
||||
}
|
||||
|
||||
void KHardwareTimer::Finalize() {
|
||||
m_kernel.System().CoreTiming().UnscheduleEvent(m_event_type, reinterpret_cast<uintptr_t>(this));
|
||||
m_wakeup_time = std::numeric_limits<s64>::max();
|
||||
m_event_type.reset();
|
||||
}
|
||||
|
||||
void KHardwareTimer::DoTask() {
|
||||
// Handle the interrupt.
|
||||
{
|
||||
KScopedSchedulerLock slk{m_kernel};
|
||||
KScopedSpinLock lk(this->GetLock());
|
||||
|
||||
//! Ignore this event if needed.
|
||||
if (!this->GetInterruptEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable the timer interrupt while we handle this.
|
||||
this->DisableInterrupt();
|
||||
|
||||
if (const s64 next_time = this->DoInterruptTaskImpl(GetTick());
|
||||
0 < next_time && next_time <= m_wakeup_time) {
|
||||
// We have a next time, so we should set the time to interrupt and turn the interrupt
|
||||
// on.
|
||||
this->EnableInterrupt(next_time);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the timer interrupt.
|
||||
// Kernel::GetInterruptManager().ClearInterrupt(KInterruptName_NonSecurePhysicalTimer,
|
||||
// GetCurrentCoreId());
|
||||
}
|
||||
|
||||
void KHardwareTimer::EnableInterrupt(s64 wakeup_time) {
|
||||
this->DisableInterrupt();
|
||||
|
||||
m_wakeup_time = wakeup_time;
|
||||
m_kernel.System().CoreTiming().ScheduleEvent(std::chrono::nanoseconds{m_wakeup_time},
|
||||
m_event_type, reinterpret_cast<uintptr_t>(this),
|
||||
true);
|
||||
}
|
||||
|
||||
void KHardwareTimer::DisableInterrupt() {
|
||||
m_kernel.System().CoreTiming().UnscheduleEventWithoutWait(m_event_type,
|
||||
reinterpret_cast<uintptr_t>(this));
|
||||
m_wakeup_time = std::numeric_limits<s64>::max();
|
||||
}
|
||||
|
||||
s64 KHardwareTimer::GetTick() const {
|
||||
return m_kernel.System().CoreTiming().GetGlobalTimeNs().count();
|
||||
}
|
||||
|
||||
bool KHardwareTimer::GetInterruptEnabled() {
|
||||
return m_wakeup_time != std::numeric_limits<s64>::max();
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/kernel/k_hardware_timer_base.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
struct EventType;
|
||||
} // namespace Core::Timing
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class KHardwareTimer : /* public KInterruptTask, */ public KHardwareTimerBase {
|
||||
public:
|
||||
explicit KHardwareTimer(KernelCore& kernel) : KHardwareTimerBase{kernel} {}
|
||||
|
||||
// Public API.
|
||||
void Initialize();
|
||||
void Finalize();
|
||||
|
||||
s64 GetCount() const {
|
||||
return GetTick();
|
||||
}
|
||||
|
||||
void RegisterTask(KTimerTask* task, s64 time_from_now) {
|
||||
this->RegisterAbsoluteTask(task, GetTick() + time_from_now);
|
||||
}
|
||||
|
||||
void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) {
|
||||
KScopedDisableDispatch dd{m_kernel};
|
||||
KScopedSpinLock lk{this->GetLock()};
|
||||
|
||||
if (this->RegisterAbsoluteTaskImpl(task, task_time)) {
|
||||
if (task_time <= m_wakeup_time) {
|
||||
this->EnableInterrupt(task_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void EnableInterrupt(s64 wakeup_time);
|
||||
void DisableInterrupt();
|
||||
bool GetInterruptEnabled();
|
||||
s64 GetTick() const;
|
||||
void DoTask();
|
||||
|
||||
private:
|
||||
// Absolute time in nanoseconds
|
||||
s64 m_wakeup_time{std::numeric_limits<s64>::max()};
|
||||
std::shared_ptr<Core::Timing::EventType> m_event_type{};
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -0,0 +1,92 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/kernel/k_spin_lock.h"
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/hle/kernel/k_timer_task.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class KHardwareTimerBase {
|
||||
public:
|
||||
explicit KHardwareTimerBase(KernelCore& kernel) : m_kernel{kernel} {}
|
||||
|
||||
void CancelTask(KTimerTask* task) {
|
||||
KScopedDisableDispatch dd{m_kernel};
|
||||
KScopedSpinLock lk{m_lock};
|
||||
|
||||
if (const s64 task_time = task->GetTime(); task_time > 0) {
|
||||
this->RemoveTaskFromTree(task);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
KSpinLock& GetLock() {
|
||||
return m_lock;
|
||||
}
|
||||
|
||||
s64 DoInterruptTaskImpl(s64 cur_time) {
|
||||
// We want to handle all tasks, returning the next time that a task is scheduled.
|
||||
while (true) {
|
||||
// Get the next task. If there isn't one, return 0.
|
||||
KTimerTask* task = m_next_task;
|
||||
if (task == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If the task needs to be done in the future, do it in the future and not now.
|
||||
if (const s64 task_time = task->GetTime(); task_time > cur_time) {
|
||||
return task_time;
|
||||
}
|
||||
|
||||
// Remove the task from the tree of tasks, and update our next task.
|
||||
this->RemoveTaskFromTree(task);
|
||||
|
||||
// Handle the task.
|
||||
task->OnTimer();
|
||||
}
|
||||
}
|
||||
|
||||
bool RegisterAbsoluteTaskImpl(KTimerTask* task, s64 task_time) {
|
||||
ASSERT(task_time > 0);
|
||||
|
||||
// Set the task's time, and insert it into our tree.
|
||||
task->SetTime(task_time);
|
||||
m_task_tree.insert(*task);
|
||||
|
||||
// Update our next task if relevant.
|
||||
if (m_next_task != nullptr && m_next_task->GetTime() <= task_time) {
|
||||
return false;
|
||||
}
|
||||
m_next_task = task;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
void RemoveTaskFromTree(KTimerTask* task) {
|
||||
// Erase from the tree.
|
||||
auto it = m_task_tree.erase(m_task_tree.iterator_to(*task));
|
||||
|
||||
// Clear the task's scheduled time.
|
||||
task->SetTime(0);
|
||||
|
||||
// Update our next task if relevant.
|
||||
if (m_next_task == task) {
|
||||
m_next_task = (it != m_task_tree.end()) ? std::addressof(*it) : nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
KernelCore& m_kernel;
|
||||
|
||||
private:
|
||||
using TimerTaskTree = Common::IntrusiveRedBlackTreeBaseTraits<KTimerTask>::TreeType<KTimerTask>;
|
||||
|
||||
KSpinLock m_lock{};
|
||||
TimerTaskTree m_task_tree{};
|
||||
KTimerTask* m_next_task{};
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -68,7 +68,7 @@ bool KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) {
|
||||
|
||||
// Add the current thread as a waiter on the owner.
|
||||
KThread* owner_thread = reinterpret_cast<KThread*>(_owner & ~1ULL);
|
||||
cur_thread->SetAddressKey(reinterpret_cast<uintptr_t>(std::addressof(tag)));
|
||||
cur_thread->SetKernelAddressKey(reinterpret_cast<uintptr_t>(std::addressof(tag)));
|
||||
owner_thread->AddWaiter(cur_thread);
|
||||
|
||||
// Begin waiting to hold the lock.
|
||||
|
||||
@@ -67,9 +67,9 @@ constexpr size_t KernelPageBufferAdditionalSize = 0x33C000;
|
||||
constexpr std::size_t KernelResourceSize = KernelPageTableHeapSize + KernelInitialPageHeapSize +
|
||||
KernelSlabHeapSize + KernelPageBufferHeapSize;
|
||||
|
||||
constexpr bool IsKernelAddressKey(VAddr key) {
|
||||
return KernelVirtualAddressSpaceBase <= key && key <= KernelVirtualAddressSpaceLast;
|
||||
}
|
||||
//! NB: Use KThread::GetAddressKeyIsKernel().
|
||||
//! See explanation for deviation of GetAddressKey.
|
||||
bool IsKernelAddressKey(VAddr key) = delete;
|
||||
|
||||
constexpr bool IsKernelAddress(VAddr address) {
|
||||
return KernelVirtualAddressSpaceBase <= address && address < KernelVirtualAddressSpaceEnd;
|
||||
|
||||
@@ -223,7 +223,7 @@ Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages,
|
||||
|
||||
// Ensure that we don't leave anything un-freed.
|
||||
ON_RESULT_FAILURE {
|
||||
for (const auto& it : out->Nodes()) {
|
||||
for (const auto& it : *out) {
|
||||
auto& manager = this->GetManager(it.GetAddress());
|
||||
const size_t node_num_pages = std::min<u64>(
|
||||
it.GetNumPages(), (manager.GetEndAddress() - it.GetAddress()) / PageSize);
|
||||
@@ -285,7 +285,7 @@ Result KMemoryManager::AllocateAndOpen(KPageGroup* out, size_t num_pages, u32 op
|
||||
m_has_optimized_process[static_cast<size_t>(pool)], true));
|
||||
|
||||
// Open the first reference to the pages.
|
||||
for (const auto& block : out->Nodes()) {
|
||||
for (const auto& block : *out) {
|
||||
PAddr cur_address = block.GetAddress();
|
||||
size_t remaining_pages = block.GetNumPages();
|
||||
while (remaining_pages > 0) {
|
||||
@@ -335,7 +335,7 @@ Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32
|
||||
// Perform optimized memory tracking, if we should.
|
||||
if (optimized) {
|
||||
// Iterate over the allocated blocks.
|
||||
for (const auto& block : out->Nodes()) {
|
||||
for (const auto& block : *out) {
|
||||
// Get the block extents.
|
||||
const PAddr block_address = block.GetAddress();
|
||||
const size_t block_pages = block.GetNumPages();
|
||||
@@ -391,7 +391,7 @@ Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32
|
||||
}
|
||||
} else {
|
||||
// Set all the allocated memory.
|
||||
for (const auto& block : out->Nodes()) {
|
||||
for (const auto& block : *out) {
|
||||
std::memset(m_system.DeviceMemory().GetPointer<void>(block.GetAddress()), fill_pattern,
|
||||
block.GetSize());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/kernel/k_dynamic_resource_manager.h"
|
||||
#include "core/hle/kernel/k_memory_manager.h"
|
||||
#include "core/hle/kernel/k_page_group.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/svc_results.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
void KPageGroup::Finalize() {
|
||||
KBlockInfo* cur = m_first_block;
|
||||
while (cur != nullptr) {
|
||||
KBlockInfo* next = cur->GetNext();
|
||||
m_manager->Free(cur);
|
||||
cur = next;
|
||||
}
|
||||
|
||||
m_first_block = nullptr;
|
||||
m_last_block = nullptr;
|
||||
}
|
||||
|
||||
void KPageGroup::CloseAndReset() {
|
||||
auto& mm = m_kernel.MemoryManager();
|
||||
|
||||
KBlockInfo* cur = m_first_block;
|
||||
while (cur != nullptr) {
|
||||
KBlockInfo* next = cur->GetNext();
|
||||
mm.Close(cur->GetAddress(), cur->GetNumPages());
|
||||
m_manager->Free(cur);
|
||||
cur = next;
|
||||
}
|
||||
|
||||
m_first_block = nullptr;
|
||||
m_last_block = nullptr;
|
||||
}
|
||||
|
||||
size_t KPageGroup::GetNumPages() const {
|
||||
size_t num_pages = 0;
|
||||
|
||||
for (const auto& it : *this) {
|
||||
num_pages += it.GetNumPages();
|
||||
}
|
||||
|
||||
return num_pages;
|
||||
}
|
||||
|
||||
Result KPageGroup::AddBlock(KPhysicalAddress addr, size_t num_pages) {
|
||||
// Succeed immediately if we're adding no pages.
|
||||
R_SUCCEED_IF(num_pages == 0);
|
||||
|
||||
// Check for overflow.
|
||||
ASSERT(addr < addr + num_pages * PageSize);
|
||||
|
||||
// Try to just append to the last block.
|
||||
if (m_last_block != nullptr) {
|
||||
R_SUCCEED_IF(m_last_block->TryConcatenate(addr, num_pages));
|
||||
}
|
||||
|
||||
// Allocate a new block.
|
||||
KBlockInfo* new_block = m_manager->Allocate();
|
||||
R_UNLESS(new_block != nullptr, ResultOutOfResource);
|
||||
|
||||
// Initialize the block.
|
||||
new_block->Initialize(addr, num_pages);
|
||||
|
||||
// Add the block to our list.
|
||||
if (m_last_block != nullptr) {
|
||||
m_last_block->SetNext(new_block);
|
||||
} else {
|
||||
m_first_block = new_block;
|
||||
}
|
||||
m_last_block = new_block;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void KPageGroup::Open() const {
|
||||
auto& mm = m_kernel.MemoryManager();
|
||||
|
||||
for (const auto& it : *this) {
|
||||
mm.Open(it.GetAddress(), it.GetNumPages());
|
||||
}
|
||||
}
|
||||
|
||||
void KPageGroup::OpenFirst() const {
|
||||
auto& mm = m_kernel.MemoryManager();
|
||||
|
||||
for (const auto& it : *this) {
|
||||
mm.OpenFirst(it.GetAddress(), it.GetNumPages());
|
||||
}
|
||||
}
|
||||
|
||||
void KPageGroup::Close() const {
|
||||
auto& mm = m_kernel.MemoryManager();
|
||||
|
||||
for (const auto& it : *this) {
|
||||
mm.Close(it.GetAddress(), it.GetNumPages());
|
||||
}
|
||||
}
|
||||
|
||||
bool KPageGroup::IsEquivalentTo(const KPageGroup& rhs) const {
|
||||
auto lit = this->begin();
|
||||
auto rit = rhs.begin();
|
||||
auto lend = this->end();
|
||||
auto rend = rhs.end();
|
||||
|
||||
while (lit != lend && rit != rend) {
|
||||
if (*lit != *rit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
++lit;
|
||||
++rit;
|
||||
}
|
||||
|
||||
return lit == lend && rit == rend;
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@@ -13,24 +13,23 @@
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class KBlockInfoManager;
|
||||
class KernelCore;
|
||||
class KPageGroup;
|
||||
|
||||
class KBlockInfo {
|
||||
private:
|
||||
friend class KPageGroup;
|
||||
|
||||
public:
|
||||
constexpr KBlockInfo() = default;
|
||||
constexpr explicit KBlockInfo() : m_next(nullptr) {}
|
||||
|
||||
constexpr void Initialize(PAddr addr, size_t np) {
|
||||
constexpr void Initialize(KPhysicalAddress addr, size_t np) {
|
||||
ASSERT(Common::IsAligned(addr, PageSize));
|
||||
ASSERT(static_cast<u32>(np) == np);
|
||||
|
||||
m_page_index = static_cast<u32>(addr) / PageSize;
|
||||
m_page_index = static_cast<u32>(addr / PageSize);
|
||||
m_num_pages = static_cast<u32>(np);
|
||||
}
|
||||
|
||||
constexpr PAddr GetAddress() const {
|
||||
constexpr KPhysicalAddress GetAddress() const {
|
||||
return m_page_index * PageSize;
|
||||
}
|
||||
constexpr size_t GetNumPages() const {
|
||||
@@ -39,10 +38,10 @@ public:
|
||||
constexpr size_t GetSize() const {
|
||||
return this->GetNumPages() * PageSize;
|
||||
}
|
||||
constexpr PAddr GetEndAddress() const {
|
||||
constexpr KPhysicalAddress GetEndAddress() const {
|
||||
return (m_page_index + m_num_pages) * PageSize;
|
||||
}
|
||||
constexpr PAddr GetLastAddress() const {
|
||||
constexpr KPhysicalAddress GetLastAddress() const {
|
||||
return this->GetEndAddress() - 1;
|
||||
}
|
||||
|
||||
@@ -62,8 +61,8 @@ public:
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
constexpr bool IsStrictlyBefore(PAddr addr) const {
|
||||
const PAddr end = this->GetEndAddress();
|
||||
constexpr bool IsStrictlyBefore(KPhysicalAddress addr) const {
|
||||
const KPhysicalAddress end = this->GetEndAddress();
|
||||
|
||||
if (m_page_index != 0 && end == 0) {
|
||||
return false;
|
||||
@@ -72,11 +71,11 @@ public:
|
||||
return end < addr;
|
||||
}
|
||||
|
||||
constexpr bool operator<(PAddr addr) const {
|
||||
constexpr bool operator<(KPhysicalAddress addr) const {
|
||||
return this->IsStrictlyBefore(addr);
|
||||
}
|
||||
|
||||
constexpr bool TryConcatenate(PAddr addr, size_t np) {
|
||||
constexpr bool TryConcatenate(KPhysicalAddress addr, size_t np) {
|
||||
if (addr != 0 && addr == this->GetEndAddress()) {
|
||||
m_num_pages += static_cast<u32>(np);
|
||||
return true;
|
||||
@@ -90,96 +89,118 @@ private:
|
||||
}
|
||||
|
||||
private:
|
||||
friend class KPageGroup;
|
||||
|
||||
KBlockInfo* m_next{};
|
||||
u32 m_page_index{};
|
||||
u32 m_num_pages{};
|
||||
};
|
||||
static_assert(sizeof(KBlockInfo) <= 0x10);
|
||||
|
||||
class KPageGroup final {
|
||||
class KPageGroup {
|
||||
public:
|
||||
class Node final {
|
||||
class Iterator {
|
||||
public:
|
||||
constexpr Node(u64 addr_, std::size_t num_pages_) : addr{addr_}, num_pages{num_pages_} {}
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = const KBlockInfo;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = value_type*;
|
||||
using reference = value_type&;
|
||||
|
||||
constexpr u64 GetAddress() const {
|
||||
return addr;
|
||||
constexpr explicit Iterator(pointer n) : m_node(n) {}
|
||||
|
||||
constexpr bool operator==(const Iterator& rhs) const {
|
||||
return m_node == rhs.m_node;
|
||||
}
|
||||
constexpr bool operator!=(const Iterator& rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
constexpr std::size_t GetNumPages() const {
|
||||
return num_pages;
|
||||
constexpr pointer operator->() const {
|
||||
return m_node;
|
||||
}
|
||||
constexpr reference operator*() const {
|
||||
return *m_node;
|
||||
}
|
||||
|
||||
constexpr std::size_t GetSize() const {
|
||||
return GetNumPages() * PageSize;
|
||||
constexpr Iterator& operator++() {
|
||||
m_node = m_node->GetNext();
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Iterator operator++(int) {
|
||||
const Iterator it{*this};
|
||||
++(*this);
|
||||
return it;
|
||||
}
|
||||
|
||||
private:
|
||||
u64 addr{};
|
||||
std::size_t num_pages{};
|
||||
pointer m_node{};
|
||||
};
|
||||
|
||||
public:
|
||||
KPageGroup() = default;
|
||||
KPageGroup(u64 address, u64 num_pages) {
|
||||
ASSERT(AddBlock(address, num_pages).IsSuccess());
|
||||
explicit KPageGroup(KernelCore& kernel, KBlockInfoManager* m)
|
||||
: m_kernel{kernel}, m_manager{m} {}
|
||||
~KPageGroup() {
|
||||
this->Finalize();
|
||||
}
|
||||
|
||||
constexpr std::list<Node>& Nodes() {
|
||||
return nodes;
|
||||
void CloseAndReset();
|
||||
void Finalize();
|
||||
|
||||
Iterator begin() const {
|
||||
return Iterator{m_first_block};
|
||||
}
|
||||
Iterator end() const {
|
||||
return Iterator{nullptr};
|
||||
}
|
||||
bool empty() const {
|
||||
return m_first_block == nullptr;
|
||||
}
|
||||
|
||||
constexpr const std::list<Node>& Nodes() const {
|
||||
return nodes;
|
||||
Result AddBlock(KPhysicalAddress addr, size_t num_pages);
|
||||
void Open() const;
|
||||
void OpenFirst() const;
|
||||
void Close() const;
|
||||
|
||||
size_t GetNumPages() const;
|
||||
|
||||
bool IsEquivalentTo(const KPageGroup& rhs) const;
|
||||
|
||||
bool operator==(const KPageGroup& rhs) const {
|
||||
return this->IsEquivalentTo(rhs);
|
||||
}
|
||||
|
||||
std::size_t GetNumPages() const {
|
||||
std::size_t num_pages = 0;
|
||||
for (const Node& node : nodes) {
|
||||
num_pages += node.GetNumPages();
|
||||
}
|
||||
return num_pages;
|
||||
bool operator!=(const KPageGroup& rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
bool IsEqual(KPageGroup& other) const {
|
||||
auto this_node = nodes.begin();
|
||||
auto other_node = other.nodes.begin();
|
||||
while (this_node != nodes.end() && other_node != other.nodes.end()) {
|
||||
if (this_node->GetAddress() != other_node->GetAddress() ||
|
||||
this_node->GetNumPages() != other_node->GetNumPages()) {
|
||||
return false;
|
||||
}
|
||||
this_node = std::next(this_node);
|
||||
other_node = std::next(other_node);
|
||||
}
|
||||
|
||||
return this_node == nodes.end() && other_node == other.nodes.end();
|
||||
}
|
||||
|
||||
Result AddBlock(u64 address, u64 num_pages) {
|
||||
if (!num_pages) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
if (!nodes.empty()) {
|
||||
const auto node = nodes.back();
|
||||
if (node.GetAddress() + node.GetNumPages() * PageSize == address) {
|
||||
address = node.GetAddress();
|
||||
num_pages += node.GetNumPages();
|
||||
nodes.pop_back();
|
||||
}
|
||||
}
|
||||
nodes.push_back({address, num_pages});
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
bool Empty() const {
|
||||
return nodes.empty();
|
||||
}
|
||||
|
||||
void Finalize() {}
|
||||
|
||||
private:
|
||||
std::list<Node> nodes;
|
||||
KernelCore& m_kernel;
|
||||
KBlockInfo* m_first_block{};
|
||||
KBlockInfo* m_last_block{};
|
||||
KBlockInfoManager* m_manager{};
|
||||
};
|
||||
|
||||
class KScopedPageGroup {
|
||||
public:
|
||||
explicit KScopedPageGroup(const KPageGroup* gp) : m_pg(gp) {
|
||||
if (m_pg) {
|
||||
m_pg->Open();
|
||||
}
|
||||
}
|
||||
explicit KScopedPageGroup(const KPageGroup& gp) : KScopedPageGroup(std::addressof(gp)) {}
|
||||
~KScopedPageGroup() {
|
||||
if (m_pg) {
|
||||
m_pg->Close();
|
||||
}
|
||||
}
|
||||
|
||||
void CancelClose() {
|
||||
m_pg = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
const KPageGroup* m_pg{};
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -100,7 +100,7 @@ constexpr size_t GetAddressSpaceWidthFromType(FileSys::ProgramAddressSpaceType a
|
||||
|
||||
KPageTable::KPageTable(Core::System& system_)
|
||||
: m_general_lock{system_.Kernel()},
|
||||
m_map_physical_memory_lock{system_.Kernel()}, m_system{system_} {}
|
||||
m_map_physical_memory_lock{system_.Kernel()}, m_system{system_}, m_kernel{system_.Kernel()} {}
|
||||
|
||||
KPageTable::~KPageTable() = default;
|
||||
|
||||
@@ -373,7 +373,7 @@ Result KPageTable::MapProcessCode(VAddr addr, size_t num_pages, KMemoryState sta
|
||||
m_memory_block_slab_manager);
|
||||
|
||||
// Allocate and open.
|
||||
KPageGroup pg;
|
||||
KPageGroup pg{m_kernel, m_block_info_manager};
|
||||
R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpen(
|
||||
&pg, num_pages,
|
||||
KMemoryManager::EncodeOption(KMemoryManager::Pool::Application, m_allocation_option)));
|
||||
@@ -432,9 +432,12 @@ Result KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, size_t si
|
||||
const size_t num_pages = size / PageSize;
|
||||
|
||||
// Create page groups for the memory being mapped.
|
||||
KPageGroup pg;
|
||||
KPageGroup pg{m_kernel, m_block_info_manager};
|
||||
AddRegionToPages(src_address, num_pages, pg);
|
||||
|
||||
// We're going to perform an update, so create a helper.
|
||||
KScopedPageTableUpdater updater(this);
|
||||
|
||||
// Reprotect the source as kernel-read/not mapped.
|
||||
const auto new_perm = static_cast<KMemoryPermission>(KMemoryPermission::KernelRead |
|
||||
KMemoryPermission::NotMapped);
|
||||
@@ -447,7 +450,10 @@ Result KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, size_t si
|
||||
});
|
||||
|
||||
// Map the alias pages.
|
||||
R_TRY(MapPages(dst_address, pg, new_perm));
|
||||
const KPageProperties dst_properties = {new_perm, false, false,
|
||||
DisableMergeAttribute::DisableHead};
|
||||
R_TRY(
|
||||
this->MapPageGroupImpl(updater.GetPageList(), dst_address, pg, dst_properties, false));
|
||||
|
||||
// We successfully mapped the alias pages, so we don't need to unprotect the src pages on
|
||||
// failure.
|
||||
@@ -593,7 +599,7 @@ Result KPageTable::MakePageGroup(KPageGroup& pg, VAddr addr, size_t num_pages) {
|
||||
const size_t size = num_pages * PageSize;
|
||||
|
||||
// We're making a new group, not adding to an existing one.
|
||||
R_UNLESS(pg.Empty(), ResultInvalidCurrentMemory);
|
||||
R_UNLESS(pg.empty(), ResultInvalidCurrentMemory);
|
||||
|
||||
// Begin traversal.
|
||||
Common::PageTable::TraversalContext context;
|
||||
@@ -640,11 +646,10 @@ Result KPageTable::MakePageGroup(KPageGroup& pg, VAddr addr, size_t num_pages) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
bool KPageTable::IsValidPageGroup(const KPageGroup& pg_ll, VAddr addr, size_t num_pages) {
|
||||
bool KPageTable::IsValidPageGroup(const KPageGroup& pg, VAddr addr, size_t num_pages) {
|
||||
ASSERT(this->IsLockedByCurrentThread());
|
||||
|
||||
const size_t size = num_pages * PageSize;
|
||||
const auto& pg = pg_ll.Nodes();
|
||||
const auto& memory_layout = m_system.Kernel().MemoryLayout();
|
||||
|
||||
// Empty groups are necessarily invalid.
|
||||
@@ -942,9 +947,6 @@ Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_add
|
||||
|
||||
ON_RESULT_FAILURE {
|
||||
if (cur_mapped_addr != dst_addr) {
|
||||
// HACK: Manually close the pages.
|
||||
HACK_ClosePages(dst_addr, (cur_mapped_addr - dst_addr) / PageSize);
|
||||
|
||||
ASSERT(Operate(dst_addr, (cur_mapped_addr - dst_addr) / PageSize,
|
||||
KMemoryPermission::None, OperationType::Unmap)
|
||||
.IsSuccess());
|
||||
@@ -1020,9 +1022,6 @@ Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_add
|
||||
// Map the page.
|
||||
R_TRY(Operate(cur_mapped_addr, 1, test_perm, OperationType::Map, start_partial_page));
|
||||
|
||||
// HACK: Manually open the pages.
|
||||
HACK_OpenPages(start_partial_page, 1);
|
||||
|
||||
// Update tracking extents.
|
||||
cur_mapped_addr += PageSize;
|
||||
cur_block_addr += PageSize;
|
||||
@@ -1051,9 +1050,6 @@ Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_add
|
||||
R_TRY(Operate(cur_mapped_addr, cur_block_size / PageSize, test_perm, OperationType::Map,
|
||||
cur_block_addr));
|
||||
|
||||
// HACK: Manually open the pages.
|
||||
HACK_OpenPages(cur_block_addr, cur_block_size / PageSize);
|
||||
|
||||
// Update tracking extents.
|
||||
cur_mapped_addr += cur_block_size;
|
||||
cur_block_addr = next_entry.phys_addr;
|
||||
@@ -1073,9 +1069,6 @@ Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_add
|
||||
R_TRY(Operate(cur_mapped_addr, last_block_size / PageSize, test_perm, OperationType::Map,
|
||||
cur_block_addr));
|
||||
|
||||
// HACK: Manually open the pages.
|
||||
HACK_OpenPages(cur_block_addr, last_block_size / PageSize);
|
||||
|
||||
// Update tracking extents.
|
||||
cur_mapped_addr += last_block_size;
|
||||
cur_block_addr += last_block_size;
|
||||
@@ -1107,9 +1100,6 @@ Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_add
|
||||
|
||||
// Map the page.
|
||||
R_TRY(Operate(cur_mapped_addr, 1, test_perm, OperationType::Map, end_partial_page));
|
||||
|
||||
// HACK: Manually open the pages.
|
||||
HACK_OpenPages(end_partial_page, 1);
|
||||
}
|
||||
|
||||
// Update memory blocks to reflect our changes
|
||||
@@ -1211,9 +1201,6 @@ Result KPageTable::CleanupForIpcServer(VAddr address, size_t size, KMemoryState
|
||||
const size_t aligned_size = aligned_end - aligned_start;
|
||||
const size_t aligned_num_pages = aligned_size / PageSize;
|
||||
|
||||
// HACK: Manually close the pages.
|
||||
HACK_ClosePages(aligned_start, aligned_num_pages);
|
||||
|
||||
// Unmap the pages.
|
||||
R_TRY(Operate(aligned_start, aligned_num_pages, KMemoryPermission::None, OperationType::Unmap));
|
||||
|
||||
@@ -1501,17 +1488,6 @@ void KPageTable::CleanupForIpcClientOnServerSetupFailure([[maybe_unused]] PageLi
|
||||
}
|
||||
}
|
||||
|
||||
void KPageTable::HACK_OpenPages(PAddr phys_addr, size_t num_pages) {
|
||||
m_system.Kernel().MemoryManager().OpenFirst(phys_addr, num_pages);
|
||||
}
|
||||
|
||||
void KPageTable::HACK_ClosePages(VAddr virt_addr, size_t num_pages) {
|
||||
for (size_t index = 0; index < num_pages; ++index) {
|
||||
const auto paddr = GetPhysicalAddr(virt_addr + (index * PageSize));
|
||||
m_system.Kernel().MemoryManager().Close(paddr, 1);
|
||||
}
|
||||
}
|
||||
|
||||
Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
|
||||
// Lock the physical memory lock.
|
||||
KScopedLightLock phys_lk(m_map_physical_memory_lock);
|
||||
@@ -1572,7 +1548,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
|
||||
R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
|
||||
|
||||
// Allocate pages for the new memory.
|
||||
KPageGroup pg;
|
||||
KPageGroup pg{m_kernel, m_block_info_manager};
|
||||
R_TRY(m_system.Kernel().MemoryManager().AllocateForProcess(
|
||||
&pg, (size - mapped_size) / PageSize, m_allocate_option, 0, 0));
|
||||
|
||||
@@ -1650,7 +1626,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
|
||||
KScopedPageTableUpdater updater(this);
|
||||
|
||||
// Prepare to iterate over the memory.
|
||||
auto pg_it = pg.Nodes().begin();
|
||||
auto pg_it = pg.begin();
|
||||
PAddr pg_phys_addr = pg_it->GetAddress();
|
||||
size_t pg_pages = pg_it->GetNumPages();
|
||||
|
||||
@@ -1680,9 +1656,6 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
|
||||
last_unmap_address + 1 - cur_address) /
|
||||
PageSize;
|
||||
|
||||
// HACK: Manually close the pages.
|
||||
HACK_ClosePages(cur_address, cur_pages);
|
||||
|
||||
// Unmap.
|
||||
ASSERT(Operate(cur_address, cur_pages, KMemoryPermission::None,
|
||||
OperationType::Unmap)
|
||||
@@ -1703,7 +1676,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
|
||||
// Release any remaining unmapped memory.
|
||||
m_system.Kernel().MemoryManager().OpenFirst(pg_phys_addr, pg_pages);
|
||||
m_system.Kernel().MemoryManager().Close(pg_phys_addr, pg_pages);
|
||||
for (++pg_it; pg_it != pg.Nodes().end(); ++pg_it) {
|
||||
for (++pg_it; pg_it != pg.end(); ++pg_it) {
|
||||
m_system.Kernel().MemoryManager().OpenFirst(pg_it->GetAddress(),
|
||||
pg_it->GetNumPages());
|
||||
m_system.Kernel().MemoryManager().Close(pg_it->GetAddress(),
|
||||
@@ -1731,7 +1704,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
|
||||
// Check if we're at the end of the physical block.
|
||||
if (pg_pages == 0) {
|
||||
// Ensure there are more pages to map.
|
||||
ASSERT(pg_it != pg.Nodes().end());
|
||||
ASSERT(pg_it != pg.end());
|
||||
|
||||
// Advance our physical block.
|
||||
++pg_it;
|
||||
@@ -1742,10 +1715,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
|
||||
// Map whatever we can.
|
||||
const size_t cur_pages = std::min(pg_pages, map_pages);
|
||||
R_TRY(Operate(cur_address, cur_pages, KMemoryPermission::UserReadWrite,
|
||||
OperationType::Map, pg_phys_addr));
|
||||
|
||||
// HACK: Manually open the pages.
|
||||
HACK_OpenPages(pg_phys_addr, cur_pages);
|
||||
OperationType::MapFirst, pg_phys_addr));
|
||||
|
||||
// Advance.
|
||||
cur_address += cur_pages * PageSize;
|
||||
@@ -1888,9 +1858,6 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
|
||||
last_address + 1 - cur_address) /
|
||||
PageSize;
|
||||
|
||||
// HACK: Manually close the pages.
|
||||
HACK_ClosePages(cur_address, cur_pages);
|
||||
|
||||
// Unmap.
|
||||
ASSERT(Operate(cur_address, cur_pages, KMemoryPermission::None, OperationType::Unmap)
|
||||
.IsSuccess());
|
||||
@@ -1920,7 +1887,8 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KPageTable::MapMemory(VAddr dst_address, VAddr src_address, size_t size) {
|
||||
Result KPageTable::MapMemory(KProcessAddress dst_address, KProcessAddress src_address,
|
||||
size_t size) {
|
||||
// Lock the table.
|
||||
KScopedLightLock lk(m_general_lock);
|
||||
|
||||
@@ -1941,53 +1909,73 @@ Result KPageTable::MapMemory(VAddr dst_address, VAddr src_address, size_t size)
|
||||
KMemoryAttribute::None));
|
||||
|
||||
// Create an update allocator for the source.
|
||||
Result src_allocator_result{ResultSuccess};
|
||||
Result src_allocator_result;
|
||||
KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result),
|
||||
m_memory_block_slab_manager,
|
||||
num_src_allocator_blocks);
|
||||
R_TRY(src_allocator_result);
|
||||
|
||||
// Create an update allocator for the destination.
|
||||
Result dst_allocator_result{ResultSuccess};
|
||||
Result dst_allocator_result;
|
||||
KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result),
|
||||
m_memory_block_slab_manager,
|
||||
num_dst_allocator_blocks);
|
||||
R_TRY(dst_allocator_result);
|
||||
|
||||
// Map the memory.
|
||||
KPageGroup page_linked_list;
|
||||
const size_t num_pages{size / PageSize};
|
||||
const KMemoryPermission new_src_perm = static_cast<KMemoryPermission>(
|
||||
KMemoryPermission::KernelRead | KMemoryPermission::NotMapped);
|
||||
const KMemoryAttribute new_src_attr = KMemoryAttribute::Locked;
|
||||
|
||||
AddRegionToPages(src_address, num_pages, page_linked_list);
|
||||
{
|
||||
// Determine the number of pages being operated on.
|
||||
const size_t num_pages = size / PageSize;
|
||||
|
||||
// Create page groups for the memory being unmapped.
|
||||
KPageGroup pg{m_kernel, m_block_info_manager};
|
||||
|
||||
// Create the page group representing the source.
|
||||
R_TRY(this->MakePageGroup(pg, src_address, num_pages));
|
||||
|
||||
// We're going to perform an update, so create a helper.
|
||||
KScopedPageTableUpdater updater(this);
|
||||
|
||||
// Reprotect the source as kernel-read/not mapped.
|
||||
auto block_guard = detail::ScopeExit([&] {
|
||||
Operate(src_address, num_pages, KMemoryPermission::UserReadWrite,
|
||||
OperationType::ChangePermissions);
|
||||
});
|
||||
R_TRY(Operate(src_address, num_pages, new_src_perm, OperationType::ChangePermissions));
|
||||
R_TRY(MapPages(dst_address, page_linked_list, KMemoryPermission::UserReadWrite));
|
||||
const KMemoryPermission new_src_perm = static_cast<KMemoryPermission>(
|
||||
KMemoryPermission::KernelRead | KMemoryPermission::NotMapped);
|
||||
const KMemoryAttribute new_src_attr = KMemoryAttribute::Locked;
|
||||
const KPageProperties src_properties = {new_src_perm, false, false,
|
||||
DisableMergeAttribute::DisableHeadBodyTail};
|
||||
R_TRY(this->Operate(src_address, num_pages, src_properties.perm,
|
||||
OperationType::ChangePermissions));
|
||||
|
||||
block_guard.Cancel();
|
||||
// Ensure that we unprotect the source pages on failure.
|
||||
ON_RESULT_FAILURE {
|
||||
const KPageProperties unprotect_properties = {
|
||||
KMemoryPermission::UserReadWrite, false, false,
|
||||
DisableMergeAttribute::EnableHeadBodyTail};
|
||||
ASSERT(this->Operate(src_address, num_pages, unprotect_properties.perm,
|
||||
OperationType::ChangePermissions) == ResultSuccess);
|
||||
};
|
||||
|
||||
// Map the alias pages.
|
||||
const KPageProperties dst_map_properties = {KMemoryPermission::UserReadWrite, false, false,
|
||||
DisableMergeAttribute::DisableHead};
|
||||
R_TRY(this->MapPageGroupImpl(updater.GetPageList(), dst_address, pg, dst_map_properties,
|
||||
false));
|
||||
|
||||
// Apply the memory block updates.
|
||||
m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages,
|
||||
src_state, new_src_perm, new_src_attr,
|
||||
KMemoryBlockDisableMergeAttribute::Locked,
|
||||
KMemoryBlockDisableMergeAttribute::None);
|
||||
m_memory_block_manager.Update(
|
||||
std::addressof(dst_allocator), dst_address, num_pages, KMemoryState::Stack,
|
||||
KMemoryPermission::UserReadWrite, KMemoryAttribute::None,
|
||||
KMemoryBlockDisableMergeAttribute::Normal, KMemoryBlockDisableMergeAttribute::None);
|
||||
}
|
||||
|
||||
// Apply the memory block updates.
|
||||
m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, src_state,
|
||||
new_src_perm, new_src_attr,
|
||||
KMemoryBlockDisableMergeAttribute::Locked,
|
||||
KMemoryBlockDisableMergeAttribute::None);
|
||||
m_memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages,
|
||||
KMemoryState::Stack, KMemoryPermission::UserReadWrite,
|
||||
KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal,
|
||||
KMemoryBlockDisableMergeAttribute::None);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KPageTable::UnmapMemory(VAddr dst_address, VAddr src_address, size_t size) {
|
||||
Result KPageTable::UnmapMemory(KProcessAddress dst_address, KProcessAddress src_address,
|
||||
size_t size) {
|
||||
// Lock the table.
|
||||
KScopedLightLock lk(m_general_lock);
|
||||
|
||||
@@ -2009,108 +1997,208 @@ Result KPageTable::UnmapMemory(VAddr dst_address, VAddr src_address, size_t size
|
||||
KMemoryPermission::None, KMemoryAttribute::All, KMemoryAttribute::None));
|
||||
|
||||
// Create an update allocator for the source.
|
||||
Result src_allocator_result{ResultSuccess};
|
||||
Result src_allocator_result;
|
||||
KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result),
|
||||
m_memory_block_slab_manager,
|
||||
num_src_allocator_blocks);
|
||||
R_TRY(src_allocator_result);
|
||||
|
||||
// Create an update allocator for the destination.
|
||||
Result dst_allocator_result{ResultSuccess};
|
||||
Result dst_allocator_result;
|
||||
KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result),
|
||||
m_memory_block_slab_manager,
|
||||
num_dst_allocator_blocks);
|
||||
R_TRY(dst_allocator_result);
|
||||
|
||||
KPageGroup src_pages;
|
||||
KPageGroup dst_pages;
|
||||
const size_t num_pages{size / PageSize};
|
||||
|
||||
AddRegionToPages(src_address, num_pages, src_pages);
|
||||
AddRegionToPages(dst_address, num_pages, dst_pages);
|
||||
|
||||
R_UNLESS(dst_pages.IsEqual(src_pages), ResultInvalidMemoryRegion);
|
||||
|
||||
// Unmap the memory.
|
||||
{
|
||||
auto block_guard = detail::ScopeExit([&] { MapPages(dst_address, dst_pages, dst_perm); });
|
||||
// Determine the number of pages being operated on.
|
||||
const size_t num_pages = size / PageSize;
|
||||
|
||||
R_TRY(Operate(dst_address, num_pages, KMemoryPermission::None, OperationType::Unmap));
|
||||
R_TRY(Operate(src_address, num_pages, KMemoryPermission::UserReadWrite,
|
||||
OperationType::ChangePermissions));
|
||||
// Create page groups for the memory being unmapped.
|
||||
KPageGroup pg{m_kernel, m_block_info_manager};
|
||||
|
||||
block_guard.Cancel();
|
||||
// Create the page group representing the destination.
|
||||
R_TRY(this->MakePageGroup(pg, dst_address, num_pages));
|
||||
|
||||
// Ensure the page group is the valid for the source.
|
||||
R_UNLESS(this->IsValidPageGroup(pg, src_address, num_pages), ResultInvalidMemoryRegion);
|
||||
|
||||
// We're going to perform an update, so create a helper.
|
||||
KScopedPageTableUpdater updater(this);
|
||||
|
||||
// Unmap the aliased copy of the pages.
|
||||
const KPageProperties dst_unmap_properties = {KMemoryPermission::None, false, false,
|
||||
DisableMergeAttribute::None};
|
||||
R_TRY(
|
||||
this->Operate(dst_address, num_pages, dst_unmap_properties.perm, OperationType::Unmap));
|
||||
|
||||
// Ensure that we re-map the aliased pages on failure.
|
||||
ON_RESULT_FAILURE {
|
||||
this->RemapPageGroup(updater.GetPageList(), dst_address, size, pg);
|
||||
};
|
||||
|
||||
// Try to set the permissions for the source pages back to what they should be.
|
||||
const KPageProperties src_properties = {KMemoryPermission::UserReadWrite, false, false,
|
||||
DisableMergeAttribute::EnableAndMergeHeadBodyTail};
|
||||
R_TRY(this->Operate(src_address, num_pages, src_properties.perm,
|
||||
OperationType::ChangePermissions));
|
||||
|
||||
// Apply the memory block updates.
|
||||
m_memory_block_manager.Update(
|
||||
std::addressof(src_allocator), src_address, num_pages, src_state,
|
||||
KMemoryPermission::UserReadWrite, KMemoryAttribute::None,
|
||||
KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Locked);
|
||||
m_memory_block_manager.Update(
|
||||
std::addressof(dst_allocator), dst_address, num_pages, KMemoryState::None,
|
||||
KMemoryPermission::None, KMemoryAttribute::None,
|
||||
KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Normal);
|
||||
}
|
||||
|
||||
// Apply the memory block updates.
|
||||
m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, src_state,
|
||||
KMemoryPermission::UserReadWrite, KMemoryAttribute::None,
|
||||
KMemoryBlockDisableMergeAttribute::None,
|
||||
KMemoryBlockDisableMergeAttribute::Locked);
|
||||
m_memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages,
|
||||
KMemoryState::None, KMemoryPermission::None,
|
||||
KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None,
|
||||
KMemoryBlockDisableMergeAttribute::Normal);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KPageTable::MapPages(VAddr addr, const KPageGroup& page_linked_list,
|
||||
KMemoryPermission perm) {
|
||||
Result KPageTable::AllocateAndMapPagesImpl(PageLinkedList* page_list, KProcessAddress address,
|
||||
size_t num_pages, KMemoryPermission perm) {
|
||||
ASSERT(this->IsLockedByCurrentThread());
|
||||
|
||||
VAddr cur_addr{addr};
|
||||
// Create a page group to hold the pages we allocate.
|
||||
KPageGroup pg{m_kernel, m_block_info_manager};
|
||||
|
||||
for (const auto& node : page_linked_list.Nodes()) {
|
||||
if (const auto result{
|
||||
Operate(cur_addr, node.GetNumPages(), perm, OperationType::Map, node.GetAddress())};
|
||||
result.IsError()) {
|
||||
const size_t num_pages{(addr - cur_addr) / PageSize};
|
||||
// Allocate the pages.
|
||||
R_TRY(
|
||||
m_kernel.MemoryManager().AllocateAndOpen(std::addressof(pg), num_pages, m_allocate_option));
|
||||
|
||||
ASSERT(Operate(addr, num_pages, KMemoryPermission::None, OperationType::Unmap)
|
||||
.IsSuccess());
|
||||
// Ensure that the page group is closed when we're done working with it.
|
||||
SCOPE_EXIT({ pg.Close(); });
|
||||
|
||||
R_RETURN(result);
|
||||
}
|
||||
|
||||
cur_addr += node.GetNumPages() * PageSize;
|
||||
// Clear all pages.
|
||||
for (const auto& it : pg) {
|
||||
std::memset(m_system.DeviceMemory().GetPointer<void>(it.GetAddress()), m_heap_fill_value,
|
||||
it.GetSize());
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KPageTable::MapPages(VAddr address, KPageGroup& page_linked_list, KMemoryState state,
|
||||
KMemoryPermission perm) {
|
||||
// Check that the map is in range.
|
||||
const size_t num_pages{page_linked_list.GetNumPages()};
|
||||
const size_t size{num_pages * PageSize};
|
||||
R_UNLESS(this->CanContain(address, size, state), ResultInvalidCurrentMemory);
|
||||
|
||||
// Lock the table.
|
||||
KScopedLightLock lk(m_general_lock);
|
||||
|
||||
// Check the memory state.
|
||||
R_TRY(this->CheckMemoryState(address, size, KMemoryState::All, KMemoryState::Free,
|
||||
KMemoryPermission::None, KMemoryPermission::None,
|
||||
KMemoryAttribute::None, KMemoryAttribute::None));
|
||||
|
||||
// Create an update allocator.
|
||||
Result allocator_result{ResultSuccess};
|
||||
KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
|
||||
m_memory_block_slab_manager);
|
||||
|
||||
// Map the pages.
|
||||
R_TRY(MapPages(address, page_linked_list, perm));
|
||||
R_RETURN(this->Operate(address, num_pages, pg, OperationType::MapGroup));
|
||||
}
|
||||
|
||||
// Update the blocks.
|
||||
m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, state, perm,
|
||||
KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal,
|
||||
KMemoryBlockDisableMergeAttribute::None);
|
||||
Result KPageTable::MapPageGroupImpl(PageLinkedList* page_list, KProcessAddress address,
|
||||
const KPageGroup& pg, const KPageProperties properties,
|
||||
bool reuse_ll) {
|
||||
ASSERT(this->IsLockedByCurrentThread());
|
||||
|
||||
// Note the current address, so that we can iterate.
|
||||
const KProcessAddress start_address = address;
|
||||
KProcessAddress cur_address = address;
|
||||
|
||||
// Ensure that we clean up on failure.
|
||||
ON_RESULT_FAILURE {
|
||||
ASSERT(!reuse_ll);
|
||||
if (cur_address != start_address) {
|
||||
const KPageProperties unmap_properties = {KMemoryPermission::None, false, false,
|
||||
DisableMergeAttribute::None};
|
||||
ASSERT(this->Operate(start_address, (cur_address - start_address) / PageSize,
|
||||
unmap_properties.perm, OperationType::Unmap) == ResultSuccess);
|
||||
}
|
||||
};
|
||||
|
||||
// Iterate, mapping all pages in the group.
|
||||
for (const auto& block : pg) {
|
||||
// Map and advance.
|
||||
const KPageProperties cur_properties =
|
||||
(cur_address == start_address)
|
||||
? properties
|
||||
: KPageProperties{properties.perm, properties.io, properties.uncached,
|
||||
DisableMergeAttribute::None};
|
||||
this->Operate(cur_address, block.GetNumPages(), cur_properties.perm, OperationType::Map,
|
||||
block.GetAddress());
|
||||
cur_address += block.GetSize();
|
||||
}
|
||||
|
||||
// We succeeded!
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KPageTable::MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, PAddr phys_addr,
|
||||
bool is_pa_valid, VAddr region_start, size_t region_num_pages,
|
||||
void KPageTable::RemapPageGroup(PageLinkedList* page_list, KProcessAddress address, size_t size,
|
||||
const KPageGroup& pg) {
|
||||
ASSERT(this->IsLockedByCurrentThread());
|
||||
|
||||
// Note the current address, so that we can iterate.
|
||||
const KProcessAddress start_address = address;
|
||||
const KProcessAddress last_address = start_address + size - 1;
|
||||
const KProcessAddress end_address = last_address + 1;
|
||||
|
||||
// Iterate over the memory.
|
||||
auto pg_it = pg.begin();
|
||||
ASSERT(pg_it != pg.end());
|
||||
|
||||
KPhysicalAddress pg_phys_addr = pg_it->GetAddress();
|
||||
size_t pg_pages = pg_it->GetNumPages();
|
||||
|
||||
auto it = m_memory_block_manager.FindIterator(start_address);
|
||||
while (true) {
|
||||
// Check that the iterator is valid.
|
||||
ASSERT(it != m_memory_block_manager.end());
|
||||
|
||||
// Get the memory info.
|
||||
const KMemoryInfo info = it->GetMemoryInfo();
|
||||
|
||||
// Determine the range to map.
|
||||
KProcessAddress map_address = std::max<VAddr>(info.GetAddress(), start_address);
|
||||
const KProcessAddress map_end_address = std::min<VAddr>(info.GetEndAddress(), end_address);
|
||||
ASSERT(map_end_address != map_address);
|
||||
|
||||
// Determine if we should disable head merge.
|
||||
const bool disable_head_merge =
|
||||
info.GetAddress() >= start_address &&
|
||||
True(info.GetDisableMergeAttribute() & KMemoryBlockDisableMergeAttribute::Normal);
|
||||
const KPageProperties map_properties = {
|
||||
info.GetPermission(), false, false,
|
||||
disable_head_merge ? DisableMergeAttribute::DisableHead : DisableMergeAttribute::None};
|
||||
|
||||
// While we have pages to map, map them.
|
||||
size_t map_pages = (map_end_address - map_address) / PageSize;
|
||||
while (map_pages > 0) {
|
||||
// Check if we're at the end of the physical block.
|
||||
if (pg_pages == 0) {
|
||||
// Ensure there are more pages to map.
|
||||
ASSERT(pg_it != pg.end());
|
||||
|
||||
// Advance our physical block.
|
||||
++pg_it;
|
||||
pg_phys_addr = pg_it->GetAddress();
|
||||
pg_pages = pg_it->GetNumPages();
|
||||
}
|
||||
|
||||
// Map whatever we can.
|
||||
const size_t cur_pages = std::min(pg_pages, map_pages);
|
||||
ASSERT(this->Operate(map_address, map_pages, map_properties.perm, OperationType::Map,
|
||||
pg_phys_addr) == ResultSuccess);
|
||||
|
||||
// Advance.
|
||||
map_address += cur_pages * PageSize;
|
||||
map_pages -= cur_pages;
|
||||
|
||||
pg_phys_addr += cur_pages * PageSize;
|
||||
pg_pages -= cur_pages;
|
||||
}
|
||||
|
||||
// Check if we're done.
|
||||
if (last_address <= info.GetLastAddress()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Advance.
|
||||
++it;
|
||||
}
|
||||
|
||||
// Check that we re-mapped precisely the page group.
|
||||
ASSERT((++pg_it) == pg.end());
|
||||
}
|
||||
|
||||
Result KPageTable::MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment,
|
||||
KPhysicalAddress phys_addr, bool is_pa_valid,
|
||||
KProcessAddress region_start, size_t region_num_pages,
|
||||
KMemoryState state, KMemoryPermission perm) {
|
||||
ASSERT(Common::IsAligned(alignment, PageSize) && alignment >= PageSize);
|
||||
|
||||
@@ -2123,26 +2211,30 @@ Result KPageTable::MapPages(VAddr* out_addr, size_t num_pages, size_t alignment,
|
||||
KScopedLightLock lk(m_general_lock);
|
||||
|
||||
// Find a random address to map at.
|
||||
VAddr addr = this->FindFreeArea(region_start, region_num_pages, num_pages, alignment, 0,
|
||||
this->GetNumGuardPages());
|
||||
KProcessAddress addr = this->FindFreeArea(region_start, region_num_pages, num_pages, alignment,
|
||||
0, this->GetNumGuardPages());
|
||||
R_UNLESS(addr != 0, ResultOutOfMemory);
|
||||
ASSERT(Common::IsAligned(addr, alignment));
|
||||
ASSERT(this->CanContain(addr, num_pages * PageSize, state));
|
||||
ASSERT(this->CheckMemoryState(addr, num_pages * PageSize, KMemoryState::All, KMemoryState::Free,
|
||||
KMemoryPermission::None, KMemoryPermission::None,
|
||||
KMemoryAttribute::None, KMemoryAttribute::None)
|
||||
.IsSuccess());
|
||||
KMemoryAttribute::None, KMemoryAttribute::None) == ResultSuccess);
|
||||
|
||||
// Create an update allocator.
|
||||
Result allocator_result{ResultSuccess};
|
||||
Result allocator_result;
|
||||
KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
|
||||
m_memory_block_slab_manager);
|
||||
R_TRY(allocator_result);
|
||||
|
||||
// We're going to perform an update, so create a helper.
|
||||
KScopedPageTableUpdater updater(this);
|
||||
|
||||
// Perform mapping operation.
|
||||
if (is_pa_valid) {
|
||||
R_TRY(this->Operate(addr, num_pages, perm, OperationType::Map, phys_addr));
|
||||
const KPageProperties properties = {perm, false, false, DisableMergeAttribute::DisableHead};
|
||||
R_TRY(this->Operate(addr, num_pages, properties.perm, OperationType::Map, phys_addr));
|
||||
} else {
|
||||
UNIMPLEMENTED();
|
||||
R_TRY(this->AllocateAndMapPagesImpl(updater.GetPageList(), addr, num_pages, perm));
|
||||
}
|
||||
|
||||
// Update the blocks.
|
||||
@@ -2155,28 +2247,45 @@ Result KPageTable::MapPages(VAddr* out_addr, size_t num_pages, size_t alignment,
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KPageTable::UnmapPages(VAddr addr, const KPageGroup& page_linked_list) {
|
||||
ASSERT(this->IsLockedByCurrentThread());
|
||||
Result KPageTable::MapPages(KProcessAddress address, size_t num_pages, KMemoryState state,
|
||||
KMemoryPermission perm) {
|
||||
// Check that the map is in range.
|
||||
const size_t size = num_pages * PageSize;
|
||||
R_UNLESS(this->CanContain(address, size, state), ResultInvalidCurrentMemory);
|
||||
|
||||
VAddr cur_addr{addr};
|
||||
// Lock the table.
|
||||
KScopedLightLock lk(m_general_lock);
|
||||
|
||||
for (const auto& node : page_linked_list.Nodes()) {
|
||||
if (const auto result{Operate(cur_addr, node.GetNumPages(), KMemoryPermission::None,
|
||||
OperationType::Unmap)};
|
||||
result.IsError()) {
|
||||
R_RETURN(result);
|
||||
}
|
||||
// Check the memory state.
|
||||
size_t num_allocator_blocks;
|
||||
R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size,
|
||||
KMemoryState::All, KMemoryState::Free, KMemoryPermission::None,
|
||||
KMemoryPermission::None, KMemoryAttribute::None,
|
||||
KMemoryAttribute::None));
|
||||
|
||||
cur_addr += node.GetNumPages() * PageSize;
|
||||
}
|
||||
// Create an update allocator.
|
||||
Result allocator_result;
|
||||
KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
|
||||
m_memory_block_slab_manager, num_allocator_blocks);
|
||||
R_TRY(allocator_result);
|
||||
|
||||
// We're going to perform an update, so create a helper.
|
||||
KScopedPageTableUpdater updater(this);
|
||||
|
||||
// Map the pages.
|
||||
R_TRY(this->AllocateAndMapPagesImpl(updater.GetPageList(), address, num_pages, perm));
|
||||
|
||||
// Update the blocks.
|
||||
m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, state, perm,
|
||||
KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal,
|
||||
KMemoryBlockDisableMergeAttribute::None);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KPageTable::UnmapPages(VAddr address, KPageGroup& page_linked_list, KMemoryState state) {
|
||||
Result KPageTable::UnmapPages(KProcessAddress address, size_t num_pages, KMemoryState state) {
|
||||
// Check that the unmap is in range.
|
||||
const size_t num_pages{page_linked_list.GetNumPages()};
|
||||
const size_t size{num_pages * PageSize};
|
||||
const size_t size = num_pages * PageSize;
|
||||
R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
|
||||
|
||||
// Lock the table.
|
||||
@@ -2190,13 +2299,18 @@ Result KPageTable::UnmapPages(VAddr address, KPageGroup& page_linked_list, KMemo
|
||||
KMemoryAttribute::None));
|
||||
|
||||
// Create an update allocator.
|
||||
Result allocator_result{ResultSuccess};
|
||||
Result allocator_result;
|
||||
KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
|
||||
m_memory_block_slab_manager, num_allocator_blocks);
|
||||
R_TRY(allocator_result);
|
||||
|
||||
// We're going to perform an update, so create a helper.
|
||||
KScopedPageTableUpdater updater(this);
|
||||
|
||||
// Perform the unmap.
|
||||
R_TRY(UnmapPages(address, page_linked_list));
|
||||
const KPageProperties unmap_properties = {KMemoryPermission::None, false, false,
|
||||
DisableMergeAttribute::None};
|
||||
R_TRY(this->Operate(address, num_pages, unmap_properties.perm, OperationType::Unmap));
|
||||
|
||||
// Update the blocks.
|
||||
m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free,
|
||||
@@ -2207,29 +2321,130 @@ Result KPageTable::UnmapPages(VAddr address, KPageGroup& page_linked_list, KMemo
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KPageTable::UnmapPages(VAddr address, size_t num_pages, KMemoryState state) {
|
||||
// Check that the unmap is in range.
|
||||
const size_t size = num_pages * PageSize;
|
||||
R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
|
||||
Result KPageTable::MapPageGroup(KProcessAddress* out_addr, const KPageGroup& pg,
|
||||
KProcessAddress region_start, size_t region_num_pages,
|
||||
KMemoryState state, KMemoryPermission perm) {
|
||||
ASSERT(!this->IsLockedByCurrentThread());
|
||||
|
||||
// Ensure this is a valid map request.
|
||||
const size_t num_pages = pg.GetNumPages();
|
||||
R_UNLESS(this->CanContain(region_start, region_num_pages * PageSize, state),
|
||||
ResultInvalidCurrentMemory);
|
||||
R_UNLESS(num_pages < region_num_pages, ResultOutOfMemory);
|
||||
|
||||
// Lock the table.
|
||||
KScopedLightLock lk(m_general_lock);
|
||||
|
||||
// Check the memory state.
|
||||
size_t num_allocator_blocks{};
|
||||
// Find a random address to map at.
|
||||
KProcessAddress addr = this->FindFreeArea(region_start, region_num_pages, num_pages, PageSize,
|
||||
0, this->GetNumGuardPages());
|
||||
R_UNLESS(addr != 0, ResultOutOfMemory);
|
||||
ASSERT(this->CanContain(addr, num_pages * PageSize, state));
|
||||
ASSERT(this->CheckMemoryState(addr, num_pages * PageSize, KMemoryState::All, KMemoryState::Free,
|
||||
KMemoryPermission::None, KMemoryPermission::None,
|
||||
KMemoryAttribute::None, KMemoryAttribute::None) == ResultSuccess);
|
||||
|
||||
// Create an update allocator.
|
||||
Result allocator_result;
|
||||
KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
|
||||
m_memory_block_slab_manager);
|
||||
R_TRY(allocator_result);
|
||||
|
||||
// We're going to perform an update, so create a helper.
|
||||
KScopedPageTableUpdater updater(this);
|
||||
|
||||
// Perform mapping operation.
|
||||
const KPageProperties properties = {perm, state == KMemoryState::Io, false,
|
||||
DisableMergeAttribute::DisableHead};
|
||||
R_TRY(this->MapPageGroupImpl(updater.GetPageList(), addr, pg, properties, false));
|
||||
|
||||
// Update the blocks.
|
||||
m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm,
|
||||
KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal,
|
||||
KMemoryBlockDisableMergeAttribute::None);
|
||||
|
||||
// We successfully mapped the pages.
|
||||
*out_addr = addr;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KPageTable::MapPageGroup(KProcessAddress addr, const KPageGroup& pg, KMemoryState state,
|
||||
KMemoryPermission perm) {
|
||||
ASSERT(!this->IsLockedByCurrentThread());
|
||||
|
||||
// Ensure this is a valid map request.
|
||||
const size_t num_pages = pg.GetNumPages();
|
||||
const size_t size = num_pages * PageSize;
|
||||
R_UNLESS(this->CanContain(addr, size, state), ResultInvalidCurrentMemory);
|
||||
|
||||
// Lock the table.
|
||||
KScopedLightLock lk(m_general_lock);
|
||||
|
||||
// Check if state allows us to map.
|
||||
size_t num_allocator_blocks;
|
||||
R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), addr, size,
|
||||
KMemoryState::All, KMemoryState::Free, KMemoryPermission::None,
|
||||
KMemoryPermission::None, KMemoryAttribute::None,
|
||||
KMemoryAttribute::None));
|
||||
|
||||
// Create an update allocator.
|
||||
Result allocator_result;
|
||||
KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
|
||||
m_memory_block_slab_manager, num_allocator_blocks);
|
||||
R_TRY(allocator_result);
|
||||
|
||||
// We're going to perform an update, so create a helper.
|
||||
KScopedPageTableUpdater updater(this);
|
||||
|
||||
// Perform mapping operation.
|
||||
const KPageProperties properties = {perm, state == KMemoryState::Io, false,
|
||||
DisableMergeAttribute::DisableHead};
|
||||
R_TRY(this->MapPageGroupImpl(updater.GetPageList(), addr, pg, properties, false));
|
||||
|
||||
// Update the blocks.
|
||||
m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm,
|
||||
KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal,
|
||||
KMemoryBlockDisableMergeAttribute::None);
|
||||
|
||||
// We successfully mapped the pages.
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KPageTable::UnmapPageGroup(KProcessAddress address, const KPageGroup& pg,
|
||||
KMemoryState state) {
|
||||
ASSERT(!this->IsLockedByCurrentThread());
|
||||
|
||||
// Ensure this is a valid unmap request.
|
||||
const size_t num_pages = pg.GetNumPages();
|
||||
const size_t size = num_pages * PageSize;
|
||||
R_UNLESS(this->CanContain(address, size, state), ResultInvalidCurrentMemory);
|
||||
|
||||
// Lock the table.
|
||||
KScopedLightLock lk(m_general_lock);
|
||||
|
||||
// Check if state allows us to unmap.
|
||||
size_t num_allocator_blocks;
|
||||
R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size,
|
||||
KMemoryState::All, state, KMemoryPermission::None,
|
||||
KMemoryPermission::None, KMemoryAttribute::All,
|
||||
KMemoryAttribute::None));
|
||||
|
||||
// Check that the page group is valid.
|
||||
R_UNLESS(this->IsValidPageGroup(pg, address, num_pages), ResultInvalidCurrentMemory);
|
||||
|
||||
// Create an update allocator.
|
||||
Result allocator_result{ResultSuccess};
|
||||
Result allocator_result;
|
||||
KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
|
||||
m_memory_block_slab_manager, num_allocator_blocks);
|
||||
R_TRY(allocator_result);
|
||||
|
||||
// Perform the unmap.
|
||||
R_TRY(Operate(address, num_pages, KMemoryPermission::None, OperationType::Unmap));
|
||||
// We're going to perform an update, so create a helper.
|
||||
KScopedPageTableUpdater updater(this);
|
||||
|
||||
// Perform unmapping operation.
|
||||
const KPageProperties properties = {KMemoryPermission::None, false, false,
|
||||
DisableMergeAttribute::None};
|
||||
R_TRY(this->Operate(address, num_pages, properties.perm, OperationType::Unmap));
|
||||
|
||||
// Update the blocks.
|
||||
m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free,
|
||||
@@ -2527,13 +2742,13 @@ Result KPageTable::SetHeapSize(VAddr* out, size_t size) {
|
||||
R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
|
||||
|
||||
// Allocate pages for the heap extension.
|
||||
KPageGroup pg;
|
||||
KPageGroup pg{m_kernel, m_block_info_manager};
|
||||
R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpen(
|
||||
&pg, allocation_size / PageSize,
|
||||
KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option)));
|
||||
|
||||
// Clear all the newly allocated pages.
|
||||
for (const auto& it : pg.Nodes()) {
|
||||
for (const auto& it : pg) {
|
||||
std::memset(m_system.DeviceMemory().GetPointer<void>(it.GetAddress()), m_heap_fill_value,
|
||||
it.GetSize());
|
||||
}
|
||||
@@ -2589,42 +2804,6 @@ Result KPageTable::SetHeapSize(VAddr* out, size_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
ResultVal<VAddr> KPageTable::AllocateAndMapMemory(size_t needed_num_pages, size_t align,
|
||||
bool is_map_only, VAddr region_start,
|
||||
size_t region_num_pages, KMemoryState state,
|
||||
KMemoryPermission perm, PAddr map_addr) {
|
||||
KScopedLightLock lk(m_general_lock);
|
||||
|
||||
R_UNLESS(CanContain(region_start, region_num_pages * PageSize, state),
|
||||
ResultInvalidCurrentMemory);
|
||||
R_UNLESS(region_num_pages > needed_num_pages, ResultOutOfMemory);
|
||||
const VAddr addr{
|
||||
AllocateVirtualMemory(region_start, region_num_pages, needed_num_pages, align)};
|
||||
R_UNLESS(addr, ResultOutOfMemory);
|
||||
|
||||
// Create an update allocator.
|
||||
Result allocator_result{ResultSuccess};
|
||||
KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
|
||||
m_memory_block_slab_manager);
|
||||
|
||||
if (is_map_only) {
|
||||
R_TRY(Operate(addr, needed_num_pages, perm, OperationType::Map, map_addr));
|
||||
} else {
|
||||
KPageGroup page_group;
|
||||
R_TRY(m_system.Kernel().MemoryManager().AllocateForProcess(
|
||||
&page_group, needed_num_pages,
|
||||
KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option), 0, 0));
|
||||
R_TRY(Operate(addr, needed_num_pages, page_group, OperationType::MapGroup));
|
||||
}
|
||||
|
||||
// Update the blocks.
|
||||
m_memory_block_manager.Update(std::addressof(allocator), addr, needed_num_pages, state, perm,
|
||||
KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal,
|
||||
KMemoryBlockDisableMergeAttribute::None);
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
Result KPageTable::LockForMapDeviceAddressSpace(bool* out_is_io, VAddr address, size_t size,
|
||||
KMemoryPermission perm, bool is_aligned,
|
||||
bool check_heap) {
|
||||
@@ -2795,19 +2974,28 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, const KPageGroup& page_
|
||||
ASSERT(num_pages > 0);
|
||||
ASSERT(num_pages == page_group.GetNumPages());
|
||||
|
||||
for (const auto& node : page_group.Nodes()) {
|
||||
const size_t size{node.GetNumPages() * PageSize};
|
||||
switch (operation) {
|
||||
case OperationType::MapGroup: {
|
||||
// We want to maintain a new reference to every page in the group.
|
||||
KScopedPageGroup spg(page_group);
|
||||
|
||||
switch (operation) {
|
||||
case OperationType::MapGroup:
|
||||
for (const auto& node : page_group) {
|
||||
const size_t size{node.GetNumPages() * PageSize};
|
||||
|
||||
// Map the pages.
|
||||
m_system.Memory().MapMemoryRegion(*m_page_table_impl, addr, size, node.GetAddress());
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
break;
|
||||
|
||||
addr += size;
|
||||
}
|
||||
|
||||
addr += size;
|
||||
// We succeeded! We want to persist the reference to the pages.
|
||||
spg.CancelClose();
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
@@ -2822,13 +3010,29 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, KMemoryPermission perm,
|
||||
ASSERT(ContainsPages(addr, num_pages));
|
||||
|
||||
switch (operation) {
|
||||
case OperationType::Unmap:
|
||||
case OperationType::Unmap: {
|
||||
// Ensure that any pages we track close on exit.
|
||||
KPageGroup pages_to_close{m_kernel, this->GetBlockInfoManager()};
|
||||
SCOPE_EXIT({ pages_to_close.CloseAndReset(); });
|
||||
|
||||
this->AddRegionToPages(addr, num_pages, pages_to_close);
|
||||
m_system.Memory().UnmapRegion(*m_page_table_impl, addr, num_pages * PageSize);
|
||||
break;
|
||||
}
|
||||
case OperationType::MapFirst:
|
||||
case OperationType::Map: {
|
||||
ASSERT(map_addr);
|
||||
ASSERT(Common::IsAligned(map_addr, PageSize));
|
||||
m_system.Memory().MapMemoryRegion(*m_page_table_impl, addr, num_pages * PageSize, map_addr);
|
||||
|
||||
// Open references to pages, if we should.
|
||||
if (IsHeapPhysicalAddress(m_kernel.MemoryLayout(), map_addr)) {
|
||||
if (operation == OperationType::MapFirst) {
|
||||
m_kernel.MemoryManager().OpenFirst(map_addr, num_pages);
|
||||
} else {
|
||||
m_kernel.MemoryManager().Open(map_addr, num_pages);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OperationType::Separate: {
|
||||
|
||||
@@ -24,12 +24,36 @@ class System;
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
enum class DisableMergeAttribute : u8 {
|
||||
None = (0U << 0),
|
||||
DisableHead = (1U << 0),
|
||||
DisableHeadAndBody = (1U << 1),
|
||||
EnableHeadAndBody = (1U << 2),
|
||||
DisableTail = (1U << 3),
|
||||
EnableTail = (1U << 4),
|
||||
EnableAndMergeHeadBodyTail = (1U << 5),
|
||||
EnableHeadBodyTail = EnableHeadAndBody | EnableTail,
|
||||
DisableHeadBodyTail = DisableHeadAndBody | DisableTail,
|
||||
};
|
||||
|
||||
struct KPageProperties {
|
||||
KMemoryPermission perm;
|
||||
bool io;
|
||||
bool uncached;
|
||||
DisableMergeAttribute disable_merge_attributes;
|
||||
};
|
||||
static_assert(std::is_trivial_v<KPageProperties>);
|
||||
static_assert(sizeof(KPageProperties) == sizeof(u32));
|
||||
|
||||
class KBlockInfoManager;
|
||||
class KMemoryBlockManager;
|
||||
class KResourceLimit;
|
||||
class KSystemResource;
|
||||
|
||||
class KPageTable final {
|
||||
protected:
|
||||
struct PageLinkedList;
|
||||
|
||||
public:
|
||||
enum class ICacheInvalidationStrategy : u32 { InvalidateRange, InvalidateAll };
|
||||
|
||||
@@ -57,27 +81,12 @@ public:
|
||||
Result UnmapPhysicalMemory(VAddr addr, size_t size);
|
||||
Result MapMemory(VAddr dst_addr, VAddr src_addr, size_t size);
|
||||
Result UnmapMemory(VAddr dst_addr, VAddr src_addr, size_t size);
|
||||
Result MapPages(VAddr addr, KPageGroup& page_linked_list, KMemoryState state,
|
||||
KMemoryPermission perm);
|
||||
Result MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, PAddr phys_addr,
|
||||
KMemoryState state, KMemoryPermission perm) {
|
||||
R_RETURN(this->MapPages(out_addr, num_pages, alignment, phys_addr, true,
|
||||
this->GetRegionAddress(state),
|
||||
this->GetRegionSize(state) / PageSize, state, perm));
|
||||
}
|
||||
Result UnmapPages(VAddr addr, KPageGroup& page_linked_list, KMemoryState state);
|
||||
Result UnmapPages(VAddr address, size_t num_pages, KMemoryState state);
|
||||
Result SetProcessMemoryPermission(VAddr addr, size_t size, Svc::MemoryPermission svc_perm);
|
||||
KMemoryInfo QueryInfo(VAddr addr);
|
||||
Result SetMemoryPermission(VAddr addr, size_t size, Svc::MemoryPermission perm);
|
||||
Result SetMemoryAttribute(VAddr addr, size_t size, u32 mask, u32 attr);
|
||||
Result SetMaxHeapSize(size_t size);
|
||||
Result SetHeapSize(VAddr* out, size_t size);
|
||||
ResultVal<VAddr> AllocateAndMapMemory(size_t needed_num_pages, size_t align, bool is_map_only,
|
||||
VAddr region_start, size_t region_num_pages,
|
||||
KMemoryState state, KMemoryPermission perm,
|
||||
PAddr map_addr = 0);
|
||||
|
||||
Result LockForMapDeviceAddressSpace(bool* out_is_io, VAddr address, size_t size,
|
||||
KMemoryPermission perm, bool is_aligned, bool check_heap);
|
||||
Result LockForUnmapDeviceAddressSpace(VAddr address, size_t size, bool check_heap);
|
||||
@@ -107,8 +116,46 @@ public:
|
||||
return *m_page_table_impl;
|
||||
}
|
||||
|
||||
KBlockInfoManager* GetBlockInfoManager() {
|
||||
return m_block_info_manager;
|
||||
}
|
||||
|
||||
bool CanContain(VAddr addr, size_t size, KMemoryState state) const;
|
||||
|
||||
Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment,
|
||||
KPhysicalAddress phys_addr, KProcessAddress region_start,
|
||||
size_t region_num_pages, KMemoryState state, KMemoryPermission perm) {
|
||||
R_RETURN(this->MapPages(out_addr, num_pages, alignment, phys_addr, true, region_start,
|
||||
region_num_pages, state, perm));
|
||||
}
|
||||
|
||||
Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment,
|
||||
KPhysicalAddress phys_addr, KMemoryState state, KMemoryPermission perm) {
|
||||
R_RETURN(this->MapPages(out_addr, num_pages, alignment, phys_addr, true,
|
||||
this->GetRegionAddress(state),
|
||||
this->GetRegionSize(state) / PageSize, state, perm));
|
||||
}
|
||||
|
||||
Result MapPages(KProcessAddress* out_addr, size_t num_pages, KMemoryState state,
|
||||
KMemoryPermission perm) {
|
||||
R_RETURN(this->MapPages(out_addr, num_pages, PageSize, 0, false,
|
||||
this->GetRegionAddress(state),
|
||||
this->GetRegionSize(state) / PageSize, state, perm));
|
||||
}
|
||||
|
||||
Result MapPages(KProcessAddress address, size_t num_pages, KMemoryState state,
|
||||
KMemoryPermission perm);
|
||||
Result UnmapPages(KProcessAddress address, size_t num_pages, KMemoryState state);
|
||||
|
||||
Result MapPageGroup(KProcessAddress* out_addr, const KPageGroup& pg,
|
||||
KProcessAddress region_start, size_t region_num_pages, KMemoryState state,
|
||||
KMemoryPermission perm);
|
||||
Result MapPageGroup(KProcessAddress address, const KPageGroup& pg, KMemoryState state,
|
||||
KMemoryPermission perm);
|
||||
Result UnmapPageGroup(KProcessAddress address, const KPageGroup& pg, KMemoryState state);
|
||||
void RemapPageGroup(PageLinkedList* page_list, KProcessAddress address, size_t size,
|
||||
const KPageGroup& pg);
|
||||
|
||||
protected:
|
||||
struct PageLinkedList {
|
||||
private:
|
||||
@@ -162,11 +209,9 @@ private:
|
||||
static constexpr KMemoryAttribute DefaultMemoryIgnoreAttr =
|
||||
KMemoryAttribute::IpcLocked | KMemoryAttribute::DeviceShared;
|
||||
|
||||
Result MapPages(VAddr addr, const KPageGroup& page_linked_list, KMemoryPermission perm);
|
||||
Result MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, PAddr phys_addr,
|
||||
bool is_pa_valid, VAddr region_start, size_t region_num_pages,
|
||||
KMemoryState state, KMemoryPermission perm);
|
||||
Result UnmapPages(VAddr addr, const KPageGroup& page_linked_list);
|
||||
Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment,
|
||||
KPhysicalAddress phys_addr, bool is_pa_valid, KProcessAddress region_start,
|
||||
size_t region_num_pages, KMemoryState state, KMemoryPermission perm);
|
||||
bool IsRegionContiguous(VAddr addr, u64 size) const;
|
||||
void AddRegionToPages(VAddr start, size_t num_pages, KPageGroup& page_linked_list);
|
||||
KMemoryInfo QueryInfoImpl(VAddr addr);
|
||||
@@ -261,9 +306,10 @@ private:
|
||||
void CleanupForIpcClientOnServerSetupFailure(PageLinkedList* page_list, VAddr address,
|
||||
size_t size, KMemoryPermission prot_perm);
|
||||
|
||||
// HACK: These will be removed once we automatically manage page reference counts.
|
||||
void HACK_OpenPages(PAddr phys_addr, size_t num_pages);
|
||||
void HACK_ClosePages(VAddr virt_addr, size_t num_pages);
|
||||
Result AllocateAndMapPagesImpl(PageLinkedList* page_list, KProcessAddress address,
|
||||
size_t num_pages, KMemoryPermission perm);
|
||||
Result MapPageGroupImpl(PageLinkedList* page_list, KProcessAddress address,
|
||||
const KPageGroup& pg, const KPageProperties properties, bool reuse_ll);
|
||||
|
||||
mutable KLightLock m_general_lock;
|
||||
mutable KLightLock m_map_physical_memory_lock;
|
||||
@@ -488,6 +534,7 @@ private:
|
||||
std::unique_ptr<Common::PageTable> m_page_table_impl;
|
||||
|
||||
Core::System& m_system;
|
||||
KernelCore& m_kernel;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -417,9 +417,8 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:
|
||||
}
|
||||
|
||||
void KProcess::Run(s32 main_thread_priority, u64 stack_size) {
|
||||
AllocateMainThreadStack(stack_size);
|
||||
ASSERT(AllocateMainThreadStack(stack_size) == ResultSuccess);
|
||||
resource_limit->Reserve(LimitableResource::ThreadCountMax, 1);
|
||||
resource_limit->Reserve(LimitableResource::PhysicalMemoryMax, main_thread_stack_size);
|
||||
|
||||
const std::size_t heap_capacity{memory_usage_capacity - (main_thread_stack_size + image_size)};
|
||||
ASSERT(!page_table.SetMaxHeapSize(heap_capacity).IsError());
|
||||
@@ -675,20 +674,31 @@ void KProcess::ChangeState(State new_state) {
|
||||
}
|
||||
|
||||
Result KProcess::AllocateMainThreadStack(std::size_t stack_size) {
|
||||
ASSERT(stack_size);
|
||||
// Ensure that we haven't already allocated stack.
|
||||
ASSERT(main_thread_stack_size == 0);
|
||||
|
||||
// The kernel always ensures that the given stack size is page aligned.
|
||||
main_thread_stack_size = Common::AlignUp(stack_size, PageSize);
|
||||
// Ensure that we're allocating a valid stack.
|
||||
stack_size = Common::AlignUp(stack_size, PageSize);
|
||||
// R_UNLESS(stack_size + image_size <= m_max_process_memory, ResultOutOfMemory);
|
||||
R_UNLESS(stack_size + image_size >= image_size, ResultOutOfMemory);
|
||||
|
||||
const VAddr start{page_table.GetStackRegionStart()};
|
||||
const std::size_t size{page_table.GetStackRegionEnd() - start};
|
||||
// Place a tentative reservation of memory for our new stack.
|
||||
KScopedResourceReservation mem_reservation(this, Svc::LimitableResource::PhysicalMemoryMax,
|
||||
stack_size);
|
||||
R_UNLESS(mem_reservation.Succeeded(), ResultLimitReached);
|
||||
|
||||
CASCADE_RESULT(main_thread_stack_top,
|
||||
page_table.AllocateAndMapMemory(
|
||||
main_thread_stack_size / PageSize, PageSize, false, start, size / PageSize,
|
||||
KMemoryState::Stack, KMemoryPermission::UserReadWrite));
|
||||
// Allocate and map our stack.
|
||||
if (stack_size) {
|
||||
KProcessAddress stack_bottom;
|
||||
R_TRY(page_table.MapPages(std::addressof(stack_bottom), stack_size / PageSize,
|
||||
KMemoryState::Stack, KMemoryPermission::UserReadWrite));
|
||||
|
||||
main_thread_stack_top += main_thread_stack_size;
|
||||
main_thread_stack_top = stack_bottom + stack_size;
|
||||
main_thread_stack_size = stack_size;
|
||||
}
|
||||
|
||||
// We succeeded! Commit our memory reservation.
|
||||
mem_reservation.Commit();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user